[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

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



commit 1464901fe3fed48066683d225265fc5deba58d40
Author: SandroB <supp.sandrob@xxxxxxxxx>
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@xxxxxxxxx>
+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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxx>
+#
+# 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@xxxxxx>
+# 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@xxxxxxxxx>
+#
+# 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@xxxxxxxxx>
+#
+# 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@xxxxxxxxx>
+#
+# 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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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@xxxxxxxxx>
+ * 
+ * @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 "PacketPassFifoQueue.h"
+
+static void schedule (PacketPassFifoQueue *o)
+{
+    ASSERT(!o->freeing)
+    ASSERT(!o->sending_flow)
+    ASSERT(!LinkedList1_IsEmpty(&o->waiting_flows_list))
+    ASSERT(!BPending_IsSet(&o->schedule_job))
+    
+    // get first waiting flow
+    PacketPassFifoQueueFlow *flow = UPPER_OBJECT(LinkedList1_GetFirst(&o->waiting_flows_list), PacketPassFifoQueueFlow, waiting_flows_list_node);
+    ASSERT(flow->queue == o)
+    ASSERT(flow->is_waiting)
+    
+    // remove it from queue
+    LinkedList1_Remove(&o->waiting_flows_list, &flow->waiting_flows_list_node);
+    flow->is_waiting = 0;
+    
+    // send
+    PacketPassInterface_Sender_Send(o->output, flow->waiting_data, flow->waiting_len);
+    o->sending_flow = flow;
+}
+
+static void schedule_job_handler (PacketPassFifoQueue *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->freeing)
+    ASSERT(!o->sending_flow)
+    
+    if (!LinkedList1_IsEmpty(&o->waiting_flows_list)) {
+        schedule(o);
+    }
+}
+
+static void input_handler_send (PacketPassFifoQueueFlow *o, uint8_t *data, int data_len)
+{
+    PacketPassFifoQueue *queue = o->queue;
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->is_waiting)
+    ASSERT(o != queue->sending_flow)
+    ASSERT(!queue->freeing)
+    
+    // queue flow
+    o->waiting_data = data;
+    o->waiting_len = data_len;
+    LinkedList1_Append(&queue->waiting_flows_list, &o->waiting_flows_list_node);
+    o->is_waiting = 1;
+    
+    // schedule
+    if (!queue->sending_flow && !BPending_IsSet(&queue->schedule_job)) {
+        schedule(queue);
+    }
+}
+
+static void output_handler_done (PacketPassFifoQueue *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->sending_flow)
+    ASSERT(!BPending_IsSet(&o->schedule_job))
+    ASSERT(!o->freeing)
+    ASSERT(!o->sending_flow->is_waiting)
+    
+    PacketPassFifoQueueFlow *flow = o->sending_flow;
+    
+    // set no sending flow
+    o->sending_flow = NULL;
+    
+    // schedule schedule job
+    BPending_Set(&o->schedule_job);
+    
+    // done input
+    PacketPassInterface_Done(&flow->input);
+    
+    // call busy handler if set
+    if (flow->handler_busy) {
+        // handler is one-shot, unset it before calling
+        PacketPassFifoQueue_handler_busy handler = flow->handler_busy;
+        flow->handler_busy = NULL;
+        
+        // call handler
+        handler(flow->user);
+        return;
+    }
+}
+
+void PacketPassFifoQueue_Init (PacketPassFifoQueue *o, PacketPassInterface *output, BPendingGroup *pg)
+{
+    // init arguments
+    o->output = output;
+    o->pg = pg;
+    
+    // init output
+    PacketPassInterface_Sender_Init(output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // init waiting flows list
+    LinkedList1_Init(&o->waiting_flows_list);
+    
+    // set no sending flow
+    o->sending_flow = NULL;
+    
+    // init schedule job
+    BPending_Init(&o->schedule_job, pg, (BPending_handler)schedule_job_handler, o);
+    
+    // set not freeing
+    o->freeing = 0;
+    
+    DebugCounter_Init(&o->d_flows_ctr);
+    DebugObject_Init(&o->d_obj);
+}
+
+void PacketPassFifoQueue_Free (PacketPassFifoQueue *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Free(&o->d_flows_ctr);
+    ASSERT(LinkedList1_IsEmpty(&o->waiting_flows_list))
+    ASSERT(!o->sending_flow)
+    
+    // free schedule job
+    BPending_Free(&o->schedule_job);
+}
+
+void PacketPassFifoQueue_PrepareFree (PacketPassFifoQueue *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // set freeing
+    o->freeing = 1;
+}
+
+void PacketPassFifoQueueFlow_Init (PacketPassFifoQueueFlow *o, PacketPassFifoQueue *queue)
+{
+    DebugObject_Access(&queue->d_obj);
+    ASSERT(!queue->freeing)
+    
+    // init arguments
+    o->queue = queue;
+    
+    // init input
+    PacketPassInterface_Init(&o->input, PacketPassInterface_GetMTU(queue->output), (PacketPassInterface_handler_send)input_handler_send, o, queue->pg);
+    
+    // set not waiting
+    o->is_waiting = 0;
+    
+    // set no busy handler
+    o->handler_busy = NULL;
+    
+    DebugCounter_Increment(&queue->d_flows_ctr);
+    DebugObject_Init(&o->d_obj);
+}
+
+void PacketPassFifoQueueFlow_Free (PacketPassFifoQueueFlow *o)
+{
+    PacketPassFifoQueue *queue = o->queue;
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Decrement(&queue->d_flows_ctr);
+    ASSERT(queue->freeing || o != queue->sending_flow)
+    
+    // remove from sending flow
+    if (o == queue->sending_flow) {
+        queue->sending_flow = NULL;
+    }
+    
+    // remove from waiting flows list
+    if (o->is_waiting) {
+        LinkedList1_Remove(&queue->waiting_flows_list, &o->waiting_flows_list_node);
+    }
+    
+    // free input
+    PacketPassInterface_Free(&o->input);
+}
+
+void PacketPassFifoQueueFlow_AssertFree (PacketPassFifoQueueFlow *o)
+{
+    PacketPassFifoQueue *queue = o->queue;
+    B_USE(queue)
+    DebugObject_Access(&o->d_obj);
+    ASSERT(queue->freeing || o != queue->sending_flow)
+}
+
+int PacketPassFifoQueueFlow_IsBusy (PacketPassFifoQueueFlow *o)
+{
+    PacketPassFifoQueue *queue = o->queue;
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!queue->freeing)
+    
+    return (o == queue->sending_flow);
+}
+
+void PacketPassFifoQueueFlow_SetBusyHandler (PacketPassFifoQueueFlow *o, PacketPassFifoQueue_handler_busy handler_busy, void *user)
+{
+    PacketPassFifoQueue *queue = o->queue;
+    B_USE(queue)
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!queue->freeing)
+    ASSERT(o == queue->sending_flow)
+    
+    // set (or unset) busy handler
+    o->handler_busy = handler_busy;
+    o->user = user;
+}
+
+PacketPassInterface * PacketPassFifoQueueFlow_GetInput (PacketPassFifoQueueFlow *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
diff --git a/external/badvpn_dns/flow/PacketPassFifoQueue.h b/external/badvpn_dns/flow/PacketPassFifoQueue.h
new file mode 100644
index 0000000..cefe79b
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassFifoQueue.h
@@ -0,0 +1,76 @@
+/**
+ * @file PacketPassFifoQueue.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_PACKETPASSFIFOQUEUE_H
+#define BADVPN_PACKETPASSFIFOQUEUE_H
+
+#include <misc/debugcounter.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+typedef void (*PacketPassFifoQueue_handler_busy) (void *user);
+
+struct PacketPassFifoQueueFlow_s;
+
+typedef struct {
+    PacketPassInterface *output;
+    BPendingGroup *pg;
+    LinkedList1 waiting_flows_list;
+    struct PacketPassFifoQueueFlow_s *sending_flow;
+    BPending schedule_job;
+    int freeing;
+    DebugCounter d_flows_ctr;
+    DebugObject d_obj;
+} PacketPassFifoQueue;
+
+typedef struct PacketPassFifoQueueFlow_s {
+    PacketPassFifoQueue *queue;
+    PacketPassInterface input;
+    int is_waiting;
+    LinkedList1Node waiting_flows_list_node;
+    uint8_t *waiting_data;
+    int waiting_len;
+    PacketPassFifoQueue_handler_busy handler_busy;
+    void *user;
+    DebugObject d_obj;
+} PacketPassFifoQueueFlow;
+
+void PacketPassFifoQueue_Init (PacketPassFifoQueue *o, PacketPassInterface *output, BPendingGroup *pg);
+void PacketPassFifoQueue_Free (PacketPassFifoQueue *o);
+void PacketPassFifoQueue_PrepareFree (PacketPassFifoQueue *o);
+
+void PacketPassFifoQueueFlow_Init (PacketPassFifoQueueFlow *o, PacketPassFifoQueue *queue);
+void PacketPassFifoQueueFlow_Free (PacketPassFifoQueueFlow *o);
+void PacketPassFifoQueueFlow_AssertFree (PacketPassFifoQueueFlow *o);
+int PacketPassFifoQueueFlow_IsBusy (PacketPassFifoQueueFlow *o);
+void PacketPassFifoQueueFlow_SetBusyHandler (PacketPassFifoQueueFlow *o, PacketPassFifoQueue_handler_busy handler_busy, void *user);
+PacketPassInterface * PacketPassFifoQueueFlow_GetInput (PacketPassFifoQueueFlow *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketPassInterface.c b/external/badvpn_dns/flow/PacketPassInterface.c
new file mode 100644
index 0000000..dfa6ed5
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassInterface.c
@@ -0,0 +1,68 @@
+/**
+ * @file PacketPassInterface.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <flow/PacketPassInterface.h>
+
+void _PacketPassInterface_job_operation (PacketPassInterface *i)
+{
+    ASSERT(i->state == PPI_STATE_OPERATION_PENDING)
+    DebugObject_Access(&i->d_obj);
+    
+    // set state
+    i->state = PPI_STATE_BUSY;
+    
+    // call handler
+    i->handler_operation(i->user_provider, i->job_operation_data, i->job_operation_len);
+    return;
+}
+
+void _PacketPassInterface_job_requestcancel (PacketPassInterface *i)
+{
+    ASSERT(i->state == PPI_STATE_BUSY)
+    ASSERT(i->cancel_requested)
+    ASSERT(i->handler_requestcancel)
+    DebugObject_Access(&i->d_obj);
+    
+    // call handler
+    i->handler_requestcancel(i->user_provider);
+    return;
+}
+
+void _PacketPassInterface_job_done (PacketPassInterface *i)
+{
+    ASSERT(i->state == PPI_STATE_DONE_PENDING)
+    DebugObject_Access(&i->d_obj);
+    
+    // set state
+    i->state = PPI_STATE_NONE;
+    
+    // call handler
+    i->handler_done(i->user_user);
+    return;
+}
diff --git a/external/badvpn_dns/flow/PacketPassInterface.h b/external/badvpn_dns/flow/PacketPassInterface.h
new file mode 100644
index 0000000..0cc2274
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassInterface.h
@@ -0,0 +1,236 @@
+/**
+ * @file PacketPassInterface.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Interface allowing a packet sender to pass data packets to a packet receiver.
+ */
+
+#ifndef BADVPN_FLOW_PACKETPASSINTERFACE_H
+#define BADVPN_FLOW_PACKETPASSINTERFACE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+
+#define PPI_STATE_NONE 1
+#define PPI_STATE_OPERATION_PENDING 2
+#define PPI_STATE_BUSY 3
+#define PPI_STATE_DONE_PENDING 4
+
+typedef void (*PacketPassInterface_handler_send) (void *user, uint8_t *data, int data_len);
+
+typedef void (*PacketPassInterface_handler_requestcancel) (void *user);
+
+typedef void (*PacketPassInterface_handler_done) (void *user);
+
+typedef struct {
+    // provider data
+    int mtu;
+    PacketPassInterface_handler_send handler_operation;
+    PacketPassInterface_handler_requestcancel handler_requestcancel;
+    void *user_provider;
+    
+    // user data
+    PacketPassInterface_handler_done handler_done;
+    void *user_user;
+    
+    // operation job
+    BPending job_operation;
+    uint8_t *job_operation_data;
+    int job_operation_len;
+    
+    // requestcancel job
+    BPending job_requestcancel;
+    
+    // done job
+    BPending job_done;
+    
+    // state
+    int state;
+    int cancel_requested;
+    
+    DebugObject d_obj;
+} PacketPassInterface;
+
+static void PacketPassInterface_Init (PacketPassInterface *i, int mtu, PacketPassInterface_handler_send handler_operation, void *user, BPendingGroup *pg);
+
+static void PacketPassInterface_Free (PacketPassInterface *i);
+
+static void PacketPassInterface_EnableCancel (PacketPassInterface *i, PacketPassInterface_handler_requestcancel handler_requestcancel);
+
+static void PacketPassInterface_Done (PacketPassInterface *i);
+
+static int PacketPassInterface_GetMTU (PacketPassInterface *i);
+
+static void PacketPassInterface_Sender_Init (PacketPassInterface *i, PacketPassInterface_handler_done handler_done, void *user);
+
+static void PacketPassInterface_Sender_Send (PacketPassInterface *i, uint8_t *data, int data_len);
+
+static void PacketPassInterface_Sender_RequestCancel (PacketPassInterface *i);
+
+static int PacketPassInterface_HasCancel (PacketPassInterface *i);
+
+void _PacketPassInterface_job_operation (PacketPassInterface *i);
+void _PacketPassInterface_job_requestcancel (PacketPassInterface *i);
+void _PacketPassInterface_job_done (PacketPassInterface *i);
+
+void PacketPassInterface_Init (PacketPassInterface *i, int mtu, PacketPassInterface_handler_send handler_operation, void *user, BPendingGroup *pg)
+{
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    i->mtu = mtu;
+    i->handler_operation = handler_operation;
+    i->handler_requestcancel = NULL;
+    i->user_provider = user;
+    
+    // set no user
+    i->handler_done = NULL;
+    
+    // init jobs
+    BPending_Init(&i->job_operation, pg, (BPending_handler)_PacketPassInterface_job_operation, i);
+    BPending_Init(&i->job_requestcancel, pg, (BPending_handler)_PacketPassInterface_job_requestcancel, i);
+    BPending_Init(&i->job_done, pg, (BPending_handler)_PacketPassInterface_job_done, i);
+    
+    // set state
+    i->state = PPI_STATE_NONE;
+    
+    DebugObject_Init(&i->d_obj);
+}
+
+void PacketPassInterface_Free (PacketPassInterface *i)
+{
+    DebugObject_Free(&i->d_obj);
+    
+    // free jobs
+    BPending_Free(&i->job_done);
+    BPending_Free(&i->job_requestcancel);
+    BPending_Free(&i->job_operation);
+}
+
+void PacketPassInterface_EnableCancel (PacketPassInterface *i, PacketPassInterface_handler_requestcancel handler_requestcancel)
+{
+    ASSERT(!i->handler_requestcancel)
+    ASSERT(!i->handler_done)
+    ASSERT(handler_requestcancel)
+    
+    i->handler_requestcancel = handler_requestcancel;
+}
+
+void PacketPassInterface_Done (PacketPassInterface *i)
+{
+    ASSERT(i->state == PPI_STATE_BUSY)
+    DebugObject_Access(&i->d_obj);
+    
+    // unset requestcancel job
+    BPending_Unset(&i->job_requestcancel);
+    
+    // schedule done
+    BPending_Set(&i->job_done);
+    
+    // set state
+    i->state = PPI_STATE_DONE_PENDING;
+}
+
+int PacketPassInterface_GetMTU (PacketPassInterface *i)
+{
+    DebugObject_Access(&i->d_obj);
+    
+    return i->mtu;
+}
+
+void PacketPassInterface_Sender_Init (PacketPassInterface *i, PacketPassInterface_handler_done handler_done, void *user)
+{
+    ASSERT(handler_done)
+    ASSERT(!i->handler_done)
+    DebugObject_Access(&i->d_obj);
+    
+    i->handler_done = handler_done;
+    i->user_user = user;
+}
+
+void PacketPassInterface_Sender_Send (PacketPassInterface *i, uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= i->mtu)
+    ASSERT(!(data_len > 0) || data)
+    ASSERT(i->state == PPI_STATE_NONE)
+    ASSERT(i->handler_done)
+    DebugObject_Access(&i->d_obj);
+    
+    // schedule operation
+    i->job_operation_data = data;
+    i->job_operation_len = data_len;
+    BPending_Set(&i->job_operation);
+    
+    // set state
+    i->state = PPI_STATE_OPERATION_PENDING;
+    i->cancel_requested = 0;
+}
+
+void PacketPassInterface_Sender_RequestCancel (PacketPassInterface *i)
+{
+    ASSERT(i->state == PPI_STATE_OPERATION_PENDING || i->state == PPI_STATE_BUSY || i->state == PPI_STATE_DONE_PENDING)
+    ASSERT(i->handler_requestcancel)
+    DebugObject_Access(&i->d_obj);
+    
+    // ignore multiple cancel requests
+    if (i->cancel_requested) {
+        return;
+    }
+    
+    // remember we requested cancel
+    i->cancel_requested = 1;
+    
+    if (i->state == PPI_STATE_OPERATION_PENDING) {
+        // unset operation job
+        BPending_Unset(&i->job_operation);
+        
+        // set done job
+        BPending_Set(&i->job_done);
+        
+        // set state
+        i->state = PPI_STATE_DONE_PENDING;
+    } else if (i->state == PPI_STATE_BUSY) {
+        // set requestcancel job
+        BPending_Set(&i->job_requestcancel);
+    }
+}
+
+int PacketPassInterface_HasCancel (PacketPassInterface *i)
+{
+    DebugObject_Access(&i->d_obj);
+    
+    return !!i->handler_requestcancel;
+}
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketPassNotifier.c b/external/badvpn_dns/flow/PacketPassNotifier.c
new file mode 100644
index 0000000..0132746
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassNotifier.c
@@ -0,0 +1,103 @@
+/**
+ * @file PacketPassNotifier.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <flow/PacketPassNotifier.h>
+
+void input_handler_send (PacketPassNotifier *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // schedule send
+    PacketPassInterface_Sender_Send(o->output, data, data_len);
+    
+    // if we have a handler, call it
+    if (o->handler) {
+        o->handler(o->handler_user, data, data_len);
+        return;
+    }
+}
+
+void input_handler_requestcancel (PacketPassNotifier *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    PacketPassInterface_Sender_RequestCancel(o->output);
+}
+
+void output_handler_done (PacketPassNotifier *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    PacketPassInterface_Done(&o->input);
+}
+
+void PacketPassNotifier_Init (PacketPassNotifier *o, PacketPassInterface *output, BPendingGroup *pg)
+{
+    // init arguments
+    o->output = output;
+    
+    // init input
+    PacketPassInterface_Init(&o->input, PacketPassInterface_GetMTU(o->output), (PacketPassInterface_handler_send)input_handler_send, o, pg);
+    if (PacketPassInterface_HasCancel(o->output)) {
+        PacketPassInterface_EnableCancel(&o->input, (PacketPassInterface_handler_requestcancel)input_handler_requestcancel);
+    }
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // set no handler
+    o->handler = NULL;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void PacketPassNotifier_Free (PacketPassNotifier *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // free input
+    PacketPassInterface_Free(&o->input);
+}
+
+PacketPassInterface * PacketPassNotifier_GetInput (PacketPassNotifier *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
+
+void PacketPassNotifier_SetHandler (PacketPassNotifier *o, PacketPassNotifier_handler_notify handler, void *user)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    o->handler = handler;
+    o->handler_user = user;
+}
diff --git a/external/badvpn_dns/flow/PacketPassNotifier.h b/external/badvpn_dns/flow/PacketPassNotifier.h
new file mode 100644
index 0000000..b2fab13
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassNotifier.h
@@ -0,0 +1,99 @@
+/**
+ * @file PacketPassNotifier.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 calles a handler function before
+ * passing a packet from input to output.
+ */
+
+#ifndef BADVPN_FLOW_PACKETPASSNOTIFIER_H
+#define BADVPN_FLOW_PACKETPASSNOTIFIER_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * Handler function called when input calls Send, but before the call is passed on to output.
+ * 
+ * @param user value specified in {@link PacketPassNotifier_SetHandler}
+ * @param data packet provided by input
+ * @param data_len size of the packet
+ */
+typedef void (*PacketPassNotifier_handler_notify) (void *user, uint8_t *data, int data_len);
+
+/**
+ * A {@link PacketPassInterface} layer which calles a handler function before
+ * passing a packet from input to output.
+ */
+typedef struct {
+    PacketPassInterface input;
+    PacketPassInterface *output;
+    PacketPassNotifier_handler_notify handler;
+    void *handler_user;
+    DebugObject d_obj;
+} PacketPassNotifier;
+
+/**
+ * Initializes the object.
+ *
+ * @param o the object
+ * @param output output interface
+ * @param pg pending group
+ */
+void PacketPassNotifier_Init (PacketPassNotifier *o, PacketPassInterface *output, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void PacketPassNotifier_Free (PacketPassNotifier *o);
+
+/**
+ * Returns the input interface.
+ * The MTU of the interface will be the same as of the output interface.
+ * The interface supports cancel functionality if the output interface supports it.
+ *
+ * @param o the object
+ * @return input interface
+ */
+PacketPassInterface * PacketPassNotifier_GetInput (PacketPassNotifier *o);
+
+/**
+ * Configures a handler function to call before passing input packets to output.
+ *
+ * @param o the object
+ * @param handler handler function, or NULL to disable.
+ * @param user value to pass to handler function. Ignored if handler is NULL.
+ */
+void PacketPassNotifier_SetHandler (PacketPassNotifier *o, PacketPassNotifier_handler_notify handler, void *user);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketPassPriorityQueue.c b/external/badvpn_dns/flow/PacketPassPriorityQueue.c
new file mode 100644
index 0000000..9534f1b
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassPriorityQueue.c
@@ -0,0 +1,283 @@
+/**
+ * @file PacketPassPriorityQueue.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/compare.h>
+
+#include <flow/PacketPassPriorityQueue.h>
+
+static int compare_flows (PacketPassPriorityQueueFlow *f1, PacketPassPriorityQueueFlow *f2)
+{
+    int cmp = B_COMPARE(f1->priority, f2->priority);
+    if (cmp) {
+        return cmp;
+    }
+    
+    return B_COMPARE((uintptr_t)f1, (uintptr_t)f2);
+}
+
+#include "PacketPassPriorityQueue_tree.h"
+#include <structure/SAvl_impl.h>
+
+static void schedule (PacketPassPriorityQueue *m)
+{
+    ASSERT(!m->sending_flow)
+    ASSERT(!m->freeing)
+    ASSERT(!PacketPassPriorityQueue__Tree_IsEmpty(&m->queued_tree))
+    
+    // get first queued flow
+    PacketPassPriorityQueueFlow *qflow = PacketPassPriorityQueue__Tree_GetFirst(&m->queued_tree, 0);
+    ASSERT(qflow->is_queued)
+    
+    // remove flow from queue
+    PacketPassPriorityQueue__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;
+}
+
+static void schedule_job_handler (PacketPassPriorityQueue *m)
+{
+    ASSERT(!m->sending_flow)
+    ASSERT(!m->freeing)
+    DebugObject_Access(&m->d_obj);
+    
+    if (!PacketPassPriorityQueue__Tree_IsEmpty(&m->queued_tree)) {
+        schedule(m);
+    }
+}
+
+static void input_handler_send (PacketPassPriorityQueueFlow *flow, uint8_t *data, int data_len)
+{
+    PacketPassPriorityQueue *m = flow->m;
+    
+    ASSERT(flow != m->sending_flow)
+    ASSERT(!flow->is_queued)
+    ASSERT(!m->freeing)
+    DebugObject_Access(&flow->d_obj);
+    
+    // queue flow
+    flow->queued.data = data;
+    flow->queued.data_len = data_len;
+    int res = PacketPassPriorityQueue__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 (PacketPassPriorityQueue *m)
+{
+    ASSERT(m->sending_flow)
+    ASSERT(!BPending_IsSet(&m->schedule_job))
+    ASSERT(!m->freeing)
+    ASSERT(!m->sending_flow->is_queued)
+    
+    PacketPassPriorityQueueFlow *flow = m->sending_flow;
+    
+    // sending finished
+    m->sending_flow = NULL;
+    
+    // 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
+        PacketPassPriorityQueue_handler_busy handler = flow->handler_busy;
+        flow->handler_busy = NULL;
+        
+        // call handler
+        handler(flow->user);
+        return;
+    }
+}
+
+void PacketPassPriorityQueue_Init (PacketPassPriorityQueue *m, PacketPassInterface *output, BPendingGroup *pg, int use_cancel)
+{
+    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;
+    
+    // init output
+    PacketPassInterface_Sender_Init(m->output, (PacketPassInterface_handler_done)output_handler_done, m);
+    
+    // not sending
+    m->sending_flow = NULL;
+    
+    // init queued tree
+    PacketPassPriorityQueue__Tree_Init(&m->queued_tree);
+    
+    // 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);
+}
+
+void PacketPassPriorityQueue_Free (PacketPassPriorityQueue *m)
+{
+    ASSERT(PacketPassPriorityQueue__Tree_IsEmpty(&m->queued_tree))
+    ASSERT(!m->sending_flow)
+    DebugCounter_Free(&m->d_ctr);
+    DebugObject_Free(&m->d_obj);
+    
+    // free schedule job
+    BPending_Free(&m->schedule_job);
+}
+
+void PacketPassPriorityQueue_PrepareFree (PacketPassPriorityQueue *m)
+{
+    DebugObject_Access(&m->d_obj);
+    
+    // set freeing
+    m->freeing = 1;
+}
+
+int PacketPassPriorityQueue_GetMTU (PacketPassPriorityQueue *m)
+{
+    DebugObject_Access(&m->d_obj);
+    
+    return PacketPassInterface_GetMTU(m->output);
+}
+
+void PacketPassPriorityQueueFlow_Init (PacketPassPriorityQueueFlow *flow, PacketPassPriorityQueue *m, int priority)
+{
+    ASSERT(!m->freeing)
+    DebugObject_Access(&m->d_obj);
+    
+    // init arguments
+    flow->m = m;
+    flow->priority = priority;
+    
+    // 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);
+    
+    // is not queued
+    flow->is_queued = 0;
+    
+    DebugObject_Init(&flow->d_obj);
+    DebugCounter_Increment(&m->d_ctr);
+}
+
+void PacketPassPriorityQueueFlow_Free (PacketPassPriorityQueueFlow *flow)
+{
+    PacketPassPriorityQueue *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 queue
+    if (flow->is_queued) {
+        PacketPassPriorityQueue__Tree_Remove(&m->queued_tree, 0, flow);
+    }
+    
+    // free input
+    PacketPassInterface_Free(&flow->input);
+}
+
+void PacketPassPriorityQueueFlow_AssertFree (PacketPassPriorityQueueFlow *flow)
+{
+    PacketPassPriorityQueue *m = flow->m;
+    B_USE(m)
+    
+    ASSERT(m->freeing || flow != m->sending_flow)
+    DebugObject_Access(&flow->d_obj);
+}
+
+int PacketPassPriorityQueueFlow_IsBusy (PacketPassPriorityQueueFlow *flow)
+{
+    PacketPassPriorityQueue *m = flow->m;
+    
+    ASSERT(!m->freeing)
+    DebugObject_Access(&flow->d_obj);
+    
+    return (flow == m->sending_flow);
+}
+
+void PacketPassPriorityQueueFlow_RequestCancel (PacketPassPriorityQueueFlow *flow)
+{
+    PacketPassPriorityQueue *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 PacketPassPriorityQueueFlow_SetBusyHandler (PacketPassPriorityQueueFlow *flow, PacketPassPriorityQueue_handler_busy handler, void *user)
+{
+    PacketPassPriorityQueue *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 * PacketPassPriorityQueueFlow_GetInput (PacketPassPriorityQueueFlow *flow)
+{
+    DebugObject_Access(&flow->d_obj);
+    
+    return &flow->input;
+}
diff --git a/external/badvpn_dns/flow/PacketPassPriorityQueue.h b/external/badvpn_dns/flow/PacketPassPriorityQueue.h
new file mode 100644
index 0000000..3ac78eb
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassPriorityQueue.h
@@ -0,0 +1,192 @@
+/**
+ * @file PacketPassPriorityQueue.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Priority queue using {@link PacketPassInterface}.
+ */
+
+#ifndef BADVPN_FLOW_PACKETPASSPRIORITYQUEUE_H
+#define BADVPN_FLOW_PACKETPASSPRIORITYQUEUE_H
+
+#include <stdint.h>
+
+#include <misc/debugcounter.h>
+#include <structure/SAvl.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+#include <flow/PacketPassInterface.h>
+
+typedef void (*PacketPassPriorityQueue_handler_busy) (void *user);
+
+struct PacketPassPriorityQueueFlow_s;
+
+#include "PacketPassPriorityQueue_tree.h"
+#include <structure/SAvl_decl.h>
+
+typedef struct PacketPassPriorityQueueFlow_s {
+    struct PacketPassPriorityQueue_s *m;
+    int priority;
+    PacketPassPriorityQueue_handler_busy handler_busy;
+    void *user;
+    PacketPassInterface input;
+    int is_queued;
+    struct {
+        PacketPassPriorityQueue__TreeNode tree_node;
+        uint8_t *data;
+        int data_len;
+    } queued;
+    DebugObject d_obj;
+} PacketPassPriorityQueueFlow;
+
+/**
+ * Priority queue using {@link PacketPassInterface}.
+ */
+typedef struct PacketPassPriorityQueue_s {
+    PacketPassInterface *output;
+    BPendingGroup *pg;
+    int use_cancel;
+    struct PacketPassPriorityQueueFlow_s *sending_flow;
+    PacketPassPriorityQueue__Tree queued_tree;
+    int freeing;
+    BPending schedule_job;
+    DebugObject d_obj;
+    DebugCounter d_ctr;
+} PacketPassPriorityQueue;
+
+/**
+ * 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.
+ */
+void PacketPassPriorityQueue_Init (PacketPassPriorityQueue *m, PacketPassInterface *output, BPendingGroup *pg, int use_cancel);
+
+/**
+ * Frees the queue.
+ * All flows must have been freed.
+ *
+ * @param m the object
+ */
+void PacketPassPriorityQueue_Free (PacketPassPriorityQueue *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 PacketPassPriorityQueue_PrepareFree (PacketPassPriorityQueue *m);
+
+/**
+ * Returns the MTU of the queue.
+ *
+ * @param m the object
+ */
+int PacketPassPriorityQueue_GetMTU (PacketPassPriorityQueue *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
+ * @param priority flow priority. Lower value means higher priority.
+ */
+void PacketPassPriorityQueueFlow_Init (PacketPassPriorityQueueFlow *flow, PacketPassPriorityQueue *m, int priority);
+
+/**
+ * Frees a queue flow.
+ * Unless the queue is in freeing state:
+ * - The flow must not be busy as indicated by {@link PacketPassPriorityQueueFlow_IsBusy}.
+ * - Must not be called from queue calls to output.
+ *
+ * @param flow the object
+ */
+void PacketPassPriorityQueueFlow_Free (PacketPassPriorityQueueFlow *flow);
+
+/**
+ * Does nothing.
+ * It must be possible to free the flow (see {@link PacketPassPriorityQueueFlow}).
+ * 
+ * @param flow the object
+ */
+void PacketPassPriorityQueueFlow_AssertFree (PacketPassPriorityQueueFlow *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 PacketPassPriorityQueueFlow_IsBusy (PacketPassPriorityQueueFlow *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 PacketPassPriorityQueueFlow_IsBusy}.
+ * Queue must not be in freeing state.
+ * 
+ * @param flow the object
+ */
+void PacketPassPriorityQueueFlow_RequestCancel (PacketPassPriorityQueueFlow *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 PacketPassPriorityQueueFlow_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 PacketPassPriorityQueueFlow_SetBusyHandler (PacketPassPriorityQueueFlow *flow, PacketPassPriorityQueue_handler_busy handler, void *user);
+
+/**
+ * Returns the input interface of the flow.
+ *
+ * @param flow the object
+ * @return input interface
+ */
+PacketPassInterface * PacketPassPriorityQueueFlow_GetInput (PacketPassPriorityQueueFlow *flow);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketPassPriorityQueue_tree.h b/external/badvpn_dns/flow/PacketPassPriorityQueue_tree.h
new file mode 100644
index 0000000..0d8b213
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassPriorityQueue_tree.h
@@ -0,0 +1,7 @@
+#define SAVL_PARAM_NAME PacketPassPriorityQueue__Tree
+#define SAVL_PARAM_FEATURE_COUNTS 0
+#define SAVL_PARAM_FEATURE_NOKEYS 1
+#define SAVL_PARAM_TYPE_ENTRY struct PacketPassPriorityQueueFlow_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/PacketProtoDecoder.c b/external/badvpn_dns/flow/PacketProtoDecoder.c
new file mode 100644
index 0000000..68d26c5
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketProtoDecoder.c
@@ -0,0 +1,182 @@
+/**
+ * @file PacketProtoDecoder.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/debug.h>
+#include <misc/byteorder.h>
+#include <misc/minmax.h>
+#include <base/BLog.h>
+
+#include <flow/PacketProtoDecoder.h>
+
+#include <generated/blog_channel_PacketProtoDecoder.h>
+
+static void process_data (PacketProtoDecoder *enc);
+static void input_handler_done (PacketProtoDecoder *enc, int data_len);
+static void output_handler_done (PacketProtoDecoder *enc);
+
+void process_data (PacketProtoDecoder *enc)
+{
+    int was_error = 0;
+    
+    do {
+        uint8_t *data = enc->buf + enc->buf_start;
+        int left = enc->buf_used;
+        
+        // check if header was received
+        if (left < sizeof(struct packetproto_header)) {
+            break;
+        }
+        struct packetproto_header header;
+        memcpy(&header, data, sizeof(header));
+        data += sizeof(struct packetproto_header);
+        left -= sizeof(struct packetproto_header);
+        int data_len = ltoh16(header.len);
+        
+        // check data length
+        if (data_len > enc->output_mtu) {
+            BLog(BLOG_NOTICE, "error: packet too large");
+            was_error = 1;
+            break;
+        }
+        
+        // check if whole packet was received
+        if (left < data_len) {
+            break;
+        }
+        
+        // update buffer
+        enc->buf_start += sizeof(struct packetproto_header) + data_len;
+        enc->buf_used -= sizeof(struct packetproto_header) + data_len;
+        
+        // submit packet
+        PacketPassInterface_Sender_Send(enc->output, data, data_len);
+        return;
+    } while (0);
+    
+    if (was_error) {
+        // reset buffer
+        enc->buf_start = 0;
+        enc->buf_used = 0;
+    } else {
+        // if we reached the end of the buffer, wrap around to allow more data to be received
+        if (enc->buf_start + enc->buf_used == enc->buf_size) {
+            memmove(enc->buf, enc->buf + enc->buf_start, enc->buf_used);
+            enc->buf_start = 0;
+        }
+    }
+    
+    // receive data
+    StreamRecvInterface_Receiver_Recv(enc->input, enc->buf + (enc->buf_start + enc->buf_used), enc->buf_size - (enc->buf_start + enc->buf_used));
+    
+    // if we had error, report it
+    if (was_error) {
+        enc->handler_error(enc->user);
+        return;
+    }
+}
+
+static void input_handler_done (PacketProtoDecoder *enc, int data_len)
+{
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= enc->buf_size - (enc->buf_start + enc->buf_used))
+    DebugObject_Access(&enc->d_obj);
+    
+    // update buffer
+    enc->buf_used += data_len;
+    
+    // process data
+    process_data(enc);
+    return;
+}
+
+void output_handler_done (PacketProtoDecoder *enc)
+{
+    DebugObject_Access(&enc->d_obj);
+    
+    // process data
+    process_data(enc);
+    return;
+}
+
+int PacketProtoDecoder_Init (PacketProtoDecoder *enc, StreamRecvInterface *input, PacketPassInterface *output, BPendingGroup *pg, void *user, PacketProtoDecoder_handler_error handler_error)
+{
+    // init arguments
+    enc->input = input;
+    enc->output = output;
+    enc->user = user;
+    enc->handler_error = handler_error;
+    
+    // init input
+    StreamRecvInterface_Receiver_Init(enc->input, (StreamRecvInterface_handler_done)input_handler_done, enc);
+    
+    // init output
+    PacketPassInterface_Sender_Init(enc->output, (PacketPassInterface_handler_done)output_handler_done, enc);
+    
+    // set output MTU, limit by maximum payload size
+    enc->output_mtu = bmin_int(PacketPassInterface_GetMTU(enc->output), PACKETPROTO_MAXPAYLOAD);
+    
+    // init buffer state
+    enc->buf_size = PACKETPROTO_ENCLEN(enc->output_mtu);
+    enc->buf_start = 0;
+    enc->buf_used = 0;
+    
+    // allocate buffer
+    if (!(enc->buf = (uint8_t *)malloc(enc->buf_size))) {
+        goto fail0;
+    }
+    
+    // start receiving
+    StreamRecvInterface_Receiver_Recv(enc->input, enc->buf, enc->buf_size);
+    
+    DebugObject_Init(&enc->d_obj);
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void PacketProtoDecoder_Free (PacketProtoDecoder *enc)
+{
+    DebugObject_Free(&enc->d_obj);
+    
+    // free buffer
+    free(enc->buf);
+}
+
+void PacketProtoDecoder_Reset (PacketProtoDecoder *enc)
+{
+    DebugObject_Access(&enc->d_obj);
+    
+    enc->buf_start += enc->buf_used;
+    enc->buf_used = 0;
+}
diff --git a/external/badvpn_dns/flow/PacketProtoDecoder.h b/external/badvpn_dns/flow/PacketProtoDecoder.h
new file mode 100644
index 0000000..2c20694
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketProtoDecoder.h
@@ -0,0 +1,96 @@
+/**
+ * @file PacketProtoDecoder.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 a stream according to PacketProto.
+ */
+
+#ifndef BADVPN_FLOW_PACKETPROTODECODER_H
+#define BADVPN_FLOW_PACKETPROTODECODER_H
+
+#include <stdint.h>
+
+#include <protocol/packetproto.h>
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <flow/StreamRecvInterface.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * Handler called when a protocol error occurs.
+ * When an error occurs, the decoder is reset to the initial state.
+ * 
+ * @param user as in {@link PacketProtoDecoder_Init}
+ */
+typedef void (*PacketProtoDecoder_handler_error) (void *user);
+
+typedef struct {
+    StreamRecvInterface *input;
+    PacketPassInterface *output;
+    void *user;
+    PacketProtoDecoder_handler_error handler_error;
+    int output_mtu;
+    int buf_size;
+    int buf_start;
+    int buf_used;
+    uint8_t *buf;
+    DebugObject d_obj;
+} PacketProtoDecoder;
+
+/**
+ * Initializes the object.
+ *
+ * @param enc the object
+ * @param input input interface. The decoder will accept packets with payload size up to its MTU
+ *              (but the payload can never be more than PACKETPROTO_MAXPAYLOAD).
+ * @param output output interface
+ * @param pg pending group
+ * @param user argument to handlers
+ * @param handler_error error handler
+ * @return 1 on success, 0 on failure
+ */
+int PacketProtoDecoder_Init (PacketProtoDecoder *enc, StreamRecvInterface *input, PacketPassInterface *output, BPendingGroup *pg, void *user, PacketProtoDecoder_handler_error handler_error) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ *
+ * @param enc the object
+ */
+void PacketProtoDecoder_Free (PacketProtoDecoder *enc);
+
+/**
+ * Clears the internal buffer.
+ * The next data received from the input will be treated as a new
+ * PacketProto stream.
+ *
+ * @param enc the object
+ */
+void PacketProtoDecoder_Reset (PacketProtoDecoder *enc);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketProtoEncoder.c b/external/badvpn_dns/flow/PacketProtoEncoder.c
new file mode 100644
index 0000000..00aaa95
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketProtoEncoder.c
@@ -0,0 +1,101 @@
+/**
+ * @file PacketProtoEncoder.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <protocol/packetproto.h>
+#include <misc/balign.h>
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+
+#include <flow/PacketProtoEncoder.h>
+
+static void output_handler_recv (PacketProtoEncoder *enc, uint8_t *data)
+{
+    ASSERT(!enc->output_packet)
+    ASSERT(data)
+    DebugObject_Access(&enc->d_obj);
+    
+    // schedule receive
+    enc->output_packet = data;
+    PacketRecvInterface_Receiver_Recv(enc->input, enc->output_packet + sizeof(struct packetproto_header));
+}
+
+static void input_handler_done (PacketProtoEncoder *enc, int in_len)
+{
+    ASSERT(enc->output_packet)
+    DebugObject_Access(&enc->d_obj);
+    
+    // write length
+    struct packetproto_header pp;
+    pp.len = htol16(in_len);
+    memcpy(enc->output_packet, &pp, sizeof(pp));
+    
+    // finish output packet
+    enc->output_packet = NULL;
+    PacketRecvInterface_Done(&enc->output, PACKETPROTO_ENCLEN(in_len));
+}
+
+void PacketProtoEncoder_Init (PacketProtoEncoder *enc, PacketRecvInterface *input, BPendingGroup *pg)
+{
+    ASSERT(PacketRecvInterface_GetMTU(input) <= PACKETPROTO_MAXPAYLOAD)
+    
+    // init arguments
+    enc->input = input;
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(enc->input, (PacketRecvInterface_handler_done)input_handler_done, enc);
+    
+    // init output
+    PacketRecvInterface_Init(
+        &enc->output, PACKETPROTO_ENCLEN(PacketRecvInterface_GetMTU(enc->input)),
+        (PacketRecvInterface_handler_recv)output_handler_recv, enc, pg
+    );
+    
+    // set no output packet
+    enc->output_packet = NULL;
+    
+    DebugObject_Init(&enc->d_obj);
+}
+
+void PacketProtoEncoder_Free (PacketProtoEncoder *enc)
+{
+    DebugObject_Free(&enc->d_obj);
+
+    // free input
+    PacketRecvInterface_Free(&enc->output);
+}
+
+PacketRecvInterface * PacketProtoEncoder_GetOutput (PacketProtoEncoder *enc)
+{
+    DebugObject_Access(&enc->d_obj);
+    
+    return &enc->output;
+}
diff --git a/external/badvpn_dns/flow/PacketProtoEncoder.h b/external/badvpn_dns/flow/PacketProtoEncoder.h
new file mode 100644
index 0000000..022aa00
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketProtoEncoder.h
@@ -0,0 +1,80 @@
+/**
+ * @file PacketProtoEncoder.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 PacketProto.
+ */
+
+#ifndef BADVPN_FLOW_PACKETPROTOENCODER_H
+#define BADVPN_FLOW_PACKETPROTOENCODER_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * Object which encodes packets according to PacketProto.
+ *
+ * Input is with {@link PacketRecvInterface}.
+ * Output is with {@link PacketRecvInterface}.
+ */
+typedef struct {
+    PacketRecvInterface *input;
+    PacketRecvInterface output;
+    uint8_t *output_packet;
+    DebugObject d_obj;
+} PacketProtoEncoder;
+
+/**
+ * Initializes the object.
+ *
+ * @param enc the object
+ * @param input input interface. Its MTU must be <=PACKETPROTO_MAXPAYLOAD.
+ * @param pg pending group
+ */
+void PacketProtoEncoder_Init (PacketProtoEncoder *enc, PacketRecvInterface *input, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param enc the object
+ */
+void PacketProtoEncoder_Free (PacketProtoEncoder *enc);
+
+/**
+ * Returns the output interface.
+ * The MTU of the output interface is PACKETPROTO_ENCLEN(MTU of input interface).
+ *
+ * @param enc the object
+ * @return output interface
+ */
+PacketRecvInterface * PacketProtoEncoder_GetOutput (PacketProtoEncoder *enc);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketProtoFlow.c b/external/badvpn_dns/flow/PacketProtoFlow.c
new file mode 100644
index 0000000..8fad8e2
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketProtoFlow.c
@@ -0,0 +1,82 @@
+/**
+ * @file PacketProtoFlow.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <protocol/packetproto.h>
+#include <misc/debug.h>
+
+#include <flow/PacketProtoFlow.h>
+
+int PacketProtoFlow_Init (PacketProtoFlow *o, int input_mtu, int num_packets, PacketPassInterface *output, BPendingGroup *pg)
+{
+    ASSERT(input_mtu >= 0)
+    ASSERT(input_mtu <= PACKETPROTO_MAXPAYLOAD)
+    ASSERT(num_packets > 0)
+    ASSERT(PacketPassInterface_GetMTU(output) >= PACKETPROTO_ENCLEN(input_mtu))
+    
+    // init async input
+    BufferWriter_Init(&o->ainput, input_mtu, pg);
+    
+    // init encoder
+    PacketProtoEncoder_Init(&o->encoder, BufferWriter_GetOutput(&o->ainput), pg);
+    
+    // init buffer
+    if (!PacketBuffer_Init(&o->buffer, PacketProtoEncoder_GetOutput(&o->encoder), output, num_packets, pg)) {
+        goto fail0;
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail0:
+    PacketProtoEncoder_Free(&o->encoder);
+    BufferWriter_Free(&o->ainput);
+    return 0;
+}
+
+void PacketProtoFlow_Free (PacketProtoFlow *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free buffer
+    PacketBuffer_Free(&o->buffer);
+    
+    // free encoder
+    PacketProtoEncoder_Free(&o->encoder);
+    
+    // free async input
+    BufferWriter_Free(&o->ainput);
+}
+
+BufferWriter * PacketProtoFlow_GetInput (PacketProtoFlow *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->ainput;
+}
diff --git a/external/badvpn_dns/flow/PacketProtoFlow.h b/external/badvpn_dns/flow/PacketProtoFlow.h
new file mode 100644
index 0000000..05e1233
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketProtoFlow.h
@@ -0,0 +1,83 @@
+/**
+ * @file PacketProtoFlow.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Buffer which encodes packets with PacketProto, with {@link BufferWriter}
+ * input and {@link PacketPassInterface} output.
+ */
+
+#ifndef BADVPN_FLOW_PACKETPROTOFLOW_H
+#define BADVPN_FLOW_PACKETPROTOFLOW_H
+
+#include <misc/debug.h>
+
+#include <base/DebugObject.h>
+#include <flow/BufferWriter.h>
+#include <flow/PacketProtoEncoder.h>
+#include <flow/PacketBuffer.h>
+
+/**
+ * Buffer which encodes packets with PacketProto, with {@link BufferWriter}
+ * input and {@link PacketPassInterface} output.
+ */
+typedef struct {
+    BufferWriter ainput;
+    PacketProtoEncoder encoder;
+    PacketBuffer buffer;
+    DebugObject d_obj;
+} PacketProtoFlow;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param input_mtu maximum input packet size. Must be >=0 and <=PACKETPROTO_MAXPAYLOAD.
+ * @param num_packets minimum number of packets the buffer should hold. Must be >0.
+ * @param output output interface. Its MTU must be >=PACKETPROTO_ENCLEN(input_mtu).
+ * @param pg pending group
+ * @return 1 on success, 0 on failure
+ */
+int PacketProtoFlow_Init (PacketProtoFlow *o, int input_mtu, int num_packets, PacketPassInterface *output, BPendingGroup *pg) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void PacketProtoFlow_Free (PacketProtoFlow *o);
+
+/**
+ * Returns the input interface.
+ * 
+ * @param o the object
+ * @return input interface
+ */
+BufferWriter * PacketProtoFlow_GetInput (PacketProtoFlow *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketRecvBlocker.c b/external/badvpn_dns/flow/PacketRecvBlocker.c
new file mode 100644
index 0000000..7e679f8
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketRecvBlocker.c
@@ -0,0 +1,99 @@
+/**
+ * @file PacketRecvBlocker.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/PacketRecvBlocker.h>
+
+static void output_handler_recv (PacketRecvBlocker *o, uint8_t *data)
+{
+    ASSERT(!o->out_have)
+    DebugObject_Access(&o->d_obj);
+    
+    // remember packet
+    o->out_have = 1;
+    o->out = data;
+    o->out_input_blocking = 0;
+}
+
+static void input_handler_done (PacketRecvBlocker *o, int data_len)
+{
+    ASSERT(o->out_have)
+    ASSERT(o->out_input_blocking)
+    DebugObject_Access(&o->d_obj);
+    
+    // schedule done
+    o->out_have = 0;
+    PacketRecvInterface_Done(&o->output, data_len);
+}
+
+void PacketRecvBlocker_Init (PacketRecvBlocker *o, PacketRecvInterface *input, BPendingGroup *pg)
+{
+    // init arguments
+    o->input = input;
+    
+    // init output
+    PacketRecvInterface_Init(&o->output, PacketRecvInterface_GetMTU(o->input), (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    // have no output packet
+    o->out_have = 0;
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void PacketRecvBlocker_Free (PacketRecvBlocker *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // free output
+    PacketRecvInterface_Free(&o->output);
+}
+
+PacketRecvInterface * PacketRecvBlocker_GetOutput (PacketRecvBlocker *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
+
+void PacketRecvBlocker_AllowBlockedPacket (PacketRecvBlocker *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    if (!o->out_have || o->out_input_blocking) {
+        return;
+    }
+    
+    // schedule receive
+    o->out_input_blocking = 1;
+    PacketRecvInterface_Receiver_Recv(o->input, o->out);
+}
diff --git a/external/badvpn_dns/flow/PacketRecvBlocker.h b/external/badvpn_dns/flow/PacketRecvBlocker.h
new file mode 100644
index 0000000..ba98d06
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketRecvBlocker.h
@@ -0,0 +1,90 @@
+/**
+ * @file PacketRecvBlocker.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * {@link PacketRecvInterface} layer which blocks all output recv calls and only
+ * passes a single blocked call on to input when the user wants so.
+ */
+
+#ifndef BADVPN_FLOW_PACKETRECVBLOCKER_H
+#define BADVPN_FLOW_PACKETRECVBLOCKER_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * {@link PacketRecvInterface} layer which blocks all output recv calls and only
+ * passes a single blocked call on to input when the user wants so.
+ */
+typedef struct {
+    PacketRecvInterface output;
+    int out_have;
+    uint8_t *out;
+    int out_input_blocking;
+    PacketRecvInterface *input;
+    DebugObject d_obj;
+} PacketRecvBlocker;
+
+/**
+ * Initializes the object.
+ *
+ * @param o the object
+ * @param input input interface
+ */
+void PacketRecvBlocker_Init (PacketRecvBlocker *o, PacketRecvInterface *input, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void PacketRecvBlocker_Free (PacketRecvBlocker *o);
+
+/**
+ * Returns the output interface.
+ * The MTU of the output interface will be the same as of the input interface.
+ *
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * PacketRecvBlocker_GetOutput (PacketRecvBlocker *o);
+
+/**
+ * Passes a blocked output recv call to input if there is one and it has not
+ * been passed yet. Otherwise it does nothing.
+ * Must not be called from input Recv calls.
+ * This function may invoke I/O.
+ *
+ * @param o the object
+ */
+void PacketRecvBlocker_AllowBlockedPacket (PacketRecvBlocker *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketRecvConnector.c b/external/badvpn_dns/flow/PacketRecvConnector.c
new file mode 100644
index 0000000..455db23
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketRecvConnector.c
@@ -0,0 +1,123 @@
+/**
+ * @file PacketRecvConnector.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/PacketRecvConnector.h>
+
+static void output_handler_recv (PacketRecvConnector *o, uint8_t *data)
+{
+    ASSERT(!o->out_have)
+    DebugObject_Access(&o->d_obj);
+    
+    // remember output packet
+    o->out_have = 1;
+    o->out = data;
+    
+    if (o->input) {
+        // schedule receive
+        PacketRecvInterface_Receiver_Recv(o->input, o->out);
+    }
+}
+
+static void input_handler_done (PacketRecvConnector *o, int data_len)
+{
+    ASSERT(o->out_have)
+    ASSERT(o->input)
+    DebugObject_Access(&o->d_obj);
+    
+    // have no output packet
+    o->out_have = 0;
+    
+    // allow output to receive more packets
+    PacketRecvInterface_Done(&o->output, data_len);
+}
+
+void PacketRecvConnector_Init (PacketRecvConnector *o, int mtu, BPendingGroup *pg)
+{
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    o->output_mtu = mtu;
+    
+    // init output
+    PacketRecvInterface_Init(&o->output, o->output_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    // have no output packet
+    o->out_have = 0;
+    
+    // have no input
+    o->input = NULL;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void PacketRecvConnector_Free (PacketRecvConnector *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free output
+    PacketRecvInterface_Free(&o->output);
+}
+
+PacketRecvInterface * PacketRecvConnector_GetOutput (PacketRecvConnector *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
+
+void PacketRecvConnector_ConnectInput (PacketRecvConnector *o, PacketRecvInterface *input)
+{
+    ASSERT(!o->input)
+    ASSERT(PacketRecvInterface_GetMTU(input) <= o->output_mtu)
+    DebugObject_Access(&o->d_obj);
+    
+    // set input
+    o->input = input;
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o);
+    
+    // if we have an output packet, schedule receive
+    if (o->out_have) {
+        PacketRecvInterface_Receiver_Recv(o->input, o->out);
+    }
+}
+
+void PacketRecvConnector_DisconnectInput (PacketRecvConnector *o)
+{
+    ASSERT(o->input)
+    DebugObject_Access(&o->d_obj);
+    
+    // set no input
+    o->input = NULL;
+}
diff --git a/external/badvpn_dns/flow/PacketRecvConnector.h b/external/badvpn_dns/flow/PacketRecvConnector.h
new file mode 100644
index 0000000..25cf851
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketRecvConnector.h
@@ -0,0 +1,102 @@
+/**
+ * @file PacketRecvConnector.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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} layer which allows the input to be
+ * connected and disconnected on the fly.
+ */
+
+#ifndef BADVPN_FLOW_PACKETRECVCONNECTOR_H
+#define BADVPN_FLOW_PACKETRECVCONNECTOR_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * A {@link PacketRecvInterface} layer which allows the input to be
+ * connected and disconnected on the fly.
+ */
+typedef struct {
+    PacketRecvInterface output;
+    int output_mtu;
+    int out_have;
+    uint8_t *out;
+    PacketRecvInterface *input;
+    DebugObject d_obj;
+} PacketRecvConnector;
+
+/**
+ * Initializes the object.
+ * The object is initialized in not connected state.
+ *
+ * @param o the object
+ * @param mtu maximum output packet size. Must be >=0.
+ * @param pg pending group
+ */
+void PacketRecvConnector_Init (PacketRecvConnector *o, int mtu, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void PacketRecvConnector_Free (PacketRecvConnector *o);
+
+/**
+ * Returns the output interface.
+ * The MTU of the interface will be as in {@link PacketRecvConnector_Init}.
+ *
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * PacketRecvConnector_GetOutput (PacketRecvConnector *o);
+
+/**
+ * Connects input.
+ * The object must be in not connected state.
+ * The object enters connected state.
+ *
+ * @param o the object
+ * @param output input to connect. Its MTU must be <= MTU specified in
+ *               {@link PacketRecvConnector_Init}.
+ */
+void PacketRecvConnector_ConnectInput (PacketRecvConnector *o, PacketRecvInterface *input);
+
+/**
+ * Disconnects input.
+ * The object must be in connected state.
+ * The object enters not connected state.
+ *
+ * @param o the object
+ */
+void PacketRecvConnector_DisconnectInput (PacketRecvConnector *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketRecvInterface.c b/external/badvpn_dns/flow/PacketRecvInterface.c
new file mode 100644
index 0000000..40bb8c6
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketRecvInterface.c
@@ -0,0 +1,56 @@
+/**
+ * @file PacketRecvInterface.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <flow/PacketRecvInterface.h>
+
+void _PacketRecvInterface_job_operation (PacketRecvInterface *i)
+{
+    ASSERT(i->state == PRI_STATE_OPERATION_PENDING)
+    DebugObject_Access(&i->d_obj);
+    
+    // set state
+    i->state = PRI_STATE_BUSY;
+    
+    // call handler
+    i->handler_operation(i->user_provider, i->job_operation_data);
+    return;
+}
+
+void _PacketRecvInterface_job_done (PacketRecvInterface *i)
+{
+    ASSERT(i->state == PRI_STATE_DONE_PENDING)
+    DebugObject_Access(&i->d_obj);
+    
+    // set state
+    i->state = PRI_STATE_NONE;
+    
+    // call handler
+    i->handler_done(i->user_user, i->job_done_len);
+    return;
+}
diff --git a/external/badvpn_dns/flow/PacketRecvInterface.h b/external/badvpn_dns/flow/PacketRecvInterface.h
new file mode 100644
index 0000000..6350ed2
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketRecvInterface.h
@@ -0,0 +1,170 @@
+/**
+ * @file PacketRecvInterface.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Interface allowing a packet receiver to receive data packets from a packet sender.
+ */
+
+#ifndef BADVPN_FLOW_PACKETRECVINTERFACE_H
+#define BADVPN_FLOW_PACKETRECVINTERFACE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+
+#define PRI_STATE_NONE 1
+#define PRI_STATE_OPERATION_PENDING 2
+#define PRI_STATE_BUSY 3
+#define PRI_STATE_DONE_PENDING 4
+
+typedef void (*PacketRecvInterface_handler_recv) (void *user, uint8_t *data);
+
+typedef void (*PacketRecvInterface_handler_done) (void *user, int data_len);
+
+typedef struct {
+    // provider data
+    int mtu;
+    PacketRecvInterface_handler_recv handler_operation;
+    void *user_provider;
+
+    // user data
+    PacketRecvInterface_handler_done handler_done;
+    void *user_user;
+    
+    // operation job
+    BPending job_operation;
+    uint8_t *job_operation_data;
+    
+    // done job
+    BPending job_done;
+    int job_done_len;
+    
+    // state
+    int state;
+    
+    DebugObject d_obj;
+} PacketRecvInterface;
+
+static void PacketRecvInterface_Init (PacketRecvInterface *i, int mtu, PacketRecvInterface_handler_recv handler_operation, void *user, BPendingGroup *pg);
+
+static void PacketRecvInterface_Free (PacketRecvInterface *i);
+
+static void PacketRecvInterface_Done (PacketRecvInterface *i, int data_len);
+
+static int PacketRecvInterface_GetMTU (PacketRecvInterface *i);
+
+static void PacketRecvInterface_Receiver_Init (PacketRecvInterface *i, PacketRecvInterface_handler_done handler_done, void *user);
+
+static void PacketRecvInterface_Receiver_Recv (PacketRecvInterface *i, uint8_t *data);
+
+void _PacketRecvInterface_job_operation (PacketRecvInterface *i);
+void _PacketRecvInterface_job_done (PacketRecvInterface *i);
+
+void PacketRecvInterface_Init (PacketRecvInterface *i, int mtu, PacketRecvInterface_handler_recv handler_operation, void *user, BPendingGroup *pg)
+{
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    i->mtu = mtu;
+    i->handler_operation = handler_operation;
+    i->user_provider = user;
+    
+    // set no user
+    i->handler_done = NULL;
+    
+    // init jobs
+    BPending_Init(&i->job_operation, pg, (BPending_handler)_PacketRecvInterface_job_operation, i);
+    BPending_Init(&i->job_done, pg, (BPending_handler)_PacketRecvInterface_job_done, i);
+    
+    // set state
+    i->state = PRI_STATE_NONE;
+    
+    DebugObject_Init(&i->d_obj);
+}
+
+void PacketRecvInterface_Free (PacketRecvInterface *i)
+{
+    DebugObject_Free(&i->d_obj);
+    
+    // free jobs
+    BPending_Free(&i->job_done);
+    BPending_Free(&i->job_operation);
+}
+
+void PacketRecvInterface_Done (PacketRecvInterface *i, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= i->mtu)
+    ASSERT(i->state == PRI_STATE_BUSY)
+    DebugObject_Access(&i->d_obj);
+    
+    // schedule done
+    i->job_done_len = data_len;
+    BPending_Set(&i->job_done);
+    
+    // set state
+    i->state = PRI_STATE_DONE_PENDING;
+}
+
+int PacketRecvInterface_GetMTU (PacketRecvInterface *i)
+{
+    DebugObject_Access(&i->d_obj);
+    
+    return i->mtu;
+}
+
+void PacketRecvInterface_Receiver_Init (PacketRecvInterface *i, PacketRecvInterface_handler_done handler_done, void *user)
+{
+    ASSERT(handler_done)
+    ASSERT(!i->handler_done)
+    DebugObject_Access(&i->d_obj);
+    
+    i->handler_done = handler_done;
+    i->user_user = user;
+}
+
+void PacketRecvInterface_Receiver_Recv (PacketRecvInterface *i, uint8_t *data)
+{
+    ASSERT(!(i->mtu > 0) || data)
+    ASSERT(i->state == PRI_STATE_NONE)
+    ASSERT(i->handler_done)
+    DebugObject_Access(&i->d_obj);
+    
+    // schedule operation
+    i->job_operation_data = data;
+    BPending_Set(&i->job_operation);
+    
+    // set state
+    i->state = PRI_STATE_OPERATION_PENDING;
+}
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketRouter.c b/external/badvpn_dns/flow/PacketRouter.c
new file mode 100644
index 0000000..7b9f5f9
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketRouter.c
@@ -0,0 +1,129 @@
+/**
+ * @file PacketRouter.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <flow/PacketRouter.h>
+
+static void input_handler_done (PacketRouter *o, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->mtu - o->recv_offset)
+    ASSERT(!BPending_IsSet(&o->next_job))
+    DebugObject_Access(&o->d_obj);
+    
+    // set next job
+    BPending_Set(&o->next_job);
+    
+    // call handler
+    o->handler(o->user, RouteBufferSource_Pointer(&o->rbs), data_len);
+    return;
+}
+
+static void next_job_handler (PacketRouter *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // receive
+    PacketRecvInterface_Receiver_Recv(o->input, RouteBufferSource_Pointer(&o->rbs) + o->recv_offset);
+}
+
+int PacketRouter_Init (PacketRouter *o, int mtu, int recv_offset, PacketRecvInterface *input, PacketRouter_handler handler, void *user, BPendingGroup *pg)
+{
+    ASSERT(mtu >= 0)
+    ASSERT(recv_offset >= 0)
+    ASSERT(recv_offset <= mtu)
+    ASSERT(PacketRecvInterface_GetMTU(input) <= mtu - recv_offset)
+    
+    // init arguments
+    o->mtu = mtu;
+    o->recv_offset = recv_offset;
+    o->input = input;
+    o->handler = handler;
+    o->user = user;
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o);
+    
+    // init RouteBufferSource
+    if (!RouteBufferSource_Init(&o->rbs, mtu)) {
+        goto fail0;
+    }
+    
+    // init next job
+    BPending_Init(&o->next_job, pg, (BPending_handler)next_job_handler, o);
+    
+    // receive
+    PacketRecvInterface_Receiver_Recv(o->input, RouteBufferSource_Pointer(&o->rbs) + o->recv_offset);
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void PacketRouter_Free (PacketRouter *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free next job
+    BPending_Free(&o->next_job);
+    
+    // free RouteBufferSource
+    RouteBufferSource_Free(&o->rbs);
+}
+
+int PacketRouter_Route (PacketRouter *o, int len, RouteBuffer *output, uint8_t **next_buf, int copy_offset, int copy_len)
+{
+    ASSERT(len >= 0)
+    ASSERT(len <= o->mtu)
+    ASSERT(RouteBuffer_GetMTU(output) == o->mtu)
+    ASSERT(copy_offset >= 0)
+    ASSERT(copy_offset <= o->mtu)
+    ASSERT(copy_len >= 0)
+    ASSERT(copy_len <= o->mtu - copy_offset)
+    ASSERT(BPending_IsSet(&o->next_job))
+    DebugObject_Access(&o->d_obj);
+    
+    if (!RouteBufferSource_Route(&o->rbs, len, output, copy_offset, copy_len)) {
+        return 0;
+    }
+    
+    if (next_buf) {
+        *next_buf = RouteBufferSource_Pointer(&o->rbs);
+    }
+    
+    return 1;
+}
+
+void PacketRouter_AssertRoute (PacketRouter *o)
+{
+    ASSERT(BPending_IsSet(&o->next_job))
+    DebugObject_Access(&o->d_obj);
+}
diff --git a/external/badvpn_dns/flow/PacketRouter.h b/external/badvpn_dns/flow/PacketRouter.h
new file mode 100644
index 0000000..3226078
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketRouter.h
@@ -0,0 +1,126 @@
+/**
+ * @file PacketRouter.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 simplifies routing packets to {@link RouteBuffer}'s from a
+ * {@link PacketRecvInterface} input.
+ */
+
+#ifndef BADVPN_FLOW_PACKETROUTER_H
+#define BADVPN_FLOW_PACKETROUTER_H
+
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+#include <flow/PacketRecvInterface.h>
+#include <flow/RouteBuffer.h>
+
+/**
+ * Handler called when a packet is received, allowing the user to route it
+ * to one or more buffers using {@link PacketRouter_Route}.
+ * 
+ * @param user as in {@link PacketRouter_Init}
+ * @param buf the buffer for the packet. May be modified by the user.
+ *            Will have space for mtu bytes. Only valid in the job context of
+ *            this handler, until {@link PacketRouter_Route} is called successfully.
+ * @param recv_len length of the input packet (located at recv_offset bytes offset)
+ */
+typedef void (*PacketRouter_handler) (void *user, uint8_t *buf, int recv_len);
+
+/**
+ * Object which simplifies routing packets to {@link RouteBuffer}'s from a
+ * {@link PacketRecvInterface} input.
+ * 
+ * Packets are routed by calling {@link PacketRouter_Route} (possibly multiple times)
+ * from the job context of the {@link PacketRouter_handler} handler.
+ */
+typedef struct {
+    int mtu;
+    int recv_offset;
+    PacketRecvInterface *input;
+    PacketRouter_handler handler;
+    void *user;
+    RouteBufferSource rbs;
+    BPending next_job;
+    DebugObject d_obj;
+} PacketRouter;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param mtu maximum packet size. Must be >=0. It will only be possible to route packets to
+ *            {@link RouteBuffer}'s with the same MTU.
+ * @param recv_offset offset from the beginning for receiving input packets.
+ *                    Must be >=0 and <=mtu. The leading space should be initialized
+ *                    by the user before routing a packet.
+ * @param input input interface. Its MTU must be <= mtu - recv_offset.
+ * @param handler handler called when a packet is received to allow the user to route it
+ * @param user value passed to handler
+ * @param pg pending group
+ * @return 1 on success, 0 on failure
+ */
+int PacketRouter_Init (PacketRouter *o, int mtu, int recv_offset, PacketRecvInterface *input, PacketRouter_handler handler, void *user, BPendingGroup *pg) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void PacketRouter_Free (PacketRouter *o);
+
+/**
+ * Routes the current packet to the given buffer.
+ * Must be called from the job context of the {@link PacketRouter_handler} handler.
+ * On success, copies part of the current packet to next one (regardless if next_buf
+ * is provided or not; if not, copies before receiving another packet).
+ * 
+ * @param o the object
+ * @param len total packet length (e.g. recv_offset + (recv_len from handler)).
+ *            Must be >=0 and <=mtu.
+ * @param output buffer to route to. Its MTU must be the same as of this object.
+ * @param next_buf if not NULL, on success, will be set to the address of a new current
+ *                 packet that can be routed. The pointer will be valid in the job context of
+ *                 the calling handler, until this function is called successfully again
+ *                 (as for the original pointer provided by the handler).
+ * @param copy_offset Offset from the beginning for copying to the next packet.
+ *                    Must be >=0 and <=mtu.
+ * @param copy_len Number of bytes to copy from the old current
+ *                 packet to the next one. Must be >=0 and <= mtu - copy_offset.
+ * @return 1 on success, 0 on failure (buffer full)
+ */
+int PacketRouter_Route (PacketRouter *o, int len, RouteBuffer *output, uint8_t **next_buf, int copy_offset, int copy_len);
+
+/**
+ * Asserts that {@link PacketRouter_Route} can be called.
+ * 
+ * @param o the object
+ */
+void PacketRouter_AssertRoute (PacketRouter *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketStreamSender.c b/external/badvpn_dns/flow/PacketStreamSender.c
new file mode 100644
index 0000000..153a72b
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketStreamSender.c
@@ -0,0 +1,111 @@
+/**
+ * @file PacketStreamSender.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <flow/PacketStreamSender.h>
+
+static void send_data (PacketStreamSender *s)
+{
+    ASSERT(s->in_len >= 0)
+    
+    if (s->in_used < s->in_len) {
+        // send more data
+        StreamPassInterface_Sender_Send(s->output, s->in + s->in_used, s->in_len - s->in_used);
+    } else {
+        // finish input packet
+        s->in_len = -1;
+        PacketPassInterface_Done(&s->input);
+    }
+}
+
+static void input_handler_send (PacketStreamSender *s, uint8_t *data, int data_len)
+{
+    ASSERT(s->in_len == -1)
+    ASSERT(data_len >= 0)
+    DebugObject_Access(&s->d_obj);
+    
+    // set input packet
+    s->in_len = data_len;
+    s->in = data;
+    s->in_used = 0;
+    
+    // send
+    send_data(s);
+}
+
+static void output_handler_done (PacketStreamSender *s, int data_len)
+{
+    ASSERT(s->in_len >= 0)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= s->in_len - s->in_used)
+    DebugObject_Access(&s->d_obj);
+    
+    // update number of bytes sent
+    s->in_used += data_len;
+    
+    // send
+    send_data(s);
+}
+
+void PacketStreamSender_Init (PacketStreamSender *s, StreamPassInterface *output, int mtu, BPendingGroup *pg)
+{
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    s->output = output;
+    
+    // init input
+    PacketPassInterface_Init(&s->input, mtu, (PacketPassInterface_handler_send)input_handler_send, s, pg);
+    
+    // init output
+    StreamPassInterface_Sender_Init(s->output, (StreamPassInterface_handler_done)output_handler_done, s);
+    
+    // have no input packet
+    s->in_len = -1;
+    
+    DebugObject_Init(&s->d_obj);
+}
+
+void PacketStreamSender_Free (PacketStreamSender *s)
+{
+    DebugObject_Free(&s->d_obj);
+    
+    // free input
+    PacketPassInterface_Free(&s->input);
+}
+
+PacketPassInterface * PacketStreamSender_GetInput (PacketStreamSender *s)
+{
+    DebugObject_Access(&s->d_obj);
+    
+    return &s->input;
+}
diff --git a/external/badvpn_dns/flow/PacketStreamSender.h b/external/badvpn_dns/flow/PacketStreamSender.h
new file mode 100644
index 0000000..735360c
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketStreamSender.h
@@ -0,0 +1,83 @@
+/**
+ * @file PacketStreamSender.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 forwards packets obtained with {@link PacketPassInterface}
+ * as a stream with {@link StreamPassInterface} (i.e. it concatenates them).
+ */
+
+#ifndef BADVPN_FLOW_PACKETSTREAMSENDER_H
+#define BADVPN_FLOW_PACKETSTREAMSENDER_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/StreamPassInterface.h>
+
+/**
+ * Object which forwards packets obtained with {@link PacketPassInterface}
+ * as a stream with {@link StreamPassInterface} (i.e. it concatenates them).
+ */
+typedef struct {
+    DebugObject d_obj;
+    PacketPassInterface input;
+    StreamPassInterface *output;
+    int in_len;
+    uint8_t *in;
+    int in_used;
+} PacketStreamSender;
+
+/**
+ * Initializes the object.
+ *
+ * @param s the object
+ * @param output output interface
+ * @param mtu input MTU. Must be >=0.
+ * @param pg pending group
+ */
+void PacketStreamSender_Init (PacketStreamSender *s, StreamPassInterface *output, int mtu, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param s the object
+ */
+void PacketStreamSender_Free (PacketStreamSender *s);
+
+/**
+ * Returns the input interface.
+ * Its MTU will be as in {@link PacketStreamSender_Init}.
+ *
+ * @param s the object
+ * @return input interface
+ */
+PacketPassInterface * PacketStreamSender_GetInput (PacketStreamSender *s);
+
+#endif
diff --git a/external/badvpn_dns/flow/RouteBuffer.c b/external/badvpn_dns/flow/RouteBuffer.c
new file mode 100644
index 0000000..dec7be4
--- /dev/null
+++ b/external/badvpn_dns/flow/RouteBuffer.c
@@ -0,0 +1,256 @@
+/**
+ * @file RouteBuffer.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+
+#include <flow/RouteBuffer.h>
+
+static struct RouteBuffer_packet * alloc_packet (int mtu)
+{
+    if (mtu > SIZE_MAX - sizeof(struct RouteBuffer_packet)) {
+        return NULL;
+    }
+    
+    // allocate memory
+    struct RouteBuffer_packet *p = (struct RouteBuffer_packet *)malloc(sizeof(*p) + mtu);
+    if (!p) {
+        return NULL;
+    }
+    
+    return p;
+}
+
+static int alloc_free_packet (RouteBuffer *o)
+{
+    struct RouteBuffer_packet *p = alloc_packet(o->mtu);
+    if (!p) {
+        return 0;
+    }
+    
+    // add to free packets list
+    LinkedList1_Append(&o->packets_free, &p->node);
+    
+    return 1;
+}
+
+static void free_free_packets (RouteBuffer *o)
+{
+    while (!LinkedList1_IsEmpty(&o->packets_free)) {
+        // get packet
+        struct RouteBuffer_packet *p = UPPER_OBJECT(LinkedList1_GetLast(&o->packets_free), struct RouteBuffer_packet, node);
+        
+        // remove from free packets list
+        LinkedList1_Remove(&o->packets_free, &p->node);
+        
+        // free memory
+        free(p);
+    }
+}
+
+static void release_used_packet (RouteBuffer *o)
+{
+    ASSERT(!LinkedList1_IsEmpty(&o->packets_used))
+    
+    // get packet
+    struct RouteBuffer_packet *p = UPPER_OBJECT(LinkedList1_GetFirst(&o->packets_used), struct RouteBuffer_packet, node);
+    
+    // remove from used packets list
+    LinkedList1_Remove(&o->packets_used, &p->node);
+    
+    // add to free packets list
+    LinkedList1_Append(&o->packets_free, &p->node);
+}
+
+static void send_used_packet (RouteBuffer *o)
+{
+    ASSERT(!LinkedList1_IsEmpty(&o->packets_used))
+    
+    // get packet
+    struct RouteBuffer_packet *p = UPPER_OBJECT(LinkedList1_GetFirst(&o->packets_used), struct RouteBuffer_packet, node);
+    
+    // send
+    PacketPassInterface_Sender_Send(o->output, (uint8_t *)(p + 1), p->len);
+}
+
+static void output_handler_done (RouteBuffer *o)
+{
+    ASSERT(!LinkedList1_IsEmpty(&o->packets_used))
+    DebugObject_Access(&o->d_obj);
+    
+    // release packet
+    release_used_packet(o);
+    
+    // send next packet if there is one
+    if (!LinkedList1_IsEmpty(&o->packets_used)) {
+        send_used_packet(o);
+    }
+}
+
+int RouteBuffer_Init (RouteBuffer *o, int mtu, PacketPassInterface *output, int buf_size)
+{
+    ASSERT(mtu >= 0)
+    ASSERT(PacketPassInterface_GetMTU(output) >= mtu)
+    ASSERT(buf_size > 0)
+    
+    // init arguments
+    o->mtu = mtu;
+    o->output = output;
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // init free packets list
+    LinkedList1_Init(&o->packets_free);
+    
+    // init used packets list
+    LinkedList1_Init(&o->packets_used);
+    
+    // allocate packets
+    for (int i = 0; i < buf_size; i++) {
+        if (!alloc_free_packet(o)) {
+            goto fail1;
+        }
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail1:
+    free_free_packets(o);
+    return 0;
+}
+
+void RouteBuffer_Free (RouteBuffer *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // release packets so they can be freed
+    while (!LinkedList1_IsEmpty(&o->packets_used)) {
+        release_used_packet(o);
+    }
+    
+    // free packets
+    free_free_packets(o);
+}
+
+int RouteBuffer_GetMTU (RouteBuffer *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return o->mtu;
+}
+
+int RouteBufferSource_Init (RouteBufferSource *o, int mtu)
+{
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    o->mtu = mtu;
+    
+    // allocate current packet
+    if (!(o->current_packet = alloc_packet(o->mtu))) {
+        goto fail0;
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void RouteBufferSource_Free (RouteBufferSource *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free current packet
+    free(o->current_packet);
+}
+
+uint8_t * RouteBufferSource_Pointer (RouteBufferSource *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return (uint8_t *)(o->current_packet + 1);
+}
+
+int RouteBufferSource_Route (RouteBufferSource *o, int len, RouteBuffer *b, int copy_offset, int copy_len)
+{
+    ASSERT(len >= 0)
+    ASSERT(len <= o->mtu)
+    ASSERT(b->mtu == o->mtu)
+    ASSERT(copy_offset >= 0)
+    ASSERT(copy_offset <= o->mtu)
+    ASSERT(copy_len >= 0)
+    ASSERT(copy_len <= o->mtu - copy_offset)
+    DebugObject_Access(&b->d_obj);
+    DebugObject_Access(&o->d_obj);
+    
+    // check if there's space in the buffer
+    if (LinkedList1_IsEmpty(&b->packets_free)) {
+        return 0;
+    }
+    
+    int was_empty = LinkedList1_IsEmpty(&b->packets_used);
+    
+    struct RouteBuffer_packet *p = o->current_packet;
+    
+    // set packet length
+    p->len = len;
+    
+    // append packet to used packets list
+    LinkedList1_Append(&b->packets_used, &p->node);
+    
+    // get a free packet
+    struct RouteBuffer_packet *np = UPPER_OBJECT(LinkedList1_GetLast(&b->packets_free), struct RouteBuffer_packet, node);
+    
+    // remove it from free packets list
+    LinkedList1_Remove(&b->packets_free, &np->node);
+    
+    // make it the current packet
+    o->current_packet = np;
+    
+    // copy packet
+    if (copy_len > 0) {
+        memcpy((uint8_t *)(np + 1) + copy_offset, (uint8_t *)(p + 1) + copy_offset, copy_len);
+    }
+    
+    // start sending if required
+    if (was_empty) {
+        send_used_packet(b);
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/flow/RouteBuffer.h b/external/badvpn_dns/flow/RouteBuffer.h
new file mode 100644
index 0000000..d0f7b41
--- /dev/null
+++ b/external/badvpn_dns/flow/RouteBuffer.h
@@ -0,0 +1,139 @@
+/**
+ * @file RouteBuffer.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 for zero-copy packet routing.
+ */
+
+#ifndef BADVPN_FLOW_ROUTEBUFFER_H
+#define BADVPN_FLOW_ROUTEBUFFER_H
+
+#include <misc/debug.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+struct RouteBuffer_packet {
+    LinkedList1Node node;
+    int len;
+};
+
+/**
+ * Packet buffer for zero-copy packet routing.
+ * 
+ * Packets are buffered using {@link RouteBufferSource} objects.
+ */
+typedef struct {
+    int mtu;
+    PacketPassInterface *output;
+    LinkedList1 packets_free;
+    LinkedList1 packets_used;
+    DebugObject d_obj;
+} RouteBuffer;
+
+/**
+ * Object through which packets are buffered into {@link RouteBuffer} objects.
+ * 
+ * A packet is routed by calling {@link RouteBufferSource_Pointer}, writing it to
+ * the returned address, then calling {@link RouteBufferSource_Route}.
+ */
+typedef struct {
+    int mtu;
+    struct RouteBuffer_packet *current_packet;
+    DebugObject d_obj;
+} RouteBufferSource;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param mtu maximum packet size. Must be >=0. It will only be possible to route packets to this buffer
+ *            from {@link RouteBufferSource}.s with the same MTU.
+ * @param output output interface. Its MTU must be >=mtu.
+ * @param buf_size size of the buffer in number of packet. Must be >0.
+ * @return 1 on success, 0 on failure
+ */
+int RouteBuffer_Init (RouteBuffer *o, int mtu, PacketPassInterface *output, int buf_size) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ */
+void RouteBuffer_Free (RouteBuffer *o);
+
+/**
+ * Retuns the buffer's MTU (mtu argument to {@link RouteBuffer_Init}).
+ * 
+ * @return MTU
+ */
+int RouteBuffer_GetMTU (RouteBuffer *o);
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param mtu maximum packet size. Must be >=0. The object will only be able to route packets
+ *            to {@link RouteBuffer}'s with the same MTU.
+ * @return 1 on success, 0 on failure
+ */
+int RouteBufferSource_Init (RouteBufferSource *o, int mtu) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void RouteBufferSource_Free (RouteBufferSource *o);
+
+/**
+ * Returns a pointer to the current packet.
+ * The pointed to memory area will have space for MTU bytes.
+ * The pointer is only valid until {@link RouteBufferSource_Route} succeeds.
+ * 
+ * @param o the object
+ * @return pointer to the current packet
+ */
+uint8_t * RouteBufferSource_Pointer (RouteBufferSource *o);
+
+/**
+ * Routes the current packet to a given buffer.
+ * On success, this invalidates the pointer previously returned from
+ * {@link RouteBufferSource_Pointer}.
+ * 
+ * @param o the object
+ * @param len length of the packet. Must be >=0 and <=MTU.
+ * @param b buffer to route to. Its MTU must equal this object's MTU.
+ * @param copy_offset Offset from the beginning for copying. Must be >=0 and
+ *                    <=mtu.
+ * @param copy_len Number of bytes to copy from the old current packet to the new one.
+ *                 Must be >=0 and <= mtu - copy_offset.
+ * @return 1 on success, 0 on failure
+ */
+int RouteBufferSource_Route (RouteBufferSource *o, int len, RouteBuffer *b, int copy_offset, int copy_len);
+
+#endif
diff --git a/external/badvpn_dns/flow/SinglePacketBuffer.c b/external/badvpn_dns/flow/SinglePacketBuffer.c
new file mode 100644
index 0000000..bbc72ae
--- /dev/null
+++ b/external/badvpn_dns/flow/SinglePacketBuffer.c
@@ -0,0 +1,87 @@
+/**
+ * @file SinglePacketBuffer.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/SinglePacketBuffer.h>
+
+static void input_handler_done (SinglePacketBuffer *o, int in_len)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    PacketPassInterface_Sender_Send(o->output, o->buf, in_len);
+}
+
+static void output_handler_done (SinglePacketBuffer *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    PacketRecvInterface_Receiver_Recv(o->input, o->buf);
+}
+
+int SinglePacketBuffer_Init (SinglePacketBuffer *o, PacketRecvInterface *input, PacketPassInterface *output, BPendingGroup *pg) 
+{
+    ASSERT(PacketPassInterface_GetMTU(output) >= PacketRecvInterface_GetMTU(input))
+    
+    // init arguments
+    o->input = input;
+    o->output = output;
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o);
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // init buffer
+    if (!(o->buf = (uint8_t *)BAlloc(PacketRecvInterface_GetMTU(o->input)))) {
+        goto fail1;
+    }
+    
+    // schedule receive
+    PacketRecvInterface_Receiver_Recv(o->input, o->buf);
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail1:
+    return 0;
+}
+
+void SinglePacketBuffer_Free (SinglePacketBuffer *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free buffer
+    BFree(o->buf);
+}
diff --git a/external/badvpn_dns/flow/SinglePacketBuffer.h b/external/badvpn_dns/flow/SinglePacketBuffer.h
new file mode 100644
index 0000000..87314a5
--- /dev/null
+++ b/external/badvpn_dns/flow/SinglePacketBuffer.h
@@ -0,0 +1,75 @@
+/**
+ * @file SinglePacketBuffer.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * than can store only a single packet.
+ */
+
+#ifndef BADVPN_FLOW_SINGLEPACKETBUFFER_H
+#define BADVPN_FLOW_SINGLEPACKETBUFFER_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * Packet buffer with {@link PacketRecvInterface} input and {@link PacketPassInterface} output
+ * than can store only a single packet.
+ */
+typedef struct {
+    DebugObject d_obj;
+    PacketRecvInterface *input;
+    PacketPassInterface *output;
+    uint8_t *buf;
+} SinglePacketBuffer;
+
+/**
+ * Initializes the object.
+ * Output MTU must be >= input MTU.
+ *
+ * @param o the object
+ * @param input input interface
+ * @param output output interface
+ * @param pg pending group
+ * @return 1 on success, 0 on failure
+ */
+int SinglePacketBuffer_Init (SinglePacketBuffer *o, PacketRecvInterface *input, PacketPassInterface *output, BPendingGroup *pg) WARN_UNUSED;
+
+/**
+ * Frees the object
+ *
+ * @param o the object
+ */
+void SinglePacketBuffer_Free (SinglePacketBuffer *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/SinglePacketSender.c b/external/badvpn_dns/flow/SinglePacketSender.c
new file mode 100644
index 0000000..b1f3ec7
--- /dev/null
+++ b/external/badvpn_dns/flow/SinglePacketSender.c
@@ -0,0 +1,72 @@
+/**
+ * @file SinglePacketSender.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/SinglePacketSender.h>
+
+static void call_handler (SinglePacketSender *o)
+{
+    DEBUGERROR(&o->d_err, o->handler(o->user));
+}
+
+static void output_handler_done (SinglePacketSender *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // notify user
+    call_handler(o);
+    return;
+}
+
+void SinglePacketSender_Init (SinglePacketSender *o, uint8_t *packet, int packet_len, PacketPassInterface *output, SinglePacketSender_handler handler, void *user, BPendingGroup *pg)
+{
+    ASSERT(packet_len >= 0)
+    ASSERT(packet_len <= PacketPassInterface_GetMTU(output))
+    
+    // init arguments
+    o->output = output;
+    o->handler = handler;
+    o->user = user;
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // schedule send
+    PacketPassInterface_Sender_Send(o->output, packet, packet_len);
+    
+    DebugObject_Init(&o->d_obj);
+    DebugError_Init(&o->d_err, pg);
+}
+
+void SinglePacketSender_Free (SinglePacketSender *o)
+{
+    DebugError_Free(&o->d_err);
+    DebugObject_Free(&o->d_obj);
+}
diff --git a/external/badvpn_dns/flow/SinglePacketSender.h b/external/badvpn_dns/flow/SinglePacketSender.h
new file mode 100644
index 0000000..c9289d8
--- /dev/null
+++ b/external/badvpn_dns/flow/SinglePacketSender.h
@@ -0,0 +1,82 @@
+/**
+ * @file SinglePacketSender.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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} source which sends a single packet.
+ */
+
+#ifndef BADVPN_FLOW_SINGLEPACKETSENDER_H
+#define BADVPN_FLOW_SINGLEPACKETSENDER_H
+
+#include <stdint.h>
+
+#include <misc/debugerror.h>
+#include <base/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * Handler function called after the packet is sent.
+ * The object must be freed from within this handler.
+ * 
+ * @param user as in {@link SinglePacketSender_Init}.
+ */
+typedef void (*SinglePacketSender_handler) (void *user);
+
+/**
+ * A {@link PacketPassInterface} source which sends a single packet.
+ */
+typedef struct {
+    PacketPassInterface *output;
+    SinglePacketSender_handler handler;
+    void *user;
+    DebugObject d_obj;
+    DebugError d_err;
+} SinglePacketSender;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param packet packet to be sent. Must be available as long as the object exists.
+ * @param packet_len length of the packet. Must be >=0 and <=(MTU of output).
+ * @param output output interface
+ * @param handler handler to call when the packet is sent
+ * @param user value to pass to handler
+ * @param pg pending group
+ */
+void SinglePacketSender_Init (SinglePacketSender *o, uint8_t *packet, int packet_len, PacketPassInterface *output, SinglePacketSender_handler handler, void *user, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void SinglePacketSender_Free (SinglePacketSender *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/SingleStreamReceiver.c b/external/badvpn_dns/flow/SingleStreamReceiver.c
new file mode 100644
index 0000000..b463290
--- /dev/null
+++ b/external/badvpn_dns/flow/SingleStreamReceiver.c
@@ -0,0 +1,82 @@
+/**
+ * @file SingleStreamReceiver.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "SingleStreamReceiver.h"
+
+static void input_handler_done (SingleStreamReceiver *o, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->packet_len - o->pos)
+    
+    // update position
+    o->pos += data_len;
+    
+    // if everything was received, notify user
+    if (o->pos == o->packet_len) {
+        DEBUGERROR(&o->d_err, o->handler(o->user));
+        return;
+    }
+    
+    // receive more
+    StreamRecvInterface_Receiver_Recv(o->input, o->packet + o->pos, o->packet_len - o->pos);
+}
+
+void SingleStreamReceiver_Init (SingleStreamReceiver *o, uint8_t *packet, int packet_len, StreamRecvInterface *input, BPendingGroup *pg, void *user, SingleStreamReceiver_handler handler)
+{
+    ASSERT(packet_len > 0)
+    ASSERT(handler)
+    
+    // init arguments
+    o->packet = packet;
+    o->packet_len = packet_len;
+    o->input = input;
+    o->user = user;
+    o->handler = handler;
+    
+    // set position zero
+    o->pos = 0;
+    
+    // init output
+    StreamRecvInterface_Receiver_Init(o->input, (StreamRecvInterface_handler_done)input_handler_done, o);
+    
+    // start receiving
+    StreamRecvInterface_Receiver_Recv(o->input, o->packet + o->pos, o->packet_len - o->pos);
+    
+    DebugError_Init(&o->d_err, pg);
+    DebugObject_Init(&o->d_obj);
+}
+
+void SingleStreamReceiver_Free (SingleStreamReceiver *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+}
diff --git a/external/badvpn_dns/flow/SingleStreamReceiver.h b/external/badvpn_dns/flow/SingleStreamReceiver.h
new file mode 100644
index 0000000..c9c6219
--- /dev/null
+++ b/external/badvpn_dns/flow/SingleStreamReceiver.h
@@ -0,0 +1,53 @@
+/**
+ * @file SingleStreamReceiver.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_SINGLESTREAMRECEIVER_H
+#define BADVPN_SINGLESTREAMRECEIVER_H
+
+#include <misc/debugerror.h>
+#include <base/DebugObject.h>
+#include <flow/StreamRecvInterface.h>
+
+typedef void (*SingleStreamReceiver_handler) (void *user);
+
+typedef struct {
+    uint8_t *packet;
+    int packet_len;
+    StreamRecvInterface *input;
+    void *user;
+    SingleStreamReceiver_handler handler;
+    int pos;
+    DebugError d_err;
+    DebugObject d_obj;
+} SingleStreamReceiver;
+
+void SingleStreamReceiver_Init (SingleStreamReceiver *o, uint8_t *packet, int packet_len, StreamRecvInterface *input, BPendingGroup *pg, void *user, SingleStreamReceiver_handler handler);
+void SingleStreamReceiver_Free (SingleStreamReceiver *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/SingleStreamSender.c b/external/badvpn_dns/flow/SingleStreamSender.c
new file mode 100644
index 0000000..eb33306
--- /dev/null
+++ b/external/badvpn_dns/flow/SingleStreamSender.c
@@ -0,0 +1,82 @@
+/**
+ * @file SingleStreamSender.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "SingleStreamSender.h"
+
+static void output_handler_done (SingleStreamSender *o, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->packet_len - o->pos)
+    
+    // update position
+    o->pos += data_len;
+    
+    // if everything was sent, notify user
+    if (o->pos == o->packet_len) {
+        DEBUGERROR(&o->d_err, o->handler(o->user));
+        return;
+    }
+    
+    // send more
+    StreamPassInterface_Sender_Send(o->output, o->packet + o->pos, o->packet_len - o->pos);
+}
+
+void SingleStreamSender_Init (SingleStreamSender *o, uint8_t *packet, int packet_len, StreamPassInterface *output, BPendingGroup *pg, void *user, SingleStreamSender_handler handler)
+{
+    ASSERT(packet_len > 0)
+    ASSERT(handler)
+    
+    // init arguments
+    o->packet = packet;
+    o->packet_len = packet_len;
+    o->output = output;
+    o->user = user;
+    o->handler = handler;
+    
+    // set position zero
+    o->pos = 0;
+    
+    // init output
+    StreamPassInterface_Sender_Init(o->output, (StreamPassInterface_handler_done)output_handler_done, o);
+    
+    // start sending
+    StreamPassInterface_Sender_Send(o->output, o->packet + o->pos, o->packet_len - o->pos);
+    
+    DebugError_Init(&o->d_err, pg);
+    DebugObject_Init(&o->d_obj);
+}
+
+void SingleStreamSender_Free (SingleStreamSender *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+}
diff --git a/external/badvpn_dns/flow/SingleStreamSender.h b/external/badvpn_dns/flow/SingleStreamSender.h
new file mode 100644
index 0000000..180a9bf
--- /dev/null
+++ b/external/badvpn_dns/flow/SingleStreamSender.h
@@ -0,0 +1,53 @@
+/**
+ * @file SingleStreamSender.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_SINGLESTREAMSENDER_H
+#define BADVPN_SINGLESTREAMSENDER_H
+
+#include <misc/debugerror.h>
+#include <base/DebugObject.h>
+#include <flow/StreamPassInterface.h>
+
+typedef void (*SingleStreamSender_handler) (void *user);
+
+typedef struct {
+    uint8_t *packet;
+    int packet_len;
+    StreamPassInterface *output;
+    void *user;
+    SingleStreamSender_handler handler;
+    int pos;
+    DebugError d_err;
+    DebugObject d_obj;
+} SingleStreamSender;
+
+void SingleStreamSender_Init (SingleStreamSender *o, uint8_t *packet, int packet_len, StreamPassInterface *output, BPendingGroup *pg, void *user, SingleStreamSender_handler handler);
+void SingleStreamSender_Free (SingleStreamSender *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/StreamPacketSender.c b/external/badvpn_dns/flow/StreamPacketSender.c
new file mode 100644
index 0000000..1e0a949
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamPacketSender.c
@@ -0,0 +1,90 @@
+/**
+ * @file StreamPacketSender.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "StreamPacketSender.h"
+
+static void input_handler_send (StreamPacketSender *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(data_len > 0)
+    
+    // limit length to MTU and remember
+    if (data_len > o->output_mtu) {
+        o->sending_len = o->output_mtu;
+    } else {
+        o->sending_len = data_len;
+    }
+    
+    // send
+    PacketPassInterface_Sender_Send(o->output, data, o->sending_len);
+}
+
+static void output_handler_done (StreamPacketSender *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // done
+    StreamPassInterface_Done(&o->input, o->sending_len);
+}
+
+void StreamPacketSender_Init (StreamPacketSender *o, PacketPassInterface *output, BPendingGroup *pg)
+{
+    ASSERT(PacketPassInterface_GetMTU(output) > 0)
+    
+    // init arguments
+    o->output = output;
+    
+    // remember output MTU
+    o->output_mtu = PacketPassInterface_GetMTU(output);
+    
+    // init input
+    StreamPassInterface_Init(&o->input, (StreamPassInterface_handler_send)input_handler_send, o, pg);
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void StreamPacketSender_Free (StreamPacketSender *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free input
+    StreamPassInterface_Free(&o->input);
+}
+
+StreamPassInterface * StreamPacketSender_GetInput (StreamPacketSender *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
diff --git a/external/badvpn_dns/flow/StreamPacketSender.h b/external/badvpn_dns/flow/StreamPacketSender.h
new file mode 100644
index 0000000..19bda5e
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamPacketSender.h
@@ -0,0 +1,77 @@
+/**
+ * @file StreamPacketSender.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_STREAMPACKETSENDER_H
+#define BADVPN_STREAMPACKETSENDER_H
+
+#include <base/DebugObject.h>
+#include <flow/StreamPassInterface.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * Object which breaks an input stream into output packets. The resulting
+ * packets will have positive length, and, when concatenated, will form the
+ * original stream.
+ * 
+ * Input is with {@link StreamPassInterface}.
+ * Output is with {@link PacketPassInterface}.
+ */
+typedef struct {
+    PacketPassInterface *output;
+    int output_mtu;
+    StreamPassInterface input;
+    int sending_len;
+    DebugObject d_obj;
+} StreamPacketSender;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param output output interface. Its MTU must be >0.
+ * @param pg pending group we live in
+ */
+void StreamPacketSender_Init (StreamPacketSender *o, PacketPassInterface *output, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void StreamPacketSender_Free (StreamPacketSender *o);
+
+/**
+ * Returns the input interface.
+ * 
+ * @param o the object
+ * @return input interface
+ */
+StreamPassInterface * StreamPacketSender_GetInput (StreamPacketSender *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/StreamPassConnector.c b/external/badvpn_dns/flow/StreamPassConnector.c
new file mode 100644
index 0000000..d3075c7
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamPassConnector.c
@@ -0,0 +1,120 @@
+/**
+ * @file StreamPassConnector.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/StreamPassConnector.h>
+
+static void input_handler_send (StreamPassConnector *o, uint8_t *data, int data_len)
+{
+    ASSERT(data_len > 0)
+    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
+        StreamPassInterface_Sender_Send(o->output, o->in, o->in_len);
+    }
+}
+
+static void output_handler_done (StreamPassConnector *o, int data_len)
+{
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->in_len)
+    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
+    StreamPassInterface_Done(&o->input, data_len);
+}
+
+void StreamPassConnector_Init (StreamPassConnector *o, BPendingGroup *pg)
+{
+    // init output
+    StreamPassInterface_Init(&o->input, (StreamPassInterface_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 StreamPassConnector_Free (StreamPassConnector *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free output
+    StreamPassInterface_Free(&o->input);
+}
+
+StreamPassInterface * StreamPassConnector_GetInput (StreamPassConnector *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
+
+void StreamPassConnector_ConnectOutput (StreamPassConnector *o, StreamPassInterface *output)
+{
+    ASSERT(!o->output)
+    DebugObject_Access(&o->d_obj);
+    
+    // set output
+    o->output = output;
+    
+    // init output
+    StreamPassInterface_Sender_Init(o->output, (StreamPassInterface_handler_done)output_handler_done, o);
+    
+    // if we have an input packet, schedule send
+    if (o->in_len > 0) {
+        StreamPassInterface_Sender_Send(o->output, o->in, o->in_len);
+    }
+}
+
+void StreamPassConnector_DisconnectOutput (StreamPassConnector *o)
+{
+    ASSERT(o->output)
+    DebugObject_Access(&o->d_obj);
+    
+    // set no output
+    o->output = NULL;
+}
diff --git a/external/badvpn_dns/flow/StreamPassConnector.h b/external/badvpn_dns/flow/StreamPassConnector.h
new file mode 100644
index 0000000..aa79195
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamPassConnector.h
@@ -0,0 +1,98 @@
+/**
+ * @file StreamPassConnector.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 StreamPassInterface} layer which allows the output to be
+ * connected and disconnected on the fly.
+ */
+
+#ifndef BADVPN_FLOW_STREAMPASSCONNECTOR_H
+#define BADVPN_FLOW_STREAMPASSCONNECTOR_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/StreamPassInterface.h>
+
+/**
+ * A {@link StreamPassInterface} layer which allows the output to be
+ * connected and disconnected on the fly.
+ */
+typedef struct {
+    StreamPassInterface input;
+    int in_len;
+    uint8_t *in;
+    StreamPassInterface *output;
+    DebugObject d_obj;
+} StreamPassConnector;
+
+/**
+ * Initializes the object.
+ * The object is initialized in not connected state.
+ *
+ * @param o the object
+ * @param pg pending group
+ */
+void StreamPassConnector_Init (StreamPassConnector *o, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void StreamPassConnector_Free (StreamPassConnector *o);
+
+/**
+ * Returns the input interface.
+ *
+ * @param o the object
+ * @return input interface
+ */
+StreamPassInterface * StreamPassConnector_GetInput (StreamPassConnector *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
+ */
+void StreamPassConnector_ConnectOutput (StreamPassConnector *o, StreamPassInterface *output);
+
+/**
+ * Disconnects output.
+ * The object must be in connected state.
+ * The object enters not connected state.
+ *
+ * @param o the object
+ */
+void StreamPassConnector_DisconnectOutput (StreamPassConnector *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/StreamPassInterface.c b/external/badvpn_dns/flow/StreamPassInterface.c
new file mode 100644
index 0000000..f0dc547
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamPassInterface.c
@@ -0,0 +1,56 @@
+/**
+ * @file StreamPassInterface.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <flow/StreamPassInterface.h>
+
+void _StreamPassInterface_job_operation (StreamPassInterface *i)
+{
+    ASSERT(i->state == SPI_STATE_OPERATION_PENDING)
+    DebugObject_Access(&i->d_obj);
+    
+    // set state
+    i->state = SPI_STATE_BUSY;
+    
+    // call handler
+    i->handler_operation(i->user_provider, i->job_operation_data, i->job_operation_len);
+    return;
+}
+
+void _StreamPassInterface_job_done (StreamPassInterface *i)
+{
+    ASSERT(i->state == SPI_STATE_DONE_PENDING)
+    DebugObject_Access(&i->d_obj);
+    
+    // set state
+    i->state = SPI_STATE_NONE;
+    
+    // call handler
+    i->handler_done(i->user_user, i->job_done_len);
+    return;
+}
diff --git a/external/badvpn_dns/flow/StreamPassInterface.h b/external/badvpn_dns/flow/StreamPassInterface.h
new file mode 100644
index 0000000..1fe650e
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamPassInterface.h
@@ -0,0 +1,165 @@
+/**
+ * @file StreamPassInterface.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Interface allowing a stream sender to pass stream data to a stream receiver.
+ * 
+ * Note that this interface behaves exactly the same and has the same code as
+ * {@link StreamRecvInterface} if names and its external semantics are disregarded.
+ * If you modify this file, you should probably modify {@link StreamRecvInterface}
+ * too.
+ */
+
+#ifndef BADVPN_FLOW_STREAMPASSINTERFACE_H
+#define BADVPN_FLOW_STREAMPASSINTERFACE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+
+#define SPI_STATE_NONE 1
+#define SPI_STATE_OPERATION_PENDING 2
+#define SPI_STATE_BUSY 3
+#define SPI_STATE_DONE_PENDING 4
+
+typedef void (*StreamPassInterface_handler_send) (void *user, uint8_t *data, int data_len);
+
+typedef void (*StreamPassInterface_handler_done) (void *user, int data_len);
+
+typedef struct {
+    // provider data
+    StreamPassInterface_handler_send handler_operation;
+    void *user_provider;
+    
+    // user data
+    StreamPassInterface_handler_done handler_done;
+    void *user_user;
+    
+    // operation job
+    BPending job_operation;
+    uint8_t *job_operation_data;
+    int job_operation_len;
+    
+    // done job
+    BPending job_done;
+    int job_done_len;
+    
+    // state
+    int state;
+    
+    DebugObject d_obj;
+} StreamPassInterface;
+
+static void StreamPassInterface_Init (StreamPassInterface *i, StreamPassInterface_handler_send handler_operation, void *user, BPendingGroup *pg);
+
+static void StreamPassInterface_Free (StreamPassInterface *i);
+
+static void StreamPassInterface_Done (StreamPassInterface *i, int data_len);
+
+static void StreamPassInterface_Sender_Init (StreamPassInterface *i, StreamPassInterface_handler_done handler_done, void *user);
+
+static void StreamPassInterface_Sender_Send (StreamPassInterface *i, uint8_t *data, int data_len);
+
+void _StreamPassInterface_job_operation (StreamPassInterface *i);
+void _StreamPassInterface_job_done (StreamPassInterface *i);
+
+void StreamPassInterface_Init (StreamPassInterface *i, StreamPassInterface_handler_send handler_operation, void *user, BPendingGroup *pg)
+{
+    // init arguments
+    i->handler_operation = handler_operation;
+    i->user_provider = user;
+    
+    // set no user
+    i->handler_done = NULL;
+    
+    // init jobs
+    BPending_Init(&i->job_operation, pg, (BPending_handler)_StreamPassInterface_job_operation, i);
+    BPending_Init(&i->job_done, pg, (BPending_handler)_StreamPassInterface_job_done, i);
+    
+    // set state
+    i->state = SPI_STATE_NONE;
+    
+    DebugObject_Init(&i->d_obj);
+}
+
+void StreamPassInterface_Free (StreamPassInterface *i)
+{
+    DebugObject_Free(&i->d_obj);
+    
+    // free jobs
+    BPending_Free(&i->job_done);
+    BPending_Free(&i->job_operation);
+}
+
+void StreamPassInterface_Done (StreamPassInterface *i, int data_len)
+{
+    ASSERT(i->state == SPI_STATE_BUSY)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= i->job_operation_len)
+    DebugObject_Access(&i->d_obj);
+    
+    // schedule done
+    i->job_done_len = data_len;
+    BPending_Set(&i->job_done);
+    
+    // set state
+    i->state = SPI_STATE_DONE_PENDING;
+}
+
+void StreamPassInterface_Sender_Init (StreamPassInterface *i, StreamPassInterface_handler_done handler_done, void *user)
+{
+    ASSERT(handler_done)
+    ASSERT(!i->handler_done)
+    DebugObject_Access(&i->d_obj);
+    
+    i->handler_done = handler_done;
+    i->user_user = user;
+}
+
+void StreamPassInterface_Sender_Send (StreamPassInterface *i, uint8_t *data, int data_len)
+{
+    ASSERT(data_len > 0)
+    ASSERT(data)
+    ASSERT(i->state == SPI_STATE_NONE)
+    ASSERT(i->handler_done)
+    DebugObject_Access(&i->d_obj);
+    
+    // schedule operation
+    i->job_operation_data = data;
+    i->job_operation_len = data_len;
+    BPending_Set(&i->job_operation);
+    
+    // set state
+    i->state = SPI_STATE_OPERATION_PENDING;
+}
+
+#endif
diff --git a/external/badvpn_dns/flow/StreamRecvConnector.c b/external/badvpn_dns/flow/StreamRecvConnector.c
new file mode 100644
index 0000000..beb6a88
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamRecvConnector.c
@@ -0,0 +1,120 @@
+/**
+ * @file StreamRecvConnector.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/StreamRecvConnector.h>
+
+static void output_handler_recv (StreamRecvConnector *o, uint8_t *data, int data_avail)
+{
+    ASSERT(data_avail > 0)
+    ASSERT(o->out_avail == -1)
+    DebugObject_Access(&o->d_obj);
+    
+    // remember output packet
+    o->out_avail = data_avail;
+    o->out = data;
+    
+    if (o->input) {
+        // schedule receive
+        StreamRecvInterface_Receiver_Recv(o->input, o->out, o->out_avail);
+    }
+}
+
+static void input_handler_done (StreamRecvConnector *o, int data_len)
+{
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->out_avail)
+    ASSERT(o->out_avail > 0)
+    ASSERT(o->input)
+    DebugObject_Access(&o->d_obj);
+    
+    // have no output packet
+    o->out_avail = -1;
+    
+    // allow output to receive more packets
+    StreamRecvInterface_Done(&o->output, data_len);
+}
+
+void StreamRecvConnector_Init (StreamRecvConnector *o, BPendingGroup *pg)
+{
+    // init output
+    StreamRecvInterface_Init(&o->output, (StreamRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    // have no output packet
+    o->out_avail = -1;
+    
+    // have no input
+    o->input = NULL;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void StreamRecvConnector_Free (StreamRecvConnector *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free output
+    StreamRecvInterface_Free(&o->output);
+}
+
+StreamRecvInterface * StreamRecvConnector_GetOutput (StreamRecvConnector *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
+
+void StreamRecvConnector_ConnectInput (StreamRecvConnector *o, StreamRecvInterface *input)
+{
+    ASSERT(!o->input)
+    DebugObject_Access(&o->d_obj);
+    
+    // set input
+    o->input = input;
+    
+    // init input
+    StreamRecvInterface_Receiver_Init(o->input, (StreamRecvInterface_handler_done)input_handler_done, o);
+    
+    // if we have an output packet, schedule receive
+    if (o->out_avail > 0) {
+        StreamRecvInterface_Receiver_Recv(o->input, o->out, o->out_avail);
+    }
+}
+
+void StreamRecvConnector_DisconnectInput (StreamRecvConnector *o)
+{
+    ASSERT(o->input)
+    DebugObject_Access(&o->d_obj);
+    
+    // set no input
+    o->input = NULL;
+}
diff --git a/external/badvpn_dns/flow/StreamRecvConnector.h b/external/badvpn_dns/flow/StreamRecvConnector.h
new file mode 100644
index 0000000..ed111ce
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamRecvConnector.h
@@ -0,0 +1,98 @@
+/**
+ * @file StreamRecvConnector.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 StreamRecvInterface} layer which allows the input to be
+ * connected and disconnected on the fly.
+ */
+
+#ifndef BADVPN_FLOW_STREAMRECVCONNECTOR_H
+#define BADVPN_FLOW_STREAMRECVCONNECTOR_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/StreamRecvInterface.h>
+
+/**
+ * A {@link StreamRecvInterface} layer which allows the input to be
+ * connected and disconnected on the fly.
+ */
+typedef struct {
+    StreamRecvInterface output;
+    int out_avail;
+    uint8_t *out;
+    StreamRecvInterface *input;
+    DebugObject d_obj;
+} StreamRecvConnector;
+
+/**
+ * Initializes the object.
+ * The object is initialized in not connected state.
+ *
+ * @param o the object
+ * @param pg pending group
+ */
+void StreamRecvConnector_Init (StreamRecvConnector *o, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void StreamRecvConnector_Free (StreamRecvConnector *o);
+
+/**
+ * Returns the output interface.
+ *
+ * @param o the object
+ * @return output interface
+ */
+StreamRecvInterface * StreamRecvConnector_GetOutput (StreamRecvConnector *o);
+
+/**
+ * Connects input.
+ * The object must be in not connected state.
+ * The object enters connected state.
+ *
+ * @param o the object
+ * @param output input to connect
+ */
+void StreamRecvConnector_ConnectInput (StreamRecvConnector *o, StreamRecvInterface *input);
+
+/**
+ * Disconnects input.
+ * The object must be in connected state.
+ * The object enters not connected state.
+ *
+ * @param o the object
+ */
+void StreamRecvConnector_DisconnectInput (StreamRecvConnector *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/StreamRecvInterface.c b/external/badvpn_dns/flow/StreamRecvInterface.c
new file mode 100644
index 0000000..697e1ae
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamRecvInterface.c
@@ -0,0 +1,56 @@
+/**
+ * @file StreamRecvInterface.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <flow/StreamRecvInterface.h>
+
+void _StreamRecvInterface_job_operation (StreamRecvInterface *i)
+{
+    ASSERT(i->state == SRI_STATE_OPERATION_PENDING)
+    DebugObject_Access(&i->d_obj);
+    
+    // set state
+    i->state = SRI_STATE_BUSY;
+    
+    // call handler
+    i->handler_operation(i->user_provider, i->job_operation_data, i->job_operation_len);
+    return;
+}
+
+void _StreamRecvInterface_job_done (StreamRecvInterface *i)
+{
+    ASSERT(i->state == SRI_STATE_DONE_PENDING)
+    DebugObject_Access(&i->d_obj);
+    
+    // set state
+    i->state = SRI_STATE_NONE;
+    
+    // call handler
+    i->handler_done(i->user_user, i->job_done_len);
+    return;
+}
diff --git a/external/badvpn_dns/flow/StreamRecvInterface.h b/external/badvpn_dns/flow/StreamRecvInterface.h
new file mode 100644
index 0000000..cd4a181
--- /dev/null
+++ b/external/badvpn_dns/flow/StreamRecvInterface.h
@@ -0,0 +1,165 @@
+/**
+ * @file StreamRecvInterface.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Interface allowing a stream receiver to receive stream data from a stream sender.
+ * 
+ * Note that this interface behaves exactly the same and has the same code as
+ * {@link StreamPassInterface} if names and its external semantics are disregarded.
+ * If you modify this file, you should probably modify {@link StreamPassInterface}
+ * too.
+ */
+
+#ifndef BADVPN_FLOW_STREAMRECVINTERFACE_H
+#define BADVPN_FLOW_STREAMRECVINTERFACE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+
+#define SRI_STATE_NONE 1
+#define SRI_STATE_OPERATION_PENDING 2
+#define SRI_STATE_BUSY 3
+#define SRI_STATE_DONE_PENDING 4
+
+typedef void (*StreamRecvInterface_handler_recv) (void *user, uint8_t *data, int data_len);
+
+typedef void (*StreamRecvInterface_handler_done) (void *user, int data_len);
+
+typedef struct {
+    // provider data
+    StreamRecvInterface_handler_recv handler_operation;
+    void *user_provider;
+    
+    // user data
+    StreamRecvInterface_handler_done handler_done;
+    void *user_user;
+    
+    // operation job
+    BPending job_operation;
+    uint8_t *job_operation_data;
+    int job_operation_len;
+    
+    // done job
+    BPending job_done;
+    int job_done_len;
+    
+    // state
+    int state;
+    
+    DebugObject d_obj;
+} StreamRecvInterface;
+
+static void StreamRecvInterface_Init (StreamRecvInterface *i, StreamRecvInterface_handler_recv handler_operation, void *user, BPendingGroup *pg);
+
+static void StreamRecvInterface_Free (StreamRecvInterface *i);
+
+static void StreamRecvInterface_Done (StreamRecvInterface *i, int data_len);
+
+static void StreamRecvInterface_Receiver_Init (StreamRecvInterface *i, StreamRecvInterface_handler_done handler_done, void *user);
+
+static void StreamRecvInterface_Receiver_Recv (StreamRecvInterface *i, uint8_t *data, int data_len);
+
+void _StreamRecvInterface_job_operation (StreamRecvInterface *i);
+void _StreamRecvInterface_job_done (StreamRecvInterface *i);
+
+void StreamRecvInterface_Init (StreamRecvInterface *i, StreamRecvInterface_handler_recv handler_operation, void *user, BPendingGroup *pg)
+{
+    // init arguments
+    i->handler_operation = handler_operation;
+    i->user_provider = user;
+    
+    // set no user
+    i->handler_done = NULL;
+    
+    // init jobs
+    BPending_Init(&i->job_operation, pg, (BPending_handler)_StreamRecvInterface_job_operation, i);
+    BPending_Init(&i->job_done, pg, (BPending_handler)_StreamRecvInterface_job_done, i);
+    
+    // set state
+    i->state = SRI_STATE_NONE;
+    
+    DebugObject_Init(&i->d_obj);
+}
+
+void StreamRecvInterface_Free (StreamRecvInterface *i)
+{
+    DebugObject_Free(&i->d_obj);
+    
+    // free jobs
+    BPending_Free(&i->job_done);
+    BPending_Free(&i->job_operation);
+}
+
+void StreamRecvInterface_Done (StreamRecvInterface *i, int data_len)
+{
+    ASSERT(i->state == SRI_STATE_BUSY)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= i->job_operation_len)
+    DebugObject_Access(&i->d_obj);
+    
+    // schedule done
+    i->job_done_len = data_len;
+    BPending_Set(&i->job_done);
+    
+    // set state
+    i->state = SRI_STATE_DONE_PENDING;
+}
+
+void StreamRecvInterface_Receiver_Init (StreamRecvInterface *i, StreamRecvInterface_handler_done handler_done, void *user)
+{
+    ASSERT(handler_done)
+    ASSERT(!i->handler_done)
+    DebugObject_Access(&i->d_obj);
+    
+    i->handler_done = handler_done;
+    i->user_user = user;
+}
+
+void StreamRecvInterface_Receiver_Recv (StreamRecvInterface *i, uint8_t *data, int data_len)
+{
+    ASSERT(data_len > 0)
+    ASSERT(data)
+    ASSERT(i->state == SRI_STATE_NONE)
+    ASSERT(i->handler_done)
+    DebugObject_Access(&i->d_obj);
+    
+    // schedule operation
+    i->job_operation_data = data;
+    i->job_operation_len = data_len;
+    BPending_Set(&i->job_operation);
+    
+    // set state
+    i->state = SRI_STATE_OPERATION_PENDING;
+}
+
+#endif
diff --git a/external/badvpn_dns/flowextra/CMakeLists.txt b/external/badvpn_dns/flowextra/CMakeLists.txt
new file mode 100644
index 0000000..6ceb1c0
--- /dev/null
+++ b/external/badvpn_dns/flowextra/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(FLOWEXTRA_SOURCES
+    PacketPassInactivityMonitor.c
+    KeepaliveIO.c
+)
+badvpn_add_library(flowextra "flow;system" "" "${FLOWEXTRA_SOURCES}")
diff --git a/external/badvpn_dns/flowextra/KeepaliveIO.c b/external/badvpn_dns/flowextra/KeepaliveIO.c
new file mode 100644
index 0000000..4e80366
--- /dev/null
+++ b/external/badvpn_dns/flowextra/KeepaliveIO.c
@@ -0,0 +1,112 @@
+/**
+ * @file KeepaliveIO.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "KeepaliveIO.h"
+
+static void keepalive_handler (KeepaliveIO *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    PacketRecvBlocker_AllowBlockedPacket(&o->ka_blocker);
+}
+
+int KeepaliveIO_Init (KeepaliveIO *o, BReactor *reactor, PacketPassInterface *output, PacketRecvInterface *keepalive_input, btime_t keepalive_interval_ms)
+{
+    ASSERT(PacketRecvInterface_GetMTU(keepalive_input) <= PacketPassInterface_GetMTU(output))
+    ASSERT(keepalive_interval_ms > 0)
+    
+    // set arguments
+    o->reactor = reactor;
+    
+    // init keep-alive sender
+    PacketPassInactivityMonitor_Init(&o->kasender, output, o->reactor, keepalive_interval_ms, (PacketPassInactivityMonitor_handler)keepalive_handler, o);
+    
+    // init queue
+    PacketPassPriorityQueue_Init(&o->queue, PacketPassInactivityMonitor_GetInput(&o->kasender), BReactor_PendingGroup(o->reactor), 0);
+    
+    // init keepalive flow
+    PacketPassPriorityQueueFlow_Init(&o->ka_qflow, &o->queue, -1);
+    
+    // init keepalive blocker
+    PacketRecvBlocker_Init(&o->ka_blocker, keepalive_input, BReactor_PendingGroup(reactor));
+    
+    // init keepalive buffer
+    if (!SinglePacketBuffer_Init(&o->ka_buffer, PacketRecvBlocker_GetOutput(&o->ka_blocker), PacketPassPriorityQueueFlow_GetInput(&o->ka_qflow), BReactor_PendingGroup(o->reactor))) {
+        goto fail1;
+    }
+    
+    // init user flow
+    PacketPassPriorityQueueFlow_Init(&o->user_qflow, &o->queue, 0);
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail1:
+    PacketRecvBlocker_Free(&o->ka_blocker);
+    PacketPassPriorityQueueFlow_Free(&o->ka_qflow);
+    PacketPassPriorityQueue_Free(&o->queue);
+    PacketPassInactivityMonitor_Free(&o->kasender);
+    return 0;
+}
+
+void KeepaliveIO_Free (KeepaliveIO *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // allow freeing queue flows
+    PacketPassPriorityQueue_PrepareFree(&o->queue);
+    
+    // free user flow
+    PacketPassPriorityQueueFlow_Free(&o->user_qflow);
+    
+    // free keepalive buffer
+    SinglePacketBuffer_Free(&o->ka_buffer);
+    
+    // free keepalive blocker
+    PacketRecvBlocker_Free(&o->ka_blocker);
+    
+    // free keepalive flow
+    PacketPassPriorityQueueFlow_Free(&o->ka_qflow);
+    
+    // free queue
+    PacketPassPriorityQueue_Free(&o->queue);
+    
+    // free keep-alive sender
+    PacketPassInactivityMonitor_Free(&o->kasender);
+}
+
+PacketPassInterface * KeepaliveIO_GetInput (KeepaliveIO *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return PacketPassPriorityQueueFlow_GetInput(&o->user_qflow);
+}
diff --git a/external/badvpn_dns/flowextra/KeepaliveIO.h b/external/badvpn_dns/flowextra/KeepaliveIO.h
new file mode 100644
index 0000000..d89321c
--- /dev/null
+++ b/external/badvpn_dns/flowextra/KeepaliveIO.h
@@ -0,0 +1,88 @@
+/**
+ * @file KeepaliveIO.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 for sending keep-alive packets.
+ */
+
+#ifndef BADVPN_KEEPALIVEIO
+#define BADVPN_KEEPALIVEIO
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketRecvInterface.h>
+#include <flow/PacketPassPriorityQueue.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketRecvBlocker.h>
+#include <flowextra/PacketPassInactivityMonitor.h>
+
+/**
+ * A {@link PacketPassInterface} layer for sending keep-alive packets.
+ */
+typedef struct {
+    BReactor *reactor;
+    PacketPassInactivityMonitor kasender;
+    PacketPassPriorityQueue queue;
+    PacketPassPriorityQueueFlow user_qflow;
+    PacketPassPriorityQueueFlow ka_qflow;
+    SinglePacketBuffer ka_buffer;
+    PacketRecvBlocker ka_blocker;
+    DebugObject d_obj;
+} KeepaliveIO;
+
+/**
+ * Initializes the object.
+ *
+ * @param o the object
+ * @param reactor reactor we live in
+ * @param output output interface
+ * @param keepalive_input keepalive input interface. Its MTU must be <= MTU of output.
+ * @param keepalive_interval_ms keepalive interval in milliseconds. Must be >0.
+ * @return 1 on success, 0 on failure
+ */
+int KeepaliveIO_Init (KeepaliveIO *o, BReactor *reactor, PacketPassInterface *output, PacketRecvInterface *keepalive_input, btime_t keepalive_interval_ms) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void KeepaliveIO_Free (KeepaliveIO *o);
+
+/**
+ * Returns the input interface.
+ *
+ * @param o the object
+ * @return input interface
+ */
+PacketPassInterface * KeepaliveIO_GetInput (KeepaliveIO *o);
+
+#endif
diff --git a/external/badvpn_dns/flowextra/PacketPassInactivityMonitor.c b/external/badvpn_dns/flowextra/PacketPassInactivityMonitor.c
new file mode 100644
index 0000000..6531fe0
--- /dev/null
+++ b/external/badvpn_dns/flowextra/PacketPassInactivityMonitor.c
@@ -0,0 +1,131 @@
+/**
+ * @file PacketPassInactivityMonitor.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "PacketPassInactivityMonitor.h"
+
+static void input_handler_send (PacketPassInactivityMonitor *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // schedule send
+    PacketPassInterface_Sender_Send(o->output, data, data_len);
+    
+    // stop timer
+    BReactor_RemoveTimer(o->reactor, &o->timer);
+}
+
+static void input_handler_requestcancel (PacketPassInactivityMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // request cancel
+    PacketPassInterface_Sender_RequestCancel(o->output);
+}
+
+static void output_handler_done (PacketPassInactivityMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // output no longer busy, restart timer
+    BReactor_SetTimer(o->reactor, &o->timer);
+    
+    // call done
+    PacketPassInterface_Done(&o->input);
+}
+
+static void timer_handler (PacketPassInactivityMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // restart timer
+    BReactor_SetTimer(o->reactor, &o->timer);
+    
+    // call handler
+    if (o->handler) {
+        o->handler(o->user);
+        return;
+    }
+}
+
+void PacketPassInactivityMonitor_Init (PacketPassInactivityMonitor *o, PacketPassInterface *output, BReactor *reactor, btime_t interval, PacketPassInactivityMonitor_handler handler, void *user)
+{
+    // init arguments
+    o->output = output;
+    o->reactor = reactor;
+    o->handler = handler;
+    o->user = user;
+    
+    // init input
+    PacketPassInterface_Init(&o->input, PacketPassInterface_GetMTU(o->output), (PacketPassInterface_handler_send)input_handler_send, o, BReactor_PendingGroup(o->reactor));
+    if (PacketPassInterface_HasCancel(o->output)) {
+        PacketPassInterface_EnableCancel(&o->input, (PacketPassInterface_handler_requestcancel)input_handler_requestcancel);
+    }
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // init timer
+    BTimer_Init(&o->timer, interval, (BTimer_handler)timer_handler, o);
+    BReactor_SetTimer(o->reactor, &o->timer);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void PacketPassInactivityMonitor_Free (PacketPassInactivityMonitor *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // free timer
+    BReactor_RemoveTimer(o->reactor, &o->timer);
+    
+    // free input
+    PacketPassInterface_Free(&o->input);
+}
+
+PacketPassInterface * PacketPassInactivityMonitor_GetInput (PacketPassInactivityMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
+
+void PacketPassInactivityMonitor_SetHandler (PacketPassInactivityMonitor *o, PacketPassInactivityMonitor_handler handler, void *user)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    o->handler = handler;
+    o->user = user;
+}
+
+void PacketPassInactivityMonitor_Force (PacketPassInactivityMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    BReactor_SetTimerAfter(o->reactor, &o->timer, 0);
+}
diff --git a/external/badvpn_dns/flowextra/PacketPassInactivityMonitor.h b/external/badvpn_dns/flowextra/PacketPassInactivityMonitor.h
new file mode 100644
index 0000000..5857618
--- /dev/null
+++ b/external/badvpn_dns/flowextra/PacketPassInactivityMonitor.h
@@ -0,0 +1,124 @@
+/**
+ * @file PacketPassInactivityMonitor.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 for detecting inactivity.
+ */
+
+#ifndef BADVPN_PACKETPASSINACTIVITYMONITOR_H
+#define BADVPN_PACKETPASSINACTIVITYMONITOR_H
+
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * Handler function invoked when inactivity is detected.
+ * It is guaranteed that the interfaces are in not sending state.
+ *
+ * @param user value given to {@link PacketPassInactivityMonitor_Init}
+ */
+typedef void (*PacketPassInactivityMonitor_handler) (void *user);
+
+/**
+ * A {@link PacketPassInterface} layer for detecting inactivity.
+ * It reports inactivity to a user provided handler function.
+ *
+ * The object behaves like that:
+ * ("timer set" means started with the given timeout whether if was running or not,
+ * "timer unset" means stopped if it was running)
+ *     - There is a timer.
+ *     - The timer is set when the object is initialized.
+ *     - When the input calls Send, the call is passed on to the output.
+ *       If the output accepted the packet, the timer is set. If the output
+ *       blocked the packet, the timer is unset.
+ *     - When the output calls Done, the timer is set, and the call is
+ *       passed on to the input.
+ *     - When the input calls Cancel, the timer is set, and the call is
+ *       passed on to the output.
+ *     - When the timer expires, the timer is set, ant the user's handler
+ *       function is invoked.
+ */
+typedef struct {
+    DebugObject d_obj;
+    PacketPassInterface *output;
+    BReactor *reactor;
+    PacketPassInactivityMonitor_handler handler;
+    void *user;
+    PacketPassInterface input;
+    BTimer timer;
+} PacketPassInactivityMonitor;
+
+/**
+ * Initializes the object.
+ * See {@link PacketPassInactivityMonitor} for details.
+ *
+ * @param o the object
+ * @param output output interface
+ * @param reactor reactor we live in
+ * @param interval timer value in milliseconds
+ * @param handler handler function for reporting inactivity, or NULL to disable
+ * @param user value passed to handler functions
+ */
+void PacketPassInactivityMonitor_Init (PacketPassInactivityMonitor *o, PacketPassInterface *output, BReactor *reactor, btime_t interval, PacketPassInactivityMonitor_handler handler, void *user);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void PacketPassInactivityMonitor_Free (PacketPassInactivityMonitor *o);
+
+/**
+ * Returns the input interface.
+ * The MTU of the interface will be the same as of the output interface.
+ * The interface supports cancel functionality if the output interface supports it.
+ *
+ * @param o the object
+ * @return input interface
+ */
+PacketPassInterface * PacketPassInactivityMonitor_GetInput (PacketPassInactivityMonitor *o);
+
+/**
+ * Sets or removes the inactivity handler.
+ *
+ * @param o the object
+ * @param handler handler function for reporting inactivity, or NULL to disable
+ * @param user value passed to handler functions
+ */
+void PacketPassInactivityMonitor_SetHandler (PacketPassInactivityMonitor *o, PacketPassInactivityMonitor_handler handler, void *user);
+
+/**
+ * Sets the timer to expire immediately in order to force an inactivity report.
+ * 
+ * @param o the object
+ */
+void PacketPassInactivityMonitor_Force (PacketPassInactivityMonitor *o);
+
+#endif
diff --git a/external/badvpn_dns/generate_files b/external/badvpn_dns/generate_files
new file mode 100755
index 0000000..f473369
--- /dev/null
+++ b/external/badvpn_dns/generate_files
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+set -e
+
+PHP_CMD=( php )
+FLEX_CMD=( flex )
+BISON_CMD=( bison )
+
+OUT_DIR="generated/"
+
+function bproto() {
+    local input="$1"
+    local name="$2"
+    "${PHP_CMD[@]}" bproto_generator/bproto.php --input-file "${input}" --output-dir "${OUT_DIR}" --name "bproto_${name}"
+}
+
+function do_flex() {
+    local input="$1"
+    local name="$2"
+    "${FLEX_CMD[@]}" -o "${OUT_DIR}/flex_${name}.c" --header-file="${OUT_DIR}/flex_${name}.h" "${input}"
+    "${PHP_CMD[@]}" fix_flex.php "${OUT_DIR}/flex_${name}.c"
+    "${PHP_CMD[@]}" fix_flex.php "${OUT_DIR}/flex_${name}.h"
+}
+
+function do_bison() {
+    local input="$1"
+    local name="$2"
+    "${BISON_CMD[@]}" -d -o "${OUT_DIR}/bison_${name}.c" "${input}"
+}
+
+function do_lemon() {
+    local input="$1"
+    local name=$(basename "${input}")
+    (
+        cd generated &&
+        rm -f "${name}" &&
+        cp ../"${input}" "${name}" &&
+        ../lemon/lemon "${name}"
+    )
+}
+
+mkdir -p generated
+
+bproto tests/bproto_test.bproto bproto_test
+bproto protocol/msgproto.bproto msgproto
+bproto protocol/addr.bproto addr
+do_flex predicate/BPredicate.l BPredicate
+do_bison predicate/BPredicate.y BPredicate
+"${PHP_CMD[@]}" blog_generator/blog.php --input-file blog_channels.txt --output-dir "${OUT_DIR}"
+do_lemon ncd/NCDConfigParser_parse.y
+do_lemon ncd/NCDValParser_parse.y
diff --git a/external/badvpn_dns/generated/NCDConfigParser_parse.c b/external/badvpn_dns/generated/NCDConfigParser_parse.c
new file mode 100644
index 0000000..3ebdceb
--- /dev/null
+++ b/external/badvpn_dns/generated/NCDConfigParser_parse.c
@@ -0,0 +1,1890 @@
+/* Driver template for the LEMON parser generator.
+** The author disclaims copyright to this source code.
+*/
+/* First off, code is included that follows the "include" declaration
+** in the input grammar file. */
+#include <stdio.h>
+#line 30 "NCDConfigParser_parse.y"
+
+
+#include <string.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <misc/concat_strings.h>
+#include <ncd/NCDAst.h>
+
+struct parser_out {
+    int out_of_memory;
+    int syntax_error;
+    int have_ast;
+    NCDProgram ast;
+};
+
+struct token {
+    char *str;
+    size_t len;
+};
+
+struct program {
+    int have;
+    NCDProgram v;
+};
+
+struct block {
+    int have;
+    NCDBlock v;
+};
+
+struct statement {
+    int have;
+    NCDStatement v;
+};
+
+struct ifblock {
+    int have;
+    NCDIfBlock v;
+};
+
+struct value {
+    int have;
+    NCDValue v;
+};
+
+static void free_token (struct token o) { free(o.str); }
+static void free_program (struct program o) { if (o.have) NCDProgram_Free(&o.v); }
+static void free_block (struct block o) { if (o.have) NCDBlock_Free(&o.v); }
+static void free_statement (struct statement o) { if (o.have) NCDStatement_Free(&o.v); }
+static void free_ifblock (struct ifblock o) { if (o.have) NCDIfBlock_Free(&o.v); }
+static void free_value (struct value o) { if (o.have) NCDValue_Free(&o.v); }
+
+#line 62 "NCDConfigParser_parse.c"
+/* Next is all token values, in a form suitable for use by makeheaders.
+** This section will be null unless lemon is run with the -m switch.
+*/
+/* 
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands. 
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+/* Make sure the INTERFACE macro is defined.
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+**    YYCODETYPE         is the data type used for storing terminal
+**                       and nonterminal numbers.  "unsigned char" is
+**                       used if there are fewer than 250 terminals
+**                       and nonterminals.  "int" is used otherwise.
+**    YYNOCODE           is a number of type YYCODETYPE which corresponds
+**                       to no legal terminal or nonterminal number.  This
+**                       number is used to fill in empty slots of the hash 
+**                       table.
+**    YYFALLBACK         If defined, this indicates that one or more tokens
+**                       have fall-back values which should be used if the
+**                       original value of the token will not parse.
+**    YYACTIONTYPE       is the data type used for storing terminal
+**                       and nonterminal numbers.  "unsigned char" is
+**                       used if there are fewer than 250 rules and
+**                       states combined.  "int" is used otherwise.
+**    ParseTOKENTYPE     is the data type used for minor tokens given 
+**                       directly to the parser from the tokenizer.
+**    YYMINORTYPE        is the data type used for all minor tokens.
+**                       This is typically a union of many types, one of
+**                       which is ParseTOKENTYPE.  The entry in the union
+**                       for base tokens is called "yy0".
+**    YYSTACKDEPTH       is the maximum depth of the parser's stack.  If
+**                       zero the stack is dynamically sized using realloc()
+**    ParseARG_SDECL     A static variable declaration for the %extra_argument
+**    ParseARG_PDECL     A parameter declaration for the %extra_argument
+**    ParseARG_STORE     Code to store %extra_argument into yypParser
+**    ParseARG_FETCH     Code to extract %extra_argument from yypParser
+**    YYNSTATE           the combined number of states.
+**    YYNRULE            the number of rules in the grammar
+**    YYERRORSYMBOL      is the code number of the error symbol.  If not
+**                       defined, then do no error processing.
+*/
+#define YYCODETYPE unsigned char
+#define YYNOCODE 41
+#define YYACTIONTYPE unsigned char
+#define ParseTOKENTYPE  struct token 
+typedef union {
+  int yyinit;
+  ParseTOKENTYPE yy0;
+  int yy12;
+  char * yy33;
+  struct block yy37;
+  struct value yy51;
+  struct program yy54;
+  struct ifblock yy76;
+  struct statement yy79;
+} YYMINORTYPE;
+#ifndef YYSTACKDEPTH
+#define YYSTACKDEPTH 0
+#endif
+#define ParseARG_SDECL  struct parser_out *parser_out ;
+#define ParseARG_PDECL , struct parser_out *parser_out 
+#define ParseARG_FETCH  struct parser_out *parser_out  = yypParser->parser_out 
+#define ParseARG_STORE yypParser->parser_out  = parser_out 
+#define YYNSTATE 99
+#define YYNRULE 38
+#define YY_NO_ACTION      (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION  (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION   (YYNSTATE+YYNRULE)
+
+/* The yyzerominor constant is used to initialize instances of
+** YYMINORTYPE objects to zero. */
+static const YYMINORTYPE yyzerominor = { 0 };
+
+/* Define the yytestcase() macro to be a no-op if is not already defined
+** otherwise.
+**
+** Applications can choose to define yytestcase() in the %include section
+** to a macro that can assist in verifying code coverage.  For production
+** code the yytestcase() macro should be turned off.  But it is useful
+** for testing.
+*/
+#ifndef yytestcase
+# define yytestcase(X)
+#endif
+
+
+/* Next are the tables used to determine what action to take based on the
+** current state and lookahead token.  These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.  
+**
+** Suppose the action integer is N.  Then the action is determined as
+** follows
+**
+**   0 <= N < YYNSTATE                  Shift N.  That is, push the lookahead
+**                                      token onto the stack and goto state N.
+**
+**   YYNSTATE <= N < YYNSTATE+YYNRULE   Reduce by rule N-YYNSTATE.
+**
+**   N == YYNSTATE+YYNRULE              A syntax error has occurred.
+**
+**   N == YYNSTATE+YYNRULE+1            The parser accepts its input.
+**
+**   N == YYNSTATE+YYNRULE+2            No such action.  Denotes unused
+**                                      slots in the yy_action[] table.
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as
+**
+**      yy_action[ yy_shift_ofst[S] + X ]
+**
+** If the index value yy_shift_ofst[S]+X is out of range or if the value
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
+** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
+** and that yy_default[S] should be used instead.  
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol.  If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
+** YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+**  yy_action[]        A single table containing all actions.
+**  yy_lookahead[]     A table containing the lookahead for each entry in
+**                     yy_action.  Used to detect hash collisions.
+**  yy_shift_ofst[]    For each state, the offset into yy_action for
+**                     shifting terminals.
+**  yy_reduce_ofst[]   For each state, the offset into yy_action for
+**                     shifting non-terminals after a reduce.
+**  yy_default[]       Default action for each state.
+*/
+static const YYACTIONTYPE yy_action[] = {
+ /*     0 */    86,   39,   80,   87,   68,   88,   42,   86,   48,   80,
+ /*    10 */    87,    1,   88,   42,   24,   85,   78,   41,    3,   82,
+ /*    20 */    86,   40,   43,   87,   41,   88,   42,   41,   85,   79,
+ /*    30 */    41,    3,    4,   86,   50,   56,   87,   46,   88,   44,
+ /*    40 */    47,   85,   49,   41,    3,    4,   89,   34,   86,   35,
+ /*    50 */    81,   87,   72,   88,   42,   73,   54,   86,    4,   55,
+ /*    60 */    87,   84,   88,   44,   26,   97,   36,   75,   76,   36,
+ /*    70 */    86,   61,   66,   87,   86,   88,   45,   87,   86,   88,
+ /*    80 */    51,   87,   86,   88,   57,   87,   33,   88,   69,   15,
+ /*    90 */    59,   27,   98,   38,   31,   99,   62,   37,   18,   15,
+ /*   100 */    36,  138,   15,   53,   31,   15,   67,   31,   15,   60,
+ /*   110 */    31,   15,   94,   31,   15,   65,   31,   19,   71,   31,
+ /*   120 */    20,   11,   77,   23,   74,   22,    5,   83,    6,   90,
+ /*   130 */     2,   91,    7,   25,    8,   12,   52,   21,   36,   13,
+ /*   140 */     9,   92,   58,   32,   28,   14,   93,   63,   29,   64,
+ /*   150 */    16,  139,   96,   95,   10,   17,   70,   30,
+};
+static const YYCODETYPE yy_lookahead[] = {
+ /*     0 */    30,   31,   32,   33,   15,   35,   36,   30,   31,   32,
+ /*    10 */    33,    7,   35,   36,   10,    2,    4,    4,    5,    6,
+ /*    20 */    30,   37,   32,   33,    4,   35,   36,    4,    2,   30,
+ /*    30 */     4,    5,   19,   30,   11,   12,   33,   34,   35,   36,
+ /*    40 */    30,    2,   37,    4,    5,   19,   20,    1,   30,    3,
+ /*    50 */    32,   33,   24,   35,   36,   24,   37,   30,   19,   16,
+ /*    60 */    33,   34,   35,   36,   26,   27,   38,   21,   22,   38,
+ /*    70 */    30,   37,   37,   33,   30,   35,   36,   33,   30,   35,
+ /*    80 */    36,   33,   30,   35,   36,   33,   24,   35,   36,   25,
+ /*    90 */     8,   28,   27,   29,   30,    0,   14,    4,    2,   25,
+ /*   100 */    38,   39,   25,   29,   30,   25,   29,   30,   25,   29,
+ /*   110 */    30,   25,   29,   30,   25,   29,   30,    2,   29,   30,
+ /*   120 */     6,    5,    9,   17,   24,    8,   18,    6,   18,   20,
+ /*   130 */     7,    9,   14,    8,    7,    5,    8,    6,   38,    5,
+ /*   140 */     7,    9,   13,    4,    6,    5,    9,    4,    6,    8,
+ /*   150 */     5,   40,    6,    9,    7,    5,    8,    6,
+};
+#define YY_SHIFT_USE_DFLT (-12)
+#define YY_SHIFT_MAX 71
+static const short yy_shift_ofst[] = {
+ /*     0 */    46,   39,   39,   13,   26,   39,   39,   39,   39,   39,
+ /*    10 */    39,   23,   23,   23,   23,   23,   23,   23,   46,   46,
+ /*    20 */    46,  -11,   12,   20,   20,   12,   43,   12,   12,   12,
+ /*    30 */   -11,    4,   82,   95,   96,  115,   93,  116,  114,  117,
+ /*    40 */   113,  106,  108,  121,  118,  110,  109,  123,  125,  122,
+ /*    50 */   127,  128,  130,  131,  132,  134,  133,  129,  139,  140,
+ /*    60 */   138,  137,  143,  141,  145,  142,  144,  146,  147,  148,
+ /*    70 */   150,  151,
+};
+#define YY_REDUCE_USE_DFLT (-31)
+#define YY_REDUCE_MAX 30
+static const signed char yy_reduce_ofst[] = {
+ /*     0 */    62,  -30,  -23,  -10,    3,   18,   27,   40,   44,   48,
+ /*    10 */    52,   64,   74,   77,   80,   83,   86,   89,   28,   31,
+ /*    20 */   100,   38,  -16,   -1,   10,    5,   63,   19,   34,   35,
+ /*    30 */    65,
+};
+static const YYACTIONTYPE yy_default[] = {
+ /*     0 */   100,  119,  119,  137,  137,  137,  137,  137,  137,  137,
+ /*    10 */   137,  137,  137,  137,  137,  115,  137,  137,  100,  100,
+ /*    20 */   100,  109,  133,  137,  137,  133,  113,  133,  133,  133,
+ /*    30 */   111,  137,  137,  137,  137,  137,  137,  137,  137,  137,
+ /*    40 */   137,  117,  121,  137,  137,  125,  137,  137,  137,  137,
+ /*    50 */   137,  137,  137,  137,  137,  137,  137,  137,  137,  137,
+ /*    60 */   137,  137,  137,  137,  137,  137,  137,  137,  137,  137,
+ /*    70 */   137,  137,  101,  102,  103,  135,  136,  104,  134,  118,
+ /*    80 */   120,  122,  123,  124,  126,  129,  130,  131,  132,  127,
+ /*    90 */   128,  105,  106,  107,  116,  108,  114,  110,  112,
+};
+#define YY_SZ_ACTTAB (int)(sizeof(yy_action)/sizeof(yy_action[0]))
+
+/* The next table maps tokens into fallback tokens.  If a construct
+** like the following:
+** 
+**      %fallback ID X Y Z.
+**
+** appears in the grammar, then ID becomes a fallback token for X, Y,
+** and Z.  Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack.  Information stored includes:
+**
+**   +  The state number for the parser at this level of the stack.
+**
+**   +  The value of the token stored at this level of the stack.
+**      (In other words, the "major" token.)
+**
+**   +  The semantic value stored at this level of the stack.  This is
+**      the information used by the action routines in the grammar.
+**      It is sometimes called the "minor" token.
+*/
+struct yyStackEntry {
+  YYACTIONTYPE stateno;  /* The state-number */
+  YYCODETYPE major;      /* The major token value.  This is the code
+                         ** number for the token at this stack level */
+  YYMINORTYPE minor;     /* The user-supplied minor token value.  This
+                         ** is the value of the token  */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+  int yyidx;                    /* Index of top element in stack */
+#ifdef YYTRACKMAXSTACKDEPTH
+  int yyidxMax;                 /* Maximum value of yyidx */
+#endif
+  int yyerrcnt;                 /* Shifts left before out of the error */
+  ParseARG_SDECL                /* A place to hold %extra_argument */
+#if YYSTACKDEPTH<=0
+  int yystksz;                  /* Current side of the stack */
+  yyStackEntry *yystack;        /* The parser's stack */
+#else
+  yyStackEntry yystack[YYSTACKDEPTH];  /* The parser's stack */
+#endif
+};
+typedef struct yyParser yyParser;
+
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* 
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message.  Tracing is turned off
+** by making either argument NULL 
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+**      If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+**      line of trace output.  If NULL, then tracing is
+**      turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
+  yyTraceFILE = TraceFILE;
+  yyTracePrompt = zTracePrompt;
+  if( yyTraceFILE==0 ) yyTracePrompt = 0;
+  else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required.  The following table supplies these names */
+static const char *const yyTokenName[] = { 
+  "$",             "INCLUDE",       "STRING",        "INCLUDE_GUARD",
+  "NAME",          "CURLY_OPEN",    "CURLY_CLOSE",   "ROUND_OPEN",  
+  "ROUND_CLOSE",   "SEMICOLON",     "ARROW",         "IF",          
+  "FOREACH",       "AS",            "COLON",         "ELIF",        
+  "ELSE",          "DOT",           "COMMA",         "BRACKET_OPEN",
+  "BRACKET_CLOSE",  "PROCESS",       "TEMPLATE",      "error",       
+  "processes",     "statement",     "elif_maybe",    "elif",        
+  "else_maybe",    "statements",    "dotted_name",   "statement_args_maybe",
+  "list_contents",  "list",          "map_contents",  "map",         
+  "value",         "name_maybe",    "process_or_template",  "input",       
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *const yyRuleName[] = {
+ /*   0 */ "input ::= processes",
+ /*   1 */ "processes ::=",
+ /*   2 */ "processes ::= INCLUDE STRING processes",
+ /*   3 */ "processes ::= INCLUDE_GUARD STRING processes",
+ /*   4 */ "processes ::= process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes",
+ /*   5 */ "statement ::= dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON",
+ /*   6 */ "statement ::= dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON",
+ /*   7 */ "statement ::= IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON",
+ /*   8 */ "statement ::= FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON",
+ /*   9 */ "statement ::= FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON",
+ /*  10 */ "elif_maybe ::=",
+ /*  11 */ "elif_maybe ::= elif",
+ /*  12 */ "elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE",
+ /*  13 */ "elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif",
+ /*  14 */ "else_maybe ::=",
+ /*  15 */ "else_maybe ::= ELSE CURLY_OPEN statements CURLY_CLOSE",
+ /*  16 */ "statements ::= statement",
+ /*  17 */ "statements ::= statement statements",
+ /*  18 */ "dotted_name ::= NAME",
+ /*  19 */ "dotted_name ::= NAME DOT dotted_name",
+ /*  20 */ "statement_args_maybe ::=",
+ /*  21 */ "statement_args_maybe ::= list_contents",
+ /*  22 */ "list_contents ::= value",
+ /*  23 */ "list_contents ::= value COMMA list_contents",
+ /*  24 */ "list ::= CURLY_OPEN CURLY_CLOSE",
+ /*  25 */ "list ::= CURLY_OPEN list_contents CURLY_CLOSE",
+ /*  26 */ "map_contents ::= value COLON value",
+ /*  27 */ "map_contents ::= value COLON value COMMA map_contents",
+ /*  28 */ "map ::= BRACKET_OPEN BRACKET_CLOSE",
+ /*  29 */ "map ::= BRACKET_OPEN map_contents BRACKET_CLOSE",
+ /*  30 */ "value ::= STRING",
+ /*  31 */ "value ::= dotted_name",
+ /*  32 */ "value ::= list",
+ /*  33 */ "value ::= map",
+ /*  34 */ "name_maybe ::=",
+ /*  35 */ "name_maybe ::= NAME",
+ /*  36 */ "process_or_template ::= PROCESS",
+ /*  37 */ "process_or_template ::= TEMPLATE",
+};
+#endif /* NDEBUG */
+
+
+#if YYSTACKDEPTH<=0
+/*
+** Try to increase the size of the parser stack.
+*/
+static void yyGrowStack(yyParser *p){
+  int newSize;
+  yyStackEntry *pNew;
+
+  newSize = p->yystksz*2 + 100;
+  pNew = realloc(p->yystack, newSize*sizeof(pNew[0]));
+  if( pNew ){
+    p->yystack = pNew;
+    p->yystksz = newSize;
+#ifndef NDEBUG
+    if( yyTraceFILE ){
+      fprintf(yyTraceFILE,"%sStack grows to %d entries!\n",
+              yyTracePrompt, p->yystksz);
+    }
+#endif
+  }
+}
+#endif
+
+/* 
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser.  This pointer is used in subsequent calls
+** to Parse and ParseFree.
+*/
+void *ParseAlloc(void *(*mallocProc)(size_t)){
+  yyParser *pParser;
+  pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
+  if( pParser ){
+    pParser->yyidx = -1;
+#ifdef YYTRACKMAXSTACKDEPTH
+    pParser->yyidxMax = 0;
+#endif
+#if YYSTACKDEPTH<=0
+    pParser->yystack = NULL;
+    pParser->yystksz = 0;
+    yyGrowStack(pParser);
+#endif
+  }
+  return pParser;
+}
+
+/* The following function deletes the value associated with a
+** symbol.  The symbol can be either a terminal or nonterminal.
+** "yymajor" is the symbol code, and "yypminor" is a pointer to
+** the value.
+*/
+static void yy_destructor(
+  yyParser *yypParser,    /* The parser */
+  YYCODETYPE yymajor,     /* Type code for object to destroy */
+  YYMINORTYPE *yypminor   /* The object to be destroyed */
+){
+  ParseARG_FETCH;
+  switch( yymajor ){
+    /* Here is inserted the actions which take place when a
+    ** terminal or non-terminal is destroyed.  This can happen
+    ** when the symbol is popped from the stack during a
+    ** reduce or during error processing or when a parser is 
+    ** being destroyed before it is finished parsing.
+    **
+    ** Note: during a reduce, the only symbols destroyed are those
+    ** which appear on the RHS of the rule, but which are not used
+    ** inside the C code.
+    */
+      /* TERMINAL Destructor */
+    case 1: /* INCLUDE */
+    case 2: /* STRING */
+    case 3: /* INCLUDE_GUARD */
+    case 4: /* NAME */
+    case 5: /* CURLY_OPEN */
+    case 6: /* CURLY_CLOSE */
+    case 7: /* ROUND_OPEN */
+    case 8: /* ROUND_CLOSE */
+    case 9: /* SEMICOLON */
+    case 10: /* ARROW */
+    case 11: /* IF */
+    case 12: /* FOREACH */
+    case 13: /* AS */
+    case 14: /* COLON */
+    case 15: /* ELIF */
+    case 16: /* ELSE */
+    case 17: /* DOT */
+    case 18: /* COMMA */
+    case 19: /* BRACKET_OPEN */
+    case 20: /* BRACKET_CLOSE */
+    case 21: /* PROCESS */
+    case 22: /* TEMPLATE */
+{
+#line 89 "NCDConfigParser_parse.y"
+ free_token((yypminor->yy0)); 
+#line 523 "NCDConfigParser_parse.c"
+}
+      break;
+    case 24: /* processes */
+{
+#line 108 "NCDConfigParser_parse.y"
+ (void)parser_out; free_program((yypminor->yy54)); 
+#line 530 "NCDConfigParser_parse.c"
+}
+      break;
+    case 25: /* statement */
+{
+#line 109 "NCDConfigParser_parse.y"
+ free_statement((yypminor->yy79)); 
+#line 537 "NCDConfigParser_parse.c"
+}
+      break;
+    case 26: /* elif_maybe */
+    case 27: /* elif */
+{
+#line 110 "NCDConfigParser_parse.y"
+ free_ifblock((yypminor->yy76)); 
+#line 545 "NCDConfigParser_parse.c"
+}
+      break;
+    case 28: /* else_maybe */
+    case 29: /* statements */
+{
+#line 112 "NCDConfigParser_parse.y"
+ free_block((yypminor->yy37)); 
+#line 553 "NCDConfigParser_parse.c"
+}
+      break;
+    case 30: /* dotted_name */
+    case 37: /* name_maybe */
+{
+#line 114 "NCDConfigParser_parse.y"
+ free((yypminor->yy33)); 
+#line 561 "NCDConfigParser_parse.c"
+}
+      break;
+    case 31: /* statement_args_maybe */
+    case 32: /* list_contents */
+    case 33: /* list */
+    case 34: /* map_contents */
+    case 35: /* map */
+    case 36: /* value */
+{
+#line 115 "NCDConfigParser_parse.y"
+ free_value((yypminor->yy51)); 
+#line 573 "NCDConfigParser_parse.c"
+}
+      break;
+    default:  break;   /* If no destructor action specified: do nothing */
+  }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+**
+** Return the major token number for the symbol popped.
+*/
+static int yy_pop_parser_stack(yyParser *pParser){
+  YYCODETYPE yymajor;
+  yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
+
+  if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+  if( yyTraceFILE && pParser->yyidx>=0 ){
+    fprintf(yyTraceFILE,"%sPopping %s\n",
+      yyTracePrompt,
+      yyTokenName[yytos->major]);
+  }
+#endif
+  yymajor = yytos->major;
+  yy_destructor(pParser, yymajor, &yytos->minor);
+  pParser->yyidx--;
+  return yymajor;
+}
+
+/* 
+** Deallocate and destroy a parser.  Destructors are all called for
+** all stack elements before shutting the parser down.
+**
+** Inputs:
+** <ul>
+** <li>  A pointer to the parser.  This should be a pointer
+**       obtained from ParseAlloc.
+** <li>  A pointer to a function used to reclaim memory obtained
+**       from malloc.
+** </ul>
+*/
+void ParseFree(
+  void *p,                    /* The parser to be deleted */
+  void (*freeProc)(void*)     /* Function used to reclaim memory */
+){
+  yyParser *pParser = (yyParser*)p;
+  if( pParser==0 ) return;
+  while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
+#if YYSTACKDEPTH<=0
+  free(pParser->yystack);
+#endif
+  (*freeProc)((void*)pParser);
+}
+
+/*
+** Return the peak depth of the stack for a parser.
+*/
+#ifdef YYTRACKMAXSTACKDEPTH
+int ParseStackPeak(void *p){
+  yyParser *pParser = (yyParser*)p;
+  return pParser->yyidxMax;
+}
+#endif
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead.  If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_shift_action(
+  yyParser *pParser,        /* The parser */
+  YYCODETYPE iLookAhead     /* The look-ahead token */
+){
+  int i;
+  int stateno = pParser->yystack[pParser->yyidx].stateno;
+ 
+  if( stateno>YY_SHIFT_MAX || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){
+    return yy_default[stateno];
+  }
+  assert( iLookAhead!=YYNOCODE );
+  i += iLookAhead;
+  if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+    if( iLookAhead>0 ){
+#ifdef YYFALLBACK
+      YYCODETYPE iFallback;            /* Fallback token */
+      if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+             && (iFallback = yyFallback[iLookAhead])!=0 ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+             yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+        }
+#endif
+        return yy_find_shift_action(pParser, iFallback);
+      }
+#endif
+#ifdef YYWILDCARD
+      {
+        int j = i - iLookAhead + YYWILDCARD;
+        if( j>=0 && j<YY_SZ_ACTTAB && yy_lookahead[j]==YYWILDCARD ){
+#ifndef NDEBUG
+          if( yyTraceFILE ){
+            fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n",
+               yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]);
+          }
+#endif /* NDEBUG */
+          return yy_action[j];
+        }
+      }
+#endif /* YYWILDCARD */
+    }
+    return yy_default[stateno];
+  }else{
+    return yy_action[i];
+  }
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead.  If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_reduce_action(
+  int stateno,              /* Current state number */
+  YYCODETYPE iLookAhead     /* The look-ahead token */
+){
+  int i;
+#ifdef YYERRORSYMBOL
+  if( stateno>YY_REDUCE_MAX ){
+    return yy_default[stateno];
+  }
+#else
+  assert( stateno<=YY_REDUCE_MAX );
+#endif
+  i = yy_reduce_ofst[stateno];
+  assert( i!=YY_REDUCE_USE_DFLT );
+  assert( iLookAhead!=YYNOCODE );
+  i += iLookAhead;
+#ifdef YYERRORSYMBOL
+  if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+    return yy_default[stateno];
+  }
+#else
+  assert( i>=0 && i<YY_SZ_ACTTAB );
+  assert( yy_lookahead[i]==iLookAhead );
+#endif
+  return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
+   ParseARG_FETCH;
+   yypParser->yyidx--;
+#ifndef NDEBUG
+   if( yyTraceFILE ){
+     fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+   }
+#endif
+   while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+   /* Here code is inserted which will execute if the parser
+   ** stack every overflows */
+#line 130 "NCDConfigParser_parse.y"
+
+    if (yypMinor) {
+        free_token(yypMinor->yy0);
+    }
+#line 751 "NCDConfigParser_parse.c"
+   ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
+}
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+  yyParser *yypParser,          /* The parser to be shifted */
+  int yyNewState,               /* The new state to shift in */
+  int yyMajor,                  /* The major token to shift in */
+  YYMINORTYPE *yypMinor         /* Pointer to the minor token to shift in */
+){
+  yyStackEntry *yytos;
+  yypParser->yyidx++;
+#ifdef YYTRACKMAXSTACKDEPTH
+  if( yypParser->yyidx>yypParser->yyidxMax ){
+    yypParser->yyidxMax = yypParser->yyidx;
+  }
+#endif
+#if YYSTACKDEPTH>0 
+  if( yypParser->yyidx>=YYSTACKDEPTH ){
+    yyStackOverflow(yypParser, yypMinor);
+    return;
+  }
+#else
+  if( yypParser->yyidx>=yypParser->yystksz ){
+    yyGrowStack(yypParser);
+    if( yypParser->yyidx>=yypParser->yystksz ){
+      yyStackOverflow(yypParser, yypMinor);
+      return;
+    }
+  }
+#endif
+  yytos = &yypParser->yystack[yypParser->yyidx];
+  yytos->stateno = (YYACTIONTYPE)yyNewState;
+  yytos->major = (YYCODETYPE)yyMajor;
+  yytos->minor = *yypMinor;
+#ifndef NDEBUG
+  if( yyTraceFILE && yypParser->yyidx>0 ){
+    int i;
+    fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
+    fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
+    for(i=1; i<=yypParser->yyidx; i++)
+      fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
+    fprintf(yyTraceFILE,"\n");
+  }
+#endif
+}
+
+/* The following table contains information about every rule that
+** is used during the reduce.
+*/
+static const struct {
+  YYCODETYPE lhs;         /* Symbol on the left-hand side of the rule */
+  unsigned char nrhs;     /* Number of right-hand side symbols in the rule */
+} yyRuleInfo[] = {
+  { 39, 1 },
+  { 24, 0 },
+  { 24, 3 },
+  { 24, 3 },
+  { 24, 6 },
+  { 25, 6 },
+  { 25, 8 },
+  { 25, 11 },
+  { 25, 11 },
+  { 25, 13 },
+  { 26, 0 },
+  { 26, 1 },
+  { 27, 7 },
+  { 27, 8 },
+  { 28, 0 },
+  { 28, 4 },
+  { 29, 1 },
+  { 29, 2 },
+  { 30, 1 },
+  { 30, 3 },
+  { 31, 0 },
+  { 31, 1 },
+  { 32, 1 },
+  { 32, 3 },
+  { 33, 2 },
+  { 33, 3 },
+  { 34, 3 },
+  { 34, 5 },
+  { 35, 2 },
+  { 35, 3 },
+  { 36, 1 },
+  { 36, 1 },
+  { 36, 1 },
+  { 36, 1 },
+  { 37, 0 },
+  { 37, 1 },
+  { 38, 1 },
+  { 38, 1 },
+};
+
+static void yy_accept(yyParser*);  /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+*/
+static void yy_reduce(
+  yyParser *yypParser,         /* The parser */
+  int yyruleno                 /* Number of the rule by which to reduce */
+){
+  int yygoto;                     /* The next state */
+  int yyact;                      /* The next action */
+  YYMINORTYPE yygotominor;        /* The LHS of the rule reduced */
+  yyStackEntry *yymsp;            /* The top of the parser's stack */
+  int yysize;                     /* Amount to pop the stack */
+  ParseARG_FETCH;
+  yymsp = &yypParser->yystack[yypParser->yyidx];
+#ifndef NDEBUG
+  if( yyTraceFILE && yyruleno>=0 
+        && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
+    fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+      yyRuleName[yyruleno]);
+  }
+#endif /* NDEBUG */
+
+  /* Silence complaints from purify about yygotominor being uninitialized
+  ** in some cases when it is copied into the stack after the following
+  ** switch.  yygotominor is uninitialized when a rule reduces that does
+  ** not set the value of its left-hand side nonterminal.  Leaving the
+  ** value of the nonterminal uninitialized is utterly harmless as long
+  ** as the value is never used.  So really the only thing this code
+  ** accomplishes is to quieten purify.  
+  **
+  ** 2007-01-16:  The wireshark project (www.wireshark.org) reports that
+  ** without this code, their parser segfaults.  I'm not sure what there
+  ** parser is doing to make this happen.  This is the second bug report
+  ** from wireshark this week.  Clearly they are stressing Lemon in ways
+  ** that it has not been previously stressed...  (SQLite ticket #2172)
+  */
+  /*memset(&yygotominor, 0, sizeof(yygotominor));*/
+  yygotominor = yyzerominor;
+
+
+  switch( yyruleno ){
+  /* Beginning here are the reduction cases.  A typical example
+  ** follows:
+  **   case 0:
+  **  #line <lineno> <grammarfile>
+  **     { ... }           // User supplied code
+  **  #line <lineno> <thisfile>
+  **     break;
+  */
+      case 0: /* input ::= processes */
+#line 136 "NCDConfigParser_parse.y"
+{
+    ASSERT(!parser_out->have_ast)
+
+    if (yymsp[0].minor.yy54.have) {
+        parser_out->have_ast = 1;
+        parser_out->ast = yymsp[0].minor.yy54.v;
+    }
+}
+#line 910 "NCDConfigParser_parse.c"
+        break;
+      case 1: /* processes ::= */
+#line 145 "NCDConfigParser_parse.y"
+{
+    NCDProgram prog;
+    NCDProgram_Init(&prog);
+    
+    yygotominor.yy54.have = 1;
+    yygotominor.yy54.v = prog;
+}
+#line 921 "NCDConfigParser_parse.c"
+        break;
+      case 2: /* processes ::= INCLUDE STRING processes */
+#line 153 "NCDConfigParser_parse.y"
+{
+    ASSERT(yymsp[-1].minor.yy0.str)
+    if (!yymsp[0].minor.yy54.have) {
+        goto failA0;
+    }
+    
+    NCDProgramElem elem;
+    if (!NCDProgramElem_InitInclude(&elem, yymsp[-1].minor.yy0.str, yymsp[-1].minor.yy0.len)) {
+        goto failA0;
+    }
+    
+    if (!NCDProgram_PrependElem(&yymsp[0].minor.yy54.v, elem)) {
+        goto failA1;
+    }
+    
+    yygotominor.yy54.have = 1;
+    yygotominor.yy54.v = yymsp[0].minor.yy54.v;
+    yymsp[0].minor.yy54.have = 0;
+    goto doneA;
+
+failA1:
+    NCDProgramElem_Free(&elem);
+failA0:
+    yygotominor.yy54.have = 0;
+    parser_out->out_of_memory = 1;
+doneA:
+    free_token(yymsp[-1].minor.yy0);
+    free_program(yymsp[0].minor.yy54);
+  yy_destructor(yypParser,1,&yymsp[-2].minor);
+}
+#line 955 "NCDConfigParser_parse.c"
+        break;
+      case 3: /* processes ::= INCLUDE_GUARD STRING processes */
+#line 183 "NCDConfigParser_parse.y"
+{
+    ASSERT(yymsp[-1].minor.yy0.str)
+    if (!yymsp[0].minor.yy54.have) {
+        goto failZ0;
+    }
+    
+    NCDProgramElem elem;
+    if (!NCDProgramElem_InitIncludeGuard(&elem, yymsp[-1].minor.yy0.str, yymsp[-1].minor.yy0.len)) {
+        goto failZ0;
+    }
+    
+    if (!NCDProgram_PrependElem(&yymsp[0].minor.yy54.v, elem)) {
+        goto failZ1;
+    }
+    
+    yygotominor.yy54.have = 1;
+    yygotominor.yy54.v = yymsp[0].minor.yy54.v;
+    yymsp[0].minor.yy54.have = 0;
+    goto doneZ;
+
+failZ1:
+    NCDProgramElem_Free(&elem);
+failZ0:
+    yygotominor.yy54.have = 0;
+    parser_out->out_of_memory = 1;
+doneZ:
+    free_token(yymsp[-1].minor.yy0);
+    free_program(yymsp[0].minor.yy54);
+  yy_destructor(yypParser,3,&yymsp[-2].minor);
+}
+#line 989 "NCDConfigParser_parse.c"
+        break;
+      case 4: /* processes ::= process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes */
+#line 213 "NCDConfigParser_parse.y"
+{
+    ASSERT(yymsp[-4].minor.yy0.str)
+    if (!yymsp[-2].minor.yy37.have || !yymsp[0].minor.yy54.have) {
+        goto failB0;
+    }
+
+    NCDProcess proc;
+    if (!NCDProcess_Init(&proc, yymsp[-5].minor.yy12, yymsp[-4].minor.yy0.str, yymsp[-2].minor.yy37.v)) {
+        goto failB0;
+    }
+    yymsp[-2].minor.yy37.have = 0;
+    
+    NCDProgramElem elem;
+    NCDProgramElem_InitProcess(&elem, proc);
+
+    if (!NCDProgram_PrependElem(&yymsp[0].minor.yy54.v, elem)) {
+        goto failB1;
+    }
+
+    yygotominor.yy54.have = 1;
+    yygotominor.yy54.v = yymsp[0].minor.yy54.v;
+    yymsp[0].minor.yy54.have = 0;
+    goto doneB;
+
+failB1:
+    NCDProgramElem_Free(&elem);
+failB0:
+    yygotominor.yy54.have = 0;
+    parser_out->out_of_memory = 1;
+doneB:
+    free_token(yymsp[-4].minor.yy0);
+    free_block(yymsp[-2].minor.yy37);
+    free_program(yymsp[0].minor.yy54);
+  yy_destructor(yypParser,5,&yymsp[-3].minor);
+  yy_destructor(yypParser,6,&yymsp[-1].minor);
+}
+#line 1029 "NCDConfigParser_parse.c"
+        break;
+      case 5: /* statement ::= dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON */
+#line 248 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-5].minor.yy33 || !yymsp[-3].minor.yy51.have) {
+        goto failC0;
+    }
+
+    if (!NCDStatement_InitReg(&yygotominor.yy79.v, yymsp[-1].minor.yy33, NULL, yymsp[-5].minor.yy33, yymsp[-3].minor.yy51.v)) {
+        goto failC0;
+    }
+    yymsp[-3].minor.yy51.have = 0;
+
+    yygotominor.yy79.have = 1;
+    goto doneC;
+
+failC0:
+    yygotominor.yy79.have = 0;
+    parser_out->out_of_memory = 1;
+doneC:
+    free(yymsp[-5].minor.yy33);
+    free_value(yymsp[-3].minor.yy51);
+    free(yymsp[-1].minor.yy33);
+  yy_destructor(yypParser,7,&yymsp[-4].minor);
+  yy_destructor(yypParser,8,&yymsp[-2].minor);
+  yy_destructor(yypParser,9,&yymsp[0].minor);
+}
+#line 1057 "NCDConfigParser_parse.c"
+        break;
+      case 6: /* statement ::= dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON */
+#line 270 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-7].minor.yy33 || !yymsp[-5].minor.yy33 || !yymsp[-3].minor.yy51.have) {
+        goto failD0;
+    }
+
+    if (!NCDStatement_InitReg(&yygotominor.yy79.v, yymsp[-1].minor.yy33, yymsp[-7].minor.yy33, yymsp[-5].minor.yy33, yymsp[-3].minor.yy51.v)) {
+        goto failD0;
+    }
+    yymsp[-3].minor.yy51.have = 0;
+
+    yygotominor.yy79.have = 1;
+    goto doneD;
+
+failD0:
+    yygotominor.yy79.have = 0;
+    parser_out->out_of_memory = 1;
+doneD:
+    free(yymsp[-7].minor.yy33);
+    free(yymsp[-5].minor.yy33);
+    free_value(yymsp[-3].minor.yy51);
+    free(yymsp[-1].minor.yy33);
+  yy_destructor(yypParser,10,&yymsp[-6].minor);
+  yy_destructor(yypParser,7,&yymsp[-4].minor);
+  yy_destructor(yypParser,8,&yymsp[-2].minor);
+  yy_destructor(yypParser,9,&yymsp[0].minor);
+}
+#line 1087 "NCDConfigParser_parse.c"
+        break;
+      case 7: /* statement ::= IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON */
+#line 293 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-8].minor.yy51.have || !yymsp[-5].minor.yy37.have || !yymsp[-3].minor.yy76.have) {
+        goto failE0;
+    }
+
+    NCDIf ifc;
+    NCDIf_Init(&ifc, yymsp[-8].minor.yy51.v, yymsp[-5].minor.yy37.v);
+    yymsp[-8].minor.yy51.have = 0;
+    yymsp[-5].minor.yy37.have = 0;
+
+    if (!NCDIfBlock_PrependIf(&yymsp[-3].minor.yy76.v, ifc)) {
+        NCDIf_Free(&ifc);
+        goto failE0;
+    }
+
+    if (!NCDStatement_InitIf(&yygotominor.yy79.v, yymsp[-1].minor.yy33, yymsp[-3].minor.yy76.v)) {
+        goto failE0;
+    }
+    yymsp[-3].minor.yy76.have = 0;
+
+    if (yymsp[-2].minor.yy37.have) {
+        NCDStatement_IfAddElse(&yygotominor.yy79.v, yymsp[-2].minor.yy37.v);
+        yymsp[-2].minor.yy37.have = 0;
+    }
+
+    yygotominor.yy79.have = 1;
+    goto doneE;
+
+failE0:
+    yygotominor.yy79.have = 0;
+    parser_out->out_of_memory = 1;
+doneE:
+    free_value(yymsp[-8].minor.yy51);
+    free_block(yymsp[-5].minor.yy37);
+    free_ifblock(yymsp[-3].minor.yy76);
+    free_block(yymsp[-2].minor.yy37);
+    free(yymsp[-1].minor.yy33);
+  yy_destructor(yypParser,11,&yymsp[-10].minor);
+  yy_destructor(yypParser,7,&yymsp[-9].minor);
+  yy_destructor(yypParser,8,&yymsp[-7].minor);
+  yy_destructor(yypParser,5,&yymsp[-6].minor);
+  yy_destructor(yypParser,6,&yymsp[-4].minor);
+  yy_destructor(yypParser,9,&yymsp[0].minor);
+}
+#line 1135 "NCDConfigParser_parse.c"
+        break;
+      case 8: /* statement ::= FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON */
+#line 332 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-8].minor.yy51.have || !yymsp[-6].minor.yy0.str || !yymsp[-3].minor.yy37.have) {
+        goto failEA0;
+    }
+    
+    if (!NCDStatement_InitForeach(&yygotominor.yy79.v, yymsp[-1].minor.yy33, yymsp[-8].minor.yy51.v, yymsp[-6].minor.yy0.str, NULL, yymsp[-3].minor.yy37.v)) {
+        goto failEA0;
+    }
+    yymsp[-8].minor.yy51.have = 0;
+    yymsp[-3].minor.yy37.have = 0;
+    
+    yygotominor.yy79.have = 1;
+    goto doneEA0;
+    
+failEA0:
+    yygotominor.yy79.have = 0;
+    parser_out->out_of_memory = 1;
+doneEA0:
+    free_value(yymsp[-8].minor.yy51);
+    free_token(yymsp[-6].minor.yy0);
+    free_block(yymsp[-3].minor.yy37);
+    free(yymsp[-1].minor.yy33);
+  yy_destructor(yypParser,12,&yymsp[-10].minor);
+  yy_destructor(yypParser,7,&yymsp[-9].minor);
+  yy_destructor(yypParser,13,&yymsp[-7].minor);
+  yy_destructor(yypParser,8,&yymsp[-5].minor);
+  yy_destructor(yypParser,5,&yymsp[-4].minor);
+  yy_destructor(yypParser,6,&yymsp[-2].minor);
+  yy_destructor(yypParser,9,&yymsp[0].minor);
+}
+#line 1169 "NCDConfigParser_parse.c"
+        break;
+      case 9: /* statement ::= FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON */
+#line 356 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-10].minor.yy51.have || !yymsp[-8].minor.yy0.str || !yymsp[-6].minor.yy0.str || !yymsp[-3].minor.yy37.have) {
+        goto failEB0;
+    }
+    
+    if (!NCDStatement_InitForeach(&yygotominor.yy79.v, yymsp[-1].minor.yy33, yymsp[-10].minor.yy51.v, yymsp[-8].minor.yy0.str, yymsp[-6].minor.yy0.str, yymsp[-3].minor.yy37.v)) {
+        goto failEB0;
+    }
+    yymsp[-10].minor.yy51.have = 0;
+    yymsp[-3].minor.yy37.have = 0;
+    
+    yygotominor.yy79.have = 1;
+    goto doneEB0;
+    
+failEB0:
+    yygotominor.yy79.have = 0;
+    parser_out->out_of_memory = 1;
+doneEB0:
+    free_value(yymsp[-10].minor.yy51);
+    free_token(yymsp[-8].minor.yy0);
+    free_token(yymsp[-6].minor.yy0);
+    free_block(yymsp[-3].minor.yy37);
+    free(yymsp[-1].minor.yy33);
+  yy_destructor(yypParser,12,&yymsp[-12].minor);
+  yy_destructor(yypParser,7,&yymsp[-11].minor);
+  yy_destructor(yypParser,13,&yymsp[-9].minor);
+  yy_destructor(yypParser,14,&yymsp[-7].minor);
+  yy_destructor(yypParser,8,&yymsp[-5].minor);
+  yy_destructor(yypParser,5,&yymsp[-4].minor);
+  yy_destructor(yypParser,6,&yymsp[-2].minor);
+  yy_destructor(yypParser,9,&yymsp[0].minor);
+}
+#line 1205 "NCDConfigParser_parse.c"
+        break;
+      case 10: /* elif_maybe ::= */
+#line 381 "NCDConfigParser_parse.y"
+{
+    NCDIfBlock_Init(&yygotominor.yy76.v);
+    yygotominor.yy76.have = 1;
+}
+#line 1213 "NCDConfigParser_parse.c"
+        break;
+      case 11: /* elif_maybe ::= elif */
+#line 386 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy76 = yymsp[0].minor.yy76;
+}
+#line 1220 "NCDConfigParser_parse.c"
+        break;
+      case 12: /* elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE */
+#line 390 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-4].minor.yy51.have || !yymsp[-1].minor.yy37.have) {
+        goto failF0;
+    }
+
+    NCDIfBlock_Init(&yygotominor.yy76.v);
+
+    NCDIf ifc;
+    NCDIf_Init(&ifc, yymsp[-4].minor.yy51.v, yymsp[-1].minor.yy37.v);
+    yymsp[-4].minor.yy51.have = 0;
+    yymsp[-1].minor.yy37.have = 0;
+
+    if (!NCDIfBlock_PrependIf(&yygotominor.yy76.v, ifc)) {
+        goto failF1;
+    }
+
+    yygotominor.yy76.have = 1;
+    goto doneF0;
+
+failF1:
+    NCDIf_Free(&ifc);
+    NCDIfBlock_Free(&yygotominor.yy76.v);
+failF0:
+    yygotominor.yy76.have = 0;
+    parser_out->out_of_memory = 1;
+doneF0:
+    free_value(yymsp[-4].minor.yy51);
+    free_block(yymsp[-1].minor.yy37);
+  yy_destructor(yypParser,15,&yymsp[-6].minor);
+  yy_destructor(yypParser,7,&yymsp[-5].minor);
+  yy_destructor(yypParser,8,&yymsp[-3].minor);
+  yy_destructor(yypParser,5,&yymsp[-2].minor);
+  yy_destructor(yypParser,6,&yymsp[0].minor);
+}
+#line 1258 "NCDConfigParser_parse.c"
+        break;
+      case 13: /* elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif */
+#line 420 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-5].minor.yy51.have || !yymsp[-2].minor.yy37.have || !yymsp[0].minor.yy76.have) {
+        goto failG0;
+    }
+
+    NCDIf ifc;
+    NCDIf_Init(&ifc, yymsp[-5].minor.yy51.v, yymsp[-2].minor.yy37.v);
+    yymsp[-5].minor.yy51.have = 0;
+    yymsp[-2].minor.yy37.have = 0;
+
+    if (!NCDIfBlock_PrependIf(&yymsp[0].minor.yy76.v, ifc)) {
+        goto failG1;
+    }
+
+    yygotominor.yy76.have = 1;
+    yygotominor.yy76.v = yymsp[0].minor.yy76.v;
+    yymsp[0].minor.yy76.have = 0;
+    goto doneG0;
+
+failG1:
+    NCDIf_Free(&ifc);
+failG0:
+    yygotominor.yy76.have = 0;
+    parser_out->out_of_memory = 1;
+doneG0:
+    free_value(yymsp[-5].minor.yy51);
+    free_block(yymsp[-2].minor.yy37);
+    free_ifblock(yymsp[0].minor.yy76);
+  yy_destructor(yypParser,15,&yymsp[-7].minor);
+  yy_destructor(yypParser,7,&yymsp[-6].minor);
+  yy_destructor(yypParser,8,&yymsp[-4].minor);
+  yy_destructor(yypParser,5,&yymsp[-3].minor);
+  yy_destructor(yypParser,6,&yymsp[-1].minor);
+}
+#line 1296 "NCDConfigParser_parse.c"
+        break;
+      case 14: /* else_maybe ::= */
+#line 450 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy37.have = 0;
+}
+#line 1303 "NCDConfigParser_parse.c"
+        break;
+      case 15: /* else_maybe ::= ELSE CURLY_OPEN statements CURLY_CLOSE */
+#line 454 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy37 = yymsp[-1].minor.yy37;
+  yy_destructor(yypParser,16,&yymsp[-3].minor);
+  yy_destructor(yypParser,5,&yymsp[-2].minor);
+  yy_destructor(yypParser,6,&yymsp[0].minor);
+}
+#line 1313 "NCDConfigParser_parse.c"
+        break;
+      case 16: /* statements ::= statement */
+#line 458 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[0].minor.yy79.have) {
+        goto failH0;
+    }
+
+    NCDBlock_Init(&yygotominor.yy37.v);
+
+    if (!NCDBlock_PrependStatement(&yygotominor.yy37.v, yymsp[0].minor.yy79.v)) {
+        goto failH1;
+    }
+    yymsp[0].minor.yy79.have = 0;
+
+    yygotominor.yy37.have = 1;
+    goto doneH;
+
+failH1:
+    NCDBlock_Free(&yygotominor.yy37.v);
+failH0:
+    yygotominor.yy37.have = 0;
+    parser_out->out_of_memory = 1;
+doneH:
+    free_statement(yymsp[0].minor.yy79);
+}
+#line 1340 "NCDConfigParser_parse.c"
+        break;
+      case 17: /* statements ::= statement statements */
+#line 482 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-1].minor.yy79.have || !yymsp[0].minor.yy37.have) {
+        goto failI0;
+    }
+
+    if (!NCDBlock_PrependStatement(&yymsp[0].minor.yy37.v, yymsp[-1].minor.yy79.v)) {
+        goto failI1;
+    }
+    yymsp[-1].minor.yy79.have = 0;
+
+    yygotominor.yy37.have = 1;
+    yygotominor.yy37.v = yymsp[0].minor.yy37.v;
+    yymsp[0].minor.yy37.have = 0;
+    goto doneI;
+
+failI1:
+    NCDBlock_Free(&yygotominor.yy37.v);
+failI0:
+    yygotominor.yy37.have = 0;
+    parser_out->out_of_memory = 1;
+doneI:
+    free_statement(yymsp[-1].minor.yy79);
+    free_block(yymsp[0].minor.yy37);
+}
+#line 1368 "NCDConfigParser_parse.c"
+        break;
+      case 18: /* dotted_name ::= NAME */
+      case 35: /* name_maybe ::= NAME */ yytestcase(yyruleno==35);
+#line 507 "NCDConfigParser_parse.y"
+{
+    ASSERT(yymsp[0].minor.yy0.str)
+
+    yygotominor.yy33 = yymsp[0].minor.yy0.str;
+}
+#line 1378 "NCDConfigParser_parse.c"
+        break;
+      case 19: /* dotted_name ::= NAME DOT dotted_name */
+#line 513 "NCDConfigParser_parse.y"
+{
+    ASSERT(yymsp[-2].minor.yy0.str)
+    if (!yymsp[0].minor.yy33) {
+        goto failJ0;
+    }
+
+    if (!(yygotominor.yy33 = concat_strings(3, yymsp[-2].minor.yy0.str, ".", yymsp[0].minor.yy33))) {
+        goto failJ0;
+    }
+
+    goto doneJ;
+
+failJ0:
+    yygotominor.yy33 = NULL;
+    parser_out->out_of_memory = 1;
+doneJ:
+    free_token(yymsp[-2].minor.yy0);
+    free(yymsp[0].minor.yy33);
+  yy_destructor(yypParser,17,&yymsp[-1].minor);
+}
+#line 1402 "NCDConfigParser_parse.c"
+        break;
+      case 20: /* statement_args_maybe ::= */
+#line 533 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy51.have = 1;
+    NCDValue_InitList(&yygotominor.yy51.v);
+}
+#line 1410 "NCDConfigParser_parse.c"
+        break;
+      case 21: /* statement_args_maybe ::= list_contents */
+      case 32: /* value ::= list */ yytestcase(yyruleno==32);
+      case 33: /* value ::= map */ yytestcase(yyruleno==33);
+#line 538 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy51 = yymsp[0].minor.yy51;
+}
+#line 1419 "NCDConfigParser_parse.c"
+        break;
+      case 22: /* list_contents ::= value */
+#line 542 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[0].minor.yy51.have) {
+        goto failL0;
+    }
+
+    NCDValue_InitList(&yygotominor.yy51.v);
+
+    if (!NCDValue_ListPrepend(&yygotominor.yy51.v, yymsp[0].minor.yy51.v)) {
+        goto failL1;
+    }
+    yymsp[0].minor.yy51.have = 0;
+
+    yygotominor.yy51.have = 1;
+    goto doneL;
+
+failL1:
+    NCDValue_Free(&yygotominor.yy51.v);
+failL0:
+    yygotominor.yy51.have = 0;
+    parser_out->out_of_memory = 1;
+doneL:
+    free_value(yymsp[0].minor.yy51);
+}
+#line 1446 "NCDConfigParser_parse.c"
+        break;
+      case 23: /* list_contents ::= value COMMA list_contents */
+#line 566 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-2].minor.yy51.have || !yymsp[0].minor.yy51.have) {
+        goto failM0;
+    }
+
+    if (!NCDValue_ListPrepend(&yymsp[0].minor.yy51.v, yymsp[-2].minor.yy51.v)) {
+        goto failM0;
+    }
+    yymsp[-2].minor.yy51.have = 0;
+
+    yygotominor.yy51.have = 1;
+    yygotominor.yy51.v = yymsp[0].minor.yy51.v;
+    yymsp[0].minor.yy51.have = 0;
+    goto doneM;
+
+failM0:
+    yygotominor.yy51.have = 0;
+    parser_out->out_of_memory = 1;
+doneM:
+    free_value(yymsp[-2].minor.yy51);
+    free_value(yymsp[0].minor.yy51);
+  yy_destructor(yypParser,18,&yymsp[-1].minor);
+}
+#line 1473 "NCDConfigParser_parse.c"
+        break;
+      case 24: /* list ::= CURLY_OPEN CURLY_CLOSE */
+#line 589 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy51.have = 1;
+    NCDValue_InitList(&yygotominor.yy51.v);
+  yy_destructor(yypParser,5,&yymsp[-1].minor);
+  yy_destructor(yypParser,6,&yymsp[0].minor);
+}
+#line 1483 "NCDConfigParser_parse.c"
+        break;
+      case 25: /* list ::= CURLY_OPEN list_contents CURLY_CLOSE */
+#line 594 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy51 = yymsp[-1].minor.yy51;
+  yy_destructor(yypParser,5,&yymsp[-2].minor);
+  yy_destructor(yypParser,6,&yymsp[0].minor);
+}
+#line 1492 "NCDConfigParser_parse.c"
+        break;
+      case 26: /* map_contents ::= value COLON value */
+#line 598 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-2].minor.yy51.have || !yymsp[0].minor.yy51.have) {
+        goto failS0;
+    }
+
+    NCDValue_InitMap(&yygotominor.yy51.v);
+
+    if (!NCDValue_MapPrepend(&yygotominor.yy51.v, yymsp[-2].minor.yy51.v, yymsp[0].minor.yy51.v)) {
+        goto failS1;
+    }
+    yymsp[-2].minor.yy51.have = 0;
+    yymsp[0].minor.yy51.have = 0;
+
+    yygotominor.yy51.have = 1;
+    goto doneS;
+
+failS1:
+    NCDValue_Free(&yygotominor.yy51.v);
+failS0:
+    yygotominor.yy51.have = 0;
+    parser_out->out_of_memory = 1;
+doneS:
+    free_value(yymsp[-2].minor.yy51);
+    free_value(yymsp[0].minor.yy51);
+  yy_destructor(yypParser,14,&yymsp[-1].minor);
+}
+#line 1522 "NCDConfigParser_parse.c"
+        break;
+      case 27: /* map_contents ::= value COLON value COMMA map_contents */
+#line 624 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-4].minor.yy51.have || !yymsp[-2].minor.yy51.have || !yymsp[0].minor.yy51.have) {
+        goto failT0;
+    }
+
+    if (!NCDValue_MapPrepend(&yymsp[0].minor.yy51.v, yymsp[-4].minor.yy51.v, yymsp[-2].minor.yy51.v)) {
+        goto failT0;
+    }
+    yymsp[-4].minor.yy51.have = 0;
+    yymsp[-2].minor.yy51.have = 0;
+
+    yygotominor.yy51.have = 1;
+    yygotominor.yy51.v = yymsp[0].minor.yy51.v;
+    yymsp[0].minor.yy51.have = 0;
+    goto doneT;
+
+failT0:
+    yygotominor.yy51.have = 0;
+    parser_out->out_of_memory = 1;
+doneT:
+    free_value(yymsp[-4].minor.yy51);
+    free_value(yymsp[-2].minor.yy51);
+    free_value(yymsp[0].minor.yy51);
+  yy_destructor(yypParser,14,&yymsp[-3].minor);
+  yy_destructor(yypParser,18,&yymsp[-1].minor);
+}
+#line 1552 "NCDConfigParser_parse.c"
+        break;
+      case 28: /* map ::= BRACKET_OPEN BRACKET_CLOSE */
+#line 649 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy51.have = 1;
+    NCDValue_InitMap(&yygotominor.yy51.v);
+  yy_destructor(yypParser,19,&yymsp[-1].minor);
+  yy_destructor(yypParser,20,&yymsp[0].minor);
+}
+#line 1562 "NCDConfigParser_parse.c"
+        break;
+      case 29: /* map ::= BRACKET_OPEN map_contents BRACKET_CLOSE */
+#line 654 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy51 = yymsp[-1].minor.yy51;
+  yy_destructor(yypParser,19,&yymsp[-2].minor);
+  yy_destructor(yypParser,20,&yymsp[0].minor);
+}
+#line 1571 "NCDConfigParser_parse.c"
+        break;
+      case 30: /* value ::= STRING */
+#line 658 "NCDConfigParser_parse.y"
+{
+    ASSERT(yymsp[0].minor.yy0.str)
+
+    if (!NCDValue_InitStringBin(&yygotominor.yy51.v, (uint8_t *)yymsp[0].minor.yy0.str, yymsp[0].minor.yy0.len)) {
+        goto failU0;
+    }
+
+    yygotominor.yy51.have = 1;
+    goto doneU;
+
+failU0:
+    yygotominor.yy51.have = 0;
+    parser_out->out_of_memory = 1;
+doneU:
+    free_token(yymsp[0].minor.yy0);
+}
+#line 1591 "NCDConfigParser_parse.c"
+        break;
+      case 31: /* value ::= dotted_name */
+#line 675 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[0].minor.yy33) {
+        goto failV0;
+    }
+
+    if (!NCDValue_InitVar(&yygotominor.yy51.v, yymsp[0].minor.yy33)) {
+        goto failV0;
+    }
+
+    yygotominor.yy51.have = 1;
+    goto doneV;
+
+failV0:
+    yygotominor.yy51.have = 0;
+    parser_out->out_of_memory = 1;
+doneV:
+    free(yymsp[0].minor.yy33);
+}
+#line 1613 "NCDConfigParser_parse.c"
+        break;
+      case 34: /* name_maybe ::= */
+#line 702 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy33 = NULL;
+}
+#line 1620 "NCDConfigParser_parse.c"
+        break;
+      case 36: /* process_or_template ::= PROCESS */
+#line 712 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy12 = 0;
+  yy_destructor(yypParser,21,&yymsp[0].minor);
+}
+#line 1628 "NCDConfigParser_parse.c"
+        break;
+      case 37: /* process_or_template ::= TEMPLATE */
+#line 716 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy12 = 1;
+  yy_destructor(yypParser,22,&yymsp[0].minor);
+}
+#line 1636 "NCDConfigParser_parse.c"
+        break;
+      default:
+        break;
+  };
+  yygoto = yyRuleInfo[yyruleno].lhs;
+  yysize = yyRuleInfo[yyruleno].nrhs;
+  yypParser->yyidx -= yysize;
+  yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto);
+  if( yyact < YYNSTATE ){
+#ifdef NDEBUG
+    /* If we are not debugging and the reduce action popped at least
+    ** one element off the stack, then we can push the new element back
+    ** onto the stack here, and skip the stack overflow test in yy_shift().
+    ** That gives a significant speed improvement. */
+    if( yysize ){
+      yypParser->yyidx++;
+      yymsp -= yysize-1;
+      yymsp->stateno = (YYACTIONTYPE)yyact;
+      yymsp->major = (YYCODETYPE)yygoto;
+      yymsp->minor = yygotominor;
+    }else
+#endif
+    {
+      yy_shift(yypParser,yyact,yygoto,&yygotominor);
+    }
+  }else{
+    assert( yyact == YYNSTATE + YYNRULE + 1 );
+    yy_accept(yypParser);
+  }
+}
+
+/*
+** The following code executes when the parse fails
+*/
+#ifndef YYNOERRORRECOVERY
+static void yy_parse_failed(
+  yyParser *yypParser           /* The parser */
+){
+  ParseARG_FETCH;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+  }
+#endif
+  while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+  /* Here code is inserted which will be executed whenever the
+  ** parser fails */
+  ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+#endif /* YYNOERRORRECOVERY */
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+  yyParser *yypParser,           /* The parser */
+  int yymajor,                   /* The major type of the error token */
+  YYMINORTYPE yyminor            /* The minor type of the error token */
+){
+  ParseARG_FETCH;
+#define TOKEN (yyminor.yy0)
+#line 125 "NCDConfigParser_parse.y"
+
+    parser_out->syntax_error = 1;
+#line 1701 "NCDConfigParser_parse.c"
+  ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+  yyParser *yypParser           /* The parser */
+){
+  ParseARG_FETCH;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+  }
+#endif
+  while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+  /* Here code is inserted which will be executed whenever the
+  ** parser accepts */
+  ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "ParseAlloc" which describes the current state of the parser.
+** The second argument is the major token number.  The third is
+** the minor token.  The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void Parse(
+  void *yyp,                   /* The parser */
+  int yymajor,                 /* The major token code number */
+  ParseTOKENTYPE yyminor       /* The value for the token */
+  ParseARG_PDECL               /* Optional %extra_argument parameter */
+){
+  YYMINORTYPE yyminorunion;
+  int yyact;            /* The parser action. */
+  int yyendofinput;     /* True if we are at the end of input */
+#ifdef YYERRORSYMBOL
+  int yyerrorhit = 0;   /* True if yymajor has invoked an error */
+#endif
+  yyParser *yypParser;  /* The parser */
+
+  /* (re)initialize the parser, if necessary */
+  yypParser = (yyParser*)yyp;
+  if( yypParser->yyidx<0 ){
+#if YYSTACKDEPTH<=0
+    if( yypParser->yystksz <=0 ){
+      /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/
+      yyminorunion = yyzerominor;
+      yyStackOverflow(yypParser, &yyminorunion);
+      return;
+    }
+#endif
+    yypParser->yyidx = 0;
+    yypParser->yyerrcnt = -1;
+    yypParser->yystack[0].stateno = 0;
+    yypParser->yystack[0].major = 0;
+  }
+  yyminorunion.yy0 = yyminor;
+  yyendofinput = (yymajor==0);
+  ParseARG_STORE;
+
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+  }
+#endif
+
+  do{
+    yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor);
+    if( yyact<YYNSTATE ){
+      assert( !yyendofinput );  /* Impossible to shift the $ token */
+      yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+      yypParser->yyerrcnt--;
+      yymajor = YYNOCODE;
+    }else if( yyact < YYNSTATE + YYNRULE ){
+      yy_reduce(yypParser,yyact-YYNSTATE);
+    }else{
+      assert( yyact == YY_ERROR_ACTION );
+#ifdef YYERRORSYMBOL
+      int yymx;
+#endif
+#ifndef NDEBUG
+      if( yyTraceFILE ){
+        fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+      }
+#endif
+#ifdef YYERRORSYMBOL
+      /* A syntax error has occurred.
+      ** The response to an error depends upon whether or not the
+      ** grammar defines an error token "ERROR".  
+      **
+      ** This is what we do if the grammar does define ERROR:
+      **
+      **  * Call the %syntax_error function.
+      **
+      **  * Begin popping the stack until we enter a state where
+      **    it is legal to shift the error symbol, then shift
+      **    the error symbol.
+      **
+      **  * Set the error count to three.
+      **
+      **  * Begin accepting and shifting new tokens.  No new error
+      **    processing will occur until three tokens have been
+      **    shifted successfully.
+      **
+      */
+      if( yypParser->yyerrcnt<0 ){
+        yy_syntax_error(yypParser,yymajor,yyminorunion);
+      }
+      yymx = yypParser->yystack[yypParser->yyidx].major;
+      if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+             yyTracePrompt,yyTokenName[yymajor]);
+        }
+#endif
+        yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion);
+        yymajor = YYNOCODE;
+      }else{
+         while(
+          yypParser->yyidx >= 0 &&
+          yymx != YYERRORSYMBOL &&
+          (yyact = yy_find_reduce_action(
+                        yypParser->yystack[yypParser->yyidx].stateno,
+                        YYERRORSYMBOL)) >= YYNSTATE
+        ){
+          yy_pop_parser_stack(yypParser);
+        }
+        if( yypParser->yyidx < 0 || yymajor==0 ){
+          yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+          yy_parse_failed(yypParser);
+          yymajor = YYNOCODE;
+        }else if( yymx!=YYERRORSYMBOL ){
+          YYMINORTYPE u2;
+          u2.YYERRSYMDT = 0;
+          yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
+        }
+      }
+      yypParser->yyerrcnt = 3;
+      yyerrorhit = 1;
+#elif defined(YYNOERRORRECOVERY)
+      /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to
+      ** do any kind of error recovery.  Instead, simply invoke the syntax
+      ** error routine and continue going as if nothing had happened.
+      **
+      ** Applications can set this macro (for example inside %include) if
+      ** they intend to abandon the parse upon the first syntax error seen.
+      */
+      yy_syntax_error(yypParser,yymajor,yyminorunion);
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      yymajor = YYNOCODE;
+      
+#else  /* YYERRORSYMBOL is not defined */
+      /* This is what we do if the grammar does not define ERROR:
+      **
+      **  * Report an error message, and throw away the input token.
+      **
+      **  * If the input token is $, then fail the parse.
+      **
+      ** As before, subsequent error messages are suppressed until
+      ** three input tokens have been successfully shifted.
+      */
+      if( yypParser->yyerrcnt<=0 ){
+        yy_syntax_error(yypParser,yymajor,yyminorunion);
+      }
+      yypParser->yyerrcnt = 3;
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      if( yyendofinput ){
+        yy_parse_failed(yypParser);
+      }
+      yymajor = YYNOCODE;
+#endif
+    }
+  }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
+  return;
+}
diff --git a/external/badvpn_dns/generated/NCDConfigParser_parse.h b/external/badvpn_dns/generated/NCDConfigParser_parse.h
new file mode 100644
index 0000000..086a574
--- /dev/null
+++ b/external/badvpn_dns/generated/NCDConfigParser_parse.h
@@ -0,0 +1,22 @@
+#define INCLUDE                         1
+#define STRING                          2
+#define INCLUDE_GUARD                   3
+#define NAME                            4
+#define CURLY_OPEN                      5
+#define CURLY_CLOSE                     6
+#define ROUND_OPEN                      7
+#define ROUND_CLOSE                     8
+#define SEMICOLON                       9
+#define ARROW                          10
+#define IF                             11
+#define FOREACH                        12
+#define AS                             13
+#define COLON                          14
+#define ELIF                           15
+#define ELSE                           16
+#define DOT                            17
+#define COMMA                          18
+#define BRACKET_OPEN                   19
+#define BRACKET_CLOSE                  20
+#define PROCESS                        21
+#define TEMPLATE                       22
diff --git a/external/badvpn_dns/generated/NCDConfigParser_parse.out b/external/badvpn_dns/generated/NCDConfigParser_parse.out
new file mode 100644
index 0000000..bdf830b
--- /dev/null
+++ b/external/badvpn_dns/generated/NCDConfigParser_parse.out
@@ -0,0 +1,950 @@
+State 0:
+          input ::= * processes
+      (1) processes ::= *
+          processes ::= * INCLUDE STRING processes
+          processes ::= * INCLUDE_GUARD STRING processes
+          processes ::= * process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes
+          process_or_template ::= * PROCESS
+          process_or_template ::= * TEMPLATE
+
+                       INCLUDE shift  34
+                 INCLUDE_GUARD shift  35
+                       PROCESS shift  75
+                      TEMPLATE shift  76
+                     processes shift  33
+           process_or_template shift  36
+                         input accept
+                     {default} reduce 1
+
+State 1:
+          statement ::= dotted_name ROUND_OPEN * statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+     (20) statement_args_maybe ::= *
+          statement_args_maybe ::= * list_contents
+          list_contents ::= * value
+          list_contents ::= * value COMMA list_contents
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                   dotted_name shift  86
+          statement_args_maybe shift  39
+                 list_contents shift  80
+                          list shift  87
+                           map shift  88
+                         value shift  42
+                     {default} reduce 20
+
+State 2:
+          statement ::= dotted_name ARROW dotted_name ROUND_OPEN * statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+     (20) statement_args_maybe ::= *
+          statement_args_maybe ::= * list_contents
+          list_contents ::= * value
+          list_contents ::= * value COMMA list_contents
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                   dotted_name shift  86
+          statement_args_maybe shift  48
+                 list_contents shift  80
+                          list shift  87
+                           map shift  88
+                         value shift  42
+                     {default} reduce 20
+
+State 3:
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+          list_contents ::= * value
+          list_contents ::= * value COMMA list_contents
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= CURLY_OPEN * CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          list ::= CURLY_OPEN * list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                   CURLY_CLOSE shift  82
+                  BRACKET_OPEN shift  4
+                   dotted_name shift  86
+                 list_contents shift  43
+                          list shift  87
+                           map shift  88
+                         value shift  42
+
+State 4:
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map_contents ::= * value COLON value
+          map_contents ::= * value COLON value COMMA map_contents
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= BRACKET_OPEN * BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          map ::= BRACKET_OPEN * map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                 BRACKET_CLOSE shift  89
+                   dotted_name shift  86
+                          list shift  87
+                  map_contents shift  46
+                           map shift  88
+                         value shift  44
+
+State 5:
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+          list_contents ::= * value
+          list_contents ::= * value COMMA list_contents
+          list_contents ::= value COMMA * list_contents
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                   dotted_name shift  86
+                 list_contents shift  81
+                          list shift  87
+                           map shift  88
+                         value shift  42
+
+State 6:
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map_contents ::= * value COLON value
+          map_contents ::= * value COLON value COMMA map_contents
+          map_contents ::= value COLON value COMMA * map_contents
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                   dotted_name shift  86
+                          list shift  87
+                  map_contents shift  84
+                           map shift  88
+                         value shift  44
+
+State 7:
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map_contents ::= value COLON * value
+          map_contents ::= value COLON * value COMMA map_contents
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                   dotted_name shift  86
+                          list shift  87
+                           map shift  88
+                         value shift  45
+
+State 8:
+          statement ::= IF ROUND_OPEN * value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                   dotted_name shift  86
+                          list shift  87
+                           map shift  88
+                         value shift  51
+
+State 9:
+          statement ::= FOREACH ROUND_OPEN * value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= FOREACH ROUND_OPEN * value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                   dotted_name shift  86
+                          list shift  87
+                           map shift  88
+                         value shift  57
+
+State 10:
+          elif ::= ELIF ROUND_OPEN * value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE
+          elif ::= ELIF ROUND_OPEN * value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * dotted_name
+          value ::= * list
+          value ::= * map
+
+                        STRING shift  85
+                          NAME shift  41
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                   dotted_name shift  86
+                          list shift  87
+                           map shift  88
+                         value shift  69
+
+State 11:
+          processes ::= process_or_template NAME CURLY_OPEN * statements CURLY_CLOSE processes
+          statement ::= * dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statements ::= * statement
+          statements ::= * statement statements
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+
+                          NAME shift  41
+                            IF shift  50
+                       FOREACH shift  56
+                     statement shift  15
+                    statements shift  38
+                   dotted_name shift  31
+
+State 12:
+          statement ::= * dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+          statement ::= IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN * statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statements ::= * statement
+          statements ::= * statement statements
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+
+                          NAME shift  41
+                            IF shift  50
+                       FOREACH shift  56
+                     statement shift  15
+                    statements shift  53
+                   dotted_name shift  31
+
+State 13:
+          statement ::= * dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          else_maybe ::= ELSE CURLY_OPEN * statements CURLY_CLOSE
+          statements ::= * statement
+          statements ::= * statement statements
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+
+                          NAME shift  41
+                            IF shift  50
+                       FOREACH shift  56
+                     statement shift  15
+                    statements shift  67
+                   dotted_name shift  31
+
+State 14:
+          statement ::= * dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN * statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statements ::= * statement
+          statements ::= * statement statements
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+
+                          NAME shift  41
+                            IF shift  50
+                       FOREACH shift  56
+                     statement shift  15
+                    statements shift  60
+                   dotted_name shift  31
+
+State 15:
+          statement ::= * dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statements ::= * statement
+     (16) statements ::= statement *
+          statements ::= * statement statements
+          statements ::= statement * statements
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+
+                          NAME shift  41
+                            IF shift  50
+                       FOREACH shift  56
+                     statement shift  15
+                    statements shift  94
+                   dotted_name shift  31
+                     {default} reduce 16
+
+State 16:
+          statement ::= * dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN * statements CURLY_CLOSE name_maybe SEMICOLON
+          statements ::= * statement
+          statements ::= * statement statements
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+
+                          NAME shift  41
+                            IF shift  50
+                       FOREACH shift  56
+                     statement shift  15
+                    statements shift  65
+                   dotted_name shift  31
+
+State 17:
+          statement ::= * dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= * IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= * FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN * statements CURLY_CLOSE
+          elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN * statements CURLY_CLOSE elif
+          statements ::= * statement
+          statements ::= * statement statements
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+
+                          NAME shift  41
+                            IF shift  50
+                       FOREACH shift  56
+                     statement shift  15
+                    statements shift  71
+                   dotted_name shift  31
+
+State 18:
+      (1) processes ::= *
+          processes ::= * INCLUDE STRING processes
+          processes ::= INCLUDE STRING * processes
+          processes ::= * INCLUDE_GUARD STRING processes
+          processes ::= * process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes
+          process_or_template ::= * PROCESS
+          process_or_template ::= * TEMPLATE
+
+                       INCLUDE shift  34
+                 INCLUDE_GUARD shift  35
+                       PROCESS shift  75
+                      TEMPLATE shift  76
+                     processes shift  72
+           process_or_template shift  36
+                     {default} reduce 1
+
+State 19:
+      (1) processes ::= *
+          processes ::= * INCLUDE STRING processes
+          processes ::= * INCLUDE_GUARD STRING processes
+          processes ::= INCLUDE_GUARD STRING * processes
+          processes ::= * process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes
+          process_or_template ::= * PROCESS
+          process_or_template ::= * TEMPLATE
+
+                       INCLUDE shift  34
+                 INCLUDE_GUARD shift  35
+                       PROCESS shift  75
+                      TEMPLATE shift  76
+                     processes shift  73
+           process_or_template shift  36
+                     {default} reduce 1
+
+State 20:
+      (1) processes ::= *
+          processes ::= * INCLUDE STRING processes
+          processes ::= * INCLUDE_GUARD STRING processes
+          processes ::= * process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes
+          processes ::= process_or_template NAME CURLY_OPEN statements CURLY_CLOSE * processes
+          process_or_template ::= * PROCESS
+          process_or_template ::= * TEMPLATE
+
+                       INCLUDE shift  34
+                 INCLUDE_GUARD shift  35
+                       PROCESS shift  75
+                      TEMPLATE shift  76
+                     processes shift  74
+           process_or_template shift  36
+                     {default} reduce 1
+
+State 21:
+          statement ::= IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE * elif_maybe else_maybe name_maybe SEMICOLON
+     (10) elif_maybe ::= *
+          elif_maybe ::= * elif
+          elif ::= * ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE
+          elif ::= * ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif
+
+                          ELIF shift  68
+                    elif_maybe shift  26
+                          elif shift  97
+                     {default} reduce 10
+
+State 22:
+          statement ::= dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE * name_maybe SEMICOLON
+     (34) name_maybe ::= *
+          name_maybe ::= * NAME
+
+                          NAME shift  78
+                    name_maybe shift  40
+                     {default} reduce 34
+
+State 23:
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+          dotted_name ::= NAME DOT * dotted_name
+
+                          NAME shift  41
+                   dotted_name shift  79
+
+State 24:
+          statement ::= dotted_name ARROW * dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          dotted_name ::= * NAME
+          dotted_name ::= * NAME DOT dotted_name
+
+                          NAME shift  41
+                   dotted_name shift  47
+
+State 25:
+          statement ::= dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE * name_maybe SEMICOLON
+     (34) name_maybe ::= *
+          name_maybe ::= * NAME
+
+                          NAME shift  78
+                    name_maybe shift  49
+                     {default} reduce 34
+
+State 26:
+          statement ::= IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe * else_maybe name_maybe SEMICOLON
+     (14) else_maybe ::= *
+          else_maybe ::= * ELSE CURLY_OPEN statements CURLY_CLOSE
+
+                          ELSE shift  55
+                    else_maybe shift  27
+                     {default} reduce 14
+
+State 27:
+          statement ::= IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe * name_maybe SEMICOLON
+     (34) name_maybe ::= *
+          name_maybe ::= * NAME
+
+                          NAME shift  78
+                    name_maybe shift  54
+                     {default} reduce 34
+
+State 28:
+          statement ::= FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE * name_maybe SEMICOLON
+     (34) name_maybe ::= *
+          name_maybe ::= * NAME
+
+                          NAME shift  78
+                    name_maybe shift  61
+                     {default} reduce 34
+
+State 29:
+          statement ::= FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE * name_maybe SEMICOLON
+     (34) name_maybe ::= *
+          name_maybe ::= * NAME
+
+                          NAME shift  78
+                    name_maybe shift  66
+                     {default} reduce 34
+
+State 30:
+          elif ::= * ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE
+     (12) elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE *
+          elif ::= * ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif
+          elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE * elif
+
+                          ELIF shift  68
+                          elif shift  98
+                     {default} reduce 12
+
+State 31:
+          statement ::= dotted_name * ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+          statement ::= dotted_name * ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+
+                    ROUND_OPEN shift  1
+                         ARROW shift  24
+
+State 32:
+          statement ::= FOREACH ROUND_OPEN value AS NAME * ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= FOREACH ROUND_OPEN value AS NAME * COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+
+                   ROUND_CLOSE shift  59
+                         COLON shift  62
+
+State 33:
+      (0) input ::= processes *
+
+                             $ reduce 0
+
+State 34:
+          processes ::= INCLUDE * STRING processes
+
+                        STRING shift  18
+
+State 35:
+          processes ::= INCLUDE_GUARD * STRING processes
+
+                        STRING shift  19
+
+State 36:
+          processes ::= process_or_template * NAME CURLY_OPEN statements CURLY_CLOSE processes
+
+                          NAME shift  37
+
+State 37:
+          processes ::= process_or_template NAME * CURLY_OPEN statements CURLY_CLOSE processes
+
+                    CURLY_OPEN shift  11
+
+State 38:
+          processes ::= process_or_template NAME CURLY_OPEN statements * CURLY_CLOSE processes
+
+                   CURLY_CLOSE shift  20
+
+State 39:
+          statement ::= dotted_name ROUND_OPEN statement_args_maybe * ROUND_CLOSE name_maybe SEMICOLON
+
+                   ROUND_CLOSE shift  22
+
+State 40:
+          statement ::= dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe * SEMICOLON
+
+                     SEMICOLON shift  77
+
+State 41:
+     (18) dotted_name ::= NAME *
+          dotted_name ::= NAME * DOT dotted_name
+
+                           DOT shift  23
+                     {default} reduce 18
+
+State 42:
+     (22) list_contents ::= value *
+          list_contents ::= value * COMMA list_contents
+
+                         COMMA shift  5
+                     {default} reduce 22
+
+State 43:
+          list ::= CURLY_OPEN list_contents * CURLY_CLOSE
+
+                   CURLY_CLOSE shift  83
+
+State 44:
+          map_contents ::= value * COLON value
+          map_contents ::= value * COLON value COMMA map_contents
+
+                         COLON shift  7
+
+State 45:
+     (26) map_contents ::= value COLON value *
+          map_contents ::= value COLON value * COMMA map_contents
+
+                         COMMA shift  6
+                     {default} reduce 26
+
+State 46:
+          map ::= BRACKET_OPEN map_contents * BRACKET_CLOSE
+
+                 BRACKET_CLOSE shift  90
+
+State 47:
+          statement ::= dotted_name ARROW dotted_name * ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
+
+                    ROUND_OPEN shift  2
+
+State 48:
+          statement ::= dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe * ROUND_CLOSE name_maybe SEMICOLON
+
+                   ROUND_CLOSE shift  25
+
+State 49:
+          statement ::= dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe * SEMICOLON
+
+                     SEMICOLON shift  91
+
+State 50:
+          statement ::= IF * ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+
+                    ROUND_OPEN shift  8
+
+State 51:
+          statement ::= IF ROUND_OPEN value * ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+
+                   ROUND_CLOSE shift  52
+
+State 52:
+          statement ::= IF ROUND_OPEN value ROUND_CLOSE * CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+
+                    CURLY_OPEN shift  12
+
+State 53:
+          statement ::= IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements * CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON
+
+                   CURLY_CLOSE shift  21
+
+State 54:
+          statement ::= IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe * SEMICOLON
+
+                     SEMICOLON shift  92
+
+State 55:
+          else_maybe ::= ELSE * CURLY_OPEN statements CURLY_CLOSE
+
+                    CURLY_OPEN shift  13
+
+State 56:
+          statement ::= FOREACH * ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= FOREACH * ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+
+                    ROUND_OPEN shift  9
+
+State 57:
+          statement ::= FOREACH ROUND_OPEN value * AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= FOREACH ROUND_OPEN value * AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+
+                            AS shift  58
+
+State 58:
+          statement ::= FOREACH ROUND_OPEN value AS * NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+          statement ::= FOREACH ROUND_OPEN value AS * NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+
+                          NAME shift  32
+
+State 59:
+          statement ::= FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE * CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+
+                    CURLY_OPEN shift  14
+
+State 60:
+          statement ::= FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements * CURLY_CLOSE name_maybe SEMICOLON
+
+                   CURLY_CLOSE shift  28
+
+State 61:
+          statement ::= FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe * SEMICOLON
+
+                     SEMICOLON shift  93
+
+State 62:
+          statement ::= FOREACH ROUND_OPEN value AS NAME COLON * NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+
+                          NAME shift  63
+
+State 63:
+          statement ::= FOREACH ROUND_OPEN value AS NAME COLON NAME * ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+
+                   ROUND_CLOSE shift  64
+
+State 64:
+          statement ::= FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE * CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON
+
+                    CURLY_OPEN shift  16
+
+State 65:
+          statement ::= FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements * CURLY_CLOSE name_maybe SEMICOLON
+
+                   CURLY_CLOSE shift  29
+
+State 66:
+          statement ::= FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe * SEMICOLON
+
+                     SEMICOLON shift  95
+
+State 67:
+          else_maybe ::= ELSE CURLY_OPEN statements * CURLY_CLOSE
+
+                   CURLY_CLOSE shift  96
+
+State 68:
+          elif ::= ELIF * ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE
+          elif ::= ELIF * ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif
+
+                    ROUND_OPEN shift  10
+
+State 69:
+          elif ::= ELIF ROUND_OPEN value * ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE
+          elif ::= ELIF ROUND_OPEN value * ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif
+
+                   ROUND_CLOSE shift  70
+
+State 70:
+          elif ::= ELIF ROUND_OPEN value ROUND_CLOSE * CURLY_OPEN statements CURLY_CLOSE
+          elif ::= ELIF ROUND_OPEN value ROUND_CLOSE * CURLY_OPEN statements CURLY_CLOSE elif
+
+                    CURLY_OPEN shift  17
+
+State 71:
+          elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements * CURLY_CLOSE
+          elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements * CURLY_CLOSE elif
+
+                   CURLY_CLOSE shift  30
+
+State 72:
+      (2) processes ::= INCLUDE STRING processes *
+
+                     {default} reduce 2
+
+State 73:
+      (3) processes ::= INCLUDE_GUARD STRING processes *
+
+                     {default} reduce 3
+
+State 74:
+      (4) processes ::= process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes *
+
+                     {default} reduce 4
+
+State 75:
+     (36) process_or_template ::= PROCESS *
+
+                     {default} reduce 36
+
+State 76:
+     (37) process_or_template ::= TEMPLATE *
+
+                     {default} reduce 37
+
+State 77:
+      (5) statement ::= dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON *
+
+                     {default} reduce 5
+
+State 78:
+     (35) name_maybe ::= NAME *
+
+                     {default} reduce 35
+
+State 79:
+     (19) dotted_name ::= NAME DOT dotted_name *
+
+                     {default} reduce 19
+
+State 80:
+     (21) statement_args_maybe ::= list_contents *
+
+                     {default} reduce 21
+
+State 81:
+     (23) list_contents ::= value COMMA list_contents *
+
+                     {default} reduce 23
+
+State 82:
+     (24) list ::= CURLY_OPEN CURLY_CLOSE *
+
+                     {default} reduce 24
+
+State 83:
+     (25) list ::= CURLY_OPEN list_contents CURLY_CLOSE *
+
+                     {default} reduce 25
+
+State 84:
+     (27) map_contents ::= value COLON value COMMA map_contents *
+
+                     {default} reduce 27
+
+State 85:
+     (30) value ::= STRING *
+
+                     {default} reduce 30
+
+State 86:
+     (31) value ::= dotted_name *
+
+                     {default} reduce 31
+
+State 87:
+     (32) value ::= list *
+
+                     {default} reduce 32
+
+State 88:
+     (33) value ::= map *
+
+                     {default} reduce 33
+
+State 89:
+     (28) map ::= BRACKET_OPEN BRACKET_CLOSE *
+
+                     {default} reduce 28
+
+State 90:
+     (29) map ::= BRACKET_OPEN map_contents BRACKET_CLOSE *
+
+                     {default} reduce 29
+
+State 91:
+      (6) statement ::= dotted_name ARROW dotted_name ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON *
+
+                     {default} reduce 6
+
+State 92:
+      (7) statement ::= IF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif_maybe else_maybe name_maybe SEMICOLON *
+
+                     {default} reduce 7
+
+State 93:
+      (8) statement ::= FOREACH ROUND_OPEN value AS NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON *
+
+                     {default} reduce 8
+
+State 94:
+     (17) statements ::= statement statements *
+
+                     {default} reduce 17
+
+State 95:
+      (9) statement ::= FOREACH ROUND_OPEN value AS NAME COLON NAME ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE name_maybe SEMICOLON *
+
+                     {default} reduce 9
+
+State 96:
+     (15) else_maybe ::= ELSE CURLY_OPEN statements CURLY_CLOSE *
+
+                     {default} reduce 15
+
+State 97:
+     (11) elif_maybe ::= elif *
+
+                     {default} reduce 11
+
+State 98:
+     (13) elif ::= ELIF ROUND_OPEN value ROUND_CLOSE CURLY_OPEN statements CURLY_CLOSE elif *
+
+                     {default} reduce 13
+
+----------------------------------------------------
+Symbols:
+    0: $:
+    1: INCLUDE
+    2: STRING
+    3: INCLUDE_GUARD
+    4: NAME
+    5: CURLY_OPEN
+    6: CURLY_CLOSE
+    7: ROUND_OPEN
+    8: ROUND_CLOSE
+    9: SEMICOLON
+   10: ARROW
+   11: IF
+   12: FOREACH
+   13: AS
+   14: COLON
+   15: ELIF
+   16: ELSE
+   17: DOT
+   18: COMMA
+   19: BRACKET_OPEN
+   20: BRACKET_CLOSE
+   21: PROCESS
+   22: TEMPLATE
+   23: error:
+   24: processes: <lambda> INCLUDE INCLUDE_GUARD PROCESS TEMPLATE
+   25: statement: NAME IF FOREACH
+   26: elif_maybe: <lambda> ELIF
+   27: elif: ELIF
+   28: else_maybe: <lambda> ELSE
+   29: statements: NAME IF FOREACH
+   30: dotted_name: NAME
+   31: statement_args_maybe: <lambda> STRING NAME CURLY_OPEN BRACKET_OPEN
+   32: list_contents: STRING NAME CURLY_OPEN BRACKET_OPEN
+   33: list: CURLY_OPEN
+   34: map_contents: STRING NAME CURLY_OPEN BRACKET_OPEN
+   35: map: BRACKET_OPEN
+   36: value: STRING NAME CURLY_OPEN BRACKET_OPEN
+   37: name_maybe: <lambda> NAME
+   38: process_or_template: PROCESS TEMPLATE
+   39: input: INCLUDE INCLUDE_GUARD PROCESS TEMPLATE
diff --git a/external/badvpn_dns/generated/NCDConfigParser_parse.y b/external/badvpn_dns/generated/NCDConfigParser_parse.y
new file mode 100644
index 0000000..fdf89f6
--- /dev/null
+++ b/external/badvpn_dns/generated/NCDConfigParser_parse.y
@@ -0,0 +1,718 @@
+/**
+ * @file NCDConfigParser.y
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 {
+
+#include <string.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <misc/concat_strings.h>
+#include <ncd/NCDAst.h>
+
+struct parser_out {
+    int out_of_memory;
+    int syntax_error;
+    int have_ast;
+    NCDProgram ast;
+};
+
+struct token {
+    char *str;
+    size_t len;
+};
+
+struct program {
+    int have;
+    NCDProgram v;
+};
+
+struct block {
+    int have;
+    NCDBlock v;
+};
+
+struct statement {
+    int have;
+    NCDStatement v;
+};
+
+struct ifblock {
+    int have;
+    NCDIfBlock v;
+};
+
+struct value {
+    int have;
+    NCDValue v;
+};
+
+static void free_token (struct token o) { free(o.str); }
+static void free_program (struct program o) { if (o.have) NCDProgram_Free(&o.v); }
+static void free_block (struct block o) { if (o.have) NCDBlock_Free(&o.v); }
+static void free_statement (struct statement o) { if (o.have) NCDStatement_Free(&o.v); }
+static void free_ifblock (struct ifblock o) { if (o.have) NCDIfBlock_Free(&o.v); }
+static void free_value (struct value o) { if (o.have) NCDValue_Free(&o.v); }
+
+}
+
+%extra_argument { struct parser_out *parser_out }
+
+%token_type { struct token }
+
+%token_destructor { free_token($$); }
+
+%type processes { struct program }
+%type statement { struct statement }
+%type elif_maybe { struct ifblock }
+%type elif { struct ifblock }
+%type else_maybe { struct block }
+%type statements { struct block }
+%type dotted_name { char * }
+%type statement_args_maybe { struct value }
+%type list_contents { struct value }
+%type list { struct value }
+%type map_contents { struct value }
+%type map  { struct value }
+%type value  { struct value }
+%type name_maybe { char * }
+%type process_or_template { int }
+
+// mention parser_out in some destructor to a void unused variable warning
+%destructor processes { (void)parser_out; free_program($$); }
+%destructor statement { free_statement($$); }
+%destructor elif_maybe { free_ifblock($$); }
+%destructor elif { free_ifblock($$); }
+%destructor else_maybe { free_block($$); }
+%destructor statements { free_block($$); }
+%destructor dotted_name { free($$); }
+%destructor statement_args_maybe { free_value($$); }
+%destructor list_contents { free_value($$); }
+%destructor list { free_value($$); }
+%destructor map_contents { free_value($$); }
+%destructor map { free_value($$); }
+%destructor value { free_value($$); }
+%destructor name_maybe { free($$); }
+
+%stack_size 0
+
+%syntax_error {
+    parser_out->syntax_error = 1;
+}
+
+// workaroud Lemon bug: if the stack overflows, the token that caused the overflow will be leaked
+%stack_overflow {
+    if (yypMinor) {
+        free_token(yypMinor->yy0);
+    }
+}
+
+input ::= processes(A). {
+    ASSERT(!parser_out->have_ast)
+
+    if (A.have) {
+        parser_out->have_ast = 1;
+        parser_out->ast = A.v;
+    }
+}
+
+processes(R) ::= . {
+    NCDProgram prog;
+    NCDProgram_Init(&prog);
+    
+    R.have = 1;
+    R.v = prog;
+}
+
+processes(R) ::= INCLUDE STRING(A) processes(N). {
+    ASSERT(A.str)
+    if (!N.have) {
+        goto failA0;
+    }
+    
+    NCDProgramElem elem;
+    if (!NCDProgramElem_InitInclude(&elem, A.str, A.len)) {
+        goto failA0;
+    }
+    
+    if (!NCDProgram_PrependElem(&N.v, elem)) {
+        goto failA1;
+    }
+    
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneA;
+
+failA1:
+    NCDProgramElem_Free(&elem);
+failA0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneA:
+    free_token(A);
+    free_program(N);
+}
+
+processes(R) ::= INCLUDE_GUARD STRING(A) processes(N). {
+    ASSERT(A.str)
+    if (!N.have) {
+        goto failZ0;
+    }
+    
+    NCDProgramElem elem;
+    if (!NCDProgramElem_InitIncludeGuard(&elem, A.str, A.len)) {
+        goto failZ0;
+    }
+    
+    if (!NCDProgram_PrependElem(&N.v, elem)) {
+        goto failZ1;
+    }
+    
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneZ;
+
+failZ1:
+    NCDProgramElem_Free(&elem);
+failZ0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneZ:
+    free_token(A);
+    free_program(N);
+}
+
+processes(R) ::= process_or_template(T) NAME(A) CURLY_OPEN statements(B) CURLY_CLOSE processes(N). {
+    ASSERT(A.str)
+    if (!B.have || !N.have) {
+        goto failB0;
+    }
+
+    NCDProcess proc;
+    if (!NCDProcess_Init(&proc, T, A.str, B.v)) {
+        goto failB0;
+    }
+    B.have = 0;
+    
+    NCDProgramElem elem;
+    NCDProgramElem_InitProcess(&elem, proc);
+
+    if (!NCDProgram_PrependElem(&N.v, elem)) {
+        goto failB1;
+    }
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneB;
+
+failB1:
+    NCDProgramElem_Free(&elem);
+failB0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneB:
+    free_token(A);
+    free_block(B);
+    free_program(N);
+}
+
+statement(R) ::= dotted_name(A) ROUND_OPEN statement_args_maybe(B) ROUND_CLOSE name_maybe(C) SEMICOLON. {
+    if (!A || !B.have) {
+        goto failC0;
+    }
+
+    if (!NCDStatement_InitReg(&R.v, C, NULL, A, B.v)) {
+        goto failC0;
+    }
+    B.have = 0;
+
+    R.have = 1;
+    goto doneC;
+
+failC0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneC:
+    free(A);
+    free_value(B);
+    free(C);
+}
+
+statement(R) ::= dotted_name(M) ARROW dotted_name(A) ROUND_OPEN statement_args_maybe(B) ROUND_CLOSE name_maybe(C) SEMICOLON. {
+    if (!M || !A || !B.have) {
+        goto failD0;
+    }
+
+    if (!NCDStatement_InitReg(&R.v, C, M, A, B.v)) {
+        goto failD0;
+    }
+    B.have = 0;
+
+    R.have = 1;
+    goto doneD;
+
+failD0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneD:
+    free(M);
+    free(A);
+    free_value(B);
+    free(C);
+}
+
+statement(R) ::= IF ROUND_OPEN value(A) ROUND_CLOSE CURLY_OPEN statements(B) CURLY_CLOSE elif_maybe(I) else_maybe(E) name_maybe(C) SEMICOLON. {
+    if (!A.have || !B.have || !I.have) {
+        goto failE0;
+    }
+
+    NCDIf ifc;
+    NCDIf_Init(&ifc, A.v, B.v);
+    A.have = 0;
+    B.have = 0;
+
+    if (!NCDIfBlock_PrependIf(&I.v, ifc)) {
+        NCDIf_Free(&ifc);
+        goto failE0;
+    }
+
+    if (!NCDStatement_InitIf(&R.v, C, I.v)) {
+        goto failE0;
+    }
+    I.have = 0;
+
+    if (E.have) {
+        NCDStatement_IfAddElse(&R.v, E.v);
+        E.have = 0;
+    }
+
+    R.have = 1;
+    goto doneE;
+
+failE0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneE:
+    free_value(A);
+    free_block(B);
+    free_ifblock(I);
+    free_block(E);
+    free(C);
+}
+
+statement(R) ::= FOREACH ROUND_OPEN value(A) AS NAME(B) ROUND_CLOSE CURLY_OPEN statements(S) CURLY_CLOSE name_maybe(N) SEMICOLON. {
+    if (!A.have || !B.str || !S.have) {
+        goto failEA0;
+    }
+    
+    if (!NCDStatement_InitForeach(&R.v, N, A.v, B.str, NULL, S.v)) {
+        goto failEA0;
+    }
+    A.have = 0;
+    S.have = 0;
+    
+    R.have = 1;
+    goto doneEA0;
+    
+failEA0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneEA0:
+    free_value(A);
+    free_token(B);
+    free_block(S);
+    free(N);
+}
+
+statement(R) ::= FOREACH ROUND_OPEN value(A) AS NAME(B) COLON NAME(C) ROUND_CLOSE CURLY_OPEN statements(S) CURLY_CLOSE name_maybe(N) SEMICOLON. {
+    if (!A.have || !B.str || !C.str || !S.have) {
+        goto failEB0;
+    }
+    
+    if (!NCDStatement_InitForeach(&R.v, N, A.v, B.str, C.str, S.v)) {
+        goto failEB0;
+    }
+    A.have = 0;
+    S.have = 0;
+    
+    R.have = 1;
+    goto doneEB0;
+    
+failEB0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneEB0:
+    free_value(A);
+    free_token(B);
+    free_token(C);
+    free_block(S);
+    free(N);
+}
+
+elif_maybe(R) ::= . {
+    NCDIfBlock_Init(&R.v);
+    R.have = 1;
+}
+
+elif_maybe(R) ::= elif(A). {
+    R = A;
+}
+
+elif(R) ::= ELIF ROUND_OPEN value(A) ROUND_CLOSE CURLY_OPEN statements(B) CURLY_CLOSE. {
+    if (!A.have || !B.have) {
+        goto failF0;
+    }
+
+    NCDIfBlock_Init(&R.v);
+
+    NCDIf ifc;
+    NCDIf_Init(&ifc, A.v, B.v);
+    A.have = 0;
+    B.have = 0;
+
+    if (!NCDIfBlock_PrependIf(&R.v, ifc)) {
+        goto failF1;
+    }
+
+    R.have = 1;
+    goto doneF0;
+
+failF1:
+    NCDIf_Free(&ifc);
+    NCDIfBlock_Free(&R.v);
+failF0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneF0:
+    free_value(A);
+    free_block(B);
+}
+
+elif(R) ::= ELIF ROUND_OPEN value(A) ROUND_CLOSE CURLY_OPEN statements(B) CURLY_CLOSE elif(N). {
+    if (!A.have || !B.have || !N.have) {
+        goto failG0;
+    }
+
+    NCDIf ifc;
+    NCDIf_Init(&ifc, A.v, B.v);
+    A.have = 0;
+    B.have = 0;
+
+    if (!NCDIfBlock_PrependIf(&N.v, ifc)) {
+        goto failG1;
+    }
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneG0;
+
+failG1:
+    NCDIf_Free(&ifc);
+failG0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneG0:
+    free_value(A);
+    free_block(B);
+    free_ifblock(N);
+}
+
+else_maybe(R) ::= . {
+    R.have = 0;
+}
+
+else_maybe(R) ::= ELSE CURLY_OPEN statements(B) CURLY_CLOSE. {
+    R = B;
+}
+
+statements(R) ::= statement(A). {
+    if (!A.have) {
+        goto failH0;
+    }
+
+    NCDBlock_Init(&R.v);
+
+    if (!NCDBlock_PrependStatement(&R.v, A.v)) {
+        goto failH1;
+    }
+    A.have = 0;
+
+    R.have = 1;
+    goto doneH;
+
+failH1:
+    NCDBlock_Free(&R.v);
+failH0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneH:
+    free_statement(A);
+}
+
+statements(R) ::= statement(A) statements(N). {
+    if (!A.have || !N.have) {
+        goto failI0;
+    }
+
+    if (!NCDBlock_PrependStatement(&N.v, A.v)) {
+        goto failI1;
+    }
+    A.have = 0;
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneI;
+
+failI1:
+    NCDBlock_Free(&R.v);
+failI0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneI:
+    free_statement(A);
+    free_block(N);
+}
+
+dotted_name(R) ::= NAME(A). {
+    ASSERT(A.str)
+
+    R = A.str;
+}
+
+dotted_name(R) ::= NAME(A) DOT dotted_name(N). {
+    ASSERT(A.str)
+    if (!N) {
+        goto failJ0;
+    }
+
+    if (!(R = concat_strings(3, A.str, ".", N))) {
+        goto failJ0;
+    }
+
+    goto doneJ;
+
+failJ0:
+    R = NULL;
+    parser_out->out_of_memory = 1;
+doneJ:
+    free_token(A);
+    free(N);
+}
+
+statement_args_maybe(R) ::= . {
+    R.have = 1;
+    NCDValue_InitList(&R.v);
+}
+
+statement_args_maybe(R) ::= list_contents(A). {
+    R = A;
+}
+
+list_contents(R) ::= value(A). {
+    if (!A.have) {
+        goto failL0;
+    }
+
+    NCDValue_InitList(&R.v);
+
+    if (!NCDValue_ListPrepend(&R.v, A.v)) {
+        goto failL1;
+    }
+    A.have = 0;
+
+    R.have = 1;
+    goto doneL;
+
+failL1:
+    NCDValue_Free(&R.v);
+failL0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneL:
+    free_value(A);
+}
+
+list_contents(R) ::= value(A) COMMA list_contents(N). {
+    if (!A.have || !N.have) {
+        goto failM0;
+    }
+
+    if (!NCDValue_ListPrepend(&N.v, A.v)) {
+        goto failM0;
+    }
+    A.have = 0;
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneM;
+
+failM0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneM:
+    free_value(A);
+    free_value(N);
+}
+
+list(R) ::= CURLY_OPEN CURLY_CLOSE. {
+    R.have = 1;
+    NCDValue_InitList(&R.v);
+}
+
+list(R) ::= CURLY_OPEN list_contents(A) CURLY_CLOSE. {
+    R = A;
+}
+
+map_contents(R) ::= value(A) COLON value(B). {
+    if (!A.have || !B.have) {
+        goto failS0;
+    }
+
+    NCDValue_InitMap(&R.v);
+
+    if (!NCDValue_MapPrepend(&R.v, A.v, B.v)) {
+        goto failS1;
+    }
+    A.have = 0;
+    B.have = 0;
+
+    R.have = 1;
+    goto doneS;
+
+failS1:
+    NCDValue_Free(&R.v);
+failS0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneS:
+    free_value(A);
+    free_value(B);
+}
+
+map_contents(R) ::= value(A) COLON value(B) COMMA map_contents(N). {
+    if (!A.have || !B.have || !N.have) {
+        goto failT0;
+    }
+
+    if (!NCDValue_MapPrepend(&N.v, A.v, B.v)) {
+        goto failT0;
+    }
+    A.have = 0;
+    B.have = 0;
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneT;
+
+failT0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneT:
+    free_value(A);
+    free_value(B);
+    free_value(N);
+}
+
+map(R) ::= BRACKET_OPEN BRACKET_CLOSE. {
+    R.have = 1;
+    NCDValue_InitMap(&R.v);
+}
+
+map(R) ::= BRACKET_OPEN map_contents(A) BRACKET_CLOSE. {
+    R = A;
+}
+
+value(R) ::= STRING(A). {
+    ASSERT(A.str)
+
+    if (!NCDValue_InitStringBin(&R.v, (uint8_t *)A.str, A.len)) {
+        goto failU0;
+    }
+
+    R.have = 1;
+    goto doneU;
+
+failU0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneU:
+    free_token(A);
+}
+
+value(R) ::= dotted_name(A). {
+    if (!A) {
+        goto failV0;
+    }
+
+    if (!NCDValue_InitVar(&R.v, A)) {
+        goto failV0;
+    }
+
+    R.have = 1;
+    goto doneV;
+
+failV0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneV:
+    free(A);
+}
+
+value(R) ::= list(A). {
+    R = A;
+}
+
+value(R) ::= map(A). {
+    R = A;
+}
+
+name_maybe(R) ::= . {
+    R = NULL;
+}
+
+name_maybe(R) ::= NAME(A). {
+    ASSERT(A.str)
+
+    R = A.str;
+}
+
+process_or_template(R) ::= PROCESS. {
+    R = 0;
+}
+
+process_or_template(R) ::= TEMPLATE. {
+    R = 1;
+}
diff --git a/external/badvpn_dns/generated/NCDValParser_parse.c b/external/badvpn_dns/generated/NCDValParser_parse.c
new file mode 100644
index 0000000..f7039cc
--- /dev/null
+++ b/external/badvpn_dns/generated/NCDValParser_parse.c
@@ -0,0 +1,1119 @@
+/* Driver template for the LEMON parser generator.
+** The author disclaims copyright to this source code.
+*/
+/* First off, code is included that follows the "include" declaration
+** in the input grammar file. */
+#include <stdio.h>
+/* Next is all token values, in a form suitable for use by makeheaders.
+** This section will be null unless lemon is run with the -m switch.
+*/
+/* 
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands. 
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+/* Make sure the INTERFACE macro is defined.
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+**    YYCODETYPE         is the data type used for storing terminal
+**                       and nonterminal numbers.  "unsigned char" is
+**                       used if there are fewer than 250 terminals
+**                       and nonterminals.  "int" is used otherwise.
+**    YYNOCODE           is a number of type YYCODETYPE which corresponds
+**                       to no legal terminal or nonterminal number.  This
+**                       number is used to fill in empty slots of the hash 
+**                       table.
+**    YYFALLBACK         If defined, this indicates that one or more tokens
+**                       have fall-back values which should be used if the
+**                       original value of the token will not parse.
+**    YYACTIONTYPE       is the data type used for storing terminal
+**                       and nonterminal numbers.  "unsigned char" is
+**                       used if there are fewer than 250 rules and
+**                       states combined.  "int" is used otherwise.
+**    ParseTOKENTYPE     is the data type used for minor tokens given 
+**                       directly to the parser from the tokenizer.
+**    YYMINORTYPE        is the data type used for all minor tokens.
+**                       This is typically a union of many types, one of
+**                       which is ParseTOKENTYPE.  The entry in the union
+**                       for base tokens is called "yy0".
+**    YYSTACKDEPTH       is the maximum depth of the parser's stack.  If
+**                       zero the stack is dynamically sized using realloc()
+**    ParseARG_SDECL     A static variable declaration for the %extra_argument
+**    ParseARG_PDECL     A parameter declaration for the %extra_argument
+**    ParseARG_STORE     Code to store %extra_argument into yypParser
+**    ParseARG_FETCH     Code to extract %extra_argument from yypParser
+**    YYNSTATE           the combined number of states.
+**    YYNRULE            the number of rules in the grammar
+**    YYERRORSYMBOL      is the code number of the error symbol.  If not
+**                       defined, then do no error processing.
+*/
+#define YYCODETYPE unsigned char
+#define YYNOCODE 16
+#define YYACTIONTYPE unsigned char
+#define ParseTOKENTYPE  struct token 
+typedef union {
+  int yyinit;
+  ParseTOKENTYPE yy0;
+  struct value yy1;
+} YYMINORTYPE;
+#ifndef YYSTACKDEPTH
+#define YYSTACKDEPTH 0
+#endif
+#define ParseARG_SDECL  struct parser_state *parser_out ;
+#define ParseARG_PDECL , struct parser_state *parser_out 
+#define ParseARG_FETCH  struct parser_state *parser_out  = yypParser->parser_out 
+#define ParseARG_STORE yypParser->parser_out  = parser_out 
+#define YYNSTATE 21
+#define YYNRULE 12
+#define YY_NO_ACTION      (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION  (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION   (YYNSTATE+YYNRULE)
+
+/* The yyzerominor constant is used to initialize instances of
+** YYMINORTYPE objects to zero. */
+static const YYMINORTYPE yyzerominor = { 0 };
+
+/* Define the yytestcase() macro to be a no-op if is not already defined
+** otherwise.
+**
+** Applications can choose to define yytestcase() in the %include section
+** to a macro that can assist in verifying code coverage.  For production
+** code the yytestcase() macro should be turned off.  But it is useful
+** for testing.
+*/
+#ifndef yytestcase
+# define yytestcase(X)
+#endif
+
+
+/* Next are the tables used to determine what action to take based on the
+** current state and lookahead token.  These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.  
+**
+** Suppose the action integer is N.  Then the action is determined as
+** follows
+**
+**   0 <= N < YYNSTATE                  Shift N.  That is, push the lookahead
+**                                      token onto the stack and goto state N.
+**
+**   YYNSTATE <= N < YYNSTATE+YYNRULE   Reduce by rule N-YYNSTATE.
+**
+**   N == YYNSTATE+YYNRULE              A syntax error has occurred.
+**
+**   N == YYNSTATE+YYNRULE+1            The parser accepts its input.
+**
+**   N == YYNSTATE+YYNRULE+2            No such action.  Denotes unused
+**                                      slots in the yy_action[] table.
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as
+**
+**      yy_action[ yy_shift_ofst[S] + X ]
+**
+** If the index value yy_shift_ofst[S]+X is out of range or if the value
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
+** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
+** and that yy_default[S] should be used instead.  
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol.  If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
+** YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+**  yy_action[]        A single table containing all actions.
+**  yy_lookahead[]     A table containing the lookahead for each entry in
+**                     yy_action.  Used to detect hash collisions.
+**  yy_shift_ofst[]    For each state, the offset into yy_action for
+**                     shifting terminals.
+**  yy_reduce_ofst[]   For each state, the offset into yy_action for
+**                     shifting non-terminals after a reduce.
+**  yy_default[]       Default action for each state.
+*/
+static const YYACTIONTYPE yy_action[] = {
+ /*     0 */    15,   21,   16,    6,   34,    1,   19,    3,    2,   15,
+ /*    10 */    14,   16,    9,   11,   15,    5,   16,    7,   18,    1,
+ /*    20 */    35,   20,    2,   17,   14,   15,   10,   16,    8,   12,
+ /*    30 */    15,    4,   16,    7,   15,   13,   16,    8,    1,   35,
+ /*    40 */    35,    2,   35,   14,
+};
+static const YYCODETYPE yy_lookahead[] = {
+ /*     0 */    10,    0,   12,   13,   14,    2,    3,    1,    5,   10,
+ /*    10 */     7,   12,   13,    9,   10,    4,   12,   13,    6,    2,
+ /*    20 */    15,    3,    5,    6,    7,   10,   11,   12,   13,    9,
+ /*    30 */    10,    1,   12,   13,   10,   11,   12,   13,    2,   15,
+ /*    40 */    15,    5,   15,    7,
+};
+#define YY_SHIFT_USE_DFLT (-1)
+#define YY_SHIFT_MAX 11
+static const signed char yy_shift_ofst[] = {
+ /*     0 */    36,    3,   17,   36,   36,   36,    1,    6,   11,   30,
+ /*    10 */    12,   18,
+};
+#define YY_REDUCE_USE_DFLT (-11)
+#define YY_REDUCE_MAX 5
+static const signed char yy_reduce_ofst[] = {
+ /*     0 */   -10,    4,   15,   20,   24,   -1,
+};
+static const YYACTIONTYPE yy_default[] = {
+ /*     0 */    33,   33,   33,   33,   33,   33,   33,   22,   33,   26,
+ /*    10 */    33,   33,   23,   27,   30,   31,   32,   28,   29,   24,
+ /*    20 */    25,
+};
+#define YY_SZ_ACTTAB (int)(sizeof(yy_action)/sizeof(yy_action[0]))
+
+/* The next table maps tokens into fallback tokens.  If a construct
+** like the following:
+** 
+**      %fallback ID X Y Z.
+**
+** appears in the grammar, then ID becomes a fallback token for X, Y,
+** and Z.  Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack.  Information stored includes:
+**
+**   +  The state number for the parser at this level of the stack.
+**
+**   +  The value of the token stored at this level of the stack.
+**      (In other words, the "major" token.)
+**
+**   +  The semantic value stored at this level of the stack.  This is
+**      the information used by the action routines in the grammar.
+**      It is sometimes called the "minor" token.
+*/
+struct yyStackEntry {
+  YYACTIONTYPE stateno;  /* The state-number */
+  YYCODETYPE major;      /* The major token value.  This is the code
+                         ** number for the token at this stack level */
+  YYMINORTYPE minor;     /* The user-supplied minor token value.  This
+                         ** is the value of the token  */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+  int yyidx;                    /* Index of top element in stack */
+#ifdef YYTRACKMAXSTACKDEPTH
+  int yyidxMax;                 /* Maximum value of yyidx */
+#endif
+  int yyerrcnt;                 /* Shifts left before out of the error */
+  ParseARG_SDECL                /* A place to hold %extra_argument */
+#if YYSTACKDEPTH<=0
+  int yystksz;                  /* Current side of the stack */
+  yyStackEntry *yystack;        /* The parser's stack */
+#else
+  yyStackEntry yystack[YYSTACKDEPTH];  /* The parser's stack */
+#endif
+};
+typedef struct yyParser yyParser;
+
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* 
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message.  Tracing is turned off
+** by making either argument NULL 
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+**      If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+**      line of trace output.  If NULL, then tracing is
+**      turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
+  yyTraceFILE = TraceFILE;
+  yyTracePrompt = zTracePrompt;
+  if( yyTraceFILE==0 ) yyTracePrompt = 0;
+  else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required.  The following table supplies these names */
+static const char *const yyTokenName[] = { 
+  "$",             "COMMA",         "CURLY_OPEN",    "CURLY_CLOSE", 
+  "COLON",         "BRACKET_OPEN",  "BRACKET_CLOSE",  "STRING",      
+  "error",         "list_contents",  "list",          "map_contents",
+  "map",           "value",         "input",       
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *const yyRuleName[] = {
+ /*   0 */ "input ::= value",
+ /*   1 */ "list_contents ::= value",
+ /*   2 */ "list_contents ::= value COMMA list_contents",
+ /*   3 */ "list ::= CURLY_OPEN CURLY_CLOSE",
+ /*   4 */ "list ::= CURLY_OPEN list_contents CURLY_CLOSE",
+ /*   5 */ "map_contents ::= value COLON value",
+ /*   6 */ "map_contents ::= value COLON value COMMA map_contents",
+ /*   7 */ "map ::= BRACKET_OPEN BRACKET_CLOSE",
+ /*   8 */ "map ::= BRACKET_OPEN map_contents BRACKET_CLOSE",
+ /*   9 */ "value ::= STRING",
+ /*  10 */ "value ::= list",
+ /*  11 */ "value ::= map",
+};
+#endif /* NDEBUG */
+
+
+#if YYSTACKDEPTH<=0
+/*
+** Try to increase the size of the parser stack.
+*/
+static void yyGrowStack(yyParser *p){
+  int newSize;
+  yyStackEntry *pNew;
+
+  newSize = p->yystksz*2 + 100;
+  pNew = realloc(p->yystack, newSize*sizeof(pNew[0]));
+  if( pNew ){
+    p->yystack = pNew;
+    p->yystksz = newSize;
+#ifndef NDEBUG
+    if( yyTraceFILE ){
+      fprintf(yyTraceFILE,"%sStack grows to %d entries!\n",
+              yyTracePrompt, p->yystksz);
+    }
+#endif
+  }
+}
+#endif
+
+/* 
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser.  This pointer is used in subsequent calls
+** to Parse and ParseFree.
+*/
+void *ParseAlloc(void *(*mallocProc)(size_t)){
+  yyParser *pParser;
+  pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
+  if( pParser ){
+    pParser->yyidx = -1;
+#ifdef YYTRACKMAXSTACKDEPTH
+    pParser->yyidxMax = 0;
+#endif
+#if YYSTACKDEPTH<=0
+    pParser->yystack = NULL;
+    pParser->yystksz = 0;
+    yyGrowStack(pParser);
+#endif
+  }
+  return pParser;
+}
+
+/* The following function deletes the value associated with a
+** symbol.  The symbol can be either a terminal or nonterminal.
+** "yymajor" is the symbol code, and "yypminor" is a pointer to
+** the value.
+*/
+static void yy_destructor(
+  yyParser *yypParser,    /* The parser */
+  YYCODETYPE yymajor,     /* Type code for object to destroy */
+  YYMINORTYPE *yypminor   /* The object to be destroyed */
+){
+  ParseARG_FETCH;
+  switch( yymajor ){
+    /* Here is inserted the actions which take place when a
+    ** terminal or non-terminal is destroyed.  This can happen
+    ** when the symbol is popped from the stack during a
+    ** reduce or during error processing or when a parser is 
+    ** being destroyed before it is finished parsing.
+    **
+    ** Note: during a reduce, the only symbols destroyed are those
+    ** which appear on the RHS of the rule, but which are not used
+    ** inside the C code.
+    */
+      /* TERMINAL Destructor */
+    case 1: /* COMMA */
+    case 2: /* CURLY_OPEN */
+    case 3: /* CURLY_CLOSE */
+    case 4: /* COLON */
+    case 5: /* BRACKET_OPEN */
+    case 6: /* BRACKET_CLOSE */
+    case 7: /* STRING */
+{
+#line 37 "NCDValParser_parse.y"
+ free_token((yypminor->yy0)); 
+#line 377 "NCDValParser_parse.c"
+}
+      break;
+    case 9: /* list_contents */
+{
+#line 47 "NCDValParser_parse.y"
+ (void)parser_out; 
+#line 384 "NCDValParser_parse.c"
+}
+      break;
+    default:  break;   /* If no destructor action specified: do nothing */
+  }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+**
+** Return the major token number for the symbol popped.
+*/
+static int yy_pop_parser_stack(yyParser *pParser){
+  YYCODETYPE yymajor;
+  yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
+
+  if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+  if( yyTraceFILE && pParser->yyidx>=0 ){
+    fprintf(yyTraceFILE,"%sPopping %s\n",
+      yyTracePrompt,
+      yyTokenName[yytos->major]);
+  }
+#endif
+  yymajor = yytos->major;
+  yy_destructor(pParser, yymajor, &yytos->minor);
+  pParser->yyidx--;
+  return yymajor;
+}
+
+/* 
+** Deallocate and destroy a parser.  Destructors are all called for
+** all stack elements before shutting the parser down.
+**
+** Inputs:
+** <ul>
+** <li>  A pointer to the parser.  This should be a pointer
+**       obtained from ParseAlloc.
+** <li>  A pointer to a function used to reclaim memory obtained
+**       from malloc.
+** </ul>
+*/
+void ParseFree(
+  void *p,                    /* The parser to be deleted */
+  void (*freeProc)(void*)     /* Function used to reclaim memory */
+){
+  yyParser *pParser = (yyParser*)p;
+  if( pParser==0 ) return;
+  while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
+#if YYSTACKDEPTH<=0
+  free(pParser->yystack);
+#endif
+  (*freeProc)((void*)pParser);
+}
+
+/*
+** Return the peak depth of the stack for a parser.
+*/
+#ifdef YYTRACKMAXSTACKDEPTH
+int ParseStackPeak(void *p){
+  yyParser *pParser = (yyParser*)p;
+  return pParser->yyidxMax;
+}
+#endif
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead.  If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_shift_action(
+  yyParser *pParser,        /* The parser */
+  YYCODETYPE iLookAhead     /* The look-ahead token */
+){
+  int i;
+  int stateno = pParser->yystack[pParser->yyidx].stateno;
+ 
+  if( stateno>YY_SHIFT_MAX || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){
+    return yy_default[stateno];
+  }
+  assert( iLookAhead!=YYNOCODE );
+  i += iLookAhead;
+  if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+    if( iLookAhead>0 ){
+#ifdef YYFALLBACK
+      YYCODETYPE iFallback;            /* Fallback token */
+      if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+             && (iFallback = yyFallback[iLookAhead])!=0 ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+             yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+        }
+#endif
+        return yy_find_shift_action(pParser, iFallback);
+      }
+#endif
+#ifdef YYWILDCARD
+      {
+        int j = i - iLookAhead + YYWILDCARD;
+        if( j>=0 && j<YY_SZ_ACTTAB && yy_lookahead[j]==YYWILDCARD ){
+#ifndef NDEBUG
+          if( yyTraceFILE ){
+            fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n",
+               yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]);
+          }
+#endif /* NDEBUG */
+          return yy_action[j];
+        }
+      }
+#endif /* YYWILDCARD */
+    }
+    return yy_default[stateno];
+  }else{
+    return yy_action[i];
+  }
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead.  If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_reduce_action(
+  int stateno,              /* Current state number */
+  YYCODETYPE iLookAhead     /* The look-ahead token */
+){
+  int i;
+#ifdef YYERRORSYMBOL
+  if( stateno>YY_REDUCE_MAX ){
+    return yy_default[stateno];
+  }
+#else
+  assert( stateno<=YY_REDUCE_MAX );
+#endif
+  i = yy_reduce_ofst[stateno];
+  assert( i!=YY_REDUCE_USE_DFLT );
+  assert( iLookAhead!=YYNOCODE );
+  i += iLookAhead;
+#ifdef YYERRORSYMBOL
+  if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+    return yy_default[stateno];
+  }
+#else
+  assert( i>=0 && i<YY_SZ_ACTTAB );
+  assert( yy_lookahead[i]==iLookAhead );
+#endif
+  return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
+   ParseARG_FETCH;
+   yypParser->yyidx--;
+#ifndef NDEBUG
+   if( yyTraceFILE ){
+     fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+   }
+#endif
+   while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+   /* Here code is inserted which will execute if the parser
+   ** stack every overflows */
+#line 58 "NCDValParser_parse.y"
+
+    if (yypMinor) {
+        free_token(yypMinor->yy0);
+    }
+#line 562 "NCDValParser_parse.c"
+   ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
+}
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+  yyParser *yypParser,          /* The parser to be shifted */
+  int yyNewState,               /* The new state to shift in */
+  int yyMajor,                  /* The major token to shift in */
+  YYMINORTYPE *yypMinor         /* Pointer to the minor token to shift in */
+){
+  yyStackEntry *yytos;
+  yypParser->yyidx++;
+#ifdef YYTRACKMAXSTACKDEPTH
+  if( yypParser->yyidx>yypParser->yyidxMax ){
+    yypParser->yyidxMax = yypParser->yyidx;
+  }
+#endif
+#if YYSTACKDEPTH>0 
+  if( yypParser->yyidx>=YYSTACKDEPTH ){
+    yyStackOverflow(yypParser, yypMinor);
+    return;
+  }
+#else
+  if( yypParser->yyidx>=yypParser->yystksz ){
+    yyGrowStack(yypParser);
+    if( yypParser->yyidx>=yypParser->yystksz ){
+      yyStackOverflow(yypParser, yypMinor);
+      return;
+    }
+  }
+#endif
+  yytos = &yypParser->yystack[yypParser->yyidx];
+  yytos->stateno = (YYACTIONTYPE)yyNewState;
+  yytos->major = (YYCODETYPE)yyMajor;
+  yytos->minor = *yypMinor;
+#ifndef NDEBUG
+  if( yyTraceFILE && yypParser->yyidx>0 ){
+    int i;
+    fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
+    fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
+    for(i=1; i<=yypParser->yyidx; i++)
+      fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
+    fprintf(yyTraceFILE,"\n");
+  }
+#endif
+}
+
+/* The following table contains information about every rule that
+** is used during the reduce.
+*/
+static const struct {
+  YYCODETYPE lhs;         /* Symbol on the left-hand side of the rule */
+  unsigned char nrhs;     /* Number of right-hand side symbols in the rule */
+} yyRuleInfo[] = {
+  { 14, 1 },
+  { 9, 1 },
+  { 9, 3 },
+  { 10, 2 },
+  { 10, 3 },
+  { 11, 3 },
+  { 11, 5 },
+  { 12, 2 },
+  { 12, 3 },
+  { 13, 1 },
+  { 13, 1 },
+  { 13, 1 },
+};
+
+static void yy_accept(yyParser*);  /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+*/
+static void yy_reduce(
+  yyParser *yypParser,         /* The parser */
+  int yyruleno                 /* Number of the rule by which to reduce */
+){
+  int yygoto;                     /* The next state */
+  int yyact;                      /* The next action */
+  YYMINORTYPE yygotominor;        /* The LHS of the rule reduced */
+  yyStackEntry *yymsp;            /* The top of the parser's stack */
+  int yysize;                     /* Amount to pop the stack */
+  ParseARG_FETCH;
+  yymsp = &yypParser->yystack[yypParser->yyidx];
+#ifndef NDEBUG
+  if( yyTraceFILE && yyruleno>=0 
+        && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
+    fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+      yyRuleName[yyruleno]);
+  }
+#endif /* NDEBUG */
+
+  /* Silence complaints from purify about yygotominor being uninitialized
+  ** in some cases when it is copied into the stack after the following
+  ** switch.  yygotominor is uninitialized when a rule reduces that does
+  ** not set the value of its left-hand side nonterminal.  Leaving the
+  ** value of the nonterminal uninitialized is utterly harmless as long
+  ** as the value is never used.  So really the only thing this code
+  ** accomplishes is to quieten purify.  
+  **
+  ** 2007-01-16:  The wireshark project (www.wireshark.org) reports that
+  ** without this code, their parser segfaults.  I'm not sure what there
+  ** parser is doing to make this happen.  This is the second bug report
+  ** from wireshark this week.  Clearly they are stressing Lemon in ways
+  ** that it has not been previously stressed...  (SQLite ticket #2172)
+  */
+  /*memset(&yygotominor, 0, sizeof(yygotominor));*/
+  yygotominor = yyzerominor;
+
+
+  switch( yyruleno ){
+  /* Beginning here are the reduction cases.  A typical example
+  ** follows:
+  **   case 0:
+  **  #line <lineno> <grammarfile>
+  **     { ... }           // User supplied code
+  **  #line <lineno> <thisfile>
+  **     break;
+  */
+      case 0: /* input ::= value */
+#line 64 "NCDValParser_parse.y"
+{
+    if (!yymsp[0].minor.yy1.have) {
+        goto failZ0;
+    }
+    
+    if (!NCDVal_IsInvalid(parser_out->value)) {
+        // should never happen
+        parser_out->error_flags |= ERROR_FLAG_SYNTAX;
+        goto failZ0;
+    }
+    
+    if (!NCDValCons_Complete(&parser_out->cons, yymsp[0].minor.yy1.v, &parser_out->value, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failZ0;
+    }
+    
+failZ0:;
+}
+#line 705 "NCDValParser_parse.c"
+        break;
+      case 1: /* list_contents ::= value */
+#line 83 "NCDValParser_parse.y"
+{
+    if (!yymsp[0].minor.yy1.have) {
+        goto failL0;
+    }
+
+    NCDValCons_NewList(&parser_out->cons, &yygotominor.yy1.v);
+
+    if (!NCDValCons_ListPrepend(&parser_out->cons, &yygotominor.yy1.v, yymsp[0].minor.yy1.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failL0;
+    }
+
+    yygotominor.yy1.have = 1;
+    goto doneL;
+
+failL0:
+    yygotominor.yy1.have = 0;
+doneL:;
+}
+#line 728 "NCDValParser_parse.c"
+        break;
+      case 2: /* list_contents ::= value COMMA list_contents */
+#line 103 "NCDValParser_parse.y"
+{
+    if (!yymsp[-2].minor.yy1.have || !yymsp[0].minor.yy1.have) {
+        goto failM0;
+    }
+
+    if (!NCDValCons_ListPrepend(&parser_out->cons, &yymsp[0].minor.yy1.v, yymsp[-2].minor.yy1.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failM0;
+    }
+
+    yygotominor.yy1.have = 1;
+    yygotominor.yy1.v = yymsp[0].minor.yy1.v;
+    goto doneM;
+
+failM0:
+    yygotominor.yy1.have = 0;
+doneM:;
+  yy_destructor(yypParser,1,&yymsp[-1].minor);
+}
+#line 751 "NCDValParser_parse.c"
+        break;
+      case 3: /* list ::= CURLY_OPEN CURLY_CLOSE */
+#line 122 "NCDValParser_parse.y"
+{
+    NCDValCons_NewList(&parser_out->cons, &yygotominor.yy1.v);
+    yygotominor.yy1.have = 1;
+  yy_destructor(yypParser,2,&yymsp[-1].minor);
+  yy_destructor(yypParser,3,&yymsp[0].minor);
+}
+#line 761 "NCDValParser_parse.c"
+        break;
+      case 4: /* list ::= CURLY_OPEN list_contents CURLY_CLOSE */
+#line 127 "NCDValParser_parse.y"
+{
+    yygotominor.yy1 = yymsp[-1].minor.yy1;
+  yy_destructor(yypParser,2,&yymsp[-2].minor);
+  yy_destructor(yypParser,3,&yymsp[0].minor);
+}
+#line 770 "NCDValParser_parse.c"
+        break;
+      case 5: /* map_contents ::= value COLON value */
+#line 131 "NCDValParser_parse.y"
+{
+    if (!yymsp[-2].minor.yy1.have || !yymsp[0].minor.yy1.have) {
+        goto failS0;
+    }
+
+    NCDValCons_NewMap(&parser_out->cons, &yygotominor.yy1.v);
+
+    if (!NCDValCons_MapInsert(&parser_out->cons, &yygotominor.yy1.v, yymsp[-2].minor.yy1.v, yymsp[0].minor.yy1.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failS0;
+    }
+
+    yygotominor.yy1.have = 1;
+    goto doneS;
+
+failS0:
+    yygotominor.yy1.have = 0;
+doneS:;
+  yy_destructor(yypParser,4,&yymsp[-1].minor);
+}
+#line 794 "NCDValParser_parse.c"
+        break;
+      case 6: /* map_contents ::= value COLON value COMMA map_contents */
+#line 151 "NCDValParser_parse.y"
+{
+    if (!yymsp[-4].minor.yy1.have || !yymsp[-2].minor.yy1.have || !yymsp[0].minor.yy1.have) {
+        goto failT0;
+    }
+
+    if (!NCDValCons_MapInsert(&parser_out->cons, &yymsp[0].minor.yy1.v, yymsp[-4].minor.yy1.v, yymsp[-2].minor.yy1.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failT0;
+    }
+
+    yygotominor.yy1.have = 1;
+    yygotominor.yy1.v = yymsp[0].minor.yy1.v;
+    goto doneT;
+
+failT0:
+    yygotominor.yy1.have = 0;
+doneT:;
+  yy_destructor(yypParser,4,&yymsp[-3].minor);
+  yy_destructor(yypParser,1,&yymsp[-1].minor);
+}
+#line 818 "NCDValParser_parse.c"
+        break;
+      case 7: /* map ::= BRACKET_OPEN BRACKET_CLOSE */
+#line 170 "NCDValParser_parse.y"
+{
+    NCDValCons_NewMap(&parser_out->cons, &yygotominor.yy1.v);
+    yygotominor.yy1.have = 1;
+  yy_destructor(yypParser,5,&yymsp[-1].minor);
+  yy_destructor(yypParser,6,&yymsp[0].minor);
+}
+#line 828 "NCDValParser_parse.c"
+        break;
+      case 8: /* map ::= BRACKET_OPEN map_contents BRACKET_CLOSE */
+#line 175 "NCDValParser_parse.y"
+{
+    yygotominor.yy1 = yymsp[-1].minor.yy1;
+  yy_destructor(yypParser,5,&yymsp[-2].minor);
+  yy_destructor(yypParser,6,&yymsp[0].minor);
+}
+#line 837 "NCDValParser_parse.c"
+        break;
+      case 9: /* value ::= STRING */
+#line 179 "NCDValParser_parse.y"
+{
+    ASSERT(yymsp[0].minor.yy0.str)
+
+    if (!NCDValCons_NewString(&parser_out->cons, (const uint8_t *)yymsp[0].minor.yy0.str, yymsp[0].minor.yy0.len, &yygotominor.yy1.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failU0;
+    }
+
+    yygotominor.yy1.have = 1;
+    goto doneU;
+
+failU0:
+    yygotominor.yy1.have = 0;
+doneU:;
+    free_token(yymsp[0].minor.yy0);
+}
+#line 857 "NCDValParser_parse.c"
+        break;
+      case 10: /* value ::= list */
+      case 11: /* value ::= map */ yytestcase(yyruleno==11);
+#line 196 "NCDValParser_parse.y"
+{
+    yygotominor.yy1 = yymsp[0].minor.yy1;
+}
+#line 865 "NCDValParser_parse.c"
+        break;
+      default:
+        break;
+  };
+  yygoto = yyRuleInfo[yyruleno].lhs;
+  yysize = yyRuleInfo[yyruleno].nrhs;
+  yypParser->yyidx -= yysize;
+  yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto);
+  if( yyact < YYNSTATE ){
+#ifdef NDEBUG
+    /* If we are not debugging and the reduce action popped at least
+    ** one element off the stack, then we can push the new element back
+    ** onto the stack here, and skip the stack overflow test in yy_shift().
+    ** That gives a significant speed improvement. */
+    if( yysize ){
+      yypParser->yyidx++;
+      yymsp -= yysize-1;
+      yymsp->stateno = (YYACTIONTYPE)yyact;
+      yymsp->major = (YYCODETYPE)yygoto;
+      yymsp->minor = yygotominor;
+    }else
+#endif
+    {
+      yy_shift(yypParser,yyact,yygoto,&yygotominor);
+    }
+  }else{
+    assert( yyact == YYNSTATE + YYNRULE + 1 );
+    yy_accept(yypParser);
+  }
+}
+
+/*
+** The following code executes when the parse fails
+*/
+#ifndef YYNOERRORRECOVERY
+static void yy_parse_failed(
+  yyParser *yypParser           /* The parser */
+){
+  ParseARG_FETCH;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+  }
+#endif
+  while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+  /* Here code is inserted which will be executed whenever the
+  ** parser fails */
+  ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+#endif /* YYNOERRORRECOVERY */
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+  yyParser *yypParser,           /* The parser */
+  int yymajor,                   /* The major type of the error token */
+  YYMINORTYPE yyminor            /* The minor type of the error token */
+){
+  ParseARG_FETCH;
+#define TOKEN (yyminor.yy0)
+#line 53 "NCDValParser_parse.y"
+
+    parser_out->error_flags |= ERROR_FLAG_SYNTAX;
+#line 930 "NCDValParser_parse.c"
+  ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+  yyParser *yypParser           /* The parser */
+){
+  ParseARG_FETCH;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+  }
+#endif
+  while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+  /* Here code is inserted which will be executed whenever the
+  ** parser accepts */
+  ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "ParseAlloc" which describes the current state of the parser.
+** The second argument is the major token number.  The third is
+** the minor token.  The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void Parse(
+  void *yyp,                   /* The parser */
+  int yymajor,                 /* The major token code number */
+  ParseTOKENTYPE yyminor       /* The value for the token */
+  ParseARG_PDECL               /* Optional %extra_argument parameter */
+){
+  YYMINORTYPE yyminorunion;
+  int yyact;            /* The parser action. */
+  int yyendofinput;     /* True if we are at the end of input */
+#ifdef YYERRORSYMBOL
+  int yyerrorhit = 0;   /* True if yymajor has invoked an error */
+#endif
+  yyParser *yypParser;  /* The parser */
+
+  /* (re)initialize the parser, if necessary */
+  yypParser = (yyParser*)yyp;
+  if( yypParser->yyidx<0 ){
+#if YYSTACKDEPTH<=0
+    if( yypParser->yystksz <=0 ){
+      /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/
+      yyminorunion = yyzerominor;
+      yyStackOverflow(yypParser, &yyminorunion);
+      return;
+    }
+#endif
+    yypParser->yyidx = 0;
+    yypParser->yyerrcnt = -1;
+    yypParser->yystack[0].stateno = 0;
+    yypParser->yystack[0].major = 0;
+  }
+  yyminorunion.yy0 = yyminor;
+  yyendofinput = (yymajor==0);
+  ParseARG_STORE;
+
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+  }
+#endif
+
+  do{
+    yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor);
+    if( yyact<YYNSTATE ){
+      assert( !yyendofinput );  /* Impossible to shift the $ token */
+      yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+      yypParser->yyerrcnt--;
+      yymajor = YYNOCODE;
+    }else if( yyact < YYNSTATE + YYNRULE ){
+      yy_reduce(yypParser,yyact-YYNSTATE);
+    }else{
+      assert( yyact == YY_ERROR_ACTION );
+#ifdef YYERRORSYMBOL
+      int yymx;
+#endif
+#ifndef NDEBUG
+      if( yyTraceFILE ){
+        fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+      }
+#endif
+#ifdef YYERRORSYMBOL
+      /* A syntax error has occurred.
+      ** The response to an error depends upon whether or not the
+      ** grammar defines an error token "ERROR".  
+      **
+      ** This is what we do if the grammar does define ERROR:
+      **
+      **  * Call the %syntax_error function.
+      **
+      **  * Begin popping the stack until we enter a state where
+      **    it is legal to shift the error symbol, then shift
+      **    the error symbol.
+      **
+      **  * Set the error count to three.
+      **
+      **  * Begin accepting and shifting new tokens.  No new error
+      **    processing will occur until three tokens have been
+      **    shifted successfully.
+      **
+      */
+      if( yypParser->yyerrcnt<0 ){
+        yy_syntax_error(yypParser,yymajor,yyminorunion);
+      }
+      yymx = yypParser->yystack[yypParser->yyidx].major;
+      if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+             yyTracePrompt,yyTokenName[yymajor]);
+        }
+#endif
+        yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion);
+        yymajor = YYNOCODE;
+      }else{
+         while(
+          yypParser->yyidx >= 0 &&
+          yymx != YYERRORSYMBOL &&
+          (yyact = yy_find_reduce_action(
+                        yypParser->yystack[yypParser->yyidx].stateno,
+                        YYERRORSYMBOL)) >= YYNSTATE
+        ){
+          yy_pop_parser_stack(yypParser);
+        }
+        if( yypParser->yyidx < 0 || yymajor==0 ){
+          yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+          yy_parse_failed(yypParser);
+          yymajor = YYNOCODE;
+        }else if( yymx!=YYERRORSYMBOL ){
+          YYMINORTYPE u2;
+          u2.YYERRSYMDT = 0;
+          yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
+        }
+      }
+      yypParser->yyerrcnt = 3;
+      yyerrorhit = 1;
+#elif defined(YYNOERRORRECOVERY)
+      /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to
+      ** do any kind of error recovery.  Instead, simply invoke the syntax
+      ** error routine and continue going as if nothing had happened.
+      **
+      ** Applications can set this macro (for example inside %include) if
+      ** they intend to abandon the parse upon the first syntax error seen.
+      */
+      yy_syntax_error(yypParser,yymajor,yyminorunion);
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      yymajor = YYNOCODE;
+      
+#else  /* YYERRORSYMBOL is not defined */
+      /* This is what we do if the grammar does not define ERROR:
+      **
+      **  * Report an error message, and throw away the input token.
+      **
+      **  * If the input token is $, then fail the parse.
+      **
+      ** As before, subsequent error messages are suppressed until
+      ** three input tokens have been successfully shifted.
+      */
+      if( yypParser->yyerrcnt<=0 ){
+        yy_syntax_error(yypParser,yymajor,yyminorunion);
+      }
+      yypParser->yyerrcnt = 3;
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      if( yyendofinput ){
+        yy_parse_failed(yypParser);
+      }
+      yymajor = YYNOCODE;
+#endif
+    }
+  }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
+  return;
+}
diff --git a/external/badvpn_dns/generated/NCDValParser_parse.h b/external/badvpn_dns/generated/NCDValParser_parse.h
new file mode 100644
index 0000000..fd19b49
--- /dev/null
+++ b/external/badvpn_dns/generated/NCDValParser_parse.h
@@ -0,0 +1,7 @@
+#define COMMA                           1
+#define CURLY_OPEN                      2
+#define CURLY_CLOSE                     3
+#define COLON                           4
+#define BRACKET_OPEN                    5
+#define BRACKET_CLOSE                   6
+#define STRING                          7
diff --git a/external/badvpn_dns/generated/NCDValParser_parse.out b/external/badvpn_dns/generated/NCDValParser_parse.out
new file mode 100644
index 0000000..9f42273
--- /dev/null
+++ b/external/badvpn_dns/generated/NCDValParser_parse.out
@@ -0,0 +1,217 @@
+State 0:
+          input ::= * value
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * list
+          value ::= * map
+
+                    CURLY_OPEN shift  1
+                  BRACKET_OPEN shift  2
+                        STRING shift  14
+                          list shift  15
+                           map shift  16
+                         value shift  6
+                         input accept
+
+State 1:
+          list_contents ::= * value
+          list_contents ::= * value COMMA list_contents
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= CURLY_OPEN * CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          list ::= CURLY_OPEN * list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * list
+          value ::= * map
+
+                    CURLY_OPEN shift  1
+                   CURLY_CLOSE shift  19
+                  BRACKET_OPEN shift  2
+                        STRING shift  14
+                 list_contents shift  11
+                          list shift  15
+                           map shift  16
+                         value shift  7
+
+State 2:
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map_contents ::= * value COLON value
+          map_contents ::= * value COLON value COMMA map_contents
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= BRACKET_OPEN * BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          map ::= BRACKET_OPEN * map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * list
+          value ::= * map
+
+                    CURLY_OPEN shift  1
+                  BRACKET_OPEN shift  2
+                 BRACKET_CLOSE shift  17
+                        STRING shift  14
+                          list shift  15
+                  map_contents shift  10
+                           map shift  16
+                         value shift  8
+
+State 3:
+          list_contents ::= * value
+          list_contents ::= * value COMMA list_contents
+          list_contents ::= value COMMA * list_contents
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * list
+          value ::= * map
+
+                    CURLY_OPEN shift  1
+                  BRACKET_OPEN shift  2
+                        STRING shift  14
+                 list_contents shift  12
+                          list shift  15
+                           map shift  16
+                         value shift  7
+
+State 4:
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map_contents ::= * value COLON value
+          map_contents ::= * value COLON value COMMA map_contents
+          map_contents ::= value COLON value COMMA * map_contents
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * list
+          value ::= * map
+
+                    CURLY_OPEN shift  1
+                  BRACKET_OPEN shift  2
+                        STRING shift  14
+                          list shift  15
+                  map_contents shift  13
+                           map shift  16
+                         value shift  8
+
+State 5:
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map_contents ::= value COLON * value
+          map_contents ::= value COLON * value COMMA map_contents
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * list
+          value ::= * map
+
+                    CURLY_OPEN shift  1
+                  BRACKET_OPEN shift  2
+                        STRING shift  14
+                          list shift  15
+                           map shift  16
+                         value shift  9
+
+State 6:
+      (0) input ::= value *
+
+                             $ reduce 0
+
+State 7:
+      (1) list_contents ::= value *
+          list_contents ::= value * COMMA list_contents
+
+                         COMMA shift  3
+                     {default} reduce 1
+
+State 8:
+          map_contents ::= value * COLON value
+          map_contents ::= value * COLON value COMMA map_contents
+
+                         COLON shift  5
+
+State 9:
+      (5) map_contents ::= value COLON value *
+          map_contents ::= value COLON value * COMMA map_contents
+
+                         COMMA shift  4
+                     {default} reduce 5
+
+State 10:
+          map ::= BRACKET_OPEN map_contents * BRACKET_CLOSE
+
+                 BRACKET_CLOSE shift  18
+
+State 11:
+          list ::= CURLY_OPEN list_contents * CURLY_CLOSE
+
+                   CURLY_CLOSE shift  20
+
+State 12:
+      (2) list_contents ::= value COMMA list_contents *
+
+                     {default} reduce 2
+
+State 13:
+      (6) map_contents ::= value COLON value COMMA map_contents *
+
+                     {default} reduce 6
+
+State 14:
+      (9) value ::= STRING *
+
+                     {default} reduce 9
+
+State 15:
+     (10) value ::= list *
+
+                     {default} reduce 10
+
+State 16:
+     (11) value ::= map *
+
+                     {default} reduce 11
+
+State 17:
+      (7) map ::= BRACKET_OPEN BRACKET_CLOSE *
+
+                     {default} reduce 7
+
+State 18:
+      (8) map ::= BRACKET_OPEN map_contents BRACKET_CLOSE *
+
+                     {default} reduce 8
+
+State 19:
+      (3) list ::= CURLY_OPEN CURLY_CLOSE *
+
+                     {default} reduce 3
+
+State 20:
+      (4) list ::= CURLY_OPEN list_contents CURLY_CLOSE *
+
+                     {default} reduce 4
+
+----------------------------------------------------
+Symbols:
+    0: $:
+    1: COMMA
+    2: CURLY_OPEN
+    3: CURLY_CLOSE
+    4: COLON
+    5: BRACKET_OPEN
+    6: BRACKET_CLOSE
+    7: STRING
+    8: error:
+    9: list_contents: CURLY_OPEN BRACKET_OPEN STRING
+   10: list: CURLY_OPEN
+   11: map_contents: CURLY_OPEN BRACKET_OPEN STRING
+   12: map: BRACKET_OPEN
+   13: value: CURLY_OPEN BRACKET_OPEN STRING
+   14: input: CURLY_OPEN BRACKET_OPEN STRING
diff --git a/external/badvpn_dns/generated/NCDValParser_parse.y b/external/badvpn_dns/generated/NCDValParser_parse.y
new file mode 100644
index 0000000..ced123d
--- /dev/null
+++ b/external/badvpn_dns/generated/NCDValParser_parse.y
@@ -0,0 +1,202 @@
+/**
+ * @file NCDConfigParser_parse.y
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+// argument for passing state to parser hooks
+%extra_argument { struct parser_state *parser_out }
+
+// type of structure representing tokens
+%token_type { struct token }
+
+// token destructor frees extra memory allocated for tokens
+%token_destructor { free_token($$); }
+
+// types of nonterminals
+%type list_contents { struct value }
+%type list { struct value }
+%type map_contents { struct value }
+%type map  { struct value }
+%type value  { struct value }
+
+// mention parser_out in some destructor to and avoid unused variable warning
+%destructor list_contents { (void)parser_out; }
+
+// try to dynamically grow the parse stack
+%stack_size 0
+
+// on syntax error, set the corresponding error flag
+%syntax_error {
+    parser_out->error_flags |= ERROR_FLAG_SYNTAX;
+}
+
+// workaroud Lemon bug: if the stack overflows, the token that caused the overflow will be leaked
+%stack_overflow {
+    if (yypMinor) {
+        free_token(yypMinor->yy0);
+    }
+}
+
+input ::= value(A). {
+    if (!A.have) {
+        goto failZ0;
+    }
+    
+    if (!NCDVal_IsInvalid(parser_out->value)) {
+        // should never happen
+        parser_out->error_flags |= ERROR_FLAG_SYNTAX;
+        goto failZ0;
+    }
+    
+    if (!NCDValCons_Complete(&parser_out->cons, A.v, &parser_out->value, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failZ0;
+    }
+    
+failZ0:;
+}
+
+list_contents(R) ::= value(A). {
+    if (!A.have) {
+        goto failL0;
+    }
+
+    NCDValCons_NewList(&parser_out->cons, &R.v);
+
+    if (!NCDValCons_ListPrepend(&parser_out->cons, &R.v, A.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failL0;
+    }
+
+    R.have = 1;
+    goto doneL;
+
+failL0:
+    R.have = 0;
+doneL:;
+}
+
+list_contents(R) ::= value(A) COMMA list_contents(N). {
+    if (!A.have || !N.have) {
+        goto failM0;
+    }
+
+    if (!NCDValCons_ListPrepend(&parser_out->cons, &N.v, A.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failM0;
+    }
+
+    R.have = 1;
+    R.v = N.v;
+    goto doneM;
+
+failM0:
+    R.have = 0;
+doneM:;
+}
+
+list(R) ::= CURLY_OPEN CURLY_CLOSE. {
+    NCDValCons_NewList(&parser_out->cons, &R.v);
+    R.have = 1;
+}
+
+list(R) ::= CURLY_OPEN list_contents(A) CURLY_CLOSE. {
+    R = A;
+}
+
+map_contents(R) ::= value(A) COLON value(B). {
+    if (!A.have || !B.have) {
+        goto failS0;
+    }
+
+    NCDValCons_NewMap(&parser_out->cons, &R.v);
+
+    if (!NCDValCons_MapInsert(&parser_out->cons, &R.v, A.v, B.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failS0;
+    }
+
+    R.have = 1;
+    goto doneS;
+
+failS0:
+    R.have = 0;
+doneS:;
+}
+
+map_contents(R) ::= value(A) COLON value(B) COMMA map_contents(N). {
+    if (!A.have || !B.have || !N.have) {
+        goto failT0;
+    }
+
+    if (!NCDValCons_MapInsert(&parser_out->cons, &N.v, A.v, B.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failT0;
+    }
+
+    R.have = 1;
+    R.v = N.v;
+    goto doneT;
+
+failT0:
+    R.have = 0;
+doneT:;
+}
+
+map(R) ::= BRACKET_OPEN BRACKET_CLOSE. {
+    NCDValCons_NewMap(&parser_out->cons, &R.v);
+    R.have = 1;
+}
+
+map(R) ::= BRACKET_OPEN map_contents(A) BRACKET_CLOSE. {
+    R = A;
+}
+
+value(R) ::= STRING(A). {
+    ASSERT(A.str)
+
+    if (!NCDValCons_NewString(&parser_out->cons, (const uint8_t *)A.str, A.len, &R.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failU0;
+    }
+
+    R.have = 1;
+    goto doneU;
+
+failU0:
+    R.have = 0;
+doneU:;
+    free_token(A);
+}
+
+value(R) ::= list(A). {
+    R = A;
+}
+
+value(R) ::= map(A). {
+    R = A;
+}
diff --git a/external/badvpn_dns/generated/bison_BPredicate.c b/external/badvpn_dns/generated/bison_BPredicate.c
new file mode 100644
index 0000000..5a0a605
--- /dev/null
+++ b/external/badvpn_dns/generated/bison_BPredicate.c
@@ -0,0 +1,2168 @@
+/* A Bison parser, made by GNU Bison 2.7.12-4996.  */
+
+/* Bison implementation for Yacc-like parsers in C
+   
+      Copyright (C) 1984, 1989-1990, 2000-2013 Free Software Foundation, Inc.
+   
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+   
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
+   
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+   simplifying the original so-called "semantic" parser.  */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+   infringing on user name space.  This should be done even for local
+   variables, as they might otherwise be expanded by user macros.
+   There are some unavoidable exceptions within include files to
+   define necessary library symbols; they are noted "INFRINGES ON
+   USER NAME SPACE" below.  */
+
+/* Identify Bison output.  */
+#define YYBISON 1
+
+/* Bison version.  */
+#define YYBISON_VERSION "2.7.12-4996"
+
+/* Skeleton name.  */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers.  */
+#define YYPURE 1
+
+/* Push parsers.  */
+#define YYPUSH 0
+
+/* Pull parsers.  */
+#define YYPULL 1
+
+
+
+
+/* Copy the first part of user declarations.  */
+/* Line 371 of yacc.c  */
+#line 34 "predicate/BPredicate.y"
+
+
+#include <stdlib.h>
+
+#include <predicate/BPredicate_internal.h>
+#include <predicate/BPredicate_parser.h>
+
+#define YYLEX_PARAM scanner
+
+static struct predicate_node * make_constant (int val)
+{
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        return NULL;
+    }
+
+    n->type = NODE_CONSTANT;
+    n->constant.val = val;
+
+    return n;
+}
+
+static struct predicate_node * make_negation (struct predicate_node *op)
+{
+    if (!op) {
+        goto fail;
+    }
+
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->type = NODE_NEG;
+    n->neg.op = op;
+
+    return n;
+
+fail:
+    if (op) {
+        free_predicate_node(op);
+    }
+    return NULL;
+}
+
+static struct predicate_node * make_conjunction (struct predicate_node *op1, struct predicate_node *op2)
+{
+    if (!op1 || !op2) {
+        goto fail;
+    }
+
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->type = NODE_CONJUNCT;
+    n->conjunct.op1 = op1;
+    n->conjunct.op2 = op2;
+
+    return n;
+
+fail:
+    if (op1) {
+        free_predicate_node(op1);
+    }
+    if (op2) {
+        free_predicate_node(op2);
+    }
+    return NULL;
+}
+
+static struct predicate_node * make_disjunction (struct predicate_node *op1, struct predicate_node *op2)
+{
+    if (!op1 || !op2) {
+        goto fail;
+    }
+
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->type = NODE_DISJUNCT;
+    n->disjunct.op1 = op1;
+    n->disjunct.op2 = op2;
+
+    return n;
+
+fail:
+    if (op1) {
+        free_predicate_node(op1);
+    }
+    if (op2) {
+        free_predicate_node(op2);
+    }
+    return NULL;
+}
+
+static struct predicate_node * make_function (char *name, struct arguments_node *args, int need_args)
+{
+    if (!name || (!args && need_args)) {
+        goto fail;
+    }
+
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->type = NODE_FUNCTION;
+    n->function.name = name;
+    n->function.args = args;
+
+    return n;
+
+fail:
+    if (name) {
+        free(name);
+    }
+    if (args) {
+        free_arguments_node(args);
+    }
+    return NULL;
+}
+
+static struct arguments_node * make_arguments (struct arguments_arg arg, struct arguments_node *next, int need_next)
+{
+    if (arg.type == ARGUMENT_INVALID || (!next && need_next)) {
+        goto fail;
+    }
+
+    struct arguments_node *n = (struct arguments_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->arg = arg;
+    n->next = next;
+
+    return n;
+
+fail:
+    free_argument(arg);
+    if (next) {
+        free_arguments_node(next);
+    }
+    return NULL;
+}
+
+static struct arguments_arg make_argument_predicate (struct predicate_node *pr)
+{
+    struct arguments_arg ret;
+
+    if (!pr) {
+        goto fail;
+    }
+
+    ret.type = ARGUMENT_PREDICATE;
+    ret.predicate = pr;
+
+    return ret;
+
+fail:
+    ret.type = ARGUMENT_INVALID;
+    return ret;
+}
+
+static struct arguments_arg make_argument_string (char *string)
+{
+    struct arguments_arg ret;
+
+    if (!string) {
+        goto fail;
+    }
+
+    ret.type = ARGUMENT_STRING;
+    ret.string = string;
+
+    return ret;
+
+fail:
+    ret.type = ARGUMENT_INVALID;
+    return ret;
+}
+
+
+/* Line 371 of yacc.c  */
+#line 256 "generated//bison_BPredicate.c"
+
+# ifndef YY_NULL
+#  if defined __cplusplus && 201103L <= __cplusplus
+#   define YY_NULL nullptr
+#  else
+#   define YY_NULL 0
+#  endif
+# endif
+
+/* Enabling verbose error messages.  */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE 0
+#endif
+
+/* In a future release of Bison, this section will be replaced
+   by #include "bison_BPredicate.h".  */
+#ifndef YY_YY_GENERATED_BISON_BPREDICATE_H_INCLUDED
+# define YY_YY_GENERATED_BISON_BPREDICATE_H_INCLUDED
+/* Enabling traces.  */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Tokens.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+   /* Put the tokens into the symbol table, so that GDB and other debuggers
+      know about them.  */
+   enum yytokentype {
+     STRING = 258,
+     NAME = 259,
+     PEER1_NAME = 260,
+     PEER2_NAME = 261,
+     AND = 262,
+     OR = 263,
+     NOT = 264,
+     SPAR = 265,
+     EPAR = 266,
+     CONSTANT_TRUE = 267,
+     CONSTANT_FALSE = 268,
+     COMMA = 269
+   };
+#endif
+
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
+{
+/* Line 387 of yacc.c  */
+#line 227 "predicate/BPredicate.y"
+
+    char *text;
+    struct predicate_node *node;
+    struct arguments_node *arg_node;
+    struct predicate_node nfaw;
+    struct arguments_arg arg_arg;
+
+
+/* Line 387 of yacc.c  */
+#line 322 "generated//bison_BPredicate.c"
+} YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED
+typedef struct YYLTYPE
+{
+  int first_line;
+  int first_column;
+  int last_line;
+  int last_column;
+} YYLTYPE;
+# define yyltype YYLTYPE /* obsolescent; will be withdrawn */
+# define YYLTYPE_IS_DECLARED 1
+# define YYLTYPE_IS_TRIVIAL 1
+#endif
+
+
+#ifdef YYPARSE_PARAM
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void *YYPARSE_PARAM);
+#else
+int yyparse ();
+#endif
+#else /* ! YYPARSE_PARAM */
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void *scanner, struct predicate_node **result);
+#else
+int yyparse ();
+#endif
+#endif /* ! YYPARSE_PARAM */
+
+#endif /* !YY_YY_GENERATED_BISON_BPREDICATE_H_INCLUDED  */
+
+/* Copy the second part of user declarations.  */
+
+/* Line 390 of yacc.c  */
+#line 362 "generated//bison_BPredicate.c"
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#elif (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+typedef signed char yytype_int8;
+#else
+typedef short int yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+#  define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+#  define YYSIZE_T size_t
+# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+#  include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+#  define YYSIZE_T size_t
+# else
+#  define YYSIZE_T unsigned int
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+#  if ENABLE_NLS
+#   include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+#   define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+#  endif
+# endif
+# ifndef YY_
+#  define YY_(Msgid) Msgid
+# endif
+#endif
+
+#ifndef __attribute__
+/* This feature is available in gcc versions 2.5 and later.  */
+# if (! defined __GNUC__ || __GNUC__ < 2 \
+      || (__GNUC__ == 2 && __GNUC_MINOR__ < 5))
+#  define __attribute__(Spec) /* empty */
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E.  */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(E) ((void) (E))
+#else
+# define YYUSE(E) /* empty */
+#endif
+
+
+/* Identity function, used to suppress warnings about constant conditions.  */
+#ifndef lint
+# define YYID(N) (N)
+#else
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static int
+YYID (int yyi)
+#else
+static int
+YYID (yyi)
+    int yyi;
+#endif
+{
+  return yyi;
+}
+#endif
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols.  */
+
+# ifdef YYSTACK_USE_ALLOCA
+#  if YYSTACK_USE_ALLOCA
+#   ifdef __GNUC__
+#    define YYSTACK_ALLOC __builtin_alloca
+#   elif defined __BUILTIN_VA_ARG_INCR
+#    include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+#   elif defined _AIX
+#    define YYSTACK_ALLOC __alloca
+#   elif defined _MSC_VER
+#    include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+#    define alloca _alloca
+#   else
+#    define YYSTACK_ALLOC alloca
+#    if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+#     include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+      /* Use EXIT_SUCCESS as a witness for stdlib.h.  */
+#     ifndef EXIT_SUCCESS
+#      define EXIT_SUCCESS 0
+#     endif
+#    endif
+#   endif
+#  endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+   /* Pacify GCC's `empty if-body' warning.  */
+#  define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0))
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+    /* The OS might guarantee only one guard page at the bottom of the stack,
+       and a page size can be as small as 4096 bytes.  So we cannot safely
+       invoke alloca (N) if N exceeds 4096.  Use a slightly smaller number
+       to allow for a few compiler-allocated temporary stack slots.  */
+#   define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+#  endif
+# else
+#  define YYSTACK_ALLOC YYMALLOC
+#  define YYSTACK_FREE YYFREE
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+#   define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+#  endif
+#  if (defined __cplusplus && ! defined EXIT_SUCCESS \
+       && ! ((defined YYMALLOC || defined malloc) \
+	     && (defined YYFREE || defined free)))
+#   include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+#   ifndef EXIT_SUCCESS
+#    define EXIT_SUCCESS 0
+#   endif
+#  endif
+#  ifndef YYMALLOC
+#   define YYMALLOC malloc
+#   if ! defined malloc && ! defined EXIT_SUCCESS && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
+#  ifndef YYFREE
+#   define YYFREE free
+#   if ! defined free && ! defined EXIT_SUCCESS && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+     && (! defined __cplusplus \
+	 || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \
+	     && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member.  */
+union yyalloc
+{
+  yytype_int16 yyss_alloc;
+  YYSTYPE yyvs_alloc;
+  YYLTYPE yyls_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next.  */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+   N elements.  */
+# define YYSTACK_BYTES(N) \
+     ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \
+      + 2 * YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one.  The
+   local variables YYSIZE and YYSTACKSIZE give the old and new number of
+   elements in the stack, and YYPTR gives the new location of the
+   stack.  Advance YYPTR to a properly aligned location for the next
+   stack.  */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack)				\
+    do									\
+      {									\
+	YYSIZE_T yynewbytes;						\
+	YYCOPY (&yyptr->Stack_alloc, Stack, yysize);			\
+	Stack = &yyptr->Stack_alloc;					\
+	yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+	yyptr += yynewbytes / sizeof (*yyptr);				\
+      }									\
+    while (YYID (0))
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST.  The source and destination do
+   not overlap.  */
+# ifndef YYCOPY
+#  if defined __GNUC__ && 1 < __GNUC__
+#   define YYCOPY(Dst, Src, Count) \
+      __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src)))
+#  else
+#   define YYCOPY(Dst, Src, Count)              \
+      do                                        \
+        {                                       \
+          YYSIZE_T yyi;                         \
+          for (yyi = 0; yyi < (Count); yyi++)   \
+            (Dst)[yyi] = (Src)[yyi];            \
+        }                                       \
+      while (YYID (0))
+#  endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state.  */
+#define YYFINAL  17
+/* YYLAST -- Last index in YYTABLE.  */
+#define YYLAST   37
+
+/* YYNTOKENS -- Number of terminals.  */
+#define YYNTOKENS  15
+/* YYNNTS -- Number of nonterminals.  */
+#define YYNNTS  11
+/* YYNRULES -- Number of rules.  */
+#define YYNRULES  20
+/* YYNRULES -- Number of states.  */
+#define YYNSTATES  31
+
+/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX.  */
+#define YYUNDEFTOK  2
+#define YYMAXUTOK   269
+
+#define YYTRANSLATE(YYX)						\
+  ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX.  */
+static const yytype_uint8 yytranslate[] =
+{
+       0,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,     2,     2,     2,     2,     2,     1,     2,     3,     4,
+       5,     6,     7,     8,     9,    10,    11,    12,    13,    14
+};
+
+#if YYDEBUG
+/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in
+   YYRHS.  */
+static const yytype_uint8 yyprhs[] =
+{
+       0,     0,     3,     5,     7,     9,    11,    13,    15,    17,
+      19,    21,    25,    28,    32,    36,    40,    45,    47,    51,
+      53
+};
+
+/* YYRHS -- A `-1'-separated list of the rules' RHS.  */
+static const yytype_int8 yyrhs[] =
+{
+      16,     0,    -1,    17,    -1,    18,    -1,    19,    -1,    20,
+      -1,    21,    -1,    22,    -1,    23,    -1,    12,    -1,    13,
+      -1,    10,    17,    11,    -1,     9,    17,    -1,    17,     7,
+      17,    -1,    17,     8,    17,    -1,     4,    10,    11,    -1,
+       4,    10,    24,    11,    -1,    25,    -1,    25,    14,    24,
+      -1,    17,    -1,     3,    -1
+};
+
+/* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
+static const yytype_uint16 yyrline[] =
+{
+       0,   276,   276,   281,   281,   281,   281,   281,   281,   284,
+     288,   294,   300,   306,   312,   318,   322,   328,   332,   338,
+     342
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || 0
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+   First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
+static const char *const yytname[] =
+{
+  "$end", "error", "$undefined", "STRING", "NAME", "PEER1_NAME",
+  "PEER2_NAME", "AND", "OR", "NOT", "SPAR", "EPAR", "CONSTANT_TRUE",
+  "CONSTANT_FALSE", "COMMA", "$accept", "input", "predicate", "constant",
+  "parentheses", "neg", "conjunct", "disjunct", "function", "arguments",
+  "argument", YY_NULL
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to
+   token YYLEX-NUM.  */
+static const yytype_uint16 yytoknum[] =
+{
+       0,   256,   257,   258,   259,   260,   261,   262,   263,   264,
+     265,   266,   267,   268,   269
+};
+# endif
+
+/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives.  */
+static const yytype_uint8 yyr1[] =
+{
+       0,    15,    16,    17,    17,    17,    17,    17,    17,    18,
+      18,    19,    20,    21,    22,    23,    23,    24,    24,    25,
+      25
+};
+
+/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN.  */
+static const yytype_uint8 yyr2[] =
+{
+       0,     2,     1,     1,     1,     1,     1,     1,     1,     1,
+       1,     3,     2,     3,     3,     3,     4,     1,     3,     1,
+       1
+};
+
+/* YYDEFACT[STATE-NAME] -- Default reduction number in state STATE-NUM.
+   Performed when YYTABLE doesn't specify something else to do.  Zero
+   means the default is an error.  */
+static const yytype_uint8 yydefact[] =
+{
+       0,     0,     0,     0,     9,    10,     0,     2,     3,     4,
+       5,     6,     7,     8,     0,    12,     0,     1,     0,     0,
+      20,    15,    19,     0,    17,    11,    13,    14,    16,     0,
+      18
+};
+
+/* YYDEFGOTO[NTERM-NUM].  */
+static const yytype_int8 yydefgoto[] =
+{
+      -1,     6,    22,     8,     9,    10,    11,    12,    13,    23,
+      24
+};
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+   STATE-NUM.  */
+#define YYPACT_NINF -10
+static const yytype_int8 yypact[] =
+{
+      19,    -9,    19,    19,   -10,   -10,     8,    -1,   -10,   -10,
+     -10,   -10,   -10,   -10,     1,   -10,    26,   -10,    19,    19,
+     -10,   -10,    -1,    -2,     3,   -10,   -10,    13,   -10,    12,
+     -10
+};
+
+/* YYPGOTO[NTERM-NUM].  */
+static const yytype_int8 yypgoto[] =
+{
+     -10,   -10,     0,   -10,   -10,   -10,   -10,   -10,   -10,    -3,
+     -10
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]].  What to do in state STATE-NUM.  If
+   positive, shift that token.  If negative, reduce the rule which
+   number is the opposite.  If YYTABLE_NINF, syntax error.  */
+#define YYTABLE_NINF -1
+static const yytype_uint8 yytable[] =
+{
+       7,    14,    15,    16,    20,     1,    18,    19,    17,    28,
+       2,     3,    21,     4,     5,    20,     1,    29,    26,    27,
+      18,     2,     3,     1,     4,     5,    30,     0,     2,     3,
+       0,     4,     5,    18,    19,     0,     0,    25
+};
+
+#define yypact_value_is_default(Yystate) \
+  (!!((Yystate) == (-10)))
+
+#define yytable_value_is_error(Yytable_value) \
+  YYID (0)
+
+static const yytype_int8 yycheck[] =
+{
+       0,    10,     2,     3,     3,     4,     7,     8,     0,    11,
+       9,    10,    11,    12,    13,     3,     4,    14,    18,    19,
+       7,     9,    10,     4,    12,    13,    29,    -1,     9,    10,
+      -1,    12,    13,     7,     8,    -1,    -1,    11
+};
+
+/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+   symbol of state STATE-NUM.  */
+static const yytype_uint8 yystos[] =
+{
+       0,     4,     9,    10,    12,    13,    16,    17,    18,    19,
+      20,    21,    22,    23,    10,    17,    17,     0,     7,     8,
+       3,    11,    17,    24,    25,    11,    17,    17,    11,    14,
+      24
+};
+
+#define yyerrok		(yyerrstatus = 0)
+#define yyclearin	(yychar = YYEMPTY)
+#define YYEMPTY		(-2)
+#define YYEOF		0
+
+#define YYACCEPT	goto yyacceptlab
+#define YYABORT		goto yyabortlab
+#define YYERROR		goto yyerrorlab
+
+
+/* Like YYERROR except do call yyerror.  This remains here temporarily
+   to ease the transition to the new meaning of YYERROR, for GCC.
+   Once GCC version 2 has supplanted version 1, this can go.  However,
+   YYFAIL appears to be in use.  Nevertheless, it is formally deprecated
+   in Bison 2.4.2's NEWS entry, where a plan to phase it out is
+   discussed.  */
+
+#define YYFAIL		goto yyerrlab
+#if defined YYFAIL
+  /* This is here to suppress warnings from the GCC cpp's
+     -Wunused-macros.  Normally we don't worry about that warning, but
+     some users do, and we want to make it easy for users to remove
+     YYFAIL uses, which will produce warnings from Bison 2.5.  */
+#endif
+
+#define YYRECOVERING()  (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value)                                  \
+do                                                              \
+  if (yychar == YYEMPTY)                                        \
+    {                                                           \
+      yychar = (Token);                                         \
+      yylval = (Value);                                         \
+      YYPOPSTACK (yylen);                                       \
+      yystate = *yyssp;                                         \
+      goto yybackup;                                            \
+    }                                                           \
+  else                                                          \
+    {                                                           \
+      yyerror (&yylloc, scanner, result, YY_("syntax error: cannot back up")); \
+      YYERROR;							\
+    }								\
+while (YYID (0))
+
+/* Error token number */
+#define YYTERROR	1
+#define YYERRCODE	256
+
+
+/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
+   If N is 0, then set CURRENT to the empty location which ends
+   the previous symbol: RHS[0] (always defined).  */
+
+#ifndef YYLLOC_DEFAULT
+# define YYLLOC_DEFAULT(Current, Rhs, N)                                \
+    do                                                                  \
+      if (YYID (N))                                                     \
+        {                                                               \
+          (Current).first_line   = YYRHSLOC (Rhs, 1).first_line;        \
+          (Current).first_column = YYRHSLOC (Rhs, 1).first_column;      \
+          (Current).last_line    = YYRHSLOC (Rhs, N).last_line;         \
+          (Current).last_column  = YYRHSLOC (Rhs, N).last_column;       \
+        }                                                               \
+      else                                                              \
+        {                                                               \
+          (Current).first_line   = (Current).last_line   =              \
+            YYRHSLOC (Rhs, 0).last_line;                                \
+          (Current).first_column = (Current).last_column =              \
+            YYRHSLOC (Rhs, 0).last_column;                              \
+        }                                                               \
+    while (YYID (0))
+#endif
+
+#define YYRHSLOC(Rhs, K) ((Rhs)[K])
+
+
+/* YY_LOCATION_PRINT -- Print the location on the stream.
+   This macro was not mandated originally: define only if we know
+   we won't break user code: when these are the locations we know.  */
+
+#ifndef YY_LOCATION_PRINT
+# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL
+
+/* Print *YYLOCP on YYO.  Private, do not rely on its existence. */
+
+__attribute__((__unused__))
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static unsigned
+yy_location_print_ (FILE *yyo, YYLTYPE const * const yylocp)
+#else
+static unsigned
+yy_location_print_ (yyo, yylocp)
+    FILE *yyo;
+    YYLTYPE const * const yylocp;
+#endif
+{
+  unsigned res = 0;
+  int end_col = 0 != yylocp->last_column ? yylocp->last_column - 1 : 0;
+  if (0 <= yylocp->first_line)
+    {
+      res += fprintf (yyo, "%d", yylocp->first_line);
+      if (0 <= yylocp->first_column)
+        res += fprintf (yyo, ".%d", yylocp->first_column);
+    }
+  if (0 <= yylocp->last_line)
+    {
+      if (yylocp->first_line < yylocp->last_line)
+        {
+          res += fprintf (yyo, "-%d", yylocp->last_line);
+          if (0 <= end_col)
+            res += fprintf (yyo, ".%d", end_col);
+        }
+      else if (0 <= end_col && yylocp->first_column < end_col)
+        res += fprintf (yyo, "-%d", end_col);
+    }
+  return res;
+ }
+
+#  define YY_LOCATION_PRINT(File, Loc)          \
+  yy_location_print_ (File, &(Loc))
+
+# else
+#  define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+# endif
+#endif
+
+
+/* YYLEX -- calling `yylex' with the right arguments.  */
+#ifdef YYLEX_PARAM
+# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM)
+#else
+# define YYLEX yylex (&yylval, &yylloc)
+#endif
+
+/* Enable debugging if requested.  */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+#  include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+#  define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args)			\
+do {						\
+  if (yydebug)					\
+    YYFPRINTF Args;				\
+} while (YYID (0))
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)			  \
+do {									  \
+  if (yydebug)								  \
+    {									  \
+      YYFPRINTF (stderr, "%s ", Title);					  \
+      yy_symbol_print (stderr,						  \
+		  Type, Value, Location, scanner, result); \
+      YYFPRINTF (stderr, "\n");						  \
+    }									  \
+} while (YYID (0))
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT.  |
+`--------------------------------*/
+
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, void *scanner, struct predicate_node **result)
+#else
+static void
+yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, scanner, result)
+    FILE *yyoutput;
+    int yytype;
+    YYSTYPE const * const yyvaluep;
+    YYLTYPE const * const yylocationp;
+    void *scanner;
+    struct predicate_node **result;
+#endif
+{
+  FILE *yyo = yyoutput;
+  YYUSE (yyo);
+  if (!yyvaluep)
+    return;
+  YYUSE (yylocationp);
+  YYUSE (scanner);
+  YYUSE (result);
+# ifdef YYPRINT
+  if (yytype < YYNTOKENS)
+    YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
+# else
+  YYUSE (yyoutput);
+# endif
+  YYUSE (yytype);
+}
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT.  |
+`--------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, void *scanner, struct predicate_node **result)
+#else
+static void
+yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp, scanner, result)
+    FILE *yyoutput;
+    int yytype;
+    YYSTYPE const * const yyvaluep;
+    YYLTYPE const * const yylocationp;
+    void *scanner;
+    struct predicate_node **result;
+#endif
+{
+  if (yytype < YYNTOKENS)
+    YYFPRINTF (yyoutput, "token %s (", yytname[yytype]);
+  else
+    YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]);
+
+  YY_LOCATION_PRINT (yyoutput, *yylocationp);
+  YYFPRINTF (yyoutput, ": ");
+  yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, scanner, result);
+  YYFPRINTF (yyoutput, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included).                                                   |
+`------------------------------------------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop)
+#else
+static void
+yy_stack_print (yybottom, yytop)
+    yytype_int16 *yybottom;
+    yytype_int16 *yytop;
+#endif
+{
+  YYFPRINTF (stderr, "Stack now");
+  for (; yybottom <= yytop; yybottom++)
+    {
+      int yybot = *yybottom;
+      YYFPRINTF (stderr, " %d", yybot);
+    }
+  YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top)				\
+do {								\
+  if (yydebug)							\
+    yy_stack_print ((Bottom), (Top));				\
+} while (YYID (0))
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced.  |
+`------------------------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, void *scanner, struct predicate_node **result)
+#else
+static void
+yy_reduce_print (yyvsp, yylsp, yyrule, scanner, result)
+    YYSTYPE *yyvsp;
+    YYLTYPE *yylsp;
+    int yyrule;
+    void *scanner;
+    struct predicate_node **result;
+#endif
+{
+  int yynrhs = yyr2[yyrule];
+  int yyi;
+  unsigned long int yylno = yyrline[yyrule];
+  YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+	     yyrule - 1, yylno);
+  /* The symbols being reduced.  */
+  for (yyi = 0; yyi < yynrhs; yyi++)
+    {
+      YYFPRINTF (stderr, "   $%d = ", yyi + 1);
+      yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi],
+		       &(yyvsp[(yyi + 1) - (yynrhs)])
+		       , &(yylsp[(yyi + 1) - (yynrhs)])		       , scanner, result);
+      YYFPRINTF (stderr, "\n");
+    }
+}
+
+# define YY_REDUCE_PRINT(Rule)		\
+do {					\
+  if (yydebug)				\
+    yy_reduce_print (yyvsp, yylsp, Rule, scanner, result); \
+} while (YYID (0))
+
+/* Nonzero means print parse trace.  It is left uninitialized so that
+   multiple parsers can coexist.  */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks.  */
+#ifndef	YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+   if the built-in stack extension method is used).
+
+   Do not make this value too large; the results are undefined if
+   YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+   evaluated with infinite-precision integer arithmetic.  */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+#  if defined __GLIBC__ && defined _STRING_H
+#   define yystrlen strlen
+#  else
+/* Return the length of YYSTR.  */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static YYSIZE_T
+yystrlen (const char *yystr)
+#else
+static YYSIZE_T
+yystrlen (yystr)
+    const char *yystr;
+#endif
+{
+  YYSIZE_T yylen;
+  for (yylen = 0; yystr[yylen]; yylen++)
+    continue;
+  return yylen;
+}
+#  endif
+# endif
+
+# ifndef yystpcpy
+#  if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+#   define yystpcpy stpcpy
+#  else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+   YYDEST.  */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+#else
+static char *
+yystpcpy (yydest, yysrc)
+    char *yydest;
+    const char *yysrc;
+#endif
+{
+  char *yyd = yydest;
+  const char *yys = yysrc;
+
+  while ((*yyd++ = *yys++) != '\0')
+    continue;
+
+  return yyd - 1;
+}
+#  endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+   quotes and backslashes, so that it's suitable for yyerror.  The
+   heuristic is that double-quoting is unnecessary unless the string
+   contains an apostrophe, a comma, or backslash (other than
+   backslash-backslash).  YYSTR is taken from yytname.  If YYRES is
+   null, do not copy; instead, return the length of what the result
+   would have been.  */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+  if (*yystr == '"')
+    {
+      YYSIZE_T yyn = 0;
+      char const *yyp = yystr;
+
+      for (;;)
+	switch (*++yyp)
+	  {
+	  case '\'':
+	  case ',':
+	    goto do_not_strip_quotes;
+
+	  case '\\':
+	    if (*++yyp != '\\')
+	      goto do_not_strip_quotes;
+	    /* Fall through.  */
+	  default:
+	    if (yyres)
+	      yyres[yyn] = *yyp;
+	    yyn++;
+	    break;
+
+	  case '"':
+	    if (yyres)
+	      yyres[yyn] = '\0';
+	    return yyn;
+	  }
+    do_not_strip_quotes: ;
+    }
+
+  if (! yyres)
+    return yystrlen (yystr);
+
+  return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message
+   about the unexpected token YYTOKEN for the state stack whose top is
+   YYSSP.
+
+   Return 0 if *YYMSG was successfully written.  Return 1 if *YYMSG is
+   not large enough to hold the message.  In that case, also set
+   *YYMSG_ALLOC to the required number of bytes.  Return 2 if the
+   required number of bytes is too large to store.  */
+static int
+yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg,
+                yytype_int16 *yyssp, int yytoken)
+{
+  YYSIZE_T yysize0 = yytnamerr (YY_NULL, yytname[yytoken]);
+  YYSIZE_T yysize = yysize0;
+  enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+  /* Internationalized format string. */
+  const char *yyformat = YY_NULL;
+  /* Arguments of yyformat. */
+  char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+  /* Number of reported tokens (one for the "unexpected", one per
+     "expected"). */
+  int yycount = 0;
+
+  /* There are many possibilities here to consider:
+     - Assume YYFAIL is not used.  It's too flawed to consider.  See
+       <http://lists.gnu.org/archive/html/bison-patches/2009-12/msg00024.html>
+       for details.  YYERROR is fine as it does not invoke this
+       function.
+     - If this state is a consistent state with a default action, then
+       the only way this function was invoked is if the default action
+       is an error action.  In that case, don't check for expected
+       tokens because there are none.
+     - The only way there can be no lookahead present (in yychar) is if
+       this state is a consistent state with a default action.  Thus,
+       detecting the absence of a lookahead is sufficient to determine
+       that there is no unexpected or expected token to report.  In that
+       case, just report a simple "syntax error".
+     - Don't assume there isn't a lookahead just because this state is a
+       consistent state with a default action.  There might have been a
+       previous inconsistent state, consistent state with a non-default
+       action, or user semantic action that manipulated yychar.
+     - Of course, the expected token list depends on states to have
+       correct lookahead information, and it depends on the parser not
+       to perform extra reductions after fetching a lookahead from the
+       scanner and before detecting a syntax error.  Thus, state merging
+       (from LALR or IELR) and default reductions corrupt the expected
+       token list.  However, the list is correct for canonical LR with
+       one exception: it will still contain any token that will not be
+       accepted due to an error action in a later state.
+  */
+  if (yytoken != YYEMPTY)
+    {
+      int yyn = yypact[*yyssp];
+      yyarg[yycount++] = yytname[yytoken];
+      if (!yypact_value_is_default (yyn))
+        {
+          /* Start YYX at -YYN if negative to avoid negative indexes in
+             YYCHECK.  In other words, skip the first -YYN actions for
+             this state because they are default actions.  */
+          int yyxbegin = yyn < 0 ? -yyn : 0;
+          /* Stay within bounds of both yycheck and yytname.  */
+          int yychecklim = YYLAST - yyn + 1;
+          int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+          int yyx;
+
+          for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+            if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR
+                && !yytable_value_is_error (yytable[yyx + yyn]))
+              {
+                if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+                  {
+                    yycount = 1;
+                    yysize = yysize0;
+                    break;
+                  }
+                yyarg[yycount++] = yytname[yyx];
+                {
+                  YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULL, yytname[yyx]);
+                  if (! (yysize <= yysize1
+                         && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+                    return 2;
+                  yysize = yysize1;
+                }
+              }
+        }
+    }
+
+  switch (yycount)
+    {
+# define YYCASE_(N, S)                      \
+      case N:                               \
+        yyformat = S;                       \
+      break
+      YYCASE_(0, YY_("syntax error"));
+      YYCASE_(1, YY_("syntax error, unexpected %s"));
+      YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
+      YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+      YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+      YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+# undef YYCASE_
+    }
+
+  {
+    YYSIZE_T yysize1 = yysize + yystrlen (yyformat);
+    if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+      return 2;
+    yysize = yysize1;
+  }
+
+  if (*yymsg_alloc < yysize)
+    {
+      *yymsg_alloc = 2 * yysize;
+      if (! (yysize <= *yymsg_alloc
+             && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM))
+        *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM;
+      return 1;
+    }
+
+  /* Avoid sprintf, as that infringes on the user's name space.
+     Don't have undefined behavior even if the translation
+     produced a string with the wrong number of "%s"s.  */
+  {
+    char *yyp = *yymsg;
+    int yyi = 0;
+    while ((*yyp = *yyformat) != '\0')
+      if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount)
+        {
+          yyp += yytnamerr (yyp, yyarg[yyi++]);
+          yyformat += 2;
+        }
+      else
+        {
+          yyp++;
+          yyformat++;
+        }
+  }
+  return 0;
+}
+#endif /* YYERROR_VERBOSE */
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol.  |
+`-----------------------------------------------*/
+
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, void *scanner, struct predicate_node **result)
+#else
+static void
+yydestruct (yymsg, yytype, yyvaluep, yylocationp, scanner, result)
+    const char *yymsg;
+    int yytype;
+    YYSTYPE *yyvaluep;
+    YYLTYPE *yylocationp;
+    void *scanner;
+    struct predicate_node **result;
+#endif
+{
+  YYUSE (yyvaluep);
+  YYUSE (yylocationp);
+  YYUSE (scanner);
+  YYUSE (result);
+
+  if (!yymsg)
+    yymsg = "Deleting";
+  YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
+
+  switch (yytype)
+    {
+      case 3: /* STRING */
+/* Line 1393 of yacc.c  */
+#line 240 "predicate/BPredicate.y"
+        {
+    free(((*yyvaluep).text));
+};
+/* Line 1393 of yacc.c  */
+#line 1391 "generated//bison_BPredicate.c"
+        break;
+      case 4: /* NAME */
+/* Line 1393 of yacc.c  */
+#line 240 "predicate/BPredicate.y"
+        {
+    free(((*yyvaluep).text));
+};
+/* Line 1393 of yacc.c  */
+#line 1400 "generated//bison_BPredicate.c"
+        break;
+      case 17: /* predicate */
+/* Line 1393 of yacc.c  */
+#line 250 "predicate/BPredicate.y"
+        {
+    if (((*yyvaluep).node)) {
+        free_predicate_node(((*yyvaluep).node));
+    }
+};
+/* Line 1393 of yacc.c  */
+#line 1411 "generated//bison_BPredicate.c"
+        break;
+      case 18: /* constant */
+/* Line 1393 of yacc.c  */
+#line 250 "predicate/BPredicate.y"
+        {
+    if (((*yyvaluep).node)) {
+        free_predicate_node(((*yyvaluep).node));
+    }
+};
+/* Line 1393 of yacc.c  */
+#line 1422 "generated//bison_BPredicate.c"
+        break;
+      case 19: /* parentheses */
+/* Line 1393 of yacc.c  */
+#line 250 "predicate/BPredicate.y"
+        {
+    if (((*yyvaluep).node)) {
+        free_predicate_node(((*yyvaluep).node));
+    }
+};
+/* Line 1393 of yacc.c  */
+#line 1433 "generated//bison_BPredicate.c"
+        break;
+      case 20: /* neg */
+/* Line 1393 of yacc.c  */
+#line 250 "predicate/BPredicate.y"
+        {
+    if (((*yyvaluep).node)) {
+        free_predicate_node(((*yyvaluep).node));
+    }
+};
+/* Line 1393 of yacc.c  */
+#line 1444 "generated//bison_BPredicate.c"
+        break;
+      case 21: /* conjunct */
+/* Line 1393 of yacc.c  */
+#line 250 "predicate/BPredicate.y"
+        {
+    if (((*yyvaluep).node)) {
+        free_predicate_node(((*yyvaluep).node));
+    }
+};
+/* Line 1393 of yacc.c  */
+#line 1455 "generated//bison_BPredicate.c"
+        break;
+      case 22: /* disjunct */
+/* Line 1393 of yacc.c  */
+#line 250 "predicate/BPredicate.y"
+        {
+    if (((*yyvaluep).node)) {
+        free_predicate_node(((*yyvaluep).node));
+    }
+};
+/* Line 1393 of yacc.c  */
+#line 1466 "generated//bison_BPredicate.c"
+        break;
+      case 23: /* function */
+/* Line 1393 of yacc.c  */
+#line 250 "predicate/BPredicate.y"
+        {
+    if (((*yyvaluep).node)) {
+        free_predicate_node(((*yyvaluep).node));
+    }
+};
+/* Line 1393 of yacc.c  */
+#line 1477 "generated//bison_BPredicate.c"
+        break;
+      case 24: /* arguments */
+/* Line 1393 of yacc.c  */
+#line 257 "predicate/BPredicate.y"
+        {
+    if (((*yyvaluep).arg_node)) {
+        free_arguments_node(((*yyvaluep).arg_node));
+    }
+};
+/* Line 1393 of yacc.c  */
+#line 1488 "generated//bison_BPredicate.c"
+        break;
+      case 25: /* argument */
+/* Line 1393 of yacc.c  */
+#line 264 "predicate/BPredicate.y"
+        {
+    free_argument(((*yyvaluep).arg_arg));
+};
+/* Line 1393 of yacc.c  */
+#line 1497 "generated//bison_BPredicate.c"
+        break;
+
+      default:
+        break;
+    }
+}
+
+
+
+
+/*----------.
+| yyparse.  |
+`----------*/
+
+#ifdef YYPARSE_PARAM
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+int
+yyparse (void *YYPARSE_PARAM)
+#else
+int
+yyparse (YYPARSE_PARAM)
+    void *YYPARSE_PARAM;
+#endif
+#else /* ! YYPARSE_PARAM */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+int
+yyparse (void *scanner, struct predicate_node **result)
+#else
+int
+yyparse (scanner, result)
+    void *scanner;
+    struct predicate_node **result;
+#endif
+#endif
+{
+/* The lookahead symbol.  */
+int yychar;
+
+
+#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
+/* Suppress an incorrect diagnostic about yylval being uninitialized.  */
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+    _Pragma ("GCC diagnostic push") \
+    _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\
+    _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+    _Pragma ("GCC diagnostic pop")
+#else
+/* Default value used for initialization, for pacifying older GCCs
+   or non-GCC compilers.  */
+static YYSTYPE yyval_default;
+# define YY_INITIAL_VALUE(Value) = Value
+#endif
+static YYLTYPE yyloc_default
+# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL
+  = { 1, 1, 1, 1 }
+# endif
+;
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+/* The semantic value of the lookahead symbol.  */
+YYSTYPE yylval YY_INITIAL_VALUE(yyval_default);
+
+/* Location data for the lookahead symbol.  */
+YYLTYPE yylloc = yyloc_default;
+
+
+    /* Number of syntax errors so far.  */
+    int yynerrs;
+
+    int yystate;
+    /* Number of tokens to shift before error messages enabled.  */
+    int yyerrstatus;
+
+    /* The stacks and their tools:
+       `yyss': related to states.
+       `yyvs': related to semantic values.
+       `yyls': related to locations.
+
+       Refer to the stacks through separate pointers, to allow yyoverflow
+       to reallocate them elsewhere.  */
+
+    /* The state stack.  */
+    yytype_int16 yyssa[YYINITDEPTH];
+    yytype_int16 *yyss;
+    yytype_int16 *yyssp;
+
+    /* The semantic value stack.  */
+    YYSTYPE yyvsa[YYINITDEPTH];
+    YYSTYPE *yyvs;
+    YYSTYPE *yyvsp;
+
+    /* The location stack.  */
+    YYLTYPE yylsa[YYINITDEPTH];
+    YYLTYPE *yyls;
+    YYLTYPE *yylsp;
+
+    /* The locations where the error started and ended.  */
+    YYLTYPE yyerror_range[3];
+
+    YYSIZE_T yystacksize;
+
+  int yyn;
+  int yyresult;
+  /* Lookahead token as an internal (translated) token number.  */
+  int yytoken = 0;
+  /* The variables used to return semantic value and location from the
+     action routines.  */
+  YYSTYPE yyval;
+  YYLTYPE yyloc;
+
+#if YYERROR_VERBOSE
+  /* Buffer for error messages, and its allocated size.  */
+  char yymsgbuf[128];
+  char *yymsg = yymsgbuf;
+  YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+#define YYPOPSTACK(N)   (yyvsp -= (N), yyssp -= (N), yylsp -= (N))
+
+  /* The number of symbols on the RHS of the reduced rule.
+     Keep to zero when no symbol should be popped.  */
+  int yylen = 0;
+
+  yyssp = yyss = yyssa;
+  yyvsp = yyvs = yyvsa;
+  yylsp = yyls = yylsa;
+  yystacksize = YYINITDEPTH;
+
+  YYDPRINTF ((stderr, "Starting parse\n"));
+
+  yystate = 0;
+  yyerrstatus = 0;
+  yynerrs = 0;
+  yychar = YYEMPTY; /* Cause a token to be read.  */
+  yylsp[0] = yylloc;
+  goto yysetstate;
+
+/*------------------------------------------------------------.
+| yynewstate -- Push a new state, which is found in yystate.  |
+`------------------------------------------------------------*/
+ yynewstate:
+  /* In all cases, when you get here, the value and location stacks
+     have just been pushed.  So pushing a state here evens the stacks.  */
+  yyssp++;
+
+ yysetstate:
+  *yyssp = yystate;
+
+  if (yyss + yystacksize - 1 <= yyssp)
+    {
+      /* Get the current used size of the three stacks, in elements.  */
+      YYSIZE_T yysize = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+      {
+	/* Give user a chance to reallocate the stack.  Use copies of
+	   these so that the &'s don't force the real ones into
+	   memory.  */
+	YYSTYPE *yyvs1 = yyvs;
+	yytype_int16 *yyss1 = yyss;
+	YYLTYPE *yyls1 = yyls;
+
+	/* Each stack pointer address is followed by the size of the
+	   data in use in that stack, in bytes.  This used to be a
+	   conditional around just the two extra args, but that might
+	   be undefined if yyoverflow is a macro.  */
+	yyoverflow (YY_("memory exhausted"),
+		    &yyss1, yysize * sizeof (*yyssp),
+		    &yyvs1, yysize * sizeof (*yyvsp),
+		    &yyls1, yysize * sizeof (*yylsp),
+		    &yystacksize);
+
+	yyls = yyls1;
+	yyss = yyss1;
+	yyvs = yyvs1;
+      }
+#else /* no yyoverflow */
+# ifndef YYSTACK_RELOCATE
+      goto yyexhaustedlab;
+# else
+      /* Extend the stack our own way.  */
+      if (YYMAXDEPTH <= yystacksize)
+	goto yyexhaustedlab;
+      yystacksize *= 2;
+      if (YYMAXDEPTH < yystacksize)
+	yystacksize = YYMAXDEPTH;
+
+      {
+	yytype_int16 *yyss1 = yyss;
+	union yyalloc *yyptr =
+	  (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+	if (! yyptr)
+	  goto yyexhaustedlab;
+	YYSTACK_RELOCATE (yyss_alloc, yyss);
+	YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+	YYSTACK_RELOCATE (yyls_alloc, yyls);
+#  undef YYSTACK_RELOCATE
+	if (yyss1 != yyssa)
+	  YYSTACK_FREE (yyss1);
+      }
+# endif
+#endif /* no yyoverflow */
+
+      yyssp = yyss + yysize - 1;
+      yyvsp = yyvs + yysize - 1;
+      yylsp = yyls + yysize - 1;
+
+      YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+		  (unsigned long int) yystacksize));
+
+      if (yyss + yystacksize - 1 <= yyssp)
+	YYABORT;
+    }
+
+  YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+
+  if (yystate == YYFINAL)
+    YYACCEPT;
+
+  goto yybackup;
+
+/*-----------.
+| yybackup.  |
+`-----------*/
+yybackup:
+
+  /* Do appropriate processing given the current state.  Read a
+     lookahead token if we need one and don't already have one.  */
+
+  /* First try to decide what to do without reference to lookahead token.  */
+  yyn = yypact[yystate];
+  if (yypact_value_is_default (yyn))
+    goto yydefault;
+
+  /* Not known => get a lookahead token if don't already have one.  */
+
+  /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol.  */
+  if (yychar == YYEMPTY)
+    {
+      YYDPRINTF ((stderr, "Reading a token: "));
+      yychar = YYLEX;
+    }
+
+  if (yychar <= YYEOF)
+    {
+      yychar = yytoken = YYEOF;
+      YYDPRINTF ((stderr, "Now at end of input.\n"));
+    }
+  else
+    {
+      yytoken = YYTRANSLATE (yychar);
+      YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+    }
+
+  /* If the proper action on seeing token YYTOKEN is to reduce or to
+     detect an error, take that action.  */
+  yyn += yytoken;
+  if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+    goto yydefault;
+  yyn = yytable[yyn];
+  if (yyn <= 0)
+    {
+      if (yytable_value_is_error (yyn))
+        goto yyerrlab;
+      yyn = -yyn;
+      goto yyreduce;
+    }
+
+  /* Count tokens shifted since error; after three, turn off error
+     status.  */
+  if (yyerrstatus)
+    yyerrstatus--;
+
+  /* Shift the lookahead token.  */
+  YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+  /* Discard the shifted token.  */
+  yychar = YYEMPTY;
+
+  yystate = yyn;
+  YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+  *++yyvsp = yylval;
+  YY_IGNORE_MAYBE_UNINITIALIZED_END
+  *++yylsp = yylloc;
+  goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state.  |
+`-----------------------------------------------------------*/
+yydefault:
+  yyn = yydefact[yystate];
+  if (yyn == 0)
+    goto yyerrlab;
+  goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- Do a reduction.  |
+`-----------------------------*/
+yyreduce:
+  /* yyn is the number of a rule to reduce with.  */
+  yylen = yyr2[yyn];
+
+  /* If YYLEN is nonzero, implement the default value of the action:
+     `$$ = $1'.
+
+     Otherwise, the following line sets YYVAL to garbage.
+     This behavior is undocumented and Bison
+     users should not rely upon it.  Assigning to YYVAL
+     unconditionally makes the parser a bit smaller, and it avoids a
+     GCC warning that YYVAL may be used uninitialized.  */
+  yyval = yyvsp[1-yylen];
+
+  /* Default location.  */
+  YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen);
+  YY_REDUCE_PRINT (yyn);
+  switch (yyn)
+    {
+        case 2:
+/* Line 1787 of yacc.c  */
+#line 276 "predicate/BPredicate.y"
+    {
+        *result = (yyvsp[(1) - (1)].node);
+    }
+    break;
+
+  case 9:
+/* Line 1787 of yacc.c  */
+#line 284 "predicate/BPredicate.y"
+    {
+        (yyval.node) = make_constant(1);
+    }
+    break;
+
+  case 10:
+/* Line 1787 of yacc.c  */
+#line 288 "predicate/BPredicate.y"
+    {
+        (yyval.node) = make_constant(0);
+    }
+    break;
+
+  case 11:
+/* Line 1787 of yacc.c  */
+#line 294 "predicate/BPredicate.y"
+    {
+        (yyval.node) = (yyvsp[(2) - (3)].node);
+    }
+    break;
+
+  case 12:
+/* Line 1787 of yacc.c  */
+#line 300 "predicate/BPredicate.y"
+    {
+        (yyval.node) = make_negation((yyvsp[(2) - (2)].node));
+    }
+    break;
+
+  case 13:
+/* Line 1787 of yacc.c  */
+#line 306 "predicate/BPredicate.y"
+    {
+        (yyval.node) = make_conjunction((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node));
+    }
+    break;
+
+  case 14:
+/* Line 1787 of yacc.c  */
+#line 312 "predicate/BPredicate.y"
+    {
+        (yyval.node) = make_disjunction((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node));
+    }
+    break;
+
+  case 15:
+/* Line 1787 of yacc.c  */
+#line 318 "predicate/BPredicate.y"
+    {
+        (yyval.node) = make_function((yyvsp[(1) - (3)].text), NULL, 0);
+    }
+    break;
+
+  case 16:
+/* Line 1787 of yacc.c  */
+#line 322 "predicate/BPredicate.y"
+    {
+        (yyval.node) = make_function((yyvsp[(1) - (4)].text), (yyvsp[(3) - (4)].arg_node), 1);
+    }
+    break;
+
+  case 17:
+/* Line 1787 of yacc.c  */
+#line 328 "predicate/BPredicate.y"
+    {
+        (yyval.arg_node) = make_arguments((yyvsp[(1) - (1)].arg_arg), NULL, 0);
+    }
+    break;
+
+  case 18:
+/* Line 1787 of yacc.c  */
+#line 332 "predicate/BPredicate.y"
+    {
+        (yyval.arg_node) = make_arguments((yyvsp[(1) - (3)].arg_arg), (yyvsp[(3) - (3)].arg_node), 1);
+    }
+    break;
+
+  case 19:
+/* Line 1787 of yacc.c  */
+#line 338 "predicate/BPredicate.y"
+    {
+        (yyval.arg_arg) = make_argument_predicate((yyvsp[(1) - (1)].node));
+    }
+    break;
+
+  case 20:
+/* Line 1787 of yacc.c  */
+#line 342 "predicate/BPredicate.y"
+    {
+        (yyval.arg_arg) = make_argument_string((yyvsp[(1) - (1)].text));
+    }
+    break;
+
+
+/* Line 1787 of yacc.c  */
+#line 1932 "generated//bison_BPredicate.c"
+      default: break;
+    }
+  /* User semantic actions sometimes alter yychar, and that requires
+     that yytoken be updated with the new translation.  We take the
+     approach of translating immediately before every use of yytoken.
+     One alternative is translating here after every semantic action,
+     but that translation would be missed if the semantic action invokes
+     YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+     if it invokes YYBACKUP.  In the case of YYABORT or YYACCEPT, an
+     incorrect destructor might then be invoked immediately.  In the
+     case of YYERROR or YYBACKUP, subsequent parser actions might lead
+     to an incorrect destructor call or verbose syntax error message
+     before the lookahead is translated.  */
+  YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+  YYPOPSTACK (yylen);
+  yylen = 0;
+  YY_STACK_PRINT (yyss, yyssp);
+
+  *++yyvsp = yyval;
+  *++yylsp = yyloc;
+
+  /* Now `shift' the result of the reduction.  Determine what state
+     that goes to, based on the state we popped back to and the rule
+     number reduced by.  */
+
+  yyn = yyr1[yyn];
+
+  yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
+  if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+    yystate = yytable[yystate];
+  else
+    yystate = yydefgoto[yyn - YYNTOKENS];
+
+  goto yynewstate;
+
+
+/*------------------------------------.
+| yyerrlab -- here on detecting error |
+`------------------------------------*/
+yyerrlab:
+  /* Make sure we have latest lookahead translation.  See comments at
+     user semantic actions for why this is necessary.  */
+  yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar);
+
+  /* If not already recovering from an error, report this error.  */
+  if (!yyerrstatus)
+    {
+      ++yynerrs;
+#if ! YYERROR_VERBOSE
+      yyerror (&yylloc, scanner, result, YY_("syntax error"));
+#else
+# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \
+                                        yyssp, yytoken)
+      {
+        char const *yymsgp = YY_("syntax error");
+        int yysyntax_error_status;
+        yysyntax_error_status = YYSYNTAX_ERROR;
+        if (yysyntax_error_status == 0)
+          yymsgp = yymsg;
+        else if (yysyntax_error_status == 1)
+          {
+            if (yymsg != yymsgbuf)
+              YYSTACK_FREE (yymsg);
+            yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc);
+            if (!yymsg)
+              {
+                yymsg = yymsgbuf;
+                yymsg_alloc = sizeof yymsgbuf;
+                yysyntax_error_status = 2;
+              }
+            else
+              {
+                yysyntax_error_status = YYSYNTAX_ERROR;
+                yymsgp = yymsg;
+              }
+          }
+        yyerror (&yylloc, scanner, result, yymsgp);
+        if (yysyntax_error_status == 2)
+          goto yyexhaustedlab;
+      }
+# undef YYSYNTAX_ERROR
+#endif
+    }
+
+  yyerror_range[1] = yylloc;
+
+  if (yyerrstatus == 3)
+    {
+      /* If just tried and failed to reuse lookahead token after an
+	 error, discard it.  */
+
+      if (yychar <= YYEOF)
+	{
+	  /* Return failure if at end of input.  */
+	  if (yychar == YYEOF)
+	    YYABORT;
+	}
+      else
+	{
+	  yydestruct ("Error: discarding",
+		      yytoken, &yylval, &yylloc, scanner, result);
+	  yychar = YYEMPTY;
+	}
+    }
+
+  /* Else will try to reuse lookahead token after shifting the error
+     token.  */
+  goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR.  |
+`---------------------------------------------------*/
+yyerrorlab:
+
+  /* Pacify compilers like GCC when the user code never invokes
+     YYERROR and the label yyerrorlab therefore never appears in user
+     code.  */
+  if (/*CONSTCOND*/ 0)
+     goto yyerrorlab;
+
+  yyerror_range[1] = yylsp[1-yylen];
+  /* Do not reclaim the symbols of the rule which action triggered
+     this YYERROR.  */
+  YYPOPSTACK (yylen);
+  yylen = 0;
+  YY_STACK_PRINT (yyss, yyssp);
+  yystate = *yyssp;
+  goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR.  |
+`-------------------------------------------------------------*/
+yyerrlab1:
+  yyerrstatus = 3;	/* Each real token shifted decrements this.  */
+
+  for (;;)
+    {
+      yyn = yypact[yystate];
+      if (!yypact_value_is_default (yyn))
+	{
+	  yyn += YYTERROR;
+	  if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+	    {
+	      yyn = yytable[yyn];
+	      if (0 < yyn)
+		break;
+	    }
+	}
+
+      /* Pop the current state because it cannot handle the error token.  */
+      if (yyssp == yyss)
+	YYABORT;
+
+      yyerror_range[1] = *yylsp;
+      yydestruct ("Error: popping",
+		  yystos[yystate], yyvsp, yylsp, scanner, result);
+      YYPOPSTACK (1);
+      yystate = *yyssp;
+      YY_STACK_PRINT (yyss, yyssp);
+    }
+
+  YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+  *++yyvsp = yylval;
+  YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+  yyerror_range[2] = yylloc;
+  /* Using YYLLOC is tempting, but would change the location of
+     the lookahead.  YYLOC is available though.  */
+  YYLLOC_DEFAULT (yyloc, yyerror_range, 2);
+  *++yylsp = yyloc;
+
+  /* Shift the error token.  */
+  YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+  yystate = yyn;
+  goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here.  |
+`-------------------------------------*/
+yyacceptlab:
+  yyresult = 0;
+  goto yyreturn;
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here.  |
+`-----------------------------------*/
+yyabortlab:
+  yyresult = 1;
+  goto yyreturn;
+
+#if !defined yyoverflow || YYERROR_VERBOSE
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here.  |
+`-------------------------------------------------*/
+yyexhaustedlab:
+  yyerror (&yylloc, scanner, result, YY_("memory exhausted"));
+  yyresult = 2;
+  /* Fall through.  */
+#endif
+
+yyreturn:
+  if (yychar != YYEMPTY)
+    {
+      /* Make sure we have latest lookahead translation.  See comments at
+         user semantic actions for why this is necessary.  */
+      yytoken = YYTRANSLATE (yychar);
+      yydestruct ("Cleanup: discarding lookahead",
+                  yytoken, &yylval, &yylloc, scanner, result);
+    }
+  /* Do not reclaim the symbols of the rule which action triggered
+     this YYABORT or YYACCEPT.  */
+  YYPOPSTACK (yylen);
+  YY_STACK_PRINT (yyss, yyssp);
+  while (yyssp != yyss)
+    {
+      yydestruct ("Cleanup: popping",
+		  yystos[*yyssp], yyvsp, yylsp, scanner, result);
+      YYPOPSTACK (1);
+    }
+#ifndef yyoverflow
+  if (yyss != yyssa)
+    YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+  if (yymsg != yymsgbuf)
+    YYSTACK_FREE (yymsg);
+#endif
+  /* Make sure YYID is used.  */
+  return YYID (yyresult);
+}
+
+
diff --git a/external/badvpn_dns/generated/bison_BPredicate.h b/external/badvpn_dns/generated/bison_BPredicate.h
new file mode 100644
index 0000000..2bf36ce
--- /dev/null
+++ b/external/badvpn_dns/generated/bison_BPredicate.h
@@ -0,0 +1,114 @@
+/* A Bison parser, made by GNU Bison 2.7.12-4996.  */
+
+/* Bison interface for Yacc-like parsers in C
+   
+      Copyright (C) 1984, 1989-1990, 2000-2013 Free Software Foundation, Inc.
+   
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+   
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
+   
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
+
+#ifndef YY_YY_GENERATED_BISON_BPREDICATE_H_INCLUDED
+# define YY_YY_GENERATED_BISON_BPREDICATE_H_INCLUDED
+/* Enabling traces.  */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Tokens.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+   /* Put the tokens into the symbol table, so that GDB and other debuggers
+      know about them.  */
+   enum yytokentype {
+     STRING = 258,
+     NAME = 259,
+     PEER1_NAME = 260,
+     PEER2_NAME = 261,
+     AND = 262,
+     OR = 263,
+     NOT = 264,
+     SPAR = 265,
+     EPAR = 266,
+     CONSTANT_TRUE = 267,
+     CONSTANT_FALSE = 268,
+     COMMA = 269
+   };
+#endif
+
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
+{
+/* Line 2053 of yacc.c  */
+#line 227 "predicate/BPredicate.y"
+
+    char *text;
+    struct predicate_node *node;
+    struct arguments_node *arg_node;
+    struct predicate_node nfaw;
+    struct arguments_arg arg_arg;
+
+
+/* Line 2053 of yacc.c  */
+#line 80 "generated//bison_BPredicate.h"
+} YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED
+typedef struct YYLTYPE
+{
+  int first_line;
+  int first_column;
+  int last_line;
+  int last_column;
+} YYLTYPE;
+# define yyltype YYLTYPE /* obsolescent; will be withdrawn */
+# define YYLTYPE_IS_DECLARED 1
+# define YYLTYPE_IS_TRIVIAL 1
+#endif
+
+
+#ifdef YYPARSE_PARAM
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void *YYPARSE_PARAM);
+#else
+int yyparse ();
+#endif
+#else /* ! YYPARSE_PARAM */
+#if defined __STDC__ || defined __cplusplus
+int yyparse (void *scanner, struct predicate_node **result);
+#else
+int yyparse ();
+#endif
+#endif /* ! YYPARSE_PARAM */
+
+#endif /* !YY_YY_GENERATED_BISON_BPREDICATE_H_INCLUDED  */
diff --git a/external/badvpn_dns/generated/blog_channel_BArpProbe.h b/external/badvpn_dns/generated/blog_channel_BArpProbe.h
new file mode 100644
index 0000000..f168e63
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BArpProbe.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BArpProbe
diff --git a/external/badvpn_dns/generated/blog_channel_BConnection.h b/external/badvpn_dns/generated/blog_channel_BConnection.h
new file mode 100644
index 0000000..8447db0
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BConnection.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BConnection
diff --git a/external/badvpn_dns/generated/blog_channel_BDHCPClient.h b/external/badvpn_dns/generated/blog_channel_BDHCPClient.h
new file mode 100644
index 0000000..aacf34d
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BDHCPClient.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BDHCPClient
diff --git a/external/badvpn_dns/generated/blog_channel_BDHCPClientCore.h b/external/badvpn_dns/generated/blog_channel_BDHCPClientCore.h
new file mode 100644
index 0000000..64fc74d
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BDHCPClientCore.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BDHCPClientCore
diff --git a/external/badvpn_dns/generated/blog_channel_BDatagram.h b/external/badvpn_dns/generated/blog_channel_BDatagram.h
new file mode 100644
index 0000000..d95cf24
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BDatagram.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BDatagram
diff --git a/external/badvpn_dns/generated/blog_channel_BEncryption.h b/external/badvpn_dns/generated/blog_channel_BEncryption.h
new file mode 100644
index 0000000..6991f37
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BEncryption.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BEncryption
diff --git a/external/badvpn_dns/generated/blog_channel_BInputProcess.h b/external/badvpn_dns/generated/blog_channel_BInputProcess.h
new file mode 100644
index 0000000..f65f715
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BInputProcess.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BInputProcess
diff --git a/external/badvpn_dns/generated/blog_channel_BLockReactor.h b/external/badvpn_dns/generated/blog_channel_BLockReactor.h
new file mode 100644
index 0000000..5aab6d4
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BLockReactor.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BLockReactor
diff --git a/external/badvpn_dns/generated/blog_channel_BNetwork.h b/external/badvpn_dns/generated/blog_channel_BNetwork.h
new file mode 100644
index 0000000..c5e3bc1
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BNetwork.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BNetwork
diff --git a/external/badvpn_dns/generated/blog_channel_BPredicate.h b/external/badvpn_dns/generated/blog_channel_BPredicate.h
new file mode 100644
index 0000000..1a683f1
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BPredicate.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BPredicate
diff --git a/external/badvpn_dns/generated/blog_channel_BProcess.h b/external/badvpn_dns/generated/blog_channel_BProcess.h
new file mode 100644
index 0000000..e11e5a6
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BProcess.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BProcess
diff --git a/external/badvpn_dns/generated/blog_channel_BReactor.h b/external/badvpn_dns/generated/blog_channel_BReactor.h
new file mode 100644
index 0000000..d111dc7
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BReactor.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BReactor
diff --git a/external/badvpn_dns/generated/blog_channel_BSSLConnection.h b/external/badvpn_dns/generated/blog_channel_BSSLConnection.h
new file mode 100644
index 0000000..bd55826
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BSSLConnection.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BSSLConnection
diff --git a/external/badvpn_dns/generated/blog_channel_BSignal.h b/external/badvpn_dns/generated/blog_channel_BSignal.h
new file mode 100644
index 0000000..2820ebc
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BSignal.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BSignal
diff --git a/external/badvpn_dns/generated/blog_channel_BSocksClient.h b/external/badvpn_dns/generated/blog_channel_BSocksClient.h
new file mode 100644
index 0000000..72086fa
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BSocksClient.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BSocksClient
diff --git a/external/badvpn_dns/generated/blog_channel_BTap.h b/external/badvpn_dns/generated/blog_channel_BTap.h
new file mode 100644
index 0000000..ab3e951
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BTap.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BTap
diff --git a/external/badvpn_dns/generated/blog_channel_BThreadSignal.h b/external/badvpn_dns/generated/blog_channel_BThreadSignal.h
new file mode 100644
index 0000000..f39fc36
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BThreadSignal.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BThreadSignal
diff --git a/external/badvpn_dns/generated/blog_channel_BThreadWork.h b/external/badvpn_dns/generated/blog_channel_BThreadWork.h
new file mode 100644
index 0000000..f68383c
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BThreadWork.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BThreadWork
diff --git a/external/badvpn_dns/generated/blog_channel_BTime.h b/external/badvpn_dns/generated/blog_channel_BTime.h
new file mode 100644
index 0000000..b323ee7
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BTime.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BTime
diff --git a/external/badvpn_dns/generated/blog_channel_BUnixSignal.h b/external/badvpn_dns/generated/blog_channel_BUnixSignal.h
new file mode 100644
index 0000000..914b21b
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_BUnixSignal.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_BUnixSignal
diff --git a/external/badvpn_dns/generated/blog_channel_DPReceive.h b/external/badvpn_dns/generated/blog_channel_DPReceive.h
new file mode 100644
index 0000000..99889b5
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_DPReceive.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_DPReceive
diff --git a/external/badvpn_dns/generated/blog_channel_DPRelay.h b/external/badvpn_dns/generated/blog_channel_DPRelay.h
new file mode 100644
index 0000000..bc0153b
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_DPRelay.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_DPRelay
diff --git a/external/badvpn_dns/generated/blog_channel_DataProto.h b/external/badvpn_dns/generated/blog_channel_DataProto.h
new file mode 100644
index 0000000..a6f900a
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_DataProto.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_DataProto
diff --git a/external/badvpn_dns/generated/blog_channel_DatagramPeerIO.h b/external/badvpn_dns/generated/blog_channel_DatagramPeerIO.h
new file mode 100644
index 0000000..16e37b5
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_DatagramPeerIO.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_DatagramPeerIO
diff --git a/external/badvpn_dns/generated/blog_channel_FragmentProtoAssembler.h b/external/badvpn_dns/generated/blog_channel_FragmentProtoAssembler.h
new file mode 100644
index 0000000..25289ef
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_FragmentProtoAssembler.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_FragmentProtoAssembler
diff --git a/external/badvpn_dns/generated/blog_channel_FrameDecider.h b/external/badvpn_dns/generated/blog_channel_FrameDecider.h
new file mode 100644
index 0000000..5dbf3c4
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_FrameDecider.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_FrameDecider
diff --git a/external/badvpn_dns/generated/blog_channel_LineBuffer.h b/external/badvpn_dns/generated/blog_channel_LineBuffer.h
new file mode 100644
index 0000000..4286a74
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_LineBuffer.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_LineBuffer
diff --git a/external/badvpn_dns/generated/blog_channel_Listener.h b/external/badvpn_dns/generated/blog_channel_Listener.h
new file mode 100644
index 0000000..f61bfb3
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_Listener.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_Listener
diff --git a/external/badvpn_dns/generated/blog_channel_NCDBuildProgram.h b/external/badvpn_dns/generated/blog_channel_NCDBuildProgram.h
new file mode 100644
index 0000000..1a6cdf9
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDBuildProgram.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDBuildProgram
diff --git a/external/badvpn_dns/generated/blog_channel_NCDConfigParser.h b/external/badvpn_dns/generated/blog_channel_NCDConfigParser.h
new file mode 100644
index 0000000..92d98d0
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDConfigParser.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDConfigParser
diff --git a/external/badvpn_dns/generated/blog_channel_NCDConfigTokenizer.h b/external/badvpn_dns/generated/blog_channel_NCDConfigTokenizer.h
new file mode 100644
index 0000000..0b3b689
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDConfigTokenizer.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDConfigTokenizer
diff --git a/external/badvpn_dns/generated/blog_channel_NCDIfConfig.h b/external/badvpn_dns/generated/blog_channel_NCDIfConfig.h
new file mode 100644
index 0000000..91bdbda
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDIfConfig.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDIfConfig
diff --git a/external/badvpn_dns/generated/blog_channel_NCDInterfaceMonitor.h b/external/badvpn_dns/generated/blog_channel_NCDInterfaceMonitor.h
new file mode 100644
index 0000000..22c0f8d
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDInterfaceMonitor.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDInterfaceMonitor
diff --git a/external/badvpn_dns/generated/blog_channel_NCDModuleIndex.h b/external/badvpn_dns/generated/blog_channel_NCDModuleIndex.h
new file mode 100644
index 0000000..3487664
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDModuleIndex.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDModuleIndex
diff --git a/external/badvpn_dns/generated/blog_channel_NCDModuleProcess.h b/external/badvpn_dns/generated/blog_channel_NCDModuleProcess.h
new file mode 100644
index 0000000..db34dcc
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDModuleProcess.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDModuleProcess
diff --git a/external/badvpn_dns/generated/blog_channel_NCDPlaceholderDb.h b/external/badvpn_dns/generated/blog_channel_NCDPlaceholderDb.h
new file mode 100644
index 0000000..f1f1db2
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDPlaceholderDb.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDPlaceholderDb
diff --git a/external/badvpn_dns/generated/blog_channel_NCDRequest.h b/external/badvpn_dns/generated/blog_channel_NCDRequest.h
new file mode 100644
index 0000000..5edc18a
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDRequest.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDRequest
diff --git a/external/badvpn_dns/generated/blog_channel_NCDRequestClient.h b/external/badvpn_dns/generated/blog_channel_NCDRequestClient.h
new file mode 100644
index 0000000..1e696d8
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDRequestClient.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDRequestClient
diff --git a/external/badvpn_dns/generated/blog_channel_NCDRfkillMonitor.h b/external/badvpn_dns/generated/blog_channel_NCDRfkillMonitor.h
new file mode 100644
index 0000000..56eba88
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDRfkillMonitor.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDRfkillMonitor
diff --git a/external/badvpn_dns/generated/blog_channel_NCDUdevCache.h b/external/badvpn_dns/generated/blog_channel_NCDUdevCache.h
new file mode 100644
index 0000000..088fc9b
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDUdevCache.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDUdevCache
diff --git a/external/badvpn_dns/generated/blog_channel_NCDUdevManager.h b/external/badvpn_dns/generated/blog_channel_NCDUdevManager.h
new file mode 100644
index 0000000..e9d6375
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDUdevManager.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDUdevManager
diff --git a/external/badvpn_dns/generated/blog_channel_NCDUdevMonitor.h b/external/badvpn_dns/generated/blog_channel_NCDUdevMonitor.h
new file mode 100644
index 0000000..bd93249
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDUdevMonitor.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDUdevMonitor
diff --git a/external/badvpn_dns/generated/blog_channel_NCDUdevMonitorParser.h b/external/badvpn_dns/generated/blog_channel_NCDUdevMonitorParser.h
new file mode 100644
index 0000000..a7d560f
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDUdevMonitorParser.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDUdevMonitorParser
diff --git a/external/badvpn_dns/generated/blog_channel_NCDVal.h b/external/badvpn_dns/generated/blog_channel_NCDVal.h
new file mode 100644
index 0000000..f2b67c2
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDVal.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDVal
diff --git a/external/badvpn_dns/generated/blog_channel_NCDValGenerator.h b/external/badvpn_dns/generated/blog_channel_NCDValGenerator.h
new file mode 100644
index 0000000..193826b
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDValGenerator.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDValGenerator
diff --git a/external/badvpn_dns/generated/blog_channel_NCDValParser.h b/external/badvpn_dns/generated/blog_channel_NCDValParser.h
new file mode 100644
index 0000000..1d44acb
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_NCDValParser.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDValParser
diff --git a/external/badvpn_dns/generated/blog_channel_PRStreamSink.h b/external/badvpn_dns/generated/blog_channel_PRStreamSink.h
new file mode 100644
index 0000000..b70b61c
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_PRStreamSink.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_PRStreamSink
diff --git a/external/badvpn_dns/generated/blog_channel_PRStreamSource.h b/external/badvpn_dns/generated/blog_channel_PRStreamSource.h
new file mode 100644
index 0000000..e16d93d
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_PRStreamSource.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_PRStreamSource
diff --git a/external/badvpn_dns/generated/blog_channel_PacketProtoDecoder.h b/external/badvpn_dns/generated/blog_channel_PacketProtoDecoder.h
new file mode 100644
index 0000000..fbfa5d8
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_PacketProtoDecoder.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_PacketProtoDecoder
diff --git a/external/badvpn_dns/generated/blog_channel_PasswordListener.h b/external/badvpn_dns/generated/blog_channel_PasswordListener.h
new file mode 100644
index 0000000..6ff0bb5
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_PasswordListener.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_PasswordListener
diff --git a/external/badvpn_dns/generated/blog_channel_PeerChat.h b/external/badvpn_dns/generated/blog_channel_PeerChat.h
new file mode 100644
index 0000000..cadf230
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_PeerChat.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_PeerChat
diff --git a/external/badvpn_dns/generated/blog_channel_SPProtoDecoder.h b/external/badvpn_dns/generated/blog_channel_SPProtoDecoder.h
new file mode 100644
index 0000000..09bf259
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_SPProtoDecoder.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_SPProtoDecoder
diff --git a/external/badvpn_dns/generated/blog_channel_ServerConnection.h b/external/badvpn_dns/generated/blog_channel_ServerConnection.h
new file mode 100644
index 0000000..faea1dd
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ServerConnection.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ServerConnection
diff --git a/external/badvpn_dns/generated/blog_channel_SocksUdpGwClient.h b/external/badvpn_dns/generated/blog_channel_SocksUdpGwClient.h
new file mode 100644
index 0000000..6ba39ae
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_SocksUdpGwClient.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_SocksUdpGwClient
diff --git a/external/badvpn_dns/generated/blog_channel_StreamPeerIO.h b/external/badvpn_dns/generated/blog_channel_StreamPeerIO.h
new file mode 100644
index 0000000..0359736
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_StreamPeerIO.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_StreamPeerIO
diff --git a/external/badvpn_dns/generated/blog_channel_UdpGwClient.h b/external/badvpn_dns/generated/blog_channel_UdpGwClient.h
new file mode 100644
index 0000000..8530376
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_UdpGwClient.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_UdpGwClient
diff --git a/external/badvpn_dns/generated/blog_channel_addr.h b/external/badvpn_dns/generated/blog_channel_addr.h
new file mode 100644
index 0000000..512db28
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_addr.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_addr
diff --git a/external/badvpn_dns/generated/blog_channel_client.h b/external/badvpn_dns/generated/blog_channel_client.h
new file mode 100644
index 0000000..c851b77
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_client.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_client
diff --git a/external/badvpn_dns/generated/blog_channel_dostest_attacker.h b/external/badvpn_dns/generated/blog_channel_dostest_attacker.h
new file mode 100644
index 0000000..b267c8f
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_dostest_attacker.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_dostest_attacker
diff --git a/external/badvpn_dns/generated/blog_channel_dostest_server.h b/external/badvpn_dns/generated/blog_channel_dostest_server.h
new file mode 100644
index 0000000..8d3988e
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_dostest_server.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_dostest_server
diff --git a/external/badvpn_dns/generated/blog_channel_flooder.h b/external/badvpn_dns/generated/blog_channel_flooder.h
new file mode 100644
index 0000000..94f595e
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_flooder.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_flooder
diff --git a/external/badvpn_dns/generated/blog_channel_lwip.h b/external/badvpn_dns/generated/blog_channel_lwip.h
new file mode 100644
index 0000000..fb5687d
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_lwip.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_lwip
diff --git a/external/badvpn_dns/generated/blog_channel_ncd.h b/external/badvpn_dns/generated/blog_channel_ncd.h
new file mode 100644
index 0000000..9bf2956
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_alias.h b/external/badvpn_dns/generated/blog_channel_ncd_alias.h
new file mode 100644
index 0000000..5b52bf2
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_alias.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_alias
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_arithmetic.h b/external/badvpn_dns/generated/blog_channel_ncd_arithmetic.h
new file mode 100644
index 0000000..66c08a8
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_arithmetic.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_arithmetic
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_assert.h b/external/badvpn_dns/generated/blog_channel_ncd_assert.h
new file mode 100644
index 0000000..21e4d41
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_assert.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_assert
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_backtrack.h b/external/badvpn_dns/generated/blog_channel_ncd_backtrack.h
new file mode 100644
index 0000000..ea669f7
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_backtrack.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_backtrack
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_blocker.h b/external/badvpn_dns/generated/blog_channel_ncd_blocker.h
new file mode 100644
index 0000000..a897b9f
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_blocker.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_blocker
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_buffer.h b/external/badvpn_dns/generated/blog_channel_ncd_buffer.h
new file mode 100644
index 0000000..64e4433
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_buffer.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_buffer
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_call2.h b/external/badvpn_dns/generated/blog_channel_ncd_call2.h
new file mode 100644
index 0000000..4b64608
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_call2.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_call2
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_choose.h b/external/badvpn_dns/generated/blog_channel_ncd_choose.h
new file mode 100644
index 0000000..a915036
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_choose.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_choose
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_concat.h b/external/badvpn_dns/generated/blog_channel_ncd_concat.h
new file mode 100644
index 0000000..8c54ccb
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_concat.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_concat
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_daemon.h b/external/badvpn_dns/generated/blog_channel_ncd_daemon.h
new file mode 100644
index 0000000..0a3ae3f
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_daemon.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_daemon
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_depend.h b/external/badvpn_dns/generated/blog_channel_ncd_depend.h
new file mode 100644
index 0000000..ae1ff8e
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_depend.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_depend
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_depend_scope.h b/external/badvpn_dns/generated/blog_channel_ncd_depend_scope.h
new file mode 100644
index 0000000..1168714
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_depend_scope.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_depend_scope
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_dynamic_depend.h b/external/badvpn_dns/generated/blog_channel_ncd_dynamic_depend.h
new file mode 100644
index 0000000..7ff305e
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_dynamic_depend.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_dynamic_depend
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_exit.h b/external/badvpn_dns/generated/blog_channel_ncd_exit.h
new file mode 100644
index 0000000..2d2e3af
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_exit.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_exit
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_explode.h b/external/badvpn_dns/generated/blog_channel_ncd_explode.h
new file mode 100644
index 0000000..b7dc820
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_explode.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_explode
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_file.h b/external/badvpn_dns/generated/blog_channel_ncd_file.h
new file mode 100644
index 0000000..6cfa5a5
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_file.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_file
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_file_open.h b/external/badvpn_dns/generated/blog_channel_ncd_file_open.h
new file mode 100644
index 0000000..dd4ecb5
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_file_open.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_file_open
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_foreach.h b/external/badvpn_dns/generated/blog_channel_ncd_foreach.h
new file mode 100644
index 0000000..430b229
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_foreach.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_foreach
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_from_string.h b/external/badvpn_dns/generated/blog_channel_ncd_from_string.h
new file mode 100644
index 0000000..e409fff
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_from_string.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_from_string
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_getargs.h b/external/badvpn_dns/generated/blog_channel_ncd_getargs.h
new file mode 100644
index 0000000..da7631d
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_getargs.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_getargs
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_getenv.h b/external/badvpn_dns/generated/blog_channel_ncd_getenv.h
new file mode 100644
index 0000000..4f29021
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_getenv.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_getenv
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_if.h b/external/badvpn_dns/generated/blog_channel_ncd_if.h
new file mode 100644
index 0000000..11a09a2
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_if.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_if
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_imperative.h b/external/badvpn_dns/generated/blog_channel_ncd_imperative.h
new file mode 100644
index 0000000..362df87
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_imperative.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_imperative
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_implode.h b/external/badvpn_dns/generated/blog_channel_ncd_implode.h
new file mode 100644
index 0000000..5bb66d5
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_implode.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_implode
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_index.h b/external/badvpn_dns/generated/blog_channel_ncd_index.h
new file mode 100644
index 0000000..666bbe9
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_index.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_index
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_list.h b/external/badvpn_dns/generated/blog_channel_ncd_list.h
new file mode 100644
index 0000000..f153be7
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_list.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_list
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_load_module.h b/external/badvpn_dns/generated/blog_channel_ncd_load_module.h
new file mode 100644
index 0000000..c27dddb
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_load_module.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_load_module
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_log.h b/external/badvpn_dns/generated/blog_channel_ncd_log.h
new file mode 100644
index 0000000..9ae2dc9
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_log.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_log
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_log_msg.h b/external/badvpn_dns/generated/blog_channel_ncd_log_msg.h
new file mode 100644
index 0000000..9e51b7e
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_log_msg.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_log_msg
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_logical.h b/external/badvpn_dns/generated/blog_channel_ncd_logical.h
new file mode 100644
index 0000000..688453d
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_logical.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_logical
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_multidepend.h b/external/badvpn_dns/generated/blog_channel_ncd_multidepend.h
new file mode 100644
index 0000000..a82953d
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_multidepend.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_multidepend
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_backend_badvpn.h b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_badvpn.h
new file mode 100644
index 0000000..c9964c1
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_badvpn.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_backend_badvpn
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_backend_rfkill.h b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_rfkill.h
new file mode 100644
index 0000000..e69896f
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_rfkill.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_backend_rfkill
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_backend_waitdevice.h b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_waitdevice.h
new file mode 100644
index 0000000..63c4f24
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_waitdevice.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_backend_waitdevice
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_backend_waitlink.h b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_waitlink.h
new file mode 100644
index 0000000..96244c0
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_waitlink.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_backend_waitlink
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_backend_wpa_supplicant.h b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_wpa_supplicant.h
new file mode 100644
index 0000000..22572d3
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_backend_wpa_supplicant.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_backend_wpa_supplicant
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_dns.h b/external/badvpn_dns/generated/blog_channel_ncd_net_dns.h
new file mode 100644
index 0000000..01c3744
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_dns.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_dns
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_iptables.h b/external/badvpn_dns/generated/blog_channel_ncd_net_iptables.h
new file mode 100644
index 0000000..42e2382
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_iptables.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_iptables
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_addr.h b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_addr.h
new file mode 100644
index 0000000..75bcb24
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_addr.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv4_addr
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_addr_in_network.h b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_addr_in_network.h
new file mode 100644
index 0000000..41f2df2
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_addr_in_network.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv4_addr_in_network
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_arp_probe.h b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_arp_probe.h
new file mode 100644
index 0000000..18f7c78
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_arp_probe.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv4_arp_probe
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_dhcp.h b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_dhcp.h
new file mode 100644
index 0000000..51fa61f
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_dhcp.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv4_dhcp
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_route.h b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_route.h
new file mode 100644
index 0000000..e181a90
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv4_route.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv4_route
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_addr.h b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_addr.h
new file mode 100644
index 0000000..bd6bd10
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_addr.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv6_addr
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_addr_in_network.h b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_addr_in_network.h
new file mode 100644
index 0000000..ba33921
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_addr_in_network.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv6_addr_in_network
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_route.h b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_route.h
new file mode 100644
index 0000000..b72e4d3
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_route.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv6_route
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_wait_dynamic_addr.h b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_wait_dynamic_addr.h
new file mode 100644
index 0000000..ff7d6e1
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_ipv6_wait_dynamic_addr.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_ipv6_wait_dynamic_addr
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_up.h b/external/badvpn_dns/generated/blog_channel_ncd_net_up.h
new file mode 100644
index 0000000..7acdede
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_up.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_up
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_net_watch_interfaces.h b/external/badvpn_dns/generated/blog_channel_ncd_net_watch_interfaces.h
new file mode 100644
index 0000000..7fc078f
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_net_watch_interfaces.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_net_watch_interfaces
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_netmask.h b/external/badvpn_dns/generated/blog_channel_ncd_netmask.h
new file mode 100644
index 0000000..10993f0
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_netmask.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_netmask
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_ondemand.h b/external/badvpn_dns/generated/blog_channel_ncd_ondemand.h
new file mode 100644
index 0000000..c7a0578
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_ondemand.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_ondemand
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_parse.h b/external/badvpn_dns/generated/blog_channel_ncd_parse.h
new file mode 100644
index 0000000..672155b
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_parse.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_parse
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_print.h b/external/badvpn_dns/generated/blog_channel_ncd_print.h
new file mode 100644
index 0000000..22638f3
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_print.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_print
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_process_manager.h b/external/badvpn_dns/generated/blog_channel_ncd_process_manager.h
new file mode 100644
index 0000000..627ba0e
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_process_manager.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_process_manager
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_reboot.h b/external/badvpn_dns/generated/blog_channel_ncd_reboot.h
new file mode 100644
index 0000000..0e31d55
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_reboot.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_reboot
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_ref.h b/external/badvpn_dns/generated/blog_channel_ncd_ref.h
new file mode 100644
index 0000000..4f9f24a
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_ref.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_ref
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_regex_match.h b/external/badvpn_dns/generated/blog_channel_ncd_regex_match.h
new file mode 100644
index 0000000..3081347
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_regex_match.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_regex_match
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_request.h b/external/badvpn_dns/generated/blog_channel_ncd_request.h
new file mode 100644
index 0000000..00103ea
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_request.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_request
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_run.h b/external/badvpn_dns/generated/blog_channel_ncd_run.h
new file mode 100644
index 0000000..036a93e
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_run.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_run
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_runonce.h b/external/badvpn_dns/generated/blog_channel_ncd_runonce.h
new file mode 100644
index 0000000..2e54452
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_runonce.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_runonce
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_sleep.h b/external/badvpn_dns/generated/blog_channel_ncd_sleep.h
new file mode 100644
index 0000000..fb6c7fe
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_sleep.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_sleep
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_socket.h b/external/badvpn_dns/generated/blog_channel_ncd_socket.h
new file mode 100644
index 0000000..3c1f0c4
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_socket.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_socket
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_spawn.h b/external/badvpn_dns/generated/blog_channel_ncd_spawn.h
new file mode 100644
index 0000000..b9b3b24
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_spawn.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_spawn
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_strcmp.h b/external/badvpn_dns/generated/blog_channel_ncd_strcmp.h
new file mode 100644
index 0000000..6ef09ad
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_strcmp.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_strcmp
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_substr.h b/external/badvpn_dns/generated/blog_channel_ncd_substr.h
new file mode 100644
index 0000000..691ad0e
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_substr.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_substr
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_sys_evdev.h b/external/badvpn_dns/generated/blog_channel_ncd_sys_evdev.h
new file mode 100644
index 0000000..4a7244e
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_sys_evdev.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_sys_evdev
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_sys_request_client.h b/external/badvpn_dns/generated/blog_channel_ncd_sys_request_client.h
new file mode 100644
index 0000000..ce0f9e4
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_sys_request_client.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_sys_request_client
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_sys_request_server.h b/external/badvpn_dns/generated/blog_channel_ncd_sys_request_server.h
new file mode 100644
index 0000000..1197958
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_sys_request_server.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_sys_request_server
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_sys_start_process.h b/external/badvpn_dns/generated/blog_channel_ncd_sys_start_process.h
new file mode 100644
index 0000000..45c2edc
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_sys_start_process.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_sys_start_process
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_sys_watch_directory.h b/external/badvpn_dns/generated/blog_channel_ncd_sys_watch_directory.h
new file mode 100644
index 0000000..e190da5
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_sys_watch_directory.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_sys_watch_directory
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_sys_watch_input.h b/external/badvpn_dns/generated/blog_channel_ncd_sys_watch_input.h
new file mode 100644
index 0000000..b899555
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_sys_watch_input.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_sys_watch_input
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_sys_watch_usb.h b/external/badvpn_dns/generated/blog_channel_ncd_sys_watch_usb.h
new file mode 100644
index 0000000..bc5102a
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_sys_watch_usb.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_sys_watch_usb
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_timer.h b/external/badvpn_dns/generated/blog_channel_ncd_timer.h
new file mode 100644
index 0000000..beaa73d
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_timer.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_timer
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_to_string.h b/external/badvpn_dns/generated/blog_channel_ncd_to_string.h
new file mode 100644
index 0000000..41cd8b9
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_to_string.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_to_string
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_try.h b/external/badvpn_dns/generated/blog_channel_ncd_try.h
new file mode 100644
index 0000000..bb76c68
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_try.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_try
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_value.h b/external/badvpn_dns/generated/blog_channel_ncd_value.h
new file mode 100644
index 0000000..fa624e8
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_value.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_value
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_valuemetic.h b/external/badvpn_dns/generated/blog_channel_ncd_valuemetic.h
new file mode 100644
index 0000000..385d2bb
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_valuemetic.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_valuemetic
diff --git a/external/badvpn_dns/generated/blog_channel_ncd_var.h b/external/badvpn_dns/generated/blog_channel_ncd_var.h
new file mode 100644
index 0000000..fa5c0c4
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_ncd_var.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_ncd_var
diff --git a/external/badvpn_dns/generated/blog_channel_nsskey.h b/external/badvpn_dns/generated/blog_channel_nsskey.h
new file mode 100644
index 0000000..66e6a72
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_nsskey.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_nsskey
diff --git a/external/badvpn_dns/generated/blog_channel_server.h b/external/badvpn_dns/generated/blog_channel_server.h
new file mode 100644
index 0000000..acb3ed0
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_server.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_server
diff --git a/external/badvpn_dns/generated/blog_channel_tun2socks.h b/external/badvpn_dns/generated/blog_channel_tun2socks.h
new file mode 100644
index 0000000..21c1ce2
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_tun2socks.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_tun2socks
diff --git a/external/badvpn_dns/generated/blog_channel_udpgw.h b/external/badvpn_dns/generated/blog_channel_udpgw.h
new file mode 100644
index 0000000..504a352
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channel_udpgw.h
@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_udpgw
diff --git a/external/badvpn_dns/generated/blog_channels_defines.h b/external/badvpn_dns/generated/blog_channels_defines.h
new file mode 100644
index 0000000..d89dc86
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channels_defines.h
@@ -0,0 +1,146 @@
+#define BLOG_CHANNEL_server 0
+#define BLOG_CHANNEL_client 1
+#define BLOG_CHANNEL_flooder 2
+#define BLOG_CHANNEL_tun2socks 3
+#define BLOG_CHANNEL_ncd 4
+#define BLOG_CHANNEL_ncd_var 5
+#define BLOG_CHANNEL_ncd_list 6
+#define BLOG_CHANNEL_ncd_depend 7
+#define BLOG_CHANNEL_ncd_multidepend 8
+#define BLOG_CHANNEL_ncd_dynamic_depend 9
+#define BLOG_CHANNEL_ncd_concat 10
+#define BLOG_CHANNEL_ncd_if 11
+#define BLOG_CHANNEL_ncd_strcmp 12
+#define BLOG_CHANNEL_ncd_regex_match 13
+#define BLOG_CHANNEL_ncd_logical 14
+#define BLOG_CHANNEL_ncd_sleep 15
+#define BLOG_CHANNEL_ncd_print 16
+#define BLOG_CHANNEL_ncd_blocker 17
+#define BLOG_CHANNEL_ncd_run 18
+#define BLOG_CHANNEL_ncd_runonce 19
+#define BLOG_CHANNEL_ncd_daemon 20
+#define BLOG_CHANNEL_ncd_spawn 21
+#define BLOG_CHANNEL_ncd_imperative 22
+#define BLOG_CHANNEL_ncd_ref 23
+#define BLOG_CHANNEL_ncd_index 24
+#define BLOG_CHANNEL_ncd_alias 25
+#define BLOG_CHANNEL_ncd_process_manager 26
+#define BLOG_CHANNEL_ncd_ondemand 27
+#define BLOG_CHANNEL_ncd_foreach 28
+#define BLOG_CHANNEL_ncd_choose 29
+#define BLOG_CHANNEL_ncd_net_backend_waitdevice 30
+#define BLOG_CHANNEL_ncd_net_backend_waitlink 31
+#define BLOG_CHANNEL_ncd_net_backend_badvpn 32
+#define BLOG_CHANNEL_ncd_net_backend_wpa_supplicant 33
+#define BLOG_CHANNEL_ncd_net_backend_rfkill 34
+#define BLOG_CHANNEL_ncd_net_up 35
+#define BLOG_CHANNEL_ncd_net_dns 36
+#define BLOG_CHANNEL_ncd_net_iptables 37
+#define BLOG_CHANNEL_ncd_net_ipv4_addr 38
+#define BLOG_CHANNEL_ncd_net_ipv4_route 39
+#define BLOG_CHANNEL_ncd_net_ipv4_dhcp 40
+#define BLOG_CHANNEL_ncd_net_ipv4_arp_probe 41
+#define BLOG_CHANNEL_ncd_net_watch_interfaces 42
+#define BLOG_CHANNEL_ncd_sys_watch_input 43
+#define BLOG_CHANNEL_ncd_sys_watch_usb 44
+#define BLOG_CHANNEL_ncd_sys_evdev 45
+#define BLOG_CHANNEL_ncd_sys_watch_directory 46
+#define BLOG_CHANNEL_StreamPeerIO 47
+#define BLOG_CHANNEL_DatagramPeerIO 48
+#define BLOG_CHANNEL_BReactor 49
+#define BLOG_CHANNEL_BSignal 50
+#define BLOG_CHANNEL_FragmentProtoAssembler 51
+#define BLOG_CHANNEL_BPredicate 52
+#define BLOG_CHANNEL_ServerConnection 53
+#define BLOG_CHANNEL_Listener 54
+#define BLOG_CHANNEL_DataProto 55
+#define BLOG_CHANNEL_FrameDecider 56
+#define BLOG_CHANNEL_BSocksClient 57
+#define BLOG_CHANNEL_BDHCPClientCore 58
+#define BLOG_CHANNEL_BDHCPClient 59
+#define BLOG_CHANNEL_NCDIfConfig 60
+#define BLOG_CHANNEL_BUnixSignal 61
+#define BLOG_CHANNEL_BProcess 62
+#define BLOG_CHANNEL_PRStreamSink 63
+#define BLOG_CHANNEL_PRStreamSource 64
+#define BLOG_CHANNEL_PacketProtoDecoder 65
+#define BLOG_CHANNEL_DPRelay 66
+#define BLOG_CHANNEL_BThreadWork 67
+#define BLOG_CHANNEL_DPReceive 68
+#define BLOG_CHANNEL_BInputProcess 69
+#define BLOG_CHANNEL_NCDUdevMonitorParser 70
+#define BLOG_CHANNEL_NCDUdevMonitor 71
+#define BLOG_CHANNEL_NCDUdevCache 72
+#define BLOG_CHANNEL_NCDUdevManager 73
+#define BLOG_CHANNEL_BTime 74
+#define BLOG_CHANNEL_BEncryption 75
+#define BLOG_CHANNEL_SPProtoDecoder 76
+#define BLOG_CHANNEL_LineBuffer 77
+#define BLOG_CHANNEL_BTap 78
+#define BLOG_CHANNEL_lwip 79
+#define BLOG_CHANNEL_NCDConfigTokenizer 80
+#define BLOG_CHANNEL_NCDConfigParser 81
+#define BLOG_CHANNEL_NCDValParser 82
+#define BLOG_CHANNEL_nsskey 83
+#define BLOG_CHANNEL_addr 84
+#define BLOG_CHANNEL_PasswordListener 85
+#define BLOG_CHANNEL_NCDInterfaceMonitor 86
+#define BLOG_CHANNEL_NCDRfkillMonitor 87
+#define BLOG_CHANNEL_udpgw 88
+#define BLOG_CHANNEL_UdpGwClient 89
+#define BLOG_CHANNEL_SocksUdpGwClient 90
+#define BLOG_CHANNEL_BNetwork 91
+#define BLOG_CHANNEL_BConnection 92
+#define BLOG_CHANNEL_BSSLConnection 93
+#define BLOG_CHANNEL_BDatagram 94
+#define BLOG_CHANNEL_PeerChat 95
+#define BLOG_CHANNEL_BArpProbe 96
+#define BLOG_CHANNEL_NCDModuleIndex 97
+#define BLOG_CHANNEL_NCDModuleProcess 98
+#define BLOG_CHANNEL_NCDValGenerator 99
+#define BLOG_CHANNEL_ncd_from_string 100
+#define BLOG_CHANNEL_ncd_to_string 101
+#define BLOG_CHANNEL_ncd_value 102
+#define BLOG_CHANNEL_ncd_try 103
+#define BLOG_CHANNEL_ncd_sys_request_server 104
+#define BLOG_CHANNEL_NCDRequest 105
+#define BLOG_CHANNEL_ncd_net_ipv6_wait_dynamic_addr 106
+#define BLOG_CHANNEL_NCDRequestClient 107
+#define BLOG_CHANNEL_ncd_request 108
+#define BLOG_CHANNEL_ncd_sys_request_client 109
+#define BLOG_CHANNEL_ncd_exit 110
+#define BLOG_CHANNEL_ncd_getargs 111
+#define BLOG_CHANNEL_ncd_arithmetic 112
+#define BLOG_CHANNEL_ncd_parse 113
+#define BLOG_CHANNEL_ncd_valuemetic 114
+#define BLOG_CHANNEL_ncd_file 115
+#define BLOG_CHANNEL_ncd_netmask 116
+#define BLOG_CHANNEL_ncd_implode 117
+#define BLOG_CHANNEL_ncd_call2 118
+#define BLOG_CHANNEL_ncd_assert 119
+#define BLOG_CHANNEL_ncd_reboot 120
+#define BLOG_CHANNEL_ncd_explode 121
+#define BLOG_CHANNEL_NCDPlaceholderDb 122
+#define BLOG_CHANNEL_NCDVal 123
+#define BLOG_CHANNEL_ncd_net_ipv6_addr 124
+#define BLOG_CHANNEL_ncd_net_ipv6_route 125
+#define BLOG_CHANNEL_ncd_net_ipv4_addr_in_network 126
+#define BLOG_CHANNEL_ncd_net_ipv6_addr_in_network 127
+#define BLOG_CHANNEL_dostest_server 128
+#define BLOG_CHANNEL_dostest_attacker 129
+#define BLOG_CHANNEL_ncd_timer 130
+#define BLOG_CHANNEL_ncd_file_open 131
+#define BLOG_CHANNEL_ncd_backtrack 132
+#define BLOG_CHANNEL_ncd_socket 133
+#define BLOG_CHANNEL_ncd_depend_scope 134
+#define BLOG_CHANNEL_ncd_substr 135
+#define BLOG_CHANNEL_ncd_sys_start_process 136
+#define BLOG_CHANNEL_NCDBuildProgram 137
+#define BLOG_CHANNEL_ncd_log 138
+#define BLOG_CHANNEL_ncd_log_msg 139
+#define BLOG_CHANNEL_ncd_buffer 140
+#define BLOG_CHANNEL_ncd_getenv 141
+#define BLOG_CHANNEL_BThreadSignal 142
+#define BLOG_CHANNEL_BLockReactor 143
+#define BLOG_CHANNEL_ncd_load_module 144
+#define BLOG_NUM_CHANNELS 145
diff --git a/external/badvpn_dns/generated/blog_channels_list.h b/external/badvpn_dns/generated/blog_channels_list.h
new file mode 100644
index 0000000..c903c0f
--- /dev/null
+++ b/external/badvpn_dns/generated/blog_channels_list.h
@@ -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/generated/bproto_addr.h b/external/badvpn_dns/generated/bproto_addr.h
new file mode 100644
index 0000000..fbd96a8
--- /dev/null
+++ b/external/badvpn_dns/generated/bproto_addr.h
@@ -0,0 +1,675 @@
+/*
+    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>
+
+
+#define addr_SIZEtype (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint8_s))
+#define addr_SIZEip_port (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (2))
+#define addr_SIZEipv4_addr (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (4))
+#define addr_SIZEipv6_addr (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (16))
+
+typedef struct {
+    uint8_t *out;
+    int used;
+    int type_count;
+    int ip_port_count;
+    int ipv4_addr_count;
+    int ipv6_addr_count;
+} addrWriter;
+
+static void addrWriter_Init (addrWriter *o, uint8_t *out);
+static int addrWriter_Finish (addrWriter *o);
+static void addrWriter_Addtype (addrWriter *o, uint8_t v);
+static uint8_t * addrWriter_Addip_port (addrWriter *o);
+static uint8_t * addrWriter_Addipv4_addr (addrWriter *o);
+static uint8_t * addrWriter_Addipv6_addr (addrWriter *o);
+
+typedef struct {
+    uint8_t *buf;
+    int buf_len;
+    int type_start;
+    int type_span;
+    int type_pos;
+    int ip_port_start;
+    int ip_port_span;
+    int ip_port_pos;
+    int ipv4_addr_start;
+    int ipv4_addr_span;
+    int ipv4_addr_pos;
+    int ipv6_addr_start;
+    int ipv6_addr_span;
+    int ipv6_addr_pos;
+} addrParser;
+
+static int addrParser_Init (addrParser *o, uint8_t *buf, int buf_len);
+static int addrParser_GotEverything (addrParser *o);
+static int addrParser_Gettype (addrParser *o, uint8_t *v);
+static void addrParser_Resettype (addrParser *o);
+static void addrParser_Forwardtype (addrParser *o);
+static int addrParser_Getip_port (addrParser *o, uint8_t **data);
+static void addrParser_Resetip_port (addrParser *o);
+static void addrParser_Forwardip_port (addrParser *o);
+static int addrParser_Getipv4_addr (addrParser *o, uint8_t **data);
+static void addrParser_Resetipv4_addr (addrParser *o);
+static void addrParser_Forwardipv4_addr (addrParser *o);
+static int addrParser_Getipv6_addr (addrParser *o, uint8_t **data);
+static void addrParser_Resetipv6_addr (addrParser *o);
+static void addrParser_Forwardipv6_addr (addrParser *o);
+
+void addrWriter_Init (addrWriter *o, uint8_t *out)
+{
+    o->out = out;
+    o->used = 0;
+    o->type_count = 0;
+    o->ip_port_count = 0;
+    o->ipv4_addr_count = 0;
+    o->ipv6_addr_count = 0;
+}
+
+int addrWriter_Finish (addrWriter *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->type_count == 1)
+    ASSERT(o->ip_port_count >= 0 && o->ip_port_count <= 1)
+    ASSERT(o->ipv4_addr_count >= 0 && o->ipv4_addr_count <= 1)
+    ASSERT(o->ipv6_addr_count >= 0 && o->ipv6_addr_count <= 1)
+
+    return o->used;
+}
+
+void addrWriter_Addtype (addrWriter *o, uint8_t v)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->type_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(1);
+    header.type = htol16(BPROTO_TYPE_UINT8);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint8_s data;
+    data.v = htol8(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint8_s);
+
+    o->type_count++;
+}
+
+uint8_t * addrWriter_Addip_port (addrWriter *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->ip_port_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(2);
+    header.type = htol16(BPROTO_TYPE_CONSTDATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_data_header_s data;
+    data.len = htol32(2);
+    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 += (2);
+
+    o->ip_port_count++;
+
+    return dest;
+}
+
+uint8_t * addrWriter_Addipv4_addr (addrWriter *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->ipv4_addr_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(3);
+    header.type = htol16(BPROTO_TYPE_CONSTDATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_data_header_s data;
+    data.len = htol32(4);
+    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 += (4);
+
+    o->ipv4_addr_count++;
+
+    return dest;
+}
+
+uint8_t * addrWriter_Addipv6_addr (addrWriter *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->ipv6_addr_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(4);
+    header.type = htol16(BPROTO_TYPE_CONSTDATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_data_header_s data;
+    data.len = htol32(16);
+    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 += (16);
+
+    o->ipv6_addr_count++;
+
+    return dest;
+}
+
+int addrParser_Init (addrParser *o, uint8_t *buf, int buf_len)
+{
+    ASSERT(buf_len >= 0)
+
+    o->buf = buf;
+    o->buf_len = buf_len;
+    o->type_start = o->buf_len;
+    o->type_span = 0;
+    o->type_pos = 0;
+    o->ip_port_start = o->buf_len;
+    o->ip_port_span = 0;
+    o->ip_port_pos = 0;
+    o->ipv4_addr_start = o->buf_len;
+    o->ipv4_addr_span = 0;
+    o->ipv4_addr_pos = 0;
+    o->ipv6_addr_start = o->buf_len;
+    o->ipv6_addr_span = 0;
+    o->ipv6_addr_pos = 0;
+
+    int type_count = 0;
+    int ip_port_count = 0;
+    int ipv4_addr_count = 0;
+    int ipv6_addr_count = 0;
+
+    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) {
+            case BPROTO_TYPE_UINT8: {
+                if (!(left >= sizeof(struct BProto_uint8_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+
+                switch (id) {
+                    case 1:
+                        if (o->type_start == o->buf_len) {
+                            o->type_start = entry_pos;
+                        }
+                        o->type_span = pos - o->type_start;
+                        type_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                if (!(left >= sizeof(struct BProto_uint16_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                if (!(left >= sizeof(struct BProto_uint32_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                if (!(left >= sizeof(struct BProto_uint64_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            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) {
+                    case 2:
+                        if (!(type == BPROTO_TYPE_CONSTDATA)) {
+                            return 0;
+                        }
+                        if (!(payload_len == (2))) {
+                            return 0;
+                        }
+                        if (o->ip_port_start == o->buf_len) {
+                            o->ip_port_start = entry_pos;
+                        }
+                        o->ip_port_span = pos - o->ip_port_start;
+                        ip_port_count++;
+                        break;
+                    case 3:
+                        if (!(type == BPROTO_TYPE_CONSTDATA)) {
+                            return 0;
+                        }
+                        if (!(payload_len == (4))) {
+                            return 0;
+                        }
+                        if (o->ipv4_addr_start == o->buf_len) {
+                            o->ipv4_addr_start = entry_pos;
+                        }
+                        o->ipv4_addr_span = pos - o->ipv4_addr_start;
+                        ipv4_addr_count++;
+                        break;
+                    case 4:
+                        if (!(type == BPROTO_TYPE_CONSTDATA)) {
+                            return 0;
+                        }
+                        if (!(payload_len == (16))) {
+                            return 0;
+                        }
+                        if (o->ipv6_addr_start == o->buf_len) {
+                            o->ipv6_addr_start = entry_pos;
+                        }
+                        o->ipv6_addr_span = pos - o->ipv6_addr_start;
+                        ipv6_addr_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            default:
+                return 0;
+        }
+    }
+
+    if (!(type_count == 1)) {
+        return 0;
+    }
+    if (!(ip_port_count <= 1)) {
+        return 0;
+    }
+    if (!(ipv4_addr_count <= 1)) {
+        return 0;
+    }
+    if (!(ipv6_addr_count <= 1)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+int addrParser_GotEverything (addrParser *o)
+{
+    return (
+        o->type_pos == o->type_span
+        &&
+        o->ip_port_pos == o->ip_port_span
+        &&
+        o->ipv4_addr_pos == o->ipv4_addr_span
+        &&
+        o->ipv6_addr_pos == o->ipv6_addr_span
+    );
+}
+
+int addrParser_Gettype (addrParser *o, uint8_t *v)
+{
+    ASSERT(o->type_pos >= 0)
+    ASSERT(o->type_pos <= o->type_span)
+
+    int left = o->type_span - o->type_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->type_start + o->type_pos, sizeof(header));
+        o->type_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                struct BProto_uint8_s val;
+                memcpy(&val, o->buf + o->type_start + o->type_pos, sizeof(val));
+                o->type_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+
+                if (id == 1) {
+                    *v = ltoh8(val.v);
+                    return 1;
+                }
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->type_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->type_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->type_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->type_start + o->type_pos, sizeof(val));
+                o->type_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)
+                o->type_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void addrParser_Resettype (addrParser *o)
+{
+    o->type_pos = 0;
+}
+
+void addrParser_Forwardtype (addrParser *o)
+{
+    o->type_pos = o->type_span;
+}
+
+int addrParser_Getip_port (addrParser *o, uint8_t **data)
+{
+    ASSERT(o->ip_port_pos >= 0)
+    ASSERT(o->ip_port_pos <= o->ip_port_span)
+
+    int left = o->ip_port_span - o->ip_port_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->ip_port_start + o->ip_port_pos, sizeof(header));
+        o->ip_port_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->ip_port_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->ip_port_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->ip_port_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->ip_port_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->ip_port_start + o->ip_port_pos, sizeof(val));
+                o->ip_port_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)
+                uint8_t *payload = o->buf + o->ip_port_start + o->ip_port_pos;
+                o->ip_port_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_CONSTDATA && id == 2) {
+                    *data = payload;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void addrParser_Resetip_port (addrParser *o)
+{
+    o->ip_port_pos = 0;
+}
+
+void addrParser_Forwardip_port (addrParser *o)
+{
+    o->ip_port_pos = o->ip_port_span;
+}
+
+int addrParser_Getipv4_addr (addrParser *o, uint8_t **data)
+{
+    ASSERT(o->ipv4_addr_pos >= 0)
+    ASSERT(o->ipv4_addr_pos <= o->ipv4_addr_span)
+
+    int left = o->ipv4_addr_span - o->ipv4_addr_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->ipv4_addr_start + o->ipv4_addr_pos, sizeof(header));
+        o->ipv4_addr_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->ipv4_addr_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->ipv4_addr_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->ipv4_addr_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->ipv4_addr_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->ipv4_addr_start + o->ipv4_addr_pos, sizeof(val));
+                o->ipv4_addr_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)
+                uint8_t *payload = o->buf + o->ipv4_addr_start + o->ipv4_addr_pos;
+                o->ipv4_addr_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_CONSTDATA && id == 3) {
+                    *data = payload;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void addrParser_Resetipv4_addr (addrParser *o)
+{
+    o->ipv4_addr_pos = 0;
+}
+
+void addrParser_Forwardipv4_addr (addrParser *o)
+{
+    o->ipv4_addr_pos = o->ipv4_addr_span;
+}
+
+int addrParser_Getipv6_addr (addrParser *o, uint8_t **data)
+{
+    ASSERT(o->ipv6_addr_pos >= 0)
+    ASSERT(o->ipv6_addr_pos <= o->ipv6_addr_span)
+
+    int left = o->ipv6_addr_span - o->ipv6_addr_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->ipv6_addr_start + o->ipv6_addr_pos, sizeof(header));
+        o->ipv6_addr_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->ipv6_addr_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->ipv6_addr_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->ipv6_addr_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->ipv6_addr_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->ipv6_addr_start + o->ipv6_addr_pos, sizeof(val));
+                o->ipv6_addr_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)
+                uint8_t *payload = o->buf + o->ipv6_addr_start + o->ipv6_addr_pos;
+                o->ipv6_addr_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_CONSTDATA && id == 4) {
+                    *data = payload;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void addrParser_Resetipv6_addr (addrParser *o)
+{
+    o->ipv6_addr_pos = 0;
+}
+
+void addrParser_Forwardipv6_addr (addrParser *o)
+{
+    o->ipv6_addr_pos = o->ipv6_addr_span;
+}
+
diff --git a/external/badvpn_dns/generated/bproto_bproto_test.h b/external/badvpn_dns/generated/bproto_bproto_test.h
new file mode 100644
index 0000000..dc4ad80
--- /dev/null
+++ b/external/badvpn_dns/generated/bproto_bproto_test.h
@@ -0,0 +1,1029 @@
+/*
+    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>
+
+
+#define msg1_SIZEa (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s))
+#define msg1_SIZEb (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint32_s))
+#define msg1_SIZEc (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint64_s))
+#define msg1_SIZEd (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s))
+#define msg1_SIZEe (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint8_s))
+#define msg1_SIZEf(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))
+#define msg1_SIZEg (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (4))
+
+typedef struct {
+    uint8_t *out;
+    int used;
+    int a_count;
+    int b_count;
+    int c_count;
+    int d_count;
+    int e_count;
+    int f_count;
+    int g_count;
+} msg1Writer;
+
+static void msg1Writer_Init (msg1Writer *o, uint8_t *out);
+static int msg1Writer_Finish (msg1Writer *o);
+static void msg1Writer_Adda (msg1Writer *o, uint16_t v);
+static void msg1Writer_Addb (msg1Writer *o, uint32_t v);
+static void msg1Writer_Addc (msg1Writer *o, uint64_t v);
+static void msg1Writer_Addd (msg1Writer *o, uint16_t v);
+static void msg1Writer_Adde (msg1Writer *o, uint8_t v);
+static uint8_t * msg1Writer_Addf (msg1Writer *o, int len);
+static uint8_t * msg1Writer_Addg (msg1Writer *o);
+
+typedef struct {
+    uint8_t *buf;
+    int buf_len;
+    int a_start;
+    int a_span;
+    int a_pos;
+    int b_start;
+    int b_span;
+    int b_pos;
+    int c_start;
+    int c_span;
+    int c_pos;
+    int d_start;
+    int d_span;
+    int d_pos;
+    int e_start;
+    int e_span;
+    int e_pos;
+    int f_start;
+    int f_span;
+    int f_pos;
+    int g_start;
+    int g_span;
+    int g_pos;
+} msg1Parser;
+
+static int msg1Parser_Init (msg1Parser *o, uint8_t *buf, int buf_len);
+static int msg1Parser_GotEverything (msg1Parser *o);
+static int msg1Parser_Geta (msg1Parser *o, uint16_t *v);
+static void msg1Parser_Reseta (msg1Parser *o);
+static void msg1Parser_Forwarda (msg1Parser *o);
+static int msg1Parser_Getb (msg1Parser *o, uint32_t *v);
+static void msg1Parser_Resetb (msg1Parser *o);
+static void msg1Parser_Forwardb (msg1Parser *o);
+static int msg1Parser_Getc (msg1Parser *o, uint64_t *v);
+static void msg1Parser_Resetc (msg1Parser *o);
+static void msg1Parser_Forwardc (msg1Parser *o);
+static int msg1Parser_Getd (msg1Parser *o, uint16_t *v);
+static void msg1Parser_Resetd (msg1Parser *o);
+static void msg1Parser_Forwardd (msg1Parser *o);
+static int msg1Parser_Gete (msg1Parser *o, uint8_t *v);
+static void msg1Parser_Resete (msg1Parser *o);
+static void msg1Parser_Forwarde (msg1Parser *o);
+static int msg1Parser_Getf (msg1Parser *o, uint8_t **data, int *data_len);
+static void msg1Parser_Resetf (msg1Parser *o);
+static void msg1Parser_Forwardf (msg1Parser *o);
+static int msg1Parser_Getg (msg1Parser *o, uint8_t **data);
+static void msg1Parser_Resetg (msg1Parser *o);
+static void msg1Parser_Forwardg (msg1Parser *o);
+
+void msg1Writer_Init (msg1Writer *o, uint8_t *out)
+{
+    o->out = out;
+    o->used = 0;
+    o->a_count = 0;
+    o->b_count = 0;
+    o->c_count = 0;
+    o->d_count = 0;
+    o->e_count = 0;
+    o->f_count = 0;
+    o->g_count = 0;
+}
+
+int msg1Writer_Finish (msg1Writer *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->a_count == 1)
+    ASSERT(o->b_count >= 0 && o->b_count <= 1)
+    ASSERT(o->c_count >= 1)
+    ASSERT(o->d_count >= 0)
+    ASSERT(o->e_count == 1)
+    ASSERT(o->f_count == 1)
+    ASSERT(o->g_count == 1)
+
+    return o->used;
+}
+
+void msg1Writer_Adda (msg1Writer *o, uint16_t v)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->a_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(5);
+    header.type = htol16(BPROTO_TYPE_UINT16);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint16_s data;
+    data.v = htol16(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint16_s);
+
+    o->a_count++;
+}
+
+void msg1Writer_Addb (msg1Writer *o, uint32_t v)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->b_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(6);
+    header.type = htol16(BPROTO_TYPE_UINT32);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint32_s data;
+    data.v = htol32(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint32_s);
+
+    o->b_count++;
+}
+
+void msg1Writer_Addc (msg1Writer *o, uint64_t v)
+{
+    ASSERT(o->used >= 0)
+    
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(7);
+    header.type = htol16(BPROTO_TYPE_UINT64);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint64_s data;
+    data.v = htol64(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint64_s);
+
+    o->c_count++;
+}
+
+void msg1Writer_Addd (msg1Writer *o, uint16_t v)
+{
+    ASSERT(o->used >= 0)
+    
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(8);
+    header.type = htol16(BPROTO_TYPE_UINT16);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint16_s data;
+    data.v = htol16(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint16_s);
+
+    o->d_count++;
+}
+
+void msg1Writer_Adde (msg1Writer *o, uint8_t v)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->e_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(9);
+    header.type = htol16(BPROTO_TYPE_UINT8);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint8_s data;
+    data.v = htol8(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint8_s);
+
+    o->e_count++;
+}
+
+uint8_t * msg1Writer_Addf (msg1Writer *o, int len)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->f_count == 0)
+    ASSERT(len >= 0 && len <= UINT32_MAX)
+
+    struct BProto_header_s header;
+    header.id = htol16(10);
+    header.type = htol16(BPROTO_TYPE_DATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    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;
+
+    o->f_count++;
+
+    return dest;
+}
+
+uint8_t * msg1Writer_Addg (msg1Writer *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->g_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(11);
+    header.type = htol16(BPROTO_TYPE_CONSTDATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_data_header_s data;
+    data.len = htol32(4);
+    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 += (4);
+
+    o->g_count++;
+
+    return dest;
+}
+
+int msg1Parser_Init (msg1Parser *o, uint8_t *buf, int buf_len)
+{
+    ASSERT(buf_len >= 0)
+
+    o->buf = buf;
+    o->buf_len = buf_len;
+    o->a_start = o->buf_len;
+    o->a_span = 0;
+    o->a_pos = 0;
+    o->b_start = o->buf_len;
+    o->b_span = 0;
+    o->b_pos = 0;
+    o->c_start = o->buf_len;
+    o->c_span = 0;
+    o->c_pos = 0;
+    o->d_start = o->buf_len;
+    o->d_span = 0;
+    o->d_pos = 0;
+    o->e_start = o->buf_len;
+    o->e_span = 0;
+    o->e_pos = 0;
+    o->f_start = o->buf_len;
+    o->f_span = 0;
+    o->f_pos = 0;
+    o->g_start = o->buf_len;
+    o->g_span = 0;
+    o->g_pos = 0;
+
+    int a_count = 0;
+    int b_count = 0;
+    int c_count = 0;
+    int d_count = 0;
+    int e_count = 0;
+    int f_count = 0;
+    int g_count = 0;
+
+    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) {
+            case BPROTO_TYPE_UINT8: {
+                if (!(left >= sizeof(struct BProto_uint8_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+
+                switch (id) {
+                    case 9:
+                        if (o->e_start == o->buf_len) {
+                            o->e_start = entry_pos;
+                        }
+                        o->e_span = pos - o->e_start;
+                        e_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                if (!(left >= sizeof(struct BProto_uint16_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                switch (id) {
+                    case 5:
+                        if (o->a_start == o->buf_len) {
+                            o->a_start = entry_pos;
+                        }
+                        o->a_span = pos - o->a_start;
+                        a_count++;
+                        break;
+                    case 8:
+                        if (o->d_start == o->buf_len) {
+                            o->d_start = entry_pos;
+                        }
+                        o->d_span = pos - o->d_start;
+                        d_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                if (!(left >= sizeof(struct BProto_uint32_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+
+                switch (id) {
+                    case 6:
+                        if (o->b_start == o->buf_len) {
+                            o->b_start = entry_pos;
+                        }
+                        o->b_span = pos - o->b_start;
+                        b_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                if (!(left >= sizeof(struct BProto_uint64_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+
+                switch (id) {
+                    case 7:
+                        if (o->c_start == o->buf_len) {
+                            o->c_start = entry_pos;
+                        }
+                        o->c_span = pos - o->c_start;
+                        c_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            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) {
+                    case 10:
+                        if (!(type == BPROTO_TYPE_DATA)) {
+                            return 0;
+                        }
+                        if (o->f_start == o->buf_len) {
+                            o->f_start = entry_pos;
+                        }
+                        o->f_span = pos - o->f_start;
+                        f_count++;
+                        break;
+                    case 11:
+                        if (!(type == BPROTO_TYPE_CONSTDATA)) {
+                            return 0;
+                        }
+                        if (!(payload_len == (4))) {
+                            return 0;
+                        }
+                        if (o->g_start == o->buf_len) {
+                            o->g_start = entry_pos;
+                        }
+                        o->g_span = pos - o->g_start;
+                        g_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            default:
+                return 0;
+        }
+    }
+
+    if (!(a_count == 1)) {
+        return 0;
+    }
+    if (!(b_count <= 1)) {
+        return 0;
+    }
+    if (!(c_count >= 1)) {
+        return 0;
+    }
+    if (!(e_count == 1)) {
+        return 0;
+    }
+    if (!(f_count == 1)) {
+        return 0;
+    }
+    if (!(g_count == 1)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+int msg1Parser_GotEverything (msg1Parser *o)
+{
+    return (
+        o->a_pos == o->a_span
+        &&
+        o->b_pos == o->b_span
+        &&
+        o->c_pos == o->c_span
+        &&
+        o->d_pos == o->d_span
+        &&
+        o->e_pos == o->e_span
+        &&
+        o->f_pos == o->f_span
+        &&
+        o->g_pos == o->g_span
+    );
+}
+
+int msg1Parser_Geta (msg1Parser *o, uint16_t *v)
+{
+    ASSERT(o->a_pos >= 0)
+    ASSERT(o->a_pos <= o->a_span)
+
+    int left = o->a_span - o->a_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->a_start + o->a_pos, sizeof(header));
+        o->a_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->a_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                struct BProto_uint16_s val;
+                memcpy(&val, o->buf + o->a_start + o->a_pos, sizeof(val));
+                o->a_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                if (id == 5) {
+                    *v = ltoh16(val.v);
+                    return 1;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->a_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->a_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->a_start + o->a_pos, sizeof(val));
+                o->a_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)
+                o->a_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg1Parser_Reseta (msg1Parser *o)
+{
+    o->a_pos = 0;
+}
+
+void msg1Parser_Forwarda (msg1Parser *o)
+{
+    o->a_pos = o->a_span;
+}
+
+int msg1Parser_Getb (msg1Parser *o, uint32_t *v)
+{
+    ASSERT(o->b_pos >= 0)
+    ASSERT(o->b_pos <= o->b_span)
+
+    int left = o->b_span - o->b_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->b_start + o->b_pos, sizeof(header));
+        o->b_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->b_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->b_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                struct BProto_uint32_s val;
+                memcpy(&val, o->buf + o->b_start + o->b_pos, sizeof(val));
+                o->b_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+
+                if (id == 6) {
+                    *v = ltoh32(val.v);
+                    return 1;
+                }
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->b_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->b_start + o->b_pos, sizeof(val));
+                o->b_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)
+                o->b_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg1Parser_Resetb (msg1Parser *o)
+{
+    o->b_pos = 0;
+}
+
+void msg1Parser_Forwardb (msg1Parser *o)
+{
+    o->b_pos = o->b_span;
+}
+
+int msg1Parser_Getc (msg1Parser *o, uint64_t *v)
+{
+    ASSERT(o->c_pos >= 0)
+    ASSERT(o->c_pos <= o->c_span)
+
+    int left = o->c_span - o->c_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->c_start + o->c_pos, sizeof(header));
+        o->c_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->c_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->c_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->c_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                struct BProto_uint64_s val;
+                memcpy(&val, o->buf + o->c_start + o->c_pos, sizeof(val));
+                o->c_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+
+                if (id == 7) {
+                    *v = ltoh64(val.v);
+                    return 1;
+                }
+            } break;
+            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->c_start + o->c_pos, sizeof(val));
+                o->c_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)
+                o->c_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg1Parser_Resetc (msg1Parser *o)
+{
+    o->c_pos = 0;
+}
+
+void msg1Parser_Forwardc (msg1Parser *o)
+{
+    o->c_pos = o->c_span;
+}
+
+int msg1Parser_Getd (msg1Parser *o, uint16_t *v)
+{
+    ASSERT(o->d_pos >= 0)
+    ASSERT(o->d_pos <= o->d_span)
+
+    int left = o->d_span - o->d_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->d_start + o->d_pos, sizeof(header));
+        o->d_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->d_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                struct BProto_uint16_s val;
+                memcpy(&val, o->buf + o->d_start + o->d_pos, sizeof(val));
+                o->d_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                if (id == 8) {
+                    *v = ltoh16(val.v);
+                    return 1;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->d_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->d_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->d_start + o->d_pos, sizeof(val));
+                o->d_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)
+                o->d_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg1Parser_Resetd (msg1Parser *o)
+{
+    o->d_pos = 0;
+}
+
+void msg1Parser_Forwardd (msg1Parser *o)
+{
+    o->d_pos = o->d_span;
+}
+
+int msg1Parser_Gete (msg1Parser *o, uint8_t *v)
+{
+    ASSERT(o->e_pos >= 0)
+    ASSERT(o->e_pos <= o->e_span)
+
+    int left = o->e_span - o->e_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->e_start + o->e_pos, sizeof(header));
+        o->e_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                struct BProto_uint8_s val;
+                memcpy(&val, o->buf + o->e_start + o->e_pos, sizeof(val));
+                o->e_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+
+                if (id == 9) {
+                    *v = ltoh8(val.v);
+                    return 1;
+                }
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->e_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->e_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->e_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->e_start + o->e_pos, sizeof(val));
+                o->e_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)
+                o->e_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg1Parser_Resete (msg1Parser *o)
+{
+    o->e_pos = 0;
+}
+
+void msg1Parser_Forwarde (msg1Parser *o)
+{
+    o->e_pos = o->e_span;
+}
+
+int msg1Parser_Getf (msg1Parser *o, uint8_t **data, int *data_len)
+{
+    ASSERT(o->f_pos >= 0)
+    ASSERT(o->f_pos <= o->f_span)
+
+    int left = o->f_span - o->f_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->f_start + o->f_pos, sizeof(header));
+        o->f_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->f_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->f_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->f_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->f_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->f_start + o->f_pos, sizeof(val));
+                o->f_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)
+                uint8_t *payload = o->buf + o->f_start + o->f_pos;
+                o->f_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_DATA && id == 10) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg1Parser_Resetf (msg1Parser *o)
+{
+    o->f_pos = 0;
+}
+
+void msg1Parser_Forwardf (msg1Parser *o)
+{
+    o->f_pos = o->f_span;
+}
+
+int msg1Parser_Getg (msg1Parser *o, uint8_t **data)
+{
+    ASSERT(o->g_pos >= 0)
+    ASSERT(o->g_pos <= o->g_span)
+
+    int left = o->g_span - o->g_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->g_start + o->g_pos, sizeof(header));
+        o->g_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->g_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->g_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->g_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->g_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->g_start + o->g_pos, sizeof(val));
+                o->g_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)
+                uint8_t *payload = o->buf + o->g_start + o->g_pos;
+                o->g_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_CONSTDATA && id == 11) {
+                    *data = payload;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg1Parser_Resetg (msg1Parser *o)
+{
+    o->g_pos = 0;
+}
+
+void msg1Parser_Forwardg (msg1Parser *o)
+{
+    o->g_pos = o->g_span;
+}
+
diff --git a/external/badvpn_dns/generated/bproto_msgproto.h b/external/badvpn_dns/generated/bproto_msgproto.h
new file mode 100644
index 0000000..e429223
--- /dev/null
+++ b/external/badvpn_dns/generated/bproto_msgproto.h
@@ -0,0 +1,2122 @@
+/*
+    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>
+
+
+#define msg_SIZEtype (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s))
+#define msg_SIZEpayload(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))
+
+typedef struct {
+    uint8_t *out;
+    int used;
+    int type_count;
+    int payload_count;
+} msgWriter;
+
+static void msgWriter_Init (msgWriter *o, uint8_t *out);
+static int msgWriter_Finish (msgWriter *o);
+static void msgWriter_Addtype (msgWriter *o, uint16_t v);
+static uint8_t * msgWriter_Addpayload (msgWriter *o, int len);
+
+typedef struct {
+    uint8_t *buf;
+    int buf_len;
+    int type_start;
+    int type_span;
+    int type_pos;
+    int payload_start;
+    int payload_span;
+    int payload_pos;
+} msgParser;
+
+static int msgParser_Init (msgParser *o, uint8_t *buf, int buf_len);
+static int msgParser_GotEverything (msgParser *o);
+static int msgParser_Gettype (msgParser *o, uint16_t *v);
+static void msgParser_Resettype (msgParser *o);
+static void msgParser_Forwardtype (msgParser *o);
+static int msgParser_Getpayload (msgParser *o, uint8_t **data, int *data_len);
+static void msgParser_Resetpayload (msgParser *o);
+static void msgParser_Forwardpayload (msgParser *o);
+
+void msgWriter_Init (msgWriter *o, uint8_t *out)
+{
+    o->out = out;
+    o->used = 0;
+    o->type_count = 0;
+    o->payload_count = 0;
+}
+
+int msgWriter_Finish (msgWriter *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->type_count == 1)
+    ASSERT(o->payload_count == 1)
+
+    return o->used;
+}
+
+void msgWriter_Addtype (msgWriter *o, uint16_t v)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->type_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(1);
+    header.type = htol16(BPROTO_TYPE_UINT16);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint16_s data;
+    data.v = htol16(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint16_s);
+
+    o->type_count++;
+}
+
+uint8_t * msgWriter_Addpayload (msgWriter *o, int len)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->payload_count == 0)
+    ASSERT(len >= 0 && len <= UINT32_MAX)
+
+    struct BProto_header_s header;
+    header.id = htol16(2);
+    header.type = htol16(BPROTO_TYPE_DATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    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;
+
+    o->payload_count++;
+
+    return dest;
+}
+
+int msgParser_Init (msgParser *o, uint8_t *buf, int buf_len)
+{
+    ASSERT(buf_len >= 0)
+
+    o->buf = buf;
+    o->buf_len = buf_len;
+    o->type_start = o->buf_len;
+    o->type_span = 0;
+    o->type_pos = 0;
+    o->payload_start = o->buf_len;
+    o->payload_span = 0;
+    o->payload_pos = 0;
+
+    int type_count = 0;
+    int payload_count = 0;
+
+    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) {
+            case BPROTO_TYPE_UINT8: {
+                if (!(left >= sizeof(struct BProto_uint8_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                if (!(left >= sizeof(struct BProto_uint16_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                switch (id) {
+                    case 1:
+                        if (o->type_start == o->buf_len) {
+                            o->type_start = entry_pos;
+                        }
+                        o->type_span = pos - o->type_start;
+                        type_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                if (!(left >= sizeof(struct BProto_uint32_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                if (!(left >= sizeof(struct BProto_uint64_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            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) {
+                    case 2:
+                        if (!(type == BPROTO_TYPE_DATA)) {
+                            return 0;
+                        }
+                        if (o->payload_start == o->buf_len) {
+                            o->payload_start = entry_pos;
+                        }
+                        o->payload_span = pos - o->payload_start;
+                        payload_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            default:
+                return 0;
+        }
+    }
+
+    if (!(type_count == 1)) {
+        return 0;
+    }
+    if (!(payload_count == 1)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+int msgParser_GotEverything (msgParser *o)
+{
+    return (
+        o->type_pos == o->type_span
+        &&
+        o->payload_pos == o->payload_span
+    );
+}
+
+int msgParser_Gettype (msgParser *o, uint16_t *v)
+{
+    ASSERT(o->type_pos >= 0)
+    ASSERT(o->type_pos <= o->type_span)
+
+    int left = o->type_span - o->type_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->type_start + o->type_pos, sizeof(header));
+        o->type_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->type_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                struct BProto_uint16_s val;
+                memcpy(&val, o->buf + o->type_start + o->type_pos, sizeof(val));
+                o->type_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                if (id == 1) {
+                    *v = ltoh16(val.v);
+                    return 1;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->type_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->type_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->type_start + o->type_pos, sizeof(val));
+                o->type_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)
+                o->type_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msgParser_Resettype (msgParser *o)
+{
+    o->type_pos = 0;
+}
+
+void msgParser_Forwardtype (msgParser *o)
+{
+    o->type_pos = o->type_span;
+}
+
+int msgParser_Getpayload (msgParser *o, uint8_t **data, int *data_len)
+{
+    ASSERT(o->payload_pos >= 0)
+    ASSERT(o->payload_pos <= o->payload_span)
+
+    int left = o->payload_span - o->payload_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->payload_start + o->payload_pos, sizeof(header));
+        o->payload_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->payload_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->payload_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->payload_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->payload_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->payload_start + o->payload_pos, sizeof(val));
+                o->payload_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)
+                uint8_t *payload = o->buf + o->payload_start + o->payload_pos;
+                o->payload_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_DATA && id == 2) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msgParser_Resetpayload (msgParser *o)
+{
+    o->payload_pos = 0;
+}
+
+void msgParser_Forwardpayload (msgParser *o)
+{
+    o->payload_pos = o->payload_span;
+}
+
+#define msg_youconnect_SIZEaddr(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))
+#define msg_youconnect_SIZEkey(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))
+#define msg_youconnect_SIZEpassword (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint64_s))
+
+typedef struct {
+    uint8_t *out;
+    int used;
+    int addr_count;
+    int key_count;
+    int password_count;
+} msg_youconnectWriter;
+
+static void msg_youconnectWriter_Init (msg_youconnectWriter *o, uint8_t *out);
+static int msg_youconnectWriter_Finish (msg_youconnectWriter *o);
+static uint8_t * msg_youconnectWriter_Addaddr (msg_youconnectWriter *o, int len);
+static uint8_t * msg_youconnectWriter_Addkey (msg_youconnectWriter *o, int len);
+static void msg_youconnectWriter_Addpassword (msg_youconnectWriter *o, uint64_t v);
+
+typedef struct {
+    uint8_t *buf;
+    int buf_len;
+    int addr_start;
+    int addr_span;
+    int addr_pos;
+    int key_start;
+    int key_span;
+    int key_pos;
+    int password_start;
+    int password_span;
+    int password_pos;
+} msg_youconnectParser;
+
+static int msg_youconnectParser_Init (msg_youconnectParser *o, uint8_t *buf, int buf_len);
+static int msg_youconnectParser_GotEverything (msg_youconnectParser *o);
+static int msg_youconnectParser_Getaddr (msg_youconnectParser *o, uint8_t **data, int *data_len);
+static void msg_youconnectParser_Resetaddr (msg_youconnectParser *o);
+static void msg_youconnectParser_Forwardaddr (msg_youconnectParser *o);
+static int msg_youconnectParser_Getkey (msg_youconnectParser *o, uint8_t **data, int *data_len);
+static void msg_youconnectParser_Resetkey (msg_youconnectParser *o);
+static void msg_youconnectParser_Forwardkey (msg_youconnectParser *o);
+static int msg_youconnectParser_Getpassword (msg_youconnectParser *o, uint64_t *v);
+static void msg_youconnectParser_Resetpassword (msg_youconnectParser *o);
+static void msg_youconnectParser_Forwardpassword (msg_youconnectParser *o);
+
+void msg_youconnectWriter_Init (msg_youconnectWriter *o, uint8_t *out)
+{
+    o->out = out;
+    o->used = 0;
+    o->addr_count = 0;
+    o->key_count = 0;
+    o->password_count = 0;
+}
+
+int msg_youconnectWriter_Finish (msg_youconnectWriter *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->addr_count >= 1)
+    ASSERT(o->key_count >= 0 && o->key_count <= 1)
+    ASSERT(o->password_count >= 0 && o->password_count <= 1)
+
+    return o->used;
+}
+
+uint8_t * msg_youconnectWriter_Addaddr (msg_youconnectWriter *o, int len)
+{
+    ASSERT(o->used >= 0)
+    
+    ASSERT(len >= 0 && len <= UINT32_MAX)
+
+    struct BProto_header_s header;
+    header.id = htol16(1);
+    header.type = htol16(BPROTO_TYPE_DATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    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;
+
+    o->addr_count++;
+
+    return dest;
+}
+
+uint8_t * msg_youconnectWriter_Addkey (msg_youconnectWriter *o, int len)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->key_count == 0)
+    ASSERT(len >= 0 && len <= UINT32_MAX)
+
+    struct BProto_header_s header;
+    header.id = htol16(2);
+    header.type = htol16(BPROTO_TYPE_DATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    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;
+
+    o->key_count++;
+
+    return dest;
+}
+
+void msg_youconnectWriter_Addpassword (msg_youconnectWriter *o, uint64_t v)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->password_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(3);
+    header.type = htol16(BPROTO_TYPE_UINT64);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint64_s data;
+    data.v = htol64(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint64_s);
+
+    o->password_count++;
+}
+
+int msg_youconnectParser_Init (msg_youconnectParser *o, uint8_t *buf, int buf_len)
+{
+    ASSERT(buf_len >= 0)
+
+    o->buf = buf;
+    o->buf_len = buf_len;
+    o->addr_start = o->buf_len;
+    o->addr_span = 0;
+    o->addr_pos = 0;
+    o->key_start = o->buf_len;
+    o->key_span = 0;
+    o->key_pos = 0;
+    o->password_start = o->buf_len;
+    o->password_span = 0;
+    o->password_pos = 0;
+
+    int addr_count = 0;
+    int key_count = 0;
+    int password_count = 0;
+
+    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) {
+            case BPROTO_TYPE_UINT8: {
+                if (!(left >= sizeof(struct BProto_uint8_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                if (!(left >= sizeof(struct BProto_uint16_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                if (!(left >= sizeof(struct BProto_uint32_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                if (!(left >= sizeof(struct BProto_uint64_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+
+                switch (id) {
+                    case 3:
+                        if (o->password_start == o->buf_len) {
+                            o->password_start = entry_pos;
+                        }
+                        o->password_span = pos - o->password_start;
+                        password_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            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) {
+                    case 1:
+                        if (!(type == BPROTO_TYPE_DATA)) {
+                            return 0;
+                        }
+                        if (o->addr_start == o->buf_len) {
+                            o->addr_start = entry_pos;
+                        }
+                        o->addr_span = pos - o->addr_start;
+                        addr_count++;
+                        break;
+                    case 2:
+                        if (!(type == BPROTO_TYPE_DATA)) {
+                            return 0;
+                        }
+                        if (o->key_start == o->buf_len) {
+                            o->key_start = entry_pos;
+                        }
+                        o->key_span = pos - o->key_start;
+                        key_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            default:
+                return 0;
+        }
+    }
+
+    if (!(addr_count >= 1)) {
+        return 0;
+    }
+    if (!(key_count <= 1)) {
+        return 0;
+    }
+    if (!(password_count <= 1)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+int msg_youconnectParser_GotEverything (msg_youconnectParser *o)
+{
+    return (
+        o->addr_pos == o->addr_span
+        &&
+        o->key_pos == o->key_span
+        &&
+        o->password_pos == o->password_span
+    );
+}
+
+int msg_youconnectParser_Getaddr (msg_youconnectParser *o, uint8_t **data, int *data_len)
+{
+    ASSERT(o->addr_pos >= 0)
+    ASSERT(o->addr_pos <= o->addr_span)
+
+    int left = o->addr_span - o->addr_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->addr_start + o->addr_pos, sizeof(header));
+        o->addr_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->addr_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->addr_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->addr_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->addr_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->addr_start + o->addr_pos, sizeof(val));
+                o->addr_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)
+                uint8_t *payload = o->buf + o->addr_start + o->addr_pos;
+                o->addr_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_DATA && id == 1) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg_youconnectParser_Resetaddr (msg_youconnectParser *o)
+{
+    o->addr_pos = 0;
+}
+
+void msg_youconnectParser_Forwardaddr (msg_youconnectParser *o)
+{
+    o->addr_pos = o->addr_span;
+}
+
+int msg_youconnectParser_Getkey (msg_youconnectParser *o, uint8_t **data, int *data_len)
+{
+    ASSERT(o->key_pos >= 0)
+    ASSERT(o->key_pos <= o->key_span)
+
+    int left = o->key_span - o->key_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->key_start + o->key_pos, sizeof(header));
+        o->key_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->key_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->key_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->key_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->key_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->key_start + o->key_pos, sizeof(val));
+                o->key_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)
+                uint8_t *payload = o->buf + o->key_start + o->key_pos;
+                o->key_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_DATA && id == 2) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg_youconnectParser_Resetkey (msg_youconnectParser *o)
+{
+    o->key_pos = 0;
+}
+
+void msg_youconnectParser_Forwardkey (msg_youconnectParser *o)
+{
+    o->key_pos = o->key_span;
+}
+
+int msg_youconnectParser_Getpassword (msg_youconnectParser *o, uint64_t *v)
+{
+    ASSERT(o->password_pos >= 0)
+    ASSERT(o->password_pos <= o->password_span)
+
+    int left = o->password_span - o->password_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->password_start + o->password_pos, sizeof(header));
+        o->password_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->password_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->password_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->password_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                struct BProto_uint64_s val;
+                memcpy(&val, o->buf + o->password_start + o->password_pos, sizeof(val));
+                o->password_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+
+                if (id == 3) {
+                    *v = ltoh64(val.v);
+                    return 1;
+                }
+            } break;
+            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->password_start + o->password_pos, sizeof(val));
+                o->password_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)
+                o->password_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg_youconnectParser_Resetpassword (msg_youconnectParser *o)
+{
+    o->password_pos = 0;
+}
+
+void msg_youconnectParser_Forwardpassword (msg_youconnectParser *o)
+{
+    o->password_pos = o->password_span;
+}
+
+#define msg_youconnect_addr_SIZEname(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))
+#define msg_youconnect_addr_SIZEaddr(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))
+
+typedef struct {
+    uint8_t *out;
+    int used;
+    int name_count;
+    int addr_count;
+} msg_youconnect_addrWriter;
+
+static void msg_youconnect_addrWriter_Init (msg_youconnect_addrWriter *o, uint8_t *out);
+static int msg_youconnect_addrWriter_Finish (msg_youconnect_addrWriter *o);
+static uint8_t * msg_youconnect_addrWriter_Addname (msg_youconnect_addrWriter *o, int len);
+static uint8_t * msg_youconnect_addrWriter_Addaddr (msg_youconnect_addrWriter *o, int len);
+
+typedef struct {
+    uint8_t *buf;
+    int buf_len;
+    int name_start;
+    int name_span;
+    int name_pos;
+    int addr_start;
+    int addr_span;
+    int addr_pos;
+} msg_youconnect_addrParser;
+
+static int msg_youconnect_addrParser_Init (msg_youconnect_addrParser *o, uint8_t *buf, int buf_len);
+static int msg_youconnect_addrParser_GotEverything (msg_youconnect_addrParser *o);
+static int msg_youconnect_addrParser_Getname (msg_youconnect_addrParser *o, uint8_t **data, int *data_len);
+static void msg_youconnect_addrParser_Resetname (msg_youconnect_addrParser *o);
+static void msg_youconnect_addrParser_Forwardname (msg_youconnect_addrParser *o);
+static int msg_youconnect_addrParser_Getaddr (msg_youconnect_addrParser *o, uint8_t **data, int *data_len);
+static void msg_youconnect_addrParser_Resetaddr (msg_youconnect_addrParser *o);
+static void msg_youconnect_addrParser_Forwardaddr (msg_youconnect_addrParser *o);
+
+void msg_youconnect_addrWriter_Init (msg_youconnect_addrWriter *o, uint8_t *out)
+{
+    o->out = out;
+    o->used = 0;
+    o->name_count = 0;
+    o->addr_count = 0;
+}
+
+int msg_youconnect_addrWriter_Finish (msg_youconnect_addrWriter *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->name_count == 1)
+    ASSERT(o->addr_count == 1)
+
+    return o->used;
+}
+
+uint8_t * msg_youconnect_addrWriter_Addname (msg_youconnect_addrWriter *o, int len)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->name_count == 0)
+    ASSERT(len >= 0 && len <= UINT32_MAX)
+
+    struct BProto_header_s header;
+    header.id = htol16(1);
+    header.type = htol16(BPROTO_TYPE_DATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    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;
+
+    o->name_count++;
+
+    return dest;
+}
+
+uint8_t * msg_youconnect_addrWriter_Addaddr (msg_youconnect_addrWriter *o, int len)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->addr_count == 0)
+    ASSERT(len >= 0 && len <= UINT32_MAX)
+
+    struct BProto_header_s header;
+    header.id = htol16(2);
+    header.type = htol16(BPROTO_TYPE_DATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    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;
+
+    o->addr_count++;
+
+    return dest;
+}
+
+int msg_youconnect_addrParser_Init (msg_youconnect_addrParser *o, uint8_t *buf, int buf_len)
+{
+    ASSERT(buf_len >= 0)
+
+    o->buf = buf;
+    o->buf_len = buf_len;
+    o->name_start = o->buf_len;
+    o->name_span = 0;
+    o->name_pos = 0;
+    o->addr_start = o->buf_len;
+    o->addr_span = 0;
+    o->addr_pos = 0;
+
+    int name_count = 0;
+    int addr_count = 0;
+
+    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) {
+            case BPROTO_TYPE_UINT8: {
+                if (!(left >= sizeof(struct BProto_uint8_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                if (!(left >= sizeof(struct BProto_uint16_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                if (!(left >= sizeof(struct BProto_uint32_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                if (!(left >= sizeof(struct BProto_uint64_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            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) {
+                    case 1:
+                        if (!(type == BPROTO_TYPE_DATA)) {
+                            return 0;
+                        }
+                        if (o->name_start == o->buf_len) {
+                            o->name_start = entry_pos;
+                        }
+                        o->name_span = pos - o->name_start;
+                        name_count++;
+                        break;
+                    case 2:
+                        if (!(type == BPROTO_TYPE_DATA)) {
+                            return 0;
+                        }
+                        if (o->addr_start == o->buf_len) {
+                            o->addr_start = entry_pos;
+                        }
+                        o->addr_span = pos - o->addr_start;
+                        addr_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            default:
+                return 0;
+        }
+    }
+
+    if (!(name_count == 1)) {
+        return 0;
+    }
+    if (!(addr_count == 1)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+int msg_youconnect_addrParser_GotEverything (msg_youconnect_addrParser *o)
+{
+    return (
+        o->name_pos == o->name_span
+        &&
+        o->addr_pos == o->addr_span
+    );
+}
+
+int msg_youconnect_addrParser_Getname (msg_youconnect_addrParser *o, uint8_t **data, int *data_len)
+{
+    ASSERT(o->name_pos >= 0)
+    ASSERT(o->name_pos <= o->name_span)
+
+    int left = o->name_span - o->name_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->name_start + o->name_pos, sizeof(header));
+        o->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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->name_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->name_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->name_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->name_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->name_start + o->name_pos, sizeof(val));
+                o->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)
+                uint8_t *payload = o->buf + o->name_start + o->name_pos;
+                o->name_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_DATA && id == 1) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg_youconnect_addrParser_Resetname (msg_youconnect_addrParser *o)
+{
+    o->name_pos = 0;
+}
+
+void msg_youconnect_addrParser_Forwardname (msg_youconnect_addrParser *o)
+{
+    o->name_pos = o->name_span;
+}
+
+int msg_youconnect_addrParser_Getaddr (msg_youconnect_addrParser *o, uint8_t **data, int *data_len)
+{
+    ASSERT(o->addr_pos >= 0)
+    ASSERT(o->addr_pos <= o->addr_span)
+
+    int left = o->addr_span - o->addr_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->addr_start + o->addr_pos, sizeof(header));
+        o->addr_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->addr_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->addr_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->addr_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->addr_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->addr_start + o->addr_pos, sizeof(val));
+                o->addr_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)
+                uint8_t *payload = o->buf + o->addr_start + o->addr_pos;
+                o->addr_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_DATA && id == 2) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg_youconnect_addrParser_Resetaddr (msg_youconnect_addrParser *o)
+{
+    o->addr_pos = 0;
+}
+
+void msg_youconnect_addrParser_Forwardaddr (msg_youconnect_addrParser *o)
+{
+    o->addr_pos = o->addr_span;
+}
+
+#define msg_seed_SIZEseed_id (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s))
+#define msg_seed_SIZEkey(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))
+#define msg_seed_SIZEiv(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))
+
+typedef struct {
+    uint8_t *out;
+    int used;
+    int seed_id_count;
+    int key_count;
+    int iv_count;
+} msg_seedWriter;
+
+static void msg_seedWriter_Init (msg_seedWriter *o, uint8_t *out);
+static int msg_seedWriter_Finish (msg_seedWriter *o);
+static void msg_seedWriter_Addseed_id (msg_seedWriter *o, uint16_t v);
+static uint8_t * msg_seedWriter_Addkey (msg_seedWriter *o, int len);
+static uint8_t * msg_seedWriter_Addiv (msg_seedWriter *o, int len);
+
+typedef struct {
+    uint8_t *buf;
+    int buf_len;
+    int seed_id_start;
+    int seed_id_span;
+    int seed_id_pos;
+    int key_start;
+    int key_span;
+    int key_pos;
+    int iv_start;
+    int iv_span;
+    int iv_pos;
+} msg_seedParser;
+
+static int msg_seedParser_Init (msg_seedParser *o, uint8_t *buf, int buf_len);
+static int msg_seedParser_GotEverything (msg_seedParser *o);
+static int msg_seedParser_Getseed_id (msg_seedParser *o, uint16_t *v);
+static void msg_seedParser_Resetseed_id (msg_seedParser *o);
+static void msg_seedParser_Forwardseed_id (msg_seedParser *o);
+static int msg_seedParser_Getkey (msg_seedParser *o, uint8_t **data, int *data_len);
+static void msg_seedParser_Resetkey (msg_seedParser *o);
+static void msg_seedParser_Forwardkey (msg_seedParser *o);
+static int msg_seedParser_Getiv (msg_seedParser *o, uint8_t **data, int *data_len);
+static void msg_seedParser_Resetiv (msg_seedParser *o);
+static void msg_seedParser_Forwardiv (msg_seedParser *o);
+
+void msg_seedWriter_Init (msg_seedWriter *o, uint8_t *out)
+{
+    o->out = out;
+    o->used = 0;
+    o->seed_id_count = 0;
+    o->key_count = 0;
+    o->iv_count = 0;
+}
+
+int msg_seedWriter_Finish (msg_seedWriter *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->seed_id_count == 1)
+    ASSERT(o->key_count == 1)
+    ASSERT(o->iv_count == 1)
+
+    return o->used;
+}
+
+void msg_seedWriter_Addseed_id (msg_seedWriter *o, uint16_t v)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->seed_id_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(1);
+    header.type = htol16(BPROTO_TYPE_UINT16);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint16_s data;
+    data.v = htol16(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint16_s);
+
+    o->seed_id_count++;
+}
+
+uint8_t * msg_seedWriter_Addkey (msg_seedWriter *o, int len)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->key_count == 0)
+    ASSERT(len >= 0 && len <= UINT32_MAX)
+
+    struct BProto_header_s header;
+    header.id = htol16(2);
+    header.type = htol16(BPROTO_TYPE_DATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    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;
+
+    o->key_count++;
+
+    return dest;
+}
+
+uint8_t * msg_seedWriter_Addiv (msg_seedWriter *o, int len)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->iv_count == 0)
+    ASSERT(len >= 0 && len <= UINT32_MAX)
+
+    struct BProto_header_s header;
+    header.id = htol16(3);
+    header.type = htol16(BPROTO_TYPE_DATA);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    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;
+
+    o->iv_count++;
+
+    return dest;
+}
+
+int msg_seedParser_Init (msg_seedParser *o, uint8_t *buf, int buf_len)
+{
+    ASSERT(buf_len >= 0)
+
+    o->buf = buf;
+    o->buf_len = buf_len;
+    o->seed_id_start = o->buf_len;
+    o->seed_id_span = 0;
+    o->seed_id_pos = 0;
+    o->key_start = o->buf_len;
+    o->key_span = 0;
+    o->key_pos = 0;
+    o->iv_start = o->buf_len;
+    o->iv_span = 0;
+    o->iv_pos = 0;
+
+    int seed_id_count = 0;
+    int key_count = 0;
+    int iv_count = 0;
+
+    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) {
+            case BPROTO_TYPE_UINT8: {
+                if (!(left >= sizeof(struct BProto_uint8_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                if (!(left >= sizeof(struct BProto_uint16_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                switch (id) {
+                    case 1:
+                        if (o->seed_id_start == o->buf_len) {
+                            o->seed_id_start = entry_pos;
+                        }
+                        o->seed_id_span = pos - o->seed_id_start;
+                        seed_id_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                if (!(left >= sizeof(struct BProto_uint32_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                if (!(left >= sizeof(struct BProto_uint64_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            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) {
+                    case 2:
+                        if (!(type == BPROTO_TYPE_DATA)) {
+                            return 0;
+                        }
+                        if (o->key_start == o->buf_len) {
+                            o->key_start = entry_pos;
+                        }
+                        o->key_span = pos - o->key_start;
+                        key_count++;
+                        break;
+                    case 3:
+                        if (!(type == BPROTO_TYPE_DATA)) {
+                            return 0;
+                        }
+                        if (o->iv_start == o->buf_len) {
+                            o->iv_start = entry_pos;
+                        }
+                        o->iv_span = pos - o->iv_start;
+                        iv_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            default:
+                return 0;
+        }
+    }
+
+    if (!(seed_id_count == 1)) {
+        return 0;
+    }
+    if (!(key_count == 1)) {
+        return 0;
+    }
+    if (!(iv_count == 1)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+int msg_seedParser_GotEverything (msg_seedParser *o)
+{
+    return (
+        o->seed_id_pos == o->seed_id_span
+        &&
+        o->key_pos == o->key_span
+        &&
+        o->iv_pos == o->iv_span
+    );
+}
+
+int msg_seedParser_Getseed_id (msg_seedParser *o, uint16_t *v)
+{
+    ASSERT(o->seed_id_pos >= 0)
+    ASSERT(o->seed_id_pos <= o->seed_id_span)
+
+    int left = o->seed_id_span - o->seed_id_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->seed_id_start + o->seed_id_pos, sizeof(header));
+        o->seed_id_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->seed_id_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                struct BProto_uint16_s val;
+                memcpy(&val, o->buf + o->seed_id_start + o->seed_id_pos, sizeof(val));
+                o->seed_id_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                if (id == 1) {
+                    *v = ltoh16(val.v);
+                    return 1;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->seed_id_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->seed_id_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->seed_id_start + o->seed_id_pos, sizeof(val));
+                o->seed_id_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)
+                o->seed_id_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg_seedParser_Resetseed_id (msg_seedParser *o)
+{
+    o->seed_id_pos = 0;
+}
+
+void msg_seedParser_Forwardseed_id (msg_seedParser *o)
+{
+    o->seed_id_pos = o->seed_id_span;
+}
+
+int msg_seedParser_Getkey (msg_seedParser *o, uint8_t **data, int *data_len)
+{
+    ASSERT(o->key_pos >= 0)
+    ASSERT(o->key_pos <= o->key_span)
+
+    int left = o->key_span - o->key_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->key_start + o->key_pos, sizeof(header));
+        o->key_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->key_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->key_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->key_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->key_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->key_start + o->key_pos, sizeof(val));
+                o->key_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)
+                uint8_t *payload = o->buf + o->key_start + o->key_pos;
+                o->key_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_DATA && id == 2) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg_seedParser_Resetkey (msg_seedParser *o)
+{
+    o->key_pos = 0;
+}
+
+void msg_seedParser_Forwardkey (msg_seedParser *o)
+{
+    o->key_pos = o->key_span;
+}
+
+int msg_seedParser_Getiv (msg_seedParser *o, uint8_t **data, int *data_len)
+{
+    ASSERT(o->iv_pos >= 0)
+    ASSERT(o->iv_pos <= o->iv_span)
+
+    int left = o->iv_span - o->iv_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->iv_start + o->iv_pos, sizeof(header));
+        o->iv_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->iv_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                o->iv_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->iv_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->iv_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->iv_start + o->iv_pos, sizeof(val));
+                o->iv_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)
+                uint8_t *payload = o->buf + o->iv_start + o->iv_pos;
+                o->iv_pos += payload_len;
+                left -= payload_len;
+
+                if (type == BPROTO_TYPE_DATA && id == 3) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg_seedParser_Resetiv (msg_seedParser *o)
+{
+    o->iv_pos = 0;
+}
+
+void msg_seedParser_Forwardiv (msg_seedParser *o)
+{
+    o->iv_pos = o->iv_span;
+}
+
+#define msg_confirmseed_SIZEseed_id (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint16_s))
+
+typedef struct {
+    uint8_t *out;
+    int used;
+    int seed_id_count;
+} msg_confirmseedWriter;
+
+static void msg_confirmseedWriter_Init (msg_confirmseedWriter *o, uint8_t *out);
+static int msg_confirmseedWriter_Finish (msg_confirmseedWriter *o);
+static void msg_confirmseedWriter_Addseed_id (msg_confirmseedWriter *o, uint16_t v);
+
+typedef struct {
+    uint8_t *buf;
+    int buf_len;
+    int seed_id_start;
+    int seed_id_span;
+    int seed_id_pos;
+} msg_confirmseedParser;
+
+static int msg_confirmseedParser_Init (msg_confirmseedParser *o, uint8_t *buf, int buf_len);
+static int msg_confirmseedParser_GotEverything (msg_confirmseedParser *o);
+static int msg_confirmseedParser_Getseed_id (msg_confirmseedParser *o, uint16_t *v);
+static void msg_confirmseedParser_Resetseed_id (msg_confirmseedParser *o);
+static void msg_confirmseedParser_Forwardseed_id (msg_confirmseedParser *o);
+
+void msg_confirmseedWriter_Init (msg_confirmseedWriter *o, uint8_t *out)
+{
+    o->out = out;
+    o->used = 0;
+    o->seed_id_count = 0;
+}
+
+int msg_confirmseedWriter_Finish (msg_confirmseedWriter *o)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->seed_id_count == 1)
+
+    return o->used;
+}
+
+void msg_confirmseedWriter_Addseed_id (msg_confirmseedWriter *o, uint16_t v)
+{
+    ASSERT(o->used >= 0)
+    ASSERT(o->seed_id_count == 0)
+    
+
+    struct BProto_header_s header;
+    header.id = htol16(1);
+    header.type = htol16(BPROTO_TYPE_UINT16);
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+    struct BProto_uint16_s data;
+    data.v = htol16(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint16_s);
+
+    o->seed_id_count++;
+}
+
+int msg_confirmseedParser_Init (msg_confirmseedParser *o, uint8_t *buf, int buf_len)
+{
+    ASSERT(buf_len >= 0)
+
+    o->buf = buf;
+    o->buf_len = buf_len;
+    o->seed_id_start = o->buf_len;
+    o->seed_id_span = 0;
+    o->seed_id_pos = 0;
+
+    int seed_id_count = 0;
+
+    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) {
+            case BPROTO_TYPE_UINT8: {
+                if (!(left >= sizeof(struct BProto_uint8_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                if (!(left >= sizeof(struct BProto_uint16_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                switch (id) {
+                    case 1:
+                        if (o->seed_id_start == o->buf_len) {
+                            o->seed_id_start = entry_pos;
+                        }
+                        o->seed_id_span = pos - o->seed_id_start;
+                        seed_id_count++;
+                        break;
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                if (!(left >= sizeof(struct BProto_uint32_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                if (!(left >= sizeof(struct BProto_uint64_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+
+                switch (id) {
+                    default:
+                        return 0;
+                }
+            } break;
+            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) {
+                    default:
+                        return 0;
+                }
+            } break;
+            default:
+                return 0;
+        }
+    }
+
+    if (!(seed_id_count == 1)) {
+        return 0;
+    }
+
+    return 1;
+}
+
+int msg_confirmseedParser_GotEverything (msg_confirmseedParser *o)
+{
+    return (
+        o->seed_id_pos == o->seed_id_span
+    );
+}
+
+int msg_confirmseedParser_Getseed_id (msg_confirmseedParser *o, uint16_t *v)
+{
+    ASSERT(o->seed_id_pos >= 0)
+    ASSERT(o->seed_id_pos <= o->seed_id_span)
+
+    int left = o->seed_id_span - o->seed_id_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->seed_id_start + o->seed_id_pos, sizeof(header));
+        o->seed_id_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) {
+            case BPROTO_TYPE_UINT8: {
+                ASSERT(left >= sizeof(struct BProto_uint8_s))
+                o->seed_id_pos += sizeof(struct BProto_uint8_s);
+                left -= sizeof(struct BProto_uint8_s);
+            } break;
+            case BPROTO_TYPE_UINT16: {
+                ASSERT(left >= sizeof(struct BProto_uint16_s))
+                struct BProto_uint16_s val;
+                memcpy(&val, o->buf + o->seed_id_start + o->seed_id_pos, sizeof(val));
+                o->seed_id_pos += sizeof(struct BProto_uint16_s);
+                left -= sizeof(struct BProto_uint16_s);
+
+                if (id == 1) {
+                    *v = ltoh16(val.v);
+                    return 1;
+                }
+            } break;
+            case BPROTO_TYPE_UINT32: {
+                ASSERT(left >= sizeof(struct BProto_uint32_s))
+                o->seed_id_pos += sizeof(struct BProto_uint32_s);
+                left -= sizeof(struct BProto_uint32_s);
+            } break;
+            case BPROTO_TYPE_UINT64: {
+                ASSERT(left >= sizeof(struct BProto_uint64_s))
+                o->seed_id_pos += sizeof(struct BProto_uint64_s);
+                left -= sizeof(struct BProto_uint64_s);
+            } break;
+            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->seed_id_start + o->seed_id_pos, sizeof(val));
+                o->seed_id_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)
+                o->seed_id_pos += payload_len;
+                left -= payload_len;
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+void msg_confirmseedParser_Resetseed_id (msg_confirmseedParser *o)
+{
+    o->seed_id_pos = 0;
+}
+
+void msg_confirmseedParser_Forwardseed_id (msg_confirmseedParser *o)
+{
+    o->seed_id_pos = o->seed_id_span;
+}
+
diff --git a/external/badvpn_dns/generated/flex_BPredicate.c b/external/badvpn_dns/generated/flex_BPredicate.c
new file mode 100644
index 0000000..95f4ff6
--- /dev/null
+++ b/external/badvpn_dns/generated/flex_BPredicate.c
@@ -0,0 +1,2143 @@
+#line 2 "generated//flex_BPredicate.c"
+
+#line 4 "generated//flex_BPredicate.c"
+
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 37
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <stdint.h>. Non-C99 systems may or may not. */
+
+#if 1
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types. 
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <stdint.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t; 
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN               (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN              (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN              (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX               (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX              (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX              (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX              (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX             (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX             (4294967295U)
+#endif
+
+#endif /* ! C99 */
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else	/* ! __cplusplus */
+
+/* C99 requires __STDC__ to be defined as 1. */
+#if defined (__STDC__)
+
+#define YY_USE_CONST
+
+#endif	/* defined (__STDC__) */
+#endif	/* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index.  If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* An opaque pointer. */
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void* yyscan_t;
+#endif
+
+/* For convenience, these vars (plus the bison vars far below)
+   are macros in the reentrant scanner. */
+#define yyin yyg->yyin_r
+#define yyout yyg->yyout_r
+#define yyextra yyg->yyextra_r
+#define yyleng yyg->yyleng_r
+#define yytext yyg->yytext_r
+#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno)
+#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column)
+#define yy_flex_debug yyg->yy_flex_debug_r
+
+/* Enter a start condition.  This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN yyg->yy_start = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state.  The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START ((yyg->yy_start - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart(yyin ,yyscanner )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#define YY_BUF_SIZE 16384
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE   ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+    #define YY_LESS_LINENO(n)
+    
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+		*yy_cp = yyg->yy_hold_char; \
+		YY_RESTORE_YY_MORE_OFFSET \
+		yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+		YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+		} \
+	while ( 0 )
+
+#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner )
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+	{
+	FILE *yy_input_file;
+
+	char *yy_ch_buf;		/* input buffer */
+	char *yy_buf_pos;		/* current position in input buffer */
+
+	/* Size of input buffer in bytes, not including room for EOB
+	 * characters.
+	 */
+	yy_size_t yy_buf_size;
+
+	/* Number of characters read into yy_ch_buf, not including EOB
+	 * characters.
+	 */
+	yy_size_t yy_n_chars;
+
+	/* Whether we "own" the buffer - i.e., we know we created it,
+	 * and can realloc() it to grow it, and should free() it to
+	 * delete it.
+	 */
+	int yy_is_our_buffer;
+
+	/* Whether this is an "interactive" input source; if so, and
+	 * if we're using stdio for input, then we want to use getc()
+	 * instead of fread(), to make sure we stop fetching input after
+	 * each newline.
+	 */
+	int yy_is_interactive;
+
+	/* Whether we're considered to be at the beginning of a line.
+	 * If so, '^' rules will be active on the next match, otherwise
+	 * not.
+	 */
+	int yy_at_bol;
+
+    int yy_bs_lineno; /**< The line count. */
+    int yy_bs_column; /**< The column count. */
+    
+	/* Whether to try to fill the input buffer when we reach the
+	 * end of it.
+	 */
+	int yy_fill_buffer;
+
+	int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+	/* When an EOF's been seen but there's still some text to process
+	 * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+	 * shouldn't try reading from the input source any more.  We might
+	 * still have a bunch of tokens to match, though, because of
+	 * possible backing-up.
+	 *
+	 * When we actually see the EOF, we change the status to "new"
+	 * (via yyrestart()), so that the user can continue scanning by
+	 * just pointing yyin at a new input file.
+	 */
+#define YY_BUFFER_EOF_PENDING 2
+
+	};
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \
+                          ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \
+                          : NULL)
+
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top]
+
+void yyrestart (FILE *input_file ,yyscan_t yyscanner );
+void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+YY_BUFFER_STATE yy_create_buffer (FILE *file,int size ,yyscan_t yyscanner );
+void yy_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void yy_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+void yypop_buffer_state (yyscan_t yyscanner );
+
+static void yyensure_buffer_stack (yyscan_t yyscanner );
+static void yy_load_buffer_state (yyscan_t yyscanner );
+static void yy_init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner );
+
+#define YY_FLUSH_BUFFER yy_flush_buffer(YY_CURRENT_BUFFER ,yyscanner)
+
+YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner );
+YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner );
+YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,yy_size_t len ,yyscan_t yyscanner );
+
+void *yyalloc (yy_size_t ,yyscan_t yyscanner );
+void *yyrealloc (void *,yy_size_t ,yyscan_t yyscanner );
+void yyfree (void * ,yyscan_t yyscanner );
+
+#define yy_new_buffer yy_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+	{ \
+	if ( ! YY_CURRENT_BUFFER ){ \
+        yyensure_buffer_stack (yyscanner); \
+		YY_CURRENT_BUFFER_LVALUE =    \
+            yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \
+	} \
+	YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+	}
+
+#define yy_set_bol(at_bol) \
+	{ \
+	if ( ! YY_CURRENT_BUFFER ){\
+        yyensure_buffer_stack (yyscanner); \
+		YY_CURRENT_BUFFER_LVALUE =    \
+            yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \
+	} \
+	YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+	}
+
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* Begin user sect3 */
+
+#define yywrap(yyscanner) 1
+#define YY_SKIP_YYWRAP
+
+typedef unsigned char YY_CHAR;
+
+typedef int yy_state_type;
+
+#define yytext_ptr yytext_r
+
+static yy_state_type yy_get_previous_state (yyscan_t yyscanner );
+static yy_state_type yy_try_NUL_trans (yy_state_type current_state  ,yyscan_t yyscanner);
+static int yy_get_next_buffer (yyscan_t yyscanner );
+static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner );
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+	yyg->yytext_ptr = yy_bp; \
+	yyleng = (size_t) (yy_cp - yy_bp); \
+	yyg->yy_hold_char = *yy_cp; \
+	*yy_cp = '\0'; \
+	yyg->yy_c_buf_p = yy_cp;
+
+#define YY_NUM_RULES 13
+#define YY_END_OF_BUFFER 14
+/* This struct is not used in this scanner,
+   but its presence is necessary. */
+struct yy_trans_info
+	{
+	flex_int32_t yy_verify;
+	flex_int32_t yy_nxt;
+	};
+static yyconst flex_int16_t yy_accept[34] =
+    {   0,
+        0,    0,   14,   12,   11,   11,   12,    1,    2,    3,
+        9,    9,    9,    9,    9,    9,   11,    0,   10,    9,
+        9,    9,    5,    9,    9,    4,    6,    9,    9,    9,
+        7,    8,    0
+    } ;
+
+static yyconst flex_int32_t yy_ec[256] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    2,    3,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    2,    1,    4,    1,    1,    1,    1,    1,    5,
+        6,    1,    1,    7,    1,    1,    1,    8,    8,    8,
+        8,    8,    8,    8,    8,    8,    8,    1,    1,    1,
+        1,    1,    1,    1,    9,    8,    8,   10,    8,    8,
+        8,    8,    8,    8,    8,    8,    8,   11,   12,    8,
+        8,   13,    8,   14,    8,    8,    8,    8,    8,    8,
+        1,    1,    1,    1,    8,    1,   15,    8,    8,    8,
+
+       16,   17,    8,    8,    8,    8,    8,   18,    8,    8,
+        8,    8,    8,   19,   20,   21,   22,    8,    8,    8,
+        8,    8,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1
+    } ;
+
+static yyconst flex_int32_t yy_meta[23] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    2,    2,    2,
+        2,    2,    2,    2,    2,    2,    2,    2,    2,    2,
+        2,    2
+    } ;
+
+static yyconst flex_int16_t yy_base[36] =
+    {   0,
+        0,    0,   46,   47,   21,   23,   41,   47,   47,   47,
+        0,   33,   31,   29,   26,   21,   25,   35,   47,    0,
+       28,   23,    0,   18,   13,    0,    0,   14,   17,   16,
+        0,    0,   47,   28,   29
+    } ;
+
+static yyconst flex_int16_t yy_def[36] =
+    {   0,
+       33,    1,   33,   33,   33,   33,   34,   33,   33,   33,
+       35,   35,   35,   35,   35,   35,   33,   34,   33,   35,
+       35,   35,   35,   35,   35,   35,   35,   35,   35,   35,
+       35,   35,    0,   33,   33
+    } ;
+
+static yyconst flex_int16_t yy_nxt[70] =
+    {   0,
+        4,    5,    6,    7,    8,    9,   10,   11,   12,   11,
+       13,   14,   11,   11,   11,   11,   15,   11,   11,   11,
+       16,   11,   17,   17,   17,   17,   17,   17,   18,   18,
+       20,   32,   31,   30,   29,   28,   27,   26,   19,   25,
+       24,   23,   22,   21,   19,   33,    3,   33,   33,   33,
+       33,   33,   33,   33,   33,   33,   33,   33,   33,   33,
+       33,   33,   33,   33,   33,   33,   33,   33,   33
+    } ;
+
+static yyconst flex_int16_t yy_chk[70] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    5,    5,    6,    6,   17,   17,   34,   34,
+       35,   30,   29,   28,   25,   24,   22,   21,   18,   16,
+       15,   14,   13,   12,    7,    3,   33,   33,   33,   33,
+       33,   33,   33,   33,   33,   33,   33,   33,   33,   33,
+       33,   33,   33,   33,   33,   33,   33,   33,   33
+    } ;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+#line 1 "predicate/BPredicate.l"
+/**
+ * @file BPredicate.l
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * {@link BPredicate} lexer file.
+ */
+#line 35 "predicate/BPredicate.l"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <predicate/LexMemoryBufferInput.h>
+#include <predicate/BPredicate_internal.h>
+
+#include <generated/bison_BPredicate.h>
+
+#define YY_INPUT(buffer, res, max_size) \
+    int bytes_read = LexMemoryBufferInput_Read((LexMemoryBufferInput *)yyget_extra(yyscanner), buffer, max_size); \
+    res = (bytes_read == 0 ? YY_NULL : bytes_read);
+
+#define YY_NO_UNISTD_H 1
+#line 503 "generated//flex_BPredicate.c"
+
+#define INITIAL 0
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* Holds the entire state of the reentrant scanner. */
+struct yyguts_t
+    {
+
+    /* User-defined. Not touched by flex. */
+    YY_EXTRA_TYPE yyextra_r;
+
+    /* The rest are the same as the globals declared in the non-reentrant scanner. */
+    FILE *yyin_r, *yyout_r;
+    size_t yy_buffer_stack_top; /**< index of top of stack. */
+    size_t yy_buffer_stack_max; /**< capacity of stack. */
+    YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */
+    char yy_hold_char;
+    yy_size_t yy_n_chars;
+    yy_size_t yyleng_r;
+    char *yy_c_buf_p;
+    int yy_init;
+    int yy_start;
+    int yy_did_buffer_switch_on_eof;
+    int yy_start_stack_ptr;
+    int yy_start_stack_depth;
+    int *yy_start_stack;
+    yy_state_type yy_last_accepting_state;
+    char* yy_last_accepting_cpos;
+
+    int yylineno_r;
+    int yy_flex_debug_r;
+
+    char *yytext_r;
+    int yy_more_flag;
+    int yy_more_len;
+
+    YYSTYPE * yylval_r;
+
+    YYLTYPE * yylloc_r;
+
+    }; /* end struct yyguts_t */
+
+static int yy_init_globals (yyscan_t yyscanner );
+
+    /* This must go here because YYSTYPE and YYLTYPE are included
+     * from bison output in section 1.*/
+    #    define yylval yyg->yylval_r
+    
+    #    define yylloc yyg->yylloc_r
+    
+int yylex_init (yyscan_t* scanner);
+
+int yylex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner);
+
+/* Accessor methods to globals.
+   These are made visible to non-reentrant scanners for convenience. */
+
+int yylex_destroy (yyscan_t yyscanner );
+
+int yyget_debug (yyscan_t yyscanner );
+
+void yyset_debug (int debug_flag ,yyscan_t yyscanner );
+
+YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner );
+
+void yyset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner );
+
+FILE *yyget_in (yyscan_t yyscanner );
+
+void yyset_in  (FILE * in_str ,yyscan_t yyscanner );
+
+FILE *yyget_out (yyscan_t yyscanner );
+
+void yyset_out  (FILE * out_str ,yyscan_t yyscanner );
+
+yy_size_t yyget_leng (yyscan_t yyscanner );
+
+char *yyget_text (yyscan_t yyscanner );
+
+int yyget_lineno (yyscan_t yyscanner );
+
+void yyset_lineno (int line_number ,yyscan_t yyscanner );
+
+int yyget_column  (yyscan_t yyscanner );
+
+void yyset_column (int column_no ,yyscan_t yyscanner );
+
+YYSTYPE * yyget_lval (yyscan_t yyscanner );
+
+void yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner );
+
+       YYLTYPE *yyget_lloc (yyscan_t yyscanner );
+    
+        void yyset_lloc (YYLTYPE * yylloc_param ,yyscan_t yyscanner );
+    
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap (yyscan_t yyscanner );
+#else
+extern int yywrap (yyscan_t yyscanner );
+#endif
+#endif
+
+    static void yyunput (int c,char *buf_ptr  ,yyscan_t yyscanner);
+    
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner);
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner);
+#endif
+
+#ifndef YY_NO_INPUT
+
+#ifdef __cplusplus
+static int yyinput (yyscan_t yyscanner );
+#else
+static int input (yyscan_t yyscanner );
+#endif
+
+#endif
+
+    static void yy_push_state (int new_state ,yyscan_t yyscanner);
+    
+    static void yy_pop_state (yyscan_t yyscanner );
+    
+    static int yy_top_state (yyscan_t yyscanner );
+    
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0)
+#endif
+
+/* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+		{ \
+		int c = '*'; \
+		size_t n; \
+		for ( n = 0; n < max_size && \
+			     (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+			buf[n] = (char) c; \
+		if ( c == '\n' ) \
+			buf[n++] = (char) c; \
+		if ( c == EOF && ferror( yyin ) ) \
+			YY_FATAL_ERROR( "input in flex scanner failed" ); \
+		result = n; \
+		} \
+	else \
+		{ \
+		errno=0; \
+		while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \
+			{ \
+			if( errno != EINTR) \
+				{ \
+				YY_FATAL_ERROR( "input in flex scanner failed" ); \
+				break; \
+				} \
+			errno=0; \
+			clearerr(yyin); \
+			} \
+		}\
+\
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner)
+#endif
+
+/* end tables serialization structures and prototypes */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int yylex \
+               (YYSTYPE * yylval_param,YYLTYPE * yylloc_param ,yyscan_t yyscanner);
+
+#define YY_DECL int yylex \
+               (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner)
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+	YY_USER_ACTION
+
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+	register yy_state_type yy_current_state;
+	register char *yy_cp, *yy_bp;
+	register int yy_act;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+#line 52 "predicate/BPredicate.l"
+
+#line 756 "generated//flex_BPredicate.c"
+
+    yylval = yylval_param;
+
+    yylloc = yylloc_param;
+
+	if ( !yyg->yy_init )
+		{
+		yyg->yy_init = 1;
+
+#ifdef YY_USER_INIT
+		YY_USER_INIT;
+#endif
+
+		if ( ! yyg->yy_start )
+			yyg->yy_start = 1;	/* first start state */
+
+		if ( ! yyin )
+			yyin = stdin;
+
+		if ( ! yyout )
+			yyout = stdout;
+
+		if ( ! YY_CURRENT_BUFFER ) {
+			yyensure_buffer_stack (yyscanner);
+			YY_CURRENT_BUFFER_LVALUE =
+				yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner);
+		}
+
+		yy_load_buffer_state(yyscanner );
+		}
+
+	while ( 1 )		/* loops until end-of-file is reached */
+		{
+		yy_cp = yyg->yy_c_buf_p;
+
+		/* Support of yytext. */
+		*yy_cp = yyg->yy_hold_char;
+
+		/* yy_bp points to the position in yy_ch_buf of the start of
+		 * the current run.
+		 */
+		yy_bp = yy_cp;
+
+		yy_current_state = yyg->yy_start;
+yy_match:
+		do
+			{
+			register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+			if ( yy_accept[yy_current_state] )
+				{
+				yyg->yy_last_accepting_state = yy_current_state;
+				yyg->yy_last_accepting_cpos = yy_cp;
+				}
+			while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+				{
+				yy_current_state = (int) yy_def[yy_current_state];
+				if ( yy_current_state >= 34 )
+					yy_c = yy_meta[(unsigned int) yy_c];
+				}
+			yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+			++yy_cp;
+			}
+		while ( yy_current_state != 33 );
+		yy_cp = yyg->yy_last_accepting_cpos;
+		yy_current_state = yyg->yy_last_accepting_state;
+
+yy_find_action:
+		yy_act = yy_accept[yy_current_state];
+
+		YY_DO_BEFORE_ACTION;
+
+do_action:	/* This label is used only to access EOF actions. */
+
+		switch ( yy_act )
+	{ /* beginning of action switch */
+			case 0: /* must back up */
+			/* undo the effects of YY_DO_BEFORE_ACTION */
+			*yy_cp = yyg->yy_hold_char;
+			yy_cp = yyg->yy_last_accepting_cpos;
+			yy_current_state = yyg->yy_last_accepting_state;
+			goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 53 "predicate/BPredicate.l"
+return SPAR;
+	YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 54 "predicate/BPredicate.l"
+return EPAR;
+	YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 55 "predicate/BPredicate.l"
+return COMMA;
+	YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 56 "predicate/BPredicate.l"
+return AND;
+	YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 57 "predicate/BPredicate.l"
+return OR;
+	YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 58 "predicate/BPredicate.l"
+return NOT;
+	YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 59 "predicate/BPredicate.l"
+return CONSTANT_TRUE;
+	YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 60 "predicate/BPredicate.l"
+return CONSTANT_FALSE;
+	YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 61 "predicate/BPredicate.l"
+{
+                    int l = strlen(yytext);
+                    char *p = (char *)malloc(l + 1);
+                    if (p) {
+                        memcpy(p, yytext, l);
+                        p[l] = '\0';
+                    }
+                    yylval->text = p;
+                    return NAME;
+                }
+	YY_BREAK
+case 10:
+/* rule 10 can match eol */
+YY_RULE_SETUP
+#line 71 "predicate/BPredicate.l"
+{
+                    int l = strlen(yytext);
+                    char *p = (char *)malloc(l - 1);
+                    if (p) {
+                        memcpy(p, yytext + 1, l - 2);
+                        p[l - 2] = '\0';
+                    }
+                    yylval->text = p;
+                    return STRING;
+                }
+	YY_BREAK
+case 11:
+/* rule 11 can match eol */
+YY_RULE_SETUP
+#line 81 "predicate/BPredicate.l"
+;
+	YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 82 "predicate/BPredicate.l"
+LexMemoryBufferInput_SetError((LexMemoryBufferInput *)yyget_extra(yyscanner)); return 0; // remember failure and report EOF
+	YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 83 "predicate/BPredicate.l"
+ECHO;
+	YY_BREAK
+#line 924 "generated//flex_BPredicate.c"
+case YY_STATE_EOF(INITIAL):
+	yyterminate();
+
+	case YY_END_OF_BUFFER:
+		{
+		/* Amount of text matched not including the EOB char. */
+		int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1;
+
+		/* Undo the effects of YY_DO_BEFORE_ACTION. */
+		*yy_cp = yyg->yy_hold_char;
+		YY_RESTORE_YY_MORE_OFFSET
+
+		if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+			{
+			/* We're scanning a new file or input source.  It's
+			 * possible that this happened because the user
+			 * just pointed yyin at a new source and called
+			 * yylex().  If so, then we have to assure
+			 * consistency between YY_CURRENT_BUFFER and our
+			 * globals.  Here is the right place to do so, because
+			 * this is the first action (other than possibly a
+			 * back-up) that will match for the new input source.
+			 */
+			yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+			YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+			YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+			}
+
+		/* Note that here we test for yy_c_buf_p "<=" to the position
+		 * of the first EOB in the buffer, since yy_c_buf_p will
+		 * already have been incremented past the NUL character
+		 * (since all states make transitions on EOB to the
+		 * end-of-buffer state).  Contrast this with the test
+		 * in input().
+		 */
+		if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] )
+			{ /* This was really a NUL. */
+			yy_state_type yy_next_state;
+
+			yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text;
+
+			yy_current_state = yy_get_previous_state( yyscanner );
+
+			/* Okay, we're now positioned to make the NUL
+			 * transition.  We couldn't have
+			 * yy_get_previous_state() go ahead and do it
+			 * for us because it doesn't know how to deal
+			 * with the possibility of jamming (and we don't
+			 * want to build jamming into it because then it
+			 * will run more slowly).
+			 */
+
+			yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner);
+
+			yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+
+			if ( yy_next_state )
+				{
+				/* Consume the NUL. */
+				yy_cp = ++yyg->yy_c_buf_p;
+				yy_current_state = yy_next_state;
+				goto yy_match;
+				}
+
+			else
+				{
+				yy_cp = yyg->yy_last_accepting_cpos;
+				yy_current_state = yyg->yy_last_accepting_state;
+				goto yy_find_action;
+				}
+			}
+
+		else switch ( yy_get_next_buffer( yyscanner ) )
+			{
+			case EOB_ACT_END_OF_FILE:
+				{
+				yyg->yy_did_buffer_switch_on_eof = 0;
+
+				if ( yywrap(yyscanner ) )
+					{
+					/* Note: because we've taken care in
+					 * yy_get_next_buffer() to have set up
+					 * yytext, we can now set up
+					 * yy_c_buf_p so that if some total
+					 * hoser (like flex itself) wants to
+					 * call the scanner after we return the
+					 * YY_NULL, it'll still work - another
+					 * YY_NULL will get returned.
+					 */
+					yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ;
+
+					yy_act = YY_STATE_EOF(YY_START);
+					goto do_action;
+					}
+
+				else
+					{
+					if ( ! yyg->yy_did_buffer_switch_on_eof )
+						YY_NEW_FILE;
+					}
+				break;
+				}
+
+			case EOB_ACT_CONTINUE_SCAN:
+				yyg->yy_c_buf_p =
+					yyg->yytext_ptr + yy_amount_of_matched_text;
+
+				yy_current_state = yy_get_previous_state( yyscanner );
+
+				yy_cp = yyg->yy_c_buf_p;
+				yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+				goto yy_match;
+
+			case EOB_ACT_LAST_MATCH:
+				yyg->yy_c_buf_p =
+				&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars];
+
+				yy_current_state = yy_get_previous_state( yyscanner );
+
+				yy_cp = yyg->yy_c_buf_p;
+				yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+				goto yy_find_action;
+			}
+		break;
+		}
+
+	default:
+		YY_FATAL_ERROR(
+			"fatal flex scanner internal error--no action found" );
+	} /* end of action switch */
+		} /* end of scanning one token */
+} /* end of yylex */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ *	EOB_ACT_LAST_MATCH -
+ *	EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ *	EOB_ACT_END_OF_FILE - end of file
+ */
+static int yy_get_next_buffer (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+	register char *source = yyg->yytext_ptr;
+	register int number_to_move, i;
+	int ret_val;
+
+	if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] )
+		YY_FATAL_ERROR(
+		"fatal flex scanner internal error--end of buffer missed" );
+
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+		{ /* Don't try to fill the buffer, so this is an EOF. */
+		if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 )
+			{
+			/* We matched a single character, the EOB, so
+			 * treat this as a final EOF.
+			 */
+			return EOB_ACT_END_OF_FILE;
+			}
+
+		else
+			{
+			/* We matched some text prior to the EOB, first
+			 * process it.
+			 */
+			return EOB_ACT_LAST_MATCH;
+			}
+		}
+
+	/* Try to read more data. */
+
+	/* First move last chars to start of buffer. */
+	number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1;
+
+	for ( i = 0; i < number_to_move; ++i )
+		*(dest++) = *(source++);
+
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+		/* don't do the read, it's not guaranteed to return an EOF,
+		 * just force an EOF
+		 */
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0;
+
+	else
+		{
+			yy_size_t num_to_read =
+			YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+		while ( num_to_read <= 0 )
+			{ /* Not enough room in the buffer - grow it. */
+
+			/* just a shorter name for the current buffer */
+			YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE;
+
+			int yy_c_buf_p_offset =
+				(int) (yyg->yy_c_buf_p - b->yy_ch_buf);
+
+			if ( b->yy_is_our_buffer )
+				{
+				yy_size_t new_size = b->yy_buf_size * 2;
+
+				if ( new_size <= 0 )
+					b->yy_buf_size += b->yy_buf_size / 8;
+				else
+					b->yy_buf_size *= 2;
+
+				b->yy_ch_buf = (char *)
+					/* Include room in for 2 EOB chars. */
+					yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner );
+				}
+			else
+				/* Can't grow it, we don't own it. */
+				b->yy_ch_buf = 0;
+
+			if ( ! b->yy_ch_buf )
+				YY_FATAL_ERROR(
+				"fatal error - scanner input buffer overflow" );
+
+			yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+			num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+						number_to_move - 1;
+
+			}
+
+		if ( num_to_read > YY_READ_BUF_SIZE )
+			num_to_read = YY_READ_BUF_SIZE;
+
+		/* Read in more data. */
+		YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+			yyg->yy_n_chars, num_to_read );
+
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+		}
+
+	if ( yyg->yy_n_chars == 0 )
+		{
+		if ( number_to_move == YY_MORE_ADJ )
+			{
+			ret_val = EOB_ACT_END_OF_FILE;
+			yyrestart(yyin  ,yyscanner);
+			}
+
+		else
+			{
+			ret_val = EOB_ACT_LAST_MATCH;
+			YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+				YY_BUFFER_EOF_PENDING;
+			}
+		}
+
+	else
+		ret_val = EOB_ACT_CONTINUE_SCAN;
+
+	if ((yy_size_t) (yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
+		/* Extend the array by 50%, plus the number we really need. */
+		yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1);
+		YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ,yyscanner );
+		if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+			YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" );
+	}
+
+	yyg->yy_n_chars += number_to_move;
+	YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR;
+	YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR;
+
+	yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+	return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+    static yy_state_type yy_get_previous_state (yyscan_t yyscanner)
+{
+	register yy_state_type yy_current_state;
+	register char *yy_cp;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	yy_current_state = yyg->yy_start;
+
+	for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp )
+		{
+		register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+		if ( yy_accept[yy_current_state] )
+			{
+			yyg->yy_last_accepting_state = yy_current_state;
+			yyg->yy_last_accepting_cpos = yy_cp;
+			}
+		while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+			{
+			yy_current_state = (int) yy_def[yy_current_state];
+			if ( yy_current_state >= 34 )
+				yy_c = yy_meta[(unsigned int) yy_c];
+			}
+		yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+		}
+
+	return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ *	next_state = yy_try_NUL_trans( current_state );
+ */
+    static yy_state_type yy_try_NUL_trans  (yy_state_type yy_current_state , yyscan_t yyscanner)
+{
+	register int yy_is_jam;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */
+	register char *yy_cp = yyg->yy_c_buf_p;
+
+	register YY_CHAR yy_c = 1;
+	if ( yy_accept[yy_current_state] )
+		{
+		yyg->yy_last_accepting_state = yy_current_state;
+		yyg->yy_last_accepting_cpos = yy_cp;
+		}
+	while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+		{
+		yy_current_state = (int) yy_def[yy_current_state];
+		if ( yy_current_state >= 34 )
+			yy_c = yy_meta[(unsigned int) yy_c];
+		}
+	yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+	yy_is_jam = (yy_current_state == 33);
+
+	(void)yyg;
+	return yy_is_jam ? 0 : yy_current_state;
+}
+
+    static void yyunput (int c, register char * yy_bp , yyscan_t yyscanner)
+{
+	register char *yy_cp;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+    yy_cp = yyg->yy_c_buf_p;
+
+	/* undo effects of setting up yytext */
+	*yy_cp = yyg->yy_hold_char;
+
+	if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+		{ /* need to shift things up to make room */
+		/* +2 for EOB chars. */
+		register yy_size_t number_to_move = yyg->yy_n_chars + 2;
+		register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[
+					YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2];
+		register char *source =
+				&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move];
+
+		while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+			*--dest = *--source;
+
+		yy_cp += (int) (dest - source);
+		yy_bp += (int) (dest - source);
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars =
+			yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_buf_size;
+
+		if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+			YY_FATAL_ERROR( "flex scanner push-back overflow" );
+		}
+
+	*--yy_cp = (char) c;
+
+	yyg->yytext_ptr = yy_bp;
+	yyg->yy_hold_char = *yy_cp;
+	yyg->yy_c_buf_p = yy_cp;
+}
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+    static int yyinput (yyscan_t yyscanner)
+#else
+    static int input  (yyscan_t yyscanner)
+#endif
+
+{
+	int c;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	*yyg->yy_c_buf_p = yyg->yy_hold_char;
+
+	if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR )
+		{
+		/* yy_c_buf_p now points to the character we want to return.
+		 * If this occurs *before* the EOB characters, then it's a
+		 * valid NUL; if not, then we've hit the end of the buffer.
+		 */
+		if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] )
+			/* This was really a NUL. */
+			*yyg->yy_c_buf_p = '\0';
+
+		else
+			{ /* need more input */
+			yy_size_t offset = yyg->yy_c_buf_p - yyg->yytext_ptr;
+			++yyg->yy_c_buf_p;
+
+			switch ( yy_get_next_buffer( yyscanner ) )
+				{
+				case EOB_ACT_LAST_MATCH:
+					/* This happens because yy_g_n_b()
+					 * sees that we've accumulated a
+					 * token and flags that we need to
+					 * try matching the token before
+					 * proceeding.  But for input(),
+					 * there's no matching to consider.
+					 * So convert the EOB_ACT_LAST_MATCH
+					 * to EOB_ACT_END_OF_FILE.
+					 */
+
+					/* Reset buffer status. */
+					yyrestart(yyin ,yyscanner);
+
+					/*FALLTHROUGH*/
+
+				case EOB_ACT_END_OF_FILE:
+					{
+					if ( yywrap(yyscanner ) )
+						return EOF;
+
+					if ( ! yyg->yy_did_buffer_switch_on_eof )
+						YY_NEW_FILE;
+#ifdef __cplusplus
+					return yyinput(yyscanner);
+#else
+					return input(yyscanner);
+#endif
+					}
+
+				case EOB_ACT_CONTINUE_SCAN:
+					yyg->yy_c_buf_p = yyg->yytext_ptr + offset;
+					break;
+				}
+			}
+		}
+
+	c = *(unsigned char *) yyg->yy_c_buf_p;	/* cast for 8-bit char's */
+	*yyg->yy_c_buf_p = '\0';	/* preserve yytext */
+	yyg->yy_hold_char = *++yyg->yy_c_buf_p;
+
+	return c;
+}
+#endif	/* ifndef YY_NO_INPUT */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ * @param yyscanner The scanner object.
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+    void yyrestart  (FILE * input_file , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if ( ! YY_CURRENT_BUFFER ){
+        yyensure_buffer_stack (yyscanner);
+		YY_CURRENT_BUFFER_LVALUE =
+            yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner);
+	}
+
+	yy_init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner);
+	yy_load_buffer_state(yyscanner );
+}
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ * @param yyscanner The scanner object.
+ */
+    void yy_switch_to_buffer  (YY_BUFFER_STATE  new_buffer , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	/* TODO. We should be able to replace this entire function body
+	 * with
+	 *		yypop_buffer_state();
+	 *		yypush_buffer_state(new_buffer);
+     */
+	yyensure_buffer_stack (yyscanner);
+	if ( YY_CURRENT_BUFFER == new_buffer )
+		return;
+
+	if ( YY_CURRENT_BUFFER )
+		{
+		/* Flush out information for old buffer. */
+		*yyg->yy_c_buf_p = yyg->yy_hold_char;
+		YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p;
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+		}
+
+	YY_CURRENT_BUFFER_LVALUE = new_buffer;
+	yy_load_buffer_state(yyscanner );
+
+	/* We don't actually know whether we did this switch during
+	 * EOF (yywrap()) processing, but the only time this flag
+	 * is looked at is after yywrap() is called, so it's safe
+	 * to go ahead and always set it.
+	 */
+	yyg->yy_did_buffer_switch_on_eof = 1;
+}
+
+static void yy_load_buffer_state  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+	yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+	yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+	yyg->yy_hold_char = *yyg->yy_c_buf_p;
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ * @param yyscanner The scanner object.
+ * @return the allocated buffer state.
+ */
+    YY_BUFFER_STATE yy_create_buffer  (FILE * file, int  size , yyscan_t yyscanner)
+{
+	YY_BUFFER_STATE b;
+    
+	b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+	b->yy_buf_size = size;
+
+	/* yy_ch_buf has to be 2 characters longer than the size given because
+	 * we need to put in 2 end-of-buffer characters.
+	 */
+	b->yy_ch_buf = (char *) yyalloc(b->yy_buf_size + 2 ,yyscanner );
+	if ( ! b->yy_ch_buf )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+	b->yy_is_our_buffer = 1;
+
+	yy_init_buffer(b,file ,yyscanner);
+
+	return b;
+}
+
+/** Destroy the buffer.
+ * @param b a buffer created with yy_create_buffer()
+ * @param yyscanner The scanner object.
+ */
+    void yy_delete_buffer (YY_BUFFER_STATE  b , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if ( ! b )
+		return;
+
+	if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+		YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+	if ( b->yy_is_our_buffer )
+		yyfree((void *) b->yy_ch_buf ,yyscanner );
+
+	yyfree((void *) b ,yyscanner );
+}
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a yyrestart() or at EOF.
+ */
+    static void yy_init_buffer  (YY_BUFFER_STATE  b, FILE * file , yyscan_t yyscanner)
+
+{
+	int oerrno = errno;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	yy_flush_buffer(b ,yyscanner);
+
+	b->yy_input_file = file;
+	b->yy_fill_buffer = 1;
+
+    /* If b is the current buffer, then yy_init_buffer was _probably_
+     * called from yyrestart() or through yy_get_next_buffer.
+     * In that case, we don't want to reset the lineno or column.
+     */
+    if (b != YY_CURRENT_BUFFER){
+        b->yy_bs_lineno = 1;
+        b->yy_bs_column = 0;
+    }
+
+        b->yy_is_interactive = 0;
+    
+	errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ * @param yyscanner The scanner object.
+ */
+    void yy_flush_buffer (YY_BUFFER_STATE  b , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if ( ! b )
+		return;
+
+	b->yy_n_chars = 0;
+
+	/* We always need two end-of-buffer characters.  The first causes
+	 * a transition to the end-of-buffer state.  The second causes
+	 * a jam in that state.
+	 */
+	b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+	b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+	b->yy_buf_pos = &b->yy_ch_buf[0];
+
+	b->yy_at_bol = 1;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	if ( b == YY_CURRENT_BUFFER )
+		yy_load_buffer_state(yyscanner );
+}
+
+/** Pushes the new state onto the stack. The new state becomes
+ *  the current state. This function will allocate the stack
+ *  if necessary.
+ *  @param new_buffer The new state.
+ *  @param yyscanner The scanner object.
+ */
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if (new_buffer == NULL)
+		return;
+
+	yyensure_buffer_stack(yyscanner);
+
+	/* This block is copied from yy_switch_to_buffer. */
+	if ( YY_CURRENT_BUFFER )
+		{
+		/* Flush out information for old buffer. */
+		*yyg->yy_c_buf_p = yyg->yy_hold_char;
+		YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p;
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+		}
+
+	/* Only push if top exists. Otherwise, replace top. */
+	if (YY_CURRENT_BUFFER)
+		yyg->yy_buffer_stack_top++;
+	YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+	/* copied from yy_switch_to_buffer. */
+	yy_load_buffer_state(yyscanner );
+	yyg->yy_did_buffer_switch_on_eof = 1;
+}
+
+/** Removes and deletes the top of the stack, if present.
+ *  The next element becomes the new top.
+ *  @param yyscanner The scanner object.
+ */
+void yypop_buffer_state (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if (!YY_CURRENT_BUFFER)
+		return;
+
+	yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner);
+	YY_CURRENT_BUFFER_LVALUE = NULL;
+	if (yyg->yy_buffer_stack_top > 0)
+		--yyg->yy_buffer_stack_top;
+
+	if (YY_CURRENT_BUFFER) {
+		yy_load_buffer_state(yyscanner );
+		yyg->yy_did_buffer_switch_on_eof = 1;
+	}
+}
+
+/* Allocates the stack if it does not exist.
+ *  Guarantees space for at least one push.
+ */
+static void yyensure_buffer_stack (yyscan_t yyscanner)
+{
+	yy_size_t num_to_alloc;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if (!yyg->yy_buffer_stack) {
+
+		/* First allocation is just for 2 elements, since we don't know if this
+		 * scanner will even need a stack. We use 2 instead of 1 to avoid an
+		 * immediate realloc on the next call.
+         */
+		num_to_alloc = 1;
+		yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc
+								(num_to_alloc * sizeof(struct yy_buffer_state*)
+								, yyscanner);
+		if ( ! yyg->yy_buffer_stack )
+			YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+								  
+		memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+				
+		yyg->yy_buffer_stack_max = num_to_alloc;
+		yyg->yy_buffer_stack_top = 0;
+		return;
+	}
+
+	if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){
+
+		/* Increase the buffer to prepare for a possible push. */
+		int grow_size = 8 /* arbitrary grow size */;
+
+		num_to_alloc = yyg->yy_buffer_stack_max + grow_size;
+		yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc
+								(yyg->yy_buffer_stack,
+								num_to_alloc * sizeof(struct yy_buffer_state*)
+								, yyscanner);
+		if ( ! yyg->yy_buffer_stack )
+			YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+		/* zero only the new slots.*/
+		memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*));
+		yyg->yy_buffer_stack_max = num_to_alloc;
+	}
+}
+
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object. 
+ */
+YY_BUFFER_STATE yy_scan_buffer  (char * base, yy_size_t  size , yyscan_t yyscanner)
+{
+	YY_BUFFER_STATE b;
+    
+	if ( size < 2 ||
+	     base[size-2] != YY_END_OF_BUFFER_CHAR ||
+	     base[size-1] != YY_END_OF_BUFFER_CHAR )
+		/* They forgot to leave room for the EOB's. */
+		return 0;
+
+	b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+	b->yy_buf_size = size - 2;	/* "- 2" to take care of EOB's */
+	b->yy_buf_pos = b->yy_ch_buf = base;
+	b->yy_is_our_buffer = 0;
+	b->yy_input_file = 0;
+	b->yy_n_chars = b->yy_buf_size;
+	b->yy_is_interactive = 0;
+	b->yy_at_bol = 1;
+	b->yy_fill_buffer = 0;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	yy_switch_to_buffer(b ,yyscanner );
+
+	return b;
+}
+
+/** Setup the input buffer state to scan a string. The next call to yylex() will
+ * scan from a @e copy of @a str.
+ * @param yystr a NUL-terminated string to scan
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ *       yy_scan_bytes() instead.
+ */
+YY_BUFFER_STATE yy_scan_string (yyconst char * yystr , yyscan_t yyscanner)
+{
+    
+	return yy_scan_bytes(yystr,strlen(yystr) ,yyscanner);
+}
+
+/** Setup the input buffer state to scan the given bytes. The next call to yylex() will
+ * scan from a @e copy of @a bytes.
+ * @param yybytes the byte buffer to scan
+ * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_bytes  (yyconst char * yybytes, yy_size_t  _yybytes_len , yyscan_t yyscanner)
+{
+	YY_BUFFER_STATE b;
+	char *buf;
+	yy_size_t n;
+	int i;
+    
+	/* Get memory for full buffer, including space for trailing EOB's. */
+	n = _yybytes_len + 2;
+	buf = (char *) yyalloc(n ,yyscanner );
+	if ( ! buf )
+		YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+	for ( i = 0; i < _yybytes_len; ++i )
+		buf[i] = yybytes[i];
+
+	buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+
+	b = yy_scan_buffer(buf,n ,yyscanner);
+	if ( ! b )
+		YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+	/* It's okay to grow etc. this buffer, and we should throw it
+	 * away when we're done.
+	 */
+	b->yy_is_our_buffer = 1;
+
+	return b;
+}
+
+    static void yy_push_state (int  new_state , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if ( yyg->yy_start_stack_ptr >= yyg->yy_start_stack_depth )
+		{
+		yy_size_t new_size;
+
+		yyg->yy_start_stack_depth += YY_START_STACK_INCR;
+		new_size = yyg->yy_start_stack_depth * sizeof( int );
+
+		if ( ! yyg->yy_start_stack )
+			yyg->yy_start_stack = (int *) yyalloc(new_size ,yyscanner );
+
+		else
+			yyg->yy_start_stack = (int *) yyrealloc((void *) yyg->yy_start_stack,new_size ,yyscanner );
+
+		if ( ! yyg->yy_start_stack )
+			YY_FATAL_ERROR( "out of memory expanding start-condition stack" );
+		}
+
+	yyg->yy_start_stack[yyg->yy_start_stack_ptr++] = YY_START;
+
+	BEGIN(new_state);
+}
+
+    static void yy_pop_state  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if ( --yyg->yy_start_stack_ptr < 0 )
+		YY_FATAL_ERROR( "start-condition stack underflow" );
+
+	BEGIN(yyg->yy_start_stack[yyg->yy_start_stack_ptr]);
+}
+
+    static int yy_top_state  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	return yyg->yy_start_stack[yyg->yy_start_stack_ptr - 1];
+}
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner)
+{
+    	(void) fprintf( stderr, "%s\n", msg );
+	exit( YY_EXIT_FAILURE );
+}
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+		yytext[yyleng] = yyg->yy_hold_char; \
+		yyg->yy_c_buf_p = yytext + yyless_macro_arg; \
+		yyg->yy_hold_char = *yyg->yy_c_buf_p; \
+		*yyg->yy_c_buf_p = '\0'; \
+		yyleng = yyless_macro_arg; \
+		} \
+	while ( 0 )
+
+/* Accessor  methods (get/set functions) to struct members. */
+
+/** Get the user-defined data for this scanner.
+ * @param yyscanner The scanner object.
+ */
+YY_EXTRA_TYPE yyget_extra  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyextra;
+}
+
+/** Get the current line number.
+ * @param yyscanner The scanner object.
+ */
+int yyget_lineno  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    
+        if (! YY_CURRENT_BUFFER)
+            return 0;
+    
+    return yylineno;
+}
+
+/** Get the current column number.
+ * @param yyscanner The scanner object.
+ */
+int yyget_column  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    
+        if (! YY_CURRENT_BUFFER)
+            return 0;
+    
+    return yycolumn;
+}
+
+/** Get the input stream.
+ * @param yyscanner The scanner object.
+ */
+FILE *yyget_in  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyin;
+}
+
+/** Get the output stream.
+ * @param yyscanner The scanner object.
+ */
+FILE *yyget_out  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyout;
+}
+
+/** Get the length of the current token.
+ * @param yyscanner The scanner object.
+ */
+yy_size_t yyget_leng  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyleng;
+}
+
+/** Get the current token.
+ * @param yyscanner The scanner object.
+ */
+
+char *yyget_text  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yytext;
+}
+
+/** Set the user-defined data. This data is never touched by the scanner.
+ * @param user_defined The data to be associated with this scanner.
+ * @param yyscanner The scanner object.
+ */
+void yyset_extra (YY_EXTRA_TYPE  user_defined , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yyextra = user_defined ;
+}
+
+/** Set the current line number.
+ * @param line_number
+ * @param yyscanner The scanner object.
+ */
+void yyset_lineno (int  line_number , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+        /* lineno is only valid if an input buffer exists. */
+        if (! YY_CURRENT_BUFFER )
+           YY_FATAL_ERROR( "yyset_lineno called with no buffer" );
+    
+    yylineno = line_number;
+}
+
+/** Set the current column.
+ * @param line_number
+ * @param yyscanner The scanner object.
+ */
+void yyset_column (int  column_no , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+        /* column is only valid if an input buffer exists. */
+        if (! YY_CURRENT_BUFFER )
+           YY_FATAL_ERROR( "yyset_column called with no buffer" );
+    
+    yycolumn = column_no;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param in_str A readable stream.
+ * @param yyscanner The scanner object.
+ * @see yy_switch_to_buffer
+ */
+void yyset_in (FILE *  in_str , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yyin = in_str ;
+}
+
+void yyset_out (FILE *  out_str , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yyout = out_str ;
+}
+
+int yyget_debug  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yy_flex_debug;
+}
+
+void yyset_debug (int  bdebug , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yy_flex_debug = bdebug ;
+}
+
+/* Accessor methods for yylval and yylloc */
+
+YYSTYPE * yyget_lval  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yylval;
+}
+
+void yyset_lval (YYSTYPE *  yylval_param , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yylval = yylval_param;
+}
+
+YYLTYPE *yyget_lloc  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yylloc;
+}
+    
+void yyset_lloc (YYLTYPE *  yylloc_param , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yylloc = yylloc_param;
+}
+    
+/* User-visible API */
+
+/* yylex_init is special because it creates the scanner itself, so it is
+ * the ONLY reentrant function that doesn't take the scanner as the last argument.
+ * That's why we explicitly handle the declaration, instead of using our macros.
+ */
+
+int yylex_init(yyscan_t* ptr_yy_globals)
+
+{
+    if (ptr_yy_globals == NULL){
+        errno = EINVAL;
+        return 1;
+    }
+
+    *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL );
+
+    if (*ptr_yy_globals == NULL){
+        errno = ENOMEM;
+        return 1;
+    }
+
+    /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */
+    memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
+
+    return yy_init_globals ( *ptr_yy_globals );
+}
+
+/* yylex_init_extra has the same functionality as yylex_init, but follows the
+ * convention of taking the scanner as the last argument. Note however, that
+ * this is a *pointer* to a scanner, as it will be allocated by this call (and
+ * is the reason, too, why this function also must handle its own declaration).
+ * The user defined value in the first argument will be available to yyalloc in
+ * the yyextra field.
+ */
+
+int yylex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals )
+
+{
+    struct yyguts_t dummy_yyguts;
+
+    yyset_extra (yy_user_defined, &dummy_yyguts);
+
+    if (ptr_yy_globals == NULL){
+        errno = EINVAL;
+        return 1;
+    }
+	
+    *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts );
+	
+    if (*ptr_yy_globals == NULL){
+        errno = ENOMEM;
+        return 1;
+    }
+    
+    /* By setting to 0xAA, we expose bugs in
+    yy_init_globals. Leave at 0x00 for releases. */
+    memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
+    
+    yyset_extra (yy_user_defined, *ptr_yy_globals);
+    
+    return yy_init_globals ( *ptr_yy_globals );
+}
+
+static int yy_init_globals (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    /* Initialization is the same as for the non-reentrant scanner.
+     * This function is called from yylex_destroy(), so don't allocate here.
+     */
+
+    yyg->yy_buffer_stack = 0;
+    yyg->yy_buffer_stack_top = 0;
+    yyg->yy_buffer_stack_max = 0;
+    yyg->yy_c_buf_p = (char *) 0;
+    yyg->yy_init = 0;
+    yyg->yy_start = 0;
+
+    yyg->yy_start_stack_ptr = 0;
+    yyg->yy_start_stack_depth = 0;
+    yyg->yy_start_stack =  NULL;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+    yyin = stdin;
+    yyout = stdout;
+#else
+    yyin = (FILE *) 0;
+    yyout = (FILE *) 0;
+#endif
+
+    /* For future reference: Set errno on error, since we are called by
+     * yylex_init()
+     */
+    return 0;
+}
+
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+    /* Pop the buffer stack, destroying each element. */
+	while(YY_CURRENT_BUFFER){
+		yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner );
+		YY_CURRENT_BUFFER_LVALUE = NULL;
+		yypop_buffer_state(yyscanner);
+	}
+
+	/* Destroy the stack itself. */
+	yyfree(yyg->yy_buffer_stack ,yyscanner);
+	yyg->yy_buffer_stack = NULL;
+
+    /* Destroy the start condition stack. */
+        yyfree(yyg->yy_start_stack ,yyscanner );
+        yyg->yy_start_stack = NULL;
+
+    /* Reset the globals. This is important in a non-reentrant scanner so the next time
+     * yylex() is called, initialization will occur. */
+    yy_init_globals( yyscanner);
+
+    /* Destroy the main struct (reentrant only). */
+    yyfree ( yyscanner , yyscanner );
+    yyscanner = NULL;
+    return 0;
+}
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner)
+{
+	register int i;
+	for ( i = 0; i < n; ++i )
+		s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner)
+{
+	register int n;
+	for ( n = 0; s[n]; ++n )
+		;
+
+	return n;
+}
+#endif
+
+void *yyalloc (yy_size_t  size , yyscan_t yyscanner)
+{
+	return (void *) malloc( size );
+}
+
+void *yyrealloc  (void * ptr, yy_size_t  size , yyscan_t yyscanner)
+{
+	/* The cast to (char *) in the following accommodates both
+	 * implementations that use char* generic pointers, and those
+	 * that use void* generic pointers.  It works with the latter
+	 * because both ANSI C and C++ allow castless assignment from
+	 * any pointer type to void*, and deal with argument conversions
+	 * as though doing an assignment.
+	 */
+	return (void *) realloc( (char *) ptr, size );
+}
+
+void yyfree (void * ptr , yyscan_t yyscanner)
+{
+	free( (char *) ptr );	/* see yyrealloc() for (char *) cast */
+}
+
+#define YYTABLES_NAME "yytables"
+
+#line 83 "predicate/BPredicate.l"
+
+
+
diff --git a/external/badvpn_dns/generated/flex_BPredicate.h b/external/badvpn_dns/generated/flex_BPredicate.h
new file mode 100644
index 0000000..f3d2428
--- /dev/null
+++ b/external/badvpn_dns/generated/flex_BPredicate.h
@@ -0,0 +1,350 @@
+#ifndef yyHEADER_H
+#define yyHEADER_H 1
+#define yyIN_HEADER 1
+
+#line 6 "generated//flex_BPredicate.h"
+
+#line 8 "generated//flex_BPredicate.h"
+
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 37
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <stdint.h>. Non-C99 systems may or may not. */
+
+#if 1
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types. 
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <stdint.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t; 
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN               (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN              (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN              (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX               (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX              (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX              (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX              (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX             (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX             (4294967295U)
+#endif
+
+#endif /* ! C99 */
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else	/* ! __cplusplus */
+
+/* C99 requires __STDC__ to be defined as 1. */
+#if defined (__STDC__)
+
+#define YY_USE_CONST
+
+#endif	/* defined (__STDC__) */
+#endif	/* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+/* An opaque pointer. */
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void* yyscan_t;
+#endif
+
+/* For convenience, these vars (plus the bison vars far below)
+   are macros in the reentrant scanner. */
+#define yyin yyg->yyin_r
+#define yyout yyg->yyout_r
+#define yyextra yyg->yyextra_r
+#define yyleng yyg->yyleng_r
+#define yytext yyg->yytext_r
+#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno)
+#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column)
+#define yy_flex_debug yyg->yy_flex_debug_r
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#define YY_BUF_SIZE 16384
+#endif
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+	{
+	FILE *yy_input_file;
+
+	char *yy_ch_buf;		/* input buffer */
+	char *yy_buf_pos;		/* current position in input buffer */
+
+	/* Size of input buffer in bytes, not including room for EOB
+	 * characters.
+	 */
+	yy_size_t yy_buf_size;
+
+	/* Number of characters read into yy_ch_buf, not including EOB
+	 * characters.
+	 */
+	yy_size_t yy_n_chars;
+
+	/* Whether we "own" the buffer - i.e., we know we created it,
+	 * and can realloc() it to grow it, and should free() it to
+	 * delete it.
+	 */
+	int yy_is_our_buffer;
+
+	/* Whether this is an "interactive" input source; if so, and
+	 * if we're using stdio for input, then we want to use getc()
+	 * instead of fread(), to make sure we stop fetching input after
+	 * each newline.
+	 */
+	int yy_is_interactive;
+
+	/* Whether we're considered to be at the beginning of a line.
+	 * If so, '^' rules will be active on the next match, otherwise
+	 * not.
+	 */
+	int yy_at_bol;
+
+    int yy_bs_lineno; /**< The line count. */
+    int yy_bs_column; /**< The column count. */
+    
+	/* Whether to try to fill the input buffer when we reach the
+	 * end of it.
+	 */
+	int yy_fill_buffer;
+
+	int yy_buffer_status;
+
+	};
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+void yyrestart (FILE *input_file ,yyscan_t yyscanner );
+void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+YY_BUFFER_STATE yy_create_buffer (FILE *file,int size ,yyscan_t yyscanner );
+void yy_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void yy_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+void yypop_buffer_state (yyscan_t yyscanner );
+
+YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner );
+YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner );
+YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,yy_size_t len ,yyscan_t yyscanner );
+
+void *yyalloc (yy_size_t ,yyscan_t yyscanner );
+void *yyrealloc (void *,yy_size_t ,yyscan_t yyscanner );
+void yyfree (void * ,yyscan_t yyscanner );
+
+/* Begin user sect3 */
+
+#define yywrap(yyscanner) 1
+#define YY_SKIP_YYWRAP
+
+#define yytext_ptr yytext_r
+
+#ifdef YY_HEADER_EXPORT_START_CONDITIONS
+#define INITIAL 0
+
+#endif
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+int yylex_init (yyscan_t* scanner);
+
+int yylex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner);
+
+/* Accessor methods to globals.
+   These are made visible to non-reentrant scanners for convenience. */
+
+int yylex_destroy (yyscan_t yyscanner );
+
+int yyget_debug (yyscan_t yyscanner );
+
+void yyset_debug (int debug_flag ,yyscan_t yyscanner );
+
+YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner );
+
+void yyset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner );
+
+FILE *yyget_in (yyscan_t yyscanner );
+
+void yyset_in  (FILE * in_str ,yyscan_t yyscanner );
+
+FILE *yyget_out (yyscan_t yyscanner );
+
+void yyset_out  (FILE * out_str ,yyscan_t yyscanner );
+
+yy_size_t yyget_leng (yyscan_t yyscanner );
+
+char *yyget_text (yyscan_t yyscanner );
+
+int yyget_lineno (yyscan_t yyscanner );
+
+void yyset_lineno (int line_number ,yyscan_t yyscanner );
+
+int yyget_column  (yyscan_t yyscanner );
+
+void yyset_column (int column_no ,yyscan_t yyscanner );
+
+YYSTYPE * yyget_lval (yyscan_t yyscanner );
+
+void yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner );
+
+       YYLTYPE *yyget_lloc (yyscan_t yyscanner );
+    
+        void yyset_lloc (YYLTYPE * yylloc_param ,yyscan_t yyscanner );
+    
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap (yyscan_t yyscanner );
+#else
+extern int yywrap (yyscan_t yyscanner );
+#endif
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner);
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner);
+#endif
+
+#ifndef YY_NO_INPUT
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int yylex \
+               (YYSTYPE * yylval_param,YYLTYPE * yylloc_param ,yyscan_t yyscanner);
+
+#define YY_DECL int yylex \
+               (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner)
+#endif /* !YY_DECL */
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+#undef YY_NEW_FILE
+#undef YY_FLUSH_BUFFER
+#undef yy_set_bol
+#undef yy_new_buffer
+#undef yy_set_interactive
+#undef YY_DO_BEFORE_ACTION
+
+#ifdef YY_DECL_IS_OURS
+#undef YY_DECL_IS_OURS
+#undef YY_DECL
+#endif
+
+#line 83 "predicate/BPredicate.l"
+
+
+#line 349 "generated//flex_BPredicate.h"
+#undef yyIN_HEADER
+#endif /* yyHEADER_H */
diff --git a/external/badvpn_dns/lemon/lemon.c b/external/badvpn_dns/lemon/lemon.c
new file mode 100644
index 0000000..8336e9a
--- /dev/null
+++ b/external/badvpn_dns/lemon/lemon.c
@@ -0,0 +1,4889 @@
+/*
+** This file contains all sources (including headers) to the LEMON
+** LALR(1) parser generator.  The sources have been combined into a
+** single file to make it easy to include LEMON in the source tree
+** and Makefile of another program.
+**
+** The author of this program disclaims copyright.
+*/
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#ifndef __WIN32__
+#   if defined(_WIN32) || defined(WIN32)
+#	define __WIN32__
+#   endif
+#endif
+
+#ifdef __WIN32__
+extern int access();
+#else
+#include <unistd.h>
+#endif
+
+/* #define PRIVATE static */
+#define PRIVATE
+
+#ifdef TEST
+#define MAXRHS 5       /* Set low to exercise exception code */
+#else
+#define MAXRHS 1000
+#endif
+
+static char *msort(char*,char**,int(*)(const char*,const char*));
+
+/*
+** Compilers are getting increasingly pedantic about type conversions
+** as C evolves ever closer to Ada....  To work around the latest problems
+** we have to define the following variant of strlen().
+*/
+#define lemonStrlen(X)   ((int)strlen(X))
+
+static struct action *Action_new(void);
+static struct action *Action_sort(struct action *);
+
+/********** From the file "build.h" ************************************/
+void FindRulePrecedences();
+void FindFirstSets();
+void FindStates();
+void FindLinks();
+void FindFollowSets();
+void FindActions();
+
+/********* From the file "configlist.h" *********************************/
+void Configlist_init(/* void */);
+struct config *Configlist_add(/* struct rule *, int */);
+struct config *Configlist_addbasis(/* struct rule *, int */);
+void Configlist_closure(/* void */);
+void Configlist_sort(/* void */);
+void Configlist_sortbasis(/* void */);
+struct config *Configlist_return(/* void */);
+struct config *Configlist_basis(/* void */);
+void Configlist_eat(/* struct config * */);
+void Configlist_reset(/* void */);
+
+/********* From the file "error.h" ***************************************/
+void ErrorMsg(const char *, int,const char *, ...);
+
+/****** From the file "option.h" ******************************************/
+struct s_options {
+  enum { OPT_FLAG=1,  OPT_INT,  OPT_DBL,  OPT_STR,
+         OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR} type;
+  char *label;
+  char *arg;
+  char *message;
+};
+int    OptInit(/* char**,struct s_options*,FILE* */);
+int    OptNArgs(/* void */);
+char  *OptArg(/* int */);
+void   OptErr(/* int */);
+void   OptPrint(/* void */);
+
+/******** From the file "parse.h" *****************************************/
+void Parse(/* struct lemon *lemp */);
+
+/********* From the file "plink.h" ***************************************/
+struct plink *Plink_new(/* void */);
+void Plink_add(/* struct plink **, struct config * */);
+void Plink_copy(/* struct plink **, struct plink * */);
+void Plink_delete(/* struct plink * */);
+
+/********** From the file "report.h" *************************************/
+void Reprint(/* struct lemon * */);
+void ReportOutput(/* struct lemon * */);
+void ReportTable(/* struct lemon * */);
+void ReportHeader(/* struct lemon * */);
+void CompressTables(/* struct lemon * */);
+void ResortStates(/* struct lemon * */);
+
+/********** From the file "set.h" ****************************************/
+void  SetSize(/* int N */);             /* All sets will be of size N */
+char *SetNew(/* void */);               /* A new set for element 0..N */
+void  SetFree(/* char* */);             /* Deallocate a set */
+
+int SetAdd(/* char*,int */);            /* Add element to a set */
+int SetUnion(/* char *A,char *B */);    /* A <- A U B, thru element N */
+
+#define SetFind(X,Y) (X[Y])       /* True if Y is in set X */
+
+/********** From the file "struct.h" *************************************/
+/*
+** Principal data structures for the LEMON parser generator.
+*/
+
+typedef enum {LEMON_FALSE=0, LEMON_TRUE} Boolean;
+
+/* Symbols (terminals and nonterminals) of the grammar are stored
+** in the following: */
+struct symbol {
+  char *name;              /* Name of the symbol */
+  int index;               /* Index number for this symbol */
+  enum {
+    TERMINAL,
+    NONTERMINAL,
+    MULTITERMINAL
+  } type;                  /* Symbols are all either TERMINALS or NTs */
+  struct rule *rule;       /* Linked list of rules of this (if an NT) */
+  struct symbol *fallback; /* fallback token in case this token doesn't parse */
+  int prec;                /* Precedence if defined (-1 otherwise) */
+  enum e_assoc {
+    LEFT,
+    RIGHT,
+    NONE,
+    UNK
+  } assoc;                 /* Associativity if precedence is defined */
+  char *firstset;          /* First-set for all rules of this symbol */
+  Boolean lambda;          /* True if NT and can generate an empty string */
+  int useCnt;              /* Number of times used */
+  char *destructor;        /* Code which executes whenever this symbol is
+                           ** popped from the stack during error processing */
+  int destLineno;          /* Line number for start of destructor */
+  char *datatype;          /* The data type of information held by this
+                           ** object. Only used if type==NONTERMINAL */
+  int dtnum;               /* The data type number.  In the parser, the value
+                           ** stack is a union.  The .yy%d element of this
+                           ** union is the correct data type for this object */
+  /* The following fields are used by MULTITERMINALs only */
+  int nsubsym;             /* Number of constituent symbols in the MULTI */
+  struct symbol **subsym;  /* Array of constituent symbols */
+};
+
+/* Each production rule in the grammar is stored in the following
+** structure.  */
+struct rule {
+  struct symbol *lhs;      /* Left-hand side of the rule */
+  char *lhsalias;          /* Alias for the LHS (NULL if none) */
+  int lhsStart;            /* True if left-hand side is the start symbol */
+  int ruleline;            /* Line number for the rule */
+  int nrhs;                /* Number of RHS symbols */
+  struct symbol **rhs;     /* The RHS symbols */
+  char **rhsalias;         /* An alias for each RHS symbol (NULL if none) */
+  int line;                /* Line number at which code begins */
+  char *code;              /* The code executed when this rule is reduced */
+  struct symbol *precsym;  /* Precedence symbol for this rule */
+  int index;               /* An index number for this rule */
+  Boolean canReduce;       /* True if this rule is ever reduced */
+  struct rule *nextlhs;    /* Next rule with the same LHS */
+  struct rule *next;       /* Next rule in the global list */
+};
+
+/* A configuration is a production rule of the grammar together with
+** a mark (dot) showing how much of that rule has been processed so far.
+** Configurations also contain a follow-set which is a list of terminal
+** symbols which are allowed to immediately follow the end of the rule.
+** Every configuration is recorded as an instance of the following: */
+struct config {
+  struct rule *rp;         /* The rule upon which the configuration is based */
+  int dot;                 /* The parse point */
+  char *fws;               /* Follow-set for this configuration only */
+  struct plink *fplp;      /* Follow-set forward propagation links */
+  struct plink *bplp;      /* Follow-set backwards propagation links */
+  struct state *stp;       /* Pointer to state which contains this */
+  enum {
+    COMPLETE,              /* The status is used during followset and */
+    INCOMPLETE             /*    shift computations */
+  } status;
+  struct config *next;     /* Next configuration in the state */
+  struct config *bp;       /* The next basis configuration */
+};
+
+/* Every shift or reduce operation is stored as one of the following */
+struct action {
+  struct symbol *sp;       /* The look-ahead symbol */
+  enum e_action {
+    SHIFT,
+    ACCEPT,
+    REDUCE,
+    ERROR,
+    SSCONFLICT,              /* A shift/shift conflict */
+    SRCONFLICT,              /* Was a reduce, but part of a conflict */
+    RRCONFLICT,              /* Was a reduce, but part of a conflict */
+    SH_RESOLVED,             /* Was a shift.  Precedence resolved conflict */
+    RD_RESOLVED,             /* Was reduce.  Precedence resolved conflict */
+    NOT_USED                 /* Deleted by compression */
+  } type;
+  union {
+    struct state *stp;     /* The new state, if a shift */
+    struct rule *rp;       /* The rule, if a reduce */
+  } x;
+  struct action *next;     /* Next action for this state */
+  struct action *collide;  /* Next action with the same hash */
+};
+
+/* Each state of the generated parser's finite state machine
+** is encoded as an instance of the following structure. */
+struct state {
+  struct config *bp;       /* The basis configurations for this state */
+  struct config *cfp;      /* All configurations in this set */
+  int statenum;            /* Sequential number for this state */
+  struct action *ap;       /* Array of actions for this state */
+  int nTknAct, nNtAct;     /* Number of actions on terminals and nonterminals */
+  int iTknOfst, iNtOfst;   /* yy_action[] offset for terminals and nonterms */
+  int iDflt;               /* Default action */
+};
+#define NO_OFFSET (-2147483647)
+
+/* A followset propagation link indicates that the contents of one
+** configuration followset should be propagated to another whenever
+** the first changes. */
+struct plink {
+  struct config *cfp;      /* The configuration to which linked */
+  struct plink *next;      /* The next propagate link */
+};
+
+/* The state vector for the entire parser generator is recorded as
+** follows.  (LEMON uses no global variables and makes little use of
+** static variables.  Fields in the following structure can be thought
+** of as begin global variables in the program.) */
+struct lemon {
+  struct state **sorted;   /* Table of states sorted by state number */
+  struct rule *rule;       /* List of all rules */
+  int nstate;              /* Number of states */
+  int nrule;               /* Number of rules */
+  int nsymbol;             /* Number of terminal and nonterminal symbols */
+  int nterminal;           /* Number of terminal symbols */
+  struct symbol **symbols; /* Sorted array of pointers to symbols */
+  int errorcnt;            /* Number of errors */
+  struct symbol *errsym;   /* The error symbol */
+  struct symbol *wildcard; /* Token that matches anything */
+  char *name;              /* Name of the generated parser */
+  char *arg;               /* Declaration of the 3th argument to parser */
+  char *tokentype;         /* Type of terminal symbols in the parser stack */
+  char *vartype;           /* The default type of non-terminal symbols */
+  char *start;             /* Name of the start symbol for the grammar */
+  char *stacksize;         /* Size of the parser stack */
+  char *include;           /* Code to put at the start of the C file */
+  char *error;             /* Code to execute when an error is seen */
+  char *overflow;          /* Code to execute on a stack overflow */
+  char *failure;           /* Code to execute on parser failure */
+  char *accept;            /* Code to execute when the parser excepts */
+  char *extracode;         /* Code appended to the generated file */
+  char *tokendest;         /* Code to execute to destroy token data */
+  char *vardest;           /* Code for the default non-terminal destructor */
+  char *filename;          /* Name of the input file */
+  char *outname;           /* Name of the current output file */
+  char *tokenprefix;       /* A prefix added to token names in the .h file */
+  int nconflict;           /* Number of parsing conflicts */
+  int tablesize;           /* Size of the parse tables */
+  int basisflag;           /* Print only basis configurations */
+  int has_fallback;        /* True if any %fallback is seen in the grammar */
+  int nolinenosflag;       /* True if #line statements should not be printed */
+  char *argv0;             /* Name of the program */
+};
+
+#define MemoryCheck(X) if((X)==0){ \
+  extern void memory_error(); \
+  memory_error(); \
+}
+
+/**************** From the file "table.h" *********************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+**              "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file!  Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+/* Routines for handling a strings */
+
+char *Strsafe();
+
+void Strsafe_init(/* void */);
+int Strsafe_insert(/* char * */);
+char *Strsafe_find(/* char * */);
+
+/* Routines for handling symbols of the grammar */
+
+struct symbol *Symbol_new();
+int Symbolcmpp(/* struct symbol **, struct symbol ** */);
+void Symbol_init(/* void */);
+int Symbol_insert(/* struct symbol *, char * */);
+struct symbol *Symbol_find(/* char * */);
+struct symbol *Symbol_Nth(/* int */);
+int Symbol_count(/*  */);
+struct symbol **Symbol_arrayof(/*  */);
+
+/* Routines to manage the state table */
+
+int Configcmp(/* struct config *, struct config * */);
+struct state *State_new();
+void State_init(/* void */);
+int State_insert(/* struct state *, struct config * */);
+struct state *State_find(/* struct config * */);
+struct state **State_arrayof(/*  */);
+
+/* Routines used for efficiency in Configlist_add */
+
+void Configtable_init(/* void */);
+int Configtable_insert(/* struct config * */);
+struct config *Configtable_find(/* struct config * */);
+void Configtable_clear(/* int(*)(struct config *) */);
+/****************** From the file "action.c" *******************************/
+/*
+** Routines processing parser actions in the LEMON parser generator.
+*/
+
+/* Allocate a new parser action */
+static struct action *Action_new(void){
+  static struct action *freelist = 0;
+  struct action *new;
+
+  if( freelist==0 ){
+    int i;
+    int amt = 100;
+    freelist = (struct action *)calloc(amt, sizeof(struct action));
+    if( freelist==0 ){
+      fprintf(stderr,"Unable to allocate memory for a new parser action.");
+      exit(1);
+    }
+    for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+    freelist[amt-1].next = 0;
+  }
+  new = freelist;
+  freelist = freelist->next;
+  return new;
+}
+
+/* Compare two actions for sorting purposes.  Return negative, zero, or
+** positive if the first action is less than, equal to, or greater than
+** the first
+*/
+static int actioncmp(
+  struct action *ap1,
+  struct action *ap2
+){
+  int rc;
+  rc = ap1->sp->index - ap2->sp->index;
+  if( rc==0 ){
+    rc = (int)ap1->type - (int)ap2->type;
+  }
+  if( rc==0 && ap1->type==REDUCE ){
+    rc = ap1->x.rp->index - ap2->x.rp->index;
+  }
+  return rc;
+}
+
+/* Sort parser actions */
+static struct action *Action_sort(
+  struct action *ap
+){
+  ap = (struct action *)msort((char *)ap,(char **)&ap->next,
+                              (int(*)(const char*,const char*))actioncmp);
+  return ap;
+}
+
+void Action_add(app,type,sp,arg)
+struct action **app;
+enum e_action type;
+struct symbol *sp;
+char *arg;
+{
+  struct action *new;
+  new = Action_new();
+  new->next = *app;
+  *app = new;
+  new->type = type;
+  new->sp = sp;
+  if( type==SHIFT ){
+    new->x.stp = (struct state *)arg;
+  }else{
+    new->x.rp = (struct rule *)arg;
+  }
+}
+/********************** New code to implement the "acttab" module ***********/
+/*
+** This module implements routines use to construct the yy_action[] table.
+*/
+
+/*
+** The state of the yy_action table under construction is an instance of
+** the following structure
+*/
+typedef struct acttab acttab;
+struct acttab {
+  int nAction;                 /* Number of used slots in aAction[] */
+  int nActionAlloc;            /* Slots allocated for aAction[] */
+  struct {
+    int lookahead;             /* Value of the lookahead token */
+    int action;                /* Action to take on the given lookahead */
+  } *aAction,                  /* The yy_action[] table under construction */
+    *aLookahead;               /* A single new transaction set */
+  int mnLookahead;             /* Minimum aLookahead[].lookahead */
+  int mnAction;                /* Action associated with mnLookahead */
+  int mxLookahead;             /* Maximum aLookahead[].lookahead */
+  int nLookahead;              /* Used slots in aLookahead[] */
+  int nLookaheadAlloc;         /* Slots allocated in aLookahead[] */
+};
+
+/* Return the number of entries in the yy_action table */
+#define acttab_size(X) ((X)->nAction)
+
+/* The value for the N-th entry in yy_action */
+#define acttab_yyaction(X,N)  ((X)->aAction[N].action)
+
+/* The value for the N-th entry in yy_lookahead */
+#define acttab_yylookahead(X,N)  ((X)->aAction[N].lookahead)
+
+/* Free all memory associated with the given acttab */
+void acttab_free(acttab *p){
+  free( p->aAction );
+  free( p->aLookahead );
+  free( p );
+}
+
+/* Allocate a new acttab structure */
+acttab *acttab_alloc(void){
+  acttab *p = calloc( 1, sizeof(*p) );
+  if( p==0 ){
+    fprintf(stderr,"Unable to allocate memory for a new acttab.");
+    exit(1);
+  }
+  memset(p, 0, sizeof(*p));
+  return p;
+}
+
+/* Add a new action to the current transaction set
+*/
+void acttab_action(acttab *p, int lookahead, int action){
+  if( p->nLookahead>=p->nLookaheadAlloc ){
+    p->nLookaheadAlloc += 25;
+    p->aLookahead = realloc( p->aLookahead,
+                             sizeof(p->aLookahead[0])*p->nLookaheadAlloc );
+    if( p->aLookahead==0 ){
+      fprintf(stderr,"malloc failed\n");
+      exit(1);
+    }
+  }
+  if( p->nLookahead==0 ){
+    p->mxLookahead = lookahead;
+    p->mnLookahead = lookahead;
+    p->mnAction = action;
+  }else{
+    if( p->mxLookahead<lookahead ) p->mxLookahead = lookahead;
+    if( p->mnLookahead>lookahead ){
+      p->mnLookahead = lookahead;
+      p->mnAction = action;
+    }
+  }
+  p->aLookahead[p->nLookahead].lookahead = lookahead;
+  p->aLookahead[p->nLookahead].action = action;
+  p->nLookahead++;
+}
+
+/*
+** Add the transaction set built up with prior calls to acttab_action()
+** into the current action table.  Then reset the transaction set back
+** to an empty set in preparation for a new round of acttab_action() calls.
+**
+** Return the offset into the action table of the new transaction.
+*/
+int acttab_insert(acttab *p){
+  int i, j, k, n;
+  assert( p->nLookahead>0 );
+
+  /* Make sure we have enough space to hold the expanded action table
+  ** in the worst case.  The worst case occurs if the transaction set
+  ** must be appended to the current action table
+  */
+  n = p->mxLookahead + 1;
+  if( p->nAction + n >= p->nActionAlloc ){
+    int oldAlloc = p->nActionAlloc;
+    p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20;
+    p->aAction = realloc( p->aAction,
+                          sizeof(p->aAction[0])*p->nActionAlloc);
+    if( p->aAction==0 ){
+      fprintf(stderr,"malloc failed\n");
+      exit(1);
+    }
+    for(i=oldAlloc; i<p->nActionAlloc; i++){
+      p->aAction[i].lookahead = -1;
+      p->aAction[i].action = -1;
+    }
+  }
+
+  /* Scan the existing action table looking for an offset where we can
+  ** insert the current transaction set.  Fall out of the loop when that
+  ** offset is found.  In the worst case, we fall out of the loop when
+  ** i reaches p->nAction, which means we append the new transaction set.
+  **
+  ** i is the index in p->aAction[] where p->mnLookahead is inserted.
+  */
+  for(i=0; i<p->nAction+p->mnLookahead; i++){
+    if( p->aAction[i].lookahead<0 ){
+      for(j=0; j<p->nLookahead; j++){
+        k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+        if( k<0 ) break;
+        if( p->aAction[k].lookahead>=0 ) break;
+      }
+      if( j<p->nLookahead ) continue;
+      for(j=0; j<p->nAction; j++){
+        if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break;
+      }
+      if( j==p->nAction ){
+        break;  /* Fits in empty slots */
+      }
+    }else if( p->aAction[i].lookahead==p->mnLookahead ){
+      if( p->aAction[i].action!=p->mnAction ) continue;
+      for(j=0; j<p->nLookahead; j++){
+        k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+        if( k<0 || k>=p->nAction ) break;
+        if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break;
+        if( p->aLookahead[j].action!=p->aAction[k].action ) break;
+      }
+      if( j<p->nLookahead ) continue;
+      n = 0;
+      for(j=0; j<p->nAction; j++){
+        if( p->aAction[j].lookahead<0 ) continue;
+        if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++;
+      }
+      if( n==p->nLookahead ){
+        break;  /* Same as a prior transaction set */
+      }
+    }
+  }
+  /* Insert transaction set at index i. */
+  for(j=0; j<p->nLookahead; j++){
+    k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+    p->aAction[k] = p->aLookahead[j];
+    if( k>=p->nAction ) p->nAction = k+1;
+  }
+  p->nLookahead = 0;
+
+  /* Return the offset that is added to the lookahead in order to get the
+  ** index into yy_action of the action */
+  return i - p->mnLookahead;
+}
+
+/********************** From the file "build.c" *****************************/
+/*
+** Routines to construction the finite state machine for the LEMON
+** parser generator.
+*/
+
+/* Find a precedence symbol of every rule in the grammar.
+** 
+** Those rules which have a precedence symbol coded in the input
+** grammar using the "[symbol]" construct will already have the
+** rp->precsym field filled.  Other rules take as their precedence
+** symbol the first RHS symbol with a defined precedence.  If there
+** are not RHS symbols with a defined precedence, the precedence
+** symbol field is left blank.
+*/
+void FindRulePrecedences(xp)
+struct lemon *xp;
+{
+  struct rule *rp;
+  for(rp=xp->rule; rp; rp=rp->next){
+    if( rp->precsym==0 ){
+      int i, j;
+      for(i=0; i<rp->nrhs && rp->precsym==0; i++){
+        struct symbol *sp = rp->rhs[i];
+        if( sp->type==MULTITERMINAL ){
+          for(j=0; j<sp->nsubsym; j++){
+            if( sp->subsym[j]->prec>=0 ){
+              rp->precsym = sp->subsym[j];
+              break;
+            }
+          }
+        }else if( sp->prec>=0 ){
+          rp->precsym = rp->rhs[i];
+	}
+      }
+    }
+  }
+  return;
+}
+
+/* Find all nonterminals which will generate the empty string.
+** Then go back and compute the first sets of every nonterminal.
+** The first set is the set of all terminal symbols which can begin
+** a string generated by that nonterminal.
+*/
+void FindFirstSets(lemp)
+struct lemon *lemp;
+{
+  int i, j;
+  struct rule *rp;
+  int progress;
+
+  for(i=0; i<lemp->nsymbol; i++){
+    lemp->symbols[i]->lambda = LEMON_FALSE;
+  }
+  for(i=lemp->nterminal; i<lemp->nsymbol; i++){
+    lemp->symbols[i]->firstset = SetNew();
+  }
+
+  /* First compute all lambdas */
+  do{
+    progress = 0;
+    for(rp=lemp->rule; rp; rp=rp->next){
+      if( rp->lhs->lambda ) continue;
+      for(i=0; i<rp->nrhs; i++){
+         struct symbol *sp = rp->rhs[i];
+         if( sp->type!=TERMINAL || sp->lambda==LEMON_FALSE ) break;
+      }
+      if( i==rp->nrhs ){
+        rp->lhs->lambda = LEMON_TRUE;
+        progress = 1;
+      }
+    }
+  }while( progress );
+
+  /* Now compute all first sets */
+  do{
+    struct symbol *s1, *s2;
+    progress = 0;
+    for(rp=lemp->rule; rp; rp=rp->next){
+      s1 = rp->lhs;
+      for(i=0; i<rp->nrhs; i++){
+        s2 = rp->rhs[i];
+        if( s2->type==TERMINAL ){
+          progress += SetAdd(s1->firstset,s2->index);
+          break;
+        }else if( s2->type==MULTITERMINAL ){
+          for(j=0; j<s2->nsubsym; j++){
+            progress += SetAdd(s1->firstset,s2->subsym[j]->index);
+          }
+          break;
+	}else if( s1==s2 ){
+          if( s1->lambda==LEMON_FALSE ) break;
+	}else{
+          progress += SetUnion(s1->firstset,s2->firstset);
+          if( s2->lambda==LEMON_FALSE ) break;
+	}
+      }
+    }
+  }while( progress );
+  return;
+}
+
+/* Compute all LR(0) states for the grammar.  Links
+** are added to between some states so that the LR(1) follow sets
+** can be computed later.
+*/
+PRIVATE struct state *getstate(/* struct lemon * */);  /* forward reference */
+void FindStates(lemp)
+struct lemon *lemp;
+{
+  struct symbol *sp;
+  struct rule *rp;
+
+  Configlist_init();
+
+  /* Find the start symbol */
+  if( lemp->start ){
+    sp = Symbol_find(lemp->start);
+    if( sp==0 ){
+      ErrorMsg(lemp->filename,0,
+"The specified start symbol \"%s\" is not \
+in a nonterminal of the grammar.  \"%s\" will be used as the start \
+symbol instead.",lemp->start,lemp->rule->lhs->name);
+      lemp->errorcnt++;
+      sp = lemp->rule->lhs;
+    }
+  }else{
+    sp = lemp->rule->lhs;
+  }
+
+  /* Make sure the start symbol doesn't occur on the right-hand side of
+  ** any rule.  Report an error if it does.  (YACC would generate a new
+  ** start symbol in this case.) */
+  for(rp=lemp->rule; rp; rp=rp->next){
+    int i;
+    for(i=0; i<rp->nrhs; i++){
+      if( rp->rhs[i]==sp ){   /* FIX ME:  Deal with multiterminals */
+        ErrorMsg(lemp->filename,0,
+"The start symbol \"%s\" occurs on the \
+right-hand side of a rule. This will result in a parser which \
+does not work properly.",sp->name);
+        lemp->errorcnt++;
+      }
+    }
+  }
+
+  /* The basis configuration set for the first state
+  ** is all rules which have the start symbol as their
+  ** left-hand side */
+  for(rp=sp->rule; rp; rp=rp->nextlhs){
+    struct config *newcfp;
+    rp->lhsStart = 1;
+    newcfp = Configlist_addbasis(rp,0);
+    SetAdd(newcfp->fws,0);
+  }
+
+  /* Compute the first state.  All other states will be
+  ** computed automatically during the computation of the first one.
+  ** The returned pointer to the first state is not used. */
+  (void)getstate(lemp);
+  return;
+}
+
+/* Return a pointer to a state which is described by the configuration
+** list which has been built from calls to Configlist_add.
+*/
+PRIVATE void buildshifts(/* struct lemon *, struct state * */); /* Forwd ref */
+PRIVATE struct state *getstate(lemp)
+struct lemon *lemp;
+{
+  struct config *cfp, *bp;
+  struct state *stp;
+
+  /* Extract the sorted basis of the new state.  The basis was constructed
+  ** by prior calls to "Configlist_addbasis()". */
+  Configlist_sortbasis();
+  bp = Configlist_basis();
+
+  /* Get a state with the same basis */
+  stp = State_find(bp);
+  if( stp ){
+    /* A state with the same basis already exists!  Copy all the follow-set
+    ** propagation links from the state under construction into the
+    ** preexisting state, then return a pointer to the preexisting state */
+    struct config *x, *y;
+    for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){
+      Plink_copy(&y->bplp,x->bplp);
+      Plink_delete(x->fplp);
+      x->fplp = x->bplp = 0;
+    }
+    cfp = Configlist_return();
+    Configlist_eat(cfp);
+  }else{
+    /* This really is a new state.  Construct all the details */
+    Configlist_closure(lemp);    /* Compute the configuration closure */
+    Configlist_sort();           /* Sort the configuration closure */
+    cfp = Configlist_return();   /* Get a pointer to the config list */
+    stp = State_new();           /* A new state structure */
+    MemoryCheck(stp);
+    stp->bp = bp;                /* Remember the configuration basis */
+    stp->cfp = cfp;              /* Remember the configuration closure */
+    stp->statenum = lemp->nstate++; /* Every state gets a sequence number */
+    stp->ap = 0;                 /* No actions, yet. */
+    State_insert(stp,stp->bp);   /* Add to the state table */
+    buildshifts(lemp,stp);       /* Recursively compute successor states */
+  }
+  return stp;
+}
+
+/*
+** Return true if two symbols are the same.
+*/
+int same_symbol(a,b)
+struct symbol *a;
+struct symbol *b;
+{
+  int i;
+  if( a==b ) return 1;
+  if( a->type!=MULTITERMINAL ) return 0;
+  if( b->type!=MULTITERMINAL ) return 0;
+  if( a->nsubsym!=b->nsubsym ) return 0;
+  for(i=0; i<a->nsubsym; i++){
+    if( a->subsym[i]!=b->subsym[i] ) return 0;
+  }
+  return 1;
+}
+
+/* Construct all successor states to the given state.  A "successor"
+** state is any state which can be reached by a shift action.
+*/
+PRIVATE void buildshifts(lemp,stp)
+struct lemon *lemp;
+struct state *stp;     /* The state from which successors are computed */
+{
+  struct config *cfp;  /* For looping thru the config closure of "stp" */
+  struct config *bcfp; /* For the inner loop on config closure of "stp" */
+  struct config *new;  /* */
+  struct symbol *sp;   /* Symbol following the dot in configuration "cfp" */
+  struct symbol *bsp;  /* Symbol following the dot in configuration "bcfp" */
+  struct state *newstp; /* A pointer to a successor state */
+
+  /* Each configuration becomes complete after it contibutes to a successor
+  ** state.  Initially, all configurations are incomplete */
+  for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE;
+
+  /* Loop through all configurations of the state "stp" */
+  for(cfp=stp->cfp; cfp; cfp=cfp->next){
+    if( cfp->status==COMPLETE ) continue;    /* Already used by inner loop */
+    if( cfp->dot>=cfp->rp->nrhs ) continue;  /* Can't shift this config */
+    Configlist_reset();                      /* Reset the new config set */
+    sp = cfp->rp->rhs[cfp->dot];             /* Symbol after the dot */
+
+    /* For every configuration in the state "stp" which has the symbol "sp"
+    ** following its dot, add the same configuration to the basis set under
+    ** construction but with the dot shifted one symbol to the right. */
+    for(bcfp=cfp; bcfp; bcfp=bcfp->next){
+      if( bcfp->status==COMPLETE ) continue;    /* Already used */
+      if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */
+      bsp = bcfp->rp->rhs[bcfp->dot];           /* Get symbol after dot */
+      if( !same_symbol(bsp,sp) ) continue;      /* Must be same as for "cfp" */
+      bcfp->status = COMPLETE;                  /* Mark this config as used */
+      new = Configlist_addbasis(bcfp->rp,bcfp->dot+1);
+      Plink_add(&new->bplp,bcfp);
+    }
+
+    /* Get a pointer to the state described by the basis configuration set
+    ** constructed in the preceding loop */
+    newstp = getstate(lemp);
+
+    /* The state "newstp" is reached from the state "stp" by a shift action
+    ** on the symbol "sp" */
+    if( sp->type==MULTITERMINAL ){
+      int i;
+      for(i=0; i<sp->nsubsym; i++){
+        Action_add(&stp->ap,SHIFT,sp->subsym[i],(char*)newstp);
+      }
+    }else{
+      Action_add(&stp->ap,SHIFT,sp,(char *)newstp);
+    }
+  }
+}
+
+/*
+** Construct the propagation links
+*/
+void FindLinks(lemp)
+struct lemon *lemp;
+{
+  int i;
+  struct config *cfp, *other;
+  struct state *stp;
+  struct plink *plp;
+
+  /* Housekeeping detail:
+  ** Add to every propagate link a pointer back to the state to
+  ** which the link is attached. */
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    for(cfp=stp->cfp; cfp; cfp=cfp->next){
+      cfp->stp = stp;
+    }
+  }
+
+  /* Convert all backlinks into forward links.  Only the forward
+  ** links are used in the follow-set computation. */
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    for(cfp=stp->cfp; cfp; cfp=cfp->next){
+      for(plp=cfp->bplp; plp; plp=plp->next){
+        other = plp->cfp;
+        Plink_add(&other->fplp,cfp);
+      }
+    }
+  }
+}
+
+/* Compute all followsets.
+**
+** A followset is the set of all symbols which can come immediately
+** after a configuration.
+*/
+void FindFollowSets(lemp)
+struct lemon *lemp;
+{
+  int i;
+  struct config *cfp;
+  struct plink *plp;
+  int progress;
+  int change;
+
+  for(i=0; i<lemp->nstate; i++){
+    for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+      cfp->status = INCOMPLETE;
+    }
+  }
+  
+  do{
+    progress = 0;
+    for(i=0; i<lemp->nstate; i++){
+      for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+        if( cfp->status==COMPLETE ) continue;
+        for(plp=cfp->fplp; plp; plp=plp->next){
+          change = SetUnion(plp->cfp->fws,cfp->fws);
+          if( change ){
+            plp->cfp->status = INCOMPLETE;
+            progress = 1;
+	  }
+	}
+        cfp->status = COMPLETE;
+      }
+    }
+  }while( progress );
+}
+
+static int resolve_conflict();
+
+/* Compute the reduce actions, and resolve conflicts.
+*/
+void FindActions(lemp)
+struct lemon *lemp;
+{
+  int i,j;
+  struct config *cfp;
+  struct state *stp;
+  struct symbol *sp;
+  struct rule *rp;
+
+  /* Add all of the reduce actions 
+  ** A reduce action is added for each element of the followset of
+  ** a configuration which has its dot at the extreme right.
+  */
+  for(i=0; i<lemp->nstate; i++){   /* Loop over all states */
+    stp = lemp->sorted[i];
+    for(cfp=stp->cfp; cfp; cfp=cfp->next){  /* Loop over all configurations */
+      if( cfp->rp->nrhs==cfp->dot ){        /* Is dot at extreme right? */
+        for(j=0; j<lemp->nterminal; j++){
+          if( SetFind(cfp->fws,j) ){
+            /* Add a reduce action to the state "stp" which will reduce by the
+            ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */
+            Action_add(&stp->ap,REDUCE,lemp->symbols[j],(char *)cfp->rp);
+          }
+	}
+      }
+    }
+  }
+
+  /* Add the accepting token */
+  if( lemp->start ){
+    sp = Symbol_find(lemp->start);
+    if( sp==0 ) sp = lemp->rule->lhs;
+  }else{
+    sp = lemp->rule->lhs;
+  }
+  /* Add to the first state (which is always the starting state of the
+  ** finite state machine) an action to ACCEPT if the lookahead is the
+  ** start nonterminal.  */
+  Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0);
+
+  /* Resolve conflicts */
+  for(i=0; i<lemp->nstate; i++){
+    struct action *ap, *nap;
+    struct state *stp;
+    stp = lemp->sorted[i];
+    /* assert( stp->ap ); */
+    stp->ap = Action_sort(stp->ap);
+    for(ap=stp->ap; ap && ap->next; ap=ap->next){
+      for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){
+         /* The two actions "ap" and "nap" have the same lookahead.
+         ** Figure out which one should be used */
+         lemp->nconflict += resolve_conflict(ap,nap,lemp->errsym);
+      }
+    }
+  }
+
+  /* Report an error for each rule that can never be reduced. */
+  for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = LEMON_FALSE;
+  for(i=0; i<lemp->nstate; i++){
+    struct action *ap;
+    for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){
+      if( ap->type==REDUCE ) ap->x.rp->canReduce = LEMON_TRUE;
+    }
+  }
+  for(rp=lemp->rule; rp; rp=rp->next){
+    if( rp->canReduce ) continue;
+    ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n");
+    lemp->errorcnt++;
+  }
+}
+
+/* Resolve a conflict between the two given actions.  If the
+** conflict can't be resolved, return non-zero.
+**
+** NO LONGER TRUE:
+**   To resolve a conflict, first look to see if either action
+**   is on an error rule.  In that case, take the action which
+**   is not associated with the error rule.  If neither or both
+**   actions are associated with an error rule, then try to
+**   use precedence to resolve the conflict.
+**
+** If either action is a SHIFT, then it must be apx.  This
+** function won't work if apx->type==REDUCE and apy->type==SHIFT.
+*/
+static int resolve_conflict(apx,apy,errsym)
+struct action *apx;
+struct action *apy;
+struct symbol *errsym;   /* The error symbol (if defined.  NULL otherwise) */
+{
+  struct symbol *spx, *spy;
+  int errcnt = 0;
+  assert( apx->sp==apy->sp );  /* Otherwise there would be no conflict */
+  if( apx->type==SHIFT && apy->type==SHIFT ){
+    apy->type = SSCONFLICT;
+    errcnt++;
+  }
+  if( apx->type==SHIFT && apy->type==REDUCE ){
+    spx = apx->sp;
+    spy = apy->x.rp->precsym;
+    if( spy==0 || spx->prec<0 || spy->prec<0 ){
+      /* Not enough precedence information. */
+      apy->type = SRCONFLICT;
+      errcnt++;
+    }else if( spx->prec>spy->prec ){    /* Lower precedence wins */
+      apy->type = RD_RESOLVED;
+    }else if( spx->prec<spy->prec ){
+      apx->type = SH_RESOLVED;
+    }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */
+      apy->type = RD_RESOLVED;                             /* associativity */
+    }else if( spx->prec==spy->prec && spx->assoc==LEFT ){  /* to break tie */
+      apx->type = SH_RESOLVED;
+    }else{
+      assert( spx->prec==spy->prec && spx->assoc==NONE );
+      apy->type = SRCONFLICT;
+      errcnt++;
+    }
+  }else if( apx->type==REDUCE && apy->type==REDUCE ){
+    spx = apx->x.rp->precsym;
+    spy = apy->x.rp->precsym;
+    if( spx==0 || spy==0 || spx->prec<0 ||
+    spy->prec<0 || spx->prec==spy->prec ){
+      apy->type = RRCONFLICT;
+      errcnt++;
+    }else if( spx->prec>spy->prec ){
+      apy->type = RD_RESOLVED;
+    }else if( spx->prec<spy->prec ){
+      apx->type = RD_RESOLVED;
+    }
+  }else{
+    assert( 
+      apx->type==SH_RESOLVED ||
+      apx->type==RD_RESOLVED ||
+      apx->type==SSCONFLICT ||
+      apx->type==SRCONFLICT ||
+      apx->type==RRCONFLICT ||
+      apy->type==SH_RESOLVED ||
+      apy->type==RD_RESOLVED ||
+      apy->type==SSCONFLICT ||
+      apy->type==SRCONFLICT ||
+      apy->type==RRCONFLICT
+    );
+    /* The REDUCE/SHIFT case cannot happen because SHIFTs come before
+    ** REDUCEs on the list.  If we reach this point it must be because
+    ** the parser conflict had already been resolved. */
+  }
+  return errcnt;
+}
+/********************* From the file "configlist.c" *************************/
+/*
+** Routines to processing a configuration list and building a state
+** in the LEMON parser generator.
+*/
+
+static struct config *freelist = 0;      /* List of free configurations */
+static struct config *current = 0;       /* Top of list of configurations */
+static struct config **currentend = 0;   /* Last on list of configs */
+static struct config *basis = 0;         /* Top of list of basis configs */
+static struct config **basisend = 0;     /* End of list of basis configs */
+
+/* Return a pointer to a new configuration */
+PRIVATE struct config *newconfig(){
+  struct config *new;
+  if( freelist==0 ){
+    int i;
+    int amt = 3;
+    freelist = (struct config *)calloc( amt, sizeof(struct config) );
+    if( freelist==0 ){
+      fprintf(stderr,"Unable to allocate memory for a new configuration.");
+      exit(1);
+    }
+    for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+    freelist[amt-1].next = 0;
+  }
+  new = freelist;
+  freelist = freelist->next;
+  return new;
+}
+
+/* The configuration "old" is no longer used */
+PRIVATE void deleteconfig(old)
+struct config *old;
+{
+  old->next = freelist;
+  freelist = old;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_init(){
+  current = 0;
+  currentend = &current;
+  basis = 0;
+  basisend = &basis;
+  Configtable_init();
+  return;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_reset(){
+  current = 0;
+  currentend = &current;
+  basis = 0;
+  basisend = &basis;
+  Configtable_clear(0);
+  return;
+}
+
+/* Add another configuration to the configuration list */
+struct config *Configlist_add(rp,dot)
+struct rule *rp;    /* The rule */
+int dot;            /* Index into the RHS of the rule where the dot goes */
+{
+  struct config *cfp, model;
+
+  assert( currentend!=0 );
+  model.rp = rp;
+  model.dot = dot;
+  cfp = Configtable_find(&model);
+  if( cfp==0 ){
+    cfp = newconfig();
+    cfp->rp = rp;
+    cfp->dot = dot;
+    cfp->fws = SetNew();
+    cfp->stp = 0;
+    cfp->fplp = cfp->bplp = 0;
+    cfp->next = 0;
+    cfp->bp = 0;
+    *currentend = cfp;
+    currentend = &cfp->next;
+    Configtable_insert(cfp);
+  }
+  return cfp;
+}
+
+/* Add a basis configuration to the configuration list */
+struct config *Configlist_addbasis(rp,dot)
+struct rule *rp;
+int dot;
+{
+  struct config *cfp, model;
+
+  assert( basisend!=0 );
+  assert( currentend!=0 );
+  model.rp = rp;
+  model.dot = dot;
+  cfp = Configtable_find(&model);
+  if( cfp==0 ){
+    cfp = newconfig();
+    cfp->rp = rp;
+    cfp->dot = dot;
+    cfp->fws = SetNew();
+    cfp->stp = 0;
+    cfp->fplp = cfp->bplp = 0;
+    cfp->next = 0;
+    cfp->bp = 0;
+    *currentend = cfp;
+    currentend = &cfp->next;
+    *basisend = cfp;
+    basisend = &cfp->bp;
+    Configtable_insert(cfp);
+  }
+  return cfp;
+}
+
+/* Compute the closure of the configuration list */
+void Configlist_closure(lemp)
+struct lemon *lemp;
+{
+  struct config *cfp, *newcfp;
+  struct rule *rp, *newrp;
+  struct symbol *sp, *xsp;
+  int i, dot;
+
+  assert( currentend!=0 );
+  for(cfp=current; cfp; cfp=cfp->next){
+    rp = cfp->rp;
+    dot = cfp->dot;
+    if( dot>=rp->nrhs ) continue;
+    sp = rp->rhs[dot];
+    if( sp->type==NONTERMINAL ){
+      if( sp->rule==0 && sp!=lemp->errsym ){
+        ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.",
+          sp->name);
+        lemp->errorcnt++;
+      }
+      for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){
+        newcfp = Configlist_add(newrp,0);
+        for(i=dot+1; i<rp->nrhs; i++){
+          xsp = rp->rhs[i];
+          if( xsp->type==TERMINAL ){
+            SetAdd(newcfp->fws,xsp->index);
+            break;
+          }else if( xsp->type==MULTITERMINAL ){
+            int k;
+            for(k=0; k<xsp->nsubsym; k++){
+              SetAdd(newcfp->fws, xsp->subsym[k]->index);
+            }
+            break;
+	  }else{
+            SetUnion(newcfp->fws,xsp->firstset);
+            if( xsp->lambda==LEMON_FALSE ) break;
+	  }
+	}
+        if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp);
+      }
+    }
+  }
+  return;
+}
+
+/* Sort the configuration list */
+void Configlist_sort(){
+  current = (struct config *)msort((char *)current,(char **)&(current->next),Configcmp);
+  currentend = 0;
+  return;
+}
+
+/* Sort the basis configuration list */
+void Configlist_sortbasis(){
+  basis = (struct config *)msort((char *)current,(char **)&(current->bp),Configcmp);
+  basisend = 0;
+  return;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_return(){
+  struct config *old;
+  old = current;
+  current = 0;
+  currentend = 0;
+  return old;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_basis(){
+  struct config *old;
+  old = basis;
+  basis = 0;
+  basisend = 0;
+  return old;
+}
+
+/* Free all elements of the given configuration list */
+void Configlist_eat(cfp)
+struct config *cfp;
+{
+  struct config *nextcfp;
+  for(; cfp; cfp=nextcfp){
+    nextcfp = cfp->next;
+    assert( cfp->fplp==0 );
+    assert( cfp->bplp==0 );
+    if( cfp->fws ) SetFree(cfp->fws);
+    deleteconfig(cfp);
+  }
+  return;
+}
+/***************** From the file "error.c" *********************************/
+/*
+** Code for printing error message.
+*/
+
+/* Find a good place to break "msg" so that its length is at least "min"
+** but no more than "max".  Make the point as close to max as possible.
+*/
+static int findbreak(msg,min,max)
+char *msg;
+int min;
+int max;
+{
+  int i,spot;
+  char c;
+  for(i=spot=min; i<=max; i++){
+    c = msg[i];
+    if( c=='\t' ) msg[i] = ' ';
+    if( c=='\n' ){ msg[i] = ' '; spot = i; break; }
+    if( c==0 ){ spot = i; break; }
+    if( c=='-' && i<max-1 ) spot = i+1;
+    if( c==' ' ) spot = i;
+  }
+  return spot;
+}
+
+/*
+** The error message is split across multiple lines if necessary.  The
+** splits occur at a space, if there is a space available near the end
+** of the line.
+*/
+#define ERRMSGSIZE  10000 /* Hope this is big enough.  No way to error check */
+#define LINEWIDTH      79 /* Max width of any output line */
+#define PREFIXLIMIT    30 /* Max width of the prefix on each line */
+void ErrorMsg(const char *filename, int lineno, const char *format, ...){
+  char errmsg[ERRMSGSIZE];
+  char prefix[PREFIXLIMIT+10];
+  int errmsgsize;
+  int prefixsize;
+  int availablewidth;
+  va_list ap;
+  int end, restart, base;
+
+  va_start(ap, format);
+  /* Prepare a prefix to be prepended to every output line */
+  if( lineno>0 ){
+    sprintf(prefix,"%.*s:%d: ",PREFIXLIMIT-10,filename,lineno);
+  }else{
+    sprintf(prefix,"%.*s: ",PREFIXLIMIT-10,filename);
+  }
+  prefixsize = lemonStrlen(prefix);
+  availablewidth = LINEWIDTH - prefixsize;
+
+  /* Generate the error message */
+  vsprintf(errmsg,format,ap);
+  va_end(ap);
+  errmsgsize = lemonStrlen(errmsg);
+  /* Remove trailing '\n's from the error message. */
+  while( errmsgsize>0 && errmsg[errmsgsize-1]=='\n' ){
+     errmsg[--errmsgsize] = 0;
+  }
+
+  /* Print the error message */
+  base = 0;
+  while( errmsg[base]!=0 ){
+    end = restart = findbreak(&errmsg[base],0,availablewidth);
+    restart += base;
+    while( errmsg[restart]==' ' ) restart++;
+    fprintf(stdout,"%s%.*s\n",prefix,end,&errmsg[base]);
+    base = restart;
+  }
+}
+/**************** From the file "main.c" ************************************/
+/*
+** Main program file for the LEMON parser generator.
+*/
+
+/* Report an out-of-memory condition and abort.  This function
+** is used mostly by the "MemoryCheck" macro in struct.h
+*/
+void memory_error(){
+  fprintf(stderr,"Out of memory.  Aborting...\n");
+  exit(1);
+}
+
+static int nDefine = 0;      /* Number of -D options on the command line */
+static char **azDefine = 0;  /* Name of the -D macros */
+
+/* This routine is called with the argument to each -D command-line option.
+** Add the macro defined to the azDefine array.
+*/
+static void handle_D_option(char *z){
+  char **paz;
+  nDefine++;
+  azDefine = realloc(azDefine, sizeof(azDefine[0])*nDefine);
+  if( azDefine==0 ){
+    fprintf(stderr,"out of memory\n");
+    exit(1);
+  }
+  paz = &azDefine[nDefine-1];
+  *paz = malloc( lemonStrlen(z)+1 );
+  if( *paz==0 ){
+    fprintf(stderr,"out of memory\n");
+    exit(1);
+  }
+  strcpy(*paz, z);
+  for(z=*paz; *z && *z!='='; z++){}
+  *z = 0;
+}
+
+
+/* The main program.  Parse the command line and do it... */
+int main(argc,argv)
+int argc;
+char **argv;
+{
+  static int version = 0;
+  static int rpflag = 0;
+  static int basisflag = 0;
+  static int compress = 0;
+  static int quiet = 0;
+  static int statistics = 0;
+  static int mhflag = 0;
+  static int nolinenosflag = 0;
+  static struct s_options options[] = {
+    {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."},
+    {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."},
+    {OPT_FSTR, "D", (char*)handle_D_option, "Define an %ifdef macro."},
+    {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."},
+    {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file."},
+    {OPT_FLAG, "l", (char*)&nolinenosflag, "Do not print #line statements."},
+    {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."},
+    {OPT_FLAG, "s", (char*)&statistics,
+                                   "Print parser stats to standard output."},
+    {OPT_FLAG, "x", (char*)&version, "Print the version number."},
+    {OPT_FLAG,0,0,0}
+  };
+  int i;
+  struct lemon lem;
+
+  OptInit(argv,options,stderr);
+  if( version ){
+     printf("Lemon version 1.0\n");
+     exit(0); 
+  }
+  if( OptNArgs()!=1 ){
+    fprintf(stderr,"Exactly one filename argument is required.\n");
+    exit(1);
+  }
+  memset(&lem, 0, sizeof(lem));
+  lem.errorcnt = 0;
+
+  /* Initialize the machine */
+  Strsafe_init();
+  Symbol_init();
+  State_init();
+  lem.argv0 = argv[0];
+  lem.filename = OptArg(0);
+  lem.basisflag = basisflag;
+  lem.nolinenosflag = nolinenosflag;
+  Symbol_new("$");
+  lem.errsym = Symbol_new("error");
+  lem.errsym->useCnt = 0;
+
+  /* Parse the input file */
+  Parse(&lem);
+  if( lem.errorcnt ) exit(lem.errorcnt);
+  if( lem.nrule==0 ){
+    fprintf(stderr,"Empty grammar.\n");
+    exit(1);
+  }
+
+  /* Count and index the symbols of the grammar */
+  lem.nsymbol = Symbol_count();
+  Symbol_new("{default}");
+  lem.symbols = Symbol_arrayof();
+  for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i;
+  qsort(lem.symbols,lem.nsymbol+1,sizeof(struct symbol*),
+        (int(*)())Symbolcmpp);
+  for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i;
+  for(i=1; isupper(lem.symbols[i]->name[0]); i++);
+  lem.nterminal = i;
+
+  /* Generate a reprint of the grammar, if requested on the command line */
+  if( rpflag ){
+    Reprint(&lem);
+  }else{
+    /* Initialize the size for all follow and first sets */
+    SetSize(lem.nterminal+1);
+
+    /* Find the precedence for every production rule (that has one) */
+    FindRulePrecedences(&lem);
+
+    /* Compute the lambda-nonterminals and the first-sets for every
+    ** nonterminal */
+    FindFirstSets(&lem);
+
+    /* Compute all LR(0) states.  Also record follow-set propagation
+    ** links so that the follow-set can be computed later */
+    lem.nstate = 0;
+    FindStates(&lem);
+    lem.sorted = State_arrayof();
+
+    /* Tie up loose ends on the propagation links */
+    FindLinks(&lem);
+
+    /* Compute the follow set of every reducible configuration */
+    FindFollowSets(&lem);
+
+    /* Compute the action tables */
+    FindActions(&lem);
+
+    /* Compress the action tables */
+    if( compress==0 ) CompressTables(&lem);
+
+    /* Reorder and renumber the states so that states with fewer choices
+    ** occur at the end. */
+    ResortStates(&lem);
+
+    /* Generate a report of the parser generated.  (the "y.output" file) */
+    if( !quiet ) ReportOutput(&lem);
+
+    /* Generate the source code for the parser */
+    ReportTable(&lem, mhflag);
+
+    /* Produce a header file for use by the scanner.  (This step is
+    ** omitted if the "-m" option is used because makeheaders will
+    ** generate the file for us.) */
+    if( !mhflag ) ReportHeader(&lem);
+  }
+  if( statistics ){
+    printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n",
+      lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule);
+    printf("                   %d states, %d parser table entries, %d conflicts\n",
+      lem.nstate, lem.tablesize, lem.nconflict);
+  }
+  if( lem.nconflict ){
+    fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict);
+  }
+  exit(lem.errorcnt + lem.nconflict);
+  return (lem.errorcnt + lem.nconflict);
+}
+/******************** From the file "msort.c" *******************************/
+/*
+** A generic merge-sort program.
+**
+** USAGE:
+** Let "ptr" be a pointer to some structure which is at the head of
+** a null-terminated list.  Then to sort the list call:
+**
+**     ptr = msort(ptr,&(ptr->next),cmpfnc);
+**
+** In the above, "cmpfnc" is a pointer to a function which compares
+** two instances of the structure and returns an integer, as in
+** strcmp.  The second argument is a pointer to the pointer to the
+** second element of the linked list.  This address is used to compute
+** the offset to the "next" field within the structure.  The offset to
+** the "next" field must be constant for all structures in the list.
+**
+** The function returns a new pointer which is the head of the list
+** after sorting.
+**
+** ALGORITHM:
+** Merge-sort.
+*/
+
+/*
+** Return a pointer to the next structure in the linked list.
+*/
+#define NEXT(A) (*(char**)(((unsigned long)A)+offset))
+
+/*
+** Inputs:
+**   a:       A sorted, null-terminated linked list.  (May be null).
+**   b:       A sorted, null-terminated linked list.  (May be null).
+**   cmp:     A pointer to the comparison function.
+**   offset:  Offset in the structure to the "next" field.
+**
+** Return Value:
+**   A pointer to the head of a sorted list containing the elements
+**   of both a and b.
+**
+** Side effects:
+**   The "next" pointers for elements in the lists a and b are
+**   changed.
+*/
+static char *merge(
+  char *a,
+  char *b,
+  int (*cmp)(const char*,const char*),
+  int offset
+){
+  char *ptr, *head;
+
+  if( a==0 ){
+    head = b;
+  }else if( b==0 ){
+    head = a;
+  }else{
+    if( (*cmp)(a,b)<0 ){
+      ptr = a;
+      a = NEXT(a);
+    }else{
+      ptr = b;
+      b = NEXT(b);
+    }
+    head = ptr;
+    while( a && b ){
+      if( (*cmp)(a,b)<0 ){
+        NEXT(ptr) = a;
+        ptr = a;
+        a = NEXT(a);
+      }else{
+        NEXT(ptr) = b;
+        ptr = b;
+        b = NEXT(b);
+      }
+    }
+    if( a ) NEXT(ptr) = a;
+    else    NEXT(ptr) = b;
+  }
+  return head;
+}
+
+/*
+** Inputs:
+**   list:      Pointer to a singly-linked list of structures.
+**   next:      Pointer to pointer to the second element of the list.
+**   cmp:       A comparison function.
+**
+** Return Value:
+**   A pointer to the head of a sorted list containing the elements
+**   orginally in list.
+**
+** Side effects:
+**   The "next" pointers for elements in list are changed.
+*/
+#define LISTSIZE 30
+static char *msort(
+  char *list,
+  char **next,
+  int (*cmp)(const char*,const char*)
+){
+  unsigned long offset;
+  char *ep;
+  char *set[LISTSIZE];
+  int i;
+  offset = (unsigned long)next - (unsigned long)list;
+  for(i=0; i<LISTSIZE; i++) set[i] = 0;
+  while( list ){
+    ep = list;
+    list = NEXT(list);
+    NEXT(ep) = 0;
+    for(i=0; i<LISTSIZE-1 && set[i]!=0; i++){
+      ep = merge(ep,set[i],cmp,offset);
+      set[i] = 0;
+    }
+    set[i] = ep;
+  }
+  ep = 0;
+  for(i=0; i<LISTSIZE; i++) if( set[i] ) ep = merge(ep,set[i],cmp,offset);
+  return ep;
+}
+/************************ From the file "option.c" **************************/
+static char **argv;
+static struct s_options *op;
+static FILE *errstream;
+
+#define ISOPT(X) ((X)[0]=='-'||(X)[0]=='+'||strchr((X),'=')!=0)
+
+/*
+** Print the command line with a carrot pointing to the k-th character
+** of the n-th field.
+*/
+static void errline(n,k,err)
+int n;
+int k;
+FILE *err;
+{
+  int spcnt, i;
+  if( argv[0] ) fprintf(err,"%s",argv[0]);
+  spcnt = lemonStrlen(argv[0]) + 1;
+  for(i=1; i<n && argv[i]; i++){
+    fprintf(err," %s",argv[i]);
+    spcnt += lemonStrlen(argv[i])+1;
+  }
+  spcnt += k;
+  for(; argv[i]; i++) fprintf(err," %s",argv[i]);
+  if( spcnt<20 ){
+    fprintf(err,"\n%*s^-- here\n",spcnt,"");
+  }else{
+    fprintf(err,"\n%*shere --^\n",spcnt-7,"");
+  }
+}
+
+/*
+** Return the index of the N-th non-switch argument.  Return -1
+** if N is out of range.
+*/
+static int argindex(n)
+int n;
+{
+  int i;
+  int dashdash = 0;
+  if( argv!=0 && *argv!=0 ){
+    for(i=1; argv[i]; i++){
+      if( dashdash || !ISOPT(argv[i]) ){
+        if( n==0 ) return i;
+        n--;
+      }
+      if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+    }
+  }
+  return -1;
+}
+
+static char emsg[] = "Command line syntax error: ";
+
+/*
+** Process a flag command line argument.
+*/
+static int handleflags(i,err)
+int i;
+FILE *err;
+{
+  int v;
+  int errcnt = 0;
+  int j;
+  for(j=0; op[j].label; j++){
+    if( strncmp(&argv[i][1],op[j].label,lemonStrlen(op[j].label))==0 ) break;
+  }
+  v = argv[i][0]=='-' ? 1 : 0;
+  if( op[j].label==0 ){
+    if( err ){
+      fprintf(err,"%sundefined option.\n",emsg);
+      errline(i,1,err);
+    }
+    errcnt++;
+  }else if( op[j].type==OPT_FLAG ){
+    *((int*)op[j].arg) = v;
+  }else if( op[j].type==OPT_FFLAG ){
+    (*(void(*)())(op[j].arg))(v);
+  }else if( op[j].type==OPT_FSTR ){
+    (*(void(*)())(op[j].arg))(&argv[i][2]);
+  }else{
+    if( err ){
+      fprintf(err,"%smissing argument on switch.\n",emsg);
+      errline(i,1,err);
+    }
+    errcnt++;
+  }
+  return errcnt;
+}
+
+/*
+** Process a command line switch which has an argument.
+*/
+static int handleswitch(i,err)
+int i;
+FILE *err;
+{
+  int lv = 0;
+  double dv = 0.0;
+  char *sv = 0, *end;
+  char *cp;
+  int j;
+  int errcnt = 0;
+  cp = strchr(argv[i],'=');
+  assert( cp!=0 );
+  *cp = 0;
+  for(j=0; op[j].label; j++){
+    if( strcmp(argv[i],op[j].label)==0 ) break;
+  }
+  *cp = '=';
+  if( op[j].label==0 ){
+    if( err ){
+      fprintf(err,"%sundefined option.\n",emsg);
+      errline(i,0,err);
+    }
+    errcnt++;
+  }else{
+    cp++;
+    switch( op[j].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        if( err ){
+          fprintf(err,"%soption requires an argument.\n",emsg);
+          errline(i,0,err);
+        }
+        errcnt++;
+        break;
+      case OPT_DBL:
+      case OPT_FDBL:
+        dv = strtod(cp,&end);
+        if( *end ){
+          if( err ){
+            fprintf(err,"%sillegal character in floating-point argument.\n",emsg);
+            errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+          }
+          errcnt++;
+        }
+        break;
+      case OPT_INT:
+      case OPT_FINT:
+        lv = strtol(cp,&end,0);
+        if( *end ){
+          if( err ){
+            fprintf(err,"%sillegal character in integer argument.\n",emsg);
+            errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+          }
+          errcnt++;
+        }
+        break;
+      case OPT_STR:
+      case OPT_FSTR:
+        sv = cp;
+        break;
+    }
+    switch( op[j].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        break;
+      case OPT_DBL:
+        *(double*)(op[j].arg) = dv;
+        break;
+      case OPT_FDBL:
+        (*(void(*)())(op[j].arg))(dv);
+        break;
+      case OPT_INT:
+        *(int*)(op[j].arg) = lv;
+        break;
+      case OPT_FINT:
+        (*(void(*)())(op[j].arg))((int)lv);
+        break;
+      case OPT_STR:
+        *(char**)(op[j].arg) = sv;
+        break;
+      case OPT_FSTR:
+        (*(void(*)())(op[j].arg))(sv);
+        break;
+    }
+  }
+  return errcnt;
+}
+
+int OptInit(a,o,err)
+char **a;
+struct s_options *o;
+FILE *err;
+{
+  int errcnt = 0;
+  argv = a;
+  op = o;
+  errstream = err;
+  if( argv && *argv && op ){
+    int i;
+    for(i=1; argv[i]; i++){
+      if( argv[i][0]=='+' || argv[i][0]=='-' ){
+        errcnt += handleflags(i,err);
+      }else if( strchr(argv[i],'=') ){
+        errcnt += handleswitch(i,err);
+      }
+    }
+  }
+  if( errcnt>0 ){
+    fprintf(err,"Valid command line options for \"%s\" are:\n",*a);
+    OptPrint();
+    exit(1);
+  }
+  return 0;
+}
+
+int OptNArgs(){
+  int cnt = 0;
+  int dashdash = 0;
+  int i;
+  if( argv!=0 && argv[0]!=0 ){
+    for(i=1; argv[i]; i++){
+      if( dashdash || !ISOPT(argv[i]) ) cnt++;
+      if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+    }
+  }
+  return cnt;
+}
+
+char *OptArg(n)
+int n;
+{
+  int i;
+  i = argindex(n);
+  return i>=0 ? argv[i] : 0;
+}
+
+void OptErr(n)
+int n;
+{
+  int i;
+  i = argindex(n);
+  if( i>=0 ) errline(i,0,errstream);
+}
+
+void OptPrint(){
+  int i;
+  int max, len;
+  max = 0;
+  for(i=0; op[i].label; i++){
+    len = lemonStrlen(op[i].label) + 1;
+    switch( op[i].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        break;
+      case OPT_INT:
+      case OPT_FINT:
+        len += 9;       /* length of "<integer>" */
+        break;
+      case OPT_DBL:
+      case OPT_FDBL:
+        len += 6;       /* length of "<real>" */
+        break;
+      case OPT_STR:
+      case OPT_FSTR:
+        len += 8;       /* length of "<string>" */
+        break;
+    }
+    if( len>max ) max = len;
+  }
+  for(i=0; op[i].label; i++){
+    switch( op[i].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        fprintf(errstream,"  -%-*s  %s\n",max,op[i].label,op[i].message);
+        break;
+      case OPT_INT:
+      case OPT_FINT:
+        fprintf(errstream,"  %s=<integer>%*s  %s\n",op[i].label,
+          (int)(max-lemonStrlen(op[i].label)-9),"",op[i].message);
+        break;
+      case OPT_DBL:
+      case OPT_FDBL:
+        fprintf(errstream,"  %s=<real>%*s  %s\n",op[i].label,
+          (int)(max-lemonStrlen(op[i].label)-6),"",op[i].message);
+        break;
+      case OPT_STR:
+      case OPT_FSTR:
+        fprintf(errstream,"  %s=<string>%*s  %s\n",op[i].label,
+          (int)(max-lemonStrlen(op[i].label)-8),"",op[i].message);
+        break;
+    }
+  }
+}
+/*********************** From the file "parse.c" ****************************/
+/*
+** Input file parser for the LEMON parser generator.
+*/
+
+/* The state of the parser */
+struct pstate {
+  char *filename;       /* Name of the input file */
+  int tokenlineno;      /* Linenumber at which current token starts */
+  int errorcnt;         /* Number of errors so far */
+  char *tokenstart;     /* Text of current token */
+  struct lemon *gp;     /* Global state vector */
+  enum e_state {
+    INITIALIZE,
+    WAITING_FOR_DECL_OR_RULE,
+    WAITING_FOR_DECL_KEYWORD,
+    WAITING_FOR_DECL_ARG,
+    WAITING_FOR_PRECEDENCE_SYMBOL,
+    WAITING_FOR_ARROW,
+    IN_RHS,
+    LHS_ALIAS_1,
+    LHS_ALIAS_2,
+    LHS_ALIAS_3,
+    RHS_ALIAS_1,
+    RHS_ALIAS_2,
+    PRECEDENCE_MARK_1,
+    PRECEDENCE_MARK_2,
+    RESYNC_AFTER_RULE_ERROR,
+    RESYNC_AFTER_DECL_ERROR,
+    WAITING_FOR_DESTRUCTOR_SYMBOL,
+    WAITING_FOR_DATATYPE_SYMBOL,
+    WAITING_FOR_FALLBACK_ID,
+    WAITING_FOR_WILDCARD_ID
+  } state;                   /* The state of the parser */
+  struct symbol *fallback;   /* The fallback token */
+  struct symbol *lhs;        /* Left-hand side of current rule */
+  char *lhsalias;            /* Alias for the LHS */
+  int nrhs;                  /* Number of right-hand side symbols seen */
+  struct symbol *rhs[MAXRHS];  /* RHS symbols */
+  char *alias[MAXRHS];       /* Aliases for each RHS symbol (or NULL) */
+  struct rule *prevrule;     /* Previous rule parsed */
+  char *declkeyword;         /* Keyword of a declaration */
+  char **declargslot;        /* Where the declaration argument should be put */
+  int insertLineMacro;       /* Add #line before declaration insert */
+  int *decllinenoslot;       /* Where to write declaration line number */
+  enum e_assoc declassoc;    /* Assign this association to decl arguments */
+  int preccounter;           /* Assign this precedence to decl arguments */
+  struct rule *firstrule;    /* Pointer to first rule in the grammar */
+  struct rule *lastrule;     /* Pointer to the most recently parsed rule */
+};
+
+/* Parse a single token */
+static void parseonetoken(psp)
+struct pstate *psp;
+{
+  char *x;
+  x = Strsafe(psp->tokenstart);     /* Save the token permanently */
+#if 0
+  printf("%s:%d: Token=[%s] state=%d\n",psp->filename,psp->tokenlineno,
+    x,psp->state);
+#endif
+  switch( psp->state ){
+    case INITIALIZE:
+      psp->prevrule = 0;
+      psp->preccounter = 0;
+      psp->firstrule = psp->lastrule = 0;
+      psp->gp->nrule = 0;
+      /* Fall thru to next case */
+    case WAITING_FOR_DECL_OR_RULE:
+      if( x[0]=='%' ){
+        psp->state = WAITING_FOR_DECL_KEYWORD;
+      }else if( islower(x[0]) ){
+        psp->lhs = Symbol_new(x);
+        psp->nrhs = 0;
+        psp->lhsalias = 0;
+        psp->state = WAITING_FOR_ARROW;
+      }else if( x[0]=='{' ){
+        if( psp->prevrule==0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+"There is no prior rule opon which to attach the code \
+fragment which begins on this line.");
+          psp->errorcnt++;
+	}else if( psp->prevrule->code!=0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+"Code fragment beginning on this line is not the first \
+to follow the previous rule.");
+          psp->errorcnt++;
+        }else{
+          psp->prevrule->line = psp->tokenlineno;
+          psp->prevrule->code = &x[1];
+	}
+      }else if( x[0]=='[' ){
+        psp->state = PRECEDENCE_MARK_1;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Token \"%s\" should be either \"%%\" or a nonterminal name.",
+          x);
+        psp->errorcnt++;
+      }
+      break;
+    case PRECEDENCE_MARK_1:
+      if( !isupper(x[0]) ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "The precedence symbol must be a terminal.");
+        psp->errorcnt++;
+      }else if( psp->prevrule==0 ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "There is no prior rule to assign precedence \"[%s]\".",x);
+        psp->errorcnt++;
+      }else if( psp->prevrule->precsym!=0 ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+"Precedence mark on this line is not the first \
+to follow the previous rule.");
+        psp->errorcnt++;
+      }else{
+        psp->prevrule->precsym = Symbol_new(x);
+      }
+      psp->state = PRECEDENCE_MARK_2;
+      break;
+    case PRECEDENCE_MARK_2:
+      if( x[0]!=']' ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \"]\" on precedence mark.");
+        psp->errorcnt++;
+      }
+      psp->state = WAITING_FOR_DECL_OR_RULE;
+      break;
+    case WAITING_FOR_ARROW:
+      if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+        psp->state = IN_RHS;
+      }else if( x[0]=='(' ){
+        psp->state = LHS_ALIAS_1;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Expected to see a \":\" following the LHS symbol \"%s\".",
+          psp->lhs->name);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case LHS_ALIAS_1:
+      if( isalpha(x[0]) ){
+        psp->lhsalias = x;
+        psp->state = LHS_ALIAS_2;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "\"%s\" is not a valid alias for the LHS \"%s\"\n",
+          x,psp->lhs->name);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case LHS_ALIAS_2:
+      if( x[0]==')' ){
+        psp->state = LHS_ALIAS_3;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case LHS_ALIAS_3:
+      if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+        psp->state = IN_RHS;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \"->\" following: \"%s(%s)\".",
+           psp->lhs->name,psp->lhsalias);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case IN_RHS:
+      if( x[0]=='.' ){
+        struct rule *rp;
+        rp = (struct rule *)calloc( sizeof(struct rule) + 
+             sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs, 1);
+        if( rp==0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Can't allocate enough memory for this rule.");
+          psp->errorcnt++;
+          psp->prevrule = 0;
+	}else{
+          int i;
+          rp->ruleline = psp->tokenlineno;
+          rp->rhs = (struct symbol**)&rp[1];
+          rp->rhsalias = (char**)&(rp->rhs[psp->nrhs]);
+          for(i=0; i<psp->nrhs; i++){
+            rp->rhs[i] = psp->rhs[i];
+            rp->rhsalias[i] = psp->alias[i];
+	  }
+          rp->lhs = psp->lhs;
+          rp->lhsalias = psp->lhsalias;
+          rp->nrhs = psp->nrhs;
+          rp->code = 0;
+          rp->precsym = 0;
+          rp->index = psp->gp->nrule++;
+          rp->nextlhs = rp->lhs->rule;
+          rp->lhs->rule = rp;
+          rp->next = 0;
+          if( psp->firstrule==0 ){
+            psp->firstrule = psp->lastrule = rp;
+	  }else{
+            psp->lastrule->next = rp;
+            psp->lastrule = rp;
+	  }
+          psp->prevrule = rp;
+	}
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( isalpha(x[0]) ){
+        if( psp->nrhs>=MAXRHS ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Too many symbols on RHS of rule beginning at \"%s\".",
+            x);
+          psp->errorcnt++;
+          psp->state = RESYNC_AFTER_RULE_ERROR;
+	}else{
+          psp->rhs[psp->nrhs] = Symbol_new(x);
+          psp->alias[psp->nrhs] = 0;
+          psp->nrhs++;
+	}
+      }else if( (x[0]=='|' || x[0]=='/') && psp->nrhs>0 ){
+        struct symbol *msp = psp->rhs[psp->nrhs-1];
+        if( msp->type!=MULTITERMINAL ){
+          struct symbol *origsp = msp;
+          msp = calloc(1,sizeof(*msp));
+          memset(msp, 0, sizeof(*msp));
+          msp->type = MULTITERMINAL;
+          msp->nsubsym = 1;
+          msp->subsym = calloc(1,sizeof(struct symbol*));
+          msp->subsym[0] = origsp;
+          msp->name = origsp->name;
+          psp->rhs[psp->nrhs-1] = msp;
+        }
+        msp->nsubsym++;
+        msp->subsym = realloc(msp->subsym, sizeof(struct symbol*)*msp->nsubsym);
+        msp->subsym[msp->nsubsym-1] = Symbol_new(&x[1]);
+        if( islower(x[1]) || islower(msp->subsym[0]->name[0]) ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Cannot form a compound containing a non-terminal");
+          psp->errorcnt++;
+        }
+      }else if( x[0]=='(' && psp->nrhs>0 ){
+        psp->state = RHS_ALIAS_1;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Illegal character on RHS of rule: \"%s\".",x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case RHS_ALIAS_1:
+      if( isalpha(x[0]) ){
+        psp->alias[psp->nrhs-1] = x;
+        psp->state = RHS_ALIAS_2;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n",
+          x,psp->rhs[psp->nrhs-1]->name);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case RHS_ALIAS_2:
+      if( x[0]==')' ){
+        psp->state = IN_RHS;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case WAITING_FOR_DECL_KEYWORD:
+      if( isalpha(x[0]) ){
+        psp->declkeyword = x;
+        psp->declargslot = 0;
+        psp->decllinenoslot = 0;
+        psp->insertLineMacro = 1;
+        psp->state = WAITING_FOR_DECL_ARG;
+        if( strcmp(x,"name")==0 ){
+          psp->declargslot = &(psp->gp->name);
+          psp->insertLineMacro = 0;
+	}else if( strcmp(x,"include")==0 ){
+          psp->declargslot = &(psp->gp->include);
+	}else if( strcmp(x,"code")==0 ){
+          psp->declargslot = &(psp->gp->extracode);
+	}else if( strcmp(x,"token_destructor")==0 ){
+          psp->declargslot = &psp->gp->tokendest;
+	}else if( strcmp(x,"default_destructor")==0 ){
+          psp->declargslot = &psp->gp->vardest;
+	}else if( strcmp(x,"token_prefix")==0 ){
+          psp->declargslot = &psp->gp->tokenprefix;
+          psp->insertLineMacro = 0;
+	}else if( strcmp(x,"syntax_error")==0 ){
+          psp->declargslot = &(psp->gp->error);
+	}else if( strcmp(x,"parse_accept")==0 ){
+          psp->declargslot = &(psp->gp->accept);
+	}else if( strcmp(x,"parse_failure")==0 ){
+          psp->declargslot = &(psp->gp->failure);
+	}else if( strcmp(x,"stack_overflow")==0 ){
+          psp->declargslot = &(psp->gp->overflow);
+        }else if( strcmp(x,"extra_argument")==0 ){
+          psp->declargslot = &(psp->gp->arg);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"token_type")==0 ){
+          psp->declargslot = &(psp->gp->tokentype);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"default_type")==0 ){
+          psp->declargslot = &(psp->gp->vartype);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"stack_size")==0 ){
+          psp->declargslot = &(psp->gp->stacksize);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"start_symbol")==0 ){
+          psp->declargslot = &(psp->gp->start);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"left")==0 ){
+          psp->preccounter++;
+          psp->declassoc = LEFT;
+          psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+        }else if( strcmp(x,"right")==0 ){
+          psp->preccounter++;
+          psp->declassoc = RIGHT;
+          psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+        }else if( strcmp(x,"nonassoc")==0 ){
+          psp->preccounter++;
+          psp->declassoc = NONE;
+          psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+	}else if( strcmp(x,"destructor")==0 ){
+          psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL;
+	}else if( strcmp(x,"type")==0 ){
+          psp->state = WAITING_FOR_DATATYPE_SYMBOL;
+        }else if( strcmp(x,"fallback")==0 ){
+          psp->fallback = 0;
+          psp->state = WAITING_FOR_FALLBACK_ID;
+        }else if( strcmp(x,"wildcard")==0 ){
+          psp->state = WAITING_FOR_WILDCARD_ID;
+        }else{
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Unknown declaration keyword: \"%%%s\".",x);
+          psp->errorcnt++;
+          psp->state = RESYNC_AFTER_DECL_ERROR;
+	}
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Illegal declaration keyword: \"%s\".",x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }
+      break;
+    case WAITING_FOR_DESTRUCTOR_SYMBOL:
+      if( !isalpha(x[0]) ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Symbol name missing after %destructor keyword");
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        psp->declargslot = &sp->destructor;
+        psp->decllinenoslot = &sp->destLineno;
+        psp->insertLineMacro = 1;
+        psp->state = WAITING_FOR_DECL_ARG;
+      }
+      break;
+    case WAITING_FOR_DATATYPE_SYMBOL:
+      if( !isalpha(x[0]) ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Symbol name missing after %destructor keyword");
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        psp->declargslot = &sp->datatype;
+        psp->insertLineMacro = 0;
+        psp->state = WAITING_FOR_DECL_ARG;
+      }
+      break;
+    case WAITING_FOR_PRECEDENCE_SYMBOL:
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( isupper(x[0]) ){
+        struct symbol *sp;
+        sp = Symbol_new(x);
+        if( sp->prec>=0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Symbol \"%s\" has already be given a precedence.",x);
+          psp->errorcnt++;
+	}else{
+          sp->prec = psp->preccounter;
+          sp->assoc = psp->declassoc;
+	}
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Can't assign a precedence to \"%s\".",x);
+        psp->errorcnt++;
+      }
+      break;
+    case WAITING_FOR_DECL_ARG:
+      if( x[0]=='{' || x[0]=='\"' || isalnum(x[0]) ){
+        char *zOld, *zNew, *zBuf, *z;
+        int nOld, n, nLine, nNew, nBack;
+        int addLineMacro;
+        char zLine[50];
+        zNew = x;
+        if( zNew[0]=='"' || zNew[0]=='{' ) zNew++;
+        nNew = lemonStrlen(zNew);
+        if( *psp->declargslot ){
+          zOld = *psp->declargslot;
+        }else{
+          zOld = "";
+        }
+        nOld = lemonStrlen(zOld);
+        n = nOld + nNew + 20;
+        addLineMacro = !psp->gp->nolinenosflag && psp->insertLineMacro &&
+                        (psp->decllinenoslot==0 || psp->decllinenoslot[0]!=0);
+        if( addLineMacro ){
+          for(z=psp->filename, nBack=0; *z; z++){
+            if( *z=='\\' ) nBack++;
+          }
+          sprintf(zLine, "#line %d ", psp->tokenlineno);
+          nLine = lemonStrlen(zLine);
+          n += nLine + lemonStrlen(psp->filename) + nBack;
+        }
+        *psp->declargslot = zBuf = realloc(*psp->declargslot, n);
+        zBuf += nOld;
+        if( addLineMacro ){
+          if( nOld && zBuf[-1]!='\n' ){
+            *(zBuf++) = '\n';
+          }
+          memcpy(zBuf, zLine, nLine);
+          zBuf += nLine;
+          *(zBuf++) = '"';
+          for(z=psp->filename; *z; z++){
+            if( *z=='\\' ){
+              *(zBuf++) = '\\';
+            }
+            *(zBuf++) = *z;
+          }
+          *(zBuf++) = '"';
+          *(zBuf++) = '\n';
+        }
+        if( psp->decllinenoslot && psp->decllinenoslot[0]==0 ){
+          psp->decllinenoslot[0] = psp->tokenlineno;
+        }
+        memcpy(zBuf, zNew, nNew);
+        zBuf += nNew;
+        *zBuf = 0;
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Illegal argument to %%%s: %s",psp->declkeyword,x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }
+      break;
+    case WAITING_FOR_FALLBACK_ID:
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( !isupper(x[0]) ){
+        ErrorMsg(psp->filename, psp->tokenlineno,
+          "%%fallback argument \"%s\" should be a token", x);
+        psp->errorcnt++;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        if( psp->fallback==0 ){
+          psp->fallback = sp;
+        }else if( sp->fallback ){
+          ErrorMsg(psp->filename, psp->tokenlineno,
+            "More than one fallback assigned to token %s", x);
+          psp->errorcnt++;
+        }else{
+          sp->fallback = psp->fallback;
+          psp->gp->has_fallback = 1;
+        }
+      }
+      break;
+    case WAITING_FOR_WILDCARD_ID:
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( !isupper(x[0]) ){
+        ErrorMsg(psp->filename, psp->tokenlineno,
+          "%%wildcard argument \"%s\" should be a token", x);
+        psp->errorcnt++;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        if( psp->gp->wildcard==0 ){
+          psp->gp->wildcard = sp;
+        }else{
+          ErrorMsg(psp->filename, psp->tokenlineno,
+            "Extra wildcard to token: %s", x);
+          psp->errorcnt++;
+        }
+      }
+      break;
+    case RESYNC_AFTER_RULE_ERROR:
+/*      if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+**      break; */
+    case RESYNC_AFTER_DECL_ERROR:
+      if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+      if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD;
+      break;
+  }
+}
+
+/* Run the preprocessor over the input file text.  The global variables
+** azDefine[0] through azDefine[nDefine-1] contains the names of all defined
+** macros.  This routine looks for "%ifdef" and "%ifndef" and "%endif" and
+** comments them out.  Text in between is also commented out as appropriate.
+*/
+static void preprocess_input(char *z){
+  int i, j, k, n;
+  int exclude = 0;
+  int start = 0;
+  int lineno = 1;
+  int start_lineno = 1;
+  for(i=0; z[i]; i++){
+    if( z[i]=='\n' ) lineno++;
+    if( z[i]!='%' || (i>0 && z[i-1]!='\n') ) continue;
+    if( strncmp(&z[i],"%endif",6)==0 && isspace(z[i+6]) ){
+      if( exclude ){
+        exclude--;
+        if( exclude==0 ){
+          for(j=start; j<i; j++) if( z[j]!='\n' ) z[j] = ' ';
+        }
+      }
+      for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+    }else if( (strncmp(&z[i],"%ifdef",6)==0 && isspace(z[i+6]))
+          || (strncmp(&z[i],"%ifndef",7)==0 && isspace(z[i+7])) ){
+      if( exclude ){
+        exclude++;
+      }else{
+        for(j=i+7; isspace(z[j]); j++){}
+        for(n=0; z[j+n] && !isspace(z[j+n]); n++){}
+        exclude = 1;
+        for(k=0; k<nDefine; k++){
+          if( strncmp(azDefine[k],&z[j],n)==0 && lemonStrlen(azDefine[k])==n ){
+            exclude = 0;
+            break;
+          }
+        }
+        if( z[i+3]=='n' ) exclude = !exclude;
+        if( exclude ){
+          start = i;
+          start_lineno = lineno;
+        }
+      }
+      for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+    }
+  }
+  if( exclude ){
+    fprintf(stderr,"unterminated %%ifdef starting on line %d\n", start_lineno);
+    exit(1);
+  }
+}
+
+/* In spite of its name, this function is really a scanner.  It read
+** in the entire input file (all at once) then tokenizes it.  Each
+** token is passed to the function "parseonetoken" which builds all
+** the appropriate data structures in the global state vector "gp".
+*/
+void Parse(gp)
+struct lemon *gp;
+{
+  struct pstate ps;
+  FILE *fp;
+  char *filebuf;
+  int filesize;
+  int lineno;
+  int c;
+  char *cp, *nextcp;
+  int startline = 0;
+
+  memset(&ps, '\0', sizeof(ps));
+  ps.gp = gp;
+  ps.filename = gp->filename;
+  ps.errorcnt = 0;
+  ps.state = INITIALIZE;
+
+  /* Begin by reading the input file */
+  fp = fopen(ps.filename,"rb");
+  if( fp==0 ){
+    ErrorMsg(ps.filename,0,"Can't open this file for reading.");
+    gp->errorcnt++;
+    return;
+  }
+  fseek(fp,0,2);
+  filesize = ftell(fp);
+  rewind(fp);
+  filebuf = (char *)malloc( filesize+1 );
+  if( filebuf==0 ){
+    ErrorMsg(ps.filename,0,"Can't allocate %d of memory to hold this file.",
+      filesize+1);
+    gp->errorcnt++;
+    return;
+  }
+  if( fread(filebuf,1,filesize,fp)!=filesize ){
+    ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.",
+      filesize);
+    free(filebuf);
+    gp->errorcnt++;
+    return;
+  }
+  fclose(fp);
+  filebuf[filesize] = 0;
+
+  /* Make an initial pass through the file to handle %ifdef and %ifndef */
+  preprocess_input(filebuf);
+
+  /* Now scan the text of the input file */
+  lineno = 1;
+  for(cp=filebuf; (c= *cp)!=0; ){
+    if( c=='\n' ) lineno++;              /* Keep track of the line number */
+    if( isspace(c) ){ cp++; continue; }  /* Skip all white space */
+    if( c=='/' && cp[1]=='/' ){          /* Skip C++ style comments */
+      cp+=2;
+      while( (c= *cp)!=0 && c!='\n' ) cp++;
+      continue;
+    }
+    if( c=='/' && cp[1]=='*' ){          /* Skip C style comments */
+      cp+=2;
+      while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){
+        if( c=='\n' ) lineno++;
+        cp++;
+      }
+      if( c ) cp++;
+      continue;
+    }
+    ps.tokenstart = cp;                /* Mark the beginning of the token */
+    ps.tokenlineno = lineno;           /* Linenumber on which token begins */
+    if( c=='\"' ){                     /* String literals */
+      cp++;
+      while( (c= *cp)!=0 && c!='\"' ){
+        if( c=='\n' ) lineno++;
+        cp++;
+      }
+      if( c==0 ){
+        ErrorMsg(ps.filename,startline,
+"String starting on this line is not terminated before the end of the file.");
+        ps.errorcnt++;
+        nextcp = cp;
+      }else{
+        nextcp = cp+1;
+      }
+    }else if( c=='{' ){               /* A block of C code */
+      int level;
+      cp++;
+      for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){
+        if( c=='\n' ) lineno++;
+        else if( c=='{' ) level++;
+        else if( c=='}' ) level--;
+        else if( c=='/' && cp[1]=='*' ){  /* Skip comments */
+          int prevc;
+          cp = &cp[2];
+          prevc = 0;
+          while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){
+            if( c=='\n' ) lineno++;
+            prevc = c;
+            cp++;
+	  }
+	}else if( c=='/' && cp[1]=='/' ){  /* Skip C++ style comments too */
+          cp = &cp[2];
+          while( (c= *cp)!=0 && c!='\n' ) cp++;
+          if( c ) lineno++;
+	}else if( c=='\'' || c=='\"' ){    /* String a character literals */
+          int startchar, prevc;
+          startchar = c;
+          prevc = 0;
+          for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){
+            if( c=='\n' ) lineno++;
+            if( prevc=='\\' ) prevc = 0;
+            else              prevc = c;
+	  }
+	}
+      }
+      if( c==0 ){
+        ErrorMsg(ps.filename,ps.tokenlineno,
+"C code starting on this line is not terminated before the end of the file.");
+        ps.errorcnt++;
+        nextcp = cp;
+      }else{
+        nextcp = cp+1;
+      }
+    }else if( isalnum(c) ){          /* Identifiers */
+      while( (c= *cp)!=0 && (isalnum(c) || c=='_') ) cp++;
+      nextcp = cp;
+    }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */
+      cp += 3;
+      nextcp = cp;
+    }else if( (c=='/' || c=='|') && isalpha(cp[1]) ){
+      cp += 2;
+      while( (c = *cp)!=0 && (isalnum(c) || c=='_') ) cp++;
+      nextcp = cp;
+    }else{                          /* All other (one character) operators */
+      cp++;
+      nextcp = cp;
+    }
+    c = *cp;
+    *cp = 0;                        /* Null terminate the token */
+    parseonetoken(&ps);             /* Parse the token */
+    *cp = c;                        /* Restore the buffer */
+    cp = nextcp;
+  }
+  free(filebuf);                    /* Release the buffer after parsing */
+  gp->rule = ps.firstrule;
+  gp->errorcnt = ps.errorcnt;
+}
+/*************************** From the file "plink.c" *********************/
+/*
+** Routines processing configuration follow-set propagation links
+** in the LEMON parser generator.
+*/
+static struct plink *plink_freelist = 0;
+
+/* Allocate a new plink */
+struct plink *Plink_new(){
+  struct plink *new;
+
+  if( plink_freelist==0 ){
+    int i;
+    int amt = 100;
+    plink_freelist = (struct plink *)calloc( amt, sizeof(struct plink) );
+    if( plink_freelist==0 ){
+      fprintf(stderr,
+      "Unable to allocate memory for a new follow-set propagation link.\n");
+      exit(1);
+    }
+    for(i=0; i<amt-1; i++) plink_freelist[i].next = &plink_freelist[i+1];
+    plink_freelist[amt-1].next = 0;
+  }
+  new = plink_freelist;
+  plink_freelist = plink_freelist->next;
+  return new;
+}
+
+/* Add a plink to a plink list */
+void Plink_add(plpp,cfp)
+struct plink **plpp;
+struct config *cfp;
+{
+  struct plink *new;
+  new = Plink_new();
+  new->next = *plpp;
+  *plpp = new;
+  new->cfp = cfp;
+}
+
+/* Transfer every plink on the list "from" to the list "to" */
+void Plink_copy(to,from)
+struct plink **to;
+struct plink *from;
+{
+  struct plink *nextpl;
+  while( from ){
+    nextpl = from->next;
+    from->next = *to;
+    *to = from;
+    from = nextpl;
+  }
+}
+
+/* Delete every plink on the list */
+void Plink_delete(plp)
+struct plink *plp;
+{
+  struct plink *nextpl;
+
+  while( plp ){
+    nextpl = plp->next;
+    plp->next = plink_freelist;
+    plink_freelist = plp;
+    plp = nextpl;
+  }
+}
+/*********************** From the file "report.c" **************************/
+/*
+** Procedures for generating reports and tables in the LEMON parser generator.
+*/
+
+/* Generate a filename with the given suffix.  Space to hold the
+** name comes from malloc() and must be freed by the calling
+** function.
+*/
+PRIVATE char *file_makename(lemp,suffix)
+struct lemon *lemp;
+char *suffix;
+{
+  char *name;
+  char *cp;
+
+  name = malloc( lemonStrlen(lemp->filename) + lemonStrlen(suffix) + 5 );
+  if( name==0 ){
+    fprintf(stderr,"Can't allocate space for a filename.\n");
+    exit(1);
+  }
+  strcpy(name,lemp->filename);
+  cp = strrchr(name,'.');
+  if( cp ) *cp = 0;
+  strcat(name,suffix);
+  return name;
+}
+
+/* Open a file with a name based on the name of the input file,
+** but with a different (specified) suffix, and return a pointer
+** to the stream */
+PRIVATE FILE *file_open(lemp,suffix,mode)
+struct lemon *lemp;
+char *suffix;
+char *mode;
+{
+  FILE *fp;
+
+  if( lemp->outname ) free(lemp->outname);
+  lemp->outname = file_makename(lemp, suffix);
+  fp = fopen(lemp->outname,mode);
+  if( fp==0 && *mode=='w' ){
+    fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname);
+    lemp->errorcnt++;
+    return 0;
+  }
+  return fp;
+}
+
+/* Duplicate the input file without comments and without actions 
+** on rules */
+void Reprint(lemp)
+struct lemon *lemp;
+{
+  struct rule *rp;
+  struct symbol *sp;
+  int i, j, maxlen, len, ncolumns, skip;
+  printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename);
+  maxlen = 10;
+  for(i=0; i<lemp->nsymbol; i++){
+    sp = lemp->symbols[i];
+    len = lemonStrlen(sp->name);
+    if( len>maxlen ) maxlen = len;
+  }
+  ncolumns = 76/(maxlen+5);
+  if( ncolumns<1 ) ncolumns = 1;
+  skip = (lemp->nsymbol + ncolumns - 1)/ncolumns;
+  for(i=0; i<skip; i++){
+    printf("//");
+    for(j=i; j<lemp->nsymbol; j+=skip){
+      sp = lemp->symbols[j];
+      assert( sp->index==j );
+      printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name);
+    }
+    printf("\n");
+  }
+  for(rp=lemp->rule; rp; rp=rp->next){
+    printf("%s",rp->lhs->name);
+    /*    if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */
+    printf(" ::=");
+    for(i=0; i<rp->nrhs; i++){
+      sp = rp->rhs[i];
+      printf(" %s", sp->name);
+      if( sp->type==MULTITERMINAL ){
+        for(j=1; j<sp->nsubsym; j++){
+          printf("|%s", sp->subsym[j]->name);
+        }
+      }
+      /* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */
+    }
+    printf(".");
+    if( rp->precsym ) printf(" [%s]",rp->precsym->name);
+    /* if( rp->code ) printf("\n    %s",rp->code); */
+    printf("\n");
+  }
+}
+
+void ConfigPrint(fp,cfp)
+FILE *fp;
+struct config *cfp;
+{
+  struct rule *rp;
+  struct symbol *sp;
+  int i, j;
+  rp = cfp->rp;
+  fprintf(fp,"%s ::=",rp->lhs->name);
+  for(i=0; i<=rp->nrhs; i++){
+    if( i==cfp->dot ) fprintf(fp," *");
+    if( i==rp->nrhs ) break;
+    sp = rp->rhs[i];
+    fprintf(fp," %s", sp->name);
+    if( sp->type==MULTITERMINAL ){
+      for(j=1; j<sp->nsubsym; j++){
+        fprintf(fp,"|%s",sp->subsym[j]->name);
+      }
+    }
+  }
+}
+
+/* #define TEST */
+#if 0
+/* Print a set */
+PRIVATE void SetPrint(out,set,lemp)
+FILE *out;
+char *set;
+struct lemon *lemp;
+{
+  int i;
+  char *spacer;
+  spacer = "";
+  fprintf(out,"%12s[","");
+  for(i=0; i<lemp->nterminal; i++){
+    if( SetFind(set,i) ){
+      fprintf(out,"%s%s",spacer,lemp->symbols[i]->name);
+      spacer = " ";
+    }
+  }
+  fprintf(out,"]\n");
+}
+
+/* Print a plink chain */
+PRIVATE void PlinkPrint(out,plp,tag)
+FILE *out;
+struct plink *plp;
+char *tag;
+{
+  while( plp ){
+    fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->statenum);
+    ConfigPrint(out,plp->cfp);
+    fprintf(out,"\n");
+    plp = plp->next;
+  }
+}
+#endif
+
+/* Print an action to the given file descriptor.  Return FALSE if
+** nothing was actually printed.
+*/
+int PrintAction(struct action *ap, FILE *fp, int indent){
+  int result = 1;
+  switch( ap->type ){
+    case SHIFT:
+      fprintf(fp,"%*s shift  %d",indent,ap->sp->name,ap->x.stp->statenum);
+      break;
+    case REDUCE:
+      fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index);
+      break;
+    case ACCEPT:
+      fprintf(fp,"%*s accept",indent,ap->sp->name);
+      break;
+    case ERROR:
+      fprintf(fp,"%*s error",indent,ap->sp->name);
+      break;
+    case SRCONFLICT:
+    case RRCONFLICT:
+      fprintf(fp,"%*s reduce %-3d ** Parsing conflict **",
+        indent,ap->sp->name,ap->x.rp->index);
+      break;
+    case SSCONFLICT:
+      fprintf(fp,"%*s shift  %d ** Parsing conflict **", 
+        indent,ap->sp->name,ap->x.stp->statenum);
+      break;
+    case SH_RESOLVED:
+    case RD_RESOLVED:
+    case NOT_USED:
+      result = 0;
+      break;
+  }
+  return result;
+}
+
+/* Generate the "y.output" log file */
+void ReportOutput(lemp)
+struct lemon *lemp;
+{
+  int i;
+  struct state *stp;
+  struct config *cfp;
+  struct action *ap;
+  FILE *fp;
+
+  fp = file_open(lemp,".out","wb");
+  if( fp==0 ) return;
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    fprintf(fp,"State %d:\n",stp->statenum);
+    if( lemp->basisflag ) cfp=stp->bp;
+    else                  cfp=stp->cfp;
+    while( cfp ){
+      char buf[20];
+      if( cfp->dot==cfp->rp->nrhs ){
+        sprintf(buf,"(%d)",cfp->rp->index);
+        fprintf(fp,"    %5s ",buf);
+      }else{
+        fprintf(fp,"          ");
+      }
+      ConfigPrint(fp,cfp);
+      fprintf(fp,"\n");
+#if 0
+      SetPrint(fp,cfp->fws,lemp);
+      PlinkPrint(fp,cfp->fplp,"To  ");
+      PlinkPrint(fp,cfp->bplp,"From");
+#endif
+      if( lemp->basisflag ) cfp=cfp->bp;
+      else                  cfp=cfp->next;
+    }
+    fprintf(fp,"\n");
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( PrintAction(ap,fp,30) ) fprintf(fp,"\n");
+    }
+    fprintf(fp,"\n");
+  }
+  fprintf(fp, "----------------------------------------------------\n");
+  fprintf(fp, "Symbols:\n");
+  for(i=0; i<lemp->nsymbol; i++){
+    int j;
+    struct symbol *sp;
+
+    sp = lemp->symbols[i];
+    fprintf(fp, "  %3d: %s", i, sp->name);
+    if( sp->type==NONTERMINAL ){
+      fprintf(fp, ":");
+      if( sp->lambda ){
+        fprintf(fp, " <lambda>");
+      }
+      for(j=0; j<lemp->nterminal; j++){
+        if( sp->firstset && SetFind(sp->firstset, j) ){
+          fprintf(fp, " %s", lemp->symbols[j]->name);
+        }
+      }
+    }
+    fprintf(fp, "\n");
+  }
+  fclose(fp);
+  return;
+}
+
+/* Search for the file "name" which is in the same directory as
+** the exacutable */
+PRIVATE char *pathsearch(argv0,name,modemask)
+char *argv0;
+char *name;
+int modemask;
+{
+  char *pathlist;
+  char *path,*cp;
+  char c;
+
+#ifdef __WIN32__
+  cp = strrchr(argv0,'\\');
+#else
+  cp = strrchr(argv0,'/');
+#endif
+  if( cp ){
+    c = *cp;
+    *cp = 0;
+    path = (char *)malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 );
+    if( path ) sprintf(path,"%s/%s",argv0,name);
+    *cp = c;
+  }else{
+    extern char *getenv();
+    pathlist = getenv("PATH");
+    if( pathlist==0 ) pathlist = ".:/bin:/usr/bin";
+    path = (char *)malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 );
+    if( path!=0 ){
+      while( *pathlist ){
+        cp = strchr(pathlist,':');
+        if( cp==0 ) cp = &pathlist[lemonStrlen(pathlist)];
+        c = *cp;
+        *cp = 0;
+        sprintf(path,"%s/%s",pathlist,name);
+        *cp = c;
+        if( c==0 ) pathlist = "";
+        else pathlist = &cp[1];
+        if( access(path,modemask)==0 ) break;
+      }
+    }
+  }
+  return path;
+}
+
+/* Given an action, compute the integer value for that action
+** which is to be put in the action table of the generated machine.
+** Return negative if no action should be generated.
+*/
+PRIVATE int compute_action(lemp,ap)
+struct lemon *lemp;
+struct action *ap;
+{
+  int act;
+  switch( ap->type ){
+    case SHIFT:  act = ap->x.stp->statenum;            break;
+    case REDUCE: act = ap->x.rp->index + lemp->nstate; break;
+    case ERROR:  act = lemp->nstate + lemp->nrule;     break;
+    case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break;
+    default:     act = -1; break;
+  }
+  return act;
+}
+
+#define LINESIZE 1000
+/* The next cluster of routines are for reading the template file
+** and writing the results to the generated parser */
+/* The first function transfers data from "in" to "out" until
+** a line is seen which begins with "%%".  The line number is
+** tracked.
+**
+** if name!=0, then any word that begin with "Parse" is changed to
+** begin with *name instead.
+*/
+PRIVATE void tplt_xfer(name,in,out,lineno)
+char *name;
+FILE *in;
+FILE *out;
+int *lineno;
+{
+  int i, iStart;
+  char line[LINESIZE];
+  while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){
+    (*lineno)++;
+    iStart = 0;
+    if( name ){
+      for(i=0; line[i]; i++){
+        if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0
+          && (i==0 || !isalpha(line[i-1]))
+        ){
+          if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]);
+          fprintf(out,"%s",name);
+          i += 4;
+          iStart = i+1;
+        }
+      }
+    }
+    fprintf(out,"%s",&line[iStart]);
+  }
+}
+
+/* The next function finds the template file and opens it, returning
+** a pointer to the opened file. */
+PRIVATE FILE *tplt_open(lemp)
+struct lemon *lemp;
+{
+  static char templatename[] = "lempar.c";
+  char buf[1000];
+  FILE *in;
+  char *tpltname;
+  char *cp;
+
+  cp = strrchr(lemp->filename,'.');
+  if( cp ){
+    sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename);
+  }else{
+    sprintf(buf,"%s.lt",lemp->filename);
+  }
+  if( access(buf,004)==0 ){
+    tpltname = buf;
+  }else if( access(templatename,004)==0 ){
+    tpltname = templatename;
+  }else{
+    tpltname = pathsearch(lemp->argv0,templatename,0);
+  }
+  if( tpltname==0 ){
+    fprintf(stderr,"Can't find the parser driver template file \"%s\".\n",
+    templatename);
+    lemp->errorcnt++;
+    return 0;
+  }
+  in = fopen(tpltname,"rb");
+  if( in==0 ){
+    fprintf(stderr,"Can't open the template file \"%s\".\n",templatename);
+    lemp->errorcnt++;
+    return 0;
+  }
+  return in;
+}
+
+/* Print a #line directive line to the output file. */
+PRIVATE void tplt_linedir(out,lineno,filename)
+FILE *out;
+int lineno;
+char *filename;
+{
+  fprintf(out,"#line %d \"",lineno);
+  while( *filename ){
+    if( *filename == '\\' ) putc('\\',out);
+    putc(*filename,out);
+    filename++;
+  }
+  fprintf(out,"\"\n");
+}
+
+/* Print a string to the file and keep the linenumber up to date */
+PRIVATE void tplt_print(out,lemp,str,lineno)
+FILE *out;
+struct lemon *lemp;
+char *str;
+int *lineno;
+{
+  if( str==0 ) return;
+  while( *str ){
+    putc(*str,out);
+    if( *str=='\n' ) (*lineno)++;
+    str++;
+  }
+  if( str[-1]!='\n' ){
+    putc('\n',out);
+    (*lineno)++;
+  }
+  if (!lemp->nolinenosflag) {
+    (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); 
+  }
+  return;
+}
+
+/*
+** The following routine emits code for the destructor for the
+** symbol sp
+*/
+void emit_destructor_code(out,sp,lemp,lineno)
+FILE *out;
+struct symbol *sp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp = 0;
+
+ if( sp->type==TERMINAL ){
+   cp = lemp->tokendest;
+   if( cp==0 ) return;
+   fprintf(out,"{\n"); (*lineno)++;
+ }else if( sp->destructor ){
+   cp = sp->destructor;
+   fprintf(out,"{\n"); (*lineno)++;
+   if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,sp->destLineno,lemp->filename); }
+ }else if( lemp->vardest ){
+   cp = lemp->vardest;
+   if( cp==0 ) return;
+   fprintf(out,"{\n"); (*lineno)++;
+ }else{
+   assert( 0 );  /* Cannot happen */
+ }
+ for(; *cp; cp++){
+   if( *cp=='$' && cp[1]=='$' ){
+     fprintf(out,"(yypminor->yy%d)",sp->dtnum);
+     cp++;
+     continue;
+   }
+   if( *cp=='\n' ) (*lineno)++;
+   fputc(*cp,out);
+ }
+ fprintf(out,"\n"); (*lineno)++;
+ if (!lemp->nolinenosflag) { 
+   (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); 
+ }
+ fprintf(out,"}\n"); (*lineno)++;
+ return;
+}
+
+/*
+** Return TRUE (non-zero) if the given symbol has a destructor.
+*/
+int has_destructor(sp, lemp)
+struct symbol *sp;
+struct lemon *lemp;
+{
+  int ret;
+  if( sp->type==TERMINAL ){
+    ret = lemp->tokendest!=0;
+  }else{
+    ret = lemp->vardest!=0 || sp->destructor!=0;
+  }
+  return ret;
+}
+
+/*
+** Append text to a dynamically allocated string.  If zText is 0 then
+** reset the string to be empty again.  Always return the complete text
+** of the string (which is overwritten with each call).
+**
+** n bytes of zText are stored.  If n==0 then all of zText up to the first
+** \000 terminator is stored.  zText can contain up to two instances of
+** %d.  The values of p1 and p2 are written into the first and second
+** %d.
+**
+** If n==-1, then the previous character is overwritten.
+*/
+PRIVATE char *append_str(char *zText, int n, int p1, int p2){
+  static char *z = 0;
+  static int alloced = 0;
+  static int used = 0;
+  int c;
+  char zInt[40];
+
+  if( zText==0 ){
+    used = 0;
+    return z;
+  }
+  if( n<=0 ){
+    if( n<0 ){
+      used += n;
+      assert( used>=0 );
+    }
+    n = lemonStrlen(zText);
+  }
+  if( n+sizeof(zInt)*2+used >= alloced ){
+    alloced = n + sizeof(zInt)*2 + used + 200;
+    z = realloc(z,  alloced);
+  }
+  if( z==0 ) return "";
+  while( n-- > 0 ){
+    c = *(zText++);
+    if( c=='%' && n>0 && zText[0]=='d' ){
+      sprintf(zInt, "%d", p1);
+      p1 = p2;
+      strcpy(&z[used], zInt);
+      used += lemonStrlen(&z[used]);
+      zText++;
+      n--;
+    }else{
+      z[used++] = c;
+    }
+  }
+  z[used] = 0;
+  return z;
+}
+
+/*
+** zCode is a string that is the action associated with a rule.  Expand
+** the symbols in this string so that the refer to elements of the parser
+** stack.
+*/
+PRIVATE void translate_code(struct lemon *lemp, struct rule *rp){
+  char *cp, *xp;
+  int i;
+  char lhsused = 0;    /* True if the LHS element has been used */
+  char used[MAXRHS];   /* True for each RHS element which is used */
+
+  for(i=0; i<rp->nrhs; i++) used[i] = 0;
+  lhsused = 0;
+
+  if( rp->code==0 ){
+    rp->code = "\n";
+    rp->line = rp->ruleline;
+  }
+
+  append_str(0,0,0,0);
+  for(cp=rp->code; *cp; cp++){
+    if( isalpha(*cp) && (cp==rp->code || (!isalnum(cp[-1]) && cp[-1]!='_')) ){
+      char saved;
+      for(xp= &cp[1]; isalnum(*xp) || *xp=='_'; xp++);
+      saved = *xp;
+      *xp = 0;
+      if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){
+        append_str("yygotominor.yy%d",0,rp->lhs->dtnum,0);
+        cp = xp;
+        lhsused = 1;
+      }else{
+        for(i=0; i<rp->nrhs; i++){
+          if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){
+            if( cp!=rp->code && cp[-1]=='@' ){
+              /* If the argument is of the form @X then substituted
+              ** the token number of X, not the value of X */
+              append_str("yymsp[%d].major",-1,i-rp->nrhs+1,0);
+            }else{
+              struct symbol *sp = rp->rhs[i];
+              int dtnum;
+              if( sp->type==MULTITERMINAL ){
+                dtnum = sp->subsym[0]->dtnum;
+              }else{
+                dtnum = sp->dtnum;
+              }
+              append_str("yymsp[%d].minor.yy%d",0,i-rp->nrhs+1, dtnum);
+            }
+            cp = xp;
+            used[i] = 1;
+            break;
+          }
+        }
+      }
+      *xp = saved;
+    }
+    append_str(cp, 1, 0, 0);
+  } /* End loop */
+
+  /* Check to make sure the LHS has been used */
+  if( rp->lhsalias && !lhsused ){
+    ErrorMsg(lemp->filename,rp->ruleline,
+      "Label \"%s\" for \"%s(%s)\" is never used.",
+        rp->lhsalias,rp->lhs->name,rp->lhsalias);
+    lemp->errorcnt++;
+  }
+
+  /* Generate destructor code for RHS symbols which are not used in the
+  ** reduce code */
+  for(i=0; i<rp->nrhs; i++){
+    if( rp->rhsalias[i] && !used[i] ){
+      ErrorMsg(lemp->filename,rp->ruleline,
+        "Label %s for \"%s(%s)\" is never used.",
+        rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]);
+      lemp->errorcnt++;
+    }else if( rp->rhsalias[i]==0 ){
+      if( has_destructor(rp->rhs[i],lemp) ){
+        append_str("  yy_destructor(yypParser,%d,&yymsp[%d].minor);\n", 0,
+           rp->rhs[i]->index,i-rp->nrhs+1);
+      }else{
+        /* No destructor defined for this term */
+      }
+    }
+  }
+  if( rp->code ){
+    cp = append_str(0,0,0,0);
+    rp->code = Strsafe(cp?cp:"");
+  }
+}
+
+/* 
+** Generate code which executes when the rule "rp" is reduced.  Write
+** the code to "out".  Make sure lineno stays up-to-date.
+*/
+PRIVATE void emit_code(out,rp,lemp,lineno)
+FILE *out;
+struct rule *rp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp;
+
+ /* Generate code to do the reduce action */
+ if( rp->code ){
+   if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,rp->line,lemp->filename); }
+   fprintf(out,"{%s",rp->code);
+   for(cp=rp->code; *cp; cp++){
+     if( *cp=='\n' ) (*lineno)++;
+   } /* End loop */
+   fprintf(out,"}\n"); (*lineno)++;
+   if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); }
+ } /* End if( rp->code ) */
+
+ return;
+}
+
+/*
+** Print the definition of the union used for the parser's data stack.
+** This union contains fields for every possible data type for tokens
+** and nonterminals.  In the process of computing and printing this
+** union, also set the ".dtnum" field of every terminal and nonterminal
+** symbol.
+*/
+void print_stack_union(out,lemp,plineno,mhflag)
+FILE *out;                  /* The output stream */
+struct lemon *lemp;         /* The main info structure for this parser */
+int *plineno;               /* Pointer to the line number */
+int mhflag;                 /* True if generating makeheaders output */
+{
+  int lineno = *plineno;    /* The line number of the output */
+  char **types;             /* A hash table of datatypes */
+  int arraysize;            /* Size of the "types" array */
+  int maxdtlength;          /* Maximum length of any ".datatype" field. */
+  char *stddt;              /* Standardized name for a datatype */
+  int i,j;                  /* Loop counters */
+  int hash;                 /* For hashing the name of a type */
+  char *name;               /* Name of the parser */
+
+  /* Allocate and initialize types[] and allocate stddt[] */
+  arraysize = lemp->nsymbol * 2;
+  types = (char**)calloc( arraysize, sizeof(char*) );
+  for(i=0; i<arraysize; i++) types[i] = 0;
+  maxdtlength = 0;
+  if( lemp->vartype ){
+    maxdtlength = lemonStrlen(lemp->vartype);
+  }
+  for(i=0; i<lemp->nsymbol; i++){
+    int len;
+    struct symbol *sp = lemp->symbols[i];
+    if( sp->datatype==0 ) continue;
+    len = lemonStrlen(sp->datatype);
+    if( len>maxdtlength ) maxdtlength = len;
+  }
+  stddt = (char*)malloc( maxdtlength*2 + 1 );
+  if( types==0 || stddt==0 ){
+    fprintf(stderr,"Out of memory.\n");
+    exit(1);
+  }
+
+  /* Build a hash table of datatypes. The ".dtnum" field of each symbol
+  ** is filled in with the hash index plus 1.  A ".dtnum" value of 0 is
+  ** used for terminal symbols.  If there is no %default_type defined then
+  ** 0 is also used as the .dtnum value for nonterminals which do not specify
+  ** a datatype using the %type directive.
+  */
+  for(i=0; i<lemp->nsymbol; i++){
+    struct symbol *sp = lemp->symbols[i];
+    char *cp;
+    if( sp==lemp->errsym ){
+      sp->dtnum = arraysize+1;
+      continue;
+    }
+    if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){
+      sp->dtnum = 0;
+      continue;
+    }
+    cp = sp->datatype;
+    if( cp==0 ) cp = lemp->vartype;
+    j = 0;
+    while( isspace(*cp) ) cp++;
+    while( *cp ) stddt[j++] = *cp++;
+    while( j>0 && isspace(stddt[j-1]) ) j--;
+    stddt[j] = 0;
+    if( lemp->tokentype && strcmp(stddt, lemp->tokentype)==0 ){
+      sp->dtnum = 0;
+      continue;
+    }
+    hash = 0;
+    for(j=0; stddt[j]; j++){
+      hash = hash*53 + stddt[j];
+    }
+    hash = (hash & 0x7fffffff)%arraysize;
+    while( types[hash] ){
+      if( strcmp(types[hash],stddt)==0 ){
+        sp->dtnum = hash + 1;
+        break;
+      }
+      hash++;
+      if( hash>=arraysize ) hash = 0;
+    }
+    if( types[hash]==0 ){
+      sp->dtnum = hash + 1;
+      types[hash] = (char*)malloc( lemonStrlen(stddt)+1 );
+      if( types[hash]==0 ){
+        fprintf(stderr,"Out of memory.\n");
+        exit(1);
+      }
+      strcpy(types[hash],stddt);
+    }
+  }
+
+  /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */
+  name = lemp->name ? lemp->name : "Parse";
+  lineno = *plineno;
+  if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; }
+  fprintf(out,"#define %sTOKENTYPE %s\n",name,
+    lemp->tokentype?lemp->tokentype:"void*");  lineno++;
+  if( mhflag ){ fprintf(out,"#endif\n"); lineno++; }
+  fprintf(out,"typedef union {\n"); lineno++;
+  fprintf(out,"  int yyinit;\n"); lineno++;
+  fprintf(out,"  %sTOKENTYPE yy0;\n",name); lineno++;
+  for(i=0; i<arraysize; i++){
+    if( types[i]==0 ) continue;
+    fprintf(out,"  %s yy%d;\n",types[i],i+1); lineno++;
+    free(types[i]);
+  }
+  if( lemp->errsym->useCnt ){
+    fprintf(out,"  int yy%d;\n",lemp->errsym->dtnum); lineno++;
+  }
+  free(stddt);
+  free(types);
+  fprintf(out,"} YYMINORTYPE;\n"); lineno++;
+  *plineno = lineno;
+}
+
+/*
+** Return the name of a C datatype able to represent values between
+** lwr and upr, inclusive.
+*/
+static const char *minimum_size_type(int lwr, int upr){
+  if( lwr>=0 ){
+    if( upr<=255 ){
+      return "unsigned char";
+    }else if( upr<65535 ){
+      return "unsigned short int";
+    }else{
+      return "unsigned int";
+    }
+  }else if( lwr>=-127 && upr<=127 ){
+    return "signed char";
+  }else if( lwr>=-32767 && upr<32767 ){
+    return "short";
+  }else{
+    return "int";
+  }
+}
+
+/*
+** Each state contains a set of token transaction and a set of
+** nonterminal transactions.  Each of these sets makes an instance
+** of the following structure.  An array of these structures is used
+** to order the creation of entries in the yy_action[] table.
+*/
+struct axset {
+  struct state *stp;   /* A pointer to a state */
+  int isTkn;           /* True to use tokens.  False for non-terminals */
+  int nAction;         /* Number of actions */
+};
+
+/*
+** Compare to axset structures for sorting purposes
+*/
+static int axset_compare(const void *a, const void *b){
+  struct axset *p1 = (struct axset*)a;
+  struct axset *p2 = (struct axset*)b;
+  return p2->nAction - p1->nAction;
+}
+
+/*
+** Write text on "out" that describes the rule "rp".
+*/
+static void writeRuleText(FILE *out, struct rule *rp){
+  int j;
+  fprintf(out,"%s ::=", rp->lhs->name);
+  for(j=0; j<rp->nrhs; j++){
+    struct symbol *sp = rp->rhs[j];
+    fprintf(out," %s", sp->name);
+    if( sp->type==MULTITERMINAL ){
+      int k;
+      for(k=1; k<sp->nsubsym; k++){
+        fprintf(out,"|%s",sp->subsym[k]->name);
+      }
+    }
+  }
+}
+
+
+/* Generate C source code for the parser */
+void ReportTable(lemp, mhflag)
+struct lemon *lemp;
+int mhflag;     /* Output in makeheaders format if true */
+{
+  FILE *out, *in;
+  char line[LINESIZE];
+  int  lineno;
+  struct state *stp;
+  struct action *ap;
+  struct rule *rp;
+  struct acttab *pActtab;
+  int i, j, n;
+  char *name;
+  int mnTknOfst, mxTknOfst;
+  int mnNtOfst, mxNtOfst;
+  struct axset *ax;
+
+  in = tplt_open(lemp);
+  if( in==0 ) return;
+  out = file_open(lemp,".c","wb");
+  if( out==0 ){
+    fclose(in);
+    return;
+  }
+  lineno = 1;
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the include code, if any */
+  tplt_print(out,lemp,lemp->include,&lineno);
+  if( mhflag ){
+    char *name = file_makename(lemp, ".h");
+    fprintf(out,"#include \"%s\"\n", name); lineno++;
+    free(name);
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate #defines for all tokens */
+  if( mhflag ){
+    char *prefix;
+    fprintf(out,"#if INTERFACE\n"); lineno++;
+    if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+    else                    prefix = "";
+    for(i=1; i<lemp->nterminal; i++){
+      fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+      lineno++;
+    }
+    fprintf(out,"#endif\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the defines */
+  fprintf(out,"#define YYCODETYPE %s\n",
+    minimum_size_type(0, lemp->nsymbol+1)); lineno++;
+  fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1);  lineno++;
+  fprintf(out,"#define YYACTIONTYPE %s\n",
+    minimum_size_type(0, lemp->nstate+lemp->nrule+5));  lineno++;
+  if( lemp->wildcard ){
+    fprintf(out,"#define YYWILDCARD %d\n",
+       lemp->wildcard->index); lineno++;
+  }
+  print_stack_union(out,lemp,&lineno,mhflag);
+  fprintf(out, "#ifndef YYSTACKDEPTH\n"); lineno++;
+  if( lemp->stacksize ){
+    fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize);  lineno++;
+  }else{
+    fprintf(out,"#define YYSTACKDEPTH 100\n");  lineno++;
+  }
+  fprintf(out, "#endif\n"); lineno++;
+  if( mhflag ){
+    fprintf(out,"#if INTERFACE\n"); lineno++;
+  }
+  name = lemp->name ? lemp->name : "Parse";
+  if( lemp->arg && lemp->arg[0] ){
+    int i;
+    i = lemonStrlen(lemp->arg);
+    while( i>=1 && isspace(lemp->arg[i-1]) ) i--;
+    while( i>=1 && (isalnum(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--;
+    fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg);  lineno++;
+    fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg);  lineno++;
+    fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n",
+                 name,lemp->arg,&lemp->arg[i]);  lineno++;
+    fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n",
+                 name,&lemp->arg[i],&lemp->arg[i]);  lineno++;
+  }else{
+    fprintf(out,"#define %sARG_SDECL\n",name);  lineno++;
+    fprintf(out,"#define %sARG_PDECL\n",name);  lineno++;
+    fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
+    fprintf(out,"#define %sARG_STORE\n",name); lineno++;
+  }
+  if( mhflag ){
+    fprintf(out,"#endif\n"); lineno++;
+  }
+  fprintf(out,"#define YYNSTATE %d\n",lemp->nstate);  lineno++;
+  fprintf(out,"#define YYNRULE %d\n",lemp->nrule);  lineno++;
+  if( lemp->errsym->useCnt ){
+    fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index);  lineno++;
+    fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum);  lineno++;
+  }
+  if( lemp->has_fallback ){
+    fprintf(out,"#define YYFALLBACK 1\n");  lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the action table and its associates:
+  **
+  **  yy_action[]        A single table containing all actions.
+  **  yy_lookahead[]     A table containing the lookahead for each entry in
+  **                     yy_action.  Used to detect hash collisions.
+  **  yy_shift_ofst[]    For each state, the offset into yy_action for
+  **                     shifting terminals.
+  **  yy_reduce_ofst[]   For each state, the offset into yy_action for
+  **                     shifting non-terminals after a reduce.
+  **  yy_default[]       Default action for each state.
+  */
+
+  /* Compute the actions on all states and count them up */
+  ax = calloc(lemp->nstate*2, sizeof(ax[0]));
+  if( ax==0 ){
+    fprintf(stderr,"malloc failed\n");
+    exit(1);
+  }
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    ax[i*2].stp = stp;
+    ax[i*2].isTkn = 1;
+    ax[i*2].nAction = stp->nTknAct;
+    ax[i*2+1].stp = stp;
+    ax[i*2+1].isTkn = 0;
+    ax[i*2+1].nAction = stp->nNtAct;
+  }
+  mxTknOfst = mnTknOfst = 0;
+  mxNtOfst = mnNtOfst = 0;
+
+  /* Compute the action table.  In order to try to keep the size of the
+  ** action table to a minimum, the heuristic of placing the largest action
+  ** sets first is used.
+  */
+  qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare);
+  pActtab = acttab_alloc();
+  for(i=0; i<lemp->nstate*2 && ax[i].nAction>0; i++){
+    stp = ax[i].stp;
+    if( ax[i].isTkn ){
+      for(ap=stp->ap; ap; ap=ap->next){
+        int action;
+        if( ap->sp->index>=lemp->nterminal ) continue;
+        action = compute_action(lemp, ap);
+        if( action<0 ) continue;
+        acttab_action(pActtab, ap->sp->index, action);
+      }
+      stp->iTknOfst = acttab_insert(pActtab);
+      if( stp->iTknOfst<mnTknOfst ) mnTknOfst = stp->iTknOfst;
+      if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst;
+    }else{
+      for(ap=stp->ap; ap; ap=ap->next){
+        int action;
+        if( ap->sp->index<lemp->nterminal ) continue;
+        if( ap->sp->index==lemp->nsymbol ) continue;
+        action = compute_action(lemp, ap);
+        if( action<0 ) continue;
+        acttab_action(pActtab, ap->sp->index, action);
+      }
+      stp->iNtOfst = acttab_insert(pActtab);
+      if( stp->iNtOfst<mnNtOfst ) mnNtOfst = stp->iNtOfst;
+      if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst;
+    }
+  }
+  free(ax);
+
+  /* Output the yy_action table */
+  fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++;
+  n = acttab_size(pActtab);
+  for(i=j=0; i<n; i++){
+    int action = acttab_yyaction(pActtab, i);
+    if( action<0 ) action = lemp->nstate + lemp->nrule + 2;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", action);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the yy_lookahead table */
+  fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++;
+  for(i=j=0; i<n; i++){
+    int la = acttab_yylookahead(pActtab, i);
+    if( la<0 ) la = lemp->nsymbol;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", la);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the yy_shift_ofst[] table */
+  fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", mnTknOfst-1); lineno++;
+  n = lemp->nstate;
+  while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--;
+  fprintf(out, "#define YY_SHIFT_MAX %d\n", n-1); lineno++;
+  fprintf(out, "static const %s yy_shift_ofst[] = {\n", 
+          minimum_size_type(mnTknOfst-1, mxTknOfst)); lineno++;
+  for(i=j=0; i<n; i++){
+    int ofst;
+    stp = lemp->sorted[i];
+    ofst = stp->iTknOfst;
+    if( ofst==NO_OFFSET ) ofst = mnTknOfst - 1;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", ofst);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the yy_reduce_ofst[] table */
+  fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++;
+  n = lemp->nstate;
+  while( n>0 && lemp->sorted[n-1]->iNtOfst==NO_OFFSET ) n--;
+  fprintf(out, "#define YY_REDUCE_MAX %d\n", n-1); lineno++;
+  fprintf(out, "static const %s yy_reduce_ofst[] = {\n", 
+          minimum_size_type(mnNtOfst-1, mxNtOfst)); lineno++;
+  for(i=j=0; i<n; i++){
+    int ofst;
+    stp = lemp->sorted[i];
+    ofst = stp->iNtOfst;
+    if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", ofst);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the default action table */
+  fprintf(out, "static const YYACTIONTYPE yy_default[] = {\n"); lineno++;
+  n = lemp->nstate;
+  for(i=j=0; i<n; i++){
+    stp = lemp->sorted[i];
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", stp->iDflt);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the table of fallback tokens.
+  */
+  if( lemp->has_fallback ){
+    int mx = lemp->nterminal - 1;
+    while( mx>0 && lemp->symbols[mx]->fallback==0 ){ mx--; }
+    for(i=0; i<=mx; i++){
+      struct symbol *p = lemp->symbols[i];
+      if( p->fallback==0 ){
+        fprintf(out, "    0,  /* %10s => nothing */\n", p->name);
+      }else{
+        fprintf(out, "  %3d,  /* %10s => %s */\n", p->fallback->index,
+          p->name, p->fallback->name);
+      }
+      lineno++;
+    }
+  }
+  tplt_xfer(lemp->name, in, out, &lineno);
+
+  /* Generate a table containing the symbolic name of every symbol
+  */
+  for(i=0; i<lemp->nsymbol; i++){
+    sprintf(line,"\"%s\",",lemp->symbols[i]->name);
+    fprintf(out,"  %-15s",line);
+    if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; }
+  }
+  if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate a table containing a text string that describes every
+  ** rule in the rule set of the grammar.  This information is used
+  ** when tracing REDUCE actions.
+  */
+  for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+    assert( rp->index==i );
+    fprintf(out," /* %3d */ \"", i);
+    writeRuleText(out, rp);
+    fprintf(out,"\",\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes every time a symbol is popped from
+  ** the stack while processing errors or while destroying the parser. 
+  ** (In other words, generate the %destructor actions)
+  */
+  if( lemp->tokendest ){
+    int once = 1;
+    for(i=0; i<lemp->nsymbol; i++){
+      struct symbol *sp = lemp->symbols[i];
+      if( sp==0 || sp->type!=TERMINAL ) continue;
+      if( once ){
+        fprintf(out, "      /* TERMINAL Destructor */\n"); lineno++;
+        once = 0;
+      }
+      fprintf(out,"    case %d: /* %s */\n", sp->index, sp->name); lineno++;
+    }
+    for(i=0; i<lemp->nsymbol && lemp->symbols[i]->type!=TERMINAL; i++);
+    if( i<lemp->nsymbol ){
+      emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+      fprintf(out,"      break;\n"); lineno++;
+    }
+  }
+  if( lemp->vardest ){
+    struct symbol *dflt_sp = 0;
+    int once = 1;
+    for(i=0; i<lemp->nsymbol; i++){
+      struct symbol *sp = lemp->symbols[i];
+      if( sp==0 || sp->type==TERMINAL ||
+          sp->index<=0 || sp->destructor!=0 ) continue;
+      if( once ){
+        fprintf(out, "      /* Default NON-TERMINAL Destructor */\n"); lineno++;
+        once = 0;
+      }
+      fprintf(out,"    case %d: /* %s */\n", sp->index, sp->name); lineno++;
+      dflt_sp = sp;
+    }
+    if( dflt_sp!=0 ){
+      emit_destructor_code(out,dflt_sp,lemp,&lineno);
+    }
+    fprintf(out,"      break;\n"); lineno++;
+  }
+  for(i=0; i<lemp->nsymbol; i++){
+    struct symbol *sp = lemp->symbols[i];
+    if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue;
+    fprintf(out,"    case %d: /* %s */\n", sp->index, sp->name); lineno++;
+
+    /* Combine duplicate destructors into a single case */
+    for(j=i+1; j<lemp->nsymbol; j++){
+      struct symbol *sp2 = lemp->symbols[j];
+      if( sp2 && sp2->type!=TERMINAL && sp2->destructor
+          && sp2->dtnum==sp->dtnum
+          && strcmp(sp->destructor,sp2->destructor)==0 ){
+         fprintf(out,"    case %d: /* %s */\n",
+                 sp2->index, sp2->name); lineno++;
+         sp2->destructor = 0;
+      }
+    }
+
+    emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+    fprintf(out,"      break;\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes whenever the parser stack overflows */
+  tplt_print(out,lemp,lemp->overflow,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the table of rule information 
+  **
+  ** Note: This code depends on the fact that rules are number
+  ** sequentually beginning with 0.
+  */
+  for(rp=lemp->rule; rp; rp=rp->next){
+    fprintf(out,"  { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which execution during each REDUCE action */
+  for(rp=lemp->rule; rp; rp=rp->next){
+    translate_code(lemp, rp);
+  }
+  /* First output rules other than the default: rule */
+  for(rp=lemp->rule; rp; rp=rp->next){
+    struct rule *rp2;               /* Other rules with the same action */
+    if( rp->code==0 ) continue;
+    if( rp->code[0]=='\n' && rp->code[1]==0 ) continue; /* Will be default: */
+    fprintf(out,"      case %d: /* ", rp->index);
+    writeRuleText(out, rp);
+    fprintf(out, " */\n"); lineno++;
+    for(rp2=rp->next; rp2; rp2=rp2->next){
+      if( rp2->code==rp->code ){
+        fprintf(out,"      case %d: /* ", rp2->index);
+        writeRuleText(out, rp2);
+        fprintf(out," */ yytestcase(yyruleno==%d);\n", rp2->index); lineno++;
+        rp2->code = 0;
+      }
+    }
+    emit_code(out,rp,lemp,&lineno);
+    fprintf(out,"        break;\n"); lineno++;
+    rp->code = 0;
+  }
+  /* Finally, output the default: rule.  We choose as the default: all
+  ** empty actions. */
+  fprintf(out,"      default:\n"); lineno++;
+  for(rp=lemp->rule; rp; rp=rp->next){
+    if( rp->code==0 ) continue;
+    assert( rp->code[0]=='\n' && rp->code[1]==0 );
+    fprintf(out,"      /* (%d) ", rp->index);
+    writeRuleText(out, rp);
+    fprintf(out, " */ yytestcase(yyruleno==%d);\n", rp->index); lineno++;
+  }
+  fprintf(out,"        break;\n"); lineno++;
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes if a parse fails */
+  tplt_print(out,lemp,lemp->failure,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes when a syntax error occurs */
+  tplt_print(out,lemp,lemp->error,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes when the parser accepts its input */
+  tplt_print(out,lemp,lemp->accept,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Append any addition code the user desires */
+  tplt_print(out,lemp,lemp->extracode,&lineno);
+
+  fclose(in);
+  fclose(out);
+  return;
+}
+
+/* Generate a header file for the parser */
+void ReportHeader(lemp)
+struct lemon *lemp;
+{
+  FILE *out, *in;
+  char *prefix;
+  char line[LINESIZE];
+  char pattern[LINESIZE];
+  int i;
+
+  if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+  else                    prefix = "";
+  in = file_open(lemp,".h","rb");
+  if( in ){
+    for(i=1; i<lemp->nterminal && fgets(line,LINESIZE,in); i++){
+      sprintf(pattern,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+      if( strcmp(line,pattern) ) break;
+    }
+    fclose(in);
+    if( i==lemp->nterminal ){
+      /* No change in the file.  Don't rewrite it. */
+      return;
+    }
+  }
+  out = file_open(lemp,".h","wb");
+  if( out ){
+    for(i=1; i<lemp->nterminal; i++){
+      fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+    }
+    fclose(out);  
+  }
+  return;
+}
+
+/* Reduce the size of the action tables, if possible, by making use
+** of defaults.
+**
+** In this version, we take the most frequent REDUCE action and make
+** it the default.  Except, there is no default if the wildcard token
+** is a possible look-ahead.
+*/
+void CompressTables(lemp)
+struct lemon *lemp;
+{
+  struct state *stp;
+  struct action *ap, *ap2;
+  struct rule *rp, *rp2, *rbest;
+  int nbest, n;
+  int i;
+  int usesWildcard;
+
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    nbest = 0;
+    rbest = 0;
+    usesWildcard = 0;
+
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( ap->type==SHIFT && ap->sp==lemp->wildcard ){
+        usesWildcard = 1;
+      }
+      if( ap->type!=REDUCE ) continue;
+      rp = ap->x.rp;
+      if( rp->lhsStart ) continue;
+      if( rp==rbest ) continue;
+      n = 1;
+      for(ap2=ap->next; ap2; ap2=ap2->next){
+        if( ap2->type!=REDUCE ) continue;
+        rp2 = ap2->x.rp;
+        if( rp2==rbest ) continue;
+        if( rp2==rp ) n++;
+      }
+      if( n>nbest ){
+        nbest = n;
+        rbest = rp;
+      }
+    }
+ 
+    /* Do not make a default if the number of rules to default
+    ** is not at least 1 or if the wildcard token is a possible
+    ** lookahead.
+    */
+    if( nbest<1 || usesWildcard ) continue;
+
+
+    /* Combine matching REDUCE actions into a single default */
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( ap->type==REDUCE && ap->x.rp==rbest ) break;
+    }
+    assert( ap );
+    ap->sp = Symbol_new("{default}");
+    for(ap=ap->next; ap; ap=ap->next){
+      if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED;
+    }
+    stp->ap = Action_sort(stp->ap);
+  }
+}
+
+
+/*
+** Compare two states for sorting purposes.  The smaller state is the
+** one with the most non-terminal actions.  If they have the same number
+** of non-terminal actions, then the smaller is the one with the most
+** token actions.
+*/
+static int stateResortCompare(const void *a, const void *b){
+  const struct state *pA = *(const struct state**)a;
+  const struct state *pB = *(const struct state**)b;
+  int n;
+
+  n = pB->nNtAct - pA->nNtAct;
+  if( n==0 ){
+    n = pB->nTknAct - pA->nTknAct;
+  }
+  return n;
+}
+
+
+/*
+** Renumber and resort states so that states with fewer choices
+** occur at the end.  Except, keep state 0 as the first state.
+*/
+void ResortStates(lemp)
+struct lemon *lemp;
+{
+  int i;
+  struct state *stp;
+  struct action *ap;
+
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    stp->nTknAct = stp->nNtAct = 0;
+    stp->iDflt = lemp->nstate + lemp->nrule;
+    stp->iTknOfst = NO_OFFSET;
+    stp->iNtOfst = NO_OFFSET;
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( compute_action(lemp,ap)>=0 ){
+        if( ap->sp->index<lemp->nterminal ){
+          stp->nTknAct++;
+        }else if( ap->sp->index<lemp->nsymbol ){
+          stp->nNtAct++;
+        }else{
+          stp->iDflt = compute_action(lemp, ap);
+        }
+      }
+    }
+  }
+  qsort(&lemp->sorted[1], lemp->nstate-1, sizeof(lemp->sorted[0]),
+        stateResortCompare);
+  for(i=0; i<lemp->nstate; i++){
+    lemp->sorted[i]->statenum = i;
+  }
+}
+
+
+/***************** From the file "set.c" ************************************/
+/*
+** Set manipulation routines for the LEMON parser generator.
+*/
+
+static int size = 0;
+
+/* Set the set size */
+void SetSize(n)
+int n;
+{
+  size = n+1;
+}
+
+/* Allocate a new set */
+char *SetNew(){
+  char *s;
+  s = (char*)calloc( size, 1);
+  if( s==0 ){
+    extern void memory_error();
+    memory_error();
+  }
+  return s;
+}
+
+/* Deallocate a set */
+void SetFree(s)
+char *s;
+{
+  free(s);
+}
+
+/* Add a new element to the set.  Return TRUE if the element was added
+** and FALSE if it was already there. */
+int SetAdd(s,e)
+char *s;
+int e;
+{
+  int rv;
+  assert( e>=0 && e<size );
+  rv = s[e];
+  s[e] = 1;
+  return !rv;
+}
+
+/* Add every element of s2 to s1.  Return TRUE if s1 changes. */
+int SetUnion(s1,s2)
+char *s1;
+char *s2;
+{
+  int i, progress;
+  progress = 0;
+  for(i=0; i<size; i++){
+    if( s2[i]==0 ) continue;
+    if( s1[i]==0 ){
+      progress = 1;
+      s1[i] = 1;
+    }
+  }
+  return progress;
+}
+/********************** From the file "table.c" ****************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+**              "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file!  Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+PRIVATE int strhash(x)
+char *x;
+{
+  int h = 0;
+  while( *x) h = h*13 + *(x++);
+  return h;
+}
+
+/* Works like strdup, sort of.  Save a string in malloced memory, but
+** keep strings in a table so that the same string is not in more
+** than one place.
+*/
+char *Strsafe(y)
+char *y;
+{
+  char *z;
+
+  if( y==0 ) return 0;
+  z = Strsafe_find(y);
+  if( z==0 && (z=malloc( lemonStrlen(y)+1 ))!=0 ){
+    strcpy(z,y);
+    Strsafe_insert(z);
+  }
+  MemoryCheck(z);
+  return z;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x1".
+*/
+struct s_x1 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x1node *tbl;  /* The data stored here */
+  struct s_x1node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x1".
+*/
+typedef struct s_x1node {
+  char *data;                  /* The data */
+  struct s_x1node *next;   /* Next entry with the same hash */
+  struct s_x1node **from;  /* Previous link */
+} x1node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x1 *x1a;
+
+/* Allocate a new associative array */
+void Strsafe_init(){
+  if( x1a ) return;
+  x1a = (struct s_x1*)malloc( sizeof(struct s_x1) );
+  if( x1a ){
+    x1a->size = 1024;
+    x1a->count = 0;
+    x1a->tbl = (x1node*)malloc( 
+      (sizeof(x1node) + sizeof(x1node*))*1024 );
+    if( x1a->tbl==0 ){
+      free(x1a);
+      x1a = 0;
+    }else{
+      int i;
+      x1a->ht = (x1node**)&(x1a->tbl[1024]);
+      for(i=0; i<1024; i++) x1a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Strsafe_insert(data)
+char *data;
+{
+  x1node *np;
+  int h;
+  int ph;
+
+  if( x1a==0 ) return 0;
+  ph = strhash(data);
+  h = ph & (x1a->size-1);
+  np = x1a->ht[h];
+  while( np ){
+    if( strcmp(np->data,data)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x1a->count>=x1a->size ){
+    /* Need to make the hash table bigger */
+    int i,size;
+    struct s_x1 array;
+    array.size = size = x1a->size*2;
+    array.count = x1a->count;
+    array.tbl = (x1node*)malloc(
+      (sizeof(x1node) + sizeof(x1node*))*size );
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x1node**)&(array.tbl[size]);
+    for(i=0; i<size; i++) array.ht[i] = 0;
+    for(i=0; i<x1a->count; i++){
+      x1node *oldnp, *newnp;
+      oldnp = &(x1a->tbl[i]);
+      h = strhash(oldnp->data) & (size-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    free(x1a->tbl);
+    *x1a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x1a->size-1);
+  np = &(x1a->tbl[x1a->count++]);
+  np->data = data;
+  if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next);
+  np->next = x1a->ht[h];
+  x1a->ht[h] = np;
+  np->from = &(x1a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+char *Strsafe_find(key)
+char *key;
+{
+  int h;
+  x1node *np;
+
+  if( x1a==0 ) return 0;
+  h = strhash(key) & (x1a->size-1);
+  np = x1a->ht[h];
+  while( np ){
+    if( strcmp(np->data,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Return a pointer to the (terminal or nonterminal) symbol "x".
+** Create a new symbol if this is the first time "x" has been seen.
+*/
+struct symbol *Symbol_new(x)
+char *x;
+{
+  struct symbol *sp;
+
+  sp = Symbol_find(x);
+  if( sp==0 ){
+    sp = (struct symbol *)calloc(1, sizeof(struct symbol) );
+    MemoryCheck(sp);
+    sp->name = Strsafe(x);
+    sp->type = isupper(*x) ? TERMINAL : NONTERMINAL;
+    sp->rule = 0;
+    sp->fallback = 0;
+    sp->prec = -1;
+    sp->assoc = UNK;
+    sp->firstset = 0;
+    sp->lambda = LEMON_FALSE;
+    sp->destructor = 0;
+    sp->destLineno = 0;
+    sp->datatype = 0;
+    sp->useCnt = 0;
+    Symbol_insert(sp,sp->name);
+  }
+  sp->useCnt++;
+  return sp;
+}
+
+/* Compare two symbols for working purposes
+**
+** Symbols that begin with upper case letters (terminals or tokens)
+** must sort before symbols that begin with lower case letters
+** (non-terminals).  Other than that, the order does not matter.
+**
+** We find experimentally that leaving the symbols in their original
+** order (the order they appeared in the grammar file) gives the
+** smallest parser tables in SQLite.
+*/
+int Symbolcmpp(struct symbol **a, struct symbol **b){
+  int i1 = (**a).index + 10000000*((**a).name[0]>'Z');
+  int i2 = (**b).index + 10000000*((**b).name[0]>'Z');
+  return i1-i2;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x2".
+*/
+struct s_x2 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x2node *tbl;  /* The data stored here */
+  struct s_x2node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x2".
+*/
+typedef struct s_x2node {
+  struct symbol *data;                  /* The data */
+  char *key;                   /* The key */
+  struct s_x2node *next;   /* Next entry with the same hash */
+  struct s_x2node **from;  /* Previous link */
+} x2node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x2 *x2a;
+
+/* Allocate a new associative array */
+void Symbol_init(){
+  if( x2a ) return;
+  x2a = (struct s_x2*)malloc( sizeof(struct s_x2) );
+  if( x2a ){
+    x2a->size = 128;
+    x2a->count = 0;
+    x2a->tbl = (x2node*)malloc( 
+      (sizeof(x2node) + sizeof(x2node*))*128 );
+    if( x2a->tbl==0 ){
+      free(x2a);
+      x2a = 0;
+    }else{
+      int i;
+      x2a->ht = (x2node**)&(x2a->tbl[128]);
+      for(i=0; i<128; i++) x2a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Symbol_insert(data,key)
+struct symbol *data;
+char *key;
+{
+  x2node *np;
+  int h;
+  int ph;
+
+  if( x2a==0 ) return 0;
+  ph = strhash(key);
+  h = ph & (x2a->size-1);
+  np = x2a->ht[h];
+  while( np ){
+    if( strcmp(np->key,key)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x2a->count>=x2a->size ){
+    /* Need to make the hash table bigger */
+    int i,size;
+    struct s_x2 array;
+    array.size = size = x2a->size*2;
+    array.count = x2a->count;
+    array.tbl = (x2node*)malloc(
+      (sizeof(x2node) + sizeof(x2node*))*size );
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x2node**)&(array.tbl[size]);
+    for(i=0; i<size; i++) array.ht[i] = 0;
+    for(i=0; i<x2a->count; i++){
+      x2node *oldnp, *newnp;
+      oldnp = &(x2a->tbl[i]);
+      h = strhash(oldnp->key) & (size-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->key = oldnp->key;
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    free(x2a->tbl);
+    *x2a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x2a->size-1);
+  np = &(x2a->tbl[x2a->count++]);
+  np->key = key;
+  np->data = data;
+  if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next);
+  np->next = x2a->ht[h];
+  x2a->ht[h] = np;
+  np->from = &(x2a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+struct symbol *Symbol_find(key)
+char *key;
+{
+  int h;
+  x2node *np;
+
+  if( x2a==0 ) return 0;
+  h = strhash(key) & (x2a->size-1);
+  np = x2a->ht[h];
+  while( np ){
+    if( strcmp(np->key,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Return the n-th data.  Return NULL if n is out of range. */
+struct symbol *Symbol_Nth(n)
+int n;
+{
+  struct symbol *data;
+  if( x2a && n>0 && n<=x2a->count ){
+    data = x2a->tbl[n-1].data;
+  }else{
+    data = 0;
+  }
+  return data;
+}
+
+/* Return the size of the array */
+int Symbol_count()
+{
+  return x2a ? x2a->count : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc.  Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct symbol **Symbol_arrayof()
+{
+  struct symbol **array;
+  int i,size;
+  if( x2a==0 ) return 0;
+  size = x2a->count;
+  array = (struct symbol **)calloc(size, sizeof(struct symbol *));
+  if( array ){
+    for(i=0; i<size; i++) array[i] = x2a->tbl[i].data;
+  }
+  return array;
+}
+
+/* Compare two configurations */
+int Configcmp(a,b)
+struct config *a;
+struct config *b;
+{
+  int x;
+  x = a->rp->index - b->rp->index;
+  if( x==0 ) x = a->dot - b->dot;
+  return x;
+}
+
+/* Compare two states */
+PRIVATE int statecmp(a,b)
+struct config *a;
+struct config *b;
+{
+  int rc;
+  for(rc=0; rc==0 && a && b;  a=a->bp, b=b->bp){
+    rc = a->rp->index - b->rp->index;
+    if( rc==0 ) rc = a->dot - b->dot;
+  }
+  if( rc==0 ){
+    if( a ) rc = 1;
+    if( b ) rc = -1;
+  }
+  return rc;
+}
+
+/* Hash a state */
+PRIVATE int statehash(a)
+struct config *a;
+{
+  int h=0;
+  while( a ){
+    h = h*571 + a->rp->index*37 + a->dot;
+    a = a->bp;
+  }
+  return h;
+}
+
+/* Allocate a new state structure */
+struct state *State_new()
+{
+  struct state *new;
+  new = (struct state *)calloc(1, sizeof(struct state) );
+  MemoryCheck(new);
+  return new;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x3".
+*/
+struct s_x3 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x3node *tbl;  /* The data stored here */
+  struct s_x3node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x3".
+*/
+typedef struct s_x3node {
+  struct state *data;                  /* The data */
+  struct config *key;                   /* The key */
+  struct s_x3node *next;   /* Next entry with the same hash */
+  struct s_x3node **from;  /* Previous link */
+} x3node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x3 *x3a;
+
+/* Allocate a new associative array */
+void State_init(){
+  if( x3a ) return;
+  x3a = (struct s_x3*)malloc( sizeof(struct s_x3) );
+  if( x3a ){
+    x3a->size = 128;
+    x3a->count = 0;
+    x3a->tbl = (x3node*)malloc( 
+      (sizeof(x3node) + sizeof(x3node*))*128 );
+    if( x3a->tbl==0 ){
+      free(x3a);
+      x3a = 0;
+    }else{
+      int i;
+      x3a->ht = (x3node**)&(x3a->tbl[128]);
+      for(i=0; i<128; i++) x3a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int State_insert(data,key)
+struct state *data;
+struct config *key;
+{
+  x3node *np;
+  int h;
+  int ph;
+
+  if( x3a==0 ) return 0;
+  ph = statehash(key);
+  h = ph & (x3a->size-1);
+  np = x3a->ht[h];
+  while( np ){
+    if( statecmp(np->key,key)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x3a->count>=x3a->size ){
+    /* Need to make the hash table bigger */
+    int i,size;
+    struct s_x3 array;
+    array.size = size = x3a->size*2;
+    array.count = x3a->count;
+    array.tbl = (x3node*)malloc(
+      (sizeof(x3node) + sizeof(x3node*))*size );
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x3node**)&(array.tbl[size]);
+    for(i=0; i<size; i++) array.ht[i] = 0;
+    for(i=0; i<x3a->count; i++){
+      x3node *oldnp, *newnp;
+      oldnp = &(x3a->tbl[i]);
+      h = statehash(oldnp->key) & (size-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->key = oldnp->key;
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    free(x3a->tbl);
+    *x3a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x3a->size-1);
+  np = &(x3a->tbl[x3a->count++]);
+  np->key = key;
+  np->data = data;
+  if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next);
+  np->next = x3a->ht[h];
+  x3a->ht[h] = np;
+  np->from = &(x3a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+struct state *State_find(key)
+struct config *key;
+{
+  int h;
+  x3node *np;
+
+  if( x3a==0 ) return 0;
+  h = statehash(key) & (x3a->size-1);
+  np = x3a->ht[h];
+  while( np ){
+    if( statecmp(np->key,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc.  Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct state **State_arrayof()
+{
+  struct state **array;
+  int i,size;
+  if( x3a==0 ) return 0;
+  size = x3a->count;
+  array = (struct state **)malloc( sizeof(struct state *)*size );
+  if( array ){
+    for(i=0; i<size; i++) array[i] = x3a->tbl[i].data;
+  }
+  return array;
+}
+
+/* Hash a configuration */
+PRIVATE int confighash(a)
+struct config *a;
+{
+  int h=0;
+  h = h*571 + a->rp->index*37 + a->dot;
+  return h;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x4".
+*/
+struct s_x4 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x4node *tbl;  /* The data stored here */
+  struct s_x4node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x4".
+*/
+typedef struct s_x4node {
+  struct config *data;                  /* The data */
+  struct s_x4node *next;   /* Next entry with the same hash */
+  struct s_x4node **from;  /* Previous link */
+} x4node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x4 *x4a;
+
+/* Allocate a new associative array */
+void Configtable_init(){
+  if( x4a ) return;
+  x4a = (struct s_x4*)malloc( sizeof(struct s_x4) );
+  if( x4a ){
+    x4a->size = 64;
+    x4a->count = 0;
+    x4a->tbl = (x4node*)malloc( 
+      (sizeof(x4node) + sizeof(x4node*))*64 );
+    if( x4a->tbl==0 ){
+      free(x4a);
+      x4a = 0;
+    }else{
+      int i;
+      x4a->ht = (x4node**)&(x4a->tbl[64]);
+      for(i=0; i<64; i++) x4a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Configtable_insert(data)
+struct config *data;
+{
+  x4node *np;
+  int h;
+  int ph;
+
+  if( x4a==0 ) return 0;
+  ph = confighash(data);
+  h = ph & (x4a->size-1);
+  np = x4a->ht[h];
+  while( np ){
+    if( Configcmp(np->data,data)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x4a->count>=x4a->size ){
+    /* Need to make the hash table bigger */
+    int i,size;
+    struct s_x4 array;
+    array.size = size = x4a->size*2;
+    array.count = x4a->count;
+    array.tbl = (x4node*)malloc(
+      (sizeof(x4node) + sizeof(x4node*))*size );
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x4node**)&(array.tbl[size]);
+    for(i=0; i<size; i++) array.ht[i] = 0;
+    for(i=0; i<x4a->count; i++){
+      x4node *oldnp, *newnp;
+      oldnp = &(x4a->tbl[i]);
+      h = confighash(oldnp->data) & (size-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    free(x4a->tbl);
+    *x4a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x4a->size-1);
+  np = &(x4a->tbl[x4a->count++]);
+  np->data = data;
+  if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next);
+  np->next = x4a->ht[h];
+  x4a->ht[h] = np;
+  np->from = &(x4a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+struct config *Configtable_find(key)
+struct config *key;
+{
+  int h;
+  x4node *np;
+
+  if( x4a==0 ) return 0;
+  h = confighash(key) & (x4a->size-1);
+  np = x4a->ht[h];
+  while( np ){
+    if( Configcmp(np->data,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Remove all data from the table.  Pass each data to the function "f"
+** as it is removed.  ("f" may be null to avoid this step.) */
+void Configtable_clear(f)
+int(*f)(/* struct config * */);
+{
+  int i;
+  if( x4a==0 || x4a->count==0 ) return;
+  if( f ) for(i=0; i<x4a->count; i++) (*f)(x4a->tbl[i].data);
+  for(i=0; i<x4a->size; i++) x4a->ht[i] = 0;
+  x4a->count = 0;
+  return;
+}
diff --git a/external/badvpn_dns/lemon/lempar.c b/external/badvpn_dns/lemon/lempar.c
new file mode 100644
index 0000000..774b875
--- /dev/null
+++ b/external/badvpn_dns/lemon/lempar.c
@@ -0,0 +1,842 @@
+/* Driver template for the LEMON parser generator.
+** The author disclaims copyright to this source code.
+*/
+/* First off, code is included that follows the "include" declaration
+** in the input grammar file. */
+#include <stdio.h>
+%%
+/* Next is all token values, in a form suitable for use by makeheaders.
+** This section will be null unless lemon is run with the -m switch.
+*/
+/* 
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands. 
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+%%
+/* Make sure the INTERFACE macro is defined.
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+**    YYCODETYPE         is the data type used for storing terminal
+**                       and nonterminal numbers.  "unsigned char" is
+**                       used if there are fewer than 250 terminals
+**                       and nonterminals.  "int" is used otherwise.
+**    YYNOCODE           is a number of type YYCODETYPE which corresponds
+**                       to no legal terminal or nonterminal number.  This
+**                       number is used to fill in empty slots of the hash 
+**                       table.
+**    YYFALLBACK         If defined, this indicates that one or more tokens
+**                       have fall-back values which should be used if the
+**                       original value of the token will not parse.
+**    YYACTIONTYPE       is the data type used for storing terminal
+**                       and nonterminal numbers.  "unsigned char" is
+**                       used if there are fewer than 250 rules and
+**                       states combined.  "int" is used otherwise.
+**    ParseTOKENTYPE     is the data type used for minor tokens given 
+**                       directly to the parser from the tokenizer.
+**    YYMINORTYPE        is the data type used for all minor tokens.
+**                       This is typically a union of many types, one of
+**                       which is ParseTOKENTYPE.  The entry in the union
+**                       for base tokens is called "yy0".
+**    YYSTACKDEPTH       is the maximum depth of the parser's stack.  If
+**                       zero the stack is dynamically sized using realloc()
+**    ParseARG_SDECL     A static variable declaration for the %extra_argument
+**    ParseARG_PDECL     A parameter declaration for the %extra_argument
+**    ParseARG_STORE     Code to store %extra_argument into yypParser
+**    ParseARG_FETCH     Code to extract %extra_argument from yypParser
+**    YYNSTATE           the combined number of states.
+**    YYNRULE            the number of rules in the grammar
+**    YYERRORSYMBOL      is the code number of the error symbol.  If not
+**                       defined, then do no error processing.
+*/
+%%
+#define YY_NO_ACTION      (YYNSTATE+YYNRULE+2)
+#define YY_ACCEPT_ACTION  (YYNSTATE+YYNRULE+1)
+#define YY_ERROR_ACTION   (YYNSTATE+YYNRULE)
+
+/* The yyzerominor constant is used to initialize instances of
+** YYMINORTYPE objects to zero. */
+static const YYMINORTYPE yyzerominor = { 0 };
+
+/* Define the yytestcase() macro to be a no-op if is not already defined
+** otherwise.
+**
+** Applications can choose to define yytestcase() in the %include section
+** to a macro that can assist in verifying code coverage.  For production
+** code the yytestcase() macro should be turned off.  But it is useful
+** for testing.
+*/
+#ifndef yytestcase
+# define yytestcase(X)
+#endif
+
+
+/* Next are the tables used to determine what action to take based on the
+** current state and lookahead token.  These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.  
+**
+** Suppose the action integer is N.  Then the action is determined as
+** follows
+**
+**   0 <= N < YYNSTATE                  Shift N.  That is, push the lookahead
+**                                      token onto the stack and goto state N.
+**
+**   YYNSTATE <= N < YYNSTATE+YYNRULE   Reduce by rule N-YYNSTATE.
+**
+**   N == YYNSTATE+YYNRULE              A syntax error has occurred.
+**
+**   N == YYNSTATE+YYNRULE+1            The parser accepts its input.
+**
+**   N == YYNSTATE+YYNRULE+2            No such action.  Denotes unused
+**                                      slots in the yy_action[] table.
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as
+**
+**      yy_action[ yy_shift_ofst[S] + X ]
+**
+** If the index value yy_shift_ofst[S]+X is out of range or if the value
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
+** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
+** and that yy_default[S] should be used instead.  
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol.  If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
+** YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+**  yy_action[]        A single table containing all actions.
+**  yy_lookahead[]     A table containing the lookahead for each entry in
+**                     yy_action.  Used to detect hash collisions.
+**  yy_shift_ofst[]    For each state, the offset into yy_action for
+**                     shifting terminals.
+**  yy_reduce_ofst[]   For each state, the offset into yy_action for
+**                     shifting non-terminals after a reduce.
+**  yy_default[]       Default action for each state.
+*/
+%%
+#define YY_SZ_ACTTAB (int)(sizeof(yy_action)/sizeof(yy_action[0]))
+
+/* The next table maps tokens into fallback tokens.  If a construct
+** like the following:
+** 
+**      %fallback ID X Y Z.
+**
+** appears in the grammar, then ID becomes a fallback token for X, Y,
+** and Z.  Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+%%
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack.  Information stored includes:
+**
+**   +  The state number for the parser at this level of the stack.
+**
+**   +  The value of the token stored at this level of the stack.
+**      (In other words, the "major" token.)
+**
+**   +  The semantic value stored at this level of the stack.  This is
+**      the information used by the action routines in the grammar.
+**      It is sometimes called the "minor" token.
+*/
+struct yyStackEntry {
+  YYACTIONTYPE stateno;  /* The state-number */
+  YYCODETYPE major;      /* The major token value.  This is the code
+                         ** number for the token at this stack level */
+  YYMINORTYPE minor;     /* The user-supplied minor token value.  This
+                         ** is the value of the token  */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+  int yyidx;                    /* Index of top element in stack */
+#ifdef YYTRACKMAXSTACKDEPTH
+  int yyidxMax;                 /* Maximum value of yyidx */
+#endif
+  int yyerrcnt;                 /* Shifts left before out of the error */
+  ParseARG_SDECL                /* A place to hold %extra_argument */
+#if YYSTACKDEPTH<=0
+  int yystksz;                  /* Current side of the stack */
+  yyStackEntry *yystack;        /* The parser's stack */
+#else
+  yyStackEntry yystack[YYSTACKDEPTH];  /* The parser's stack */
+#endif
+};
+typedef struct yyParser yyParser;
+
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* 
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message.  Tracing is turned off
+** by making either argument NULL 
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+**      If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+**      line of trace output.  If NULL, then tracing is
+**      turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
+  yyTraceFILE = TraceFILE;
+  yyTracePrompt = zTracePrompt;
+  if( yyTraceFILE==0 ) yyTracePrompt = 0;
+  else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required.  The following table supplies these names */
+static const char *const yyTokenName[] = { 
+%%
+};
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *const yyRuleName[] = {
+%%
+};
+#endif /* NDEBUG */
+
+
+#if YYSTACKDEPTH<=0
+/*
+** Try to increase the size of the parser stack.
+*/
+static void yyGrowStack(yyParser *p){
+  int newSize;
+  yyStackEntry *pNew;
+
+  newSize = p->yystksz*2 + 100;
+  pNew = realloc(p->yystack, newSize*sizeof(pNew[0]));
+  if( pNew ){
+    p->yystack = pNew;
+    p->yystksz = newSize;
+#ifndef NDEBUG
+    if( yyTraceFILE ){
+      fprintf(yyTraceFILE,"%sStack grows to %d entries!\n",
+              yyTracePrompt, p->yystksz);
+    }
+#endif
+  }
+}
+#endif
+
+/* 
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser.  This pointer is used in subsequent calls
+** to Parse and ParseFree.
+*/
+void *ParseAlloc(void *(*mallocProc)(size_t)){
+  yyParser *pParser;
+  pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
+  if( pParser ){
+    pParser->yyidx = -1;
+#ifdef YYTRACKMAXSTACKDEPTH
+    pParser->yyidxMax = 0;
+#endif
+#if YYSTACKDEPTH<=0
+    pParser->yystack = NULL;
+    pParser->yystksz = 0;
+    yyGrowStack(pParser);
+#endif
+  }
+  return pParser;
+}
+
+/* The following function deletes the value associated with a
+** symbol.  The symbol can be either a terminal or nonterminal.
+** "yymajor" is the symbol code, and "yypminor" is a pointer to
+** the value.
+*/
+static void yy_destructor(
+  yyParser *yypParser,    /* The parser */
+  YYCODETYPE yymajor,     /* Type code for object to destroy */
+  YYMINORTYPE *yypminor   /* The object to be destroyed */
+){
+  ParseARG_FETCH;
+  switch( yymajor ){
+    /* Here is inserted the actions which take place when a
+    ** terminal or non-terminal is destroyed.  This can happen
+    ** when the symbol is popped from the stack during a
+    ** reduce or during error processing or when a parser is 
+    ** being destroyed before it is finished parsing.
+    **
+    ** Note: during a reduce, the only symbols destroyed are those
+    ** which appear on the RHS of the rule, but which are not used
+    ** inside the C code.
+    */
+%%
+    default:  break;   /* If no destructor action specified: do nothing */
+  }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+**
+** Return the major token number for the symbol popped.
+*/
+static int yy_pop_parser_stack(yyParser *pParser){
+  YYCODETYPE yymajor;
+  yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
+
+  if( pParser->yyidx<0 ) return 0;
+#ifndef NDEBUG
+  if( yyTraceFILE && pParser->yyidx>=0 ){
+    fprintf(yyTraceFILE,"%sPopping %s\n",
+      yyTracePrompt,
+      yyTokenName[yytos->major]);
+  }
+#endif
+  yymajor = yytos->major;
+  yy_destructor(pParser, yymajor, &yytos->minor);
+  pParser->yyidx--;
+  return yymajor;
+}
+
+/* 
+** Deallocate and destroy a parser.  Destructors are all called for
+** all stack elements before shutting the parser down.
+**
+** Inputs:
+** <ul>
+** <li>  A pointer to the parser.  This should be a pointer
+**       obtained from ParseAlloc.
+** <li>  A pointer to a function used to reclaim memory obtained
+**       from malloc.
+** </ul>
+*/
+void ParseFree(
+  void *p,                    /* The parser to be deleted */
+  void (*freeProc)(void*)     /* Function used to reclaim memory */
+){
+  yyParser *pParser = (yyParser*)p;
+  if( pParser==0 ) return;
+  while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
+#if YYSTACKDEPTH<=0
+  free(pParser->yystack);
+#endif
+  (*freeProc)((void*)pParser);
+}
+
+/*
+** Return the peak depth of the stack for a parser.
+*/
+#ifdef YYTRACKMAXSTACKDEPTH
+int ParseStackPeak(void *p){
+  yyParser *pParser = (yyParser*)p;
+  return pParser->yyidxMax;
+}
+#endif
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead.  If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_shift_action(
+  yyParser *pParser,        /* The parser */
+  YYCODETYPE iLookAhead     /* The look-ahead token */
+){
+  int i;
+  int stateno = pParser->yystack[pParser->yyidx].stateno;
+ 
+  if( stateno>YY_SHIFT_MAX || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){
+    return yy_default[stateno];
+  }
+  assert( iLookAhead!=YYNOCODE );
+  i += iLookAhead;
+  if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+    if( iLookAhead>0 ){
+#ifdef YYFALLBACK
+      YYCODETYPE iFallback;            /* Fallback token */
+      if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
+             && (iFallback = yyFallback[iLookAhead])!=0 ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+             yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+        }
+#endif
+        return yy_find_shift_action(pParser, iFallback);
+      }
+#endif
+#ifdef YYWILDCARD
+      {
+        int j = i - iLookAhead + YYWILDCARD;
+        if( j>=0 && j<YY_SZ_ACTTAB && yy_lookahead[j]==YYWILDCARD ){
+#ifndef NDEBUG
+          if( yyTraceFILE ){
+            fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n",
+               yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]);
+          }
+#endif /* NDEBUG */
+          return yy_action[j];
+        }
+      }
+#endif /* YYWILDCARD */
+    }
+    return yy_default[stateno];
+  }else{
+    return yy_action[i];
+  }
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+**
+** If the look-ahead token is YYNOCODE, then check to see if the action is
+** independent of the look-ahead.  If it is, return the action, otherwise
+** return YY_NO_ACTION.
+*/
+static int yy_find_reduce_action(
+  int stateno,              /* Current state number */
+  YYCODETYPE iLookAhead     /* The look-ahead token */
+){
+  int i;
+#ifdef YYERRORSYMBOL
+  if( stateno>YY_REDUCE_MAX ){
+    return yy_default[stateno];
+  }
+#else
+  assert( stateno<=YY_REDUCE_MAX );
+#endif
+  i = yy_reduce_ofst[stateno];
+  assert( i!=YY_REDUCE_USE_DFLT );
+  assert( iLookAhead!=YYNOCODE );
+  i += iLookAhead;
+#ifdef YYERRORSYMBOL
+  if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
+    return yy_default[stateno];
+  }
+#else
+  assert( i>=0 && i<YY_SZ_ACTTAB );
+  assert( yy_lookahead[i]==iLookAhead );
+#endif
+  return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
+   ParseARG_FETCH;
+   yypParser->yyidx--;
+#ifndef NDEBUG
+   if( yyTraceFILE ){
+     fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+   }
+#endif
+   while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+   /* Here code is inserted which will execute if the parser
+   ** stack every overflows */
+%%
+   ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
+}
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+  yyParser *yypParser,          /* The parser to be shifted */
+  int yyNewState,               /* The new state to shift in */
+  int yyMajor,                  /* The major token to shift in */
+  YYMINORTYPE *yypMinor         /* Pointer to the minor token to shift in */
+){
+  yyStackEntry *yytos;
+  yypParser->yyidx++;
+#ifdef YYTRACKMAXSTACKDEPTH
+  if( yypParser->yyidx>yypParser->yyidxMax ){
+    yypParser->yyidxMax = yypParser->yyidx;
+  }
+#endif
+#if YYSTACKDEPTH>0 
+  if( yypParser->yyidx>=YYSTACKDEPTH ){
+    yyStackOverflow(yypParser, yypMinor);
+    return;
+  }
+#else
+  if( yypParser->yyidx>=yypParser->yystksz ){
+    yyGrowStack(yypParser);
+    if( yypParser->yyidx>=yypParser->yystksz ){
+      yyStackOverflow(yypParser, yypMinor);
+      return;
+    }
+  }
+#endif
+  yytos = &yypParser->yystack[yypParser->yyidx];
+  yytos->stateno = (YYACTIONTYPE)yyNewState;
+  yytos->major = (YYCODETYPE)yyMajor;
+  yytos->minor = *yypMinor;
+#ifndef NDEBUG
+  if( yyTraceFILE && yypParser->yyidx>0 ){
+    int i;
+    fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
+    fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
+    for(i=1; i<=yypParser->yyidx; i++)
+      fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
+    fprintf(yyTraceFILE,"\n");
+  }
+#endif
+}
+
+/* The following table contains information about every rule that
+** is used during the reduce.
+*/
+static const struct {
+  YYCODETYPE lhs;         /* Symbol on the left-hand side of the rule */
+  unsigned char nrhs;     /* Number of right-hand side symbols in the rule */
+} yyRuleInfo[] = {
+%%
+};
+
+static void yy_accept(yyParser*);  /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+*/
+static void yy_reduce(
+  yyParser *yypParser,         /* The parser */
+  int yyruleno                 /* Number of the rule by which to reduce */
+){
+  int yygoto;                     /* The next state */
+  int yyact;                      /* The next action */
+  YYMINORTYPE yygotominor;        /* The LHS of the rule reduced */
+  yyStackEntry *yymsp;            /* The top of the parser's stack */
+  int yysize;                     /* Amount to pop the stack */
+  ParseARG_FETCH;
+  yymsp = &yypParser->yystack[yypParser->yyidx];
+#ifndef NDEBUG
+  if( yyTraceFILE && yyruleno>=0 
+        && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
+    fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
+      yyRuleName[yyruleno]);
+  }
+#endif /* NDEBUG */
+
+  /* Silence complaints from purify about yygotominor being uninitialized
+  ** in some cases when it is copied into the stack after the following
+  ** switch.  yygotominor is uninitialized when a rule reduces that does
+  ** not set the value of its left-hand side nonterminal.  Leaving the
+  ** value of the nonterminal uninitialized is utterly harmless as long
+  ** as the value is never used.  So really the only thing this code
+  ** accomplishes is to quieten purify.  
+  **
+  ** 2007-01-16:  The wireshark project (www.wireshark.org) reports that
+  ** without this code, their parser segfaults.  I'm not sure what there
+  ** parser is doing to make this happen.  This is the second bug report
+  ** from wireshark this week.  Clearly they are stressing Lemon in ways
+  ** that it has not been previously stressed...  (SQLite ticket #2172)
+  */
+  /*memset(&yygotominor, 0, sizeof(yygotominor));*/
+  yygotominor = yyzerominor;
+
+
+  switch( yyruleno ){
+  /* Beginning here are the reduction cases.  A typical example
+  ** follows:
+  **   case 0:
+  **  #line <lineno> <grammarfile>
+  **     { ... }           // User supplied code
+  **  #line <lineno> <thisfile>
+  **     break;
+  */
+%%
+  };
+  yygoto = yyRuleInfo[yyruleno].lhs;
+  yysize = yyRuleInfo[yyruleno].nrhs;
+  yypParser->yyidx -= yysize;
+  yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto);
+  if( yyact < YYNSTATE ){
+#ifdef NDEBUG
+    /* If we are not debugging and the reduce action popped at least
+    ** one element off the stack, then we can push the new element back
+    ** onto the stack here, and skip the stack overflow test in yy_shift().
+    ** That gives a significant speed improvement. */
+    if( yysize ){
+      yypParser->yyidx++;
+      yymsp -= yysize-1;
+      yymsp->stateno = (YYACTIONTYPE)yyact;
+      yymsp->major = (YYCODETYPE)yygoto;
+      yymsp->minor = yygotominor;
+    }else
+#endif
+    {
+      yy_shift(yypParser,yyact,yygoto,&yygotominor);
+    }
+  }else{
+    assert( yyact == YYNSTATE + YYNRULE + 1 );
+    yy_accept(yypParser);
+  }
+}
+
+/*
+** The following code executes when the parse fails
+*/
+#ifndef YYNOERRORRECOVERY
+static void yy_parse_failed(
+  yyParser *yypParser           /* The parser */
+){
+  ParseARG_FETCH;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+  }
+#endif
+  while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+  /* Here code is inserted which will be executed whenever the
+  ** parser fails */
+%%
+  ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+#endif /* YYNOERRORRECOVERY */
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+  yyParser *yypParser,           /* The parser */
+  int yymajor,                   /* The major type of the error token */
+  YYMINORTYPE yyminor            /* The minor type of the error token */
+){
+  ParseARG_FETCH;
+#define TOKEN (yyminor.yy0)
+%%
+  ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+  yyParser *yypParser           /* The parser */
+){
+  ParseARG_FETCH;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+  }
+#endif
+  while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
+  /* Here code is inserted which will be executed whenever the
+  ** parser accepts */
+%%
+  ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "ParseAlloc" which describes the current state of the parser.
+** The second argument is the major token number.  The third is
+** the minor token.  The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void Parse(
+  void *yyp,                   /* The parser */
+  int yymajor,                 /* The major token code number */
+  ParseTOKENTYPE yyminor       /* The value for the token */
+  ParseARG_PDECL               /* Optional %extra_argument parameter */
+){
+  YYMINORTYPE yyminorunion;
+  int yyact;            /* The parser action. */
+  int yyendofinput;     /* True if we are at the end of input */
+#ifdef YYERRORSYMBOL
+  int yyerrorhit = 0;   /* True if yymajor has invoked an error */
+#endif
+  yyParser *yypParser;  /* The parser */
+
+  /* (re)initialize the parser, if necessary */
+  yypParser = (yyParser*)yyp;
+  if( yypParser->yyidx<0 ){
+#if YYSTACKDEPTH<=0
+    if( yypParser->yystksz <=0 ){
+      /*memset(&yyminorunion, 0, sizeof(yyminorunion));*/
+      yyminorunion = yyzerominor;
+      yyStackOverflow(yypParser, &yyminorunion);
+      return;
+    }
+#endif
+    yypParser->yyidx = 0;
+    yypParser->yyerrcnt = -1;
+    yypParser->yystack[0].stateno = 0;
+    yypParser->yystack[0].major = 0;
+  }
+  yyminorunion.yy0 = yyminor;
+  yyendofinput = (yymajor==0);
+  ParseARG_STORE;
+
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
+  }
+#endif
+
+  do{
+    yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor);
+    if( yyact<YYNSTATE ){
+      assert( !yyendofinput );  /* Impossible to shift the $ token */
+      yy_shift(yypParser,yyact,yymajor,&yyminorunion);
+      yypParser->yyerrcnt--;
+      yymajor = YYNOCODE;
+    }else if( yyact < YYNSTATE + YYNRULE ){
+      yy_reduce(yypParser,yyact-YYNSTATE);
+    }else{
+      assert( yyact == YY_ERROR_ACTION );
+#ifdef YYERRORSYMBOL
+      int yymx;
+#endif
+#ifndef NDEBUG
+      if( yyTraceFILE ){
+        fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+      }
+#endif
+#ifdef YYERRORSYMBOL
+      /* A syntax error has occurred.
+      ** The response to an error depends upon whether or not the
+      ** grammar defines an error token "ERROR".  
+      **
+      ** This is what we do if the grammar does define ERROR:
+      **
+      **  * Call the %syntax_error function.
+      **
+      **  * Begin popping the stack until we enter a state where
+      **    it is legal to shift the error symbol, then shift
+      **    the error symbol.
+      **
+      **  * Set the error count to three.
+      **
+      **  * Begin accepting and shifting new tokens.  No new error
+      **    processing will occur until three tokens have been
+      **    shifted successfully.
+      **
+      */
+      if( yypParser->yyerrcnt<0 ){
+        yy_syntax_error(yypParser,yymajor,yyminorunion);
+      }
+      yymx = yypParser->yystack[yypParser->yyidx].major;
+      if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+             yyTracePrompt,yyTokenName[yymajor]);
+        }
+#endif
+        yy_destructor(yypParser, (YYCODETYPE)yymajor,&yyminorunion);
+        yymajor = YYNOCODE;
+      }else{
+         while(
+          yypParser->yyidx >= 0 &&
+          yymx != YYERRORSYMBOL &&
+          (yyact = yy_find_reduce_action(
+                        yypParser->yystack[yypParser->yyidx].stateno,
+                        YYERRORSYMBOL)) >= YYNSTATE
+        ){
+          yy_pop_parser_stack(yypParser);
+        }
+        if( yypParser->yyidx < 0 || yymajor==0 ){
+          yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+          yy_parse_failed(yypParser);
+          yymajor = YYNOCODE;
+        }else if( yymx!=YYERRORSYMBOL ){
+          YYMINORTYPE u2;
+          u2.YYERRSYMDT = 0;
+          yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
+        }
+      }
+      yypParser->yyerrcnt = 3;
+      yyerrorhit = 1;
+#elif defined(YYNOERRORRECOVERY)
+      /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to
+      ** do any kind of error recovery.  Instead, simply invoke the syntax
+      ** error routine and continue going as if nothing had happened.
+      **
+      ** Applications can set this macro (for example inside %include) if
+      ** they intend to abandon the parse upon the first syntax error seen.
+      */
+      yy_syntax_error(yypParser,yymajor,yyminorunion);
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      yymajor = YYNOCODE;
+      
+#else  /* YYERRORSYMBOL is not defined */
+      /* This is what we do if the grammar does not define ERROR:
+      **
+      **  * Report an error message, and throw away the input token.
+      **
+      **  * If the input token is $, then fail the parse.
+      **
+      ** As before, subsequent error messages are suppressed until
+      ** three input tokens have been successfully shifted.
+      */
+      if( yypParser->yyerrcnt<=0 ){
+        yy_syntax_error(yypParser,yymajor,yyminorunion);
+      }
+      yypParser->yyerrcnt = 3;
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      if( yyendofinput ){
+        yy_parse_failed(yypParser);
+      }
+      yymajor = YYNOCODE;
+#endif
+    }
+  }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
+  return;
+}
diff --git a/external/badvpn_dns/lime/HOWTO b/external/badvpn_dns/lime/HOWTO
new file mode 100644
index 0000000..f447c84
--- /dev/null
+++ b/external/badvpn_dns/lime/HOWTO
@@ -0,0 +1,70 @@
+Lime: An LALR(1) parser generator in and for PHP.
+
+Interpretter pattern got you down? Time to use a real parser? Welcome to Lime.
+
+If you're familiar with BISON or YACC, you may want to read the metagrammar.
+It's written in the Lime input language, so you'll get a head-start on
+understanding how to use Lime.
+
+0. If you're not running Linux on an IA32 box, then you will have to rebuild
+	lime_scan_tokens for your system. It should be enough to erase it,
+	and then type "CFLAGS=-O2 make lime_scan_tokens" at the bash prompt.
+
+1. Stare at the file lime/metagrammar to understand the syntax. You're seeing
+	slightly modified and tweaked Backus-Naur forms. The main differences
+	are that you get to name your components, instead of refering to them
+	by numbers the way that BISON demands. This idea was stolen from the
+	C-based "Lemon" parser from which Lime derives its name. Incidentally,
+	the author of Lemon disclaimed copyright, so you get a copy of the C
+	code that taught me LALR(1) parsing better than any book, despite the
+	obvious difficulties in understanding it. Oh, and one other thing:
+	symbols are terminal if the scanner feeds them to the parser. They
+	are non-terminal if they appear on the left side of a production rule.
+	Lime names semantic categories using strings instead of the numbers
+	that BISON-based parsers use, so you don't have to declare any list of
+	terminal symbols anywhere.
+
+2. Look at the file lime/lime.php to see what pragmas are defined. To be more
+	specific, you might look at the method lime::pragma(), which at the
+	time of this writing, supports "%left", "%right", "%nonassoc",
+	"%start", and "%class". The first three are for operator precedence.
+	The last two declare the start symbol and the name of a PHP class to
+	generate which will hold all the bottom-up parsing tables.
+
+3. Write a grammar file.
+
+4. php /path/to/lime/lime.php list-of-grammar-files > my_parser.php
+
+5. Read the function parse_lime_grammar() in lime.php to understand
+	how to integrate your parser into your program.
+
+6. Integrate your parser as follows:
+
+--------------- CUT ---------------
+
+include_once "lime/parse_engine.php";
+include_once "my_parser.php";
+#
+# Later:
+#
+$parser = new parse_engine(new my_parser());
+#
+# And still later:
+#
+try {
+	while (..something..) {
+		$parser->eat($type, $val);
+		# You figure out how to get the parameters.
+	}
+	# And after the last token has been eaten:
+	$parser->eat_eof();
+} catch (parse_error $e) {
+	die($e->getMessage());
+}
+return $parser->semantic;
+
+--------------- CUT ---------------
+
+7. You now have the computed semantic value of whatever you parsed. Add salt
+	and pepper to taste, and serve.
+
diff --git a/external/badvpn_dns/lime/flex_token_stream.php b/external/badvpn_dns/lime/flex_token_stream.php
new file mode 100644
index 0000000..c21e411
--- /dev/null
+++ b/external/badvpn_dns/lime/flex_token_stream.php
@@ -0,0 +1,34 @@
+<?php
+
+
+
+abstract class flex_scanner {
+	/*
+	Let's face it: PHP is not up to lexical processing. GNU flex handles
+	it well, so I've created a little protocol for delegating the work.
+	Extend this class so that executable() gives a path to your lexical
+	analyser program.
+	*/
+	abstract function executable();
+	function __construct($path) {
+		if (!is_readable($path)) throw new Exception("$path is not readable.");
+		putenv("PHP_LIME_SCAN_STDIN=$path");
+		$scanner = $this->executable();
+		$tokens = explode("\0", `$scanner < "\$PHP_LIME_SCAN_STDIN"`);
+		array_pop($tokens);
+		$this->tokens = $tokens;
+		$this->lineno = 1;
+	}
+	function next() {
+		if (list($key, $token) = each($this->tokens)) {
+			list($this->lineno, $type, $text) = explode("\1", $token);
+			return array($type, $text);
+		}
+	}
+	function feed($parser) {
+		while (list($type, $text) = $this->next()) {
+			$parser->eat($type, $text);
+		}
+		return $parser->eat_eof();
+	}
+}
diff --git a/external/badvpn_dns/lime/lemon.c b/external/badvpn_dns/lime/lemon.c
new file mode 100644
index 0000000..708b353
--- /dev/null
+++ b/external/badvpn_dns/lime/lemon.c
@@ -0,0 +1,4588 @@
+/*
+** This file contains all sources (including headers) to the LEMON
+** LALR(1) parser generator.  The sources have been combined into a
+** single file to make it easy to include LEMON in the source tree
+** and Makefile of another program.
+**
+** The author of this program disclaims copyright.
+*/
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#ifndef __WIN32__
+#   if defined(_WIN32) || defined(WIN32)
+#	define __WIN32__
+#   endif
+#endif
+
+/* #define PRIVATE static */
+#define PRIVATE
+
+#ifdef TEST
+#define MAXRHS 5       /* Set low to exercise exception code */
+#else
+#define MAXRHS 1000
+#endif
+
+char *msort();
+extern void *malloc();
+
+/******** From the file "action.h" *************************************/
+struct action *Action_new();
+struct action *Action_sort();
+
+/********* From the file "assert.h" ************************************/
+void myassert();
+#ifndef NDEBUG
+#  define assert(X) if(!(X))myassert(__FILE__,__LINE__)
+#else
+#  define assert(X)
+#endif
+
+/********** From the file "build.h" ************************************/
+void FindRulePrecedences();
+void FindFirstSets();
+void FindStates();
+void FindLinks();
+void FindFollowSets();
+void FindActions();
+
+/********* From the file "configlist.h" *********************************/
+void Configlist_init(/* void */);
+struct config *Configlist_add(/* struct rule *, int */);
+struct config *Configlist_addbasis(/* struct rule *, int */);
+void Configlist_closure(/* void */);
+void Configlist_sort(/* void */);
+void Configlist_sortbasis(/* void */);
+struct config *Configlist_return(/* void */);
+struct config *Configlist_basis(/* void */);
+void Configlist_eat(/* struct config * */);
+void Configlist_reset(/* void */);
+
+/********* From the file "error.h" ***************************************/
+void ErrorMsg(const char *, int,const char *, ...);
+
+/****** From the file "option.h" ******************************************/
+struct s_options {
+  enum { OPT_FLAG=1,  OPT_INT,  OPT_DBL,  OPT_STR,
+         OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR} type;
+  char *label;
+  char *arg;
+  char *message;
+};
+int    OptInit(/* char**,struct s_options*,FILE* */);
+int    OptNArgs(/* void */);
+char  *OptArg(/* int */);
+void   OptErr(/* int */);
+void   OptPrint(/* void */);
+
+/******** From the file "parse.h" *****************************************/
+void Parse(/* struct lemon *lemp */);
+
+/********* From the file "plink.h" ***************************************/
+struct plink *Plink_new(/* void */);
+void Plink_add(/* struct plink **, struct config * */);
+void Plink_copy(/* struct plink **, struct plink * */);
+void Plink_delete(/* struct plink * */);
+
+/********** From the file "report.h" *************************************/
+void Reprint(/* struct lemon * */);
+void ReportOutput(/* struct lemon * */);
+void ReportTable(/* struct lemon * */);
+void ReportHeader(/* struct lemon * */);
+void CompressTables(/* struct lemon * */);
+
+/********** From the file "set.h" ****************************************/
+void  SetSize(/* int N */);             /* All sets will be of size N */
+char *SetNew(/* void */);               /* A new set for element 0..N */
+void  SetFree(/* char* */);             /* Deallocate a set */
+
+int SetAdd(/* char*,int */);            /* Add element to a set */
+int SetUnion(/* char *A,char *B */);    /* A <- A U B, thru element N */
+
+#define SetFind(X,Y) (X[Y])       /* True if Y is in set X */
+
+/********** From the file "struct.h" *************************************/
+/*
+** Principal data structures for the LEMON parser generator.
+*/
+
+typedef enum {B_FALSE=0, B_TRUE} Boolean;
+
+/* Symbols (terminals and nonterminals) of the grammar are stored
+** in the following: */
+struct symbol {
+  char *name;              /* Name of the symbol */
+  int index;               /* Index number for this symbol */
+  enum {
+    TERMINAL,
+    NONTERMINAL
+  } type;                  /* Symbols are all either TERMINALS or NTs */
+  struct rule *rule;       /* Linked list of rules of this (if an NT) */
+  struct symbol *fallback; /* fallback token in case this token doesn't parse */
+  int prec;                /* Precedence if defined (-1 otherwise) */
+  enum e_assoc {
+    LEFT,
+    RIGHT,
+    NONE,
+    UNK
+  } assoc;                 /* Associativity if predecence is defined */
+  char *firstset;          /* First-set for all rules of this symbol */
+  Boolean lambda;          /* True if NT and can generate an empty string */
+  char *destructor;        /* Code which executes whenever this symbol is
+                           ** popped from the stack during error processing */
+  int destructorln;        /* Line number of destructor code */
+  char *datatype;          /* The data type of information held by this
+                           ** object. Only used if type==NONTERMINAL */
+  int dtnum;               /* The data type number.  In the parser, the value
+                           ** stack is a union.  The .yy%d element of this
+                           ** union is the correct data type for this object */
+};
+
+/* Each production rule in the grammar is stored in the following
+** structure.  */
+struct rule {
+  struct symbol *lhs;      /* Left-hand side of the rule */
+  char *lhsalias;          /* Alias for the LHS (NULL if none) */
+  int ruleline;            /* Line number for the rule */
+  int nrhs;                /* Number of RHS symbols */
+  struct symbol **rhs;     /* The RHS symbols */
+  char **rhsalias;         /* An alias for each RHS symbol (NULL if none) */
+  int line;                /* Line number at which code begins */
+  char *code;              /* The code executed when this rule is reduced */
+  struct symbol *precsym;  /* Precedence symbol for this rule */
+  int index;               /* An index number for this rule */
+  Boolean canReduce;       /* True if this rule is ever reduced */
+  struct rule *nextlhs;    /* Next rule with the same LHS */
+  struct rule *next;       /* Next rule in the global list */
+};
+
+/* A configuration is a production rule of the grammar together with
+** a mark (dot) showing how much of that rule has been processed so far.
+** Configurations also contain a follow-set which is a list of terminal
+** symbols which are allowed to immediately follow the end of the rule.
+** Every configuration is recorded as an instance of the following: */
+struct config {
+  struct rule *rp;         /* The rule upon which the configuration is based */
+  int dot;                 /* The parse point */
+  char *fws;               /* Follow-set for this configuration only */
+  struct plink *fplp;      /* Follow-set forward propagation links */
+  struct plink *bplp;      /* Follow-set backwards propagation links */
+  struct state *stp;       /* Pointer to state which contains this */
+  enum {
+    COMPLETE,              /* The status is used during followset and */
+    INCOMPLETE             /*    shift computations */
+  } status;
+  struct config *next;     /* Next configuration in the state */
+  struct config *bp;       /* The next basis configuration */
+};
+
+/* Every shift or reduce operation is stored as one of the following */
+struct action {
+  struct symbol *sp;       /* The look-ahead symbol */
+  enum e_action {
+    SHIFT,
+    ACCEPT,
+    REDUCE,
+    ERROR,
+    CONFLICT,                /* Was a reduce, but part of a conflict */
+    SH_RESOLVED,             /* Was a shift.  Precedence resolved conflict */
+    RD_RESOLVED,             /* Was reduce.  Precedence resolved conflict */
+    NOT_USED                 /* Deleted by compression */
+  } type;
+  union {
+    struct state *stp;     /* The new state, if a shift */
+    struct rule *rp;       /* The rule, if a reduce */
+  } x;
+  struct action *next;     /* Next action for this state */
+  struct action *collide;  /* Next action with the same hash */
+};
+
+/* Each state of the generated parser's finite state machine
+** is encoded as an instance of the following structure. */
+struct state {
+  struct config *bp;       /* The basis configurations for this state */
+  struct config *cfp;      /* All configurations in this set */
+  int index;               /* Sequencial number for this state */
+  struct action *ap;       /* Array of actions for this state */
+  int nTknAct, nNtAct;     /* Number of actions on terminals and nonterminals */
+  int iTknOfst, iNtOfst;   /* yy_action[] offset for terminals and nonterms */
+  int iDflt;               /* Default action */
+};
+#define NO_OFFSET (-2147483647)
+
+/* A followset propagation link indicates that the contents of one
+** configuration followset should be propagated to another whenever
+** the first changes. */
+struct plink {
+  struct config *cfp;      /* The configuration to which linked */
+  struct plink *next;      /* The next propagate link */
+};
+
+/* The state vector for the entire parser generator is recorded as
+** follows.  (LEMON uses no global variables and makes little use of
+** static variables.  Fields in the following structure can be thought
+** of as begin global variables in the program.) */
+struct lemon {
+  struct state **sorted;   /* Table of states sorted by state number */
+  struct rule *rule;       /* List of all rules */
+  int nstate;              /* Number of states */
+  int nrule;               /* Number of rules */
+  int nsymbol;             /* Number of terminal and nonterminal symbols */
+  int nterminal;           /* Number of terminal symbols */
+  struct symbol **symbols; /* Sorted array of pointers to symbols */
+  int errorcnt;            /* Number of errors */
+  struct symbol *errsym;   /* The error symbol */
+  char *name;              /* Name of the generated parser */
+  char *arg;               /* Declaration of the 3th argument to parser */
+  char *tokentype;         /* Type of terminal symbols in the parser stack */
+  char *vartype;           /* The default type of non-terminal symbols */
+  char *start;             /* Name of the start symbol for the grammar */
+  char *stacksize;         /* Size of the parser stack */
+  char *include;           /* Code to put at the start of the C file */
+  int  includeln;          /* Line number for start of include code */
+  char *error;             /* Code to execute when an error is seen */
+  int  errorln;            /* Line number for start of error code */
+  char *overflow;          /* Code to execute on a stack overflow */
+  int  overflowln;         /* Line number for start of overflow code */
+  char *failure;           /* Code to execute on parser failure */
+  int  failureln;          /* Line number for start of failure code */
+  char *accept;            /* Code to execute when the parser excepts */
+  int  acceptln;           /* Line number for the start of accept code */
+  char *extracode;         /* Code appended to the generated file */
+  int  extracodeln;        /* Line number for the start of the extra code */
+  char *tokendest;         /* Code to execute to destroy token data */
+  int  tokendestln;        /* Line number for token destroyer code */
+  char *vardest;           /* Code for the default non-terminal destructor */
+  int  vardestln;          /* Line number for default non-term destructor code*/
+  char *filename;          /* Name of the input file */
+  char *outname;           /* Name of the current output file */
+  char *tokenprefix;       /* A prefix added to token names in the .h file */
+  int nconflict;           /* Number of parsing conflicts */
+  int tablesize;           /* Size of the parse tables */
+  int basisflag;           /* Print only basis configurations */
+  int has_fallback;        /* True if any %fallback is seen in the grammer */
+  char *argv0;             /* Name of the program */
+};
+
+#define MemoryCheck(X) if((X)==0){ \
+  extern void memory_error(); \
+  memory_error(); \
+}
+
+/**************** From the file "table.h" *********************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+**              "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file!  Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+/* Routines for handling a strings */
+
+char *Strsafe();
+
+void Strsafe_init(/* void */);
+int Strsafe_insert(/* char * */);
+char *Strsafe_find(/* char * */);
+
+/* Routines for handling symbols of the grammar */
+
+struct symbol *Symbol_new();
+int Symbolcmpp(/* struct symbol **, struct symbol ** */);
+void Symbol_init(/* void */);
+int Symbol_insert(/* struct symbol *, char * */);
+struct symbol *Symbol_find(/* char * */);
+struct symbol *Symbol_Nth(/* int */);
+int Symbol_count(/*  */);
+struct symbol **Symbol_arrayof(/*  */);
+
+/* Routines to manage the state table */
+
+int Configcmp(/* struct config *, struct config * */);
+struct state *State_new();
+void State_init(/* void */);
+int State_insert(/* struct state *, struct config * */);
+struct state *State_find(/* struct config * */);
+struct state **State_arrayof(/*  */);
+
+/* Routines used for efficiency in Configlist_add */
+
+void Configtable_init(/* void */);
+int Configtable_insert(/* struct config * */);
+struct config *Configtable_find(/* struct config * */);
+void Configtable_clear(/* int(*)(struct config *) */);
+/****************** From the file "action.c" *******************************/
+/*
+** Routines processing parser actions in the LEMON parser generator.
+*/
+
+/* Allocate a new parser action */
+struct action *Action_new(){
+  static struct action *freelist = 0;
+  struct action *new;
+
+  if( freelist==0 ){
+    int i;
+    int amt = 100;
+    freelist = (struct action *)malloc( sizeof(struct action)*amt );
+    if( freelist==0 ){
+      fprintf(stderr,"Unable to allocate memory for a new parser action.");
+      exit(1);
+    }
+    for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+    freelist[amt-1].next = 0;
+  }
+  new = freelist;
+  freelist = freelist->next;
+  return new;
+}
+
+/* Compare two actions */
+static int actioncmp(ap1,ap2)
+struct action *ap1;
+struct action *ap2;
+{
+  int rc;
+  rc = ap1->sp->index - ap2->sp->index;
+  if( rc==0 ) rc = (int)ap1->type - (int)ap2->type;
+  if( rc==0 ){
+    assert( ap1->type==REDUCE || ap1->type==RD_RESOLVED || ap1->type==CONFLICT);
+    assert( ap2->type==REDUCE || ap2->type==RD_RESOLVED || ap2->type==CONFLICT);
+    rc = ap1->x.rp->index - ap2->x.rp->index;
+  }
+  return rc;
+}
+
+/* Sort parser actions */
+struct action *Action_sort(ap)
+struct action *ap;
+{
+  ap = (struct action *)msort((char *)ap,(char **)&ap->next,actioncmp);
+  return ap;
+}
+
+void Action_add(app,type,sp,arg)
+struct action **app;
+enum e_action type;
+struct symbol *sp;
+char *arg;
+{
+  struct action *new;
+  new = Action_new();
+  new->next = *app;
+  *app = new;
+  new->type = type;
+  new->sp = sp;
+  if( type==SHIFT ){
+    new->x.stp = (struct state *)arg;
+  }else{
+    new->x.rp = (struct rule *)arg;
+  }
+}
+/********************** New code to implement the "acttab" module ***********/
+/*
+** This module implements routines use to construct the yy_action[] table.
+*/
+
+/*
+** The state of the yy_action table under construction is an instance of
+** the following structure
+*/
+typedef struct acttab acttab;
+struct acttab {
+  int nAction;                 /* Number of used slots in aAction[] */
+  int nActionAlloc;            /* Slots allocated for aAction[] */
+  struct {
+    int lookahead;             /* Value of the lookahead token */
+    int action;                /* Action to take on the given lookahead */
+  } *aAction,                  /* The yy_action[] table under construction */
+    *aLookahead;               /* A single new transaction set */
+  int mnLookahead;             /* Minimum aLookahead[].lookahead */
+  int mnAction;                /* Action associated with mnLookahead */
+  int mxLookahead;             /* Maximum aLookahead[].lookahead */
+  int nLookahead;              /* Used slots in aLookahead[] */
+  int nLookaheadAlloc;         /* Slots allocated in aLookahead[] */
+};
+
+/* Return the number of entries in the yy_action table */
+#define acttab_size(X) ((X)->nAction)
+
+/* The value for the N-th entry in yy_action */
+#define acttab_yyaction(X,N)  ((X)->aAction[N].action)
+
+/* The value for the N-th entry in yy_lookahead */
+#define acttab_yylookahead(X,N)  ((X)->aAction[N].lookahead)
+
+/* Free all memory associated with the given acttab */
+void acttab_free(acttab *p){
+  free( p->aAction );
+  free( p->aLookahead );
+  free( p );
+}
+
+/* Allocate a new acttab structure */
+acttab *acttab_alloc(void){
+  acttab *p = malloc( sizeof(*p) );
+  if( p==0 ){
+    fprintf(stderr,"Unable to allocate memory for a new acttab.");
+    exit(1);
+  }
+  memset(p, 0, sizeof(*p));
+  return p;
+}
+
+/* Add a new action to the current transaction set
+*/
+void acttab_action(acttab *p, int lookahead, int action){
+  if( p->nLookahead>=p->nLookaheadAlloc ){
+    p->nLookaheadAlloc += 25;
+    p->aLookahead = realloc( p->aLookahead,
+                             sizeof(p->aLookahead[0])*p->nLookaheadAlloc );
+    if( p->aLookahead==0 ){
+      fprintf(stderr,"malloc failed\n");
+      exit(1);
+    }
+  }
+  if( p->nLookahead==0 ){
+    p->mxLookahead = lookahead;
+    p->mnLookahead = lookahead;
+    p->mnAction = action;
+  }else{
+    if( p->mxLookahead<lookahead ) p->mxLookahead = lookahead;
+    if( p->mnLookahead>lookahead ){
+      p->mnLookahead = lookahead;
+      p->mnAction = action;
+    }
+  }
+  p->aLookahead[p->nLookahead].lookahead = lookahead;
+  p->aLookahead[p->nLookahead].action = action;
+  p->nLookahead++;
+}
+
+/*
+** Add the transaction set built up with prior calls to acttab_action()
+** into the current action table.  Then reset the transaction set back
+** to an empty set in preparation for a new round of acttab_action() calls.
+**
+** Return the offset into the action table of the new transaction.
+*/
+int acttab_insert(acttab *p){
+  int i, j, k, n;
+  assert( p->nLookahead>0 );
+
+  /* Make sure we have enough space to hold the expanded action table
+  ** in the worst case.  The worst case occurs if the transaction set
+  ** must be appended to the current action table
+  */
+  n = p->mxLookahead + 1;
+  if( p->nAction + n >= p->nActionAlloc ){
+    int oldAlloc = p->nActionAlloc;
+    p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20;
+    p->aAction = realloc( p->aAction,
+                          sizeof(p->aAction[0])*p->nActionAlloc);
+    if( p->aAction==0 ){
+      fprintf(stderr,"malloc failed\n");
+      exit(1);
+    }
+    for(i=oldAlloc; i<p->nActionAlloc; i++){
+      p->aAction[i].lookahead = -1;
+      p->aAction[i].action = -1;
+    }
+  }
+
+  /* Scan the existing action table looking for an offset where we can
+  ** insert the current transaction set.  Fall out of the loop when that
+  ** offset is found.  In the worst case, we fall out of the loop when
+  ** i reaches p->nAction, which means we append the new transaction set.
+  **
+  ** i is the index in p->aAction[] where p->mnLookahead is inserted.
+  */
+  for(i=0; i<p->nAction+p->mnLookahead; i++){
+    if( p->aAction[i].lookahead<0 ){
+      for(j=0; j<p->nLookahead; j++){
+        k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+        if( k<0 ) break;
+        if( p->aAction[k].lookahead>=0 ) break;
+      }
+      if( j<p->nLookahead ) continue;
+      for(j=0; j<p->nAction; j++){
+        if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break;
+      }
+      if( j==p->nAction ){
+        break;  /* Fits in empty slots */
+      }
+    }else if( p->aAction[i].lookahead==p->mnLookahead ){
+      if( p->aAction[i].action!=p->mnAction ) continue;
+      for(j=0; j<p->nLookahead; j++){
+        k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+        if( k<0 || k>=p->nAction ) break;
+        if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break;
+        if( p->aLookahead[j].action!=p->aAction[k].action ) break;
+      }
+      if( j<p->nLookahead ) continue;
+      n = 0;
+      for(j=0; j<p->nAction; j++){
+        if( p->aAction[j].lookahead<0 ) continue;
+        if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++;
+      }
+      if( n==p->nLookahead ){
+        break;  /* Same as a prior transaction set */
+      }
+    }
+  }
+  /* Insert transaction set at index i. */
+  for(j=0; j<p->nLookahead; j++){
+    k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+    p->aAction[k] = p->aLookahead[j];
+    if( k>=p->nAction ) p->nAction = k+1;
+  }
+  p->nLookahead = 0;
+
+  /* Return the offset that is added to the lookahead in order to get the
+  ** index into yy_action of the action */
+  return i - p->mnLookahead;
+}
+
+/********************** From the file "assert.c" ****************************/
+/*
+** A more efficient way of handling assertions.
+*/
+void myassert(file,line)
+char *file;
+int line;
+{
+  fprintf(stderr,"Assertion failed on line %d of file \"%s\"\n",line,file);
+  exit(1);
+}
+/********************** From the file "build.c" *****************************/
+/*
+** Routines to construction the finite state machine for the LEMON
+** parser generator.
+*/
+
+/* Find a precedence symbol of every rule in the grammar.
+** 
+** Those rules which have a precedence symbol coded in the input
+** grammar using the "[symbol]" construct will already have the
+** rp->precsym field filled.  Other rules take as their precedence
+** symbol the first RHS symbol with a defined precedence.  If there
+** are not RHS symbols with a defined precedence, the precedence
+** symbol field is left blank.
+*/
+void FindRulePrecedences(xp)
+struct lemon *xp;
+{
+  struct rule *rp;
+  for(rp=xp->rule; rp; rp=rp->next){
+    if( rp->precsym==0 ){
+      int i;
+      for(i=0; i<rp->nrhs; i++){
+        if( rp->rhs[i]->prec>=0 ){
+          rp->precsym = rp->rhs[i];
+          break;
+	}
+      }
+    }
+  }
+  return;
+}
+
+/* Find all nonterminals which will generate the empty string.
+** Then go back and compute the first sets of every nonterminal.
+** The first set is the set of all terminal symbols which can begin
+** a string generated by that nonterminal.
+*/
+void FindFirstSets(lemp)
+struct lemon *lemp;
+{
+  int i;
+  struct rule *rp;
+  int progress;
+
+  for(i=0; i<lemp->nsymbol; i++){
+    lemp->symbols[i]->lambda = B_FALSE;
+  }
+  for(i=lemp->nterminal; i<lemp->nsymbol; i++){
+    lemp->symbols[i]->firstset = SetNew();
+  }
+
+  /* First compute all lambdas */
+  do{
+    progress = 0;
+    for(rp=lemp->rule; rp; rp=rp->next){
+      if( rp->lhs->lambda ) continue;
+      for(i=0; i<rp->nrhs; i++){
+         if( rp->rhs[i]->lambda==B_FALSE ) break;
+      }
+      if( i==rp->nrhs ){
+        rp->lhs->lambda = B_TRUE;
+        progress = 1;
+      }
+    }
+  }while( progress );
+
+  /* Now compute all first sets */
+  do{
+    struct symbol *s1, *s2;
+    progress = 0;
+    for(rp=lemp->rule; rp; rp=rp->next){
+      s1 = rp->lhs;
+      for(i=0; i<rp->nrhs; i++){
+        s2 = rp->rhs[i];
+        if( s2->type==TERMINAL ){
+          progress += SetAdd(s1->firstset,s2->index);
+          break;
+	}else if( s1==s2 ){
+          if( s1->lambda==B_FALSE ) break;
+	}else{
+          progress += SetUnion(s1->firstset,s2->firstset);
+          if( s2->lambda==B_FALSE ) break;
+	}
+      }
+    }
+  }while( progress );
+  return;
+}
+
+/* Compute all LR(0) states for the grammar.  Links
+** are added to between some states so that the LR(1) follow sets
+** can be computed later.
+*/
+PRIVATE struct state *getstate(/* struct lemon * */);  /* forward reference */
+void FindStates(lemp)
+struct lemon *lemp;
+{
+  struct symbol *sp;
+  struct rule *rp;
+
+  Configlist_init();
+
+  /* Find the start symbol */
+  if( lemp->start ){
+    sp = Symbol_find(lemp->start);
+    if( sp==0 ){
+      ErrorMsg(lemp->filename,0,
+"The specified start symbol \"%s\" is not \
+in a nonterminal of the grammar.  \"%s\" will be used as the start \
+symbol instead.",lemp->start,lemp->rule->lhs->name);
+      lemp->errorcnt++;
+      sp = lemp->rule->lhs;
+    }
+  }else{
+    sp = lemp->rule->lhs;
+  }
+
+  /* Make sure the start symbol doesn't occur on the right-hand side of
+  ** any rule.  Report an error if it does.  (YACC would generate a new
+  ** start symbol in this case.) */
+  for(rp=lemp->rule; rp; rp=rp->next){
+    int i;
+    for(i=0; i<rp->nrhs; i++){
+      if( rp->rhs[i]==sp ){
+        ErrorMsg(lemp->filename,0,
+"The start symbol \"%s\" occurs on the \
+right-hand side of a rule. This will result in a parser which \
+does not work properly.",sp->name);
+        lemp->errorcnt++;
+      }
+    }
+  }
+
+  /* The basis configuration set for the first state
+  ** is all rules which have the start symbol as their
+  ** left-hand side */
+  for(rp=sp->rule; rp; rp=rp->nextlhs){
+    struct config *newcfp;
+    newcfp = Configlist_addbasis(rp,0);
+    SetAdd(newcfp->fws,0);
+  }
+
+  /* Compute the first state.  All other states will be
+  ** computed automatically during the computation of the first one.
+  ** The returned pointer to the first state is not used. */
+  (void)getstate(lemp);
+  return;
+}
+
+/* Return a pointer to a state which is described by the configuration
+** list which has been built from calls to Configlist_add.
+*/
+PRIVATE void buildshifts(/* struct lemon *, struct state * */); /* Forwd ref */
+PRIVATE struct state *getstate(lemp)
+struct lemon *lemp;
+{
+  struct config *cfp, *bp;
+  struct state *stp;
+
+  /* Extract the sorted basis of the new state.  The basis was constructed
+  ** by prior calls to "Configlist_addbasis()". */
+  Configlist_sortbasis();
+  bp = Configlist_basis();
+
+  /* Get a state with the same basis */
+  stp = State_find(bp);
+  if( stp ){
+    /* A state with the same basis already exists!  Copy all the follow-set
+    ** propagation links from the state under construction into the
+    ** preexisting state, then return a pointer to the preexisting state */
+    struct config *x, *y;
+    for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){
+      Plink_copy(&y->bplp,x->bplp);
+      Plink_delete(x->fplp);
+      x->fplp = x->bplp = 0;
+    }
+    cfp = Configlist_return();
+    Configlist_eat(cfp);
+  }else{
+    /* This really is a new state.  Construct all the details */
+    Configlist_closure(lemp);    /* Compute the configuration closure */
+    Configlist_sort();           /* Sort the configuration closure */
+    cfp = Configlist_return();   /* Get a pointer to the config list */
+    stp = State_new();           /* A new state structure */
+    MemoryCheck(stp);
+    stp->bp = bp;                /* Remember the configuration basis */
+    stp->cfp = cfp;              /* Remember the configuration closure */
+    stp->index = lemp->nstate++; /* Every state gets a sequence number */
+    stp->ap = 0;                 /* No actions, yet. */
+    State_insert(stp,stp->bp);   /* Add to the state table */
+    buildshifts(lemp,stp);       /* Recursively compute successor states */
+  }
+  return stp;
+}
+
+/* Construct all successor states to the given state.  A "successor"
+** state is any state which can be reached by a shift action.
+*/
+PRIVATE void buildshifts(lemp,stp)
+struct lemon *lemp;
+struct state *stp;     /* The state from which successors are computed */
+{
+  struct config *cfp;  /* For looping thru the config closure of "stp" */
+  struct config *bcfp; /* For the inner loop on config closure of "stp" */
+  struct config *new;  /* */
+  struct symbol *sp;   /* Symbol following the dot in configuration "cfp" */
+  struct symbol *bsp;  /* Symbol following the dot in configuration "bcfp" */
+  struct state *newstp; /* A pointer to a successor state */
+
+  /* Each configuration becomes complete after it contibutes to a successor
+  ** state.  Initially, all configurations are incomplete */
+  for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE;
+
+  /* Loop through all configurations of the state "stp" */
+  for(cfp=stp->cfp; cfp; cfp=cfp->next){
+    if( cfp->status==COMPLETE ) continue;    /* Already used by inner loop */
+    if( cfp->dot>=cfp->rp->nrhs ) continue;  /* Can't shift this config */
+    Configlist_reset();                      /* Reset the new config set */
+    sp = cfp->rp->rhs[cfp->dot];             /* Symbol after the dot */
+
+    /* For every configuration in the state "stp" which has the symbol "sp"
+    ** following its dot, add the same configuration to the basis set under
+    ** construction but with the dot shifted one symbol to the right. */
+    for(bcfp=cfp; bcfp; bcfp=bcfp->next){
+      if( bcfp->status==COMPLETE ) continue;    /* Already used */
+      if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */
+      bsp = bcfp->rp->rhs[bcfp->dot];           /* Get symbol after dot */
+      if( bsp!=sp ) continue;                   /* Must be same as for "cfp" */
+      bcfp->status = COMPLETE;                  /* Mark this config as used */
+      new = Configlist_addbasis(bcfp->rp,bcfp->dot+1);
+      Plink_add(&new->bplp,bcfp);
+    }
+
+    /* Get a pointer to the state described by the basis configuration set
+    ** constructed in the preceding loop */
+    newstp = getstate(lemp);
+
+    /* The state "newstp" is reached from the state "stp" by a shift action
+    ** on the symbol "sp" */
+    Action_add(&stp->ap,SHIFT,sp,(char *)newstp);
+  }
+}
+
+/*
+** Construct the propagation links
+*/
+void FindLinks(lemp)
+struct lemon *lemp;
+{
+  int i;
+  struct config *cfp, *other;
+  struct state *stp;
+  struct plink *plp;
+
+  /* Housekeeping detail:
+  ** Add to every propagate link a pointer back to the state to
+  ** which the link is attached. */
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    for(cfp=stp->cfp; cfp; cfp=cfp->next){
+      cfp->stp = stp;
+    }
+  }
+
+  /* Convert all backlinks into forward links.  Only the forward
+  ** links are used in the follow-set computation. */
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    for(cfp=stp->cfp; cfp; cfp=cfp->next){
+      for(plp=cfp->bplp; plp; plp=plp->next){
+        other = plp->cfp;
+        Plink_add(&other->fplp,cfp);
+      }
+    }
+  }
+}
+
+/* Compute all followsets.
+**
+** A followset is the set of all symbols which can come immediately
+** after a configuration.
+*/
+void FindFollowSets(lemp)
+struct lemon *lemp;
+{
+  int i;
+  struct config *cfp;
+  struct plink *plp;
+  int progress;
+  int change;
+
+  for(i=0; i<lemp->nstate; i++){
+    for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+      cfp->status = INCOMPLETE;
+    }
+  }
+  
+  do{
+    progress = 0;
+    for(i=0; i<lemp->nstate; i++){
+      for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+        if( cfp->status==COMPLETE ) continue;
+        for(plp=cfp->fplp; plp; plp=plp->next){
+          change = SetUnion(plp->cfp->fws,cfp->fws);
+          if( change ){
+            plp->cfp->status = INCOMPLETE;
+            progress = 1;
+	  }
+	}
+        cfp->status = COMPLETE;
+      }
+    }
+  }while( progress );
+}
+
+static int resolve_conflict();
+
+/* Compute the reduce actions, and resolve conflicts.
+*/
+void FindActions(lemp)
+struct lemon *lemp;
+{
+  int i,j;
+  struct config *cfp;
+  struct state *stp;
+  struct symbol *sp;
+  struct rule *rp;
+
+  /* Add all of the reduce actions 
+  ** A reduce action is added for each element of the followset of
+  ** a configuration which has its dot at the extreme right.
+  */
+  for(i=0; i<lemp->nstate; i++){   /* Loop over all states */
+    stp = lemp->sorted[i];
+    for(cfp=stp->cfp; cfp; cfp=cfp->next){  /* Loop over all configurations */
+      if( cfp->rp->nrhs==cfp->dot ){        /* Is dot at extreme right? */
+        for(j=0; j<lemp->nterminal; j++){
+          if( SetFind(cfp->fws,j) ){
+            /* Add a reduce action to the state "stp" which will reduce by the
+            ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */
+            Action_add(&stp->ap,REDUCE,lemp->symbols[j],(char *)cfp->rp);
+          }
+	}
+      }
+    }
+  }
+
+  /* Add the accepting token */
+  if( lemp->start ){
+    sp = Symbol_find(lemp->start);
+    if( sp==0 ) sp = lemp->rule->lhs;
+  }else{
+    sp = lemp->rule->lhs;
+  }
+  /* Add to the first state (which is always the starting state of the
+  ** finite state machine) an action to ACCEPT if the lookahead is the
+  ** start nonterminal.  */
+  Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0);
+
+  /* Resolve conflicts */
+  for(i=0; i<lemp->nstate; i++){
+    struct action *ap, *nap;
+    struct state *stp;
+    stp = lemp->sorted[i];
+    assert( stp->ap );
+    stp->ap = Action_sort(stp->ap);
+    for(ap=stp->ap; ap && ap->next; ap=ap->next){
+      for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){
+         /* The two actions "ap" and "nap" have the same lookahead.
+         ** Figure out which one should be used */
+         lemp->nconflict += resolve_conflict(ap,nap,lemp->errsym);
+      }
+    }
+  }
+
+  /* Report an error for each rule that can never be reduced. */
+  for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = B_FALSE;
+  for(i=0; i<lemp->nstate; i++){
+    struct action *ap;
+    for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){
+      if( ap->type==REDUCE ) ap->x.rp->canReduce = B_TRUE;
+    }
+  }
+  for(rp=lemp->rule; rp; rp=rp->next){
+    if( rp->canReduce ) continue;
+    ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n");
+    lemp->errorcnt++;
+  }
+}
+
+/* Resolve a conflict between the two given actions.  If the
+** conflict can't be resolve, return non-zero.
+**
+** NO LONGER TRUE:
+**   To resolve a conflict, first look to see if either action
+**   is on an error rule.  In that case, take the action which
+**   is not associated with the error rule.  If neither or both
+**   actions are associated with an error rule, then try to
+**   use precedence to resolve the conflict.
+**
+** If either action is a SHIFT, then it must be apx.  This
+** function won't work if apx->type==REDUCE and apy->type==SHIFT.
+*/
+static int resolve_conflict(apx,apy,errsym)
+struct action *apx;
+struct action *apy;
+struct symbol *errsym;   /* The error symbol (if defined.  NULL otherwise) */
+{
+  struct symbol *spx, *spy;
+  int errcnt = 0;
+  assert( apx->sp==apy->sp );  /* Otherwise there would be no conflict */
+  if( apx->type==SHIFT && apy->type==REDUCE ){
+    spx = apx->sp;
+    spy = apy->x.rp->precsym;
+    if( spy==0 || spx->prec<0 || spy->prec<0 ){
+      /* Not enough precedence information. */
+      apy->type = CONFLICT;
+      errcnt++;
+    }else if( spx->prec>spy->prec ){    /* Lower precedence wins */
+      apy->type = RD_RESOLVED;
+    }else if( spx->prec<spy->prec ){
+      apx->type = SH_RESOLVED;
+    }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */
+      apy->type = RD_RESOLVED;                             /* associativity */
+    }else if( spx->prec==spy->prec && spx->assoc==LEFT ){  /* to break tie */
+      apx->type = SH_RESOLVED;
+    }else{
+      assert( spx->prec==spy->prec && spx->assoc==NONE );
+      apy->type = CONFLICT;
+      errcnt++;
+    }
+  }else if( apx->type==REDUCE && apy->type==REDUCE ){
+    spx = apx->x.rp->precsym;
+    spy = apy->x.rp->precsym;
+    if( spx==0 || spy==0 || spx->prec<0 ||
+    spy->prec<0 || spx->prec==spy->prec ){
+      apy->type = CONFLICT;
+      errcnt++;
+    }else if( spx->prec>spy->prec ){
+      apy->type = RD_RESOLVED;
+    }else if( spx->prec<spy->prec ){
+      apx->type = RD_RESOLVED;
+    }
+  }else{
+    assert( 
+      apx->type==SH_RESOLVED ||
+      apx->type==RD_RESOLVED ||
+      apx->type==CONFLICT ||
+      apy->type==SH_RESOLVED ||
+      apy->type==RD_RESOLVED ||
+      apy->type==CONFLICT
+    );
+    /* The REDUCE/SHIFT case cannot happen because SHIFTs come before
+    ** REDUCEs on the list.  If we reach this point it must be because
+    ** the parser conflict had already been resolved. */
+  }
+  return errcnt;
+}
+/********************* From the file "configlist.c" *************************/
+/*
+** Routines to processing a configuration list and building a state
+** in the LEMON parser generator.
+*/
+
+static struct config *freelist = 0;      /* List of free configurations */
+static struct config *current = 0;       /* Top of list of configurations */
+static struct config **currentend = 0;   /* Last on list of configs */
+static struct config *basis = 0;         /* Top of list of basis configs */
+static struct config **basisend = 0;     /* End of list of basis configs */
+
+/* Return a pointer to a new configuration */
+PRIVATE struct config *newconfig(){
+  struct config *new;
+  if( freelist==0 ){
+    int i;
+    int amt = 3;
+    freelist = (struct config *)malloc( sizeof(struct config)*amt );
+    if( freelist==0 ){
+      fprintf(stderr,"Unable to allocate memory for a new configuration.");
+      exit(1);
+    }
+    for(i=0; i<amt-1; i++) freelist[i].next = &freelist[i+1];
+    freelist[amt-1].next = 0;
+  }
+  new = freelist;
+  freelist = freelist->next;
+  return new;
+}
+
+/* The configuration "old" is no longer used */
+PRIVATE void deleteconfig(old)
+struct config *old;
+{
+  old->next = freelist;
+  freelist = old;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_init(){
+  current = 0;
+  currentend = &current;
+  basis = 0;
+  basisend = &basis;
+  Configtable_init();
+  return;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_reset(){
+  current = 0;
+  currentend = &current;
+  basis = 0;
+  basisend = &basis;
+  Configtable_clear(0);
+  return;
+}
+
+/* Add another configuration to the configuration list */
+struct config *Configlist_add(rp,dot)
+struct rule *rp;    /* The rule */
+int dot;            /* Index into the RHS of the rule where the dot goes */
+{
+  struct config *cfp, model;
+
+  assert( currentend!=0 );
+  model.rp = rp;
+  model.dot = dot;
+  cfp = Configtable_find(&model);
+  if( cfp==0 ){
+    cfp = newconfig();
+    cfp->rp = rp;
+    cfp->dot = dot;
+    cfp->fws = SetNew();
+    cfp->stp = 0;
+    cfp->fplp = cfp->bplp = 0;
+    cfp->next = 0;
+    cfp->bp = 0;
+    *currentend = cfp;
+    currentend = &cfp->next;
+    Configtable_insert(cfp);
+  }
+  return cfp;
+}
+
+/* Add a basis configuration to the configuration list */
+struct config *Configlist_addbasis(rp,dot)
+struct rule *rp;
+int dot;
+{
+  struct config *cfp, model;
+
+  assert( basisend!=0 );
+  assert( currentend!=0 );
+  model.rp = rp;
+  model.dot = dot;
+  cfp = Configtable_find(&model);
+  if( cfp==0 ){
+    cfp = newconfig();
+    cfp->rp = rp;
+    cfp->dot = dot;
+    cfp->fws = SetNew();
+    cfp->stp = 0;
+    cfp->fplp = cfp->bplp = 0;
+    cfp->next = 0;
+    cfp->bp = 0;
+    *currentend = cfp;
+    currentend = &cfp->next;
+    *basisend = cfp;
+    basisend = &cfp->bp;
+    Configtable_insert(cfp);
+  }
+  return cfp;
+}
+
+/* Compute the closure of the configuration list */
+void Configlist_closure(lemp)
+struct lemon *lemp;
+{
+  struct config *cfp, *newcfp;
+  struct rule *rp, *newrp;
+  struct symbol *sp, *xsp;
+  int i, dot;
+
+  assert( currentend!=0 );
+  for(cfp=current; cfp; cfp=cfp->next){
+    rp = cfp->rp;
+    dot = cfp->dot;
+    if( dot>=rp->nrhs ) continue;
+    sp = rp->rhs[dot];
+    if( sp->type==NONTERMINAL ){
+      if( sp->rule==0 && sp!=lemp->errsym ){
+        ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.",
+          sp->name);
+        lemp->errorcnt++;
+      }
+      for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){
+        newcfp = Configlist_add(newrp,0);
+        for(i=dot+1; i<rp->nrhs; i++){
+          xsp = rp->rhs[i];
+          if( xsp->type==TERMINAL ){
+            SetAdd(newcfp->fws,xsp->index);
+            break;
+	  }else{
+            SetUnion(newcfp->fws,xsp->firstset);
+            if( xsp->lambda==B_FALSE ) break;
+	  }
+	}
+        if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp);
+      }
+    }
+  }
+  return;
+}
+
+/* Sort the configuration list */
+void Configlist_sort(){
+  current = (struct config *)msort((char *)current,(char **)&(current->next),Configcmp);
+  currentend = 0;
+  return;
+}
+
+/* Sort the basis configuration list */
+void Configlist_sortbasis(){
+  basis = (struct config *)msort((char *)current,(char **)&(current->bp),Configcmp);
+  basisend = 0;
+  return;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_return(){
+  struct config *old;
+  old = current;
+  current = 0;
+  currentend = 0;
+  return old;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_basis(){
+  struct config *old;
+  old = basis;
+  basis = 0;
+  basisend = 0;
+  return old;
+}
+
+/* Free all elements of the given configuration list */
+void Configlist_eat(cfp)
+struct config *cfp;
+{
+  struct config *nextcfp;
+  for(; cfp; cfp=nextcfp){
+    nextcfp = cfp->next;
+    assert( cfp->fplp==0 );
+    assert( cfp->bplp==0 );
+    if( cfp->fws ) SetFree(cfp->fws);
+    deleteconfig(cfp);
+  }
+  return;
+}
+/***************** From the file "error.c" *********************************/
+/*
+** Code for printing error message.
+*/
+
+/* Find a good place to break "msg" so that its length is at least "min"
+** but no more than "max".  Make the point as close to max as possible.
+*/
+static int findbreak(msg,min,max)
+char *msg;
+int min;
+int max;
+{
+  int i,spot;
+  char c;
+  for(i=spot=min; i<=max; i++){
+    c = msg[i];
+    if( c=='\t' ) msg[i] = ' ';
+    if( c=='\n' ){ msg[i] = ' '; spot = i; break; }
+    if( c==0 ){ spot = i; break; }
+    if( c=='-' && i<max-1 ) spot = i+1;
+    if( c==' ' ) spot = i;
+  }
+  return spot;
+}
+
+/*
+** The error message is split across multiple lines if necessary.  The
+** splits occur at a space, if there is a space available near the end
+** of the line.
+*/
+#define ERRMSGSIZE  10000 /* Hope this is big enough.  No way to error check */
+#define LINEWIDTH      79 /* Max width of any output line */
+#define PREFIXLIMIT    30 /* Max width of the prefix on each line */
+void ErrorMsg(const char *filename, int lineno, const char *format, ...){
+  char errmsg[ERRMSGSIZE];
+  char prefix[PREFIXLIMIT+10];
+  int errmsgsize;
+  int prefixsize;
+  int availablewidth;
+  va_list ap;
+  int end, restart, base;
+
+  va_start(ap, format);
+  /* Prepare a prefix to be prepended to every output line */
+  if( lineno>0 ){
+    sprintf(prefix,"%.*s:%d: ",PREFIXLIMIT-10,filename,lineno);
+  }else{
+    sprintf(prefix,"%.*s: ",PREFIXLIMIT-10,filename);
+  }
+  prefixsize = strlen(prefix);
+  availablewidth = LINEWIDTH - prefixsize;
+
+  /* Generate the error message */
+  vsprintf(errmsg,format,ap);
+  va_end(ap);
+  errmsgsize = strlen(errmsg);
+  /* Remove trailing '\n's from the error message. */
+  while( errmsgsize>0 && errmsg[errmsgsize-1]=='\n' ){
+     errmsg[--errmsgsize] = 0;
+  }
+
+  /* Print the error message */
+  base = 0;
+  while( errmsg[base]!=0 ){
+    end = restart = findbreak(&errmsg[base],0,availablewidth);
+    restart += base;
+    while( errmsg[restart]==' ' ) restart++;
+    fprintf(stdout,"%s%.*s\n",prefix,end,&errmsg[base]);
+    base = restart;
+  }
+}
+/**************** From the file "main.c" ************************************/
+/*
+** Main program file for the LEMON parser generator.
+*/
+
+/* Report an out-of-memory condition and abort.  This function
+** is used mostly by the "MemoryCheck" macro in struct.h
+*/
+void memory_error(){
+  fprintf(stderr,"Out of memory.  Aborting...\n");
+  exit(1);
+}
+
+static int nDefine = 0;      /* Number of -D options on the command line */
+static char **azDefine = 0;  /* Name of the -D macros */
+
+/* This routine is called with the argument to each -D command-line option.
+** Add the macro defined to the azDefine array.
+*/
+static void handle_D_option(char *z){
+  char **paz;
+  nDefine++;
+  azDefine = realloc(azDefine, sizeof(azDefine[0])*nDefine);
+  if( azDefine==0 ){
+    fprintf(stderr,"out of memory\n");
+    exit(1);
+  }
+  paz = &azDefine[nDefine-1];
+  *paz = malloc( strlen(z)+1 );
+  if( *paz==0 ){
+    fprintf(stderr,"out of memory\n");
+    exit(1);
+  }
+  strcpy(*paz, z);
+  for(z=*paz; *z && *z!='='; z++){}
+  *z = 0;
+}
+
+
+/* The main program.  Parse the command line and do it... */
+int main(argc,argv)
+int argc;
+char **argv;
+{
+  static int version = 0;
+  static int rpflag = 0;
+  static int basisflag = 0;
+  static int compress = 0;
+  static int quiet = 0;
+  static int statistics = 0;
+  static int mhflag = 0;
+  static struct s_options options[] = {
+    {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."},
+    {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."},
+    {OPT_FSTR, "D", (char*)handle_D_option, "Define an %ifdef macro."},
+    {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."},
+    {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file"},
+    {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."},
+    {OPT_FLAG, "s", (char*)&statistics,
+                                   "Print parser stats to standard output."},
+    {OPT_FLAG, "x", (char*)&version, "Print the version number."},
+    {OPT_FLAG,0,0,0}
+  };
+  int i;
+  struct lemon lem;
+
+  OptInit(argv,options,stderr);
+  if( version ){
+     printf("Lemon version 1.0\n");
+     exit(0); 
+  }
+  if( OptNArgs()!=1 ){
+    fprintf(stderr,"Exactly one filename argument is required.\n");
+    exit(1);
+  }
+  lem.errorcnt = 0;
+
+  /* Initialize the machine */
+  Strsafe_init();
+  Symbol_init();
+  State_init();
+  lem.argv0 = argv[0];
+  lem.filename = OptArg(0);
+  lem.basisflag = basisflag;
+  lem.has_fallback = 0;
+  lem.nconflict = 0;
+  lem.name = lem.include = lem.arg = lem.tokentype = lem.start = 0;
+  lem.vartype = 0;
+  lem.stacksize = 0;
+  lem.error = lem.overflow = lem.failure = lem.accept = lem.tokendest =
+     lem.tokenprefix = lem.outname = lem.extracode = 0;
+  lem.vardest = 0;
+  lem.tablesize = 0;
+  Symbol_new("$");
+  lem.errsym = Symbol_new("error");
+
+  /* Parse the input file */
+  Parse(&lem);
+  if( lem.errorcnt ) exit(lem.errorcnt);
+  if( lem.rule==0 ){
+    fprintf(stderr,"Empty grammar.\n");
+    exit(1);
+  }
+
+  /* Count and index the symbols of the grammar */
+  lem.nsymbol = Symbol_count();
+  Symbol_new("{default}");
+  lem.symbols = Symbol_arrayof();
+  for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i;
+  qsort(lem.symbols,lem.nsymbol+1,sizeof(struct symbol*),
+        (int(*)())Symbolcmpp);
+  for(i=0; i<=lem.nsymbol; i++) lem.symbols[i]->index = i;
+  for(i=1; isupper(lem.symbols[i]->name[0]); i++);
+  lem.nterminal = i;
+
+  /* Generate a reprint of the grammar, if requested on the command line */
+  if( rpflag ){
+    Reprint(&lem);
+  }else{
+    /* Initialize the size for all follow and first sets */
+    SetSize(lem.nterminal);
+
+    /* Find the precedence for every production rule (that has one) */
+    FindRulePrecedences(&lem);
+
+    /* Compute the lambda-nonterminals and the first-sets for every
+    ** nonterminal */
+    FindFirstSets(&lem);
+
+    /* Compute all LR(0) states.  Also record follow-set propagation
+    ** links so that the follow-set can be computed later */
+    lem.nstate = 0;
+    FindStates(&lem);
+    lem.sorted = State_arrayof();
+
+    /* Tie up loose ends on the propagation links */
+    FindLinks(&lem);
+
+    /* Compute the follow set of every reducible configuration */
+    FindFollowSets(&lem);
+
+    /* Compute the action tables */
+    FindActions(&lem);
+
+    /* Compress the action tables */
+    if( compress==0 ) CompressTables(&lem);
+
+    /* Generate a report of the parser generated.  (the "y.output" file) */
+    if( !quiet ) ReportOutput(&lem);
+
+    /* Generate the source code for the parser */
+    ReportTable(&lem, mhflag);
+
+    /* Produce a header file for use by the scanner.  (This step is
+    ** omitted if the "-m" option is used because makeheaders will
+    ** generate the file for us.) */
+    if( !mhflag ) ReportHeader(&lem);
+  }
+  if( statistics ){
+    printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n",
+      lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule);
+    printf("                   %d states, %d parser table entries, %d conflicts\n",
+      lem.nstate, lem.tablesize, lem.nconflict);
+  }
+  if( lem.nconflict ){
+    fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict);
+  }
+  exit(lem.errorcnt + lem.nconflict);
+  return (lem.errorcnt + lem.nconflict);
+}
+/******************** From the file "msort.c" *******************************/
+/*
+** A generic merge-sort program.
+**
+** USAGE:
+** Let "ptr" be a pointer to some structure which is at the head of
+** a null-terminated list.  Then to sort the list call:
+**
+**     ptr = msort(ptr,&(ptr->next),cmpfnc);
+**
+** In the above, "cmpfnc" is a pointer to a function which compares
+** two instances of the structure and returns an integer, as in
+** strcmp.  The second argument is a pointer to the pointer to the
+** second element of the linked list.  This address is used to compute
+** the offset to the "next" field within the structure.  The offset to
+** the "next" field must be constant for all structures in the list.
+**
+** The function returns a new pointer which is the head of the list
+** after sorting.
+**
+** ALGORITHM:
+** Merge-sort.
+*/
+
+/*
+** Return a pointer to the next structure in the linked list.
+*/
+#define NEXT(A) (*(char**)(((unsigned long)A)+offset))
+
+/*
+** Inputs:
+**   a:       A sorted, null-terminated linked list.  (May be null).
+**   b:       A sorted, null-terminated linked list.  (May be null).
+**   cmp:     A pointer to the comparison function.
+**   offset:  Offset in the structure to the "next" field.
+**
+** Return Value:
+**   A pointer to the head of a sorted list containing the elements
+**   of both a and b.
+**
+** Side effects:
+**   The "next" pointers for elements in the lists a and b are
+**   changed.
+*/
+static char *merge(a,b,cmp,offset)
+char *a;
+char *b;
+int (*cmp)();
+int offset;
+{
+  char *ptr, *head;
+
+  if( a==0 ){
+    head = b;
+  }else if( b==0 ){
+    head = a;
+  }else{
+    if( (*cmp)(a,b)<0 ){
+      ptr = a;
+      a = NEXT(a);
+    }else{
+      ptr = b;
+      b = NEXT(b);
+    }
+    head = ptr;
+    while( a && b ){
+      if( (*cmp)(a,b)<0 ){
+        NEXT(ptr) = a;
+        ptr = a;
+        a = NEXT(a);
+      }else{
+        NEXT(ptr) = b;
+        ptr = b;
+        b = NEXT(b);
+      }
+    }
+    if( a ) NEXT(ptr) = a;
+    else    NEXT(ptr) = b;
+  }
+  return head;
+}
+
+/*
+** Inputs:
+**   list:      Pointer to a singly-linked list of structures.
+**   next:      Pointer to pointer to the second element of the list.
+**   cmp:       A comparison function.
+**
+** Return Value:
+**   A pointer to the head of a sorted list containing the elements
+**   orginally in list.
+**
+** Side effects:
+**   The "next" pointers for elements in list are changed.
+*/
+#define LISTSIZE 30
+char *msort(list,next,cmp)
+char *list;
+char **next;
+int (*cmp)();
+{
+  unsigned long offset;
+  char *ep;
+  char *set[LISTSIZE];
+  int i;
+  offset = (unsigned long)next - (unsigned long)list;
+  for(i=0; i<LISTSIZE; i++) set[i] = 0;
+  while( list ){
+    ep = list;
+    list = NEXT(list);
+    NEXT(ep) = 0;
+    for(i=0; i<LISTSIZE-1 && set[i]!=0; i++){
+      ep = merge(ep,set[i],cmp,offset);
+      set[i] = 0;
+    }
+    set[i] = ep;
+  }
+  ep = 0;
+  for(i=0; i<LISTSIZE; i++) if( set[i] ) ep = merge(ep,set[i],cmp,offset);
+  return ep;
+}
+/************************ From the file "option.c" **************************/
+static char **argv;
+static struct s_options *op;
+static FILE *errstream;
+
+#define ISOPT(X) ((X)[0]=='-'||(X)[0]=='+'||strchr((X),'=')!=0)
+
+/*
+** Print the command line with a carrot pointing to the k-th character
+** of the n-th field.
+*/
+static void errline(n,k,err)
+int n;
+int k;
+FILE *err;
+{
+  int spcnt, i;
+  spcnt = 0;
+  if( argv[0] ) fprintf(err,"%s",argv[0]);
+  spcnt = strlen(argv[0]) + 1;
+  for(i=1; i<n && argv[i]; i++){
+    fprintf(err," %s",argv[i]);
+    spcnt += strlen(argv[i]+1);
+  }
+  spcnt += k;
+  for(; argv[i]; i++) fprintf(err," %s",argv[i]);
+  if( spcnt<20 ){
+    fprintf(err,"\n%*s^-- here\n",spcnt,"");
+  }else{
+    fprintf(err,"\n%*shere --^\n",spcnt-7,"");
+  }
+}
+
+/*
+** Return the index of the N-th non-switch argument.  Return -1
+** if N is out of range.
+*/
+static int argindex(n)
+int n;
+{
+  int i;
+  int dashdash = 0;
+  if( argv!=0 && *argv!=0 ){
+    for(i=1; argv[i]; i++){
+      if( dashdash || !ISOPT(argv[i]) ){
+        if( n==0 ) return i;
+        n--;
+      }
+      if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+    }
+  }
+  return -1;
+}
+
+static char emsg[] = "Command line syntax error: ";
+
+/*
+** Process a flag command line argument.
+*/
+static int handleflags(i,err)
+int i;
+FILE *err;
+{
+  int v;
+  int errcnt = 0;
+  int j;
+  for(j=0; op[j].label; j++){
+    if( strncmp(&argv[i][1],op[j].label,strlen(op[j].label))==0 ) break;
+  }
+  v = argv[i][0]=='-' ? 1 : 0;
+  if( op[j].label==0 ){
+    if( err ){
+      fprintf(err,"%sundefined option.\n",emsg);
+      errline(i,1,err);
+    }
+    errcnt++;
+  }else if( op[j].type==OPT_FLAG ){
+    *((int*)op[j].arg) = v;
+  }else if( op[j].type==OPT_FFLAG ){
+    (*(void(*)())(op[j].arg))(v);
+  }else if( op[j].type==OPT_FSTR ){
+    (*(void(*)())(op[j].arg))(&argv[i][2]);
+  }else{
+    if( err ){
+      fprintf(err,"%smissing argument on switch.\n",emsg);
+      errline(i,1,err);
+    }
+    errcnt++;
+  }
+  return errcnt;
+}
+
+/*
+** Process a command line switch which has an argument.
+*/
+static int handleswitch(i,err)
+int i;
+FILE *err;
+{
+  int lv = 0;
+  double dv = 0.0;
+  char *sv = 0, *end;
+  char *cp;
+  int j;
+  int errcnt = 0;
+  cp = strchr(argv[i],'=');
+  *cp = 0;
+  for(j=0; op[j].label; j++){
+    if( strcmp(argv[i],op[j].label)==0 ) break;
+  }
+  *cp = '=';
+  if( op[j].label==0 ){
+    if( err ){
+      fprintf(err,"%sundefined option.\n",emsg);
+      errline(i,0,err);
+    }
+    errcnt++;
+  }else{
+    cp++;
+    switch( op[j].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        if( err ){
+          fprintf(err,"%soption requires an argument.\n",emsg);
+          errline(i,0,err);
+        }
+        errcnt++;
+        break;
+      case OPT_DBL:
+      case OPT_FDBL:
+        dv = strtod(cp,&end);
+        if( *end ){
+          if( err ){
+            fprintf(err,"%sillegal character in floating-point argument.\n",emsg);
+            errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+          }
+          errcnt++;
+        }
+        break;
+      case OPT_INT:
+      case OPT_FINT:
+        lv = strtol(cp,&end,0);
+        if( *end ){
+          if( err ){
+            fprintf(err,"%sillegal character in integer argument.\n",emsg);
+            errline(i,((unsigned long)end)-(unsigned long)argv[i],err);
+          }
+          errcnt++;
+        }
+        break;
+      case OPT_STR:
+      case OPT_FSTR:
+        sv = cp;
+        break;
+    }
+    switch( op[j].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        break;
+      case OPT_DBL:
+        *(double*)(op[j].arg) = dv;
+        break;
+      case OPT_FDBL:
+        (*(void(*)())(op[j].arg))(dv);
+        break;
+      case OPT_INT:
+        *(int*)(op[j].arg) = lv;
+        break;
+      case OPT_FINT:
+        (*(void(*)())(op[j].arg))((int)lv);
+        break;
+      case OPT_STR:
+        *(char**)(op[j].arg) = sv;
+        break;
+      case OPT_FSTR:
+        (*(void(*)())(op[j].arg))(sv);
+        break;
+    }
+  }
+  return errcnt;
+}
+
+int OptInit(a,o,err)
+char **a;
+struct s_options *o;
+FILE *err;
+{
+  int errcnt = 0;
+  argv = a;
+  op = o;
+  errstream = err;
+  if( argv && *argv && op ){
+    int i;
+    for(i=1; argv[i]; i++){
+      if( argv[i][0]=='+' || argv[i][0]=='-' ){
+        errcnt += handleflags(i,err);
+      }else if( strchr(argv[i],'=') ){
+        errcnt += handleswitch(i,err);
+      }
+    }
+  }
+  if( errcnt>0 ){
+    fprintf(err,"Valid command line options for \"%s\" are:\n",*a);
+    OptPrint();
+    exit(1);
+  }
+  return 0;
+}
+
+int OptNArgs(){
+  int cnt = 0;
+  int dashdash = 0;
+  int i;
+  if( argv!=0 && argv[0]!=0 ){
+    for(i=1; argv[i]; i++){
+      if( dashdash || !ISOPT(argv[i]) ) cnt++;
+      if( strcmp(argv[i],"--")==0 ) dashdash = 1;
+    }
+  }
+  return cnt;
+}
+
+char *OptArg(n)
+int n;
+{
+  int i;
+  i = argindex(n);
+  return i>=0 ? argv[i] : 0;
+}
+
+void OptErr(n)
+int n;
+{
+  int i;
+  i = argindex(n);
+  if( i>=0 ) errline(i,0,errstream);
+}
+
+void OptPrint(){
+  int i;
+  int max, len;
+  max = 0;
+  for(i=0; op[i].label; i++){
+    len = strlen(op[i].label) + 1;
+    switch( op[i].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        break;
+      case OPT_INT:
+      case OPT_FINT:
+        len += 9;       /* length of "<integer>" */
+        break;
+      case OPT_DBL:
+      case OPT_FDBL:
+        len += 6;       /* length of "<real>" */
+        break;
+      case OPT_STR:
+      case OPT_FSTR:
+        len += 8;       /* length of "<string>" */
+        break;
+    }
+    if( len>max ) max = len;
+  }
+  for(i=0; op[i].label; i++){
+    switch( op[i].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        fprintf(errstream,"  -%-*s  %s\n",max,op[i].label,op[i].message);
+        break;
+      case OPT_INT:
+      case OPT_FINT:
+        fprintf(errstream,"  %s=<integer>%*s  %s\n",op[i].label,
+          (int)(max-strlen(op[i].label)-9),"",op[i].message);
+        break;
+      case OPT_DBL:
+      case OPT_FDBL:
+        fprintf(errstream,"  %s=<real>%*s  %s\n",op[i].label,
+          (int)(max-strlen(op[i].label)-6),"",op[i].message);
+        break;
+      case OPT_STR:
+      case OPT_FSTR:
+        fprintf(errstream,"  %s=<string>%*s  %s\n",op[i].label,
+          (int)(max-strlen(op[i].label)-8),"",op[i].message);
+        break;
+    }
+  }
+}
+/*********************** From the file "parse.c" ****************************/
+/*
+** Input file parser for the LEMON parser generator.
+*/
+
+/* The state of the parser */
+struct pstate {
+  char *filename;       /* Name of the input file */
+  int tokenlineno;      /* Linenumber at which current token starts */
+  int errorcnt;         /* Number of errors so far */
+  char *tokenstart;     /* Text of current token */
+  struct lemon *gp;     /* Global state vector */
+  enum e_state {
+    INITIALIZE,
+    WAITING_FOR_DECL_OR_RULE,
+    WAITING_FOR_DECL_KEYWORD,
+    WAITING_FOR_DECL_ARG,
+    WAITING_FOR_PRECEDENCE_SYMBOL,
+    WAITING_FOR_ARROW,
+    IN_RHS,
+    LHS_ALIAS_1,
+    LHS_ALIAS_2,
+    LHS_ALIAS_3,
+    RHS_ALIAS_1,
+    RHS_ALIAS_2,
+    PRECEDENCE_MARK_1,
+    PRECEDENCE_MARK_2,
+    RESYNC_AFTER_RULE_ERROR,
+    RESYNC_AFTER_DECL_ERROR,
+    WAITING_FOR_DESTRUCTOR_SYMBOL,
+    WAITING_FOR_DATATYPE_SYMBOL,
+    WAITING_FOR_FALLBACK_ID
+  } state;                   /* The state of the parser */
+  struct symbol *fallback;   /* The fallback token */
+  struct symbol *lhs;        /* Left-hand side of current rule */
+  char *lhsalias;            /* Alias for the LHS */
+  int nrhs;                  /* Number of right-hand side symbols seen */
+  struct symbol *rhs[MAXRHS];  /* RHS symbols */
+  char *alias[MAXRHS];       /* Aliases for each RHS symbol (or NULL) */
+  struct rule *prevrule;     /* Previous rule parsed */
+  char *declkeyword;         /* Keyword of a declaration */
+  char **declargslot;        /* Where the declaration argument should be put */
+  int *decllnslot;           /* Where the declaration linenumber is put */
+  enum e_assoc declassoc;    /* Assign this association to decl arguments */
+  int preccounter;           /* Assign this precedence to decl arguments */
+  struct rule *firstrule;    /* Pointer to first rule in the grammar */
+  struct rule *lastrule;     /* Pointer to the most recently parsed rule */
+};
+
+/* Parse a single token */
+static void parseonetoken(psp)
+struct pstate *psp;
+{
+  char *x;
+  x = Strsafe(psp->tokenstart);     /* Save the token permanently */
+#if 0
+  printf("%s:%d: Token=[%s] state=%d\n",psp->filename,psp->tokenlineno,
+    x,psp->state);
+#endif
+  switch( psp->state ){
+    case INITIALIZE:
+      psp->prevrule = 0;
+      psp->preccounter = 0;
+      psp->firstrule = psp->lastrule = 0;
+      psp->gp->nrule = 0;
+      /* Fall thru to next case */
+    case WAITING_FOR_DECL_OR_RULE:
+      if( x[0]=='%' ){
+        psp->state = WAITING_FOR_DECL_KEYWORD;
+      }else if( islower(x[0]) ){
+        psp->lhs = Symbol_new(x);
+        psp->nrhs = 0;
+        psp->lhsalias = 0;
+        psp->state = WAITING_FOR_ARROW;
+      }else if( x[0]=='{' ){
+        if( psp->prevrule==0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+"There is not prior rule opon which to attach the code \
+fragment which begins on this line.");
+          psp->errorcnt++;
+	}else if( psp->prevrule->code!=0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+"Code fragment beginning on this line is not the first \
+to follow the previous rule.");
+          psp->errorcnt++;
+        }else{
+          psp->prevrule->line = psp->tokenlineno;
+          psp->prevrule->code = &x[1];
+	}
+      }else if( x[0]=='[' ){
+        psp->state = PRECEDENCE_MARK_1;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Token \"%s\" should be either \"%%\" or a nonterminal name.",
+          x);
+        psp->errorcnt++;
+      }
+      break;
+    case PRECEDENCE_MARK_1:
+      if( !isupper(x[0]) ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "The precedence symbol must be a terminal.");
+        psp->errorcnt++;
+      }else if( psp->prevrule==0 ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "There is no prior rule to assign precedence \"[%s]\".",x);
+        psp->errorcnt++;
+      }else if( psp->prevrule->precsym!=0 ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+"Precedence mark on this line is not the first \
+to follow the previous rule.");
+        psp->errorcnt++;
+      }else{
+        psp->prevrule->precsym = Symbol_new(x);
+      }
+      psp->state = PRECEDENCE_MARK_2;
+      break;
+    case PRECEDENCE_MARK_2:
+      if( x[0]!=']' ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \"]\" on precedence mark.");
+        psp->errorcnt++;
+      }
+      psp->state = WAITING_FOR_DECL_OR_RULE;
+      break;
+    case WAITING_FOR_ARROW:
+      if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+        psp->state = IN_RHS;
+      }else if( x[0]=='(' ){
+        psp->state = LHS_ALIAS_1;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Expected to see a \":\" following the LHS symbol \"%s\".",
+          psp->lhs->name);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case LHS_ALIAS_1:
+      if( isalpha(x[0]) ){
+        psp->lhsalias = x;
+        psp->state = LHS_ALIAS_2;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "\"%s\" is not a valid alias for the LHS \"%s\"\n",
+          x,psp->lhs->name);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case LHS_ALIAS_2:
+      if( x[0]==')' ){
+        psp->state = LHS_ALIAS_3;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case LHS_ALIAS_3:
+      if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+        psp->state = IN_RHS;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \"->\" following: \"%s(%s)\".",
+           psp->lhs->name,psp->lhsalias);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case IN_RHS:
+      if( x[0]=='.' ){
+        struct rule *rp;
+        rp = (struct rule *)malloc( sizeof(struct rule) + 
+             sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs );
+        if( rp==0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Can't allocate enough memory for this rule.");
+          psp->errorcnt++;
+          psp->prevrule = 0;
+	}else{
+          int i;
+          rp->ruleline = psp->tokenlineno;
+          rp->rhs = (struct symbol**)&rp[1];
+          rp->rhsalias = (char**)&(rp->rhs[psp->nrhs]);
+          for(i=0; i<psp->nrhs; i++){
+            rp->rhs[i] = psp->rhs[i];
+            rp->rhsalias[i] = psp->alias[i];
+	  }
+          rp->lhs = psp->lhs;
+          rp->lhsalias = psp->lhsalias;
+          rp->nrhs = psp->nrhs;
+          rp->code = 0;
+          rp->precsym = 0;
+          rp->index = psp->gp->nrule++;
+          rp->nextlhs = rp->lhs->rule;
+          rp->lhs->rule = rp;
+          rp->next = 0;
+          if( psp->firstrule==0 ){
+            psp->firstrule = psp->lastrule = rp;
+	  }else{
+            psp->lastrule->next = rp;
+            psp->lastrule = rp;
+	  }
+          psp->prevrule = rp;
+	}
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( isalpha(x[0]) ){
+        if( psp->nrhs>=MAXRHS ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Too many symbol on RHS or rule beginning at \"%s\".",
+            x);
+          psp->errorcnt++;
+          psp->state = RESYNC_AFTER_RULE_ERROR;
+	}else{
+          psp->rhs[psp->nrhs] = Symbol_new(x);
+          psp->alias[psp->nrhs] = 0;
+          psp->nrhs++;
+	}
+      }else if( x[0]=='(' && psp->nrhs>0 ){
+        psp->state = RHS_ALIAS_1;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Illegal character on RHS of rule: \"%s\".",x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case RHS_ALIAS_1:
+      if( isalpha(x[0]) ){
+        psp->alias[psp->nrhs-1] = x;
+        psp->state = RHS_ALIAS_2;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n",
+          x,psp->rhs[psp->nrhs-1]->name);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case RHS_ALIAS_2:
+      if( x[0]==')' ){
+        psp->state = IN_RHS;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case WAITING_FOR_DECL_KEYWORD:
+      if( isalpha(x[0]) ){
+        psp->declkeyword = x;
+        psp->declargslot = 0;
+        psp->decllnslot = 0;
+        psp->state = WAITING_FOR_DECL_ARG;
+        if( strcmp(x,"name")==0 ){
+          psp->declargslot = &(psp->gp->name);
+	}else if( strcmp(x,"include")==0 ){
+          psp->declargslot = &(psp->gp->include);
+          psp->decllnslot = &psp->gp->includeln;
+	}else if( strcmp(x,"code")==0 ){
+          psp->declargslot = &(psp->gp->extracode);
+          psp->decllnslot = &psp->gp->extracodeln;
+	}else if( strcmp(x,"token_destructor")==0 ){
+          psp->declargslot = &psp->gp->tokendest;
+          psp->decllnslot = &psp->gp->tokendestln;
+	}else if( strcmp(x,"default_destructor")==0 ){
+          psp->declargslot = &psp->gp->vardest;
+          psp->decllnslot = &psp->gp->vardestln;
+	}else if( strcmp(x,"token_prefix")==0 ){
+          psp->declargslot = &psp->gp->tokenprefix;
+	}else if( strcmp(x,"syntax_error")==0 ){
+          psp->declargslot = &(psp->gp->error);
+          psp->decllnslot = &psp->gp->errorln;
+	}else if( strcmp(x,"parse_accept")==0 ){
+          psp->declargslot = &(psp->gp->accept);
+          psp->decllnslot = &psp->gp->acceptln;
+	}else if( strcmp(x,"parse_failure")==0 ){
+          psp->declargslot = &(psp->gp->failure);
+          psp->decllnslot = &psp->gp->failureln;
+	}else if( strcmp(x,"stack_overflow")==0 ){
+          psp->declargslot = &(psp->gp->overflow);
+          psp->decllnslot = &psp->gp->overflowln;
+        }else if( strcmp(x,"extra_argument")==0 ){
+          psp->declargslot = &(psp->gp->arg);
+        }else if( strcmp(x,"token_type")==0 ){
+          psp->declargslot = &(psp->gp->tokentype);
+        }else if( strcmp(x,"default_type")==0 ){
+          psp->declargslot = &(psp->gp->vartype);
+        }else if( strcmp(x,"stack_size")==0 ){
+          psp->declargslot = &(psp->gp->stacksize);
+        }else if( strcmp(x,"start_symbol")==0 ){
+          psp->declargslot = &(psp->gp->start);
+        }else if( strcmp(x,"left")==0 ){
+          psp->preccounter++;
+          psp->declassoc = LEFT;
+          psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+        }else if( strcmp(x,"right")==0 ){
+          psp->preccounter++;
+          psp->declassoc = RIGHT;
+          psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+        }else if( strcmp(x,"nonassoc")==0 ){
+          psp->preccounter++;
+          psp->declassoc = NONE;
+          psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+	}else if( strcmp(x,"destructor")==0 ){
+          psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL;
+	}else if( strcmp(x,"type")==0 ){
+          psp->state = WAITING_FOR_DATATYPE_SYMBOL;
+        }else if( strcmp(x,"fallback")==0 ){
+          psp->fallback = 0;
+          psp->state = WAITING_FOR_FALLBACK_ID;
+        }else{
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Unknown declaration keyword: \"%%%s\".",x);
+          psp->errorcnt++;
+          psp->state = RESYNC_AFTER_DECL_ERROR;
+	}
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Illegal declaration keyword: \"%s\".",x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }
+      break;
+    case WAITING_FOR_DESTRUCTOR_SYMBOL:
+      if( !isalpha(x[0]) ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Symbol name missing after %destructor keyword");
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        psp->declargslot = &sp->destructor;
+        psp->decllnslot = &sp->destructorln;
+        psp->state = WAITING_FOR_DECL_ARG;
+      }
+      break;
+    case WAITING_FOR_DATATYPE_SYMBOL:
+      if( !isalpha(x[0]) ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Symbol name missing after %destructor keyword");
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        psp->declargslot = &sp->datatype;
+        psp->decllnslot = 0;
+        psp->state = WAITING_FOR_DECL_ARG;
+      }
+      break;
+    case WAITING_FOR_PRECEDENCE_SYMBOL:
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( isupper(x[0]) ){
+        struct symbol *sp;
+        sp = Symbol_new(x);
+        if( sp->prec>=0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Symbol \"%s\" has already be given a precedence.",x);
+          psp->errorcnt++;
+	}else{
+          sp->prec = psp->preccounter;
+          sp->assoc = psp->declassoc;
+	}
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Can't assign a precedence to \"%s\".",x);
+        psp->errorcnt++;
+      }
+      break;
+    case WAITING_FOR_DECL_ARG:
+      if( (x[0]=='{' || x[0]=='\"' || isalnum(x[0])) ){
+        if( *(psp->declargslot)!=0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "The argument \"%s\" to declaration \"%%%s\" is not the first.",
+            x[0]=='\"' ? &x[1] : x,psp->declkeyword);
+          psp->errorcnt++;
+          psp->state = RESYNC_AFTER_DECL_ERROR;
+	}else{
+          *(psp->declargslot) = (x[0]=='\"' || x[0]=='{') ? &x[1] : x;
+          if( psp->decllnslot ) *psp->decllnslot = psp->tokenlineno;
+          psp->state = WAITING_FOR_DECL_OR_RULE;
+	}
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Illegal argument to %%%s: %s",psp->declkeyword,x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }
+      break;
+    case WAITING_FOR_FALLBACK_ID:
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( !isupper(x[0]) ){
+        ErrorMsg(psp->filename, psp->tokenlineno,
+          "%%fallback argument \"%s\" should be a token", x);
+        psp->errorcnt++;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        if( psp->fallback==0 ){
+          psp->fallback = sp;
+        }else if( sp->fallback ){
+          ErrorMsg(psp->filename, psp->tokenlineno,
+            "More than one fallback assigned to token %s", x);
+          psp->errorcnt++;
+        }else{
+          sp->fallback = psp->fallback;
+          psp->gp->has_fallback = 1;
+        }
+      }
+      break;
+    case RESYNC_AFTER_RULE_ERROR:
+/*      if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+**      break; */
+    case RESYNC_AFTER_DECL_ERROR:
+      if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+      if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD;
+      break;
+  }
+}
+
+/* Run the proprocessor over the input file text.  The global variables
+** azDefine[0] through azDefine[nDefine-1] contains the names of all defined
+** macros.  This routine looks for "%ifdef" and "%ifndef" and "%endif" and
+** comments them out.  Text in between is also commented out as appropriate.
+*/
+static preprocess_input(char *z){
+  int i, j, k, n;
+  int exclude = 0;
+  int start;
+  int lineno = 1;
+  int start_lineno;
+  for(i=0; z[i]; i++){
+    if( z[i]=='\n' ) lineno++;
+    if( z[i]!='%' || (i>0 && z[i-1]!='\n') ) continue;
+    if( strncmp(&z[i],"%endif",6)==0 && isspace(z[i+6]) ){
+      if( exclude ){
+        exclude--;
+        if( exclude==0 ){
+          for(j=start; j<i; j++) if( z[j]!='\n' ) z[j] = ' ';
+        }
+      }
+      for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+    }else if( (strncmp(&z[i],"%ifdef",6)==0 && isspace(z[i+6]))
+          || (strncmp(&z[i],"%ifndef",7)==0 && isspace(z[i+7])) ){
+      if( exclude ){
+        exclude++;
+      }else{
+        for(j=i+7; isspace(z[j]); j++){}
+        for(n=0; z[j+n] && !isspace(z[j+n]); n++){}
+        exclude = 1;
+        for(k=0; k<nDefine; k++){
+          if( strncmp(azDefine[k],&z[j],n)==0 && strlen(azDefine[k])==n ){
+            exclude = 0;
+            break;
+          }
+        }
+        if( z[i+3]=='n' ) exclude = !exclude;
+        if( exclude ){
+          start = i;
+          start_lineno = lineno;
+        }
+      }
+      for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+    }
+  }
+  if( exclude ){
+    fprintf(stderr,"unterminated %%ifdef starting on line %d\n", start_lineno);
+    exit(1);
+  }
+}
+
+/* In spite of its name, this function is really a scanner.  It read
+** in the entire input file (all at once) then tokenizes it.  Each
+** token is passed to the function "parseonetoken" which builds all
+** the appropriate data structures in the global state vector "gp".
+*/
+void Parse(gp)
+struct lemon *gp;
+{
+  struct pstate ps;
+  FILE *fp;
+  char *filebuf;
+  int filesize;
+  int lineno;
+  int c;
+  char *cp, *nextcp;
+  int startline = 0;
+
+  ps.gp = gp;
+  ps.filename = gp->filename;
+  ps.errorcnt = 0;
+  ps.state = INITIALIZE;
+
+  /* Begin by reading the input file */
+  fp = fopen(ps.filename,"rb");
+  if( fp==0 ){
+    ErrorMsg(ps.filename,0,"Can't open this file for reading.");
+    gp->errorcnt++;
+    return;
+  }
+  fseek(fp,0,2);
+  filesize = ftell(fp);
+  rewind(fp);
+  filebuf = (char *)malloc( filesize+1 );
+  if( filebuf==0 ){
+    ErrorMsg(ps.filename,0,"Can't allocate %d of memory to hold this file.",
+      filesize+1);
+    gp->errorcnt++;
+    return;
+  }
+  if( fread(filebuf,1,filesize,fp)!=filesize ){
+    ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.",
+      filesize);
+    free(filebuf);
+    gp->errorcnt++;
+    return;
+  }
+  fclose(fp);
+  filebuf[filesize] = 0;
+
+  /* Make an initial pass through the file to handle %ifdef and %ifndef */
+  preprocess_input(filebuf);
+
+  /* Now scan the text of the input file */
+  lineno = 1;
+  for(cp=filebuf; (c= *cp)!=0; ){
+    if( c=='\n' ) lineno++;              /* Keep track of the line number */
+    if( isspace(c) ){ cp++; continue; }  /* Skip all white space */
+    if( c=='/' && cp[1]=='/' ){          /* Skip C++ style comments */
+      cp+=2;
+      while( (c= *cp)!=0 && c!='\n' ) cp++;
+      continue;
+    }
+    if( c=='/' && cp[1]=='*' ){          /* Skip C style comments */
+      cp+=2;
+      while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){
+        if( c=='\n' ) lineno++;
+        cp++;
+      }
+      if( c ) cp++;
+      continue;
+    }
+    ps.tokenstart = cp;                /* Mark the beginning of the token */
+    ps.tokenlineno = lineno;           /* Linenumber on which token begins */
+    if( c=='\"' ){                     /* String literals */
+      cp++;
+      while( (c= *cp)!=0 && c!='\"' ){
+        if( c=='\n' ) lineno++;
+        cp++;
+      }
+      if( c==0 ){
+        ErrorMsg(ps.filename,startline,
+"String starting on this line is not terminated before the end of the file.");
+        ps.errorcnt++;
+        nextcp = cp;
+      }else{
+        nextcp = cp+1;
+      }
+    }else if( c=='{' ){               /* A block of C code */
+      int level;
+      cp++;
+      for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){
+        if( c=='\n' ) lineno++;
+        else if( c=='{' ) level++;
+        else if( c=='}' ) level--;
+        else if( c=='/' && cp[1]=='*' ){  /* Skip comments */
+          int prevc;
+          cp = &cp[2];
+          prevc = 0;
+          while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){
+            if( c=='\n' ) lineno++;
+            prevc = c;
+            cp++;
+	  }
+	}else if( c=='/' && cp[1]=='/' ){  /* Skip C++ style comments too */
+          cp = &cp[2];
+          while( (c= *cp)!=0 && c!='\n' ) cp++;
+          if( c ) lineno++;
+	}else if( c=='\'' || c=='\"' ){    /* String a character literals */
+          int startchar, prevc;
+          startchar = c;
+          prevc = 0;
+          for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){
+            if( c=='\n' ) lineno++;
+            if( prevc=='\\' ) prevc = 0;
+            else              prevc = c;
+	  }
+	}
+      }
+      if( c==0 ){
+        ErrorMsg(ps.filename,ps.tokenlineno,
+"C code starting on this line is not terminated before the end of the file.");
+        ps.errorcnt++;
+        nextcp = cp;
+      }else{
+        nextcp = cp+1;
+      }
+    }else if( isalnum(c) ){          /* Identifiers */
+      while( (c= *cp)!=0 && (isalnum(c) || c=='_') ) cp++;
+      nextcp = cp;
+    }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */
+      cp += 3;
+      nextcp = cp;
+    }else{                          /* All other (one character) operators */
+      cp++;
+      nextcp = cp;
+    }
+    c = *cp;
+    *cp = 0;                        /* Null terminate the token */
+    parseonetoken(&ps);             /* Parse the token */
+    *cp = c;                        /* Restore the buffer */
+    cp = nextcp;
+  }
+  free(filebuf);                    /* Release the buffer after parsing */
+  gp->rule = ps.firstrule;
+  gp->errorcnt = ps.errorcnt;
+}
+/*************************** From the file "plink.c" *********************/
+/*
+** Routines processing configuration follow-set propagation links
+** in the LEMON parser generator.
+*/
+static struct plink *plink_freelist = 0;
+
+/* Allocate a new plink */
+struct plink *Plink_new(){
+  struct plink *new;
+
+  if( plink_freelist==0 ){
+    int i;
+    int amt = 100;
+    plink_freelist = (struct plink *)malloc( sizeof(struct plink)*amt );
+    if( plink_freelist==0 ){
+      fprintf(stderr,
+      "Unable to allocate memory for a new follow-set propagation link.\n");
+      exit(1);
+    }
+    for(i=0; i<amt-1; i++) plink_freelist[i].next = &plink_freelist[i+1];
+    plink_freelist[amt-1].next = 0;
+  }
+  new = plink_freelist;
+  plink_freelist = plink_freelist->next;
+  return new;
+}
+
+/* Add a plink to a plink list */
+void Plink_add(plpp,cfp)
+struct plink **plpp;
+struct config *cfp;
+{
+  struct plink *new;
+  new = Plink_new();
+  new->next = *plpp;
+  *plpp = new;
+  new->cfp = cfp;
+}
+
+/* Transfer every plink on the list "from" to the list "to" */
+void Plink_copy(to,from)
+struct plink **to;
+struct plink *from;
+{
+  struct plink *nextpl;
+  while( from ){
+    nextpl = from->next;
+    from->next = *to;
+    *to = from;
+    from = nextpl;
+  }
+}
+
+/* Delete every plink on the list */
+void Plink_delete(plp)
+struct plink *plp;
+{
+  struct plink *nextpl;
+
+  while( plp ){
+    nextpl = plp->next;
+    plp->next = plink_freelist;
+    plink_freelist = plp;
+    plp = nextpl;
+  }
+}
+/*********************** From the file "report.c" **************************/
+/*
+** Procedures for generating reports and tables in the LEMON parser generator.
+*/
+
+/* Generate a filename with the given suffix.  Space to hold the
+** name comes from malloc() and must be freed by the calling
+** function.
+*/
+PRIVATE char *file_makename(lemp,suffix)
+struct lemon *lemp;
+char *suffix;
+{
+  char *name;
+  char *cp;
+
+  name = malloc( strlen(lemp->filename) + strlen(suffix) + 5 );
+  if( name==0 ){
+    fprintf(stderr,"Can't allocate space for a filename.\n");
+    exit(1);
+  }
+  strcpy(name,lemp->filename);
+  cp = strrchr(name,'.');
+  if( cp ) *cp = 0;
+  strcat(name,suffix);
+  return name;
+}
+
+/* Open a file with a name based on the name of the input file,
+** but with a different (specified) suffix, and return a pointer
+** to the stream */
+PRIVATE FILE *file_open(lemp,suffix,mode)
+struct lemon *lemp;
+char *suffix;
+char *mode;
+{
+  FILE *fp;
+
+  if( lemp->outname ) free(lemp->outname);
+  lemp->outname = file_makename(lemp, suffix);
+  fp = fopen(lemp->outname,mode);
+  if( fp==0 && *mode=='w' ){
+    fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname);
+    lemp->errorcnt++;
+    return 0;
+  }
+  return fp;
+}
+
+/* Duplicate the input file without comments and without actions 
+** on rules */
+void Reprint(lemp)
+struct lemon *lemp;
+{
+  struct rule *rp;
+  struct symbol *sp;
+  int i, j, maxlen, len, ncolumns, skip;
+  printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename);
+  maxlen = 10;
+  for(i=0; i<lemp->nsymbol; i++){
+    sp = lemp->symbols[i];
+    len = strlen(sp->name);
+    if( len>maxlen ) maxlen = len;
+  }
+  ncolumns = 76/(maxlen+5);
+  if( ncolumns<1 ) ncolumns = 1;
+  skip = (lemp->nsymbol + ncolumns - 1)/ncolumns;
+  for(i=0; i<skip; i++){
+    printf("//");
+    for(j=i; j<lemp->nsymbol; j+=skip){
+      sp = lemp->symbols[j];
+      assert( sp->index==j );
+      printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name);
+    }
+    printf("\n");
+  }
+  for(rp=lemp->rule; rp; rp=rp->next){
+    printf("%s",rp->lhs->name);
+/*    if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */
+    printf(" ::=");
+    for(i=0; i<rp->nrhs; i++){
+      printf(" %s",rp->rhs[i]->name);
+/*      if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */
+    }
+    printf(".");
+    if( rp->precsym ) printf(" [%s]",rp->precsym->name);
+/*    if( rp->code ) printf("\n    %s",rp->code); */
+    printf("\n");
+  }
+}
+
+void ConfigPrint(fp,cfp)
+FILE *fp;
+struct config *cfp;
+{
+  struct rule *rp;
+  int i;
+  rp = cfp->rp;
+  fprintf(fp,"%s ::=",rp->lhs->name);
+  for(i=0; i<=rp->nrhs; i++){
+    if( i==cfp->dot ) fprintf(fp," *");
+    if( i==rp->nrhs ) break;
+    fprintf(fp," %s",rp->rhs[i]->name);
+  }
+}
+
+/* #define TEST */
+#ifdef TEST
+/* Print a set */
+PRIVATE void SetPrint(out,set,lemp)
+FILE *out;
+char *set;
+struct lemon *lemp;
+{
+  int i;
+  char *spacer;
+  spacer = "";
+  fprintf(out,"%12s[","");
+  for(i=0; i<lemp->nterminal; i++){
+    if( SetFind(set,i) ){
+      fprintf(out,"%s%s",spacer,lemp->symbols[i]->name);
+      spacer = " ";
+    }
+  }
+  fprintf(out,"]\n");
+}
+
+/* Print a plink chain */
+PRIVATE void PlinkPrint(out,plp,tag)
+FILE *out;
+struct plink *plp;
+char *tag;
+{
+  while( plp ){
+    fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->index);
+    ConfigPrint(out,plp->cfp);
+    fprintf(out,"\n");
+    plp = plp->next;
+  }
+}
+#endif
+
+/* Print an action to the given file descriptor.  Return FALSE if
+** nothing was actually printed.
+*/
+int PrintAction(struct action *ap, FILE *fp, int indent){
+  int result = 1;
+  switch( ap->type ){
+    case SHIFT:
+      fprintf(fp,"%*s shift  %d",indent,ap->sp->name,ap->x.stp->index);
+      break;
+    case REDUCE:
+      fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index);
+      break;
+    case ACCEPT:
+      fprintf(fp,"%*s accept",indent,ap->sp->name);
+      break;
+    case ERROR:
+      fprintf(fp,"%*s error",indent,ap->sp->name);
+      break;
+    case CONFLICT:
+      fprintf(fp,"%*s reduce %-3d ** Parsing conflict **",
+        indent,ap->sp->name,ap->x.rp->index);
+      break;
+    case SH_RESOLVED:
+    case RD_RESOLVED:
+    case NOT_USED:
+      result = 0;
+      break;
+  }
+  return result;
+}
+
+/* Generate the "y.output" log file */
+void ReportOutput(lemp)
+struct lemon *lemp;
+{
+  int i;
+  struct state *stp;
+  struct config *cfp;
+  struct action *ap;
+  FILE *fp;
+
+  fp = file_open(lemp,".out","wb");
+  if( fp==0 ) return;
+  fprintf(fp," \b");
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    fprintf(fp,"State %d:\n",stp->index);
+    if( lemp->basisflag ) cfp=stp->bp;
+    else                  cfp=stp->cfp;
+    while( cfp ){
+      char buf[20];
+      if( cfp->dot==cfp->rp->nrhs ){
+        sprintf(buf,"(%d)",cfp->rp->index);
+        fprintf(fp,"    %5s ",buf);
+      }else{
+        fprintf(fp,"          ");
+      }
+      ConfigPrint(fp,cfp);
+      fprintf(fp,"\n");
+#ifdef TEST
+      SetPrint(fp,cfp->fws,lemp);
+      PlinkPrint(fp,cfp->fplp,"To  ");
+      PlinkPrint(fp,cfp->bplp,"From");
+#endif
+      if( lemp->basisflag ) cfp=cfp->bp;
+      else                  cfp=cfp->next;
+    }
+    fprintf(fp,"\n");
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( PrintAction(ap,fp,30) ) fprintf(fp,"\n");
+    }
+    fprintf(fp,"\n");
+  }
+  fclose(fp);
+  return;
+}
+
+/* Search for the file "name" which is in the same directory as
+** the exacutable */
+PRIVATE char *pathsearch(argv0,name,modemask)
+char *argv0;
+char *name;
+int modemask;
+{
+  char *pathlist;
+  char *path,*cp;
+  char c;
+  extern int access();
+
+#ifdef __WIN32__
+  cp = strrchr(argv0,'\\');
+#else
+  cp = strrchr(argv0,'/');
+#endif
+  if( cp ){
+    c = *cp;
+    *cp = 0;
+    path = (char *)malloc( strlen(argv0) + strlen(name) + 2 );
+    if( path ) sprintf(path,"%s/%s",argv0,name);
+    *cp = c;
+  }else{
+    extern char *getenv();
+    pathlist = getenv("PATH");
+    if( pathlist==0 ) pathlist = ".:/bin:/usr/bin";
+    path = (char *)malloc( strlen(pathlist)+strlen(name)+2 );
+    if( path!=0 ){
+      while( *pathlist ){
+        cp = strchr(pathlist,':');
+        if( cp==0 ) cp = &pathlist[strlen(pathlist)];
+        c = *cp;
+        *cp = 0;
+        sprintf(path,"%s/%s",pathlist,name);
+        *cp = c;
+        if( c==0 ) pathlist = "";
+        else pathlist = &cp[1];
+        if( access(path,modemask)==0 ) break;
+      }
+    }
+  }
+  return path;
+}
+
+/* Given an action, compute the integer value for that action
+** which is to be put in the action table of the generated machine.
+** Return negative if no action should be generated.
+*/
+PRIVATE int compute_action(lemp,ap)
+struct lemon *lemp;
+struct action *ap;
+{
+  int act;
+  switch( ap->type ){
+    case SHIFT:  act = ap->x.stp->index;               break;
+    case REDUCE: act = ap->x.rp->index + lemp->nstate; break;
+    case ERROR:  act = lemp->nstate + lemp->nrule;     break;
+    case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break;
+    default:     act = -1; break;
+  }
+  return act;
+}
+
+#define LINESIZE 1000
+/* The next cluster of routines are for reading the template file
+** and writing the results to the generated parser */
+/* The first function transfers data from "in" to "out" until
+** a line is seen which begins with "%%".  The line number is
+** tracked.
+**
+** if name!=0, then any word that begin with "Parse" is changed to
+** begin with *name instead.
+*/
+PRIVATE void tplt_xfer(name,in,out,lineno)
+char *name;
+FILE *in;
+FILE *out;
+int *lineno;
+{
+  int i, iStart;
+  char line[LINESIZE];
+  while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){
+    (*lineno)++;
+    iStart = 0;
+    if( name ){
+      for(i=0; line[i]; i++){
+        if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0
+          && (i==0 || !isalpha(line[i-1]))
+        ){
+          if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]);
+          fprintf(out,"%s",name);
+          i += 4;
+          iStart = i+1;
+        }
+      }
+    }
+    fprintf(out,"%s",&line[iStart]);
+  }
+}
+
+/* The next function finds the template file and opens it, returning
+** a pointer to the opened file. */
+PRIVATE FILE *tplt_open(lemp)
+struct lemon *lemp;
+{
+  static char templatename[] = "lempar.c";
+  char buf[1000];
+  FILE *in;
+  char *tpltname;
+  char *cp;
+
+  cp = strrchr(lemp->filename,'.');
+  if( cp ){
+    sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename);
+  }else{
+    sprintf(buf,"%s.lt",lemp->filename);
+  }
+  if( access(buf,004)==0 ){
+    tpltname = buf;
+  }else if( access(templatename,004)==0 ){
+    tpltname = templatename;
+  }else{
+    tpltname = pathsearch(lemp->argv0,templatename,0);
+  }
+  if( tpltname==0 ){
+    fprintf(stderr,"Can't find the parser driver template file \"%s\".\n",
+    templatename);
+    lemp->errorcnt++;
+    return 0;
+  }
+  in = fopen(tpltname,"rb");
+  if( in==0 ){
+    fprintf(stderr,"Can't open the template file \"%s\".\n",templatename);
+    lemp->errorcnt++;
+    return 0;
+  }
+  return in;
+}
+
+/* Print a #line directive line to the output file. */
+PRIVATE void tplt_linedir(out,lineno,filename)
+FILE *out;
+int lineno;
+char *filename;
+{
+  fprintf(out,"#line %d \"",lineno);
+  while( *filename ){
+    if( *filename == '\\' ) putc('\\',out);
+    putc(*filename,out);
+    filename++;
+  }
+  fprintf(out,"\"\n");
+}
+
+/* Print a string to the file and keep the linenumber up to date */
+PRIVATE void tplt_print(out,lemp,str,strln,lineno)
+FILE *out;
+struct lemon *lemp;
+char *str;
+int strln;
+int *lineno;
+{
+  if( str==0 ) return;
+  tplt_linedir(out,strln,lemp->filename);
+  (*lineno)++;
+  while( *str ){
+    if( *str=='\n' ) (*lineno)++;
+    putc(*str,out);
+    str++;
+  }
+  if( str[-1]!='\n' ){
+    putc('\n',out);
+    (*lineno)++;
+  }
+  tplt_linedir(out,*lineno+2,lemp->outname); 
+  (*lineno)+=2;
+  return;
+}
+
+/*
+** The following routine emits code for the destructor for the
+** symbol sp
+*/
+void emit_destructor_code(out,sp,lemp,lineno)
+FILE *out;
+struct symbol *sp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp = 0;
+
+ int linecnt = 0;
+ if( sp->type==TERMINAL ){
+   cp = lemp->tokendest;
+   if( cp==0 ) return;
+   tplt_linedir(out,lemp->tokendestln,lemp->filename);
+   fprintf(out,"{");
+ }else if( sp->destructor ){
+   cp = sp->destructor;
+   tplt_linedir(out,sp->destructorln,lemp->filename);
+   fprintf(out,"{");
+ }else if( lemp->vardest ){
+   cp = lemp->vardest;
+   if( cp==0 ) return;
+   tplt_linedir(out,lemp->vardestln,lemp->filename);
+   fprintf(out,"{");
+ }else{
+   assert( 0 );  /* Cannot happen */
+ }
+ for(; *cp; cp++){
+   if( *cp=='$' && cp[1]=='$' ){
+     fprintf(out,"(yypminor->yy%d)",sp->dtnum);
+     cp++;
+     continue;
+   }
+   if( *cp=='\n' ) linecnt++;
+   fputc(*cp,out);
+ }
+ (*lineno) += 3 + linecnt;
+ fprintf(out,"}\n");
+ tplt_linedir(out,*lineno,lemp->outname);
+ return;
+}
+
+/*
+** Return TRUE (non-zero) if the given symbol has a destructor.
+*/
+int has_destructor(sp, lemp)
+struct symbol *sp;
+struct lemon *lemp;
+{
+  int ret;
+  if( sp->type==TERMINAL ){
+    ret = lemp->tokendest!=0;
+  }else{
+    ret = lemp->vardest!=0 || sp->destructor!=0;
+  }
+  return ret;
+}
+
+/*
+** Append text to a dynamically allocated string.  If zText is 0 then
+** reset the string to be empty again.  Always return the complete text
+** of the string (which is overwritten with each call).
+**
+** n bytes of zText are stored.  If n==0 then all of zText up to the first
+** \000 terminator is stored.  zText can contain up to two instances of
+** %d.  The values of p1 and p2 are written into the first and second
+** %d.
+**
+** If n==-1, then the previous character is overwritten.
+*/
+PRIVATE char *append_str(char *zText, int n, int p1, int p2){
+  static char *z = 0;
+  static int alloced = 0;
+  static int used = 0;
+  int c;
+  char zInt[40];
+
+  if( zText==0 ){
+    used = 0;
+    return z;
+  }
+  if( n<=0 ){
+    if( n<0 ){
+      used += n;
+      assert( used>=0 );
+    }
+    n = strlen(zText);
+  }
+  if( n+sizeof(zInt)*2+used >= alloced ){
+    alloced = n + sizeof(zInt)*2 + used + 200;
+    z = realloc(z,  alloced);
+  }
+  if( z==0 ) return "";
+  while( n-- > 0 ){
+    c = *(zText++);
+    if( c=='%' && zText[0]=='d' ){
+      sprintf(zInt, "%d", p1);
+      p1 = p2;
+      strcpy(&z[used], zInt);
+      used += strlen(&z[used]);
+      zText++;
+      n--;
+    }else{
+      z[used++] = c;
+    }
+  }
+  z[used] = 0;
+  return z;
+}
+
+/*
+** zCode is a string that is the action associated with a rule.  Expand
+** the symbols in this string so that the refer to elements of the parser
+** stack.
+*/
+PRIVATE void translate_code(struct lemon *lemp, struct rule *rp){
+  char *cp, *xp;
+  int i;
+  char lhsused = 0;    /* True if the LHS element has been used */
+  char used[MAXRHS];   /* True for each RHS element which is used */
+
+  for(i=0; i<rp->nrhs; i++) used[i] = 0;
+  lhsused = 0;
+
+  append_str(0,0,0,0);
+  for(cp=rp->code; *cp; cp++){
+    if( isalpha(*cp) && (cp==rp->code || (!isalnum(cp[-1]) && cp[-1]!='_')) ){
+      char saved;
+      for(xp= &cp[1]; isalnum(*xp) || *xp=='_'; xp++);
+      saved = *xp;
+      *xp = 0;
+      if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){
+        append_str("yygotominor.yy%d",0,rp->lhs->dtnum,0);
+        cp = xp;
+        lhsused = 1;
+      }else{
+        for(i=0; i<rp->nrhs; i++){
+          if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){
+            if( cp!=rp->code && cp[-1]=='@' ){
+              /* If the argument is of the form @X then substituted
+              ** the token number of X, not the value of X */
+              append_str("yymsp[%d].major",-1,i-rp->nrhs+1,0);
+            }else{
+              append_str("yymsp[%d].minor.yy%d",0,
+                         i-rp->nrhs+1,rp->rhs[i]->dtnum);
+            }
+            cp = xp;
+            used[i] = 1;
+            break;
+          }
+        }
+      }
+      *xp = saved;
+    }
+    append_str(cp, 1, 0, 0);
+  } /* End loop */
+
+  /* Check to make sure the LHS has been used */
+  if( rp->lhsalias && !lhsused ){
+    ErrorMsg(lemp->filename,rp->ruleline,
+      "Label \"%s\" for \"%s(%s)\" is never used.",
+        rp->lhsalias,rp->lhs->name,rp->lhsalias);
+    lemp->errorcnt++;
+  }
+
+  /* Generate destructor code for RHS symbols which are not used in the
+  ** reduce code */
+  for(i=0; i<rp->nrhs; i++){
+    if( rp->rhsalias[i] && !used[i] ){
+      ErrorMsg(lemp->filename,rp->ruleline,
+        "Label %s for \"%s(%s)\" is never used.",
+        rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]);
+      lemp->errorcnt++;
+    }else if( rp->rhsalias[i]==0 ){
+      if( has_destructor(rp->rhs[i],lemp) ){
+        append_str("  yy_destructor(%d,&yymsp[%d].minor);\n", 0,
+           rp->rhs[i]->index,i-rp->nrhs+1);
+      }else{
+        /* No destructor defined for this term */
+      }
+    }
+  }
+  cp = append_str(0,0,0,0);
+  rp->code = Strsafe(cp);
+}
+
+/* 
+** Generate code which executes when the rule "rp" is reduced.  Write
+** the code to "out".  Make sure lineno stays up-to-date.
+*/
+PRIVATE void emit_code(out,rp,lemp,lineno)
+FILE *out;
+struct rule *rp;
+struct lemon *lemp;
+int *lineno;
+{
+ char *cp;
+ int linecnt = 0;
+
+ /* Generate code to do the reduce action */
+ if( rp->code ){
+   tplt_linedir(out,rp->line,lemp->filename);
+   fprintf(out,"{%s",rp->code);
+   for(cp=rp->code; *cp; cp++){
+     if( *cp=='\n' ) linecnt++;
+   } /* End loop */
+   (*lineno) += 3 + linecnt;
+   fprintf(out,"}\n");
+   tplt_linedir(out,*lineno,lemp->outname);
+ } /* End if( rp->code ) */
+
+ return;
+}
+
+/*
+** Print the definition of the union used for the parser's data stack.
+** This union contains fields for every possible data type for tokens
+** and nonterminals.  In the process of computing and printing this
+** union, also set the ".dtnum" field of every terminal and nonterminal
+** symbol.
+*/
+void print_stack_union(out,lemp,plineno,mhflag)
+FILE *out;                  /* The output stream */
+struct lemon *lemp;         /* The main info structure for this parser */
+int *plineno;               /* Pointer to the line number */
+int mhflag;                 /* True if generating makeheaders output */
+{
+  int lineno = *plineno;    /* The line number of the output */
+  char **types;             /* A hash table of datatypes */
+  int arraysize;            /* Size of the "types" array */
+  int maxdtlength;          /* Maximum length of any ".datatype" field. */
+  char *stddt;              /* Standardized name for a datatype */
+  int i,j;                  /* Loop counters */
+  int hash;                 /* For hashing the name of a type */
+  char *name;               /* Name of the parser */
+
+  /* Allocate and initialize types[] and allocate stddt[] */
+  arraysize = lemp->nsymbol * 2;
+  types = (char**)malloc( arraysize * sizeof(char*) );
+  for(i=0; i<arraysize; i++) types[i] = 0;
+  maxdtlength = 0;
+  if( lemp->vartype ){
+    maxdtlength = strlen(lemp->vartype);
+  }
+  for(i=0; i<lemp->nsymbol; i++){
+    int len;
+    struct symbol *sp = lemp->symbols[i];
+    if( sp->datatype==0 ) continue;
+    len = strlen(sp->datatype);
+    if( len>maxdtlength ) maxdtlength = len;
+  }
+  stddt = (char*)malloc( maxdtlength*2 + 1 );
+  if( types==0 || stddt==0 ){
+    fprintf(stderr,"Out of memory.\n");
+    exit(1);
+  }
+
+  /* Build a hash table of datatypes. The ".dtnum" field of each symbol
+  ** is filled in with the hash index plus 1.  A ".dtnum" value of 0 is
+  ** used for terminal symbols.  If there is no %default_type defined then
+  ** 0 is also used as the .dtnum value for nonterminals which do not specify
+  ** a datatype using the %type directive.
+  */
+  for(i=0; i<lemp->nsymbol; i++){
+    struct symbol *sp = lemp->symbols[i];
+    char *cp;
+    if( sp==lemp->errsym ){
+      sp->dtnum = arraysize+1;
+      continue;
+    }
+    if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){
+      sp->dtnum = 0;
+      continue;
+    }
+    cp = sp->datatype;
+    if( cp==0 ) cp = lemp->vartype;
+    j = 0;
+    while( isspace(*cp) ) cp++;
+    while( *cp ) stddt[j++] = *cp++;
+    while( j>0 && isspace(stddt[j-1]) ) j--;
+    stddt[j] = 0;
+    hash = 0;
+    for(j=0; stddt[j]; j++){
+      hash = hash*53 + stddt[j];
+    }
+    hash = (hash & 0x7fffffff)%arraysize;
+    while( types[hash] ){
+      if( strcmp(types[hash],stddt)==0 ){
+        sp->dtnum = hash + 1;
+        break;
+      }
+      hash++;
+      if( hash>=arraysize ) hash = 0;
+    }
+    if( types[hash]==0 ){
+      sp->dtnum = hash + 1;
+      types[hash] = (char*)malloc( strlen(stddt)+1 );
+      if( types[hash]==0 ){
+        fprintf(stderr,"Out of memory.\n");
+        exit(1);
+      }
+      strcpy(types[hash],stddt);
+    }
+  }
+
+  /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */
+  name = lemp->name ? lemp->name : "Parse";
+  lineno = *plineno;
+  if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; }
+  fprintf(out,"#define %sTOKENTYPE %s\n",name,
+    lemp->tokentype?lemp->tokentype:"void*");  lineno++;
+  if( mhflag ){ fprintf(out,"#endif\n"); lineno++; }
+  fprintf(out,"typedef union {\n"); lineno++;
+  fprintf(out,"  %sTOKENTYPE yy0;\n",name); lineno++;
+  for(i=0; i<arraysize; i++){
+    if( types[i]==0 ) continue;
+    fprintf(out,"  %s yy%d;\n",types[i],i+1); lineno++;
+    free(types[i]);
+  }
+  fprintf(out,"  int yy%d;\n",lemp->errsym->dtnum); lineno++;
+  free(stddt);
+  free(types);
+  fprintf(out,"} YYMINORTYPE;\n"); lineno++;
+  *plineno = lineno;
+}
+
+/*
+** Return the name of a C datatype able to represent values between
+** lwr and upr, inclusive.
+*/
+static const char *minimum_size_type(int lwr, int upr){
+  if( lwr>=0 ){
+    if( upr<=255 ){
+      return "unsigned char";
+    }else if( upr<65535 ){
+      return "unsigned short int";
+    }else{
+      return "unsigned int";
+    }
+  }else if( lwr>=-127 && upr<=127 ){
+    return "signed char";
+  }else if( lwr>=-32767 && upr<32767 ){
+    return "short";
+  }else{
+    return "int";
+  }
+}
+
+/*
+** Each state contains a set of token transaction and a set of
+** nonterminal transactions.  Each of these sets makes an instance
+** of the following structure.  An array of these structures is used
+** to order the creation of entries in the yy_action[] table.
+*/
+struct axset {
+  struct state *stp;   /* A pointer to a state */
+  int isTkn;           /* True to use tokens.  False for non-terminals */
+  int nAction;         /* Number of actions */
+};
+
+/*
+** Compare to axset structures for sorting purposes
+*/
+static int axset_compare(const void *a, const void *b){
+  struct axset *p1 = (struct axset*)a;
+  struct axset *p2 = (struct axset*)b;
+  return p2->nAction - p1->nAction;
+}
+
+/* Generate C source code for the parser */
+void ReportTable(lemp, mhflag)
+struct lemon *lemp;
+int mhflag;     /* Output in makeheaders format if true */
+{
+  FILE *out, *in;
+  char line[LINESIZE];
+  int  lineno;
+  struct state *stp;
+  struct action *ap;
+  struct rule *rp;
+  struct acttab *pActtab;
+  int i, j, n;
+  char *name;
+  int mnTknOfst, mxTknOfst;
+  int mnNtOfst, mxNtOfst;
+  struct axset *ax;
+
+  in = tplt_open(lemp);
+  if( in==0 ) return;
+  out = file_open(lemp,".c","wb");
+  if( out==0 ){
+    fclose(in);
+    return;
+  }
+  lineno = 1;
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the include code, if any */
+  tplt_print(out,lemp,lemp->include,lemp->includeln,&lineno);
+  if( mhflag ){
+    char *name = file_makename(lemp, ".h");
+    fprintf(out,"#include \"%s\"\n", name); lineno++;
+    free(name);
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate #defines for all tokens */
+  if( mhflag ){
+    char *prefix;
+    fprintf(out,"#if INTERFACE\n"); lineno++;
+    if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+    else                    prefix = "";
+    for(i=1; i<lemp->nterminal; i++){
+      fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+      lineno++;
+    }
+    fprintf(out,"#endif\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the defines */
+  fprintf(out,"#define YYCODETYPE %s\n",
+    minimum_size_type(0, lemp->nsymbol+5)); lineno++;
+  fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1);  lineno++;
+  fprintf(out,"#define YYACTIONTYPE %s\n",
+    minimum_size_type(0, lemp->nstate+lemp->nrule+5));  lineno++;
+  print_stack_union(out,lemp,&lineno,mhflag);
+  if( lemp->stacksize ){
+    if( atoi(lemp->stacksize)<=0 ){
+      ErrorMsg(lemp->filename,0,
+"Illegal stack size: [%s].  The stack size should be an integer constant.",
+        lemp->stacksize);
+      lemp->errorcnt++;
+      lemp->stacksize = "100";
+    }
+    fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize);  lineno++;
+  }else{
+    fprintf(out,"#define YYSTACKDEPTH 100\n");  lineno++;
+  }
+  if( mhflag ){
+    fprintf(out,"#if INTERFACE\n"); lineno++;
+  }
+  name = lemp->name ? lemp->name : "Parse";
+  if( lemp->arg && lemp->arg[0] ){
+    int i;
+    i = strlen(lemp->arg);
+    while( i>=1 && isspace(lemp->arg[i-1]) ) i--;
+    while( i>=1 && (isalnum(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--;
+    fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg);  lineno++;
+    fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg);  lineno++;
+    fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n",
+                 name,lemp->arg,&lemp->arg[i]);  lineno++;
+    fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n",
+                 name,&lemp->arg[i],&lemp->arg[i]);  lineno++;
+  }else{
+    fprintf(out,"#define %sARG_SDECL\n",name);  lineno++;
+    fprintf(out,"#define %sARG_PDECL\n",name);  lineno++;
+    fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
+    fprintf(out,"#define %sARG_STORE\n",name); lineno++;
+  }
+  if( mhflag ){
+    fprintf(out,"#endif\n"); lineno++;
+  }
+  fprintf(out,"#define YYNSTATE %d\n",lemp->nstate);  lineno++;
+  fprintf(out,"#define YYNRULE %d\n",lemp->nrule);  lineno++;
+  fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index);  lineno++;
+  fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum);  lineno++;
+  if( lemp->has_fallback ){
+    fprintf(out,"#define YYFALLBACK 1\n");  lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the action table and its associates:
+  **
+  **  yy_action[]        A single table containing all actions.
+  **  yy_lookahead[]     A table containing the lookahead for each entry in
+  **                     yy_action.  Used to detect hash collisions.
+  **  yy_shift_ofst[]    For each state, the offset into yy_action for
+  **                     shifting terminals.
+  **  yy_reduce_ofst[]   For each state, the offset into yy_action for
+  **                     shifting non-terminals after a reduce.
+  **  yy_default[]       Default action for each state.
+  */
+
+  /* Compute the actions on all states and count them up */
+  ax = malloc( sizeof(ax[0])*lemp->nstate*2 );
+  if( ax==0 ){
+    fprintf(stderr,"malloc failed\n");
+    exit(1);
+  }
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    stp->nTknAct = stp->nNtAct = 0;
+    stp->iDflt = lemp->nstate + lemp->nrule;
+    stp->iTknOfst = NO_OFFSET;
+    stp->iNtOfst = NO_OFFSET;
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( compute_action(lemp,ap)>=0 ){
+        if( ap->sp->index<lemp->nterminal ){
+          stp->nTknAct++;
+        }else if( ap->sp->index<lemp->nsymbol ){
+          stp->nNtAct++;
+        }else{
+          stp->iDflt = compute_action(lemp, ap);
+        }
+      }
+    }
+    ax[i*2].stp = stp;
+    ax[i*2].isTkn = 1;
+    ax[i*2].nAction = stp->nTknAct;
+    ax[i*2+1].stp = stp;
+    ax[i*2+1].isTkn = 0;
+    ax[i*2+1].nAction = stp->nNtAct;
+  }
+  mxTknOfst = mnTknOfst = 0;
+  mxNtOfst = mnNtOfst = 0;
+
+  /* Compute the action table.  In order to try to keep the size of the
+  ** action table to a minimum, the heuristic of placing the largest action
+  ** sets first is used.
+  */
+  qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare);
+  pActtab = acttab_alloc();
+  for(i=0; i<lemp->nstate*2 && ax[i].nAction>0; i++){
+    stp = ax[i].stp;
+    if( ax[i].isTkn ){
+      for(ap=stp->ap; ap; ap=ap->next){
+        int action;
+        if( ap->sp->index>=lemp->nterminal ) continue;
+        action = compute_action(lemp, ap);
+        if( action<0 ) continue;
+        acttab_action(pActtab, ap->sp->index, action);
+      }
+      stp->iTknOfst = acttab_insert(pActtab);
+      if( stp->iTknOfst<mnTknOfst ) mnTknOfst = stp->iTknOfst;
+      if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst;
+    }else{
+      for(ap=stp->ap; ap; ap=ap->next){
+        int action;
+        if( ap->sp->index<lemp->nterminal ) continue;
+        if( ap->sp->index==lemp->nsymbol ) continue;
+        action = compute_action(lemp, ap);
+        if( action<0 ) continue;
+        acttab_action(pActtab, ap->sp->index, action);
+      }
+      stp->iNtOfst = acttab_insert(pActtab);
+      if( stp->iNtOfst<mnNtOfst ) mnNtOfst = stp->iNtOfst;
+      if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst;
+    }
+  }
+  free(ax);
+
+  /* Output the yy_action table */
+  fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++;
+  n = acttab_size(pActtab);
+  for(i=j=0; i<n; i++){
+    int action = acttab_yyaction(pActtab, i);
+    if( action<0 ) action = lemp->nsymbol + lemp->nrule + 2;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", action);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the yy_lookahead table */
+  fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++;
+  for(i=j=0; i<n; i++){
+    int la = acttab_yylookahead(pActtab, i);
+    if( la<0 ) la = lemp->nsymbol;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", la);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the yy_shift_ofst[] table */
+  fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", mnTknOfst-1); lineno++;
+  fprintf(out, "static const %s yy_shift_ofst[] = {\n", 
+          minimum_size_type(mnTknOfst-1, mxTknOfst)); lineno++;
+  n = lemp->nstate;
+  for(i=j=0; i<n; i++){
+    int ofst;
+    stp = lemp->sorted[i];
+    ofst = stp->iTknOfst;
+    if( ofst==NO_OFFSET ) ofst = mnTknOfst - 1;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", ofst);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the yy_reduce_ofst[] table */
+  fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++;
+  fprintf(out, "static const %s yy_reduce_ofst[] = {\n", 
+          minimum_size_type(mnNtOfst-1, mxNtOfst)); lineno++;
+  n = lemp->nstate;
+  for(i=j=0; i<n; i++){
+    int ofst;
+    stp = lemp->sorted[i];
+    ofst = stp->iNtOfst;
+    if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", ofst);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the default action table */
+  fprintf(out, "static const YYACTIONTYPE yy_default[] = {\n"); lineno++;
+  n = lemp->nstate;
+  for(i=j=0; i<n; i++){
+    stp = lemp->sorted[i];
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", stp->iDflt);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the table of fallback tokens.
+  */
+  if( lemp->has_fallback ){
+    for(i=0; i<lemp->nterminal; i++){
+      struct symbol *p = lemp->symbols[i];
+      if( p->fallback==0 ){
+        fprintf(out, "    0,  /* %10s => nothing */\n", p->name);
+      }else{
+        fprintf(out, "  %3d,  /* %10s => %s */\n", p->fallback->index,
+          p->name, p->fallback->name);
+      }
+      lineno++;
+    }
+  }
+  tplt_xfer(lemp->name, in, out, &lineno);
+
+  /* Generate a table containing the symbolic name of every symbol
+  */
+  for(i=0; i<lemp->nsymbol; i++){
+    sprintf(line,"\"%s\",",lemp->symbols[i]->name);
+    fprintf(out,"  %-15s",line);
+    if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; }
+  }
+  if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate a table containing a text string that describes every
+  ** rule in the rule set of the grammer.  This information is used
+  ** when tracing REDUCE actions.
+  */
+  for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+    assert( rp->index==i );
+    fprintf(out," /* %3d */ \"%s ::=", i, rp->lhs->name);
+    for(j=0; j<rp->nrhs; j++) fprintf(out," %s",rp->rhs[j]->name);
+    fprintf(out,"\",\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes every time a symbol is popped from
+  ** the stack while processing errors or while destroying the parser. 
+  ** (In other words, generate the %destructor actions)
+  */
+  if( lemp->tokendest ){
+    for(i=0; i<lemp->nsymbol; i++){
+      struct symbol *sp = lemp->symbols[i];
+      if( sp==0 || sp->type!=TERMINAL ) continue;
+      fprintf(out,"    case %d:\n",sp->index); lineno++;
+    }
+    for(i=0; i<lemp->nsymbol && lemp->symbols[i]->type!=TERMINAL; i++);
+    if( i<lemp->nsymbol ){
+      emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+      fprintf(out,"      break;\n"); lineno++;
+    }
+  }
+  for(i=0; i<lemp->nsymbol; i++){
+    struct symbol *sp = lemp->symbols[i];
+    if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue;
+    fprintf(out,"    case %d:\n",sp->index); lineno++;
+
+    /* Combine duplicate destructors into a single case */
+    for(j=i+1; j<lemp->nsymbol; j++){
+      struct symbol *sp2 = lemp->symbols[j];
+      if( sp2 && sp2->type!=TERMINAL && sp2->destructor
+          && sp2->dtnum==sp->dtnum
+          && strcmp(sp->destructor,sp2->destructor)==0 ){
+         fprintf(out,"    case %d:\n",sp2->index); lineno++;
+         sp2->destructor = 0;
+      }
+    }
+
+    emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+    fprintf(out,"      break;\n"); lineno++;
+  }
+  if( lemp->vardest ){
+    struct symbol *dflt_sp = 0;
+    for(i=0; i<lemp->nsymbol; i++){
+      struct symbol *sp = lemp->symbols[i];
+      if( sp==0 || sp->type==TERMINAL ||
+          sp->index<=0 || sp->destructor!=0 ) continue;
+      fprintf(out,"    case %d:\n",sp->index); lineno++;
+      dflt_sp = sp;
+    }
+    if( dflt_sp!=0 ){
+      emit_destructor_code(out,dflt_sp,lemp,&lineno);
+      fprintf(out,"      break;\n"); lineno++;
+    }
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes whenever the parser stack overflows */
+  tplt_print(out,lemp,lemp->overflow,lemp->overflowln,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the table of rule information 
+  **
+  ** Note: This code depends on the fact that rules are number
+  ** sequentually beginning with 0.
+  */
+  for(rp=lemp->rule; rp; rp=rp->next){
+    fprintf(out,"  { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which execution during each REDUCE action */
+  for(rp=lemp->rule; rp; rp=rp->next){
+    if( rp->code ) translate_code(lemp, rp);
+  }
+  for(rp=lemp->rule; rp; rp=rp->next){
+    struct rule *rp2;
+    if( rp->code==0 ) continue;
+    fprintf(out,"      case %d:\n",rp->index); lineno++;
+    for(rp2=rp->next; rp2; rp2=rp2->next){
+      if( rp2->code==rp->code ){
+        fprintf(out,"      case %d:\n",rp2->index); lineno++;
+        rp2->code = 0;
+      }
+    }
+    emit_code(out,rp,lemp,&lineno);
+    fprintf(out,"        break;\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes if a parse fails */
+  tplt_print(out,lemp,lemp->failure,lemp->failureln,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes when a syntax error occurs */
+  tplt_print(out,lemp,lemp->error,lemp->errorln,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes when the parser accepts its input */
+  tplt_print(out,lemp,lemp->accept,lemp->acceptln,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Append any addition code the user desires */
+  tplt_print(out,lemp,lemp->extracode,lemp->extracodeln,&lineno);
+
+  fclose(in);
+  fclose(out);
+  return;
+}
+
+/* Generate a header file for the parser */
+void ReportHeader(lemp)
+struct lemon *lemp;
+{
+  FILE *out, *in;
+  char *prefix;
+  char line[LINESIZE];
+  char pattern[LINESIZE];
+  int i;
+
+  if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+  else                    prefix = "";
+  in = file_open(lemp,".h","rb");
+  if( in ){
+    for(i=1; i<lemp->nterminal && fgets(line,LINESIZE,in); i++){
+      sprintf(pattern,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+      if( strcmp(line,pattern) ) break;
+    }
+    fclose(in);
+    if( i==lemp->nterminal ){
+      /* No change in the file.  Don't rewrite it. */
+      return;
+    }
+  }
+  out = file_open(lemp,".h","wb");
+  if( out ){
+    for(i=1; i<lemp->nterminal; i++){
+      fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+    }
+    fclose(out);  
+  }
+  return;
+}
+
+/* Reduce the size of the action tables, if possible, by making use
+** of defaults.
+**
+** In this version, we take the most frequent REDUCE action and make
+** it the default.  Only default a reduce if there are more than one.
+*/
+void CompressTables(lemp)
+struct lemon *lemp;
+{
+  struct state *stp;
+  struct action *ap, *ap2;
+  struct rule *rp, *rp2, *rbest;
+  int nbest, n;
+  int i;
+
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    nbest = 0;
+    rbest = 0;
+
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( ap->type!=REDUCE ) continue;
+      rp = ap->x.rp;
+      if( rp==rbest ) continue;
+      n = 1;
+      for(ap2=ap->next; ap2; ap2=ap2->next){
+        if( ap2->type!=REDUCE ) continue;
+        rp2 = ap2->x.rp;
+        if( rp2==rbest ) continue;
+        if( rp2==rp ) n++;
+      }
+      if( n>nbest ){
+        nbest = n;
+        rbest = rp;
+      }
+    }
+ 
+    /* Do not make a default if the number of rules to default
+    ** is not at least 2 */
+    if( nbest<2 ) continue;
+
+
+    /* Combine matching REDUCE actions into a single default */
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( ap->type==REDUCE && ap->x.rp==rbest ) break;
+    }
+    assert( ap );
+    ap->sp = Symbol_new("{default}");
+    for(ap=ap->next; ap; ap=ap->next){
+      if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED;
+    }
+    stp->ap = Action_sort(stp->ap);
+  }
+}
+
+/***************** From the file "set.c" ************************************/
+/*
+** Set manipulation routines for the LEMON parser generator.
+*/
+
+static int size = 0;
+
+/* Set the set size */
+void SetSize(n)
+int n;
+{
+  size = n+1;
+}
+
+/* Allocate a new set */
+char *SetNew(){
+  char *s;
+  int i;
+  s = (char*)malloc( size );
+  if( s==0 ){
+    extern void memory_error();
+    memory_error();
+  }
+  for(i=0; i<size; i++) s[i] = 0;
+  return s;
+}
+
+/* Deallocate a set */
+void SetFree(s)
+char *s;
+{
+  free(s);
+}
+
+/* Add a new element to the set.  Return TRUE if the element was added
+** and FALSE if it was already there. */
+int SetAdd(s,e)
+char *s;
+int e;
+{
+  int rv;
+  rv = s[e];
+  s[e] = 1;
+  return !rv;
+}
+
+/* Add every element of s2 to s1.  Return TRUE if s1 changes. */
+int SetUnion(s1,s2)
+char *s1;
+char *s2;
+{
+  int i, progress;
+  progress = 0;
+  for(i=0; i<size; i++){
+    if( s2[i]==0 ) continue;
+    if( s1[i]==0 ){
+      progress = 1;
+      s1[i] = 1;
+    }
+  }
+  return progress;
+}
+/********************** From the file "table.c" ****************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+**              "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file!  Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+PRIVATE int strhash(x)
+char *x;
+{
+  int h = 0;
+  while( *x) h = h*13 + *(x++);
+  return h;
+}
+
+/* Works like strdup, sort of.  Save a string in malloced memory, but
+** keep strings in a table so that the same string is not in more
+** than one place.
+*/
+char *Strsafe(y)
+char *y;
+{
+  char *z;
+
+  z = Strsafe_find(y);
+  if( z==0 && (z=malloc( strlen(y)+1 ))!=0 ){
+    strcpy(z,y);
+    Strsafe_insert(z);
+  }
+  MemoryCheck(z);
+  return z;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x1".
+*/
+struct s_x1 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x1node *tbl;  /* The data stored here */
+  struct s_x1node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x1".
+*/
+typedef struct s_x1node {
+  char *data;                  /* The data */
+  struct s_x1node *next;   /* Next entry with the same hash */
+  struct s_x1node **from;  /* Previous link */
+} x1node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x1 *x1a;
+
+/* Allocate a new associative array */
+void Strsafe_init(){
+  if( x1a ) return;
+  x1a = (struct s_x1*)malloc( sizeof(struct s_x1) );
+  if( x1a ){
+    x1a->size = 1024;
+    x1a->count = 0;
+    x1a->tbl = (x1node*)malloc( 
+      (sizeof(x1node) + sizeof(x1node*))*1024 );
+    if( x1a->tbl==0 ){
+      free(x1a);
+      x1a = 0;
+    }else{
+      int i;
+      x1a->ht = (x1node**)&(x1a->tbl[1024]);
+      for(i=0; i<1024; i++) x1a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Strsafe_insert(data)
+char *data;
+{
+  x1node *np;
+  int h;
+  int ph;
+
+  if( x1a==0 ) return 0;
+  ph = strhash(data);
+  h = ph & (x1a->size-1);
+  np = x1a->ht[h];
+  while( np ){
+    if( strcmp(np->data,data)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x1a->count>=x1a->size ){
+    /* Need to make the hash table bigger */
+    int i,size;
+    struct s_x1 array;
+    array.size = size = x1a->size*2;
+    array.count = x1a->count;
+    array.tbl = (x1node*)malloc(
+      (sizeof(x1node) + sizeof(x1node*))*size );
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x1node**)&(array.tbl[size]);
+    for(i=0; i<size; i++) array.ht[i] = 0;
+    for(i=0; i<x1a->count; i++){
+      x1node *oldnp, *newnp;
+      oldnp = &(x1a->tbl[i]);
+      h = strhash(oldnp->data) & (size-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    free(x1a->tbl);
+    *x1a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x1a->size-1);
+  np = &(x1a->tbl[x1a->count++]);
+  np->data = data;
+  if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next);
+  np->next = x1a->ht[h];
+  x1a->ht[h] = np;
+  np->from = &(x1a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+char *Strsafe_find(key)
+char *key;
+{
+  int h;
+  x1node *np;
+
+  if( x1a==0 ) return 0;
+  h = strhash(key) & (x1a->size-1);
+  np = x1a->ht[h];
+  while( np ){
+    if( strcmp(np->data,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Return a pointer to the (terminal or nonterminal) symbol "x".
+** Create a new symbol if this is the first time "x" has been seen.
+*/
+struct symbol *Symbol_new(x)
+char *x;
+{
+  struct symbol *sp;
+
+  sp = Symbol_find(x);
+  if( sp==0 ){
+    sp = (struct symbol *)malloc( sizeof(struct symbol) );
+    MemoryCheck(sp);
+    sp->name = Strsafe(x);
+    sp->type = isupper(*x) ? TERMINAL : NONTERMINAL;
+    sp->rule = 0;
+    sp->fallback = 0;
+    sp->prec = -1;
+    sp->assoc = UNK;
+    sp->firstset = 0;
+    sp->lambda = B_FALSE;
+    sp->destructor = 0;
+    sp->datatype = 0;
+    Symbol_insert(sp,sp->name);
+  }
+  return sp;
+}
+
+/* Compare two symbols for working purposes
+**
+** Symbols that begin with upper case letters (terminals or tokens)
+** must sort before symbols that begin with lower case letters
+** (non-terminals).  Other than that, the order does not matter.
+**
+** We find experimentally that leaving the symbols in their original
+** order (the order they appeared in the grammar file) gives the
+** smallest parser tables in SQLite.
+*/
+int Symbolcmpp(struct symbol **a, struct symbol **b){
+  int i1 = (**a).index + 10000000*((**a).name[0]>'Z');
+  int i2 = (**b).index + 10000000*((**b).name[0]>'Z');
+  return i1-i2;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x2".
+*/
+struct s_x2 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x2node *tbl;  /* The data stored here */
+  struct s_x2node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x2".
+*/
+typedef struct s_x2node {
+  struct symbol *data;                  /* The data */
+  char *key;                   /* The key */
+  struct s_x2node *next;   /* Next entry with the same hash */
+  struct s_x2node **from;  /* Previous link */
+} x2node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x2 *x2a;
+
+/* Allocate a new associative array */
+void Symbol_init(){
+  if( x2a ) return;
+  x2a = (struct s_x2*)malloc( sizeof(struct s_x2) );
+  if( x2a ){
+    x2a->size = 128;
+    x2a->count = 0;
+    x2a->tbl = (x2node*)malloc( 
+      (sizeof(x2node) + sizeof(x2node*))*128 );
+    if( x2a->tbl==0 ){
+      free(x2a);
+      x2a = 0;
+    }else{
+      int i;
+      x2a->ht = (x2node**)&(x2a->tbl[128]);
+      for(i=0; i<128; i++) x2a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Symbol_insert(data,key)
+struct symbol *data;
+char *key;
+{
+  x2node *np;
+  int h;
+  int ph;
+
+  if( x2a==0 ) return 0;
+  ph = strhash(key);
+  h = ph & (x2a->size-1);
+  np = x2a->ht[h];
+  while( np ){
+    if( strcmp(np->key,key)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x2a->count>=x2a->size ){
+    /* Need to make the hash table bigger */
+    int i,size;
+    struct s_x2 array;
+    array.size = size = x2a->size*2;
+    array.count = x2a->count;
+    array.tbl = (x2node*)malloc(
+      (sizeof(x2node) + sizeof(x2node*))*size );
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x2node**)&(array.tbl[size]);
+    for(i=0; i<size; i++) array.ht[i] = 0;
+    for(i=0; i<x2a->count; i++){
+      x2node *oldnp, *newnp;
+      oldnp = &(x2a->tbl[i]);
+      h = strhash(oldnp->key) & (size-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->key = oldnp->key;
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    free(x2a->tbl);
+    *x2a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x2a->size-1);
+  np = &(x2a->tbl[x2a->count++]);
+  np->key = key;
+  np->data = data;
+  if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next);
+  np->next = x2a->ht[h];
+  x2a->ht[h] = np;
+  np->from = &(x2a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+struct symbol *Symbol_find(key)
+char *key;
+{
+  int h;
+  x2node *np;
+
+  if( x2a==0 ) return 0;
+  h = strhash(key) & (x2a->size-1);
+  np = x2a->ht[h];
+  while( np ){
+    if( strcmp(np->key,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Return the n-th data.  Return NULL if n is out of range. */
+struct symbol *Symbol_Nth(n)
+int n;
+{
+  struct symbol *data;
+  if( x2a && n>0 && n<=x2a->count ){
+    data = x2a->tbl[n-1].data;
+  }else{
+    data = 0;
+  }
+  return data;
+}
+
+/* Return the size of the array */
+int Symbol_count()
+{
+  return x2a ? x2a->count : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc.  Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct symbol **Symbol_arrayof()
+{
+  struct symbol **array;
+  int i,size;
+  if( x2a==0 ) return 0;
+  size = x2a->count;
+  array = (struct symbol **)malloc( sizeof(struct symbol *)*size );
+  if( array ){
+    for(i=0; i<size; i++) array[i] = x2a->tbl[i].data;
+  }
+  return array;
+}
+
+/* Compare two configurations */
+int Configcmp(a,b)
+struct config *a;
+struct config *b;
+{
+  int x;
+  x = a->rp->index - b->rp->index;
+  if( x==0 ) x = a->dot - b->dot;
+  return x;
+}
+
+/* Compare two states */
+PRIVATE int statecmp(a,b)
+struct config *a;
+struct config *b;
+{
+  int rc;
+  for(rc=0; rc==0 && a && b;  a=a->bp, b=b->bp){
+    rc = a->rp->index - b->rp->index;
+    if( rc==0 ) rc = a->dot - b->dot;
+  }
+  if( rc==0 ){
+    if( a ) rc = 1;
+    if( b ) rc = -1;
+  }
+  return rc;
+}
+
+/* Hash a state */
+PRIVATE int statehash(a)
+struct config *a;
+{
+  int h=0;
+  while( a ){
+    h = h*571 + a->rp->index*37 + a->dot;
+    a = a->bp;
+  }
+  return h;
+}
+
+/* Allocate a new state structure */
+struct state *State_new()
+{
+  struct state *new;
+  new = (struct state *)malloc( sizeof(struct state) );
+  MemoryCheck(new);
+  return new;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x3".
+*/
+struct s_x3 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x3node *tbl;  /* The data stored here */
+  struct s_x3node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x3".
+*/
+typedef struct s_x3node {
+  struct state *data;                  /* The data */
+  struct config *key;                   /* The key */
+  struct s_x3node *next;   /* Next entry with the same hash */
+  struct s_x3node **from;  /* Previous link */
+} x3node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x3 *x3a;
+
+/* Allocate a new associative array */
+void State_init(){
+  if( x3a ) return;
+  x3a = (struct s_x3*)malloc( sizeof(struct s_x3) );
+  if( x3a ){
+    x3a->size = 128;
+    x3a->count = 0;
+    x3a->tbl = (x3node*)malloc( 
+      (sizeof(x3node) + sizeof(x3node*))*128 );
+    if( x3a->tbl==0 ){
+      free(x3a);
+      x3a = 0;
+    }else{
+      int i;
+      x3a->ht = (x3node**)&(x3a->tbl[128]);
+      for(i=0; i<128; i++) x3a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int State_insert(data,key)
+struct state *data;
+struct config *key;
+{
+  x3node *np;
+  int h;
+  int ph;
+
+  if( x3a==0 ) return 0;
+  ph = statehash(key);
+  h = ph & (x3a->size-1);
+  np = x3a->ht[h];
+  while( np ){
+    if( statecmp(np->key,key)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x3a->count>=x3a->size ){
+    /* Need to make the hash table bigger */
+    int i,size;
+    struct s_x3 array;
+    array.size = size = x3a->size*2;
+    array.count = x3a->count;
+    array.tbl = (x3node*)malloc(
+      (sizeof(x3node) + sizeof(x3node*))*size );
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x3node**)&(array.tbl[size]);
+    for(i=0; i<size; i++) array.ht[i] = 0;
+    for(i=0; i<x3a->count; i++){
+      x3node *oldnp, *newnp;
+      oldnp = &(x3a->tbl[i]);
+      h = statehash(oldnp->key) & (size-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->key = oldnp->key;
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    free(x3a->tbl);
+    *x3a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x3a->size-1);
+  np = &(x3a->tbl[x3a->count++]);
+  np->key = key;
+  np->data = data;
+  if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next);
+  np->next = x3a->ht[h];
+  x3a->ht[h] = np;
+  np->from = &(x3a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+struct state *State_find(key)
+struct config *key;
+{
+  int h;
+  x3node *np;
+
+  if( x3a==0 ) return 0;
+  h = statehash(key) & (x3a->size-1);
+  np = x3a->ht[h];
+  while( np ){
+    if( statecmp(np->key,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc.  Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct state **State_arrayof()
+{
+  struct state **array;
+  int i,size;
+  if( x3a==0 ) return 0;
+  size = x3a->count;
+  array = (struct state **)malloc( sizeof(struct state *)*size );
+  if( array ){
+    for(i=0; i<size; i++) array[i] = x3a->tbl[i].data;
+  }
+  return array;
+}
+
+/* Hash a configuration */
+PRIVATE int confighash(a)
+struct config *a;
+{
+  int h=0;
+  h = h*571 + a->rp->index*37 + a->dot;
+  return h;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x4".
+*/
+struct s_x4 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x4node *tbl;  /* The data stored here */
+  struct s_x4node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x4".
+*/
+typedef struct s_x4node {
+  struct config *data;                  /* The data */
+  struct s_x4node *next;   /* Next entry with the same hash */
+  struct s_x4node **from;  /* Previous link */
+} x4node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x4 *x4a;
+
+/* Allocate a new associative array */
+void Configtable_init(){
+  if( x4a ) return;
+  x4a = (struct s_x4*)malloc( sizeof(struct s_x4) );
+  if( x4a ){
+    x4a->size = 64;
+    x4a->count = 0;
+    x4a->tbl = (x4node*)malloc( 
+      (sizeof(x4node) + sizeof(x4node*))*64 );
+    if( x4a->tbl==0 ){
+      free(x4a);
+      x4a = 0;
+    }else{
+      int i;
+      x4a->ht = (x4node**)&(x4a->tbl[64]);
+      for(i=0; i<64; i++) x4a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Configtable_insert(data)
+struct config *data;
+{
+  x4node *np;
+  int h;
+  int ph;
+
+  if( x4a==0 ) return 0;
+  ph = confighash(data);
+  h = ph & (x4a->size-1);
+  np = x4a->ht[h];
+  while( np ){
+    if( Configcmp(np->data,data)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x4a->count>=x4a->size ){
+    /* Need to make the hash table bigger */
+    int i,size;
+    struct s_x4 array;
+    array.size = size = x4a->size*2;
+    array.count = x4a->count;
+    array.tbl = (x4node*)malloc(
+      (sizeof(x4node) + sizeof(x4node*))*size );
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x4node**)&(array.tbl[size]);
+    for(i=0; i<size; i++) array.ht[i] = 0;
+    for(i=0; i<x4a->count; i++){
+      x4node *oldnp, *newnp;
+      oldnp = &(x4a->tbl[i]);
+      h = confighash(oldnp->data) & (size-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    free(x4a->tbl);
+    *x4a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x4a->size-1);
+  np = &(x4a->tbl[x4a->count++]);
+  np->data = data;
+  if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next);
+  np->next = x4a->ht[h];
+  x4a->ht[h] = np;
+  np->from = &(x4a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+struct config *Configtable_find(key)
+struct config *key;
+{
+  int h;
+  x4node *np;
+
+  if( x4a==0 ) return 0;
+  h = confighash(key) & (x4a->size-1);
+  np = x4a->ht[h];
+  while( np ){
+    if( Configcmp(np->data,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Remove all data from the table.  Pass each data to the function "f"
+** as it is removed.  ("f" may be null to avoid this step.) */
+void Configtable_clear(f)
+int(*f)(/* struct config * */);
+{
+  int i;
+  if( x4a==0 || x4a->count==0 ) return;
+  if( f ) for(i=0; i<x4a->count; i++) (*f)(x4a->tbl[i].data);
+  for(i=0; i<x4a->size; i++) x4a->ht[i] = 0;
+  x4a->count = 0;
+  return;
+}
diff --git a/external/badvpn_dns/lime/lime.bootstrap b/external/badvpn_dns/lime/lime.bootstrap
new file mode 100644
index 0000000..63791c7
--- /dev/null
+++ b/external/badvpn_dns/lime/lime.bootstrap
@@ -0,0 +1,31 @@
+There is nothing to see here. Go and look at the file called "metagrammar".
+
+: $$ = new lime();
+grammar pragma toklist stop : $$->pragma($2, $3);
+grammar rewrite stop : $2->update($$);
+to grammar
+: {$$=array();}
+toklist sym : $$[] = $2;
+toklist lit : $$[] = $2;
+to toklist
+sym '=' rhs : $$ = new lime_rewrite($1); $$->add_rhs($3);
+rewrite '|' rhs : $$->add_rhs($3);
+to rewrite
+list : $$ = new lime_rhs($1, '');
+list action : $$ = new lime_rhs($1, $2);
+to rhs
+action : $$ = new lime_action($1, NULL);
+action lambda : $$ = new lime_action($1, $2);
+sym : $$ = new lime_glyph($1, NULL);
+sym lambda : $$ = new lime_glyph($1, $2);
+lit : $$ = new lime_glyph($1, NULL);
+to slot
+: $$ = new lime_rhs();
+rhs slot : $$->add($2);
+to rhs
+'{' code '}' : $$ = $2;
+to action
+:
+code php : $$.=$2;
+code '{' code '}' : $$.='{'.$3.'}';
+to code
diff --git a/external/badvpn_dns/lime/lime.php b/external/badvpn_dns/lime/lime.php
new file mode 100644
index 0000000..b049225
--- /dev/null
+++ b/external/badvpn_dns/lime/lime.php
@@ -0,0 +1,910 @@
+<?php
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+ 
+define('LIME_DIR', dirname(__FILE__));
+
+function emit($str) { fputs(STDERR, $str."\n"); }
+
+class Bug extends Exception {}
+function bug($gripe='Bug found.') { throw new Bug($gripe); }
+function bug_if($falacy, $gripe='Bug found.') { if ($falacy) throw new Bug($gripe); }
+function bug_unless($assertion, $gripe='Bug found.') { if (!$assertion) throw new Bug($gripe); }
+
+include_once(LIME_DIR.'/parse_engine.php');
+include_once(LIME_DIR.'/set.so.php');
+include_once(LIME_DIR.'/flex_token_stream.php');
+
+function lime_token_reference($pos) { return '$tokens['.$pos.']'; }
+function lime_token_reference_callback($foo) { return lime_token_reference($foo[1]-1); }
+
+class cf_action {
+	function __construct($code) { $this->code=$code; }
+}
+class step {
+	/*
+	Base class for parse table instructions. The main idea is to make the
+	subclasses responsible for conflict resolution among themselves. It also
+	forms a sort of interface to the parse table.
+	*/
+	function __construct($sym) {
+		bug_unless($sym instanceof sym);
+		$this->sym = $sym;
+	}
+	function glyph() { return $this->sym->name; }
+}
+class error extends step {
+	function sane() { return false; }
+	function instruction() { bug("This should not happen."); }
+	function decide($that) { return $this; /* An error shall remain one. */ }
+}
+class shift extends step {
+	function __construct($sym, $q) {
+		parent::__construct($sym);
+		$this->q = $q;
+	}
+	function sane() { return true; }
+	function instruction() { return "s $this->q"; }
+	function decide($that) {
+		# shift-shift conflicts are impossible.
+		# shift-accept conflicts are a bug.
+		# so we can infer:
+		bug_unless($that instanceof reduce);
+		
+		# That being said, the resolution is a matter of precedence.
+		$shift_prec = $this->sym->right_prec;
+		$reduce_prec = $that->rule->prec;
+		
+		# If we don't have defined precedence levels for both options,
+		# then we default to shifting:
+		if (!($shift_prec and $reduce_prec)) return $this;
+		
+		# Otherwise, use the step with higher precedence.
+		if ($shift_prec > $reduce_prec) return $this;
+		if ($reduce_prec > $shift_prec) return $that;
+		
+		# The "nonassoc" works by giving equal precedence to both options,
+		# which means to put an error instruction in the parse table.
+		return new error($this->sym);
+	}
+}
+class reduce extends step {
+	function __construct($sym, $rule) {
+		bug_unless($rule instanceof rule);
+		parent::__construct($sym);
+		$this->rule = $rule;
+	}
+	function sane() { return true; }
+	function instruction() { return 'r '.$this->rule->id; }
+	function decide($that) {
+		# This means that the input grammar has a reduce-reduce conflict.
+		# Such things are considered an error in the input.
+		throw new RRC($this, $that);
+		#exit(1);
+		# BISON would go with the first encountered reduce thus:
+		# return $this;
+	}
+}
+class accept extends step {
+	function __construct($sym) { parent::__construct($sym); }
+	function sane() { return true; }
+	function instruction() { return 'a '.$this->sym->name; }
+}
+class RRC extends Exception {
+	function __construct($a, $b) {
+		parent::__construct("Reduce-Reduce Conflict");
+		$this->a = $a;
+		$this->b = $b;
+	}
+	function make_noise() {
+		emit(sprintf(
+			"Reduce-Reduce Conflict:\n%s\n%s\nLookahead is (%s)",
+			$this->a->rule->text(),
+			$this->b->rule->text(),
+			$this->a->glyph()
+		));
+	}
+}
+class state {
+	function __construct($id, $key, $close) {
+		$this->id = $id;
+		$this->key = $key;
+		$this->close = $close;	# config key -> object
+		ksort($this->close);
+		$this->action = array();
+	}
+	function dump() {
+		echo " * ".$this->id.' / '.$this->key."\n";
+		foreach ($this->close as $config) $config->dump();
+	}
+	function add_shift($sym, $state) {
+		$this->add_instruction(new shift($sym, $state->id));
+	}
+	function add_reduce($sym, $rule) {
+		$this->add_instruction(new reduce($sym, $rule));
+	}
+	function add_accept($sym) {
+		$this->add_instruction(new accept($sym));
+	}
+	function add_instruction($step) {
+		bug_unless($step instanceof step);
+		$this->action[] = $step;
+	}
+	function find_reductions($lime) {
+		# rightmost configurations followset yields reduce.
+		foreach($this->close as $c) {
+			if ($c->rightmost) {
+				foreach ($c->follow->all() as $glyph) $this->add_reduce($lime->sym($glyph), $c->rule);
+			}
+		}
+	}
+	function resolve_conflicts() {
+		# For each possible lookahead, find one (and only one) step to take.
+		$table = array();
+		foreach ($this->action as $step) {
+			$glyph = $step->glyph();
+			if (isset($table[$glyph])) {
+				# There's a conflict. The shifts all came first, which
+				# simplifies the coding for the step->decide() methods.
+				try {
+					$table[$glyph] = $table[$glyph]->decide($step);
+				} catch (RRC $e) {
+					emit("State $this->id:");
+					$e->make_noise();
+				}
+			} else {
+				# This glyph is yet unprocessed, so the step at hand is
+				# our best current guess at what the grammar indicates.
+				$table[$glyph] = $step;
+			}
+		}
+		
+		# Now that we have the correct steps chosen, this routine is oddly
+		# also responsible for turning that table into the form that will
+		# eventually be passed to the parse engine. (So FIXME?)
+		$out = array();
+		foreach ($table as $glyph => $step) {
+			if ($step->sane()) $out[$glyph] = $step->instruction();
+		}
+		return $out;
+	}
+	function segment_config() {
+		# Filter $this->close into categories based on the symbol_after_the_dot.
+		$f = array();
+		foreach ($this->close as $c) {
+			$p = $c->symbol_after_the_dot;
+			if (!$p) continue;
+			$f[$p->name][] = $c;
+		}
+		return $f;
+	}
+}
+class sym {
+	function __construct($name, $id) {
+		$this->name=$name;
+		$this->id=$id;
+		$this->term = true;	# Until proven otherwise.
+		$this->rule = array();
+		$this->config = array();
+		$this->lambda = false;
+		$this->first = new set();
+		$this->left_prec = $this->right_prec = 0;
+	}
+	function summary() {
+		$out = '';
+		foreach ($this->rule as $rule) $out .= $rule->text()."\n";
+		return $out;
+	}
+}
+class rule {
+	function __construct($id, $sym, $rhs, $code, $look, $replace) {
+		$this->id = $id;
+		$this->sym = $sym;
+		$this->rhs = $rhs;
+		$this->code = $code;
+		$this->look = $look;
+		bug_unless(is_int($look));
+		$this->replace = $replace;
+		#$this->prec_sym = $prec_sym;
+		$this->prec = 0;
+		$this->first = array();
+		$this->epsilon = count($rhs);
+	}
+	function lhs_glyph() { return $this->sym->name; }
+	function determine_precedence() {
+		# We may eventually expand to allow explicit prec_symbol declarations.
+		# Until then, we'll go with the rightmost terminal, which is what
+		# BISON does. People probably expect that. The leftmost terminal
+		# is a reasonable alternative behaviour, but I don't see the big
+		# deal just now.
+		
+		#$prec_sym = $this->prec_sym;
+		#if (!$prec_sym)
+		$prec_sym = $this->rightmost_terminal();
+		if (!$prec_sym) return;
+		$this->prec = $prec_sym->left_prec;
+	}
+	private function rightmost_terminal() {
+		$symbol = NULL;
+		$rhs = $this->rhs;
+		while ($rhs) {
+			$symbol = array_pop($rhs);
+			if ($symbol->term) break;
+		}
+		return $symbol;
+	}
+	function text() {
+		$t = "($this->id) ".$this->lhs_glyph().' :=';
+		foreach($this->rhs as $s) $t .= '  '.$s->name;
+		return $t;
+	}
+	function table(lime_language $lang) {
+		return array(
+			'symbol' => $this->lhs_glyph(),
+			'len' => $this->look,
+			'replace' => $this->replace,
+			'code' => $lang->fixup($this->code),
+			'text' => $this->text(),
+		);
+	}
+	function lambda() {
+		foreach ($this->rhs as $sym) if (!$sym->lambda) return false;
+		return true;
+	}
+	function find_first() {
+		$dot = count($this->rhs);
+		$last  = $this->first[$dot] = new set();
+		while ($dot) {
+			$dot--;
+			$symbol_after_the_dot = $this->rhs[$dot];
+			$first = $symbol_after_the_dot->first->all();
+			bug_if(empty($first) and !$symbol_after_the_dot->lambda);
+			$set = new set($first);
+			if ($symbol_after_the_dot->lambda) {
+				$set->union($last);
+				if ($this->epsilon == $dot+1) $this->epsilon = $dot;
+			}
+			$last = $this->first[$dot] = $set;
+		}
+	}
+	function teach_symbol_of_first_set() {
+		$go = false;
+		foreach ($this->rhs as $sym) {
+			if ($this->sym->first->union($sym->first)) $go = true;
+			if (!$sym->lambda) break;
+		}
+		return $go;
+	}
+	function lambda_from($dot) {
+		return $this->epsilon <= $dot;
+	}
+	function leftmost($follow) {
+		return new config($this, 0, $follow);
+	}
+	function dotted_text($dot) {
+		$out = $this->lhs_glyph().' :=';
+		$idx = -1;
+		foreach($this->rhs as $idx => $s) {
+			if ($idx == $dot) $out .= ' .';
+			$out .= '  '.$s->name;
+		}
+		if ($dot > $idx) $out .= ' .';
+		return $out;
+	}
+}
+class config {
+	function __construct($rule, $dot, $follow) {
+		$this->rule=$rule;
+		$this->dot = $dot;
+		$this->key = "$rule->id.$dot";
+		$this->rightmost = count($rule->rhs) <= $dot;
+		$this->symbol_after_the_dot = $this->rightmost ? null : $rule->rhs[$dot];
+		$this->_blink = array();
+		$this->follow = new set($follow);
+		$this->_flink=  array();
+		bug_unless($this->rightmost or count($rule));
+	}
+	function text() {
+		$out = $this->rule->dotted_text($this->dot);
+		$out .= ' [ '.implode(' ', $this->follow->all()).' ]';
+		return $out;
+	}
+	function blink($config) {
+		$this->_blink[] = $config;
+	}
+	function next() {
+		bug_if($this->rightmost);
+		$c = new config($this->rule, $this->dot+1, array());
+		# Anything in the follow set for this config will also be in the next.
+		# However, we link it backwards because we might wind up selecting a
+		# pre-existing state, and the housekeeping is easier in the first half
+		# of the program. We'll fix it before doing the propagation.
+		$c->blink($this);
+		return $c;
+	}
+	function copy_links_from($that) {
+		foreach($that->_blink as $c) $this->blink($c);
+	}
+	function lambda() {
+		return $this->rule->lambda_from($this->dot);
+	}
+	function simple_follow() {
+		return $this->rule->first[$this->dot+1]->all();
+	}
+	function epsilon_follows() {
+		return $this->rule->lambda_from($this->dot+1);
+	}
+	function fixlinks() {
+		foreach ($this->_blink as $that) $that->_flink[] = $this;
+		$this->blink = array();
+	}
+	function dump() {
+		echo "   * ";
+		echo $this->key.' : ';
+		echo $this->rule->dotted_text($this->dot);
+		echo $this->follow->text();
+		foreach ($this->_flink as $c) echo $c->key.' / ';
+		echo "\n";
+	}
+}
+class lime {
+	var $parser_class = 'parser';
+	function __construct() {
+		$this->p_next = 1;
+		$this->sym = array();
+		$this->rule = array();
+		$this->start_symbol_set = array();
+		$this->state = array();
+		$this->stop = $this->sym('#');
+		#$err = $this->sym('error');
+		$err->term = false;
+		$this->lang = new lime_language_php();
+	}
+	function language() { return $this->lang; }
+	function build_parser() {
+		$this->add_start_rule();
+		foreach ($this->rule as $r) $r->determine_precedence();
+		$this->find_sym_lamdba();
+		$this->find_sym_first();
+		foreach ($this->rule as $rule) $rule->find_first();
+		$initial = $this->find_states();
+		$this->fixlinks();
+		# $this->dump_configurations();
+		$this->find_follow_sets();
+		foreach($this->state as $s) $s->find_reductions($this);
+		$i = $this->resolve_conflicts();
+		$a = $this->rule_table();
+		$qi = $initial->id;
+		return $this->lang->ptab_to_class($this->parser_class, compact('a', 'qi', 'i'));
+	}
+	function rule_table() {
+		$s = array();
+		foreach ($this->rule as $i => $r) {
+			$s[$i] = $r->table($this->lang);
+		}
+		return $s;
+	}
+	function add_rule($symbol, $rhs, $code) {
+		$this->add_raw_rule($symbol, $rhs, $code, count($rhs), true);
+	}
+	function trump_up_bogus_lhs($real) {
+		return "'$real'".count($this->rule);
+	}
+	function add_raw_rule($lhs, $rhs, $code, $look, $replace) { 
+		$sym = $this->sym($lhs);
+		$sym->term=false;
+		if (empty($rhs)) $sym->lambda = true;
+		$rs = array();
+		foreach ($rhs as $str) $rs[] = $this->sym($str);
+		$rid = count($this->rule);
+		$r = new rule($rid, $sym, $rs, $code, $look, $replace);
+		$this->rule[$rid] = $r;
+		$sym->rule[] = $r;
+	}
+	function sym($str) {
+		if (!isset($this->sym[$str])) $this->sym[$str] = new sym($str, count($this->sym));
+		return $this->sym[$str];
+	}
+	function summary() {
+		$out = '';
+		foreach ($this->sym as $sym) if (!$sym->term) $out .= $sym->summary();
+		return $out;
+	}
+	private function find_sym_lamdba() {
+		do {
+			$go = false;
+			foreach ($this->sym as $sym) if (!$sym->lambda) {
+				foreach ($sym->rule as $rule) if ($rule->lambda()) {
+					$go = true;
+					$sym->lambda = true;
+				}
+			}
+		} while ($go);
+	}
+	private function teach_terminals_first_set() {
+		foreach ($this->sym as $sym) if ($sym->term) $sym->first->add($sym->name);
+	}
+	private function find_sym_first() {
+		$this->teach_terminals_first_set();
+		do {
+			$go = false;
+			foreach ($this->rule as $r) if ($r->teach_symbol_of_first_set()) $go = true;
+		} while ($go);
+	}
+	function add_start_rule() {
+		$rewrite = new lime_rewrite("'start'");
+		$rhs = new lime_rhs();
+		$rhs->add(new lime_glyph($this->deduce_start_symbol()->name, NULL));
+		#$rhs->add(new lime_glyph($this->stop->name, NULL));
+		$rewrite->add_rhs($rhs);
+		$rewrite->update($this);
+	}
+	private function deduce_start_symbol() {
+		$candidate = current($this->start_symbol_set);
+		# Did the person try to set a start symbol at all?
+		if (!$candidate) return $this->first_rule_lhs();
+		# Do we actually have such a symbol on the left of a rule?
+		if ($candidate->terminal) return $this->first_rule_lhs();
+		# Ok, it's a decent choice. We need to return the symbol entry.
+		return $this->sym($candidate);
+	}
+	private function first_rule_lhs() {
+		reset($this->rule);
+		$r = current($this->rule);
+		return $r->sym;
+	}
+	function find_states() {
+		/*
+		Build an initial state. This is a recursive process which digs out
+		the LR(0) state graph. 
+		*/
+		$start_glyph = "'start'";
+		$sym = $this->sym($start_glyph);
+		$basis = array();
+		foreach($sym->rule as $rule) {
+			$c = $rule->leftmost(array('#'));
+			$basis[$c->key] = $c;
+		}
+		$initial = $this->get_state($basis);
+		$initial->add_accept($sym);
+		return $initial;
+	}
+	function get_state($basis) {
+		$key = array_keys($basis);
+		sort($key);
+		$key = implode(' ', $key);
+		if (isset($this->state[$key])) {
+			# Copy all the links around...
+			$state = $this->state[$key];
+			foreach($basis as $config) $state->close[$config->key]->copy_links_from($config);
+			return $state;
+		} else {
+			$close = $this->state_closure($basis);
+			$this->state[$key] = $state = new state(count($this->state), $key, $close);
+			$this->build_shifts($state);
+			return $state;
+		}
+	}
+	private function state_closure($q) {
+		# $q is a list of config.
+		$close = array();
+		while ($config = array_pop($q)) {
+			if (isset($close[$config->key])) {
+				$close[$config->key]->copy_links_from($config);
+				$close[$config->key]->follow->union($config->follow);
+				continue;
+			}
+			$close[$config->key] = $config;
+			
+			$symbol_after_the_dot = $config->symbol_after_the_dot;
+			if (!$symbol_after_the_dot) continue;
+			
+			if (! $symbol_after_the_dot->term) {
+				foreach ($symbol_after_the_dot->rule as $r) {
+					$station = $r->leftmost($config->simple_follow());
+					if ($config->epsilon_follows()) $station->blink($config);
+					$q[] = $station;
+				}
+				# The following turned out to be wrong. Don't do it.
+				#if ($symbol_after_the_dot->lambda) {
+				#	$q[] = $config->next();
+				#}
+			}
+			
+		}
+		return $close;
+	}
+	function build_shifts($state) {
+		foreach ($state->segment_config() as $glyph => $segment) {
+			$basis = array();
+			foreach ($segment as $preshift) {
+				$postshift = $preshift->next();
+				$basis[$postshift->key] = $postshift;
+			}
+			$dest = $this->get_state($basis);
+			$state->add_shift($this->sym($glyph), $dest);
+		}
+	}
+	function fixlinks() {
+		foreach ($this->state as $s) foreach ($s->close as $c) $c->fixlinks();
+	}
+	function find_follow_sets() {
+		$q = array();
+		foreach ($this->state as $s) foreach ($s->close as $c) $q[] = $c;
+		while ($q) {
+			$c = array_shift($q);
+			foreach ($c->_flink as $d) {
+				if ($d->follow->union($c->follow)) $q[] = $d;
+			}
+		}
+	}
+	private function set_assoc($ss, $l, $r) {
+		$p = ($this->p_next++)*2;
+		foreach ($ss as $glyph) {
+			$s = $this->sym($glyph);
+			$s->left_prec = $p+$l;
+			$s->right_prec = $p+$r;
+		}
+	}
+	function left_assoc($ss) { $this->set_assoc($ss, 1, 0); }
+	function right_assoc($ss) { $this->set_assoc($ss, 0, 1); }
+	function non_assoc($ss) { $this->set_assoc($ss, 0, 0); }
+	private function resolve_conflicts() {
+		# For each state, try to find one and only one
+		# thing to do for any given lookahead.
+		$i = array();
+		foreach ($this->state as $s) $i[$s->id] = $s->resolve_conflicts();
+		return $i;
+	}
+	function dump_configurations() {
+		foreach ($this->state as $q) $q->dump();
+	}
+	function dump_first_sets() {
+		foreach ($this->sym as $s) {
+			echo " * ";
+			echo $s->name.' : ';
+			echo $s->first->text();
+			echo "\n";
+		}
+	}
+	function add_rule_with_actions($lhs, $rhs) {
+		# First, make sure this thing is well-formed.
+		if(!is_object(end($rhs))) $rhs[] = new cf_action('');
+		# Now, split it into chunks based on the actions.
+		$look = -1;
+		$subrule = array();
+		$subsymbol = '';
+		while (count($rhs)) {
+			$it = array_shift($rhs);
+			$look ++;
+			if (is_string($it)) {
+				$subrule[] = $it;
+			} else {
+				$code = $it->code;
+				# It's an action.
+				# Is it the last one?
+				if (count($rhs)) {
+					# no.
+					$subsymbol = $this->trump_up_bogus_lhs($lhs);
+					$this->add_raw_rule($subsymbol, $subrule, $code, $look, false);
+					$subrule = array($subsymbol);
+				} else {
+					# yes.
+					$this->add_raw_rule($lhs, $subrule, $code, $look, true);
+				}
+			}
+		}
+	}
+	function pragma($type, $args) {
+		switch ($type) {
+			case 'left':
+			$this->left_assoc($args);
+			break;
+			
+			case 'right':
+			$this->right_assoc($args);
+			break;
+			
+			case 'nonassoc':
+			$this->non_assoc($args);
+			break;
+			
+			case 'start':
+			$this->start_symbol_set = $args;
+			break;
+			
+			case 'class':
+			$this->parser_class = $args[0];
+			break;
+			
+			default:
+			emit(sprintf("Bad Parser Pragma: (%s)", $type));
+			exit(1);
+		}
+	}
+}
+class lime_language {}
+class lime_language_php extends lime_language {
+	private function result_code($expr) { return "\$result = $expr;\n"; }
+	function default_result() { return $this->result_code('reset($tokens)'); }
+	function result_pos($pos) { return $this->result_code(lime_token_reference($pos)); }
+	function bind($name, $pos) { return "\$$name =& \$tokens[$pos];\n"; }
+	function fixup($code) {
+		$code = preg_replace_callback('/\\$(\d+)/', 'lime_token_reference_callback', $code);
+		$code = preg_replace('/\\$\\$/', '$result', $code);
+		return $code;
+	}
+	function to_php($code) {
+		return $code;
+	}
+	function ptab_to_class($parser_class, $ptab) {
+		$code = "class $parser_class extends lime_parser {\n";
+		$code .= 'var $qi = '.var_export($ptab['qi'], true).";\n";
+		$code .= 'var $i = '.var_export($ptab['i'], true).";\n";
+		
+		
+		$rc = array();
+		$method = array();
+		$rules = array();
+		foreach($ptab['a'] as $k => $a) {
+			$symbol = preg_replace('/[^\w]/', '', $a['symbol']);
+			$rn = ++$rc[$symbol];
+			$mn = "reduce_${k}_${symbol}_${rn}";
+			$method[$k] = $mn;
+			$comment = "#\n# $a[text]\n#\n";
+			$php = $this->to_php($a['code']);
+			$code .= "function $mn(".LIME_CALL_PROTOCOL.") {\n$comment$php\n}\n\n";
+			
+			
+			unset($a['code']);
+			unset($a['text']);
+			$rules[$k] = $a;
+		}
+		
+		$code .= 'var $method = '.var_export($method, true).";\n";
+		$code .= 'var $a = '.var_export($rules, true).";\n";
+		
+		
+		
+		$code .= "}\n";
+		#echo $code;
+		return $code;
+	}
+}
+class lime_rhs {
+	function __construct() {
+		/**
+		Construct and add glyphs and actions in whatever order.
+		Then, add this to a lime_rewrite.
+		
+		Don't call install_rule.
+		The rewrite will do that for you when you "update" with it.
+		*/
+		$this->rhs = array();
+	}
+	function add($slot) {
+		bug_unless($slot instanceof lime_slot);
+		$this->rhs[] = $slot;
+	}
+	function install_rule(lime $lime, $lhs) {
+		# This is the part that has to break the rule into subrules if necessary.
+		$rhs = $this->rhs;
+		# First, make sure this thing is well-formed.
+		if (!(end($rhs) instanceof lime_action)) $rhs[] = new lime_action('', NULL);
+		# Now, split it into chunks based on the actions.
+		
+		$lang = $lime->language();
+		$result_code = $lang->default_result();
+		$look = -1;
+		$subrule = array();
+		$subsymbol = '';
+		$preamble = '';
+		while (count($rhs)) {
+			$it = array_shift($rhs);
+			$look ++;
+			if ($it instanceof lime_glyph) {
+				$subrule[] = $it->data;
+			} elseif ($it instanceof lime_action) {
+				$code = $it->data;
+				# It's an action.
+				# Is it the last one?
+				if (count($rhs)) {
+					# no.
+					$subsymbol = $lime->trump_up_bogus_lhs($lhs);
+					$action = $lang->default_result().$preamble.$code;
+					$lime->add_raw_rule($subsymbol, $subrule, $action, $look, false);
+					$subrule = array($subsymbol);
+				} else {
+					# yes.
+					$action = $result_code.$preamble.$code;
+					$lime->add_raw_rule($lhs, $subrule, $action, $look, true);
+				}
+			} else {
+				impossible();
+			}
+			if ($it->name == '$') $result_code = $lang->result_pos($look);
+			elseif ($it->name) $preamble .= $lang->bind($it->name, $look);
+		}
+	}
+}
+class lime_rewrite {
+	function __construct($glyph) {
+		/**
+		Construct one of these with the name of the lhs.
+		Add some rhs-es to it.
+		Finally, "update" the lime you're building.
+		*/
+		$this->glyph = $glyph;
+		$this->rhs = array();
+	}
+	function add_rhs($rhs) {
+		bug_unless($rhs instanceof lime_rhs);
+		$this->rhs[] = $rhs;
+	}
+	function update(lime $lime) {
+		foreach ($this->rhs as $rhs) {
+			$rhs->install_rule($lime, $this->glyph);
+			
+		}
+	}
+}
+class lime_slot {
+	/**
+	This keeps track of one position in an rhs.
+	We specialize to handle actions and glyphs.
+	If there is a name for the slot, we store it here.
+	Later on, this structure will be consulted in the formation of
+	actual production rules.
+	*/
+	function __construct($data, $name) {
+		$this->data = $data;
+		$this->name = $name;
+	}
+	function preamble($pos) {
+		if (strlen($this->name) > 0) {
+			return "\$$this->name =& \$tokens[$pos];\n";
+		}
+	}
+}
+class lime_glyph extends lime_slot {}
+class lime_action extends lime_slot {}
+function lime_bootstrap() {
+	
+	/*
+	
+	This function isn't too terribly interesting to the casual observer.
+	You're probably better off looking at parse_lime_grammar() instead.
+	
+	Ok, if you insist, I'll explain.
+	
+	The input to Lime is a CFG parser definition. That definition is
+	written in some language. (The Lime language, to be exact.)
+	Anyway, I have to parse the Lime language and compile it into a
+	very complex data structure from which a parser is eventually
+	built. What better way than to use Lime itself to parse its own
+	language? Well, it's almost that simple, but not quite.
+	
+	The Lime language is fairly potent, but a restricted subset of
+	its features was used to write a metagrammar. Then, I hand-translated
+	that metagrammar into another form which is easy to snarf up.
+	In the process of reading that simplified form, this function 
+	builds the same sort of data structure that later gets turned into
+	a parser. The last step is to run the parser generation algorithm,
+	eval() the resulting PHP code, and voila! With no hard work, I can
+	suddenly read and comprehend the full range of the Lime language
+	without ever having written an algorithm to do so. It feels like magic.
+	
+	*/
+	
+	$bootstrap = LIME_DIR."/lime.bootstrap";
+	$lime = new lime();
+	$lime->parser_class = 'lime_metaparser';
+	$rhs = array();
+	bug_unless(is_readable($bootstrap));
+	foreach(file($bootstrap) as $l) {
+		$a = explode(":", $l, 2);
+		if (count($a) == 2) {
+			list($pattern, $code) = $a;
+			$sl = new lime_rhs();
+			$pattern = trim($pattern);
+			if (strlen($pattern)>0) {
+				foreach (explode(' ', $pattern) as $glyph) $sl->add(new lime_glyph($glyph, NULL));
+			}
+			$sl->add(new lime_action($code, NULL));
+			$rhs[] = $sl;
+		} else {
+			$m = preg_match('/^to (\w+)$/', $l, $r);
+			if ($m == 0) continue;
+			$g = $r[1];
+			$rw = new lime_rewrite($g);
+			foreach($rhs as $b) $rw->add_rhs($b);
+			$rw->update($lime);
+			$rhs = array();
+		}
+	}
+	$parser_code = $lime->build_parser();
+	eval($parser_code);
+}
+
+class voodoo_scanner extends flex_scanner {
+	/*
+	
+	The voodoo is in the way I do lexical processing on grammar definition
+	files. They contain embedded bits of PHP, and it's important to keep
+	track of things like strings, comments, and matched braces. It seemed
+	like an ideal problem to solve with GNU flex, so I wrote a little
+	scanner in flex and C to dig out the tokens for me. Of course, I need
+	the tokens in PHP, so I designed a simple binary wrapper for them which
+	also contains line-number information, guaranteed to help out if you
+	write a grammar which surprises the parser in any manner.
+	
+	*/
+	function executable() { return LIME_DIR.'/lime_scan_tokens'; }
+}
+
+function parse_lime_grammar($path) {
+	/*
+	
+	This is a good function to read because it teaches you how to interface
+	with a Lime parser. I've tried to isolate out the bits that aren't
+	instructive in that regard.
+	
+	*/
+	if (!class_exists('lime_metaparser')) lime_bootstrap();
+	
+	$parse_engine = new parse_engine(new lime_metaparser());
+	$scanner = new voodoo_scanner($path);
+	try {
+		# The result of parsing a Lime grammar is a Lime object.
+		$lime = $scanner->feed($parse_engine);
+		# Calling its build_parser() method gets the output PHP code.
+		return $lime->build_parser();
+	} catch (parse_error $e) {
+		die ($e->getMessage()." in $path line $scanner->lineno.\n");
+	}
+}
+
+
+if ($_SERVER['argv']) {
+	$code = '';
+	array_shift($_SERVER['argv']);	# Strip out the program name.
+	foreach ($_SERVER['argv'] as $path) {
+		$code .= parse_lime_grammar($path);
+	}
+	
+	echo "<?php\n\n";
+?>
+
+/*
+
+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.
+
+*/
+
+<?php
+	echo $code;
+}
diff --git a/external/badvpn_dns/lime/lime_scan_tokens.l b/external/badvpn_dns/lime/lime_scan_tokens.l
new file mode 100644
index 0000000..d4aae9a
--- /dev/null
+++ b/external/badvpn_dns/lime/lime_scan_tokens.l
@@ -0,0 +1,121 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+%{
+void out(char*t, char*v);
+void lit();
+void tok(char*t);
+void php();
+%}
+
+%option stack
+%option yylineno
+%option main
+
+%x code
+%x dquote
+%x squote
+
+CHAR	\n|.
+
+ALPHA [a-zA-Z]
+DIGIT [0-9]
+ALNUM {ALPHA}|{DIGIT}
+WORD {ALNUM}|_
+STOP "."
+
+SYM {ALPHA}{WORD}*'*
+LIT '.'
+
+ESC "\"{CHAR}
+SCHAR [^\']|ESC
+DCHAR [^\"]|ESC
+COM "//"|"#"
+
+CC [^*\n]
+CX "*"+{CC}+
+CT "*"+"/"
+BLOCKCMT "/*"({CC}|{CX})*{CT}
+
+%x pragma
+
+
+%%
+
+[[:space:]]+ {}
+#.* {}
+
+{STOP} 			out("stop", ".");
+{SYM} 			tok("sym");
+{LIT}			tok("lit");
+"/"{WORD}+		|
+"/$"			out("lambda", yytext+1);
+"%"{WORD}+ 		{
+	out("pragma", yytext+1);
+	yy_push_state(pragma);
+}
+
+<*>"{"		{
+	lit();
+	yy_push_state(code);
+}
+
+.	lit();
+
+
+<pragma>{
+\n {
+	out("stop", ".");
+	yy_pop_state();
+}
+[[:space:]] {}
+{SYM} 			tok("sym");
+{LIT}			tok("lit");
+.	lit();
+}
+
+<code>{
+"}" {
+	lit();
+	yy_pop_state();
+}
+'{SCHAR}*'		php();
+\"{DCHAR}*\"	php();
+{COM}.*			php();
+{BLOCKCMT}		php();
+[^{}'"#/]+		php();
+.				php();
+}
+
+%%
+
+void lit() {
+	char lit[] = "'.'";
+	lit[1] = *yytext;
+	out(lit, yytext);
+}
+
+void tok(char*t) {
+	out(t, yytext);
+}
+
+void php() {
+	out("php", yytext);
+}
+
+void out(char*type, char*value) {
+	printf("%d\001%s\001%s", yylineno, type, value);
+	fputc(0, stdout);
+}
diff --git a/external/badvpn_dns/lime/metagrammar b/external/badvpn_dns/lime/metagrammar
new file mode 100644
index 0000000..5d057c0
--- /dev/null
+++ b/external/badvpn_dns/lime/metagrammar
@@ -0,0 +1,58 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# This is the grammar for all other grammar files that will work with the
+# Lime LALR(1) Context-Free Grammar Parser Generator.
+# You can read this to get an idea how things work, but this file is not
+# actually used in the system. Rather, it's an implementation guide for the
+# file "lime.bootstrap".
+
+%class lime_metaparser
+%start grammar
+
+grammar
+= {$$ = new lime();}
+| grammar/$ pragma/p toklist/t stop {$$->pragma($p, $t);}
+| grammar/$ rewrite/r stop {$r->update($$);}
+.
+
+rewrite
+= sym/s '=' rhs/r {$$ = new lime_rewrite($s); $$->add_rhs($r);}
+| rewrite/$ '|' rhs/r {$$->add_rhs($r);}
+.
+
+slot
+= action/a {$$ = new lime_action($a, NULL);}
+| action/a lambda/l {$$ = new lime_action($a, $l);}
+| sym/s {$$ = new lime_glyph($s, NULL);}
+| sym/s lambda/l {$$ = new lime_glyph($s, $l);}
+| lit/l {$$ = new lime_glyph($l, NULL);}
+.
+
+rhs
+= {$$ = new lime_rhs();}
+| rhs/$ slot/s {$$->add($s);}
+.
+
+action = '{' code/$ '}' .
+
+toklist = {$$=array();}
+| toklist/$ sym/s {$$[] = $s;}
+| toklist/$ lit/l {$$[] = $l;}
+.
+
+code = {}
+| code/$ php/p {$$.=$p;}
+| code/$ '{' code/c '}' {$$.='{'.$c.'}';}
+.
diff --git a/external/badvpn_dns/lime/parse_engine.php b/external/badvpn_dns/lime/parse_engine.php
new file mode 100644
index 0000000..fd54cc4
--- /dev/null
+++ b/external/badvpn_dns/lime/parse_engine.php
@@ -0,0 +1,252 @@
+<?php
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+
+define('LIME_CALL_PROTOCOL', '$tokens, &$result');
+
+abstract class lime_parser {
+}
+
+class parse_error extends Exception {}	# If this happens, the input doesn't match the grammar.
+class parse_bug extends Exception {}	# If this happens, I made a mistake.
+
+class parse_unexpected_token extends parse_error {
+	function __construct($type, $state) {
+		parent::__construct("Unexpected token of type ($type)");
+		$this->type = $type;
+		$this->state = $state;
+	}
+}
+class parse_premature_eof extends parse_error {
+	function __construct() {
+		parent::__construct("Premature EOF");
+	}
+}
+
+
+class parse_stack {
+	function __construct($qi) {
+		$this->q = $qi;
+		$this->qs = array();
+		$this->ss = array();
+	}
+	function shift($q, $semantic) {
+		$this->ss[] = $semantic;
+		$this->qs[] = $this->q;
+		$this->q = $q;
+		# echo "Shift $q -- $semantic<br/>\n";
+	}
+	function top_n($n) {
+		if (!$n) return array();
+		return array_slice($this->ss, 0-$n);
+	}
+	function pop_n($n) {
+		if (!$n) return array();
+		$qq = array_splice($this->qs, 0-$n);
+		$this->q = $qq[0];
+		return array_splice($this->ss, 0-$n);
+	}
+	function occupied() { return !empty($this->ss); }
+	function index($n) {
+		if ($n) $this->q = $this->qs[count($this->qs)-$n];
+	}
+	function text() {
+		return $this->q." : ".implode(' . ', array_reverse($this->qs));
+	}
+}
+class parse_engine {
+	function __construct($parser) {
+		$this->parser = $parser;
+		$this->qi = $parser->qi;
+		$this->rule = $parser->a;
+		$this->step = $parser->i;
+		#$this->prepare_callables();
+		$this->reset();
+		#$this->debug = false;
+	}
+	function reset() {
+		$this->accept = false;
+		$this->stack = new parse_stack($this->qi);
+	}
+	private function enter_error_tolerant_state() {
+		while ($this->stack->occupied()) {
+			if ($this->has_step_for('error')) return true;
+			$this->drop();
+		};
+		return false;
+	}
+	private function drop() { $this->stack->pop_n(1); }
+	function eat_eof() {
+		{/*
+		
+		So that I don't get any brilliant misguided ideas:
+		
+		The "accept" step happens when we try to eat a start symbol.
+		That happens because the reductions up the stack at the end
+		finally (and symetrically) tell the parser to eat a symbol
+		representing what they've just shifted off the end of the stack
+		and reduced. However, that doesn't put the parser into any
+		special different state. Therefore, it's back at the start
+		state.
+		
+		That being said, the parser is ready to reduce an EOF to the
+		empty program, if given a grammar that allows them.
+		
+		So anyway, if you literally tell the parser to eat an EOF
+		symbol, then after it's done reducing and accepting the prior
+		program, it's going to think it has another symbol to deal with.
+		That is the EOF symbol, which means to reduce the empty program,
+		accept it, and then continue trying to eat the terminal EOF.
+		
+		This infinte loop quickly runs out of memory.
+		
+		That's why the real EOF algorithm doesn't try to pretend that
+		EOF is a terminal. Like the invented start symbol, it's special.
+		
+		Instead, we pretend to want to eat EOF, but never actually
+		try to get it into the parse stack. (It won't fit.) In short,
+		we look up what reduction is indicated at each step in the
+		process of rolling up the parse stack.
+		
+		The repetition is because one reduction is not guaranteed to
+		cascade into another and clean up the entire parse stack.
+		Rather, it will instead shift each partial production as it
+		is forced to completion by the EOF lookahead.
+		*/}
+		
+		# We must reduce as if having read the EOF symbol
+		do {
+			# and we have to try at least once, because if nothing
+			# has ever been shifted, then the stack will be empty
+			# at the start.
+			list($opcode, $operand) = $this->step_for('#');
+			switch ($opcode) {
+				case 'r': $this->reduce($operand); break;
+				case 'e': $this->premature_eof(); break;
+				default: throw new parse_bug(); break;
+			}
+		} while ($this->stack->occupied());
+		{/*
+		If the sentence is well-formed according to the grammar, then
+		this will eventually result in eating a start symbol, which
+		causes the "accept" instruction to fire. Otherwise, the
+		step('#') method will indicate an error in the syntax, which
+		here means a premature EOF.
+		
+		Incedentally, some tremendous amount of voodoo with the parse
+		stack might help find the beginning of some unfinished
+		production that the sentence was cut off during, but as a
+		general rule that would require deeper knowledge.
+		*/}
+		if (!$this->accept) throw new parse_bug();
+		return $this->semantic;
+	}
+	private function premature_eof() {
+		$seen = array();
+		while ($this->enter_error_tolerant_state()) {
+			if (isset($seen[$this->state()])) {
+				// This means that it's pointless to try here.
+				// We're guaranteed that the stack is occupied.
+				$this->drop();
+				continue;
+			}
+			$seen[$this->state()] = true;
+			
+			$this->eat('error', NULL);
+			if ($this->has_step_for('#')) {
+				// Good. We can continue as normal.
+				return;
+			} else {
+				// That attempt to resolve the error condition
+				// did not work. There's no point trying to
+				// figure out how much to slice off the stack.
+				// The rest of the algorithm will make it happen.
+			}
+		}
+		throw new parse_premature_eof();
+	}
+	private function current_row() { return $this->step[$this->state()]; }
+	private function step_for($type) {
+		$row = $this->current_row();
+		if (!isset($row[$type])) return array('e', $this->stack->q);
+		return explode(' ', $row[$type]);
+	}
+	private function has_step_for($type) {
+		$row = $this->current_row();
+		return isset($row[$type]);
+	}
+	private function state() { return $this->stack->q; }
+	function eat($type, $semantic) {
+		# assert('$type == trim($type)');
+		# if ($this->debug) echo "Trying to eat a ($type)\n";
+		list($opcode, $operand) = $this->step_for($type);
+		switch ($opcode) {
+			case 's':
+			# if ($this->debug) echo "shift $type to state $operand\n";
+			$this->stack->shift($operand, $semantic);
+			# echo $this->stack->text()." shift $type<br/>\n";
+			break;
+			
+			case 'r':
+			$this->reduce($operand);
+			$this->eat($type, $semantic);
+			# Yes, this is tail-recursive. It's also the simplest way.
+			break;
+			
+			case 'a':
+			if ($this->stack->occupied()) throw new parse_bug('Accept should happen with empty stack.');
+			$this->accept = true;
+			#if ($this->debug) echo ("Accept\n\n");
+			$this->semantic = $semantic;
+			break;
+			
+			case 'e':
+			# This is thought to be the uncommon, exceptional path, so
+			# it's OK that this algorithm will cause the stack to
+			# flutter while the parse engine waits for an edible token.
+			# if ($this->debug) echo "($type) causes a problem.\n";
+			if ($this->enter_error_tolerant_state()) {
+				$this->eat('error', NULL);
+				if ($this->has_step_for($type)) $this->eat($type, $semantic);
+			} else {
+				# If that didn't work, give up:
+				throw new parse_error("Parse Error: ($type)($semantic) not expected");
+			}
+			break;
+			
+			default:
+			throw new parse_bug("Bad parse table instruction ".htmlspecialchars($opcode));
+		}
+	}
+	private function reduce($rule_id) {
+		$rule = $this->rule[$rule_id];
+		$len = $rule['len'];
+		$semantic = $this->perform_action($rule_id, $this->stack->top_n($len));
+		#echo $semantic.br();
+		if ($rule['replace']) $this->stack->pop_n($len);
+		else $this->stack->index($len);
+		$this->eat($rule['symbol'], $semantic);
+	}
+	private function perform_action($rule_id, $slice) {
+		# we have this weird calling convention....
+		$result = null;
+		$method = $this->parser->method[$rule_id];
+		#if ($this->debug) echo "rule $id: $method\n";
+		$this->parser->$method($slice, $result);
+		return $result;
+	}
+}
diff --git a/external/badvpn_dns/lime/set.so.php b/external/badvpn_dns/lime/set.so.php
new file mode 100644
index 0000000..ef87c6c
--- /dev/null
+++ b/external/badvpn_dns/lime/set.so.php
@@ -0,0 +1,29 @@
+<?php
+
+/*
+File: set.so.php
+License: GPL
+Purpose: We should really have a "set" data type. It's too useful.
+*/
+
+class set {
+	function __construct($list=array()) { $this->data = array_count_values($list); }
+	function has($item) { return isset($this->data[$item]); }
+	function add($item) { $this->data[$item] = true; }
+	function del($item) { unset($this->data[$item]); return $item;}
+	function all() { return array_keys($this->data); }
+	function one() { return key($this->data); }
+	function count() { return count($this->data); }
+	function pop() { return $this->del($this->one()); }
+	function union($that) {
+		$progress = false;
+		foreach ($that->all() as $item) if (!$this->has($item)) {
+			$this->add($item);
+			$progress = true;
+		}
+		return $progress;
+	}
+	function text() {
+		return ' { '.implode(' ', $this->all()).' } ';
+	}
+}
diff --git a/external/badvpn_dns/lwip/CHANGELOG b/external/badvpn_dns/lwip/CHANGELOG
new file mode 100644
index 0000000..685b568
--- /dev/null
+++ b/external/badvpn_dns/lwip/CHANGELOG
@@ -0,0 +1,3396 @@
+HISTORY
+
+(CVS HEAD)
+
+  * [Enter new changes just after this line - do not remove this line]
+
+ ++ New features:
+
+  2012-03-25: Simon Goldschmidt (idea by Mason)
+  * posix/*: added posix-compatibility include files posix/netdb.h and posix/sys/socket.h
+    which are a simple wrapper to the correct lwIP include files.
+ 
+  2012-01-16: Simon Goldschmidt
+  * opt.h, icmp.c: Added option CHECKSUM_GEN_ICMP
+
+  2011-12-17: Simon Goldschmidt
+  * ip.h: implemented API functions to access so_options of IP pcbs (UDP, TCP, RAW)
+    (fixes bug #35061)
+
+  2011-09-27: Simon Goldschmidt
+  * opt.h, tcp.c, tcp_in.c: Implemented limiting data on ooseq queue (task #9989)
+    (define TCP_OOSEQ_MAX_BYTES / TCP_OOSEQ_MAX_PBUFS in lwipopts.h)
+
+  2011-09-21: Simon Goldschmidt
+  * opt.h, api.h, api_lib.c, api_msg.h/.c, sockets.c: Implemented timeout on
+    send (TCP only, bug #33820)
+
+  2011-09-21: Simon Goldschmidt
+  * init.c: Converted runtime-sanity-checks into compile-time checks that can
+    be disabled (since runtime checks can often not be seen on embedded targets)
+
+  2011-09-11: Simon Goldschmidt
+  * ppp.h, ppp_impl.h: splitted ppp.h to an internal and external header file
+    to get a clear separation of which functions an application or port may use
+    (task #11281)
+
+ 2011-09-11: Simon Goldschmidt
+  * opt.h, tcp_impl.h, tcp.c, udp.h/.c: Added a config option to randomize
+    initial local TCP/UDP ports (so that different port ranges are used after
+    a reboot; bug #33818; this one added tcp_init/udp_init functions again)
+
+  2011-09-03: Simon Goldschmidt
+  * dhcp.c: DHCP uses LWIP_RAND() for xid's (bug #30302)
+
+  2011-08-24: Simon Goldschmidt
+  * opt.h, netif.h/.c: added netif remove callback (bug #32397)
+
+  2011-07-26: Simon Goldschmidt
+  * etharp.c: ETHARP_SUPPORT_VLAN: add support for an external VLAN filter
+    function instead of only checking for one VLAN (define ETHARP_VLAN_CHECK_FN)
+
+  2011-07-21: Simon Goldschmidt (patch by hanhui)
+  * ip4.c, etharp.c, pbuf.h: bug #33634 ip_forward() have a faulty behaviour:
+    Added pbuf flags to mark incoming packets as link-layer broadcast/multicast.
+    Also added code to allow ip_forward() to forward non-broadcast packets to
+    the input netif (set IP_FORWARD_ALLOW_TX_ON_RX_NETIF==1).
+
+  2011-07-21: Simon Goldschmidt
+  * sockets.c, opt.h: (bug #30185): added LWIP_FIONREAD_LINUXMODE that makes
+    ioctl/FIONREAD return the size of the next pending datagram.
+
+  2011-06-26: Simon Goldschmidt (patch by Cameron Gutman)
+  * tcp.c, tcp_out.c: bug #33604: added some more asserts to check that
+    pcb->state != LISTEN
+
+ 2011-05-25: Simon Goldschmidt
+  * again nearly the whole stack, renamed ip.c to ip4.c, ip_addr.c to ip4_addr.c,
+    combined ipv4/ipv6 inet_chksum.c, added ip.h, ip_addr.h: Combined IPv4
+    and IPv6 code where possible, added defines to access IPv4/IPv6 in non-IP
+    code so that the code is more readable.
+
+  2011-05-17: Patch by Ivan Delamer (only checked in by Simon Goldschmidt)
+  * nearly the whole stack: Finally, we got decent IPv6 support, big thanks to
+    Ivan! (this is work in progress: we're just post release anyway :-)
+
+   2011-05-14: Simon Goldschmidt (patch by Stéphane Lesage)
+  * tcpip.c/.h: patch #7449 allow tcpip callback from interrupt with static
+    memory message
+
+
+ ++ Bugfixes:
+
+  2013-01-15: Simon Goldschmidt
+  * ip4.c: fixed bug #37665 ip_canforward operates on address in wrong byte order
+
+  2013-01-15: Simon Goldschmidt
+  * pbuf.h: fixed bug #38097 pbuf_free_ooseq() warning
+
+  2013-01-14: Simon Goldschmidt
+  * dns.c: fixed bug #37705 Possible memory corruption in DNS query
+
+  2013-01-11: Simon Goldschmidt
+  * raw.c: fixed bug #38066 Raw pcbs can alter packet without eating it
+
+  2012-09-26: Simon Goldschmidt
+  * api_msg.c: fixed bug #37405 'err_tcp()' uses already freed 'netconn' object
+
+  2012-09-26: patch by Henrik Persson
+  * dhcp.c: patch #7843 Fix corner case with dhcp timeouts
+
+  2012-09-26: patch by Henrik Persson
+  * dhcp.c: patch #7840 Segfault in dhcp_parse_reply if no end marker in dhcp packet
+
+  2012-08-22: Simon Goldschmidt
+  * memp.c: fixed bug #37166: memp_sanity check loops itself
+
+  2012-08-13: Simon Goldschmidt
+  * dhcp.c: fixed bug #36645: Calling dhcp_release before dhcp_start
+    dereferences NULL
+
+  2012-08-13: Simon Goldschmidt
+  * msg_out.c: fixed bug #36840 snmp_send_trap() NULL de-reference if traps
+    configured but no interfaces available
+
+  2012-08-13: Simon Goldschmidt
+  * dns.c: fixed bug #36899 DNS TTL 0 is cached for a long time
+
+  2012-05-11: Simon Goldschmidt (patch by Marty)
+  * memp.c: fixed bug #36412: memp.c does not compile when
+    MEMP_OVERFLOW_CHECK > zero and MEMP_SEPARATE_POOLS == 1
+
+  2012-05-08: Simon Goldschmidt
+  * tcp_out.c: fixed bug #36380: unsent_oversize mismatch in 1.4.1RC1 (this was
+    a debug-check issue only)
+
+  2012-05-03: Simon Goldschmidt (patch by Sylvain Rochet)
+  * ppp.c: fixed bug #36283 (PPP struct used on header size computation and
+    not packed)
+
+  2012-05-03: Simon Goldschmidt (patch by David Empson)
+  * ppp.c: fixed bug #36388 (PPP: checksum-only in last pbuf leads to pbuf with
+    zero length)
+
+  2012-03-27: Simon Goldschmidt
+  * vj.c: fixed bug #35756 header length calculation problem in ppp/vj.c
+
+  2012-03-27: Simon Goldschmidt (patch by Mason)
+  * tcp_out.c: fixed bug #35945: SYN packet should provide the recv MSS not the
+    send MSS
+
+  2012-03-25: Simon Goldschmidt
+  * api_msg.c: Fixed bug #35817: do_connect() invalidly signals op_completed
+    for UDP/RAW with LWIP_TCPIP_CORE_LOCKING==1
+
+  2012-03-25: Simon Goldschmidt
+  * api_msg.h, api_lib.c, api_msg.c, netifapi.c: fixed bug #35931: Name space
+    pollution in api_msg.c and netifapi.c
+
+  2012-03-22: Simon Goldschmidt
+  * ip4.c: fixed bug #35927: missing refragmentaion in ip_forward
+ 
+  2012-03-20: Simon Goldschmidt (patch by Mason)
+  * netdb.c: fixed bug #35907: lwip_gethostbyname_r returns an invalid h_addr_list
+ 
+  2012-03-12: Simon Goldschmidt (patch by Bostjan Meglic)
+  * ppp.c: fixed bug #35809: PPP GetMask(): Compiler warning on big endian,
+    possible bug on little endian system
+
+  2012-02-23: Simon Goldschmidt
+  * etharp.c: fixed bug #35595: Impossible to send broadcast without a gateway
+    (introduced when fixing bug# 33551)
+
+  2012-02-16: Simon Goldschmidt
+  * ppp.c: fixed pbuf leak when PPP session is aborted through pppSigHUP()
+    (bug #35541: PPP Memory Leak)
+
+  2012-02-16: Simon Goldschmidt
+  * etharp.c: fixed bug #35531: Impossible to send multicast without a gateway
+    (introduced when fixing bug# 33551)
+
+  2012-02-16: Simon Goldschmidt (patch by Stéphane Lesage)
+  * msg_in.c, msg_out.c: fixed bug #35536 SNMP: error too big response is malformed
+
+  2012-02-15: Simon Goldschmidt
+  * init.c: fixed bug #35537: MEMP_NUM_* sanity checks should be disabled with
+    MEMP_MEM_MALLOC==1
+
+  2012-02-12: Simon Goldschmidt
+  * tcp.h, tcp_in.c, tcp_out.c: partly fixed bug #25882: TCP hangs on
+    MSS > pcb->snd_wnd (by not creating segments bigger than half the window)
+
+  2012-02-11: Simon Goldschmidt
+  * tcp.c: fixed bug #35435: No pcb state check before adding it to time-wait
+    queue while closing
+
+  2012-01-22: Simon Goldschmidt
+  * tcp.c, tcp_in.c: fixed bug #35305: pcb may be freed too early on shutdown(WR)
+
+  2012-01-21: Simon Goldschmidt
+  * tcp.c: fixed bug #34636: FIN_WAIT_2 - Incorrect shutdown of TCP pcb
+
+  2012-01-20: Simon Goldschmidt
+  * dhcp.c: fixed bug #35151: DHCP asserts on incoming option lengths
+
+ 2012-01-20: Simon Goldschmidt
+  * pbuf.c: fixed bug #35291: NULL pointer in pbuf_copy
+
+  2011-11-25: Simon Goldschmidt
+  * tcp.h/.c, tcp_impl.h, tcp_in.c: fixed bug #31177: tcp timers can corrupt
+    tcp_active_pcbs in some cases
+
+  2011-11-23: Simon Goldschmidt
+  * sys.c: fixed bug #34884: sys_msleep() body needs to be surrounded with
+    '#ifndef sys_msleep'
+
+  2011-11-22: Simon Goldschmidt
+  * netif.c, etharp.h/.c: fixed bug #34684: Clear the arp table cache when
+    netif is brought down
+
+  2011-10-28: Simon Goldschmidt
+  * tcp_in.c: fixed bug #34638: Dead code in tcp_receive - pcb->dupacks
+
+  2011-10-23: Simon Goldschmidt
+  * mem.c: fixed bug #34429: possible memory corruption with
+    LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT set to 1
+
+  2011-10-18: Simon Goldschmidt
+  * arch.h, netdb.c: fixed bug #34592: lwip_gethostbyname_r uses nonstandard
+    error value
+
+  2011-10-18: Simon Goldschmidt
+  * opt.h: fixed default values of TCP_SNDLOWAT and TCP_SNDQUEUELOWAT for small
+    windows (bug #34176 select after non-blocking send times out)
+
+  2011-10-18: Simon Goldschmidt
+  * tcp_impl.h, tcp_out.c: fixed bug #34587: TCP_BUILD_MSS_OPTION doesn't
+    consider netif->mtu, causes slow network
+
+  2011-10-18: Simon Goldschmidt
+  * sockets.c: fixed bug #34581 missing parentheses in udplite sockets code
+
+  2011-10-18: Simon Goldschmidt
+  * sockets.h: fixed bug #34580 fcntl() is missing in LWIP_COMPAT_SOCKETS
+
+  2011-10-17: Simon Goldschmidt
+  * api_msg.c: fixed bug #34569: shutdown(SHUT_WR) crashes netconn/socket api
+
+  2011-10-13: Simon Goldschmidt
+  * tcp_in.c, tcp_out.c: fixed bug #34517 (persist timer is started although no
+    zero window is received) by starting the persist timer when a zero window is
+    received, not when we have more data queued for sending than fits into the
+    window
+
+  2011-10-13: Simon Goldschmidt
+  * def.h, timers.c: fixed bug #34541: LWIP_U32_DIFF is unnecessarily complex
+
+  2011-10-13: Simon Goldschmidt
+  * sockets.c, api_lib.c: fixed bug #34540: compiler error when CORE_LOCKING is
+    used and not all protocols are enabled
+
+  2011-10-12: Simon Goldschmidt
+  * pbuf.c: fixed bug #34534: Error in sending fragmented IP if MEM_ALIGNMENT > 4
+
+  2011-10-09: Simon Goldschmidt
+  * tcp_out.c: fixed bug #34426: tcp_zero_window_probe() transmits incorrect
+    byte value when pcb->unacked != NULL
+
+  2011-10-09: Simon Goldschmidt
+  * ip4.c: fixed bug #34447 LWIP_IP_ACCEPT_UDP_PORT(dst_port) wrong
+
+  2011-09-27: Simon Goldschmidt
+  * tcp_in.c, tcp_out.c: Reset pcb->unsent_oversize in 2 more places...
+
+  2011-09-27: Simon Goldschmidt
+  * tcp_in.c: fixed bug #28288: Data after FIN in oos queue
+
+  2011-09-27: Simon Goldschmidt
+  * dhcp.c: fixed bug #34406 dhcp_option_hostname() can overflow the pbuf
+
+  2011-09-24: Simon Goldschmidt
+  * mem.h: fixed bug #34377 MEM_SIZE_F is not defined if MEM_LIBC_MALLOC==1
+
+  2011-09-23: Simon Goldschmidt
+  * pbuf.h, tcp.c, tcp_in.c: fixed bug #33871: rejecting TCP_EVENT_RECV() for
+    the last packet including FIN can lose data
+
+  2011-09-22: Simon Goldschmidt
+  * tcp_impl.h: fixed bug #34355: nagle does not take snd_buf/snd_queuelen into
+    account
+
+  2011-09-21: Simon Goldschmidt
+  * opt.h: fixed default value of TCP_SND_BUF to not violate the sanity checks
+    in init.c
+
+  2011-09-20: Simon Goldschmidt
+  * timers.c: fixed bug #34337 (possible NULL pointer in sys_check_timeouts)
+
+  2011-09-11: Simon Goldschmidt
+  * tcp_out.c: use pcb->mss instead of TCP_MSS for preallocate mss-sized pbufs
+    (bug #34019)
+
+  2011-09-09: Simon Goldschmidt
+  * udp.c: fixed bug #34072: UDP broadcast is received from wrong UDP pcb if
+    udp port matches
+
+  2011-09-03: Simon Goldschmidt
+  * tcp_in.c: fixed bug #33952 PUSH flag in incoming packet is lost when packet
+    is aggregated and sent to application
+
+  2011-09-01: Simon Goldschmidt
+  * opt.h: fixed bug #31809 LWIP_EVENT_API in opts.h is inconsistent compared
+    to other options
+
+  2011-09-01: Simon Goldschmidt
+  * tcp_in.c: fixed bug #34111 RST for ACK to listening pcb has wrong seqno
+
+  2011-08-24: Simon Goldschmidt
+  * inet6.h: fixed bug #34124 struct in6_addr does not conform to the standard
+
+  2011-08-24: Simon Goldschmidt
+  * api_msg.c, sockets.c: fixed bug #33956 Wrong error returned when calling
+    accept() on UDP connections
+
+  2011-08-24: Simon Goldschmidt
+  * sockets.h: fixed bug #34057 socklen_t should be a typedef
+
+  2011-08-24: Simon Goldschmidt
+  * pbuf.c: fixed bug #34112 Odd check in pbuf_alloced_custom (typo)
+
+  2011-08-24: Simon Goldschmidt
+  * dhcp.c: fixed bug #34122 dhcp: hostname can overflow
+
+  2011-08-24: Simon Goldschmidt
+  * netif.c: fixed bug #34121 netif_add/netif_set_ipaddr fail on NULL ipaddr
+
+  2011-08-22: Simon Goldschmidt
+  * tcp_out.c: fixed bug #33962 TF_FIN not always set after FIN is sent. (This
+    merely prevents nagle from not transmitting fast after closing.)
+
+  2011-07-22: Simon Goldschmidt
+  * api_lib.c, api_msg.c, sockets.c, api.h: fixed bug #31084 (socket API returns
+    always EMSGSIZE on non-blocking sockets if data size > send buffers) -> now
+    lwip_send() sends as much as possible for non-blocking sockets
+
+  2011-07-22: Simon Goldschmidt
+  * pbuf.c/.h, timers.c: freeing ooseq pbufs when the pbuf pool is empty implemented
+    for NO_SYS==1: when not using sys_check_timeouts(), call PBUF_CHECK_FREE_OOSEQ()
+    at regular intervals from main level.
+
+  2011-07-21: Simon Goldschmidt
+  * etharp.c: fixed bug #33551 (ARP entries may time out although in use) by
+    sending an ARP request when an ARP entry is used in the last minute before
+    it would time out.
+
+  2011-07-04: Simon Goldschmidt
+  * sys_arch.txt: Fixed documentation after changing sys arch prototypes for 1.4.0.
+
+  2011-06-26: Simon Goldschmidt
+  * tcp.c: fixed bug #31723 (tcp_kill_prio() kills pcbs with the same prio) by
+    updating its documentation only.
+
+ 2011-06-26: Simon Goldschmidt
+  * mem.c: fixed bug #33545: With MEM_USE_POOLS==1, mem_malloc can return an
+    unaligned pointer.
+
+  2011-06-26: Simon Goldschmidt
+  * mem.c: fixed bug #33544 "warning in mem.c in lwip 1.4.0 with NO_SYS=1"
+
+
+
+(STABLE-1.4.0)
+
+  ++ New features:
+
+  2011-03-27: Simon Goldschmidt
+  * tcp_impl.h, tcp_in.c, tcp_out.c: Removed 'dataptr' from 'struct tcp_seg' and
+    calculate it in tcp_zero_window_probe (the only place where it was used).
+
+  2010-11-21: Simon Goldschmidt
+  * dhcp.c/.h: Added a function to deallocate the struct dhcp from a netif
+    (fixes bug #31525).
+
+  2010-07-12: Simon Goldschmidt (patch by Stephane Lesage)
+  * ip.c, udp.c/.h, pbuf.h, sockets.c: task #10495: Added support for
+    IP_MULTICAST_LOOP at socket- and raw-API level.
+
+  2010-06-16: Simon Goldschmidt
+  * ip.c: Added an optional define (LWIP_IP_ACCEPT_UDP_PORT) that can allow
+    link-layer-addressed UDP traffic to be received while a netif is down (just
+    like DHCP during configuration)
+
+  2010-05-22: Simon Goldschmidt
+  * many many files: bug #27352: removed packing from ip_addr_t, the packed
+    version is now only used in protocol headers. Added global storage for
+    current src/dest IP address while in input functions.
+
+  2010-05-16: Simon Goldschmidt
+  * def.h: task #10391: Add preprocessor-macros for compile-time htonl
+    calculation (and use them throughout the stack where applicable)
+
+  2010-05-16: Simon Goldschmidt
+  * opt.h, memp_std.h, memp.c, ppp_oe.h/.c: PPPoE now uses its own MEMP pool
+    instead of the heap (moved struct pppoe_softc from ppp_oe.c to ppp_oe.h)
+
+  2010-05-16: Simon Goldschmidt
+  * opt.h, memp_std.h, dns.h/.c: DNS_LOCAL_HOSTLIST_IS_DYNAMIC uses its own
+    MEMP pool instead of the heap
+
+  2010-05-13: Simon Goldschmidt
+  * tcp.c, udp.c: task #6995: Implement SO_REUSEADDR (correctly), added
+    new option SO_REUSE_RXTOALL to pass received UDP broadcast/multicast
+    packets to more than one pcb.
+
+  2010-05-02: Simon Goldschmidt
+  * netbuf.h/.c, sockets.c, api_msg.c: use checksum-on-copy for sending
+    UDP data for LWIP_NETIF_TX_SINGLE_PBUF==1
+
+  2010-04-30: Simon Goldschmidt
+  * udp.h/.c, pbuf.h/.c: task #6849: added udp_send(_to/_if) functions that
+    take a precalculated checksum, added pbuf_fill_chksum() to copy data
+    into a pbuf and at the same time calculating the checksum for that data
+
+  2010-04-29: Simon Goldschmidt
+  * ip_addr.h, etharp.h/.c, autoip.c: Create overridable macros for copying
+    2-byte-aligned IP addresses and MAC addresses
+
+  2010-04-28: Patch by Bill Auerbach
+  * ip.c: Inline generating IP checksum to save a function call
+
+  2010-04-14: Simon Goldschmidt
+  * tcpip.h/.c, timers.c: Added an overridable define to get informed when the
+    tcpip_thread processes messages or timeouts to implement a watchdog.
+
+  2010-03-28: Simon Goldschmidt
+  * ip_frag.c: create a new (contiguous) PBUF_RAM for every outgoing
+    fragment if LWIP_NETIF_TX_SINGLE_PBUF==1
+
+  2010-03-27: Simon Goldschmidt
+  * etharp.c: Speedup TX by moving code from find_entry to etharp_output/
+    etharp_query to prevent unnecessary function calls (inspired by
+    patch #7135).
+
+  2010-03-20: Simon Goldschmidt
+  * opt.h, tcpip.c/.h: Added an option to disable tcpip_(un)timeout code
+    since the linker cannot do this automatically to save space.
+
+  2010-03-20: Simon Goldschmidt
+  * opt.h, etharp.c/.h: Added support for static ARP table entries
+
+  2010-03-14: Simon Goldschmidt
+  * tcp_impl.h, tcp_out.c, inet_chksum.h/.c: task #6849: Calculate checksum
+    when creating TCP segments, not when (re-)transmitting them.
+
+  2010-03-07: Simon Goldschmidt
+  * sockets.c: bug #28775 (select/event_callback: only check select_cb_list
+    on change) plus use SYS_LIGHTWEIGHT_PROT to protect the select code.
+    This should speed up receiving data on sockets as the select code in
+    event_callback is only executed when select is waiting.
+
+  2010-03-06: Simon Goldschmidt
+  * tcp_out.c: task #7013 (Create option to have all packets delivered to
+    netif->output in one piece): Always copy to try to create single pbufs
+    in tcp_write.
+
+  2010-03-06: Simon Goldschmidt
+  * api.h, api_lib.c, sockets.c: task #10167 (sockets: speed up TCP recv
+    by not allocating a netbuf): added function netconn_recv_tcp_pbuf()
+    for tcp netconns to receive pbufs, not netbufs; use that function
+    for tcp sockets.
+
+  2010-03-05: Jakob Ole Stoklundsen / Simon Goldschmidt
+  * opt.h, tcp.h, tcp_impl.h, tcp.c, tcp_in.c, tcp_out.c: task #7040:
+    Work on tcp_enqueue: Don't waste memory when chaining segments,
+    added option TCP_OVERSIZE to prevent creating many small pbufs when
+    calling tcp_write with many small blocks of data. Instead, pbufs are
+    allocated larger than needed and the space is used for later calls to
+    tcp_write.
+
+  2010-02-21: Simon Goldschmidt
+  * stats.c/.h: Added const char* name to mem- and memp-stats for easier
+    debugging.
+
+  2010-02-21: Simon Goldschmidt
+  * tcp.h (and usages), added tcp_impl.h: Splitted API and internal
+    implementation of tcp to make API usage cleare to application programmers
+
+  2010-02-14: Simon Goldschmidt/Stephane Lesage
+  * ip_addr.h: Improved some defines working on ip addresses, added faster
+    macro to copy addresses that cannot be NULL
+
+  2010-02-13: Simon Goldschmidt
+  * api.h, api_lib.c, api_msg.c, sockets.c: task #7865 (implement non-
+    blocking send operation)
+
+  2010-02-12: Simon Goldschmidt
+  * sockets.c/.h: Added a minimal version of posix fctl() to have a
+    standardised way to set O_NONBLOCK for nonblocking sockets.
+
+  2010-02-12: Simon Goldschmidt
+  * dhcp.c/.h, autoip.c/.h: task #10139 (Prefer statically allocated
+    memory): added autoip_set_struct() and dhcp_set_struct() to let autoip
+    and dhcp work with user-allocated structs instead of callin mem_malloc
+
+  2010-02-12: Simon Goldschmidt/Jeff Barber
+  * tcp.c/h: patch #6865 (SO_REUSEADDR for TCP): if pcb.so_options has
+    SOF_REUSEADDR set, allow binding to endpoint in TIME_WAIT
+
+  2010-02-12: Simon Goldschmidt
+  * sys layer: task #10139 (Prefer statically allocated memory): converted
+    mbox and semaphore functions to take pointers to sys_mbox_t/sys_sem_t;
+    converted sys_mbox_new/sys_sem_new to take pointers and return err_t;
+    task #7212: Add Mutex concept in sys_arch (define LWIP_COMPAT_MUTEX
+    to let sys.h use binary semaphores instead of mutexes - as before)
+
+  2010-02-09: Simon Goldschmidt (Simon Kallweit)
+  * timers.c/.h: Added function sys_restart_timeouts() from patch #7085
+    (Restart system timeout handling)
+
+  2010-02-09: Simon Goldschmidt
+  * netif.c/.h, removed loopif.c/.h: task #10153 (Integrate loopif into
+    netif.c) - loopif does not have to be created by the port any more,
+    just define LWIP_HAVE_LOOPIF to 1.
+
+  2010-02-08: Simon Goldschmidt
+  * inet.h, ip_addr.c/.h: Added reentrant versions of inet_ntoa/ipaddr_ntoa
+    inet_ntoa_r/ipaddr_ntoa_r
+
+  2010-02-08: Simon Goldschmidt
+  * netif.h: Added netif_s/get_igmp_mac_filter() macros
+
+  2010-02-05: Simon Goldschmidt
+  * netif.h: Added function-like macros to get/set the hostname on a netif
+
+  2010-02-04: Simon Goldschmidt
+  * nearly every file: Replaced struct ip_addr by typedef ip_addr_t to
+    make changing the actual implementation behind the typedef easier.
+
+  2010-02-01: Simon Goldschmidt
+  * opt.h, memp_std.h, dns.h, netdb.c, memp.c: Let netdb use a memp pool
+    for allocating memory when getaddrinfo() is called.
+
+  2010-01-31: Simon Goldschmidt
+  * dhcp.h, dhcp.c: Reworked the code that parses DHCP options: parse
+    them once instead of parsing for every option. This also removes
+    the need for mem_malloc from dhcp_recv and makes it possible to
+    correctly retrieve the BOOTP file.
+
+  2010-01-30: simon Goldschmidt
+  * sockets.c: Use SYS_LIGHTWEIGHT_PROT instead of a semaphore to protect
+    the sockets array.
+
+  2010-01-29: Simon Goldschmidt (patch by Laura Garrett)
+  * api.h, api_msg.c, sockets.c: Added except set support in select
+    (patch #6860)
+
+  2010-01-29: Simon Goldschmidt (patch by Laura Garrett)
+  * api.h, sockets.h, err.h, api_lib.c, api_msg.c, sockets.c, err.c:
+    Add non-blocking support for connect (partly from patch #6860),
+    plus many cleanups in socket & netconn API.
+
+  2010-01-27: Simon Goldschmidt
+  * opt.h, tcp.h, init.c, api_msg.c: Added TCP_SNDQUEUELOWAT corresponding
+    to TCP_SNDLOWAT and added tcp_sndqueuelen() - this fixes bug #28605
+
+  2010-01-26: Simon Goldschmidt
+  * snmp: Use memp pools for snmp instead of the heap; added 4 new pools.
+
+  2010-01-14: Simon Goldschmidt
+  * ppp.c/.h: Fixed bug #27856: PPP: Set netif link- and status-callback
+    by adding ppp_set_netif_statuscallback()/ppp_set_netif_linkcallback()
+
+  2010-01-13: Simon Goldschmidt
+  * mem.c: The heap now may be moved to user-defined memory by defining
+    LWIP_RAM_HEAP_POINTER as a void pointer to that memory's address
+    (patch #6966 and bug #26133)
+
+  2010-01-10: Simon Goldschmidt (Bill Auerbach)
+  * opt.h, memp.c: patch #6822 (Add option to place memory pools in
+    separate arrays)
+
+  2010-01-10: Simon Goldschmidt
+  * init.c, igmp.c: patch #6463 (IGMP - Adding Random Delay): added define
+    LWIP_RAND() for lwip-wide randomization (to be defined in cc.h)
+
+  2009-12-31: Simon Goldschmidt
+  * tcpip.c, init.c, memp.c, sys.c, memp_std.h, sys.h, tcpip.h
+    added timers.c/.h: Separated timer implementation from semaphore/mbox
+    implementation, moved timer implementation to timers.c/.h, timers are
+    now only called from tcpip_thread or by explicitly checking them.
+    (TASK#7235)
+
+  2009-12-27: Simon Goldschmidt
+  * opt.h, etharp.h/.c, init.c, tcpip.c: Added an additional option
+    LWIP_ETHERNET to support ethernet without ARP (necessary for pure PPPoE)
+
+
+  ++ Bugfixes:
+
+  2011-04-20: Simon Goldschmidt
+  * sys_arch.txt: sys_arch_timeouts() is not needed any more.
+
+  2011-04-13: Simon Goldschmidt
+  * tcp.c, udp.c: Fixed bug #33048 (Bad range for IP source port numbers) by
+    using ports in the IANA private/dynamic range (49152 through 65535).
+
+  2011-03-29: Simon Goldschmidt, patch by Emil Lhungdahl:
+  * etharp.h/.c: Fixed broken VLAN support.
+
+  2011-03-27: Simon Goldschmidt
+  * tcp.c: Fixed bug #32926 (TCP_RMV(&tcp_bound_pcbs) is called on unbound tcp
+    pcbs) by checking if the pcb was bound (local_port != 0).
+
+  2011-03-27: Simon Goldschmidt
+  * ppp.c: Fixed bug #32280 (ppp: a pbuf is freed twice)
+
+  2011-03-27: Simon Goldschmidt
+  * sockets.c: Fixed bug #32906: lwip_connect+lwip_send did not work for udp and
+    raw pcbs with LWIP_TCPIP_CORE_LOCKING==1.
+  
+  2011-03-27: Simon Goldschmidt
+  * tcp_out.c: Fixed bug #32820 (Outgoing TCP connections created before route
+    is present never times out) by starting retransmission timer before checking
+    route.
+
+  2011-03-22: Simon Goldschmidt
+  * ppp.c: Fixed bug #32648 (PPP code crashes when terminating a link) by only
+    calling sio_read_abort() if the file descriptor is valid.
+
+  2011-03-14: Simon Goldschmidt
+  * err.h/.c, sockets.c, api_msg.c: fixed bug #31748 (Calling non-blocking connect
+    more than once can render a socket useless) since it mainly involves changing
+    "FATAL" classification of error codes: ERR_USE and ERR_ISCONN just aren't fatal.
+
+  2011-03-13: Simon Goldschmidt
+  * sockets.c: fixed bug #32769 (ESHUTDOWN is linux-specific) by fixing
+    err_to_errno_table (ERR_CLSD: ENOTCONN instead of ESHUTDOWN), ERR_ISCONN:
+    use EALRADY instead of -1
+
+  2011-03-13: Simon Goldschmidt
+  * api_lib.c: netconn_accept: return ERR_ABRT instead of ERR_CLSD if the
+    connection has been aborted by err_tcp (since this is not a normal closing
+    procedure).
+
+  2011-03-13: Simon Goldschmidt
+  * tcp.c: tcp_bind: return ERR_VAL instead of ERR_ISCONN when trying to bind
+    with pcb->state != CLOSED
+
+  2011-02-17: Simon Goldschmidt
+  * rawapi.txt: Fixed bug #32561 tcp_poll argument definition out-of-order in
+    documentation
+
+  2011-02-17: Simon Goldschmidt
+  * many files: Added missing U/UL modifiers to fix 16-bit-arch portability.
+
+  2011-01-24: Simon Goldschmidt
+  * sockets.c: Fixed bug #31741: lwip_select seems to have threading problems
+
+  2010-12-02: Simon Goldschmidt
+  * err.h: Fixed ERR_IS_FATAL so that ERR_WOULDBLOCK is not fatal.
+
+  2010-11-23: Simon Goldschmidt
+  * api.h, api_lib.c, api_msg.c, sockets.c: netconn.recv_avail is only used for
+    LWIP_SO_RCVBUF and ioctl/FIONREAD.
+
+  2010-11-23: Simon Goldschmidt
+  * etharp.c: Fixed bug #31720: ARP-queueing: RFC 1122 recommends to queue at
+    least 1 packet -> ARP_QUEUEING==0 now queues the most recent packet.
+
+  2010-11-23: Simon Goldschmidt
+  * tcp_in.c: Fixed bug #30577: tcp_input: don't discard ACK-only packets after
+    refusing 'refused_data' again.
+  
+  2010-11-22: Simon Goldschmidt
+  * sockets.c: Fixed bug #31590: getsockopt(... SO_ERROR ...) gives EINPROGRESS
+    after a successful nonblocking connection.
+
+  2010-11-22: Simon Goldschmidt
+  * etharp.c: Fixed bug #31722: IP packets sent with an AutoIP source addr
+    must be sent link-local
+
+  2010-11-22: Simon Goldschmidt
+  * timers.c: patch #7329: tcp_timer_needed prototype was ifdef'ed out for
+    LWIP_TIMERS==0
+
+  2010-11-20: Simon Goldschmidt
+  * sockets.c: Fixed bug #31170: lwip_setsockopt() does not set socket number
+
+  2010-11-20: Simon Goldschmidt
+  * sockets.h: Fixed bug #31304: Changed SHUT_RD, SHUT_WR and SHUT_RDWR to
+    resemble other stacks.
+
+  2010-11-20: Simon Goldschmidt
+  * dns.c: Fixed bug #31535: TCP_SND_QUEUELEN must be at least 2 or else
+    no-copy TCP writes will never succeed.
+
+  2010-11-20: Simon Goldschmidt
+  * dns.c: Fixed bug #31701: Error return value from dns_gethostbyname() does
+    not match documentation: return ERR_ARG instead of ERR_VAL if not
+    initialized or wrong argument.
+
+  2010-10-20: Simon Goldschmidt
+  * sockets.h: Fixed bug #31385: sizeof(struct sockaddr) is 30 but should be 16
+
+  2010-10-05: Simon Goldschmidt
+  * dhcp.c: Once again fixed #30038: DHCP/AutoIP cooperation failed when
+    replugging the network cable after an AutoIP address was assigned.
+
+  2010-08-10: Simon Goldschmidt
+  * tcp.c: Fixed bug #30728: tcp_new_port() did not check listen pcbs
+
+  2010-08-03: Simon Goldschmidt
+  * udp.c, raw.c: Don't chain empty pbufs when sending them (fixes bug #30625)
+
+  2010-08-01: Simon Goldschmidt (patch by Greg Renda)
+  * ppp.c: Applied patch #7264 (PPP protocols are rejected incorrectly on big
+    endian architectures)
+  
+  2010-07-28: Simon Goldschmidt
+  * api_lib.c, api_msg.c, sockets.c, mib2.c: Fixed compilation with TCP or UDP
+    disabled.
+  
+  2010-07-27: Simon Goldschmidt
+  * tcp.c: Fixed bug #30565 (tcp_connect() check bound list): that check did no
+    harm but never did anything
+  
+  2010-07-21: Simon Goldschmidt
+  * ip.c: Fixed invalid fix for bug #30402 (CHECKSUM_GEN_IP_INLINE does not
+    add IP options)
+
+  2010-07-16: Kieran Mansley
+  * msg_in.c: Fixed SNMP ASN constant defines to not use ! operator 
+
+  2010-07-10: Simon Goldschmidt
+  * ip.c: Fixed bug #30402: CHECKSUM_GEN_IP_INLINE does not add IP options
+
+  2010-06-30: Simon Goldschmidt
+  * api_msg.c: fixed bug #30300 (shutdown parameter was not initialized in
+    netconn_delete)
+
+  2010-06-28: Kieran Mansley
+  * timers.c remove unportable printing of C function pointers
+
+  2010-06-24: Simon Goldschmidt
+  * init.c, timers.c/.h, opt.h, memp_std.h: From patch #7221: added flag
+    NO_SYS_NO_TIMERS to drop timer support for NO_SYS==1 for easier upgrading
+
+  2010-06-24: Simon Goldschmidt
+  * api(_lib).c/.h, api_msg.c/.h, sockets.c/.h: Fixed bug #10088: Correctly
+    implemented shutdown at socket level.
+
+  2010-06-21: Simon Goldschmidt
+  * pbuf.c/.h, ip_frag.c/.h, opt.h, memp_std.h: Fixed bug #29361 (ip_frag has
+    problems with zero-copy DMA MACs) by adding custom pbufs and implementing
+    custom pbufs that reference other (original) pbufs. Additionally set
+    IP_FRAG_USES_STATIC_BUF=0 as default to be on the safe side.
+
+  2010-06-15: Simon Goldschmidt
+  * dhcp.c: Fixed bug #29970: DHCP endian issue parsing option responses
+
+  2010-06-14: Simon Goldschmidt
+  * autoip.c: Fixed bug #30039: AutoIP does not reuse previous addresses
+
+  2010-06-12: Simon Goldschmidt
+  * dhcp.c: Fixed bug #30038: dhcp_network_changed doesn't reset AUTOIP coop
+    state
+
+  2010-05-17: Simon Goldschmidt
+  * netdb.c: Correctly NULL-terminate h_addr_list
+
+  2010-05-16: Simon Goldschmidt
+  * def.h/.c: changed the semantics of LWIP_PREFIX_BYTEORDER_FUNCS to prevent
+    "symbol already defined" i.e. when linking to winsock
+
+  2010-05-05: Simon Goldschmidt
+  * def.h, timers.c: Fixed bug #29769 (sys_check_timeouts: sys_now() may
+    overflow)
+
+  2010-04-21: Simon Goldschmidt
+  * api_msg.c: Fixed bug #29617 (sometime cause stall on delete listening
+    connection)
+
+  2010-03-28: Luca Ceresoli
+  * ip_addr.c/.h: patch #7143: Add a few missing const qualifiers
+
+  2010-03-27: Luca Ceresoli
+  * mib2.c: patch #7130: remove meaningless const qualifiers
+
+  2010-03-26: Simon Goldschmidt
+  * tcp_out.c: Make LWIP_NETIF_TX_SINGLE_PBUF work for TCP, too
+
+  2010-03-26: Simon Goldschmidt
+  * various files: Fixed compiling with different options disabled (TCP/UDP),
+    triggered by bug #29345; don't allocate acceptmbox if LWIP_TCP is disabled
+
+  2010-03-25: Simon Goldschmidt
+  * sockets.c: Fixed bug #29332: lwip_select() processes readset incorrectly
+
+  2010-03-25: Simon Goldschmidt
+  * tcp_in.c, test_tcp_oos.c: Fixed bug #29080: Correctly handle remote side
+    overrunning our rcv_wnd in ooseq case.
+
+  2010-03-22: Simon Goldschmidt
+  * tcp.c: tcp_listen() did not copy the pcb's prio.
+
+  2010-03-19: Simon Goldschmidt
+  * snmp_msg.c: Fixed bug #29256: SNMP Trap address was not correctly set
+
+  2010-03-14: Simon Goldschmidt
+  * opt.h, etharp.h: Fixed bug #29148 (Incorrect PBUF_POOL_BUFSIZE for ports
+    where ETH_PAD_SIZE > 0) by moving definition of ETH_PAD_SIZE to opt.h
+    and basing PBUF_LINK_HLEN on it.
+
+  2010-03-08: Simon Goldschmidt
+  * netif.c, ipv4/ip.c: task #10241 (AutoIP: don't break existing connections
+    when assiging routable address): when checking incoming packets and
+    aborting existing connection on address change, filter out link-local
+    addresses.
+
+  2010-03-06: Simon Goldschmidt
+  * sockets.c: Fixed LWIP_NETIF_TX_SINGLE_PBUF for LWIP_TCPIP_CORE_LOCKING
+
+  2010-03-06: Simon Goldschmidt
+  * ipv4/ip.c: Don't try to forward link-local addresses
+
+  2010-03-06: Simon Goldschmidt
+  * etharp.c: Fixed bug #29087: etharp: don't send packets for LinkLocal-
+    addresses to gw
+
+  2010-03-05: Simon Goldschmidt
+  * dhcp.c: Fixed bug #29072: Correctly set ciaddr based on message-type
+    and state.
+
+  2010-03-05: Simon Goldschmidt
+  * api_msg.c: Correctly set TCP_WRITE_FLAG_MORE when netconn_write is split
+    into multiple calls to tcp_write.    
+
+  2010-02-21: Simon Goldschmidt
+  * opt.h, mem.h, dns.c: task #10140: Remove DNS_USES_STATIC_BUF (keep
+    the implementation of DNS_USES_STATIC_BUF==1)
+
+  2010-02-20: Simon Goldschmidt
+  * tcp.h, tcp.c, tcp_in.c, tcp_out.c: Task #10088: Correctly implement
+    close() vs. shutdown(). Now the application does not get any more
+    recv callbacks after calling tcp_close(). Added tcp_shutdown().
+
+  2010-02-19: Simon Goldschmidt
+  * mem.c/.h, pbuf.c: Renamed mem_realloc() to mem_trim() to prevent
+    confusion with realloc()
+
+  2010-02-15: Simon Goldschmidt/Stephane Lesage
+  * netif.c/.h: Link status does not depend on LWIP_NETIF_LINK_CALLBACK
+    (fixes bug #28899)
+
+  2010-02-14: Simon Goldschmidt
+  * netif.c: Fixed bug #28877 (Duplicate ARP gratuitous packet with
+    LWIP_NETIF_LINK_CALLBACK set on) by only sending if both link- and
+    admin-status of a netif are up
+
+  2010-02-14: Simon Goldschmidt
+  * opt.h: Disable ETHARP_TRUST_IP_MAC by default since it slows down packet
+    reception and is not really necessary
+
+  2010-02-14: Simon Goldschmidt
+  * etharp.c/.h: Fixed ARP input processing: only add a new entry if a
+    request was directed as us (RFC 826, Packet Reception), otherwise
+    only update existing entries; internalized some functions
+
+  2010-02-14: Simon Goldschmidt
+  * netif.h, etharp.c, tcpip.c: Fixed bug #28183 (ARP and TCP/IP cannot be
+    disabled on netif used for PPPoE) by adding a new netif flag
+    (NETIF_FLAG_ETHERNET) that tells the stack the device is an ethernet
+    device but prevents usage of ARP (so that ethernet_input can be used
+    for PPPoE).
+
+  2010-02-12: Simon Goldschmidt
+  * netif.c: netif_set_link_up/down: only do something if the link state
+    actually changes
+
+  2010-02-12: Simon Goldschmidt/Stephane Lesage
+  * api_msg.c: Fixed bug #28865 (Cannot close socket/netconn in non-blocking
+    connect)
+
+  2010-02-12: Simon Goldschmidt
+  * mem.h: Fixed bug #28866 (mem_realloc function defined in mem.h)
+
+  2010-02-09: Simon Goldschmidt
+  * api_lib.c, api_msg.c, sockets.c, api.h, api_msg.h: Fixed bug #22110
+   (recv() makes receive window update for data that wasn't received by
+    application)
+
+  2010-02-09: Simon Goldschmidt/Stephane Lesage
+  * sockets.c: Fixed bug #28853 (lwip_recvfrom() returns 0 on receive time-out
+    or any netconn_recv() error)
+
+  2010-02-09: Simon Goldschmidt
+  * ppp.c: task #10154 (PPP: Update snmp in/out counters for tx/rx packets)
+
+  2010-02-09: Simon Goldschmidt
+  * netif.c: For loopback packets, adjust the stats- and snmp-counters
+    for the loopback netif.
+
+  2010-02-08: Simon Goldschmidt
+  * igmp.c/.h, ip.h: Moved most defines from igmp.h to igmp.c for clarity
+    since they are not used anywhere else.
+
+  2010-02-08: Simon Goldschmidt (Stéphane Lesage)
+  * igmp.c, igmp.h, stats.c, stats.h: Improved IGMP stats
+    (patch from bug #28798)
+
+  2010-02-08: Simon Goldschmidt (Stéphane Lesage)
+  * igmp.c: Fixed bug #28798 (Error in "Max Response Time" processing) and
+    another bug when LWIP_RAND() returns zero.
+
+  2010-02-04: Simon Goldschmidt
+  * nearly every file: Use macros defined in ip_addr.h (some of them new)
+    to work with IP addresses (preparation for bug #27352 - Change ip_addr
+    from struct to typedef (u32_t) - and better code).
+
+  2010-01-31: Simon Goldschmidt
+  * netif.c: Don't call the link-callback from netif_set_up/down() since
+    this invalidly retriggers DHCP.
+
+  2010-01-29: Simon Goldschmidt
+  * ip_addr.h, inet.h, def.h, inet.c, def.c, more: Cleanly separate the
+    portability file inet.h and its contents from the stack: moved htonX-
+    functions to def.h (and the new def.c - they are not ipv4 dependent),
+    let inet.h depend on ip_addr.h and not the other way round.
+    This fixes bug #28732.
+
+  2010-01-28: Kieran Mansley
+  * tcp.c: Ensure ssthresh >= 2*MSS
+
+  2010-01-27: Simon Goldschmidt
+  * tcp.h, tcp.c, tcp_in.c: Fixed bug #27871: Calling tcp_abort() in recv
+    callback can lead to accessing unallocated memory. As a consequence,
+    ERR_ABRT means the application has called tcp_abort()!
+
+  2010-01-25: Simon Goldschmidt
+  * snmp_structs.h, msg_in.c: Partly fixed bug #22070 (MIB_OBJECT_WRITE_ONLY
+    not implemented in SNMP): write-only or not-accessible are still
+    returned by getnext (though not by get)
+
+  2010-01-24: Simon Goldschmidt
+  * snmp: Renamed the private mib node from 'private' to 'mib_private' to
+    not use reserved C/C++ keywords
+
+  2010-01-23: Simon Goldschmidt
+  * sockets.c: Fixed bug #28716: select() returns 0 after waiting for less
+    than 1 ms
+
+  2010-01-21: Simon Goldschmidt
+  * tcp.c, api_msg.c: Fixed bug #28651 (tcp_connect: no callbacks called
+    if tcp_enqueue fails) both in raw- and netconn-API
+
+  2010-01-19: Simon Goldschmidt
+  * api_msg.c: Fixed bug #27316: netconn: Possible deadlock in err_tcp
+
+  2010-01-18: Iordan Neshev/Simon Goldschmidt
+  * src/netif/ppp: reorganised PPP sourcecode to 2.3.11 including some
+    bugfix backports from 2.4.x.
+
+  2010-01-18: Simon Goldschmidt
+  * mem.c: Fixed bug #28679: mem_realloc calculates mem_stats wrong
+
+  2010-01-17: Simon Goldschmidt
+  * api_lib.c, api_msg.c, (api_msg.h, api.h, sockets.c, tcpip.c):
+    task #10102: "netconn: clean up conn->err threading issues" by adding
+    error return value to struct api_msg_msg
+
+  2010-01-17: Simon Goldschmidt
+  * api.h, api_lib.c, sockets.c: Changed netconn_recv() and netconn_accept()
+    to return err_t (bugs #27709 and #28087)
+
+  2010-01-14: Simon Goldschmidt
+  * ...: Use typedef for function prototypes throughout the stack.
+
+  2010-01-13: Simon Goldschmidt
+  * api_msg.h/.c, api_lib.c: Fixed bug #26672 (close connection when receive
+    window = 0) by correctly draining recvmbox/acceptmbox
+
+  2010-01-11: Simon Goldschmidt
+  * pap.c: Fixed bug #13315 (PPP PAP authentication can result in
+    erroneous callbacks) by copying the code from recent pppd
+
+  2010-01-10: Simon Goldschmidt
+  * raw.c: Fixed bug #28506 (raw_bind should filter received packets)
+
+  2010-01-10: Simon Goldschmidt
+  * tcp.h/.c: bug #28127 (remove call to tcp_output() from tcp_ack(_now)())
+
+  2010-01-08: Simon Goldschmidt
+  * sockets.c: Fixed bug #28519 (lwip_recvfrom bug with len > 65535)
+
+  2010-01-08: Simon Goldschmidt
+  * dns.c: Copy hostname for DNS_LOCAL_HOSTLIST_IS_DYNAMIC==1 since string
+    passed to dns_local_addhost() might be volatile
+
+  2010-01-07: Simon Goldschmidt
+  * timers.c, tcp.h: Call tcp_timer_needed() with NO_SYS==1, too
+
+  2010-01-06: Simon Goldschmidt
+  * netdb.h: Fixed bug #28496: missing include guards in netdb.h
+
+  2009-12-31: Simon Goldschmidt
+  * many ppp files: Reorganised PPP source code from ucip structure to pppd
+    structure to easily compare our code against the pppd code (around v2.3.1)
+
+  2009-12-27: Simon Goldschmidt
+  * tcp_in.c: Another fix for bug #28241 (ooseq processing) and adapted
+    unit test
+
+
+(STABLE-1.3.2)
+
+  ++ New features:
+
+  2009-10-27 Simon Goldschmidt/Stephan Lesage
+  * netifapi.c/.h: Added netifapi_netif_set_addr()
+
+  2009-10-07 Simon Goldschmidt/Fabian Koch
+  * api_msg.c, netbuf.c/.h, opt.h: patch #6888: Patch for UDP Netbufs to
+    support dest-addr and dest-port (optional: LWIP_NETBUF_RECVINFO)
+
+  2009-08-26 Simon Goldschmidt/Simon Kallweit
+  * slipif.c/.h: bug #26397: SLIP polling support
+
+  2009-08-25 Simon Goldschmidt
+  * opt.h, etharp.h/.c: task #9033: Support IEEE 802.1q tagged frame (VLAN),
+    New configuration options ETHARP_SUPPORT_VLAN and ETHARP_VLAN_CHECK.
+
+  2009-08-25 Simon Goldschmidt
+  * ip_addr.h, netdb.c: patch #6900: added define ip_ntoa(struct ip_addr*)
+
+  2009-08-24 Jakob Stoklund Olesen
+  * autoip.c, dhcp.c, netif.c: patch #6725: Teach AutoIP and DHCP to respond
+    to netif_set_link_up().
+
+  2009-08-23 Simon Goldschmidt
+  * tcp.h/.c: Added function tcp_debug_state_str() to convert a tcp state
+    to a human-readable string.
+
+  ++ Bugfixes:
+
+  2009-12-24: Kieran Mansley
+  * tcp_in.c Apply patches from Oleg Tyshev to improve OOS processing
+    (BUG#28241)
+
+  2009-12-06: Simon Goldschmidt
+  * ppp.h/.c: Fixed bug #27079 (Yet another leak in PPP): outpacket_buf can
+    be statically allocated (like in ucip)
+
+  2009-12-04: Simon Goldschmidt (patch by Ioardan Neshev)
+  * pap.c: patch #6969: PPP: missing PAP authentication UNTIMEOUT
+
+  2009-12-03: Simon Goldschmidt
+  * tcp.h, tcp_in.c, tcp_out.c: Fixed bug #28106: dup ack for fast retransmit
+    could have non-zero length
+
+  2009-12-02: Simon Goldschmidt
+  * tcp_in.c: Fixed bug #27904: TCP sends too many ACKs: delay resetting
+    tcp_input_pcb until after calling the pcb's callbacks
+
+  2009-11-29: Simon Goldschmidt
+  * tcp_in.c: Fixed bug #28054: Two segments with FIN flag on the out-of-
+    sequence queue, also fixed PBUF_POOL leak in the out-of-sequence code
+
+  2009-11-29: Simon Goldschmidt
+  * pbuf.c: Fixed bug #28064: pbuf_alloc(PBUF_POOL) is not thread-safe by
+    queueing a call into tcpip_thread to free ooseq-bufs if the pool is empty
+
+  2009-11-26: Simon Goldschmidt
+  * tcp.h: Fixed bug #28098: Nagle can prevent fast retransmit from sending
+    segment
+
+  2009-11-26: Simon Goldschmidt
+  * tcp.h, sockets.c: Fixed bug #28099: API required to disable Nagle
+    algorithm at PCB level
+
+  2009-11-22: Simon Goldschmidt
+  * tcp_out.c: Fixed bug #27905: FIN isn't combined with data on unsent
+
+  2009-11-22: Simon Goldschmidt (suggested by Bill Auerbach)
+  * tcp.c: tcp_alloc: prevent increasing stats.err for MEMP_TCP_PCB when
+    reusing time-wait pcb
+
+  2009-11-20: Simon Goldschmidt (patch by Albert Bartel)
+  * sockets.c: Fixed bug #28062: Data received directly after accepting
+    does not wake up select
+
+  2009-11-11: Simon Goldschmidt
+  * netdb.h: Fixed bug #27994: incorrect define for freeaddrinfo(addrinfo)
+
+  2009-10-30: Simon Goldschmidt
+  * opt.h: Increased default value for TCP_MSS to 536, updated default
+    value for TCP_WND to 4*TCP_MSS to keep delayed ACK working.
+
+  2009-10-28: Kieran Mansley
+  * tcp_in.c, tcp_out.c, tcp.h: re-work the fast retransmission code
+    to follow algorithm from TCP/IP Illustrated
+
+  2009-10-27: Kieran Mansley
+  * tcp_in.c: fix BUG#27445: grow cwnd with every duplicate ACK
+
+  2009-10-25: Simon Goldschmidt
+  * tcp.h: bug-fix in the TCP_EVENT_RECV macro (has to call tcp_recved if
+    pcb->recv is NULL to keep rcv_wnd correct)
+
+  2009-10-25: Simon Goldschmidt
+  * tcp_in.c: Fixed bug #26251: RST process in TIME_WAIT TCP state
+
+  2009-10-23: Simon Goldschmidt (David Empson)
+  * tcp.c: Fixed bug #27783: Silly window avoidance for small window sizes
+
+  2009-10-21: Simon Goldschmidt
+  * tcp_in.c: Fixed bug #27215: TCP sent() callback gives leading and
+    trailing 1 byte len (SYN/FIN)
+
+  2009-10-21: Simon Goldschmidt
+  * tcp_out.c: Fixed bug #27315: zero window probe and FIN
+
+  2009-10-19: Simon Goldschmidt
+  * dhcp.c/.h: Minor code simplification (don't store received pbuf, change
+    conditional code to assert where applicable), check pbuf length before
+    testing for valid reply
+
+  2009-10-19: Simon Goldschmidt
+  * dhcp.c: Removed most calls to udp_connect since they aren't necessary
+    when using udp_sendto_if() - always stay connected to IP_ADDR_ANY.
+
+  2009-10-16: Simon Goldschmidt
+  * ip.c: Fixed bug #27390: Source IP check in ip_input() causes it to drop
+    valid DHCP packets -> allow 0.0.0.0 as source address when LWIP_DHCP is
+    enabled
+
+  2009-10-15: Simon Goldschmidt (Oleg Tyshev)
+  * tcp_in.c: Fixed bug #27329: dupacks by unidirectional data transmit
+
+  2009-10-15: Simon Goldschmidt
+  * api_lib.c: Fixed bug #27709: conn->err race condition on netconn_recv()
+    timeout
+
+  2009-10-15: Simon Goldschmidt
+  * autoip.c: Fixed bug #27704: autoip starts with wrong address
+    LWIP_AUTOIP_CREATE_SEED_ADDR() returned address in host byte order instead
+    of network byte order
+
+  2009-10-11 Simon Goldschmidt (Jörg Kesten)
+  * tcp_out.c: Fixed bug #27504: tcp_enqueue wrongly concatenates segments
+    which are not consecutive when retransmitting unacked segments
+
+  2009-10-09 Simon Goldschmidt
+  * opt.h: Fixed default values of some stats to only be enabled if used
+    Fixes bug #27338: sys_stats is defined when NO_SYS = 1
+
+  2009-08-30 Simon Goldschmidt
+  * ip.c: Fixed bug bug #27345: "ip_frag() does not use the LWIP_NETIF_LOOPBACK
+    function" by checking for loopback before calling ip_frag
+
+  2009-08-25 Simon Goldschmidt
+  * dhcp.c: fixed invalid dependency to etharp_query if DHCP_DOES_ARP_CHECK==0
+
+  2009-08-23 Simon Goldschmidt
+  * ppp.c: bug #27078: Possible memory leak in pppInit()
+
+  2009-08-23 Simon Goldschmidt
+  * netdb.c, dns.c: bug #26657: DNS, if host name is "localhost", result
+    is error.
+
+  2009-08-23 Simon Goldschmidt
+  * opt.h, init.c: bug #26649: TCP fails when TCP_MSS > TCP_SND_BUF
+    Fixed wrong parenthesis, added check in init.c
+
+  2009-08-23 Simon Goldschmidt
+  * ppp.c: bug #27266: wait-state debug message in pppMain occurs every ms
+
+  2009-08-23 Simon Goldschmidt
+  * many ppp files: bug #27267: Added include to string.h where needed
+
+  2009-08-23 Simon Goldschmidt
+  * tcp.h: patch #6843: tcp.h macro optimization patch (for little endian)
+
+
+(STABLE-1.3.1)
+
+  ++ New features:
+
+  2009-05-10 Simon Goldschmidt
+  * opt.h, sockets.c, pbuf.c, netbuf.h, pbuf.h: task #7013: Added option
+    LWIP_NETIF_TX_SINGLE_PBUF to try to create transmit packets from only
+    one pbuf to help MACs that don't support scatter-gather DMA.
+
+  2009-05-09 Simon Goldschmidt
+  * icmp.h, icmp.c: Shrinked ICMP code, added option to NOT check icoming
+    ECHO pbuf for size (just use it): LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
+
+  2009-05-05 Simon Goldschmidt, Jakob Stoklund Olesen
+  * ip.h, ip.c: Added ip_current_netif() & ip_current_header() to receive
+    extended info about the currently received packet.
+
+  2009-04-27 Simon Goldschmidt
+  * sys.h: Made SYS_LIGHTWEIGHT_PROT and sys_now() work with NO_SYS=1
+
+  2009-04-25 Simon Goldschmidt
+  * mem.c, opt.h: Added option MEM_USE_POOLS_TRY_BIGGER_POOL to try the next
+    bigger malloc pool if one is empty (only usable with MEM_USE_POOLS).
+
+  2009-04-21 Simon Goldschmidt
+  * dns.c, init.c, dns.h, opt.h: task #7507, patch #6786: DNS supports static
+    hosts table. New configuration options DNS_LOCAL_HOSTLIST and
+    DNS_LOCAL_HOSTLIST_IS_DYNAMIC. Also, DNS_LOOKUP_LOCAL_EXTERN() can be defined
+    as an external function for lookup.
+
+  2009-04-15 Simon Goldschmidt
+  * dhcp.c: patch #6763: Global DHCP XID can be redefined to something more unique
+
+  2009-03-31 Kieran Mansley
+  * tcp.c, tcp_out.c, tcp_in.c, sys.h, tcp.h, opts.h: add support for
+    TCP timestamp options, off by default.  Rework tcp_enqueue() to
+    take option flags rather than specified option data
+
+  2009-02-18 Simon Goldschmidt
+  * cc.h: Added printf formatter for size_t: SZT_F
+
+  2009-02-16 Simon Goldschmidt (patch by Rishi Khan)
+  * icmp.c, opt.h: patch #6539: (configurable) response to broadcast- and multicast
+    pings
+
+  2009-02-12 Simon Goldschmidt
+  * init.h: Added LWIP_VERSION to get the current version of the stack
+
+  2009-02-11 Simon Goldschmidt (suggested by Gottfried Spitaler)
+  * opt.h, memp.h/.c: added MEMP_MEM_MALLOC to use mem_malloc/mem_free instead
+    of the pool allocator (can save code size with MEM_LIBC_MALLOC if libc-malloc
+    is otherwise used)
+
+  2009-01-28 Jonathan Larmour (suggested by Bill Bauerbach)
+  * ipv4/inet_chksum.c, ipv4/lwip/inet_chksum.h: inet_chksum_pseudo_partial()
+  is only used by UDPLITE at present, so conditionalise it.
+
+  2008-12-03 Simon Goldschmidt (base on patch from Luca Ceresoli)
+  * autoip.c: checked in (slightly modified) patch #6683: Customizable AUTOIP
+    "seed" address. This should reduce AUTOIP conflicts if
+    LWIP_AUTOIP_CREATE_SEED_ADDR is overridden.
+
+  2008-10-02 Jonathan Larmour and Rishi Khan
+  * sockets.c (lwip_accept): Return EWOULDBLOCK if would block on non-blocking
+    socket.
+
+  2008-06-30 Simon Goldschmidt
+  * mem.c, opt.h, stats.h: fixed bug #21433: Calling mem_free/pbuf_free from
+    interrupt context isn't safe: LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT allows
+    mem_free to run between mem_malloc iterations. Added illegal counter for
+    mem stats.
+
+  2008-06-27 Simon Goldschmidt
+  * stats.h/.c, some other files: patch #6483: stats module improvement:
+    Added defines to display each module's statistic individually, added stats
+    defines for MEM, MEMP and SYS modules, removed (unused) rexmit counter.
+
+  2008-06-17 Simon Goldschmidt
+  * err.h: patch #6459: Made err_t overridable to use a more efficient type
+    (define LWIP_ERR_T in cc.h)
+
+  2008-06-17 Simon Goldschmidt
+  * slipif.c: patch #6480: Added a configuration option for slipif for symmetry
+    to loopif
+
+  2008-06-17 Simon Goldschmidt (patch by Luca Ceresoli)
+  * netif.c, loopif.c, ip.c, netif.h, loopif.h, opt.h: Checked in slightly
+    modified version of patch # 6370: Moved loopif code to netif.c so that
+    loopback traffic is supported on all netifs (all local IPs).
+    Added option to limit loopback packets for each netifs.
+
+
+  ++ Bugfixes:
+  2009-08-12 Kieran Mansley
+  * tcp_in.c, tcp.c: Fix bug #27209: handle trimming of segments when
+    out of window or out of order properly
+
+  2009-08-12 Kieran Mansley
+  * tcp_in.c: Fix bug #27199: use snd_wl2 instead of snd_wl1
+
+  2009-07-28 Simon Goldschmidt
+  * mem.h: Fixed bug #27105: "realloc() cannot replace mem_realloc()"s
+
+  2009-07-27 Kieran Mansley
+  * api.h api_msg.h netdb.h sockets.h: add missing #include directives
+
+  2009-07-09 Kieran Mansley
+  * api_msg.c, sockets.c, api.h: BUG23240 use signed counters for
+    recv_avail and don't increment counters until message successfully
+    sent to mbox
+
+  2009-06-25 Kieran Mansley
+  * api_msg.c api.h: BUG26722: initialise netconn write variables 
+    in netconn_alloc
+
+  2009-06-25 Kieran Mansley
+  * tcp.h: BUG26879: set ret value in TCP_EVENT macros when function is not set
+
+  2009-06-25 Kieran Mansley
+  * tcp.c, tcp_in.c, tcp_out.c, tcp.h: BUG26301 and BUG26267: correct
+    simultaneous close behaviour, and make snd_nxt have the same meaning 
+    as in the RFCs.
+
+  2009-05-12 Simon Goldschmidt
+  * etharp.h, etharp.c, netif.c: fixed bug #26507: "Gratuitous ARP depends on
+    arp_table / uses etharp_query" by adding etharp_gratuitous()
+
+  2009-05-12 Simon Goldschmidt
+  * ip.h, ip.c, igmp.c: bug #26487: Added ip_output_if_opt that can add IP options
+    to the IP header (used by igmp_ip_output_if)
+
+  2009-05-06 Simon Goldschmidt
+  * inet_chksum.c: On little endian architectures, use LWIP_PLATFORM_HTONS (if
+    defined) for SWAP_BYTES_IN_WORD to speed up checksumming.
+
+  2009-05-05 Simon Goldschmidt
+  * sockets.c: bug #26405: Prematurely released semaphore causes lwip_select()
+    to crash
+
+  2009-05-04 Simon Goldschmidt
+  * init.c: snmp was not initialized in lwip_init()
+
+  2009-05-04 Frédéric Bernon
+  * dhcp.c, netbios.c: Changes if IP_SOF_BROADCAST is enabled.
+
+  2009-05-03 Simon Goldschmidt
+  * tcp.h: bug #26349: Nagle algorithm doesn't send although segment is full
+    (and unsent->next == NULL)
+
+  2009-05-02 Simon Goldschmidt
+  * tcpip.h, tcpip.c: fixed tcpip_untimeout (does not need the time, broken after
+    1.3.0 in CVS only) - fixes compilation of ppp_oe.c
+
+  2009-05-02 Simon Goldschmidt
+  * msg_in.c: fixed bug #25636: SNMPSET value is ignored for integer fields
+
+  2009-05-01 Simon Goldschmidt
+  * pap.c: bug #21680: PPP upap_rauthnak() drops legal NAK packets
+
+  2009-05-01 Simon Goldschmidt
+  * ppp.c: bug #24228: Memory corruption with PPP and DHCP
+
+  2009-04-29 Frédéric Bernon
+  * raw.c, udp.c, init.c, opt.h, ip.h, sockets.h: bug #26309: Implement the
+    SO(F)_BROADCAST filter for all API layers. Avoid the unindented reception
+    of broadcast packets even when this option wasn't set. Port maintainers
+    which want to enable this filter have to set IP_SOF_BROADCAST=1 in opt.h.
+    If you want this option also filter broadcast on recv operations, you also
+    have to set IP_SOF_BROADCAST_RECV=1 in opt.h.
+
+  2009-04-28 Simon Goldschmidt, Jakob Stoklund Olesen
+  * dhcp.c: patch #6721, bugs #25575, #25576: Some small fixes to DHCP and
+    DHCP/AUTOIP cooperation
+
+  2009-04-25 Simon Goldschmidt, Oleg Tyshev
+  * tcp_out.c: bug #24212: Deadlocked tcp_retransmit due to exceeded pcb->cwnd
+    Fixed by sorting the unsent and unacked queues (segments are inserted at the
+    right place in tcp_output and tcp_rexmit).
+
+  2009-04-25 Simon Goldschmidt
+  * memp.c, mem.c, memp.h, mem_std.h: bug #26213 "Problem with memory allocation
+    when debugging": memp_sizes contained the wrong sizes (including sanity
+    regions); memp pools for MEM_USE_POOLS were too small
+
+  2009-04-24 Simon Goldschmidt, Frédéric Bernon
+  * inet.c: patch #6765: Fix a small problem with the last changes (incorrect
+    behavior, with with ip address string not ended by a '\0', a space or a
+    end of line)
+
+  2009-04-19 Simon Goldschmidt
+  * rawapi.txt: Fixed bug #26069: Corrected documentation: if tcp_connect fails,
+    pcb->err is called, not pcb->connected (with an error code).
+
+  2009-04-19 Simon Goldschmidt
+  * tcp_out.c: Fixed bug #26236: "TCP options (timestamp) don't work with
+    no-copy-tcpwrite": deallocate option data, only concat segments with same flags
+
+  2009-04-19 Simon Goldschmidt
+  * tcp_out.c: Fixed bug #25094: "Zero-length pbuf" (options are now allocated
+    in the header pbuf, not the data pbuf)
+
+  2009-04-18 Simon Goldschmidt
+  * api_msg.c: fixed bug #25695: Segmentation fault in do_writemore()
+
+  2009-04-15 Simon Goldschmidt
+  * sockets.c: tried to fix bug #23559: lwip_recvfrom problem with tcp
+
+  2009-04-15 Simon Goldschmidt
+  * dhcp.c: task #9192: mem_free of dhcp->options_in and dhcp->msg_in
+
+  2009-04-15 Simon Goldschmidt
+  * ip.c, ip6.c, tcp_out.c, ip.h: patch #6808: Add a utility function
+    ip_hinted_output() (for smaller code mainly)
+
+  2009-04-15 Simon Goldschmidt
+  * inet.c: patch #6765: Supporting new line characters in inet_aton()
+
+  2009-04-15 Simon Goldschmidt
+  * dhcp.c: patch #6764: DHCP rebind and renew did not send hostnam option;
+    Converted constant OPTION_MAX_MSG_SIZE to netif->mtu, check if netif->mtu
+    is big enough in dhcp_start
+
+  2009-04-15 Simon Goldschmidt
+  * netbuf.c: bug #26027: netbuf_chain resulted in pbuf memory leak
+
+  2009-04-15 Simon Goldschmidt
+  * sockets.c, ppp.c: bug #25763: corrected 4 occurrences of SMEMCPY to MEMCPY
+
+  2009-04-15 Simon Goldschmidt
+  * sockets.c: bug #26121: set_errno can be overridden
+
+  2009-04-09 Kieran Mansley (patch from Luca Ceresoli <lucaceresoli>)
+  * init.c, opt.h: Patch#6774 TCP_QUEUE_OOSEQ breaks compilation when
+    LWIP_TCP==0
+
+  2009-04-09 Kieran Mansley (patch from Roy Lee <roylee17>)
+  * tcp.h: Patch#6802 Add do-while-clauses to those function like
+    macros in tcp.h
+
+  2009-03-31 Kieran Mansley
+  * tcp.c, tcp_in.c, tcp_out.c, tcp.h, opt.h: Rework the way window
+    updates are calculated and sent (BUG20515)
+
+  * tcp_in.c: cope with SYN packets received during established states,
+    and retransmission of initial SYN.
+
+  * tcp_out.c: set push bit correctly when tcp segments are merged
+
+  2009-03-27 Kieran Mansley
+  * tcp_out.c set window correctly on probes (correcting change made
+    yesterday)
+
+  2009-03-26 Kieran Mansley
+  * tcp.c, tcp_in.c, tcp.h: add tcp_abandon() to cope with dropping
+    connections where no reset required (bug #25622)
+
+  * tcp_out.c: set TCP_ACK flag on keepalive and zero window probes 
+    (bug #20779)
+
+  2009-02-18 Simon Goldschmidt (Jonathan Larmour and Bill Auerbach)
+  * ip_frag.c: patch #6528: the buffer used for IP_FRAG_USES_STATIC_BUF could be
+    too small depending on MEM_ALIGNMENT
+
+  2009-02-16 Simon Goldschmidt
+  * sockets.h/.c, api_*.h/.c: fixed arguments of socket functions to match the standard;
+    converted size argument of netconn_write to 'size_t'
+
+  2009-02-16 Simon Goldschmidt
+  * tcp.h, tcp.c: fixed bug #24440: TCP connection close problem on 64-bit host
+    by moving accept callback function pointer to TCP_PCB_COMMON
+
+  2009-02-12 Simon Goldschmidt
+  * dhcp.c: fixed bug #25345 (DHCPDECLINE is sent with "Maximum message size"
+    option)
+
+  2009-02-11 Simon Goldschmidt
+  * dhcp.c: fixed bug #24480 (releasing old udp_pdb and pbuf in dhcp_start)
+
+  2009-02-11 Simon Goldschmidt
+  * opt.h, api_msg.c: added configurable default valud for netconn->recv_bufsize:
+    RECV_BUFSIZE_DEFAULT (fixes bug #23726: pbuf pool exhaustion on slow recv())
+
+  2009-02-10 Simon Goldschmidt
+  * tcp.c: fixed bug #25467: Listen backlog is not reset on timeout in SYN_RCVD:
+    Accepts_pending is decrease on a corresponding listen pcb when a connection
+    in state SYN_RCVD is close.
+
+  2009-01-28 Jonathan Larmour
+  * pbuf.c: reclaim pbufs from TCP out-of-sequence segments if we run
+    out of pool pbufs.
+
+  2008-12-19 Simon Goldschmidt
+  * many files: patch #6699: fixed some warnings on platform where sizeof(int) == 2 
+
+  2008-12-10 Tamas Somogyi, Frédéric Bernon
+  * sockets.c: fixed bug #25051: lwip_recvfrom problem with udp: fromaddr and
+    port uses deleted netbuf.
+
+  2008-10-18 Simon Goldschmidt
+  * tcp_in.c: fixed bug ##24596: Vulnerability on faulty TCP options length
+    in tcp_parseopt
+
+  2008-10-15 Simon Goldschmidt
+  * ip_frag.c: fixed bug #24517: IP reassembly crashes on unaligned IP headers
+    by packing the struct ip_reass_helper.
+
+  2008-10-03 David Woodhouse, Jonathan Larmour
+  * etharp.c (etharp_arp_input): Fix type aliasing problem copying ip address.
+
+  2008-10-02 Jonathan Larmour
+  * dns.c: Hard-code structure sizes, to avoid issues on some compilers where
+    padding is included.
+
+  2008-09-30 Jonathan Larmour
+  * sockets.c (lwip_accept): check addr isn't NULL. If it's valid, do an
+    assertion check that addrlen isn't NULL.
+
+  2008-09-30 Jonathan Larmour
+  * tcp.c: Fix bug #24227, wrong error message in tcp_bind.
+
+  2008-08-26 Simon Goldschmidt
+  * inet.h, ip_addr.h: fixed bug #24132: Cross-dependency between ip_addr.h and
+    inet.h -> moved declaration of struct in_addr from ip_addr.h to inet.h
+
+  2008-08-14 Simon Goldschmidt
+  * api_msg.c: fixed bug #23847: do_close_internal references freed memory (when
+    tcp_close returns != ERR_OK)
+
+  2008-07-08 Frédéric Bernon
+  * stats.h: Fix some build bugs introduced with patch #6483 (missing some parameters
+    in macros, mainly if MEM_STATS=0 and MEMP_STATS=0).
+
+  2008-06-24 Jonathan Larmour
+  * tcp_in.c: Fix for bug #23693 as suggested by Art R. Ensure cseg is unused
+    if tcp_seg_copy fails.
+
+  2008-06-17 Simon Goldschmidt
+  * inet_chksum.c: Checked in some ideas of patch #6460 (loop optimizations)
+    and created defines for swapping bytes and folding u32 to u16.
+
+  2008-05-30 Kieran Mansley
+  * tcp_in.c Remove redundant "if" statement, and use real rcv_wnd
+    rather than rcv_ann_wnd when deciding if packets are in-window.
+    Contributed by <arasmussen@xxxxxxxxxxxxxxxxxxxxxxxxxxx>
+
+  2008-05-30 Kieran Mansley
+  * mem.h: Fix BUG#23254.  Change macro definition of mem_* to allow
+    passing as function pointers when MEM_LIBC_MALLOC is defined.
+
+  2008-05-09 Jonathan Larmour
+  * err.h, err.c, sockets.c: Fix bug #23119: Reorder timeout error code to
+    stop it being treated as a fatal error.
+
+  2008-04-15 Simon Goldschmidt
+  * dhcp.c: fixed bug #22804: dhcp_stop doesn't clear NETIF_FLAG_DHCP
+    (flag now cleared)
+
+  2008-03-27 Simon Goldschmidt
+  * mem.c, tcpip.c, tcpip.h, opt.h: fixed bug #21433 (Calling mem_free/pbuf_free
+    from interrupt context isn't safe): set LWIP_USE_HEAP_FROM_INTERRUPT to 1
+    in lwipopts.h or use pbuf_free_callback(p)/mem_free_callback(m) to free pbufs
+    or heap memory from interrupt context
+
+  2008-03-26 Simon Goldschmidt
+  * tcp_in.c, tcp.c: fixed bug #22249: division by zero could occur if a remote
+    host sent a zero mss as TCP option.
+
+
+(STABLE-1.3.0)
+
+  ++ New features:
+
+  2008-03-10 Jonathan Larmour
+  * inet_chksum.c: Allow choice of one of the sample algorithms to be
+    made from lwipopts.h. Fix comment on how to override LWIP_CHKSUM.
+
+  2008-01-22 Frédéric Bernon
+  * tcp.c, tcp_in.c, tcp.h, opt.h: Rename LWIP_CALCULATE_EFF_SEND_MSS in 
+    TCP_CALCULATE_EFF_SEND_MSS to have coherent TCP options names.
+
+  2008-01-14 Frédéric Bernon
+  * rawapi.txt, api_msg.c, tcp.c, tcp_in.c, tcp.h: changes for task #7675 "Enable
+    to refuse data on a TCP_EVENT_RECV call". Important, behavior changes for the
+    tcp_recv callback (see rawapi.txt).
+
+  2008-01-14 Frédéric Bernon, Marc Chaland
+  * ip.c: Integrate patch #6369" ip_input : checking before realloc".
+  
+  2008-01-12 Frédéric Bernon
+  * tcpip.h, tcpip.c, api.h, api_lib.c, api_msg.c, sockets.c: replace the field
+    netconn::sem per netconn::op_completed like suggested for the task #7490
+    "Add return value to sys_mbox_post".
+
+  2008-01-12 Frédéric Bernon
+  * api_msg.c, opt.h: replace DEFAULT_RECVMBOX_SIZE per DEFAULT_TCP_RECVMBOX_SIZE,
+    DEFAULT_UDP_RECVMBOX_SIZE and DEFAULT_RAW_RECVMBOX_SIZE (to optimize queues
+    sizes), like suggested for the task #7490 "Add return value to sys_mbox_post".
+
+  2008-01-10 Frédéric Bernon
+  * tcpip.h, tcpip.c: add tcpip_callback_with_block function for the task #7490
+    "Add return value to sys_mbox_post". tcpip_callback is always defined as
+    "blocking" ("block" parameter = 1).
+
+  2008-01-10 Frédéric Bernon
+  * tcpip.h, tcpip.c, api.h, api_lib.c, api_msg.c, sockets.c: replace the field
+    netconn::mbox (sys_mbox_t) per netconn::sem (sys_sem_t) for the task #7490
+    "Add return value to sys_mbox_post".
+
+  2008-01-05 Frédéric Bernon
+  * sys_arch.txt, api.h, api_lib.c, api_msg.h, api_msg.c, tcpip.c, sys.h, opt.h:
+    Introduce changes for task #7490 "Add return value to sys_mbox_post" with some
+    modifications in the sys_mbox api: sys_mbox_new take a "size" parameters which
+    indicate the number of pointers query by the mailbox. There is three defines
+    in opt.h to indicate sizes for tcpip::mbox, netconn::recvmbox, and for the 
+    netconn::acceptmbox. Port maintainers, you can decide to just add this new 
+    parameter in your implementation, but to ignore it to keep the previous behavior.
+    The new sys_mbox_trypost function return a value to know if the mailbox is
+    full or if the message is posted. Take a look to sys_arch.txt for more details.
+    This new function is used in tcpip_input (so, can be called in an interrupt
+    context since the function is not blocking), and in recv_udp and recv_raw.
+
+  2008-01-04 Frédéric Bernon, Simon Goldschmidt, Jonathan Larmour
+  * rawapi.txt, api.h, api_lib.c, api_msg.h, api_msg.c, sockets.c, tcp.h, tcp.c,
+    tcp_in.c, init.c, opt.h: rename backlog options with TCP_ prefix, limit the
+    "backlog" parameter in an u8_t, 0 is interpreted as "smallest queue", add
+    documentation in the rawapi.txt file.
+
+  2007-12-31 Kieran Mansley (based on patch from Per-Henrik Lundbolm)
+  * tcp.c, tcp_in.c, tcp_out.c, tcp.h: Add TCP persist timer
+
+  2007-12-31 Frédéric Bernon, Luca Ceresoli
+  * autoip.c, etharp.c: ip_addr.h: Integrate patch #6348: "Broadcast ARP packets
+    in autoip". The change in etharp_raw could be removed, since all calls to
+    etharp_raw use ethbroadcast for the "ethdst_addr" parameter. But it could be
+    wrong in the future.
+
+  2007-12-30 Frédéric Bernon, Tom Evans
+  * ip.c: Fix bug #21846 "LwIP doesn't appear to perform any IP Source Address
+    Filtering" reported by Tom Evans.
+
+  2007-12-21 Frédéric Bernon, Simon Goldschmidt, Jonathan Larmour
+  * tcp.h, opt.h, api.h, api_msg.h, tcp.c, tcp_in.c, api_lib.c, api_msg.c,
+    sockets.c, init.c: task #7252: Implement TCP listen backlog: Warning: raw API
+    applications have to call 'tcp_accepted(pcb)' in their accept callback to
+    keep accepting new connections.
+
+  2007-12-13 Frédéric Bernon
+  * api_msg.c, err.h, err.c, sockets.c, dns.c, dns.h: replace "enum dns_result"
+    by err_t type. Add a new err_t code "ERR_INPROGRESS".
+
+  2007-12-12 Frédéric Bernon
+  * dns.h, dns.c, opt.h: move DNS options to the "right" place. Most visibles
+    are the one which have ram usage.
+
+  2007-12-05 Frédéric Bernon
+  * netdb.c: add a LWIP_DNS_API_HOSTENT_STORAGE option to decide to use a static
+    set of variables (=0) or a local one (=1). In this last case, your port should
+    provide a function "struct hostent* sys_thread_hostent( struct hostent* h)"
+    which have to do a copy of "h" and return a pointer ont the "per-thread" copy.
+
+  2007-12-03 Simon Goldschmidt
+  * ip.c: ip_input: check if a packet is for inp first before checking all other
+    netifs on netif_list (speeds up packet receiving in most cases)
+
+  2007-11-30 Simon Goldschmidt
+  * udp.c, raw.c: task #7497: Sort lists (pcb, netif, ...) for faster access
+    UDP: move a (connected) pcb selected for input to the front of the list of
+    pcbs so that it is found faster next time. Same for RAW pcbs that have eaten
+    a packet.
+
+  2007-11-28 Simon Goldschmidt
+  * etharp.c, stats.c, stats.h, opt.h: Introduced ETHARP_STATS
+
+  2007-11-25 Simon Goldschmidt
+  * dhcp.c: dhcp_unfold_reply() uses pbuf_copy_partial instead of its own copy
+    algorithm.
+
+  2007-11-24 Simon Goldschmidt
+  * netdb.h, netdb.c, sockets.h/.c: Moved lwip_gethostbyname from sockets.c
+    to the new file netdb.c; included lwip_getaddrinfo.
+
+  2007-11-21 Simon Goldschmidt
+  * tcp.h, opt.h, tcp.c, tcp_in.c: implemented calculating the effective send-mss
+    based on the MTU of the netif used to send. Enabled by default. Disable by
+    setting LWIP_CALCULATE_EFF_SEND_MSS to 0. This fixes bug #21492.
+
+  2007-11-19 Frédéric Bernon
+  * api_msg.c, dns.h, dns.c: Implement DNS_DOES_NAME_CHECK option (check if name
+    received match the name query), implement DNS_USES_STATIC_BUF (the place where
+    copy dns payload to parse the response), return an error if there is no place
+    for a new query, and fix some minor problems.
+
+  2007-11-16 Simon Goldschmidt
+  * new files: ipv4/inet.c, ipv4/inet_chksum.c, ipv6/inet6.c
+    removed files: core/inet.c, core/inet6.c
+    Moved inet files into ipv4/ipv6 directory; splitted inet.c/inet.h into
+    inet and chksum part; changed includes in all lwIP files as appropriate
+
+  2007-11-16 Simon Goldschmidt
+  * api.h, api_msg.h, api_lib.c, api_msg.c, socket.h, socket.c: Added sequential
+    dns resolver function for netconn api (netconn_gethostbyname) and socket api
+    (gethostbyname/gethostbyname_r).
+
+  2007-11-15 Jim Pettinato, Frédéric Bernon
+  * opt.h, init.c, tcpip.c, dhcp.c, dns.h, dns.c: add DNS client for simple name
+    requests with RAW api interface. Initialization is done in lwip_init() with
+    build time options. DNS timer is added in tcpip_thread context. DHCP can set
+    DNS server ip addresses when options are received. You need to set LWIP_DNS=1
+    in your lwipopts.h file (LWIP_DNS=0 in opt.h). DNS_DEBUG can be set to get
+    some traces with LWIP_DEBUGF. Sanity check have been added. There is a "todo"
+    list with points to improve.
+
+  2007-11-06 Simon Goldschmidt
+  * opt.h, mib2.c: Patch #6215: added ifAdminStatus write support (if explicitly
+    enabled by defining SNMP_SAFE_REQUESTS to 0); added code to check link status
+    for ifOperStatus if LWIP_NETIF_LINK_CALLBACK is defined.
+
+  2007-11-06 Simon Goldschmidt
+  * api.h, api_msg.h and dependent files: Task #7410: Removed the need to include
+    core header files in api.h (ip/tcp/udp/raw.h) to hide the internal
+    implementation from netconn api applications.
+
+  2007-11-03 Frédéric Bernon
+  * api.h, api_lib.c, api_msg.c, sockets.c, opt.h: add SO_RCVBUF option for UDP &
+    RAW netconn. You need to set LWIP_SO_RCVBUF=1 in your lwipopts.h (it's disabled
+    by default). Netconn API users can use the netconn_recv_bufsize macro to access
+    it. This is a first release which have to be improve for TCP. Note it used the
+    netconn::recv_avail which need to be more "thread-safe" (note there is already
+    the problem for FIONREAD with lwip_ioctl/ioctlsocket).
+
+  2007-11-01 Frédéric Bernon, Marc Chaland
+  * sockets.h, sockets.c, api.h, api_lib.c, api_msg.h, api_msg.c, tcp.h, tcp_out.c:
+    Integrate "patch #6250 : MSG_MORE flag for send". MSG_MORE is used at socket api
+    layer, NETCONN_MORE at netconn api layer, and TCP_WRITE_FLAG_MORE at raw api
+    layer. This option enable to delayed TCP PUSH flag on multiple "write" calls.
+    Note that previous "copy" parameter for "write" APIs is now called "apiflags".
+
+  2007-10-24 Frédéric Bernon
+  * api.h, api_lib.c, api_msg.c: Add macro API_EVENT in the same spirit than 
+    TCP_EVENT_xxx macros to get a code more readable. It could also help to remove
+    some code (like we have talk in "patch #5919 : Create compile switch to remove
+    select code"), but it could be done later.
+
+  2007-10-08 Simon Goldschmidt
+  * many files: Changed initialization: many init functions are not needed any
+    more since we now rely on the compiler initializing global and static
+    variables to zero!
+
+  2007-10-06 Simon Goldschmidt
+  * ip_frag.c, memp.c, mib2.c, ip_frag.h, memp_std.h, opt.h: Changed IP_REASSEMBLY
+    to enqueue the received pbufs so that multiple packets can be reassembled
+    simultaneously and no static reassembly buffer is needed.
+
+  2007-10-05 Simon Goldschmidt
+  * tcpip.c, etharp.h, etharp.c: moved ethernet_input from tcpip.c to etharp.c so
+    all netifs (or ports) can use it.
+
+  2007-10-05 Frédéric Bernon
+  * netifapi.h, netifapi.c: add function netifapi_netif_set_default. Change the 
+    common function to reduce a little bit the footprint (for all functions using
+    only the "netif" parameter).
+
+  2007-10-03 Frédéric Bernon
+  * netifapi.h, netifapi.c: add functions netifapi_netif_set_up, netifapi_netif_set_down,
+    netifapi_autoip_start and netifapi_autoip_stop. Use a common function to reduce
+    a little bit the footprint (for all functions using only the "netif" parameter).
+
+  2007-09-15 Frédéric Bernon
+  * udp.h, udp.c, sockets.c: Changes for "#20503 IGMP Improvement". Add IP_MULTICAST_IF
+    option in socket API, and a new field "multicast_ip" in "struct udp_pcb" (for
+    netconn and raw API users), only if LWIP_IGMP=1. Add getsockopt processing for
+    IP_MULTICAST_TTL and IP_MULTICAST_IF.
+
+  2007-09-10 Frédéric Bernon
+  * snmp.h, mib2.c: enable to remove SNMP timer (which consumne several cycles
+    even when it's not necessary). snmp_agent.txt tell to call snmp_inc_sysuptime()
+    each 10ms (but, it's intrusive if you use sys_timeout feature). Now, you can
+    decide to call snmp_add_sysuptime(100) each 1000ms (which is bigger "step", but
+    call to a lower frequency). Or, you can decide to not call snmp_inc_sysuptime()
+    or snmp_add_sysuptime(), and to define the SNMP_GET_SYSUPTIME(sysuptime) macro.
+    This one is undefined by default in mib2.c. SNMP_GET_SYSUPTIME is called inside
+    snmp_get_sysuptime(u32_t *value), and enable to change "sysuptime" value only
+    when it's queried (any direct call to "sysuptime" is changed by a call to 
+    snmp_get_sysuptime).
+
+  2007-09-09 Frédéric Bernon, Bill Florac
+  * igmp.h, igmp.c, netif.h, netif.c, ip.c: To enable to have interfaces with IGMP,
+    and others without it, there is a new NETIF_FLAG_IGMP flag to set in netif->flags
+    if you want IGMP on an interface. igmp_stop() is now called inside netif_remove().
+    igmp_report_groups() is now called inside netif_set_link_up() (need to have
+    LWIP_NETIF_LINK_CALLBACK=1) to resend reports once the link is up (avoid to wait
+    the next query message to receive the matching multicast streams).
+
+  2007-09-08 Frédéric Bernon
+  * sockets.c, ip.h, api.h, tcp.h: declare a "struct ip_pcb" which only contains
+    IP_PCB. Add in the netconn's "pcb" union a "struct ip_pcb *ip;" (no size change).
+    Use this new field to access to common pcb fields (ttl, tos, so_options, etc...).
+    Enable to access to these fields with LWIP_TCP=0.
+
+  2007-09-05 Frédéric Bernon
+  * udp.c, ipv4/icmp.c, ipv4/ip.c, ipv6/icmp.c, ipv6/ip6.c, ipv4/icmp.h,
+    ipv6/icmp.h, opt.h: Integrate "task #7272 : LWIP_ICMP option". The new option
+    LWIP_ICMP enable/disable ICMP module inside the IP stack (enable per default).
+    Be careful, disabling ICMP make your product non-compliant to RFC1122, but
+    help to reduce footprint, and to reduce "visibility" on the Internet.
+
+  2007-09-05 Frédéric Bernon, Bill Florac
+  * opt.h, sys.h, tcpip.c, slipif.c, ppp.c, sys_arch.txt: Change parameters list
+    for sys_thread_new (see "task #7252 : Create sys_thread_new_ex()"). Two new
+    parameters have to be provided: a task name, and a task stack size. For this
+    one, since it's platform dependant, you could define the best one for you in
+    your lwipopts.h. For port maintainers, you can just add these new parameters
+    in your sys_arch.c file, and but it's not mandatory, use them in your OS
+    specific functions.
+
+  2007-09-05 Frédéric Bernon
+  * inet.c, autoip.c, msg_in.c, msg_out.c, init.c: Move some build time checkings
+    inside init.c for task #7142 "Sanity check user-configurable values".
+
+  2007-09-04 Frédéric Bernon, Bill Florac
+  * igmp.h, igmp.c, memp_std.h, memp.c, init.c, opt.h: Replace mem_malloc call by
+    memp_malloc, and use a new MEMP_NUM_IGMP_GROUP option (see opt.h to define the
+    value). It will avoid potential fragmentation problems, use a counter to know
+    how many times a group is used on an netif, and free it when all applications
+    leave it. MEMP_NUM_IGMP_GROUP got 8 as default value (and init.c got a sanity
+    check if LWIP_IGMP!=0).
+
+  2007-09-03 Frédéric Bernon
+  * igmp.h, igmp.c, sockets.c, api_msg.c: Changes for "#20503 IGMP Improvement".
+    Initialize igmp_mac_filter to NULL in netif_add (this field should be set in
+    the netif's "init" function). Use the "imr_interface" field (for socket layer)
+    and/or the "interface" field (for netconn layer), for join/leave operations.
+    The igmp_join/leavegroup first parameter change from a netif to an ipaddr.
+    This field could be a netif's ipaddr, or "any" (same meaning than ip_addr_isany).
+
+  2007-08-30 Frédéric Bernon
+  * Add netbuf.h, netbuf.c, Change api.h, api_lib.c: #7249 "Split netbuf functions
+    from api/api_lib". Now netbuf API is independant of netconn, and can be used
+    with other API (application based on raw API, or future "socket2" API). Ports
+    maintainers just have to add src/api/netbuf.c in their makefile/projects.
+
+  2007-08-30 Frédéric Bernon, Jonathan Larmour
+  * init.c: Add first version of lwip_sanity_check for task #7142 "Sanity check
+    user-configurable values".
+
+  2007-08-29 Frédéric Bernon
+  * igmp.h, igmp.c, tcpip.c, init.c, netif.c: change igmp_init and add igmp_start.
+    igmp_start is call inside netif_add. Now, igmp initialization is in the same
+    spirit than the others modules. Modify some IGMP debug traces.
+
+  2007-08-29 Frédéric Bernon
+  * Add init.h, init.c, Change opt.h, tcpip.c: Task  #7213 "Add a lwip_init function"
+    Add lwip_init function to regroup all modules initializations, and to provide
+    a place to add code for task #7142 "Sanity check user-configurable values".
+    Ports maintainers should remove direct initializations calls from their code,
+    and add init.c in their makefiles. Note that lwip_init() function is called
+    inside tcpip_init, but can also be used by raw api users since all calls are
+    disabled when matching options are disabled. Also note that their is new options
+    in opt.h, you should configure in your lwipopts.h (they are enabled per default).
+
+  2007-08-26 Marc Boucher
+  * api_msg.c: do_close_internal(): Reset the callbacks and arg (conn) to NULL
+    since they can under certain circumstances be called with an invalid conn
+    pointer after the connection has been closed (and conn has been freed). 
+
+  2007-08-25 Frédéric Bernon (Artem Migaev's Patch)
+  * netif.h, netif.c: Integrate "patch #6163 : Function to check if link layer is up".
+    Add a netif_is_link_up() function if LWIP_NETIF_LINK_CALLBACK option is set.
+
+  2007-08-22 Frédéric Bernon
+  * netif.h, netif.c, opt.h: Rename LWIP_NETIF_CALLBACK in LWIP_NETIF_STATUS_CALLBACK
+    to be coherent with new LWIP_NETIF_LINK_CALLBACK option before next release.
+
+  2007-08-22 Frédéric Bernon
+  * tcpip.h, tcpip.c, ethernetif.c, opt.h: remove options ETHARP_TCPIP_INPUT &
+    ETHARP_TCPIP_ETHINPUT, now, only "ethinput" code is supported, even if the 
+    name is tcpip_input (we keep the name of 1.2.0 function).
+
+  2007-08-17 Jared Grubb
+  * memp_std.h, memp.h, memp.c, mem.c, stats.c: (Task #7136) Centralize mempool 
+    settings into new memp_std.h and optional user file lwippools.h. This adds
+    more dynamic mempools, and allows the user to create an arbitrary number of
+    mempools for mem_malloc.
+
+  2007-08-16 Marc Boucher
+  * api_msg.c: Initialize newconn->state to NETCONN_NONE in accept_function;
+    otherwise it was left to NETCONN_CLOSE and sent_tcp() could prematurely
+    close the connection.
+
+  2007-08-16 Marc Boucher
+  * sockets.c: lwip_accept(): check netconn_peer() error return.
+
+  2007-08-16 Marc Boucher
+  * mem.c, mem.h: Added mem_calloc().
+
+  2007-08-16 Marc Boucher
+  * tcpip.c, tcpip.h memp.c, memp.h: Added distinct memp (MEMP_TCPIP_MSG_INPKT)
+    for input packets to prevent floods from consuming all of MEMP_TCPIP_MSG
+    and starving other message types.
+    Renamed MEMP_TCPIP_MSG to MEMP_TCPIP_MSG_API
+
+  2007-08-16 Marc Boucher
+  * pbuf.c, pbuf.h, etharp.c, tcp_in.c, sockets.c: Split pbuf flags in pbuf
+    type and flgs (later renamed to flags).
+    Use enum pbuf_flag as pbuf_type.  Renumber PBUF_FLAG_*.
+    Improved lwip_recvfrom().  TCP push now propagated.
+
+  2007-08-16 Marc Boucher
+  * ethernetif.c, contrib/ports/various: ethbroadcast now a shared global
+    provided by etharp.
+
+  2007-08-16 Marc Boucher
+  * ppp_oe.c ppp_oe.h, auth.c chap.c fsm.c lcp.c ppp.c ppp.h,
+    etharp.c ethernetif.c, etharp.h, opt.h tcpip.h, tcpip.c:
+    Added PPPoE support and various PPP improvements.
+
+  2007-07-25 Simon Goldschmidt
+  * api_lib.c, ip_frag.c, pbuf.c, api.h, pbuf.h: Introduced pbuf_copy_partial,
+    making netbuf_copy_partial use this function.
+
+  2007-07-25 Simon Goldschmidt
+  * tcp_in.c: Fix bug #20506: Slow start / initial congestion window starts with
+    2 * mss (instead of 1 * mss previously) to comply with some newer RFCs and
+    other stacks.
+
+  2007-07-13 Jared Grubb (integrated by Frédéric Bernon)
+  * opt.h, netif.h, netif.c, ethernetif.c: Add new configuration option to add
+    a link callback in the netif struct, and functions to handle it. Be carefull
+    for port maintainers to add the NETIF_FLAG_LINK_UP flag (like in ethernetif.c)
+    if you want to be sure to be compatible with future changes...
+
+  2007-06-30 Frédéric Bernon
+  * sockets.h, sockets.c: Implement MSG_PEEK flag for recv/recvfrom functions.
+
+  2007-06-21 Simon Goldschmidt
+  * etharp.h, etharp.c: Combined etharp_request with etharp_raw for both
+    LWIP_AUTOIP =0 and =1 to remove redundant code.
+
+  2007-06-21 Simon Goldschmidt
+  * mem.c, memp.c, mem.h, memp.h, opt.h: task #6863: Introduced the option
+    MEM_USE_POOLS to use 4 pools with different sized elements instead of a
+    heap. This both prevents memory fragmentation and gives a higher speed
+    at the cost of more memory consumption. Turned off by default.
+
+  2007-06-21 Simon Goldschmidt
+  * api_lib.c, api_msg.c, api.h, api_msg.h: Converted the length argument of
+    netconn_write (and therefore also api_msg_msg.msg.w.len) from u16_t into
+    int to be able to send a bigger buffer than 64K with one time (mainly
+    used from lwip_send).
+
+  2007-06-21 Simon Goldschmidt
+  * tcp.h, api_msg.c: Moved the nagle algorithm from netconn_write/do_write
+    into a define (tcp_output_nagle) in tcp.h to provide it to raw api users, too.
+
+  2007-06-21 Simon Goldschmidt
+  * api.h, api_lib.c, api_msg.c: Fixed bug #20021: Moved sendbuf-processing in
+    netconn_write from api_lib.c to api_msg.c to also prevent multiple context-
+    changes on low memory or empty send-buffer.
+
+  2007-06-18 Simon Goldschmidt
+  * etharp.c, etharp.h: Changed etharp to use a defined hardware address length
+    of 6 to avoid loading netif->hwaddr_len every time (since this file is only
+    used for ethernet and struct eth_addr already had a defined length of 6).
+
+  2007-06-17 Simon Goldschmidt
+  * sockets.c, sockets.h: Implemented socket options SO_NO_CHECK for UDP sockets
+    to disable UDP checksum generation on transmit.
+
+  2007-06-13 Frédéric Bernon, Simon Goldschmidt
+  * debug.h, api_msg.c: change LWIP_ERROR to use it to check errors like invalid
+    pointers or parameters, and let the possibility to redefined it in cc.h. Use
+    this macro to check "conn" parameter in api_msg.c functions.
+
+  2007-06-11 Simon Goldschmidt
+  * sockets.c, sockets.h: Added UDP lite support for sockets
+
+  2007-06-10 Simon Goldschmidt
+  * udp.h, opt.h, api_msg.c, ip.c, udp.c: Included switch LWIP_UDPLITE (enabled
+    by default) to switch off UDP-Lite support if not needed (reduces udp.c code
+    size)
+
+  2007-06-09 Dominik Spies (integrated by Frédéric Bernon)
+  * autoip.h, autoip.c, dhcp.h, dhcp.c, netif.h, netif.c, etharp.h, etharp.c, opt.h:
+    AutoIP implementation available for IPv4, with new options LWIP_AUTOIP and
+    LWIP_DHCP_AUTOIP_COOP if you want to cooperate with DHCP. Some tips to adapt
+    (see TODO mark in the source code).
+
+  2007-06-09 Simon Goldschmidt
+  * etharp.h, etharp.c, ethernetif.c: Modified order of parameters for
+    etharp_output() to match netif->output so etharp_output() can be used
+    directly as netif->output to save one function call.
+
+  2007-06-08 Simon Goldschmidt
+  * netif.h, ethernetif.c, slipif.c, loopif.c: Added define
+    NETIF_INIT_SNMP(netif, type, speed) to initialize per-netif snmp variables,
+    added initialization of those to ethernetif, slipif and loopif.
+
+  2007-05-18 Simon Goldschmidt
+  * opt.h, ip_frag.c, ip_frag.h, ip.c: Added option IP_FRAG_USES_STATIC_BUF
+    (defaulting to off for now) that can be set to 0 to send fragmented
+    packets by passing PBUF_REFs down the stack.
+
+  2007-05-23 Frédéric Bernon
+  * api_lib.c: Implement SO_RCVTIMEO for accept and recv on TCP
+    connections, such present in patch #5959.
+
+  2007-05-23 Frédéric Bernon
+  * api.h, api_lib.c, api_msg.c, sockets.c: group the different NETCONN_UDPxxx
+    code in only one part...
+
+  2007-05-18 Simon Goldschmidt
+  * opt.h, memp.h, memp.c: Added option MEMP_OVERFLOW_CHECK to check for memp
+    elements to overflow. This is achieved by adding some bytes before and after
+    each pool element (increasing their size, of course), filling them with a
+    prominent value and checking them on freeing the element.
+    Set it to 2 to also check every element in every pool each time memp_malloc()
+    or memp_free() is called (slower but more helpful).
+
+  2007-05-10 Simon Goldschmidt
+  * opt.h, memp.h, memp.c, pbuf.c (see task #6831): use a new memp pool for
+    PBUF_POOL pbufs instead of the old pool implementation in pbuf.c to reduce
+    code size.
+
+  2007-05-11 Frédéric Bernon
+  * sockets.c, api_lib.c, api_msg.h, api_msg.c, netifapi.h, netifapi.c, tcpip.c:
+    Include a function pointer instead of a table index in the message to reduce
+    footprint. Disable some part of lwip_send and lwip_sendto if some options are
+    not set (LWIP_TCP, LWIP_UDP, LWIP_RAW).
+
+  2007-05-10 Simon Goldschmidt
+  * *.h (except netif/ppp/*.h): Included patch #5448: include '#ifdef __cplusplus
+    \ extern "C" {' in all header files. Now you can write your application using
+    the lwIP stack in C++ and simply #include the core files. Note I have left
+    out the netif/ppp/*h header files for now, since I don't know which files are
+    included by applications and which are for internal use only.
+
+  2007-05-09 Simon Goldschmidt
+  * opt.h, *.c/*.h: Included patch #5920: Create define to override C-library
+    memcpy. 2 Defines are created: MEMCPY() for normal memcpy, SMEMCPY() for
+    situations where some compilers might inline the copy and save a function
+    call. Also replaced all calls to memcpy() with calls to (S)MEMCPY().
+
+  2007-05-08 Simon Goldschmidt
+  * mem.h: If MEM_LIBC_MALLOC==1, allow the defines (e.g. mem_malloc() -> malloc())
+    to be overriden in case the C-library malloc implementation is not protected
+    against concurrent access.
+
+  2007-05-04 Simon Goldschmidt (Atte Kojo)
+  * etharp.c: Introduced fast one-entry-cache to speed up ARP lookup when sending
+    multiple packets to the same host.
+
+  2007-05-04 Frédéric Bernon, Jonathan Larmour
+  * sockets.c, api.h, api_lib.c, api_msg.h, api_msg.c: Fix bug #19162 "lwip_sento: a possible
+    to corrupt remote addr/port connection state". Reduce problems "not enought memory" with
+    netbuf (if we receive lot of datagrams). Improve lwip_sendto (only one exchange between
+    sockets api and api_msg which run in tcpip_thread context). Add netconn_sento function.
+    Warning, if you directly access to "fromaddr" & "fromport" field from netbuf struct,
+    these fields are now renamed "addr" & "port".
+
+  2007-04-11 Jonathan Larmour
+  * sys.h, api_lib.c: Provide new sys_mbox_tryfetch function. Require ports to provide new
+    sys_arch_mbox_tryfetch function to get a message if one is there, otherwise return
+    with SYS_MBOX_EMPTY. sys_arch_mbox_tryfetch can be implemented as a function-like macro
+    by the port in sys_arch.h if desired.
+
+  2007-04-06 Frédéric Bernon, Simon Goldschmidt
+  * opt.h, tcpip.h, tcpip.c, netifapi.h, netifapi.c: New configuration option LWIP_NETIF_API
+    allow to use thread-safe functions to add/remove netif in list, and to start/stop dhcp
+    clients, using new functions from netifapi.h. Disable as default (no port change to do).
+
+  2007-04-05 Frédéric Bernon
+  * sockets.c: remplace ENOBUFS errors on alloc_socket by ENFILE to be more BSD compliant.
+
+  2007-04-04 Simon Goldschmidt
+  * arch.h, api_msg.c, dhcp.c, msg_in.c, sockets.c: Introduced #define LWIP_UNUSED_ARG(x)
+    use this for and architecture-independent form to tell the compiler you intentionally
+    are not using this variable. Can be overriden in cc.h.
+
+  2007-03-28 Frédéric Bernon
+  * opt.h, netif.h, dhcp.h, dhcp.c: New configuration option LWIP_NETIF_HOSTNAME allow to
+    define a hostname in netif struct (this is just a pointer, so, you can use a hardcoded
+    string, point on one of your's ethernetif field, or alloc a string you will free yourself).
+    It will be used by DHCP to register a client hostname, but can also be use when you call
+    snmp_set_sysname.
+
+  2007-03-28 Frédéric Bernon
+  * netif.h, netif.c: A new NETIF_FLAG_ETHARP flag is defined in netif.h, to allow to 
+    initialize a network interface's flag with. It tell this interface is an ethernet
+    device, and we can use ARP with it to do a "gratuitous ARP" (RFC 3220 "IP Mobility
+    Support for IPv4" section 4.6) when interface is "up" with netif_set_up().
+
+  2007-03-26 Frédéric Bernon, Jonathan Larmour
+  * opt.h, tcpip.c: New configuration option LWIP_ARP allow to disable ARP init at build
+    time if you only use PPP or SLIP. The default is enable. Note we don't have to call 
+    etharp_init in your port's initilization sequence if you use tcpip.c, because this call
+    is done in tcpip_init function.
+
+  2007-03-22 Frédéric Bernon
+  * stats.h, stats.c, msg_in.c: Stats counters can be change to u32_t if necessary with the
+    new option LWIP_STATS_LARGE. If you need this option, define LWIP_STATS_LARGE to 1 in
+    your lwipopts.h. More, unused counters are not defined in the stats structs, and not 
+    display by stats_display(). Note that some options (SYS_STATS and RAW_STATS) are defined
+    but never used. Fix msg_in.c with the correct #if test for a stat display.
+
+  2007-03-21 Kieran Mansley
+  * netif.c, netif.h: Apply patch#4197 with some changes (originator: rireland@xxxxxxxxx). 
+    Provides callback on netif up/down state change.
+
+  2007-03-11 Frédéric Bernon, Mace Gael, Steve Reynolds
+  * sockets.h, sockets.c, api.h, api_lib.c, api_msg.h, api_msg.c, igmp.h, igmp.c,
+    ip.c, netif.h, tcpip.c, opt.h:
+    New configuration option LWIP_IGMP to enable IGMP processing. Based on only one 
+    filter per all network interfaces. Declare a new function in netif to enable to
+    control the MAC filter (to reduce lwIP traffic processing).
+
+  2007-03-11 Frédéric Bernon
+  * tcp.h, tcp.c, sockets.c, tcp_out.c, tcp_in.c, opt.h: Keepalive values can
+    be configured at run time with LWIP_TCP_KEEPALIVE, but don't change this
+    unless you know what you're doing (default are RFC1122 compliant). Note
+    that TCP_KEEPIDLE and TCP_KEEPINTVL have to be set in seconds.
+
+  2007-03-08 Frédéric Bernon
+  * tcp.h: Keepalive values can be configured at compile time, but don't change
+    this unless you know what you're doing (default are RFC1122 compliant).
+
+  2007-03-08 Frédéric Bernon
+  * sockets.c, api.h, api_lib.c, tcpip.c, sys.h, sys.c, err.c, opt.h:
+    Implement LWIP_SO_RCVTIMEO configuration option to enable/disable SO_RCVTIMEO
+    on UDP sockets/netconn.
+
+  2007-03-08 Simon Goldschmidt
+  * snmp_msg.h, msg_in.c: SNMP UDP ports can be configured at compile time.
+
+  2007-03-06 Frédéric Bernon
+  * api.h, api_lib.c, sockets.h, sockets.c, tcpip.c, sys.h, sys.c, err.h: 
+    Implement SO_RCVTIMEO on UDP sockets/netconn.
+
+  2007-02-28 Kieran Mansley (based on patch from Simon Goldschmidt)
+  * api_lib.c, tcpip.c, memp.c, memp.h: make API msg structs allocated
+    on the stack and remove the API msg type from memp
+
+  2007-02-26 Jonathan Larmour (based on patch from Simon Goldschmidt)
+  * sockets.h, sockets.c: Move socket initialization to new
+    lwip_socket_init() function.
+    NOTE: this changes the API with ports. Ports will have to be
+    updated to call lwip_socket_init() now.
+
+  2007-02-26 Jonathan Larmour (based on patch from Simon Goldschmidt)
+  * api_lib.c: Use memcpy in netbuf_copy_partial.
+
+
+  ++ Bug fixes:
+
+  2008-03-17 Frédéric Bernon, Ed Kerekes
+  * igmp.h, igmp.c: Fix bug #22613 "IGMP iphdr problem" (could have
+    some problems to fill the IP header on some targets, use now the
+    ip.h macros to do it).
+
+  2008-03-13 Frédéric Bernon
+  * sockets.c: Fix bug #22435 "lwip_recvfrom with TCP break;". Using
+    (lwip_)recvfrom with valid "from" and "fromlen" parameters, on a
+    TCP connection caused a crash. Note that using (lwip_)recvfrom
+    like this is a bit slow and that using (lwip)getpeername is the
+    good lwip way to do it (so, using recv is faster on tcp sockets).
+
+  2008-03-12 Frédéric Bernon, Jonathan Larmour
+  * api_msg.c, contrib/apps/ping.c: Fix bug #22530 "api_msg.c's
+    recv_raw() does not consume data", and the ping sample (with
+    LWIP_SOCKET=1, the code did the wrong supposition that lwip_recvfrom
+    returned the IP payload, without the IP header).
+
+  2008-03-04 Jonathan Larmour
+  * mem.c, stats.c, mem.h: apply patch #6414 to avoid compiler errors
+  and/or warnings on some systems where mem_size_t and size_t differ.
+  * pbuf.c, ppp.c: Fix warnings on some systems with mem_malloc.
+
+  2008-03-04 Kieran Mansley (contributions by others) 
+  * Numerous small compiler error/warning fixes from contributions to
+    mailing list after 1.3.0 release candidate made.
+
+  2008-01-25 Cui hengbin (integrated by Frédéric Bernon)
+  * dns.c: Fix bug #22108 "DNS problem" caused by unaligned structures.
+
+  2008-01-15 Kieran Mansley
+  * tcp_out.c: BUG20511.  Modify persist timer to start when we are
+    prevented from sending by a small send window, not just a zero
+    send window.
+
+  2008-01-09 Jonathan Larmour
+  * opt.h, ip.c: Rename IP_OPTIONS define to IP_OPTIONS_ALLOWED to avoid
+    conflict with Linux system headers.
+
+  2008-01-06 Jonathan Larmour
+  * dhcp.c: fix bug #19927: "DHCP NACK problem" by clearing any existing set IP
+    address entirely on receiving a DHCPNAK, and restarting discovery.
+
+  2007-12-21 Simon Goldschmidt
+  * sys.h, api_lib.c, api_msg.c, sockets.c: fix bug #21698: "netconn->recv_avail
+    is not protected" by using new macros for interlocked access to modify/test
+    netconn->recv_avail.
+
+  2007-12-20 Kieran Mansley (based on patch from Oleg Tyshev)
+  * tcp_in.c: fix bug# 21535 (nrtx not reset correctly in SYN_SENT state)
+
+  2007-12-20 Kieran Mansley (based on patch from Per-Henrik Lundbolm)
+  * tcp.c, tcp_in.c, tcp_out.c, tcp.h: fix bug #20199 (better handling
+    of silly window avoidance and prevent lwIP from shrinking the window)
+
+  2007-12-04 Simon Goldschmidt
+  * tcp.c, tcp_in.c: fix bug #21699 (segment leak in ooseq processing when last
+    data packet was lost): add assert that all segment lists are empty in
+    tcp_pcb_remove before setting pcb to CLOSED state; don't directly set CLOSED
+    state from LAST_ACK in tcp_process
+
+  2007-12-02 Simon Goldschmidt
+  * sockets.h: fix bug #21654: exclude definition of struct timeval from #ifndef FD_SET
+    If including <sys/time.h> for system-struct timeval, LWIP_TIMEVAL_PRIVATE now
+    has to be set to 0 in lwipopts.h
+
+  2007-12-02 Simon Goldschmidt
+  * api_msg.c, api_lib.c: fix bug #21656 (recvmbox problem in netconn API): always
+    allocate a recvmbox in netconn_new_with_proto_and_callback. For a tcp-listen
+    netconn, this recvmbox is later freed and a new mbox is allocated for acceptmbox.
+    This is a fix for thread-safety and allocates all items needed for a netconn
+    when the netconn is created.
+
+  2007-11-30 Simon Goldschmidt
+  * udp.c: first attempt to fix bug #21655 (DHCP doesn't work reliably with multiple
+    netifs): if LWIP_DHCP is enabled, UDP packets to DHCP_CLIENT_PORT are passed
+    to netif->dhcp->pcb only (if that exists) and not to any other pcb for the same
+    port (only solution to let UDP pcbs 'bind' to a netif instead of an IP address)
+
+  2007-11-27 Simon Goldschmidt
+  * ip.c: fixed bug #21643 (udp_send/raw_send don't fail if netif is down) by
+    letting ip_route only use netifs that are up.
+
+  2007-11-27 Simon Goldschmidt
+  * err.h, api_lib.c, api_msg.c, sockets.c: Changed error handling: ERR_MEM, ERR_BUF
+    and ERR_RTE are seen as non-fatal, all other errors are fatal. netconns and
+    sockets block most operations once they have seen a fatal error.
+
+  2007-11-27 Simon Goldschmidt
+  * udp.h, udp.c, dhcp.c: Implemented new function udp_sendto_if which takes the
+    netif to send as an argument (to be able to send on netifs that are down).
+
+  2007-11-26 Simon Goldschmidt
+  * tcp_in.c: Fixed bug #21582: pcb->acked accounting can be wrong when ACKs
+    arrive out-of-order
+
+  2007-11-21 Simon Goldschmidt
+  * tcp.h, tcp_out.c, api_msg.c: Fixed bug #20287: tcp_output_nagle sends too early
+    Fixed the nagle algorithm; nagle now also works for all raw API applications
+    and has to be explicitly disabled with 'tcp_pcb->flags |= TF_NODELAY'
+
+  2007-11-12 Frédéric Bernon
+  * sockets.c, api.h, api_lib.c, api_msg.h, api_msg.c: Fixed bug #20900. Now, most
+    of the netconn_peer and netconn_addr processing is done inside tcpip_thread
+    context in do_getaddr.
+
+  2007-11-10 Simon Goldschmidt
+  * etharp.c: Fixed bug: assert fired when MEMP_ARP_QUEUE was empty (which can
+    happen any time). Now the packet simply isn't enqueued when out of memory.
+
+  2007-11-01 Simon Goldschmidt
+  * tcp.c, tcp_in.c: Fixed bug #21494: The send mss (pcb->mss) is set to 536 (or
+    TCP_MSS if that is smaller) as long as no MSS option is received from the
+    remote host.
+
+  2007-11-01 Simon Goldschmidt
+  * tcp.h, tcp.c, tcp_in.c: Fixed bug #21491: The MSS option sent (with SYN)
+    is now based on TCP_MSS instead of pcb->mss (on passive open now effectively
+    sending our configured TCP_MSS instead of the one received).
+
+  2007-11-01 Simon Goldschmidt
+  * tcp_in.c: Fixed bug #21181: On active open, the initial congestion window was
+    calculated based on the configured TCP_MSS, not on the MSS option received
+    with SYN+ACK.
+
+  2007-10-09 Simon Goldschmidt
+  * udp.c, inet.c, inet.h: Fixed UDPLite: send: Checksum was always generated too
+    short and also was generated wrong if checksum coverage != tot_len;
+    receive: checksum was calculated wrong if checksum coverage != tot_len
+
+  2007-10-08 Simon Goldschmidt
+  * mem.c: lfree was not updated in mem_realloc!
+
+  2007-10-07 Frédéric Bernon
+  * sockets.c, api.h, api_lib.c: First step to fix "bug #20900 : Potential
+    crash error problem with netconn_peer & netconn_addr". VERY IMPORTANT:
+    this change cause an API breakage for netconn_addr, since a parameter
+    type change. Any compiler should cause an error without any changes in
+    yours netconn_peer calls (so, it can't be a "silent change"). It also
+    reduce a little bit the footprint for socket layer (lwip_getpeername &
+    lwip_getsockname use now a common lwip_getaddrname function since 
+    netconn_peer & netconn_addr have the same parameters).
+
+  2007-09-20 Simon Goldschmidt
+  * tcp.c: Fixed bug #21080 (tcp_bind without check pcbs in TIME_WAIT state)
+    by checking  tcp_tw_pcbs also
+
+  2007-09-19 Simon Goldschmidt
+  * icmp.c: Fixed bug #21107 (didn't reset IP TTL in ICMP echo replies)
+
+  2007-09-15 Mike Kleshov
+  * mem.c: Fixed bug #21077 (inaccuracy in calculation of lwip_stat.mem.used)
+
+  2007-09-06 Frédéric Bernon
+  * several-files: replace some #include "arch/cc.h" by "lwip/arch.h", or simply remove
+    it as long as "lwip/opt.h" is included before (this one include "lwip/debug.h" which
+    already include "lwip/arch.h"). Like that, default defines are provided by "lwip/arch.h"
+    if they are not defined in cc.h, in the same spirit than "lwip/opt.h" for lwipopts.h.
+
+  2007-08-30 Frédéric Bernon
+  * igmp.h, igmp.c: Some changes to remove some redundant code, add some traces, 
+    and fix some coding style.
+
+  2007-08-28 Frédéric Bernon
+  * tcpip.c: Fix TCPIP_MSG_INPKT processing: now, tcpip_input can be used for any
+    kind of packets. These packets are considered like Ethernet packets (payload 
+    pointing to ethhdr) if the netif got the NETIF_FLAG_ETHARP flag. Else, packets 
+    are considered like IP packets (payload pointing to iphdr).
+
+  2007-08-27 Frédéric Bernon
+  * api.h, api_lib.c, api_msg.c: First fix for "bug #20900 : Potential crash error
+    problem with netconn_peer & netconn_addr". Introduce NETCONN_LISTEN netconn_state
+    and remove obsolete ones (NETCONN_RECV & NETCONN_ACCEPT).
+
+  2007-08-24 Kieran Mansley
+  * inet.c Modify (acc >> 16) test to ((acc >> 16) != 0) to help buggy
+    compiler (Paradigm C++)
+
+  2007-08-09 Frédéric Bernon, Bill Florac
+  * stats.h, stats.c, igmp.h, igmp.c, opt.h: Fix for bug #20503 : IGMP Improvement.
+    Introduce IGMP_STATS to centralize statistics management.
+
+  2007-08-09 Frédéric Bernon, Bill Florac
+  * udp.c: Fix for bug #20503 : IGMP Improvement. Enable to receive a multicast
+    packet on a udp pcb binded on an netif's IP address, and not on "any".
+
+  2007-08-09 Frédéric Bernon, Bill Florac
+  * igmp.h, igmp.c, ip.c: Fix minor changes from bug #20503 : IGMP Improvement.
+    This is mainly on using lookup/lookfor, and some coding styles...
+
+  2007-07-26 Frédéric Bernon (and "thedoctor")
+  * igmp.c: Fix bug #20595 to accept IGMPv3 "Query" messages.
+
+  2007-07-25 Simon Goldschmidt
+  * api_msg.c, tcp.c: Another fix for bug #20021: by not returning an error if
+    tcp_output fails in tcp_close, the code in do_close_internal gets simpler
+    (tcp_output is called again later from tcp timers).
+
+  2007-07-25 Simon Goldschmidt
+  * ip_frag.c: Fixed bug #20429: use the new pbuf_copy_partial instead of the old
+    copy_from_pbuf, which illegally modified the given pbuf.
+
+  2007-07-25 Simon Goldschmidt
+  * tcp_out.c: tcp_enqueue: pcb->snd_queuelen didn't work for chaine PBUF_RAMs:
+    changed snd_queuelen++ to snd_queuelen += pbuf_clen(p).
+
+  2007-07-24 Simon Goldschmidt
+  * api_msg.c, tcp.c: Fix bug #20480: Check the pcb passed to tcp_listen() for the
+    correct state (must be CLOSED).
+
+  2007-07-13 Thomas Taranowski (commited by Jared Grubb)
+  * memp.c: Fix bug #20478: memp_malloc returned NULL+MEMP_SIZE on failed
+    allocation. It now returns NULL.
+
+  2007-07-13 Frédéric Bernon
+  * api_msg.c: Fix bug #20318: api_msg "recv" callbacks don't call pbuf_free in
+    all error cases.
+
+  2007-07-13 Frédéric Bernon
+  * api_msg.c: Fix bug #20315: possible memory leak problem if tcp_listen failed,
+    because current code doesn't follow rawapi.txt documentation.
+
+  2007-07-13 Kieran Mansley
+  * src/core/tcp_in.c Apply patch#5741 from Oleg Tyshev to fix bug in
+    out of sequence processing of received packets
+
+  2007-07-03 Simon Goldschmidt
+  * nearly-all-files: Added assertions where PBUF_RAM pbufs are used and an
+    assumption is made that this pbuf is in one piece (i.e. not chained). These
+    assumptions clash with the possibility of converting to fully pool-based
+    pbuf implementations, where PBUF_RAM pbufs might be chained.
+
+  2007-07-03 Simon Goldschmidt
+  * api.h, api_lib.c, api_msg.c: Final fix for bug #20021 and some other problems
+    when closing tcp netconns: removed conn->sem, less context switches when
+    closing, both netconn_close and netconn_delete should safely close tcp
+    connections.
+
+  2007-07-02 Simon Goldschmidt
+  * ipv4/ip.h, ipv6/ip.h, opt.h, netif.h, etharp.h, ipv4/ip.c, netif.c, raw.c,
+    tcp_out.c, udp.c, etharp.c: Added option LWIP_NETIF_HWADDRHINT (default=off)
+    to cache ARP table indices with each pcb instead of single-entry cache for
+    the complete stack.
+
+  2007-07-02 Simon Goldschmidt
+  * tcp.h, tcp.c, tcp_in.c, tcp_out.c: Added some ASSERTS and casts to prevent
+    warnings when assigning to smaller types.
+
+  2007-06-28 Simon Goldschmidt
+  * tcp_out.c: Added check to prevent tcp_pcb->snd_queuelen from overflowing.
+
+  2007-06-28 Simon Goldschmidt
+  * tcp.h: Fixed bug #20287: Fixed nagle algorithm (sending was done too early if
+    a segment contained chained pbufs)
+
+  2007-06-28 Frédéric Bernon
+  * autoip.c: replace most of rand() calls by a macro LWIP_AUTOIP_RAND which compute
+    a "pseudo-random" value based on netif's MAC and some autoip fields. It's always
+    possible to define this macro in your own lwipopts.h to always use C library's
+    rand(). Note that autoip_create_rand_addr doesn't use this macro.
+
+  2007-06-28 Frédéric Bernon
+  * netifapi.h, netifapi.c, tcpip.h, tcpip.c: Update code to handle the option
+    LWIP_TCPIP_CORE_LOCKING, and do some changes to be coherent with last modifications
+    in api_lib/api_msg (use pointers and not type with table, etc...) 
+
+  2007-06-26 Simon Goldschmidt
+  * udp.h: Fixed bug #20259: struct udp_hdr was lacking the packin defines.
+
+  2007-06-25 Simon Goldschmidt
+  * udp.c: Fixed bug #20253: icmp_dest_unreach was called with a wrong p->payload
+    for udp packets with no matching pcb.
+
+  2007-06-25 Simon Goldschmidt
+  * udp.c: Fixed bug #20220: UDP PCB search in udp_input(): a non-local match
+    could get udp input packets if the remote side matched.
+
+  2007-06-13 Simon Goldschmidt
+  * netif.c: Fixed bug #20180 (TCP pcbs listening on IP_ADDR_ANY could get
+    changed in netif_set_ipaddr if previous netif->ip_addr.addr was 0.
+
+  2007-06-13 Simon Goldschmidt
+  * api_msg.c: pcb_new sets conn->err if protocol is not implemented
+    -> netconn_new_..() does not allocate a new connection for unsupported
+    protocols.
+
+  2007-06-13 Frédéric Bernon, Simon Goldschmidt
+  * api_lib.c: change return expression in netconn_addr and netconn_peer, because
+    conn->err was reset to ERR_OK without any reasons (and error was lost)...
+
+  2007-06-13 Frédéric Bernon, Matthias Weisser
+  * opt.h, mem.h, mem.c, memp.c, pbuf.c, ip_frag.c, vj.c: Fix bug #20162. Rename
+    MEM_ALIGN in LWIP_MEM_ALIGN and MEM_ALIGN_SIZE in LWIP_MEM_ALIGN_SIZE to avoid
+    some macro names collision with some OS macros.
+
+  2007-06-11 Simon Goldschmidt
+  * udp.c: UDP Lite: corrected the use of chksum_len (based on RFC3828: if it's 0,
+    create checksum over the complete packet. On RX, if it's < 8 (and not 0),
+    discard the packet. Also removed the duplicate 'udphdr->chksum = 0' for both
+    UDP & UDP Lite.
+
+  2007-06-11 Srinivas Gollakota & Oleg Tyshev
+  * tcp_out.c: Fix for bug #20075 : "A problem with keep-alive timer and TCP flags"
+    where TCP flags wasn't initialized in tcp_keepalive.
+
+  2007-06-03 Simon Goldschmidt
+  * udp.c: udp_input(): Input pbuf was not freed if pcb had no recv function
+    registered, p->payload was modified without modifying p->len if sending
+    icmp_dest_unreach() (had no negative effect but was definitively wrong).
+
+  2007-06-03 Simon Goldschmidt
+  * icmp.c: Corrected bug #19937: For responding to an icmp echo request, icmp
+    re-used the input pbuf even if that didn't have enough space to include the
+    link headers. Now the space is tested and a new pbuf is allocated for the
+    echo response packet if the echo request pbuf isn't big enough.
+
+  2007-06-01 Simon Goldschmidt
+  * sockets.c: Checked in patch #5914: Moved sockopt processing into tcpip_thread.
+
+  2007-05-23 Frédéric Bernon
+  * api_lib.c, sockets.c: Fixed bug #5958 for netconn_listen (acceptmbox only
+    allocated by do_listen if success) and netconn_accept errors handling. In
+    most of api_lib functions, we replace some errors checkings like "if (conn==NULL)"
+    by ASSERT, except for netconn_delete.
+
+  2007-05-23 Frédéric Bernon
+  * api_lib.c: Fixed bug #5957 "Safe-thread problem inside netconn_recv" to return
+    an error code if it's impossible to fetch a pbuf on a TCP connection (and not
+    directly close the recvmbox).
+
+  2007-05-22 Simon Goldschmidt
+  * tcp.c: Fixed bug #1895 (tcp_bind not correct) by introducing a list of
+    bound but unconnected (and non-listening) tcp_pcbs.
+
+  2007-05-22 Frédéric Bernon
+  * sys.h, sys.c, api_lib.c, tcpip.c: remove sys_mbox_fetch_timeout() (was only
+    used for LWIP_SO_RCVTIMEO option) and use sys_arch_mbox_fetch() instead of
+    sys_mbox_fetch() in api files. Now, users SHOULD NOT use internal lwIP features
+    like "sys_timeout" in their application threads.
+
+  2007-05-22 Frédéric Bernon
+  * api.h, api_lib.c, api_msg.h, api_msg.c: change the struct api_msg_msg to see
+    which parameters are used by which do_xxx function, and to avoid "misusing"
+    parameters (patch #5938).
+
+  2007-05-22 Simon Goldschmidt
+  * api_lib.c, api_msg.c, raw.c, api.h, api_msg.h, raw.h: Included patch #5938:
+    changed raw_pcb.protocol from u16_t to u8_t since for IPv4 and IPv6, proto
+    is only 8 bits wide. This affects the api, as there, the protocol was
+    u16_t, too.
+
+  2007-05-18 Simon Goldschmidt
+  * memp.c: addition to patch #5913: smaller pointer was returned but
+    memp_memory was the same size -> did not save memory.
+
+  2007-05-16 Simon Goldschmidt
+  * loopif.c, slipif.c: Fix bug #19729: free pbuf if netif->input() returns
+    != ERR_OK.
+
+  2007-05-16 Simon Goldschmidt
+  * api_msg.c, udp.c: If a udp_pcb has a local_ip set, check if it is the same
+    as the one of the netif used for sending to prevent sending from old
+    addresses after a netif address gets changed (partly fixes bug #3168).
+
+  2007-05-16 Frédéric Bernon
+  * tcpip.c, igmp.h, igmp.c: Fixed bug "#19800 : IGMP: igmp_tick() will not work
+    with NO_SYS=1". Note that igmp_init is always in tcpip_thread (and not in 
+    tcpip_init) because we have to be sure that network interfaces are already
+    added (mac filter is updated only in igmp_init for the moment).
+
+  2007-05-16 Simon Goldschmidt
+  * mem.c, memp.c: Removed semaphores from memp, changed sys_sem_wait calls
+    into sys_arch_sem_wait calls to prevent timers from running while waiting
+    for the heap. This fixes bug #19167.
+
+  2007-05-13 Simon Goldschmidt
+  * tcp.h, sockets.h, sockets.c: Fixed bug from patch #5865 by moving the defines
+    for socket options (lwip_set/-getsockopt) used with level IPPROTO_TCP from
+    tcp.h to sockets.h.
+
+  2007-05-07 Simon Goldschmidt
+  * mem.c: Another attempt to fix bug #17922.
+
+  2007-05-04 Simon Goldschmidt
+  * pbuf.c, pbuf.h, etharp.c: Further update to ARP queueing: Changed pbuf_copy()
+    implementation so that it can be reused (don't allocate the target
+    pbuf inside pbuf_copy()).
+
+  2007-05-04 Simon Goldschmidt
+  * memp.c: checked in patch #5913: in memp_malloc() we can return memp as mem
+    to save a little RAM (next pointer of memp is not used while not in pool).
+
+  2007-05-03 "maq"
+  * sockets.c: Fix ioctl FIONREAD when some data remains from last recv.
+    (patch #3574).
+
+  2007-04-23 Simon Goldschmidt
+  * loopif.c, loopif.h, opt.h, src/netif/FILES: fix bug #2595: "loopif results
+    in NULL reference for incoming TCP packets". Loopif has to be configured
+    (using LWIP_LOOPIF_MULTITHREADING) to directly call netif->input()
+    (multithreading environments, e.g. netif->input() = tcpip_input()) or
+    putting packets on a list that is fed to the stack by calling loopif_poll()
+    (single-thread / NO_SYS / polling environment where e.g.
+    netif->input() = ip_input).
+
+  2007-04-17 Jonathan Larmour
+  * pbuf.c: Use s32_t in pbuf_realloc(), as an s16_t can't reliably hold
+    the difference between two u16_t's.
+  * sockets.h: FD_SETSIZE needs to match number of sockets, which is
+    MEMP_NUM_NETCONN in sockets.c right now.
+
+  2007-04-12 Jonathan Larmour
+  * icmp.c: Reset IP header TTL in ICMP ECHO responses (bug #19580).
+
+  2007-04-12 Kieran Mansley
+  * tcp.c, tcp_in.c, tcp_out.c, tcp.h: Modify way the retransmission
+    timer is reset to fix bug#19434, with help from Oleg Tyshev.
+
+  2007-04-11 Simon Goldschmidt
+  * etharp.c, pbuf.c, pbuf.h: 3rd fix for bug #11400 (arp-queuing): More pbufs than
+    previously thought need to be copied (everything but PBUF_ROM!). Cleaned up
+    pbuf.c: removed functions no needed any more (by etharp).
+
+  2007-04-11 Kieran Mansley
+  * inet.c, ip_addr.h, sockets.h, sys.h, tcp.h: Apply patch #5745: Fix
+    "Constant is long" warnings with 16bit compilers.  Contributed by
+    avatar@xxxxxxxxxxxxxxxxxxxx
+
+  2007-04-05 Frédéric Bernon, Jonathan Larmour
+  * api_msg.c: Fix bug #16830: "err_tcp() posts to connection mailbox when no pend on
+    the mailbox is active". Now, the post is only done during a connect, and do_send,
+    do_write and do_join_leave_group don't do anything if a previous error was signaled.
+
+  2007-04-03 Frédéric Bernon
+  * ip.c: Don't set the IP_DF ("Don't fragment") flag in the IP header in IP output
+    packets. See patch #5834.
+
+  2007-03-30 Frédéric Bernon
+  * api_msg.c: add a "pcb_new" helper function to avoid redundant code, and to add
+    missing  pcb allocations checking (in do_bind, and for each raw_new). Fix style.
+
+  2007-03-30 Frédéric Bernon
+  * most of files: prefix all debug.h define with "LWIP_" to avoid any conflict with
+    others environment defines (these were too "generic").
+
+  2007-03-28 Frédéric Bernon
+  * api.h, api_lib.c, sockets.c: netbuf_ref doesn't check its internal pbuf_alloc call
+    result and can cause a crash. lwip_send now check netbuf_ref result.
+
+  2007-03-28 Simon Goldschmidt
+  * sockets.c Remove "#include <errno.h>" from sockets.c to avoid multiple
+    definition of macros (in errno.h and lwip/arch.h) if LWIP_PROVIDE_ERRNO is
+    defined. This is the way it should have been already (looking at
+    doc/sys_arch.txt)
+
+  2007-03-28 Kieran Mansley
+  * opt.h Change default PBUF_POOL_BUFSIZE (again) to accomodate default MSS +
+    IP and TCP headers *and* physical link headers
+
+  2007-03-26 Frédéric Bernon (based on patch from Dmitry Potapov)
+  * api_lib.c: patch for netconn_write(), fixes a possible race condition which cause
+    to send some garbage. It is not a definitive solution, but the patch does solve
+    the problem for most cases.
+
+  2007-03-22 Frédéric Bernon
+  * api_msg.h, api_msg.c: Remove obsolete API_MSG_ACCEPT and do_accept (never used).
+
+  2007-03-22 Frédéric Bernon
+  * api_lib.c: somes resources couldn't be freed if there was errors during
+    netconn_new_with_proto_and_callback.
+
+  2007-03-22 Frédéric Bernon
+  * ethernetif.c: update netif->input calls to check return value. In older ports,
+    it's a good idea to upgrade them, even if before, there could be another problem
+    (access to an uninitialized mailbox).
+
+  2007-03-21 Simon Goldschmidt
+  * sockets.c: fixed bug #5067 (essentialy a signed/unsigned warning fixed
+    by casting to unsigned).
+
+  2007-03-21 Frédéric Bernon
+  * api_lib.c, api_msg.c, tcpip.c: integrate sys_mbox_fetch(conn->mbox, NULL) calls from
+    api_lib.c to tcpip.c's tcpip_apimsg(). Now, use a local variable and not a
+    dynamic one from memp to send tcpip_msg to tcpip_thread in a synchrone call.
+    Free tcpip_msg from tcpip_apimsg is not done in tcpip_thread. This give a
+    faster and more reliable communication between api_lib and tcpip.
+
+  2007-03-21 Frédéric Bernon
+  * opt.h: Add LWIP_NETIF_CALLBACK (to avoid compiler warning) and set it to 0.
+
+  2007-03-21 Frédéric Bernon
+  * api_msg.c, igmp.c, igmp.h: Fix C++ style comments
+
+  2007-03-21 Kieran Mansley
+  * opt.h Change default PBUF_POOL_BUFSIZE to accomodate default MSS +
+    IP and TCP headers
+
+  2007-03-21 Kieran Mansley
+  * Fix all uses of pbuf_header to check the return value.  In some
+    cases just assert if it fails as I'm not sure how to fix them, but
+    this is no worse than before when they would carry on regardless
+    of the failure.
+
+  2007-03-21 Kieran Mansley
+  * sockets.c, igmp.c, igmp.h, memp.h: Fix C++ style comments and
+    comment out missing header include in icmp.c
+
+  2007-03-20 Frédéric Bernon
+  * memp.h, stats.c: Fix stats_display function where memp_names table wasn't
+    synchronized with memp.h.
+
+  2007-03-20 Frédéric Bernon
+  * tcpip.c: Initialize tcpip's mbox, and verify if initialized in tcpip_input,
+    tcpip_ethinput, tcpip_callback, tcpip_apimsg, to fix a init problem with 
+    network interfaces. Also fix a compiler warning.
+
+  2007-03-20 Kieran Mansley
+  * udp.c: Only try and use pbuf_header() to make space for headers if
+    not a ROM or REF pbuf.
+
+  2007-03-19 Frédéric Bernon
+  * api_msg.h, api_msg.c, tcpip.h, tcpip.c: Add return types to tcpip_apimsg()
+    and api_msg_post().
+
+  2007-03-19 Frédéric Bernon
+  * Remove unimplemented "memp_realloc" function from memp.h.
+
+  2007-03-11 Simon Goldschmidt
+  * pbuf.c: checked in patch #5796: pbuf_alloc: len field claculation caused
+    memory corruption.
+
+  2007-03-11 Simon Goldschmidt (based on patch from Dmitry Potapov)
+  * api_lib.c, sockets.c, api.h, api_msg.h, sockets.h: Fixed bug #19251
+    (missing `const' qualifier in socket functions), to get more compatible to
+    standard POSIX sockets.
+
+  2007-03-11 Frédéric Bernon (based on patch from Dmitry Potapov)
+  * sockets.c: Add asserts inside bind, connect and sendto to check input
+    parameters. Remove excessive set_errno() calls after get_socket(), because
+    errno is set inside of get_socket(). Move last sock_set_errno() inside
+    lwip_close.
+
+  2007-03-09 Simon Goldschmidt
+  * memp.c: Fixed bug #11400: New etharp queueing introduced bug: memp_memory
+    was allocated too small.
+
+  2007-03-06 Simon Goldschmidt
+  * tcpip.c: Initialize dhcp timers in tcpip_thread (if LWIP_DHCP) to protect
+    the stack from concurrent access.
+
+  2007-03-06 Frédéric Bernon, Dmitry Potapov
+  * tcpip.c, ip_frag.c, ethernetif.c: Fix some build problems, and a redundancy
+    call to "lwip_stats.link.recv++;" in low_level_input() & ethernetif_input().
+
+  2007-03-06 Simon Goldschmidt
+  * ip_frag.c, ip_frag.h: Reduce code size: don't include code in those files
+    if IP_FRAG == 0 and IP_REASSEMBLY == 0
+
+  2007-03-06 Frédéric Bernon, Simon Goldschmidt
+  * opt.h, ip_frag.h, tcpip.h, tcpip.c, ethernetif.c: add new configuration
+    option named ETHARP_TCPIP_ETHINPUT, which enable the new tcpip_ethinput.
+    Allow to do ARP processing for incoming packets inside tcpip_thread
+    (protecting ARP layer against concurrent access). You can also disable
+    old code using tcp_input with new define ETHARP_TCPIP_INPUT set to 0.
+    Older ports have to use tcpip_ethinput.
+
+  2007-03-06 Simon Goldschmidt (based on patch from Dmitry Potapov)
+  * err.h, err.c: fixed compiler warning "initialization dircards qualifiers
+    from pointer target type"
+
+  2007-03-05 Frédéric Bernon
+  * opt.h, sockets.h: add new configuration options (LWIP_POSIX_SOCKETS_IO_NAMES,
+    ETHARP_TRUST_IP_MAC, review SO_REUSE)
+
+  2007-03-04 Frédéric Bernon
+  * api_msg.c: Remove some compiler warnings : parameter "pcb" was never
+    referenced.
+
+  2007-03-04 Frédéric Bernon
+  * api_lib.c: Fix "[patch #5764] api_lib.c cleanup: after patch #5687" (from
+    Dmitry Potapov).
+    The api_msg struct stay on the stack (not moved to netconn struct).
+
+  2007-03-04 Simon Goldschmidt (based on patch from Dmitry Potapov)
+  * pbuf.c: Fix BUG#19168 - pbuf_free can cause deadlock (if
+    SYS_LIGHTWEIGHT_PROT=1 & freeing PBUF_RAM when mem_sem is not available)
+    Also fixed cast warning in pbuf_alloc()
+
+  2007-03-04 Simon Goldschmidt
+  * etharp.c, etharp.h, memp.c, memp.h, opt.h: Fix BUG#11400 - don't corrupt
+    existing pbuf chain when enqueuing multiple pbufs to a pending ARP request
+
+  2007-03-03 Frédéric Bernon
+  * udp.c: remove obsolete line "static struct udp_pcb *pcb_cache = NULL;"
+    It is static, and never used in udp.c except udp_init().
+
+  2007-03-02 Simon Goldschmidt
+  * tcpip.c: Moved call to ip_init(), udp_init() and tcp_init() from
+    tcpip_thread() to tcpip_init(). This way, raw API connections can be
+    initialized before tcpip_thread is running (e.g. before OS is started)
+
+  2007-03-02 Frédéric Bernon
+  * rawapi.txt: Fix documentation mismatch with etharp.h about etharp_tmr's call
+    interval.
+
+  2007-02-28 Kieran Mansley 
+  * pbuf.c: Fix BUG#17645 - ensure pbuf payload pointer is not moved
+    outside the region of the pbuf by pbuf_header()
+
+  2007-02-28 Kieran Mansley 
+  * sockets.c: Fix BUG#19161 - ensure milliseconds timeout is non-zero
+    when supplied timeout is also non-zero 
+
+(STABLE-1.2.0)
+
+  2006-12-05 Leon Woestenberg
+  * CHANGELOG: Mention STABLE-1.2.0 release.
+
+  ++ New features:
+
+  2006-12-01 Christiaan Simons
+  * mem.h, opt.h: Added MEM_LIBC_MALLOC option.
+    Note this is a workaround. Currently I have no other options left.
+
+  2006-10-26 Christiaan Simons (accepted patch by Jonathan Larmour)
+  * ipv4/ip_frag.c: rename MAX_MTU to IP_FRAG_MAX_MTU and move define
+    to include/lwip/opt.h.
+  * ipv4/lwip/ip_frag.h: Remove unused IP_REASS_INTERVAL.
+    Move IP_REASS_MAXAGE and IP_REASS_BUFSIZE to include/lwip/opt.h.
+  * opt.h: Add above new options.
+
+  2006-08-18 Christiaan Simons
+  * tcp_{in,out}.c: added SNMP counters.
+  * ipv4/ip.c: added SNMP counters.
+  * ipv4/ip_frag.c: added SNMP counters.
+
+  2006-08-08 Christiaan Simons
+  * etharp.{c,h}: added etharp_find_addr() to read
+    (stable) ethernet/IP address pair from ARP table
+
+  2006-07-14 Christiaan Simons
+  * mib_structs.c: added
+  * include/lwip/snmp_structs.h: added
+  * netif.{c,h}, netif/ethernetif.c: added SNMP statistics to netif struct
+
+  2006-07-06 Christiaan Simons
+  * snmp/asn1_{enc,dec}.c added
+  * snmp/mib2.c added
+  * snmp/msg_{in,out}.c added
+  * include/lwip/snmp_asn1.h added
+  * include/lwip/snmp_msg.h added
+  * doc/snmp_agent.txt added
+
+  2006-03-29 Christiaan Simons
+  * inet.c, inet.h: Added platform byteswap support.
+    Added LWIP_PLATFORM_BYTESWAP define (defaults to 0) and
+    optional LWIP_PLATFORM_HTONS(), LWIP_PLATFORM_HTONL() macros.
+
+  ++ Bug fixes:
+
+  2006-11-30 Christiaan Simons
+  * dhcp.c: Fixed false triggers of request_timeout.
+
+  2006-11-28 Christiaan Simons
+  * netif.c: In netif_add() fixed missing clear of ip_addr, netmask, gw and flags.
+
+  2006-10-11 Christiaan Simons
+  * api_lib.c etharp.c, ip.c, memp.c, stats.c, sys.{c,h} tcp.h:
+    Partially accepted patch #5449 for ANSI C compatibility / build fixes.
+  * ipv4/lwip/ip.h ipv6/lwip/ip.h: Corrected UDP-Lite protocol
+    identifier from 170 to 136 (bug #17574).
+
+  2006-10-10 Christiaan Simons
+  * api_msg.c: Fixed Nagle algorithm as reported by Bob Grice.
+
+  2006-08-17 Christiaan Simons
+  * udp.c: Fixed bug #17200, added check for broadcast
+    destinations for PCBs bound to a unicast address.
+
+  2006-08-07 Christiaan Simons
+  * api_msg.c: Flushing TCP output in do_close() (bug #15926).
+
+  2006-06-27 Christiaan Simons
+  * api_msg.c: Applied patch for cold case (bug #11135).
+    In accept_function() ensure newconn->callback is always initialized.
+
+  2006-06-15 Christiaan Simons
+  * mem.h: added MEM_SIZE_F alias to fix an ancient cold case (bug #1748),
+    facilitate printing of mem_size_t and u16_t statistics.
+
+  2006-06-14 Christiaan Simons
+  * api_msg.c: Applied patch #5146 to handle allocation failures
+    in accept() by Kevin Lawson.
+
+  2006-05-26 Christiaan Simons
+  * api_lib.c: Removed conn->sem creation and destruction 
+    from netconn_write() and added sys_sem_new to netconn_new_*.
+
+(STABLE-1_1_1)
+
+  2006-03-03  Christiaan Simons
+  * ipv4/ip_frag.c: Added bound-checking assertions on ip_reassbitmap
+    access and added pbuf_alloc() return value checks.
+
+  2006-01-01  Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * tcp_{in,out}.c, tcp_out.c: Removed 'even sndbuf' fix in TCP, which is
+    now handled by the checksum routine properly.
+
+  2006-02-27  Leon Woestenberg <leon.woestenberg@xxxxxxx>
+   * pbuf.c: Fix alignment; pbuf_init() would not work unless
+     pbuf_pool_memory[] was properly aligned. (Patch by Curt McDowell.)
+
+  2005-12-20  Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * tcp.c: Remove PCBs which stay in LAST_ACK state too long. Patch
+    submitted by Mitrani Hiroshi.
+
+  2005-12-15  Christiaan Simons
+  * inet.c: Disabled the added summing routine to preserve code space.
+
+  2005-12-14  Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * tcp_in.c: Duplicate FIN ACK race condition fix by Kelvin Lawson.
+    Added Curt McDowell's optimized checksumming routine for future
+    inclusion. Need to create test case for unaliged, aligned, odd,
+    even length combination of cases on various endianess machines.
+
+  2005-12-09  Christiaan Simons
+  * inet.c: Rewrote standard checksum routine in proper portable C.
+
+  2005-11-25  Christiaan Simons
+  * udp.c tcp.c: Removed SO_REUSE hack. Should reside in socket code only.
+  * *.c: introduced cc.h LWIP_DEBUG formatters matching the u16_t, s16_t,
+    u32_t, s32_t typedefs. This solves most debug word-length assumes.
+
+  2005-07-17 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * inet.c: Fixed unaligned 16-bit access in the standard checksum
+    routine by Peter Jolasson.
+  * slipif.c: Fixed implementation assumption of single-pbuf datagrams.
+
+  2005-02-04 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * tcp_out.c: Fixed uninitialized 'queue' referenced in memerr branch.
+  * tcp_{out|in}.c: Applied patch fixing unaligned access.
+
+  2005-01-04 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * pbuf.c: Fixed missing semicolon after LWIP_DEBUG statement.
+
+  2005-01-03 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * udp.c: UDP pcb->recv() was called even when it was NULL.
+
+(STABLE-1_1_0)
+
+  2004-12-28 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * etharp.*: Disabled multiple packets on the ARP queue.
+    This clashes with TCP queueing.
+
+  2004-11-28 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * etharp.*: Fixed race condition from ARP request to ARP timeout.
+    Halved the ARP period, doubled the period counts.
+    ETHARP_MAX_PENDING now should be at least 2. This prevents
+    the counter from reaching 0 right away (which would allow
+    too little time for ARP responses to be received).
+
+  2004-11-25 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * dhcp.c: Decline messages were not multicast but unicast.
+  * etharp.c: ETHARP_CREATE is renamed to ETHARP_TRY_HARD.
+    Do not try hard to insert arbitrary packet's source address,
+    etharp_ip_input() now calls etharp_update() without ETHARP_TRY_HARD. 
+    etharp_query() now always DOES call ETHARP_TRY_HARD so that users
+    querying an address will see it appear in the cache (DHCP could
+    suffer from this when a server invalidly gave an in-use address.)
+  * ipv4/ip_addr.h: Renamed ip_addr_maskcmp() to _netcmp() as we are
+    comparing network addresses (identifiers), not the network masks
+    themselves.
+  * ipv4/ip_addr.c: ip_addr_isbroadcast() now checks that the given
+    IP address actually belongs to the network of the given interface.
+
+  2004-11-24 Kieran Mansley <kjm25@xxxxxxxxx>
+  * tcp.c: Increment pcb->snd_buf when ACK is received in SYN_SENT state.
+
+(STABLE-1_1_0-RC1)
+
+  2004-10-16 Kieran Mansley <kjm25@xxxxxxxxx>
+  * tcp.c: Add code to tcp_recved() to send an ACK (window update) immediately,
+    even if one is already pending, if the rcv_wnd is above a threshold
+    (currently TCP_WND/2). This avoids waiting for a timer to expire to send a
+    delayed ACK in order to open the window if the stack is only receiving data.
+
+  2004-09-12 Kieran Mansley <kjm25@xxxxxxxxx>
+  * tcp*.*: Retransmit time-out handling improvement by Sam Jansen.
+
+  2004-08-20 Tony Mountifield <tony@xxxxxxxxxxxxx>
+  * etharp.c: Make sure the first pbuf queued on an ARP entry
+    is properly ref counted.
+
+  2004-07-27 Tony Mountifield <tony@xxxxxxxxxxxxx>
+  * debug.h: Added (int) cast in LWIP_DEBUGF() to avoid compiler
+    warnings about comparison.
+  * pbuf.c: Stopped compiler complaining of empty if statement
+    when LWIP_DEBUGF() empty.  Closed an unclosed comment.
+  * tcp.c: Stopped compiler complaining of empty if statement
+    when LWIP_DEBUGF() empty.
+  * ip.h Corrected IPH_TOS() macro: returns a byte, so doesn't need htons().
+  * inet.c: Added a couple of casts to quiet the compiler.
+    No need to test isascii(c) before isdigit(c) or isxdigit(c).
+
+  2004-07-22 Tony Mountifield <tony@xxxxxxxxxxxxx>
+  * inet.c: Made data types consistent in inet_ntoa().
+    Added casts for return values of checksum routines, to pacify compiler.
+  * ip_frag.c, tcp_out.c, sockets.c, pbuf.c
+    Small corrections to some debugging statements, to pacify compiler.
+
+  2004-07-21 Tony Mountifield <tony@xxxxxxxxxxxxx>
+  * etharp.c: Removed spurious semicolon and added missing end-of-comment.
+  * ethernetif.c Updated low_level_output() to match prototype for
+    netif->linkoutput and changed low_level_input() similarly for consistency.
+  * api_msg.c: Changed recv_raw() from int to u8_t, to match prototype
+    of raw_recv() in raw.h and so avoid compiler error.
+  * sockets.c: Added trivial (int) cast to keep compiler happier.
+  * ip.c, netif.c Changed debug statements to use the tidier ip4_addrN() macros.
+
+(STABLE-1_0_0)
+
+  ++ Changes:
+
+  2004-07-05 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * sockets.*: Restructured LWIP_PRIVATE_TIMEVAL. Make sure
+    your cc.h file defines this either 1 or 0. If non-defined,
+    defaults to 1.
+  * .c: Added <string.h> and <errno.h> includes where used.
+  * etharp.c: Made some array indices unsigned.
+
+  2004-06-27 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * netif.*: Added netif_set_up()/down().
+  * dhcp.c: Changes to restart program flow.
+
+  2004-05-07 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * etharp.c: In find_entry(), instead of a list traversal per candidate, do a
+    single-pass lookup for different candidates. Should exploit locality.
+
+  2004-04-29 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * tcp*.c: Cleaned up source comment documentation for Doxygen processing.
+  * opt.h: ETHARP_ALWAYS_INSERT option removed to comply with ARP RFC.
+  * etharp.c: update_arp_entry() only adds new ARP entries when adviced to by
+    the caller. This deprecates the ETHARP_ALWAYS_INSERT overrule option.
+
+  ++ Bug fixes:
+
+  2004-04-27 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * etharp.c: Applied patch of bug #8708 by Toni Mountifield with a solution
+    suggested by Timmy Brolin. Fix for 32-bit processors that cannot access
+    non-aligned 32-bit words, such as soms 32-bit TCP/IP header fields. Fix
+    is to prefix the 14-bit Ethernet headers with two padding bytes.
+
+  2004-04-23 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+  * ip_addr.c: Fix in the ip_addr_isbroadcast() check.
+  * etharp.c: Fixed the case where the packet that initiates the ARP request
+    is not queued, and gets lost. Fixed the case where the packets destination
+    address is already known; we now always queue the packet and perform an ARP
+    request.
+
+(STABLE-0_7_0)
+
+  ++ Bug fixes:
+
+  * Fixed TCP bug for SYN_SENT to ESTABLISHED state transition.
+  * Fixed TCP bug in dequeueing of FIN from out of order segment queue.
+  * Fixed two possible NULL references in rare cases.
+
+(STABLE-0_6_6)
+
+  ++ Bug fixes:
+
+  * Fixed DHCP which did not include the IP address in DECLINE messages.
+
+  ++ Changes:
+
+  * etharp.c has been hauled over a bit.
+
+(STABLE-0_6_5)
+
+  ++ Bug fixes:
+
+  * Fixed TCP bug induced by bad window resizing with unidirectional TCP traffic.
+  * Packets sent from ARP queue had invalid source hardware address.
+
+  ++ Changes:
+
+  * Pass-by ARP requests do now update the cache.
+
+  ++ New features:
+
+  * No longer dependent on ctype.h.
+  * New socket options.
+  * Raw IP pcb support.
+
+(STABLE-0_6_4)
+
+  ++ Bug fixes:
+
+  * Some debug formatters and casts fixed.
+  * Numereous fixes in PPP.
+
+  ++ Changes:
+
+  * DEBUGF now is LWIP_DEBUGF
+  * pbuf_dechain() has been re-enabled.
+  * Mentioned the changed use of CVS branches in README.
+
+(STABLE-0_6_3)
+
+  ++ Bug fixes:
+
+  * Fixed pool pbuf memory leak in pbuf_alloc().
+    Occured if not enough PBUF_POOL pbufs for a packet pbuf chain.
+    Reported by Savin Zlobec.
+
+  * PBUF_POOL chains had their tot_len field not set for non-first
+    pbufs. Fixed in pbuf_alloc().
+
+  ++ New features:
+
+  * Added PPP stack contributed by Marc Boucher
+
+  ++ Changes:
+
+  * Now drops short packets for ICMP/UDP/TCP protocols. More robust.
+
+  * ARP queueuing now queues the latest packet instead of the first.
+    This is the RFC recommended behaviour, but can be overridden in
+    lwipopts.h.
+
+(0.6.2)
+
+  ++ Bugfixes:
+
+  * TCP has been fixed to deal with the new use of the pbuf->ref
+    counter.
+
+  * DHCP dhcp_inform() crash bug fixed.
+
+  ++ Changes:
+
+  * Removed pbuf_pool_free_cache and pbuf_pool_alloc_cache. Also removed
+    pbuf_refresh(). This has sped up pbuf pool operations considerably.
+    Implemented by David Haas.
+
+(0.6.1)
+
+  ++ New features:
+
+  * The packet buffer implementation has been enhanced to support
+    zero-copy and copy-on-demand for packet buffers which have their
+    payloads in application-managed memory.
+    Implemented by David Haas.
+
+    Use PBUF_REF to make a pbuf refer to RAM. lwIP will use zero-copy
+    if an outgoing packet can be directly sent on the link, or perform
+    a copy-on-demand when necessary.
+
+    The application can safely assume the packet is sent, and the RAM
+    is available to the application directly after calling udp_send()
+    or similar function.
+
+  ++ Bugfixes:
+
+  * ARP_QUEUEING should now correctly work for all cases, including
+    PBUF_REF.
+    Implemented by Leon Woestenberg.
+
+  ++ Changes:
+
+  * IP_ADDR_ANY is no longer a NULL pointer. Instead, it is a pointer
+    to a '0.0.0.0' IP address.
+
+  * The packet buffer implementation is changed. The pbuf->ref counter
+    meaning has changed, and several pbuf functions have been
+    adapted accordingly.
+
+  * netif drivers have to be changed to set the hardware address length field
+    that must be initialized correctly by the driver (hint: 6 for Ethernet MAC).
+    See the contrib/ports/c16x cs8900 driver as a driver example.
+
+  * netif's have a dhcp field that must be initialized to NULL by the driver.
+    See the contrib/ports/c16x cs8900 driver as a driver example.
+
+(0.5.x) This file has been unmaintained up to 0.6.1. All changes are
+  logged in CVS but have not been explained here.
+
+(0.5.3) Changes since version 0.5.2
+
+  ++ Bugfixes:
+
+  * memp_malloc(MEMP_API_MSG) could fail with multiple application
+    threads because it wasn't protected by semaphores.
+
+  ++ Other changes:
+
+  * struct ip_addr now packed.
+
+  * The name of the time variable in arp.c has been changed to ctime
+    to avoid conflicts with the time() function.
+
+(0.5.2) Changes since version 0.5.1
+
+  ++ New features:
+
+  * A new TCP function, tcp_tmr(), now handles both TCP timers.
+
+  ++ Bugfixes:
+
+  * A bug in tcp_parseopt() could cause the stack to hang because of a
+    malformed TCP option.
+
+  * The address of new connections in the accept() function in the BSD
+    socket library was not handled correctly.
+
+  * pbuf_dechain() did not update the ->tot_len field of the tail.
+
+  * Aborted TCP connections were not handled correctly in all
+    situations.
+
+  ++ Other changes:
+
+  * All protocol header structs are now packed.
+
+  * The ->len field in the tcp_seg structure now counts the actual
+    amount of data, and does not add one for SYN and FIN segments.
+
+(0.5.1) Changes since version 0.5.0
+
+  ++ New features:
+
+  * Possible to run as a user process under Linux.
+
+  * Preliminary support for cross platform packed structs.
+
+  * ARP timer now implemented.
+
+  ++ Bugfixes:
+
+  * TCP output queue length was badly initialized when opening
+    connections.
+
+  * TCP delayed ACKs were not sent correctly.
+
+  * Explicit initialization of BSS segment variables.
+
+  * read() in BSD socket library could drop data.
+
+  * Problems with memory alignment.
+
+  * Situations when all TCP buffers were used could lead to
+    starvation.
+
+  * TCP MSS option wasn't parsed correctly.
+
+  * Problems with UDP checksum calculation.
+
+  * IP multicast address tests had endianess problems.
+
+  * ARP requests had wrong destination hardware address.
+
+  ++ Other changes:
+
+  * struct eth_addr changed from u16_t[3] array to u8_t[6].
+
+  * A ->linkoutput() member was added to struct netif.
+
+  * TCP and UDP ->dest_* struct members where changed to ->remote_*.
+
+  * ntoh* macros are now null definitions for big endian CPUs.
+
+(0.5.0) Changes since version 0.4.2
+
+  ++ New features:
+
+  * Redesigned operating system emulation layer to make porting easier.
+
+  * Better control over TCP output buffers.
+
+  * Documenation added.
+
+  ++ Bugfixes:
+
+  * Locking issues in buffer management.
+
+  * Bugfixes in the sequential API.
+
+  * IP forwarding could cause memory leakage. This has been fixed.
+
+  ++ Other changes:
+
+  * Directory structure somewhat changed; the core/ tree has been
+    collapsed.
+
+(0.4.2) Changes since version 0.4.1
+
+  ++ New features:
+
+  * Experimental ARP implementation added.
+
+  * Skeleton Ethernet driver added.
+
+  * Experimental BSD socket API library added.
+
+  ++ Bugfixes:
+
+  * In very intense situations, memory leakage could occur. This has
+    been fixed.
+
+  ++ Other changes:
+
+  * Variables named "data" and "code" have been renamed in order to
+    avoid name conflicts in certain compilers.
+
+  * Variable++ have in appliciable cases been translated to ++variable
+    since some compilers generate better code in the latter case.
+
+(0.4.1) Changes since version 0.4
+
+  ++ New features:
+
+  * TCP: Connection attempts time out earlier than data
+    transmissions. Nagle algorithm implemented. Push flag set on the
+    last segment in a burst.
+
+  * UDP: experimental support for UDP-Lite extensions.
+
+  ++ Bugfixes:
+
+  * TCP: out of order segments were in some cases handled incorrectly,
+    and this has now been fixed. Delayed acknowledgements was broken
+    in 0.4, has now been fixed. Binding to an address that is in use
+    now results in an error. Reset connections sometimes hung an
+    application; this has been fixed.
+
+  * Checksum calculation sometimes failed for chained pbufs with odd
+    lengths. This has been fixed.
+
+  * API: a lot of bug fixes in the API. The UDP API has been improved
+    and tested. Error reporting and handling has been
+    improved. Logical flaws and race conditions for incoming TCP
+    connections has been found and removed.
+
+  * Memory manager: alignment issues. Reallocating memory sometimes
+    failed, this has been fixed.
+
+  * Generic library: bcopy was flawed and has been fixed.
+
+  ++ Other changes:
+
+  * API: all datatypes has been changed from generic ones such as
+    ints, to specified ones such as u16_t. Functions that return
+    errors now have the correct type (err_t).
+
+  * General: A lot of code cleaned up and debugging code removed. Many
+    portability issues have been fixed.
+
+  * The license was changed; the advertising clause was removed.
+
+  * C64 port added.
+
+  * Thanks: Huge thanks go to Dagan Galarneau, Horst Garnetzke, Petri
+    Kosunen, Mikael Caleres, and Frits Wilmink for reporting and
+    fixing bugs!
+
+(0.4) Changes since version 0.3.1
+
+  * Memory management has been radically changed; instead of
+    allocating memory from a shared heap, memory for objects that are
+    rapidly allocated and deallocated is now kept in pools. Allocation
+    and deallocation from those memory pools is very fast. The shared
+    heap is still present but is used less frequently.
+
+  * The memory, memory pool, and packet buffer subsystems now support
+    4-, 2-, or 1-byte alignment.
+
+  * "Out of memory" situations are handled in a more robust way.
+
+  * Stack usage has been reduced.
+
+  * Easier configuration of lwIP parameters such as memory usage,
+    TTLs, statistics gathering, etc. All configuration parameters are
+    now kept in a single header file "lwipopts.h".
+
+  * The directory structure has been changed slightly so that all
+    architecture specific files are kept under the src/arch
+    hierarchy.
+
+  * Error propagation has been improved, both in the protocol modules
+    and in the API.
+
+  * The code for the RTXC architecture has been implemented, tested
+    and put to use.
+
+  * Bugs have been found and corrected in the TCP, UDP, IP, API, and
+    the Internet checksum modules.
+
+  * Bugs related to porting between a 32-bit and a 16-bit architecture
+    have been found and corrected.
+
+  * The license has been changed slightly to conform more with the
+    original BSD license, including the advertisement clause.
+
+(0.3.1) Changes since version 0.3
+
+  * Fix of a fatal bug in the buffer management. Pbufs with allocated
+    RAM never returned the RAM when the pbuf was deallocated.
+
+  * TCP congestion control, window updates and retransmissions did not
+    work correctly. This has now been fixed.
+
+  * Bugfixes in the API.
+
+(0.3) Changes since version 0.2
+
+  * New and improved directory structure. All include files are now
+    kept in a dedicated include/ directory.
+
+  * The API now has proper error handling. A new function,
+    netconn_err(), now returns an error code for the connection in
+    case of errors.
+
+  * Improvements in the memory management subsystem. The system now
+    keeps a pointer to the lowest free memory block. A new function,
+    mem_malloc2() tries to allocate memory once, and if it fails tries
+    to free some memory and retry the allocation.
+
+  * Much testing has been done with limited memory
+    configurations. lwIP now does a better job when overloaded.
+
+  * Some bugfixes and improvements to the buffer (pbuf) subsystem.
+
+  * Many bugfixes in the TCP code:
+
+    - Fixed a bug in tcp_close().
+
+    - The TCP receive window was incorrectly closed when out of
+      sequence segments was received. This has been fixed.
+
+    - Connections are now timed-out of the FIN-WAIT-2 state.
+
+    - The initial congestion window could in some cases be too
+      large. This has been fixed.
+
+    - The retransmission queue could in some cases be screwed up. This
+      has been fixed.
+
+    - TCP RST flag now handled correctly.
+
+    - Out of sequence data was in some cases never delivered to the
+      application. This has been fixed.
+
+    - Retransmitted segments now contain the correct acknowledgment
+      number and advertised window.
+
+    - TCP retransmission timeout backoffs are not correctly computed
+      (ala BSD). After a number of retransmissions, TCP now gives up
+      the connection.
+
+  * TCP connections now are kept on three lists, one for active
+    connections, one for listening connections, and one for
+    connections that are in TIME-WAIT. This greatly speeds up the fast
+    timeout processing for sending delayed ACKs.
+
+  * TCP now provides proper feedback to the application when a
+    connection has been successfully set up.
+
+  * More comments have been added to the code. The code has also been
+    somewhat cleaned up.
+
+(0.2) Initial public release.
diff --git a/external/badvpn_dns/lwip/CMakeLists.txt b/external/badvpn_dns/lwip/CMakeLists.txt
new file mode 100644
index 0000000..121892d
--- /dev/null
+++ b/external/badvpn_dns/lwip/CMakeLists.txt
@@ -0,0 +1,27 @@
+set(LWIP_SOURCES
+    src/core/timers.c
+    src/core/udp.c
+    src/core/memp.c
+    src/core/init.c
+    src/core/pbuf.c
+    src/core/tcp.c
+    src/core/tcp_out.c
+    src/core/sys.c
+    src/core/netif.c
+    src/core/def.c
+    src/core/mem.c
+    src/core/tcp_in.c
+    src/core/stats.c
+    src/core/inet_chksum.c
+    src/core/ipv4/icmp.c
+    src/core/ipv4/ip4.c
+    src/core/ipv4/ip4_addr.c
+    src/core/ipv4/ip_frag.c
+    src/core/ipv6/ip6.c
+    src/core/ipv6/nd6.c
+    src/core/ipv6/icmp6.c
+    src/core/ipv6/ip6_addr.c
+    src/core/ipv6/ip6_frag.c
+    custom/sys.c
+)
+badvpn_add_library(lwip "system" "" "${LWIP_SOURCES}")
diff --git a/external/badvpn_dns/lwip/COPYING b/external/badvpn_dns/lwip/COPYING
new file mode 100644
index 0000000..e23898b
--- /dev/null
+++ b/external/badvpn_dns/lwip/COPYING
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2001, 2002 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+
diff --git a/external/badvpn_dns/lwip/FILES b/external/badvpn_dns/lwip/FILES
new file mode 100644
index 0000000..6625319
--- /dev/null
+++ b/external/badvpn_dns/lwip/FILES
@@ -0,0 +1,4 @@
+src/      - The source code for the lwIP TCP/IP stack.
+doc/      - The documentation for lwIP.
+
+See also the FILES file in each subdirectory.
diff --git a/external/badvpn_dns/lwip/README b/external/badvpn_dns/lwip/README
new file mode 100644
index 0000000..a62cc4f
--- /dev/null
+++ b/external/badvpn_dns/lwip/README
@@ -0,0 +1,89 @@
+INTRODUCTION
+
+lwIP is a small independent implementation of the TCP/IP protocol
+suite that has been developed by Adam Dunkels at the Computer and
+Networks Architectures (CNA) lab at the Swedish Institute of Computer
+Science (SICS).
+
+The focus of the lwIP TCP/IP implementation is to reduce the RAM usage
+while still having a full scale TCP. This making lwIP suitable for use
+in embedded systems with tens of kilobytes of free RAM and room for
+around 40 kilobytes of code ROM.
+
+FEATURES
+
+  * IP (Internet Protocol) including packet forwarding over multiple network
+    interfaces
+  * ICMP (Internet Control Message Protocol) for network maintenance and debugging
+  * IGMP (Internet Group Management Protocol) for multicast traffic management
+  * UDP (User Datagram Protocol) including experimental UDP-lite extensions
+  * TCP (Transmission Control Protocol) with congestion control, RTT estimation
+    and fast recovery/fast retransmit
+  * Specialized raw/native API for enhanced performance
+  * Optional Berkeley-like socket API
+  * DNS (Domain names resolver)
+  * SNMP (Simple Network Management Protocol)
+  * DHCP (Dynamic Host Configuration Protocol)
+  * AUTOIP (for IPv4, conform with RFC 3927)
+  * PPP (Point-to-Point Protocol)
+  * ARP (Address Resolution Protocol) for Ethernet
+
+LICENSE
+
+lwIP is freely available under a BSD license.
+
+DEVELOPMENT
+
+lwIP has grown into an excellent TCP/IP stack for embedded devices,
+and developers using the stack often submit bug fixes, improvements,
+and additions to the stack to further increase its usefulness.
+
+Development of lwIP is hosted on Savannah, a central point for
+software development, maintenance and distribution. Everyone can
+help improve lwIP by use of Savannah's interface, CVS and the
+mailing list. A core team of developers will commit changes to the
+CVS source tree.
+
+The lwIP TCP/IP stack is maintained in the 'lwip' CVS module and
+contributions (such as platform ports) are in the 'contrib' module.
+
+See doc/savannah.txt for details on CVS server access for users and
+developers.
+
+Last night's CVS tar ball can be downloaded from:
+  http://savannah.gnu.org/cvs.backups/lwip.tar.gz [CHANGED - NEEDS FIXING]
+
+The current CVS trees are web-browsable:
+  http://savannah.nongnu.org/cgi-bin/viewcvs/lwip/lwip/
+  http://savannah.nongnu.org/cgi-bin/viewcvs/lwip/contrib/
+
+Submit patches and bugs via the lwIP project page:
+  http://savannah.nongnu.org/projects/lwip/
+
+
+DOCUMENTATION
+
+The original out-dated homepage of lwIP and Adam Dunkels' papers on
+lwIP are at the official lwIP home page:
+  http://www.sics.se/~adam/lwip/
+
+Self documentation of the source code is regularly extracted from the
+current CVS sources and is available from this web page:
+  http://www.nongnu.org/lwip/
+
+There is now a constantly growin wiki about lwIP at
+  http://lwip.wikia.com/wiki/LwIP_Wiki
+
+Also, there are mailing lists you can subscribe at
+  http://savannah.nongnu.org/mail/?group=lwip
+plus searchable archives:
+  http://lists.nongnu.org/archive/html/lwip-users/
+  http://lists.nongnu.org/archive/html/lwip-devel/
+
+Reading Adam's papers, the files in docs/, browsing the source code
+documentation and browsing the mailing list archives is a good way to
+become familiar with the design of lwIP.
+
+Adam Dunkels <adam@xxxxxxx>
+Leon Woestenberg <leon.woestenberg@xxxxxxx>
+
diff --git a/external/badvpn_dns/lwip/UPGRADING b/external/badvpn_dns/lwip/UPGRADING
new file mode 100644
index 0000000..6501107
--- /dev/null
+++ b/external/badvpn_dns/lwip/UPGRADING
@@ -0,0 +1,144 @@
+This file lists major changes between release versions that require
+ports or applications to be changed. Use it to update a port or an
+application written for an older version of lwIP to correctly work
+with newer versions.
+
+
+(CVS HEAD)
+
+  * [Enter new changes just after this line - do not remove this line]
+
+  ++ Application changes:
+
+  * Replaced struct ip_addr by typedef ip_addr_t (struct ip_addr is kept for
+    compatibility to old applications, but will be removed in the future).
+
+  * Renamed mem_realloc() to mem_trim() to prevent confusion with realloc()
+
+  +++ Raw API:
+    * Changed the semantics of tcp_close() (since it was rather a
+      shutdown before): Now the application does *NOT* get any calls to the recv
+      callback (aside from NULL/closed) after calling tcp_close()
+
+    * When calling tcp_abort() from a raw API TCP callback function,
+      make sure you return ERR_ABRT to prevent accessing unallocated memory.
+      (ERR_ABRT now means the applicaiton has called tcp_abort!)
+
+  +++ Netconn API:
+    * Changed netconn_receive() and netconn_accept() to return
+      err_t, not a pointer to new data/netconn.
+
+  +++ Socket API:
+    * LWIP_SO_RCVTIMEO: when accept() or recv() time out, they
+      now set errno to EWOULDBLOCK/EAGAIN, not ETIMEDOUT.
+
+    * Added a minimal version of posix fctl() to have a
+      standardised way to set O_NONBLOCK for nonblocking sockets.
+
+  +++ all APIs:
+    * correctly implemented SO(F)_REUSEADDR
+
+  ++ Port changes
+
+  +++ new files:
+
+    * Added 4 new files: def.c, timers.c, timers.h, tcp_impl.h:
+
+    * Moved stack-internal parts of tcp.h to tcp_impl.h, tcp.h now only contains
+      the actual application programmer's API
+  
+    * Separated timer implementation from sys.h/.c, moved to timers.h/.c;
+      Added timer implementation for NO_SYS==1, set NO_SYS_NO_TIMERS==1 if you
+      still want to use your own timer implementation for NO_SYS==0 (as before).
+
+  +++ sys layer:
+
+    * Converted mbox- and semaphore-functions to take pointers to sys_mbox_t/
+      sys_sem_t;
+
+    * Converted sys_mbox_new/sys_sem_new to take pointers and return err_t;
+
+    * Added Mutex concept in sys_arch (define LWIP_COMPAT_MUTEX to let sys.h use
+      binary semaphores instead of mutexes - as before)
+
+  +++ new options:
+
+     * Don't waste memory when chaining segments, added option TCP_OVERSIZE to
+       prevent creating many small pbufs when calling tcp_write with many small
+       blocks of data. Instead, pbufs are allocated larger than needed and the
+       space is used for later calls to tcp_write.
+
+     * Added LWIP_NETIF_TX_SINGLE_PBUF to always copy to try to create single pbufs
+       in tcp_write/udp_send.
+
+    * Added an additional option LWIP_ETHERNET to support ethernet without ARP
+      (necessary for pure PPPoE)
+
+    * Add MEMP_SEPARATE_POOLS to place memory pools in separate arrays. This may
+      be used to place these pools into user-defined memory by using external
+      declaration.
+
+    * Added TCP_SNDQUEUELOWAT corresponding to TCP_SNDLOWAT
+
+  +++ new pools:
+
+     * Netdb uses a memp pool for allocating memory when getaddrinfo() is called,
+       so MEMP_NUM_NETDB has to be set accordingly.
+
+     * DNS_LOCAL_HOSTLIST_IS_DYNAMIC uses a memp pool instead of the heap, so
+       MEMP_NUM_LOCALHOSTLIST has to be set accordingly.
+
+     * Snmp-agent uses a memp pools instead of the heap, so MEMP_NUM_SNMP_* have
+       to be set accordingly.
+
+     * PPPoE uses a MEMP pool instead of the heap, so MEMP_NUM_PPPOE_INTERFACES
+       has to be set accordingly
+
+  * Integrated loopif into netif.c - loopif does not have to be created by the
+    port any more, just define LWIP_HAVE_LOOPIF to 1.
+
+  * Added define LWIP_RAND() for lwip-wide randomization (needs to be defined
+    in cc.h, e.g. used by igmp)
+
+  * Added printf-formatter X8_F to printf u8_t as hex
+
+  * The heap now may be moved to user-defined memory by defining
+    LWIP_RAM_HEAP_POINTER as a void pointer to that memory's address
+
+  * added autoip_set_struct() and dhcp_set_struct() to let autoip and dhcp work
+    with user-allocated structs instead of calling mem_malloc
+
+  * Added const char* name to mem- and memp-stats for easier debugging.
+
+  * Calculate the TCP/UDP checksum while copying to only fetch data once:
+    Define LWIP_CHKSUM_COPY to a memcpy-like function that returns the checksum
+
+  * Added SO_REUSE_RXTOALL to pass received UDP broadcast/multicast packets to
+    more than one pcb.
+
+  * Changed the semantics of ARP_QUEUEING==0: ARP_QUEUEING now cannot be turned
+    off any more, if this is set to 0, only one packet (the most recent one) is
+    queued (like demanded by RFC 1122).
+
+  
+  ++ Major bugfixes/improvements
+
+  * Implemented tcp_shutdown() to only shut down one end of a connection
+  * Implemented shutdown() at socket- and netconn-level
+  * Added errorset support to select() + improved select speed overhead
+  * Merged pppd to v2.3.11 (including some backported bugfixes from 2.4.x)
+  * Added timer implementation for NO_SYS==1 (may be disabled with NO_SYS_NO_TIMERS==1
+  * Use macros defined in ip_addr.h to work with IP addresses
+  * Implemented many nonblocking socket/netconn functions
+  * Fixed ARP input processing: only add a new entry if a request was directed as us
+  * mem_realloc() to mem_trim() to prevent confusion with realloc()
+  * Some improvements for AutoIP (don't route/forward link-local addresses, don't break
+    existing connections when assigning a routable address)
+  * Correctly handle remote side overrunning our rcv_wnd in ooseq case
+  * Removed packing from ip_addr_t, the packed version is now only used in protocol headers
+  * Corrected PBUF_POOL_BUFSIZE for ports where ETH_PAD_SIZE > 0
+  * Added support for static ARP table entries
+
+(STABLE-1.3.2)
+
+  * initial version of this file
diff --git a/external/badvpn_dns/lwip/custom/arch/cc.h b/external/badvpn_dns/lwip/custom/arch/cc.h
new file mode 100644
index 0000000..653a2e2
--- /dev/null
+++ b/external/badvpn_dns/lwip/custom/arch/cc.h
@@ -0,0 +1,96 @@
+/**
+ * @file cc.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 LWIP_CUSTOM_CC_H
+#define LWIP_CUSTOM_CC_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/packed.h>
+#include <misc/print_macros.h>
+#include <misc/byteorder.h>
+#include <base/BLog.h>
+
+#define u8_t uint8_t
+#define s8_t int8_t
+#define u16_t uint16_t
+#define s16_t int16_t
+#define u32_t uint32_t
+#define s32_t int32_t
+#define mem_ptr_t uintptr_t
+
+#define PACK_STRUCT_BEGIN B_START_PACKED
+#define PACK_STRUCT_END B_END_PACKED
+#define PACK_STRUCT_STRUCT B_PACKED
+
+#define LWIP_PLATFORM_DIAG(x) { if (BLog_WouldLog(BLOG_CHANNEL_lwip, BLOG_INFO)) { BLog_Begin(); BLog_Append x; BLog_Finish(BLOG_CHANNEL_lwip, BLOG_INFO); } }
+#define LWIP_PLATFORM_ASSERT(x) { fprintf(stderr, "%s: lwip assertion failure: %s\n", __FUNCTION__, (x)); abort(); }
+
+#define U16_F PRIu16
+#define S16_F PRId16
+#define X16_F PRIx16
+#define U32_F PRIu32
+#define S32_F PRId32
+#define X32_F PRIx32
+#define SZT_F "zu"
+
+#define LWIP_PLATFORM_BYTESWAP 1
+#define LWIP_PLATFORM_HTONS(x) hton16(x)
+#define LWIP_PLATFORM_HTONL(x) hton32(x)
+
+#define LWIP_RAND() ( \
+    (((uint32_t)(rand() & 0xFF)) << 24) | \
+    (((uint32_t)(rand() & 0xFF)) << 16) | \
+    (((uint32_t)(rand() & 0xFF)) << 8) | \
+    (((uint32_t)(rand() & 0xFF)) << 0) \
+)
+
+// for BYTE_ORDER
+#if defined(BADVPN_USE_WINAPI) && !defined(_MSC_VER)
+    #include <sys/param.h>
+#elif defined(BADVPN_LINUX)
+    #include <endian.h>
+#elif defined(BADVPN_FREEBSD)
+    #include <machine/endian.h>
+#else
+    #define LITTLE_ENDIAN 1234
+    #define BIG_ENDIAN 4321
+    #if defined(BADVPN_LITTLE_ENDIAN)
+        #define BYTE_ORDER LITTLE_ENDIAN
+    #else
+        #define BYTE_ORDER BIG_ENDIAN
+    #endif
+#endif
+
+#endif
diff --git a/external/badvpn_dns/lwip/custom/arch/perf.h b/external/badvpn_dns/lwip/custom/arch/perf.h
new file mode 100644
index 0000000..09c9d47
--- /dev/null
+++ b/external/badvpn_dns/lwip/custom/arch/perf.h
@@ -0,0 +1,36 @@
+/**
+ * @file perf.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 LWIP_CUSTOM_PERF_H
+#define LWIP_CUSTOM_PERF_H
+
+#define PERF_START
+#define PERF_STOP(x)
+
+#endif
diff --git a/external/badvpn_dns/lwip/custom/lwipopts.h b/external/badvpn_dns/lwip/custom/lwipopts.h
new file mode 100644
index 0000000..64e03ec
--- /dev/null
+++ b/external/badvpn_dns/lwip/custom/lwipopts.h
@@ -0,0 +1,70 @@
+/**
+ * @file lwipopts.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 LWIP_CUSTOM_LWIPOPTS_H
+#define LWIP_CUSTOM_LWIPOPTS_H
+
+#define NO_SYS 1
+#define MEM_ALIGNMENT 4
+
+#define LWIP_ARP 0
+#define ARP_QUEUEING 0
+#define IP_FORWARD 0
+#define LWIP_ICMP 1
+#define LWIP_RAW 0
+#define LWIP_DHCP 0
+#define LWIP_AUTOIP 0
+#define LWIP_SNMP 0
+#define LWIP_IGMP 0
+#define LWIP_DNS 0
+#define LWIP_UDP 0
+#define LWIP_UDPLITE 0
+#define LWIP_TCP 1
+#define LWIP_CALLBACK_API 1
+#define LWIP_NETIF_API 0
+#define LWIP_NETIF_LOOPBACK 0
+#define LWIP_HAVE_LOOPIF 0
+#define LWIP_HAVE_SLIPIF 0
+#define LWIP_NETCONN 0
+#define LWIP_SOCKET 0
+#define PPP_SUPPORT 0
+#define LWIP_IPV6 1
+#define LWIP_IPV6_MLD 0
+#define LWIP_IPV6_AUTOCONFIG 0
+
+#define MEMP_NUM_TCP_PCB_LISTEN 16
+#define MEMP_NUM_TCP_PCB 1024
+#define TCP_MSS 1460
+#define TCP_SND_BUF 16384
+#define TCP_SND_QUEUELEN (4 * (TCP_SND_BUF)/(TCP_MSS))
+
+#define MEM_LIBC_MALLOC 1
+#define MEMP_MEM_MALLOC 1
+
+#endif
diff --git a/external/badvpn_dns/lwip/custom/sys.c b/external/badvpn_dns/lwip/custom/sys.c
new file mode 100644
index 0000000..efd1455
--- /dev/null
+++ b/external/badvpn_dns/lwip/custom/sys.c
@@ -0,0 +1,37 @@
+/**
+ * @file sys.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <system/BTime.h>
+
+#include <lwip/sys.h>
+
+u32_t sys_now (void)
+{
+    return btime_gettime();
+}
diff --git a/external/badvpn_dns/lwip/doc/FILES b/external/badvpn_dns/lwip/doc/FILES
new file mode 100644
index 0000000..05d356f
--- /dev/null
+++ b/external/badvpn_dns/lwip/doc/FILES
@@ -0,0 +1,6 @@
+savannah.txt   - How to obtain the current development source code.
+contrib.txt    - How to contribute to lwIP as a developer.
+rawapi.txt     - The documentation for the core API of lwIP.
+                 Also provides an overview about the other APIs and multithreading.
+snmp_agent.txt - The documentation for the lwIP SNMP agent.
+sys_arch.txt   - The documentation for a system abstraction layer of lwIP.
diff --git a/external/badvpn_dns/lwip/doc/contrib.txt b/external/badvpn_dns/lwip/doc/contrib.txt
new file mode 100644
index 0000000..39596fc
--- /dev/null
+++ b/external/badvpn_dns/lwip/doc/contrib.txt
@@ -0,0 +1,63 @@
+1 Introduction
+
+This document describes some guidelines for people participating
+in lwIP development.
+
+2 How to contribute to lwIP
+
+Here is a short list of suggestions to anybody working with lwIP and 
+trying to contribute bug reports, fixes, enhancements, platform ports etc.
+First of all as you may already know lwIP is a volunteer project so feedback
+to fixes or questions might often come late. Hopefully the bug and patch tracking 
+features of Savannah help us not lose users' input.
+
+2.1 Source code style:
+
+1. do not use tabs.
+2. indentation is two spaces per level (i.e. per tab).
+3. end debug messages with a trailing newline (\n).
+4. one space between keyword and opening bracket.
+5. no space between function and opening bracket.
+6. one space and no newline before opening curly braces of a block.
+7. closing curly brace on a single line.
+8. spaces surrounding assignment and comparisons.
+9. don't initialize static and/or global variables to zero, the compiler takes care of that.
+10. use current source code style as further reference.
+
+2.2 Source code documentation style:
+
+1. JavaDoc compliant and Doxygen compatible.
+2. Function documentation above functions in .c files, not .h files.
+   (This forces you to synchronize documentation and implementation.)
+3. Use current documentation style as further reference.
+ 
+2.3 Bug reports and patches:
+
+1. Make sure you are reporting bugs or send patches against the latest
+   sources. (From the latest release and/or the current CVS sources.)
+2. If you think you found a bug make sure it's not already filed in the
+   bugtracker at Savannah.
+3. If you have a fix put the patch on Savannah. If it is a patch that affects
+   both core and arch specific stuff please separate them so that the core can
+   be applied separately while leaving the other patch 'open'. The prefered way
+   is to NOT touch archs you can't test and let maintainers take care of them.
+   This is a good way to see if they are used at all - the same goes for unix
+   netifs except tapif.
+4. Do not file a bug and post a fix to it to the patch area. Either a bug report
+   or a patch will be enough.
+   If you correct an existing bug then attach the patch to the bug rather than creating a new entry in the patch area.
+5. Trivial patches (compiler warning, indentation and spelling fixes or anything obvious which takes a line or two)
+   can go to the lwip-users list. This is still the fastest way of interaction and the list is not so crowded
+   as to allow for loss of fixes. Putting bugs on Savannah and subsequently closing them is too much an overhead
+   for reporting a compiler warning fix.
+6. Patches should be specific to a single change or to related changes.Do not mix bugfixes with spelling and other
+   trivial fixes unless the bugfix is trivial too.Do not reorganize code and rename identifiers in the same patch you
+   change behaviour if not necessary.A patch is easier to read and understand if it's to the point and short than
+   if it's not to the point and long :) so the chances for it to be applied are greater. 
+
+2.4 Platform porters:
+
+1. If you have ported lwIP to a platform (an OS, a uC/processor or a combination of these) and
+   you think it could benefit others[1] you might want discuss this on the mailing list. You
+   can also ask for CVS access to submit and maintain your port in the contrib CVS module.
+   
\ No newline at end of file
diff --git a/external/badvpn_dns/lwip/doc/rawapi.txt b/external/badvpn_dns/lwip/doc/rawapi.txt
new file mode 100644
index 0000000..8c19030
--- /dev/null
+++ b/external/badvpn_dns/lwip/doc/rawapi.txt
@@ -0,0 +1,511 @@
+Raw TCP/IP interface for lwIP
+
+Authors: Adam Dunkels, Leon Woestenberg, Christiaan Simons
+
+lwIP provides three Application Program's Interfaces (APIs) for programs
+to use for communication with the TCP/IP code:
+* low-level "core" / "callback" or "raw" API.
+* higher-level "sequential" API.
+* BSD-style socket API.
+
+The sequential API provides a way for ordinary, sequential, programs
+to use the lwIP stack. It is quite similar to the BSD socket API. The
+model of execution is based on the blocking open-read-write-close
+paradigm. Since the TCP/IP stack is event based by nature, the TCP/IP
+code and the application program must reside in different execution
+contexts (threads).
+
+The socket API is a compatibility API for existing applications,
+currently it is built on top of the sequential API. It is meant to
+provide all functions needed to run socket API applications running
+on other platforms (e.g. unix / windows etc.). However, due to limitations
+in the specification of this API, there might be incompatibilities
+that require small modifications of existing programs.
+
+** Threading
+
+lwIP started targeting single-threaded environments. When adding multi-
+threading support, instead of making the core thread-safe, another
+approach was chosen: there is one main thread running the lwIP core
+(also known as the "tcpip_thread"). The raw API may only be used from
+this thread! Application threads using the sequential- or socket API
+communicate with this main thread through message passing.
+
+      As such, the list of functions that may be called from
+      other threads or an ISR is very limited! Only functions
+      from these API header files are thread-safe:
+      - api.h
+      - netbuf.h
+      - netdb.h
+      - netifapi.h
+      - sockets.h
+      - sys.h
+
+      Additionaly, memory (de-)allocation functions may be
+      called from multiple threads (not ISR!) with NO_SYS=0
+      since they are protected by SYS_LIGHTWEIGHT_PROT and/or
+      semaphores.
+
+      Only since 1.3.0, if SYS_LIGHTWEIGHT_PROT is set to 1
+      and LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT is set to 1,
+      pbuf_free() may also be called from another thread or
+      an ISR (since only then, mem_free - for PBUF_RAM - may
+      be called from an ISR: otherwise, the HEAP is only
+      protected by semaphores).
+      
+
+** The remainder of this document discusses the "raw" API. **
+
+The raw TCP/IP interface allows the application program to integrate
+better with the TCP/IP code. Program execution is event based by
+having callback functions being called from within the TCP/IP
+code. The TCP/IP code and the application program both run in the same
+thread. The sequential API has a much higher overhead and is not very
+well suited for small systems since it forces a multithreaded paradigm
+on the application.
+
+The raw TCP/IP interface is not only faster in terms of code execution
+time but is also less memory intensive. The drawback is that program
+development is somewhat harder and application programs written for
+the raw TCP/IP interface are more difficult to understand. Still, this
+is the preferred way of writing applications that should be small in
+code size and memory usage.
+
+Both APIs can be used simultaneously by different application
+programs. In fact, the sequential API is implemented as an application
+program using the raw TCP/IP interface.
+
+--- Callbacks
+
+Program execution is driven by callbacks. Each callback is an ordinary
+C function that is called from within the TCP/IP code. Every callback
+function is passed the current TCP or UDP connection state as an
+argument. Also, in order to be able to keep program specific state,
+the callback functions are called with a program specified argument
+that is independent of the TCP/IP state.
+
+The function for setting the application connection state is:
+
+- void tcp_arg(struct tcp_pcb *pcb, void *arg)
+
+  Specifies the program specific state that should be passed to all
+  other callback functions. The "pcb" argument is the current TCP
+  connection control block, and the "arg" argument is the argument
+  that will be passed to the callbacks.
+
+  
+--- TCP connection setup
+
+The functions used for setting up connections is similar to that of
+the sequential API and of the BSD socket API. A new TCP connection
+identifier (i.e., a protocol control block - PCB) is created with the
+tcp_new() function. This PCB can then be either set to listen for new
+incoming connections or be explicitly connected to another host.
+
+- struct tcp_pcb *tcp_new(void)
+
+  Creates a new connection identifier (PCB). If memory is not
+  available for creating the new pcb, NULL is returned.
+
+- err_t tcp_bind(struct tcp_pcb *pcb, ip_addr_t *ipaddr,
+                 u16_t port)
+
+  Binds the pcb to a local IP address and port number. The IP address
+  can be specified as IP_ADDR_ANY in order to bind the connection to
+  all local IP addresses.
+
+  If another connection is bound to the same port, the function will
+  return ERR_USE, otherwise ERR_OK is returned.
+
+- struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)
+
+  Commands a pcb to start listening for incoming connections. When an
+  incoming connection is accepted, the function specified with the
+  tcp_accept() function will be called. The pcb will have to be bound
+  to a local port with the tcp_bind() function.
+
+  The tcp_listen() function returns a new connection identifier, and
+  the one passed as an argument to the function will be
+  deallocated. The reason for this behavior is that less memory is
+  needed for a connection that is listening, so tcp_listen() will
+  reclaim the memory needed for the original connection and allocate a
+  new smaller memory block for the listening connection.
+
+  tcp_listen() may return NULL if no memory was available for the
+  listening connection. If so, the memory associated with the pcb
+  passed as an argument to tcp_listen() will not be deallocated.
+
+- struct tcp_pcb *tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog)
+
+  Same as tcp_listen, but limits the number of outstanding connections
+  in the listen queue to the value specified by the backlog argument.
+  To use it, your need to set TCP_LISTEN_BACKLOG=1 in your lwipopts.h.
+
+- void tcp_accepted(struct tcp_pcb *pcb)
+
+  Inform lwIP that an incoming connection has been accepted. This would
+  usually be called from the accept callback. This allows lwIP to perform
+  housekeeping tasks, such as allowing further incoming connections to be
+  queued in the listen backlog.
+  ATTENTION: the PCB passed in must be the listening pcb, not the pcb passed
+  into the accept callback!
+
+- void tcp_accept(struct tcp_pcb *pcb,
+                  err_t (* accept)(void *arg, struct tcp_pcb *newpcb,
+                                   err_t err))
+
+  Specified the callback function that should be called when a new
+  connection arrives on a listening connection.
+
+- err_t tcp_connect(struct tcp_pcb *pcb, ip_addr_t *ipaddr,
+                    u16_t port, err_t (* connected)(void *arg,
+                                                    struct tcp_pcb *tpcb,
+                                                    err_t err));
+
+  Sets up the pcb to connect to the remote host and sends the
+  initial SYN segment which opens the connection. 
+
+  The tcp_connect() function returns immediately; it does not wait for
+  the connection to be properly setup. Instead, it will call the
+  function specified as the fourth argument (the "connected" argument)
+  when the connection is established. If the connection could not be
+  properly established, either because the other host refused the
+  connection or because the other host didn't answer, the "err"
+  callback function of this pcb (registered with tcp_err, see below)
+  will be called.
+
+  The tcp_connect() function can return ERR_MEM if no memory is
+  available for enqueueing the SYN segment. If the SYN indeed was
+  enqueued successfully, the tcp_connect() function returns ERR_OK.
+
+
+--- Sending TCP data
+
+TCP data is sent by enqueueing the data with a call to
+tcp_write(). When the data is successfully transmitted to the remote
+host, the application will be notified with a call to a specified
+callback function.
+
+- err_t tcp_write(struct tcp_pcb *pcb, const void *dataptr, u16_t len,
+                  u8_t apiflags)
+
+  Enqueues the data pointed to by the argument dataptr. The length of
+  the data is passed as the len parameter. The apiflags can be one or more of:
+  - TCP_WRITE_FLAG_COPY: indicates whether the new memory should be allocated
+    for the data to be copied into. If this flag is not given, no new memory
+    should be allocated and the data should only be referenced by pointer. This
+    also means that the memory behind dataptr must not change until the data is
+    ACKed by the remote host
+  - TCP_WRITE_FLAG_MORE: indicates that more data follows. If this is given,
+    the PSH flag is set in the last segment created by this call to tcp_write.
+    If this flag is given, the PSH flag is not set.
+
+  The tcp_write() function will fail and return ERR_MEM if the length
+  of the data exceeds the current send buffer size or if the length of
+  the queue of outgoing segment is larger than the upper limit defined
+  in lwipopts.h. The number of bytes available in the output queue can
+  be retrieved with the tcp_sndbuf() function.
+
+  The proper way to use this function is to call the function with at
+  most tcp_sndbuf() bytes of data. If the function returns ERR_MEM,
+  the application should wait until some of the currently enqueued
+  data has been successfully received by the other host and try again.
+
+- void tcp_sent(struct tcp_pcb *pcb,
+                err_t (* sent)(void *arg, struct tcp_pcb *tpcb,
+                u16_t len))
+
+  Specifies the callback function that should be called when data has
+  successfully been received (i.e., acknowledged) by the remote
+  host. The len argument passed to the callback function gives the
+  amount bytes that was acknowledged by the last acknowledgment.
+
+  
+--- Receiving TCP data
+
+TCP data reception is callback based - an application specified
+callback function is called when new data arrives. When the
+application has taken the data, it has to call the tcp_recved()
+function to indicate that TCP can advertise increase the receive
+window.
+
+- void tcp_recv(struct tcp_pcb *pcb,
+                err_t (* recv)(void *arg, struct tcp_pcb *tpcb,
+                               struct pbuf *p, err_t err))
+
+  Sets the callback function that will be called when new data
+  arrives. The callback function will be passed a NULL pbuf to
+  indicate that the remote host has closed the connection. If
+  there are no errors and the callback function is to return
+  ERR_OK, then it must free the pbuf. Otherwise, it must not
+  free the pbuf so that lwIP core code can store it.
+
+- void tcp_recved(struct tcp_pcb *pcb, u16_t len)
+
+  Must be called when the application has received the data. The len
+  argument indicates the length of the received data.
+
+
+--- Application polling
+
+When a connection is idle (i.e., no data is either transmitted or
+received), lwIP will repeatedly poll the application by calling a
+specified callback function. This can be used either as a watchdog
+timer for killing connections that have stayed idle for too long, or
+as a method of waiting for memory to become available. For instance,
+if a call to tcp_write() has failed because memory wasn't available,
+the application may use the polling functionality to call tcp_write()
+again when the connection has been idle for a while.
+
+- void tcp_poll(struct tcp_pcb *pcb, 
+                err_t (* poll)(void *arg, struct tcp_pcb *tpcb),
+                u8_t interval)
+
+  Specifies the polling interval and the callback function that should
+  be called to poll the application. The interval is specified in
+  number of TCP coarse grained timer shots, which typically occurs
+  twice a second. An interval of 10 means that the application would
+  be polled every 5 seconds.
+
+
+--- Closing and aborting connections
+
+- err_t tcp_close(struct tcp_pcb *pcb)
+
+  Closes the connection. The function may return ERR_MEM if no memory
+  was available for closing the connection. If so, the application
+  should wait and try again either by using the acknowledgment
+  callback or the polling functionality. If the close succeeds, the
+  function returns ERR_OK.
+
+  The pcb is deallocated by the TCP code after a call to tcp_close(). 
+
+- void tcp_abort(struct tcp_pcb *pcb)
+
+  Aborts the connection by sending a RST (reset) segment to the remote
+  host. The pcb is deallocated. This function never fails.
+
+  ATTENTION: When calling this from one of the TCP callbacks, make
+  sure you always return ERR_ABRT (and never return ERR_ABRT otherwise
+  or you will risk accessing deallocated memory or memory leaks!
+
+
+If a connection is aborted because of an error, the application is
+alerted of this event by the err callback. Errors that might abort a
+connection are when there is a shortage of memory. The callback
+function to be called is set using the tcp_err() function.
+
+- void tcp_err(struct tcp_pcb *pcb, void (* err)(void *arg,
+       err_t err))
+
+  The error callback function does not get the pcb passed to it as a
+  parameter since the pcb may already have been deallocated.
+
+
+--- Lower layer TCP interface
+
+TCP provides a simple interface to the lower layers of the
+system. During system initialization, the function tcp_init() has
+to be called before any other TCP function is called. When the system
+is running, the two timer functions tcp_fasttmr() and tcp_slowtmr()
+must be called with regular intervals. The tcp_fasttmr() should be
+called every TCP_FAST_INTERVAL milliseconds (defined in tcp.h) and
+tcp_slowtmr() should be called every TCP_SLOW_INTERVAL milliseconds. 
+
+
+--- UDP interface
+
+The UDP interface is similar to that of TCP, but due to the lower
+level of complexity of UDP, the interface is significantly simpler.
+
+- struct udp_pcb *udp_new(void)
+
+  Creates a new UDP pcb which can be used for UDP communication. The
+  pcb is not active until it has either been bound to a local address
+  or connected to a remote address.
+
+- void udp_remove(struct udp_pcb *pcb)
+
+  Removes and deallocates the pcb.  
+  
+- err_t udp_bind(struct udp_pcb *pcb, ip_addr_t *ipaddr,
+                 u16_t port)
+
+  Binds the pcb to a local address. The IP-address argument "ipaddr"
+  can be IP_ADDR_ANY to indicate that it should listen to any local IP
+  address. The function currently always return ERR_OK.
+
+- err_t udp_connect(struct udp_pcb *pcb, ip_addr_t *ipaddr,
+                    u16_t port)
+
+  Sets the remote end of the pcb. This function does not generate any
+  network traffic, but only set the remote address of the pcb.
+
+- err_t udp_disconnect(struct udp_pcb *pcb)
+
+  Remove the remote end of the pcb. This function does not generate
+  any network traffic, but only removes the remote address of the pcb.
+
+- err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
+
+  Sends the pbuf p. The pbuf is not deallocated.
+
+- void udp_recv(struct udp_pcb *pcb,
+                void (* recv)(void *arg, struct udp_pcb *upcb,
+                                         struct pbuf *p,
+                                         ip_addr_t *addr,
+                                         u16_t port),
+                              void *recv_arg)
+
+  Specifies a callback function that should be called when a UDP
+  datagram is received.
+  
+
+--- System initalization
+
+A truly complete and generic sequence for initializing the lwip stack
+cannot be given because it depends on the build configuration (lwipopts.h)
+and additional initializations for your runtime environment (e.g. timers).
+
+We can give you some idea on how to proceed when using the raw API.
+We assume a configuration using a single Ethernet netif and the
+UDP and TCP transport layers, IPv4 and the DHCP client.
+
+Call these functions in the order of appearance:
+
+- stats_init()
+
+  Clears the structure where runtime statistics are gathered.
+
+- sys_init()
+  
+  Not of much use since we set the NO_SYS 1 option in lwipopts.h,
+  to be called for easy configuration changes.
+
+- mem_init()
+
+  Initializes the dynamic memory heap defined by MEM_SIZE.
+
+- memp_init()
+
+  Initializes the memory pools defined by MEMP_NUM_x.
+
+- pbuf_init()
+
+  Initializes the pbuf memory pool defined by PBUF_POOL_SIZE.
+  
+- etharp_init()
+
+  Initializes the ARP table and queue.
+  Note: you must call etharp_tmr at a ARP_TMR_INTERVAL (5 seconds) regular interval
+  after this initialization.
+
+- ip_init()
+
+  Doesn't do much, it should be called to handle future changes.
+
+- udp_init()
+
+  Clears the UDP PCB list.
+
+- tcp_init()
+
+  Clears the TCP PCB list and clears some internal TCP timers.
+  Note: you must call tcp_fasttmr() and tcp_slowtmr() at the
+  predefined regular intervals after this initialization. 
+  
+- netif_add(struct netif *netif, ip_addr_t *ipaddr,
+            ip_addr_t *netmask, ip_addr_t *gw,
+            void *state, err_t (* init)(struct netif *netif),
+            err_t (* input)(struct pbuf *p, struct netif *netif))
+
+  Adds your network interface to the netif_list. Allocate a struct
+  netif and pass a pointer to this structure as the first argument.
+  Give pointers to cleared ip_addr structures when using DHCP,
+  or fill them with sane numbers otherwise. The state pointer may be NULL.
+
+  The init function pointer must point to a initialization function for
+  your ethernet netif interface. The following code illustrates it's use.
+  
+  err_t netif_if_init(struct netif *netif)
+  {
+    u8_t i;
+    
+    for(i = 0; i < ETHARP_HWADDR_LEN; i++) netif->hwaddr[i] = some_eth_addr[i];
+    init_my_eth_device();
+    return ERR_OK;
+  }
+  
+  For ethernet drivers, the input function pointer must point to the lwip
+  function ethernet_input() declared in "netif/etharp.h". Other drivers
+  must use ip_input() declared in "lwip/ip.h".
+  
+- netif_set_default(struct netif *netif)
+
+  Registers the default network interface.
+
+- netif_set_up(struct netif *netif)
+
+  When the netif is fully configured this function must be called.
+
+- dhcp_start(struct netif *netif)
+
+  Creates a new DHCP client for this interface on the first call.
+  Note: you must call dhcp_fine_tmr() and dhcp_coarse_tmr() at
+  the predefined regular intervals after starting the client.
+  
+  You can peek in the netif->dhcp struct for the actual DHCP status.
+
+
+--- Optimalization hints
+
+The first thing you want to optimize is the lwip_standard_checksum()
+routine from src/core/inet.c. You can override this standard
+function with the #define LWIP_CHKSUM <your_checksum_routine>.
+
+There are C examples given in inet.c or you might want to
+craft an assembly function for this. RFC1071 is a good
+introduction to this subject.
+
+Other significant improvements can be made by supplying
+assembly or inline replacements for htons() and htonl()
+if you're using a little-endian architecture.
+#define LWIP_PLATFORM_BYTESWAP 1
+#define LWIP_PLATFORM_HTONS(x) <your_htons>
+#define LWIP_PLATFORM_HTONL(x) <your_htonl>
+
+Check your network interface driver if it reads at
+a higher speed than the maximum wire-speed. If the
+hardware isn't serviced frequently and fast enough
+buffer overflows are likely to occur.
+
+E.g. when using the cs8900 driver, call cs8900if_service(ethif)
+as frequently as possible. When using an RTOS let the cs8900 interrupt
+wake a high priority task that services your driver using a binary
+semaphore or event flag. Some drivers might allow additional tuning
+to match your application and network.
+
+For a production release it is recommended to set LWIP_STATS to 0.
+Note that speed performance isn't influenced much by simply setting
+high values to the memory options.
+
+For more optimization hints take a look at the lwIP wiki.
+
+--- Zero-copy MACs
+
+To achieve zero-copy on transmit, the data passed to the raw API must
+remain unchanged until sent. Because the send- (or write-)functions return
+when the packets have been enqueued for sending, data must be kept stable
+after that, too.
+
+This implies that PBUF_RAM/PBUF_POOL pbufs passed to raw-API send functions
+must *not* be reused by the application unless their ref-count is 1.
+
+For no-copy pbufs (PBUF_ROM/PBUF_REF), data must be kept unchanged, too,
+but the stack/driver will/must copy PBUF_REF'ed data when enqueueing, while
+PBUF_ROM-pbufs are just enqueued (as ROM-data is expected to never change).
+
+Also, data passed to tcp_write without the copy-flag must not be changed!
+
+Therefore, be careful which type of PBUF you use and if you copy TCP data
+or not!
diff --git a/external/badvpn_dns/lwip/doc/savannah.txt b/external/badvpn_dns/lwip/doc/savannah.txt
new file mode 100644
index 0000000..409905b
--- /dev/null
+++ b/external/badvpn_dns/lwip/doc/savannah.txt
@@ -0,0 +1,135 @@
+Daily Use Guide for using Savannah for lwIP
+
+Table of Contents:
+
+1 - Obtaining lwIP from the CVS repository
+2 - Committers/developers CVS access using SSH (to be written)
+3 - Merging from DEVEL branch to main trunk (stable branch)
+4 - How to release lwIP
+
+
+
+1 Obtaining lwIP from the CVS repository
+----------------------------------------
+
+To perform an anonymous CVS checkout of the main trunk (this is where
+bug fixes and incremental enhancements occur), do this:
+
+cvs -z3 -d:pserver:anonymous@xxxxxxxxxxxxxx:/sources/lwip checkout lwip
+ 
+Or, obtain a stable branch (updated with bug fixes only) as follows:
+cvs -z3 -d:pserver:anonymous@xxxxxxxxxxxxxx:/sources/lwip checkout \
+  -r STABLE-0_7 -d lwip-0.7 lwip
+
+Or, obtain a specific (fixed) release as follows:
+cvs -z3 -d:pserver:anonymous@xxxxxxxxxxxxxx:/sources/lwip checkout \
+  -r STABLE-0_7_0 -d lwip-0.7.0 lwip
+
+3 Committers/developers CVS access using SSH
+--------------------------------------------
+
+The Savannah server uses SSH (Secure Shell) protocol 2 authentication and encryption.
+As such, CVS commits to the server occur through a SSH tunnel for project members.
+To create a SSH2 key pair in UNIX-like environments, do this:
+
+ssh-keygen -t dsa
+
+Under Windows, a recommended SSH client is "PuTTY", freely available with good
+documentation and a graphic user interface. Use its key generator.
+
+Now paste the id_dsa.pub contents into your Savannah account public key list. Wait
+a while so that Savannah can update its configuration (This can take minutes).
+
+Try to login using SSH:
+
+ssh -v your_login@xxxxxxxxxxxxxx
+
+If it tells you:
+
+Authenticating with public key "your_key_name"...
+Server refused to allocate pty
+
+then you could login; Savannah refuses to give you a shell - which is OK, as we
+are allowed to use SSH for CVS only. Now, you should be able to do this:
+
+export CVS_RSH=ssh
+cvs -z3 -d:ext:your_login@xxxxxxxxxxxxxx:/sources/lwip co lwip
+ 
+after which you can edit your local files with bug fixes or new features and
+commit them. Make sure you know what you are doing when using CVS to make
+changes on the repository. If in doubt, ask on the lwip-members mailing list.
+
+(If SSH asks about authenticity of the host, you can check the key
+ fingerprint against http://savannah.nongnu.org/cvs/?group=lwip)
+
+
+3 Merging from DEVEL branch to main trunk (stable)
+--------------------------------------------------
+
+Merging is a delicate process in CVS and requires the
+following disciplined steps in order to prevent conflicts
+in the future. Conflicts can be hard to solve!
+
+Merging from branch A to branch B requires that the A branch
+has a tag indicating the previous merger. This tag is called
+'merged_from_A_to_B'. After merging, the tag is moved in the
+A branch to remember this merger for future merge actions.
+
+IMPORTANT: AFTER COMMITTING A SUCCESFUL MERGE IN THE
+REPOSITORY, THE TAG MUST BE SET ON THE SOURCE BRANCH OF THE
+MERGE ACTION (REPLACING EXISTING TAGS WITH THE SAME NAME).
+
+Merge all changes in DEVEL since our last merge to main:
+
+In the working copy of the main trunk:
+cvs update -P -jmerged_from_DEVEL_to_main -jDEVEL 
+
+(This will apply the changes between 'merged_from_DEVEL_to_main'
+and 'DEVEL' to your work set of files)
+
+We can now commit the merge result.
+cvs commit -R -m "Merged from DEVEL to main." 
+
+If this worked out OK, we now move the tag in the DEVEL branch
+to this merge point, so we can use this point for future merges:
+
+cvs rtag -F -r DEVEL merged_from_DEVEL_to_main lwip 
+
+4 How to release lwIP
+---------------------
+
+First, checkout a clean copy of the branch to be released. Tag this set with
+tag name "STABLE-0_6_3". (I use release number 0.6.3 throughout this example).
+
+Login CVS using pserver authentication, then export a clean copy of the
+tagged tree. Export is similar to a checkout, except that the CVS metadata
+is not created locally. 
+
+export CVS_RSH=ssh
+cvs -z3 -d:pserver:anonymous@xxxxxxxxxxxxxx:/sources/lwip checkout \
+  -r STABLE-0_6_3 -d lwip-0.6.3 lwip
+
+Archive this directory using tar, gzip'd, bzip2'd and zip'd.
+
+tar czvf lwip-0.6.3.tar.gz lwip-0.6.3
+tar cjvf lwip-0.6.3.tar.bz2 lwip-0.6.3
+zip -r lwip-0.6.3.zip lwip-0.6.3
+
+Now, sign the archives with a detached GPG binary signature as follows:
+
+gpg -b lwip-0.6.3.tar.gz
+gpg -b lwip-0.6.3.tar.bz2
+gpg -b lwip-0.6.3.zip
+
+Upload these files using anonymous FTP:
+ncftp ftp://savannah.gnu.org/incoming/savannah/lwip
+
+ncftp>mput *0.6.3.*
+
+Additionally, you may post a news item on Savannah, like this:
+
+A new 0.6.3 release is now available here:
+http://savannah.nongnu.org/files/?group=lwip&highlight=0.6.3
+
+You will have to submit this via the user News interface, then approve
+this via the Administrator News interface.
\ No newline at end of file
diff --git a/external/badvpn_dns/lwip/doc/snmp_agent.txt b/external/badvpn_dns/lwip/doc/snmp_agent.txt
new file mode 100644
index 0000000..2653230
--- /dev/null
+++ b/external/badvpn_dns/lwip/doc/snmp_agent.txt
@@ -0,0 +1,181 @@
+SNMPv1 agent for lwIP
+
+Author: Christiaan Simons
+
+This is a brief introduction how to use and configure the SNMP agent.
+Note the agent uses the raw-API UDP interface so you may also want to
+read rawapi.txt to gain a better understanding of the SNMP message handling.
+
+0 Agent Capabilities
+====================
+
+SNMPv1 per RFC1157
+  This is an old(er) standard but is still widely supported.
+  For SNMPv2c and v3 have a greater complexity and need many
+  more lines of code. IMHO this breaks the idea of "lightweight IP".
+
+  Note the S in SNMP stands for "Simple". Note that "Simple" is
+  relative. SNMP is simple compared to the complex ISO network
+  management protocols CMIP (Common Management Information Protocol)
+  and CMOT (CMip Over Tcp).
+
+MIB II per RFC1213
+  The standard lwIP stack management information base.
+  This is a required MIB, so this is always enabled.
+  When builing lwIP without TCP, the mib-2.tcp group is omitted.
+  The groups EGP, CMOT and transmission are disabled by default.
+  
+  Most mib-2 objects are not writable except:
+  sysName, sysLocation, sysContact, snmpEnableAuthenTraps.
+  Writing to or changing the ARP and IP address and route
+  tables is not possible.
+ 
+  Note lwIP has a very limited notion of IP routing. It currently
+  doen't have a route table and doesn't have a notion of the U,G,H flags.
+  Instead lwIP uses the interface list with only one default interface
+  acting as a single gateway interface (G) for the default route.
+
+  The agent returns a "virtual table" with the default route 0.0.0.0
+  for the default interface and network routes (no H) for each
+  network interface in the netif_list.
+  All routes are considered to be up (U).
+
+Loading additional MIBs
+  MIBs can only be added in compile-time, not in run-time.
+  There is no MIB compiler thus additional MIBs must be hand coded.
+
+Large SNMP message support
+  The packet decoding and encoding routines are designed
+  to use pbuf-chains. Larger payloads than the minimum
+  SNMP requirement of 484 octets are supported if the 
+  PBUF_POOL_SIZE and IP_REASS_BUFSIZE are set to match your
+  local requirement.
+
+1 Building the Agent
+====================
+
+First of all you'll need to add the following define
+to your local lwipopts.h:
+
+#define LWIP_SNMP               1
+
+and add the source files in lwip/src/core/snmp
+and some snmp headers in lwip/src/include/lwip to your makefile.
+
+Note you'll might need to adapt you network driver to update
+the mib2 variables for your interface.
+
+2 Running the Agent
+===================
+
+The following function calls must be made in your program to
+actually get the SNMP agent running.
+
+Before starting the agent you should supply pointers
+to non-volatile memory for sysContact, sysLocation,
+and snmpEnableAuthenTraps. You can do this by calling
+
+snmp_set_syscontact()
+snmp_set_syslocation()
+snmp_set_snmpenableauthentraps()
+
+Additionally you may want to set
+
+snmp_set_sysdescr()
+snmp_set_sysobjid() (if you have a private MIB)
+snmp_set_sysname()
+
+Also before starting the agent you need to setup
+one or more trap destinations using these calls:
+
+snmp_trap_dst_enable();
+snmp_trap_dst_ip_set();
+
+In the lwIP initialisation sequence call snmp_init() just after
+the call to udp_init().
+
+Exactly every 10 msec the SNMP uptime timestamp must be updated with
+snmp_inc_sysuptime(). You should call this from a timer interrupt
+or a timer signal handler depending on your runtime environment.
+
+An alternative way to update the SNMP uptime timestamp is to do a call like
+snmp_add_sysuptime(100) each 1000ms (which is bigger "step", but call to
+a lower frequency). Another one is to not call snmp_inc_sysuptime() or
+snmp_add_sysuptime(), and to define the SNMP_GET_SYSUPTIME(sysuptime) macro.
+This one is undefined by default in mib2.c. SNMP_GET_SYSUPTIME is called inside
+snmp_get_sysuptime(u32_t *value), and enable to change "sysuptime" value only
+when it's queried (any function which need "sysuptime" have to call
+snmp_get_sysuptime).
+
+
+3 Private MIBs
+==============
+
+If want to extend the agent with your own private MIB you'll need to
+add the following define to your local lwipopts.h:
+
+#define SNMP_PRIVATE_MIB        1
+
+You must provide the private_mib.h and associated files yourself.
+Note we don't have a "MIB compiler" that generates C source from a MIB,
+so you're required to do some serious coding if you enable this!
+
+Note the lwIP enterprise ID (26381) is assigned to the lwIP project,
+ALL OBJECT IDENTIFIERS LIVING UNDER THIS ID ARE ASSIGNED BY THE lwIP
+MAINTAINERS!
+
+If you need to create your own private MIB you'll need
+to apply for your own enterprise ID with IANA: http://www.iana.org/numbers.html 
+
+You can set it by passing a struct snmp_obj_id to the agent
+using snmp_set_sysobjid(&my_object_id), just before snmp_init().
+
+Note the object identifiers for thes MIB-2 and your private MIB
+tree must be kept in sorted ascending (lexicographical) order.
+This to ensure correct getnext operation.
+
+An example for a private MIB is part of the "minimal Unix" project:
+contrib/ports/unix/proj/minimal/lwip_prvmib.c
+
+The next chapter gives a more detailed description of the
+MIB-2 tree and the optional private MIB.
+
+4 The Gory Details
+==================
+
+4.0 Object identifiers and the MIB tree.
+
+We have three distinct parts for all object identifiers:
+
+The prefix
+  .iso.org.dod.internet
+
+the middle part 
+  .mgmt.mib-2.ip.ipNetToMediaTable.ipNetToMediaEntry.ipNetToMediaPhysAddress
+
+and the index part
+  .1.192.168.0.1
+
+Objects located above the .internet hierarchy aren't supported.
+Currently only the .mgmt sub-tree is available and
+when the SNMP_PRIVATE_MIB is enabled the .private tree
+becomes available too.
+
+Object identifiers from incoming requests are checked
+for a matching prefix, middle part and index part
+or are expanded(*) for GetNext requests with short
+or inexisting names in the request.
+(* we call this "expansion" but this also
+resembles the "auto-completion" operation)
+
+The middle part is usually located in ROM (const)
+to preserve precious RAM on small microcontrollers.
+However RAM location is possible for a dynamically
+changing private tree.
+
+The index part is handled by functions which in
+turn use dynamically allocated index trees from RAM.
+These trees are updated by e.g. the etharp code
+when new entries are made or removed form the ARP cache.
+
+/** @todo more gory details */
diff --git a/external/badvpn_dns/lwip/doc/sys_arch.txt b/external/badvpn_dns/lwip/doc/sys_arch.txt
new file mode 100644
index 0000000..847cd77
--- /dev/null
+++ b/external/badvpn_dns/lwip/doc/sys_arch.txt
@@ -0,0 +1,267 @@
+sys_arch interface for lwIP 0.6++
+
+Author: Adam Dunkels
+
+The operating system emulation layer provides a common interface
+between the lwIP code and the underlying operating system kernel. The
+general idea is that porting lwIP to new architectures requires only
+small changes to a few header files and a new sys_arch
+implementation. It is also possible to do a sys_arch implementation
+that does not rely on any underlying operating system.
+
+The sys_arch provides semaphores and mailboxes to lwIP. For the full
+lwIP functionality, multiple threads support can be implemented in the
+sys_arch, but this is not required for the basic lwIP
+functionality. Previous versions of lwIP required the sys_arch to
+implement timer scheduling as well but as of lwIP 0.5 this is
+implemented in a higher layer.
+
+In addition to the source file providing the functionality of sys_arch,
+the OS emulation layer must provide several header files defining
+macros used throughout lwip.  The files required and the macros they
+must define are listed below the sys_arch description.
+
+Semaphores can be either counting or binary - lwIP works with both
+kinds. Mailboxes are used for message passing and can be implemented
+either as a queue which allows multiple messages to be posted to a
+mailbox, or as a rendez-vous point where only one message can be
+posted at a time. lwIP works with both kinds, but the former type will
+be more efficient. A message in a mailbox is just a pointer, nothing
+more. 
+
+Semaphores are represented by the type "sys_sem_t" which is typedef'd
+in the sys_arch.h file. Mailboxes are equivalently represented by the
+type "sys_mbox_t". lwIP does not place any restrictions on how
+sys_sem_t or sys_mbox_t are represented internally.
+
+Since lwIP 1.4.0, semaphore and mailbox functions are prototyped in a way that
+allows both using pointers or actual OS structures to be used. This way, memory
+required for such types can be either allocated in place (globally or on the
+stack) or on the heap (allocated internally in the "*_new()" functions).
+
+The following functions must be implemented by the sys_arch:
+
+- void sys_init(void)
+
+  Is called to initialize the sys_arch layer.
+
+- err_t sys_sem_new(sys_sem_t *sem, u8_t count)
+
+  Creates a new semaphore. The semaphore is allocated to the memory that 'sem'
+  points to (which can be both a pointer or the actual OS structure).
+  The "count" argument specifies the initial state of the semaphore (which is
+  either 0 or 1).
+  If the semaphore has been created, ERR_OK should be returned. Returning any
+  other error will provide a hint what went wrong, but except for assertions,
+  no real error handling is implemented.
+
+- void sys_sem_free(sys_sem_t *sem)
+
+  Deallocates a semaphore.
+
+- void sys_sem_signal(sys_sem_t *sem)
+
+  Signals a semaphore.
+
+- u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
+
+  Blocks the thread while waiting for the semaphore to be
+  signaled. If the "timeout" argument is non-zero, the thread should
+  only be blocked for the specified time (measured in
+  milliseconds). If the "timeout" argument is zero, the thread should be
+  blocked until the semaphore is signalled.
+
+  If the timeout argument is non-zero, the return value is the number of
+  milliseconds spent waiting for the semaphore to be signaled. If the
+  semaphore wasn't signaled within the specified time, the return value is
+  SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore
+  (i.e., it was already signaled), the function may return zero.
+
+  Notice that lwIP implements a function with a similar name,
+  sys_sem_wait(), that uses the sys_arch_sem_wait() function.
+
+- int sys_sem_valid(sys_sem_t *sem)
+
+  Returns 1 if the semaphore is valid, 0 if it is not valid.
+  When using pointers, a simple way is to check the pointer for != NULL.
+  When directly using OS structures, implementing this may be more complex.
+  This may also be a define, in which case the function is not prototyped.
+
+- void sys_sem_set_invalid(sys_sem_t *sem)
+
+  Invalidate a semaphore so that sys_sem_valid() returns 0.
+  ATTENTION: This does NOT mean that the semaphore shall be deallocated:
+  sys_sem_free() is always called before calling this function!
+  This may also be a define, in which case the function is not prototyped.
+
+- err_t sys_mbox_new(sys_mbox_t *mbox, int size)
+
+  Creates an empty mailbox for maximum "size" elements. Elements stored
+  in mailboxes are pointers. You have to define macros "_MBOX_SIZE"
+  in your lwipopts.h, or ignore this parameter in your implementation
+  and use a default size.
+  If the mailbox has been created, ERR_OK should be returned. Returning any
+  other error will provide a hint what went wrong, but except for assertions,
+  no real error handling is implemented.
+
+- void sys_mbox_free(sys_mbox_t *mbox)
+
+  Deallocates a mailbox. If there are messages still present in the
+  mailbox when the mailbox is deallocated, it is an indication of a
+  programming error in lwIP and the developer should be notified.
+
+- void sys_mbox_post(sys_mbox_t *mbox, void *msg)
+
+  Posts the "msg" to the mailbox. This function have to block until
+  the "msg" is really posted.
+
+- err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
+
+  Try to post the "msg" to the mailbox. Returns ERR_MEM if this one
+  is full, else, ERR_OK if the "msg" is posted.
+
+- u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
+
+  Blocks the thread until a message arrives in the mailbox, but does
+  not block the thread longer than "timeout" milliseconds (similar to
+  the sys_arch_sem_wait() function). If "timeout" is 0, the thread should
+  be blocked until a message arrives. The "msg" argument is a result
+  parameter that is set by the function (i.e., by doing "*msg =
+  ptr"). The "msg" parameter maybe NULL to indicate that the message
+  should be dropped.
+
+  The return values are the same as for the sys_arch_sem_wait() function:
+  Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a
+  timeout.
+
+  Note that a function with a similar name, sys_mbox_fetch(), is
+  implemented by lwIP. 
+
+- u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
+
+  This is similar to sys_arch_mbox_fetch, however if a message is not
+  present in the mailbox, it immediately returns with the code
+  SYS_MBOX_EMPTY. On success 0 is returned.
+
+  To allow for efficient implementations, this can be defined as a
+  function-like macro in sys_arch.h instead of a normal function. For
+  example, a naive implementation could be:
+    #define sys_arch_mbox_tryfetch(mbox,msg) \
+      sys_arch_mbox_fetch(mbox,msg,1)
+  although this would introduce unnecessary delays.
+
+- int sys_mbox_valid(sys_mbox_t *mbox)
+
+  Returns 1 if the mailbox is valid, 0 if it is not valid.
+  When using pointers, a simple way is to check the pointer for != NULL.
+  When directly using OS structures, implementing this may be more complex.
+  This may also be a define, in which case the function is not prototyped.
+
+- void sys_mbox_set_invalid(sys_mbox_t *mbox)
+
+  Invalidate a mailbox so that sys_mbox_valid() returns 0.
+  ATTENTION: This does NOT mean that the mailbox shall be deallocated:
+  sys_mbox_free() is always called before calling this function!
+  This may also be a define, in which case the function is not prototyped.
+
+If threads are supported by the underlying operating system and if
+such functionality is needed in lwIP, the following function will have
+to be implemented as well:
+
+- sys_thread_t sys_thread_new(char *name, void (* thread)(void *arg), void *arg, int stacksize, int prio)
+
+  Starts a new thread named "name" with priority "prio" that will begin its
+  execution in the function "thread()". The "arg" argument will be passed as an
+  argument to the thread() function. The stack size to used for this thread is
+  the "stacksize" parameter. The id of the new thread is returned. Both the id
+  and the priority are system dependent.
+
+- sys_prot_t sys_arch_protect(void)
+
+  This optional function does a "fast" critical region protection and returns
+  the previous protection level. This function is only called during very short
+  critical regions. An embedded system which supports ISR-based drivers might
+  want to implement this function by disabling interrupts. Task-based systems
+  might want to implement this by using a mutex or disabling tasking. This
+  function should support recursive calls from the same task or interrupt. In
+  other words, sys_arch_protect() could be called while already protected. In
+  that case the return value indicates that it is already protected.
+
+  sys_arch_protect() is only required if your port is supporting an operating
+  system.
+
+- void sys_arch_unprotect(sys_prot_t pval)
+
+  This optional function does a "fast" set of critical region protection to the
+  value specified by pval. See the documentation for sys_arch_protect() for
+  more information. This function is only required if your port is supporting
+  an operating system.
+
+For some configurations, you also need:
+
+- u32_t sys_now(void)
+
+  This optional function returns the current time in milliseconds (don't care
+  for wraparound, this is only used for time diffs).
+  Not implementing this function means you cannot use some modules (e.g. TCP
+  timestamps, internal timeouts for NO_SYS==1).
+
+
+Note:
+
+Be carefull with using mem_malloc() in sys_arch. When malloc() refers to
+mem_malloc() you can run into a circular function call problem. In mem.c
+mem_init() tries to allcate a semaphore using mem_malloc, which of course
+can't be performed when sys_arch uses mem_malloc.
+
+-------------------------------------------------------------------------------
+Additional files required for the "OS support" emulation layer:
+-------------------------------------------------------------------------------
+
+cc.h       - Architecture environment, some compiler specific, some
+             environment specific (probably should move env stuff 
+             to sys_arch.h.)
+
+  Typedefs for the types used by lwip -
+    u8_t, s8_t, u16_t, s16_t, u32_t, s32_t, mem_ptr_t
+
+  Compiler hints for packing lwip's structures -
+    PACK_STRUCT_FIELD(x)
+    PACK_STRUCT_STRUCT
+    PACK_STRUCT_BEGIN
+    PACK_STRUCT_END
+
+  Platform specific diagnostic output -
+    LWIP_PLATFORM_DIAG(x)    - non-fatal, print a message.
+    LWIP_PLATFORM_ASSERT(x)  - fatal, print message and abandon execution.
+    Portability defines for printf formatters:
+    U16_F, S16_F, X16_F, U32_F, S32_F, X32_F, SZT_F
+
+  "lightweight" synchronization mechanisms -
+    SYS_ARCH_DECL_PROTECT(x) - declare a protection state variable.
+    SYS_ARCH_PROTECT(x)      - enter protection mode.
+    SYS_ARCH_UNPROTECT(x)    - leave protection mode.
+
+  If the compiler does not provide memset() this file must include a
+  definition of it, or include a file which defines it.
+
+  This file must either include a system-local <errno.h> which defines
+  the standard *nix error codes, or it should #define LWIP_PROVIDE_ERRNO
+  to make lwip/arch.h define the codes which are used throughout.
+
+
+perf.h     - Architecture specific performance measurement.
+  Measurement calls made throughout lwip, these can be defined to nothing.
+    PERF_START               - start measuring something.
+    PERF_STOP(x)             - stop measuring something, and record the result.
+
+sys_arch.h - Tied to sys_arch.c
+
+  Arch dependent types for the following objects:
+    sys_sem_t, sys_mbox_t, sys_thread_t,
+  And, optionally:
+    sys_prot_t
+
+  Defines to set vars of sys_mbox_t and sys_sem_t to NULL.
+    SYS_MBOX_NULL NULL
+    SYS_SEM_NULL NULL
diff --git a/external/badvpn_dns/lwip/lwip-base-version b/external/badvpn_dns/lwip/lwip-base-version
new file mode 100644
index 0000000..b48d5b6
--- /dev/null
+++ b/external/badvpn_dns/lwip/lwip-base-version
@@ -0,0 +1 @@
+666e84eef281d0059377d0f5029c1c550488f829
diff --git a/external/badvpn_dns/lwip/src/FILES b/external/badvpn_dns/lwip/src/FILES
new file mode 100644
index 0000000..952aeab
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/FILES
@@ -0,0 +1,13 @@
+api/      - The code for the high-level wrapper API. Not needed if
+            you use the lowel-level call-back/raw API.
+
+core/     - The core of the TPC/IP stack; protocol implementations,
+            memory and buffer management, and the low-level raw API.
+
+include/  - lwIP include files.
+
+netif/    - Generic network interface device drivers are kept here,
+            as well as the ARP module.
+
+For more information on the various subdirectories, check the FILES
+file in each directory.
diff --git a/external/badvpn_dns/lwip/src/api/api_lib.c b/external/badvpn_dns/lwip/src/api/api_lib.c
new file mode 100644
index 0000000..adaaad4
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/api/api_lib.c
@@ -0,0 +1,788 @@
+/**
+ * @file
+ * Sequential API External module
+ *
+ */
+ 
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+/* This is the part of the API that is linked with
+   the application */
+
+#include "lwip/opt.h"
+
+#if LWIP_NETCONN /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/api.h"
+#include "lwip/tcpip.h"
+#include "lwip/memp.h"
+
+#include "lwip/ip.h"
+#include "lwip/raw.h"
+#include "lwip/udp.h"
+#include "lwip/tcp.h"
+
+#include <string.h>
+
+/**
+ * Create a new netconn (of a specific type) that has a callback function.
+ * The corresponding pcb is also created.
+ *
+ * @param t the type of 'connection' to create (@see enum netconn_type)
+ * @param proto the IP protocol for RAW IP pcbs
+ * @param callback a function to call on status changes (RX available, TX'ed)
+ * @return a newly allocated struct netconn or
+ *         NULL on memory error
+ */
+struct netconn*
+netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback)
+{
+  struct netconn *conn;
+  struct api_msg msg;
+
+  conn = netconn_alloc(t, callback);
+  if (conn != NULL) {
+    err_t err;
+    msg.msg.msg.n.proto = proto;
+    msg.msg.conn = conn;
+    TCPIP_APIMSG((&msg), lwip_netconn_do_newconn, err);
+    if (err != ERR_OK) {
+      LWIP_ASSERT("freeing conn without freeing pcb", conn->pcb.tcp == NULL);
+      LWIP_ASSERT("conn has no op_completed", sys_sem_valid(&conn->op_completed));
+      LWIP_ASSERT("conn has no recvmbox", sys_mbox_valid(&conn->recvmbox));
+#if LWIP_TCP
+      LWIP_ASSERT("conn->acceptmbox shouldn't exist", !sys_mbox_valid(&conn->acceptmbox));
+#endif /* LWIP_TCP */
+      sys_sem_free(&conn->op_completed);
+      sys_mbox_free(&conn->recvmbox);
+      memp_free(MEMP_NETCONN, conn);
+      return NULL;
+    }
+  }
+  return conn;
+}
+
+/**
+ * Close a netconn 'connection' and free its resources.
+ * UDP and RAW connection are completely closed, TCP pcbs might still be in a waitstate
+ * after this returns.
+ *
+ * @param conn the netconn to delete
+ * @return ERR_OK if the connection was deleted
+ */
+err_t
+netconn_delete(struct netconn *conn)
+{
+  struct api_msg msg;
+
+  /* No ASSERT here because possible to get a (conn == NULL) if we got an accept error */
+  if (conn == NULL) {
+    return ERR_OK;
+  }
+
+  msg.function = lwip_netconn_do_delconn;
+  msg.msg.conn = conn;
+  tcpip_apimsg(&msg);
+
+  netconn_free(conn);
+
+  /* don't care for return value of lwip_netconn_do_delconn since it only calls void functions */
+
+  return ERR_OK;
+}
+
+/**
+ * Get the local or remote IP address and port of a netconn.
+ * For RAW netconns, this returns the protocol instead of a port!
+ *
+ * @param conn the netconn to query
+ * @param addr a pointer to which to save the IP address
+ * @param port a pointer to which to save the port (or protocol for RAW)
+ * @param local 1 to get the local IP address, 0 to get the remote one
+ * @return ERR_CONN for invalid connections
+ *         ERR_OK if the information was retrieved
+ */
+err_t
+netconn_getaddr(struct netconn *conn, ip_addr_t *addr, u16_t *port, u8_t local)
+{
+  struct api_msg msg;
+  err_t err;
+
+  LWIP_ERROR("netconn_getaddr: invalid conn", (conn != NULL), return ERR_ARG;);
+  LWIP_ERROR("netconn_getaddr: invalid addr", (addr != NULL), return ERR_ARG;);
+  LWIP_ERROR("netconn_getaddr: invalid port", (port != NULL), return ERR_ARG;);
+
+  msg.msg.conn = conn;
+  msg.msg.msg.ad.ipaddr = ip_2_ipX(addr);
+  msg.msg.msg.ad.port = port;
+  msg.msg.msg.ad.local = local;
+  TCPIP_APIMSG(&msg, lwip_netconn_do_getaddr, err);
+
+  NETCONN_SET_SAFE_ERR(conn, err);
+  return err;
+}
+
+/**
+ * Bind a netconn to a specific local IP address and port.
+ * Binding one netconn twice might not always be checked correctly!
+ *
+ * @param conn the netconn to bind
+ * @param addr the local IP address to bind the netconn to (use IP_ADDR_ANY
+ *             to bind to all addresses)
+ * @param port the local port to bind the netconn to (not used for RAW)
+ * @return ERR_OK if bound, any other err_t on failure
+ */
+err_t
+netconn_bind(struct netconn *conn, ip_addr_t *addr, u16_t port)
+{
+  struct api_msg msg;
+  err_t err;
+
+  LWIP_ERROR("netconn_bind: invalid conn", (conn != NULL), return ERR_ARG;);
+
+  msg.msg.conn = conn;
+  msg.msg.msg.bc.ipaddr = addr;
+  msg.msg.msg.bc.port = port;
+  TCPIP_APIMSG(&msg, lwip_netconn_do_bind, err);
+
+  NETCONN_SET_SAFE_ERR(conn, err);
+  return err;
+}
+
+/**
+ * Connect a netconn to a specific remote IP address and port.
+ *
+ * @param conn the netconn to connect
+ * @param addr the remote IP address to connect to
+ * @param port the remote port to connect to (no used for RAW)
+ * @return ERR_OK if connected, return value of tcp_/udp_/raw_connect otherwise
+ */
+err_t
+netconn_connect(struct netconn *conn, ip_addr_t *addr, u16_t port)
+{
+  struct api_msg msg;
+  err_t err;
+
+  LWIP_ERROR("netconn_connect: invalid conn", (conn != NULL), return ERR_ARG;);
+
+  msg.msg.conn = conn;
+  msg.msg.msg.bc.ipaddr = addr;
+  msg.msg.msg.bc.port = port;
+#if LWIP_TCP
+#if (LWIP_UDP || LWIP_RAW)
+  if (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP)
+#endif /* (LWIP_UDP || LWIP_RAW) */
+  {
+    /* The TCP version waits for the connect to succeed,
+       so always needs to use message passing. */
+    msg.function = lwip_netconn_do_connect;
+    err = tcpip_apimsg(&msg);
+  }
+#endif /* LWIP_TCP */
+#if (LWIP_UDP || LWIP_RAW) && LWIP_TCP
+  else
+#endif /* (LWIP_UDP || LWIP_RAW) && LWIP_TCP */
+#if (LWIP_UDP || LWIP_RAW)
+  {
+     /* UDP and RAW only set flags, so we can use core-locking. */
+     TCPIP_APIMSG(&msg, lwip_netconn_do_connect, err);
+  }
+#endif /* (LWIP_UDP || LWIP_RAW) */
+
+  NETCONN_SET_SAFE_ERR(conn, err);
+  return err;
+}
+
+/**
+ * Disconnect a netconn from its current peer (only valid for UDP netconns).
+ *
+ * @param conn the netconn to disconnect
+ * @return TODO: return value is not set here...
+ */
+err_t
+netconn_disconnect(struct netconn *conn)
+{
+  struct api_msg msg;
+  err_t err;
+
+  LWIP_ERROR("netconn_disconnect: invalid conn", (conn != NULL), return ERR_ARG;);
+
+  msg.msg.conn = conn;
+  TCPIP_APIMSG(&msg, lwip_netconn_do_disconnect, err);
+
+  NETCONN_SET_SAFE_ERR(conn, err);
+  return err;
+}
+
+/**
+ * Set a TCP netconn into listen mode
+ *
+ * @param conn the tcp netconn to set to listen mode
+ * @param backlog the listen backlog, only used if TCP_LISTEN_BACKLOG==1
+ * @return ERR_OK if the netconn was set to listen (UDP and RAW netconns
+ *         don't return any error (yet?))
+ */
+err_t
+netconn_listen_with_backlog(struct netconn *conn, u8_t backlog)
+{
+#if LWIP_TCP
+  struct api_msg msg;
+  err_t err;
+
+  /* This does no harm. If TCP_LISTEN_BACKLOG is off, backlog is unused. */
+  LWIP_UNUSED_ARG(backlog);
+
+  LWIP_ERROR("netconn_listen: invalid conn", (conn != NULL), return ERR_ARG;);
+
+  msg.msg.conn = conn;
+#if TCP_LISTEN_BACKLOG
+  msg.msg.msg.lb.backlog = backlog;
+#endif /* TCP_LISTEN_BACKLOG */
+  TCPIP_APIMSG(&msg, lwip_netconn_do_listen, err);
+
+  NETCONN_SET_SAFE_ERR(conn, err);
+  return err;
+#else /* LWIP_TCP */
+  LWIP_UNUSED_ARG(conn);
+  LWIP_UNUSED_ARG(backlog);
+  return ERR_ARG;
+#endif /* LWIP_TCP */
+}
+
+/**
+ * Accept a new connection on a TCP listening netconn.
+ *
+ * @param conn the TCP listen netconn
+ * @param new_conn pointer where the new connection is stored
+ * @return ERR_OK if a new connection has been received or an error
+ *                code otherwise
+ */
+err_t
+netconn_accept(struct netconn *conn, struct netconn **new_conn)
+{
+#if LWIP_TCP
+  struct netconn *newconn;
+  err_t err;
+#if TCP_LISTEN_BACKLOG
+  struct api_msg msg;
+#endif /* TCP_LISTEN_BACKLOG */
+
+  LWIP_ERROR("netconn_accept: invalid pointer",    (new_conn != NULL),                  return ERR_ARG;);
+  *new_conn = NULL;
+  LWIP_ERROR("netconn_accept: invalid conn",       (conn != NULL),                      return ERR_ARG;);
+  LWIP_ERROR("netconn_accept: invalid acceptmbox", sys_mbox_valid(&conn->acceptmbox),   return ERR_ARG;);
+
+  err = conn->last_err;
+  if (ERR_IS_FATAL(err)) {
+    /* don't recv on fatal errors: this might block the application task
+       waiting on acceptmbox forever! */
+    return err;
+  }
+
+#if LWIP_SO_RCVTIMEO
+  if (sys_arch_mbox_fetch(&conn->acceptmbox, (void **)&newconn, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {
+    NETCONN_SET_SAFE_ERR(conn, ERR_TIMEOUT);
+    return ERR_TIMEOUT;
+  }
+#else
+  sys_arch_mbox_fetch(&conn->acceptmbox, (void **)&newconn, 0);
+#endif /* LWIP_SO_RCVTIMEO*/
+  /* Register event with callback */
+  API_EVENT(conn, NETCONN_EVT_RCVMINUS, 0);
+
+  if (newconn == NULL) {
+    /* connection has been aborted */
+    NETCONN_SET_SAFE_ERR(conn, ERR_ABRT);
+    return ERR_ABRT;
+  }
+#if TCP_LISTEN_BACKLOG
+  /* Let the stack know that we have accepted the connection. */
+  msg.msg.conn = conn;
+  /* don't care for the return value of lwip_netconn_do_recv */
+  TCPIP_APIMSG_NOERR(&msg, lwip_netconn_do_recv);
+#endif /* TCP_LISTEN_BACKLOG */
+
+  *new_conn = newconn;
+  /* don't set conn->last_err: it's only ERR_OK, anyway */
+  return ERR_OK;
+#else /* LWIP_TCP */
+  LWIP_UNUSED_ARG(conn);
+  LWIP_UNUSED_ARG(new_conn);
+  return ERR_ARG;
+#endif /* LWIP_TCP */
+}
+
+/**
+ * Receive data: actual implementation that doesn't care whether pbuf or netbuf
+ * is received
+ *
+ * @param conn the netconn from which to receive data
+ * @param new_buf pointer where a new pbuf/netbuf is stored when received data
+ * @return ERR_OK if data has been received, an error code otherwise (timeout,
+ *                memory error or another error)
+ */
+static err_t
+netconn_recv_data(struct netconn *conn, void **new_buf)
+{
+  void *buf = NULL;
+  u16_t len;
+  err_t err;
+#if LWIP_TCP
+  struct api_msg msg;
+#endif /* LWIP_TCP */
+
+  LWIP_ERROR("netconn_recv: invalid pointer", (new_buf != NULL), return ERR_ARG;);
+  *new_buf = NULL;
+  LWIP_ERROR("netconn_recv: invalid conn",    (conn != NULL),    return ERR_ARG;);
+  LWIP_ERROR("netconn_accept: invalid recvmbox", sys_mbox_valid(&conn->recvmbox), return ERR_CONN;);
+
+  err = conn->last_err;
+  if (ERR_IS_FATAL(err)) {
+    /* don't recv on fatal errors: this might block the application task
+       waiting on recvmbox forever! */
+    /* @todo: this does not allow us to fetch data that has been put into recvmbox
+       before the fatal error occurred - is that a problem? */
+    return err;
+  }
+
+#if LWIP_SO_RCVTIMEO
+  if (sys_arch_mbox_fetch(&conn->recvmbox, &buf, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {
+    NETCONN_SET_SAFE_ERR(conn, ERR_TIMEOUT);
+    return ERR_TIMEOUT;
+  }
+#else
+  sys_arch_mbox_fetch(&conn->recvmbox, &buf, 0);
+#endif /* LWIP_SO_RCVTIMEO*/
+
+#if LWIP_TCP
+#if (LWIP_UDP || LWIP_RAW)
+  if (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP)
+#endif /* (LWIP_UDP || LWIP_RAW) */
+  {
+    if (!netconn_get_noautorecved(conn) || (buf == NULL)) {
+      /* Let the stack know that we have taken the data. */
+      /* TODO: Speedup: Don't block and wait for the answer here
+         (to prevent multiple thread-switches). */
+      msg.msg.conn = conn;
+      if (buf != NULL) {
+        msg.msg.msg.r.len = ((struct pbuf *)buf)->tot_len;
+      } else {
+        msg.msg.msg.r.len = 1;
+      }
+      /* don't care for the return value of lwip_netconn_do_recv */
+      TCPIP_APIMSG_NOERR(&msg, lwip_netconn_do_recv);
+    }
+
+    /* If we are closed, we indicate that we no longer wish to use the socket */
+    if (buf == NULL) {
+      API_EVENT(conn, NETCONN_EVT_RCVMINUS, 0);
+      /* Avoid to lose any previous error code */
+      NETCONN_SET_SAFE_ERR(conn, ERR_CLSD);
+      return ERR_CLSD;
+    }
+    len = ((struct pbuf *)buf)->tot_len;
+  }
+#endif /* LWIP_TCP */
+#if LWIP_TCP && (LWIP_UDP || LWIP_RAW)
+  else
+#endif /* LWIP_TCP && (LWIP_UDP || LWIP_RAW) */
+#if (LWIP_UDP || LWIP_RAW)
+  {
+    LWIP_ASSERT("buf != NULL", buf != NULL);
+    len = netbuf_len((struct netbuf *)buf);
+  }
+#endif /* (LWIP_UDP || LWIP_RAW) */
+
+#if LWIP_SO_RCVBUF
+  SYS_ARCH_DEC(conn->recv_avail, len);
+#endif /* LWIP_SO_RCVBUF */
+  /* Register event with callback */
+  API_EVENT(conn, NETCONN_EVT_RCVMINUS, len);
+
+  LWIP_DEBUGF(API_LIB_DEBUG, ("netconn_recv_data: received %p, len=%"U16_F"\n", buf, len));
+
+  *new_buf = buf;
+  /* don't set conn->last_err: it's only ERR_OK, anyway */
+  return ERR_OK;
+}
+
+/**
+ * Receive data (in form of a pbuf) from a TCP netconn
+ *
+ * @param conn the netconn from which to receive data
+ * @param new_buf pointer where a new pbuf is stored when received data
+ * @return ERR_OK if data has been received, an error code otherwise (timeout,
+ *                memory error or another error)
+ *         ERR_ARG if conn is not a TCP netconn
+ */
+err_t
+netconn_recv_tcp_pbuf(struct netconn *conn, struct pbuf **new_buf)
+{
+  LWIP_ERROR("netconn_recv: invalid conn", (conn != NULL) &&
+             NETCONNTYPE_GROUP(netconn_type(conn)) == NETCONN_TCP, return ERR_ARG;);
+
+  return netconn_recv_data(conn, (void **)new_buf);
+}
+
+/**
+ * Receive data (in form of a netbuf containing a packet buffer) from a netconn
+ *
+ * @param conn the netconn from which to receive data
+ * @param new_buf pointer where a new netbuf is stored when received data
+ * @return ERR_OK if data has been received, an error code otherwise (timeout,
+ *                memory error or another error)
+ */
+err_t
+netconn_recv(struct netconn *conn, struct netbuf **new_buf)
+{
+#if LWIP_TCP
+  struct netbuf *buf = NULL;
+  err_t err;
+#endif /* LWIP_TCP */
+
+  LWIP_ERROR("netconn_recv: invalid pointer", (new_buf != NULL), return ERR_ARG;);
+  *new_buf = NULL;
+  LWIP_ERROR("netconn_recv: invalid conn",    (conn != NULL),    return ERR_ARG;);
+  LWIP_ERROR("netconn_accept: invalid recvmbox", sys_mbox_valid(&conn->recvmbox), return ERR_CONN;);
+
+#if LWIP_TCP
+#if (LWIP_UDP || LWIP_RAW)
+  if (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP)
+#endif /* (LWIP_UDP || LWIP_RAW) */
+  {
+    struct pbuf *p = NULL;
+    /* This is not a listening netconn, since recvmbox is set */
+
+    buf = (struct netbuf *)memp_malloc(MEMP_NETBUF);
+    if (buf == NULL) {
+      NETCONN_SET_SAFE_ERR(conn, ERR_MEM);
+      return ERR_MEM;
+    }
+
+    err = netconn_recv_data(conn, (void **)&p);
+    if (err != ERR_OK) {
+      memp_free(MEMP_NETBUF, buf);
+      return err;
+    }
+    LWIP_ASSERT("p != NULL", p != NULL);
+
+    buf->p = p;
+    buf->ptr = p;
+    buf->port = 0;
+    ipX_addr_set_any(LWIP_IPV6, &buf->addr);
+    *new_buf = buf;
+    /* don't set conn->last_err: it's only ERR_OK, anyway */
+    return ERR_OK;
+  }
+#endif /* LWIP_TCP */
+#if LWIP_TCP && (LWIP_UDP || LWIP_RAW)
+  else
+#endif /* LWIP_TCP && (LWIP_UDP || LWIP_RAW) */
+  {
+#if (LWIP_UDP || LWIP_RAW)
+    return netconn_recv_data(conn, (void **)new_buf);
+#endif /* (LWIP_UDP || LWIP_RAW) */
+  }
+}
+
+/**
+ * TCP: update the receive window: by calling this, the application
+ * tells the stack that it has processed data and is able to accept
+ * new data.
+ * ATTENTION: use with care, this is mainly used for sockets!
+ * Can only be used when calling netconn_set_noautorecved(conn, 1) before.
+ *
+ * @param conn the netconn for which to update the receive window
+ * @param length amount of data processed (ATTENTION: this must be accurate!)
+ */
+void
+netconn_recved(struct netconn *conn, u32_t length)
+{
+#if LWIP_TCP
+  if ((conn != NULL) && (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP) &&
+      (netconn_get_noautorecved(conn))) {
+    struct api_msg msg;
+    /* Let the stack know that we have taken the data. */
+    /* TODO: Speedup: Don't block and wait for the answer here
+       (to prevent multiple thread-switches). */
+    msg.msg.conn = conn;
+    msg.msg.msg.r.len = length;
+    /* don't care for the return value of lwip_netconn_do_recv */
+    TCPIP_APIMSG_NOERR(&msg, lwip_netconn_do_recv);
+  }
+#else /* LWIP_TCP */
+  LWIP_UNUSED_ARG(conn);
+  LWIP_UNUSED_ARG(length);
+#endif /* LWIP_TCP */
+}
+
+/**
+ * Send data (in form of a netbuf) to a specific remote IP address and port.
+ * Only to be used for UDP and RAW netconns (not TCP).
+ *
+ * @param conn the netconn over which to send data
+ * @param buf a netbuf containing the data to send
+ * @param addr the remote IP address to which to send the data
+ * @param port the remote port to which to send the data
+ * @return ERR_OK if data was sent, any other err_t on error
+ */
+err_t
+netconn_sendto(struct netconn *conn, struct netbuf *buf, ip_addr_t *addr, u16_t port)
+{
+  if (buf != NULL) {
+    ipX_addr_set_ipaddr(PCB_ISIPV6(conn->pcb.ip), &buf->addr, addr);
+    buf->port = port;
+    return netconn_send(conn, buf);
+  }
+  return ERR_VAL;
+}
+
+/**
+ * Send data over a UDP or RAW netconn (that is already connected).
+ *
+ * @param conn the UDP or RAW netconn over which to send data
+ * @param buf a netbuf containing the data to send
+ * @return ERR_OK if data was sent, any other err_t on error
+ */
+err_t
+netconn_send(struct netconn *conn, struct netbuf *buf)
+{
+  struct api_msg msg;
+  err_t err;
+
+  LWIP_ERROR("netconn_send: invalid conn",  (conn != NULL), return ERR_ARG;);
+
+  LWIP_DEBUGF(API_LIB_DEBUG, ("netconn_send: sending %"U16_F" bytes\n", buf->p->tot_len));
+  msg.msg.conn = conn;
+  msg.msg.msg.b = buf;
+  TCPIP_APIMSG(&msg, lwip_netconn_do_send, err);
+
+  NETCONN_SET_SAFE_ERR(conn, err);
+  return err;
+}
+
+/**
+ * Send data over a TCP netconn.
+ *
+ * @param conn the TCP netconn over which to send data
+ * @param dataptr pointer to the application buffer that contains the data to send
+ * @param size size of the application data to send
+ * @param apiflags combination of following flags :
+ * - NETCONN_COPY: data will be copied into memory belonging to the stack
+ * - NETCONN_MORE: for TCP connection, PSH flag will be set on last segment sent
+ * - NETCONN_DONTBLOCK: only write the data if all dat can be written at once
+ * @param bytes_written pointer to a location that receives the number of written bytes
+ * @return ERR_OK if data was sent, any other err_t on error
+ */
+err_t
+netconn_write_partly(struct netconn *conn, const void *dataptr, size_t size,
+                     u8_t apiflags, size_t *bytes_written)
+{
+  struct api_msg msg;
+  err_t err;
+  u8_t dontblock;
+
+  LWIP_ERROR("netconn_write: invalid conn",  (conn != NULL), return ERR_ARG;);
+  LWIP_ERROR("netconn_write: invalid conn->type",  (NETCONNTYPE_GROUP(conn->type)== NETCONN_TCP), return ERR_VAL;);
+  if (size == 0) {
+    return ERR_OK;
+  }
+  dontblock = netconn_is_nonblocking(conn) || (apiflags & NETCONN_DONTBLOCK);
+  if (dontblock && !bytes_written) {
+    /* This implies netconn_write() cannot be used for non-blocking send, since
+       it has no way to return the number of bytes written. */
+    return ERR_VAL;
+  }
+
+  /* non-blocking write sends as much  */
+  msg.msg.conn = conn;
+  msg.msg.msg.w.dataptr = dataptr;
+  msg.msg.msg.w.apiflags = apiflags;
+  msg.msg.msg.w.len = size;
+#if LWIP_SO_SNDTIMEO
+  if (conn->send_timeout != 0) {
+    /* get the time we started, which is later compared to
+        sys_now() + conn->send_timeout */
+    msg.msg.msg.w.time_started = sys_now();
+  } else {
+    msg.msg.msg.w.time_started = 0;
+  }
+#endif /* LWIP_SO_SNDTIMEO */
+
+  /* For locking the core: this _can_ be delayed on low memory/low send buffer,
+     but if it is, this is done inside api_msg.c:do_write(), so we can use the
+     non-blocking version here. */
+  TCPIP_APIMSG(&msg, lwip_netconn_do_write, err);
+  if ((err == ERR_OK) && (bytes_written != NULL)) {
+    if (dontblock
+#if LWIP_SO_SNDTIMEO
+        || (conn->send_timeout != 0)
+#endif /* LWIP_SO_SNDTIMEO */
+       ) {
+      /* nonblocking write: maybe the data has been sent partly */
+      *bytes_written = msg.msg.msg.w.len;
+    } else {
+      /* blocking call succeeded: all data has been sent if it */
+      *bytes_written = size;
+    }
+  }
+
+  NETCONN_SET_SAFE_ERR(conn, err);
+  return err;
+}
+
+/**
+ * Close ot shutdown a TCP netconn (doesn't delete it).
+ *
+ * @param conn the TCP netconn to close or shutdown
+ * @param how fully close or only shutdown one side?
+ * @return ERR_OK if the netconn was closed, any other err_t on error
+ */
+static err_t
+netconn_close_shutdown(struct netconn *conn, u8_t how)
+{
+  struct api_msg msg;
+  err_t err;
+
+  LWIP_ERROR("netconn_close: invalid conn",  (conn != NULL), return ERR_ARG;);
+
+  msg.function = lwip_netconn_do_close;
+  msg.msg.conn = conn;
+  /* shutting down both ends is the same as closing */
+  msg.msg.msg.sd.shut = how;
+  /* because of the LWIP_TCPIP_CORE_LOCKING implementation of lwip_netconn_do_close,
+     don't use TCPIP_APIMSG here */
+  err = tcpip_apimsg(&msg);
+
+  NETCONN_SET_SAFE_ERR(conn, err);
+  return err;
+}
+
+/**
+ * Close a TCP netconn (doesn't delete it).
+ *
+ * @param conn the TCP netconn to close
+ * @return ERR_OK if the netconn was closed, any other err_t on error
+ */
+err_t
+netconn_close(struct netconn *conn)
+{
+  /* shutting down both ends is the same as closing */
+  return netconn_close_shutdown(conn, NETCONN_SHUT_RDWR);
+}
+
+/**
+ * Shut down one or both sides of a TCP netconn (doesn't delete it).
+ *
+ * @param conn the TCP netconn to shut down
+ * @return ERR_OK if the netconn was closed, any other err_t on error
+ */
+err_t
+netconn_shutdown(struct netconn *conn, u8_t shut_rx, u8_t shut_tx)
+{
+  return netconn_close_shutdown(conn, (shut_rx ? NETCONN_SHUT_RD : 0) | (shut_tx ? NETCONN_SHUT_WR : 0));
+}
+
+#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
+/**
+ * Join multicast groups for UDP netconns.
+ *
+ * @param conn the UDP netconn for which to change multicast addresses
+ * @param multiaddr IP address of the multicast group to join or leave
+ * @param netif_addr the IP address of the network interface on which to send
+ *                  the igmp message
+ * @param join_or_leave flag whether to send a join- or leave-message
+ * @return ERR_OK if the action was taken, any err_t on error
+ */
+err_t
+netconn_join_leave_group(struct netconn *conn,
+                         ip_addr_t *multiaddr,
+                         ip_addr_t *netif_addr,
+                         enum netconn_igmp join_or_leave)
+{
+  struct api_msg msg;
+  err_t err;
+
+  LWIP_ERROR("netconn_join_leave_group: invalid conn",  (conn != NULL), return ERR_ARG;);
+
+  msg.msg.conn = conn;
+  msg.msg.msg.jl.multiaddr = ip_2_ipX(multiaddr);
+  msg.msg.msg.jl.netif_addr = ip_2_ipX(netif_addr);
+  msg.msg.msg.jl.join_or_leave = join_or_leave;
+  TCPIP_APIMSG(&msg, lwip_netconn_do_join_leave_group, err);
+
+  NETCONN_SET_SAFE_ERR(conn, err);
+  return err;
+}
+#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+
+#if LWIP_DNS
+/**
+ * Execute a DNS query, only one IP address is returned
+ *
+ * @param name a string representation of the DNS host name to query
+ * @param addr a preallocated ip_addr_t where to store the resolved IP address
+ * @return ERR_OK: resolving succeeded
+ *         ERR_MEM: memory error, try again later
+ *         ERR_ARG: dns client not initialized or invalid hostname
+ *         ERR_VAL: dns server response was invalid
+ */
+err_t
+netconn_gethostbyname(const char *name, ip_addr_t *addr)
+{
+  struct dns_api_msg msg;
+  err_t err;
+  sys_sem_t sem;
+
+  LWIP_ERROR("netconn_gethostbyname: invalid name", (name != NULL), return ERR_ARG;);
+  LWIP_ERROR("netconn_gethostbyname: invalid addr", (addr != NULL), return ERR_ARG;);
+
+  err = sys_sem_new(&sem, 0);
+  if (err != ERR_OK) {
+    return err;
+  }
+
+  msg.name = name;
+  msg.addr = addr;
+  msg.err = &err;
+  msg.sem = &sem;
+
+  tcpip_callback(lwip_netconn_do_gethostbyname, &msg);
+  sys_sem_wait(&sem);
+  sys_sem_free(&sem);
+
+  return err;
+}
+#endif /* LWIP_DNS*/
+
+#endif /* LWIP_NETCONN */
diff --git a/external/badvpn_dns/lwip/src/api/api_msg.c b/external/badvpn_dns/lwip/src/api/api_msg.c
new file mode 100644
index 0000000..b1a9b77
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/api/api_msg.c
@@ -0,0 +1,1610 @@
+/**
+ * @file
+ * Sequential API Internal module
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_NETCONN /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/api_msg.h"
+
+#include "lwip/ip.h"
+#include "lwip/udp.h"
+#include "lwip/tcp.h"
+#include "lwip/raw.h"
+
+#include "lwip/memp.h"
+#include "lwip/tcpip.h"
+#include "lwip/igmp.h"
+#include "lwip/dns.h"
+#include "lwip/mld6.h"
+
+#include <string.h>
+
+#define SET_NONBLOCKING_CONNECT(conn, val)  do { if(val) { \
+  (conn)->flags |= NETCONN_FLAG_IN_NONBLOCKING_CONNECT; \
+} else { \
+  (conn)->flags &= ~ NETCONN_FLAG_IN_NONBLOCKING_CONNECT; }} while(0)
+#define IN_NONBLOCKING_CONNECT(conn) (((conn)->flags & NETCONN_FLAG_IN_NONBLOCKING_CONNECT) != 0)
+
+/* forward declarations */
+#if LWIP_TCP
+static err_t lwip_netconn_do_writemore(struct netconn *conn);
+static void lwip_netconn_do_close_internal(struct netconn *conn);
+#endif
+
+#if LWIP_RAW
+/**
+ * Receive callback function for RAW netconns.
+ * Doesn't 'eat' the packet, only references it and sends it to
+ * conn->recvmbox
+ *
+ * @see raw.h (struct raw_pcb.recv) for parameters and return value
+ */
+static u8_t
+recv_raw(void *arg, struct raw_pcb *pcb, struct pbuf *p,
+    ip_addr_t *addr)
+{
+  struct pbuf *q;
+  struct netbuf *buf;
+  struct netconn *conn;
+
+  LWIP_UNUSED_ARG(addr);
+  conn = (struct netconn *)arg;
+
+  if ((conn != NULL) && sys_mbox_valid(&conn->recvmbox)) {
+#if LWIP_SO_RCVBUF
+    int recv_avail;
+    SYS_ARCH_GET(conn->recv_avail, recv_avail);
+    if ((recv_avail + (int)(p->tot_len)) > conn->recv_bufsize) {
+      return 0;
+    }
+#endif /* LWIP_SO_RCVBUF */
+    /* copy the whole packet into new pbufs */
+    q = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
+    if(q != NULL) {
+      if (pbuf_copy(q, p) != ERR_OK) {
+        pbuf_free(q);
+        q = NULL;
+      }
+    }
+
+    if (q != NULL) {
+      u16_t len;
+      buf = (struct netbuf *)memp_malloc(MEMP_NETBUF);
+      if (buf == NULL) {
+        pbuf_free(q);
+        return 0;
+      }
+
+      buf->p = q;
+      buf->ptr = q;
+      ipX_addr_copy(PCB_ISIPV6(pcb), buf->addr, *ipX_current_src_addr());
+      buf->port = pcb->protocol;
+
+      len = q->tot_len;
+      if (sys_mbox_trypost(&conn->recvmbox, buf) != ERR_OK) {
+        netbuf_delete(buf);
+        return 0;
+      } else {
+#if LWIP_SO_RCVBUF
+        SYS_ARCH_INC(conn->recv_avail, len);
+#endif /* LWIP_SO_RCVBUF */
+        /* Register event with callback */
+        API_EVENT(conn, NETCONN_EVT_RCVPLUS, len);
+      }
+    }
+  }
+
+  return 0; /* do not eat the packet */
+}
+#endif /* LWIP_RAW*/
+
+#if LWIP_UDP
+/**
+ * Receive callback function for UDP netconns.
+ * Posts the packet to conn->recvmbox or deletes it on memory error.
+ *
+ * @see udp.h (struct udp_pcb.recv) for parameters
+ */
+static void
+recv_udp(void *arg, struct udp_pcb *pcb, struct pbuf *p,
+   ip_addr_t *addr, u16_t port)
+{
+  struct netbuf *buf;
+  struct netconn *conn;
+  u16_t len;
+#if LWIP_SO_RCVBUF
+  int recv_avail;
+#endif /* LWIP_SO_RCVBUF */
+
+  LWIP_UNUSED_ARG(pcb); /* only used for asserts... */
+  LWIP_ASSERT("recv_udp must have a pcb argument", pcb != NULL);
+  LWIP_ASSERT("recv_udp must have an argument", arg != NULL);
+  conn = (struct netconn *)arg;
+  LWIP_ASSERT("recv_udp: recv for wrong pcb!", conn->pcb.udp == pcb);
+
+#if LWIP_SO_RCVBUF
+  SYS_ARCH_GET(conn->recv_avail, recv_avail);
+  if ((conn == NULL) || !sys_mbox_valid(&conn->recvmbox) ||
+      ((recv_avail + (int)(p->tot_len)) > conn->recv_bufsize)) {
+#else  /* LWIP_SO_RCVBUF */
+  if ((conn == NULL) || !sys_mbox_valid(&conn->recvmbox)) {
+#endif /* LWIP_SO_RCVBUF */
+    pbuf_free(p);
+    return;
+  }
+
+  buf = (struct netbuf *)memp_malloc(MEMP_NETBUF);
+  if (buf == NULL) {
+    pbuf_free(p);
+    return;
+  } else {
+    buf->p = p;
+    buf->ptr = p;
+    ipX_addr_set_ipaddr(ip_current_is_v6(), &buf->addr, addr);
+    buf->port = port;
+#if LWIP_NETBUF_RECVINFO
+    {
+      /* get the UDP header - always in the first pbuf, ensured by udp_input */
+      const struct udp_hdr* udphdr = ipX_next_header_ptr();
+#if LWIP_CHECKSUM_ON_COPY
+      buf->flags = NETBUF_FLAG_DESTADDR;
+#endif /* LWIP_CHECKSUM_ON_COPY */
+      ipX_addr_set(ip_current_is_v6(), &buf->toaddr, ipX_current_dest_addr());
+      buf->toport_chksum = udphdr->dest;
+    }
+#endif /* LWIP_NETBUF_RECVINFO */
+  }
+
+  len = p->tot_len;
+  if (sys_mbox_trypost(&conn->recvmbox, buf) != ERR_OK) {
+    netbuf_delete(buf);
+    return;
+  } else {
+#if LWIP_SO_RCVBUF
+    SYS_ARCH_INC(conn->recv_avail, len);
+#endif /* LWIP_SO_RCVBUF */
+    /* Register event with callback */
+    API_EVENT(conn, NETCONN_EVT_RCVPLUS, len);
+  }
+}
+#endif /* LWIP_UDP */
+
+#if LWIP_TCP
+/**
+ * Receive callback function for TCP netconns.
+ * Posts the packet to conn->recvmbox, but doesn't delete it on errors.
+ *
+ * @see tcp.h (struct tcp_pcb.recv) for parameters and return value
+ */
+static err_t
+recv_tcp(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+  struct netconn *conn;
+  u16_t len;
+
+  LWIP_UNUSED_ARG(pcb);
+  LWIP_ASSERT("recv_tcp must have a pcb argument", pcb != NULL);
+  LWIP_ASSERT("recv_tcp must have an argument", arg != NULL);
+  conn = (struct netconn *)arg;
+  LWIP_ASSERT("recv_tcp: recv for wrong pcb!", conn->pcb.tcp == pcb);
+
+  if (conn == NULL) {
+    return ERR_VAL;
+  }
+  if (!sys_mbox_valid(&conn->recvmbox)) {
+    /* recvmbox already deleted */
+    if (p != NULL) {
+      tcp_recved(pcb, p->tot_len);
+      pbuf_free(p);
+    }
+    return ERR_OK;
+  }
+  /* Unlike for UDP or RAW pcbs, don't check for available space
+     using recv_avail since that could break the connection
+     (data is already ACKed) */
+
+  /* don't overwrite fatal errors! */
+  NETCONN_SET_SAFE_ERR(conn, err);
+
+  if (p != NULL) {
+    len = p->tot_len;
+  } else {
+    len = 0;
+  }
+
+  if (sys_mbox_trypost(&conn->recvmbox, p) != ERR_OK) {
+    /* don't deallocate p: it is presented to us later again from tcp_fasttmr! */
+    return ERR_MEM;
+  } else {
+#if LWIP_SO_RCVBUF
+    SYS_ARCH_INC(conn->recv_avail, len);
+#endif /* LWIP_SO_RCVBUF */
+    /* Register event with callback */
+    API_EVENT(conn, NETCONN_EVT_RCVPLUS, len);
+  }
+
+  return ERR_OK;
+}
+
+/**
+ * Poll callback function for TCP netconns.
+ * Wakes up an application thread that waits for a connection to close
+ * or data to be sent. The application thread then takes the
+ * appropriate action to go on.
+ *
+ * Signals the conn->sem.
+ * netconn_close waits for conn->sem if closing failed.
+ *
+ * @see tcp.h (struct tcp_pcb.poll) for parameters and return value
+ */
+static err_t
+poll_tcp(void *arg, struct tcp_pcb *pcb)
+{
+  struct netconn *conn = (struct netconn *)arg;
+
+  LWIP_UNUSED_ARG(pcb);
+  LWIP_ASSERT("conn != NULL", (conn != NULL));
+
+  if (conn->state == NETCONN_WRITE) {
+    lwip_netconn_do_writemore(conn);
+  } else if (conn->state == NETCONN_CLOSE) {
+    lwip_netconn_do_close_internal(conn);
+  }
+  /* @todo: implement connect timeout here? */
+
+  /* Did a nonblocking write fail before? Then check available write-space. */
+  if (conn->flags & NETCONN_FLAG_CHECK_WRITESPACE) {
+    /* If the queued byte- or pbuf-count drops below the configured low-water limit,
+       let select mark this pcb as writable again. */
+    if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT) &&
+      (tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT)) {
+      conn->flags &= ~NETCONN_FLAG_CHECK_WRITESPACE;
+      API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0);
+    }
+  }
+
+  return ERR_OK;
+}
+
+/**
+ * Sent callback function for TCP netconns.
+ * Signals the conn->sem and calls API_EVENT.
+ * netconn_write waits for conn->sem if send buffer is low.
+ *
+ * @see tcp.h (struct tcp_pcb.sent) for parameters and return value
+ */
+static err_t
+sent_tcp(void *arg, struct tcp_pcb *pcb, u16_t len)
+{
+  struct netconn *conn = (struct netconn *)arg;
+
+  LWIP_UNUSED_ARG(pcb);
+  LWIP_ASSERT("conn != NULL", (conn != NULL));
+
+  if (conn->state == NETCONN_WRITE) {
+    lwip_netconn_do_writemore(conn);
+  } else if (conn->state == NETCONN_CLOSE) {
+    lwip_netconn_do_close_internal(conn);
+  }
+
+  if (conn) {
+    /* If the queued byte- or pbuf-count drops below the configured low-water limit,
+       let select mark this pcb as writable again. */
+    if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT) &&
+      (tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT)) {
+      conn->flags &= ~NETCONN_FLAG_CHECK_WRITESPACE;
+      API_EVENT(conn, NETCONN_EVT_SENDPLUS, len);
+    }
+  }
+  
+  return ERR_OK;
+}
+
+/**
+ * Error callback function for TCP netconns.
+ * Signals conn->sem, posts to all conn mboxes and calls API_EVENT.
+ * The application thread has then to decide what to do.
+ *
+ * @see tcp.h (struct tcp_pcb.err) for parameters
+ */
+static void
+err_tcp(void *arg, err_t err)
+{
+  struct netconn *conn;
+  enum netconn_state old_state;
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  conn = (struct netconn *)arg;
+  LWIP_ASSERT("conn != NULL", (conn != NULL));
+
+  conn->pcb.tcp = NULL;
+
+  /* no check since this is always fatal! */
+  SYS_ARCH_PROTECT(lev);
+  conn->last_err = err;
+  SYS_ARCH_UNPROTECT(lev);
+
+  /* reset conn->state now before waking up other threads */
+  old_state = conn->state;
+  conn->state = NETCONN_NONE;
+
+  /* Notify the user layer about a connection error. Used to signal
+     select. */
+  API_EVENT(conn, NETCONN_EVT_ERROR, 0);
+  /* Try to release selects pending on 'read' or 'write', too.
+     They will get an error if they actually try to read or write. */
+  API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0);
+  API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0);
+
+  /* pass NULL-message to recvmbox to wake up pending recv */
+  if (sys_mbox_valid(&conn->recvmbox)) {
+    /* use trypost to prevent deadlock */
+    sys_mbox_trypost(&conn->recvmbox, NULL);
+  }
+  /* pass NULL-message to acceptmbox to wake up pending accept */
+  if (sys_mbox_valid(&conn->acceptmbox)) {
+    /* use trypost to preven deadlock */
+    sys_mbox_trypost(&conn->acceptmbox, NULL);
+  }
+
+  if ((old_state == NETCONN_WRITE) || (old_state == NETCONN_CLOSE) ||
+      (old_state == NETCONN_CONNECT)) {
+    /* calling lwip_netconn_do_writemore/lwip_netconn_do_close_internal is not necessary
+       since the pcb has already been deleted! */
+    int was_nonblocking_connect = IN_NONBLOCKING_CONNECT(conn);
+    SET_NONBLOCKING_CONNECT(conn, 0);
+
+    if (!was_nonblocking_connect) {
+      /* set error return code */
+      LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL);
+      conn->current_msg->err = err;
+      conn->current_msg = NULL;
+      /* wake up the waiting task */
+      sys_sem_signal(&conn->op_completed);
+    }
+  } else {
+    LWIP_ASSERT("conn->current_msg == NULL", conn->current_msg == NULL);
+  }
+}
+
+/**
+ * Setup a tcp_pcb with the correct callback function pointers
+ * and their arguments.
+ *
+ * @param conn the TCP netconn to setup
+ */
+static void
+setup_tcp(struct netconn *conn)
+{
+  struct tcp_pcb *pcb;
+
+  pcb = conn->pcb.tcp;
+  tcp_arg(pcb, conn);
+  tcp_recv(pcb, recv_tcp);
+  tcp_sent(pcb, sent_tcp);
+  tcp_poll(pcb, poll_tcp, 4);
+  tcp_err(pcb, err_tcp);
+}
+
+/**
+ * Accept callback function for TCP netconns.
+ * Allocates a new netconn and posts that to conn->acceptmbox.
+ *
+ * @see tcp.h (struct tcp_pcb_listen.accept) for parameters and return value
+ */
+static err_t
+accept_function(void *arg, struct tcp_pcb *newpcb, err_t err)
+{
+  struct netconn *newconn;
+  struct netconn *conn = (struct netconn *)arg;
+
+  LWIP_DEBUGF(API_MSG_DEBUG, ("accept_function: newpcb->tate: %s\n", tcp_debug_state_str(newpcb->state)));
+
+  if (!sys_mbox_valid(&conn->acceptmbox)) {
+    LWIP_DEBUGF(API_MSG_DEBUG, ("accept_function: acceptmbox already deleted\n"));
+    return ERR_VAL;
+  }
+
+  /* We have to set the callback here even though
+   * the new socket is unknown. conn->socket is marked as -1. */
+  newconn = netconn_alloc(conn->type, conn->callback);
+  if (newconn == NULL) {
+    return ERR_MEM;
+  }
+  newconn->pcb.tcp = newpcb;
+  setup_tcp(newconn);
+  /* no protection: when creating the pcb, the netconn is not yet known
+     to the application thread */
+  newconn->last_err = err;
+
+  if (sys_mbox_trypost(&conn->acceptmbox, newconn) != ERR_OK) {
+    /* When returning != ERR_OK, the pcb is aborted in tcp_process(),
+       so do nothing here! */
+    /* remove all references to this netconn from the pcb */
+    struct tcp_pcb* pcb = newconn->pcb.tcp;
+    tcp_arg(pcb, NULL);
+    tcp_recv(pcb, NULL);
+    tcp_sent(pcb, NULL);
+    tcp_poll(pcb, NULL, 4);
+    tcp_err(pcb, NULL);
+    /* remove reference from to the pcb from this netconn */
+    newconn->pcb.tcp = NULL;
+    /* no need to drain since we know the recvmbox is empty. */
+    sys_mbox_free(&newconn->recvmbox);
+    sys_mbox_set_invalid(&newconn->recvmbox);
+    netconn_free(newconn);
+    return ERR_MEM;
+  } else {
+    /* Register event with callback */
+    API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0);
+  }
+
+  return ERR_OK;
+}
+#endif /* LWIP_TCP */
+
+/**
+ * Create a new pcb of a specific type.
+ * Called from lwip_netconn_do_newconn().
+ *
+ * @param msg the api_msg_msg describing the connection type
+ * @return msg->conn->err, but the return value is currently ignored
+ */
+static void
+pcb_new(struct api_msg_msg *msg)
+{
+  LWIP_ASSERT("pcb_new: pcb already allocated", msg->conn->pcb.tcp == NULL);
+
+  /* Allocate a PCB for this connection */
+  switch(NETCONNTYPE_GROUP(msg->conn->type)) {
+#if LWIP_RAW
+  case NETCONN_RAW:
+    msg->conn->pcb.raw = raw_new(msg->msg.n.proto);
+    if(msg->conn->pcb.raw != NULL) {
+      raw_recv(msg->conn->pcb.raw, recv_raw, msg->conn);
+    }
+    break;
+#endif /* LWIP_RAW */
+#if LWIP_UDP
+  case NETCONN_UDP:
+    msg->conn->pcb.udp = udp_new();
+    if(msg->conn->pcb.udp != NULL) {
+#if LWIP_UDPLITE
+      if (NETCONNTYPE_ISUDPLITE(msg->conn->type)) {
+        udp_setflags(msg->conn->pcb.udp, UDP_FLAGS_UDPLITE);
+      }
+#endif /* LWIP_UDPLITE */
+      if (NETCONNTYPE_ISUDPNOCHKSUM(msg->conn->type)) {
+        udp_setflags(msg->conn->pcb.udp, UDP_FLAGS_NOCHKSUM);
+      }
+      udp_recv(msg->conn->pcb.udp, recv_udp, msg->conn);
+    }
+    break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+  case NETCONN_TCP:
+    msg->conn->pcb.tcp = tcp_new();
+    if(msg->conn->pcb.tcp != NULL) {
+      setup_tcp(msg->conn);
+    }
+    break;
+#endif /* LWIP_TCP */
+  default:
+    /* Unsupported netconn type, e.g. protocol disabled */
+    msg->err = ERR_VAL;
+    return;
+  }
+  if (msg->conn->pcb.ip == NULL) {
+    msg->err = ERR_MEM;
+  }
+#if LWIP_IPV6
+  else {
+    if (NETCONNTYPE_ISIPV6(msg->conn->type)) {
+      ip_set_v6(msg->conn->pcb.ip, 1);
+    }
+  }
+#endif /* LWIP_IPV6 */
+}
+
+/**
+ * Create a new pcb of a specific type inside a netconn.
+ * Called from netconn_new_with_proto_and_callback.
+ *
+ * @param msg the api_msg_msg describing the connection type
+ */
+void
+lwip_netconn_do_newconn(struct api_msg_msg *msg)
+{
+  msg->err = ERR_OK;
+  if(msg->conn->pcb.tcp == NULL) {
+    pcb_new(msg);
+  }
+  /* Else? This "new" connection already has a PCB allocated. */
+  /* Is this an error condition? Should it be deleted? */
+  /* We currently just are happy and return. */
+
+  TCPIP_APIMSG_ACK(msg);
+}
+
+/**
+ * Create a new netconn (of a specific type) that has a callback function.
+ * The corresponding pcb is NOT created!
+ *
+ * @param t the type of 'connection' to create (@see enum netconn_type)
+ * @param proto the IP protocol for RAW IP pcbs
+ * @param callback a function to call on status changes (RX available, TX'ed)
+ * @return a newly allocated struct netconn or
+ *         NULL on memory error
+ */
+struct netconn*
+netconn_alloc(enum netconn_type t, netconn_callback callback)
+{
+  struct netconn *conn;
+  int size;
+
+  conn = (struct netconn *)memp_malloc(MEMP_NETCONN);
+  if (conn == NULL) {
+    return NULL;
+  }
+
+  conn->last_err = ERR_OK;
+  conn->type = t;
+  conn->pcb.tcp = NULL;
+
+  /* If all sizes are the same, every compiler should optimize this switch to nothing, */
+  switch(NETCONNTYPE_GROUP(t)) {
+#if LWIP_RAW
+  case NETCONN_RAW:
+    size = DEFAULT_RAW_RECVMBOX_SIZE;
+    break;
+#endif /* LWIP_RAW */
+#if LWIP_UDP
+  case NETCONN_UDP:
+    size = DEFAULT_UDP_RECVMBOX_SIZE;
+    break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+  case NETCONN_TCP:
+    size = DEFAULT_TCP_RECVMBOX_SIZE;
+    break;
+#endif /* LWIP_TCP */
+  default:
+    LWIP_ASSERT("netconn_alloc: undefined netconn_type", 0);
+    goto free_and_return;
+  }
+
+  if (sys_sem_new(&conn->op_completed, 0) != ERR_OK) {
+    goto free_and_return;
+  }
+  if (sys_mbox_new(&conn->recvmbox, size) != ERR_OK) {
+    sys_sem_free(&conn->op_completed);
+    goto free_and_return;
+  }
+
+#if LWIP_TCP
+  sys_mbox_set_invalid(&conn->acceptmbox);
+#endif
+  conn->state        = NETCONN_NONE;
+#if LWIP_SOCKET
+  /* initialize socket to -1 since 0 is a valid socket */
+  conn->socket       = -1;
+#endif /* LWIP_SOCKET */
+  conn->callback     = callback;
+#if LWIP_TCP
+  conn->current_msg  = NULL;
+  conn->write_offset = 0;
+#endif /* LWIP_TCP */
+#if LWIP_SO_SNDTIMEO
+  conn->send_timeout = 0;
+#endif /* LWIP_SO_SNDTIMEO */
+#if LWIP_SO_RCVTIMEO
+  conn->recv_timeout = 0;
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVBUF
+  conn->recv_bufsize = RECV_BUFSIZE_DEFAULT;
+  conn->recv_avail   = 0;
+#endif /* LWIP_SO_RCVBUF */
+  conn->flags = 0;
+  return conn;
+free_and_return:
+  memp_free(MEMP_NETCONN, conn);
+  return NULL;
+}
+
+/**
+ * Delete a netconn and all its resources.
+ * The pcb is NOT freed (since we might not be in the right thread context do this).
+ *
+ * @param conn the netconn to free
+ */
+void
+netconn_free(struct netconn *conn)
+{
+  LWIP_ASSERT("PCB must be deallocated outside this function", conn->pcb.tcp == NULL);
+  LWIP_ASSERT("recvmbox must be deallocated before calling this function",
+    !sys_mbox_valid(&conn->recvmbox));
+#if LWIP_TCP
+  LWIP_ASSERT("acceptmbox must be deallocated before calling this function",
+    !sys_mbox_valid(&conn->acceptmbox));
+#endif /* LWIP_TCP */
+
+  sys_sem_free(&conn->op_completed);
+  sys_sem_set_invalid(&conn->op_completed);
+
+  memp_free(MEMP_NETCONN, conn);
+}
+
+/**
+ * Delete rcvmbox and acceptmbox of a netconn and free the left-over data in
+ * these mboxes
+ *
+ * @param conn the netconn to free
+ * @bytes_drained bytes drained from recvmbox
+ * @accepts_drained pending connections drained from acceptmbox
+ */
+static void
+netconn_drain(struct netconn *conn)
+{
+  void *mem;
+#if LWIP_TCP
+  struct pbuf *p;
+#endif /* LWIP_TCP */
+
+  /* This runs in tcpip_thread, so we don't need to lock against rx packets */
+
+  /* Delete and drain the recvmbox. */
+  if (sys_mbox_valid(&conn->recvmbox)) {
+    while (sys_mbox_tryfetch(&conn->recvmbox, &mem) != SYS_MBOX_EMPTY) {
+#if LWIP_TCP
+      if (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP) {
+        if(mem != NULL) {
+          p = (struct pbuf*)mem;
+          /* pcb might be set to NULL already by err_tcp() */
+          if (conn->pcb.tcp != NULL) {
+            tcp_recved(conn->pcb.tcp, p->tot_len);
+          }
+          pbuf_free(p);
+        }
+      } else
+#endif /* LWIP_TCP */
+      {
+        netbuf_delete((struct netbuf *)mem);
+      }
+    }
+    sys_mbox_free(&conn->recvmbox);
+    sys_mbox_set_invalid(&conn->recvmbox);
+  }
+
+  /* Delete and drain the acceptmbox. */
+#if LWIP_TCP
+  if (sys_mbox_valid(&conn->acceptmbox)) {
+    while (sys_mbox_tryfetch(&conn->acceptmbox, &mem) != SYS_MBOX_EMPTY) {
+      struct netconn *newconn = (struct netconn *)mem;
+      /* Only tcp pcbs have an acceptmbox, so no need to check conn->type */
+      /* pcb might be set to NULL already by err_tcp() */
+      if (conn->pcb.tcp != NULL) {
+        tcp_accepted(conn->pcb.tcp);
+      }
+      /* drain recvmbox */
+      netconn_drain(newconn);
+      if (newconn->pcb.tcp != NULL) {
+        tcp_abort(newconn->pcb.tcp);
+        newconn->pcb.tcp = NULL;
+      }
+      netconn_free(newconn);
+    }
+    sys_mbox_free(&conn->acceptmbox);
+    sys_mbox_set_invalid(&conn->acceptmbox);
+  }
+#endif /* LWIP_TCP */
+}
+
+#if LWIP_TCP
+/**
+ * Internal helper function to close a TCP netconn: since this sometimes
+ * doesn't work at the first attempt, this function is called from multiple
+ * places.
+ *
+ * @param conn the TCP netconn to close
+ */
+static void
+lwip_netconn_do_close_internal(struct netconn *conn)
+{
+  err_t err;
+  u8_t shut, shut_rx, shut_tx, close;
+
+  LWIP_ASSERT("invalid conn", (conn != NULL));
+  LWIP_ASSERT("this is for tcp netconns only", (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP));
+  LWIP_ASSERT("conn must be in state NETCONN_CLOSE", (conn->state == NETCONN_CLOSE));
+  LWIP_ASSERT("pcb already closed", (conn->pcb.tcp != NULL));
+  LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL);
+
+  shut = conn->current_msg->msg.sd.shut;
+  shut_rx = shut & NETCONN_SHUT_RD;
+  shut_tx = shut & NETCONN_SHUT_WR;
+  /* shutting down both ends is the same as closing */
+  close = shut == NETCONN_SHUT_RDWR;
+
+  /* Set back some callback pointers */
+  if (close) {
+    tcp_arg(conn->pcb.tcp, NULL);
+  }
+  if (conn->pcb.tcp->state == LISTEN) {
+    tcp_accept(conn->pcb.tcp, NULL);
+  } else {
+    /* some callbacks have to be reset if tcp_close is not successful */
+    if (shut_rx) {
+      tcp_recv(conn->pcb.tcp, NULL);
+      tcp_accept(conn->pcb.tcp, NULL);
+    }
+    if (shut_tx) {
+      tcp_sent(conn->pcb.tcp, NULL);
+    }
+    if (close) {
+      tcp_poll(conn->pcb.tcp, NULL, 4);
+      tcp_err(conn->pcb.tcp, NULL);
+    }
+  }
+  /* Try to close the connection */
+  if (close) {
+    err = tcp_close(conn->pcb.tcp);
+  } else {
+    err = tcp_shutdown(conn->pcb.tcp, shut_rx, shut_tx);
+  }
+  if (err == ERR_OK) {
+    /* Closing succeeded */
+    conn->current_msg->err = ERR_OK;
+    conn->current_msg = NULL;
+    conn->state = NETCONN_NONE;
+    if (close) {
+      /* Set back some callback pointers as conn is going away */
+      conn->pcb.tcp = NULL;
+      /* Trigger select() in socket layer. Make sure everybody notices activity
+       on the connection, error first! */
+      API_EVENT(conn, NETCONN_EVT_ERROR, 0);
+    }
+    if (shut_rx) {
+      API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0);
+    }
+    if (shut_tx) {
+      API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0);
+    }
+    /* wake up the application task */
+    sys_sem_signal(&conn->op_completed);
+  } else {
+    /* Closing failed, restore some of the callbacks */
+    /* Closing of listen pcb will never fail! */
+    LWIP_ASSERT("Closing a listen pcb may not fail!", (conn->pcb.tcp->state != LISTEN));
+    tcp_sent(conn->pcb.tcp, sent_tcp);
+    tcp_poll(conn->pcb.tcp, poll_tcp, 4);
+    tcp_err(conn->pcb.tcp, err_tcp);
+    tcp_arg(conn->pcb.tcp, conn);
+    /* don't restore recv callback: we don't want to receive any more data */
+  }
+  /* If closing didn't succeed, we get called again either
+     from poll_tcp or from sent_tcp */
+}
+#endif /* LWIP_TCP */
+
+/**
+ * Delete the pcb inside a netconn.
+ * Called from netconn_delete.
+ *
+ * @param msg the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_delconn(struct api_msg_msg *msg)
+{
+  /* @todo TCP: abort running write/connect? */
+ if ((msg->conn->state != NETCONN_NONE) &&
+     (msg->conn->state != NETCONN_LISTEN) &&
+     (msg->conn->state != NETCONN_CONNECT)) {
+    /* this only happens for TCP netconns */
+    LWIP_ASSERT("NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP",
+                NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP);
+    msg->err = ERR_INPROGRESS;
+  } else {
+    LWIP_ASSERT("blocking connect in progress",
+      (msg->conn->state != NETCONN_CONNECT) || IN_NONBLOCKING_CONNECT(msg->conn));
+    /* Drain and delete mboxes */
+    netconn_drain(msg->conn);
+
+    if (msg->conn->pcb.tcp != NULL) {
+
+      switch (NETCONNTYPE_GROUP(msg->conn->type)) {
+#if LWIP_RAW
+      case NETCONN_RAW:
+        raw_remove(msg->conn->pcb.raw);
+        break;
+#endif /* LWIP_RAW */
+#if LWIP_UDP
+      case NETCONN_UDP:
+        msg->conn->pcb.udp->recv_arg = NULL;
+        udp_remove(msg->conn->pcb.udp);
+        break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+      case NETCONN_TCP:
+        LWIP_ASSERT("already writing or closing", msg->conn->current_msg == NULL &&
+          msg->conn->write_offset == 0);
+        msg->conn->state = NETCONN_CLOSE;
+        msg->msg.sd.shut = NETCONN_SHUT_RDWR;
+        msg->conn->current_msg = msg;
+        lwip_netconn_do_close_internal(msg->conn);
+        /* API_EVENT is called inside lwip_netconn_do_close_internal, before releasing
+           the application thread, so we can return at this point! */
+        return;
+#endif /* LWIP_TCP */
+      default:
+        break;
+      }
+      msg->conn->pcb.tcp = NULL;
+    }
+    /* tcp netconns don't come here! */
+
+    /* @todo: this lets select make the socket readable and writable,
+       which is wrong! errfd instead? */
+    API_EVENT(msg->conn, NETCONN_EVT_RCVPLUS, 0);
+    API_EVENT(msg->conn, NETCONN_EVT_SENDPLUS, 0);
+  }
+  if (sys_sem_valid(&msg->conn->op_completed)) {
+    sys_sem_signal(&msg->conn->op_completed);
+  }
+}
+
+/**
+ * Bind a pcb contained in a netconn
+ * Called from netconn_bind.
+ *
+ * @param msg the api_msg_msg pointing to the connection and containing
+ *            the IP address and port to bind to
+ */
+void
+lwip_netconn_do_bind(struct api_msg_msg *msg)
+{
+  if (ERR_IS_FATAL(msg->conn->last_err)) {
+    msg->err = msg->conn->last_err;
+  } else {
+    msg->err = ERR_VAL;
+    if (msg->conn->pcb.tcp != NULL) {
+      switch (NETCONNTYPE_GROUP(msg->conn->type)) {
+#if LWIP_RAW
+      case NETCONN_RAW:
+        msg->err = raw_bind(msg->conn->pcb.raw, msg->msg.bc.ipaddr);
+        break;
+#endif /* LWIP_RAW */
+#if LWIP_UDP
+      case NETCONN_UDP:
+        msg->err = udp_bind(msg->conn->pcb.udp, msg->msg.bc.ipaddr, msg->msg.bc.port);
+        break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+      case NETCONN_TCP:
+        msg->err = tcp_bind(msg->conn->pcb.tcp, msg->msg.bc.ipaddr, msg->msg.bc.port);
+        break;
+#endif /* LWIP_TCP */
+      default:
+        break;
+      }
+    }
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+
+#if LWIP_TCP
+/**
+ * TCP callback function if a connection (opened by tcp_connect/lwip_netconn_do_connect) has
+ * been established (or reset by the remote host).
+ *
+ * @see tcp.h (struct tcp_pcb.connected) for parameters and return values
+ */
+static err_t
+lwip_netconn_do_connected(void *arg, struct tcp_pcb *pcb, err_t err)
+{
+  struct netconn *conn;
+  int was_blocking;
+
+  LWIP_UNUSED_ARG(pcb);
+
+  conn = (struct netconn *)arg;
+
+  if (conn == NULL) {
+    return ERR_VAL;
+  }
+
+  LWIP_ASSERT("conn->state == NETCONN_CONNECT", conn->state == NETCONN_CONNECT);
+  LWIP_ASSERT("(conn->current_msg != NULL) || conn->in_non_blocking_connect",
+    (conn->current_msg != NULL) || IN_NONBLOCKING_CONNECT(conn));
+
+  if (conn->current_msg != NULL) {
+    conn->current_msg->err = err;
+  }
+  if ((NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP) && (err == ERR_OK)) {
+    setup_tcp(conn);
+  }
+  was_blocking = !IN_NONBLOCKING_CONNECT(conn);
+  SET_NONBLOCKING_CONNECT(conn, 0);
+  conn->current_msg = NULL;
+  conn->state = NETCONN_NONE;
+  if (!was_blocking) {
+    NETCONN_SET_SAFE_ERR(conn, ERR_OK);
+  }
+  API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0);
+
+  if (was_blocking) {
+    sys_sem_signal(&conn->op_completed);
+  }
+  return ERR_OK;
+}
+#endif /* LWIP_TCP */
+
+/**
+ * Connect a pcb contained inside a netconn
+ * Called from netconn_connect.
+ *
+ * @param msg the api_msg_msg pointing to the connection and containing
+ *            the IP address and port to connect to
+ */
+void
+lwip_netconn_do_connect(struct api_msg_msg *msg)
+{
+  if (msg->conn->pcb.tcp == NULL) {
+    /* This may happen when calling netconn_connect() a second time */
+    msg->err = ERR_CLSD;
+    if (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP) {
+      /* For TCP, netconn_connect() calls tcpip_apimsg(), so signal op_completed here. */
+      sys_sem_signal(&msg->conn->op_completed);
+      return;
+    }
+  } else {
+    switch (NETCONNTYPE_GROUP(msg->conn->type)) {
+#if LWIP_RAW
+    case NETCONN_RAW:
+      msg->err = raw_connect(msg->conn->pcb.raw, msg->msg.bc.ipaddr);
+      break;
+#endif /* LWIP_RAW */
+#if LWIP_UDP
+    case NETCONN_UDP:
+      msg->err = udp_connect(msg->conn->pcb.udp, msg->msg.bc.ipaddr, msg->msg.bc.port);
+      break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+    case NETCONN_TCP:
+      /* Prevent connect while doing any other action. */
+      if (msg->conn->state != NETCONN_NONE) {
+        msg->err = ERR_ISCONN;
+      } else {
+        setup_tcp(msg->conn);
+        msg->err = tcp_connect(msg->conn->pcb.tcp, msg->msg.bc.ipaddr,
+          msg->msg.bc.port, lwip_netconn_do_connected);
+        if (msg->err == ERR_OK) {
+          u8_t non_blocking = netconn_is_nonblocking(msg->conn);
+          msg->conn->state = NETCONN_CONNECT;
+          SET_NONBLOCKING_CONNECT(msg->conn, non_blocking);
+          if (non_blocking) {
+            msg->err = ERR_INPROGRESS;
+          } else {
+            msg->conn->current_msg = msg;
+            /* sys_sem_signal() is called from lwip_netconn_do_connected (or err_tcp()),
+            * when the connection is established! */
+            return;
+          }
+        }
+      }
+      /* For TCP, netconn_connect() calls tcpip_apimsg(), so signal op_completed here. */
+      sys_sem_signal(&msg->conn->op_completed);
+      return;
+#endif /* LWIP_TCP */
+    default:
+      LWIP_ERROR("Invalid netconn type", 0, do{ msg->err = ERR_VAL; }while(0));
+      break;
+    }
+  }
+  /* For all other protocols, netconn_connect() calls TCPIP_APIMSG(),
+     so use TCPIP_APIMSG_ACK() here. */
+  TCPIP_APIMSG_ACK(msg);
+}
+
+/**
+ * Connect a pcb contained inside a netconn
+ * Only used for UDP netconns.
+ * Called from netconn_disconnect.
+ *
+ * @param msg the api_msg_msg pointing to the connection to disconnect
+ */
+void
+lwip_netconn_do_disconnect(struct api_msg_msg *msg)
+{
+#if LWIP_UDP
+  if (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_UDP) {
+    udp_disconnect(msg->conn->pcb.udp);
+    msg->err = ERR_OK;
+  } else
+#endif /* LWIP_UDP */
+  {
+    msg->err = ERR_VAL;
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+
+#if LWIP_TCP
+/**
+ * Set a TCP pcb contained in a netconn into listen mode
+ * Called from netconn_listen.
+ *
+ * @param msg the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_listen(struct api_msg_msg *msg)
+{
+  if (ERR_IS_FATAL(msg->conn->last_err)) {
+    msg->err = msg->conn->last_err;
+  } else {
+    msg->err = ERR_CONN;
+    if (msg->conn->pcb.tcp != NULL) {
+      if (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP) {
+        if (msg->conn->state == NETCONN_NONE) {
+          struct tcp_pcb* lpcb;
+#if LWIP_IPV6
+          if ((msg->conn->flags & NETCONN_FLAG_IPV6_V6ONLY) == 0) {
+#if TCP_LISTEN_BACKLOG
+            lpcb = tcp_listen_dual_with_backlog(msg->conn->pcb.tcp, msg->msg.lb.backlog);
+#else  /* TCP_LISTEN_BACKLOG */
+            lpcb = tcp_listen_dual(msg->conn->pcb.tcp);
+#endif /* TCP_LISTEN_BACKLOG */
+          } else
+#endif /* LWIP_IPV6 */
+          {
+#if TCP_LISTEN_BACKLOG
+            lpcb = tcp_listen_with_backlog(msg->conn->pcb.tcp, msg->msg.lb.backlog);
+#else  /* TCP_LISTEN_BACKLOG */
+            lpcb = tcp_listen(msg->conn->pcb.tcp);
+#endif /* TCP_LISTEN_BACKLOG */
+          }
+          if (lpcb == NULL) {
+            /* in this case, the old pcb is still allocated */
+            msg->err = ERR_MEM;
+          } else {
+            /* delete the recvmbox and allocate the acceptmbox */
+            if (sys_mbox_valid(&msg->conn->recvmbox)) {
+              /** @todo: should we drain the recvmbox here? */
+              sys_mbox_free(&msg->conn->recvmbox);
+              sys_mbox_set_invalid(&msg->conn->recvmbox);
+            }
+            msg->err = ERR_OK;
+            if (!sys_mbox_valid(&msg->conn->acceptmbox)) {
+              msg->err = sys_mbox_new(&msg->conn->acceptmbox, DEFAULT_ACCEPTMBOX_SIZE);
+            }
+            if (msg->err == ERR_OK) {
+              msg->conn->state = NETCONN_LISTEN;
+              msg->conn->pcb.tcp = lpcb;
+              tcp_arg(msg->conn->pcb.tcp, msg->conn);
+              tcp_accept(msg->conn->pcb.tcp, accept_function);
+            } else {
+              /* since the old pcb is already deallocated, free lpcb now */
+              tcp_close(lpcb);
+              msg->conn->pcb.tcp = NULL;
+            }
+          }
+        }
+      } else {
+        msg->err = ERR_ARG;
+      }
+    }
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+#endif /* LWIP_TCP */
+
+/**
+ * Send some data on a RAW or UDP pcb contained in a netconn
+ * Called from netconn_send
+ *
+ * @param msg the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_send(struct api_msg_msg *msg)
+{
+  if (ERR_IS_FATAL(msg->conn->last_err)) {
+    msg->err = msg->conn->last_err;
+  } else {
+    msg->err = ERR_CONN;
+    if (msg->conn->pcb.tcp != NULL) {
+      switch (NETCONNTYPE_GROUP(msg->conn->type)) {
+#if LWIP_RAW
+      case NETCONN_RAW:
+        if (ipX_addr_isany(PCB_ISIPV6(msg->conn->pcb.ip), &msg->msg.b->addr)) {
+          msg->err = raw_send(msg->conn->pcb.raw, msg->msg.b->p);
+        } else {
+          msg->err = raw_sendto(msg->conn->pcb.raw, msg->msg.b->p, ipX_2_ip(&msg->msg.b->addr));
+        }
+        break;
+#endif
+#if LWIP_UDP
+      case NETCONN_UDP:
+#if LWIP_CHECKSUM_ON_COPY
+        if (ipX_addr_isany(PCB_ISIPV6(msg->conn->pcb.ip), &msg->msg.b->addr)) {
+          msg->err = udp_send_chksum(msg->conn->pcb.udp, msg->msg.b->p,
+            msg->msg.b->flags & NETBUF_FLAG_CHKSUM, msg->msg.b->toport_chksum);
+        } else {
+          msg->err = udp_sendto_chksum(msg->conn->pcb.udp, msg->msg.b->p,
+            ipX_2_ip(&msg->msg.b->addr), msg->msg.b->port,
+            msg->msg.b->flags & NETBUF_FLAG_CHKSUM, msg->msg.b->toport_chksum);
+        }
+#else /* LWIP_CHECKSUM_ON_COPY */
+        if (ipX_addr_isany(PCB_ISIPV6(msg->conn->pcb.ip), &msg->msg.b->addr)) {
+          msg->err = udp_send(msg->conn->pcb.udp, msg->msg.b->p);
+        } else {
+          msg->err = udp_sendto(msg->conn->pcb.udp, msg->msg.b->p, ipX_2_ip(&msg->msg.b->addr), msg->msg.b->port);
+        }
+#endif /* LWIP_CHECKSUM_ON_COPY */
+        break;
+#endif /* LWIP_UDP */
+      default:
+        break;
+      }
+    }
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+
+#if LWIP_TCP
+/**
+ * Indicate data has been received from a TCP pcb contained in a netconn
+ * Called from netconn_recv
+ *
+ * @param msg the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_recv(struct api_msg_msg *msg)
+{
+  msg->err = ERR_OK;
+  if (msg->conn->pcb.tcp != NULL) {
+    if (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP) {
+#if TCP_LISTEN_BACKLOG
+      if (msg->conn->pcb.tcp->state == LISTEN) {
+        tcp_accepted(msg->conn->pcb.tcp);
+      } else
+#endif /* TCP_LISTEN_BACKLOG */
+      {
+        u32_t remaining = msg->msg.r.len;
+        do {
+          u16_t recved = (remaining > 0xffff) ? 0xffff : (u16_t)remaining;
+          tcp_recved(msg->conn->pcb.tcp, recved);
+          remaining -= recved;
+        }while(remaining != 0);
+      }
+    }
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+
+/**
+ * See if more data needs to be written from a previous call to netconn_write.
+ * Called initially from lwip_netconn_do_write. If the first call can't send all data
+ * (because of low memory or empty send-buffer), this function is called again
+ * from sent_tcp() or poll_tcp() to send more data. If all data is sent, the
+ * blocking application thread (waiting in netconn_write) is released.
+ *
+ * @param conn netconn (that is currently in state NETCONN_WRITE) to process
+ * @return ERR_OK
+ *         ERR_MEM if LWIP_TCPIP_CORE_LOCKING=1 and sending hasn't yet finished
+ */
+static err_t
+lwip_netconn_do_writemore(struct netconn *conn)
+{
+  err_t err;
+  void *dataptr;
+  u16_t len, available;
+  u8_t write_finished = 0;
+  size_t diff;
+  u8_t dontblock = netconn_is_nonblocking(conn) ||
+       (conn->current_msg->msg.w.apiflags & NETCONN_DONTBLOCK);
+  u8_t apiflags = conn->current_msg->msg.w.apiflags;
+
+  LWIP_ASSERT("conn != NULL", conn != NULL);
+  LWIP_ASSERT("conn->state == NETCONN_WRITE", (conn->state == NETCONN_WRITE));
+  LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL);
+  LWIP_ASSERT("conn->pcb.tcp != NULL", conn->pcb.tcp != NULL);
+  LWIP_ASSERT("conn->write_offset < conn->current_msg->msg.w.len",
+    conn->write_offset < conn->current_msg->msg.w.len);
+
+#if LWIP_SO_SNDTIMEO
+  if ((conn->send_timeout != 0) &&
+      ((s32_t)(sys_now() - conn->current_msg->msg.w.time_started) >= conn->send_timeout)) {
+    write_finished = 1;
+    if (conn->write_offset == 0) {
+      /* nothing has been written */
+      err = ERR_WOULDBLOCK;
+      conn->current_msg->msg.w.len = 0;
+    } else {
+      /* partial write */
+      err = ERR_OK;
+      conn->current_msg->msg.w.len = conn->write_offset;
+    }
+  } else
+#endif /* LWIP_SO_SNDTIMEO */
+  {
+    dataptr = (u8_t*)conn->current_msg->msg.w.dataptr + conn->write_offset;
+    diff = conn->current_msg->msg.w.len - conn->write_offset;
+    if (diff > 0xffffUL) { /* max_u16_t */
+      len = 0xffff;
+#if LWIP_TCPIP_CORE_LOCKING
+      conn->flags |= NETCONN_FLAG_WRITE_DELAYED;
+#endif
+      apiflags |= TCP_WRITE_FLAG_MORE;
+    } else {
+      len = (u16_t)diff;
+    }
+    available = tcp_sndbuf(conn->pcb.tcp);
+    if (available < len) {
+      /* don't try to write more than sendbuf */
+      len = available;
+      if (dontblock){ 
+        if (!len) {
+          err = ERR_WOULDBLOCK;
+          goto err_mem;
+        }
+      } else {
+#if LWIP_TCPIP_CORE_LOCKING
+        conn->flags |= NETCONN_FLAG_WRITE_DELAYED;
+#endif
+        apiflags |= TCP_WRITE_FLAG_MORE;
+      }
+    }
+    LWIP_ASSERT("lwip_netconn_do_writemore: invalid length!", ((conn->write_offset + len) <= conn->current_msg->msg.w.len));
+    err = tcp_write(conn->pcb.tcp, dataptr, len, apiflags);
+    /* if OK or memory error, check available space */
+    if ((err == ERR_OK) || (err == ERR_MEM)) {
+err_mem:
+      if (dontblock && (len < conn->current_msg->msg.w.len)) {
+        /* non-blocking write did not write everything: mark the pcb non-writable
+           and let poll_tcp check writable space to mark the pcb writable again */
+        API_EVENT(conn, NETCONN_EVT_SENDMINUS, len);
+        conn->flags |= NETCONN_FLAG_CHECK_WRITESPACE;
+      } else if ((tcp_sndbuf(conn->pcb.tcp) <= TCP_SNDLOWAT) ||
+                 (tcp_sndqueuelen(conn->pcb.tcp) >= TCP_SNDQUEUELOWAT)) {
+        /* The queued byte- or pbuf-count exceeds the configured low-water limit,
+           let select mark this pcb as non-writable. */
+        API_EVENT(conn, NETCONN_EVT_SENDMINUS, len);
+      }
+    }
+
+    if (err == ERR_OK) {
+      conn->write_offset += len;
+      if ((conn->write_offset == conn->current_msg->msg.w.len) || dontblock) {
+        /* return sent length */
+        conn->current_msg->msg.w.len = conn->write_offset;
+        /* everything was written */
+        write_finished = 1;
+        conn->write_offset = 0;
+      }
+      tcp_output(conn->pcb.tcp);
+    } else if ((err == ERR_MEM) && !dontblock) {
+      /* If ERR_MEM, we wait for sent_tcp or poll_tcp to be called
+         we do NOT return to the application thread, since ERR_MEM is
+         only a temporary error! */
+
+      /* tcp_write returned ERR_MEM, try tcp_output anyway */
+      tcp_output(conn->pcb.tcp);
+
+#if LWIP_TCPIP_CORE_LOCKING
+      conn->flags |= NETCONN_FLAG_WRITE_DELAYED;
+#endif
+    } else {
+      /* On errors != ERR_MEM, we don't try writing any more but return
+         the error to the application thread. */
+      write_finished = 1;
+      conn->current_msg->msg.w.len = 0;
+    }
+  }
+  if (write_finished) {
+    /* everything was written: set back connection state
+       and back to application task */
+    conn->current_msg->err = err;
+    conn->current_msg = NULL;
+    conn->state = NETCONN_NONE;
+#if LWIP_TCPIP_CORE_LOCKING
+    if ((conn->flags & NETCONN_FLAG_WRITE_DELAYED) != 0)
+#endif
+    {
+      sys_sem_signal(&conn->op_completed);
+    }
+  }
+#if LWIP_TCPIP_CORE_LOCKING
+  else
+    return ERR_MEM;
+#endif
+  return ERR_OK;
+}
+#endif /* LWIP_TCP */
+
+/**
+ * Send some data on a TCP pcb contained in a netconn
+ * Called from netconn_write
+ *
+ * @param msg the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_write(struct api_msg_msg *msg)
+{
+  if (ERR_IS_FATAL(msg->conn->last_err)) {
+    msg->err = msg->conn->last_err;
+  } else {
+    if (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP) {
+#if LWIP_TCP
+      if (msg->conn->state != NETCONN_NONE) {
+        /* netconn is connecting, closing or in blocking write */
+        msg->err = ERR_INPROGRESS;
+      } else if (msg->conn->pcb.tcp != NULL) {
+        msg->conn->state = NETCONN_WRITE;
+        /* set all the variables used by lwip_netconn_do_writemore */
+        LWIP_ASSERT("already writing or closing", msg->conn->current_msg == NULL &&
+          msg->conn->write_offset == 0);
+        LWIP_ASSERT("msg->msg.w.len != 0", msg->msg.w.len != 0);
+        msg->conn->current_msg = msg;
+        msg->conn->write_offset = 0;
+#if LWIP_TCPIP_CORE_LOCKING
+        msg->conn->flags &= ~NETCONN_FLAG_WRITE_DELAYED;
+        if (lwip_netconn_do_writemore(msg->conn) != ERR_OK) {
+          LWIP_ASSERT("state!", msg->conn->state == NETCONN_WRITE);
+          UNLOCK_TCPIP_CORE();
+          sys_arch_sem_wait(&msg->conn->op_completed, 0);
+          LOCK_TCPIP_CORE();
+          LWIP_ASSERT("state!", msg->conn->state == NETCONN_NONE);
+        }
+#else /* LWIP_TCPIP_CORE_LOCKING */
+        lwip_netconn_do_writemore(msg->conn);
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+        /* for both cases: if lwip_netconn_do_writemore was called, don't ACK the APIMSG
+           since lwip_netconn_do_writemore ACKs it! */
+        return;
+      } else {
+        msg->err = ERR_CONN;
+      }
+#else /* LWIP_TCP */
+      msg->err = ERR_VAL;
+#endif /* LWIP_TCP */
+#if (LWIP_UDP || LWIP_RAW)
+    } else {
+      msg->err = ERR_VAL;
+#endif /* (LWIP_UDP || LWIP_RAW) */
+    }
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+
+/**
+ * Return a connection's local or remote address
+ * Called from netconn_getaddr
+ *
+ * @param msg the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_getaddr(struct api_msg_msg *msg)
+{
+  if (msg->conn->pcb.ip != NULL) {
+    if (msg->msg.ad.local) {
+      ipX_addr_copy(PCB_ISIPV6(msg->conn->pcb.ip), *(msg->msg.ad.ipaddr),
+        msg->conn->pcb.ip->local_ip);
+    } else {
+      ipX_addr_copy(PCB_ISIPV6(msg->conn->pcb.ip), *(msg->msg.ad.ipaddr),
+        msg->conn->pcb.ip->remote_ip);
+    }
+    msg->err = ERR_OK;
+    switch (NETCONNTYPE_GROUP(msg->conn->type)) {
+#if LWIP_RAW
+    case NETCONN_RAW:
+      if (msg->msg.ad.local) {
+        *(msg->msg.ad.port) = msg->conn->pcb.raw->protocol;
+      } else {
+        /* return an error as connecting is only a helper for upper layers */
+        msg->err = ERR_CONN;
+      }
+      break;
+#endif /* LWIP_RAW */
+#if LWIP_UDP
+    case NETCONN_UDP:
+      if (msg->msg.ad.local) {
+        *(msg->msg.ad.port) = msg->conn->pcb.udp->local_port;
+      } else {
+        if ((msg->conn->pcb.udp->flags & UDP_FLAGS_CONNECTED) == 0) {
+          msg->err = ERR_CONN;
+        } else {
+          *(msg->msg.ad.port) = msg->conn->pcb.udp->remote_port;
+        }
+      }
+      break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+    case NETCONN_TCP:
+      *(msg->msg.ad.port) = (msg->msg.ad.local?msg->conn->pcb.tcp->local_port:msg->conn->pcb.tcp->remote_port);
+      break;
+#endif /* LWIP_TCP */
+    default:
+      LWIP_ASSERT("invalid netconn_type", 0);
+      break;
+    }
+  } else {
+    msg->err = ERR_CONN;
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+
+/**
+ * Close a TCP pcb contained in a netconn
+ * Called from netconn_close
+ *
+ * @param msg the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_close(struct api_msg_msg *msg)
+{
+#if LWIP_TCP
+  /* @todo: abort running write/connect? */
+  if ((msg->conn->state != NETCONN_NONE) && (msg->conn->state != NETCONN_LISTEN)) {
+    /* this only happens for TCP netconns */
+    LWIP_ASSERT("NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP",
+                NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP);
+    msg->err = ERR_INPROGRESS;
+  } else if ((msg->conn->pcb.tcp != NULL) && (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP)) {
+    if ((msg->msg.sd.shut != NETCONN_SHUT_RDWR) && (msg->conn->state == NETCONN_LISTEN)) {
+      /* LISTEN doesn't support half shutdown */
+      msg->err = ERR_CONN;
+    } else {
+      if (msg->msg.sd.shut & NETCONN_SHUT_RD) {
+        /* Drain and delete mboxes */
+        netconn_drain(msg->conn);
+      }
+      LWIP_ASSERT("already writing or closing", msg->conn->current_msg == NULL &&
+        msg->conn->write_offset == 0);
+      msg->conn->state = NETCONN_CLOSE;
+      msg->conn->current_msg = msg;
+      lwip_netconn_do_close_internal(msg->conn);
+      /* for tcp netconns, lwip_netconn_do_close_internal ACKs the message */
+      return;
+    }
+  } else
+#endif /* LWIP_TCP */
+  {
+    msg->err = ERR_VAL;
+  }
+  sys_sem_signal(&msg->conn->op_completed);
+}
+
+#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
+/**
+ * Join multicast groups for UDP netconns.
+ * Called from netconn_join_leave_group
+ *
+ * @param msg the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_join_leave_group(struct api_msg_msg *msg)
+{ 
+  if (ERR_IS_FATAL(msg->conn->last_err)) {
+    msg->err = msg->conn->last_err;
+  } else {
+    if (msg->conn->pcb.tcp != NULL) {
+      if (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_UDP) {
+#if LWIP_UDP
+#if LWIP_IPV6 && LWIP_IPV6_MLD
+        if (PCB_ISIPV6(msg->conn->pcb.udp)) {
+          if (msg->msg.jl.join_or_leave == NETCONN_JOIN) {
+            msg->err = mld6_joingroup(ipX_2_ip6(msg->msg.jl.netif_addr),
+              ipX_2_ip6(msg->msg.jl.multiaddr));
+          } else {
+            msg->err = mld6_leavegroup(ipX_2_ip6(msg->msg.jl.netif_addr),
+              ipX_2_ip6(msg->msg.jl.multiaddr));
+          }
+        }
+        else
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+        {
+#if LWIP_IGMP
+          if (msg->msg.jl.join_or_leave == NETCONN_JOIN) {
+            msg->err = igmp_joingroup(ipX_2_ip(msg->msg.jl.netif_addr),
+              ipX_2_ip(msg->msg.jl.multiaddr));
+          } else {
+            msg->err = igmp_leavegroup(ipX_2_ip(msg->msg.jl.netif_addr),
+              ipX_2_ip(msg->msg.jl.multiaddr));
+          }
+#endif /* LWIP_IGMP */
+        }
+#endif /* LWIP_UDP */
+#if (LWIP_TCP || LWIP_RAW)
+      } else {
+        msg->err = ERR_VAL;
+#endif /* (LWIP_TCP || LWIP_RAW) */
+      }
+    } else {
+      msg->err = ERR_CONN;
+    }
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+
+#if LWIP_DNS
+/**
+ * Callback function that is called when DNS name is resolved
+ * (or on timeout). A waiting application thread is waked up by
+ * signaling the semaphore.
+ */
+static void
+lwip_netconn_do_dns_found(const char *name, ip_addr_t *ipaddr, void *arg)
+{
+  struct dns_api_msg *msg = (struct dns_api_msg*)arg;
+
+  LWIP_ASSERT("DNS response for wrong host name", strcmp(msg->name, name) == 0);
+  LWIP_UNUSED_ARG(name);
+
+  if (ipaddr == NULL) {
+    /* timeout or memory error */
+    *msg->err = ERR_VAL;
+  } else {
+    /* address was resolved */
+    *msg->err = ERR_OK;
+    *msg->addr = *ipaddr;
+  }
+  /* wake up the application task waiting in netconn_gethostbyname */
+  sys_sem_signal(msg->sem);
+}
+
+/**
+ * Execute a DNS query
+ * Called from netconn_gethostbyname
+ *
+ * @param arg the dns_api_msg pointing to the query
+ */
+void
+lwip_netconn_do_gethostbyname(void *arg)
+{
+  struct dns_api_msg *msg = (struct dns_api_msg*)arg;
+
+  *msg->err = dns_gethostbyname(msg->name, msg->addr, lwip_netconn_do_dns_found, msg);
+  if (*msg->err != ERR_INPROGRESS) {
+    /* on error or immediate success, wake up the application
+     * task waiting in netconn_gethostbyname */
+    sys_sem_signal(msg->sem);
+  }
+}
+#endif /* LWIP_DNS */
+
+#endif /* LWIP_NETCONN */
diff --git a/external/badvpn_dns/lwip/src/api/err.c b/external/badvpn_dns/lwip/src/api/err.c
new file mode 100644
index 0000000..92fa8b7
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/api/err.c
@@ -0,0 +1,75 @@
+/**
+ * @file
+ * Error Management module
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/err.h"
+
+#ifdef LWIP_DEBUG
+
+static const char *err_strerr[] = {
+           "Ok.",                    /* ERR_OK          0  */
+           "Out of memory error.",   /* ERR_MEM        -1  */
+           "Buffer error.",          /* ERR_BUF        -2  */
+           "Timeout.",               /* ERR_TIMEOUT    -3  */
+           "Routing problem.",       /* ERR_RTE        -4  */
+           "Operation in progress.", /* ERR_INPROGRESS -5  */
+           "Illegal value.",         /* ERR_VAL        -6  */
+           "Operation would block.", /* ERR_WOULDBLOCK -7  */
+           "Address in use.",        /* ERR_USE        -8  */
+           "Already connected.",     /* ERR_ISCONN     -9  */
+           "Connection aborted.",    /* ERR_ABRT       -10 */
+           "Connection reset.",      /* ERR_RST        -11 */
+           "Connection closed.",     /* ERR_CLSD       -12 */
+           "Not connected.",         /* ERR_CONN       -13 */
+           "Illegal argument.",      /* ERR_ARG        -14 */
+           "Low-level netif error.", /* ERR_IF         -15 */
+};
+
+/**
+ * Convert an lwip internal error to a string representation.
+ *
+ * @param err an lwip internal err_t
+ * @return a string representation for err
+ */
+const char *
+lwip_strerr(err_t err)
+{
+  return err_strerr[-err];
+
+}
+
+#endif /* LWIP_DEBUG */
diff --git a/external/badvpn_dns/lwip/src/api/netbuf.c b/external/badvpn_dns/lwip/src/api/netbuf.c
new file mode 100644
index 0000000..0ccd2bc
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/api/netbuf.c
@@ -0,0 +1,245 @@
+/**
+ * @file
+ * Network buffer management
+ *
+ */
+ 
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_NETCONN /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/netbuf.h"
+#include "lwip/memp.h"
+
+#include <string.h>
+
+/**
+ * Create (allocate) and initialize a new netbuf.
+ * The netbuf doesn't yet contain a packet buffer!
+ *
+ * @return a pointer to a new netbuf
+ *         NULL on lack of memory
+ */
+struct
+netbuf *netbuf_new(void)
+{
+  struct netbuf *buf;
+
+  buf = (struct netbuf *)memp_malloc(MEMP_NETBUF);
+  if (buf != NULL) {
+    buf->p = NULL;
+    buf->ptr = NULL;
+    ipX_addr_set_any(LWIP_IPV6, &buf->addr);
+    buf->port = 0;
+#if LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY
+#if LWIP_CHECKSUM_ON_COPY
+    buf->flags = 0;
+#endif /* LWIP_CHECKSUM_ON_COPY */
+    buf->toport_chksum = 0;
+#if LWIP_NETBUF_RECVINFO
+    ipX_addr_set_any(LWIP_IPV6, &buf->toaddr);
+#endif /* LWIP_NETBUF_RECVINFO */
+#endif /* LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY */
+    return buf;
+  } else {
+    return NULL;
+  }
+}
+
+/**
+ * Deallocate a netbuf allocated by netbuf_new().
+ *
+ * @param buf pointer to a netbuf allocated by netbuf_new()
+ */
+void
+netbuf_delete(struct netbuf *buf)
+{
+  if (buf != NULL) {
+    if (buf->p != NULL) {
+      pbuf_free(buf->p);
+      buf->p = buf->ptr = NULL;
+    }
+    memp_free(MEMP_NETBUF, buf);
+  }
+}
+
+/**
+ * Allocate memory for a packet buffer for a given netbuf.
+ *
+ * @param buf the netbuf for which to allocate a packet buffer
+ * @param size the size of the packet buffer to allocate
+ * @return pointer to the allocated memory
+ *         NULL if no memory could be allocated
+ */
+void *
+netbuf_alloc(struct netbuf *buf, u16_t size)
+{
+  LWIP_ERROR("netbuf_alloc: invalid buf", (buf != NULL), return NULL;);
+
+  /* Deallocate any previously allocated memory. */
+  if (buf->p != NULL) {
+    pbuf_free(buf->p);
+  }
+  buf->p = pbuf_alloc(PBUF_TRANSPORT, size, PBUF_RAM);
+  if (buf->p == NULL) {
+     return NULL;
+  }
+  LWIP_ASSERT("check that first pbuf can hold size",
+             (buf->p->len >= size));
+  buf->ptr = buf->p;
+  return buf->p->payload;
+}
+
+/**
+ * Free the packet buffer included in a netbuf
+ *
+ * @param buf pointer to the netbuf which contains the packet buffer to free
+ */
+void
+netbuf_free(struct netbuf *buf)
+{
+  LWIP_ERROR("netbuf_free: invalid buf", (buf != NULL), return;);
+  if (buf->p != NULL) {
+    pbuf_free(buf->p);
+  }
+  buf->p = buf->ptr = NULL;
+}
+
+/**
+ * Let a netbuf reference existing (non-volatile) data.
+ *
+ * @param buf netbuf which should reference the data
+ * @param dataptr pointer to the data to reference
+ * @param size size of the data
+ * @return ERR_OK if data is referenced
+ *         ERR_MEM if data couldn't be referenced due to lack of memory
+ */
+err_t
+netbuf_ref(struct netbuf *buf, const void *dataptr, u16_t size)
+{
+  LWIP_ERROR("netbuf_ref: invalid buf", (buf != NULL), return ERR_ARG;);
+  if (buf->p != NULL) {
+    pbuf_free(buf->p);
+  }
+  buf->p = pbuf_alloc(PBUF_TRANSPORT, 0, PBUF_REF);
+  if (buf->p == NULL) {
+    buf->ptr = NULL;
+    return ERR_MEM;
+  }
+  buf->p->payload = (void*)dataptr;
+  buf->p->len = buf->p->tot_len = size;
+  buf->ptr = buf->p;
+  return ERR_OK;
+}
+
+/**
+ * Chain one netbuf to another (@see pbuf_chain)
+ *
+ * @param head the first netbuf
+ * @param tail netbuf to chain after head, freed by this function, may not be reference after returning
+ */
+void
+netbuf_chain(struct netbuf *head, struct netbuf *tail)
+{
+  LWIP_ERROR("netbuf_ref: invalid head", (head != NULL), return;);
+  LWIP_ERROR("netbuf_chain: invalid tail", (tail != NULL), return;);
+  pbuf_cat(head->p, tail->p);
+  head->ptr = head->p;
+  memp_free(MEMP_NETBUF, tail);
+}
+
+/**
+ * Get the data pointer and length of the data inside a netbuf.
+ *
+ * @param buf netbuf to get the data from
+ * @param dataptr pointer to a void pointer where to store the data pointer
+ * @param len pointer to an u16_t where the length of the data is stored
+ * @return ERR_OK if the information was retreived,
+ *         ERR_BUF on error.
+ */
+err_t
+netbuf_data(struct netbuf *buf, void **dataptr, u16_t *len)
+{
+  LWIP_ERROR("netbuf_data: invalid buf", (buf != NULL), return ERR_ARG;);
+  LWIP_ERROR("netbuf_data: invalid dataptr", (dataptr != NULL), return ERR_ARG;);
+  LWIP_ERROR("netbuf_data: invalid len", (len != NULL), return ERR_ARG;);
+
+  if (buf->ptr == NULL) {
+    return ERR_BUF;
+  }
+  *dataptr = buf->ptr->payload;
+  *len = buf->ptr->len;
+  return ERR_OK;
+}
+
+/**
+ * Move the current data pointer of a packet buffer contained in a netbuf
+ * to the next part.
+ * The packet buffer itself is not modified.
+ *
+ * @param buf the netbuf to modify
+ * @return -1 if there is no next part
+ *         1  if moved to the next part but now there is no next part
+ *         0  if moved to the next part and there are still more parts
+ */
+s8_t
+netbuf_next(struct netbuf *buf)
+{
+  LWIP_ERROR("netbuf_free: invalid buf", (buf != NULL), return -1;);
+  if (buf->ptr->next == NULL) {
+    return -1;
+  }
+  buf->ptr = buf->ptr->next;
+  if (buf->ptr->next == NULL) {
+    return 1;
+  }
+  return 0;
+}
+
+/**
+ * Move the current data pointer of a packet buffer contained in a netbuf
+ * to the beginning of the packet.
+ * The packet buffer itself is not modified.
+ *
+ * @param buf the netbuf to modify
+ */
+void
+netbuf_first(struct netbuf *buf)
+{
+  LWIP_ERROR("netbuf_free: invalid buf", (buf != NULL), return;);
+  buf->ptr = buf->p;
+}
+
+#endif /* LWIP_NETCONN */
diff --git a/external/badvpn_dns/lwip/src/api/netdb.c b/external/badvpn_dns/lwip/src/api/netdb.c
new file mode 100644
index 0000000..6a4bac5
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/api/netdb.c
@@ -0,0 +1,353 @@
+/**
+ * @file
+ * API functions for name resolving
+ *
+ */
+
+/*
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Simon Goldschmidt
+ *
+ */
+
+#include "lwip/netdb.h"
+
+#if LWIP_DNS && LWIP_SOCKET
+
+#include "lwip/err.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/ip_addr.h"
+#include "lwip/api.h"
+#include "lwip/dns.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+/** helper struct for gethostbyname_r to access the char* buffer */
+struct gethostbyname_r_helper {
+  ip_addr_t *addr_list[2];
+  ip_addr_t addr;
+  char *aliases;
+};
+
+/** h_errno is exported in netdb.h for access by applications. */
+#if LWIP_DNS_API_DECLARE_H_ERRNO
+int h_errno;
+#endif /* LWIP_DNS_API_DECLARE_H_ERRNO */
+
+/** define "hostent" variables storage: 0 if we use a static (but unprotected)
+ * set of variables for lwip_gethostbyname, 1 if we use a local storage */
+#ifndef LWIP_DNS_API_HOSTENT_STORAGE
+#define LWIP_DNS_API_HOSTENT_STORAGE 0
+#endif
+
+/** define "hostent" variables storage */
+#if LWIP_DNS_API_HOSTENT_STORAGE
+#define HOSTENT_STORAGE
+#else
+#define HOSTENT_STORAGE static
+#endif /* LWIP_DNS_API_STATIC_HOSTENT */
+
+/**
+ * Returns an entry containing addresses of address family AF_INET
+ * for the host with name name.
+ * Due to dns_gethostbyname limitations, only one address is returned.
+ *
+ * @param name the hostname to resolve
+ * @return an entry containing addresses of address family AF_INET
+ *         for the host with name name
+ */
+struct hostent*
+lwip_gethostbyname(const char *name)
+{
+  err_t err;
+  ip_addr_t addr;
+
+  /* buffer variables for lwip_gethostbyname() */
+  HOSTENT_STORAGE struct hostent s_hostent;
+  HOSTENT_STORAGE char *s_aliases;
+  HOSTENT_STORAGE ip_addr_t s_hostent_addr;
+  HOSTENT_STORAGE ip_addr_t *s_phostent_addr[2];
+
+  /* query host IP address */
+  err = netconn_gethostbyname(name, &addr);
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(DNS_DEBUG, ("lwip_gethostbyname(%s) failed, err=%d\n", name, err));
+    h_errno = HOST_NOT_FOUND;
+    return NULL;
+  }
+
+  /* fill hostent */
+  s_hostent_addr = addr;
+  s_phostent_addr[0] = &s_hostent_addr;
+  s_phostent_addr[1] = NULL;
+  s_hostent.h_name = (char*)name;
+  s_hostent.h_aliases = &s_aliases;
+  s_hostent.h_addrtype = AF_INET;
+  s_hostent.h_length = sizeof(ip_addr_t);
+  s_hostent.h_addr_list = (char**)&s_phostent_addr;
+
+#if DNS_DEBUG
+  /* dump hostent */
+  LWIP_DEBUGF(DNS_DEBUG, ("hostent.h_name           == %s\n", s_hostent.h_name));
+  LWIP_DEBUGF(DNS_DEBUG, ("hostent.h_aliases        == %p\n", s_hostent.h_aliases));
+  if (s_hostent.h_aliases != NULL) {
+    u8_t idx;
+    for ( idx=0; s_hostent.h_aliases[idx]; idx++) {
+      LWIP_DEBUGF(DNS_DEBUG, ("hostent.h_aliases[%i]->   == %p\n", idx, s_hostent.h_aliases[idx]));
+      LWIP_DEBUGF(DNS_DEBUG, ("hostent.h_aliases[%i]->   == %s\n", idx, s_hostent.h_aliases[idx]));
+    }
+  }
+  LWIP_DEBUGF(DNS_DEBUG, ("hostent.h_addrtype       == %d\n", s_hostent.h_addrtype));
+  LWIP_DEBUGF(DNS_DEBUG, ("hostent.h_length         == %d\n", s_hostent.h_length));
+  LWIP_DEBUGF(DNS_DEBUG, ("hostent.h_addr_list      == %p\n", s_hostent.h_addr_list));
+  if (s_hostent.h_addr_list != NULL) {
+    u8_t idx;
+    for ( idx=0; s_hostent.h_addr_list[idx]; idx++) {
+      LWIP_DEBUGF(DNS_DEBUG, ("hostent.h_addr_list[%i]   == %p\n", idx, s_hostent.h_addr_list[idx]));
+      LWIP_DEBUGF(DNS_DEBUG, ("hostent.h_addr_list[%i]-> == %s\n", idx, ip_ntoa((ip_addr_t*)s_hostent.h_addr_list[idx])));
+    }
+  }
+#endif /* DNS_DEBUG */
+
+#if LWIP_DNS_API_HOSTENT_STORAGE
+  /* this function should return the "per-thread" hostent after copy from s_hostent */
+  return sys_thread_hostent(&s_hostent);
+#else
+  return &s_hostent;
+#endif /* LWIP_DNS_API_HOSTENT_STORAGE */
+}
+
+/**
+ * Thread-safe variant of lwip_gethostbyname: instead of using a static
+ * buffer, this function takes buffer and errno pointers as arguments
+ * and uses these for the result.
+ *
+ * @param name the hostname to resolve
+ * @param ret pre-allocated struct where to store the result
+ * @param buf pre-allocated buffer where to store additional data
+ * @param buflen the size of buf
+ * @param result pointer to a hostent pointer that is set to ret on success
+ *               and set to zero on error
+ * @param h_errnop pointer to an int where to store errors (instead of modifying
+ *                 the global h_errno)
+ * @return 0 on success, non-zero on error, additional error information
+ *         is stored in *h_errnop instead of h_errno to be thread-safe
+ */
+int
+lwip_gethostbyname_r(const char *name, struct hostent *ret, char *buf,
+                size_t buflen, struct hostent **result, int *h_errnop)
+{
+  err_t err;
+  struct gethostbyname_r_helper *h;
+  char *hostname;
+  size_t namelen;
+  int lh_errno;
+
+  if (h_errnop == NULL) {
+    /* ensure h_errnop is never NULL */
+    h_errnop = &lh_errno;
+  }
+
+  if (result == NULL) {
+    /* not all arguments given */
+    *h_errnop = EINVAL;
+    return -1;
+  }
+  /* first thing to do: set *result to nothing */
+  *result = NULL;
+  if ((name == NULL) || (ret == NULL) || (buf == NULL)) {
+    /* not all arguments given */
+    *h_errnop = EINVAL;
+    return -1;
+  }
+
+  namelen = strlen(name);
+  if (buflen < (sizeof(struct gethostbyname_r_helper) + namelen + 1 + (MEM_ALIGNMENT - 1))) {
+    /* buf can't hold the data needed + a copy of name */
+    *h_errnop = ERANGE;
+    return -1;
+  }
+
+  h = (struct gethostbyname_r_helper*)LWIP_MEM_ALIGN(buf);
+  hostname = ((char*)h) + sizeof(struct gethostbyname_r_helper);
+
+  /* query host IP address */
+  err = netconn_gethostbyname(name, &h->addr);
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(DNS_DEBUG, ("lwip_gethostbyname(%s) failed, err=%d\n", name, err));
+    *h_errnop = HOST_NOT_FOUND;
+    return -1;
+  }
+
+  /* copy the hostname into buf */
+  MEMCPY(hostname, name, namelen);
+  hostname[namelen] = 0;
+
+  /* fill hostent */
+  h->addr_list[0] = &h->addr;
+  h->addr_list[1] = NULL;
+  h->aliases = NULL;
+  ret->h_name = hostname;
+  ret->h_aliases = &h->aliases;
+  ret->h_addrtype = AF_INET;
+  ret->h_length = sizeof(ip_addr_t);
+  ret->h_addr_list = (char**)&h->addr_list;
+
+  /* set result != NULL */
+  *result = ret;
+
+  /* return success */
+  return 0;
+}
+
+/**
+ * Frees one or more addrinfo structures returned by getaddrinfo(), along with
+ * any additional storage associated with those structures. If the ai_next field
+ * of the structure is not null, the entire list of structures is freed.
+ *
+ * @param ai struct addrinfo to free
+ */
+void
+lwip_freeaddrinfo(struct addrinfo *ai)
+{
+  struct addrinfo *next;
+
+  while (ai != NULL) {
+    next = ai->ai_next;
+    memp_free(MEMP_NETDB, ai);
+    ai = next;
+  }
+}
+
+/**
+ * Translates the name of a service location (for example, a host name) and/or
+ * a service name and returns a set of socket addresses and associated
+ * information to be used in creating a socket with which to address the
+ * specified service.
+ * Memory for the result is allocated internally and must be freed by calling
+ * lwip_freeaddrinfo()!
+ *
+ * Due to a limitation in dns_gethostbyname, only the first address of a
+ * host is returned.
+ * Also, service names are not supported (only port numbers)!
+ *
+ * @param nodename descriptive name or address string of the host
+ *                 (may be NULL -> local address)
+ * @param servname port number as string of NULL 
+ * @param hints structure containing input values that set socktype and protocol
+ * @param res pointer to a pointer where to store the result (set to NULL on failure)
+ * @return 0 on success, non-zero on failure
+ */
+int
+lwip_getaddrinfo(const char *nodename, const char *servname,
+       const struct addrinfo *hints, struct addrinfo **res)
+{
+  err_t err;
+  ip_addr_t addr;
+  struct addrinfo *ai;
+  struct sockaddr_in *sa = NULL;
+  int port_nr = 0;
+  size_t total_size;
+  size_t namelen = 0;
+
+  if (res == NULL) {
+    return EAI_FAIL;
+  }
+  *res = NULL;
+  if ((nodename == NULL) && (servname == NULL)) {
+    return EAI_NONAME;
+  }
+
+  if (servname != NULL) {
+    /* service name specified: convert to port number
+     * @todo?: currently, only ASCII integers (port numbers) are supported! */
+    port_nr = atoi(servname);
+    if ((port_nr <= 0) || (port_nr > 0xffff)) {
+      return EAI_SERVICE;
+    }
+  }
+
+  if (nodename != NULL) {
+    /* service location specified, try to resolve */
+    err = netconn_gethostbyname(nodename, &addr);
+    if (err != ERR_OK) {
+      return EAI_FAIL;
+    }
+  } else {
+    /* service location specified, use loopback address */
+    ip_addr_set_loopback(&addr);
+  }
+
+  total_size = sizeof(struct addrinfo) + sizeof(struct sockaddr_in);
+  if (nodename != NULL) {
+    namelen = strlen(nodename);
+    LWIP_ASSERT("namelen is too long", (namelen + 1) <= (mem_size_t)-1);
+    total_size += namelen + 1;
+  }
+  /* If this fails, please report to lwip-devel! :-) */
+  LWIP_ASSERT("total_size <= NETDB_ELEM_SIZE: please report this!",
+    total_size <= NETDB_ELEM_SIZE);
+  ai = (struct addrinfo *)memp_malloc(MEMP_NETDB);
+  if (ai == NULL) {
+    goto memerr;
+  }
+  memset(ai, 0, total_size);
+  sa = (struct sockaddr_in*)((u8_t*)ai + sizeof(struct addrinfo));
+  /* set up sockaddr */
+  inet_addr_from_ipaddr(&sa->sin_addr, &addr);
+  sa->sin_family = AF_INET;
+  sa->sin_len = sizeof(struct sockaddr_in);
+  sa->sin_port = htons((u16_t)port_nr);
+
+  /* set up addrinfo */
+  ai->ai_family = AF_INET;
+  if (hints != NULL) {
+    /* copy socktype & protocol from hints if specified */
+    ai->ai_socktype = hints->ai_socktype;
+    ai->ai_protocol = hints->ai_protocol;
+  }
+  if (nodename != NULL) {
+    /* copy nodename to canonname if specified */
+    ai->ai_canonname = ((char*)ai + sizeof(struct addrinfo) + sizeof(struct sockaddr_in));
+    MEMCPY(ai->ai_canonname, nodename, namelen);
+    ai->ai_canonname[namelen] = 0;
+  }
+  ai->ai_addrlen = sizeof(struct sockaddr_in);
+  ai->ai_addr = (struct sockaddr*)sa;
+
+  *res = ai;
+
+  return 0;
+memerr:
+  if (ai != NULL) {
+    memp_free(MEMP_NETDB, ai);
+  }
+  return EAI_MEMORY;
+}
+
+#endif /* LWIP_DNS && LWIP_SOCKET */
diff --git a/external/badvpn_dns/lwip/src/api/netifapi.c b/external/badvpn_dns/lwip/src/api/netifapi.c
new file mode 100644
index 0000000..81403f8
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/api/netifapi.c
@@ -0,0 +1,160 @@
+/**
+ * @file
+ * Network Interface Sequential API module
+ *
+ */
+
+/*
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_NETIF_API /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/netifapi.h"
+#include "lwip/tcpip.h"
+
+/**
+ * Call netif_add() inside the tcpip_thread context.
+ */
+static void
+netifapi_do_netif_add(struct netifapi_msg_msg *msg)
+{
+  if (!netif_add( msg->netif,
+                  msg->msg.add.ipaddr,
+                  msg->msg.add.netmask,
+                  msg->msg.add.gw,
+                  msg->msg.add.state,
+                  msg->msg.add.init,
+                  msg->msg.add.input)) {
+    msg->err = ERR_IF;
+  } else {
+    msg->err = ERR_OK;
+  }
+  TCPIP_NETIFAPI_ACK(msg);
+}
+
+/**
+ * Call netif_set_addr() inside the tcpip_thread context.
+ */
+static void
+netifapi_do_netif_set_addr(struct netifapi_msg_msg *msg)
+{
+  netif_set_addr( msg->netif,
+                  msg->msg.add.ipaddr,
+                  msg->msg.add.netmask,
+                  msg->msg.add.gw);
+  msg->err = ERR_OK;
+  TCPIP_NETIFAPI_ACK(msg);
+}
+
+/**
+ * Call the "errtfunc" (or the "voidfunc" if "errtfunc" is NULL) inside the
+ * tcpip_thread context.
+ */
+static void
+netifapi_do_netif_common(struct netifapi_msg_msg *msg)
+{
+  if (msg->msg.common.errtfunc != NULL) {
+    msg->err = msg->msg.common.errtfunc(msg->netif);
+  } else {
+    msg->err = ERR_OK;
+    msg->msg.common.voidfunc(msg->netif);
+  }
+  TCPIP_NETIFAPI_ACK(msg);
+}
+
+/**
+ * Call netif_add() in a thread-safe way by running that function inside the
+ * tcpip_thread context.
+ *
+ * @note for params @see netif_add()
+ */
+err_t
+netifapi_netif_add(struct netif *netif,
+                   ip_addr_t *ipaddr,
+                   ip_addr_t *netmask,
+                   ip_addr_t *gw,
+                   void *state,
+                   netif_init_fn init,
+                   netif_input_fn input)
+{
+  struct netifapi_msg msg;
+  msg.function = netifapi_do_netif_add;
+  msg.msg.netif = netif;
+  msg.msg.msg.add.ipaddr  = ipaddr;
+  msg.msg.msg.add.netmask = netmask;
+  msg.msg.msg.add.gw      = gw;
+  msg.msg.msg.add.state   = state;
+  msg.msg.msg.add.init    = init;
+  msg.msg.msg.add.input   = input;
+  TCPIP_NETIFAPI(&msg);
+  return msg.msg.err;
+}
+
+/**
+ * Call netif_set_addr() in a thread-safe way by running that function inside the
+ * tcpip_thread context.
+ *
+ * @note for params @see netif_set_addr()
+ */
+err_t
+netifapi_netif_set_addr(struct netif *netif,
+                        ip_addr_t *ipaddr,
+                        ip_addr_t *netmask,
+                        ip_addr_t *gw)
+{
+  struct netifapi_msg msg;
+  msg.function = netifapi_do_netif_set_addr;
+  msg.msg.netif = netif;
+  msg.msg.msg.add.ipaddr  = ipaddr;
+  msg.msg.msg.add.netmask = netmask;
+  msg.msg.msg.add.gw      = gw;
+  TCPIP_NETIFAPI(&msg);
+  return msg.msg.err;
+}
+
+/**
+ * call the "errtfunc" (or the "voidfunc" if "errtfunc" is NULL) in a thread-safe
+ * way by running that function inside the tcpip_thread context.
+ *
+ * @note use only for functions where there is only "netif" parameter.
+ */
+err_t
+netifapi_netif_common(struct netif *netif, netifapi_void_fn voidfunc,
+                       netifapi_errt_fn errtfunc)
+{
+  struct netifapi_msg msg;
+  msg.function = netifapi_do_netif_common;
+  msg.msg.netif = netif;
+  msg.msg.msg.common.voidfunc = voidfunc;
+  msg.msg.msg.common.errtfunc = errtfunc;
+  TCPIP_NETIFAPI(&msg);
+  return msg.msg.err;
+}
+
+#endif /* LWIP_NETIF_API */
diff --git a/external/badvpn_dns/lwip/src/api/sockets.c b/external/badvpn_dns/lwip/src/api/sockets.c
new file mode 100644
index 0000000..6603671
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/api/sockets.c
@@ -0,0 +1,2555 @@
+/**
+ * @file
+ * Sockets BSD-Like API module
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ * Improved by Marc Boucher <marc@xxxxxxx> and David Haas <dhaas@xxxxxxxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_SOCKET /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/sockets.h"
+#include "lwip/api.h"
+#include "lwip/sys.h"
+#include "lwip/igmp.h"
+#include "lwip/inet.h"
+#include "lwip/tcp.h"
+#include "lwip/raw.h"
+#include "lwip/udp.h"
+#include "lwip/tcpip.h"
+#include "lwip/pbuf.h"
+#if LWIP_CHECKSUM_ON_COPY
+#include "lwip/inet_chksum.h"
+#endif
+
+#include <string.h>
+
+#define IP4ADDR_PORT_TO_SOCKADDR(sin, ipXaddr, port) do { \
+      (sin)->sin_len = sizeof(struct sockaddr_in); \
+      (sin)->sin_family = AF_INET; \
+      (sin)->sin_port = htons((port)); \
+      inet_addr_from_ipaddr(&(sin)->sin_addr, ipX_2_ip(ipXaddr)); \
+      memset((sin)->sin_zero, 0, SIN_ZERO_LEN); }while(0)
+#define SOCKADDR4_TO_IP4ADDR_PORT(sin, ipXaddr, port) do { \
+    inet_addr_to_ipaddr(ipX_2_ip(ipXaddr), &((sin)->sin_addr)); \
+    (port) = ntohs((sin)->sin_port); }while(0)
+
+#if LWIP_IPV6
+#define IS_SOCK_ADDR_LEN_VALID(namelen)  (((namelen) == sizeof(struct sockaddr_in)) || \
+                                         ((namelen) == sizeof(struct sockaddr_in6)))
+#define IS_SOCK_ADDR_TYPE_VALID(name)    (((name)->sa_family == AF_INET) || \
+                                         ((name)->sa_family == AF_INET6))
+#define SOCK_ADDR_TYPE_MATCH(name, sock) \
+       ((((name)->sa_family == AF_INET) && !(NETCONNTYPE_ISIPV6((sock)->conn->type))) || \
+       (((name)->sa_family == AF_INET6) && (NETCONNTYPE_ISIPV6((sock)->conn->type))))
+#define IP6ADDR_PORT_TO_SOCKADDR(sin6, ipXaddr, port) do { \
+      (sin6)->sin6_len = sizeof(struct sockaddr_in6); \
+      (sin6)->sin6_family = AF_INET6; \
+      (sin6)->sin6_port = htons((port)); \
+      (sin6)->sin6_flowinfo = 0; \
+      inet6_addr_from_ip6addr(&(sin6)->sin6_addr, ipX_2_ip6(ipXaddr)); }while(0)
+#define IPXADDR_PORT_TO_SOCKADDR(isipv6, sockaddr, ipXaddr, port) do { \
+    if (isipv6) { \
+      IP6ADDR_PORT_TO_SOCKADDR((struct sockaddr_in6*)(void*)(sockaddr), ipXaddr, port); \
+    } else { \
+      IP4ADDR_PORT_TO_SOCKADDR((struct sockaddr_in*)(void*)(sockaddr), ipXaddr, port); \
+    } } while(0)
+#define SOCKADDR6_TO_IP6ADDR_PORT(sin6, ipXaddr, port) do { \
+    inet6_addr_to_ip6addr(ipX_2_ip6(ipXaddr), &((sin6)->sin6_addr)); \
+    (port) = ntohs((sin6)->sin6_port); }while(0)
+#define SOCKADDR_TO_IPXADDR_PORT(isipv6, sockaddr, ipXaddr, port) do { \
+    if (isipv6) { \
+      SOCKADDR6_TO_IP6ADDR_PORT((struct sockaddr_in6*)(void*)(sockaddr), ipXaddr, port); \
+    } else { \
+      SOCKADDR4_TO_IP4ADDR_PORT((struct sockaddr_in*)(void*)(sockaddr), ipXaddr, port); \
+    } } while(0)
+#define DOMAIN_TO_NETCONN_TYPE(domain, type) (((domain) == AF_INET) ? \
+  (type) : (enum netconn_type)((type) | NETCONN_TYPE_IPV6))
+#else /* LWIP_IPV6 */
+#define IS_SOCK_ADDR_LEN_VALID(namelen)  ((namelen) == sizeof(struct sockaddr_in))
+#define IS_SOCK_ADDR_TYPE_VALID(name)    ((name)->sa_family == AF_INET)
+#define SOCK_ADDR_TYPE_MATCH(name, sock) 1
+#define IPXADDR_PORT_TO_SOCKADDR(isipv6, sockaddr, ipXaddr, port) \
+        IP4ADDR_PORT_TO_SOCKADDR((struct sockaddr_in*)(void*)(sockaddr), ipXaddr, port)
+#define SOCKADDR_TO_IPXADDR_PORT(isipv6, sockaddr, ipXaddr, port) \
+      SOCKADDR4_TO_IP4ADDR_PORT((struct sockaddr_in*)(void*)(sockaddr), ipXaddr, port)
+#define DOMAIN_TO_NETCONN_TYPE(domain, netconn_type) (netconn_type)
+#endif /* LWIP_IPV6 */
+
+#define IS_SOCK_ADDR_TYPE_VALID_OR_UNSPEC(name)    (((name)->sa_family == AF_UNSPEC) || \
+                                                    IS_SOCK_ADDR_TYPE_VALID(name))
+#define SOCK_ADDR_TYPE_MATCH_OR_UNSPEC(name, sock) (((name)->sa_family == AF_UNSPEC) || \
+                                                    SOCK_ADDR_TYPE_MATCH(name, sock))
+#define IS_SOCK_ADDR_ALIGNED(name)      ((((mem_ptr_t)(name)) % 4) == 0)
+
+
+
+#define NUM_SOCKETS MEMP_NUM_NETCONN
+
+/** Contains all internal pointers and states used for a socket */
+struct lwip_sock {
+  /** sockets currently are built on netconns, each socket has one netconn */
+  struct netconn *conn;
+  /** data that was left from the previous read */
+  void *lastdata;
+  /** offset in the data that was left from the previous read */
+  u16_t lastoffset;
+  /** number of times data was received, set by event_callback(),
+      tested by the receive and select functions */
+  s16_t rcvevent;
+  /** number of times data was ACKed (free send buffer), set by event_callback(),
+      tested by select */
+  u16_t sendevent;
+  /** error happened for this socket, set by event_callback(), tested by select */
+  u16_t errevent; 
+  /** last error that occurred on this socket */
+  int err;
+  /** counter of how many threads are waiting for this socket using select */
+  int select_waiting;
+};
+
+/** Description for a task waiting in select */
+struct lwip_select_cb {
+  /** Pointer to the next waiting task */
+  struct lwip_select_cb *next;
+  /** Pointer to the previous waiting task */
+  struct lwip_select_cb *prev;
+  /** readset passed to select */
+  fd_set *readset;
+  /** writeset passed to select */
+  fd_set *writeset;
+  /** unimplemented: exceptset passed to select */
+  fd_set *exceptset;
+  /** don't signal the same semaphore twice: set to 1 when signalled */
+  int sem_signalled;
+  /** semaphore to wake up a task waiting for select */
+  sys_sem_t sem;
+};
+
+/** This struct is used to pass data to the set/getsockopt_internal
+ * functions running in tcpip_thread context (only a void* is allowed) */
+struct lwip_setgetsockopt_data {
+  /** socket struct for which to change options */
+  struct lwip_sock *sock;
+#ifdef LWIP_DEBUG
+  /** socket index for which to change options */
+  int s;
+#endif /* LWIP_DEBUG */
+  /** level of the option to process */
+  int level;
+  /** name of the option to process */
+  int optname;
+  /** set: value to set the option to
+    * get: value of the option is stored here */
+  void *optval;
+  /** size of *optval */
+  socklen_t *optlen;
+  /** if an error occures, it is temporarily stored here */
+  err_t err;
+};
+
+/** A struct sockaddr replacement that has the same alignment as sockaddr_in/
+ *  sockaddr_in6 if instantiated.
+ */
+union sockaddr_aligned {
+   struct sockaddr sa;
+#if LWIP_IPV6
+   struct sockaddr_in6 sin6;
+#endif /* LWIP_IPV6 */
+   struct sockaddr_in sin;
+};
+
+
+/** The global array of available sockets */
+static struct lwip_sock sockets[NUM_SOCKETS];
+/** The global list of tasks waiting for select */
+static struct lwip_select_cb *select_cb_list;
+/** This counter is increased from lwip_select when the list is chagned
+    and checked in event_callback to see if it has changed. */
+static volatile int select_cb_ctr;
+
+/** Table to quickly map an lwIP error (err_t) to a socket error
+  * by using -err as an index */
+static const int err_to_errno_table[] = {
+  0,             /* ERR_OK          0      No error, everything OK. */
+  ENOMEM,        /* ERR_MEM        -1      Out of memory error.     */
+  ENOBUFS,       /* ERR_BUF        -2      Buffer error.            */
+  EWOULDBLOCK,   /* ERR_TIMEOUT    -3      Timeout                  */
+  EHOSTUNREACH,  /* ERR_RTE        -4      Routing problem.         */
+  EINPROGRESS,   /* ERR_INPROGRESS -5      Operation in progress    */
+  EINVAL,        /* ERR_VAL        -6      Illegal value.           */
+  EWOULDBLOCK,   /* ERR_WOULDBLOCK -7      Operation would block.   */
+  EADDRINUSE,    /* ERR_USE        -8      Address in use.          */
+  EALREADY,      /* ERR_ISCONN     -9      Already connected.       */
+  ECONNABORTED,  /* ERR_ABRT       -10     Connection aborted.      */
+  ECONNRESET,    /* ERR_RST        -11     Connection reset.        */
+  ENOTCONN,      /* ERR_CLSD       -12     Connection closed.       */
+  ENOTCONN,      /* ERR_CONN       -13     Not connected.           */
+  EIO,           /* ERR_ARG        -14     Illegal argument.        */
+  -1,            /* ERR_IF         -15     Low-level netif error    */
+};
+
+#define ERR_TO_ERRNO_TABLE_SIZE \
+  (sizeof(err_to_errno_table)/sizeof(err_to_errno_table[0]))
+
+#define err_to_errno(err) \
+  ((unsigned)(-(err)) < ERR_TO_ERRNO_TABLE_SIZE ? \
+    err_to_errno_table[-(err)] : EIO)
+
+#ifdef ERRNO
+#ifndef set_errno
+#define set_errno(err) errno = (err)
+#endif
+#else /* ERRNO */
+#define set_errno(err)
+#endif /* ERRNO */
+
+#define sock_set_errno(sk, e) do { \
+  sk->err = (e); \
+  set_errno(sk->err); \
+} while (0)
+
+/* Forward delcaration of some functions */
+static void event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len);
+static void lwip_getsockopt_internal(void *arg);
+static void lwip_setsockopt_internal(void *arg);
+
+/**
+ * Initialize this module. This function has to be called before any other
+ * functions in this module!
+ */
+void
+lwip_socket_init(void)
+{
+}
+
+/**
+ * Map a externally used socket index to the internal socket representation.
+ *
+ * @param s externally used socket index
+ * @return struct lwip_sock for the socket or NULL if not found
+ */
+static struct lwip_sock *
+get_socket(int s)
+{
+  struct lwip_sock *sock;
+
+  if ((s < 0) || (s >= NUM_SOCKETS)) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("get_socket(%d): invalid\n", s));
+    set_errno(EBADF);
+    return NULL;
+  }
+
+  sock = &sockets[s];
+
+  if (!sock->conn) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("get_socket(%d): not active\n", s));
+    set_errno(EBADF);
+    return NULL;
+  }
+
+  return sock;
+}
+
+/**
+ * Same as get_socket but doesn't set errno
+ *
+ * @param s externally used socket index
+ * @return struct lwip_sock for the socket or NULL if not found
+ */
+static struct lwip_sock *
+tryget_socket(int s)
+{
+  if ((s < 0) || (s >= NUM_SOCKETS)) {
+    return NULL;
+  }
+  if (!sockets[s].conn) {
+    return NULL;
+  }
+  return &sockets[s];
+}
+
+/**
+ * Allocate a new socket for a given netconn.
+ *
+ * @param newconn the netconn for which to allocate a socket
+ * @param accepted 1 if socket has been created by accept(),
+ *                 0 if socket has been created by socket()
+ * @return the index of the new socket; -1 on error
+ */
+static int
+alloc_socket(struct netconn *newconn, int accepted)
+{
+  int i;
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  /* allocate a new socket identifier */
+  for (i = 0; i < NUM_SOCKETS; ++i) {
+    /* Protect socket array */
+    SYS_ARCH_PROTECT(lev);
+    if (!sockets[i].conn) {
+      sockets[i].conn       = newconn;
+      /* The socket is not yet known to anyone, so no need to protect
+         after having marked it as used. */
+      SYS_ARCH_UNPROTECT(lev);
+      sockets[i].lastdata   = NULL;
+      sockets[i].lastoffset = 0;
+      sockets[i].rcvevent   = 0;
+      /* TCP sendbuf is empty, but the socket is not yet writable until connected
+       * (unless it has been created by accept()). */
+      sockets[i].sendevent  = (NETCONNTYPE_GROUP(newconn->type) == NETCONN_TCP ? (accepted != 0) : 1);
+      sockets[i].errevent   = 0;
+      sockets[i].err        = 0;
+      sockets[i].select_waiting = 0;
+      return i;
+    }
+    SYS_ARCH_UNPROTECT(lev);
+  }
+  return -1;
+}
+
+/** Free a socket. The socket's netconn must have been
+ * delete before!
+ *
+ * @param sock the socket to free
+ * @param is_tcp != 0 for TCP sockets, used to free lastdata
+ */
+static void
+free_socket(struct lwip_sock *sock, int is_tcp)
+{
+  void *lastdata;
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  lastdata         = sock->lastdata;
+  sock->lastdata   = NULL;
+  sock->lastoffset = 0;
+  sock->err        = 0;
+
+  /* Protect socket array */
+  SYS_ARCH_PROTECT(lev);
+  sock->conn       = NULL;
+  SYS_ARCH_UNPROTECT(lev);
+  /* don't use 'sock' after this line, as another task might have allocated it */
+
+  if (lastdata != NULL) {
+    if (is_tcp) {
+      pbuf_free((struct pbuf *)lastdata);
+    } else {
+      netbuf_delete((struct netbuf *)lastdata);
+    }
+  }
+}
+
+/* Below this, the well-known socket functions are implemented.
+ * Use google.com or opengroup.org to get a good description :-)
+ *
+ * Exceptions are documented!
+ */
+
+int
+lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen)
+{
+  struct lwip_sock *sock, *nsock;
+  struct netconn *newconn;
+  ipX_addr_t naddr;
+  u16_t port = 0;
+  int newsock;
+  err_t err;
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d)...\n", s));
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  if (netconn_is_nonblocking(sock->conn) && (sock->rcvevent <= 0)) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d): returning EWOULDBLOCK\n", s));
+    sock_set_errno(sock, EWOULDBLOCK);
+    return -1;
+  }
+
+  /* wait for a new connection */
+  err = netconn_accept(sock->conn, &newconn);
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d): netconn_acept failed, err=%d\n", s, err));
+    if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP) {
+      sock_set_errno(sock, EOPNOTSUPP);
+      return EOPNOTSUPP;
+    }
+    sock_set_errno(sock, err_to_errno(err));
+    return -1;
+  }
+  LWIP_ASSERT("newconn != NULL", newconn != NULL);
+  /* Prevent automatic window updates, we do this on our own! */
+  netconn_set_noautorecved(newconn, 1);
+
+  /* Note that POSIX only requires us to check addr is non-NULL. addrlen must
+   * not be NULL if addr is valid.
+   */
+  if (addr != NULL) {
+    union sockaddr_aligned tempaddr;
+    /* get the IP address and port of the remote host */
+    err = netconn_peer(newconn, ipX_2_ip(&naddr), &port);
+    if (err != ERR_OK) {
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d): netconn_peer failed, err=%d\n", s, err));
+      netconn_delete(newconn);
+      sock_set_errno(sock, err_to_errno(err));
+      return -1;
+    }
+    LWIP_ASSERT("addr valid but addrlen NULL", addrlen != NULL);
+
+    IPXADDR_PORT_TO_SOCKADDR(NETCONNTYPE_ISIPV6(newconn->type), &tempaddr, &naddr, port);
+    if (*addrlen > tempaddr.sa.sa_len) {
+      *addrlen = tempaddr.sa.sa_len;
+    }
+    MEMCPY(addr, &tempaddr, *addrlen);
+  }
+
+  newsock = alloc_socket(newconn, 1);
+  if (newsock == -1) {
+    netconn_delete(newconn);
+    sock_set_errno(sock, ENFILE);
+    return -1;
+  }
+  LWIP_ASSERT("invalid socket index", (newsock >= 0) && (newsock < NUM_SOCKETS));
+  LWIP_ASSERT("newconn->callback == event_callback", newconn->callback == event_callback);
+  nsock = &sockets[newsock];
+
+  /* See event_callback: If data comes in right away after an accept, even
+   * though the server task might not have created a new socket yet.
+   * In that case, newconn->socket is counted down (newconn->socket--),
+   * so nsock->rcvevent is >= 1 here!
+   */
+  SYS_ARCH_PROTECT(lev);
+  nsock->rcvevent += (s16_t)(-1 - newconn->socket);
+  newconn->socket = newsock;
+  SYS_ARCH_UNPROTECT(lev);
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d) returning new sock=%d", s, newsock));
+  if (addr != NULL) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, (" addr="));
+    ipX_addr_debug_print(NETCONNTYPE_ISIPV6(newconn->type), SOCKETS_DEBUG, &naddr);
+    LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F"\n", port));
+  }
+
+  sock_set_errno(sock, 0);
+  return newsock;
+}
+
+int
+lwip_bind(int s, const struct sockaddr *name, socklen_t namelen)
+{
+  struct lwip_sock *sock;
+  ipX_addr_t local_addr;
+  u16_t local_port;
+  err_t err;
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  if (!SOCK_ADDR_TYPE_MATCH(name, sock)) {
+    /* sockaddr does not match socket type (IPv4/IPv6) */
+    sock_set_errno(sock, err_to_errno(ERR_VAL));
+    return -1;
+  }
+
+  /* check size, familiy and alignment of 'name' */
+  LWIP_ERROR("lwip_bind: invalid address", (IS_SOCK_ADDR_LEN_VALID(namelen) &&
+             IS_SOCK_ADDR_TYPE_VALID(name) && IS_SOCK_ADDR_ALIGNED(name)),
+             sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
+  LWIP_UNUSED_ARG(namelen);
+
+  SOCKADDR_TO_IPXADDR_PORT((name->sa_family == AF_INET6), name, &local_addr, local_port);
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_bind(%d, addr=", s));
+  ipX_addr_debug_print(name->sa_family == AF_INET6, SOCKETS_DEBUG, &local_addr);
+  LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F")\n", local_port));
+
+  err = netconn_bind(sock->conn, ipX_2_ip(&local_addr), local_port);
+
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_bind(%d) failed, err=%d\n", s, err));
+    sock_set_errno(sock, err_to_errno(err));
+    return -1;
+  }
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_bind(%d) succeeded\n", s));
+  sock_set_errno(sock, 0);
+  return 0;
+}
+
+int
+lwip_close(int s)
+{
+  struct lwip_sock *sock;
+  int is_tcp = 0;
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_close(%d)\n", s));
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  if(sock->conn != NULL) {
+    is_tcp = NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP;
+  } else {
+    LWIP_ASSERT("sock->lastdata == NULL", sock->lastdata == NULL);
+  }
+
+  netconn_delete(sock->conn);
+
+  free_socket(sock, is_tcp);
+  set_errno(0);
+  return 0;
+}
+
+int
+lwip_connect(int s, const struct sockaddr *name, socklen_t namelen)
+{
+  struct lwip_sock *sock;
+  err_t err;
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  if (!SOCK_ADDR_TYPE_MATCH_OR_UNSPEC(name, sock)) {
+    /* sockaddr does not match socket type (IPv4/IPv6) */
+   sock_set_errno(sock, err_to_errno(ERR_VAL));
+   return -1;
+  }
+
+  /* check size, familiy and alignment of 'name' */
+  LWIP_ERROR("lwip_connect: invalid address", IS_SOCK_ADDR_LEN_VALID(namelen) &&
+             IS_SOCK_ADDR_TYPE_VALID_OR_UNSPEC(name) && IS_SOCK_ADDR_ALIGNED(name),
+             sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
+  LWIP_UNUSED_ARG(namelen);
+  if (name->sa_family == AF_UNSPEC) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d, AF_UNSPEC)\n", s));
+    err = netconn_disconnect(sock->conn);
+  } else {
+    ipX_addr_t remote_addr;
+    u16_t remote_port;
+    SOCKADDR_TO_IPXADDR_PORT((name->sa_family == AF_INET6), name, &remote_addr, remote_port);
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d, addr=", s));
+    ipX_addr_debug_print(name->sa_family == AF_INET6, SOCKETS_DEBUG, &remote_addr);
+    LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F")\n", remote_port));
+
+    err = netconn_connect(sock->conn, ipX_2_ip(&remote_addr), remote_port);
+  }
+
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d) failed, err=%d\n", s, err));
+    sock_set_errno(sock, err_to_errno(err));
+    return -1;
+  }
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d) succeeded\n", s));
+  sock_set_errno(sock, 0);
+  return 0;
+}
+
+/**
+ * Set a socket into listen mode.
+ * The socket may not have been used for another connection previously.
+ *
+ * @param s the socket to set to listening mode
+ * @param backlog (ATTENTION: needs TCP_LISTEN_BACKLOG=1)
+ * @return 0 on success, non-zero on failure
+ */
+int
+lwip_listen(int s, int backlog)
+{
+  struct lwip_sock *sock;
+  err_t err;
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_listen(%d, backlog=%d)\n", s, backlog));
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  /* limit the "backlog" parameter to fit in an u8_t */
+  backlog = LWIP_MIN(LWIP_MAX(backlog, 0), 0xff);
+
+  err = netconn_listen_with_backlog(sock->conn, (u8_t)backlog);
+
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_listen(%d) failed, err=%d\n", s, err));
+    if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP) {
+      sock_set_errno(sock, EOPNOTSUPP);
+      return EOPNOTSUPP;
+    }
+    sock_set_errno(sock, err_to_errno(err));
+    return -1;
+  }
+
+  sock_set_errno(sock, 0);
+  return 0;
+}
+
+int
+lwip_recvfrom(int s, void *mem, size_t len, int flags,
+              struct sockaddr *from, socklen_t *fromlen)
+{
+  struct lwip_sock *sock;
+  void             *buf = NULL;
+  struct pbuf      *p;
+  u16_t            buflen, copylen;
+  int              off = 0;
+  u8_t             done = 0;
+  err_t            err;
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom(%d, %p, %"SZT_F", 0x%x, ..)\n", s, mem, len, flags));
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  do {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom: top while sock->lastdata=%p\n", sock->lastdata));
+    /* Check if there is data left from the last recv operation. */
+    if (sock->lastdata) {
+      buf = sock->lastdata;
+    } else {
+      /* If this is non-blocking call, then check first */
+      if (((flags & MSG_DONTWAIT) || netconn_is_nonblocking(sock->conn)) && 
+          (sock->rcvevent <= 0)) {
+        if (off > 0) {
+          /* update receive window */
+          netconn_recved(sock->conn, (u32_t)off);
+          /* already received data, return that */
+          sock_set_errno(sock, 0);
+          return off;
+        }
+        LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom(%d): returning EWOULDBLOCK\n", s));
+        sock_set_errno(sock, EWOULDBLOCK);
+        return -1;
+      }
+
+      /* No data was left from the previous operation, so we try to get
+         some from the network. */
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP) {
+        err = netconn_recv_tcp_pbuf(sock->conn, (struct pbuf **)&buf);
+      } else {
+        err = netconn_recv(sock->conn, (struct netbuf **)&buf);
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom: netconn_recv err=%d, netbuf=%p\n",
+        err, buf));
+
+      if (err != ERR_OK) {
+        if (off > 0) {
+          /* update receive window */
+          netconn_recved(sock->conn, (u32_t)off);
+          /* already received data, return that */
+          sock_set_errno(sock, 0);
+          return off;
+        }
+        /* We should really do some error checking here. */
+        LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom(%d): buf == NULL, error is \"%s\"!\n",
+          s, lwip_strerr(err)));
+        sock_set_errno(sock, err_to_errno(err));
+        if (err == ERR_CLSD) {
+          return 0;
+        } else {
+          return -1;
+        }
+      }
+      LWIP_ASSERT("buf != NULL", buf != NULL);
+      sock->lastdata = buf;
+    }
+
+    if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP) {
+      p = (struct pbuf *)buf;
+    } else {
+      p = ((struct netbuf *)buf)->p;
+    }
+    buflen = p->tot_len;
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom: buflen=%"U16_F" len=%"SZT_F" off=%d sock->lastoffset=%"U16_F"\n",
+      buflen, len, off, sock->lastoffset));
+
+    buflen -= sock->lastoffset;
+
+    if (len > buflen) {
+      copylen = buflen;
+    } else {
+      copylen = (u16_t)len;
+    }
+
+    /* copy the contents of the received buffer into
+    the supplied memory pointer mem */
+    pbuf_copy_partial(p, (u8_t*)mem + off, copylen, sock->lastoffset);
+
+    off += copylen;
+
+    if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP) {
+      LWIP_ASSERT("invalid copylen, len would underflow", len >= copylen);
+      len -= copylen;
+      if ( (len <= 0) || 
+           (p->flags & PBUF_FLAG_PUSH) || 
+           (sock->rcvevent <= 0) || 
+           ((flags & MSG_PEEK)!=0)) {
+        done = 1;
+      }
+    } else {
+      done = 1;
+    }
+
+    /* Check to see from where the data was.*/
+    if (done) {
+#if !SOCKETS_DEBUG
+      if (from && fromlen)
+#endif /* !SOCKETS_DEBUG */
+      {
+        u16_t port;
+        ipX_addr_t tmpaddr;
+        ipX_addr_t *fromaddr;
+        union sockaddr_aligned saddr;
+        LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom(%d): addr=", s));
+        if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP) {
+          fromaddr = &tmpaddr;
+          /* @todo: this does not work for IPv6, yet */
+          netconn_getaddr(sock->conn, ipX_2_ip(fromaddr), &port, 0);
+        } else {
+          port = netbuf_fromport((struct netbuf *)buf);
+          fromaddr = netbuf_fromaddr_ipX((struct netbuf *)buf);
+        }
+        IPXADDR_PORT_TO_SOCKADDR(NETCONNTYPE_ISIPV6(netconn_type(sock->conn)),
+          &saddr, fromaddr, port);
+        ipX_addr_debug_print(NETCONNTYPE_ISIPV6(netconn_type(sock->conn)),
+          SOCKETS_DEBUG, fromaddr);
+        LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F" len=%d\n", port, off));
+#if SOCKETS_DEBUG
+        if (from && fromlen)
+#endif /* SOCKETS_DEBUG */
+        {
+          if (*fromlen > saddr.sa.sa_len) {
+            *fromlen = saddr.sa.sa_len;
+          }
+          MEMCPY(from, &saddr, *fromlen);
+        }
+      }
+    }
+
+    /* If we don't peek the incoming message... */
+    if ((flags & MSG_PEEK) == 0) {
+      /* If this is a TCP socket, check if there is data left in the
+         buffer. If so, it should be saved in the sock structure for next
+         time around. */
+      if ((NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP) && (buflen - copylen > 0)) {
+        sock->lastdata = buf;
+        sock->lastoffset += copylen;
+        LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom: lastdata now netbuf=%p\n", buf));
+      } else {
+        sock->lastdata = NULL;
+        sock->lastoffset = 0;
+        LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom: deleting netbuf=%p\n", buf));
+        if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP) {
+          pbuf_free((struct pbuf *)buf);
+        } else {
+          netbuf_delete((struct netbuf *)buf);
+        }
+      }
+    }
+  } while (!done);
+
+  if ((off > 0) && (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP)) {
+    /* update receive window */
+    netconn_recved(sock->conn, (u32_t)off);
+  }
+  sock_set_errno(sock, 0);
+  return off;
+}
+
+int
+lwip_read(int s, void *mem, size_t len)
+{
+  return lwip_recvfrom(s, mem, len, 0, NULL, NULL);
+}
+
+int
+lwip_recv(int s, void *mem, size_t len, int flags)
+{
+  return lwip_recvfrom(s, mem, len, flags, NULL, NULL);
+}
+
+int
+lwip_send(int s, const void *data, size_t size, int flags)
+{
+  struct lwip_sock *sock;
+  err_t err;
+  u8_t write_flags;
+  size_t written;
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_send(%d, data=%p, size=%"SZT_F", flags=0x%x)\n",
+                              s, data, size, flags));
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP) {
+#if (LWIP_UDP || LWIP_RAW)
+    return lwip_sendto(s, data, size, flags, NULL, 0);
+#else /* (LWIP_UDP || LWIP_RAW) */
+    sock_set_errno(sock, err_to_errno(ERR_ARG));
+    return -1;
+#endif /* (LWIP_UDP || LWIP_RAW) */
+  }
+
+  write_flags = NETCONN_COPY |
+    ((flags & MSG_MORE)     ? NETCONN_MORE      : 0) |
+    ((flags & MSG_DONTWAIT) ? NETCONN_DONTBLOCK : 0);
+  written = 0;
+  err = netconn_write_partly(sock->conn, data, size, write_flags, &written);
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_send(%d) err=%d written=%"SZT_F"\n", s, err, written));
+  sock_set_errno(sock, err_to_errno(err));
+  return (err == ERR_OK ? (int)written : -1);
+}
+
+int
+lwip_sendto(int s, const void *data, size_t size, int flags,
+       const struct sockaddr *to, socklen_t tolen)
+{
+  struct lwip_sock *sock;
+  err_t err;
+  u16_t short_size;
+  u16_t remote_port;
+#if !LWIP_TCPIP_CORE_LOCKING
+  struct netbuf buf;
+#endif
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP) {
+#if LWIP_TCP
+    return lwip_send(s, data, size, flags);
+#else /* LWIP_TCP */
+    LWIP_UNUSED_ARG(flags);
+    sock_set_errno(sock, err_to_errno(ERR_ARG));
+    return -1;
+#endif /* LWIP_TCP */
+  }
+
+  if ((to != NULL) && !SOCK_ADDR_TYPE_MATCH(to, sock)) {
+    /* sockaddr does not match socket type (IPv4/IPv6) */
+    sock_set_errno(sock, err_to_errno(ERR_VAL));
+    return -1;
+  }
+
+  /* @todo: split into multiple sendto's? */
+  LWIP_ASSERT("lwip_sendto: size must fit in u16_t", size <= 0xffff);
+  short_size = (u16_t)size;
+  LWIP_ERROR("lwip_sendto: invalid address", (((to == NULL) && (tolen == 0)) ||
+             (IS_SOCK_ADDR_LEN_VALID(tolen) &&
+             IS_SOCK_ADDR_TYPE_VALID(to) && IS_SOCK_ADDR_ALIGNED(to))),
+             sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
+  LWIP_UNUSED_ARG(tolen);
+
+#if LWIP_TCPIP_CORE_LOCKING
+  /* Special speedup for fast UDP/RAW sending: call the raw API directly
+     instead of using the netconn functions. */
+  {
+    struct pbuf* p;
+    ipX_addr_t *remote_addr;
+    ipX_addr_t remote_addr_tmp;
+
+#if LWIP_NETIF_TX_SINGLE_PBUF
+    p = pbuf_alloc(PBUF_TRANSPORT, short_size, PBUF_RAM);
+    if (p != NULL) {
+#if LWIP_CHECKSUM_ON_COPY
+      u16_t chksum = 0;
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_RAW) {
+        chksum = LWIP_CHKSUM_COPY(p->payload, data, short_size);
+      } else
+#endif /* LWIP_CHECKSUM_ON_COPY */
+      MEMCPY(p->payload, data, size);
+#else /* LWIP_NETIF_TX_SINGLE_PBUF */
+    p = pbuf_alloc(PBUF_TRANSPORT, short_size, PBUF_REF);
+    if (p != NULL) {
+      p->payload = (void*)data;
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+
+      if (to != NULL) {
+        SOCKADDR_TO_IPXADDR_PORT(to->sa_family == AF_INET6,
+          to, &remote_addr_tmp, remote_port);
+        remote_addr = &remote_addr_tmp;
+      } else {
+        remote_addr = &sock->conn->pcb.ip->remote_ip;
+#if LWIP_UDP
+        if (NETCONNTYPE_GROUP(sock->conn->type) == NETCONN_UDP) {
+          remote_port = sock->conn->pcb.udp->remote_port;
+        } else
+#endif /* LWIP_UDP */
+        {
+          remote_port = 0;
+        }
+      }
+
+      LOCK_TCPIP_CORE();
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_RAW) {
+#if LWIP_RAW
+        err = sock->conn->last_err = raw_sendto(sock->conn->pcb.raw, p, ipX_2_ip(remote_addr));
+#else /* LWIP_RAW */
+        err = ERR_ARG;
+#endif /* LWIP_RAW */
+      }
+#if LWIP_UDP && LWIP_RAW
+      else
+#endif /* LWIP_UDP && LWIP_RAW */
+      {
+#if LWIP_UDP
+#if LWIP_CHECKSUM_ON_COPY && LWIP_NETIF_TX_SINGLE_PBUF
+        err = sock->conn->last_err = udp_sendto_chksum(sock->conn->pcb.udp, p,
+          ipX_2_ip(remote_addr), remote_port, 1, chksum);
+#else /* LWIP_CHECKSUM_ON_COPY && LWIP_NETIF_TX_SINGLE_PBUF */
+        err = sock->conn->last_err = udp_sendto(sock->conn->pcb.udp, p,
+          ipX_2_ip(remote_addr), remote_port);
+#endif /* LWIP_CHECKSUM_ON_COPY && LWIP_NETIF_TX_SINGLE_PBUF */
+#else /* LWIP_UDP */
+        err = ERR_ARG;
+#endif /* LWIP_UDP */
+      }
+      UNLOCK_TCPIP_CORE();
+      
+      pbuf_free(p);
+    } else {
+      err = ERR_MEM;
+    }
+  }
+#else /* LWIP_TCPIP_CORE_LOCKING */
+  /* initialize a buffer */
+  buf.p = buf.ptr = NULL;
+#if LWIP_CHECKSUM_ON_COPY
+  buf.flags = 0;
+#endif /* LWIP_CHECKSUM_ON_COPY */
+  if (to) {
+    SOCKADDR_TO_IPXADDR_PORT((to->sa_family) == AF_INET6, to, &buf.addr, remote_port);
+  } else {
+    remote_port = 0;
+    ipX_addr_set_any(NETCONNTYPE_ISIPV6(netconn_type(sock->conn)), &buf.addr);
+  }
+  netbuf_fromport(&buf) = remote_port;
+
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_sendto(%d, data=%p, short_size=%"U16_F", flags=0x%x to=",
+              s, data, short_size, flags));
+  ipX_addr_debug_print(NETCONNTYPE_ISIPV6(netconn_type(sock->conn)),
+    SOCKETS_DEBUG, &buf.addr);
+  LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F"\n", remote_port));
+
+  /* make the buffer point to the data that should be sent */
+#if LWIP_NETIF_TX_SINGLE_PBUF
+  /* Allocate a new netbuf and copy the data into it. */
+  if (netbuf_alloc(&buf, short_size) == NULL) {
+    err = ERR_MEM;
+  } else {
+#if LWIP_CHECKSUM_ON_COPY
+    if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_RAW) {
+      u16_t chksum = LWIP_CHKSUM_COPY(buf.p->payload, data, short_size);
+      netbuf_set_chksum(&buf, chksum);
+      err = ERR_OK;
+    } else
+#endif /* LWIP_CHECKSUM_ON_COPY */
+    {
+      err = netbuf_take(&buf, data, short_size);
+    }
+  }
+#else /* LWIP_NETIF_TX_SINGLE_PBUF */
+  err = netbuf_ref(&buf, data, short_size);
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+  if (err == ERR_OK) {
+    /* send the data */
+    err = netconn_send(sock->conn, &buf);
+  }
+
+  /* deallocated the buffer */
+  netbuf_free(&buf);
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+  sock_set_errno(sock, err_to_errno(err));
+  return (err == ERR_OK ? short_size : -1);
+}
+
+int
+lwip_socket(int domain, int type, int protocol)
+{
+  struct netconn *conn;
+  int i;
+
+#if !LWIP_IPV6
+  LWIP_UNUSED_ARG(domain); /* @todo: check this */
+#endif /* LWIP_IPV6 */
+
+  /* create a netconn */
+  switch (type) {
+  case SOCK_RAW:
+    conn = netconn_new_with_proto_and_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_RAW),
+                                               (u8_t)protocol, event_callback);
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_RAW, %d) = ",
+                                 domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
+    break;
+  case SOCK_DGRAM:
+    conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain,
+                 ((protocol == IPPROTO_UDPLITE) ? NETCONN_UDPLITE : NETCONN_UDP)) ,
+                 event_callback);
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_DGRAM, %d) = ",
+                                 domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
+    break;
+  case SOCK_STREAM:
+    conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_TCP), event_callback);
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_STREAM, %d) = ",
+                                 domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
+    if (conn != NULL) {
+      /* Prevent automatic window updates, we do this on our own! */
+      netconn_set_noautorecved(conn, 1);
+    }
+    break;
+  default:
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%d, %d/UNKNOWN, %d) = -1\n",
+                                 domain, type, protocol));
+    set_errno(EINVAL);
+    return -1;
+  }
+
+  if (!conn) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("-1 / ENOBUFS (could not create netconn)\n"));
+    set_errno(ENOBUFS);
+    return -1;
+  }
+
+  i = alloc_socket(conn, 0);
+
+  if (i == -1) {
+    netconn_delete(conn);
+    set_errno(ENFILE);
+    return -1;
+  }
+  conn->socket = i;
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("%d\n", i));
+  set_errno(0);
+  return i;
+}
+
+int
+lwip_write(int s, const void *data, size_t size)
+{
+  return lwip_send(s, data, size, 0);
+}
+
+/**
+ * Go through the readset and writeset lists and see which socket of the sockets
+ * set in the sets has events. On return, readset, writeset and exceptset have
+ * the sockets enabled that had events.
+ *
+ * exceptset is not used for now!!!
+ *
+ * @param maxfdp1 the highest socket index in the sets
+ * @param readset_in:    set of sockets to check for read events
+ * @param writeset_in:   set of sockets to check for write events
+ * @param exceptset_in:  set of sockets to check for error events
+ * @param readset_out:   set of sockets that had read events
+ * @param writeset_out:  set of sockets that had write events
+ * @param exceptset_out: set os sockets that had error events
+ * @return number of sockets that had events (read/write/exception) (>= 0)
+ */
+static int
+lwip_selscan(int maxfdp1, fd_set *readset_in, fd_set *writeset_in, fd_set *exceptset_in,
+             fd_set *readset_out, fd_set *writeset_out, fd_set *exceptset_out)
+{
+  int i, nready = 0;
+  fd_set lreadset, lwriteset, lexceptset;
+  struct lwip_sock *sock;
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  FD_ZERO(&lreadset);
+  FD_ZERO(&lwriteset);
+  FD_ZERO(&lexceptset);
+
+  /* Go through each socket in each list to count number of sockets which
+     currently match */
+  for(i = 0; i < maxfdp1; i++) {
+    void* lastdata = NULL;
+    s16_t rcvevent = 0;
+    u16_t sendevent = 0;
+    u16_t errevent = 0;
+    /* First get the socket's status (protected)... */
+    SYS_ARCH_PROTECT(lev);
+    sock = tryget_socket(i);
+    if (sock != NULL) {
+      lastdata = sock->lastdata;
+      rcvevent = sock->rcvevent;
+      sendevent = sock->sendevent;
+      errevent = sock->errevent;
+    }
+    SYS_ARCH_UNPROTECT(lev);
+    /* ... then examine it: */
+    /* See if netconn of this socket is ready for read */
+    if (readset_in && FD_ISSET(i, readset_in) && ((lastdata != NULL) || (rcvevent > 0))) {
+      FD_SET(i, &lreadset);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_selscan: fd=%d ready for reading\n", i));
+      nready++;
+    }
+    /* See if netconn of this socket is ready for write */
+    if (writeset_in && FD_ISSET(i, writeset_in) && (sendevent != 0)) {
+      FD_SET(i, &lwriteset);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_selscan: fd=%d ready for writing\n", i));
+      nready++;
+    }
+    /* See if netconn of this socket had an error */
+    if (exceptset_in && FD_ISSET(i, exceptset_in) && (errevent != 0)) {
+      FD_SET(i, &lexceptset);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_selscan: fd=%d ready for exception\n", i));
+      nready++;
+    }
+  }
+  /* copy local sets to the ones provided as arguments */
+  *readset_out = lreadset;
+  *writeset_out = lwriteset;
+  *exceptset_out = lexceptset;
+
+  LWIP_ASSERT("nready >= 0", nready >= 0);
+  return nready;
+}
+
+/**
+ * Processing exceptset is not yet implemented.
+ */
+int
+lwip_select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
+            struct timeval *timeout)
+{
+  u32_t waitres = 0;
+  int nready;
+  fd_set lreadset, lwriteset, lexceptset;
+  u32_t msectimeout;
+  struct lwip_select_cb select_cb;
+  err_t err;
+  int i;
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_select(%d, %p, %p, %p, tvsec=%"S32_F" tvusec=%"S32_F")\n",
+                  maxfdp1, (void *)readset, (void *) writeset, (void *) exceptset,
+                  timeout ? (s32_t)timeout->tv_sec : (s32_t)-1,
+                  timeout ? (s32_t)timeout->tv_usec : (s32_t)-1));
+
+  /* Go through each socket in each list to count number of sockets which
+     currently match */
+  nready = lwip_selscan(maxfdp1, readset, writeset, exceptset, &lreadset, &lwriteset, &lexceptset);
+
+  /* If we don't have any current events, then suspend if we are supposed to */
+  if (!nready) {
+    if (timeout && timeout->tv_sec == 0 && timeout->tv_usec == 0) {
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_select: no timeout, returning 0\n"));
+      /* This is OK as the local fdsets are empty and nready is zero,
+         or we would have returned earlier. */
+      goto return_copy_fdsets;
+    }
+
+    /* None ready: add our semaphore to list:
+       We don't actually need any dynamic memory. Our entry on the
+       list is only valid while we are in this function, so it's ok
+       to use local variables. */
+
+    select_cb.next = NULL;
+    select_cb.prev = NULL;
+    select_cb.readset = readset;
+    select_cb.writeset = writeset;
+    select_cb.exceptset = exceptset;
+    select_cb.sem_signalled = 0;
+    err = sys_sem_new(&select_cb.sem, 0);
+    if (err != ERR_OK) {
+      /* failed to create semaphore */
+      set_errno(ENOMEM);
+      return -1;
+    }
+
+    /* Protect the select_cb_list */
+    SYS_ARCH_PROTECT(lev);
+
+    /* Put this select_cb on top of list */
+    select_cb.next = select_cb_list;
+    if (select_cb_list != NULL) {
+      select_cb_list->prev = &select_cb;
+    }
+    select_cb_list = &select_cb;
+    /* Increasing this counter tells even_callback that the list has changed. */
+    select_cb_ctr++;
+
+    /* Now we can safely unprotect */
+    SYS_ARCH_UNPROTECT(lev);
+
+    /* Increase select_waiting for each socket we are interested in */
+    for(i = 0; i < maxfdp1; i++) {
+      if ((readset && FD_ISSET(i, readset)) ||
+          (writeset && FD_ISSET(i, writeset)) ||
+          (exceptset && FD_ISSET(i, exceptset))) {
+        struct lwip_sock *sock = tryget_socket(i);
+        LWIP_ASSERT("sock != NULL", sock != NULL);
+        SYS_ARCH_PROTECT(lev);
+        sock->select_waiting++;
+        LWIP_ASSERT("sock->select_waiting > 0", sock->select_waiting > 0);
+        SYS_ARCH_UNPROTECT(lev);
+      }
+    }
+
+    /* Call lwip_selscan again: there could have been events between
+       the last scan (whithout us on the list) and putting us on the list! */
+    nready = lwip_selscan(maxfdp1, readset, writeset, exceptset, &lreadset, &lwriteset, &lexceptset);
+    if (!nready) {
+      /* Still none ready, just wait to be woken */
+      if (timeout == 0) {
+        /* Wait forever */
+        msectimeout = 0;
+      } else {
+        msectimeout =  ((timeout->tv_sec * 1000) + ((timeout->tv_usec + 500)/1000));
+        if (msectimeout == 0) {
+          /* Wait 1ms at least (0 means wait forever) */
+          msectimeout = 1;
+        }
+      }
+
+      waitres = sys_arch_sem_wait(&select_cb.sem, msectimeout);
+    }
+    /* Increase select_waiting for each socket we are interested in */
+    for(i = 0; i < maxfdp1; i++) {
+      if ((readset && FD_ISSET(i, readset)) ||
+          (writeset && FD_ISSET(i, writeset)) ||
+          (exceptset && FD_ISSET(i, exceptset))) {
+        struct lwip_sock *sock = tryget_socket(i);
+        LWIP_ASSERT("sock != NULL", sock != NULL);
+        SYS_ARCH_PROTECT(lev);
+        sock->select_waiting--;
+        LWIP_ASSERT("sock->select_waiting >= 0", sock->select_waiting >= 0);
+        SYS_ARCH_UNPROTECT(lev);
+      }
+    }
+    /* Take us off the list */
+    SYS_ARCH_PROTECT(lev);
+    if (select_cb.next != NULL) {
+      select_cb.next->prev = select_cb.prev;
+    }
+    if (select_cb_list == &select_cb) {
+      LWIP_ASSERT("select_cb.prev == NULL", select_cb.prev == NULL);
+      select_cb_list = select_cb.next;
+    } else {
+      LWIP_ASSERT("select_cb.prev != NULL", select_cb.prev != NULL);
+      select_cb.prev->next = select_cb.next;
+    }
+    /* Increasing this counter tells even_callback that the list has changed. */
+    select_cb_ctr++;
+    SYS_ARCH_UNPROTECT(lev);
+
+    sys_sem_free(&select_cb.sem);
+    if (waitres == SYS_ARCH_TIMEOUT)  {
+      /* Timeout */
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_select: timeout expired\n"));
+      /* This is OK as the local fdsets are empty and nready is zero,
+         or we would have returned earlier. */
+      goto return_copy_fdsets;
+    }
+
+    /* See what's set */
+    nready = lwip_selscan(maxfdp1, readset, writeset, exceptset, &lreadset, &lwriteset, &lexceptset);
+  }
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_select: nready=%d\n", nready));
+return_copy_fdsets:
+  set_errno(0);
+  if (readset) {
+    *readset = lreadset;
+  }
+  if (writeset) {
+    *writeset = lwriteset;
+  }
+  if (exceptset) {
+    *exceptset = lexceptset;
+  }
+  return nready;
+}
+
+/**
+ * Callback registered in the netconn layer for each socket-netconn.
+ * Processes recvevent (data available) and wakes up tasks waiting for select.
+ */
+static void
+event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len)
+{
+  int s;
+  struct lwip_sock *sock;
+  struct lwip_select_cb *scb;
+  int last_select_cb_ctr;
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  LWIP_UNUSED_ARG(len);
+
+  /* Get socket */
+  if (conn) {
+    s = conn->socket;
+    if (s < 0) {
+      /* Data comes in right away after an accept, even though
+       * the server task might not have created a new socket yet.
+       * Just count down (or up) if that's the case and we
+       * will use the data later. Note that only receive events
+       * can happen before the new socket is set up. */
+      SYS_ARCH_PROTECT(lev);
+      if (conn->socket < 0) {
+        if (evt == NETCONN_EVT_RCVPLUS) {
+          conn->socket--;
+        }
+        SYS_ARCH_UNPROTECT(lev);
+        return;
+      }
+      s = conn->socket;
+      SYS_ARCH_UNPROTECT(lev);
+    }
+
+    sock = get_socket(s);
+    if (!sock) {
+      return;
+    }
+  } else {
+    return;
+  }
+
+  SYS_ARCH_PROTECT(lev);
+  /* Set event as required */
+  switch (evt) {
+    case NETCONN_EVT_RCVPLUS:
+      sock->rcvevent++;
+      break;
+    case NETCONN_EVT_RCVMINUS:
+      sock->rcvevent--;
+      break;
+    case NETCONN_EVT_SENDPLUS:
+      sock->sendevent = 1;
+      break;
+    case NETCONN_EVT_SENDMINUS:
+      sock->sendevent = 0;
+      break;
+    case NETCONN_EVT_ERROR:
+      sock->errevent = 1;
+      break;
+    default:
+      LWIP_ASSERT("unknown event", 0);
+      break;
+  }
+
+  if (sock->select_waiting == 0) {
+    /* noone is waiting for this socket, no need to check select_cb_list */
+    SYS_ARCH_UNPROTECT(lev);
+    return;
+  }
+
+  /* Now decide if anyone is waiting for this socket */
+  /* NOTE: This code goes through the select_cb_list list multiple times
+     ONLY IF a select was actually waiting. We go through the list the number
+     of waiting select calls + 1. This list is expected to be small. */
+
+  /* At this point, SYS_ARCH is still protected! */
+again:
+  for (scb = select_cb_list; scb != NULL; scb = scb->next) {
+    if (scb->sem_signalled == 0) {
+      /* semaphore not signalled yet */
+      int do_signal = 0;
+      /* Test this select call for our socket */
+      if (sock->rcvevent > 0) {
+        if (scb->readset && FD_ISSET(s, scb->readset)) {
+          do_signal = 1;
+        }
+      }
+      if (sock->sendevent != 0) {
+        if (!do_signal && scb->writeset && FD_ISSET(s, scb->writeset)) {
+          do_signal = 1;
+        }
+      }
+      if (sock->errevent != 0) {
+        if (!do_signal && scb->exceptset && FD_ISSET(s, scb->exceptset)) {
+          do_signal = 1;
+        }
+      }
+      if (do_signal) {
+        scb->sem_signalled = 1;
+        /* Don't call SYS_ARCH_UNPROTECT() before signaling the semaphore, as this might
+           lead to the select thread taking itself off the list, invalidagin the semaphore. */
+        sys_sem_signal(&scb->sem);
+      }
+    }
+    /* unlock interrupts with each step */
+    last_select_cb_ctr = select_cb_ctr;
+    SYS_ARCH_UNPROTECT(lev);
+    /* this makes sure interrupt protection time is short */
+    SYS_ARCH_PROTECT(lev);
+    if (last_select_cb_ctr != select_cb_ctr) {
+      /* someone has changed select_cb_list, restart at the beginning */
+      goto again;
+    }
+  }
+  SYS_ARCH_UNPROTECT(lev);
+}
+
+/**
+ * Unimplemented: Close one end of a full-duplex connection.
+ * Currently, the full connection is closed.
+ */
+int
+lwip_shutdown(int s, int how)
+{
+  struct lwip_sock *sock;
+  err_t err;
+  u8_t shut_rx = 0, shut_tx = 0;
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_shutdown(%d, how=%d)\n", s, how));
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  if (sock->conn != NULL) {
+    if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP) {
+      sock_set_errno(sock, EOPNOTSUPP);
+      return EOPNOTSUPP;
+    }
+  } else {
+    sock_set_errno(sock, ENOTCONN);
+    return ENOTCONN;
+  }
+
+  if (how == SHUT_RD) {
+    shut_rx = 1;
+  } else if (how == SHUT_WR) {
+    shut_tx = 1;
+  } else if(how == SHUT_RDWR) {
+    shut_rx = 1;
+    shut_tx = 1;
+  } else {
+    sock_set_errno(sock, EINVAL);
+    return EINVAL;
+  }
+  err = netconn_shutdown(sock->conn, shut_rx, shut_tx);
+
+  sock_set_errno(sock, err_to_errno(err));
+  return (err == ERR_OK ? 0 : -1);
+}
+
+static int
+lwip_getaddrname(int s, struct sockaddr *name, socklen_t *namelen, u8_t local)
+{
+  struct lwip_sock *sock;
+  union sockaddr_aligned saddr;
+  ipX_addr_t naddr;
+  u16_t port;
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  /* get the IP address and port */
+  /* @todo: this does not work for IPv6, yet */
+  netconn_getaddr(sock->conn, ipX_2_ip(&naddr), &port, local);
+  IPXADDR_PORT_TO_SOCKADDR(NETCONNTYPE_ISIPV6(netconn_type(sock->conn)),
+    &saddr, &naddr, port);
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getaddrname(%d, addr=", s));
+  ipX_addr_debug_print(NETCONNTYPE_ISIPV6(netconn_type(sock->conn)),
+    SOCKETS_DEBUG, &naddr);
+  LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F")\n", port));
+
+  if (*namelen > saddr.sa.sa_len) {
+    *namelen = saddr.sa.sa_len;
+  }
+  MEMCPY(name, &saddr, *namelen);
+
+  sock_set_errno(sock, 0);
+  return 0;
+}
+
+int
+lwip_getpeername(int s, struct sockaddr *name, socklen_t *namelen)
+{
+  return lwip_getaddrname(s, name, namelen, 0);
+}
+
+int
+lwip_getsockname(int s, struct sockaddr *name, socklen_t *namelen)
+{
+  return lwip_getaddrname(s, name, namelen, 1);
+}
+
+int
+lwip_getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen)
+{
+  err_t err = ERR_OK;
+  struct lwip_sock *sock = get_socket(s);
+  struct lwip_setgetsockopt_data data;
+
+  if (!sock) {
+    return -1;
+  }
+
+  if ((NULL == optval) || (NULL == optlen)) {
+    sock_set_errno(sock, EFAULT);
+    return -1;
+  }
+
+  /* Do length and type checks for the various options first, to keep it readable. */
+  switch (level) {
+   
+/* Level: SOL_SOCKET */
+  case SOL_SOCKET:
+    switch (optname) {
+       
+    case SO_ACCEPTCONN:
+    case SO_BROADCAST:
+    /* UNIMPL case SO_DEBUG: */
+    /* UNIMPL case SO_DONTROUTE: */
+    case SO_ERROR:
+    case SO_KEEPALIVE:
+    /* UNIMPL case SO_CONTIMEO: */
+#if LWIP_SO_SNDTIMEO
+    case SO_SNDTIMEO:
+#endif /* LWIP_SO_SNDTIMEO */
+#if LWIP_SO_RCVTIMEO
+    case SO_RCVTIMEO:
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVBUF
+    case SO_RCVBUF:
+#endif /* LWIP_SO_RCVBUF */
+    /* UNIMPL case SO_OOBINLINE: */
+    /* UNIMPL case SO_SNDBUF: */
+    /* UNIMPL case SO_RCVLOWAT: */
+    /* UNIMPL case SO_SNDLOWAT: */
+#if SO_REUSE
+    case SO_REUSEADDR:
+    case SO_REUSEPORT:
+#endif /* SO_REUSE */
+    case SO_TYPE:
+    /* UNIMPL case SO_USELOOPBACK: */
+      if (*optlen < sizeof(int)) {
+        err = EINVAL;
+      }
+      break;
+
+    case SO_NO_CHECK:
+      if (*optlen < sizeof(int)) {
+        err = EINVAL;
+      }
+#if LWIP_UDP
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP ||
+          ((udp_flags(sock->conn->pcb.udp) & UDP_FLAGS_UDPLITE) != 0)) {
+        /* this flag is only available for UDP, not for UDP lite */
+        err = EAFNOSUPPORT;
+      }
+#endif /* LWIP_UDP */
+      break;
+
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, SOL_SOCKET, UNIMPL: optname=0x%x, ..)\n",
+                                  s, optname));
+      err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+                     
+/* Level: IPPROTO_IP */
+  case IPPROTO_IP:
+    switch (optname) {
+    /* UNIMPL case IP_HDRINCL: */
+    /* UNIMPL case IP_RCVDSTADDR: */
+    /* UNIMPL case IP_RCVIF: */
+    case IP_TTL:
+    case IP_TOS:
+      if (*optlen < sizeof(int)) {
+        err = EINVAL;
+      }
+      break;
+#if LWIP_IGMP
+    case IP_MULTICAST_TTL:
+      if (*optlen < sizeof(u8_t)) {
+        err = EINVAL;
+      }
+      break;
+    case IP_MULTICAST_IF:
+      if (*optlen < sizeof(struct in_addr)) {
+        err = EINVAL;
+      }
+      break;
+    case IP_MULTICAST_LOOP:
+      if (*optlen < sizeof(u8_t)) {
+        err = EINVAL;
+      }
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
+        err = EAFNOSUPPORT;
+      }
+      break;
+#endif /* LWIP_IGMP */
+
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, UNIMPL: optname=0x%x, ..)\n",
+                                  s, optname));
+      err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+         
+#if LWIP_TCP
+/* Level: IPPROTO_TCP */
+  case IPPROTO_TCP:
+    if (*optlen < sizeof(int)) {
+      err = EINVAL;
+      break;
+    }
+    
+    /* If this is no TCP socket, ignore any options. */
+    if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP)
+      return 0;
+
+    switch (optname) {
+    case TCP_NODELAY:
+    case TCP_KEEPALIVE:
+#if LWIP_TCP_KEEPALIVE
+    case TCP_KEEPIDLE:
+    case TCP_KEEPINTVL:
+    case TCP_KEEPCNT:
+#endif /* LWIP_TCP_KEEPALIVE */
+      break;
+       
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_TCP, UNIMPL: optname=0x%x, ..)\n",
+                                  s, optname));
+      err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_TCP */
+
+#if LWIP_IPV6
+/* Level: IPPROTO_IPV6 */
+  case IPPROTO_IPV6:
+    switch (optname) {
+    case IPV6_V6ONLY:
+      if (*optlen < sizeof(int)) {
+        err = EINVAL;
+      }
+      /* @todo: this does not work for datagram sockets, yet */
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP)
+        return 0;
+      break;
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IPV6, UNIMPL: optname=0x%x, ..)\n",
+                                  s, optname));
+      err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_IPV6 */
+
+#if LWIP_UDP && LWIP_UDPLITE
+/* Level: IPPROTO_UDPLITE */
+  case IPPROTO_UDPLITE:
+    if (*optlen < sizeof(int)) {
+      err = EINVAL;
+      break;
+    }
+    
+    /* If this is no UDP lite socket, ignore any options. */
+    if (!NETCONNTYPE_ISUDPLITE(netconn_type(sock->conn))) {
+      return 0;
+    }
+
+    switch (optname) {
+    case UDPLITE_SEND_CSCOV:
+    case UDPLITE_RECV_CSCOV:
+      break;
+       
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_UDPLITE, UNIMPL: optname=0x%x, ..)\n",
+                                  s, optname));
+      err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_UDP && LWIP_UDPLITE*/
+/* UNDEFINED LEVEL */
+  default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, level=0x%x, UNIMPL: optname=0x%x, ..)\n",
+                                  s, level, optname));
+      err = ENOPROTOOPT;
+  }  /* switch */
+
+   
+  if (err != ERR_OK) {
+    sock_set_errno(sock, err);
+    return -1;
+  }
+
+  /* Now do the actual option processing */
+  data.sock = sock;
+#ifdef LWIP_DEBUG
+  data.s = s;
+#endif /* LWIP_DEBUG */
+  data.level = level;
+  data.optname = optname;
+  data.optval = optval;
+  data.optlen = optlen;
+  data.err = err;
+  tcpip_callback(lwip_getsockopt_internal, &data);
+  sys_arch_sem_wait(&sock->conn->op_completed, 0);
+  /* maybe lwip_getsockopt_internal has changed err */
+  err = data.err;
+
+  sock_set_errno(sock, err);
+  return err ? -1 : 0;
+}
+
+static void
+lwip_getsockopt_internal(void *arg)
+{
+  struct lwip_sock *sock;
+#ifdef LWIP_DEBUG
+  int s;
+#endif /* LWIP_DEBUG */
+  int level, optname;
+  void *optval;
+  struct lwip_setgetsockopt_data *data;
+
+  LWIP_ASSERT("arg != NULL", arg != NULL);
+
+  data = (struct lwip_setgetsockopt_data*)arg;
+  sock = data->sock;
+#ifdef LWIP_DEBUG
+  s = data->s;
+#endif /* LWIP_DEBUG */
+  level = data->level;
+  optname = data->optname;
+  optval = data->optval;
+
+  switch (level) {
+
+/* Level: SOL_SOCKET */
+  case SOL_SOCKET:
+    switch (optname) {
+
+    /* The option flags */
+    case SO_ACCEPTCONN:
+    case SO_BROADCAST:
+    /* UNIMPL case SO_DEBUG: */
+    /* UNIMPL case SO_DONTROUTE: */
+    case SO_KEEPALIVE:
+    /* UNIMPL case SO_OOBINCLUDE: */
+#if SO_REUSE
+    case SO_REUSEADDR:
+    case SO_REUSEPORT:
+#endif /* SO_REUSE */
+    /*case SO_USELOOPBACK: UNIMPL */
+      *(int*)optval = ip_get_option(sock->conn->pcb.ip, optname);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, SOL_SOCKET, optname=0x%x, ..) = %s\n",
+                                  s, optname, (*(int*)optval?"on":"off")));
+      break;
+
+    case SO_TYPE:
+      switch (NETCONNTYPE_GROUP(netconn_type(sock->conn))) {
+      case NETCONN_RAW:
+        *(int*)optval = SOCK_RAW;
+        break;
+      case NETCONN_TCP:
+        *(int*)optval = SOCK_STREAM;
+        break;
+      case NETCONN_UDP:
+        *(int*)optval = SOCK_DGRAM;
+        break;
+      default: /* unrecognized socket type */
+        *(int*)optval = netconn_type(sock->conn);
+        LWIP_DEBUGF(SOCKETS_DEBUG,
+                    ("lwip_getsockopt(%d, SOL_SOCKET, SO_TYPE): unrecognized socket type %d\n",
+                    s, *(int *)optval));
+      }  /* switch (netconn_type(sock->conn)) */
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, SOL_SOCKET, SO_TYPE) = %d\n",
+                  s, *(int *)optval));
+      break;
+
+    case SO_ERROR:
+      /* only overwrite ERR_OK or tempoary errors */
+      if ((sock->err == 0) || (sock->err == EINPROGRESS)) {
+        sock_set_errno(sock, err_to_errno(sock->conn->last_err));
+      } 
+      *(int *)optval = sock->err;
+      sock->err = 0;
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, SOL_SOCKET, SO_ERROR) = %d\n",
+                  s, *(int *)optval));
+      break;
+
+#if LWIP_SO_SNDTIMEO
+    case SO_SNDTIMEO:
+      *(int *)optval = netconn_get_sendtimeout(sock->conn);
+      break;
+#endif /* LWIP_SO_SNDTIMEO */
+#if LWIP_SO_RCVTIMEO
+    case SO_RCVTIMEO:
+      *(int *)optval = netconn_get_recvtimeout(sock->conn);
+      break;
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVBUF
+    case SO_RCVBUF:
+      *(int *)optval = netconn_get_recvbufsize(sock->conn);
+      break;
+#endif /* LWIP_SO_RCVBUF */
+#if LWIP_UDP
+    case SO_NO_CHECK:
+      *(int*)optval = (udp_flags(sock->conn->pcb.udp) & UDP_FLAGS_NOCHKSUM) ? 1 : 0;
+      break;
+#endif /* LWIP_UDP*/
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+
+/* Level: IPPROTO_IP */
+  case IPPROTO_IP:
+    switch (optname) {
+    case IP_TTL:
+      *(int*)optval = sock->conn->pcb.ip->ttl;
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, IP_TTL) = %d\n",
+                  s, *(int *)optval));
+      break;
+    case IP_TOS:
+      *(int*)optval = sock->conn->pcb.ip->tos;
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, IP_TOS) = %d\n",
+                  s, *(int *)optval));
+      break;
+#if LWIP_IGMP
+    case IP_MULTICAST_TTL:
+      *(u8_t*)optval = sock->conn->pcb.ip->ttl;
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, IP_MULTICAST_TTL) = %d\n",
+                  s, *(int *)optval));
+      break;
+    case IP_MULTICAST_IF:
+      inet_addr_from_ipaddr((struct in_addr*)optval, &sock->conn->pcb.udp->multicast_ip);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, IP_MULTICAST_IF) = 0x%"X32_F"\n",
+                  s, *(u32_t *)optval));
+      break;
+    case IP_MULTICAST_LOOP:
+      if ((sock->conn->pcb.udp->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) {
+        *(u8_t*)optval = 1;
+      } else {
+        *(u8_t*)optval = 0;
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, IP_MULTICAST_LOOP) = %d\n",
+                  s, *(int *)optval));
+      break;
+#endif /* LWIP_IGMP */
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+
+#if LWIP_TCP
+/* Level: IPPROTO_TCP */
+  case IPPROTO_TCP:
+    switch (optname) {
+    case TCP_NODELAY:
+      *(int*)optval = tcp_nagle_disabled(sock->conn->pcb.tcp);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_TCP, TCP_NODELAY) = %s\n",
+                  s, (*(int*)optval)?"on":"off") );
+      break;
+    case TCP_KEEPALIVE:
+      *(int*)optval = (int)sock->conn->pcb.tcp->keep_idle;
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, TCP_KEEPALIVE) = %d\n",
+                  s, *(int *)optval));
+      break;
+
+#if LWIP_TCP_KEEPALIVE
+    case TCP_KEEPIDLE:
+      *(int*)optval = (int)(sock->conn->pcb.tcp->keep_idle/1000);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, TCP_KEEPIDLE) = %d\n",
+                  s, *(int *)optval));
+      break;
+    case TCP_KEEPINTVL:
+      *(int*)optval = (int)(sock->conn->pcb.tcp->keep_intvl/1000);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, TCP_KEEPINTVL) = %d\n",
+                  s, *(int *)optval));
+      break;
+    case TCP_KEEPCNT:
+      *(int*)optval = (int)sock->conn->pcb.tcp->keep_cnt;
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, TCP_KEEPCNT) = %d\n",
+                  s, *(int *)optval));
+      break;
+#endif /* LWIP_TCP_KEEPALIVE */
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_TCP */
+
+#if LWIP_IPV6
+/* Level: IPPROTO_IPV6 */
+  case IPPROTO_IPV6:
+    switch (optname) {
+    case IPV6_V6ONLY:
+      *(int*)optval = ((sock->conn->flags & NETCONN_FLAG_IPV6_V6ONLY) ? 1 : 0);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IPV6, IPV6_V6ONLY) = %d\n",
+                  s, *(int *)optval));
+      break;
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_IPV6 */
+
+#if LWIP_UDP && LWIP_UDPLITE
+  /* Level: IPPROTO_UDPLITE */
+  case IPPROTO_UDPLITE:
+    switch (optname) {
+    case UDPLITE_SEND_CSCOV:
+      *(int*)optval = sock->conn->pcb.udp->chksum_len_tx;
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV) = %d\n",
+                  s, (*(int*)optval)) );
+      break;
+    case UDPLITE_RECV_CSCOV:
+      *(int*)optval = sock->conn->pcb.udp->chksum_len_rx;
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV) = %d\n",
+                  s, (*(int*)optval)) );
+      break;
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_UDP */
+  default:
+    LWIP_ASSERT("unhandled level", 0);
+    break;
+  } /* switch (level) */
+  sys_sem_signal(&sock->conn->op_completed);
+}
+
+int
+lwip_setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen)
+{
+  struct lwip_sock *sock = get_socket(s);
+  err_t err = ERR_OK;
+  struct lwip_setgetsockopt_data data;
+
+  if (!sock) {
+    return -1;
+  }
+
+  if (NULL == optval) {
+    sock_set_errno(sock, EFAULT);
+    return -1;
+  }
+
+  /* Do length and type checks for the various options first, to keep it readable. */
+  switch (level) {
+
+/* Level: SOL_SOCKET */
+  case SOL_SOCKET:
+    switch (optname) {
+
+    case SO_BROADCAST:
+    /* UNIMPL case SO_DEBUG: */
+    /* UNIMPL case SO_DONTROUTE: */
+    case SO_KEEPALIVE:
+    /* UNIMPL case case SO_CONTIMEO: */
+#if LWIP_SO_SNDTIMEO
+    case SO_SNDTIMEO:
+#endif /* LWIP_SO_SNDTIMEO */
+#if LWIP_SO_RCVTIMEO
+    case SO_RCVTIMEO:
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVBUF
+    case SO_RCVBUF:
+#endif /* LWIP_SO_RCVBUF */
+    /* UNIMPL case SO_OOBINLINE: */
+    /* UNIMPL case SO_SNDBUF: */
+    /* UNIMPL case SO_RCVLOWAT: */
+    /* UNIMPL case SO_SNDLOWAT: */
+#if SO_REUSE
+    case SO_REUSEADDR:
+    case SO_REUSEPORT:
+#endif /* SO_REUSE */
+    /* UNIMPL case SO_USELOOPBACK: */
+      if (optlen < sizeof(int)) {
+        err = EINVAL;
+      }
+      break;
+    case SO_NO_CHECK:
+      if (optlen < sizeof(int)) {
+        err = EINVAL;
+      }
+#if LWIP_UDP
+      if ((NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) ||
+          ((udp_flags(sock->conn->pcb.udp) & UDP_FLAGS_UDPLITE) != 0)) {
+        /* this flag is only available for UDP, not for UDP lite */
+        err = EAFNOSUPPORT;
+      }
+#endif /* LWIP_UDP */
+      break;
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, SOL_SOCKET, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+
+/* Level: IPPROTO_IP */
+  case IPPROTO_IP:
+    switch (optname) {
+    /* UNIMPL case IP_HDRINCL: */
+    /* UNIMPL case IP_RCVDSTADDR: */
+    /* UNIMPL case IP_RCVIF: */
+    case IP_TTL:
+    case IP_TOS:
+      if (optlen < sizeof(int)) {
+        err = EINVAL;
+      }
+      break;
+#if LWIP_IGMP
+    case IP_MULTICAST_TTL:
+      if (optlen < sizeof(u8_t)) {
+        err = EINVAL;
+      }
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
+        err = EAFNOSUPPORT;
+      }
+      break;
+    case IP_MULTICAST_IF:
+      if (optlen < sizeof(struct in_addr)) {
+        err = EINVAL;
+      }
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
+        err = EAFNOSUPPORT;
+      }
+      break;
+    case IP_MULTICAST_LOOP:
+      if (optlen < sizeof(u8_t)) {
+        err = EINVAL;
+      }
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
+        err = EAFNOSUPPORT;
+      }
+      break;
+    case IP_ADD_MEMBERSHIP:
+    case IP_DROP_MEMBERSHIP:
+      if (optlen < sizeof(struct ip_mreq)) {
+        err = EINVAL;
+      }
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
+        err = EAFNOSUPPORT;
+      }
+      break;
+#endif /* LWIP_IGMP */
+      default:
+        LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IP, UNIMPL: optname=0x%x, ..)\n",
+                    s, optname));
+        err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+
+#if LWIP_TCP
+/* Level: IPPROTO_TCP */
+  case IPPROTO_TCP:
+    if (optlen < sizeof(int)) {
+      err = EINVAL;
+      break;
+    }
+
+    /* If this is no TCP socket, ignore any options. */
+    if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP)
+      return 0;
+
+    switch (optname) {
+    case TCP_NODELAY:
+    case TCP_KEEPALIVE:
+#if LWIP_TCP_KEEPALIVE
+    case TCP_KEEPIDLE:
+    case TCP_KEEPINTVL:
+    case TCP_KEEPCNT:
+#endif /* LWIP_TCP_KEEPALIVE */
+      break;
+
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_TCP, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_TCP */
+
+#if LWIP_IPV6
+/* Level: IPPROTO_IPV6 */
+  case IPPROTO_IPV6:
+    switch (optname) {
+    case IPV6_V6ONLY:
+      if (optlen < sizeof(int)) {
+        err = EINVAL;
+      }
+
+      /* @todo: this does not work for datagram sockets, yet */
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP)
+        return 0;
+
+      break;
+      default:
+        LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IPV6, UNIMPL: optname=0x%x, ..)\n",
+                    s, optname));
+        err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_IPV6 */
+
+#if LWIP_UDP && LWIP_UDPLITE
+/* Level: IPPROTO_UDPLITE */
+  case IPPROTO_UDPLITE:
+    if (optlen < sizeof(int)) {
+      err = EINVAL;
+      break;
+    }
+
+    /* If this is no UDP lite socket, ignore any options. */
+    if (!NETCONNTYPE_ISUDPLITE(netconn_type(sock->conn)))
+      return 0;
+
+    switch (optname) {
+    case UDPLITE_SEND_CSCOV:
+    case UDPLITE_RECV_CSCOV:
+      break;
+
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_UDPLITE, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_UDP && LWIP_UDPLITE */
+/* UNDEFINED LEVEL */
+  default:
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, level=0x%x, UNIMPL: optname=0x%x, ..)\n",
+                s, level, optname));
+    err = ENOPROTOOPT;
+  }  /* switch (level) */
+
+
+  if (err != ERR_OK) {
+    sock_set_errno(sock, err);
+    return -1;
+  }
+
+
+  /* Now do the actual option processing */
+  data.sock = sock;
+#ifdef LWIP_DEBUG
+  data.s = s;
+#endif /* LWIP_DEBUG */
+  data.level = level;
+  data.optname = optname;
+  data.optval = (void*)optval;
+  data.optlen = &optlen;
+  data.err = err;
+  tcpip_callback(lwip_setsockopt_internal, &data);
+  sys_arch_sem_wait(&sock->conn->op_completed, 0);
+  /* maybe lwip_setsockopt_internal has changed err */
+  err = data.err;
+
+  sock_set_errno(sock, err);
+  return err ? -1 : 0;
+}
+
+static void
+lwip_setsockopt_internal(void *arg)
+{
+  struct lwip_sock *sock;
+#ifdef LWIP_DEBUG
+  int s;
+#endif /* LWIP_DEBUG */
+  int level, optname;
+  const void *optval;
+  struct lwip_setgetsockopt_data *data;
+
+  LWIP_ASSERT("arg != NULL", arg != NULL);
+
+  data = (struct lwip_setgetsockopt_data*)arg;
+  sock = data->sock;
+#ifdef LWIP_DEBUG
+  s = data->s;
+#endif /* LWIP_DEBUG */
+  level = data->level;
+  optname = data->optname;
+  optval = data->optval;
+
+  switch (level) {
+
+/* Level: SOL_SOCKET */
+  case SOL_SOCKET:
+    switch (optname) {
+
+    /* The option flags */
+    case SO_BROADCAST:
+    /* UNIMPL case SO_DEBUG: */
+    /* UNIMPL case SO_DONTROUTE: */
+    case SO_KEEPALIVE:
+    /* UNIMPL case SO_OOBINCLUDE: */
+#if SO_REUSE
+    case SO_REUSEADDR:
+    case SO_REUSEPORT:
+#endif /* SO_REUSE */
+    /* UNIMPL case SO_USELOOPBACK: */
+      if (*(int*)optval) {
+        ip_set_option(sock->conn->pcb.ip, optname);
+      } else {
+        ip_reset_option(sock->conn->pcb.ip, optname);
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, SOL_SOCKET, optname=0x%x, ..) -> %s\n",
+                  s, optname, (*(int*)optval?"on":"off")));
+      break;
+#if LWIP_SO_SNDTIMEO
+    case SO_SNDTIMEO:
+      netconn_set_sendtimeout(sock->conn, (s32_t)*(int*)optval);
+      break;
+#endif /* LWIP_SO_SNDTIMEO */
+#if LWIP_SO_RCVTIMEO
+    case SO_RCVTIMEO:
+      netconn_set_recvtimeout(sock->conn, *(int*)optval);
+      break;
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVBUF
+    case SO_RCVBUF:
+      netconn_set_recvbufsize(sock->conn, *(int*)optval);
+      break;
+#endif /* LWIP_SO_RCVBUF */
+#if LWIP_UDP
+    case SO_NO_CHECK:
+      if (*(int*)optval) {
+        udp_setflags(sock->conn->pcb.udp, udp_flags(sock->conn->pcb.udp) | UDP_FLAGS_NOCHKSUM);
+      } else {
+        udp_setflags(sock->conn->pcb.udp, udp_flags(sock->conn->pcb.udp) & ~UDP_FLAGS_NOCHKSUM);
+      }
+      break;
+#endif /* LWIP_UDP */
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+
+/* Level: IPPROTO_IP */
+  case IPPROTO_IP:
+    switch (optname) {
+    case IP_TTL:
+      sock->conn->pcb.ip->ttl = (u8_t)(*(int*)optval);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IP, IP_TTL, ..) -> %d\n",
+                  s, sock->conn->pcb.ip->ttl));
+      break;
+    case IP_TOS:
+      sock->conn->pcb.ip->tos = (u8_t)(*(int*)optval);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IP, IP_TOS, ..)-> %d\n",
+                  s, sock->conn->pcb.ip->tos));
+      break;
+#if LWIP_IGMP
+    case IP_MULTICAST_TTL:
+      sock->conn->pcb.udp->ttl = (u8_t)(*(u8_t*)optval);
+      break;
+    case IP_MULTICAST_IF:
+      inet_addr_to_ipaddr(&sock->conn->pcb.udp->multicast_ip, (struct in_addr*)optval);
+      break;
+    case IP_MULTICAST_LOOP:
+      if (*(u8_t*)optval) {
+        udp_setflags(sock->conn->pcb.udp, udp_flags(sock->conn->pcb.udp) | UDP_FLAGS_MULTICAST_LOOP);
+      } else {
+        udp_setflags(sock->conn->pcb.udp, udp_flags(sock->conn->pcb.udp) & ~UDP_FLAGS_MULTICAST_LOOP);
+      }
+      break;
+    case IP_ADD_MEMBERSHIP:
+    case IP_DROP_MEMBERSHIP:
+      {
+        /* If this is a TCP or a RAW socket, ignore these options. */
+        struct ip_mreq *imr = (struct ip_mreq *)optval;
+        ip_addr_t if_addr;
+        ip_addr_t multi_addr;
+        inet_addr_to_ipaddr(&if_addr, &imr->imr_interface);
+        inet_addr_to_ipaddr(&multi_addr, &imr->imr_multiaddr);
+        if(optname == IP_ADD_MEMBERSHIP){
+          data->err = igmp_joingroup(&if_addr, &multi_addr);
+        } else {
+          data->err = igmp_leavegroup(&if_addr, &multi_addr);
+        }
+        if(data->err != ERR_OK) {
+          data->err = EADDRNOTAVAIL;
+        }
+      }
+      break;
+#endif /* LWIP_IGMP */
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+
+#if LWIP_TCP
+/* Level: IPPROTO_TCP */
+  case IPPROTO_TCP:
+    switch (optname) {
+    case TCP_NODELAY:
+      if (*(int*)optval) {
+        tcp_nagle_disable(sock->conn->pcb.tcp);
+      } else {
+        tcp_nagle_enable(sock->conn->pcb.tcp);
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_TCP, TCP_NODELAY) -> %s\n",
+                  s, (*(int *)optval)?"on":"off") );
+      break;
+    case TCP_KEEPALIVE:
+      sock->conn->pcb.tcp->keep_idle = (u32_t)(*(int*)optval);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_TCP, TCP_KEEPALIVE) -> %"U32_F"\n",
+                  s, sock->conn->pcb.tcp->keep_idle));
+      break;
+
+#if LWIP_TCP_KEEPALIVE
+    case TCP_KEEPIDLE:
+      sock->conn->pcb.tcp->keep_idle = 1000*(u32_t)(*(int*)optval);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_TCP, TCP_KEEPIDLE) -> %"U32_F"\n",
+                  s, sock->conn->pcb.tcp->keep_idle));
+      break;
+    case TCP_KEEPINTVL:
+      sock->conn->pcb.tcp->keep_intvl = 1000*(u32_t)(*(int*)optval);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_TCP, TCP_KEEPINTVL) -> %"U32_F"\n",
+                  s, sock->conn->pcb.tcp->keep_intvl));
+      break;
+    case TCP_KEEPCNT:
+      sock->conn->pcb.tcp->keep_cnt = (u32_t)(*(int*)optval);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_TCP, TCP_KEEPCNT) -> %"U32_F"\n",
+                  s, sock->conn->pcb.tcp->keep_cnt));
+      break;
+#endif /* LWIP_TCP_KEEPALIVE */
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_TCP*/
+
+#if LWIP_IPV6
+/* Level: IPPROTO_IPV6 */
+  case IPPROTO_IPV6:
+    switch (optname) {
+    case IPV6_V6ONLY:
+      if (*(int*)optval) {
+        sock->conn->flags |= NETCONN_FLAG_IPV6_V6ONLY;
+      } else {
+        sock->conn->flags &= ~NETCONN_FLAG_IPV6_V6ONLY;
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IPV6, IPV6_V6ONLY, ..) -> %d\n",
+                  s, ((sock->conn->flags & NETCONN_FLAG_IPV6_V6ONLY) ? 1 : 0)));
+      break;
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_IPV6 */
+
+#if LWIP_UDP && LWIP_UDPLITE
+  /* Level: IPPROTO_UDPLITE */
+  case IPPROTO_UDPLITE:
+    switch (optname) {
+    case UDPLITE_SEND_CSCOV:
+      if ((*(int*)optval != 0) && ((*(int*)optval < 8) || (*(int*)optval > 0xffff))) {
+        /* don't allow illegal values! */
+        sock->conn->pcb.udp->chksum_len_tx = 8;
+      } else {
+        sock->conn->pcb.udp->chksum_len_tx = (u16_t)*(int*)optval;
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV) -> %d\n",
+                  s, (*(int*)optval)) );
+      break;
+    case UDPLITE_RECV_CSCOV:
+      if ((*(int*)optval != 0) && ((*(int*)optval < 8) || (*(int*)optval > 0xffff))) {
+        /* don't allow illegal values! */
+        sock->conn->pcb.udp->chksum_len_rx = 8;
+      } else {
+        sock->conn->pcb.udp->chksum_len_rx = (u16_t)*(int*)optval;
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV) -> %d\n",
+                  s, (*(int*)optval)) );
+      break;
+    default:
+      LWIP_ASSERT("unhandled optname", 0);
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_UDP */
+  default:
+    LWIP_ASSERT("unhandled level", 0);
+    break;
+  }  /* switch (level) */
+  sys_sem_signal(&sock->conn->op_completed);
+}
+
+int
+lwip_ioctl(int s, long cmd, void *argp)
+{
+  struct lwip_sock *sock = get_socket(s);
+  u8_t val;
+#if LWIP_SO_RCVBUF
+  u16_t buflen = 0;
+  s16_t recv_avail;
+#endif /* LWIP_SO_RCVBUF */
+
+  if (!sock) {
+    return -1;
+  }
+
+  switch (cmd) {
+#if LWIP_SO_RCVBUF || LWIP_FIONREAD_LINUXMODE
+  case FIONREAD:
+    if (!argp) {
+      sock_set_errno(sock, EINVAL);
+      return -1;
+    }
+#if LWIP_FIONREAD_LINUXMODE
+    if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP) {
+      struct pbuf *p;
+      if (sock->lastdata) {
+        p = ((struct netbuf *)sock->lastdata)->p;
+      } else {
+        struct netbuf *rxbuf;
+        err_t err;
+        if (sock->rcvevent <= 0) {
+          *((u16_t*)argp) = 0;
+        } else {
+          err = netconn_recv(sock->conn, &rxbuf);
+          if (err != ERR_OK) {
+            *((u16_t*)argp) = 0;
+          } else {
+            sock->lastdata = rxbuf;
+            *((u16_t*)argp) = rxbuf->p->tot_len;
+          }
+        }
+      }
+      return 0;
+    }
+#endif /* LWIP_FIONREAD_LINUXMODE */
+
+#if LWIP_SO_RCVBUF
+    /* we come here if either LWIP_FIONREAD_LINUXMODE==0 or this is a TCP socket */
+    SYS_ARCH_GET(sock->conn->recv_avail, recv_avail);
+    if (recv_avail < 0) {
+      recv_avail = 0;
+    }
+    *((u16_t*)argp) = (u16_t)recv_avail;
+
+    /* Check if there is data left from the last recv operation. /maq 041215 */
+    if (sock->lastdata) {
+      struct pbuf *p = (struct pbuf *)sock->lastdata;
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP) {
+        p = ((struct netbuf *)p)->p;
+      }
+      buflen = p->tot_len;
+      buflen -= sock->lastoffset;
+
+      *((u16_t*)argp) += buflen;
+    }
+
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_ioctl(%d, FIONREAD, %p) = %"U16_F"\n", s, argp, *((u16_t*)argp)));
+    sock_set_errno(sock, 0);
+    return 0;
+#else /* LWIP_SO_RCVBUF */
+    break;
+#endif /* LWIP_SO_RCVBUF */
+#endif /* LWIP_SO_RCVBUF || LWIP_FIONREAD_LINUXMODE */
+
+  case FIONBIO:
+    val = 0;
+    if (argp && *(u32_t*)argp) {
+      val = 1;
+    }
+    netconn_set_nonblocking(sock->conn, val);
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_ioctl(%d, FIONBIO, %d)\n", s, val));
+    sock_set_errno(sock, 0);
+    return 0;
+
+  default:
+    break;
+  } /* switch (cmd) */
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_ioctl(%d, UNIMPL: 0x%lx, %p)\n", s, cmd, argp));
+  sock_set_errno(sock, ENOSYS); /* not yet implemented */
+  return -1;
+}
+
+/** A minimal implementation of fcntl.
+ * Currently only the commands F_GETFL and F_SETFL are implemented.
+ * Only the flag O_NONBLOCK is implemented.
+ */
+int
+lwip_fcntl(int s, int cmd, int val)
+{
+  struct lwip_sock *sock = get_socket(s);
+  int ret = -1;
+
+  if (!sock || !sock->conn) {
+    return -1;
+  }
+
+  switch (cmd) {
+  case F_GETFL:
+    ret = netconn_is_nonblocking(sock->conn) ? O_NONBLOCK : 0;
+    break;
+  case F_SETFL:
+    if ((val & ~O_NONBLOCK) == 0) {
+      /* only O_NONBLOCK, all other bits are zero */
+      netconn_set_nonblocking(sock->conn, val & O_NONBLOCK);
+      ret = 0;
+    }
+    break;
+  default:
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_fcntl(%d, UNIMPL: %d, %d)\n", s, cmd, val));
+    break;
+  }
+  return ret;
+}
+
+#endif /* LWIP_SOCKET */
diff --git a/external/badvpn_dns/lwip/src/api/tcpip.c b/external/badvpn_dns/lwip/src/api/tcpip.c
new file mode 100644
index 0000000..7c1c9ca
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/api/tcpip.c
@@ -0,0 +1,492 @@
+/**
+ * @file
+ * Sequential API Main thread module
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if !NO_SYS /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/sys.h"
+#include "lwip/memp.h"
+#include "lwip/mem.h"
+#include "lwip/pbuf.h"
+#include "lwip/tcpip.h"
+#include "lwip/init.h"
+#include "netif/etharp.h"
+#include "netif/ppp_oe.h"
+
+/* global variables */
+static tcpip_init_done_fn tcpip_init_done;
+static void *tcpip_init_done_arg;
+static sys_mbox_t mbox;
+
+#if LWIP_TCPIP_CORE_LOCKING
+/** The global semaphore to lock the stack. */
+sys_mutex_t lock_tcpip_core;
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+
+
+/**
+ * The main lwIP thread. This thread has exclusive access to lwIP core functions
+ * (unless access to them is not locked). Other threads communicate with this
+ * thread using message boxes.
+ *
+ * It also starts all the timers to make sure they are running in the right
+ * thread context.
+ *
+ * @param arg unused argument
+ */
+static void
+tcpip_thread(void *arg)
+{
+  struct tcpip_msg *msg;
+  LWIP_UNUSED_ARG(arg);
+
+  if (tcpip_init_done != NULL) {
+    tcpip_init_done(tcpip_init_done_arg);
+  }
+
+  LOCK_TCPIP_CORE();
+  while (1) {                          /* MAIN Loop */
+    UNLOCK_TCPIP_CORE();
+    LWIP_TCPIP_THREAD_ALIVE();
+    /* wait for a message, timeouts are processed while waiting */
+    sys_timeouts_mbox_fetch(&mbox, (void **)&msg);
+    LOCK_TCPIP_CORE();
+    switch (msg->type) {
+#if LWIP_NETCONN
+    case TCPIP_MSG_API:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
+      msg->msg.apimsg->function(&(msg->msg.apimsg->msg));
+      break;
+#endif /* LWIP_NETCONN */
+
+#if !LWIP_TCPIP_CORE_LOCKING_INPUT
+    case TCPIP_MSG_INPKT:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
+#if LWIP_ETHERNET
+      if (msg->msg.inp.netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
+        ethernet_input(msg->msg.inp.p, msg->msg.inp.netif);
+      } else
+#endif /* LWIP_ETHERNET */
+#if LWIP_IPV6
+      if ((*((unsigned char *)(msg->msg.inp.p->payload)) & 0xf0) == 0x60) {
+          ip6_input(msg->msg.inp.p, msg->msg.inp.netif);
+      } else
+#endif /* LWIP_IPV6 */
+      {
+        ip_input(msg->msg.inp.p, msg->msg.inp.netif);
+      }
+      memp_free(MEMP_TCPIP_MSG_INPKT, msg);
+      break;
+#endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */
+
+#if LWIP_NETIF_API
+    case TCPIP_MSG_NETIFAPI:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: Netif API message %p\n", (void *)msg));
+      msg->msg.netifapimsg->function(&(msg->msg.netifapimsg->msg));
+      break;
+#endif /* LWIP_NETIF_API */
+
+#if LWIP_TCPIP_TIMEOUT
+    case TCPIP_MSG_TIMEOUT:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: TIMEOUT %p\n", (void *)msg));
+      sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
+      memp_free(MEMP_TCPIP_MSG_API, msg);
+      break;
+    case TCPIP_MSG_UNTIMEOUT:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: UNTIMEOUT %p\n", (void *)msg));
+      sys_untimeout(msg->msg.tmo.h, msg->msg.tmo.arg);
+      memp_free(MEMP_TCPIP_MSG_API, msg);
+      break;
+#endif /* LWIP_TCPIP_TIMEOUT */
+
+    case TCPIP_MSG_CALLBACK:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK %p\n", (void *)msg));
+      msg->msg.cb.function(msg->msg.cb.ctx);
+      memp_free(MEMP_TCPIP_MSG_API, msg);
+      break;
+
+    case TCPIP_MSG_CALLBACK_STATIC:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK_STATIC %p\n", (void *)msg));
+      msg->msg.cb.function(msg->msg.cb.ctx);
+      break;
+
+    default:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: %d\n", msg->type));
+      LWIP_ASSERT("tcpip_thread: invalid message", 0);
+      break;
+    }
+  }
+}
+
+/**
+ * Pass a received packet to tcpip_thread for input processing
+ *
+ * @param p the received packet, p->payload pointing to the Ethernet header or
+ *          to an IP header (if inp doesn't have NETIF_FLAG_ETHARP or
+ *          NETIF_FLAG_ETHERNET flags)
+ * @param inp the network interface on which the packet was received
+ */
+err_t
+tcpip_input(struct pbuf *p, struct netif *inp)
+{
+#if LWIP_TCPIP_CORE_LOCKING_INPUT
+  err_t ret;
+  LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_input: PACKET %p/%p\n", (void *)p, (void *)inp));
+  LOCK_TCPIP_CORE();
+#if LWIP_ETHERNET
+  if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
+    ret = ethernet_input(p, inp);
+  } else
+#endif /* LWIP_ETHERNET */
+  {
+    ret = ip_input(p, inp);
+  }
+  UNLOCK_TCPIP_CORE();
+  return ret;
+#else /* LWIP_TCPIP_CORE_LOCKING_INPUT */
+  struct tcpip_msg *msg;
+
+  if (!sys_mbox_valid(&mbox)) {
+    return ERR_VAL;
+  }
+  msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_INPKT);
+  if (msg == NULL) {
+    return ERR_MEM;
+  }
+
+  msg->type = TCPIP_MSG_INPKT;
+  msg->msg.inp.p = p;
+  msg->msg.inp.netif = inp;
+  if (sys_mbox_trypost(&mbox, msg) != ERR_OK) {
+    memp_free(MEMP_TCPIP_MSG_INPKT, msg);
+    return ERR_MEM;
+  }
+  return ERR_OK;
+#endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */
+}
+
+/**
+ * Call a specific function in the thread context of
+ * tcpip_thread for easy access synchronization.
+ * A function called in that way may access lwIP core code
+ * without fearing concurrent access.
+ *
+ * @param f the function to call
+ * @param ctx parameter passed to f
+ * @param block 1 to block until the request is posted, 0 to non-blocking mode
+ * @return ERR_OK if the function was called, another err_t if not
+ */
+err_t
+tcpip_callback_with_block(tcpip_callback_fn function, void *ctx, u8_t block)
+{
+  struct tcpip_msg *msg;
+
+  if (sys_mbox_valid(&mbox)) {
+    msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_API);
+    if (msg == NULL) {
+      return ERR_MEM;
+    }
+
+    msg->type = TCPIP_MSG_CALLBACK;
+    msg->msg.cb.function = function;
+    msg->msg.cb.ctx = ctx;
+    if (block) {
+      sys_mbox_post(&mbox, msg);
+    } else {
+      if (sys_mbox_trypost(&mbox, msg) != ERR_OK) {
+        memp_free(MEMP_TCPIP_MSG_API, msg);
+        return ERR_MEM;
+      }
+    }
+    return ERR_OK;
+  }
+  return ERR_VAL;
+}
+
+#if LWIP_TCPIP_TIMEOUT
+/**
+ * call sys_timeout in tcpip_thread
+ *
+ * @param msec time in milliseconds for timeout
+ * @param h function to be called on timeout
+ * @param arg argument to pass to timeout function h
+ * @return ERR_MEM on memory error, ERR_OK otherwise
+ */
+err_t
+tcpip_timeout(u32_t msecs, sys_timeout_handler h, void *arg)
+{
+  struct tcpip_msg *msg;
+
+  if (sys_mbox_valid(&mbox)) {
+    msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_API);
+    if (msg == NULL) {
+      return ERR_MEM;
+    }
+
+    msg->type = TCPIP_MSG_TIMEOUT;
+    msg->msg.tmo.msecs = msecs;
+    msg->msg.tmo.h = h;
+    msg->msg.tmo.arg = arg;
+    sys_mbox_post(&mbox, msg);
+    return ERR_OK;
+  }
+  return ERR_VAL;
+}
+
+/**
+ * call sys_untimeout in tcpip_thread
+ *
+ * @param msec time in milliseconds for timeout
+ * @param h function to be called on timeout
+ * @param arg argument to pass to timeout function h
+ * @return ERR_MEM on memory error, ERR_OK otherwise
+ */
+err_t
+tcpip_untimeout(sys_timeout_handler h, void *arg)
+{
+  struct tcpip_msg *msg;
+
+  if (sys_mbox_valid(&mbox)) {
+    msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_API);
+    if (msg == NULL) {
+      return ERR_MEM;
+    }
+
+    msg->type = TCPIP_MSG_UNTIMEOUT;
+    msg->msg.tmo.h = h;
+    msg->msg.tmo.arg = arg;
+    sys_mbox_post(&mbox, msg);
+    return ERR_OK;
+  }
+  return ERR_VAL;
+}
+#endif /* LWIP_TCPIP_TIMEOUT */
+
+#if LWIP_NETCONN
+/**
+ * Call the lower part of a netconn_* function
+ * This function is then running in the thread context
+ * of tcpip_thread and has exclusive access to lwIP core code.
+ *
+ * @param apimsg a struct containing the function to call and its parameters
+ * @return ERR_OK if the function was called, another err_t if not
+ */
+err_t
+tcpip_apimsg(struct api_msg *apimsg)
+{
+  struct tcpip_msg msg;
+#ifdef LWIP_DEBUG
+  /* catch functions that don't set err */
+  apimsg->msg.err = ERR_VAL;
+#endif
+  
+  if (sys_mbox_valid(&mbox)) {
+    msg.type = TCPIP_MSG_API;
+    msg.msg.apimsg = apimsg;
+    sys_mbox_post(&mbox, &msg);
+    sys_arch_sem_wait(&apimsg->msg.conn->op_completed, 0);
+    return apimsg->msg.err;
+  }
+  return ERR_VAL;
+}
+
+#endif /* LWIP_NETCONN */
+
+#if LWIP_NETIF_API
+#if !LWIP_TCPIP_CORE_LOCKING
+/**
+ * Much like tcpip_apimsg, but calls the lower part of a netifapi_*
+ * function.
+ *
+ * @param netifapimsg a struct containing the function to call and its parameters
+ * @return error code given back by the function that was called
+ */
+err_t
+tcpip_netifapi(struct netifapi_msg* netifapimsg)
+{
+  struct tcpip_msg msg;
+  
+  if (sys_mbox_valid(&mbox)) {
+    err_t err = sys_sem_new(&netifapimsg->msg.sem, 0);
+    if (err != ERR_OK) {
+      netifapimsg->msg.err = err;
+      return err;
+    }
+    
+    msg.type = TCPIP_MSG_NETIFAPI;
+    msg.msg.netifapimsg = netifapimsg;
+    sys_mbox_post(&mbox, &msg);
+    sys_sem_wait(&netifapimsg->msg.sem);
+    sys_sem_free(&netifapimsg->msg.sem);
+    return netifapimsg->msg.err;
+  }
+  return ERR_VAL;
+}
+#else /* !LWIP_TCPIP_CORE_LOCKING */
+/**
+ * Call the lower part of a netifapi_* function
+ * This function has exclusive access to lwIP core code by locking it
+ * before the function is called.
+ *
+ * @param netifapimsg a struct containing the function to call and its parameters
+ * @return ERR_OK (only for compatibility fo tcpip_netifapi())
+ */
+err_t
+tcpip_netifapi_lock(struct netifapi_msg* netifapimsg)
+{
+  LOCK_TCPIP_CORE();  
+  netifapimsg->function(&(netifapimsg->msg));
+  UNLOCK_TCPIP_CORE();
+  return netifapimsg->msg.err;
+}
+#endif /* !LWIP_TCPIP_CORE_LOCKING */
+#endif /* LWIP_NETIF_API */
+
+/**
+ * Allocate a structure for a static callback message and initialize it.
+ * This is intended to be used to send "static" messages from interrupt context.
+ *
+ * @param function the function to call
+ * @param ctx parameter passed to function
+ * @return a struct pointer to pass to tcpip_trycallback().
+ */
+struct tcpip_callback_msg* tcpip_callbackmsg_new(tcpip_callback_fn function, void *ctx)
+{
+  struct tcpip_msg *msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_API);
+  if (msg == NULL) {
+    return NULL;
+  }
+  msg->type = TCPIP_MSG_CALLBACK_STATIC;
+  msg->msg.cb.function = function;
+  msg->msg.cb.ctx = ctx;
+  return (struct tcpip_callback_msg*)msg;
+}
+
+/**
+ * Free a callback message allocated by tcpip_callbackmsg_new().
+ *
+ * @param msg the message to free
+ */
+void tcpip_callbackmsg_delete(struct tcpip_callback_msg* msg)
+{
+  memp_free(MEMP_TCPIP_MSG_API, msg);
+}
+
+/**
+ * Try to post a callback-message to the tcpip_thread mbox
+ * This is intended to be used to send "static" messages from interrupt context.
+ *
+ * @param msg pointer to the message to post
+ * @return sys_mbox_trypost() return code
+ */
+err_t
+tcpip_trycallback(struct tcpip_callback_msg* msg)
+{
+  if (!sys_mbox_valid(&mbox)) {
+    return ERR_VAL;
+  }
+  return sys_mbox_trypost(&mbox, msg);
+}
+
+/**
+ * Initialize this module:
+ * - initialize all sub modules
+ * - start the tcpip_thread
+ *
+ * @param initfunc a function to call when tcpip_thread is running and finished initializing
+ * @param arg argument to pass to initfunc
+ */
+void
+tcpip_init(tcpip_init_done_fn initfunc, void *arg)
+{
+  lwip_init();
+
+  tcpip_init_done = initfunc;
+  tcpip_init_done_arg = arg;
+  if(sys_mbox_new(&mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
+    LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
+  }
+#if LWIP_TCPIP_CORE_LOCKING
+  if(sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
+    LWIP_ASSERT("failed to create lock_tcpip_core", 0);
+  }
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+
+  sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
+}
+
+/**
+ * Simple callback function used with tcpip_callback to free a pbuf
+ * (pbuf_free has a wrong signature for tcpip_callback)
+ *
+ * @param p The pbuf (chain) to be dereferenced.
+ */
+static void
+pbuf_free_int(void *p)
+{
+  struct pbuf *q = (struct pbuf *)p;
+  pbuf_free(q);
+}
+
+/**
+ * A simple wrapper function that allows you to free a pbuf from interrupt context.
+ *
+ * @param p The pbuf (chain) to be dereferenced.
+ * @return ERR_OK if callback could be enqueued, an err_t if not
+ */
+err_t
+pbuf_free_callback(struct pbuf *p)
+{
+  return tcpip_callback_with_block(pbuf_free_int, p, 0);
+}
+
+/**
+ * A simple wrapper function that allows you to free heap memory from
+ * interrupt context.
+ *
+ * @param m the heap memory to free
+ * @return ERR_OK if callback could be enqueued, an err_t if not
+ */
+err_t
+mem_free_callback(void *m)
+{
+  return tcpip_callback_with_block(mem_free, m, 0);
+}
+
+#endif /* !NO_SYS */
diff --git a/external/badvpn_dns/lwip/src/core/def.c b/external/badvpn_dns/lwip/src/core/def.c
new file mode 100644
index 0000000..352b552
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/def.c
@@ -0,0 +1,108 @@
+/**
+ * @file
+ * Common functions used throughout the stack.
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt
+ *
+ */
+
+#include "lwip/opt.h"
+#include "lwip/def.h"
+
+/**
+ * These are reference implementations of the byte swapping functions.
+ * Again with the aim of being simple, correct and fully portable.
+ * Byte swapping is the second thing you would want to optimize. You will
+ * need to port it to your architecture and in your cc.h:
+ * 
+ * #define LWIP_PLATFORM_BYTESWAP 1
+ * #define LWIP_PLATFORM_HTONS(x) <your_htons>
+ * #define LWIP_PLATFORM_HTONL(x) <your_htonl>
+ *
+ * Note ntohs() and ntohl() are merely references to the htonx counterparts.
+ */
+
+#if (LWIP_PLATFORM_BYTESWAP == 0) && (BYTE_ORDER == LITTLE_ENDIAN)
+
+/**
+ * Convert an u16_t from host- to network byte order.
+ *
+ * @param n u16_t in host byte order
+ * @return n in network byte order
+ */
+u16_t
+lwip_htons(u16_t n)
+{
+  return ((n & 0xff) << 8) | ((n & 0xff00) >> 8);
+}
+
+/**
+ * Convert an u16_t from network- to host byte order.
+ *
+ * @param n u16_t in network byte order
+ * @return n in host byte order
+ */
+u16_t
+lwip_ntohs(u16_t n)
+{
+  return lwip_htons(n);
+}
+
+/**
+ * Convert an u32_t from host- to network byte order.
+ *
+ * @param n u32_t in host byte order
+ * @return n in network byte order
+ */
+u32_t
+lwip_htonl(u32_t n)
+{
+  return ((n & 0xff) << 24) |
+    ((n & 0xff00) << 8) |
+    ((n & 0xff0000UL) >> 8) |
+    ((n & 0xff000000UL) >> 24);
+}
+
+/**
+ * Convert an u32_t from network- to host byte order.
+ *
+ * @param n u32_t in network byte order
+ * @return n in host byte order
+ */
+u32_t
+lwip_ntohl(u32_t n)
+{
+  return lwip_htonl(n);
+}
+
+#endif /* (LWIP_PLATFORM_BYTESWAP == 0) && (BYTE_ORDER == LITTLE_ENDIAN) */
diff --git a/external/badvpn_dns/lwip/src/core/dhcp.c b/external/badvpn_dns/lwip/src/core/dhcp.c
new file mode 100644
index 0000000..21fd784
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/dhcp.c
@@ -0,0 +1,1771 @@
+/**
+ * @file
+ * Dynamic Host Configuration Protocol client
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2004 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+ * Copyright (c) 2001-2004 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is a contribution to the lwIP TCP/IP stack.
+ * The Swedish Institute of Computer Science and Adam Dunkels
+ * are specifically granted permission to redistribute this
+ * source code.
+ *
+ * Author: Leon Woestenberg <leon.woestenberg@xxxxxxx>
+ *
+ * This is a DHCP client for the lwIP TCP/IP stack. It aims to conform
+ * with RFC 2131 and RFC 2132.
+ *
+ * TODO:
+ * - Support for interfaces other than Ethernet (SLIP, PPP, ...)
+ *
+ * Please coordinate changes and requests with Leon Woestenberg
+ * <leon.woestenberg@xxxxxxx>
+ *
+ * Integration with your code:
+ *
+ * In lwip/dhcp.h
+ * #define DHCP_COARSE_TIMER_SECS (recommended 60 which is a minute)
+ * #define DHCP_FINE_TIMER_MSECS (recommended 500 which equals TCP coarse timer)
+ *
+ * Then have your application call dhcp_coarse_tmr() and
+ * dhcp_fine_tmr() on the defined intervals.
+ *
+ * dhcp_start(struct netif *netif);
+ * starts a DHCP client instance which configures the interface by
+ * obtaining an IP address lease and maintaining it.
+ *
+ * Use dhcp_release(netif) to end the lease and use dhcp_stop(netif)
+ * to remove the DHCP client.
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_DHCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/stats.h"
+#include "lwip/mem.h"
+#include "lwip/udp.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+#include "lwip/def.h"
+#include "lwip/dhcp.h"
+#include "lwip/autoip.h"
+#include "lwip/dns.h"
+#include "netif/etharp.h"
+
+#include <string.h>
+
+/** DHCP_CREATE_RAND_XID: if this is set to 1, the xid is created using
+ * LWIP_RAND() (this overrides DHCP_GLOBAL_XID)
+ */
+#ifndef DHCP_CREATE_RAND_XID
+#define DHCP_CREATE_RAND_XID 1
+#endif
+
+/** Default for DHCP_GLOBAL_XID is 0xABCD0000
+ * This can be changed by defining DHCP_GLOBAL_XID and DHCP_GLOBAL_XID_HEADER, e.g.
+ *  #define DHCP_GLOBAL_XID_HEADER "stdlib.h"
+ *  #define DHCP_GLOBAL_XID rand()
+ */
+#ifdef DHCP_GLOBAL_XID_HEADER
+#include DHCP_GLOBAL_XID_HEADER /* include optional starting XID generation prototypes */
+#endif
+
+/** DHCP_OPTION_MAX_MSG_SIZE is set to the MTU
+ * MTU is checked to be big enough in dhcp_start */
+#define DHCP_MAX_MSG_LEN(netif)        (netif->mtu)
+#define DHCP_MAX_MSG_LEN_MIN_REQUIRED  576
+/** Minimum length for reply before packet is parsed */
+#define DHCP_MIN_REPLY_LEN             44
+
+#define REBOOT_TRIES 2
+
+/** Option handling: options are parsed in dhcp_parse_reply
+ * and saved in an array where other functions can load them from.
+ * This might be moved into the struct dhcp (not necessarily since
+ * lwIP is single-threaded and the array is only used while in recv
+ * callback). */
+#define DHCP_OPTION_IDX_OVERLOAD    0
+#define DHCP_OPTION_IDX_MSG_TYPE    1
+#define DHCP_OPTION_IDX_SERVER_ID   2
+#define DHCP_OPTION_IDX_LEASE_TIME  3
+#define DHCP_OPTION_IDX_T1          4
+#define DHCP_OPTION_IDX_T2          5
+#define DHCP_OPTION_IDX_SUBNET_MASK 6
+#define DHCP_OPTION_IDX_ROUTER      7
+#define DHCP_OPTION_IDX_DNS_SERVER  8
+#define DHCP_OPTION_IDX_MAX         (DHCP_OPTION_IDX_DNS_SERVER + DNS_MAX_SERVERS)
+
+/** Holds the decoded option values, only valid while in dhcp_recv.
+    @todo: move this into struct dhcp? */
+u32_t dhcp_rx_options_val[DHCP_OPTION_IDX_MAX];
+/** Holds a flag which option was received and is contained in dhcp_rx_options_val,
+    only valid while in dhcp_recv.
+    @todo: move this into struct dhcp? */
+u8_t  dhcp_rx_options_given[DHCP_OPTION_IDX_MAX];
+
+#ifdef DHCP_GLOBAL_XID
+static u32_t xid;
+static u8_t xid_initialised;
+#endif /* DHCP_GLOBAL_XID */
+
+#define dhcp_option_given(dhcp, idx)          (dhcp_rx_options_given[idx] != 0)
+#define dhcp_got_option(dhcp, idx)            (dhcp_rx_options_given[idx] = 1)
+#define dhcp_clear_option(dhcp, idx)          (dhcp_rx_options_given[idx] = 0)
+#define dhcp_clear_all_options(dhcp)          (memset(dhcp_rx_options_given, 0, sizeof(dhcp_rx_options_given)))
+#define dhcp_get_option_value(dhcp, idx)      (dhcp_rx_options_val[idx])
+#define dhcp_set_option_value(dhcp, idx, val) (dhcp_rx_options_val[idx] = (val))
+
+
+/* DHCP client state machine functions */
+static err_t dhcp_discover(struct netif *netif);
+static err_t dhcp_select(struct netif *netif);
+static void dhcp_bind(struct netif *netif);
+#if DHCP_DOES_ARP_CHECK
+static err_t dhcp_decline(struct netif *netif);
+#endif /* DHCP_DOES_ARP_CHECK */
+static err_t dhcp_rebind(struct netif *netif);
+static err_t dhcp_reboot(struct netif *netif);
+static void dhcp_set_state(struct dhcp *dhcp, u8_t new_state);
+
+/* receive, unfold, parse and free incoming messages */
+static void dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port);
+
+/* set the DHCP timers */
+static void dhcp_timeout(struct netif *netif);
+static void dhcp_t1_timeout(struct netif *netif);
+static void dhcp_t2_timeout(struct netif *netif);
+
+/* build outgoing messages */
+/* create a DHCP message, fill in common headers */
+static err_t dhcp_create_msg(struct netif *netif, struct dhcp *dhcp, u8_t message_type);
+/* free a DHCP request */
+static void dhcp_delete_msg(struct dhcp *dhcp);
+/* add a DHCP option (type, then length in bytes) */
+static void dhcp_option(struct dhcp *dhcp, u8_t option_type, u8_t option_len);
+/* add option values */
+static void dhcp_option_byte(struct dhcp *dhcp, u8_t value);
+static void dhcp_option_short(struct dhcp *dhcp, u16_t value);
+static void dhcp_option_long(struct dhcp *dhcp, u32_t value);
+#if LWIP_NETIF_HOSTNAME
+static void dhcp_option_hostname(struct dhcp *dhcp, struct netif *netif);
+#endif /* LWIP_NETIF_HOSTNAME */
+/* always add the DHCP options trailer to end and pad */
+static void dhcp_option_trailer(struct dhcp *dhcp);
+
+/**
+ * Back-off the DHCP client (because of a received NAK response).
+ *
+ * Back-off the DHCP client because of a received NAK. Receiving a
+ * NAK means the client asked for something non-sensible, for
+ * example when it tries to renew a lease obtained on another network.
+ *
+ * We clear any existing set IP address and restart DHCP negotiation
+ * afresh (as per RFC2131 3.2.3).
+ *
+ * @param netif the netif under DHCP control
+ */
+static void
+dhcp_handle_nak(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_handle_nak(netif=%p) %c%c%"U16_F"\n", 
+    (void*)netif, netif->name[0], netif->name[1], (u16_t)netif->num));
+  /* Set the interface down since the address must no longer be used, as per RFC2131 */
+  netif_set_down(netif);
+  /* remove IP address from interface */
+  netif_set_ipaddr(netif, IP_ADDR_ANY);
+  netif_set_gw(netif, IP_ADDR_ANY);
+  netif_set_netmask(netif, IP_ADDR_ANY); 
+  /* Change to a defined state */
+  dhcp_set_state(dhcp, DHCP_BACKING_OFF);
+  /* We can immediately restart discovery */
+  dhcp_discover(netif);
+}
+
+#if DHCP_DOES_ARP_CHECK
+/**
+ * Checks if the offered IP address is already in use.
+ *
+ * It does so by sending an ARP request for the offered address and
+ * entering CHECKING state. If no ARP reply is received within a small
+ * interval, the address is assumed to be free for use by us.
+ *
+ * @param netif the netif under DHCP control
+ */
+static void
+dhcp_check(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  err_t result;
+  u16_t msecs;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_check(netif=%p) %c%c\n", (void *)netif, (s16_t)netif->name[0],
+    (s16_t)netif->name[1]));
+  dhcp_set_state(dhcp, DHCP_CHECKING);
+  /* create an ARP query for the offered IP address, expecting that no host
+     responds, as the IP address should not be in use. */
+  result = etharp_query(netif, &dhcp->offered_ip_addr, NULL);
+  if (result != ERR_OK) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("dhcp_check: could not perform ARP query\n"));
+  }
+  dhcp->tries++;
+  msecs = 500;
+  dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_check(): set request timeout %"U16_F" msecs\n", msecs));
+}
+#endif /* DHCP_DOES_ARP_CHECK */
+
+/**
+ * Remember the configuration offered by a DHCP server.
+ *
+ * @param netif the netif under DHCP control
+ */
+static void
+dhcp_handle_offer(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_handle_offer(netif=%p) %c%c%"U16_F"\n",
+    (void*)netif, netif->name[0], netif->name[1], (u16_t)netif->num));
+  /* obtain the server address */
+  if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_SERVER_ID)) {
+    ip4_addr_set_u32(&dhcp->server_ip_addr, htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_SERVER_ID)));
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_STATE, ("dhcp_handle_offer(): server 0x%08"X32_F"\n",
+      ip4_addr_get_u32(&dhcp->server_ip_addr)));
+    /* remember offered address */
+    ip_addr_copy(dhcp->offered_ip_addr, dhcp->msg_in->yiaddr);
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_STATE, ("dhcp_handle_offer(): offer for 0x%08"X32_F"\n",
+      ip4_addr_get_u32(&dhcp->offered_ip_addr)));
+
+    dhcp_select(netif);
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+      ("dhcp_handle_offer(netif=%p) did not get server ID!\n", (void*)netif));
+  }
+}
+
+/**
+ * Select a DHCP server offer out of all offers.
+ *
+ * Simply select the first offer received.
+ *
+ * @param netif the netif under DHCP control
+ * @return lwIP specific error (see error.h)
+ */
+static err_t
+dhcp_select(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  err_t result;
+  u16_t msecs;
+
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_select(netif=%p) %c%c%"U16_F"\n", (void*)netif, netif->name[0], netif->name[1], (u16_t)netif->num));
+  dhcp_set_state(dhcp, DHCP_REQUESTING);
+
+  /* create and initialize the DHCP message header */
+  result = dhcp_create_msg(netif, dhcp, DHCP_REQUEST);
+  if (result == ERR_OK) {
+    dhcp_option(dhcp, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN);
+    dhcp_option_short(dhcp, DHCP_MAX_MSG_LEN(netif));
+
+    /* MUST request the offered IP address */
+    dhcp_option(dhcp, DHCP_OPTION_REQUESTED_IP, 4);
+    dhcp_option_long(dhcp, ntohl(ip4_addr_get_u32(&dhcp->offered_ip_addr)));
+
+    dhcp_option(dhcp, DHCP_OPTION_SERVER_ID, 4);
+    dhcp_option_long(dhcp, ntohl(ip4_addr_get_u32(&dhcp->server_ip_addr)));
+
+    dhcp_option(dhcp, DHCP_OPTION_PARAMETER_REQUEST_LIST, 4/*num options*/);
+    dhcp_option_byte(dhcp, DHCP_OPTION_SUBNET_MASK);
+    dhcp_option_byte(dhcp, DHCP_OPTION_ROUTER);
+    dhcp_option_byte(dhcp, DHCP_OPTION_BROADCAST);
+    dhcp_option_byte(dhcp, DHCP_OPTION_DNS_SERVER);
+
+#if LWIP_NETIF_HOSTNAME
+    dhcp_option_hostname(dhcp, netif);
+#endif /* LWIP_NETIF_HOSTNAME */
+
+    dhcp_option_trailer(dhcp);
+    /* shrink the pbuf to the actual content length */
+    pbuf_realloc(dhcp->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp->options_out_len);
+
+    /* send broadcast to any DHCP server */
+    udp_sendto_if(dhcp->pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif);
+    dhcp_delete_msg(dhcp);
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_select: REQUESTING\n"));
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("dhcp_select: could not allocate DHCP request\n"));
+  }
+  dhcp->tries++;
+  msecs = (dhcp->tries < 6 ? 1 << dhcp->tries : 60) * 1000;
+  dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_STATE, ("dhcp_select(): set request timeout %"U16_F" msecs\n", msecs));
+  return result;
+}
+
+/**
+ * The DHCP timer that checks for lease renewal/rebind timeouts.
+ */
+void
+dhcp_coarse_tmr()
+{
+  struct netif *netif = netif_list;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_coarse_tmr()\n"));
+  /* iterate through all network interfaces */
+  while (netif != NULL) {
+    /* only act on DHCP configured interfaces */
+    if (netif->dhcp != NULL) {
+      /* timer is active (non zero), and triggers (zeroes) now? */
+      if (netif->dhcp->t2_timeout-- == 1) {
+        LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_coarse_tmr(): t2 timeout\n"));
+        /* this clients' rebind timeout triggered */
+        dhcp_t2_timeout(netif);
+      /* timer is active (non zero), and triggers (zeroes) now */
+      } else if (netif->dhcp->t1_timeout-- == 1) {
+        LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_coarse_tmr(): t1 timeout\n"));
+        /* this clients' renewal timeout triggered */
+        dhcp_t1_timeout(netif);
+      }
+    }
+    /* proceed to next netif */
+    netif = netif->next;
+  }
+}
+
+/**
+ * DHCP transaction timeout handling
+ *
+ * A DHCP server is expected to respond within a short period of time.
+ * This timer checks whether an outstanding DHCP request is timed out.
+ */
+void
+dhcp_fine_tmr()
+{
+  struct netif *netif = netif_list;
+  /* loop through netif's */
+  while (netif != NULL) {
+    /* only act on DHCP configured interfaces */
+    if (netif->dhcp != NULL) {
+      /* timer is active (non zero), and is about to trigger now */      
+      if (netif->dhcp->request_timeout > 1) {
+        netif->dhcp->request_timeout--;
+      }
+      else if (netif->dhcp->request_timeout == 1) {
+        netif->dhcp->request_timeout--;
+        /* { netif->dhcp->request_timeout == 0 } */
+        LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_fine_tmr(): request timeout\n"));
+        /* this client's request timeout triggered */
+        dhcp_timeout(netif);
+      }
+    }
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+}
+
+/**
+ * A DHCP negotiation transaction, or ARP request, has timed out.
+ *
+ * The timer that was started with the DHCP or ARP request has
+ * timed out, indicating no response was received in time.
+ *
+ * @param netif the netif under DHCP control
+ */
+static void
+dhcp_timeout(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_timeout()\n"));
+  /* back-off period has passed, or server selection timed out */
+  if ((dhcp->state == DHCP_BACKING_OFF) || (dhcp->state == DHCP_SELECTING)) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_timeout(): restarting discovery\n"));
+    dhcp_discover(netif);
+  /* receiving the requested lease timed out */
+  } else if (dhcp->state == DHCP_REQUESTING) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_timeout(): REQUESTING, DHCP request timed out\n"));
+    if (dhcp->tries <= 5) {
+      dhcp_select(netif);
+    } else {
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_timeout(): REQUESTING, releasing, restarting\n"));
+      dhcp_release(netif);
+      dhcp_discover(netif);
+    }
+#if DHCP_DOES_ARP_CHECK
+  /* received no ARP reply for the offered address (which is good) */
+  } else if (dhcp->state == DHCP_CHECKING) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_timeout(): CHECKING, ARP request timed out\n"));
+    if (dhcp->tries <= 1) {
+      dhcp_check(netif);
+    /* no ARP replies on the offered address,
+       looks like the IP address is indeed free */
+    } else {
+      /* bind the interface to the offered address */
+      dhcp_bind(netif);
+    }
+#endif /* DHCP_DOES_ARP_CHECK */
+  }
+  /* did not get response to renew request? */
+  else if (dhcp->state == DHCP_RENEWING) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_timeout(): RENEWING, DHCP request timed out\n"));
+    /* just retry renewal */
+    /* note that the rebind timer will eventually time-out if renew does not work */
+    dhcp_renew(netif);
+  /* did not get response to rebind request? */
+  } else if (dhcp->state == DHCP_REBINDING) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_timeout(): REBINDING, DHCP request timed out\n"));
+    if (dhcp->tries <= 8) {
+      dhcp_rebind(netif);
+    } else {
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_timeout(): RELEASING, DISCOVERING\n"));
+      dhcp_release(netif);
+      dhcp_discover(netif);
+    }
+  } else if (dhcp->state == DHCP_REBOOTING) {
+    if (dhcp->tries < REBOOT_TRIES) {
+      dhcp_reboot(netif);
+    } else {
+      dhcp_discover(netif);
+    }
+  }
+}
+
+/**
+ * The renewal period has timed out.
+ *
+ * @param netif the netif under DHCP control
+ */
+static void
+dhcp_t1_timeout(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_STATE, ("dhcp_t1_timeout()\n"));
+  if ((dhcp->state == DHCP_REQUESTING) || (dhcp->state == DHCP_BOUND) ||
+      (dhcp->state == DHCP_RENEWING)) {
+    /* just retry to renew - note that the rebind timer (t2) will
+     * eventually time-out if renew tries fail. */
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+                ("dhcp_t1_timeout(): must renew\n"));
+    /* This slightly different to RFC2131: DHCPREQUEST will be sent from state
+       DHCP_RENEWING, not DHCP_BOUND */
+    dhcp_renew(netif);
+  }
+}
+
+/**
+ * The rebind period has timed out.
+ *
+ * @param netif the netif under DHCP control
+ */
+static void
+dhcp_t2_timeout(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_t2_timeout()\n"));
+  if ((dhcp->state == DHCP_REQUESTING) || (dhcp->state == DHCP_BOUND) ||
+      (dhcp->state == DHCP_RENEWING)) {
+    /* just retry to rebind */
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+                ("dhcp_t2_timeout(): must rebind\n"));
+    /* This slightly different to RFC2131: DHCPREQUEST will be sent from state
+       DHCP_REBINDING, not DHCP_BOUND */
+    dhcp_rebind(netif);
+  }
+}
+
+/**
+ * Handle a DHCP ACK packet
+ *
+ * @param netif the netif under DHCP control
+ */
+static void
+dhcp_handle_ack(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+#if LWIP_DNS
+  u8_t n;
+#endif /* LWIP_DNS */
+
+  /* clear options we might not get from the ACK */
+  ip_addr_set_zero(&dhcp->offered_sn_mask);
+  ip_addr_set_zero(&dhcp->offered_gw_addr);
+#if LWIP_DHCP_BOOTP_FILE
+  ip_addr_set_zero(&dhcp->offered_si_addr);
+#endif /* LWIP_DHCP_BOOTP_FILE */
+
+  /* lease time given? */
+  if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_LEASE_TIME)) {
+    /* remember offered lease time */
+    dhcp->offered_t0_lease = dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_LEASE_TIME);
+  }
+  /* renewal period given? */
+  if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_T1)) {
+    /* remember given renewal period */
+    dhcp->offered_t1_renew = dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_T1);
+  } else {
+    /* calculate safe periods for renewal */
+    dhcp->offered_t1_renew = dhcp->offered_t0_lease / 2;
+  }
+
+  /* renewal period given? */
+  if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_T2)) {
+    /* remember given rebind period */
+    dhcp->offered_t2_rebind = dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_T2);
+  } else {
+    /* calculate safe periods for rebinding */
+    dhcp->offered_t2_rebind = dhcp->offered_t0_lease;
+  }
+
+  /* (y)our internet address */
+  ip_addr_copy(dhcp->offered_ip_addr, dhcp->msg_in->yiaddr);
+
+#if LWIP_DHCP_BOOTP_FILE
+  /* copy boot server address,
+     boot file name copied in dhcp_parse_reply if not overloaded */
+  ip_addr_copy(dhcp->offered_si_addr, dhcp->msg_in->siaddr);
+#endif /* LWIP_DHCP_BOOTP_FILE */
+
+  /* subnet mask given? */
+  if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_SUBNET_MASK)) {
+    /* remember given subnet mask */
+    ip4_addr_set_u32(&dhcp->offered_sn_mask, htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_SUBNET_MASK)));
+    dhcp->subnet_mask_given = 1;
+  } else {
+    dhcp->subnet_mask_given = 0;
+  }
+
+  /* gateway router */
+  if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_ROUTER)) {
+    ip4_addr_set_u32(&dhcp->offered_gw_addr, htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_ROUTER)));
+  }
+  
+#if LWIP_DNS
+  /* DNS servers */
+  for(n = 0; (n < DNS_MAX_SERVERS) && dhcp_option_given(dhcp, DHCP_OPTION_IDX_DNS_SERVER + n); n++) {
+    ip_addr_t dns_addr;
+    ip4_addr_set_u32(&dns_addr, htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_DNS_SERVER + n)));
+    dns_setserver(n, &dns_addr);
+  }
+#endif /* LWIP_DNS */
+}
+
+/** Set a statically allocated struct dhcp to work with.
+ * Using this prevents dhcp_start to allocate it using mem_malloc.
+ *
+ * @param netif the netif for which to set the struct dhcp
+ * @param dhcp (uninitialised) dhcp struct allocated by the application
+ */
+void
+dhcp_set_struct(struct netif *netif, struct dhcp *dhcp)
+{
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+  LWIP_ASSERT("dhcp != NULL", dhcp != NULL);
+  LWIP_ASSERT("netif already has a struct dhcp set", netif->dhcp == NULL);
+
+  /* clear data structure */
+  memset(dhcp, 0, sizeof(struct dhcp));
+  /* dhcp_set_state(&dhcp, DHCP_OFF); */
+  netif->dhcp = dhcp;
+}
+
+/** Removes a struct dhcp from a netif.
+ *
+ * ATTENTION: Only use this when not using dhcp_set_struct() to allocate the
+ *            struct dhcp since the memory is passed back to the heap.
+ *
+ * @param netif the netif from which to remove the struct dhcp
+ */
+void dhcp_cleanup(struct netif *netif)
+{
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+
+  if (netif->dhcp != NULL) {
+    mem_free(netif->dhcp);
+    netif->dhcp = NULL;
+  }
+}
+
+/**
+ * Start DHCP negotiation for a network interface.
+ *
+ * If no DHCP client instance was attached to this interface,
+ * a new client is created first. If a DHCP client instance
+ * was already present, it restarts negotiation.
+ *
+ * @param netif The lwIP network interface
+ * @return lwIP error code
+ * - ERR_OK - No error
+ * - ERR_MEM - Out of memory
+ */
+err_t
+dhcp_start(struct netif *netif)
+{
+  struct dhcp *dhcp;
+  err_t result = ERR_OK;
+
+  LWIP_ERROR("netif != NULL", (netif != NULL), return ERR_ARG;);
+  dhcp = netif->dhcp;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_start(netif=%p) %c%c%"U16_F"\n", (void*)netif, netif->name[0], netif->name[1], (u16_t)netif->num));
+  /* Remove the flag that says this netif is handled by DHCP,
+     it is set when we succeeded starting. */
+  netif->flags &= ~NETIF_FLAG_DHCP;
+
+  /* check hwtype of the netif */
+  if ((netif->flags & NETIF_FLAG_ETHARP) == 0) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_start(): No ETHARP netif\n"));
+    return ERR_ARG;
+  }
+
+  /* check MTU of the netif */
+  if (netif->mtu < DHCP_MAX_MSG_LEN_MIN_REQUIRED) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_start(): Cannot use this netif with DHCP: MTU is too small\n"));
+    return ERR_MEM;
+  }
+
+  /* no DHCP client attached yet? */
+  if (dhcp == NULL) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_start(): starting new DHCP client\n"));
+    dhcp = (struct dhcp *)mem_malloc(sizeof(struct dhcp));
+    if (dhcp == NULL) {
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_start(): could not allocate dhcp\n"));
+      return ERR_MEM;
+    }
+    /* store this dhcp client in the netif */
+    netif->dhcp = dhcp;
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_start(): allocated dhcp"));
+  /* already has DHCP client attached */
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_start(): restarting DHCP configuration\n"));
+    if (dhcp->pcb != NULL) {
+      udp_remove(dhcp->pcb);
+    }
+    LWIP_ASSERT("pbuf p_out wasn't freed", dhcp->p_out == NULL);
+    LWIP_ASSERT("reply wasn't freed", dhcp->msg_in == NULL );
+  }
+    
+  /* clear data structure */
+  memset(dhcp, 0, sizeof(struct dhcp));
+  /* dhcp_set_state(&dhcp, DHCP_OFF); */
+  /* allocate UDP PCB */
+  dhcp->pcb = udp_new();
+  if (dhcp->pcb == NULL) {
+    LWIP_DEBUGF(DHCP_DEBUG  | LWIP_DBG_TRACE, ("dhcp_start(): could not obtain pcb\n"));
+    return ERR_MEM;
+  }
+  ip_set_option(dhcp->pcb, SOF_BROADCAST);
+  /* set up local and remote port for the pcb */
+  udp_bind(dhcp->pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
+  udp_connect(dhcp->pcb, IP_ADDR_ANY, DHCP_SERVER_PORT);
+  /* set up the recv callback and argument */
+  udp_recv(dhcp->pcb, dhcp_recv, netif);
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_start(): starting DHCP configuration\n"));
+  /* (re)start the DHCP negotiation */
+  result = dhcp_discover(netif);
+  if (result != ERR_OK) {
+    /* free resources allocated above */
+    dhcp_stop(netif);
+    return ERR_MEM;
+  }
+  /* Set the flag that says this netif is handled by DHCP. */
+  netif->flags |= NETIF_FLAG_DHCP;
+  return result;
+}
+
+/**
+ * Inform a DHCP server of our manual configuration.
+ *
+ * This informs DHCP servers of our fixed IP address configuration
+ * by sending an INFORM message. It does not involve DHCP address
+ * configuration, it is just here to be nice to the network.
+ *
+ * @param netif The lwIP network interface
+ */
+void
+dhcp_inform(struct netif *netif)
+{
+  struct dhcp dhcp;
+  err_t result = ERR_OK;
+  struct udp_pcb *pcb;
+
+  LWIP_ERROR("netif != NULL", (netif != NULL), return;);
+
+  memset(&dhcp, 0, sizeof(struct dhcp));
+  dhcp_set_state(&dhcp, DHCP_INFORM);
+
+  if ((netif->dhcp != NULL) && (netif->dhcp->pcb != NULL)) {
+    /* re-use existing pcb */
+    pcb = netif->dhcp->pcb;
+  } else {
+    pcb = udp_new();
+    if (pcb == NULL) {
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp_inform(): could not obtain pcb"));
+      return;
+    }
+    dhcp.pcb = pcb;
+    ip_set_option(dhcp.pcb, SOF_BROADCAST);
+    udp_bind(dhcp.pcb, IP_ADDR_ANY, DHCP_CLIENT_PORT);
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_inform(): created new udp pcb\n"));
+  }
+  /* create and initialize the DHCP message header */
+  result = dhcp_create_msg(netif, &dhcp, DHCP_INFORM);
+  if (result == ERR_OK) {
+    dhcp_option(&dhcp, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN);
+    dhcp_option_short(&dhcp, DHCP_MAX_MSG_LEN(netif));
+
+    dhcp_option_trailer(&dhcp);
+
+    pbuf_realloc(dhcp.p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp.options_out_len);
+
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_inform: INFORMING\n"));
+    udp_sendto_if(pcb, dhcp.p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif);
+    dhcp_delete_msg(&dhcp);
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp_inform: could not allocate DHCP request\n"));
+  }
+
+  if (dhcp.pcb != NULL) {
+    /* otherwise, the existing pcb was used */
+    udp_remove(dhcp.pcb);
+  }
+}
+
+/** Handle a possible change in the network configuration.
+ *
+ * This enters the REBOOTING state to verify that the currently bound
+ * address is still valid.
+ */
+void
+dhcp_network_changed(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  if (!dhcp)
+    return;
+  switch (dhcp->state) {
+  case DHCP_REBINDING:
+  case DHCP_RENEWING:
+  case DHCP_BOUND:
+  case DHCP_REBOOTING:
+    netif_set_down(netif);
+    dhcp->tries = 0;
+    dhcp_reboot(netif);
+    break;
+  case DHCP_OFF:
+    /* stay off */
+    break;
+  default:
+    dhcp->tries = 0;
+#if LWIP_DHCP_AUTOIP_COOP
+    if(dhcp->autoip_coop_state == DHCP_AUTOIP_COOP_STATE_ON) {
+      autoip_stop(netif);
+      dhcp->autoip_coop_state = DHCP_AUTOIP_COOP_STATE_OFF;
+    }
+#endif /* LWIP_DHCP_AUTOIP_COOP */
+    dhcp_discover(netif);
+    break;
+  }
+}
+
+#if DHCP_DOES_ARP_CHECK
+/**
+ * Match an ARP reply with the offered IP address.
+ *
+ * @param netif the network interface on which the reply was received
+ * @param addr The IP address we received a reply from
+ */
+void dhcp_arp_reply(struct netif *netif, ip_addr_t *addr)
+{
+  LWIP_ERROR("netif != NULL", (netif != NULL), return;);
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_arp_reply()\n"));
+  /* is a DHCP client doing an ARP check? */
+  if ((netif->dhcp != NULL) && (netif->dhcp->state == DHCP_CHECKING)) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_arp_reply(): CHECKING, arp reply for 0x%08"X32_F"\n",
+      ip4_addr_get_u32(addr)));
+    /* did a host respond with the address we
+       were offered by the DHCP server? */
+    if (ip_addr_cmp(addr, &netif->dhcp->offered_ip_addr)) {
+      /* we will not accept the offered address */
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE | LWIP_DBG_LEVEL_WARNING,
+        ("dhcp_arp_reply(): arp reply matched with offered address, declining\n"));
+      dhcp_decline(netif);
+    }
+  }
+}
+
+/**
+ * Decline an offered lease.
+ *
+ * Tell the DHCP server we do not accept the offered address.
+ * One reason to decline the lease is when we find out the address
+ * is already in use by another host (through ARP).
+ *
+ * @param netif the netif under DHCP control
+ */
+static err_t
+dhcp_decline(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  err_t result = ERR_OK;
+  u16_t msecs;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_decline()\n"));
+  dhcp_set_state(dhcp, DHCP_BACKING_OFF);
+  /* create and initialize the DHCP message header */
+  result = dhcp_create_msg(netif, dhcp, DHCP_DECLINE);
+  if (result == ERR_OK) {
+    dhcp_option(dhcp, DHCP_OPTION_REQUESTED_IP, 4);
+    dhcp_option_long(dhcp, ntohl(ip4_addr_get_u32(&dhcp->offered_ip_addr)));
+
+    dhcp_option_trailer(dhcp);
+    /* resize pbuf to reflect true size of options */
+    pbuf_realloc(dhcp->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp->options_out_len);
+
+    /* per section 4.4.4, broadcast DECLINE messages */
+    udp_sendto_if(dhcp->pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif);
+    dhcp_delete_msg(dhcp);
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_decline: BACKING OFF\n"));
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+      ("dhcp_decline: could not allocate DHCP request\n"));
+  }
+  dhcp->tries++;
+  msecs = 10*1000;
+  dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_decline(): set request timeout %"U16_F" msecs\n", msecs));
+  return result;
+}
+#endif /* DHCP_DOES_ARP_CHECK */
+
+
+/**
+ * Start the DHCP process, discover a DHCP server.
+ *
+ * @param netif the netif under DHCP control
+ */
+static err_t
+dhcp_discover(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  err_t result = ERR_OK;
+  u16_t msecs;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_discover()\n"));
+  ip_addr_set_any(&dhcp->offered_ip_addr);
+  dhcp_set_state(dhcp, DHCP_SELECTING);
+  /* create and initialize the DHCP message header */
+  result = dhcp_create_msg(netif, dhcp, DHCP_DISCOVER);
+  if (result == ERR_OK) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_discover: making request\n"));
+
+    dhcp_option(dhcp, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN);
+    dhcp_option_short(dhcp, DHCP_MAX_MSG_LEN(netif));
+
+    dhcp_option(dhcp, DHCP_OPTION_PARAMETER_REQUEST_LIST, 4/*num options*/);
+    dhcp_option_byte(dhcp, DHCP_OPTION_SUBNET_MASK);
+    dhcp_option_byte(dhcp, DHCP_OPTION_ROUTER);
+    dhcp_option_byte(dhcp, DHCP_OPTION_BROADCAST);
+    dhcp_option_byte(dhcp, DHCP_OPTION_DNS_SERVER);
+
+    dhcp_option_trailer(dhcp);
+
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_discover: realloc()ing\n"));
+    pbuf_realloc(dhcp->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp->options_out_len);
+
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_discover: sendto(DISCOVER, IP_ADDR_BROADCAST, DHCP_SERVER_PORT)\n"));
+    udp_sendto_if(dhcp->pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif);
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_discover: deleting()ing\n"));
+    dhcp_delete_msg(dhcp);
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_discover: SELECTING\n"));
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp_discover: could not allocate DHCP request\n"));
+  }
+  dhcp->tries++;
+#if LWIP_DHCP_AUTOIP_COOP
+  if(dhcp->tries >= LWIP_DHCP_AUTOIP_COOP_TRIES && dhcp->autoip_coop_state == DHCP_AUTOIP_COOP_STATE_OFF) {
+    dhcp->autoip_coop_state = DHCP_AUTOIP_COOP_STATE_ON;
+    autoip_start(netif);
+  }
+#endif /* LWIP_DHCP_AUTOIP_COOP */
+  msecs = (dhcp->tries < 6 ? 1 << dhcp->tries : 60) * 1000;
+  dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_discover(): set request timeout %"U16_F" msecs\n", msecs));
+  return result;
+}
+
+
+/**
+ * Bind the interface to the offered IP address.
+ *
+ * @param netif network interface to bind to the offered address
+ */
+static void
+dhcp_bind(struct netif *netif)
+{
+  u32_t timeout;
+  struct dhcp *dhcp;
+  ip_addr_t sn_mask, gw_addr;
+  LWIP_ERROR("dhcp_bind: netif != NULL", (netif != NULL), return;);
+  dhcp = netif->dhcp;
+  LWIP_ERROR("dhcp_bind: dhcp != NULL", (dhcp != NULL), return;);
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_bind(netif=%p) %c%c%"U16_F"\n", (void*)netif, netif->name[0], netif->name[1], (u16_t)netif->num));
+
+  /* temporary DHCP lease? */
+  if (dhcp->offered_t1_renew != 0xffffffffUL) {
+    /* set renewal period timer */
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_bind(): t1 renewal timer %"U32_F" secs\n", dhcp->offered_t1_renew));
+    timeout = (dhcp->offered_t1_renew + DHCP_COARSE_TIMER_SECS / 2) / DHCP_COARSE_TIMER_SECS;
+    if(timeout > 0xffff) {
+      timeout = 0xffff;
+    }
+    dhcp->t1_timeout = (u16_t)timeout;
+    if (dhcp->t1_timeout == 0) {
+      dhcp->t1_timeout = 1;
+    }
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_bind(): set request timeout %"U32_F" msecs\n", dhcp->offered_t1_renew*1000));
+  }
+  /* set renewal period timer */
+  if (dhcp->offered_t2_rebind != 0xffffffffUL) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_bind(): t2 rebind timer %"U32_F" secs\n", dhcp->offered_t2_rebind));
+    timeout = (dhcp->offered_t2_rebind + DHCP_COARSE_TIMER_SECS / 2) / DHCP_COARSE_TIMER_SECS;
+    if(timeout > 0xffff) {
+      timeout = 0xffff;
+    }
+    dhcp->t2_timeout = (u16_t)timeout;
+    if (dhcp->t2_timeout == 0) {
+      dhcp->t2_timeout = 1;
+    }
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_bind(): set request timeout %"U32_F" msecs\n", dhcp->offered_t2_rebind*1000));
+  }
+
+  /* If we have sub 1 minute lease, t2 and t1 will kick in at the same time. */
+  if ((dhcp->t1_timeout >= dhcp->t2_timeout) && (dhcp->t2_timeout > 0)) {
+    dhcp->t1_timeout = 0;
+  }
+
+  if (dhcp->subnet_mask_given) {
+    /* copy offered network mask */
+    ip_addr_copy(sn_mask, dhcp->offered_sn_mask);
+  } else {
+    /* subnet mask not given, choose a safe subnet mask given the network class */
+    u8_t first_octet = ip4_addr1(&dhcp->offered_ip_addr);
+    if (first_octet <= 127) {
+      ip4_addr_set_u32(&sn_mask, PP_HTONL(0xff000000UL));
+    } else if (first_octet >= 192) {
+      ip4_addr_set_u32(&sn_mask, PP_HTONL(0xffffff00UL));
+    } else {
+      ip4_addr_set_u32(&sn_mask, PP_HTONL(0xffff0000UL));
+    }
+  }
+
+  ip_addr_copy(gw_addr, dhcp->offered_gw_addr);
+  /* gateway address not given? */
+  if (ip_addr_isany(&gw_addr)) {
+    /* copy network address */
+    ip_addr_get_network(&gw_addr, &dhcp->offered_ip_addr, &sn_mask);
+    /* use first host address on network as gateway */
+    ip4_addr_set_u32(&gw_addr, ip4_addr_get_u32(&gw_addr) | PP_HTONL(0x00000001UL));
+  }
+
+#if LWIP_DHCP_AUTOIP_COOP
+  if(dhcp->autoip_coop_state == DHCP_AUTOIP_COOP_STATE_ON) {
+    autoip_stop(netif);
+    dhcp->autoip_coop_state = DHCP_AUTOIP_COOP_STATE_OFF;
+  }
+#endif /* LWIP_DHCP_AUTOIP_COOP */
+
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_STATE, ("dhcp_bind(): IP: 0x%08"X32_F"\n",
+    ip4_addr_get_u32(&dhcp->offered_ip_addr)));
+  netif_set_ipaddr(netif, &dhcp->offered_ip_addr);
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_STATE, ("dhcp_bind(): SN: 0x%08"X32_F"\n",
+    ip4_addr_get_u32(&sn_mask)));
+  netif_set_netmask(netif, &sn_mask);
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_STATE, ("dhcp_bind(): GW: 0x%08"X32_F"\n",
+    ip4_addr_get_u32(&gw_addr)));
+  netif_set_gw(netif, &gw_addr);
+  /* bring the interface up */
+  netif_set_up(netif);
+  /* netif is now bound to DHCP leased address */
+  dhcp_set_state(dhcp, DHCP_BOUND);
+}
+
+/**
+ * Renew an existing DHCP lease at the involved DHCP server.
+ *
+ * @param netif network interface which must renew its lease
+ */
+err_t
+dhcp_renew(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  err_t result;
+  u16_t msecs;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_renew()\n"));
+  dhcp_set_state(dhcp, DHCP_RENEWING);
+
+  /* create and initialize the DHCP message header */
+  result = dhcp_create_msg(netif, dhcp, DHCP_REQUEST);
+  if (result == ERR_OK) {
+    dhcp_option(dhcp, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN);
+    dhcp_option_short(dhcp, DHCP_MAX_MSG_LEN(netif));
+
+#if 0
+    dhcp_option(dhcp, DHCP_OPTION_REQUESTED_IP, 4);
+    dhcp_option_long(dhcp, ntohl(dhcp->offered_ip_addr.addr));
+#endif
+
+#if 0
+    dhcp_option(dhcp, DHCP_OPTION_SERVER_ID, 4);
+    dhcp_option_long(dhcp, ntohl(dhcp->server_ip_addr.addr));
+#endif
+
+#if LWIP_NETIF_HOSTNAME
+    dhcp_option_hostname(dhcp, netif);
+#endif /* LWIP_NETIF_HOSTNAME */
+
+    /* append DHCP message trailer */
+    dhcp_option_trailer(dhcp);
+
+    pbuf_realloc(dhcp->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp->options_out_len);
+
+    udp_sendto_if(dhcp->pcb, dhcp->p_out, &dhcp->server_ip_addr, DHCP_SERVER_PORT, netif);
+    dhcp_delete_msg(dhcp);
+
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_renew: RENEWING\n"));
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp_renew: could not allocate DHCP request\n"));
+  }
+  dhcp->tries++;
+  /* back-off on retries, but to a maximum of 20 seconds */
+  msecs = dhcp->tries < 10 ? dhcp->tries * 2000 : 20 * 1000;
+  dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_renew(): set request timeout %"U16_F" msecs\n", msecs));
+  return result;
+}
+
+/**
+ * Rebind with a DHCP server for an existing DHCP lease.
+ *
+ * @param netif network interface which must rebind with a DHCP server
+ */
+static err_t
+dhcp_rebind(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  err_t result;
+  u16_t msecs;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_rebind()\n"));
+  dhcp_set_state(dhcp, DHCP_REBINDING);
+
+  /* create and initialize the DHCP message header */
+  result = dhcp_create_msg(netif, dhcp, DHCP_REQUEST);
+  if (result == ERR_OK) {
+    dhcp_option(dhcp, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN);
+    dhcp_option_short(dhcp, DHCP_MAX_MSG_LEN(netif));
+
+#if LWIP_NETIF_HOSTNAME
+    dhcp_option_hostname(dhcp, netif);
+#endif /* LWIP_NETIF_HOSTNAME */
+
+#if 0
+    dhcp_option(dhcp, DHCP_OPTION_REQUESTED_IP, 4);
+    dhcp_option_long(dhcp, ntohl(dhcp->offered_ip_addr.addr));
+
+    dhcp_option(dhcp, DHCP_OPTION_SERVER_ID, 4);
+    dhcp_option_long(dhcp, ntohl(dhcp->server_ip_addr.addr));
+#endif
+
+    dhcp_option_trailer(dhcp);
+
+    pbuf_realloc(dhcp->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp->options_out_len);
+
+    /* broadcast to server */
+    udp_sendto_if(dhcp->pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif);
+    dhcp_delete_msg(dhcp);
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_rebind: REBINDING\n"));
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp_rebind: could not allocate DHCP request\n"));
+  }
+  dhcp->tries++;
+  msecs = dhcp->tries < 10 ? dhcp->tries * 1000 : 10 * 1000;
+  dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_rebind(): set request timeout %"U16_F" msecs\n", msecs));
+  return result;
+}
+
+/**
+ * Enter REBOOTING state to verify an existing lease
+ *
+ * @param netif network interface which must reboot
+ */
+static err_t
+dhcp_reboot(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  err_t result;
+  u16_t msecs;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_reboot()\n"));
+  dhcp_set_state(dhcp, DHCP_REBOOTING);
+
+  /* create and initialize the DHCP message header */
+  result = dhcp_create_msg(netif, dhcp, DHCP_REQUEST);
+  if (result == ERR_OK) {
+    dhcp_option(dhcp, DHCP_OPTION_MAX_MSG_SIZE, DHCP_OPTION_MAX_MSG_SIZE_LEN);
+    dhcp_option_short(dhcp, 576);
+
+    dhcp_option(dhcp, DHCP_OPTION_REQUESTED_IP, 4);
+    dhcp_option_long(dhcp, ntohl(ip4_addr_get_u32(&dhcp->offered_ip_addr)));
+
+    dhcp_option_trailer(dhcp);
+
+    pbuf_realloc(dhcp->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp->options_out_len);
+
+    /* broadcast to server */
+    udp_sendto_if(dhcp->pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif);
+    dhcp_delete_msg(dhcp);
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_reboot: REBOOTING\n"));
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp_reboot: could not allocate DHCP request\n"));
+  }
+  dhcp->tries++;
+  msecs = dhcp->tries < 10 ? dhcp->tries * 1000 : 10 * 1000;
+  dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_reboot(): set request timeout %"U16_F" msecs\n", msecs));
+  return result;
+}
+
+
+/**
+ * Release a DHCP lease.
+ *
+ * @param netif network interface which must release its lease
+ */
+err_t
+dhcp_release(struct netif *netif)
+{
+  struct dhcp *dhcp = netif->dhcp;
+  err_t result;
+  u16_t msecs;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_release()\n"));
+  if (dhcp == NULL) {
+    return ERR_ARG;
+  }
+
+  /* idle DHCP client */
+  dhcp_set_state(dhcp, DHCP_OFF);
+  /* clean old DHCP offer */
+  ip_addr_set_zero(&dhcp->server_ip_addr);
+  ip_addr_set_zero(&dhcp->offered_ip_addr);
+  ip_addr_set_zero(&dhcp->offered_sn_mask);
+  ip_addr_set_zero(&dhcp->offered_gw_addr);
+#if LWIP_DHCP_BOOTP_FILE
+  ip_addr_set_zero(&dhcp->offered_si_addr);
+#endif /* LWIP_DHCP_BOOTP_FILE */
+  dhcp->offered_t0_lease = dhcp->offered_t1_renew = dhcp->offered_t2_rebind = 0;
+  
+  /* create and initialize the DHCP message header */
+  result = dhcp_create_msg(netif, dhcp, DHCP_RELEASE);
+  if (result == ERR_OK) {
+    dhcp_option_trailer(dhcp);
+
+    pbuf_realloc(dhcp->p_out, sizeof(struct dhcp_msg) - DHCP_OPTIONS_LEN + dhcp->options_out_len);
+
+    udp_sendto_if(dhcp->pcb, dhcp->p_out, &dhcp->server_ip_addr, DHCP_SERVER_PORT, netif);
+    dhcp_delete_msg(dhcp);
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_release: RELEASED, DHCP_OFF\n"));
+  } else {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp_release: could not allocate DHCP request\n"));
+  }
+  dhcp->tries++;
+  msecs = dhcp->tries < 10 ? dhcp->tries * 1000 : 10 * 1000;
+  dhcp->request_timeout = (msecs + DHCP_FINE_TIMER_MSECS - 1) / DHCP_FINE_TIMER_MSECS;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_release(): set request timeout %"U16_F" msecs\n", msecs));
+  /* bring the interface down */
+  netif_set_down(netif);
+  /* remove IP address from interface */
+  netif_set_ipaddr(netif, IP_ADDR_ANY);
+  netif_set_gw(netif, IP_ADDR_ANY);
+  netif_set_netmask(netif, IP_ADDR_ANY);
+  
+  return result;
+}
+
+/**
+ * Remove the DHCP client from the interface.
+ *
+ * @param netif The network interface to stop DHCP on
+ */
+void
+dhcp_stop(struct netif *netif)
+{
+  struct dhcp *dhcp;
+  LWIP_ERROR("dhcp_stop: netif != NULL", (netif != NULL), return;);
+  dhcp = netif->dhcp;
+  /* Remove the flag that says this netif is handled by DHCP. */
+  netif->flags &= ~NETIF_FLAG_DHCP;
+
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_stop()\n"));
+  /* netif is DHCP configured? */
+  if (dhcp != NULL) {
+#if LWIP_DHCP_AUTOIP_COOP
+    if(dhcp->autoip_coop_state == DHCP_AUTOIP_COOP_STATE_ON) {
+      autoip_stop(netif);
+      dhcp->autoip_coop_state = DHCP_AUTOIP_COOP_STATE_OFF;
+    }
+#endif /* LWIP_DHCP_AUTOIP_COOP */
+
+    if (dhcp->pcb != NULL) {
+      udp_remove(dhcp->pcb);
+      dhcp->pcb = NULL;
+    }
+    LWIP_ASSERT("reply wasn't freed", dhcp->msg_in == NULL);
+    dhcp_set_state(dhcp, DHCP_OFF);
+  }
+}
+
+/*
+ * Set the DHCP state of a DHCP client.
+ *
+ * If the state changed, reset the number of tries.
+ */
+static void
+dhcp_set_state(struct dhcp *dhcp, u8_t new_state)
+{
+  if (new_state != dhcp->state) {
+    dhcp->state = new_state;
+    dhcp->tries = 0;
+    dhcp->request_timeout = 0;
+  }
+}
+
+/*
+ * Concatenate an option type and length field to the outgoing
+ * DHCP message.
+ *
+ */
+static void
+dhcp_option(struct dhcp *dhcp, u8_t option_type, u8_t option_len)
+{
+  LWIP_ASSERT("dhcp_option: dhcp->options_out_len + 2 + option_len <= DHCP_OPTIONS_LEN", dhcp->options_out_len + 2U + option_len <= DHCP_OPTIONS_LEN);
+  dhcp->msg_out->options[dhcp->options_out_len++] = option_type;
+  dhcp->msg_out->options[dhcp->options_out_len++] = option_len;
+}
+/*
+ * Concatenate a single byte to the outgoing DHCP message.
+ *
+ */
+static void
+dhcp_option_byte(struct dhcp *dhcp, u8_t value)
+{
+  LWIP_ASSERT("dhcp_option_byte: dhcp->options_out_len < DHCP_OPTIONS_LEN", dhcp->options_out_len < DHCP_OPTIONS_LEN);
+  dhcp->msg_out->options[dhcp->options_out_len++] = value;
+}
+
+static void
+dhcp_option_short(struct dhcp *dhcp, u16_t value)
+{
+  LWIP_ASSERT("dhcp_option_short: dhcp->options_out_len + 2 <= DHCP_OPTIONS_LEN", dhcp->options_out_len + 2U <= DHCP_OPTIONS_LEN);
+  dhcp->msg_out->options[dhcp->options_out_len++] = (u8_t)((value & 0xff00U) >> 8);
+  dhcp->msg_out->options[dhcp->options_out_len++] = (u8_t) (value & 0x00ffU);
+}
+
+static void
+dhcp_option_long(struct dhcp *dhcp, u32_t value)
+{
+  LWIP_ASSERT("dhcp_option_long: dhcp->options_out_len + 4 <= DHCP_OPTIONS_LEN", dhcp->options_out_len + 4U <= DHCP_OPTIONS_LEN);
+  dhcp->msg_out->options[dhcp->options_out_len++] = (u8_t)((value & 0xff000000UL) >> 24);
+  dhcp->msg_out->options[dhcp->options_out_len++] = (u8_t)((value & 0x00ff0000UL) >> 16);
+  dhcp->msg_out->options[dhcp->options_out_len++] = (u8_t)((value & 0x0000ff00UL) >> 8);
+  dhcp->msg_out->options[dhcp->options_out_len++] = (u8_t)((value & 0x000000ffUL));
+}
+
+#if LWIP_NETIF_HOSTNAME
+static void
+dhcp_option_hostname(struct dhcp *dhcp, struct netif *netif)
+{
+  if (netif->hostname != NULL) {
+    size_t namelen = strlen(netif->hostname);
+    if (namelen > 0) {
+      u8_t len;
+      const char *p = netif->hostname;
+      /* Shrink len to available bytes (need 2 bytes for OPTION_HOSTNAME
+         and 1 byte for trailer) */
+      size_t available = DHCP_OPTIONS_LEN - dhcp->options_out_len - 3;
+      LWIP_ASSERT("DHCP: hostname is too long!", namelen <= available);
+      len = LWIP_MIN(namelen, available);
+      dhcp_option(dhcp, DHCP_OPTION_HOSTNAME, len);
+      while (len--) {
+        dhcp_option_byte(dhcp, *p++);
+      }
+    }
+  }
+}
+#endif /* LWIP_NETIF_HOSTNAME */
+
+/**
+ * Extract the DHCP message and the DHCP options.
+ *
+ * Extract the DHCP message and the DHCP options, each into a contiguous
+ * piece of memory. As a DHCP message is variable sized by its options,
+ * and also allows overriding some fields for options, the easy approach
+ * is to first unfold the options into a conitguous piece of memory, and
+ * use that further on.
+ *
+ */
+static err_t
+dhcp_parse_reply(struct dhcp *dhcp, struct pbuf *p)
+{
+  u8_t *options;
+  u16_t offset;
+  u16_t offset_max;
+  u16_t options_idx;
+  u16_t options_idx_max;
+  struct pbuf *q;
+  int parse_file_as_options = 0;
+  int parse_sname_as_options = 0;
+
+  /* clear received options */
+  dhcp_clear_all_options(dhcp);
+  /* check that beginning of dhcp_msg (up to and including chaddr) is in first pbuf */
+  if (p->len < DHCP_SNAME_OFS) {
+    return ERR_BUF;
+  }
+  dhcp->msg_in = (struct dhcp_msg *)p->payload;
+#if LWIP_DHCP_BOOTP_FILE
+  /* clear boot file name */
+  dhcp->boot_file_name[0] = 0;
+#endif /* LWIP_DHCP_BOOTP_FILE */
+
+  /* parse options */
+
+  /* start with options field */
+  options_idx = DHCP_OPTIONS_OFS;
+  /* parse options to the end of the received packet */
+  options_idx_max = p->tot_len;
+again:
+  q = p;
+  while((q != NULL) && (options_idx >= q->len)) {
+    options_idx -= q->len;
+    options_idx_max -= q->len;
+    q = q->next;
+  }
+  if (q == NULL) {
+    return ERR_BUF;
+  }
+  offset = options_idx;
+  offset_max = options_idx_max;
+  options = (u8_t*)q->payload;
+  /* at least 1 byte to read and no end marker, then at least 3 bytes to read? */
+  while((q != NULL) && (options[offset] != DHCP_OPTION_END) && (offset < offset_max)) {
+    u8_t op = options[offset];
+    u8_t len;
+    u8_t decode_len = 0;
+    int decode_idx = -1;
+    u16_t val_offset = offset + 2;
+    /* len byte might be in the next pbuf */
+    if (offset + 1 < q->len) {
+      len = options[offset + 1];
+    } else {
+      len = (q->next != NULL ? ((u8_t*)q->next->payload)[0] : 0);
+    }
+    /* LWIP_DEBUGF(DHCP_DEBUG, ("msg_offset=%"U16_F", q->len=%"U16_F, msg_offset, q->len)); */
+    decode_len = len;
+    switch(op) {
+      /* case(DHCP_OPTION_END): handled above */
+      case(DHCP_OPTION_PAD):
+        /* special option: no len encoded */
+        decode_len = len = 0;
+        /* will be increased below */
+        offset--;
+        break;
+      case(DHCP_OPTION_SUBNET_MASK):
+        LWIP_ERROR("len == 4", len == 4, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_SUBNET_MASK;
+        break;
+      case(DHCP_OPTION_ROUTER):
+        decode_len = 4; /* only copy the first given router */
+        LWIP_ERROR("len >= decode_len", len >= decode_len, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_ROUTER;
+        break;
+      case(DHCP_OPTION_DNS_SERVER):
+        /* special case: there might be more than one server */
+        LWIP_ERROR("len % 4 == 0", len % 4 == 0, return ERR_VAL;);
+        /* limit number of DNS servers */
+        decode_len = LWIP_MIN(len, 4 * DNS_MAX_SERVERS);
+        LWIP_ERROR("len >= decode_len", len >= decode_len, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_DNS_SERVER;
+        break;
+      case(DHCP_OPTION_LEASE_TIME):
+        LWIP_ERROR("len == 4", len == 4, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_LEASE_TIME;
+        break;
+      case(DHCP_OPTION_OVERLOAD):
+        LWIP_ERROR("len == 1", len == 1, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_OVERLOAD;
+        break;
+      case(DHCP_OPTION_MESSAGE_TYPE):
+        LWIP_ERROR("len == 1", len == 1, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_MSG_TYPE;
+        break;
+      case(DHCP_OPTION_SERVER_ID):
+        LWIP_ERROR("len == 4", len == 4, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_SERVER_ID;
+        break;
+      case(DHCP_OPTION_T1):
+        LWIP_ERROR("len == 4", len == 4, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_T1;
+        break;
+      case(DHCP_OPTION_T2):
+        LWIP_ERROR("len == 4", len == 4, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_T2;
+        break;
+      default:
+        decode_len = 0;
+        LWIP_DEBUGF(DHCP_DEBUG, ("skipping option %"U16_F" in options\n", op));
+        break;
+    }
+    offset += len + 2;
+    if (decode_len > 0) {
+      u32_t value = 0;
+      u16_t copy_len;
+decode_next:
+      LWIP_ASSERT("check decode_idx", decode_idx >= 0 && decode_idx < DHCP_OPTION_IDX_MAX);
+      if (!dhcp_option_given(dhcp, decode_idx)) {
+        copy_len = LWIP_MIN(decode_len, 4);
+        pbuf_copy_partial(q, &value, copy_len, val_offset);
+        if (decode_len > 4) {
+          /* decode more than one u32_t */
+          LWIP_ERROR("decode_len % 4 == 0", decode_len % 4 == 0, return ERR_VAL;);
+          dhcp_got_option(dhcp, decode_idx);
+          dhcp_set_option_value(dhcp, decode_idx, htonl(value));
+          decode_len -= 4;
+          val_offset += 4;
+          decode_idx++;
+          goto decode_next;
+        } else if (decode_len == 4) {
+          value = ntohl(value);
+        } else {
+          LWIP_ERROR("invalid decode_len", decode_len == 1, return ERR_VAL;);
+          value = ((u8_t*)&value)[0];
+        }
+        dhcp_got_option(dhcp, decode_idx);
+        dhcp_set_option_value(dhcp, decode_idx, value);
+      }
+    }
+    if (offset >= q->len) {
+      offset -= q->len;
+      offset_max -= q->len;
+      if ((offset < offset_max) && offset_max) {
+        q = q->next;
+        LWIP_ASSERT("next pbuf was null", q);
+        options = (u8_t*)q->payload;
+      } else {
+        /* We've run out of bytes, probably no end marker. Don't proceed. */
+        break;
+      }
+    }
+  }
+  /* is this an overloaded message? */
+  if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_OVERLOAD)) {
+    u32_t overload = dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_OVERLOAD);
+    dhcp_clear_option(dhcp, DHCP_OPTION_IDX_OVERLOAD);
+    if (overload == DHCP_OVERLOAD_FILE) {
+      parse_file_as_options = 1;
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("overloaded file field\n"));
+    } else if (overload == DHCP_OVERLOAD_SNAME) {
+      parse_sname_as_options = 1;
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("overloaded sname field\n"));
+    } else if (overload == DHCP_OVERLOAD_SNAME_FILE) {
+      parse_sname_as_options = 1;
+      parse_file_as_options = 1;
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("overloaded sname and file field\n"));
+    } else {
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("invalid overload option: %d\n", (int)overload));
+    }
+#if LWIP_DHCP_BOOTP_FILE
+    if (!parse_file_as_options) {
+      /* only do this for ACK messages */
+      if (dhcp_option_given(dhcp, DHCP_OPTION_IDX_MSG_TYPE) &&
+        (dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_MSG_TYPE) == DHCP_ACK))
+      /* copy bootp file name, don't care for sname (server hostname) */
+      pbuf_copy_partial(p, dhcp->boot_file_name, DHCP_FILE_LEN-1, DHCP_FILE_OFS);
+      /* make sure the string is really NULL-terminated */
+      dhcp->boot_file_name[DHCP_FILE_LEN-1] = 0;
+    }
+#endif /* LWIP_DHCP_BOOTP_FILE */
+  }
+  if (parse_file_as_options) {
+    /* if both are overloaded, parse file first and then sname (RFC 2131 ch. 4.1) */
+    parse_file_as_options = 0;
+    options_idx = DHCP_FILE_OFS;
+    options_idx_max = DHCP_FILE_OFS + DHCP_FILE_LEN;
+    goto again;
+  } else if (parse_sname_as_options) {
+    parse_sname_as_options = 0;
+    options_idx = DHCP_SNAME_OFS;
+    options_idx_max = DHCP_SNAME_OFS + DHCP_SNAME_LEN;
+    goto again;
+  }
+  return ERR_OK;
+}
+
+/**
+ * If an incoming DHCP message is in response to us, then trigger the state machine
+ */
+static void
+dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port)
+{
+  struct netif *netif = (struct netif *)arg;
+  struct dhcp *dhcp = netif->dhcp;
+  struct dhcp_msg *reply_msg = (struct dhcp_msg *)p->payload;
+  u8_t msg_type;
+  u8_t i;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_recv(pbuf = %p) from DHCP server %"U16_F".%"U16_F".%"U16_F".%"U16_F" port %"U16_F"\n", (void*)p,
+    ip4_addr1_16(addr), ip4_addr2_16(addr), ip4_addr3_16(addr), ip4_addr4_16(addr), port));
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("pbuf->len = %"U16_F"\n", p->len));
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("pbuf->tot_len = %"U16_F"\n", p->tot_len));
+  /* prevent warnings about unused arguments */
+  LWIP_UNUSED_ARG(pcb);
+  LWIP_UNUSED_ARG(addr);
+  LWIP_UNUSED_ARG(port);
+
+  LWIP_ASSERT("reply wasn't freed", dhcp->msg_in == NULL);
+
+  if (p->len < DHCP_MIN_REPLY_LEN) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("DHCP reply message or pbuf too short\n"));
+    goto free_pbuf_and_return;
+  }
+
+  if (reply_msg->op != DHCP_BOOTREPLY) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("not a DHCP reply message, but type %"U16_F"\n", (u16_t)reply_msg->op));
+    goto free_pbuf_and_return;
+  }
+  /* iterate through hardware address and match against DHCP message */
+  for (i = 0; i < netif->hwaddr_len; i++) {
+    if (netif->hwaddr[i] != reply_msg->chaddr[i]) {
+      LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
+        ("netif->hwaddr[%"U16_F"]==%02"X16_F" != reply_msg->chaddr[%"U16_F"]==%02"X16_F"\n",
+        (u16_t)i, (u16_t)netif->hwaddr[i], (u16_t)i, (u16_t)reply_msg->chaddr[i]));
+      goto free_pbuf_and_return;
+    }
+  }
+  /* match transaction ID against what we expected */
+  if (ntohl(reply_msg->xid) != dhcp->xid) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
+      ("transaction id mismatch reply_msg->xid(%"X32_F")!=dhcp->xid(%"X32_F")\n",ntohl(reply_msg->xid),dhcp->xid));
+    goto free_pbuf_and_return;
+  }
+  /* option fields could be unfold? */
+  if (dhcp_parse_reply(dhcp, p) != ERR_OK) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+      ("problem unfolding DHCP message - too short on memory?\n"));
+    goto free_pbuf_and_return;
+  }
+
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("searching DHCP_OPTION_MESSAGE_TYPE\n"));
+  /* obtain pointer to DHCP message type */
+  if (!dhcp_option_given(dhcp, DHCP_OPTION_IDX_MSG_TYPE)) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("DHCP_OPTION_MESSAGE_TYPE option not found\n"));
+    goto free_pbuf_and_return;
+  }
+
+  /* read DHCP message type */
+  msg_type = (u8_t)dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_MSG_TYPE);
+  /* message type is DHCP ACK? */
+  if (msg_type == DHCP_ACK) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("DHCP_ACK received\n"));
+    /* in requesting state? */
+    if (dhcp->state == DHCP_REQUESTING) {
+      dhcp_handle_ack(netif);
+#if DHCP_DOES_ARP_CHECK
+      /* check if the acknowledged lease address is already in use */
+      dhcp_check(netif);
+#else
+      /* bind interface to the acknowledged lease address */
+      dhcp_bind(netif);
+#endif
+    }
+    /* already bound to the given lease address? */
+    else if ((dhcp->state == DHCP_REBOOTING) || (dhcp->state == DHCP_REBINDING) || (dhcp->state == DHCP_RENEWING)) {
+      dhcp_bind(netif);
+    }
+  }
+  /* received a DHCP_NAK in appropriate state? */
+  else if ((msg_type == DHCP_NAK) &&
+    ((dhcp->state == DHCP_REBOOTING) || (dhcp->state == DHCP_REQUESTING) ||
+     (dhcp->state == DHCP_REBINDING) || (dhcp->state == DHCP_RENEWING  ))) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("DHCP_NAK received\n"));
+    dhcp_handle_nak(netif);
+  }
+  /* received a DHCP_OFFER in DHCP_SELECTING state? */
+  else if ((msg_type == DHCP_OFFER) && (dhcp->state == DHCP_SELECTING)) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("DHCP_OFFER received in DHCP_SELECTING state\n"));
+    dhcp->request_timeout = 0;
+    /* remember offered lease */
+    dhcp_handle_offer(netif);
+  }
+free_pbuf_and_return:
+  dhcp->msg_in = NULL;
+  pbuf_free(p);
+}
+
+/**
+ * Create a DHCP request, fill in common headers
+ *
+ * @param netif the netif under DHCP control
+ * @param dhcp dhcp control struct
+ * @param message_type message type of the request
+ */
+static err_t
+dhcp_create_msg(struct netif *netif, struct dhcp *dhcp, u8_t message_type)
+{
+  u16_t i;
+#ifndef DHCP_GLOBAL_XID
+  /** default global transaction identifier starting value (easy to match
+   *  with a packet analyser). We simply increment for each new request.
+   *  Predefine DHCP_GLOBAL_XID to a better value or a function call to generate one
+   *  at runtime, any supporting function prototypes can be defined in DHCP_GLOBAL_XID_HEADER */
+#if DHCP_CREATE_RAND_XID && defined(LWIP_RAND)
+  static u32_t xid;
+#else /* DHCP_CREATE_RAND_XID && defined(LWIP_RAND) */
+  static u32_t xid = 0xABCD0000;
+#endif /* DHCP_CREATE_RAND_XID && defined(LWIP_RAND) */
+#else
+  if (!xid_initialised) {
+    xid = DHCP_GLOBAL_XID;
+    xid_initialised = !xid_initialised;
+  }
+#endif
+  LWIP_ERROR("dhcp_create_msg: netif != NULL", (netif != NULL), return ERR_ARG;);
+  LWIP_ERROR("dhcp_create_msg: dhcp != NULL", (dhcp != NULL), return ERR_VAL;);
+  LWIP_ASSERT("dhcp_create_msg: dhcp->p_out == NULL", dhcp->p_out == NULL);
+  LWIP_ASSERT("dhcp_create_msg: dhcp->msg_out == NULL", dhcp->msg_out == NULL);
+  dhcp->p_out = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct dhcp_msg), PBUF_RAM);
+  if (dhcp->p_out == NULL) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+      ("dhcp_create_msg(): could not allocate pbuf\n"));
+    return ERR_MEM;
+  }
+  LWIP_ASSERT("dhcp_create_msg: check that first pbuf can hold struct dhcp_msg",
+           (dhcp->p_out->len >= sizeof(struct dhcp_msg)));
+
+  /* reuse transaction identifier in retransmissions */
+  if (dhcp->tries == 0) {
+#if DHCP_CREATE_RAND_XID && defined(LWIP_RAND)
+    xid = LWIP_RAND();
+#else /* DHCP_CREATE_RAND_XID && defined(LWIP_RAND) */
+    xid++;
+#endif /* DHCP_CREATE_RAND_XID && defined(LWIP_RAND) */
+  }
+  dhcp->xid = xid;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE,
+              ("transaction id xid(%"X32_F")\n", xid));
+
+  dhcp->msg_out = (struct dhcp_msg *)dhcp->p_out->payload;
+
+  dhcp->msg_out->op = DHCP_BOOTREQUEST;
+  /* TODO: make link layer independent */
+  dhcp->msg_out->htype = DHCP_HTYPE_ETH;
+  dhcp->msg_out->hlen = netif->hwaddr_len;
+  dhcp->msg_out->hops = 0;
+  dhcp->msg_out->xid = htonl(dhcp->xid);
+  dhcp->msg_out->secs = 0;
+  /* we don't need the broadcast flag since we can receive unicast traffic
+     before being fully configured! */
+  dhcp->msg_out->flags = 0;
+  ip_addr_set_zero(&dhcp->msg_out->ciaddr);
+  /* set ciaddr to netif->ip_addr based on message_type and state */
+  if ((message_type == DHCP_INFORM) || (message_type == DHCP_DECLINE) ||
+      ((message_type == DHCP_REQUEST) && /* DHCP_BOUND not used for sending! */
+       ((dhcp->state==DHCP_RENEWING) || dhcp->state==DHCP_REBINDING))) {
+    ip_addr_copy(dhcp->msg_out->ciaddr, netif->ip_addr);
+  }
+  ip_addr_set_zero(&dhcp->msg_out->yiaddr);
+  ip_addr_set_zero(&dhcp->msg_out->siaddr);
+  ip_addr_set_zero(&dhcp->msg_out->giaddr);
+  for (i = 0; i < DHCP_CHADDR_LEN; i++) {
+    /* copy netif hardware address, pad with zeroes */
+    dhcp->msg_out->chaddr[i] = (i < netif->hwaddr_len && i < NETIF_MAX_HWADDR_LEN) ? netif->hwaddr[i] : 0/* pad byte*/;
+  }
+  for (i = 0; i < DHCP_SNAME_LEN; i++) {
+    dhcp->msg_out->sname[i] = 0;
+  }
+  for (i = 0; i < DHCP_FILE_LEN; i++) {
+    dhcp->msg_out->file[i] = 0;
+  }
+  dhcp->msg_out->cookie = PP_HTONL(DHCP_MAGIC_COOKIE);
+  dhcp->options_out_len = 0;
+  /* fill options field with an incrementing array (for debugging purposes) */
+  for (i = 0; i < DHCP_OPTIONS_LEN; i++) {
+    dhcp->msg_out->options[i] = (u8_t)i; /* for debugging only, no matter if truncated */
+  }
+  /* Add option MESSAGE_TYPE */
+  dhcp_option(dhcp, DHCP_OPTION_MESSAGE_TYPE, DHCP_OPTION_MESSAGE_TYPE_LEN);
+  dhcp_option_byte(dhcp, message_type);
+  return ERR_OK;
+}
+
+/**
+ * Free previously allocated memory used to send a DHCP request.
+ *
+ * @param dhcp the dhcp struct to free the request from
+ */
+static void
+dhcp_delete_msg(struct dhcp *dhcp)
+{
+  LWIP_ERROR("dhcp_delete_msg: dhcp != NULL", (dhcp != NULL), return;);
+  LWIP_ASSERT("dhcp_delete_msg: dhcp->p_out != NULL", dhcp->p_out != NULL);
+  LWIP_ASSERT("dhcp_delete_msg: dhcp->msg_out != NULL", dhcp->msg_out != NULL);
+  if (dhcp->p_out != NULL) {
+    pbuf_free(dhcp->p_out);
+  }
+  dhcp->p_out = NULL;
+  dhcp->msg_out = NULL;
+}
+
+/**
+ * Add a DHCP message trailer
+ *
+ * Adds the END option to the DHCP message, and if
+ * necessary, up to three padding bytes.
+ *
+ * @param dhcp DHCP state structure
+ */
+static void
+dhcp_option_trailer(struct dhcp *dhcp)
+{
+  LWIP_ERROR("dhcp_option_trailer: dhcp != NULL", (dhcp != NULL), return;);
+  LWIP_ASSERT("dhcp_option_trailer: dhcp->msg_out != NULL\n", dhcp->msg_out != NULL);
+  LWIP_ASSERT("dhcp_option_trailer: dhcp->options_out_len < DHCP_OPTIONS_LEN\n", dhcp->options_out_len < DHCP_OPTIONS_LEN);
+  dhcp->msg_out->options[dhcp->options_out_len++] = DHCP_OPTION_END;
+  /* packet is too small, or not 4 byte aligned? */
+  while (((dhcp->options_out_len < DHCP_MIN_OPTIONS_LEN) || (dhcp->options_out_len & 3)) &&
+         (dhcp->options_out_len < DHCP_OPTIONS_LEN)) {
+    /* add a fill/padding byte */
+    dhcp->msg_out->options[dhcp->options_out_len++] = 0;
+  }
+}
+
+#endif /* LWIP_DHCP */
diff --git a/external/badvpn_dns/lwip/src/core/dns.c b/external/badvpn_dns/lwip/src/core/dns.c
new file mode 100644
index 0000000..90821a6
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/dns.c
@@ -0,0 +1,988 @@
+/**
+ * @file
+ * DNS - host name to IP address resolver.
+ *
+ */
+
+/**
+
+ * This file implements a DNS host name to IP address resolver.
+
+ * Port to lwIP from uIP
+ * by Jim Pettinato April 2007
+
+ * uIP version Copyright (c) 2002-2003, Adam Dunkels.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ *
+ * DNS.C
+ *
+ * The lwIP DNS resolver functions are used to lookup a host name and
+ * map it to a numerical IP address. It maintains a list of resolved
+ * hostnames that can be queried with the dns_lookup() function.
+ * New hostnames can be resolved using the dns_query() function.
+ *
+ * The lwIP version of the resolver also adds a non-blocking version of
+ * gethostbyname() that will work with a raw API application. This function
+ * checks for an IP address string first and converts it if it is valid.
+ * gethostbyname() then does a dns_lookup() to see if the name is 
+ * already in the table. If so, the IP is returned. If not, a query is 
+ * issued and the function returns with a ERR_INPROGRESS status. The app
+ * using the dns client must then go into a waiting state.
+ *
+ * Once a hostname has been resolved (or found to be non-existent),
+ * the resolver code calls a specified callback function (which 
+ * must be implemented by the module that uses the resolver).
+ */
+
+/*-----------------------------------------------------------------------------
+ * RFC 1035 - Domain names - implementation and specification
+ * RFC 2181 - Clarifications to the DNS Specification
+ *----------------------------------------------------------------------------*/
+
+/** @todo: define good default values (rfc compliance) */
+/** @todo: improve answer parsing, more checkings... */
+/** @todo: check RFC1035 - 7.3. Processing responses */
+
+/*-----------------------------------------------------------------------------
+ * Includes
+ *----------------------------------------------------------------------------*/
+
+#include "lwip/opt.h"
+
+#if LWIP_DNS /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/udp.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/dns.h"
+
+#include <string.h>
+
+/** DNS server IP address */
+#ifndef DNS_SERVER_ADDRESS
+#define DNS_SERVER_ADDRESS(ipaddr)        (ip4_addr_set_u32(ipaddr, ipaddr_addr("208.67.222.222"))) /* resolver1.opendns.com */
+#endif
+
+/** DNS server port address */
+#ifndef DNS_SERVER_PORT
+#define DNS_SERVER_PORT           53
+#endif
+
+/** DNS maximum number of retries when asking for a name, before "timeout". */
+#ifndef DNS_MAX_RETRIES
+#define DNS_MAX_RETRIES           4
+#endif
+
+/** DNS resource record max. TTL (one week as default) */
+#ifndef DNS_MAX_TTL
+#define DNS_MAX_TTL               604800
+#endif
+
+/* DNS protocol flags */
+#define DNS_FLAG1_RESPONSE        0x80
+#define DNS_FLAG1_OPCODE_STATUS   0x10
+#define DNS_FLAG1_OPCODE_INVERSE  0x08
+#define DNS_FLAG1_OPCODE_STANDARD 0x00
+#define DNS_FLAG1_AUTHORATIVE     0x04
+#define DNS_FLAG1_TRUNC           0x02
+#define DNS_FLAG1_RD              0x01
+#define DNS_FLAG2_RA              0x80
+#define DNS_FLAG2_ERR_MASK        0x0f
+#define DNS_FLAG2_ERR_NONE        0x00
+#define DNS_FLAG2_ERR_NAME        0x03
+
+/* DNS protocol states */
+#define DNS_STATE_UNUSED          0
+#define DNS_STATE_NEW             1
+#define DNS_STATE_ASKING          2
+#define DNS_STATE_DONE            3
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+/** DNS message header */
+struct dns_hdr {
+  PACK_STRUCT_FIELD(u16_t id);
+  PACK_STRUCT_FIELD(u8_t flags1);
+  PACK_STRUCT_FIELD(u8_t flags2);
+  PACK_STRUCT_FIELD(u16_t numquestions);
+  PACK_STRUCT_FIELD(u16_t numanswers);
+  PACK_STRUCT_FIELD(u16_t numauthrr);
+  PACK_STRUCT_FIELD(u16_t numextrarr);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+#define SIZEOF_DNS_HDR 12
+
+/** DNS query message structure.
+    No packing needed: only used locally on the stack. */
+struct dns_query {
+  /* DNS query record starts with either a domain name or a pointer
+     to a name already present somewhere in the packet. */
+  u16_t type;
+  u16_t cls;
+};
+#define SIZEOF_DNS_QUERY 4
+
+/** DNS answer message structure.
+    No packing needed: only used locally on the stack. */
+struct dns_answer {
+  /* DNS answer record starts with either a domain name or a pointer
+     to a name already present somewhere in the packet. */
+  u16_t type;
+  u16_t cls;
+  u32_t ttl;
+  u16_t len;
+};
+#define SIZEOF_DNS_ANSWER 10
+
+/** DNS table entry */
+struct dns_table_entry {
+  u8_t  state;
+  u8_t  numdns;
+  u8_t  tmr;
+  u8_t  retries;
+  u8_t  seqno;
+  u8_t  err;
+  u32_t ttl;
+  char name[DNS_MAX_NAME_LENGTH];
+  ip_addr_t ipaddr;
+  /* pointer to callback on DNS query done */
+  dns_found_callback found;
+  void *arg;
+};
+
+#if DNS_LOCAL_HOSTLIST
+
+#if DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+/** Local host-list. For hostnames in this list, no
+ *  external name resolution is performed */
+static struct local_hostlist_entry *local_hostlist_dynamic;
+#else /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+
+/** Defining this allows the local_hostlist_static to be placed in a different
+ * linker section (e.g. FLASH) */
+#ifndef DNS_LOCAL_HOSTLIST_STORAGE_PRE
+#define DNS_LOCAL_HOSTLIST_STORAGE_PRE static
+#endif /* DNS_LOCAL_HOSTLIST_STORAGE_PRE */
+/** Defining this allows the local_hostlist_static to be placed in a different
+ * linker section (e.g. FLASH) */
+#ifndef DNS_LOCAL_HOSTLIST_STORAGE_POST
+#define DNS_LOCAL_HOSTLIST_STORAGE_POST
+#endif /* DNS_LOCAL_HOSTLIST_STORAGE_POST */
+DNS_LOCAL_HOSTLIST_STORAGE_PRE struct local_hostlist_entry local_hostlist_static[]
+  DNS_LOCAL_HOSTLIST_STORAGE_POST = DNS_LOCAL_HOSTLIST_INIT;
+
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+
+static void dns_init_local();
+#endif /* DNS_LOCAL_HOSTLIST */
+
+
+/* forward declarations */
+static void dns_recv(void *s, struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port);
+static void dns_check_entries(void);
+
+/*-----------------------------------------------------------------------------
+ * Globales
+ *----------------------------------------------------------------------------*/
+
+/* DNS variables */
+static struct udp_pcb        *dns_pcb;
+static u8_t                   dns_seqno;
+static struct dns_table_entry dns_table[DNS_TABLE_SIZE];
+static ip_addr_t              dns_servers[DNS_MAX_SERVERS];
+/** Contiguous buffer for processing responses */
+static u8_t                   dns_payload_buffer[LWIP_MEM_ALIGN_BUFFER(DNS_MSG_SIZE)];
+static u8_t*                  dns_payload;
+
+/**
+ * Initialize the resolver: set up the UDP pcb and configure the default server
+ * (DNS_SERVER_ADDRESS).
+ */
+void
+dns_init()
+{
+  ip_addr_t dnsserver;
+
+  dns_payload = (u8_t *)LWIP_MEM_ALIGN(dns_payload_buffer);
+  
+  /* initialize default DNS server address */
+  DNS_SERVER_ADDRESS(&dnsserver);
+
+  LWIP_DEBUGF(DNS_DEBUG, ("dns_init: initializing\n"));
+
+  /* if dns client not yet initialized... */
+  if (dns_pcb == NULL) {
+    dns_pcb = udp_new();
+
+    if (dns_pcb != NULL) {
+      /* initialize DNS table not needed (initialized to zero since it is a
+       * global variable) */
+      LWIP_ASSERT("For implicit initialization to work, DNS_STATE_UNUSED needs to be 0",
+        DNS_STATE_UNUSED == 0);
+
+      /* initialize DNS client */
+      udp_bind(dns_pcb, IP_ADDR_ANY, 0);
+      udp_recv(dns_pcb, dns_recv, NULL);
+
+      /* initialize default DNS primary server */
+      dns_setserver(0, &dnsserver);
+    }
+  }
+#if DNS_LOCAL_HOSTLIST
+  dns_init_local();
+#endif
+}
+
+/**
+ * Initialize one of the DNS servers.
+ *
+ * @param numdns the index of the DNS server to set must be < DNS_MAX_SERVERS
+ * @param dnsserver IP address of the DNS server to set
+ */
+void
+dns_setserver(u8_t numdns, ip_addr_t *dnsserver)
+{
+  if ((numdns < DNS_MAX_SERVERS) && (dns_pcb != NULL) &&
+      (dnsserver != NULL) && !ip_addr_isany(dnsserver)) {
+    dns_servers[numdns] = (*dnsserver);
+  }
+}
+
+/**
+ * Obtain one of the currently configured DNS server.
+ *
+ * @param numdns the index of the DNS server
+ * @return IP address of the indexed DNS server or "ip_addr_any" if the DNS
+ *         server has not been configured.
+ */
+ip_addr_t
+dns_getserver(u8_t numdns)
+{
+  if (numdns < DNS_MAX_SERVERS) {
+    return dns_servers[numdns];
+  } else {
+    return *IP_ADDR_ANY;
+  }
+}
+
+/**
+ * The DNS resolver client timer - handle retries and timeouts and should
+ * be called every DNS_TMR_INTERVAL milliseconds (every second by default).
+ */
+void
+dns_tmr(void)
+{
+  if (dns_pcb != NULL) {
+    LWIP_DEBUGF(DNS_DEBUG, ("dns_tmr: dns_check_entries\n"));
+    dns_check_entries();
+  }
+}
+
+#if DNS_LOCAL_HOSTLIST
+static void
+dns_init_local()
+{
+#if DNS_LOCAL_HOSTLIST_IS_DYNAMIC && defined(DNS_LOCAL_HOSTLIST_INIT)
+  int i;
+  struct local_hostlist_entry *entry;
+  /* Dynamic: copy entries from DNS_LOCAL_HOSTLIST_INIT to list */
+  struct local_hostlist_entry local_hostlist_init[] = DNS_LOCAL_HOSTLIST_INIT;
+  size_t namelen;
+  for (i = 0; i < sizeof(local_hostlist_init) / sizeof(struct local_hostlist_entry); i++) {
+    struct local_hostlist_entry *init_entry = &local_hostlist_init[i];
+    LWIP_ASSERT("invalid host name (NULL)", init_entry->name != NULL);
+    namelen = strlen(init_entry->name);
+    LWIP_ASSERT("namelen <= DNS_LOCAL_HOSTLIST_MAX_NAMELEN", namelen <= DNS_LOCAL_HOSTLIST_MAX_NAMELEN);
+    entry = (struct local_hostlist_entry *)memp_malloc(MEMP_LOCALHOSTLIST);
+    LWIP_ASSERT("mem-error in dns_init_local", entry != NULL);
+    if (entry != NULL) {
+      entry->name = (char*)entry + sizeof(struct local_hostlist_entry);
+      MEMCPY((char*)entry->name, init_entry->name, namelen);
+      ((char*)entry->name)[namelen] = 0;
+      entry->addr = init_entry->addr;
+      entry->next = local_hostlist_dynamic;
+      local_hostlist_dynamic = entry;
+    }
+  }
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC && defined(DNS_LOCAL_HOSTLIST_INIT) */
+}
+
+/**
+ * Scans the local host-list for a hostname.
+ *
+ * @param hostname Hostname to look for in the local host-list
+ * @return The first IP address for the hostname in the local host-list or
+ *         IPADDR_NONE if not found.
+ */
+static u32_t
+dns_lookup_local(const char *hostname)
+{
+#if DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+  struct local_hostlist_entry *entry = local_hostlist_dynamic;
+  while(entry != NULL) {
+    if(strcmp(entry->name, hostname) == 0) {
+      return ip4_addr_get_u32(&entry->addr);
+    }
+    entry = entry->next;
+  }
+#else /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+  int i;
+  for (i = 0; i < sizeof(local_hostlist_static) / sizeof(struct local_hostlist_entry); i++) {
+    if(strcmp(local_hostlist_static[i].name, hostname) == 0) {
+      return ip4_addr_get_u32(&local_hostlist_static[i].addr);
+    }
+  }
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+  return IPADDR_NONE;
+}
+
+#if DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+/** Remove all entries from the local host-list for a specific hostname
+ * and/or IP addess
+ *
+ * @param hostname hostname for which entries shall be removed from the local
+ *                 host-list
+ * @param addr address for which entries shall be removed from the local host-list
+ * @return the number of removed entries
+ */
+int
+dns_local_removehost(const char *hostname, const ip_addr_t *addr)
+{
+  int removed = 0;
+  struct local_hostlist_entry *entry = local_hostlist_dynamic;
+  struct local_hostlist_entry *last_entry = NULL;
+  while (entry != NULL) {
+    if (((hostname == NULL) || !strcmp(entry->name, hostname)) &&
+        ((addr == NULL) || ip_addr_cmp(&entry->addr, addr))) {
+      struct local_hostlist_entry *free_entry;
+      if (last_entry != NULL) {
+        last_entry->next = entry->next;
+      } else {
+        local_hostlist_dynamic = entry->next;
+      }
+      free_entry = entry;
+      entry = entry->next;
+      memp_free(MEMP_LOCALHOSTLIST, free_entry);
+      removed++;
+    } else {
+      last_entry = entry;
+      entry = entry->next;
+    }
+  }
+  return removed;
+}
+
+/**
+ * Add a hostname/IP address pair to the local host-list.
+ * Duplicates are not checked.
+ *
+ * @param hostname hostname of the new entry
+ * @param addr IP address of the new entry
+ * @return ERR_OK if succeeded or ERR_MEM on memory error
+ */
+err_t
+dns_local_addhost(const char *hostname, const ip_addr_t *addr)
+{
+  struct local_hostlist_entry *entry;
+  size_t namelen;
+  LWIP_ASSERT("invalid host name (NULL)", hostname != NULL);
+  namelen = strlen(hostname);
+  LWIP_ASSERT("namelen <= DNS_LOCAL_HOSTLIST_MAX_NAMELEN", namelen <= DNS_LOCAL_HOSTLIST_MAX_NAMELEN);
+  entry = (struct local_hostlist_entry *)memp_malloc(MEMP_LOCALHOSTLIST);
+  if (entry == NULL) {
+    return ERR_MEM;
+  }
+  entry->name = (char*)entry + sizeof(struct local_hostlist_entry);
+  MEMCPY((char*)entry->name, hostname, namelen);
+  ((char*)entry->name)[namelen] = 0;
+  ip_addr_copy(entry->addr, *addr);
+  entry->next = local_hostlist_dynamic;
+  local_hostlist_dynamic = entry;
+  return ERR_OK;
+}
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC*/
+#endif /* DNS_LOCAL_HOSTLIST */
+
+/**
+ * Look up a hostname in the array of known hostnames.
+ *
+ * @note This function only looks in the internal array of known
+ * hostnames, it does not send out a query for the hostname if none
+ * was found. The function dns_enqueue() can be used to send a query
+ * for a hostname.
+ *
+ * @param name the hostname to look up
+ * @return the hostname's IP address, as u32_t (instead of ip_addr_t to
+ *         better check for failure: != IPADDR_NONE) or IPADDR_NONE if the hostname
+ *         was not found in the cached dns_table.
+ */
+static u32_t
+dns_lookup(const char *name)
+{
+  u8_t i;
+#if DNS_LOCAL_HOSTLIST || defined(DNS_LOOKUP_LOCAL_EXTERN)
+  u32_t addr;
+#endif /* DNS_LOCAL_HOSTLIST || defined(DNS_LOOKUP_LOCAL_EXTERN) */
+#if DNS_LOCAL_HOSTLIST
+  if ((addr = dns_lookup_local(name)) != IPADDR_NONE) {
+    return addr;
+  }
+#endif /* DNS_LOCAL_HOSTLIST */
+#ifdef DNS_LOOKUP_LOCAL_EXTERN
+  if((addr = DNS_LOOKUP_LOCAL_EXTERN(name)) != IPADDR_NONE) {
+    return addr;
+  }
+#endif /* DNS_LOOKUP_LOCAL_EXTERN */
+
+  /* Walk through name list, return entry if found. If not, return NULL. */
+  for (i = 0; i < DNS_TABLE_SIZE; ++i) {
+    if ((dns_table[i].state == DNS_STATE_DONE) &&
+        (strcmp(name, dns_table[i].name) == 0)) {
+      LWIP_DEBUGF(DNS_DEBUG, ("dns_lookup: \"%s\": found = ", name));
+      ip_addr_debug_print(DNS_DEBUG, &(dns_table[i].ipaddr));
+      LWIP_DEBUGF(DNS_DEBUG, ("\n"));
+      return ip4_addr_get_u32(&dns_table[i].ipaddr);
+    }
+  }
+
+  return IPADDR_NONE;
+}
+
+#if DNS_DOES_NAME_CHECK
+/**
+ * Compare the "dotted" name "query" with the encoded name "response"
+ * to make sure an answer from the DNS server matches the current dns_table
+ * entry (otherwise, answers might arrive late for hostname not on the list
+ * any more).
+ *
+ * @param query hostname (not encoded) from the dns_table
+ * @param response encoded hostname in the DNS response
+ * @return 0: names equal; 1: names differ
+ */
+static u8_t
+dns_compare_name(unsigned char *query, unsigned char *response)
+{
+  unsigned char n;
+
+  do {
+    n = *response++;
+    /** @see RFC 1035 - 4.1.4. Message compression */
+    if ((n & 0xc0) == 0xc0) {
+      /* Compressed name */
+      break;
+    } else {
+      /* Not compressed name */
+      while (n > 0) {
+        if ((*query) != (*response)) {
+          return 1;
+        }
+        ++response;
+        ++query;
+        --n;
+      };
+      ++query;
+    }
+  } while (*response != 0);
+
+  return 0;
+}
+#endif /* DNS_DOES_NAME_CHECK */
+
+/**
+ * Walk through a compact encoded DNS name and return the end of the name.
+ *
+ * @param query encoded DNS name in the DNS server response
+ * @return end of the name
+ */
+static unsigned char *
+dns_parse_name(unsigned char *query)
+{
+  unsigned char n;
+
+  do {
+    n = *query++;
+    /** @see RFC 1035 - 4.1.4. Message compression */
+    if ((n & 0xc0) == 0xc0) {
+      /* Compressed name */
+      break;
+    } else {
+      /* Not compressed name */
+      while (n > 0) {
+        ++query;
+        --n;
+      };
+    }
+  } while (*query != 0);
+
+  return query + 1;
+}
+
+/**
+ * Send a DNS query packet.
+ *
+ * @param numdns index of the DNS server in the dns_servers table
+ * @param name hostname to query
+ * @param id index of the hostname in dns_table, used as transaction ID in the
+ *        DNS query packet
+ * @return ERR_OK if packet is sent; an err_t indicating the problem otherwise
+ */
+static err_t
+dns_send(u8_t numdns, const char* name, u8_t id)
+{
+  err_t err;
+  struct dns_hdr *hdr;
+  struct dns_query qry;
+  struct pbuf *p;
+  char *query, *nptr;
+  const char *pHostname;
+  u8_t n;
+
+  LWIP_DEBUGF(DNS_DEBUG, ("dns_send: dns_servers[%"U16_F"] \"%s\": request\n",
+              (u16_t)(numdns), name));
+  LWIP_ASSERT("dns server out of array", numdns < DNS_MAX_SERVERS);
+  LWIP_ASSERT("dns server has no IP address set", !ip_addr_isany(&dns_servers[numdns]));
+
+  /* if here, we have either a new query or a retry on a previous query to process */
+  p = pbuf_alloc(PBUF_TRANSPORT, SIZEOF_DNS_HDR + DNS_MAX_NAME_LENGTH + 1 +
+                 SIZEOF_DNS_QUERY, PBUF_RAM);
+  if (p != NULL) {
+    u16_t realloc_size;
+    LWIP_ASSERT("pbuf must be in one piece", p->next == NULL);
+    /* fill dns header */
+    hdr = (struct dns_hdr*)p->payload;
+    memset(hdr, 0, SIZEOF_DNS_HDR);
+    hdr->id = htons(id);
+    hdr->flags1 = DNS_FLAG1_RD;
+    hdr->numquestions = PP_HTONS(1);
+    query = (char*)hdr + SIZEOF_DNS_HDR;
+    pHostname = name;
+    --pHostname;
+
+    /* convert hostname into suitable query format. */
+    do {
+      ++pHostname;
+      nptr = query;
+      ++query;
+      for(n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
+        *query = *pHostname;
+        ++query;
+        ++n;
+      }
+      *nptr = n;
+    } while(*pHostname != 0);
+    *query++='\0';
+
+    /* fill dns query */
+    qry.type = PP_HTONS(DNS_RRTYPE_A);
+    qry.cls = PP_HTONS(DNS_RRCLASS_IN);
+    SMEMCPY(query, &qry, SIZEOF_DNS_QUERY);
+
+    /* resize pbuf to the exact dns query */
+    realloc_size = (u16_t)((query + SIZEOF_DNS_QUERY) - ((char*)(p->payload)));
+    LWIP_ASSERT("p->tot_len >= realloc_size", p->tot_len >= realloc_size);
+    pbuf_realloc(p, realloc_size);
+
+    /* connect to the server for faster receiving */
+    udp_connect(dns_pcb, &dns_servers[numdns], DNS_SERVER_PORT);
+    /* send dns packet */
+    err = udp_sendto(dns_pcb, p, &dns_servers[numdns], DNS_SERVER_PORT);
+
+    /* free pbuf */
+    pbuf_free(p);
+  } else {
+    err = ERR_MEM;
+  }
+
+  return err;
+}
+
+/**
+ * dns_check_entry() - see if pEntry has not yet been queried and, if so, sends out a query.
+ * Check an entry in the dns_table:
+ * - send out query for new entries
+ * - retry old pending entries on timeout (also with different servers)
+ * - remove completed entries from the table if their TTL has expired
+ *
+ * @param i index of the dns_table entry to check
+ */
+static void
+dns_check_entry(u8_t i)
+{
+  err_t err;
+  struct dns_table_entry *pEntry = &dns_table[i];
+
+  LWIP_ASSERT("array index out of bounds", i < DNS_TABLE_SIZE);
+
+  switch(pEntry->state) {
+
+    case DNS_STATE_NEW: {
+      /* initialize new entry */
+      pEntry->state   = DNS_STATE_ASKING;
+      pEntry->numdns  = 0;
+      pEntry->tmr     = 1;
+      pEntry->retries = 0;
+      
+      /* send DNS packet for this entry */
+      err = dns_send(pEntry->numdns, pEntry->name, i);
+      if (err != ERR_OK) {
+        LWIP_DEBUGF(DNS_DEBUG | LWIP_DBG_LEVEL_WARNING,
+                    ("dns_send returned error: %s\n", lwip_strerr(err)));
+      }
+      break;
+    }
+
+    case DNS_STATE_ASKING: {
+      if (--pEntry->tmr == 0) {
+        if (++pEntry->retries == DNS_MAX_RETRIES) {
+          if ((pEntry->numdns+1<DNS_MAX_SERVERS) && !ip_addr_isany(&dns_servers[pEntry->numdns+1])) {
+            /* change of server */
+            pEntry->numdns++;
+            pEntry->tmr     = 1;
+            pEntry->retries = 0;
+            break;
+          } else {
+            LWIP_DEBUGF(DNS_DEBUG, ("dns_check_entry: \"%s\": timeout\n", pEntry->name));
+            /* call specified callback function if provided */
+            if (pEntry->found)
+              (*pEntry->found)(pEntry->name, NULL, pEntry->arg);
+            /* flush this entry */
+            pEntry->state   = DNS_STATE_UNUSED;
+            pEntry->found   = NULL;
+            break;
+          }
+        }
+
+        /* wait longer for the next retry */
+        pEntry->tmr = pEntry->retries;
+
+        /* send DNS packet for this entry */
+        err = dns_send(pEntry->numdns, pEntry->name, i);
+        if (err != ERR_OK) {
+          LWIP_DEBUGF(DNS_DEBUG | LWIP_DBG_LEVEL_WARNING,
+                      ("dns_send returned error: %s\n", lwip_strerr(err)));
+        }
+      }
+      break;
+    }
+
+    case DNS_STATE_DONE: {
+      /* if the time to live is nul */
+      if ((pEntry->ttl == 0) || (--pEntry->ttl == 0)) {
+        LWIP_DEBUGF(DNS_DEBUG, ("dns_check_entry: \"%s\": flush\n", pEntry->name));
+        /* flush this entry */
+        pEntry->state = DNS_STATE_UNUSED;
+        pEntry->found = NULL;
+      }
+      break;
+    }
+    case DNS_STATE_UNUSED:
+      /* nothing to do */
+      break;
+    default:
+      LWIP_ASSERT("unknown dns_table entry state:", 0);
+      break;
+  }
+}
+
+/**
+ * Call dns_check_entry for each entry in dns_table - check all entries.
+ */
+static void
+dns_check_entries(void)
+{
+  u8_t i;
+
+  for (i = 0; i < DNS_TABLE_SIZE; ++i) {
+    dns_check_entry(i);
+  }
+}
+
+/**
+ * Receive input function for DNS response packets arriving for the dns UDP pcb.
+ *
+ * @params see udp.h
+ */
+static void
+dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port)
+{
+  u16_t i;
+  char *pHostname;
+  struct dns_hdr *hdr;
+  struct dns_answer ans;
+  struct dns_table_entry *pEntry;
+  u16_t nquestions, nanswers;
+
+  LWIP_UNUSED_ARG(arg);
+  LWIP_UNUSED_ARG(pcb);
+  LWIP_UNUSED_ARG(addr);
+  LWIP_UNUSED_ARG(port);
+
+  /* is the dns message too big ? */
+  if (p->tot_len > DNS_MSG_SIZE) {
+    LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: pbuf too big\n"));
+    /* free pbuf and return */
+    goto memerr;
+  }
+
+  /* is the dns message big enough ? */
+  if (p->tot_len < (SIZEOF_DNS_HDR + SIZEOF_DNS_QUERY + SIZEOF_DNS_ANSWER)) {
+    LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: pbuf too small\n"));
+    /* free pbuf and return */
+    goto memerr;
+  }
+
+  /* copy dns payload inside static buffer for processing */ 
+  if (pbuf_copy_partial(p, dns_payload, p->tot_len, 0) == p->tot_len) {
+    /* The ID in the DNS header should be our entry into the name table. */
+    hdr = (struct dns_hdr*)dns_payload;
+    i = htons(hdr->id);
+    if (i < DNS_TABLE_SIZE) {
+      pEntry = &dns_table[i];
+      if(pEntry->state == DNS_STATE_ASKING) {
+        /* This entry is now completed. */
+        pEntry->state = DNS_STATE_DONE;
+        pEntry->err   = hdr->flags2 & DNS_FLAG2_ERR_MASK;
+
+        /* We only care about the question(s) and the answers. The authrr
+           and the extrarr are simply discarded. */
+        nquestions = htons(hdr->numquestions);
+        nanswers   = htons(hdr->numanswers);
+
+        /* Check for error. If so, call callback to inform. */
+        if (((hdr->flags1 & DNS_FLAG1_RESPONSE) == 0) || (pEntry->err != 0) || (nquestions != 1)) {
+          LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": error in flags\n", pEntry->name));
+          /* call callback to indicate error, clean up memory and return */
+          goto responseerr;
+        }
+
+#if DNS_DOES_NAME_CHECK
+        /* Check if the name in the "question" part match with the name in the entry. */
+        if (dns_compare_name((unsigned char *)(pEntry->name), (unsigned char *)dns_payload + SIZEOF_DNS_HDR) != 0) {
+          LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response not match to query\n", pEntry->name));
+          /* call callback to indicate error, clean up memory and return */
+          goto responseerr;
+        }
+#endif /* DNS_DOES_NAME_CHECK */
+
+        /* Skip the name in the "question" part */
+        pHostname = (char *) dns_parse_name((unsigned char *)dns_payload + SIZEOF_DNS_HDR) + SIZEOF_DNS_QUERY;
+
+        while (nanswers > 0) {
+          /* skip answer resource record's host name */
+          pHostname = (char *) dns_parse_name((unsigned char *)pHostname);
+
+          /* Check for IP address type and Internet class. Others are discarded. */
+          SMEMCPY(&ans, pHostname, SIZEOF_DNS_ANSWER);
+          if((ans.type == PP_HTONS(DNS_RRTYPE_A)) && (ans.cls == PP_HTONS(DNS_RRCLASS_IN)) &&
+             (ans.len == PP_HTONS(sizeof(ip_addr_t))) ) {
+            /* read the answer resource record's TTL, and maximize it if needed */
+            pEntry->ttl = ntohl(ans.ttl);
+            if (pEntry->ttl > DNS_MAX_TTL) {
+              pEntry->ttl = DNS_MAX_TTL;
+            }
+            /* read the IP address after answer resource record's header */
+            SMEMCPY(&(pEntry->ipaddr), (pHostname+SIZEOF_DNS_ANSWER), sizeof(ip_addr_t));
+            LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response = ", pEntry->name));
+            ip_addr_debug_print(DNS_DEBUG, (&(pEntry->ipaddr)));
+            LWIP_DEBUGF(DNS_DEBUG, ("\n"));
+            /* call specified callback function if provided */
+            if (pEntry->found) {
+              (*pEntry->found)(pEntry->name, &pEntry->ipaddr, pEntry->arg);
+            }
+            if (pEntry->ttl == 0) {
+              /* RFC 883, page 29: "Zero values are
+                 interpreted to mean that the RR can only be used for the
+                 transaction in progress, and should not be cached."
+                 -> flush this entry now */
+              goto flushentry;
+            }
+            /* deallocate memory and return */
+            goto memerr;
+          } else {
+            pHostname = pHostname + SIZEOF_DNS_ANSWER + htons(ans.len);
+          }
+          --nanswers;
+        }
+        LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": error in response\n", pEntry->name));
+        /* call callback to indicate error, clean up memory and return */
+        goto responseerr;
+      }
+    }
+  }
+
+  /* deallocate memory and return */
+  goto memerr;
+
+responseerr:
+  /* ERROR: call specified callback function with NULL as name to indicate an error */
+  if (pEntry->found) {
+    (*pEntry->found)(pEntry->name, NULL, pEntry->arg);
+  }
+flushentry:
+  /* flush this entry */
+  pEntry->state = DNS_STATE_UNUSED;
+  pEntry->found = NULL;
+
+memerr:
+  /* free pbuf */
+  pbuf_free(p);
+  return;
+}
+
+/**
+ * Queues a new hostname to resolve and sends out a DNS query for that hostname
+ *
+ * @param name the hostname that is to be queried
+ * @param hostnamelen length of the hostname
+ * @param found a callback founction to be called on success, failure or timeout
+ * @param callback_arg argument to pass to the callback function
+ * @return @return a err_t return code.
+ */
+static err_t
+dns_enqueue(const char *name, size_t hostnamelen, dns_found_callback found,
+            void *callback_arg)
+{
+  u8_t i;
+  u8_t lseq, lseqi;
+  struct dns_table_entry *pEntry = NULL;
+  size_t namelen;
+
+  /* search an unused entry, or the oldest one */
+  lseq = lseqi = 0;
+  for (i = 0; i < DNS_TABLE_SIZE; ++i) {
+    pEntry = &dns_table[i];
+    /* is it an unused entry ? */
+    if (pEntry->state == DNS_STATE_UNUSED)
+      break;
+
+    /* check if this is the oldest completed entry */
+    if (pEntry->state == DNS_STATE_DONE) {
+      if ((dns_seqno - pEntry->seqno) > lseq) {
+        lseq = dns_seqno - pEntry->seqno;
+        lseqi = i;
+      }
+    }
+  }
+
+  /* if we don't have found an unused entry, use the oldest completed one */
+  if (i == DNS_TABLE_SIZE) {
+    if ((lseqi >= DNS_TABLE_SIZE) || (dns_table[lseqi].state != DNS_STATE_DONE)) {
+      /* no entry can't be used now, table is full */
+      LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": DNS entries table is full\n", name));
+      return ERR_MEM;
+    } else {
+      /* use the oldest completed one */
+      i = lseqi;
+      pEntry = &dns_table[i];
+    }
+  }
+
+  /* use this entry */
+  LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": use DNS entry %"U16_F"\n", name, (u16_t)(i)));
+
+  /* fill the entry */
+  pEntry->state = DNS_STATE_NEW;
+  pEntry->seqno = dns_seqno++;
+  pEntry->found = found;
+  pEntry->arg   = callback_arg;
+  namelen = LWIP_MIN(hostnamelen, DNS_MAX_NAME_LENGTH-1);
+  MEMCPY(pEntry->name, name, namelen);
+  pEntry->name[namelen] = 0;
+
+  /* force to send query without waiting timer */
+  dns_check_entry(i);
+
+  /* dns query is enqueued */
+  return ERR_INPROGRESS;
+}
+
+/**
+ * Resolve a hostname (string) into an IP address.
+ * NON-BLOCKING callback version for use with raw API!!!
+ *
+ * Returns immediately with one of err_t return codes:
+ * - ERR_OK if hostname is a valid IP address string or the host
+ *   name is already in the local names table.
+ * - ERR_INPROGRESS enqueue a request to be sent to the DNS server
+ *   for resolution if no errors are present.
+ * - ERR_ARG: dns client not initialized or invalid hostname
+ *
+ * @param hostname the hostname that is to be queried
+ * @param addr pointer to a ip_addr_t where to store the address if it is already
+ *             cached in the dns_table (only valid if ERR_OK is returned!)
+ * @param found a callback function to be called on success, failure or timeout (only if
+ *              ERR_INPROGRESS is returned!)
+ * @param callback_arg argument to pass to the callback function
+ * @return a err_t return code.
+ */
+err_t
+dns_gethostbyname(const char *hostname, ip_addr_t *addr, dns_found_callback found,
+                  void *callback_arg)
+{
+  u32_t ipaddr;
+  size_t hostnamelen;
+  /* not initialized or no valid server yet, or invalid addr pointer
+   * or invalid hostname or invalid hostname length */
+  if ((dns_pcb == NULL) || (addr == NULL) ||
+      (!hostname) || (!hostname[0])) {
+    return ERR_ARG;
+  }
+  hostnamelen = strlen(hostname);
+  if (hostnamelen >= DNS_MAX_NAME_LENGTH) {
+    return ERR_ARG;
+  }
+  
+
+#if LWIP_HAVE_LOOPIF
+  if (strcmp(hostname, "localhost")==0) {
+    ip_addr_set_loopback(addr);
+    return ERR_OK;
+  }
+#endif /* LWIP_HAVE_LOOPIF */
+
+  /* host name already in octet notation? set ip addr and return ERR_OK */
+  ipaddr = ipaddr_addr(hostname);
+  if (ipaddr == IPADDR_NONE) {
+    /* already have this address cached? */
+    ipaddr = dns_lookup(hostname);
+  }
+  if (ipaddr != IPADDR_NONE) {
+    ip4_addr_set_u32(addr, ipaddr);
+    return ERR_OK;
+  }
+
+  /* queue query with specified callback */
+  return dns_enqueue(hostname, hostnamelen, found, callback_arg);
+}
+
+#endif /* LWIP_DNS */
diff --git a/external/badvpn_dns/lwip/src/core/inet_chksum.c b/external/badvpn_dns/lwip/src/core/inet_chksum.c
new file mode 100644
index 0000000..8bc42c1
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/inet_chksum.c
@@ -0,0 +1,545 @@
+/**
+ * @file
+ * Incluse internet checksum functions.
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/inet_chksum.h"
+#include "lwip/def.h"
+
+#include <stddef.h>
+#include <string.h>
+
+/* These are some reference implementations of the checksum algorithm, with the
+ * aim of being simple, correct and fully portable. Checksumming is the
+ * first thing you would want to optimize for your platform. If you create
+ * your own version, link it in and in your cc.h put:
+ * 
+ * #define LWIP_CHKSUM <your_checksum_routine> 
+ *
+ * Or you can select from the implementations below by defining
+ * LWIP_CHKSUM_ALGORITHM to 1, 2 or 3.
+ */
+
+#ifndef LWIP_CHKSUM
+# define LWIP_CHKSUM lwip_standard_chksum
+# ifndef LWIP_CHKSUM_ALGORITHM
+#  define LWIP_CHKSUM_ALGORITHM 2
+# endif
+u16_t lwip_standard_chksum(void *dataptr, int len);
+#endif
+/* If none set: */
+#ifndef LWIP_CHKSUM_ALGORITHM
+# define LWIP_CHKSUM_ALGORITHM 0
+#endif
+
+#if (LWIP_CHKSUM_ALGORITHM == 1) /* Version #1 */
+/**
+ * lwip checksum
+ *
+ * @param dataptr points to start of data to be summed at any boundary
+ * @param len length of data to be summed
+ * @return host order (!) lwip checksum (non-inverted Internet sum) 
+ *
+ * @note accumulator size limits summable length to 64k
+ * @note host endianess is irrelevant (p3 RFC1071)
+ */
+u16_t
+lwip_standard_chksum(void *dataptr, u16_t len)
+{
+  u32_t acc;
+  u16_t src;
+  u8_t *octetptr;
+
+  acc = 0;
+  /* dataptr may be at odd or even addresses */
+  octetptr = (u8_t*)dataptr;
+  while (len > 1) {
+    /* declare first octet as most significant
+       thus assume network order, ignoring host order */
+    src = (*octetptr) << 8;
+    octetptr++;
+    /* declare second octet as least significant */
+    src |= (*octetptr);
+    octetptr++;
+    acc += src;
+    len -= 2;
+  }
+  if (len > 0) {
+    /* accumulate remaining octet */
+    src = (*octetptr) << 8;
+    acc += src;
+  }
+  /* add deferred carry bits */
+  acc = (acc >> 16) + (acc & 0x0000ffffUL);
+  if ((acc & 0xffff0000UL) != 0) {
+    acc = (acc >> 16) + (acc & 0x0000ffffUL);
+  }
+  /* This maybe a little confusing: reorder sum using htons()
+     instead of ntohs() since it has a little less call overhead.
+     The caller must invert bits for Internet sum ! */
+  return htons((u16_t)acc);
+}
+#endif
+
+#if (LWIP_CHKSUM_ALGORITHM == 2) /* Alternative version #2 */
+/*
+ * Curt McDowell
+ * Broadcom Corp.
+ * csm@xxxxxxxxxxxx
+ *
+ * IP checksum two bytes at a time with support for
+ * unaligned buffer.
+ * Works for len up to and including 0x20000.
+ * by Curt McDowell, Broadcom Corp. 12/08/2005
+ *
+ * @param dataptr points to start of data to be summed at any boundary
+ * @param len length of data to be summed
+ * @return host order (!) lwip checksum (non-inverted Internet sum) 
+ */
+
+u16_t
+lwip_standard_chksum(void *dataptr, int len)
+{
+  u8_t *pb = (u8_t *)dataptr;
+  u16_t *ps, t = 0;
+  u32_t sum = 0;
+  int odd = ((mem_ptr_t)pb & 1);
+
+  /* Get aligned to u16_t */
+  if (odd && len > 0) {
+    ((u8_t *)&t)[1] = *pb++;
+    len--;
+  }
+
+  /* Add the bulk of the data */
+  ps = (u16_t *)(void *)pb;
+  while (len > 1) {
+    sum += *ps++;
+    len -= 2;
+  }
+
+  /* Consume left-over byte, if any */
+  if (len > 0) {
+    ((u8_t *)&t)[0] = *(u8_t *)ps;
+  }
+
+  /* Add end bytes */
+  sum += t;
+
+  /* Fold 32-bit sum to 16 bits
+     calling this twice is propably faster than if statements... */
+  sum = FOLD_U32T(sum);
+  sum = FOLD_U32T(sum);
+
+  /* Swap if alignment was odd */
+  if (odd) {
+    sum = SWAP_BYTES_IN_WORD(sum);
+  }
+
+  return (u16_t)sum;
+}
+#endif
+
+#if (LWIP_CHKSUM_ALGORITHM == 3) /* Alternative version #3 */
+/**
+ * An optimized checksum routine. Basically, it uses loop-unrolling on
+ * the checksum loop, treating the head and tail bytes specially, whereas
+ * the inner loop acts on 8 bytes at a time. 
+ *
+ * @arg start of buffer to be checksummed. May be an odd byte address.
+ * @len number of bytes in the buffer to be checksummed.
+ * @return host order (!) lwip checksum (non-inverted Internet sum) 
+ * 
+ * by Curt McDowell, Broadcom Corp. December 8th, 2005
+ */
+
+u16_t
+lwip_standard_chksum(void *dataptr, int len)
+{
+  u8_t *pb = (u8_t *)dataptr;
+  u16_t *ps, t = 0;
+  u32_t *pl;
+  u32_t sum = 0, tmp;
+  /* starts at odd byte address? */
+  int odd = ((mem_ptr_t)pb & 1);
+
+  if (odd && len > 0) {
+    ((u8_t *)&t)[1] = *pb++;
+    len--;
+  }
+
+  ps = (u16_t *)pb;
+
+  if (((mem_ptr_t)ps & 3) && len > 1) {
+    sum += *ps++;
+    len -= 2;
+  }
+
+  pl = (u32_t *)ps;
+
+  while (len > 7)  {
+    tmp = sum + *pl++;          /* ping */
+    if (tmp < sum) {
+      tmp++;                    /* add back carry */
+    }
+
+    sum = tmp + *pl++;          /* pong */
+    if (sum < tmp) {
+      sum++;                    /* add back carry */
+    }
+
+    len -= 8;
+  }
+
+  /* make room in upper bits */
+  sum = FOLD_U32T(sum);
+
+  ps = (u16_t *)pl;
+
+  /* 16-bit aligned word remaining? */
+  while (len > 1) {
+    sum += *ps++;
+    len -= 2;
+  }
+
+  /* dangling tail byte remaining? */
+  if (len > 0) {                /* include odd byte */
+    ((u8_t *)&t)[0] = *(u8_t *)ps;
+  }
+
+  sum += t;                     /* add end bytes */
+
+  /* Fold 32-bit sum to 16 bits
+     calling this twice is propably faster than if statements... */
+  sum = FOLD_U32T(sum);
+  sum = FOLD_U32T(sum);
+
+  if (odd) {
+    sum = SWAP_BYTES_IN_WORD(sum);
+  }
+
+  return (u16_t)sum;
+}
+#endif
+
+/** Parts of the pseudo checksum which are common to IPv4 and IPv6 */
+static u16_t
+inet_cksum_pseudo_base(struct pbuf *p, u8_t proto, u16_t proto_len, u32_t acc)
+{
+  struct pbuf *q;
+  u8_t swapped = 0;
+
+  /* iterate through all pbuf in chain */
+  for(q = p; q != NULL; q = q->next) {
+    LWIP_DEBUGF(INET_DEBUG, ("inet_chksum_pseudo(): checksumming pbuf %p (has next %p) \n",
+      (void *)q, (void *)q->next));
+    acc += LWIP_CHKSUM(q->payload, q->len);
+    /*LWIP_DEBUGF(INET_DEBUG, ("inet_chksum_pseudo(): unwrapped lwip_chksum()=%"X32_F" \n", acc));*/
+    /* just executing this next line is probably faster that the if statement needed
+       to check whether we really need to execute it, and does no harm */
+    acc = FOLD_U32T(acc);
+    if (q->len % 2 != 0) {
+      swapped = 1 - swapped;
+      acc = SWAP_BYTES_IN_WORD(acc);
+    }
+    /*LWIP_DEBUGF(INET_DEBUG, ("inet_chksum_pseudo(): wrapped lwip_chksum()=%"X32_F" \n", acc));*/
+  }
+
+  if (swapped) {
+    acc = SWAP_BYTES_IN_WORD(acc);
+  }
+
+  acc += (u32_t)htons((u16_t)proto);
+  acc += (u32_t)htons(proto_len);
+
+  /* Fold 32-bit sum to 16 bits
+     calling this twice is propably faster than if statements... */
+  acc = FOLD_U32T(acc);
+  acc = FOLD_U32T(acc);
+  LWIP_DEBUGF(INET_DEBUG, ("inet_chksum_pseudo(): pbuf chain lwip_chksum()=%"X32_F"\n", acc));
+  return (u16_t)~(acc & 0xffffUL);
+}
+
+/* inet_chksum_pseudo:
+ *
+ * Calculates the pseudo Internet checksum used by TCP and UDP for a pbuf chain.
+ * IP addresses are expected to be in network byte order.
+ *
+ * @param p chain of pbufs over that a checksum should be calculated (ip data part)
+ * @param src source ip address (used for checksum of pseudo header)
+ * @param dst destination ip address (used for checksum of pseudo header)
+ * @param proto ip protocol (used for checksum of pseudo header)
+ * @param proto_len length of the ip data part (used for checksum of pseudo header)
+ * @return checksum (as u16_t) to be saved directly in the protocol header
+ */
+u16_t
+inet_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len,
+       ip_addr_t *src, ip_addr_t *dest)
+{
+  u32_t acc;
+  u32_t addr;
+
+  addr = ip4_addr_get_u32(src);
+  acc = (addr & 0xffffUL);
+  acc += ((addr >> 16) & 0xffffUL);
+  addr = ip4_addr_get_u32(dest);
+  acc += (addr & 0xffffUL);
+  acc += ((addr >> 16) & 0xffffUL);
+  /* fold down to 16 bits */
+  acc = FOLD_U32T(acc);
+  acc = FOLD_U32T(acc);
+
+  return inet_cksum_pseudo_base(p, proto, proto_len, acc);
+}
+#if LWIP_IPV6
+/**
+ * Calculates the checksum with IPv6 pseudo header used by TCP and UDP for a pbuf chain.
+ * IPv6 addresses are expected to be in network byte order.
+ *
+ * @param p chain of pbufs over that a checksum should be calculated (ip data part)
+ * @param src source ipv6 address (used for checksum of pseudo header)
+ * @param dst destination ipv6 address (used for checksum of pseudo header)
+ * @param proto ipv6 protocol/next header (used for checksum of pseudo header)
+ * @param proto_len length of the ipv6 payload (used for checksum of pseudo header)
+ * @return checksum (as u16_t) to be saved directly in the protocol header
+ */
+u16_t
+ip6_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len,
+       ip6_addr_t *src, ip6_addr_t *dest)
+{
+  u32_t acc = 0;
+  u32_t addr;
+  u8_t addr_part;
+
+  for (addr_part = 0; addr_part < 4; addr_part++) {
+    addr = src->addr[addr_part];
+    acc += (addr & 0xffffUL);
+    acc += ((addr >> 16) & 0xffffUL);
+    addr = dest->addr[addr_part];
+    acc += (addr & 0xffffUL);
+    acc += ((addr >> 16) & 0xffffUL);
+  }
+  /* fold down to 16 bits */
+  acc = FOLD_U32T(acc);
+  acc = FOLD_U32T(acc);
+
+  return inet_cksum_pseudo_base(p, proto, proto_len, acc);
+}
+#endif /* LWIP_IPV6 */
+
+/** Parts of the pseudo checksum which are common to IPv4 and IPv6 */
+static u16_t
+inet_cksum_pseudo_partial_base(struct pbuf *p, u8_t proto, u16_t proto_len,
+       u16_t chksum_len, u32_t acc)
+{
+  struct pbuf *q;
+  u8_t swapped = 0;
+  u16_t chklen;
+
+  /* iterate through all pbuf in chain */
+  for(q = p; (q != NULL) && (chksum_len > 0); q = q->next) {
+    LWIP_DEBUGF(INET_DEBUG, ("inet_chksum_pseudo(): checksumming pbuf %p (has next %p) \n",
+      (void *)q, (void *)q->next));
+    chklen = q->len;
+    if (chklen > chksum_len) {
+      chklen = chksum_len;
+    }
+    acc += LWIP_CHKSUM(q->payload, chklen);
+    chksum_len -= chklen;
+    LWIP_ASSERT("delete me", chksum_len < 0x7fff);
+    /*LWIP_DEBUGF(INET_DEBUG, ("inet_chksum_pseudo(): unwrapped lwip_chksum()=%"X32_F" \n", acc));*/
+    /* fold the upper bit down */
+    acc = FOLD_U32T(acc);
+    if (q->len % 2 != 0) {
+      swapped = 1 - swapped;
+      acc = SWAP_BYTES_IN_WORD(acc);
+    }
+    /*LWIP_DEBUGF(INET_DEBUG, ("inet_chksum_pseudo(): wrapped lwip_chksum()=%"X32_F" \n", acc));*/
+  }
+
+  if (swapped) {
+    acc = SWAP_BYTES_IN_WORD(acc);
+  }
+
+  acc += (u32_t)htons((u16_t)proto);
+  acc += (u32_t)htons(proto_len);
+
+  /* Fold 32-bit sum to 16 bits
+     calling this twice is propably faster than if statements... */
+  acc = FOLD_U32T(acc);
+  acc = FOLD_U32T(acc);
+  LWIP_DEBUGF(INET_DEBUG, ("inet_chksum_pseudo(): pbuf chain lwip_chksum()=%"X32_F"\n", acc));
+  return (u16_t)~(acc & 0xffffUL);
+}
+
+/* inet_chksum_pseudo_partial:
+ *
+ * Calculates the pseudo Internet checksum used by TCP and UDP for a pbuf chain.
+ * IP addresses are expected to be in network byte order.
+ *
+ * @param p chain of pbufs over that a checksum should be calculated (ip data part)
+ * @param src source ip address (used for checksum of pseudo header)
+ * @param dst destination ip address (used for checksum of pseudo header)
+ * @param proto ip protocol (used for checksum of pseudo header)
+ * @param proto_len length of the ip data part (used for checksum of pseudo header)
+ * @return checksum (as u16_t) to be saved directly in the protocol header
+ */
+u16_t
+inet_chksum_pseudo_partial(struct pbuf *p, u8_t proto, u16_t proto_len,
+       u16_t chksum_len, ip_addr_t *src, ip_addr_t *dest)
+{
+  u32_t acc;
+  u32_t addr;
+
+  addr = ip4_addr_get_u32(src);
+  acc = (addr & 0xffffUL);
+  acc += ((addr >> 16) & 0xffffUL);
+  addr = ip4_addr_get_u32(dest);
+  acc += (addr & 0xffffUL);
+  acc += ((addr >> 16) & 0xffffUL);
+  /* fold down to 16 bits */
+  acc = FOLD_U32T(acc);
+  acc = FOLD_U32T(acc);
+
+  return inet_cksum_pseudo_partial_base(p, proto, proto_len, chksum_len, acc);
+}
+
+#if LWIP_IPV6
+/**
+ * Calculates the checksum with IPv6 pseudo header used by TCP and UDP for a pbuf chain.
+ * IPv6 addresses are expected to be in network byte order. Will only compute for a
+ * portion of the payload.
+ *
+ * @param p chain of pbufs over that a checksum should be calculated (ip data part)
+ * @param src source ipv6 address (used for checksum of pseudo header)
+ * @param dst destination ipv6 address (used for checksum of pseudo header)
+ * @param proto ipv6 protocol/next header (used for checksum of pseudo header)
+ * @param proto_len length of the ipv6 payload (used for checksum of pseudo header)
+ * @param chksum_len number of payload bytes used to compute chksum
+ * @return checksum (as u16_t) to be saved directly in the protocol header
+ */
+u16_t
+ip6_chksum_pseudo_partial(struct pbuf *p, u8_t proto, u16_t proto_len,
+       u16_t chksum_len, ip6_addr_t *src, ip6_addr_t *dest)
+{
+  u32_t acc = 0;
+  u32_t addr;
+  u8_t addr_part;
+
+  for (addr_part = 0; addr_part < 4; addr_part++) {
+    addr = src->addr[addr_part];
+    acc += (addr & 0xffffUL);
+    acc += ((addr >> 16) & 0xffffUL);
+    addr = dest->addr[addr_part];
+    acc += (addr & 0xffffUL);
+    acc += ((addr >> 16) & 0xffffUL);
+  }
+  /* fold down to 16 bits */
+  acc = FOLD_U32T(acc);
+  acc = FOLD_U32T(acc);
+
+  return inet_cksum_pseudo_partial_base(p, proto, proto_len, chksum_len, acc);
+}
+#endif /* LWIP_IPV6 */
+
+/* inet_chksum:
+ *
+ * Calculates the Internet checksum over a portion of memory. Used primarily for IP
+ * and ICMP.
+ *
+ * @param dataptr start of the buffer to calculate the checksum (no alignment needed)
+ * @param len length of the buffer to calculate the checksum
+ * @return checksum (as u16_t) to be saved directly in the protocol header
+ */
+
+u16_t
+inet_chksum(void *dataptr, u16_t len)
+{
+  return ~LWIP_CHKSUM(dataptr, len);
+}
+
+/**
+ * Calculate a checksum over a chain of pbufs (without pseudo-header, much like
+ * inet_chksum only pbufs are used).
+ *
+ * @param p pbuf chain over that the checksum should be calculated
+ * @return checksum (as u16_t) to be saved directly in the protocol header
+ */
+u16_t
+inet_chksum_pbuf(struct pbuf *p)
+{
+  u32_t acc;
+  struct pbuf *q;
+  u8_t swapped;
+
+  acc = 0;
+  swapped = 0;
+  for(q = p; q != NULL; q = q->next) {
+    acc += LWIP_CHKSUM(q->payload, q->len);
+    acc = FOLD_U32T(acc);
+    if (q->len % 2 != 0) {
+      swapped = 1 - swapped;
+      acc = SWAP_BYTES_IN_WORD(acc);
+    }
+  }
+
+  if (swapped) {
+    acc = SWAP_BYTES_IN_WORD(acc);
+  }
+  return (u16_t)~(acc & 0xffffUL);
+}
+
+/* These are some implementations for LWIP_CHKSUM_COPY, which copies data
+ * like MEMCPY but generates a checksum at the same time. Since this is a
+ * performance-sensitive function, you might want to create your own version
+ * in assembly targeted at your hardware by defining it in lwipopts.h:
+ *   #define LWIP_CHKSUM_COPY(dst, src, len) your_chksum_copy(dst, src, len)
+ */
+
+#if (LWIP_CHKSUM_COPY_ALGORITHM == 1) /* Version #1 */
+/** Safe but slow: first call MEMCPY, then call LWIP_CHKSUM.
+ * For architectures with big caches, data might still be in cache when
+ * generating the checksum after copying.
+ */
+u16_t
+lwip_chksum_copy(void *dst, const void *src, u16_t len)
+{
+  MEMCPY(dst, src, len);
+  return LWIP_CHKSUM(dst, len);
+}
+#endif /* (LWIP_CHKSUM_COPY_ALGORITHM == 1) */
diff --git a/external/badvpn_dns/lwip/src/core/init.c b/external/badvpn_dns/lwip/src/core/init.c
new file mode 100644
index 0000000..c24c027
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/init.c
@@ -0,0 +1,345 @@
+/**
+ * @file
+ * Modules initialization
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/init.h"
+#include "lwip/stats.h"
+#include "lwip/sys.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+#include "lwip/sockets.h"
+#include "lwip/ip.h"
+#include "lwip/raw.h"
+#include "lwip/udp.h"
+#include "lwip/tcp_impl.h"
+#include "lwip/snmp_msg.h"
+#include "lwip/autoip.h"
+#include "lwip/igmp.h"
+#include "lwip/dns.h"
+#include "lwip/timers.h"
+#include "netif/etharp.h"
+#include "lwip/ip6.h"
+#include "lwip/nd6.h"
+#include "lwip/mld6.h"
+#include "lwip/api.h"
+
+/* Compile-time sanity checks for configuration errors.
+ * These can be done independently of LWIP_DEBUG, without penalty.
+ */
+#ifndef BYTE_ORDER
+  #error "BYTE_ORDER is not defined, you have to define it in your cc.h"
+#endif
+#if (!IP_SOF_BROADCAST && IP_SOF_BROADCAST_RECV)
+  #error "If you want to use broadcast filter per pcb on recv operations, you have to define IP_SOF_BROADCAST=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP && LWIP_UDPLITE)
+  #error "If you want to use UDP Lite, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP && LWIP_SNMP)
+  #error "If you want to use SNMP, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP && LWIP_DHCP)
+  #error "If you want to use DHCP, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP && LWIP_IGMP)
+  #error "If you want to use IGMP, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP && LWIP_SNMP)
+  #error "If you want to use SNMP, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP && LWIP_DNS)
+  #error "If you want to use DNS, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+#if !MEMP_MEM_MALLOC /* MEMP_NUM_* checks are disabled when not using the pool allocator */
+#if (LWIP_ARP && ARP_QUEUEING && (MEMP_NUM_ARP_QUEUE<=0))
+  #error "If you want to use ARP Queueing, you have to define MEMP_NUM_ARP_QUEUE>=1 in your lwipopts.h"
+#endif
+#if (LWIP_RAW && (MEMP_NUM_RAW_PCB<=0))
+  #error "If you want to use RAW, you have to define MEMP_NUM_RAW_PCB>=1 in your lwipopts.h"
+#endif
+#if (LWIP_UDP && (MEMP_NUM_UDP_PCB<=0))
+  #error "If you want to use UDP, you have to define MEMP_NUM_UDP_PCB>=1 in your lwipopts.h"
+#endif
+#if (LWIP_TCP && (MEMP_NUM_TCP_PCB<=0))
+  #error "If you want to use TCP, you have to define MEMP_NUM_TCP_PCB>=1 in your lwipopts.h"
+#endif
+#if (LWIP_IGMP && (MEMP_NUM_IGMP_GROUP<=1))
+  #error "If you want to use IGMP, you have to define MEMP_NUM_IGMP_GROUP>1 in your lwipopts.h"
+#endif
+#if ((LWIP_NETCONN || LWIP_SOCKET) && (MEMP_NUM_TCPIP_MSG_API<=0))
+  #error "If you want to use Sequential API, you have to define MEMP_NUM_TCPIP_MSG_API>=1 in your lwipopts.h"
+#endif
+/* There must be sufficient timeouts, taking into account requirements of the subsystems. */
+#if LWIP_TIMERS && (MEMP_NUM_SYS_TIMEOUT < (LWIP_TCP + IP_REASSEMBLY + LWIP_ARP + (2*LWIP_DHCP) + LWIP_AUTOIP + LWIP_IGMP + LWIP_DNS + PPP_SUPPORT + (LWIP_IPV6 ? (1 + LWIP_IPV6_REASS + LWIP_IPV6_MLD) : 0)))
+  #error "MEMP_NUM_SYS_TIMEOUT is too low to accomodate all required timeouts"
+#endif
+#if (IP_REASSEMBLY && (MEMP_NUM_REASSDATA > IP_REASS_MAX_PBUFS))
+  #error "MEMP_NUM_REASSDATA > IP_REASS_MAX_PBUFS doesn't make sense since each struct ip_reassdata must hold 2 pbufs at least!"
+#endif
+#endif /* !MEMP_MEM_MALLOC */
+#if (LWIP_TCP && (TCP_WND > 0xffff))
+  #error "If you want to use TCP, TCP_WND must fit in an u16_t, so, you have to reduce it in your lwipopts.h"
+#endif
+#if (LWIP_TCP && (TCP_SND_QUEUELEN > 0xffff))
+  #error "If you want to use TCP, TCP_SND_QUEUELEN must fit in an u16_t, so, you have to reduce it in your lwipopts.h"
+#endif
+#if (LWIP_TCP && (TCP_SND_QUEUELEN < 2))
+  #error "TCP_SND_QUEUELEN must be at least 2 for no-copy TCP writes to work"
+#endif
+#if (LWIP_TCP && ((TCP_MAXRTX > 12) || (TCP_SYNMAXRTX > 12)))
+  #error "If you want to use TCP, TCP_MAXRTX and TCP_SYNMAXRTX must less or equal to 12 (due to tcp_backoff table), so, you have to reduce them in your lwipopts.h"
+#endif
+#if (LWIP_TCP && TCP_LISTEN_BACKLOG && (TCP_DEFAULT_LISTEN_BACKLOG < 0) || (TCP_DEFAULT_LISTEN_BACKLOG > 0xff))
+  #error "If you want to use TCP backlog, TCP_DEFAULT_LISTEN_BACKLOG must fit into an u8_t"
+#endif
+#if (LWIP_NETIF_API && (NO_SYS==1))
+  #error "If you want to use NETIF API, you have to define NO_SYS=0 in your lwipopts.h"
+#endif
+#if ((LWIP_SOCKET || LWIP_NETCONN) && (NO_SYS==1))
+  #error "If you want to use Sequential API, you have to define NO_SYS=0 in your lwipopts.h"
+#endif
+#if (!LWIP_NETCONN && LWIP_SOCKET)
+  #error "If you want to use Socket API, you have to define LWIP_NETCONN=1 in your lwipopts.h"
+#endif
+#if (((!LWIP_DHCP) || (!LWIP_AUTOIP)) && LWIP_DHCP_AUTOIP_COOP)
+  #error "If you want to use DHCP/AUTOIP cooperation mode, you have to define LWIP_DHCP=1 and LWIP_AUTOIP=1 in your lwipopts.h"
+#endif
+#if (((!LWIP_DHCP) || (!LWIP_ARP)) && DHCP_DOES_ARP_CHECK)
+  #error "If you want to use DHCP ARP checking, you have to define LWIP_DHCP=1 and LWIP_ARP=1 in your lwipopts.h"
+#endif
+#if (!LWIP_ARP && LWIP_AUTOIP)
+  #error "If you want to use AUTOIP, you have to define LWIP_ARP=1 in your lwipopts.h"
+#endif
+#if (LWIP_SNMP && (SNMP_CONCURRENT_REQUESTS<=0))
+  #error "If you want to use SNMP, you have to define SNMP_CONCURRENT_REQUESTS>=1 in your lwipopts.h"
+#endif
+#if (LWIP_SNMP && (SNMP_TRAP_DESTINATIONS<=0))
+  #error "If you want to use SNMP, you have to define SNMP_TRAP_DESTINATIONS>=1 in your lwipopts.h"
+#endif
+#if (LWIP_TCP && ((LWIP_EVENT_API && LWIP_CALLBACK_API) || (!LWIP_EVENT_API && !LWIP_CALLBACK_API)))
+  #error "One and exactly one of LWIP_EVENT_API and LWIP_CALLBACK_API has to be enabled in your lwipopts.h"
+#endif
+#if (MEM_LIBC_MALLOC && MEM_USE_POOLS)
+  #error "MEM_LIBC_MALLOC and MEM_USE_POOLS may not both be simultaneously enabled in your lwipopts.h"
+#endif
+#if (MEM_USE_POOLS && !MEMP_USE_CUSTOM_POOLS)
+  #error "MEM_USE_POOLS requires custom pools (MEMP_USE_CUSTOM_POOLS) to be enabled in your lwipopts.h"
+#endif
+#if (PBUF_POOL_BUFSIZE <= MEM_ALIGNMENT)
+  #error "PBUF_POOL_BUFSIZE must be greater than MEM_ALIGNMENT or the offset may take the full first pbuf"
+#endif
+#if (DNS_LOCAL_HOSTLIST && !DNS_LOCAL_HOSTLIST_IS_DYNAMIC && !(defined(DNS_LOCAL_HOSTLIST_INIT)))
+  #error "you have to define define DNS_LOCAL_HOSTLIST_INIT {{'host1', 0x123}, {'host2', 0x234}} to initialize DNS_LOCAL_HOSTLIST"
+#endif
+#if PPP_SUPPORT && !PPPOS_SUPPORT & !PPPOE_SUPPORT
+  #error "PPP_SUPPORT needs either PPPOS_SUPPORT or PPPOE_SUPPORT turned on"
+#endif
+#if !LWIP_ETHERNET && (LWIP_ARP || PPPOE_SUPPORT)
+  #error "LWIP_ETHERNET needs to be turned on for LWIP_ARP or PPPOE_SUPPORT"
+#endif
+#if (LWIP_IGMP || LWIP_IPV6) && !defined(LWIP_RAND)
+  #error "When using IGMP or IPv6, LWIP_RAND() needs to be defined to a random-function returning an u32_t random value"
+#endif
+#if LWIP_TCPIP_CORE_LOCKING_INPUT && !LWIP_TCPIP_CORE_LOCKING
+  #error "When using LWIP_TCPIP_CORE_LOCKING_INPUT, LWIP_TCPIP_CORE_LOCKING must be enabled, too"
+#endif
+#if LWIP_TCP && LWIP_NETIF_TX_SINGLE_PBUF && !TCP_OVERSIZE
+  #error "LWIP_NETIF_TX_SINGLE_PBUF needs TCP_OVERSIZE enabled to create single-pbuf TCP packets"
+#endif
+#if IP_FRAG && IP_FRAG_USES_STATIC_BUF && LWIP_NETIF_TX_SINGLE_PBUF
+  #error "LWIP_NETIF_TX_SINGLE_PBUF does not work with IP_FRAG_USES_STATIC_BUF==1 as that creates pbuf queues"
+#endif
+#if LWIP_NETCONN && LWIP_TCP
+#if NETCONN_COPY != TCP_WRITE_FLAG_COPY
+  #error "NETCONN_COPY != TCP_WRITE_FLAG_COPY"
+#endif
+#if NETCONN_MORE != TCP_WRITE_FLAG_MORE
+  #error "NETCONN_MORE != TCP_WRITE_FLAG_MORE"
+#endif
+#endif /* LWIP_NETCONN && LWIP_TCP */ 
+#if LWIP_SOCKET
+/* Check that the SO_* socket options and SOF_* lwIP-internal flags match */
+#if SO_ACCEPTCONN != SOF_ACCEPTCONN
+  #error "SO_ACCEPTCONN != SOF_ACCEPTCONN"
+#endif
+#if SO_REUSEADDR != SOF_REUSEADDR
+  #error "WARNING: SO_REUSEADDR != SOF_REUSEADDR"
+#endif
+#if SO_KEEPALIVE != SOF_KEEPALIVE
+  #error "WARNING: SO_KEEPALIVE != SOF_KEEPALIVE"
+#endif
+#if SO_BROADCAST != SOF_BROADCAST
+  #error "WARNING: SO_BROADCAST != SOF_BROADCAST"
+#endif
+#if SO_LINGER != SOF_LINGER
+  #error "WARNING: SO_LINGER != SOF_LINGER"
+#endif
+#endif /* LWIP_SOCKET */
+
+
+/* Compile-time checks for deprecated options.
+ */
+#ifdef MEMP_NUM_TCPIP_MSG
+  #error "MEMP_NUM_TCPIP_MSG option is deprecated. Remove it from your lwipopts.h."
+#endif
+#ifdef MEMP_NUM_API_MSG
+  #error "MEMP_NUM_API_MSG option is deprecated. Remove it from your lwipopts.h."
+#endif
+#ifdef TCP_REXMIT_DEBUG
+  #error "TCP_REXMIT_DEBUG option is deprecated. Remove it from your lwipopts.h."
+#endif
+#ifdef RAW_STATS
+  #error "RAW_STATS option is deprecated. Remove it from your lwipopts.h."
+#endif
+#ifdef ETHARP_QUEUE_FIRST
+  #error "ETHARP_QUEUE_FIRST option is deprecated. Remove it from your lwipopts.h."
+#endif
+#ifdef ETHARP_ALWAYS_INSERT
+  #error "ETHARP_ALWAYS_INSERT option is deprecated. Remove it from your lwipopts.h."
+#endif
+
+#ifndef LWIP_DISABLE_TCP_SANITY_CHECKS
+#define LWIP_DISABLE_TCP_SANITY_CHECKS  0
+#endif
+#ifndef LWIP_DISABLE_MEMP_SANITY_CHECKS
+#define LWIP_DISABLE_MEMP_SANITY_CHECKS 0
+#endif
+
+/* MEMP sanity checks */
+#if !LWIP_DISABLE_MEMP_SANITY_CHECKS
+#if LWIP_NETCONN
+#if MEMP_MEM_MALLOC
+#if !MEMP_NUM_NETCONN && LWIP_SOCKET
+#error "lwip_sanity_check: WARNING: MEMP_NUM_NETCONN cannot be 0 when using sockets!"
+#endif
+#else /* MEMP_MEM_MALLOC */
+#if MEMP_NUM_NETCONN > (MEMP_NUM_TCP_PCB+MEMP_NUM_TCP_PCB_LISTEN+MEMP_NUM_UDP_PCB+MEMP_NUM_RAW_PCB)
+#error "lwip_sanity_check: WARNING: MEMP_NUM_NETCONN should be less than the sum of MEMP_NUM_{TCP,RAW,UDP}_PCB+MEMP_NUM_TCP_PCB_LISTEN. If you know what you are doing, define LWIP_DISABLE_MEMP_SANITY_CHECKS to 1 to disable this error."
+#endif
+#endif /* MEMP_MEM_MALLOC */
+#endif /* LWIP_NETCONN */
+#endif /* !LWIP_DISABLE_MEMP_SANITY_CHECKS */
+
+/* TCP sanity checks */
+#if !LWIP_DISABLE_TCP_SANITY_CHECKS
+#if LWIP_TCP
+#if !MEMP_MEM_MALLOC && (MEMP_NUM_TCP_SEG < TCP_SND_QUEUELEN)
+  #error "lwip_sanity_check: WARNING: MEMP_NUM_TCP_SEG should be at least as big as TCP_SND_QUEUELEN. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error."
+#endif
+#if TCP_SND_BUF < (2 * TCP_MSS)
+  #error "lwip_sanity_check: WARNING: TCP_SND_BUF must be at least as much as (2 * TCP_MSS) for things to work smoothly. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error."
+#endif
+#if TCP_SND_QUEUELEN < (2 * (TCP_SND_BUF / TCP_MSS))
+  #error "lwip_sanity_check: WARNING: TCP_SND_QUEUELEN must be at least as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error."
+#endif
+#if TCP_SNDLOWAT >= TCP_SND_BUF
+  #error "lwip_sanity_check: WARNING: TCP_SNDLOWAT must be less than TCP_SND_BUF. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error."
+#endif
+#if TCP_SNDQUEUELOWAT >= TCP_SND_QUEUELEN
+  #error "lwip_sanity_check: WARNING: TCP_SNDQUEUELOWAT must be less than TCP_SND_QUEUELEN. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error."
+#endif
+#if !MEMP_MEM_MALLOC && (PBUF_POOL_BUFSIZE <= (PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN))
+  #error "lwip_sanity_check: WARNING: PBUF_POOL_BUFSIZE does not provide enough space for protocol headers. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error."
+#endif
+#if !MEMP_MEM_MALLOC && (TCP_WND > (PBUF_POOL_SIZE * (PBUF_POOL_BUFSIZE - (PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN))))
+  #error "lwip_sanity_check: WARNING: TCP_WND is larger than space provided by PBUF_POOL_SIZE * (PBUF_POOL_BUFSIZE - protocol headers). If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error."
+#endif
+#if TCP_WND < TCP_MSS
+  #error "lwip_sanity_check: WARNING: TCP_WND is smaller than MSS. If you know what you are doing, define LWIP_DISABLE_TCP_SANITY_CHECKS to 1 to disable this error."
+#endif
+#endif /* LWIP_TCP */
+#endif /* !LWIP_DISABLE_TCP_SANITY_CHECKS */
+
+/**
+ * Perform Sanity check of user-configurable values, and initialize all modules.
+ */
+void
+lwip_init(void)
+{
+  /* Modules initialization */
+  stats_init();
+#if !NO_SYS
+  sys_init();
+#endif /* !NO_SYS */
+  mem_init();
+  memp_init();
+  pbuf_init();
+  netif_init();
+#if LWIP_SOCKET
+  lwip_socket_init();
+#endif /* LWIP_SOCKET */
+  ip_init();
+#if LWIP_ARP
+  etharp_init();
+#endif /* LWIP_ARP */
+#if LWIP_RAW
+  raw_init();
+#endif /* LWIP_RAW */
+#if LWIP_UDP
+  udp_init();
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+  tcp_init();
+#endif /* LWIP_TCP */
+#if LWIP_SNMP
+  snmp_init();
+#endif /* LWIP_SNMP */
+#if LWIP_AUTOIP
+  autoip_init();
+#endif /* LWIP_AUTOIP */
+#if LWIP_IGMP
+  igmp_init();
+#endif /* LWIP_IGMP */
+#if LWIP_DNS
+  dns_init();
+#endif /* LWIP_DNS */
+#if LWIP_IPV6
+  ip6_init();
+  nd6_init();
+#if LWIP_IPV6_MLD
+  mld6_init();
+#endif /* LWIP_IPV6_MLD */
+#endif /* LWIP_IPV6 */
+
+#if LWIP_TIMERS
+  sys_timeouts_init();
+#endif /* LWIP_TIMERS */
+}
diff --git a/external/badvpn_dns/lwip/src/core/ipv4/autoip.c b/external/badvpn_dns/lwip/src/core/ipv4/autoip.c
new file mode 100644
index 0000000..b122da2
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv4/autoip.c
@@ -0,0 +1,528 @@
+/**
+ * @file
+ * AutoIP Automatic LinkLocal IP Configuration
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2007 Dominik Spies <kontakt@xxxxxxxxx>
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Dominik Spies <kontakt@xxxxxxxxx>
+ *
+ * This is a AutoIP implementation for the lwIP TCP/IP stack. It aims to conform
+ * with RFC 3927.
+ *
+ *
+ * Please coordinate changes and requests with Dominik Spies
+ * <kontakt@xxxxxxxxx>
+ */
+
+/*******************************************************************************
+ * USAGE:
+ * 
+ * define LWIP_AUTOIP 1  in your lwipopts.h
+ * 
+ * If you don't use tcpip.c (so, don't call, you don't call tcpip_init):
+ * - First, call autoip_init().
+ * - call autoip_tmr() all AUTOIP_TMR_INTERVAL msces,
+ *   that should be defined in autoip.h.
+ *   I recommend a value of 100. The value must divide 1000 with a remainder almost 0.
+ *   Possible values are 1000, 500, 333, 250, 200, 166, 142, 125, 111, 100 ....
+ *
+ * Without DHCP:
+ * - Call autoip_start() after netif_add().
+ * 
+ * With DHCP:
+ * - define LWIP_DHCP_AUTOIP_COOP 1 in your lwipopts.h.
+ * - Configure your DHCP Client.
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_AUTOIP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/mem.h"
+#include "lwip/udp.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+#include "lwip/autoip.h"
+#include "netif/etharp.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/* 169.254.0.0 */
+#define AUTOIP_NET         0xA9FE0000
+/* 169.254.1.0 */
+#define AUTOIP_RANGE_START (AUTOIP_NET | 0x0100)
+/* 169.254.254.255 */
+#define AUTOIP_RANGE_END   (AUTOIP_NET | 0xFEFF)
+
+
+/** Pseudo random macro based on netif informations.
+ * You could use "rand()" from the C Library if you define LWIP_AUTOIP_RAND in lwipopts.h */
+#ifndef LWIP_AUTOIP_RAND
+#define LWIP_AUTOIP_RAND(netif) ( (((u32_t)((netif->hwaddr[5]) & 0xff) << 24) | \
+                                   ((u32_t)((netif->hwaddr[3]) & 0xff) << 16) | \
+                                   ((u32_t)((netif->hwaddr[2]) & 0xff) << 8) | \
+                                   ((u32_t)((netif->hwaddr[4]) & 0xff))) + \
+                                   (netif->autoip?netif->autoip->tried_llipaddr:0))
+#endif /* LWIP_AUTOIP_RAND */
+
+/**
+ * Macro that generates the initial IP address to be tried by AUTOIP.
+ * If you want to override this, define it to something else in lwipopts.h.
+ */
+#ifndef LWIP_AUTOIP_CREATE_SEED_ADDR
+#define LWIP_AUTOIP_CREATE_SEED_ADDR(netif) \
+  htonl(AUTOIP_RANGE_START + ((u32_t)(((u8_t)(netif->hwaddr[4])) | \
+                 ((u32_t)((u8_t)(netif->hwaddr[5]))) << 8)))
+#endif /* LWIP_AUTOIP_CREATE_SEED_ADDR */
+
+/* static functions */
+static void autoip_handle_arp_conflict(struct netif *netif);
+
+/* creates a pseudo random LL IP-Address for a network interface */
+static void autoip_create_addr(struct netif *netif, ip_addr_t *ipaddr);
+
+/* sends an ARP probe */
+static err_t autoip_arp_probe(struct netif *netif);
+
+/* sends an ARP announce */
+static err_t autoip_arp_announce(struct netif *netif);
+
+/* configure interface for use with current LL IP-Address */
+static err_t autoip_bind(struct netif *netif);
+
+/* start sending probes for llipaddr */
+static void autoip_start_probing(struct netif *netif);
+
+
+/** Set a statically allocated struct autoip to work with.
+ * Using this prevents autoip_start to allocate it using mem_malloc.
+ *
+ * @param netif the netif for which to set the struct autoip
+ * @param dhcp (uninitialised) dhcp struct allocated by the application
+ */
+void
+autoip_set_struct(struct netif *netif, struct autoip *autoip)
+{
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+  LWIP_ASSERT("autoip != NULL", autoip != NULL);
+  LWIP_ASSERT("netif already has a struct autoip set", netif->autoip == NULL);
+
+  /* clear data structure */
+  memset(autoip, 0, sizeof(struct autoip));
+  /* autoip->state = AUTOIP_STATE_OFF; */
+  netif->autoip = autoip;
+}
+
+/** Restart AutoIP client and check the next address (conflict detected)
+ *
+ * @param netif The netif under AutoIP control
+ */
+static void
+autoip_restart(struct netif *netif)
+{
+  netif->autoip->tried_llipaddr++;
+  autoip_start(netif);
+}
+
+/**
+ * Handle a IP address conflict after an ARP conflict detection
+ */
+static void
+autoip_handle_arp_conflict(struct netif *netif)
+{
+  /* Somehow detect if we are defending or retreating */
+  unsigned char defend = 1; /* tbd */
+
+  if (defend) {
+    if (netif->autoip->lastconflict > 0) {
+      /* retreat, there was a conflicting ARP in the last
+       * DEFEND_INTERVAL seconds
+       */
+      LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+        ("autoip_handle_arp_conflict(): we are defending, but in DEFEND_INTERVAL, retreating\n"));
+
+      /* TODO: close all TCP sessions */
+      autoip_restart(netif);
+    } else {
+      LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+        ("autoip_handle_arp_conflict(): we are defend, send ARP Announce\n"));
+      autoip_arp_announce(netif);
+      netif->autoip->lastconflict = DEFEND_INTERVAL * AUTOIP_TICKS_PER_SECOND;
+    }
+  } else {
+    LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+      ("autoip_handle_arp_conflict(): we do not defend, retreating\n"));
+    /* TODO: close all TCP sessions */
+    autoip_restart(netif);
+  }
+}
+
+/**
+ * Create an IP-Address out of range 169.254.1.0 to 169.254.254.255
+ *
+ * @param netif network interface on which create the IP-Address
+ * @param ipaddr ip address to initialize
+ */
+static void
+autoip_create_addr(struct netif *netif, ip_addr_t *ipaddr)
+{
+  /* Here we create an IP-Address out of range 169.254.1.0 to 169.254.254.255
+   * compliant to RFC 3927 Section 2.1
+   * We have 254 * 256 possibilities */
+
+  u32_t addr = ntohl(LWIP_AUTOIP_CREATE_SEED_ADDR(netif));
+  addr += netif->autoip->tried_llipaddr;
+  addr = AUTOIP_NET | (addr & 0xffff);
+  /* Now, 169.254.0.0 <= addr <= 169.254.255.255 */ 
+
+  if (addr < AUTOIP_RANGE_START) {
+    addr += AUTOIP_RANGE_END - AUTOIP_RANGE_START + 1;
+  }
+  if (addr > AUTOIP_RANGE_END) {
+    addr -= AUTOIP_RANGE_END - AUTOIP_RANGE_START + 1;
+  }
+  LWIP_ASSERT("AUTOIP address not in range", (addr >= AUTOIP_RANGE_START) &&
+    (addr <= AUTOIP_RANGE_END));
+  ip4_addr_set_u32(ipaddr, htonl(addr));
+  
+  LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+    ("autoip_create_addr(): tried_llipaddr=%"U16_F", %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+    (u16_t)(netif->autoip->tried_llipaddr), ip4_addr1_16(ipaddr), ip4_addr2_16(ipaddr),
+    ip4_addr3_16(ipaddr), ip4_addr4_16(ipaddr)));
+}
+
+/**
+ * Sends an ARP probe from a network interface
+ *
+ * @param netif network interface used to send the probe
+ */
+static err_t
+autoip_arp_probe(struct netif *netif)
+{
+  return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, &ethbroadcast,
+    (struct eth_addr *)netif->hwaddr, IP_ADDR_ANY, &ethzero,
+    &netif->autoip->llipaddr, ARP_REQUEST);
+}
+
+/**
+ * Sends an ARP announce from a network interface
+ *
+ * @param netif network interface used to send the announce
+ */
+static err_t
+autoip_arp_announce(struct netif *netif)
+{
+  return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, &ethbroadcast,
+    (struct eth_addr *)netif->hwaddr, &netif->autoip->llipaddr, &ethzero,
+    &netif->autoip->llipaddr, ARP_REQUEST);
+}
+
+/**
+ * Configure interface for use with current LL IP-Address
+ *
+ * @param netif network interface to configure with current LL IP-Address
+ */
+static err_t
+autoip_bind(struct netif *netif)
+{
+  struct autoip *autoip = netif->autoip;
+  ip_addr_t sn_mask, gw_addr;
+
+  LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE,
+    ("autoip_bind(netif=%p) %c%c%"U16_F" %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+    (void*)netif, netif->name[0], netif->name[1], (u16_t)netif->num,
+    ip4_addr1_16(&autoip->llipaddr), ip4_addr2_16(&autoip->llipaddr),
+    ip4_addr3_16(&autoip->llipaddr), ip4_addr4_16(&autoip->llipaddr)));
+
+  IP4_ADDR(&sn_mask, 255, 255, 0, 0);
+  IP4_ADDR(&gw_addr, 0, 0, 0, 0);
+
+  netif_set_ipaddr(netif, &autoip->llipaddr);
+  netif_set_netmask(netif, &sn_mask);
+  netif_set_gw(netif, &gw_addr);  
+
+  /* bring the interface up */
+  netif_set_up(netif);
+
+  return ERR_OK;
+}
+
+/**
+ * Start AutoIP client
+ *
+ * @param netif network interface on which start the AutoIP client
+ */
+err_t
+autoip_start(struct netif *netif)
+{
+  struct autoip *autoip = netif->autoip;
+  err_t result = ERR_OK;
+
+  if (netif_is_up(netif)) {
+    netif_set_down(netif);
+  }
+
+  /* Set IP-Address, Netmask and Gateway to 0 to make sure that
+   * ARP Packets are formed correctly
+   */
+  ip_addr_set_zero(&netif->ip_addr);
+  ip_addr_set_zero(&netif->netmask);
+  ip_addr_set_zero(&netif->gw);
+
+  LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+    ("autoip_start(netif=%p) %c%c%"U16_F"\n", (void*)netif, netif->name[0],
+    netif->name[1], (u16_t)netif->num));
+  if (autoip == NULL) {
+    /* no AutoIP client attached yet? */
+    LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE,
+      ("autoip_start(): starting new AUTOIP client\n"));
+    autoip = (struct autoip *)mem_malloc(sizeof(struct autoip));
+    if (autoip == NULL) {
+      LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE,
+        ("autoip_start(): could not allocate autoip\n"));
+      return ERR_MEM;
+    }
+    memset(autoip, 0, sizeof(struct autoip));
+    /* store this AutoIP client in the netif */
+    netif->autoip = autoip;
+    LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE, ("autoip_start(): allocated autoip"));
+  } else {
+    autoip->state = AUTOIP_STATE_OFF;
+    autoip->ttw = 0;
+    autoip->sent_num = 0;
+    ip_addr_set_zero(&autoip->llipaddr);
+    autoip->lastconflict = 0;
+  }
+
+  autoip_create_addr(netif, &(autoip->llipaddr));
+  autoip_start_probing(netif);
+
+  return result;
+}
+
+static void
+autoip_start_probing(struct netif *netif)
+{
+  struct autoip *autoip = netif->autoip;
+
+  autoip->state = AUTOIP_STATE_PROBING;
+  autoip->sent_num = 0;
+  LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+     ("autoip_start_probing(): changing state to PROBING: %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+      ip4_addr1_16(&netif->autoip->llipaddr), ip4_addr2_16(&netif->autoip->llipaddr),
+      ip4_addr3_16(&netif->autoip->llipaddr), ip4_addr4_16(&netif->autoip->llipaddr)));
+
+  /* time to wait to first probe, this is randomly
+   * choosen out of 0 to PROBE_WAIT seconds.
+   * compliant to RFC 3927 Section 2.2.1
+   */
+  autoip->ttw = (u16_t)(LWIP_AUTOIP_RAND(netif) % (PROBE_WAIT * AUTOIP_TICKS_PER_SECOND));
+
+  /*
+   * if we tried more then MAX_CONFLICTS we must limit our rate for
+   * accquiring and probing address
+   * compliant to RFC 3927 Section 2.2.1
+   */
+  if (autoip->tried_llipaddr > MAX_CONFLICTS) {
+    autoip->ttw = RATE_LIMIT_INTERVAL * AUTOIP_TICKS_PER_SECOND;
+  }
+}
+
+/**
+ * Handle a possible change in the network configuration.
+ *
+ * If there is an AutoIP address configured, take the interface down
+ * and begin probing with the same address.
+ */
+void
+autoip_network_changed(struct netif *netif)
+{
+  if (netif->autoip && netif->autoip->state != AUTOIP_STATE_OFF) {
+    netif_set_down(netif);
+    autoip_start_probing(netif);
+  }
+}
+
+/**
+ * Stop AutoIP client
+ *
+ * @param netif network interface on which stop the AutoIP client
+ */
+err_t
+autoip_stop(struct netif *netif)
+{
+  netif->autoip->state = AUTOIP_STATE_OFF;
+  netif_set_down(netif);
+  return ERR_OK;
+}
+
+/**
+ * Has to be called in loop every AUTOIP_TMR_INTERVAL milliseconds
+ */
+void
+autoip_tmr()
+{
+  struct netif *netif = netif_list;
+  /* loop through netif's */
+  while (netif != NULL) {
+    /* only act on AutoIP configured interfaces */
+    if (netif->autoip != NULL) {
+      if (netif->autoip->lastconflict > 0) {
+        netif->autoip->lastconflict--;
+      }
+
+      LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE,
+        ("autoip_tmr() AutoIP-State: %"U16_F", ttw=%"U16_F"\n",
+        (u16_t)(netif->autoip->state), netif->autoip->ttw));
+
+      switch(netif->autoip->state) {
+        case AUTOIP_STATE_PROBING:
+          if (netif->autoip->ttw > 0) {
+            netif->autoip->ttw--;
+          } else {
+            if (netif->autoip->sent_num >= PROBE_NUM) {
+              netif->autoip->state = AUTOIP_STATE_ANNOUNCING;
+              netif->autoip->sent_num = 0;
+              netif->autoip->ttw = ANNOUNCE_WAIT * AUTOIP_TICKS_PER_SECOND;
+              LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+                 ("autoip_tmr(): changing state to ANNOUNCING: %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+                  ip4_addr1_16(&netif->autoip->llipaddr), ip4_addr2_16(&netif->autoip->llipaddr),
+                  ip4_addr3_16(&netif->autoip->llipaddr), ip4_addr4_16(&netif->autoip->llipaddr)));
+            } else {
+              autoip_arp_probe(netif);
+              LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE,
+                ("autoip_tmr() PROBING Sent Probe\n"));
+              netif->autoip->sent_num++;
+              /* calculate time to wait to next probe */
+              netif->autoip->ttw = (u16_t)((LWIP_AUTOIP_RAND(netif) %
+                ((PROBE_MAX - PROBE_MIN) * AUTOIP_TICKS_PER_SECOND) ) +
+                PROBE_MIN * AUTOIP_TICKS_PER_SECOND);
+            }
+          }
+          break;
+
+        case AUTOIP_STATE_ANNOUNCING:
+          if (netif->autoip->ttw > 0) {
+            netif->autoip->ttw--;
+          } else {
+            if (netif->autoip->sent_num == 0) {
+             /* We are here the first time, so we waited ANNOUNCE_WAIT seconds
+              * Now we can bind to an IP address and use it.
+              *
+              * autoip_bind calls netif_set_up. This triggers a gratuitous ARP
+              * which counts as an announcement.
+              */
+              autoip_bind(netif);
+            } else {
+              autoip_arp_announce(netif);
+              LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE,
+                ("autoip_tmr() ANNOUNCING Sent Announce\n"));
+            }
+            netif->autoip->ttw = ANNOUNCE_INTERVAL * AUTOIP_TICKS_PER_SECOND;
+            netif->autoip->sent_num++;
+
+            if (netif->autoip->sent_num >= ANNOUNCE_NUM) {
+                netif->autoip->state = AUTOIP_STATE_BOUND;
+                netif->autoip->sent_num = 0;
+                netif->autoip->ttw = 0;
+                 LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+                    ("autoip_tmr(): changing state to BOUND: %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+                     ip4_addr1_16(&netif->autoip->llipaddr), ip4_addr2_16(&netif->autoip->llipaddr),
+                     ip4_addr3_16(&netif->autoip->llipaddr), ip4_addr4_16(&netif->autoip->llipaddr)));
+            }
+          }
+          break;
+      }
+    }
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+}
+
+/**
+ * Handles every incoming ARP Packet, called by etharp_arp_input.
+ *
+ * @param netif network interface to use for autoip processing
+ * @param hdr Incoming ARP packet
+ */
+void
+autoip_arp_reply(struct netif *netif, struct etharp_hdr *hdr)
+{
+  LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE, ("autoip_arp_reply()\n"));
+  if ((netif->autoip != NULL) && (netif->autoip->state != AUTOIP_STATE_OFF)) {
+   /* when ip.src == llipaddr && hw.src != netif->hwaddr
+    *
+    * when probing  ip.dst == llipaddr && hw.src != netif->hwaddr
+    * we have a conflict and must solve it
+    */
+    ip_addr_t sipaddr, dipaddr;
+    struct eth_addr netifaddr;
+    ETHADDR16_COPY(netifaddr.addr, netif->hwaddr);
+
+    /* Copy struct ip_addr2 to aligned ip_addr, to support compilers without
+     * structure packing (not using structure copy which breaks strict-aliasing rules).
+     */
+    IPADDR2_COPY(&sipaddr, &hdr->sipaddr);
+    IPADDR2_COPY(&dipaddr, &hdr->dipaddr);
+      
+    if ((netif->autoip->state == AUTOIP_STATE_PROBING) ||
+        ((netif->autoip->state == AUTOIP_STATE_ANNOUNCING) &&
+         (netif->autoip->sent_num == 0))) {
+     /* RFC 3927 Section 2.2.1:
+      * from beginning to after ANNOUNCE_WAIT
+      * seconds we have a conflict if
+      * ip.src == llipaddr OR
+      * ip.dst == llipaddr && hw.src != own hwaddr
+      */
+      if ((ip_addr_cmp(&sipaddr, &netif->autoip->llipaddr)) ||
+          (ip_addr_cmp(&dipaddr, &netif->autoip->llipaddr) &&
+           !eth_addr_cmp(&netifaddr, &hdr->shwaddr))) {
+        LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE | LWIP_DBG_LEVEL_WARNING,
+          ("autoip_arp_reply(): Probe Conflict detected\n"));
+        autoip_restart(netif);
+      }
+    } else {
+     /* RFC 3927 Section 2.5:
+      * in any state we have a conflict if
+      * ip.src == llipaddr && hw.src != own hwaddr
+      */
+      if (ip_addr_cmp(&sipaddr, &netif->autoip->llipaddr) &&
+          !eth_addr_cmp(&netifaddr, &hdr->shwaddr)) {
+        LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE | LWIP_DBG_LEVEL_WARNING,
+          ("autoip_arp_reply(): Conflicting ARP-Packet detected\n"));
+        autoip_handle_arp_conflict(netif);
+      }
+    }
+  }
+}
+
+#endif /* LWIP_AUTOIP */
diff --git a/external/badvpn_dns/lwip/src/core/ipv4/icmp.c b/external/badvpn_dns/lwip/src/core/ipv4/icmp.c
new file mode 100644
index 0000000..af47153
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv4/icmp.c
@@ -0,0 +1,338 @@
+/**
+ * @file
+ * ICMP - Internet Control Message Protocol
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+/* Some ICMP messages should be passed to the transport protocols. This
+   is not implemented. */
+
+#include "lwip/opt.h"
+
+#if LWIP_ICMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/icmp.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/ip.h"
+#include "lwip/def.h"
+#include "lwip/stats.h"
+#include "lwip/snmp.h"
+
+#include <string.h>
+
+/** Small optimization: set to 0 if incoming PBUF_POOL pbuf always can be
+ * used to modify and send a response packet (and to 1 if this is not the case,
+ * e.g. when link header is stripped of when receiving) */
+#ifndef LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
+#define LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN 1
+#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN */
+
+/* The amount of data from the original packet to return in a dest-unreachable */
+#define ICMP_DEST_UNREACH_DATASIZE 8
+
+static void icmp_send_response(struct pbuf *p, u8_t type, u8_t code);
+
+/**
+ * Processes ICMP input packets, called from ip_input().
+ *
+ * Currently only processes icmp echo requests and sends
+ * out the echo response.
+ *
+ * @param p the icmp echo request packet, p->payload pointing to the icmp header
+ * @param inp the netif on which this packet was received
+ */
+void
+icmp_input(struct pbuf *p, struct netif *inp)
+{
+  u8_t type;
+#ifdef LWIP_DEBUG
+  u8_t code;
+#endif /* LWIP_DEBUG */
+  struct icmp_echo_hdr *iecho;
+  struct ip_hdr *iphdr;
+  s16_t hlen;
+
+  ICMP_STATS_INC(icmp.recv);
+  snmp_inc_icmpinmsgs();
+
+  iphdr = (struct ip_hdr *)ip_current_header();
+  hlen = IPH_HL(iphdr) * 4;
+  if (p->len < sizeof(u16_t)*2) {
+    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: short ICMP (%"U16_F" bytes) received\n", p->tot_len));
+    goto lenerr;
+  }
+
+  type = *((u8_t *)p->payload);
+#ifdef LWIP_DEBUG
+  code = *(((u8_t *)p->payload)+1);
+#endif /* LWIP_DEBUG */
+  switch (type) {
+  case ICMP_ER:
+    /* This is OK, echo reply might have been parsed by a raw PCB
+       (as obviously, an echo request has been sent, too). */
+    break; 
+  case ICMP_ECHO:
+#if !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING
+    {
+      int accepted = 1;
+#if !LWIP_MULTICAST_PING
+      /* multicast destination address? */
+      if (ip_addr_ismulticast(ip_current_dest_addr())) {
+        accepted = 0;
+      }
+#endif /* LWIP_MULTICAST_PING */
+#if !LWIP_BROADCAST_PING
+      /* broadcast destination address? */
+      if (ip_addr_isbroadcast(ip_current_dest_addr(), inp)) {
+        accepted = 0;
+      }
+#endif /* LWIP_BROADCAST_PING */
+      /* broadcast or multicast destination address not acceptd? */
+      if (!accepted) {
+        LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: Not echoing to multicast or broadcast pings\n"));
+        ICMP_STATS_INC(icmp.err);
+        pbuf_free(p);
+        return;
+      }
+    }
+#endif /* !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING */
+    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ping\n"));
+    if (p->tot_len < sizeof(struct icmp_echo_hdr)) {
+      LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: bad ICMP echo received\n"));
+      goto lenerr;
+    }
+    if (inet_chksum_pbuf(p) != 0) {
+      LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: checksum failed for received ICMP echo\n"));
+      pbuf_free(p);
+      ICMP_STATS_INC(icmp.chkerr);
+      snmp_inc_icmpinerrors();
+      return;
+    }
+#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
+    if (pbuf_header(p, (PBUF_IP_HLEN + PBUF_LINK_HLEN))) {
+      /* p is not big enough to contain link headers
+       * allocate a new one and copy p into it
+       */
+      struct pbuf *r;
+      /* switch p->payload to ip header */
+      if (pbuf_header(p, hlen)) {
+        LWIP_ASSERT("icmp_input: moving p->payload to ip header failed\n", 0);
+        goto memerr;
+      }
+      /* allocate new packet buffer with space for link headers */
+      r = pbuf_alloc(PBUF_LINK, p->tot_len, PBUF_RAM);
+      if (r == NULL) {
+        LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: allocating new pbuf failed\n"));
+        goto memerr;
+      }
+      LWIP_ASSERT("check that first pbuf can hold struct the ICMP header",
+                  (r->len >= hlen + sizeof(struct icmp_echo_hdr)));
+      /* copy the whole packet including ip header */
+      if (pbuf_copy(r, p) != ERR_OK) {
+        LWIP_ASSERT("icmp_input: copying to new pbuf failed\n", 0);
+        goto memerr;
+      }
+      iphdr = (struct ip_hdr *)r->payload;
+      /* switch r->payload back to icmp header */
+      if (pbuf_header(r, -hlen)) {
+        LWIP_ASSERT("icmp_input: restoring original p->payload failed\n", 0);
+        goto memerr;
+      }
+      /* free the original p */
+      pbuf_free(p);
+      /* we now have an identical copy of p that has room for link headers */
+      p = r;
+    } else {
+      /* restore p->payload to point to icmp header */
+      if (pbuf_header(p, -(s16_t)(PBUF_IP_HLEN + PBUF_LINK_HLEN))) {
+        LWIP_ASSERT("icmp_input: restoring original p->payload failed\n", 0);
+        goto memerr;
+      }
+    }
+#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN */
+    /* At this point, all checks are OK. */
+    /* We generate an answer by switching the dest and src ip addresses,
+     * setting the icmp type to ECHO_RESPONSE and updating the checksum. */
+    iecho = (struct icmp_echo_hdr *)p->payload;
+    ip_addr_copy(iphdr->src, *ip_current_dest_addr());
+    ip_addr_copy(iphdr->dest, *ip_current_src_addr());
+    ICMPH_TYPE_SET(iecho, ICMP_ER);
+#if CHECKSUM_GEN_ICMP
+    /* adjust the checksum */
+    if (iecho->chksum >= PP_HTONS(0xffffU - (ICMP_ECHO << 8))) {
+      iecho->chksum += PP_HTONS(ICMP_ECHO << 8) + 1;
+    } else {
+      iecho->chksum += PP_HTONS(ICMP_ECHO << 8);
+    }
+#else /* CHECKSUM_GEN_ICMP */
+    iecho->chksum = 0;
+#endif /* CHECKSUM_GEN_ICMP */
+
+    /* Set the correct TTL and recalculate the header checksum. */
+    IPH_TTL_SET(iphdr, ICMP_TTL);
+    IPH_CHKSUM_SET(iphdr, 0);
+#if CHECKSUM_GEN_IP
+    IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));
+#endif /* CHECKSUM_GEN_IP */
+
+    ICMP_STATS_INC(icmp.xmit);
+    /* increase number of messages attempted to send */
+    snmp_inc_icmpoutmsgs();
+    /* increase number of echo replies attempted to send */
+    snmp_inc_icmpoutechoreps();
+
+    if(pbuf_header(p, hlen)) {
+      LWIP_ASSERT("Can't move over header in packet", 0);
+    } else {
+      err_t ret;
+      /* send an ICMP packet, src addr is the dest addr of the curren packet */
+      ret = ip_output_if(p, ip_current_dest_addr(), IP_HDRINCL,
+                   ICMP_TTL, 0, IP_PROTO_ICMP, inp);
+      if (ret != ERR_OK) {
+        LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ip_output_if returned an error: %c.\n", ret));
+      }
+    }
+    break;
+  default:
+    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ICMP type %"S16_F" code %"S16_F" not supported.\n", 
+                (s16_t)type, (s16_t)code));
+    ICMP_STATS_INC(icmp.proterr);
+    ICMP_STATS_INC(icmp.drop);
+  }
+  pbuf_free(p);
+  return;
+lenerr:
+  pbuf_free(p);
+  ICMP_STATS_INC(icmp.lenerr);
+  snmp_inc_icmpinerrors();
+  return;
+#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
+memerr:
+  pbuf_free(p);
+  ICMP_STATS_INC(icmp.err);
+  snmp_inc_icmpinerrors();
+  return;
+#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN */
+}
+
+/**
+ * Send an icmp 'destination unreachable' packet, called from ip_input() if
+ * the transport layer protocol is unknown and from udp_input() if the local
+ * port is not bound.
+ *
+ * @param p the input packet for which the 'unreachable' should be sent,
+ *          p->payload pointing to the IP header
+ * @param t type of the 'unreachable' packet
+ */
+void
+icmp_dest_unreach(struct pbuf *p, enum icmp_dur_type t)
+{
+  icmp_send_response(p, ICMP_DUR, t);
+}
+
+#if IP_FORWARD || IP_REASSEMBLY
+/**
+ * Send a 'time exceeded' packet, called from ip_forward() if TTL is 0.
+ *
+ * @param p the input packet for which the 'time exceeded' should be sent,
+ *          p->payload pointing to the IP header
+ * @param t type of the 'time exceeded' packet
+ */
+void
+icmp_time_exceeded(struct pbuf *p, enum icmp_te_type t)
+{
+  icmp_send_response(p, ICMP_TE, t);
+}
+
+#endif /* IP_FORWARD || IP_REASSEMBLY */
+
+/**
+ * Send an icmp packet in response to an incoming packet.
+ *
+ * @param p the input packet for which the 'unreachable' should be sent,
+ *          p->payload pointing to the IP header
+ * @param type Type of the ICMP header
+ * @param code Code of the ICMP header
+ */
+static void
+icmp_send_response(struct pbuf *p, u8_t type, u8_t code)
+{
+  struct pbuf *q;
+  struct ip_hdr *iphdr;
+  /* we can use the echo header here */
+  struct icmp_echo_hdr *icmphdr;
+  ip_addr_t iphdr_src;
+
+  /* ICMP header + IP header + 8 bytes of data */
+  q = pbuf_alloc(PBUF_IP, sizeof(struct icmp_echo_hdr) + IP_HLEN + ICMP_DEST_UNREACH_DATASIZE,
+                 PBUF_RAM);
+  if (q == NULL) {
+    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded: failed to allocate pbuf for ICMP packet.\n"));
+    return;
+  }
+  LWIP_ASSERT("check that first pbuf can hold icmp message",
+             (q->len >= (sizeof(struct icmp_echo_hdr) + IP_HLEN + ICMP_DEST_UNREACH_DATASIZE)));
+
+  iphdr = (struct ip_hdr *)p->payload;
+  LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded from "));
+  ip_addr_debug_print(ICMP_DEBUG, &(iphdr->src));
+  LWIP_DEBUGF(ICMP_DEBUG, (" to "));
+  ip_addr_debug_print(ICMP_DEBUG, &(iphdr->dest));
+  LWIP_DEBUGF(ICMP_DEBUG, ("\n"));
+
+  icmphdr = (struct icmp_echo_hdr *)q->payload;
+  icmphdr->type = type;
+  icmphdr->code = code;
+  icmphdr->id = 0;
+  icmphdr->seqno = 0;
+
+  /* copy fields from original packet */
+  SMEMCPY((u8_t *)q->payload + sizeof(struct icmp_echo_hdr), (u8_t *)p->payload,
+          IP_HLEN + ICMP_DEST_UNREACH_DATASIZE);
+
+  /* calculate checksum */
+  icmphdr->chksum = 0;
+  icmphdr->chksum = inet_chksum(icmphdr, q->len);
+  ICMP_STATS_INC(icmp.xmit);
+  /* increase number of messages attempted to send */
+  snmp_inc_icmpoutmsgs();
+  /* increase number of destination unreachable messages attempted to send */
+  snmp_inc_icmpouttimeexcds();
+  ip_addr_copy(iphdr_src, iphdr->src);
+  ip_output(q, NULL, &iphdr_src, ICMP_TTL, 0, IP_PROTO_ICMP);
+  pbuf_free(q);
+}
+
+#endif /* LWIP_ICMP */
diff --git a/external/badvpn_dns/lwip/src/core/ipv4/igmp.c b/external/badvpn_dns/lwip/src/core/ipv4/igmp.c
new file mode 100644
index 0000000..bd52744
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv4/igmp.c
@@ -0,0 +1,805 @@
+/**
+ * @file
+ * IGMP - Internet Group Management Protocol
+ *
+ */
+
+/*
+ * Copyright (c) 2002 CITEL Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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 CITEL Technologies Ltd 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 CITEL TECHNOLOGIES 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 CITEL TECHNOLOGIES OR CONTRIBUTORS 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. 
+ *
+ * This file is a contribution to the lwIP TCP/IP stack.
+ * The Swedish Institute of Computer Science and Adam Dunkels
+ * are specifically granted permission to redistribute this
+ * source code.
+*/
+
+/*-------------------------------------------------------------
+Note 1)
+Although the rfc requires V1 AND V2 capability
+we will only support v2 since now V1 is very old (August 1989)
+V1 can be added if required
+
+a debug print and statistic have been implemented to
+show this up.
+-------------------------------------------------------------
+-------------------------------------------------------------
+Note 2)
+A query for a specific group address (as opposed to ALLHOSTS)
+has now been implemented as I am unsure if it is required
+
+a debug print and statistic have been implemented to
+show this up.
+-------------------------------------------------------------
+-------------------------------------------------------------
+Note 3)
+The router alert rfc 2113 is implemented in outgoing packets
+but not checked rigorously incoming
+-------------------------------------------------------------
+Steve Reynolds
+------------------------------------------------------------*/
+
+/*-----------------------------------------------------------------------------
+ * RFC 988  - Host extensions for IP multicasting                         - V0
+ * RFC 1054 - Host extensions for IP multicasting                         -
+ * RFC 1112 - Host extensions for IP multicasting                         - V1
+ * RFC 2236 - Internet Group Management Protocol, Version 2               - V2  <- this code is based on this RFC (it's the "de facto" standard)
+ * RFC 3376 - Internet Group Management Protocol, Version 3               - V3
+ * RFC 4604 - Using Internet Group Management Protocol Version 3...       - V3+
+ * RFC 2113 - IP Router Alert Option                                      - 
+ *----------------------------------------------------------------------------*/
+
+/*-----------------------------------------------------------------------------
+ * Includes
+ *----------------------------------------------------------------------------*/
+
+#include "lwip/opt.h"
+
+#if LWIP_IGMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/igmp.h"
+#include "lwip/debug.h"
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/ip.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/netif.h"
+#include "lwip/icmp.h"
+#include "lwip/udp.h"
+#include "lwip/tcp.h"
+#include "lwip/stats.h"
+
+#include "string.h"
+
+/* 
+ * IGMP constants
+ */
+#define IGMP_TTL                       1
+#define IGMP_MINLEN                    8
+#define ROUTER_ALERT                   0x9404U
+#define ROUTER_ALERTLEN                4
+
+/*
+ * IGMP message types, including version number.
+ */
+#define IGMP_MEMB_QUERY                0x11 /* Membership query         */
+#define IGMP_V1_MEMB_REPORT            0x12 /* Ver. 1 membership report */
+#define IGMP_V2_MEMB_REPORT            0x16 /* Ver. 2 membership report */
+#define IGMP_LEAVE_GROUP               0x17 /* Leave-group message      */
+
+/* Group  membership states */
+#define IGMP_GROUP_NON_MEMBER          0
+#define IGMP_GROUP_DELAYING_MEMBER     1
+#define IGMP_GROUP_IDLE_MEMBER         2
+
+/**
+ * IGMP packet format.
+ */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct igmp_msg {
+ PACK_STRUCT_FIELD(u8_t           igmp_msgtype);
+ PACK_STRUCT_FIELD(u8_t           igmp_maxresp);
+ PACK_STRUCT_FIELD(u16_t          igmp_checksum);
+ PACK_STRUCT_FIELD(ip_addr_p_t    igmp_group_address);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+
+static struct igmp_group *igmp_lookup_group(struct netif *ifp, ip_addr_t *addr);
+static err_t  igmp_remove_group(struct igmp_group *group);
+static void   igmp_timeout( struct igmp_group *group);
+static void   igmp_start_timer(struct igmp_group *group, u8_t max_time);
+static void   igmp_delaying_member(struct igmp_group *group, u8_t maxresp);
+static err_t  igmp_ip_output_if(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest, struct netif *netif);
+static void   igmp_send(struct igmp_group *group, u8_t type);
+
+
+static struct igmp_group* igmp_group_list;
+static ip_addr_t     allsystems;
+static ip_addr_t     allrouters;
+
+
+/**
+ * Initialize the IGMP module
+ */
+void
+igmp_init(void)
+{
+  LWIP_DEBUGF(IGMP_DEBUG, ("igmp_init: initializing\n"));
+
+  IP4_ADDR(&allsystems, 224, 0, 0, 1);
+  IP4_ADDR(&allrouters, 224, 0, 0, 2);
+}
+
+#ifdef LWIP_DEBUG
+/**
+ * Dump global IGMP groups list
+ */
+void
+igmp_dump_group_list()
+{ 
+  struct igmp_group *group = igmp_group_list;
+
+  while (group != NULL) {
+    LWIP_DEBUGF(IGMP_DEBUG, ("igmp_dump_group_list: [%"U32_F"] ", (u32_t)(group->group_state)));
+    ip_addr_debug_print(IGMP_DEBUG, &group->group_address);
+    LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", group->netif));
+    group = group->next;
+  }
+  LWIP_DEBUGF(IGMP_DEBUG, ("\n"));
+}
+#else
+#define igmp_dump_group_list()
+#endif /* LWIP_DEBUG */
+
+/**
+ * Start IGMP processing on interface
+ *
+ * @param netif network interface on which start IGMP processing
+ */
+err_t
+igmp_start(struct netif *netif)
+{
+  struct igmp_group* group;
+
+  LWIP_DEBUGF(IGMP_DEBUG, ("igmp_start: starting IGMP processing on if %p\n", netif));
+
+  group = igmp_lookup_group(netif, &allsystems);
+
+  if (group != NULL) {
+    group->group_state = IGMP_GROUP_IDLE_MEMBER;
+    group->use++;
+
+    /* Allow the igmp messages at the MAC level */
+    if (netif->igmp_mac_filter != NULL) {
+      LWIP_DEBUGF(IGMP_DEBUG, ("igmp_start: igmp_mac_filter(ADD "));
+      ip_addr_debug_print(IGMP_DEBUG, &allsystems);
+      LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", netif));
+      netif->igmp_mac_filter(netif, &allsystems, IGMP_ADD_MAC_FILTER);
+    }
+
+    return ERR_OK;
+  }
+
+  return ERR_MEM;
+}
+
+/**
+ * Stop IGMP processing on interface
+ *
+ * @param netif network interface on which stop IGMP processing
+ */
+err_t
+igmp_stop(struct netif *netif)
+{
+  struct igmp_group *group = igmp_group_list;
+  struct igmp_group *prev  = NULL;
+  struct igmp_group *next;
+
+  /* look for groups joined on this interface further down the list */
+  while (group != NULL) {
+    next = group->next;
+    /* is it a group joined on this interface? */
+    if (group->netif == netif) {
+      /* is it the first group of the list? */
+      if (group == igmp_group_list) {
+        igmp_group_list = next;
+      }
+      /* is there a "previous" group defined? */
+      if (prev != NULL) {
+        prev->next = next;
+      }
+      /* disable the group at the MAC level */
+      if (netif->igmp_mac_filter != NULL) {
+        LWIP_DEBUGF(IGMP_DEBUG, ("igmp_stop: igmp_mac_filter(DEL "));
+        ip_addr_debug_print(IGMP_DEBUG, &group->group_address);
+        LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", netif));
+        netif->igmp_mac_filter(netif, &(group->group_address), IGMP_DEL_MAC_FILTER);
+      }
+      /* free group */
+      memp_free(MEMP_IGMP_GROUP, group);
+    } else {
+      /* change the "previous" */
+      prev = group;
+    }
+    /* move to "next" */
+    group = next;
+  }
+  return ERR_OK;
+}
+
+/**
+ * Report IGMP memberships for this interface
+ *
+ * @param netif network interface on which report IGMP memberships
+ */
+void
+igmp_report_groups(struct netif *netif)
+{
+  struct igmp_group *group = igmp_group_list;
+
+  LWIP_DEBUGF(IGMP_DEBUG, ("igmp_report_groups: sending IGMP reports on if %p\n", netif));
+
+  while (group != NULL) {
+    if (group->netif == netif) {
+      igmp_delaying_member(group, IGMP_JOIN_DELAYING_MEMBER_TMR);
+    }
+    group = group->next;
+  }
+}
+
+/**
+ * Search for a group in the global igmp_group_list
+ *
+ * @param ifp the network interface for which to look
+ * @param addr the group ip address to search for
+ * @return a struct igmp_group* if the group has been found,
+ *         NULL if the group wasn't found.
+ */
+struct igmp_group *
+igmp_lookfor_group(struct netif *ifp, ip_addr_t *addr)
+{
+  struct igmp_group *group = igmp_group_list;
+
+  while (group != NULL) {
+    if ((group->netif == ifp) && (ip_addr_cmp(&(group->group_address), addr))) {
+      return group;
+    }
+    group = group->next;
+  }
+
+  /* to be clearer, we return NULL here instead of
+   * 'group' (which is also NULL at this point).
+   */
+  return NULL;
+}
+
+/**
+ * Search for a specific igmp group and create a new one if not found-
+ *
+ * @param ifp the network interface for which to look
+ * @param addr the group ip address to search
+ * @return a struct igmp_group*,
+ *         NULL on memory error.
+ */
+struct igmp_group *
+igmp_lookup_group(struct netif *ifp, ip_addr_t *addr)
+{
+  struct igmp_group *group = igmp_group_list;
+  
+  /* Search if the group already exists */
+  group = igmp_lookfor_group(ifp, addr);
+  if (group != NULL) {
+    /* Group already exists. */
+    return group;
+  }
+
+  /* Group doesn't exist yet, create a new one */
+  group = (struct igmp_group *)memp_malloc(MEMP_IGMP_GROUP);
+  if (group != NULL) {
+    group->netif              = ifp;
+    ip_addr_set(&(group->group_address), addr);
+    group->timer              = 0; /* Not running */
+    group->group_state        = IGMP_GROUP_NON_MEMBER;
+    group->last_reporter_flag = 0;
+    group->use                = 0;
+    group->next               = igmp_group_list;
+    
+    igmp_group_list = group;
+  }
+
+  LWIP_DEBUGF(IGMP_DEBUG, ("igmp_lookup_group: %sallocated a new group with address ", (group?"":"impossible to ")));
+  ip_addr_debug_print(IGMP_DEBUG, addr);
+  LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", ifp));
+
+  return group;
+}
+
+/**
+ * Remove a group in the global igmp_group_list
+ *
+ * @param group the group to remove from the global igmp_group_list
+ * @return ERR_OK if group was removed from the list, an err_t otherwise
+ */
+static err_t
+igmp_remove_group(struct igmp_group *group)
+{
+  err_t err = ERR_OK;
+
+  /* Is it the first group? */
+  if (igmp_group_list == group) {
+    igmp_group_list = group->next;
+  } else {
+    /* look for group further down the list */
+    struct igmp_group *tmpGroup;
+    for (tmpGroup = igmp_group_list; tmpGroup != NULL; tmpGroup = tmpGroup->next) {
+      if (tmpGroup->next == group) {
+        tmpGroup->next = group->next;
+        break;
+      }
+    }
+    /* Group not found in the global igmp_group_list */
+    if (tmpGroup == NULL)
+      err = ERR_ARG;
+  }
+  /* free group */
+  memp_free(MEMP_IGMP_GROUP, group);
+
+  return err;
+}
+
+/**
+ * Called from ip_input() if a new IGMP packet is received.
+ *
+ * @param p received igmp packet, p->payload pointing to the igmp header
+ * @param inp network interface on which the packet was received
+ * @param dest destination ip address of the igmp packet
+ */
+void
+igmp_input(struct pbuf *p, struct netif *inp, ip_addr_t *dest)
+{
+  struct igmp_msg*   igmp;
+  struct igmp_group* group;
+  struct igmp_group* groupref;
+
+  IGMP_STATS_INC(igmp.recv);
+
+  /* Note that the length CAN be greater than 8 but only 8 are used - All are included in the checksum */    
+  if (p->len < IGMP_MINLEN) {
+    pbuf_free(p);
+    IGMP_STATS_INC(igmp.lenerr);
+    LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: length error\n"));
+    return;
+  }
+
+  LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: message from "));
+  ip_addr_debug_print(IGMP_DEBUG, &(ip_current_header()->src));
+  LWIP_DEBUGF(IGMP_DEBUG, (" to address "));
+  ip_addr_debug_print(IGMP_DEBUG, &(ip_current_header()->dest));
+  LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", inp));
+
+  /* Now calculate and check the checksum */
+  igmp = (struct igmp_msg *)p->payload;
+  if (inet_chksum(igmp, p->len)) {
+    pbuf_free(p);
+    IGMP_STATS_INC(igmp.chkerr);
+    LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: checksum error\n"));
+    return;
+  }
+
+  /* Packet is ok so find an existing group */
+  group = igmp_lookfor_group(inp, dest); /* use the destination IP address of incoming packet */
+  
+  /* If group can be found or create... */
+  if (!group) {
+    pbuf_free(p);
+    IGMP_STATS_INC(igmp.drop);
+    LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP frame not for us\n"));
+    return;
+  }
+
+  /* NOW ACT ON THE INCOMING MESSAGE TYPE... */
+  switch (igmp->igmp_msgtype) {
+   case IGMP_MEMB_QUERY: {
+     /* IGMP_MEMB_QUERY to the "all systems" address ? */
+     if ((ip_addr_cmp(dest, &allsystems)) && ip_addr_isany(&igmp->igmp_group_address)) {
+       /* THIS IS THE GENERAL QUERY */
+       LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: General IGMP_MEMB_QUERY on \"ALL SYSTEMS\" address (224.0.0.1) [igmp_maxresp=%i]\n", (int)(igmp->igmp_maxresp)));
+
+       if (igmp->igmp_maxresp == 0) {
+         IGMP_STATS_INC(igmp.rx_v1);
+         LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: got an all hosts query with time== 0 - this is V1 and not implemented - treat as v2\n"));
+         igmp->igmp_maxresp = IGMP_V1_DELAYING_MEMBER_TMR;
+       } else {
+         IGMP_STATS_INC(igmp.rx_general);
+       }
+
+       groupref = igmp_group_list;
+       while (groupref) {
+         /* Do not send messages on the all systems group address! */
+         if ((groupref->netif == inp) && (!(ip_addr_cmp(&(groupref->group_address), &allsystems)))) {
+           igmp_delaying_member(groupref, igmp->igmp_maxresp);
+         }
+         groupref = groupref->next;
+       }
+     } else {
+       /* IGMP_MEMB_QUERY to a specific group ? */
+       if (!ip_addr_isany(&igmp->igmp_group_address)) {
+         LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP_MEMB_QUERY to a specific group "));
+         ip_addr_debug_print(IGMP_DEBUG, &igmp->igmp_group_address);
+         if (ip_addr_cmp(dest, &allsystems)) {
+           ip_addr_t groupaddr;
+           LWIP_DEBUGF(IGMP_DEBUG, (" using \"ALL SYSTEMS\" address (224.0.0.1) [igmp_maxresp=%i]\n", (int)(igmp->igmp_maxresp)));
+           /* we first need to re-look for the group since we used dest last time */
+           ip_addr_copy(groupaddr, igmp->igmp_group_address);
+           group = igmp_lookfor_group(inp, &groupaddr);
+         } else {
+           LWIP_DEBUGF(IGMP_DEBUG, (" with the group address as destination [igmp_maxresp=%i]\n", (int)(igmp->igmp_maxresp)));
+         }
+
+         if (group != NULL) {
+           IGMP_STATS_INC(igmp.rx_group);
+           igmp_delaying_member(group, igmp->igmp_maxresp);
+         } else {
+           IGMP_STATS_INC(igmp.drop);
+         }
+       } else {
+         IGMP_STATS_INC(igmp.proterr);
+       }
+     }
+     break;
+   }
+   case IGMP_V2_MEMB_REPORT: {
+     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP_V2_MEMB_REPORT\n"));
+     IGMP_STATS_INC(igmp.rx_report);
+     if (group->group_state == IGMP_GROUP_DELAYING_MEMBER) {
+       /* This is on a specific group we have already looked up */
+       group->timer = 0; /* stopped */
+       group->group_state = IGMP_GROUP_IDLE_MEMBER;
+       group->last_reporter_flag = 0;
+     }
+     break;
+   }
+   default: {
+     LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: unexpected msg %d in state %d on group %p on if %p\n",
+       igmp->igmp_msgtype, group->group_state, &group, group->netif));
+     IGMP_STATS_INC(igmp.proterr);
+     break;
+   }
+  }
+
+  pbuf_free(p);
+  return;
+}
+
+/**
+ * Join a group on one network interface.
+ *
+ * @param ifaddr ip address of the network interface which should join a new group
+ * @param groupaddr the ip address of the group which to join
+ * @return ERR_OK if group was joined on the netif(s), an err_t otherwise
+ */
+err_t
+igmp_joingroup(ip_addr_t *ifaddr, ip_addr_t *groupaddr)
+{
+  err_t              err = ERR_VAL; /* no matching interface */
+  struct igmp_group *group;
+  struct netif      *netif;
+
+  /* make sure it is multicast address */
+  LWIP_ERROR("igmp_joingroup: attempt to join non-multicast address", ip_addr_ismulticast(groupaddr), return ERR_VAL;);
+  LWIP_ERROR("igmp_joingroup: attempt to join allsystems address", (!ip_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;);
+
+  /* loop through netif's */
+  netif = netif_list;
+  while (netif != NULL) {
+    /* Should we join this interface ? */
+    if ((netif->flags & NETIF_FLAG_IGMP) && ((ip_addr_isany(ifaddr) || ip_addr_cmp(&(netif->ip_addr), ifaddr)))) {
+      /* find group or create a new one if not found */
+      group = igmp_lookup_group(netif, groupaddr);
+
+      if (group != NULL) {
+        /* This should create a new group, check the state to make sure */
+        if (group->group_state != IGMP_GROUP_NON_MEMBER) {
+          LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup: join to group not in state IGMP_GROUP_NON_MEMBER\n"));
+        } else {
+          /* OK - it was new group */
+          LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup: join to new group: "));
+          ip_addr_debug_print(IGMP_DEBUG, groupaddr);
+          LWIP_DEBUGF(IGMP_DEBUG, ("\n"));
+
+          /* If first use of the group, allow the group at the MAC level */
+          if ((group->use==0) && (netif->igmp_mac_filter != NULL)) {
+            LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup: igmp_mac_filter(ADD "));
+            ip_addr_debug_print(IGMP_DEBUG, groupaddr);
+            LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", netif));
+            netif->igmp_mac_filter(netif, groupaddr, IGMP_ADD_MAC_FILTER);
+          }
+
+          IGMP_STATS_INC(igmp.tx_join);
+          igmp_send(group, IGMP_V2_MEMB_REPORT);
+
+          igmp_start_timer(group, IGMP_JOIN_DELAYING_MEMBER_TMR);
+
+          /* Need to work out where this timer comes from */
+          group->group_state = IGMP_GROUP_DELAYING_MEMBER;
+        }
+        /* Increment group use */
+        group->use++;
+        /* Join on this interface */
+        err = ERR_OK;
+      } else {
+        /* Return an error even if some network interfaces are joined */
+        /** @todo undo any other netif already joined */
+        LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup: Not enought memory to join to group\n"));
+        return ERR_MEM;
+      }
+    }
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+
+  return err;
+}
+
+/**
+ * Leave a group on one network interface.
+ *
+ * @param ifaddr ip address of the network interface which should leave a group
+ * @param groupaddr the ip address of the group which to leave
+ * @return ERR_OK if group was left on the netif(s), an err_t otherwise
+ */
+err_t
+igmp_leavegroup(ip_addr_t *ifaddr, ip_addr_t *groupaddr)
+{
+  err_t              err = ERR_VAL; /* no matching interface */
+  struct igmp_group *group;
+  struct netif      *netif;
+
+  /* make sure it is multicast address */
+  LWIP_ERROR("igmp_leavegroup: attempt to leave non-multicast address", ip_addr_ismulticast(groupaddr), return ERR_VAL;);
+  LWIP_ERROR("igmp_leavegroup: attempt to leave allsystems address", (!ip_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;);
+
+  /* loop through netif's */
+  netif = netif_list;
+  while (netif != NULL) {
+    /* Should we leave this interface ? */
+    if ((netif->flags & NETIF_FLAG_IGMP) && ((ip_addr_isany(ifaddr) || ip_addr_cmp(&(netif->ip_addr), ifaddr)))) {
+      /* find group */
+      group = igmp_lookfor_group(netif, groupaddr);
+
+      if (group != NULL) {
+        /* Only send a leave if the flag is set according to the state diagram */
+        LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: Leaving group: "));
+        ip_addr_debug_print(IGMP_DEBUG, groupaddr);
+        LWIP_DEBUGF(IGMP_DEBUG, ("\n"));
+
+        /* If there is no other use of the group */
+        if (group->use <= 1) {
+          /* If we are the last reporter for this group */
+          if (group->last_reporter_flag) {
+            LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: sending leaving group\n"));
+            IGMP_STATS_INC(igmp.tx_leave);
+            igmp_send(group, IGMP_LEAVE_GROUP);
+          }
+          
+          /* Disable the group at the MAC level */
+          if (netif->igmp_mac_filter != NULL) {
+            LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: igmp_mac_filter(DEL "));
+            ip_addr_debug_print(IGMP_DEBUG, groupaddr);
+            LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", netif));
+            netif->igmp_mac_filter(netif, groupaddr, IGMP_DEL_MAC_FILTER);
+          }
+          
+          LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: remove group: "));
+          ip_addr_debug_print(IGMP_DEBUG, groupaddr);
+          LWIP_DEBUGF(IGMP_DEBUG, ("\n"));          
+          
+          /* Free the group */
+          igmp_remove_group(group);
+        } else {
+          /* Decrement group use */
+          group->use--;
+        }
+        /* Leave on this interface */
+        err = ERR_OK;
+      } else {
+        /* It's not a fatal error on "leavegroup" */
+        LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup: not member of group\n"));
+      }
+    }
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+
+  return err;
+}
+
+/**
+ * The igmp timer function (both for NO_SYS=1 and =0)
+ * Should be called every IGMP_TMR_INTERVAL milliseconds (100 ms is default).
+ */
+void
+igmp_tmr(void)
+{
+  struct igmp_group *group = igmp_group_list;
+
+  while (group != NULL) {
+    if (group->timer > 0) {
+      group->timer--;
+      if (group->timer == 0) {
+        igmp_timeout(group);
+      }
+    }
+    group = group->next;
+  }
+}
+
+/**
+ * Called if a timeout for one group is reached.
+ * Sends a report for this group.
+ *
+ * @param group an igmp_group for which a timeout is reached
+ */
+static void
+igmp_timeout(struct igmp_group *group)
+{
+  /* If the state is IGMP_GROUP_DELAYING_MEMBER then we send a report for this group */
+  if (group->group_state == IGMP_GROUP_DELAYING_MEMBER) {
+    LWIP_DEBUGF(IGMP_DEBUG, ("igmp_timeout: report membership for group with address "));
+    ip_addr_debug_print(IGMP_DEBUG, &(group->group_address));
+    LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", group->netif));
+
+    IGMP_STATS_INC(igmp.tx_report);
+    igmp_send(group, IGMP_V2_MEMB_REPORT);
+  }
+}
+
+/**
+ * Start a timer for an igmp group
+ *
+ * @param group the igmp_group for which to start a timer
+ * @param max_time the time in multiples of IGMP_TMR_INTERVAL (decrease with
+ *        every call to igmp_tmr())
+ */
+static void
+igmp_start_timer(struct igmp_group *group, u8_t max_time)
+{
+  /* ensure the input value is > 0 */
+  if (max_time == 0) {
+    max_time = 1;
+  }
+#ifdef LWIP_RAND
+  /* ensure the random value is > 0 */
+  group->timer = (LWIP_RAND() % (max_time - 1)) + 1;
+#endif /* LWIP_RAND */
+}
+
+/**
+ * Delaying membership report for a group if necessary
+ *
+ * @param group the igmp_group for which "delaying" membership report
+ * @param maxresp query delay
+ */
+static void
+igmp_delaying_member(struct igmp_group *group, u8_t maxresp)
+{
+  if ((group->group_state == IGMP_GROUP_IDLE_MEMBER) ||
+     ((group->group_state == IGMP_GROUP_DELAYING_MEMBER) &&
+      ((group->timer == 0) || (maxresp < group->timer)))) {
+    igmp_start_timer(group, maxresp);
+    group->group_state = IGMP_GROUP_DELAYING_MEMBER;
+  }
+}
+
+
+/**
+ * Sends an IP packet on a network interface. This function constructs the IP header
+ * and calculates the IP header checksum. If the source IP address is NULL,
+ * the IP address of the outgoing network interface is filled in as source address.
+ *
+ * @param p the packet to send (p->payload points to the data, e.g. next
+            protocol header; if dest == IP_HDRINCL, p already includes an IP
+            header and p->payload points to that IP header)
+ * @param src the source IP address to send from (if src == IP_ADDR_ANY, the
+ *         IP  address of the netif used to send is used as source address)
+ * @param dest the destination IP address to send the packet to
+ * @param ttl the TTL value to be set in the IP header
+ * @param proto the PROTOCOL to be set in the IP header
+ * @param netif the netif on which to send this packet
+ * @return ERR_OK if the packet was sent OK
+ *         ERR_BUF if p doesn't have enough space for IP/LINK headers
+ *         returns errors returned by netif->output
+ */
+static err_t
+igmp_ip_output_if(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest, struct netif *netif)
+{
+  /* This is the "router alert" option */
+  u16_t ra[2];
+  ra[0] = PP_HTONS(ROUTER_ALERT);
+  ra[1] = 0x0000; /* Router shall examine packet */
+  IGMP_STATS_INC(igmp.xmit);
+  return ip_output_if_opt(p, src, dest, IGMP_TTL, 0, IP_PROTO_IGMP, netif, ra, ROUTER_ALERTLEN);
+}
+
+/**
+ * Send an igmp packet to a specific group.
+ *
+ * @param group the group to which to send the packet
+ * @param type the type of igmp packet to send
+ */
+static void
+igmp_send(struct igmp_group *group, u8_t type)
+{
+  struct pbuf*     p    = NULL;
+  struct igmp_msg* igmp = NULL;
+  ip_addr_t   src  = *IP_ADDR_ANY;
+  ip_addr_t*  dest = NULL;
+
+  /* IP header + "router alert" option + IGMP header */
+  p = pbuf_alloc(PBUF_TRANSPORT, IGMP_MINLEN, PBUF_RAM);
+  
+  if (p) {
+    igmp = (struct igmp_msg *)p->payload;
+    LWIP_ASSERT("igmp_send: check that first pbuf can hold struct igmp_msg",
+               (p->len >= sizeof(struct igmp_msg)));
+    ip_addr_copy(src, group->netif->ip_addr);
+     
+    if (type == IGMP_V2_MEMB_REPORT) {
+      dest = &(group->group_address);
+      ip_addr_copy(igmp->igmp_group_address, group->group_address);
+      group->last_reporter_flag = 1; /* Remember we were the last to report */
+    } else {
+      if (type == IGMP_LEAVE_GROUP) {
+        dest = &allrouters;
+        ip_addr_copy(igmp->igmp_group_address, group->group_address);
+      }
+    }
+
+    if ((type == IGMP_V2_MEMB_REPORT) || (type == IGMP_LEAVE_GROUP)) {
+      igmp->igmp_msgtype  = type;
+      igmp->igmp_maxresp  = 0;
+      igmp->igmp_checksum = 0;
+      igmp->igmp_checksum = inet_chksum(igmp, IGMP_MINLEN);
+
+      igmp_ip_output_if(p, &src, dest, group->netif);
+    }
+
+    pbuf_free(p);
+  } else {
+    LWIP_DEBUGF(IGMP_DEBUG, ("igmp_send: not enough memory for igmp_send\n"));
+    IGMP_STATS_INC(igmp.memerr);
+  }
+}
+
+#endif /* LWIP_IGMP */
diff --git a/external/badvpn_dns/lwip/src/core/ipv4/ip4.c b/external/badvpn_dns/lwip/src/core/ipv4/ip4.c
new file mode 100644
index 0000000..1acc255
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv4/ip4.c
@@ -0,0 +1,924 @@
+/**
+ * @file
+ * This is the IPv4 layer implementation for incoming and outgoing IP traffic.
+ * 
+ * @see ip_frag.c
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+#include "lwip/ip.h"
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/ip_frag.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/netif.h"
+#include "lwip/icmp.h"
+#include "lwip/igmp.h"
+#include "lwip/raw.h"
+#include "lwip/udp.h"
+#include "lwip/tcp_impl.h"
+#include "lwip/snmp.h"
+#include "lwip/dhcp.h"
+#include "lwip/autoip.h"
+#include "lwip/stats.h"
+#include "arch/perf.h"
+
+#include <string.h>
+
+/** Set this to 0 in the rare case of wanting to call an extra function to
+ * generate the IP checksum (in contrast to calculating it on-the-fly). */
+#ifndef LWIP_INLINE_IP_CHKSUM
+#define LWIP_INLINE_IP_CHKSUM   1
+#endif
+#if LWIP_INLINE_IP_CHKSUM && CHECKSUM_GEN_IP
+#define CHECKSUM_GEN_IP_INLINE  1
+#else
+#define CHECKSUM_GEN_IP_INLINE  0
+#endif
+
+#if LWIP_DHCP || defined(LWIP_IP_ACCEPT_UDP_PORT)
+#define IP_ACCEPT_LINK_LAYER_ADDRESSING 1
+
+/** Some defines for DHCP to let link-layer-addressed packets through while the
+ * netif is down.
+ * To use this in your own application/protocol, define LWIP_IP_ACCEPT_UDP_PORT
+ * to return 1 if the port is accepted and 0 if the port is not accepted.
+ */
+#if LWIP_DHCP && defined(LWIP_IP_ACCEPT_UDP_PORT)
+/* accept DHCP client port and custom port */
+#define IP_ACCEPT_LINK_LAYER_ADDRESSED_PORT(port) (((port) == PP_NTOHS(DHCP_CLIENT_PORT)) \
+         || (LWIP_IP_ACCEPT_UDP_PORT(port)))
+#elif defined(LWIP_IP_ACCEPT_UDP_PORT) /* LWIP_DHCP && defined(LWIP_IP_ACCEPT_UDP_PORT) */
+/* accept custom port only */
+#define IP_ACCEPT_LINK_LAYER_ADDRESSED_PORT(port) (LWIP_IP_ACCEPT_UDP_PORT(port))
+#else /* LWIP_DHCP && defined(LWIP_IP_ACCEPT_UDP_PORT) */
+/* accept DHCP client port only */
+#define IP_ACCEPT_LINK_LAYER_ADDRESSED_PORT(port) ((port) == PP_NTOHS(DHCP_CLIENT_PORT))
+#endif /* LWIP_DHCP && defined(LWIP_IP_ACCEPT_UDP_PORT) */
+
+#else /* LWIP_DHCP */
+#define IP_ACCEPT_LINK_LAYER_ADDRESSING 0
+#endif /* LWIP_DHCP */
+
+/** Global data for both IPv4 and IPv6 */
+struct ip_globals ip_data;
+
+/** The IP header ID of the next outgoing IP packet */
+static u16_t ip_id;
+
+/**
+ * Finds the appropriate network interface for a given IP address. It
+ * searches the list of network interfaces linearly. A match is found
+ * if the masked IP address of the network interface equals the masked
+ * IP address given to the function.
+ *
+ * @param dest the destination IP address for which to find the route
+ * @return the netif on which to send to reach dest
+ */
+struct netif *
+ip_route(ip_addr_t *dest)
+{
+  struct netif *netif;
+
+#ifdef LWIP_HOOK_IP4_ROUTE
+  netif = LWIP_HOOK_IP4_ROUTE(dest);
+  if (netif != NULL) {
+    return netif;
+  }
+#endif
+
+  /* iterate through netifs */
+  for (netif = netif_list; netif != NULL; netif = netif->next) {
+    /* network mask matches? */
+    if (netif_is_up(netif)) {
+      if (ip_addr_netcmp(dest, &(netif->ip_addr), &(netif->netmask))) {
+        /* return netif on which to forward IP packet */
+        return netif;
+      }
+    }
+  }
+  if ((netif_default == NULL) || (!netif_is_up(netif_default))) {
+    LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip_route: No route to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+      ip4_addr1_16(dest), ip4_addr2_16(dest), ip4_addr3_16(dest), ip4_addr4_16(dest)));
+    IP_STATS_INC(ip.rterr);
+    snmp_inc_ipoutnoroutes();
+    return NULL;
+  }
+  /* no matching netif found, use default netif */
+  return netif_default;
+}
+
+#if IP_FORWARD
+/**
+ * Determine whether an IP address is in a reserved set of addresses
+ * that may not be forwarded, or whether datagrams to that destination
+ * may be forwarded.
+ * @param p the packet to forward
+ * @param dest the destination IP address
+ * @return 1: can forward 0: discard
+ */
+static int
+ip_canforward(struct pbuf *p)
+{
+  u32_t addr = htonl(ip4_addr_get_u32(ip_current_dest_addr()));
+
+  if (p->flags & PBUF_FLAG_LLBCAST) {
+    /* don't route link-layer broadcasts */
+    return 0;
+  }
+  if ((p->flags & PBUF_FLAG_LLMCAST) && !IP_MULTICAST(addr)) {
+    /* don't route link-layer multicasts unless the destination address is an IP
+       multicast address */
+    return 0;
+  }
+  if (IP_EXPERIMENTAL(addr)) {
+    return 0;
+  }
+  if (IP_CLASSA(addr)) {
+    u32_t net = addr & IP_CLASSA_NET;
+    if ((net == 0) || (net == ((u32_t)IP_LOOPBACKNET << IP_CLASSA_NSHIFT))) {
+      /* don't route loopback packets */
+      return 0;
+    }
+  }
+  return 1;
+}
+
+/**
+ * Forwards an IP packet. It finds an appropriate route for the
+ * packet, decrements the TTL value of the packet, adjusts the
+ * checksum and outputs the packet on the appropriate interface.
+ *
+ * @param p the packet to forward (p->payload points to IP header)
+ * @param iphdr the IP header of the input packet
+ * @param inp the netif on which this packet was received
+ */
+static void
+ip_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp)
+{
+  struct netif *netif;
+
+  PERF_START;
+
+  if (!ip_canforward(p)) {
+    goto return_noroute;
+  }
+
+  /* RFC3927 2.7: do not forward link-local addresses */
+  if (ip_addr_islinklocal(ip_current_dest_addr())) {
+    LWIP_DEBUGF(IP_DEBUG, ("ip_forward: not forwarding LLA %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+      ip4_addr1_16(ip_current_dest_addr()), ip4_addr2_16(ip_current_dest_addr()),
+      ip4_addr3_16(ip_current_dest_addr()), ip4_addr4_16(ip_current_dest_addr())));
+    goto return_noroute;
+  }
+
+  /* Find network interface where to forward this IP packet to. */
+  netif = ip_route(ip_current_dest_addr());
+  if (netif == NULL) {
+    LWIP_DEBUGF(IP_DEBUG, ("ip_forward: no forwarding route for %"U16_F".%"U16_F".%"U16_F".%"U16_F" found\n",
+      ip4_addr1_16(ip_current_dest_addr()), ip4_addr2_16(ip_current_dest_addr()),
+      ip4_addr3_16(ip_current_dest_addr()), ip4_addr4_16(ip_current_dest_addr())));
+    /* @todo: send ICMP_DUR_NET? */
+    goto return_noroute;
+  }
+#if !IP_FORWARD_ALLOW_TX_ON_RX_NETIF
+  /* Do not forward packets onto the same network interface on which
+   * they arrived. */
+  if (netif == inp) {
+    LWIP_DEBUGF(IP_DEBUG, ("ip_forward: not bouncing packets back on incoming interface.\n"));
+    goto return_noroute;
+  }
+#endif /* IP_FORWARD_ALLOW_TX_ON_RX_NETIF */
+
+  /* decrement TTL */
+  IPH_TTL_SET(iphdr, IPH_TTL(iphdr) - 1);
+  /* send ICMP if TTL == 0 */
+  if (IPH_TTL(iphdr) == 0) {
+    snmp_inc_ipinhdrerrors();
+#if LWIP_ICMP
+    /* Don't send ICMP messages in response to ICMP messages */
+    if (IPH_PROTO(iphdr) != IP_PROTO_ICMP) {
+      icmp_time_exceeded(p, ICMP_TE_TTL);
+    }
+#endif /* LWIP_ICMP */
+    return;
+  }
+
+  /* Incrementally update the IP checksum. */
+  if (IPH_CHKSUM(iphdr) >= PP_HTONS(0xffffU - 0x100)) {
+    IPH_CHKSUM_SET(iphdr, IPH_CHKSUM(iphdr) + PP_HTONS(0x100) + 1);
+  } else {
+    IPH_CHKSUM_SET(iphdr, IPH_CHKSUM(iphdr) + PP_HTONS(0x100));
+  }
+
+  LWIP_DEBUGF(IP_DEBUG, ("ip_forward: forwarding packet to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+    ip4_addr1_16(ip_current_dest_addr()), ip4_addr2_16(ip_current_dest_addr()),
+    ip4_addr3_16(ip_current_dest_addr()), ip4_addr4_16(ip_current_dest_addr())));
+
+  IP_STATS_INC(ip.fw);
+  IP_STATS_INC(ip.xmit);
+  snmp_inc_ipforwdatagrams();
+
+  PERF_STOP("ip_forward");
+  /* don't fragment if interface has mtu set to 0 [loopif] */
+  if (netif->mtu && (p->tot_len > netif->mtu)) {
+    if ((IPH_OFFSET(iphdr) & PP_NTOHS(IP_DF)) == 0) {
+#if IP_FRAG
+      ip_frag(p, netif, ip_current_dest_addr());
+#else /* IP_FRAG */
+      /* @todo: send ICMP Destination Unreacheable code 13 "Communication administratively prohibited"? */
+#endif /* IP_FRAG */
+    } else {
+      /* send ICMP Destination Unreacheable code 4: "Fragmentation Needed and DF Set" */
+      icmp_dest_unreach(p, ICMP_DUR_FRAG);
+    }
+    return;
+  }
+  /* transmit pbuf on chosen interface */
+  netif->output(netif, p, ip_current_dest_addr());
+  return;
+return_noroute:
+  snmp_inc_ipoutnoroutes();
+}
+#endif /* IP_FORWARD */
+
+/**
+ * This function is called by the network interface device driver when
+ * an IP packet is received. The function does the basic checks of the
+ * IP header such as packet size being at least larger than the header
+ * size etc. If the packet was not destined for us, the packet is
+ * forwarded (using ip_forward). The IP checksum is always checked.
+ *
+ * Finally, the packet is sent to the upper layer protocol input function.
+ * 
+ * @param p the received IP packet (p->payload points to IP header)
+ * @param inp the netif on which this packet was received
+ * @return ERR_OK if the packet was processed (could return ERR_* if it wasn't
+ *         processed, but currently always returns ERR_OK)
+ */
+err_t
+ip_input(struct pbuf *p, struct netif *inp)
+{
+  struct ip_hdr *iphdr;
+  struct netif *netif;
+  u16_t iphdr_hlen;
+  u16_t iphdr_len;
+#if IP_ACCEPT_LINK_LAYER_ADDRESSING
+  int check_ip_src=1;
+#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
+
+  IP_STATS_INC(ip.recv);
+  snmp_inc_ipinreceives();
+
+  /* identify the IP header */
+  iphdr = (struct ip_hdr *)p->payload;
+  if (IPH_V(iphdr) != 4) {
+    LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_WARNING, ("IP packet dropped due to bad version number %"U16_F"\n", IPH_V(iphdr)));
+    ip_debug_print(p);
+    pbuf_free(p);
+    IP_STATS_INC(ip.err);
+    IP_STATS_INC(ip.drop);
+    snmp_inc_ipinhdrerrors();
+    return ERR_OK;
+  }
+
+#ifdef LWIP_HOOK_IP4_INPUT
+  if (LWIP_HOOK_IP4_INPUT(p, inp)) {
+    /* the packet has been eaten */
+    return ERR_OK;
+  }
+#endif
+
+  /* obtain IP header length in number of 32-bit words */
+  iphdr_hlen = IPH_HL(iphdr);
+  /* calculate IP header length in bytes */
+  iphdr_hlen *= 4;
+  /* obtain ip length in bytes */
+  iphdr_len = ntohs(IPH_LEN(iphdr));
+
+  /* header length exceeds first pbuf length, or ip length exceeds total pbuf length? */
+  if ((iphdr_hlen > p->len) || (iphdr_len > p->tot_len)) {
+    if (iphdr_hlen > p->len) {
+      LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+        ("IP header (len %"U16_F") does not fit in first pbuf (len %"U16_F"), IP packet dropped.\n",
+        iphdr_hlen, p->len));
+    }
+    if (iphdr_len > p->tot_len) {
+      LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+        ("IP (len %"U16_F") is longer than pbuf (len %"U16_F"), IP packet dropped.\n",
+        iphdr_len, p->tot_len));
+    }
+    /* free (drop) packet pbufs */
+    pbuf_free(p);
+    IP_STATS_INC(ip.lenerr);
+    IP_STATS_INC(ip.drop);
+    snmp_inc_ipindiscards();
+    return ERR_OK;
+  }
+
+  /* verify checksum */
+#if CHECKSUM_CHECK_IP
+  if (inet_chksum(iphdr, iphdr_hlen) != 0) {
+
+    LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+      ("Checksum (0x%"X16_F") failed, IP packet dropped.\n", inet_chksum(iphdr, iphdr_hlen)));
+    ip_debug_print(p);
+    pbuf_free(p);
+    IP_STATS_INC(ip.chkerr);
+    IP_STATS_INC(ip.drop);
+    snmp_inc_ipinhdrerrors();
+    return ERR_OK;
+  }
+#endif
+
+  /* Trim pbuf. This should have been done at the netif layer,
+   * but we'll do it anyway just to be sure that its done. */
+  pbuf_realloc(p, iphdr_len);
+
+  /* copy IP addresses to aligned ip_addr_t */
+  ip_addr_copy(*ipX_2_ip(&ip_data.current_iphdr_dest), iphdr->dest);
+  ip_addr_copy(*ipX_2_ip(&ip_data.current_iphdr_src), iphdr->src);
+
+  /* match packet against an interface, i.e. is this packet for us? */
+#if LWIP_IGMP
+  if (ip_addr_ismulticast(ip_current_dest_addr())) {
+    if ((inp->flags & NETIF_FLAG_IGMP) && (igmp_lookfor_group(inp, ip_current_dest_addr()))) {
+      netif = inp;
+    } else {
+      netif = NULL;
+    }
+  } else
+#endif /* LWIP_IGMP */
+  {
+    /* start trying with inp. if that's not acceptable, start walking the
+       list of configured netifs.
+       'first' is used as a boolean to mark whether we started walking the list */
+    int first = 1;
+    netif = inp;
+    do {
+      LWIP_DEBUGF(IP_DEBUG, ("ip_input: iphdr->dest 0x%"X32_F" netif->ip_addr 0x%"X32_F" (0x%"X32_F", 0x%"X32_F", 0x%"X32_F")\n",
+          ip4_addr_get_u32(&iphdr->dest), ip4_addr_get_u32(&netif->ip_addr),
+          ip4_addr_get_u32(&iphdr->dest) & ip4_addr_get_u32(&netif->netmask),
+          ip4_addr_get_u32(&netif->ip_addr) & ip4_addr_get_u32(&netif->netmask),
+          ip4_addr_get_u32(&iphdr->dest) & ~ip4_addr_get_u32(&netif->netmask)));
+
+      /* interface is up and configured? */
+      if ((netif_is_up(netif)) && (!ip_addr_isany(&(netif->ip_addr)))) {
+        /* unicast to this interface address? */
+        if (ip_addr_cmp(ip_current_dest_addr(), &(netif->ip_addr)) ||
+            /* or broadcast on this interface network address? */
+            ip_addr_isbroadcast(ip_current_dest_addr(), netif)) {
+          LWIP_DEBUGF(IP_DEBUG, ("ip_input: packet accepted on interface %c%c\n",
+              netif->name[0], netif->name[1]));
+          /* break out of for loop */
+          break;
+        }
+#if LWIP_AUTOIP
+        /* connections to link-local addresses must persist after changing
+           the netif's address (RFC3927 ch. 1.9) */
+        if ((netif->autoip != NULL) &&
+            ip_addr_cmp(ip_current_dest_addr(), &(netif->autoip->llipaddr))) {
+          LWIP_DEBUGF(IP_DEBUG, ("ip_input: LLA packet accepted on interface %c%c\n",
+              netif->name[0], netif->name[1]));
+          /* break out of for loop */
+          break;
+        }
+#endif /* LWIP_AUTOIP */
+      }
+      if (first) {
+        first = 0;
+        netif = netif_list;
+      } else {
+        netif = netif->next;
+      }
+      if (netif == inp) {
+        netif = netif->next;
+      }
+    } while(netif != NULL);
+  }
+
+#if IP_ACCEPT_LINK_LAYER_ADDRESSING
+  /* Pass DHCP messages regardless of destination address. DHCP traffic is addressed
+   * using link layer addressing (such as Ethernet MAC) so we must not filter on IP.
+   * According to RFC 1542 section 3.1.1, referred by RFC 2131).
+   *
+   * If you want to accept private broadcast communication while a netif is down,
+   * define LWIP_IP_ACCEPT_UDP_PORT(dst_port), e.g.:
+   *
+   * #define LWIP_IP_ACCEPT_UDP_PORT(dst_port) ((dst_port) == PP_NTOHS(12345))
+   */
+  if (netif == NULL) {
+    /* remote port is DHCP server? */
+    if (IPH_PROTO(iphdr) == IP_PROTO_UDP) {
+      struct udp_hdr *udphdr = (struct udp_hdr *)((u8_t *)iphdr + iphdr_hlen);
+      LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip_input: UDP packet to DHCP client port %"U16_F"\n",
+        ntohs(udphdr->dest)));
+      if (IP_ACCEPT_LINK_LAYER_ADDRESSED_PORT(udphdr->dest)) {
+        LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip_input: DHCP packet accepted.\n"));
+        netif = inp;
+        check_ip_src = 0;
+      }
+    }
+  }
+#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
+
+  /* broadcast or multicast packet source address? Compliant with RFC 1122: 3.2.1.3 */
+#if IP_ACCEPT_LINK_LAYER_ADDRESSING
+  /* DHCP servers need 0.0.0.0 to be allowed as source address (RFC 1.1.2.2: 3.2.1.3/a) */
+  if (check_ip_src && !ip_addr_isany(ip_current_src_addr()))
+#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
+  {  if ((ip_addr_isbroadcast(ip_current_src_addr(), inp)) ||
+         (ip_addr_ismulticast(ip_current_src_addr()))) {
+      /* packet source is not valid */
+      LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("ip_input: packet source is not valid.\n"));
+      /* free (drop) packet pbufs */
+      pbuf_free(p);
+      IP_STATS_INC(ip.drop);
+      snmp_inc_ipinaddrerrors();
+      snmp_inc_ipindiscards();
+      return ERR_OK;
+    }
+  }
+
+  /* if we're pretending we are everyone for TCP, assume the packet is for source interface if it
+     isn't for a local address */
+  if (netif == NULL && (inp->flags & NETIF_FLAG_PRETEND_TCP) && IPH_PROTO(iphdr) == IP_PROTO_TCP) {
+      netif = inp;
+  }
+
+  /* packet not for us? */
+  if (netif == NULL) {
+    /* packet not for us, route or discard */
+    LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip_input: packet not for us.\n"));
+#if IP_FORWARD
+    /* non-broadcast packet? */
+    if (!ip_addr_isbroadcast(ip_current_dest_addr(), inp)) {
+      /* try to forward IP packet on (other) interfaces */
+      ip_forward(p, iphdr, inp);
+    } else
+#endif /* IP_FORWARD */
+    {
+      snmp_inc_ipinaddrerrors();
+      snmp_inc_ipindiscards();
+    }
+    pbuf_free(p);
+    return ERR_OK;
+  }
+  /* packet consists of multiple fragments? */
+  if ((IPH_OFFSET(iphdr) & PP_HTONS(IP_OFFMASK | IP_MF)) != 0) {
+#if IP_REASSEMBLY /* packet fragment reassembly code present? */
+    LWIP_DEBUGF(IP_DEBUG, ("IP packet is a fragment (id=0x%04"X16_F" tot_len=%"U16_F" len=%"U16_F" MF=%"U16_F" offset=%"U16_F"), calling ip_reass()\n",
+      ntohs(IPH_ID(iphdr)), p->tot_len, ntohs(IPH_LEN(iphdr)), !!(IPH_OFFSET(iphdr) & PP_HTONS(IP_MF)), (ntohs(IPH_OFFSET(iphdr)) & IP_OFFMASK)*8));
+    /* reassemble the packet*/
+    p = ip_reass(p);
+    /* packet not fully reassembled yet? */
+    if (p == NULL) {
+      return ERR_OK;
+    }
+    iphdr = (struct ip_hdr *)p->payload;
+#else /* IP_REASSEMBLY == 0, no packet fragment reassembly code present */
+    pbuf_free(p);
+    LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("IP packet dropped since it was fragmented (0x%"X16_F") (while IP_REASSEMBLY == 0).\n",
+      ntohs(IPH_OFFSET(iphdr))));
+    IP_STATS_INC(ip.opterr);
+    IP_STATS_INC(ip.drop);
+    /* unsupported protocol feature */
+    snmp_inc_ipinunknownprotos();
+    return ERR_OK;
+#endif /* IP_REASSEMBLY */
+  }
+
+#if IP_OPTIONS_ALLOWED == 0 /* no support for IP options in the IP header? */
+
+#if LWIP_IGMP
+  /* there is an extra "router alert" option in IGMP messages which we allow for but do not police */
+  if((iphdr_hlen > IP_HLEN) &&  (IPH_PROTO(iphdr) != IP_PROTO_IGMP)) {
+#else
+  if (iphdr_hlen > IP_HLEN) {
+#endif /* LWIP_IGMP */
+    LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("IP packet dropped since there were IP options (while IP_OPTIONS_ALLOWED == 0).\n"));
+    pbuf_free(p);
+    IP_STATS_INC(ip.opterr);
+    IP_STATS_INC(ip.drop);
+    /* unsupported protocol feature */
+    snmp_inc_ipinunknownprotos();
+    return ERR_OK;
+  }
+#endif /* IP_OPTIONS_ALLOWED == 0 */
+
+  /* send to upper layers */
+  LWIP_DEBUGF(IP_DEBUG, ("ip_input: \n"));
+  ip_debug_print(p);
+  LWIP_DEBUGF(IP_DEBUG, ("ip_input: p->len %"U16_F" p->tot_len %"U16_F"\n", p->len, p->tot_len));
+
+  ip_data.current_netif = inp;
+  ip_data.current_ip4_header = iphdr;
+  ip_data.current_ip_header_tot_len = IPH_HL(iphdr) * 4;
+
+#if LWIP_RAW
+  /* raw input did not eat the packet? */
+  if (raw_input(p, inp) == 0)
+#endif /* LWIP_RAW */
+  {
+    pbuf_header(p, -iphdr_hlen); /* Move to payload, no check necessary. */
+
+    switch (IPH_PROTO(iphdr)) {
+#if LWIP_UDP
+    case IP_PROTO_UDP:
+#if LWIP_UDPLITE
+    case IP_PROTO_UDPLITE:
+#endif /* LWIP_UDPLITE */
+      snmp_inc_ipindelivers();
+      udp_input(p, inp);
+      break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+    case IP_PROTO_TCP:
+      snmp_inc_ipindelivers();
+      tcp_input(p, inp);
+      break;
+#endif /* LWIP_TCP */
+#if LWIP_ICMP
+    case IP_PROTO_ICMP:
+      snmp_inc_ipindelivers();
+      icmp_input(p, inp);
+      break;
+#endif /* LWIP_ICMP */
+#if LWIP_IGMP
+    case IP_PROTO_IGMP:
+      igmp_input(p, inp, ip_current_dest_addr());
+      break;
+#endif /* LWIP_IGMP */
+    default:
+#if LWIP_ICMP
+      /* send ICMP destination protocol unreachable unless is was a broadcast */
+      if (!ip_addr_isbroadcast(ip_current_dest_addr(), inp) &&
+          !ip_addr_ismulticast(ip_current_dest_addr())) {
+        pbuf_header(p, iphdr_hlen); /* Move to ip header, no check necessary. */
+        p->payload = iphdr;
+        icmp_dest_unreach(p, ICMP_DUR_PROTO);
+      }
+#endif /* LWIP_ICMP */
+      pbuf_free(p);
+
+      LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("Unsupported transport protocol %"U16_F"\n", IPH_PROTO(iphdr)));
+
+      IP_STATS_INC(ip.proterr);
+      IP_STATS_INC(ip.drop);
+      snmp_inc_ipinunknownprotos();
+    }
+  }
+
+  /* @todo: this is not really necessary... */
+  ip_data.current_netif = NULL;
+  ip_data.current_ip4_header = NULL;
+  ip_data.current_ip_header_tot_len = 0;
+  ip_addr_set_any(ip_current_src_addr());
+  ip_addr_set_any(ip_current_dest_addr());
+
+  return ERR_OK;
+}
+
+/**
+ * Sends an IP packet on a network interface. This function constructs
+ * the IP header and calculates the IP header checksum. If the source
+ * IP address is NULL, the IP address of the outgoing network
+ * interface is filled in as source address.
+ * If the destination IP address is IP_HDRINCL, p is assumed to already
+ * include an IP header and p->payload points to it instead of the data.
+ *
+ * @param p the packet to send (p->payload points to the data, e.g. next
+            protocol header; if dest == IP_HDRINCL, p already includes an IP
+            header and p->payload points to that IP header)
+ * @param src the source IP address to send from (if src == IP_ADDR_ANY, the
+ *         IP  address of the netif used to send is used as source address)
+ * @param dest the destination IP address to send the packet to
+ * @param ttl the TTL value to be set in the IP header
+ * @param tos the TOS value to be set in the IP header
+ * @param proto the PROTOCOL to be set in the IP header
+ * @param netif the netif on which to send this packet
+ * @return ERR_OK if the packet was sent OK
+ *         ERR_BUF if p doesn't have enough space for IP/LINK headers
+ *         returns errors returned by netif->output
+ *
+ * @note ip_id: RFC791 "some host may be able to simply use
+ *  unique identifiers independent of destination"
+ */
+err_t
+ip_output_if(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
+             u8_t ttl, u8_t tos,
+             u8_t proto, struct netif *netif)
+{
+#if IP_OPTIONS_SEND
+  return ip_output_if_opt(p, src, dest, ttl, tos, proto, netif, NULL, 0);
+}
+
+/**
+ * Same as ip_output_if() but with the possibility to include IP options:
+ *
+ * @ param ip_options pointer to the IP options, copied into the IP header
+ * @ param optlen length of ip_options
+ */
+err_t ip_output_if_opt(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
+       u8_t ttl, u8_t tos, u8_t proto, struct netif *netif, void *ip_options,
+       u16_t optlen)
+{
+#endif /* IP_OPTIONS_SEND */
+  struct ip_hdr *iphdr;
+  ip_addr_t dest_addr;
+#if CHECKSUM_GEN_IP_INLINE
+  u32_t chk_sum = 0;
+#endif /* CHECKSUM_GEN_IP_INLINE */
+
+  /* pbufs passed to IP must have a ref-count of 1 as their payload pointer
+     gets altered as the packet is passed down the stack */
+  LWIP_ASSERT("p->ref == 1", p->ref == 1);
+
+  snmp_inc_ipoutrequests();
+
+  /* Should the IP header be generated or is it already included in p? */
+  if (dest != IP_HDRINCL) {
+    u16_t ip_hlen = IP_HLEN;
+#if IP_OPTIONS_SEND
+    u16_t optlen_aligned = 0;
+    if (optlen != 0) {
+#if CHECKSUM_GEN_IP_INLINE
+      int i;
+#endif /* CHECKSUM_GEN_IP_INLINE */
+      /* round up to a multiple of 4 */
+      optlen_aligned = ((optlen + 3) & ~3);
+      ip_hlen += optlen_aligned;
+      /* First write in the IP options */
+      if (pbuf_header(p, optlen_aligned)) {
+        LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip_output_if_opt: not enough room for IP options in pbuf\n"));
+        IP_STATS_INC(ip.err);
+        snmp_inc_ipoutdiscards();
+        return ERR_BUF;
+      }
+      MEMCPY(p->payload, ip_options, optlen);
+      if (optlen < optlen_aligned) {
+        /* zero the remaining bytes */
+        memset(((char*)p->payload) + optlen, 0, optlen_aligned - optlen);
+      }
+#if CHECKSUM_GEN_IP_INLINE
+      for (i = 0; i < optlen_aligned/2; i++) {
+        chk_sum += ((u16_t*)p->payload)[i];
+      }
+#endif /* CHECKSUM_GEN_IP_INLINE */
+    }
+#endif /* IP_OPTIONS_SEND */
+    /* generate IP header */
+    if (pbuf_header(p, IP_HLEN)) {
+      LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip_output: not enough room for IP header in pbuf\n"));
+
+      IP_STATS_INC(ip.err);
+      snmp_inc_ipoutdiscards();
+      return ERR_BUF;
+    }
+
+    iphdr = (struct ip_hdr *)p->payload;
+    LWIP_ASSERT("check that first pbuf can hold struct ip_hdr",
+               (p->len >= sizeof(struct ip_hdr)));
+
+    IPH_TTL_SET(iphdr, ttl);
+    IPH_PROTO_SET(iphdr, proto);
+#if CHECKSUM_GEN_IP_INLINE
+    chk_sum += LWIP_MAKE_U16(proto, ttl);
+#endif /* CHECKSUM_GEN_IP_INLINE */
+
+    /* dest cannot be NULL here */
+    ip_addr_copy(iphdr->dest, *dest);
+#if CHECKSUM_GEN_IP_INLINE
+    chk_sum += ip4_addr_get_u32(&iphdr->dest) & 0xFFFF;
+    chk_sum += ip4_addr_get_u32(&iphdr->dest) >> 16;
+#endif /* CHECKSUM_GEN_IP_INLINE */
+
+    IPH_VHL_SET(iphdr, 4, ip_hlen / 4);
+    IPH_TOS_SET(iphdr, tos);
+#if CHECKSUM_GEN_IP_INLINE
+    chk_sum += LWIP_MAKE_U16(tos, iphdr->_v_hl);
+#endif /* CHECKSUM_GEN_IP_INLINE */
+    IPH_LEN_SET(iphdr, htons(p->tot_len));
+#if CHECKSUM_GEN_IP_INLINE
+    chk_sum += iphdr->_len;
+#endif /* CHECKSUM_GEN_IP_INLINE */
+    IPH_OFFSET_SET(iphdr, 0);
+    IPH_ID_SET(iphdr, htons(ip_id));
+#if CHECKSUM_GEN_IP_INLINE
+    chk_sum += iphdr->_id;
+#endif /* CHECKSUM_GEN_IP_INLINE */
+    ++ip_id;
+
+    if (ip_addr_isany(src)) {
+      ip_addr_copy(iphdr->src, netif->ip_addr);
+    } else {
+      /* src cannot be NULL here */
+      ip_addr_copy(iphdr->src, *src);
+    }
+
+#if CHECKSUM_GEN_IP_INLINE
+    chk_sum += ip4_addr_get_u32(&iphdr->src) & 0xFFFF;
+    chk_sum += ip4_addr_get_u32(&iphdr->src) >> 16;
+    chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF);
+    chk_sum = (chk_sum >> 16) + chk_sum;
+    chk_sum = ~chk_sum;
+    iphdr->_chksum = chk_sum; /* network order */
+#else /* CHECKSUM_GEN_IP_INLINE */
+    IPH_CHKSUM_SET(iphdr, 0);
+#if CHECKSUM_GEN_IP
+    IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen));
+#endif
+#endif /* CHECKSUM_GEN_IP_INLINE */
+  } else {
+    /* IP header already included in p */
+    iphdr = (struct ip_hdr *)p->payload;
+    ip_addr_copy(dest_addr, iphdr->dest);
+    dest = &dest_addr;
+  }
+
+  IP_STATS_INC(ip.xmit);
+
+  LWIP_DEBUGF(IP_DEBUG, ("ip_output_if: %c%c%"U16_F"\n", netif->name[0], netif->name[1], netif->num));
+  ip_debug_print(p);
+
+#if ENABLE_LOOPBACK
+  if (ip_addr_cmp(dest, &netif->ip_addr)) {
+    /* Packet to self, enqueue it for loopback */
+    LWIP_DEBUGF(IP_DEBUG, ("netif_loop_output()"));
+    return netif_loop_output(netif, p, dest);
+  }
+#if LWIP_IGMP
+  if ((p->flags & PBUF_FLAG_MCASTLOOP) != 0) {
+    netif_loop_output(netif, p, dest);
+  }
+#endif /* LWIP_IGMP */
+#endif /* ENABLE_LOOPBACK */
+#if IP_FRAG
+  /* don't fragment if interface has mtu set to 0 [loopif] */
+  if (netif->mtu && (p->tot_len > netif->mtu)) {
+    return ip_frag(p, netif, dest);
+  }
+#endif /* IP_FRAG */
+
+  LWIP_DEBUGF(IP_DEBUG, ("netif->output()"));
+  return netif->output(netif, p, dest);
+}
+
+/**
+ * Simple interface to ip_output_if. It finds the outgoing network
+ * interface and calls upon ip_output_if to do the actual work.
+ *
+ * @param p the packet to send (p->payload points to the data, e.g. next
+            protocol header; if dest == IP_HDRINCL, p already includes an IP
+            header and p->payload points to that IP header)
+ * @param src the source IP address to send from (if src == IP_ADDR_ANY, the
+ *         IP  address of the netif used to send is used as source address)
+ * @param dest the destination IP address to send the packet to
+ * @param ttl the TTL value to be set in the IP header
+ * @param tos the TOS value to be set in the IP header
+ * @param proto the PROTOCOL to be set in the IP header
+ *
+ * @return ERR_RTE if no route is found
+ *         see ip_output_if() for more return values
+ */
+err_t
+ip_output(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
+          u8_t ttl, u8_t tos, u8_t proto)
+{
+  struct netif *netif;
+
+  /* pbufs passed to IP must have a ref-count of 1 as their payload pointer
+     gets altered as the packet is passed down the stack */
+  LWIP_ASSERT("p->ref == 1", p->ref == 1);
+
+  if ((netif = ip_route(dest)) == NULL) {
+    LWIP_DEBUGF(IP_DEBUG, ("ip_output: No route to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+      ip4_addr1_16(dest), ip4_addr2_16(dest), ip4_addr3_16(dest), ip4_addr4_16(dest)));
+    IP_STATS_INC(ip.rterr);
+    return ERR_RTE;
+  }
+
+  return ip_output_if(p, src, dest, ttl, tos, proto, netif);
+}
+
+#if LWIP_NETIF_HWADDRHINT
+/** Like ip_output, but takes and addr_hint pointer that is passed on to netif->addr_hint
+ *  before calling ip_output_if.
+ *
+ * @param p the packet to send (p->payload points to the data, e.g. next
+            protocol header; if dest == IP_HDRINCL, p already includes an IP
+            header and p->payload points to that IP header)
+ * @param src the source IP address to send from (if src == IP_ADDR_ANY, the
+ *         IP  address of the netif used to send is used as source address)
+ * @param dest the destination IP address to send the packet to
+ * @param ttl the TTL value to be set in the IP header
+ * @param tos the TOS value to be set in the IP header
+ * @param proto the PROTOCOL to be set in the IP header
+ * @param addr_hint address hint pointer set to netif->addr_hint before
+ *        calling ip_output_if()
+ *
+ * @return ERR_RTE if no route is found
+ *         see ip_output_if() for more return values
+ */
+err_t
+ip_output_hinted(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
+          u8_t ttl, u8_t tos, u8_t proto, u8_t *addr_hint)
+{
+  struct netif *netif;
+  err_t err;
+
+  /* pbufs passed to IP must have a ref-count of 1 as their payload pointer
+     gets altered as the packet is passed down the stack */
+  LWIP_ASSERT("p->ref == 1", p->ref == 1);
+
+  if ((netif = ip_route(dest)) == NULL) {
+    LWIP_DEBUGF(IP_DEBUG, ("ip_output: No route to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+      ip4_addr1_16(dest), ip4_addr2_16(dest), ip4_addr3_16(dest), ip4_addr4_16(dest)));
+    IP_STATS_INC(ip.rterr);
+    return ERR_RTE;
+  }
+
+  NETIF_SET_HWADDRHINT(netif, addr_hint);
+  err = ip_output_if(p, src, dest, ttl, tos, proto, netif);
+  NETIF_SET_HWADDRHINT(netif, NULL);
+
+  return err;
+}
+#endif /* LWIP_NETIF_HWADDRHINT*/
+
+#if IP_DEBUG
+/* Print an IP header by using LWIP_DEBUGF
+ * @param p an IP packet, p->payload pointing to the IP header
+ */
+void
+ip_debug_print(struct pbuf *p)
+{
+  struct ip_hdr *iphdr = (struct ip_hdr *)p->payload;
+
+  LWIP_DEBUGF(IP_DEBUG, ("IP header:\n"));
+  LWIP_DEBUGF(IP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(IP_DEBUG, ("|%2"S16_F" |%2"S16_F" |  0x%02"X16_F" |     %5"U16_F"     | (v, hl, tos, len)\n",
+                    IPH_V(iphdr),
+                    IPH_HL(iphdr),
+                    IPH_TOS(iphdr),
+                    ntohs(IPH_LEN(iphdr))));
+  LWIP_DEBUGF(IP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(IP_DEBUG, ("|    %5"U16_F"      |%"U16_F"%"U16_F"%"U16_F"|    %4"U16_F"   | (id, flags, offset)\n",
+                    ntohs(IPH_ID(iphdr)),
+                    ntohs(IPH_OFFSET(iphdr)) >> 15 & 1,
+                    ntohs(IPH_OFFSET(iphdr)) >> 14 & 1,
+                    ntohs(IPH_OFFSET(iphdr)) >> 13 & 1,
+                    ntohs(IPH_OFFSET(iphdr)) & IP_OFFMASK));
+  LWIP_DEBUGF(IP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(IP_DEBUG, ("|  %3"U16_F"  |  %3"U16_F"  |    0x%04"X16_F"     | (ttl, proto, chksum)\n",
+                    IPH_TTL(iphdr),
+                    IPH_PROTO(iphdr),
+                    ntohs(IPH_CHKSUM(iphdr))));
+  LWIP_DEBUGF(IP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(IP_DEBUG, ("|  %3"U16_F"  |  %3"U16_F"  |  %3"U16_F"  |  %3"U16_F"  | (src)\n",
+                    ip4_addr1_16(&iphdr->src),
+                    ip4_addr2_16(&iphdr->src),
+                    ip4_addr3_16(&iphdr->src),
+                    ip4_addr4_16(&iphdr->src)));
+  LWIP_DEBUGF(IP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(IP_DEBUG, ("|  %3"U16_F"  |  %3"U16_F"  |  %3"U16_F"  |  %3"U16_F"  | (dest)\n",
+                    ip4_addr1_16(&iphdr->dest),
+                    ip4_addr2_16(&iphdr->dest),
+                    ip4_addr3_16(&iphdr->dest),
+                    ip4_addr4_16(&iphdr->dest)));
+  LWIP_DEBUGF(IP_DEBUG, ("+-------------------------------+\n"));
+}
+#endif /* IP_DEBUG */
diff --git a/external/badvpn_dns/lwip/src/core/ipv4/ip4_addr.c b/external/badvpn_dns/lwip/src/core/ipv4/ip4_addr.c
new file mode 100644
index 0000000..8f633ff
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv4/ip4_addr.c
@@ -0,0 +1,312 @@
+/**
+ * @file
+ * This is the IPv4 address tools implementation.
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+
+/* used by IP_ADDR_ANY and IP_ADDR_BROADCAST in ip_addr.h */
+const ip_addr_t ip_addr_any = { IPADDR_ANY };
+const ip_addr_t ip_addr_broadcast = { IPADDR_BROADCAST };
+
+/**
+ * Determine if an address is a broadcast address on a network interface 
+ * 
+ * @param addr address to be checked
+ * @param netif the network interface against which the address is checked
+ * @return returns non-zero if the address is a broadcast address
+ */
+u8_t
+ip4_addr_isbroadcast(u32_t addr, const struct netif *netif)
+{
+  ip_addr_t ipaddr;
+  ip4_addr_set_u32(&ipaddr, addr);
+
+  /* all ones (broadcast) or all zeroes (old skool broadcast) */
+  if ((~addr == IPADDR_ANY) ||
+      (addr == IPADDR_ANY)) {
+    return 1;
+  /* no broadcast support on this network interface? */
+  } else if ((netif->flags & NETIF_FLAG_BROADCAST) == 0) {
+    /* the given address cannot be a broadcast address
+     * nor can we check against any broadcast addresses */
+    return 0;
+  /* address matches network interface address exactly? => no broadcast */
+  } else if (addr == ip4_addr_get_u32(&netif->ip_addr)) {
+    return 0;
+  /*  on the same (sub) network... */
+  } else if (ip_addr_netcmp(&ipaddr, &(netif->ip_addr), &(netif->netmask))
+         /* ...and host identifier bits are all ones? =>... */
+          && ((addr & ~ip4_addr_get_u32(&netif->netmask)) ==
+           (IPADDR_BROADCAST & ~ip4_addr_get_u32(&netif->netmask)))) {
+    /* => network broadcast address */
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/** Checks if a netmask is valid (starting with ones, then only zeros)
+ *
+ * @param netmask the IPv4 netmask to check (in network byte order!)
+ * @return 1 if the netmask is valid, 0 if it is not
+ */
+u8_t
+ip4_addr_netmask_valid(u32_t netmask)
+{
+  u32_t mask;
+  u32_t nm_hostorder = lwip_htonl(netmask);
+
+  /* first, check for the first zero */
+  for (mask = 1UL << 31 ; mask != 0; mask >>= 1) {
+    if ((nm_hostorder & mask) == 0) {
+      break;
+    }
+  }
+  /* then check that there is no one */
+  for (; mask != 0; mask >>= 1) {
+    if ((nm_hostorder & mask) != 0) {
+      /* there is a one after the first zero -> invalid */
+      return 0;
+    }
+  }
+  /* no one after the first zero -> valid */
+  return 1;
+}
+
+/* Here for now until needed in other places in lwIP */
+#ifndef isprint
+#define in_range(c, lo, up)  ((u8_t)c >= lo && (u8_t)c <= up)
+#define isprint(c)           in_range(c, 0x20, 0x7f)
+#define isdigit(c)           in_range(c, '0', '9')
+#define isxdigit(c)          (isdigit(c) || in_range(c, 'a', 'f') || in_range(c, 'A', 'F'))
+#define islower(c)           in_range(c, 'a', 'z')
+#define isspace(c)           (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v')
+#endif
+
+/**
+ * Ascii internet address interpretation routine.
+ * The value returned is in network order.
+ *
+ * @param cp IP address in ascii represenation (e.g. "127.0.0.1")
+ * @return ip address in network order
+ */
+u32_t
+ipaddr_addr(const char *cp)
+{
+  ip_addr_t val;
+
+  if (ipaddr_aton(cp, &val)) {
+    return ip4_addr_get_u32(&val);
+  }
+  return (IPADDR_NONE);
+}
+
+/**
+ * Check whether "cp" is a valid ascii representation
+ * of an Internet address and convert to a binary address.
+ * Returns 1 if the address is valid, 0 if not.
+ * This replaces inet_addr, the return value from which
+ * cannot distinguish between failure and a local broadcast address.
+ *
+ * @param cp IP address in ascii represenation (e.g. "127.0.0.1")
+ * @param addr pointer to which to save the ip address in network order
+ * @return 1 if cp could be converted to addr, 0 on failure
+ */
+int
+ipaddr_aton(const char *cp, ip_addr_t *addr)
+{
+  u32_t val;
+  u8_t base;
+  char c;
+  u32_t parts[4];
+  u32_t *pp = parts;
+
+  c = *cp;
+  for (;;) {
+    /*
+     * Collect number up to ``.''.
+     * Values are specified as for C:
+     * 0x=hex, 0=octal, 1-9=decimal.
+     */
+    if (!isdigit(c))
+      return (0);
+    val = 0;
+    base = 10;
+    if (c == '0') {
+      c = *++cp;
+      if (c == 'x' || c == 'X') {
+        base = 16;
+        c = *++cp;
+      } else
+        base = 8;
+    }
+    for (;;) {
+      if (isdigit(c)) {
+        val = (val * base) + (int)(c - '0');
+        c = *++cp;
+      } else if (base == 16 && isxdigit(c)) {
+        val = (val << 4) | (int)(c + 10 - (islower(c) ? 'a' : 'A'));
+        c = *++cp;
+      } else
+        break;
+    }
+    if (c == '.') {
+      /*
+       * Internet format:
+       *  a.b.c.d
+       *  a.b.c   (with c treated as 16 bits)
+       *  a.b (with b treated as 24 bits)
+       */
+      if (pp >= parts + 3) {
+        return (0);
+      }
+      *pp++ = val;
+      c = *++cp;
+    } else
+      break;
+  }
+  /*
+   * Check for trailing characters.
+   */
+  if (c != '\0' && !isspace(c)) {
+    return (0);
+  }
+  /*
+   * Concoct the address according to
+   * the number of parts specified.
+   */
+  switch (pp - parts + 1) {
+
+  case 0:
+    return (0);       /* initial nondigit */
+
+  case 1:             /* a -- 32 bits */
+    break;
+
+  case 2:             /* a.b -- 8.24 bits */
+    if (val > 0xffffffUL) {
+      return (0);
+    }
+    val |= parts[0] << 24;
+    break;
+
+  case 3:             /* a.b.c -- 8.8.16 bits */
+    if (val > 0xffff) {
+      return (0);
+    }
+    val |= (parts[0] << 24) | (parts[1] << 16);
+    break;
+
+  case 4:             /* a.b.c.d -- 8.8.8.8 bits */
+    if (val > 0xff) {
+      return (0);
+    }
+    val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8);
+    break;
+  default:
+    LWIP_ASSERT("unhandled", 0);
+    break;
+  }
+  if (addr) {
+    ip4_addr_set_u32(addr, htonl(val));
+  }
+  return (1);
+}
+
+/**
+ * Convert numeric IP address into decimal dotted ASCII representation.
+ * returns ptr to static buffer; not reentrant!
+ *
+ * @param addr ip address in network order to convert
+ * @return pointer to a global static (!) buffer that holds the ASCII
+ *         represenation of addr
+ */
+char *
+ipaddr_ntoa(const ip_addr_t *addr)
+{
+  static char str[16];
+  return ipaddr_ntoa_r(addr, str, 16);
+}
+
+/**
+ * Same as ipaddr_ntoa, but reentrant since a user-supplied buffer is used.
+ *
+ * @param addr ip address in network order to convert
+ * @param buf target buffer where the string is stored
+ * @param buflen length of buf
+ * @return either pointer to buf which now holds the ASCII
+ *         representation of addr or NULL if buf was too small
+ */
+char *ipaddr_ntoa_r(const ip_addr_t *addr, char *buf, int buflen)
+{
+  u32_t s_addr;
+  char inv[3];
+  char *rp;
+  u8_t *ap;
+  u8_t rem;
+  u8_t n;
+  u8_t i;
+  int len = 0;
+
+  s_addr = ip4_addr_get_u32(addr);
+
+  rp = buf;
+  ap = (u8_t *)&s_addr;
+  for(n = 0; n < 4; n++) {
+    i = 0;
+    do {
+      rem = *ap % (u8_t)10;
+      *ap /= (u8_t)10;
+      inv[i++] = '0' + rem;
+    } while(*ap);
+    while(i--) {
+      if (len++ >= buflen) {
+        return NULL;
+      }
+      *rp++ = inv[i];
+    }
+    if (len++ >= buflen) {
+      return NULL;
+    }
+    *rp++ = '.';
+    ap++;
+  }
+  *--rp = 0;
+  return buf;
+}
diff --git a/external/badvpn_dns/lwip/src/core/ipv4/ip_frag.c b/external/badvpn_dns/lwip/src/core/ipv4/ip_frag.c
new file mode 100644
index 0000000..8d18434
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv4/ip_frag.c
@@ -0,0 +1,863 @@
+/**
+ * @file
+ * This is the IPv4 packet segmentation and reassembly implementation.
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Jani Monoses <jani@xxxxx> 
+ *         Simon Goldschmidt
+ * original reassembly code by Adam Dunkels <adam@xxxxxxx>
+ * 
+ */
+
+#include "lwip/opt.h"
+#include "lwip/ip_frag.h"
+#include "lwip/def.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/netif.h"
+#include "lwip/snmp.h"
+#include "lwip/stats.h"
+#include "lwip/icmp.h"
+
+#include <string.h>
+
+#if IP_REASSEMBLY
+/**
+ * The IP reassembly code currently has the following limitations:
+ * - IP header options are not supported
+ * - fragments must not overlap (e.g. due to different routes),
+ *   currently, overlapping or duplicate fragments are thrown away
+ *   if IP_REASS_CHECK_OVERLAP=1 (the default)!
+ *
+ * @todo: work with IP header options
+ */
+
+/** Setting this to 0, you can turn off checking the fragments for overlapping
+ * regions. The code gets a little smaller. Only use this if you know that
+ * overlapping won't occur on your network! */
+#ifndef IP_REASS_CHECK_OVERLAP
+#define IP_REASS_CHECK_OVERLAP 1
+#endif /* IP_REASS_CHECK_OVERLAP */
+
+/** Set to 0 to prevent freeing the oldest datagram when the reassembly buffer is
+ * full (IP_REASS_MAX_PBUFS pbufs are enqueued). The code gets a little smaller.
+ * Datagrams will be freed by timeout only. Especially useful when MEMP_NUM_REASSDATA
+ * is set to 1, so one datagram can be reassembled at a time, only. */
+#ifndef IP_REASS_FREE_OLDEST
+#define IP_REASS_FREE_OLDEST 1
+#endif /* IP_REASS_FREE_OLDEST */
+
+#define IP_REASS_FLAG_LASTFRAG 0x01
+
+/** This is a helper struct which holds the starting
+ * offset and the ending offset of this fragment to
+ * easily chain the fragments.
+ * It has the same packing requirements as the IP header, since it replaces
+ * the IP header in memory in incoming fragments (after copying it) to keep
+ * track of the various fragments. (-> If the IP header doesn't need packing,
+ * this struct doesn't need packing, too.)
+ */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ip_reass_helper {
+  PACK_STRUCT_FIELD(struct pbuf *next_pbuf);
+  PACK_STRUCT_FIELD(u16_t start);
+  PACK_STRUCT_FIELD(u16_t end);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define IP_ADDRESSES_AND_ID_MATCH(iphdrA, iphdrB)  \
+  (ip_addr_cmp(&(iphdrA)->src, &(iphdrB)->src) && \
+   ip_addr_cmp(&(iphdrA)->dest, &(iphdrB)->dest) && \
+   IPH_ID(iphdrA) == IPH_ID(iphdrB)) ? 1 : 0
+
+/* global variables */
+static struct ip_reassdata *reassdatagrams;
+static u16_t ip_reass_pbufcount;
+
+/* function prototypes */
+static void ip_reass_dequeue_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev);
+static int ip_reass_free_complete_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev);
+
+/**
+ * Reassembly timer base function
+ * for both NO_SYS == 0 and 1 (!).
+ *
+ * Should be called every 1000 msec (defined by IP_TMR_INTERVAL).
+ */
+void
+ip_reass_tmr(void)
+{
+  struct ip_reassdata *r, *prev = NULL;
+
+  r = reassdatagrams;
+  while (r != NULL) {
+    /* Decrement the timer. Once it reaches 0,
+     * clean up the incomplete fragment assembly */
+    if (r->timer > 0) {
+      r->timer--;
+      LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass_tmr: timer dec %"U16_F"\n",(u16_t)r->timer));
+      prev = r;
+      r = r->next;
+    } else {
+      /* reassembly timed out */
+      struct ip_reassdata *tmp;
+      LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass_tmr: timer timed out\n"));
+      tmp = r;
+      /* get the next pointer before freeing */
+      r = r->next;
+      /* free the helper struct and all enqueued pbufs */
+      ip_reass_free_complete_datagram(tmp, prev);
+     }
+   }
+}
+
+/**
+ * Free a datagram (struct ip_reassdata) and all its pbufs.
+ * Updates the total count of enqueued pbufs (ip_reass_pbufcount),
+ * SNMP counters and sends an ICMP time exceeded packet.
+ *
+ * @param ipr datagram to free
+ * @param prev the previous datagram in the linked list
+ * @return the number of pbufs freed
+ */
+static int
+ip_reass_free_complete_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev)
+{
+  u16_t pbufs_freed = 0;
+  u8_t clen;
+  struct pbuf *p;
+  struct ip_reass_helper *iprh;
+
+  LWIP_ASSERT("prev != ipr", prev != ipr);
+  if (prev != NULL) {
+    LWIP_ASSERT("prev->next == ipr", prev->next == ipr);
+  }
+
+  snmp_inc_ipreasmfails();
+#if LWIP_ICMP
+  iprh = (struct ip_reass_helper *)ipr->p->payload;
+  if (iprh->start == 0) {
+    /* The first fragment was received, send ICMP time exceeded. */
+    /* First, de-queue the first pbuf from r->p. */
+    p = ipr->p;
+    ipr->p = iprh->next_pbuf;
+    /* Then, copy the original header into it. */
+    SMEMCPY(p->payload, &ipr->iphdr, IP_HLEN);
+    icmp_time_exceeded(p, ICMP_TE_FRAG);
+    clen = pbuf_clen(p);
+    LWIP_ASSERT("pbufs_freed + clen <= 0xffff", pbufs_freed + clen <= 0xffff);
+    pbufs_freed += clen;
+    pbuf_free(p);
+  }
+#endif /* LWIP_ICMP */
+
+  /* First, free all received pbufs.  The individual pbufs need to be released 
+     separately as they have not yet been chained */
+  p = ipr->p;
+  while (p != NULL) {
+    struct pbuf *pcur;
+    iprh = (struct ip_reass_helper *)p->payload;
+    pcur = p;
+    /* get the next pointer before freeing */
+    p = iprh->next_pbuf;
+    clen = pbuf_clen(pcur);
+    LWIP_ASSERT("pbufs_freed + clen <= 0xffff", pbufs_freed + clen <= 0xffff);
+    pbufs_freed += clen;
+    pbuf_free(pcur);
+  }
+  /* Then, unchain the struct ip_reassdata from the list and free it. */
+  ip_reass_dequeue_datagram(ipr, prev);
+  LWIP_ASSERT("ip_reass_pbufcount >= clen", ip_reass_pbufcount >= pbufs_freed);
+  ip_reass_pbufcount -= pbufs_freed;
+
+  return pbufs_freed;
+}
+
+#if IP_REASS_FREE_OLDEST
+/**
+ * Free the oldest datagram to make room for enqueueing new fragments.
+ * The datagram 'fraghdr' belongs to is not freed!
+ *
+ * @param fraghdr IP header of the current fragment
+ * @param pbufs_needed number of pbufs needed to enqueue
+ *        (used for freeing other datagrams if not enough space)
+ * @return the number of pbufs freed
+ */
+static int
+ip_reass_remove_oldest_datagram(struct ip_hdr *fraghdr, int pbufs_needed)
+{
+  /* @todo Can't we simply remove the last datagram in the
+   *       linked list behind reassdatagrams?
+   */
+  struct ip_reassdata *r, *oldest, *prev;
+  int pbufs_freed = 0, pbufs_freed_current;
+  int other_datagrams;
+
+  /* Free datagrams until being allowed to enqueue 'pbufs_needed' pbufs,
+   * but don't free the datagram that 'fraghdr' belongs to! */
+  do {
+    oldest = NULL;
+    prev = NULL;
+    other_datagrams = 0;
+    r = reassdatagrams;
+    while (r != NULL) {
+      if (!IP_ADDRESSES_AND_ID_MATCH(&r->iphdr, fraghdr)) {
+        /* Not the same datagram as fraghdr */
+        other_datagrams++;
+        if (oldest == NULL) {
+          oldest = r;
+        } else if (r->timer <= oldest->timer) {
+          /* older than the previous oldest */
+          oldest = r;
+        }
+      }
+      if (r->next != NULL) {
+        prev = r;
+      }
+      r = r->next;
+    }
+    if (oldest != NULL) {
+      pbufs_freed_current = ip_reass_free_complete_datagram(oldest, prev);
+      pbufs_freed += pbufs_freed_current;
+    }
+  } while ((pbufs_freed < pbufs_needed) && (other_datagrams > 1));
+  return pbufs_freed;
+}
+#endif /* IP_REASS_FREE_OLDEST */
+
+/**
+ * Enqueues a new fragment into the fragment queue
+ * @param fraghdr points to the new fragments IP hdr
+ * @param clen number of pbufs needed to enqueue (used for freeing other datagrams if not enough space)
+ * @return A pointer to the queue location into which the fragment was enqueued
+ */
+static struct ip_reassdata*
+ip_reass_enqueue_new_datagram(struct ip_hdr *fraghdr, int clen)
+{
+  struct ip_reassdata* ipr;
+  /* No matching previous fragment found, allocate a new reassdata struct */
+  ipr = (struct ip_reassdata *)memp_malloc(MEMP_REASSDATA);
+  if (ipr == NULL) {
+#if IP_REASS_FREE_OLDEST
+    if (ip_reass_remove_oldest_datagram(fraghdr, clen) >= clen) {
+      ipr = (struct ip_reassdata *)memp_malloc(MEMP_REASSDATA);
+    }
+    if (ipr == NULL)
+#endif /* IP_REASS_FREE_OLDEST */
+    {
+      IPFRAG_STATS_INC(ip_frag.memerr);
+      LWIP_DEBUGF(IP_REASS_DEBUG,("Failed to alloc reassdata struct\n"));
+      return NULL;
+    }
+  }
+  memset(ipr, 0, sizeof(struct ip_reassdata));
+  ipr->timer = IP_REASS_MAXAGE;
+
+  /* enqueue the new structure to the front of the list */
+  ipr->next = reassdatagrams;
+  reassdatagrams = ipr;
+  /* copy the ip header for later tests and input */
+  /* @todo: no ip options supported? */
+  SMEMCPY(&(ipr->iphdr), fraghdr, IP_HLEN);
+  return ipr;
+}
+
+/**
+ * Dequeues a datagram from the datagram queue. Doesn't deallocate the pbufs.
+ * @param ipr points to the queue entry to dequeue
+ */
+static void
+ip_reass_dequeue_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev)
+{
+  
+  /* dequeue the reass struct  */
+  if (reassdatagrams == ipr) {
+    /* it was the first in the list */
+    reassdatagrams = ipr->next;
+  } else {
+    /* it wasn't the first, so it must have a valid 'prev' */
+    LWIP_ASSERT("sanity check linked list", prev != NULL);
+    prev->next = ipr->next;
+  }
+
+  /* now we can free the ip_reass struct */
+  memp_free(MEMP_REASSDATA, ipr);
+}
+
+/**
+ * Chain a new pbuf into the pbuf list that composes the datagram.  The pbuf list
+ * will grow over time as  new pbufs are rx.
+ * Also checks that the datagram passes basic continuity checks (if the last
+ * fragment was received at least once).
+ * @param root_p points to the 'root' pbuf for the current datagram being assembled.
+ * @param new_p points to the pbuf for the current fragment
+ * @return 0 if invalid, >0 otherwise
+ */
+static int
+ip_reass_chain_frag_into_datagram_and_validate(struct ip_reassdata *ipr, struct pbuf *new_p)
+{
+  struct ip_reass_helper *iprh, *iprh_tmp, *iprh_prev=NULL;
+  struct pbuf *q;
+  u16_t offset,len;
+  struct ip_hdr *fraghdr;
+  int valid = 1;
+
+  /* Extract length and fragment offset from current fragment */
+  fraghdr = (struct ip_hdr*)new_p->payload; 
+  len = ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4;
+  offset = (ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8;
+
+  /* overwrite the fragment's ip header from the pbuf with our helper struct,
+   * and setup the embedded helper structure. */
+  /* make sure the struct ip_reass_helper fits into the IP header */
+  LWIP_ASSERT("sizeof(struct ip_reass_helper) <= IP_HLEN",
+              sizeof(struct ip_reass_helper) <= IP_HLEN);
+  iprh = (struct ip_reass_helper*)new_p->payload;
+  iprh->next_pbuf = NULL;
+  iprh->start = offset;
+  iprh->end = offset + len;
+
+  /* Iterate through until we either get to the end of the list (append),
+   * or we find on with a larger offset (insert). */
+  for (q = ipr->p; q != NULL;) {
+    iprh_tmp = (struct ip_reass_helper*)q->payload;
+    if (iprh->start < iprh_tmp->start) {
+      /* the new pbuf should be inserted before this */
+      iprh->next_pbuf = q;
+      if (iprh_prev != NULL) {
+        /* not the fragment with the lowest offset */
+#if IP_REASS_CHECK_OVERLAP
+        if ((iprh->start < iprh_prev->end) || (iprh->end > iprh_tmp->start)) {
+          /* fragment overlaps with previous or following, throw away */
+          goto freepbuf;
+        }
+#endif /* IP_REASS_CHECK_OVERLAP */
+        iprh_prev->next_pbuf = new_p;
+      } else {
+        /* fragment with the lowest offset */
+        ipr->p = new_p;
+      }
+      break;
+    } else if(iprh->start == iprh_tmp->start) {
+      /* received the same datagram twice: no need to keep the datagram */
+      goto freepbuf;
+#if IP_REASS_CHECK_OVERLAP
+    } else if(iprh->start < iprh_tmp->end) {
+      /* overlap: no need to keep the new datagram */
+      goto freepbuf;
+#endif /* IP_REASS_CHECK_OVERLAP */
+    } else {
+      /* Check if the fragments received so far have no wholes. */
+      if (iprh_prev != NULL) {
+        if (iprh_prev->end != iprh_tmp->start) {
+          /* There is a fragment missing between the current
+           * and the previous fragment */
+          valid = 0;
+        }
+      }
+    }
+    q = iprh_tmp->next_pbuf;
+    iprh_prev = iprh_tmp;
+  }
+
+  /* If q is NULL, then we made it to the end of the list. Determine what to do now */
+  if (q == NULL) {
+    if (iprh_prev != NULL) {
+      /* this is (for now), the fragment with the highest offset:
+       * chain it to the last fragment */
+#if IP_REASS_CHECK_OVERLAP
+      LWIP_ASSERT("check fragments don't overlap", iprh_prev->end <= iprh->start);
+#endif /* IP_REASS_CHECK_OVERLAP */
+      iprh_prev->next_pbuf = new_p;
+      if (iprh_prev->end != iprh->start) {
+        valid = 0;
+      }
+    } else {
+#if IP_REASS_CHECK_OVERLAP
+      LWIP_ASSERT("no previous fragment, this must be the first fragment!",
+        ipr->p == NULL);
+#endif /* IP_REASS_CHECK_OVERLAP */
+      /* this is the first fragment we ever received for this ip datagram */
+      ipr->p = new_p;
+    }
+  }
+
+  /* At this point, the validation part begins: */
+  /* If we already received the last fragment */
+  if ((ipr->flags & IP_REASS_FLAG_LASTFRAG) != 0) {
+    /* and had no wholes so far */
+    if (valid) {
+      /* then check if the rest of the fragments is here */
+      /* Check if the queue starts with the first datagram */
+      if (((struct ip_reass_helper*)ipr->p->payload)->start != 0) {
+        valid = 0;
+      } else {
+        /* and check that there are no wholes after this datagram */
+        iprh_prev = iprh;
+        q = iprh->next_pbuf;
+        while (q != NULL) {
+          iprh = (struct ip_reass_helper*)q->payload;
+          if (iprh_prev->end != iprh->start) {
+            valid = 0;
+            break;
+          }
+          iprh_prev = iprh;
+          q = iprh->next_pbuf;
+        }
+        /* if still valid, all fragments are received
+         * (because to the MF==0 already arrived */
+        if (valid) {
+          LWIP_ASSERT("sanity check", ipr->p != NULL);
+          LWIP_ASSERT("sanity check",
+            ((struct ip_reass_helper*)ipr->p->payload) != iprh);
+          LWIP_ASSERT("validate_datagram:next_pbuf!=NULL",
+            iprh->next_pbuf == NULL);
+          LWIP_ASSERT("validate_datagram:datagram end!=datagram len",
+            iprh->end == ipr->datagram_len);
+        }
+      }
+    }
+    /* If valid is 0 here, there are some fragments missing in the middle
+     * (since MF == 0 has already arrived). Such datagrams simply time out if
+     * no more fragments are received... */
+    return valid;
+  }
+  /* If we come here, not all fragments were received, yet! */
+  return 0; /* not yet valid! */
+#if IP_REASS_CHECK_OVERLAP
+freepbuf:
+  ip_reass_pbufcount -= pbuf_clen(new_p);
+  pbuf_free(new_p);
+  return 0;
+#endif /* IP_REASS_CHECK_OVERLAP */
+}
+
+/**
+ * Reassembles incoming IP fragments into an IP datagram.
+ *
+ * @param p points to a pbuf chain of the fragment
+ * @return NULL if reassembly is incomplete, ? otherwise
+ */
+struct pbuf *
+ip_reass(struct pbuf *p)
+{
+  struct pbuf *r;
+  struct ip_hdr *fraghdr;
+  struct ip_reassdata *ipr;
+  struct ip_reass_helper *iprh;
+  u16_t offset, len;
+  u8_t clen;
+  struct ip_reassdata *ipr_prev = NULL;
+
+  IPFRAG_STATS_INC(ip_frag.recv);
+  snmp_inc_ipreasmreqds();
+
+  fraghdr = (struct ip_hdr*)p->payload;
+
+  if ((IPH_HL(fraghdr) * 4) != IP_HLEN) {
+    LWIP_DEBUGF(IP_REASS_DEBUG,("ip_reass: IP options currently not supported!\n"));
+    IPFRAG_STATS_INC(ip_frag.err);
+    goto nullreturn;
+  }
+
+  offset = (ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8;
+  len = ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4;
+
+  /* Check if we are allowed to enqueue more datagrams. */
+  clen = pbuf_clen(p);
+  if ((ip_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS) {
+#if IP_REASS_FREE_OLDEST
+    if (!ip_reass_remove_oldest_datagram(fraghdr, clen) ||
+        ((ip_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS))
+#endif /* IP_REASS_FREE_OLDEST */
+    {
+      /* No datagram could be freed and still too many pbufs enqueued */
+      LWIP_DEBUGF(IP_REASS_DEBUG,("ip_reass: Overflow condition: pbufct=%d, clen=%d, MAX=%d\n",
+        ip_reass_pbufcount, clen, IP_REASS_MAX_PBUFS));
+      IPFRAG_STATS_INC(ip_frag.memerr);
+      /* @todo: send ICMP time exceeded here? */
+      /* drop this pbuf */
+      goto nullreturn;
+    }
+  }
+
+  /* Look for the datagram the fragment belongs to in the current datagram queue,
+   * remembering the previous in the queue for later dequeueing. */
+  for (ipr = reassdatagrams; ipr != NULL; ipr = ipr->next) {
+    /* Check if the incoming fragment matches the one currently present
+       in the reassembly buffer. If so, we proceed with copying the
+       fragment into the buffer. */
+    if (IP_ADDRESSES_AND_ID_MATCH(&ipr->iphdr, fraghdr)) {
+      LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_reass: matching previous fragment ID=%"X16_F"\n",
+        ntohs(IPH_ID(fraghdr))));
+      IPFRAG_STATS_INC(ip_frag.cachehit);
+      break;
+    }
+    ipr_prev = ipr;
+  }
+
+  if (ipr == NULL) {
+  /* Enqueue a new datagram into the datagram queue */
+    ipr = ip_reass_enqueue_new_datagram(fraghdr, clen);
+    /* Bail if unable to enqueue */
+    if(ipr == NULL) {
+      goto nullreturn;
+    }
+  } else {
+    if (((ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) == 0) && 
+      ((ntohs(IPH_OFFSET(&ipr->iphdr)) & IP_OFFMASK) != 0)) {
+      /* ipr->iphdr is not the header from the first fragment, but fraghdr is
+       * -> copy fraghdr into ipr->iphdr since we want to have the header
+       * of the first fragment (for ICMP time exceeded and later, for copying
+       * all options, if supported)*/
+      SMEMCPY(&ipr->iphdr, fraghdr, IP_HLEN);
+    }
+  }
+  /* Track the current number of pbufs current 'in-flight', in order to limit 
+  the number of fragments that may be enqueued at any one time */
+  ip_reass_pbufcount += clen;
+
+  /* At this point, we have either created a new entry or pointing 
+   * to an existing one */
+
+  /* check for 'no more fragments', and update queue entry*/
+  if ((IPH_OFFSET(fraghdr) & PP_NTOHS(IP_MF)) == 0) {
+    ipr->flags |= IP_REASS_FLAG_LASTFRAG;
+    ipr->datagram_len = offset + len;
+    LWIP_DEBUGF(IP_REASS_DEBUG,
+     ("ip_reass: last fragment seen, total len %"S16_F"\n",
+      ipr->datagram_len));
+  }
+  /* find the right place to insert this pbuf */
+  /* @todo: trim pbufs if fragments are overlapping */
+  if (ip_reass_chain_frag_into_datagram_and_validate(ipr, p)) {
+    /* the totally last fragment (flag more fragments = 0) was received at least
+     * once AND all fragments are received */
+    ipr->datagram_len += IP_HLEN;
+
+    /* save the second pbuf before copying the header over the pointer */
+    r = ((struct ip_reass_helper*)ipr->p->payload)->next_pbuf;
+
+    /* copy the original ip header back to the first pbuf */
+    fraghdr = (struct ip_hdr*)(ipr->p->payload);
+    SMEMCPY(fraghdr, &ipr->iphdr, IP_HLEN);
+    IPH_LEN_SET(fraghdr, htons(ipr->datagram_len));
+    IPH_OFFSET_SET(fraghdr, 0);
+    IPH_CHKSUM_SET(fraghdr, 0);
+    /* @todo: do we need to set calculate the correct checksum? */
+    IPH_CHKSUM_SET(fraghdr, inet_chksum(fraghdr, IP_HLEN));
+
+    p = ipr->p;
+
+    /* chain together the pbufs contained within the reass_data list. */
+    while(r != NULL) {
+      iprh = (struct ip_reass_helper*)r->payload;
+
+      /* hide the ip header for every succeding fragment */
+      pbuf_header(r, -IP_HLEN);
+      pbuf_cat(p, r);
+      r = iprh->next_pbuf;
+    }
+    /* release the sources allocate for the fragment queue entry */
+    ip_reass_dequeue_datagram(ipr, ipr_prev);
+
+    /* and adjust the number of pbufs currently queued for reassembly. */
+    ip_reass_pbufcount -= pbuf_clen(p);
+
+    /* Return the pbuf chain */
+    return p;
+  }
+  /* the datagram is not (yet?) reassembled completely */
+  LWIP_DEBUGF(IP_REASS_DEBUG,("ip_reass_pbufcount: %d out\n", ip_reass_pbufcount));
+  return NULL;
+
+nullreturn:
+  LWIP_DEBUGF(IP_REASS_DEBUG,("ip_reass: nullreturn\n"));
+  IPFRAG_STATS_INC(ip_frag.drop);
+  pbuf_free(p);
+  return NULL;
+}
+#endif /* IP_REASSEMBLY */
+
+#if IP_FRAG
+#if IP_FRAG_USES_STATIC_BUF
+static u8_t buf[LWIP_MEM_ALIGN_SIZE(IP_FRAG_MAX_MTU + MEM_ALIGNMENT - 1)];
+#else /* IP_FRAG_USES_STATIC_BUF */
+
+#if !LWIP_NETIF_TX_SINGLE_PBUF
+/** Allocate a new struct pbuf_custom_ref */
+static struct pbuf_custom_ref*
+ip_frag_alloc_pbuf_custom_ref(void)
+{
+  return (struct pbuf_custom_ref*)memp_malloc(MEMP_FRAG_PBUF);
+}
+
+/** Free a struct pbuf_custom_ref */
+static void
+ip_frag_free_pbuf_custom_ref(struct pbuf_custom_ref* p)
+{
+  LWIP_ASSERT("p != NULL", p != NULL);
+  memp_free(MEMP_FRAG_PBUF, p);
+}
+
+/** Free-callback function to free a 'struct pbuf_custom_ref', called by
+ * pbuf_free. */
+static void
+ipfrag_free_pbuf_custom(struct pbuf *p)
+{
+  struct pbuf_custom_ref *pcr = (struct pbuf_custom_ref*)p;
+  LWIP_ASSERT("pcr != NULL", pcr != NULL);
+  LWIP_ASSERT("pcr == p", (void*)pcr == (void*)p);
+  if (pcr->original != NULL) {
+    pbuf_free(pcr->original);
+  }
+  ip_frag_free_pbuf_custom_ref(pcr);
+}
+#endif /* !LWIP_NETIF_TX_SINGLE_PBUF */
+#endif /* IP_FRAG_USES_STATIC_BUF */
+
+/**
+ * Fragment an IP datagram if too large for the netif.
+ *
+ * Chop the datagram in MTU sized chunks and send them in order
+ * by using a fixed size static memory buffer (PBUF_REF) or
+ * point PBUF_REFs into p (depending on IP_FRAG_USES_STATIC_BUF).
+ *
+ * @param p ip packet to send
+ * @param netif the netif on which to send
+ * @param dest destination ip address to which to send
+ *
+ * @return ERR_OK if sent successfully, err_t otherwise
+ */
+err_t 
+ip_frag(struct pbuf *p, struct netif *netif, ip_addr_t *dest)
+{
+  struct pbuf *rambuf;
+#if IP_FRAG_USES_STATIC_BUF
+  struct pbuf *header;
+#else
+#if !LWIP_NETIF_TX_SINGLE_PBUF
+  struct pbuf *newpbuf;
+#endif
+  struct ip_hdr *original_iphdr;
+#endif
+  struct ip_hdr *iphdr;
+  u16_t nfb;
+  u16_t left, cop;
+  u16_t mtu = netif->mtu;
+  u16_t ofo, omf;
+  u16_t last;
+  u16_t poff = IP_HLEN;
+  u16_t tmp;
+#if !IP_FRAG_USES_STATIC_BUF && !LWIP_NETIF_TX_SINGLE_PBUF
+  u16_t newpbuflen = 0;
+  u16_t left_to_copy;
+#endif
+
+  /* Get a RAM based MTU sized pbuf */
+#if IP_FRAG_USES_STATIC_BUF
+  /* When using a static buffer, we use a PBUF_REF, which we will
+   * use to reference the packet (without link header).
+   * Layer and length is irrelevant.
+   */
+  rambuf = pbuf_alloc(PBUF_LINK, 0, PBUF_REF);
+  if (rambuf == NULL) {
+    LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_frag: pbuf_alloc(PBUF_LINK, 0, PBUF_REF) failed\n"));
+    return ERR_MEM;
+  }
+  rambuf->tot_len = rambuf->len = mtu;
+  rambuf->payload = LWIP_MEM_ALIGN((void *)buf);
+
+  /* Copy the IP header in it */
+  iphdr = (struct ip_hdr *)rambuf->payload;
+  SMEMCPY(iphdr, p->payload, IP_HLEN);
+#else /* IP_FRAG_USES_STATIC_BUF */
+  original_iphdr = (struct ip_hdr *)p->payload;
+  iphdr = original_iphdr;
+#endif /* IP_FRAG_USES_STATIC_BUF */
+
+  /* Save original offset */
+  tmp = ntohs(IPH_OFFSET(iphdr));
+  ofo = tmp & IP_OFFMASK;
+  omf = tmp & IP_MF;
+
+  left = p->tot_len - IP_HLEN;
+
+  nfb = (mtu - IP_HLEN) / 8;
+
+  while (left) {
+    last = (left <= mtu - IP_HLEN);
+
+    /* Set new offset and MF flag */
+    tmp = omf | (IP_OFFMASK & (ofo));
+    if (!last) {
+      tmp = tmp | IP_MF;
+    }
+
+    /* Fill this fragment */
+    cop = last ? left : nfb * 8;
+
+#if IP_FRAG_USES_STATIC_BUF
+    poff += pbuf_copy_partial(p, (u8_t*)iphdr + IP_HLEN, cop, poff);
+#else /* IP_FRAG_USES_STATIC_BUF */
+#if LWIP_NETIF_TX_SINGLE_PBUF
+    rambuf = pbuf_alloc(PBUF_IP, cop, PBUF_RAM);
+    if (rambuf == NULL) {
+      return ERR_MEM;
+    }
+    LWIP_ASSERT("this needs a pbuf in one piece!",
+      (rambuf->len == rambuf->tot_len) && (rambuf->next == NULL));
+    poff += pbuf_copy_partial(p, rambuf->payload, cop, poff);
+    /* make room for the IP header */
+    if(pbuf_header(rambuf, IP_HLEN)) {
+      pbuf_free(rambuf);
+      return ERR_MEM;
+    }
+    /* fill in the IP header */
+    SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN);
+    iphdr = rambuf->payload;
+#else /* LWIP_NETIF_TX_SINGLE_PBUF */
+    /* When not using a static buffer, create a chain of pbufs.
+     * The first will be a PBUF_RAM holding the link and IP header.
+     * The rest will be PBUF_REFs mirroring the pbuf chain to be fragged,
+     * but limited to the size of an mtu.
+     */
+    rambuf = pbuf_alloc(PBUF_LINK, IP_HLEN, PBUF_RAM);
+    if (rambuf == NULL) {
+      return ERR_MEM;
+    }
+    LWIP_ASSERT("this needs a pbuf in one piece!",
+                (p->len >= (IP_HLEN)));
+    SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN);
+    iphdr = (struct ip_hdr *)rambuf->payload;
+
+    /* Can just adjust p directly for needed offset. */
+    p->payload = (u8_t *)p->payload + poff;
+    p->len -= poff;
+
+    left_to_copy = cop;
+    while (left_to_copy) {
+      struct pbuf_custom_ref *pcr;
+      newpbuflen = (left_to_copy < p->len) ? left_to_copy : p->len;
+      /* Is this pbuf already empty? */
+      if (!newpbuflen) {
+        p = p->next;
+        continue;
+      }
+      pcr = ip_frag_alloc_pbuf_custom_ref();
+      if (pcr == NULL) {
+        pbuf_free(rambuf);
+        return ERR_MEM;
+      }
+      /* Mirror this pbuf, although we might not need all of it. */
+      newpbuf = pbuf_alloced_custom(PBUF_RAW, newpbuflen, PBUF_REF, &pcr->pc, p->payload, newpbuflen);
+      if (newpbuf == NULL) {
+        ip_frag_free_pbuf_custom_ref(pcr);
+        pbuf_free(rambuf);
+        return ERR_MEM;
+      }
+      pbuf_ref(p);
+      pcr->original = p;
+      pcr->pc.custom_free_function = ipfrag_free_pbuf_custom;
+
+      /* Add it to end of rambuf's chain, but using pbuf_cat, not pbuf_chain
+       * so that it is removed when pbuf_dechain is later called on rambuf.
+       */
+      pbuf_cat(rambuf, newpbuf);
+      left_to_copy -= newpbuflen;
+      if (left_to_copy) {
+        p = p->next;
+      }
+    }
+    poff = newpbuflen;
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+#endif /* IP_FRAG_USES_STATIC_BUF */
+
+    /* Correct header */
+    IPH_OFFSET_SET(iphdr, htons(tmp));
+    IPH_LEN_SET(iphdr, htons(cop + IP_HLEN));
+    IPH_CHKSUM_SET(iphdr, 0);
+    IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));
+
+#if IP_FRAG_USES_STATIC_BUF
+    if (last) {
+      pbuf_realloc(rambuf, left + IP_HLEN);
+    }
+
+    /* This part is ugly: we alloc a RAM based pbuf for 
+     * the link level header for each chunk and then 
+     * free it.A PBUF_ROM style pbuf for which pbuf_header
+     * worked would make things simpler.
+     */
+    header = pbuf_alloc(PBUF_LINK, 0, PBUF_RAM);
+    if (header != NULL) {
+      pbuf_chain(header, rambuf);
+      netif->output(netif, header, dest);
+      IPFRAG_STATS_INC(ip_frag.xmit);
+      snmp_inc_ipfragcreates();
+      pbuf_free(header);
+    } else {
+      LWIP_DEBUGF(IP_REASS_DEBUG, ("ip_frag: pbuf_alloc() for header failed\n"));
+      pbuf_free(rambuf);
+      return ERR_MEM;
+    }
+#else /* IP_FRAG_USES_STATIC_BUF */
+    /* No need for separate header pbuf - we allowed room for it in rambuf
+     * when allocated.
+     */
+    netif->output(netif, rambuf, dest);
+    IPFRAG_STATS_INC(ip_frag.xmit);
+
+    /* Unfortunately we can't reuse rambuf - the hardware may still be
+     * using the buffer. Instead we free it (and the ensuing chain) and
+     * recreate it next time round the loop. If we're lucky the hardware
+     * will have already sent the packet, the free will really free, and
+     * there will be zero memory penalty.
+     */
+    
+    pbuf_free(rambuf);
+#endif /* IP_FRAG_USES_STATIC_BUF */
+    left -= cop;
+    ofo += nfb;
+  }
+#if IP_FRAG_USES_STATIC_BUF
+  pbuf_free(rambuf);
+#endif /* IP_FRAG_USES_STATIC_BUF */
+  snmp_inc_ipfragoks();
+  return ERR_OK;
+}
+#endif /* IP_FRAG */
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/README b/external/badvpn_dns/lwip/src/core/ipv6/README
new file mode 100644
index 0000000..3620004
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/README
@@ -0,0 +1 @@
+IPv6 support in lwIP is very experimental.
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/dhcp6.c b/external/badvpn_dns/lwip/src/core/ipv6/dhcp6.c
new file mode 100644
index 0000000..9656c3b
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/dhcp6.c
@@ -0,0 +1,50 @@
+/**
+ * @file
+ *
+ * DHCPv6.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6_DHCP6 /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/ip6_addr.h"
+#include "lwip/def.h"
+
+
+#endif /* LWIP_IPV6_DHCP6 */
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/ethip6.c b/external/badvpn_dns/lwip/src/core/ipv6/ethip6.c
new file mode 100644
index 0000000..ab9783a
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/ethip6.c
@@ -0,0 +1,193 @@
+/**
+ * @file
+ *
+ * Ethernet output for IPv6. Uses ND tables for link-layer addressing.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6 && LWIP_ETHERNET
+
+#include "lwip/ethip6.h"
+#include "lwip/nd6.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/netif.h"
+#include "lwip/icmp6.h"
+
+#include <string.h>
+
+#define ETHTYPE_IPV6        0x86DD
+
+/** The ethernet address */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct eth_addr {
+  PACK_STRUCT_FIELD(u8_t addr[6]);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** Ethernet header */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct eth_hdr {
+#if ETH_PAD_SIZE
+  PACK_STRUCT_FIELD(u8_t padding[ETH_PAD_SIZE]);
+#endif
+  PACK_STRUCT_FIELD(struct eth_addr dest);
+  PACK_STRUCT_FIELD(struct eth_addr src);
+  PACK_STRUCT_FIELD(u16_t type);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define SIZEOF_ETH_HDR (14 + ETH_PAD_SIZE)
+
+/**
+ * Send an IPv6 packet on the network using netif->linkoutput
+ * The ethernet header is filled in before sending.
+ *
+ * @params netif the lwIP network interface on which to send the packet
+ * @params p the packet to send, p->payload pointing to the (uninitialized) ethernet header
+ * @params src the source MAC address to be copied into the ethernet header
+ * @params dst the destination MAC address to be copied into the ethernet header
+ * @return ERR_OK if the packet was sent, any other err_t on failure
+ */
+static err_t
+ethip6_send(struct netif *netif, struct pbuf *p, struct eth_addr *src, struct eth_addr *dst)
+{
+  struct eth_hdr *ethhdr = (struct eth_hdr *)p->payload;
+
+  LWIP_ASSERT("netif->hwaddr_len must be 6 for ethip6!",
+              (netif->hwaddr_len == 6));
+  SMEMCPY(&ethhdr->dest, dst, 6);
+  SMEMCPY(&ethhdr->src, src, 6);
+  ethhdr->type = PP_HTONS(ETHTYPE_IPV6);
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("ethip6_send: sending packet %p\n", (void *)p));
+  /* send the packet */
+  return netif->linkoutput(netif, p);
+}
+
+/**
+ * Resolve and fill-in Ethernet address header for outgoing IPv6 packet.
+ *
+ * For IPv6 multicast, corresponding Ethernet addresses
+ * are selected and the packet is transmitted on the link.
+ *
+ * For unicast addresses, ...
+ *
+ * @TODO anycast addresses
+ *
+ * @param netif The lwIP network interface which the IP packet will be sent on.
+ * @param q The pbuf(s) containing the IP packet to be sent.
+ * @param ip6addr The IP address of the packet destination.
+ *
+ * @return
+ * - ERR_RTE No route to destination (no gateway to external networks),
+ * or the return type of either etharp_query() or etharp_send_ip().
+ */
+err_t
+ethip6_output(struct netif *netif, struct pbuf *q, ip6_addr_t *ip6addr)
+{
+  struct eth_addr dest;
+  s8_t i;
+
+  /* make room for Ethernet header - should not fail */
+  if (pbuf_header(q, sizeof(struct eth_hdr)) != 0) {
+    /* bail out */
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+      ("etharp_output: could not allocate room for header.\n"));
+    return ERR_BUF;
+  }
+
+  /* multicast destination IP address? */
+  if (ip6_addr_ismulticast(ip6addr)) {
+    /* Hash IP multicast address to MAC address.*/
+    dest.addr[0] = 0x33;
+    dest.addr[1] = 0x33;
+    dest.addr[2] = ((u8_t *)(&(ip6addr->addr[3])))[0];
+    dest.addr[3] = ((u8_t *)(&(ip6addr->addr[3])))[1];
+    dest.addr[4] = ((u8_t *)(&(ip6addr->addr[3])))[2];
+    dest.addr[5] = ((u8_t *)(&(ip6addr->addr[3])))[3];
+
+    /* Send out. */
+    return ethip6_send(netif, q, (struct eth_addr*)(netif->hwaddr), &dest);
+  }
+
+  /* We have a unicast destination IP address */
+  /* TODO anycast? */
+  /* Get next hop record. */
+  i = nd6_get_next_hop_entry(ip6addr, netif);
+  if (i < 0) {
+    /* failed to get a next hop neighbor record. */
+    return ERR_MEM;
+  }
+
+  /* Now that we have a destination record, send or queue the packet. */
+  if (neighbor_cache[i].state == ND6_STALE) {
+    /* Switch to delay state. */
+    neighbor_cache[i].state = ND6_DELAY;
+    neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME;
+  }
+  /* TODO should we send or queue if PROBE? send for now, to let unicast NS pass. */
+  if ((neighbor_cache[i].state == ND6_REACHABLE) ||
+      (neighbor_cache[i].state == ND6_DELAY) ||
+      (neighbor_cache[i].state == ND6_PROBE)) {
+
+    /* Send out. */
+    SMEMCPY(dest.addr, neighbor_cache[i].lladdr, 6);
+    return ethip6_send(netif, q, (struct eth_addr*)(netif->hwaddr), &dest);
+  }
+
+  /* We should queue packet on this interface. */
+  pbuf_header(q, -(s16_t)SIZEOF_ETH_HDR);
+  return nd6_queue_packet(i, q);
+}
+
+#endif /* LWIP_IPV6 && LWIP_ETHERNET */
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/icmp6.c b/external/badvpn_dns/lwip/src/core/ipv6/icmp6.c
new file mode 100644
index 0000000..ea82682
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/icmp6.c
@@ -0,0 +1,337 @@
+/**
+ * @file
+ *
+ * IPv6 version of ICMP, as per RFC 4443.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_ICMP6 && LWIP_IPV6 /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/icmp6.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+#include "lwip/nd6.h"
+#include "lwip/mld6.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+#ifndef LWIP_ICMP6_DATASIZE
+#define LWIP_ICMP6_DATASIZE   8
+#endif
+#if LWIP_ICMP6_DATASIZE == 0
+#define LWIP_ICMP6_DATASIZE   8
+#endif
+
+/* Forward declarations */
+static void icmp6_send_response(struct pbuf *p, u8_t code, u32_t data, u8_t type);
+
+
+/**
+ * Process an input ICMPv6 message. Called by ip6_input.
+ *
+ * Will generate a reply for echo requests. Other messages are forwarded
+ * to nd6_input, or mld6_input.
+ *
+ * @param p the mld packet, p->payload pointing to the icmpv6 header
+ * @param inp the netif on which this packet was received
+ */
+void
+icmp6_input(struct pbuf *p, struct netif *inp)
+{
+  struct icmp6_hdr *icmp6hdr;
+  struct pbuf * r;
+  ip6_addr_t * reply_src;
+
+  ICMP6_STATS_INC(icmp6.recv);
+
+  /* Check that ICMPv6 header fits in payload */
+  if (p->len < sizeof(struct icmp6_hdr)) {
+    /* drop short packets */
+    pbuf_free(p);
+    ICMP6_STATS_INC(icmp6.lenerr);
+    ICMP6_STATS_INC(icmp6.drop);
+    return;
+  }
+
+  icmp6hdr = (struct icmp6_hdr *)p->payload;
+
+#if LWIP_ICMP6_CHECKSUM_CHECK
+  if (ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->tot_len, ip6_current_src_addr(),
+                        ip6_current_dest_addr()) != 0) {
+    /* Checksum failed */
+    pbuf_free(p);
+    ICMP6_STATS_INC(icmp6.chkerr);
+    ICMP6_STATS_INC(icmp6.drop);
+    return;
+  }
+#endif /* LWIP_ICMP6_CHECKSUM_CHECK */
+
+  switch (icmp6hdr->type) {
+  case ICMP6_TYPE_NA: /* Neighbor advertisement */
+  case ICMP6_TYPE_NS: /* Neighbor solicitation */
+  case ICMP6_TYPE_RA: /* Router advertisement */
+  case ICMP6_TYPE_RD: /* Redirect */
+  case ICMP6_TYPE_PTB: /* Packet too big */
+    nd6_input(p, inp);
+    return;
+    break;
+  case ICMP6_TYPE_RS:
+#if LWIP_IPV6_FORWARD
+    /* TODO implement router functionality */
+#endif
+    break;
+#if LWIP_IPV6_MLD
+  case ICMP6_TYPE_MLQ:
+  case ICMP6_TYPE_MLR:
+  case ICMP6_TYPE_MLD:
+    mld6_input(p, inp);
+    return;
+    break;
+#endif
+  case ICMP6_TYPE_EREQ:
+#if !LWIP_MULTICAST_PING
+    /* multicast destination address? */
+    if (ip6_addr_ismulticast(ip6_current_dest_addr())) {
+      /* drop */
+      pbuf_free(p);
+      ICMP6_STATS_INC(icmp6.drop);
+      return;
+    }
+#endif /* LWIP_MULTICAST_PING */
+
+    /* Allocate reply. */
+    r = pbuf_alloc(PBUF_IP, p->tot_len, PBUF_RAM);
+    if (r == NULL) {
+      /* drop */
+      pbuf_free(p);
+      ICMP6_STATS_INC(icmp6.memerr);
+      return;
+    }
+
+    /* Copy echo request. */
+    if (pbuf_copy(r, p) != ERR_OK) {
+      /* drop */
+      pbuf_free(p);
+      pbuf_free(r);
+      ICMP6_STATS_INC(icmp6.err);
+      return;
+    }
+
+    /* Determine reply source IPv6 address. */
+#if LWIP_MULTICAST_PING
+    if (ip6_addr_ismulticast(ip6_current_dest_addr())) {
+      reply_src = ip6_select_source_address(inp, ip6_current_src_addr());
+      if (reply_src == NULL) {
+        /* drop */
+        pbuf_free(p);
+        pbuf_free(r);
+        ICMP6_STATS_INC(icmp6.rterr);
+        return;
+      }
+    }
+    else
+#endif /* LWIP_MULTICAST_PING */
+    {
+      reply_src = ip6_current_dest_addr();
+    }
+
+    /* Set fields in reply. */
+    ((struct icmp6_echo_hdr *)(r->payload))->type = ICMP6_TYPE_EREP;
+    ((struct icmp6_echo_hdr *)(r->payload))->chksum = 0;
+    ((struct icmp6_echo_hdr *)(r->payload))->chksum = ip6_chksum_pseudo(r,
+        IP6_NEXTH_ICMP6, r->tot_len, reply_src, ip6_current_src_addr());
+
+    /* Send reply. */
+    ICMP6_STATS_INC(icmp6.xmit);
+    ip6_output_if(r, reply_src, ip6_current_src_addr(),
+        LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, inp);
+    pbuf_free(r);
+
+    break;
+  default:
+    ICMP6_STATS_INC(icmp6.proterr);
+    ICMP6_STATS_INC(icmp6.drop);
+    break;
+  }
+
+  pbuf_free(p);
+}
+
+
+/**
+ * Send an icmpv6 'destination unreachable' packet.
+ *
+ * @param p the input packet for which the 'unreachable' should be sent,
+ *          p->payload pointing to the IPv6 header
+ * @param c ICMPv6 code for the unreachable type
+ */
+void
+icmp6_dest_unreach(struct pbuf *p, enum icmp6_dur_code c)
+{
+  icmp6_send_response(p, c, 0, ICMP6_TYPE_DUR);
+}
+
+/**
+ * Send an icmpv6 'packet too big' packet.
+ *
+ * @param p the input packet for which the 'packet too big' should be sent,
+ *          p->payload pointing to the IPv6 header
+ * @param mtu the maximum mtu that we can accept
+ */
+void
+icmp6_packet_too_big(struct pbuf *p, u32_t mtu)
+{
+  icmp6_send_response(p, 0, mtu, ICMP6_TYPE_PTB);
+}
+
+/**
+ * Send an icmpv6 'time exceeded' packet.
+ *
+ * @param p the input packet for which the 'unreachable' should be sent,
+ *          p->payload pointing to the IPv6 header
+ * @param c ICMPv6 code for the time exceeded type
+ */
+void
+icmp6_time_exceeded(struct pbuf *p, enum icmp6_te_code c)
+{
+  icmp6_send_response(p, c, 0, ICMP6_TYPE_TE);
+}
+
+/**
+ * Send an icmpv6 'parameter problem' packet.
+ *
+ * @param p the input packet for which the 'param problem' should be sent,
+ *          p->payload pointing to the IP header
+ * @param c ICMPv6 code for the param problem type
+ * @param pointer the pointer to the byte where the parameter is found
+ */
+void
+icmp6_param_problem(struct pbuf *p, enum icmp6_pp_code c, u32_t pointer)
+{
+  icmp6_send_response(p, c, pointer, ICMP6_TYPE_PP);
+}
+
+/**
+ * Send an ICMPv6 packet in response to an incoming packet.
+ *
+ * @param p the input packet for which the response should be sent,
+ *          p->payload pointing to the IPv6 header
+ * @param code Code of the ICMPv6 header
+ * @param data Additional 32-bit parameter in the ICMPv6 header
+ * @param type Type of the ICMPv6 header
+ */
+static void
+icmp6_send_response(struct pbuf *p, u8_t code, u32_t data, u8_t type)
+{
+  struct pbuf *q;
+  struct icmp6_hdr *icmp6hdr;
+  ip6_addr_t *reply_src, *reply_dest;
+  ip6_addr_t reply_src_local, reply_dest_local;
+  struct ip6_hdr *ip6hdr;
+  struct netif *netif;
+
+  /* ICMPv6 header + IPv6 header + data */
+  q = pbuf_alloc(PBUF_IP, sizeof(struct icmp6_hdr) + IP6_HLEN + LWIP_ICMP6_DATASIZE,
+                 PBUF_RAM);
+  if (q == NULL) {
+    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded: failed to allocate pbuf for ICMPv6 packet.\n"));
+    ICMP6_STATS_INC(icmp6.memerr);
+    return;
+  }
+  LWIP_ASSERT("check that first pbuf can hold icmp 6message",
+             (q->len >= (sizeof(struct icmp6_hdr) + IP6_HLEN + LWIP_ICMP6_DATASIZE)));
+
+  icmp6hdr = (struct icmp6_hdr *)q->payload;
+  icmp6hdr->type = type;
+  icmp6hdr->code = code;
+  icmp6hdr->data = data;
+
+  /* copy fields from original packet */
+  SMEMCPY((u8_t *)q->payload + sizeof(struct icmp6_hdr), (u8_t *)p->payload,
+          IP6_HLEN + LWIP_ICMP6_DATASIZE);
+
+  /* Get the destination address and netif for this ICMP message. */
+  if ((ip_current_netif() == NULL) ||
+      ((code == ICMP6_TE_FRAG) && (type == ICMP6_TYPE_TE))) {
+    /* Special case, as ip6_current_xxx is either NULL, or points
+     * to a different packet than the one that expired.
+     * We must use the addresses that are stored in the expired packet. */
+    ip6hdr = (struct ip6_hdr *)p->payload;
+    /* copy from packed address to aligned address */
+    ip6_addr_copy(reply_dest_local, ip6hdr->src);
+    ip6_addr_copy(reply_src_local, ip6hdr->dest);
+    reply_dest = &reply_dest_local;
+    reply_src = &reply_src_local;
+    netif = ip6_route(reply_src, reply_dest);
+    if (netif == NULL) {
+      /* drop */
+      pbuf_free(q);
+      ICMP6_STATS_INC(icmp6.rterr);
+      return;
+    }
+  }
+  else {
+    netif = ip_current_netif();
+    reply_dest = ip6_current_src_addr();
+
+    /* Select an address to use as source. */
+    reply_src = ip6_select_source_address(netif, reply_dest);
+    if (reply_src == NULL) {
+      /* drop */
+      pbuf_free(q);
+      ICMP6_STATS_INC(icmp6.rterr);
+      return;
+    }
+  }
+
+  /* calculate checksum */
+  icmp6hdr->chksum = 0;
+  icmp6hdr->chksum = ip6_chksum_pseudo(q, IP6_NEXTH_ICMP6, q->tot_len,
+    reply_src, reply_dest);
+
+  ICMP6_STATS_INC(icmp6.xmit);
+  ip6_output_if(q, reply_src, reply_dest, LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, netif);
+  pbuf_free(q);
+}
+
+#endif /* LWIP_ICMP6 && LWIP_IPV6 */
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/inet6.c b/external/badvpn_dns/lwip/src/core/ipv6/inet6.c
new file mode 100644
index 0000000..bdf4ff4
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/inet6.c
@@ -0,0 +1,51 @@
+/**
+ * @file
+ *
+ * INET v6 addresses.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6 && LWIP_SOCKET /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/inet6.h"
+
+/** @see ip6_addr.c for implementation of functions. */
+
+#endif /* LWIP_IPV6 */
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/ip6.c b/external/badvpn_dns/lwip/src/core/ipv6/ip6.c
new file mode 100644
index 0000000..1eb91f9
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/ip6.c
@@ -0,0 +1,1034 @@
+/**
+ * @file
+ *
+ * IPv6 layer.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/netif.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/ip6_frag.h"
+#include "lwip/icmp6.h"
+#include "lwip/raw.h"
+#include "lwip/udp.h"
+#include "lwip/tcp_impl.h"
+#include "lwip/dhcp6.h"
+#include "lwip/nd6.h"
+#include "lwip/mld6.h"
+#include "lwip/debug.h"
+#include "lwip/stats.h"
+
+
+/**
+ * Finds the appropriate network interface for a given IPv6 address. It tries to select
+ * a netif following a sequence of heuristics:
+ * 1) if there is only 1 netif, return it
+ * 2) if the destination is a link-local address, try to match the src address to a netif.
+ *    this is a tricky case because with multiple netifs, link-local addresses only have
+ *    meaning within a particular subnet/link.
+ * 3) tries to match the destination subnet to a configured address
+ * 4) tries to find a router
+ * 5) tries to match the source address to the netif
+ * 6) returns the default netif, if configured
+ *
+ * @param src the source IPv6 address, if known
+ * @param dest the destination IPv6 address for which to find the route
+ * @return the netif on which to send to reach dest
+ */
+struct netif *
+ip6_route(struct ip6_addr *src, struct ip6_addr *dest)
+{
+  struct netif *netif;
+  s8_t i;
+
+  /* If single netif configuration, fast return. */
+  if ((netif_list != NULL) && (netif_list->next == NULL)) {
+    return netif_list;
+  }
+
+  /* Special processing for link-local addresses. */
+  if (ip6_addr_islinklocal(dest)) {
+    if (ip6_addr_isany(src)) {
+      /* Use default netif. */
+      return netif_default;
+    }
+
+    /* Try to find the netif for the source address. */
+    for(netif = netif_list; netif != NULL; netif = netif->next) {
+      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+        if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+            ip6_addr_cmp(src, netif_ip6_addr(netif, i))) {
+          return netif;
+        }
+      }
+    }
+
+    /* netif not found, use default netif */
+    return netif_default;
+  }
+
+  /* See if the destination subnet matches a configured address. */
+  for(netif = netif_list; netif != NULL; netif = netif->next) {
+    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+          ip6_addr_netcmp(dest, netif_ip6_addr(netif, i))) {
+        return netif;
+      }
+    }
+  }
+
+  /* Get the netif for a suitable router. */
+  i = nd6_select_router(dest, NULL);
+  if (i >= 0) {
+    if (default_router_list[i].neighbor_entry != NULL) {
+      if (default_router_list[i].neighbor_entry->netif != NULL) {
+        return default_router_list[i].neighbor_entry->netif;
+      }
+    }
+  }
+
+  /* try with the netif that matches the source address. */
+  if (!ip6_addr_isany(src)) {
+    for(netif = netif_list; netif != NULL; netif = netif->next) {
+      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+        if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+            ip6_addr_cmp(src, netif_ip6_addr(netif, i))) {
+          return netif;
+        }
+      }
+    }
+  }
+
+  /* no matching netif found, use default netif */
+  return netif_default;
+}
+
+/**
+ * Select the best IPv6 source address for a given destination
+ * IPv6 address. Loosely follows RFC 3484. "Strong host" behavior
+ * is assumed.
+ *
+ * @param netif the netif on which to send a packet
+ * @param dest the destination we are trying to reach
+ * @return the most suitable source address to use, or NULL if no suitable
+ *         source address is found
+ */
+ip6_addr_t *
+ip6_select_source_address(struct netif *netif, ip6_addr_t * dest)
+{
+  ip6_addr_t * src = NULL;
+  u8_t i;
+
+  /* If dest is link-local, choose a link-local source. */
+  if (ip6_addr_islinklocal(dest) || ip6_addr_ismulticast_linklocal(dest) || ip6_addr_ismulticast_iflocal(dest)) {
+    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+          ip6_addr_islinklocal(netif_ip6_addr(netif, i))) {
+        return netif_ip6_addr(netif, i);
+      }
+    }
+  }
+
+  /* Choose a site-local with matching prefix. */
+  if (ip6_addr_issitelocal(dest) || ip6_addr_ismulticast_sitelocal(dest)) {
+    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+          ip6_addr_issitelocal(netif_ip6_addr(netif, i)) &&
+          ip6_addr_netcmp(dest, netif_ip6_addr(netif, i))) {
+        return netif_ip6_addr(netif, i);
+      }
+    }
+  }
+
+  /* Choose a unique-local with matching prefix. */
+  if (ip6_addr_isuniquelocal(dest) || ip6_addr_ismulticast_orglocal(dest)) {
+    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+          ip6_addr_isuniquelocal(netif_ip6_addr(netif, i)) &&
+          ip6_addr_netcmp(dest, netif_ip6_addr(netif, i))) {
+        return netif_ip6_addr(netif, i);
+      }
+    }
+  }
+
+  /* Choose a global with best matching prefix. */
+  if (ip6_addr_isglobal(dest) || ip6_addr_ismulticast_global(dest)) {
+    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+          ip6_addr_isglobal(netif_ip6_addr(netif, i))) {
+        if (src == NULL) {
+          src = netif_ip6_addr(netif, i);
+        }
+        else {
+          /* Replace src only if we find a prefix match. */
+          /* TODO find longest matching prefix. */
+          if ((!(ip6_addr_netcmp(src, dest))) &&
+              ip6_addr_netcmp(netif_ip6_addr(netif, i), dest)) {
+            src = netif_ip6_addr(netif, i);
+          }
+        }
+      }
+    }
+    if (src != NULL) {
+      return src;
+    }
+  }
+
+  /* Last resort: see if arbitrary prefix matches. */
+  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+    if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+        ip6_addr_netcmp(dest, netif_ip6_addr(netif, i))) {
+      return netif_ip6_addr(netif, i);
+    }
+  }
+
+  return NULL;
+}
+
+#if LWIP_IPV6_FORWARD
+/**
+ * Forwards an IPv6 packet. It finds an appropriate route for the
+ * packet, decrements the HL value of the packet, and outputs
+ * the packet on the appropriate interface.
+ *
+ * @param p the packet to forward (p->payload points to IP header)
+ * @param iphdr the IPv6 header of the input packet
+ * @param inp the netif on which this packet was received
+ */
+static void
+ip6_forward(struct pbuf *p, struct ip6_hdr *iphdr, struct netif *inp)
+{
+  struct netif *netif;
+
+  /* do not forward link-local addresses */
+  if (ip6_addr_islinklocal(ip6_current_dest_addr())) {
+    LWIP_DEBUGF(IP6_DEBUG, ("ip6_forward: not forwarding link-local address.\n"));
+    IP6_STATS_INC(ip6.rterr);
+    IP6_STATS_INC(ip6.drop);
+    return;
+  }
+
+  /* Find network interface where to forward this IP packet to. */
+  netif = ip6_route(IP6_ADDR_ANY, ip6_current_dest_addr());
+  if (netif == NULL) {
+    LWIP_DEBUGF(IP6_DEBUG, ("ip6_forward: no route for %"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F"\n",
+        IP6_ADDR_BLOCK1(ip6_current_dest_addr()),
+        IP6_ADDR_BLOCK2(ip6_current_dest_addr()),
+        IP6_ADDR_BLOCK3(ip6_current_dest_addr()),
+        IP6_ADDR_BLOCK4(ip6_current_dest_addr()),
+        IP6_ADDR_BLOCK5(ip6_current_dest_addr()),
+        IP6_ADDR_BLOCK6(ip6_current_dest_addr()),
+        IP6_ADDR_BLOCK7(ip6_current_dest_addr()),
+        IP6_ADDR_BLOCK8(ip6_current_dest_addr())));
+#if LWIP_ICMP6
+    /* Don't send ICMP messages in response to ICMP messages */
+    if (IP6H_NEXTH(iphdr) != IP6_NEXTH_ICMP6) {
+      icmp6_dest_unreach(p, ICMP6_DUR_NO_ROUTE);
+    }
+#endif /* LWIP_ICMP6 */
+    IP6_STATS_INC(ip6.rterr);
+    IP6_STATS_INC(ip6.drop);
+    return;
+  }
+  /* Do not forward packets onto the same network interface on which
+   * they arrived. */
+  if (netif == inp) {
+    LWIP_DEBUGF(IP6_DEBUG, ("ip6_forward: not bouncing packets back on incoming interface.\n"));
+    IP6_STATS_INC(ip6.rterr);
+    IP6_STATS_INC(ip6.drop);
+    return;
+  }
+
+  /* decrement HL */
+  IP6H_HOPLIM_SET(iphdr, IP6H_HOPLIM(iphdr) - 1);
+  /* send ICMP6 if HL == 0 */
+  if (IP6H_HOPLIM(iphdr) == 0) {
+#if LWIP_ICMP6
+    /* Don't send ICMP messages in response to ICMP messages */
+    if (IP6H_NEXTH(iphdr) != IP6_NEXTH_ICMP6) {
+      icmp6_time_exceeded(p, ICMP6_TE_HL);
+    }
+#endif /* LWIP_ICMP6 */
+    IP6_STATS_INC(ip6.drop);
+    return;
+  }
+
+  if (netif->mtu && (p->tot_len > netif->mtu)) {
+#if LWIP_ICMP6
+    /* Don't send ICMP messages in response to ICMP messages */
+    if (IP6H_NEXTH(iphdr) != IP6_NEXTH_ICMP6) {
+      icmp6_packet_too_big(p, netif->mtu);
+    }
+#endif /* LWIP_ICMP6 */
+    IP6_STATS_INC(ip6.drop);
+    return;
+  }
+
+  LWIP_DEBUGF(IP6_DEBUG, ("ip6_forward: forwarding packet to %"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F"\n",
+      IP6_ADDR_BLOCK1(ip6_current_dest_addr()),
+      IP6_ADDR_BLOCK2(ip6_current_dest_addr()),
+      IP6_ADDR_BLOCK3(ip6_current_dest_addr()),
+      IP6_ADDR_BLOCK4(ip6_current_dest_addr()),
+      IP6_ADDR_BLOCK5(ip6_current_dest_addr()),
+      IP6_ADDR_BLOCK6(ip6_current_dest_addr()),
+      IP6_ADDR_BLOCK7(ip6_current_dest_addr()),
+      IP6_ADDR_BLOCK8(ip6_current_dest_addr())));
+
+  /* transmit pbuf on chosen interface */
+  netif->output_ip6(netif, p, ip6_current_dest_addr());
+  IP6_STATS_INC(ip6.fw);
+  IP6_STATS_INC(ip6.xmit);
+  return;
+}
+#endif /* LWIP_IPV6_FORWARD */
+
+
+/**
+ * This function is called by the network interface device driver when
+ * an IPv6 packet is received. The function does the basic checks of the
+ * IP header such as packet size being at least larger than the header
+ * size etc. If the packet was not destined for us, the packet is
+ * forwarded (using ip6_forward).
+ *
+ * Finally, the packet is sent to the upper layer protocol input function.
+ *
+ * @param p the received IPv6 packet (p->payload points to IPv6 header)
+ * @param inp the netif on which this packet was received
+ * @return ERR_OK if the packet was processed (could return ERR_* if it wasn't
+ *         processed, but currently always returns ERR_OK)
+ */
+err_t
+ip6_input(struct pbuf *p, struct netif *inp)
+{
+  struct ip6_hdr *ip6hdr;
+  struct netif *netif;
+  u8_t nexth;
+  u16_t hlen; /* the current header length */
+  u8_t i;
+#if 0 /*IP_ACCEPT_LINK_LAYER_ADDRESSING*/
+  @todo
+  int check_ip_src=1;
+#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
+
+  IP6_STATS_INC(ip6.recv);
+
+  /* identify the IP header */
+  ip6hdr = (struct ip6_hdr *)p->payload;
+  if (IP6H_V(ip6hdr) != 6) {
+    LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("IPv6 packet dropped due to bad version number %"U32_F"\n",
+        IP6H_V(ip6hdr)));
+    pbuf_free(p);
+    IP6_STATS_INC(ip6.err);
+    IP6_STATS_INC(ip6.drop);
+    return ERR_OK;
+  }
+
+  /* header length exceeds first pbuf length, or ip length exceeds total pbuf length? */
+  if ((IP6_HLEN > p->len) || ((IP6H_PLEN(ip6hdr) + IP6_HLEN) > p->tot_len)) {
+    if (IP6_HLEN > p->len) {
+      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+        ("IPv6 header (len %"U16_F") does not fit in first pbuf (len %"U16_F"), IP packet dropped.\n",
+            IP6_HLEN, p->len));
+    }
+    if ((IP6H_PLEN(ip6hdr) + IP6_HLEN) > p->tot_len) {
+      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+        ("IPv6 (plen %"U16_F") is longer than pbuf (len %"U16_F"), IP packet dropped.\n",
+            IP6H_PLEN(ip6hdr) + IP6_HLEN, p->tot_len));
+    }
+    /* free (drop) packet pbufs */
+    pbuf_free(p);
+    IP6_STATS_INC(ip6.lenerr);
+    IP6_STATS_INC(ip6.drop);
+    return ERR_OK;
+  }
+
+  /* Trim pbuf. This should have been done at the netif layer,
+   * but we'll do it anyway just to be sure that its done. */
+  pbuf_realloc(p, IP6_HLEN + IP6H_PLEN(ip6hdr));
+
+  /* copy IP addresses to aligned ip6_addr_t */
+  ip6_addr_copy(ip_data.current_iphdr_dest.ip6, ip6hdr->dest);
+  ip6_addr_copy(ip_data.current_iphdr_src.ip6, ip6hdr->src);
+
+  /* current header pointer. */
+  ip_data.current_ip6_header = ip6hdr;
+
+  /* In netif, used in case we need to send ICMPv6 packets back. */
+  ip_data.current_netif = inp;
+
+  /* match packet against an interface, i.e. is this packet for us? */
+  if (ip6_addr_ismulticast(ip6_current_dest_addr())) {
+    /* Always joined to multicast if-local and link-local all-nodes group. */
+    if (ip6_addr_isallnodes_iflocal(ip6_current_dest_addr()) ||
+        ip6_addr_isallnodes_linklocal(ip6_current_dest_addr())) {
+      netif = inp;
+    }
+#if LWIP_IPV6_MLD
+    else if (mld6_lookfor_group(inp, ip6_current_dest_addr())) {
+      netif = inp;
+    }
+#else /* LWIP_IPV6_MLD */
+    else if (ip6_addr_issolicitednode(ip6_current_dest_addr())) {
+      /* Filter solicited node packets when MLD is not enabled
+       * (for Neighbor discovery). */
+      netif = NULL;
+      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+        if (ip6_addr_isvalid(netif_ip6_addr_state(inp, i)) &&
+            ip6_addr_cmp_solicitednode(ip6_current_dest_addr(), netif_ip6_addr(inp, i))) {
+          netif = inp;
+          LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: solicited node packet accepted on interface %c%c\n",
+              netif->name[0], netif->name[1]));
+          break;
+        }
+      }
+    }
+#endif /* LWIP_IPV6_MLD */
+    else {
+      netif = NULL;
+    }
+  }
+  else {
+    /* start trying with inp. if that's not acceptable, start walking the
+       list of configured netifs.
+       'first' is used as a boolean to mark whether we started walking the list */
+    int first = 1;
+    netif = inp;
+    do {
+      /* interface is up? */
+      if (netif_is_up(netif)) {
+        /* unicast to this interface address? address configured? */
+        for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+          if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+              ip6_addr_cmp(ip6_current_dest_addr(), netif_ip6_addr(netif, i))) {
+            /* exit outer loop */
+            goto netif_found;
+          }
+        }
+      }
+      if (ip6_addr_islinklocal(ip6_current_dest_addr())) {
+        /* Do not match link-local addresses to other netifs. */
+        netif = NULL;
+        break;
+      }
+      if (first) {
+        first = 0;
+        netif = netif_list;
+      } else {
+        netif = netif->next;
+      }
+      if (netif == inp) {
+        netif = netif->next;
+      }
+    } while(netif != NULL);
+netif_found:
+    LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: packet accepted on interface %c%c\n",
+        netif ? netif->name[0] : 'X', netif? netif->name[1] : 'X'));
+  }
+
+  /* "::" packet source address? (used in duplicate address detection) */
+  if (ip6_addr_isany(ip6_current_src_addr()) &&
+      (!ip6_addr_issolicitednode(ip6_current_dest_addr()))) {
+    /* packet source is not valid */
+    /* free (drop) packet pbufs */
+    LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: packet with src ANY_ADDRESS dropped\n"));
+    pbuf_free(p);
+    IP6_STATS_INC(ip6.drop);
+    goto ip6_input_cleanup;
+  }
+  
+  /* if we're pretending we are everyone for TCP, assume the packet is for source interface if it
+     isn't for a local address */
+  if (netif == NULL && (inp->flags & NETIF_FLAG_PRETEND_TCP) && IP6H_NEXTH(ip6hdr) == IP6_NEXTH_TCP) {
+      netif = inp;
+  }
+
+  /* packet not for us? */
+  if (netif == NULL) {
+    /* packet not for us, route or discard */
+    LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_TRACE, ("ip6_input: packet not for us.\n"));
+#if LWIP_IPV6_FORWARD
+    /* non-multicast packet? */
+    if (!ip6_addr_ismulticast(ip6_current_dest_addr())) {
+      /* try to forward IP packet on (other) interfaces */
+      ip6_forward(p, ip6hdr, inp);
+    }
+#endif /* LWIP_IPV6_FORWARD */
+    pbuf_free(p);
+    goto ip6_input_cleanup;
+  }
+
+  /* current netif pointer. */
+  ip_data.current_netif = netif;
+
+  /* Save next header type. */
+  nexth = IP6H_NEXTH(ip6hdr);
+
+  /* Init header length. */
+  hlen = ip_data.current_ip_header_tot_len = IP6_HLEN;
+
+  /* Move to payload. */
+  pbuf_header(p, -IP6_HLEN);
+
+  /* Process known option extension headers, if present. */
+  while (nexth != IP6_NEXTH_NONE)
+  {
+    switch (nexth) {
+    case IP6_NEXTH_HOPBYHOP:
+      LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: packet with Hop-by-Hop options header\n"));
+      /* Get next header type. */
+      nexth = *((u8_t *)p->payload);
+
+      /* Get the header length. */
+      hlen = 8 * (1 + *((u8_t *)p->payload + 1));
+      ip_data.current_ip_header_tot_len += hlen;
+
+      /* Skip over this header. */
+      if (hlen > p->len) {
+        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+          ("IPv6 options header (hlen %"U16_F") does not fit in first pbuf (len %"U16_F"), IPv6 packet dropped.\n",
+              hlen, p->len));
+        /* free (drop) packet pbufs */
+        pbuf_free(p);
+        IP6_STATS_INC(ip6.lenerr);
+        IP6_STATS_INC(ip6.drop);
+        goto ip6_input_cleanup;
+      }
+
+      pbuf_header(p, -hlen);
+      break;
+    case IP6_NEXTH_DESTOPTS:
+      LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: packet with Destination options header\n"));
+      /* Get next header type. */
+      nexth = *((u8_t *)p->payload);
+
+      /* Get the header length. */
+      hlen = 8 * (1 + *((u8_t *)p->payload + 1));
+      ip_data.current_ip_header_tot_len += hlen;
+
+      /* Skip over this header. */
+      if (hlen > p->len) {
+        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+          ("IPv6 options header (hlen %"U16_F") does not fit in first pbuf (len %"U16_F"), IPv6 packet dropped.\n",
+              hlen, p->len));
+        /* free (drop) packet pbufs */
+        pbuf_free(p);
+        IP6_STATS_INC(ip6.lenerr);
+        IP6_STATS_INC(ip6.drop);
+        goto ip6_input_cleanup;
+      }
+
+      pbuf_header(p, -hlen);
+      break;
+    case IP6_NEXTH_ROUTING:
+      LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: packet with Routing header\n"));
+      /* Get next header type. */
+      nexth = *((u8_t *)p->payload);
+
+      /* Get the header length. */
+      hlen = 8 * (1 + *((u8_t *)p->payload + 1));
+      ip_data.current_ip_header_tot_len += hlen;
+
+      /* Skip over this header. */
+      if (hlen > p->len) {
+        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+          ("IPv6 options header (hlen %"U16_F") does not fit in first pbuf (len %"U16_F"), IPv6 packet dropped.\n",
+              hlen, p->len));
+        /* free (drop) packet pbufs */
+        pbuf_free(p);
+        IP6_STATS_INC(ip6.lenerr);
+        IP6_STATS_INC(ip6.drop);
+        goto ip6_input_cleanup;
+      }
+
+      pbuf_header(p, -hlen);
+      break;
+
+    case IP6_NEXTH_FRAGMENT:
+    {
+      struct ip6_frag_hdr * frag_hdr;
+      LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: packet with Fragment header\n"));
+
+      frag_hdr = (struct ip6_frag_hdr *)p->payload;
+
+      /* Get next header type. */
+      nexth = frag_hdr->_nexth;
+
+      /* Fragment Header length. */
+      hlen = 8;
+      ip_data.current_ip_header_tot_len += hlen;
+
+      /* Make sure this header fits in current pbuf. */
+      if (hlen > p->len) {
+        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+          ("IPv6 options header (hlen %"U16_F") does not fit in first pbuf (len %"U16_F"), IPv6 packet dropped.\n",
+              hlen, p->len));
+        /* free (drop) packet pbufs */
+        pbuf_free(p);
+        IP6_FRAG_STATS_INC(ip6_frag.lenerr);
+        IP6_FRAG_STATS_INC(ip6_frag.drop);
+        goto ip6_input_cleanup;
+      }
+
+      /* Offset == 0 and more_fragments == 0? */
+      if (((frag_hdr->_fragment_offset & IP6_FRAG_OFFSET_MASK) == 0) &&
+          ((frag_hdr->_fragment_offset & IP6_FRAG_MORE_FLAG) == 0)) {
+
+        /* This is a 1-fragment packet, usually a packet that we have
+         * already reassembled. Skip this header anc continue. */
+        pbuf_header(p, -hlen);
+      }
+      else {
+#if LWIP_IPV6_REASS
+
+        /* reassemble the packet */
+        p = ip6_reass(p);
+        /* packet not fully reassembled yet? */
+        if (p == NULL) {
+          goto ip6_input_cleanup;
+        }
+
+        /* Returned p point to IPv6 header.
+         * Update all our variables and pointers and continue. */
+        ip6hdr = (struct ip6_hdr *)p->payload;
+        nexth = IP6H_NEXTH(ip6hdr);
+        hlen = ip_data.current_ip_header_tot_len = IP6_HLEN;
+        pbuf_header(p, -IP6_HLEN);
+
+#else /* LWIP_IPV6_REASS */
+        /* free (drop) packet pbufs */
+        LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: packet with Fragment header dropped (with LWIP_IPV6_REASS==0)\n"));
+        pbuf_free(p);
+        IP6_STATS_INC(ip6.opterr);
+        IP6_STATS_INC(ip6.drop);
+        goto ip6_input_cleanup;
+#endif /* LWIP_IPV6_REASS */
+      }
+      break;
+    }
+    default:
+      goto options_done;
+      break;
+    }
+  }
+options_done:
+
+  /* p points to IPv6 header again. */
+  pbuf_header(p, ip_data.current_ip_header_tot_len);
+
+  /* send to upper layers */
+  LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: \n"));
+  ip6_debug_print(p);
+  LWIP_DEBUGF(IP6_DEBUG, ("ip6_input: p->len %"U16_F" p->tot_len %"U16_F"\n", p->len, p->tot_len));
+
+#if LWIP_RAW
+  /* raw input did not eat the packet? */
+  if (raw_input(p, inp) == 0)
+#endif /* LWIP_RAW */
+  {
+    switch (nexth) {
+    case IP6_NEXTH_NONE:
+      pbuf_free(p);
+      break;
+#if LWIP_UDP
+    case IP6_NEXTH_UDP:
+#if LWIP_UDPLITE
+    case IP6_NEXTH_UDPLITE:
+#endif /* LWIP_UDPLITE */
+      /* Point to payload. */
+      pbuf_header(p, -ip_data.current_ip_header_tot_len);
+      udp_input(p, inp);
+      break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+    case IP6_NEXTH_TCP:
+      /* Point to payload. */
+      pbuf_header(p, -ip_data.current_ip_header_tot_len);
+      tcp_input(p, inp);
+      break;
+#endif /* LWIP_TCP */
+#if LWIP_ICMP6
+    case IP6_NEXTH_ICMP6:
+      /* Point to payload. */
+      pbuf_header(p, -ip_data.current_ip_header_tot_len);
+      icmp6_input(p, inp);
+      break;
+#endif /* LWIP_ICMP */
+    default:
+#if LWIP_ICMP6
+      /* send ICMP parameter problem unless it was a multicast or ICMPv6 */
+      if ((!ip6_addr_ismulticast(ip6_current_dest_addr())) &&
+          (IP6H_NEXTH(ip6hdr) != IP6_NEXTH_ICMP6)) {
+        icmp6_param_problem(p, ICMP6_PP_HEADER, ip_data.current_ip_header_tot_len - hlen);
+      }
+#endif /* LWIP_ICMP */
+      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip6_input: Unsupported transport protocol %"U16_F"\n", IP6H_NEXTH(ip6hdr)));
+      pbuf_free(p);
+      IP6_STATS_INC(ip6.proterr);
+      IP6_STATS_INC(ip6.drop);
+      break;
+    }
+  }
+
+ip6_input_cleanup:
+  ip_data.current_netif = NULL;
+  ip_data.current_ip6_header = NULL;
+  ip_data.current_ip_header_tot_len = 0;
+  ip6_addr_set_any(&ip_data.current_iphdr_src.ip6);
+  ip6_addr_set_any(&ip_data.current_iphdr_dest.ip6);
+
+  return ERR_OK;
+}
+
+
+/**
+ * Sends an IPv6 packet on a network interface. This function constructs
+ * the IPv6 header. If the source IPv6 address is NULL, the IPv6 "ANY" address is
+ * used as source (usually during network startup). If the source IPv6 address it
+ * IP6_ADDR_ANY, the most appropriate IPv6 address of the outgoing network
+ * interface is filled in as source address. If the destination IPv6 address is
+ * IP_HDRINCL, p is assumed to already include an IPv6 header and p->payload points
+ * to it instead of the data.
+ *
+ * @param p the packet to send (p->payload points to the data, e.g. next
+            protocol header; if dest == IP_HDRINCL, p already includes an
+            IPv6 header and p->payload points to that IPv6 header)
+ * @param src the source IPv6 address to send from (if src == IP6_ADDR_ANY, an
+ *         IP address of the netif is selected and used as source address.
+ *         if src == NULL, IP6_ADDR_ANY is used as source)
+ * @param dest the destination IPv6 address to send the packet to
+ * @param hl the Hop Limit value to be set in the IPv6 header
+ * @param tc the Traffic Class value to be set in the IPv6 header
+ * @param nexth the Next Header to be set in the IPv6 header
+ * @param netif the netif on which to send this packet
+ * @return ERR_OK if the packet was sent OK
+ *         ERR_BUF if p doesn't have enough space for IPv6/LINK headers
+ *         returns errors returned by netif->output
+ */
+err_t
+ip6_output_if(struct pbuf *p, ip6_addr_t *src, ip6_addr_t *dest,
+             u8_t hl, u8_t tc,
+             u8_t nexth, struct netif *netif)
+{
+  struct ip6_hdr *ip6hdr;
+  ip6_addr_t dest_addr;
+
+  /* pbufs passed to IP must have a ref-count of 1 as their payload pointer
+     gets altered as the packet is passed down the stack */
+  LWIP_ASSERT("p->ref == 1", p->ref == 1);
+
+  /* Should the IPv6 header be generated or is it already included in p? */
+  if (dest != IP_HDRINCL) {
+    /* generate IPv6 header */
+    if (pbuf_header(p, IP6_HLEN)) {
+      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip6_output: not enough room for IPv6 header in pbuf\n"));
+      IP6_STATS_INC(ip6.err);
+      return ERR_BUF;
+    }
+
+    ip6hdr = (struct ip6_hdr *)p->payload;
+    LWIP_ASSERT("check that first pbuf can hold struct ip6_hdr",
+               (p->len >= sizeof(struct ip6_hdr)));
+
+    IP6H_HOPLIM_SET(ip6hdr, hl);
+    IP6H_NEXTH_SET(ip6hdr, nexth);
+
+    /* dest cannot be NULL here */
+    ip6_addr_copy(ip6hdr->dest, *dest);
+
+    IP6H_VTCFL_SET(ip6hdr, 6, tc, 0);
+    IP6H_PLEN_SET(ip6hdr, p->tot_len - IP6_HLEN);
+
+    if (src == NULL) {
+      src = IP6_ADDR_ANY;
+    }
+    else if (ip6_addr_isany(src)) {
+      src = ip6_select_source_address(netif, dest);
+      if ((src == NULL) || ip6_addr_isany(src)) {
+        /* No appropriate source address was found for this packet. */
+        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip6_output: No suitable source address for packet.\n"));
+        IP6_STATS_INC(ip6.rterr);
+        return ERR_RTE;
+      }
+    }
+    /* src cannot be NULL here */
+    ip6_addr_copy(ip6hdr->src, *src);
+
+  } else {
+    /* IP header already included in p */
+    ip6hdr = (struct ip6_hdr *)p->payload;
+    ip6_addr_copy(dest_addr, ip6hdr->dest);
+    dest = &dest_addr;
+  }
+
+  IP6_STATS_INC(ip6.xmit);
+
+  LWIP_DEBUGF(IP6_DEBUG, ("ip6_output_if: %c%c%"U16_F"\n", netif->name[0], netif->name[1], netif->num));
+  ip6_debug_print(p);
+
+#if ENABLE_LOOPBACK
+  /* TODO implement loopback for v6
+  if (ip6_addr_cmp(dest, netif_ip6_addr(0))) {
+    return netif_loop_output(netif, p, dest);
+  }*/
+#endif /* ENABLE_LOOPBACK */
+#if LWIP_IPV6_FRAG
+  /* don't fragment if interface has mtu set to 0 [loopif] */
+  if (netif->mtu && (p->tot_len > nd6_get_destination_mtu(dest, netif))) {
+    return ip6_frag(p, netif, dest);
+  }
+#endif /* LWIP_IPV6_FRAG */
+
+  LWIP_DEBUGF(IP6_DEBUG, ("netif->output_ip6()"));
+  return netif->output_ip6(netif, p, dest);
+}
+
+/**
+ * Simple interface to ip6_output_if. It finds the outgoing network
+ * interface and calls upon ip6_output_if to do the actual work.
+ *
+ * @param p the packet to send (p->payload points to the data, e.g. next
+            protocol header; if dest == IP_HDRINCL, p already includes an
+            IPv6 header and p->payload points to that IPv6 header)
+ * @param src the source IPv6 address to send from (if src == IP6_ADDR_ANY, an
+ *         IP address of the netif is selected and used as source address.
+ *         if src == NULL, IP6_ADDR_ANY is used as source)
+ * @param dest the destination IPv6 address to send the packet to
+ * @param hl the Hop Limit value to be set in the IPv6 header
+ * @param tc the Traffic Class value to be set in the IPv6 header
+ * @param nexth the Next Header to be set in the IPv6 header
+ *
+ * @return ERR_RTE if no route is found
+ *         see ip_output_if() for more return values
+ */
+err_t
+ip6_output(struct pbuf *p, ip6_addr_t *src, ip6_addr_t *dest,
+          u8_t hl, u8_t tc, u8_t nexth)
+{
+  struct netif *netif;
+  struct ip6_hdr *ip6hdr;
+  ip6_addr_t src_addr, dest_addr;
+
+  /* pbufs passed to IPv6 must have a ref-count of 1 as their payload pointer
+     gets altered as the packet is passed down the stack */
+  LWIP_ASSERT("p->ref == 1", p->ref == 1);
+
+  if (dest != IP_HDRINCL) {
+    netif = ip6_route(src, dest);
+  } else {
+    /* IP header included in p, read addresses. */
+    ip6hdr = (struct ip6_hdr *)p->payload;
+    ip6_addr_copy(src_addr, ip6hdr->src);
+    ip6_addr_copy(dest_addr, ip6hdr->dest);
+    netif = ip6_route(&src_addr, &dest_addr);
+  }
+
+  if (netif == NULL) {
+    LWIP_DEBUGF(IP6_DEBUG, ("ip6_output: no route for %"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F"\n",
+        IP6_ADDR_BLOCK1(dest),
+        IP6_ADDR_BLOCK2(dest),
+        IP6_ADDR_BLOCK3(dest),
+        IP6_ADDR_BLOCK4(dest),
+        IP6_ADDR_BLOCK5(dest),
+        IP6_ADDR_BLOCK6(dest),
+        IP6_ADDR_BLOCK7(dest),
+        IP6_ADDR_BLOCK8(dest)));
+    IP6_STATS_INC(ip6.rterr);
+    return ERR_RTE;
+  }
+
+  return ip6_output_if(p, src, dest, hl, tc, nexth, netif);
+}
+
+
+#if LWIP_NETIF_HWADDRHINT
+/** Like ip6_output, but takes and addr_hint pointer that is passed on to netif->addr_hint
+ *  before calling ip6_output_if.
+ *
+ * @param p the packet to send (p->payload points to the data, e.g. next
+            protocol header; if dest == IP_HDRINCL, p already includes an
+            IPv6 header and p->payload points to that IPv6 header)
+ * @param src the source IPv6 address to send from (if src == IP6_ADDR_ANY, an
+ *         IP address of the netif is selected and used as source address.
+ *         if src == NULL, IP6_ADDR_ANY is used as source)
+ * @param dest the destination IPv6 address to send the packet to
+ * @param hl the Hop Limit value to be set in the IPv6 header
+ * @param tc the Traffic Class value to be set in the IPv6 header
+ * @param nexth the Next Header to be set in the IPv6 header
+ * @param addr_hint address hint pointer set to netif->addr_hint before
+ *        calling ip_output_if()
+ *
+ * @return ERR_RTE if no route is found
+ *         see ip_output_if() for more return values
+ */
+err_t
+ip6_output_hinted(struct pbuf *p, ip6_addr_t *src, ip6_addr_t *dest,
+          u8_t hl, u8_t tc, u8_t nexth, u8_t *addr_hint)
+{
+  struct netif *netif;
+  struct ip6_hdr *ip6hdr;
+  ip6_addr_t src_addr, dest_addr;
+  err_t err;
+
+  /* pbufs passed to IP must have a ref-count of 1 as their payload pointer
+     gets altered as the packet is passed down the stack */
+  LWIP_ASSERT("p->ref == 1", p->ref == 1);
+
+  if (dest != IP_HDRINCL) {
+    netif = ip6_route(src, dest);
+  } else {
+    /* IP header included in p, read addresses. */
+    ip6hdr = (struct ip6_hdr *)p->payload;
+    ip6_addr_copy(src_addr, ip6hdr->src);
+    ip6_addr_copy(dest_addr, ip6hdr->dest);
+    netif = ip6_route(&src_addr, &dest_addr);
+  }
+
+  if (netif == NULL) {
+    LWIP_DEBUGF(IP6_DEBUG, ("ip6_output: no route for %"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F"\n",
+        IP6_ADDR_BLOCK1(dest),
+        IP6_ADDR_BLOCK2(dest),
+        IP6_ADDR_BLOCK3(dest),
+        IP6_ADDR_BLOCK4(dest),
+        IP6_ADDR_BLOCK5(dest),
+        IP6_ADDR_BLOCK6(dest),
+        IP6_ADDR_BLOCK7(dest),
+        IP6_ADDR_BLOCK8(dest)));
+    IP6_STATS_INC(ip6.rterr);
+    return ERR_RTE;
+  }
+
+  NETIF_SET_HWADDRHINT(netif, addr_hint);
+  err = ip6_output_if(p, src, dest, hl, tc, nexth, netif);
+  NETIF_SET_HWADDRHINT(netif, NULL);
+
+  return err;
+}
+#endif /* LWIP_NETIF_HWADDRHINT*/
+
+#if LWIP_IPV6_MLD
+/**
+ * Add a hop-by-hop options header with a router alert option and padding.
+ *
+ * Used by MLD when sending a Multicast listener report/done message.
+ *
+ * @param p the packet to which we will prepend the options header
+ * @param nexth the next header protocol number (e.g. IP6_NEXTH_ICMP6)
+ * @param value the value of the router alert option data (e.g. IP6_ROUTER_ALERT_VALUE_MLD)
+ * @return ERR_OK if hop-by-hop header was added, ERR_* otherwise
+ */
+err_t
+ip6_options_add_hbh_ra(struct pbuf * p, u8_t nexth, u8_t value)
+{
+  struct ip6_hbh_hdr * hbh_hdr;
+
+  /* Move pointer to make room for hop-by-hop options header. */
+  if (pbuf_header(p, sizeof(struct ip6_hbh_hdr))) {
+    LWIP_DEBUGF(IP6_DEBUG, ("ip6_options: no space for options header\n"));
+    IP6_STATS_INC(ip6.err);
+    return ERR_BUF;
+  }
+
+  hbh_hdr = (struct ip6_hbh_hdr *)p->payload;
+
+  /* Set fields. */
+  hbh_hdr->_nexth = nexth;
+  hbh_hdr->_hlen = 0;
+  hbh_hdr->_ra_opt_type = IP6_ROUTER_ALERT_OPTION;
+  hbh_hdr->_ra_opt_dlen = 2;
+  hbh_hdr->_ra_opt_data = value;
+  hbh_hdr->_padn_opt_type = IP6_PADN_ALERT_OPTION;
+  hbh_hdr->_padn_opt_dlen = 0;
+
+  return ERR_OK;
+}
+#endif /* LWIP_IPV6_MLD */
+
+#if IP6_DEBUG
+/* Print an IPv6 header by using LWIP_DEBUGF
+ * @param p an IPv6 packet, p->payload pointing to the IPv6 header
+ */
+void
+ip6_debug_print(struct pbuf *p)
+{
+  struct ip6_hdr *ip6hdr = (struct ip6_hdr *)p->payload;
+
+  LWIP_DEBUGF(IP6_DEBUG, ("IPv6 header:\n"));
+  LWIP_DEBUGF(IP6_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(IP6_DEBUG, ("| %2"U16_F" |  %3"U16_F"  |      %7"U32_F"     | (ver, class, flow)\n",
+                    IP6H_V(ip6hdr),
+                    IP6H_TC(ip6hdr),
+                    IP6H_FL(ip6hdr)));
+  LWIP_DEBUGF(IP6_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(IP6_DEBUG, ("|     %5"U16_F"     |  %3"U16_F"  |  %3"U16_F"  | (plen, nexth, hopl)\n",
+                    IP6H_PLEN(ip6hdr),
+                    IP6H_NEXTH(ip6hdr),
+                    IP6H_HOPLIM(ip6hdr)));
+  LWIP_DEBUGF(IP6_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(IP6_DEBUG, ("|  %4"X32_F" |  %4"X32_F" |  %4"X32_F" |  %4"X32_F" | (src)\n",
+                    IP6_ADDR_BLOCK1(&(ip6hdr->src)),
+                    IP6_ADDR_BLOCK2(&(ip6hdr->src)),
+                    IP6_ADDR_BLOCK3(&(ip6hdr->src)),
+                    IP6_ADDR_BLOCK4(&(ip6hdr->src))));
+  LWIP_DEBUGF(IP6_DEBUG, ("|  %4"X32_F" |  %4"X32_F" |  %4"X32_F" |  %4"X32_F" |\n",
+                    IP6_ADDR_BLOCK5(&(ip6hdr->src)),
+                    IP6_ADDR_BLOCK6(&(ip6hdr->src)),
+                    IP6_ADDR_BLOCK7(&(ip6hdr->src)),
+                    IP6_ADDR_BLOCK8(&(ip6hdr->src))));
+  LWIP_DEBUGF(IP6_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(IP6_DEBUG, ("|  %4"X32_F" |  %4"X32_F" |  %4"X32_F" |  %4"X32_F" | (dest)\n",
+                    IP6_ADDR_BLOCK1(&(ip6hdr->dest)),
+                    IP6_ADDR_BLOCK2(&(ip6hdr->dest)),
+                    IP6_ADDR_BLOCK3(&(ip6hdr->dest)),
+                    IP6_ADDR_BLOCK4(&(ip6hdr->dest))));
+  LWIP_DEBUGF(IP6_DEBUG, ("|  %4"X32_F" |  %4"X32_F" |  %4"X32_F" |  %4"X32_F" |\n",
+                    IP6_ADDR_BLOCK5(&(ip6hdr->dest)),
+                    IP6_ADDR_BLOCK6(&(ip6hdr->dest)),
+                    IP6_ADDR_BLOCK7(&(ip6hdr->dest)),
+                    IP6_ADDR_BLOCK8(&(ip6hdr->dest))));
+  LWIP_DEBUGF(IP6_DEBUG, ("+-------------------------------+\n"));
+}
+#endif /* IP6_DEBUG */
+
+#endif /* LWIP_IPV6 */
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/ip6_addr.c b/external/badvpn_dns/lwip/src/core/ipv6/ip6_addr.c
new file mode 100644
index 0000000..65d2798
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/ip6_addr.c
@@ -0,0 +1,251 @@
+/**
+ * @file
+ *
+ * IPv6 addresses.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ * Functions for handling IPv6 addresses.
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/ip6_addr.h"
+#include "lwip/def.h"
+
+/* used by IP6_ADDR_ANY in ip6_addr.h */
+const ip6_addr_t ip6_addr_any = { { 0ul, 0ul, 0ul, 0ul } };
+
+#ifndef isprint
+#define in_range(c, lo, up)  ((u8_t)c >= lo && (u8_t)c <= up)
+#define isprint(c)           in_range(c, 0x20, 0x7f)
+#define isdigit(c)           in_range(c, '0', '9')
+#define isxdigit(c)          (isdigit(c) || in_range(c, 'a', 'f') || in_range(c, 'A', 'F'))
+#define islower(c)           in_range(c, 'a', 'z')
+#define isspace(c)           (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v')
+#define xchar(i)             ((i) < 10 ? '0' + (i) : 'A' + (i) - 10)
+#endif
+
+/**
+ * Check whether "cp" is a valid ascii representation
+ * of an IPv6 address and convert to a binary address.
+ * Returns 1 if the address is valid, 0 if not.
+ *
+ * @param cp IPv6 address in ascii represenation (e.g. "FF01::1")
+ * @param addr pointer to which to save the ip address in network order
+ * @return 1 if cp could be converted to addr, 0 on failure
+ */
+int
+ip6addr_aton(const char *cp, ip6_addr_t *addr)
+{
+  u32_t addr_index, zero_blocks, current_block_index, current_block_value;
+  const char * s;
+
+  /* Count the number of colons, to count the number of blocks in a "::" sequence
+     zero_blocks may be 1 even if there are no :: sequences */
+  zero_blocks = 8;
+  for (s = cp; *s != 0; s++) {
+    if (*s == ':')
+      zero_blocks--;
+    else if (!isxdigit(*s))
+      break;
+  }
+
+  /* parse each block */
+  addr_index = 0;
+  current_block_index = 0;
+  current_block_value = 0;
+  for (s = cp; *s != 0; s++) {
+    if (*s == ':') {
+      if (addr) {
+        if (current_block_index & 0x1) {
+          addr->addr[addr_index++] |= current_block_value;
+        }
+        else {
+          addr->addr[addr_index] = current_block_value << 16;
+        }
+      }
+      current_block_index++;
+      current_block_value = 0;
+      if (current_block_index > 7) {
+        /* address too long! */
+        return 0;
+      } if (s[1] == ':') {
+        s++;
+        /* "::" found, set zeros */
+        while (zero_blocks-- > 0) {
+          if (current_block_index & 0x1) {
+            addr_index++;
+          }
+          else {
+            if (addr) {
+              addr->addr[addr_index] = 0;
+            }
+          }
+          current_block_index++;
+        }
+      }
+    } else if (isxdigit(*s)) {
+      /* add current digit */
+      current_block_value = (current_block_value << 4) +
+          (isdigit(*s) ? *s - '0' :
+          10 + (islower(*s) ? *s - 'a' : *s - 'A'));
+    } else {
+      /* unexpected digit, space? CRLF? */
+      break;
+    }
+  }
+
+  if (addr) {
+    if (current_block_index & 0x1) {
+      addr->addr[addr_index++] |= current_block_value;
+    }
+    else {
+      addr->addr[addr_index] = current_block_value << 16;
+    }
+  }
+
+  /* convert to network byte order. */
+  if (addr) {
+    for (addr_index = 0; addr_index < 4; addr_index++) {
+      addr->addr[addr_index] = htonl(addr->addr[addr_index]);
+    }
+  }
+
+  if (current_block_index != 7) {
+    return 0;
+  }
+
+  return 1;
+}
+
+/**
+ * Convert numeric IPv6 address into ASCII representation.
+ * returns ptr to static buffer; not reentrant!
+ *
+ * @param addr ip6 address in network order to convert
+ * @return pointer to a global static (!) buffer that holds the ASCII
+ *         represenation of addr
+ */
+char *
+ip6addr_ntoa(const ip6_addr_t *addr)
+{
+  static char str[40];
+  return ip6addr_ntoa_r(addr, str, 40);
+}
+
+/**
+ * Same as ipaddr_ntoa, but reentrant since a user-supplied buffer is used.
+ *
+ * @param addr ip6 address in network order to convert
+ * @param buf target buffer where the string is stored
+ * @param buflen length of buf
+ * @return either pointer to buf which now holds the ASCII
+ *         representation of addr or NULL if buf was too small
+ */
+char *
+ip6addr_ntoa_r(const ip6_addr_t *addr, char *buf, int buflen)
+{
+  u32_t current_block_index, current_block_value;
+  s32_t zero_flag, i;
+
+  i = 0;
+  zero_flag = 0; /* used to indicate a zero chain for "::' */
+
+  for (current_block_index = 0; current_block_index < 8; current_block_index++) {
+    /* get the current 16-bit block */
+    current_block_value = htonl(addr->addr[current_block_index >> 1]);
+    if ((current_block_index & 0x1) == 0) {
+      current_block_value = current_block_value >> 16;
+    }
+    current_block_value &= 0xffff;
+
+    if (current_block_value == 0) {
+      /* generate empty block "::" */
+      if (!zero_flag) {
+        if (current_block_index > 0) {
+          zero_flag = 1;
+          buf[i++] = ':';
+          if (i >= buflen) return NULL;
+        }
+      }
+    }
+    else {
+      if (current_block_index > 0) {
+        buf[i++] = ':';
+        if (i >= buflen) return NULL;
+      }
+
+      if ((current_block_value & 0xf000) == 0) {
+        zero_flag = 1;
+      }
+      else {
+        buf[i++] = xchar(((current_block_value & 0xf000) >> 12));
+        zero_flag = 0;
+        if (i >= buflen) return NULL;
+      }
+
+      if (((current_block_value & 0xf00) == 0) && (zero_flag)) {
+        /* do nothing */
+      }
+      else {
+        buf[i++] = xchar(((current_block_value & 0xf00) >> 8));
+        zero_flag = 0;
+        if (i >= buflen) return NULL;
+      }
+
+      if (((current_block_value & 0xf0) == 0) && (zero_flag)) {
+        /* do nothing */
+      }
+      else {
+        buf[i++] = xchar(((current_block_value & 0xf0) >> 4));
+        zero_flag = 0;
+        if (i >= buflen) return NULL;
+      }
+
+      buf[i++] = xchar((current_block_value & 0xf));
+      if (i >= buflen) return NULL;
+
+      zero_flag = 0;
+    }
+  }
+
+  buf[i] = 0;
+
+  return buf;
+}
+#endif /* LWIP_IPV6 */
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/ip6_frag.c b/external/badvpn_dns/lwip/src/core/ipv6/ip6_frag.c
new file mode 100644
index 0000000..a43fd61
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/ip6_frag.c
@@ -0,0 +1,697 @@
+/**
+ * @file
+ *
+ * IPv6 fragmentation and reassembly.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#include "lwip/opt.h"
+#include "lwip/ip6_frag.h"
+#include "lwip/ip6.h"
+#include "lwip/icmp6.h"
+#include "lwip/nd6.h"
+
+#include "lwip/pbuf.h"
+#include "lwip/memp.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+#if LWIP_IPV6 && LWIP_IPV6_REASS  /* don't build if not configured for use in lwipopts.h */
+
+
+/** Setting this to 0, you can turn off checking the fragments for overlapping
+ * regions. The code gets a little smaller. Only use this if you know that
+ * overlapping won't occur on your network! */
+#ifndef IP_REASS_CHECK_OVERLAP
+#define IP_REASS_CHECK_OVERLAP 1
+#endif /* IP_REASS_CHECK_OVERLAP */
+
+/** Set to 0 to prevent freeing the oldest datagram when the reassembly buffer is
+ * full (IP_REASS_MAX_PBUFS pbufs are enqueued). The code gets a little smaller.
+ * Datagrams will be freed by timeout only. Especially useful when MEMP_NUM_REASSDATA
+ * is set to 1, so one datagram can be reassembled at a time, only. */
+#ifndef IP_REASS_FREE_OLDEST
+#define IP_REASS_FREE_OLDEST 1
+#endif /* IP_REASS_FREE_OLDEST */
+
+#define IP_REASS_FLAG_LASTFRAG 0x01
+
+/** This is a helper struct which holds the starting
+ * offset and the ending offset of this fragment to
+ * easily chain the fragments.
+ * It has the same packing requirements as the IPv6 header, since it replaces
+ * the Fragment Header in memory in incoming fragments to keep
+ * track of the various fragments.
+ */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ip6_reass_helper {
+  PACK_STRUCT_FIELD(struct pbuf *next_pbuf);
+  PACK_STRUCT_FIELD(u16_t start);
+  PACK_STRUCT_FIELD(u16_t end);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/* static variables */
+static struct ip6_reassdata *reassdatagrams;
+static u16_t ip6_reass_pbufcount;
+
+/* Forward declarations. */
+static void ip6_reass_free_complete_datagram(struct ip6_reassdata *ipr);
+#if IP_REASS_FREE_OLDEST
+static void ip6_reass_remove_oldest_datagram(struct ip6_reassdata *ipr, int pbufs_needed);
+#endif /* IP_REASS_FREE_OLDEST */
+
+void
+ip6_reass_tmr(void)
+{
+  struct ip6_reassdata *r, *tmp;
+
+  r = reassdatagrams;
+  while (r != NULL) {
+    /* Decrement the timer. Once it reaches 0,
+     * clean up the incomplete fragment assembly */
+    if (r->timer > 0) {
+      r->timer--;
+      r = r->next;
+    } else {
+      /* reassembly timed out */
+      tmp = r;
+      /* get the next pointer before freeing */
+      r = r->next;
+      /* free the helper struct and all enqueued pbufs */
+      ip6_reass_free_complete_datagram(tmp);
+     }
+   }
+}
+
+/**
+ * Free a datagram (struct ip6_reassdata) and all its pbufs.
+ * Updates the total count of enqueued pbufs (ip6_reass_pbufcount),
+ * sends an ICMP time exceeded packet.
+ *
+ * @param ipr datagram to free
+ */
+static void
+ip6_reass_free_complete_datagram(struct ip6_reassdata *ipr)
+{
+  struct ip6_reassdata *prev;
+  u16_t pbufs_freed = 0;
+  u8_t clen;
+  struct pbuf *p;
+  struct ip6_reass_helper *iprh;
+
+#if LWIP_ICMP6
+  iprh = (struct ip6_reass_helper *)ipr->p->payload;
+  if (iprh->start == 0) {
+    /* The first fragment was received, send ICMP time exceeded. */
+    /* First, de-queue the first pbuf from r->p. */
+    p = ipr->p;
+    ipr->p = iprh->next_pbuf;
+    /* Then, move back to the original header (we are now pointing to Fragment header). */
+    if (pbuf_header(p, (u8_t*)p->payload - (u8_t*)ipr->iphdr)) {
+      LWIP_ASSERT("ip6_reass_free: moving p->payload to ip6 header failed\n", 0);
+    }
+    else {
+      icmp6_time_exceeded(p, ICMP6_TE_FRAG);
+    }
+    clen = pbuf_clen(p);
+    LWIP_ASSERT("pbufs_freed + clen <= 0xffff", pbufs_freed + clen <= 0xffff);
+    pbufs_freed += clen;
+    pbuf_free(p);
+  }
+#endif /* LWIP_ICMP6 */
+
+  /* First, free all received pbufs.  The individual pbufs need to be released
+     separately as they have not yet been chained */
+  p = ipr->p;
+  while (p != NULL) {
+    struct pbuf *pcur;
+    iprh = (struct ip6_reass_helper *)p->payload;
+    pcur = p;
+    /* get the next pointer before freeing */
+    p = iprh->next_pbuf;
+    clen = pbuf_clen(pcur);
+    LWIP_ASSERT("pbufs_freed + clen <= 0xffff", pbufs_freed + clen <= 0xffff);
+    pbufs_freed += clen;
+    pbuf_free(pcur);
+  }
+
+  /* Then, unchain the struct ip6_reassdata from the list and free it. */
+  if (ipr == reassdatagrams) {
+    reassdatagrams = ipr->next;
+  } else {
+    prev = reassdatagrams;
+    while (prev != NULL) {
+      if (prev->next == ipr) {
+        break;
+      }
+      prev = prev->next;
+    }
+    if (prev != NULL) {
+      prev->next = ipr->next;
+    }
+  }
+  memp_free(MEMP_IP6_REASSDATA, ipr);
+
+  /* Finally, update number of pbufs in reassembly queue */
+  LWIP_ASSERT("ip_reass_pbufcount >= clen", ip6_reass_pbufcount >= pbufs_freed);
+  ip6_reass_pbufcount -= pbufs_freed;
+}
+
+#if IP_REASS_FREE_OLDEST
+/**
+ * Free the oldest datagram to make room for enqueueing new fragments.
+ * The datagram ipr is not freed!
+ *
+ * @param ipr ip6_reassdata for the current fragment
+ * @param pbufs_needed number of pbufs needed to enqueue
+ *        (used for freeing other datagrams if not enough space)
+ */
+static void
+ip6_reass_remove_oldest_datagram(struct ip6_reassdata *ipr, int pbufs_needed)
+{
+  struct ip6_reassdata *r, *oldest;
+
+  /* Free datagrams until being allowed to enqueue 'pbufs_needed' pbufs,
+   * but don't free the current datagram! */
+  do {
+    r = oldest = reassdatagrams;
+    while (r != NULL) {
+      if (r != ipr) {
+        if (r->timer <= oldest->timer) {
+          /* older than the previous oldest */
+          oldest = r;
+        }
+      }
+      r = r->next;
+    }
+    if (oldest != NULL) {
+      ip6_reass_free_complete_datagram(oldest);
+    }
+  } while (((ip6_reass_pbufcount + pbufs_needed) > IP_REASS_MAX_PBUFS) && (reassdatagrams != NULL));
+}
+#endif /* IP_REASS_FREE_OLDEST */
+
+/**
+ * Reassembles incoming IPv6 fragments into an IPv6 datagram.
+ *
+ * @param p points to the IPv6 Fragment Header
+ * @param len the length of the payload (after Fragment Header)
+ * @return NULL if reassembly is incomplete, pbuf pointing to
+ *         IPv6 Header if reassembly is complete
+ */
+struct pbuf *
+ip6_reass(struct pbuf *p)
+{
+  struct ip6_reassdata *ipr, *ipr_prev;
+  struct ip6_reass_helper *iprh, *iprh_tmp, *iprh_prev=NULL;
+  struct ip6_frag_hdr * frag_hdr;
+  u16_t offset, len;
+  u8_t clen, valid = 1;
+  struct pbuf *q;
+
+  IP6_FRAG_STATS_INC(ip6_frag.recv);
+
+  frag_hdr = (struct ip6_frag_hdr *) p->payload;
+
+  clen = pbuf_clen(p);
+
+  offset = ntohs(frag_hdr->_fragment_offset);
+
+  /* Calculate fragment length from IPv6 payload length.
+   * Adjust for headers before Fragment Header.
+   * And finally adjust by Fragment Header length. */
+  len = ntohs(ip6_current_header()->_plen);
+  len -= ((u8_t*)p->payload - (u8_t*)ip6_current_header()) - IP6_HLEN;
+  len -= IP6_FRAG_HLEN;
+
+  /* Look for the datagram the fragment belongs to in the current datagram queue,
+   * remembering the previous in the queue for later dequeueing. */
+  for (ipr = reassdatagrams, ipr_prev = NULL; ipr != NULL; ipr = ipr->next) {
+    /* Check if the incoming fragment matches the one currently present
+       in the reassembly buffer. If so, we proceed with copying the
+       fragment into the buffer. */
+    if ((frag_hdr->_identification == ipr->identification) &&
+        ip6_addr_cmp(ip6_current_src_addr(), &(ipr->iphdr->src)) &&
+        ip6_addr_cmp(ip6_current_dest_addr(), &(ipr->iphdr->dest))) {
+      IP6_FRAG_STATS_INC(ip6_frag.cachehit);
+      break;
+    }
+    ipr_prev = ipr;
+  }
+
+  if (ipr == NULL) {
+  /* Enqueue a new datagram into the datagram queue */
+    ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
+    if (ipr == NULL) {
+#if IP_REASS_FREE_OLDEST
+      /* Make room and try again. */
+      ip6_reass_remove_oldest_datagram(ipr, clen);
+      ipr = (struct ip6_reassdata *)memp_malloc(MEMP_IP6_REASSDATA);
+      if (ipr == NULL)
+#endif /* IP_REASS_FREE_OLDEST */
+      {
+        IP6_FRAG_STATS_INC(ip6_frag.memerr);
+        IP6_FRAG_STATS_INC(ip6_frag.drop);
+        goto nullreturn;
+      }
+    }
+
+    memset(ipr, 0, sizeof(struct ip6_reassdata));
+    ipr->timer = IP_REASS_MAXAGE;
+
+    /* enqueue the new structure to the front of the list */
+    ipr->next = reassdatagrams;
+    reassdatagrams = ipr;
+
+    /* Use the current IPv6 header for src/dest address reference.
+     * Eventually, we will replace it when we get the first fragment
+     * (it might be this one, in any case, it is done later). */
+    ipr->iphdr = (struct ip6_hdr *)ip6_current_header();
+
+    /* copy the fragmented packet id. */
+    ipr->identification = frag_hdr->_identification;
+
+    /* copy the nexth field */
+    ipr->nexth = frag_hdr->_nexth;
+  }
+
+  /* Check if we are allowed to enqueue more datagrams. */
+  if ((ip6_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS) {
+#if IP_REASS_FREE_OLDEST
+    ip6_reass_remove_oldest_datagram(ipr, clen);
+    if ((ip6_reass_pbufcount + clen) > IP_REASS_MAX_PBUFS)
+#endif /* IP_REASS_FREE_OLDEST */
+    {
+      /* @todo: send ICMPv6 time exceeded here? */
+      /* drop this pbuf */
+      IP6_FRAG_STATS_INC(ip6_frag.memerr);
+      IP6_FRAG_STATS_INC(ip6_frag.drop);
+      goto nullreturn;
+    }
+  }
+
+  /* Overwrite Fragment Header with our own helper struct. */
+  iprh = (struct ip6_reass_helper *)p->payload;
+  iprh->next_pbuf = NULL;
+  iprh->start = (offset & IP6_FRAG_OFFSET_MASK);
+  iprh->end = (offset & IP6_FRAG_OFFSET_MASK) + len;
+
+  /* find the right place to insert this pbuf */
+  /* Iterate through until we either get to the end of the list (append),
+   * or we find on with a larger offset (insert). */
+  for (q = ipr->p; q != NULL;) {
+    iprh_tmp = (struct ip6_reass_helper*)q->payload;
+    if (iprh->start < iprh_tmp->start) {
+#if IP_REASS_CHECK_OVERLAP
+      if (iprh->end > iprh_tmp->start) {
+        /* fragment overlaps with following, throw away */
+        IP6_FRAG_STATS_INC(ip6_frag.proterr);
+        IP6_FRAG_STATS_INC(ip6_frag.drop);
+        goto nullreturn;
+      }
+      if (iprh_prev != NULL) {
+        if (iprh->start < iprh_prev->end) {
+          /* fragment overlaps with previous, throw away */
+          IP6_FRAG_STATS_INC(ip6_frag.proterr);
+          IP6_FRAG_STATS_INC(ip6_frag.drop);
+          goto nullreturn;
+        }
+      }
+#endif /* IP_REASS_CHECK_OVERLAP */
+      /* the new pbuf should be inserted before this */
+      iprh->next_pbuf = q;
+      if (iprh_prev != NULL) {
+        /* not the fragment with the lowest offset */
+        iprh_prev->next_pbuf = p;
+      } else {
+        /* fragment with the lowest offset */
+        ipr->p = p;
+      }
+      break;
+    } else if(iprh->start == iprh_tmp->start) {
+      /* received the same datagram twice: no need to keep the datagram */
+      IP6_FRAG_STATS_INC(ip6_frag.drop);
+      goto nullreturn;
+#if IP_REASS_CHECK_OVERLAP
+    } else if(iprh->start < iprh_tmp->end) {
+      /* overlap: no need to keep the new datagram */
+      IP6_FRAG_STATS_INC(ip6_frag.proterr);
+      IP6_FRAG_STATS_INC(ip6_frag.drop);
+      goto nullreturn;
+#endif /* IP_REASS_CHECK_OVERLAP */
+    } else {
+      /* Check if the fragments received so far have no gaps. */
+      if (iprh_prev != NULL) {
+        if (iprh_prev->end != iprh_tmp->start) {
+          /* There is a fragment missing between the current
+           * and the previous fragment */
+          valid = 0;
+        }
+      }
+    }
+    q = iprh_tmp->next_pbuf;
+    iprh_prev = iprh_tmp;
+  }
+
+  /* If q is NULL, then we made it to the end of the list. Determine what to do now */
+  if (q == NULL) {
+    if (iprh_prev != NULL) {
+      /* this is (for now), the fragment with the highest offset:
+       * chain it to the last fragment */
+#if IP_REASS_CHECK_OVERLAP
+      LWIP_ASSERT("check fragments don't overlap", iprh_prev->end <= iprh->start);
+#endif /* IP_REASS_CHECK_OVERLAP */
+      iprh_prev->next_pbuf = p;
+      if (iprh_prev->end != iprh->start) {
+        valid = 0;
+      }
+    } else {
+#if IP_REASS_CHECK_OVERLAP
+      LWIP_ASSERT("no previous fragment, this must be the first fragment!",
+        ipr->p == NULL);
+#endif /* IP_REASS_CHECK_OVERLAP */
+      /* this is the first fragment we ever received for this ip datagram */
+      ipr->p = p;
+    }
+  }
+
+  /* Track the current number of pbufs current 'in-flight', in order to limit
+  the number of fragments that may be enqueued at any one time */
+  ip6_reass_pbufcount += clen;
+
+  /* Remember IPv6 header if this is the first fragment. */
+  if (iprh->start == 0) {
+    ipr->iphdr = (struct ip6_hdr *)ip6_current_header();
+  }
+
+  /* If this is the last fragment, calculate total packet length. */
+  if ((offset & IP6_FRAG_MORE_FLAG) == 0) {
+    ipr->datagram_len = iprh->end;
+  }
+
+  /* Additional validity tests: we have received first and last fragment. */
+  iprh_tmp = (struct ip6_reass_helper*)ipr->p->payload;
+  if (iprh_tmp->start != 0) {
+    valid = 0;
+  }
+  if (ipr->datagram_len == 0) {
+    valid = 0;
+  }
+
+  /* Final validity test: no gaps between current and last fragment. */
+  iprh_prev = iprh;
+  q = iprh->next_pbuf;
+  while ((q != NULL) && valid) {
+    iprh = (struct ip6_reass_helper*)q->payload;
+    if (iprh_prev->end != iprh->start) {
+      valid = 0;
+      break;
+    }
+    iprh_prev = iprh;
+    q = iprh->next_pbuf;
+  }
+
+  if (valid) {
+    /* All fragments have been received */
+
+    /* chain together the pbufs contained within the ip6_reassdata list. */
+    iprh = (struct ip6_reass_helper*) ipr->p->payload;
+    while(iprh != NULL) {
+
+      if (iprh->next_pbuf != NULL) {
+        /* Save next helper struct (will be hidden in next step). */
+        iprh_tmp = (struct ip6_reass_helper*) iprh->next_pbuf->payload;
+
+        /* hide the fragment header for every succeding fragment */
+        pbuf_header(iprh->next_pbuf, -IP6_FRAG_HLEN);
+        pbuf_cat(ipr->p, iprh->next_pbuf);
+      }
+      else {
+        iprh_tmp = NULL;
+      }
+
+      iprh = iprh_tmp;
+    }
+
+    /* Adjust datagram length by adding header lengths. */
+    ipr->datagram_len += ((u8_t*)ipr->p->payload - (u8_t*)ipr->iphdr)
+                         + IP6_FRAG_HLEN
+                         - IP6_HLEN ;
+
+    /* Set payload length in ip header. */
+    ipr->iphdr->_plen = htons(ipr->datagram_len);
+
+    /* Get the furst pbuf. */
+    p = ipr->p;
+
+    /* Restore Fragment Header in first pbuf. Mark as "single fragment"
+     * packet. Restore nexth. */
+    frag_hdr = (struct ip6_frag_hdr *) p->payload;
+    frag_hdr->_nexth = ipr->nexth;
+    frag_hdr->reserved = 0;
+    frag_hdr->_fragment_offset = 0;
+    frag_hdr->_identification = 0;
+
+    /* release the sources allocate for the fragment queue entry */
+    if (reassdatagrams == ipr) {
+      /* it was the first in the list */
+      reassdatagrams = ipr->next;
+    } else {
+      /* it wasn't the first, so it must have a valid 'prev' */
+      LWIP_ASSERT("sanity check linked list", ipr_prev != NULL);
+      ipr_prev->next = ipr->next;
+    }
+    memp_free(MEMP_IP6_REASSDATA, ipr);
+
+    /* adjust the number of pbufs currently queued for reassembly. */
+    ip6_reass_pbufcount -= pbuf_clen(p);
+
+    /* Move pbuf back to IPv6 header. */
+    if (pbuf_header(p, (u8_t*)p->payload - (u8_t*)ipr->iphdr)) {
+      LWIP_ASSERT("ip6_reass: moving p->payload to ip6 header failed\n", 0);
+      pbuf_free(p);
+      return NULL;
+    }
+
+    /* Return the pbuf chain */
+    return p;
+  }
+  /* the datagram is not (yet?) reassembled completely */
+  return NULL;
+
+nullreturn:
+  pbuf_free(p);
+  return NULL;
+}
+
+#endif /* LWIP_IPV6 ^^ LWIP_IPV6_REASS */
+
+#if LWIP_IPV6 && LWIP_IPV6_FRAG
+
+/** Allocate a new struct pbuf_custom_ref */
+static struct pbuf_custom_ref*
+ip6_frag_alloc_pbuf_custom_ref(void)
+{
+  return (struct pbuf_custom_ref*)memp_malloc(MEMP_FRAG_PBUF);
+}
+
+/** Free a struct pbuf_custom_ref */
+static void
+ip6_frag_free_pbuf_custom_ref(struct pbuf_custom_ref* p)
+{
+  LWIP_ASSERT("p != NULL", p != NULL);
+  memp_free(MEMP_FRAG_PBUF, p);
+}
+
+/** Free-callback function to free a 'struct pbuf_custom_ref', called by
+ * pbuf_free. */
+static void
+ip6_frag_free_pbuf_custom(struct pbuf *p)
+{
+  struct pbuf_custom_ref *pcr = (struct pbuf_custom_ref*)p;
+  LWIP_ASSERT("pcr != NULL", pcr != NULL);
+  LWIP_ASSERT("pcr == p", (void*)pcr == (void*)p);
+  if (pcr->original != NULL) {
+    pbuf_free(pcr->original);
+  }
+  ip6_frag_free_pbuf_custom_ref(pcr);
+}
+
+/**
+ * Fragment an IPv6 datagram if too large for the netif or path MTU.
+ *
+ * Chop the datagram in MTU sized chunks and send them in order
+ * by pointing PBUF_REFs into p
+ *
+ * @param p ipv6 packet to send
+ * @param netif the netif on which to send
+ * @param dest destination ipv6 address to which to send
+ *
+ * @return ERR_OK if sent successfully, err_t otherwise
+ */
+err_t
+ip6_frag(struct pbuf *p, struct netif *netif, ip6_addr_t *dest)
+{
+  struct ip6_hdr *original_ip6hdr;
+  struct ip6_hdr *ip6hdr;
+  struct ip6_frag_hdr * frag_hdr;
+  struct pbuf *rambuf;
+  struct pbuf *newpbuf;
+  static u32_t identification;
+  u16_t nfb;
+  u16_t left, cop;
+  u16_t mtu;
+  u16_t fragment_offset = 0;
+  u16_t last;
+  u16_t poff = IP6_HLEN;
+  u16_t newpbuflen = 0;
+  u16_t left_to_copy;
+
+  identification++;
+
+  original_ip6hdr = (struct ip6_hdr *)p->payload;
+
+  mtu = nd6_get_destination_mtu(dest, netif);
+
+  /* TODO we assume there are no options in the unfragmentable part (IPv6 header). */
+  left = p->tot_len - IP6_HLEN;
+
+  nfb = (mtu - (IP6_HLEN + IP6_FRAG_HLEN)) & IP6_FRAG_OFFSET_MASK;
+
+  while (left) {
+    last = (left <= nfb);
+
+    /* Fill this fragment */
+    cop = last ? left : nfb;
+
+    /* When not using a static buffer, create a chain of pbufs.
+     * The first will be a PBUF_RAM holding the link, IPv6, and Fragment header.
+     * The rest will be PBUF_REFs mirroring the pbuf chain to be fragged,
+     * but limited to the size of an mtu.
+     */
+    rambuf = pbuf_alloc(PBUF_LINK, IP6_HLEN + IP6_FRAG_HLEN, PBUF_RAM);
+    if (rambuf == NULL) {
+      IP6_FRAG_STATS_INC(ip6_frag.memerr);
+      return ERR_MEM;
+    }
+    LWIP_ASSERT("this needs a pbuf in one piece!",
+                (p->len >= (IP6_HLEN + IP6_FRAG_HLEN)));
+    SMEMCPY(rambuf->payload, original_ip6hdr, IP6_HLEN);
+    ip6hdr = (struct ip6_hdr *)rambuf->payload;
+    frag_hdr = (struct ip6_frag_hdr *)((u8_t*)rambuf->payload + IP6_HLEN);
+
+    /* Can just adjust p directly for needed offset. */
+    p->payload = (u8_t *)p->payload + poff;
+    p->len -= poff;
+    p->tot_len -= poff;
+
+    left_to_copy = cop;
+    while (left_to_copy) {
+      struct pbuf_custom_ref *pcr;
+      newpbuflen = (left_to_copy < p->len) ? left_to_copy : p->len;
+      /* Is this pbuf already empty? */
+      if (!newpbuflen) {
+        p = p->next;
+        continue;
+      }
+      pcr = ip6_frag_alloc_pbuf_custom_ref();
+      if (pcr == NULL) {
+        pbuf_free(rambuf);
+        IP6_FRAG_STATS_INC(ip6_frag.memerr);
+        return ERR_MEM;
+      }
+      /* Mirror this pbuf, although we might not need all of it. */
+      newpbuf = pbuf_alloced_custom(PBUF_RAW, newpbuflen, PBUF_REF, &pcr->pc, p->payload, newpbuflen);
+      if (newpbuf == NULL) {
+        ip6_frag_free_pbuf_custom_ref(pcr);
+        pbuf_free(rambuf);
+        IP6_FRAG_STATS_INC(ip6_frag.memerr);
+        return ERR_MEM;
+      }
+      pbuf_ref(p);
+      pcr->original = p;
+      pcr->pc.custom_free_function = ip6_frag_free_pbuf_custom;
+
+      /* Add it to end of rambuf's chain, but using pbuf_cat, not pbuf_chain
+       * so that it is removed when pbuf_dechain is later called on rambuf.
+       */
+      pbuf_cat(rambuf, newpbuf);
+      left_to_copy -= newpbuflen;
+      if (left_to_copy) {
+        p = p->next;
+      }
+    }
+    poff = newpbuflen;
+
+    /* Set headers */
+    frag_hdr->_nexth = original_ip6hdr->_nexth;
+    frag_hdr->reserved = 0;
+    frag_hdr->_fragment_offset = htons((fragment_offset & IP6_FRAG_OFFSET_MASK) | (last ? 0 : IP6_FRAG_MORE_FLAG));
+    frag_hdr->_identification = htonl(identification);
+
+    IP6H_NEXTH_SET(ip6hdr, IP6_NEXTH_FRAGMENT);
+    IP6H_PLEN_SET(ip6hdr, cop + IP6_FRAG_HLEN);
+
+    /* No need for separate header pbuf - we allowed room for it in rambuf
+     * when allocated.
+     */
+    IP6_FRAG_STATS_INC(ip6_frag.xmit);
+    netif->output_ip6(netif, rambuf, dest);
+
+    /* Unfortunately we can't reuse rambuf - the hardware may still be
+     * using the buffer. Instead we free it (and the ensuing chain) and
+     * recreate it next time round the loop. If we're lucky the hardware
+     * will have already sent the packet, the free will really free, and
+     * there will be zero memory penalty.
+     */
+
+    pbuf_free(rambuf);
+    left -= cop;
+    fragment_offset += cop;
+  }
+  return ERR_OK;
+}
+
+#endif /* LWIP_IPV6 && LWIP_IPV6_FRAG */
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/mld6.c b/external/badvpn_dns/lwip/src/core/ipv6/mld6.c
new file mode 100644
index 0000000..1cb2dd9
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/mld6.c
@@ -0,0 +1,580 @@
+/**
+ * @file
+ *
+ * Multicast listener discovery for IPv6. Aims to be compliant with RFC 2710.
+ * No support for MLDv2.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+/* Based on igmp.c implementation of igmp v2 protocol */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6 && LWIP_IPV6_MLD  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/mld6.h"
+#include "lwip/icmp6.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+#include "lwip/memp.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+
+/*
+ * MLD constants
+ */
+#define MLD6_HL                           1
+#define MLD6_JOIN_DELAYING_MEMBER_TMR_MS  (500)
+
+#define MLD6_GROUP_NON_MEMBER             0
+#define MLD6_GROUP_DELAYING_MEMBER        1
+#define MLD6_GROUP_IDLE_MEMBER            2
+
+
+/* The list of joined groups. */
+static struct mld_group* mld_group_list;
+
+
+/* Forward declarations. */
+static struct mld_group * mld6_new_group(struct netif *ifp, ip6_addr_t *addr);
+static err_t mld6_free_group(struct mld_group *group);
+static void mld6_delayed_report(struct mld_group *group, u16_t maxresp);
+static void mld6_send(struct mld_group *group, u8_t type);
+
+
+/**
+ * Stop MLD processing on interface
+ *
+ * @param netif network interface on which stop MLD processing
+ */
+err_t
+mld6_stop(struct netif *netif)
+{
+  struct mld_group *group = mld_group_list;
+  struct mld_group *prev  = NULL;
+  struct mld_group *next;
+
+  /* look for groups joined on this interface further down the list */
+  while (group != NULL) {
+    next = group->next;
+    /* is it a group joined on this interface? */
+    if (group->netif == netif) {
+      /* is it the first group of the list? */
+      if (group == mld_group_list) {
+        mld_group_list = next;
+      }
+      /* is there a "previous" group defined? */
+      if (prev != NULL) {
+        prev->next = next;
+      }
+      /* disable the group at the MAC level */
+      if (netif->mld_mac_filter != NULL) {
+        netif->mld_mac_filter(netif, &(group->group_address), MLD6_DEL_MAC_FILTER);
+      }
+      /* free group */
+      memp_free(MEMP_MLD6_GROUP, group);
+    } else {
+      /* change the "previous" */
+      prev = group;
+    }
+    /* move to "next" */
+    group = next;
+  }
+  return ERR_OK;
+}
+
+/**
+ * Report MLD memberships for this interface
+ *
+ * @param netif network interface on which report MLD memberships
+ */
+void
+mld6_report_groups(struct netif *netif)
+{
+  struct mld_group *group = mld_group_list;
+
+  while (group != NULL) {
+    if (group->netif == netif) {
+      mld6_delayed_report(group, MLD6_JOIN_DELAYING_MEMBER_TMR_MS);
+    }
+    group = group->next;
+  }
+}
+
+/**
+ * Search for a group that is joined on a netif
+ *
+ * @param ifp the network interface for which to look
+ * @param addr the group ipv6 address to search for
+ * @return a struct mld_group* if the group has been found,
+ *         NULL if the group wasn't found.
+ */
+struct mld_group *
+mld6_lookfor_group(struct netif *ifp, ip6_addr_t *addr)
+{
+  struct mld_group *group = mld_group_list;
+
+  while (group != NULL) {
+    if ((group->netif == ifp) && (ip6_addr_cmp(&(group->group_address), addr))) {
+      return group;
+    }
+    group = group->next;
+  }
+
+  return NULL;
+}
+
+
+/**
+ * create a new group
+ *
+ * @param ifp the network interface for which to create
+ * @param addr the new group ipv6
+ * @return a struct mld_group*,
+ *         NULL on memory error.
+ */
+static struct mld_group *
+mld6_new_group(struct netif *ifp, ip6_addr_t *addr)
+{
+  struct mld_group *group;
+
+  group = (struct mld_group *)memp_malloc(MEMP_MLD6_GROUP);
+  if (group != NULL) {
+    group->netif              = ifp;
+    ip6_addr_set(&(group->group_address), addr);
+    group->timer              = 0; /* Not running */
+    group->group_state        = MLD6_GROUP_IDLE_MEMBER;
+    group->last_reporter_flag = 0;
+    group->use                = 0;
+    group->next               = mld_group_list;
+
+    mld_group_list = group;
+  }
+
+  return group;
+}
+
+/**
+ * Remove a group in the mld_group_list and free
+ *
+ * @param group the group to remove
+ * @return ERR_OK if group was removed from the list, an err_t otherwise
+ */
+static err_t
+mld6_free_group(struct mld_group *group)
+{
+  err_t err = ERR_OK;
+
+  /* Is it the first group? */
+  if (mld_group_list == group) {
+    mld_group_list = group->next;
+  } else {
+    /* look for group further down the list */
+    struct mld_group *tmpGroup;
+    for (tmpGroup = mld_group_list; tmpGroup != NULL; tmpGroup = tmpGroup->next) {
+      if (tmpGroup->next == group) {
+        tmpGroup->next = group->next;
+        break;
+      }
+    }
+    /* Group not find group */
+    if (tmpGroup == NULL)
+      err = ERR_ARG;
+  }
+  /* free group */
+  memp_free(MEMP_MLD6_GROUP, group);
+
+  return err;
+}
+
+
+/**
+ * Process an input MLD message. Called by icmp6_input.
+ *
+ * @param p the mld packet, p->payload pointing to the icmpv6 header
+ * @param inp the netif on which this packet was received
+ */
+void
+mld6_input(struct pbuf *p, struct netif *inp)
+{
+  struct mld_header * mld_hdr;
+  struct mld_group* group;
+
+  MLD6_STATS_INC(mld6.recv);
+
+  /* Check that mld header fits in packet. */
+  if (p->len < sizeof(struct mld_header)) {
+    /* TODO debug message */
+    pbuf_free(p);
+    MLD6_STATS_INC(mld6.lenerr);
+    MLD6_STATS_INC(mld6.drop);
+    return;
+  }
+
+  mld_hdr = (struct mld_header *)p->payload;
+
+  switch (mld_hdr->type) {
+  case ICMP6_TYPE_MLQ: /* Multicast listener query. */
+  {
+    /* Is it a general query? */
+    if (ip6_addr_isallnodes_linklocal(ip6_current_dest_addr()) &&
+        ip6_addr_isany(&(mld_hdr->multicast_address))) {
+      MLD6_STATS_INC(mld6.rx_general);
+      /* Report all groups, except all nodes group, and if-local groups. */
+      group = mld_group_list;
+      while (group != NULL) {
+        if ((group->netif == inp) &&
+            (!(ip6_addr_ismulticast_iflocal(&(group->group_address)))) &&
+            (!(ip6_addr_isallnodes_linklocal(&(group->group_address))))) {
+          mld6_delayed_report(group, mld_hdr->max_resp_delay);
+        }
+        group = group->next;
+      }
+    }
+    else {
+      /* Have we joined this group?
+       * We use IP6 destination address to have a memory aligned copy.
+       * mld_hdr->multicast_address should be the same. */
+      MLD6_STATS_INC(mld6.rx_group);
+      group = mld6_lookfor_group(inp, ip6_current_dest_addr());
+      if (group != NULL) {
+        /* Schedule a report. */
+        mld6_delayed_report(group, mld_hdr->max_resp_delay);
+      }
+    }
+    break; /* ICMP6_TYPE_MLQ */
+  }
+  case ICMP6_TYPE_MLR: /* Multicast listener report. */
+  {
+    /* Have we joined this group?
+     * We use IP6 destination address to have a memory aligned copy.
+     * mld_hdr->multicast_address should be the same. */
+    MLD6_STATS_INC(mld6.rx_report);
+    group = mld6_lookfor_group(inp, ip6_current_dest_addr());
+    if (group != NULL) {
+      /* If we are waiting to report, cancel it. */
+      if (group->group_state == MLD6_GROUP_DELAYING_MEMBER) {
+        group->timer = 0; /* stopped */
+        group->group_state = MLD6_GROUP_IDLE_MEMBER;
+        group->last_reporter_flag = 0;
+      }
+    }
+    break; /* ICMP6_TYPE_MLR */
+  }
+  case ICMP6_TYPE_MLD: /* Multicast listener done. */
+  {
+    /* Do nothing, router will query us. */
+    break; /* ICMP6_TYPE_MLD */
+  }
+  default:
+    MLD6_STATS_INC(mld6.proterr);
+    MLD6_STATS_INC(mld6.drop);
+    break;
+  }
+
+  pbuf_free(p);
+}
+
+/**
+ * Join a group on a network interface.
+ *
+ * @param srcaddr ipv6 address of the network interface which should
+ *                join a new group. If IP6_ADDR_ANY, join on all netifs
+ * @param groupaddr the ipv6 address of the group to join
+ * @return ERR_OK if group was joined on the netif(s), an err_t otherwise
+ */
+err_t
+mld6_joingroup(ip6_addr_t *srcaddr, ip6_addr_t *groupaddr)
+{
+  err_t              err = ERR_VAL; /* no matching interface */
+  struct mld_group  *group;
+  struct netif      *netif;
+  u8_t               match;
+  u8_t               i;
+
+  /* loop through netif's */
+  netif = netif_list;
+  while (netif != NULL) {
+    /* Should we join this interface ? */
+    match = 0;
+    if (ip6_addr_isany(srcaddr)) {
+      match = 1;
+    }
+    else {
+      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+        if (ip6_addr_cmp(srcaddr, netif_ip6_addr(netif, i))) {
+          match = 1;
+          break;
+        }
+      }
+    }
+    if (match) {
+      /* find group or create a new one if not found */
+      group = mld6_lookfor_group(netif, groupaddr);
+
+      if (group == NULL) {
+        /* Joining a new group. Create a new group entry. */
+        group = mld6_new_group(netif, groupaddr);
+        if (group == NULL) {
+          return ERR_MEM;
+        }
+
+        /* Activate this address on the MAC layer. */
+        if (netif->mld_mac_filter != NULL) {
+          netif->mld_mac_filter(netif, groupaddr, MLD6_ADD_MAC_FILTER);
+        }
+
+        /* Report our membership. */
+        MLD6_STATS_INC(mld6.tx_report);
+        mld6_send(group, ICMP6_TYPE_MLR);
+        mld6_delayed_report(group, MLD6_JOIN_DELAYING_MEMBER_TMR_MS);
+      }
+
+      /* Increment group use */
+      group->use++;
+      err = ERR_OK;
+    }
+
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+
+  return err;
+}
+
+/**
+ * Leave a group on a network interface.
+ *
+ * @param srcaddr ipv6 address of the network interface which should
+ *                leave the group. If IP6_ISANY, leave on all netifs
+ * @param groupaddr the ipv6 address of the group to leave
+ * @return ERR_OK if group was left on the netif(s), an err_t otherwise
+ */
+err_t
+mld6_leavegroup(ip6_addr_t *srcaddr, ip6_addr_t *groupaddr)
+{
+  err_t              err = ERR_VAL; /* no matching interface */
+  struct mld_group  *group;
+  struct netif      *netif;
+  u8_t               match;
+  u8_t               i;
+
+  /* loop through netif's */
+  netif = netif_list;
+  while (netif != NULL) {
+    /* Should we leave this interface ? */
+    match = 0;
+    if (ip6_addr_isany(srcaddr)) {
+      match = 1;
+    }
+    else {
+      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+        if (ip6_addr_cmp(srcaddr, netif_ip6_addr(netif, i))) {
+          match = 1;
+          break;
+        }
+      }
+    }
+    if (match) {
+      /* find group */
+      group = mld6_lookfor_group(netif, groupaddr);
+
+      if (group != NULL) {
+        /* Leave if there is no other use of the group */
+        if (group->use <= 1) {
+          /* If we are the last reporter for this group */
+          if (group->last_reporter_flag) {
+            MLD6_STATS_INC(mld6.tx_leave);
+            mld6_send(group, ICMP6_TYPE_MLD);
+          }
+
+          /* Disable the group at the MAC level */
+          if (netif->mld_mac_filter != NULL) {
+            netif->mld_mac_filter(netif, groupaddr, MLD6_DEL_MAC_FILTER);
+          }
+
+          /* Free the group */
+          mld6_free_group(group);
+        } else {
+          /* Decrement group use */
+          group->use--;
+        }
+        /* Leave on this interface */
+        err = ERR_OK;
+      }
+    }
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+
+  return err;
+}
+
+
+/**
+ * Periodic timer for mld processing. Must be called every
+ * MLD6_TMR_INTERVAL milliseconds (100).
+ *
+ * When a delaying member expires, a membership report is sent.
+ */
+void
+mld6_tmr(void)
+{
+  struct mld_group *group = mld_group_list;
+
+  while (group != NULL) {
+    if (group->timer > 0) {
+      group->timer--;
+      if (group->timer == 0) {
+        /* If the state is MLD6_GROUP_DELAYING_MEMBER then we send a report for this group */
+        if (group->group_state == MLD6_GROUP_DELAYING_MEMBER) {
+          MLD6_STATS_INC(mld6.tx_report);
+          mld6_send(group, ICMP6_TYPE_MLR);
+          group->group_state = MLD6_GROUP_IDLE_MEMBER;
+        }
+      }
+    }
+    group = group->next;
+  }
+}
+
+/**
+ * Schedule a delayed membership report for a group
+ *
+ * @param group the mld_group for which "delaying" membership report
+ *              should be sent
+ * @param maxresp the max resp delay provided in the query
+ */
+static void
+mld6_delayed_report(struct mld_group *group, u16_t maxresp)
+{
+  /* Convert maxresp from milliseconds to tmr ticks */
+  maxresp = maxresp / MLD6_TMR_INTERVAL;
+  if (maxresp == 0) {
+    maxresp = 1;
+  }
+
+#ifdef LWIP_RAND
+  /* Randomize maxresp. (if LWIP_RAND is supported) */
+  maxresp = (LWIP_RAND() % (maxresp - 1)) + 1;
+#endif /* LWIP_RAND */
+
+  /* Apply timer value if no report has been scheduled already. */
+  if ((group->group_state == MLD6_GROUP_IDLE_MEMBER) ||
+     ((group->group_state == MLD6_GROUP_DELAYING_MEMBER) &&
+      ((group->timer == 0) || (maxresp < group->timer)))) {
+    group->timer = maxresp;
+    group->group_state = MLD6_GROUP_DELAYING_MEMBER;
+  }
+}
+
+/**
+ * Send a MLD message (report or done).
+ *
+ * An IPv6 hop-by-hop options header with a router alert option
+ * is prepended.
+ *
+ * @param group the group to report or quit
+ * @param type ICMP6_TYPE_MLR (report) or ICMP6_TYPE_MLD (done)
+ */
+static void
+mld6_send(struct mld_group *group, u8_t type)
+{
+  struct mld_header * mld_hdr;
+  struct pbuf * p;
+  ip6_addr_t * src_addr;
+
+  /* Allocate a packet. Size is MLD header + IPv6 Hop-by-hop options header. */
+  p = pbuf_alloc(PBUF_IP, sizeof(struct mld_header) + sizeof(struct ip6_hbh_hdr), PBUF_RAM);
+  if ((p == NULL) || (p->len < (sizeof(struct mld_header) + sizeof(struct ip6_hbh_hdr)))) {
+    /* We couldn't allocate a suitable pbuf. drop it. */
+    if (p != NULL) {
+      pbuf_free(p);
+    }
+    MLD6_STATS_INC(mld6.memerr);
+    return;
+  }
+
+  /* Move to make room for Hop-by-hop options header. */
+  if (pbuf_header(p, -IP6_HBH_HLEN)) {
+    pbuf_free(p);
+    MLD6_STATS_INC(mld6.lenerr);
+    return;
+  }
+
+  /* Select our source address. */
+  if (!ip6_addr_isvalid(netif_ip6_addr_state(group->netif, 0))) {
+    /* This is a special case, when we are performing duplicate address detection.
+     * We must join the multicast group, but we don't have a valid address yet. */
+    src_addr = IP6_ADDR_ANY;
+  } else {
+    /* Use link-local address as source address. */
+    src_addr = netif_ip6_addr(group->netif, 0);
+  }
+
+  /* MLD message header pointer. */
+  mld_hdr = (struct mld_header *)p->payload;
+
+  /* Set fields. */
+  mld_hdr->type = type;
+  mld_hdr->code = 0;
+  mld_hdr->chksum = 0;
+  mld_hdr->max_resp_delay = 0;
+  mld_hdr->reserved = 0;
+  ip6_addr_set(&(mld_hdr->multicast_address), &(group->group_address));
+
+  mld_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len,
+    src_addr, &(group->group_address));
+
+  /* Add hop-by-hop headers options: router alert with MLD value. */
+  ip6_options_add_hbh_ra(p, IP6_NEXTH_ICMP6, IP6_ROUTER_ALERT_VALUE_MLD);
+
+  /* Send the packet out. */
+  MLD6_STATS_INC(mld6.xmit);
+  ip6_output_if(p, (ip6_addr_isany(src_addr)) ? NULL : src_addr, &(group->group_address),
+      MLD6_HL, 0, IP6_NEXTH_HOPBYHOP, group->netif);
+  pbuf_free(p);
+}
+
+
+
+#endif /* LWIP_IPV6 */
diff --git a/external/badvpn_dns/lwip/src/core/ipv6/nd6.c b/external/badvpn_dns/lwip/src/core/ipv6/nd6.c
new file mode 100644
index 0000000..480638e
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/ipv6/nd6.c
@@ -0,0 +1,1787 @@
+/**
+ * @file
+ *
+ * Neighbor discovery and stateless address autoconfiguration for IPv6.
+ * Aims to be compliant with RFC 4861 (Neighbor discovery) and RFC 4862
+ * (Address autoconfiguration).
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/nd6.h"
+#include "lwip/pbuf.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/netif.h"
+#include "lwip/icmp6.h"
+#include "lwip/mld6.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+
+/* Router tables. */
+struct nd6_neighbor_cache_entry neighbor_cache[LWIP_ND6_NUM_NEIGHBORS];
+struct nd6_destination_cache_entry destination_cache[LWIP_ND6_NUM_DESTINATIONS];
+struct nd6_prefix_list_entry prefix_list[LWIP_ND6_NUM_PREFIXES];
+struct nd6_router_list_entry default_router_list[LWIP_ND6_NUM_ROUTERS];
+
+/* Default values, can be updated by a RA message. */
+u32_t reachable_time = LWIP_ND6_REACHABLE_TIME;
+u32_t retrans_timer = LWIP_ND6_RETRANS_TIMER; /* TODO implement this value in timer */
+
+/* Index for cache entries. */
+static u8_t nd6_cached_neighbor_index;
+static u8_t nd6_cached_destination_index;
+
+/* Multicast address holder. */
+static ip6_addr_t multicast_address;
+
+/* Static buffer to parse RA packet options (size of a prefix option, biggest option) */
+static u8_t nd6_ra_buffer[sizeof(struct prefix_option)];
+
+/* Forward declarations. */
+static s8_t nd6_find_neighbor_cache_entry(ip6_addr_t * ip6addr);
+static s8_t nd6_new_neighbor_cache_entry(void);
+static void nd6_free_neighbor_cache_entry(s8_t i);
+static s8_t nd6_find_destination_cache_entry(ip6_addr_t * ip6addr);
+static s8_t nd6_new_destination_cache_entry(void);
+static s8_t nd6_is_prefix_in_netif(ip6_addr_t * ip6addr, struct netif * netif);
+static s8_t nd6_get_router(ip6_addr_t * router_addr, struct netif * netif);
+static s8_t nd6_new_router(ip6_addr_t * router_addr, struct netif * netif);
+static s8_t nd6_get_onlink_prefix(ip6_addr_t * prefix, struct netif * netif);
+static s8_t nd6_new_onlink_prefix(ip6_addr_t * prefix, struct netif * netif);
+
+#define ND6_SEND_FLAG_MULTICAST_DEST 0x01
+#define ND6_SEND_FLAG_ALLNODES_DEST 0x02
+static void nd6_send_ns(struct netif * netif, ip6_addr_t * target_addr, u8_t flags);
+static void nd6_send_na(struct netif * netif, ip6_addr_t * target_addr, u8_t flags);
+#if LWIP_IPV6_SEND_ROUTER_SOLICIT
+static void nd6_send_rs(struct netif * netif);
+#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
+
+#if LWIP_ND6_QUEUEING
+static void nd6_free_q(struct nd6_q_entry *q);
+#else /* LWIP_ND6_QUEUEING */
+#define nd6_free_q(q) pbuf_free(q)
+#endif /* LWIP_ND6_QUEUEING */
+static void nd6_send_q(s8_t i);
+
+
+/**
+ * Process an incoming neighbor discovery message
+ *
+ * @param p the nd packet, p->payload pointing to the icmpv6 header
+ * @param inp the netif on which this packet was received
+ */
+void
+nd6_input(struct pbuf *p, struct netif *inp)
+{
+  u8_t msg_type;
+  s8_t i;
+
+  ND6_STATS_INC(nd6.recv);
+
+  msg_type = *((u8_t *)p->payload);
+  switch (msg_type) {
+  case ICMP6_TYPE_NA: /* Neighbor Advertisement. */
+  {
+    struct na_header * na_hdr;
+    struct lladdr_option * lladdr_opt;
+
+    /* Check that na header fits in packet. */
+    if (p->len < (sizeof(struct na_header))) {
+      /* TODO debug message */
+      pbuf_free(p);
+      ND6_STATS_INC(nd6.lenerr);
+      ND6_STATS_INC(nd6.drop);
+      return;
+    }
+
+    na_hdr = (struct na_header *)p->payload;
+
+    /* Unsolicited NA?*/
+    if (ip6_addr_ismulticast(ip6_current_dest_addr())) {
+      /* This is an unsolicited NA.
+       * link-layer changed?
+       * part of DAD mechanism? */
+
+      /* Check that link-layer address option also fits in packet. */
+      if (p->len < (sizeof(struct na_header) + sizeof(struct lladdr_option))) {
+        /* TODO debug message */
+        pbuf_free(p);
+        ND6_STATS_INC(nd6.lenerr);
+        ND6_STATS_INC(nd6.drop);
+        return;
+      }
+
+      lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct na_header));
+
+      /* Override ip6_current_dest_addr() so that we have an aligned copy. */
+      ip6_addr_set(ip6_current_dest_addr(), &(na_hdr->target_address));
+
+#if LWIP_IPV6_DUP_DETECT_ATTEMPTS
+      /* If the target address matches this netif, it is a DAD response. */
+      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+        if (ip6_addr_cmp(ip6_current_dest_addr(), netif_ip6_addr(inp, i))) {
+          /* We are using a duplicate address. */
+          netif_ip6_addr_set_state(inp, i, IP6_ADDR_INVALID);
+
+#if LWIP_IPV6_MLD
+          /* Leave solicited node multicast group. */
+          ip6_addr_set_solicitednode(&multicast_address, netif_ip6_addr(inp, i)->addr[3]);
+          mld6_leavegroup(netif_ip6_addr(inp, i), &multicast_address);
+#endif /* LWIP_IPV6_MLD */
+
+
+
+
+#if LWIP_IPV6_AUTOCONFIG
+          /* Check to see if this address was autoconfigured. */
+          if (!ip6_addr_islinklocal(ip6_current_dest_addr())) {
+            i = nd6_get_onlink_prefix(ip6_current_dest_addr(), inp);
+            if (i >= 0) {
+              /* Mark this prefix as duplicate, so that we don't use it
+               * to generate this address again. */
+              prefix_list[i].flags |= ND6_PREFIX_AUTOCONFIG_ADDRESS_DUPLICATE;
+            }
+          }
+#endif /* LWIP_IPV6_AUTOCONFIG */
+
+          pbuf_free(p);
+          return;
+        }
+      }
+#endif /* LWIP_IPV6_DUP_DETECT_ATTEMPTS */
+
+      /* This is an unsolicited NA, most likely there was a LLADDR change. */
+      i = nd6_find_neighbor_cache_entry(ip6_current_dest_addr());
+      if (i >= 0) {
+        if (na_hdr->flags & ND6_FLAG_OVERRIDE) {
+          MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
+        }
+      }
+    }
+    else {
+      /* This is a solicited NA.
+       * neighbor address resolution response?
+       * neighbor unreachability detection response? */
+
+      /* Override ip6_current_dest_addr() so that we have an aligned copy. */
+      ip6_addr_set(ip6_current_dest_addr(), &(na_hdr->target_address));
+
+      /* Find the cache entry corresponding to this na. */
+      i = nd6_find_neighbor_cache_entry(ip6_current_dest_addr());
+      if (i < 0) {
+        /* We no longer care about this target address. drop it. */
+        pbuf_free(p);
+        return;
+      }
+
+      /* Update cache entry. */
+      neighbor_cache[i].netif = inp;
+      neighbor_cache[i].counter.reachable_time = reachable_time;
+      if ((na_hdr->flags & ND6_FLAG_OVERRIDE) ||
+          (neighbor_cache[i].state == ND6_INCOMPLETE)) {
+        /* Check that link-layer address option also fits in packet. */
+        if (p->len < (sizeof(struct na_header) + sizeof(struct lladdr_option))) {
+          /* TODO debug message */
+          pbuf_free(p);
+          ND6_STATS_INC(nd6.lenerr);
+          ND6_STATS_INC(nd6.drop);
+          return;
+        }
+
+        lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct na_header));
+
+        MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
+      }
+      neighbor_cache[i].state = ND6_REACHABLE;
+
+      /* Send queued packets, if any. */
+      if (neighbor_cache[i].q != NULL) {
+        nd6_send_q(i);
+      }
+    }
+
+    break; /* ICMP6_TYPE_NA */
+  }
+  case ICMP6_TYPE_NS: /* Neighbor solicitation. */
+  {
+    struct ns_header * ns_hdr;
+    struct lladdr_option * lladdr_opt;
+    u8_t accepted;
+
+    /* Check that ns header fits in packet. */
+    if (p->len < sizeof(struct ns_header)) {
+      /* TODO debug message */
+      pbuf_free(p);
+      ND6_STATS_INC(nd6.lenerr);
+      ND6_STATS_INC(nd6.drop);
+      return;
+    }
+
+    ns_hdr = (struct ns_header *)p->payload;
+
+    /* Check if there is a link-layer address provided. Only point to it if in this buffer. */
+    lladdr_opt = NULL;
+    if (p->len >= (sizeof(struct ns_header) + sizeof(struct lladdr_option))) {
+      lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct ns_header));
+    }
+
+    /* Check if the target address is configured on the receiving netif. */
+    accepted = 0;
+    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
+      if ((ip6_addr_isvalid(netif_ip6_addr_state(inp, i)) ||
+           (ip6_addr_istentative(netif_ip6_addr_state(inp, i)) &&
+            ip6_addr_isany(ip6_current_src_addr()))) &&
+          ip6_addr_cmp(&(ns_hdr->target_address), netif_ip6_addr(inp, i))) {
+        accepted = 1;
+        break;
+      }
+    }
+
+    /* NS not for us? */
+    if (!accepted) {
+      pbuf_free(p);
+      return;
+    }
+
+    /* Check for ANY address in src (DAD algorithm). */
+    if (ip6_addr_isany(ip6_current_src_addr())) {
+      /* Sender is validating this address. */
+      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
+        if (ip6_addr_cmp(&(ns_hdr->target_address), netif_ip6_addr(inp, i))) {
+          /* Send a NA back so that the sender does not use this address. */
+          nd6_send_na(inp, netif_ip6_addr(inp, i), ND6_FLAG_OVERRIDE | ND6_SEND_FLAG_ALLNODES_DEST);
+          if (ip6_addr_istentative(netif_ip6_addr_state(inp, i))) {
+            /* We shouldn't use this address either. */
+            netif_ip6_addr_set_state(inp, i, IP6_ADDR_INVALID);
+          }
+        }
+      }
+    }
+    else {
+      /* Sender is trying to resolve our address. */
+      /* Verify that they included their own link-layer address. */
+      if (lladdr_opt == NULL) {
+        /* Not a valid message. */
+        pbuf_free(p);
+        ND6_STATS_INC(nd6.proterr);
+        ND6_STATS_INC(nd6.drop);
+        return;
+      }
+
+      i = nd6_find_neighbor_cache_entry(ip6_current_src_addr());
+      if ( i>= 0) {
+        /* We already have a record for the solicitor. */
+        if (neighbor_cache[i].state == ND6_INCOMPLETE) {
+          neighbor_cache[i].netif = inp;
+          MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
+
+          /* Delay probe in case we get confirmation of reachability from upper layer (TCP). */
+          neighbor_cache[i].state = ND6_DELAY;
+          neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME;
+        }
+      }
+      else
+      {
+        /* Add their IPv6 address and link-layer address to neighbor cache.
+         * We will need it at least to send a unicast NA message, but most
+         * likely we will also be communicating with this node soon. */
+        i = nd6_new_neighbor_cache_entry();
+        if (i < 0) {
+          /* We couldn't assign a cache entry for this neighbor.
+           * we won't be able to reply. drop it. */
+          pbuf_free(p);
+          ND6_STATS_INC(nd6.memerr);
+          return;
+        }
+        neighbor_cache[i].netif = inp;
+        MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
+        ip6_addr_set(&(neighbor_cache[i].next_hop_address), ip6_current_src_addr());
+
+        /* Receiving a message does not prove reachability: only in one direction.
+         * Delay probe in case we get confirmation of reachability from upper layer (TCP). */
+        neighbor_cache[i].state = ND6_DELAY;
+        neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME;
+      }
+
+      /* Override ip6_current_dest_addr() so that we have an aligned copy. */
+      ip6_addr_set(ip6_current_dest_addr(), &(ns_hdr->target_address));
+
+      /* Send back a NA for us. Allocate the reply pbuf. */
+      nd6_send_na(inp, ip6_current_dest_addr(), ND6_FLAG_SOLICITED | ND6_FLAG_OVERRIDE);
+    }
+
+    break; /* ICMP6_TYPE_NS */
+  }
+  case ICMP6_TYPE_RA: /* Router Advertisement. */
+  {
+    struct ra_header * ra_hdr;
+    u8_t * buffer; /* Used to copy options. */
+    u16_t offset;
+
+    /* Check that RA header fits in packet. */
+    if (p->len < sizeof(struct ra_header)) {
+      /* TODO debug message */
+      pbuf_free(p);
+      ND6_STATS_INC(nd6.lenerr);
+      ND6_STATS_INC(nd6.drop);
+      return;
+    }
+
+    ra_hdr = (struct ra_header *)p->payload;
+
+    /* If we are sending RS messages, stop. */
+#if LWIP_IPV6_SEND_ROUTER_SOLICIT
+    inp->rs_count = 0;
+#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
+
+    /* Get the matching default router entry. */
+    i = nd6_get_router(ip6_current_src_addr(), inp);
+    if (i < 0) {
+      /* Create a new router entry. */
+      i = nd6_new_router(ip6_current_src_addr(), inp);
+    }
+
+    if (i < 0) {
+      /* Could not create a new router entry. */
+      pbuf_free(p);
+      ND6_STATS_INC(nd6.memerr);
+      return;
+    }
+
+    /* Re-set invalidation timer. */
+    default_router_list[i].invalidation_timer = ra_hdr->router_lifetime;
+
+    /* Re-set default timer values. */
+#if LWIP_ND6_ALLOW_RA_UPDATES
+    if (ra_hdr->retrans_timer > 0) {
+      retrans_timer = ra_hdr->retrans_timer;
+    }
+    if (ra_hdr->reachable_time > 0) {
+      reachable_time = ra_hdr->reachable_time;
+    }
+#endif /* LWIP_ND6_ALLOW_RA_UPDATES */
+
+    /* TODO set default hop limit... */
+    /* ra_hdr->current_hop_limit;*/
+
+    /* Update flags in local entry (incl. preference). */
+    default_router_list[i].flags = ra_hdr->flags;
+
+    /* Offset to options. */
+    offset = sizeof(struct ra_header);
+
+    /* Process each option. */
+    while ((p->tot_len - offset) > 0) {
+      if (p->len == p->tot_len) {
+        /* no need to copy from contiguous pbuf */
+        buffer = &((u8_t*)p->payload)[offset];
+      } else {
+        buffer = nd6_ra_buffer;
+        pbuf_copy_partial(p, buffer, sizeof(struct prefix_option), offset);
+      }
+      switch (buffer[0]) {
+      case ND6_OPTION_TYPE_SOURCE_LLADDR:
+      {
+        struct lladdr_option * lladdr_opt;
+        lladdr_opt = (struct lladdr_option *)buffer;
+        if ((default_router_list[i].neighbor_entry != NULL) &&
+            (default_router_list[i].neighbor_entry->state == ND6_INCOMPLETE)) {
+          SMEMCPY(default_router_list[i].neighbor_entry->lladdr, lladdr_opt->addr, inp->hwaddr_len);
+          default_router_list[i].neighbor_entry->state = ND6_REACHABLE;
+          default_router_list[i].neighbor_entry->counter.reachable_time = reachable_time;
+        }
+        break;
+      }
+      case ND6_OPTION_TYPE_MTU:
+      {
+        struct mtu_option * mtu_opt;
+        mtu_opt = (struct mtu_option *)buffer;
+        if (mtu_opt->mtu >= 1280) {
+#if LWIP_ND6_ALLOW_RA_UPDATES
+          inp->mtu = mtu_opt->mtu;
+#endif /* LWIP_ND6_ALLOW_RA_UPDATES */
+        }
+        break;
+      }
+      case ND6_OPTION_TYPE_PREFIX_INFO:
+      {
+        struct prefix_option * prefix_opt;
+        prefix_opt = (struct prefix_option *)buffer;
+
+        if (prefix_opt->flags & ND6_PREFIX_FLAG_ON_LINK) {
+          /* Add to on-link prefix list. */
+
+          /* Get a memory-aligned copy of the prefix. */
+          ip6_addr_set(ip6_current_dest_addr(), &(prefix_opt->prefix));
+
+          /* find cache entry for this prefix. */
+          i = nd6_get_onlink_prefix(ip6_current_dest_addr(), inp);
+          if (i < 0) {
+            /* Create a new cache entry. */
+            i = nd6_new_onlink_prefix(ip6_current_dest_addr(), inp);
+          }
+          if (i >= 0) {
+            prefix_list[i].invalidation_timer = prefix_opt->valid_lifetime;
+
+#if LWIP_IPV6_AUTOCONFIG
+            if (prefix_opt->flags & ND6_PREFIX_FLAG_AUTONOMOUS) {
+              /* Mark prefix as autonomous, so that address autoconfiguration can take place.
+               * Only OR flag, so that we don't over-write other flags (such as ADDRESS_DUPLICATE)*/
+              prefix_list[i].flags |= ND6_PREFIX_AUTOCONFIG_AUTONOMOUS;
+            }
+#endif /* LWIP_IPV6_AUTOCONFIG */
+          }
+        }
+
+        break;
+      }
+      case ND6_OPTION_TYPE_ROUTE_INFO:
+      {
+        /* TODO implement preferred routes.
+        struct route_option * route_opt;
+        route_opt = (struct route_option *)buffer;*/
+
+        break;
+      }
+      default:
+        /* Unrecognized option, abort. */
+        ND6_STATS_INC(nd6.proterr);
+        break;
+      }
+      offset += 8 * ((u16_t)buffer[1]);
+    }
+
+    break; /* ICMP6_TYPE_RA */
+  }
+  case ICMP6_TYPE_RD: /* Redirect */
+  {
+    struct redirect_header * redir_hdr;
+    struct lladdr_option * lladdr_opt;
+
+    /* Check that Redir header fits in packet. */
+    if (p->len < sizeof(struct redirect_header)) {
+      /* TODO debug message */
+      pbuf_free(p);
+      ND6_STATS_INC(nd6.lenerr);
+      ND6_STATS_INC(nd6.drop);
+      return;
+    }
+
+    redir_hdr = (struct redirect_header *)p->payload;
+
+    lladdr_opt = NULL;
+    if (p->len >= (sizeof(struct redirect_header) + sizeof(struct lladdr_option))) {
+      lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct redirect_header));
+    }
+
+    /* Copy original destination address to current source address, to have an aligned copy. */
+    ip6_addr_set(ip6_current_src_addr(), &(redir_hdr->destination_address));
+
+    /* Find dest address in cache */
+    i = nd6_find_destination_cache_entry(ip6_current_src_addr());
+    if (i < 0) {
+      /* Destination not in cache, drop packet. */
+      pbuf_free(p);
+      return;
+    }
+
+    /* Set the new target address. */
+    ip6_addr_set(&(destination_cache[i].next_hop_addr), &(redir_hdr->target_address));
+
+    /* If Link-layer address of other router is given, try to add to neighbor cache. */
+    if (lladdr_opt != NULL) {
+      if (lladdr_opt->type == ND6_OPTION_TYPE_TARGET_LLADDR) {
+        /* Copy target address to current source address, to have an aligned copy. */
+        ip6_addr_set(ip6_current_src_addr(), &(redir_hdr->target_address));
+
+        i = nd6_find_neighbor_cache_entry(ip6_current_src_addr());
+        if (i < 0) {
+          i = nd6_new_neighbor_cache_entry();
+          if (i >= 0) {
+            neighbor_cache[i].netif = inp;
+            MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
+            ip6_addr_set(&(neighbor_cache[i].next_hop_address), ip6_current_src_addr());
+
+            /* Receiving a message does not prove reachability: only in one direction.
+             * Delay probe in case we get confirmation of reachability from upper layer (TCP). */
+            neighbor_cache[i].state = ND6_DELAY;
+            neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME;
+          }
+        }
+        if (i >= 0) {
+          if (neighbor_cache[i].state == ND6_INCOMPLETE) {
+            MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
+            /* Receiving a message does not prove reachability: only in one direction.
+             * Delay probe in case we get confirmation of reachability from upper layer (TCP). */
+            neighbor_cache[i].state = ND6_DELAY;
+            neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME;
+          }
+        }
+      }
+    }
+    break; /* ICMP6_TYPE_RD */
+  }
+  case ICMP6_TYPE_PTB: /* Packet too big */
+  {
+    struct icmp6_hdr *icmp6hdr; /* Packet too big message */
+    struct ip6_hdr * ip6hdr; /* IPv6 header of the packet which caused the error */
+
+    /* Check that ICMPv6 header + IPv6 header fit in payload */
+    if (p->len < (sizeof(struct icmp6_hdr) + IP6_HLEN)) {
+      /* drop short packets */
+      pbuf_free(p);
+      ND6_STATS_INC(nd6.lenerr);
+      ND6_STATS_INC(nd6.drop);
+      return;
+    }
+
+    icmp6hdr = (struct icmp6_hdr *)p->payload;
+    ip6hdr = (struct ip6_hdr *)((u8_t*)p->payload + sizeof(struct icmp6_hdr));
+
+    /* Copy original destination address to current source address, to have an aligned copy. */
+    ip6_addr_set(ip6_current_src_addr(), &(ip6hdr->dest));
+
+    /* Look for entry in destination cache. */
+    i = nd6_find_destination_cache_entry(ip6_current_src_addr());
+    if (i < 0) {
+      /* Destination not in cache, drop packet. */
+      pbuf_free(p);
+      return;
+    }
+
+    /* Change the Path MTU. */
+    destination_cache[i].pmtu = icmp6hdr->data;
+
+    break; /* ICMP6_TYPE_PTB */
+  }
+
+  default:
+    ND6_STATS_INC(nd6.proterr);
+    ND6_STATS_INC(nd6.drop);
+    break; /* default */
+  }
+
+  pbuf_free(p);
+}
+
+
+/**
+ * Periodic timer for Neighbor discovery functions:
+ *
+ * - Update neighbor reachability states
+ * - Update destination cache entries age
+ * - Update invalidation timers of default routers and on-link prefixes
+ * - Perform duplicate address detection (DAD) for our addresses
+ * - Send router solicitations
+ */
+void
+nd6_tmr(void)
+{
+  s8_t i, j;
+  struct netif * netif;
+
+  /* Process neighbor entries. */
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    switch (neighbor_cache[i].state) {
+    case ND6_INCOMPLETE:
+      if (neighbor_cache[i].counter.probes_sent >= LWIP_ND6_MAX_MULTICAST_SOLICIT) {
+        /* Retries exceeded. */
+        nd6_free_neighbor_cache_entry(i);
+      }
+      else {
+        /* Send a NS for this entry. */
+        neighbor_cache[i].counter.probes_sent++;
+        nd6_send_ns(neighbor_cache[i].netif, &(neighbor_cache[i].next_hop_address), ND6_SEND_FLAG_MULTICAST_DEST);
+      }
+      break;
+    case ND6_REACHABLE:
+      /* Send queued packets, if any are left. Should have been sent already. */
+      if (neighbor_cache[i].q != NULL) {
+        nd6_send_q(i);
+      }
+      if (neighbor_cache[i].counter.reachable_time <= ND6_TMR_INTERVAL) {
+        /* Change to stale state. */
+        neighbor_cache[i].state = ND6_STALE;
+        neighbor_cache[i].counter.stale_time = 0;
+      }
+      else {
+        neighbor_cache[i].counter.reachable_time -= ND6_TMR_INTERVAL;
+      }
+      break;
+    case ND6_STALE:
+      neighbor_cache[i].counter.stale_time += ND6_TMR_INTERVAL;
+      break;
+    case ND6_DELAY:
+      if (neighbor_cache[i].counter.delay_time <= ND6_TMR_INTERVAL) {
+        /* Change to PROBE state. */
+        neighbor_cache[i].state = ND6_PROBE;
+        neighbor_cache[i].counter.probes_sent = 0;
+      }
+      else {
+        neighbor_cache[i].counter.delay_time -= ND6_TMR_INTERVAL;
+      }
+      break;
+    case ND6_PROBE:
+      if (neighbor_cache[i].counter.probes_sent >= LWIP_ND6_MAX_MULTICAST_SOLICIT) {
+        /* Retries exceeded. */
+        nd6_free_neighbor_cache_entry(i);
+      }
+      else {
+        /* Send a NS for this entry. */
+        neighbor_cache[i].counter.probes_sent++;
+        nd6_send_ns(neighbor_cache[i].netif, &(neighbor_cache[i].next_hop_address), 0);
+      }
+      break;
+    case ND6_NO_ENTRY:
+    default:
+      /* Do nothing. */
+      break;
+    }
+  }
+
+  /* Process destination entries. */
+  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
+    destination_cache[i].age++;
+  }
+
+  /* Process router entries. */
+  for (i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
+    if (default_router_list[i].neighbor_entry != NULL) {
+      /* Active entry. */
+      if (default_router_list[i].invalidation_timer > 0) {
+        default_router_list[i].invalidation_timer -= ND6_TMR_INTERVAL / 1000;
+      }
+      if (default_router_list[i].invalidation_timer < ND6_TMR_INTERVAL / 1000) {
+        /* Less than 1 second remainig. Clear this entry. */
+        default_router_list[i].neighbor_entry->isrouter = 0;
+        default_router_list[i].neighbor_entry = NULL;
+        default_router_list[i].invalidation_timer = 0;
+        default_router_list[i].flags = 0;
+      }
+    }
+  }
+
+  /* Process prefix entries. */
+  for (i = 0; i < LWIP_ND6_NUM_PREFIXES; i++) {
+    if (prefix_list[i].invalidation_timer < ND6_TMR_INTERVAL / 1000) {
+      prefix_list[i].invalidation_timer = 0;
+    }
+    if ((prefix_list[i].invalidation_timer > 0) &&
+        (prefix_list[i].netif != NULL)) {
+      prefix_list[i].invalidation_timer -= ND6_TMR_INTERVAL / 1000;
+
+#if LWIP_IPV6_AUTOCONFIG
+      /* Initiate address autoconfiguration for this prefix, if conditions are met. */
+      if (prefix_list[i].netif->ip6_autoconfig_enabled &&
+          (prefix_list[i].flags & ND6_PREFIX_AUTOCONFIG_AUTONOMOUS) &&
+          !(prefix_list[i].flags & ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED)) {
+        /* Try to get an address on this netif that is invalid.
+         * Skip 0 index (link-local address) */
+        for (j = 1; j < LWIP_IPV6_NUM_ADDRESSES; j++) {
+          if (netif_ip6_addr_state(prefix_list[i].netif, j) == IP6_ADDRESS_STATE_INVALID) {
+            /* Generate an address using this prefix and interface ID from link-local address. */
+            prefix_list[i].netif->ip6_addr[j].addr[0] = prefix_list[i].prefix.addr[0];
+            prefix_list[i].netif->ip6_addr[j].addr[1] = prefix_list[i].prefix.addr[1];
+            prefix_list[i].netif->ip6_addr[j].addr[2] = prefix_list[i].netif->ip6_addr[0].addr[2];
+            prefix_list[i].netif->ip6_addr[j].addr[3] = prefix_list[i].netif->ip6_addr[0].addr[3];
+
+            /* Mark it as tentative (DAD will be performed if configured). */
+            netif_ip6_addr_set_state(prefix_list[i].netif, j, IP6_ADDR_TENTATIVE);
+
+            /* Mark this prefix with ADDRESS_GENERATED, so that we don't try again. */
+            prefix_list[i].flags |= ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED;
+
+            /* Exit loop. */
+            break;
+          }
+        }
+      }
+#endif /* LWIP_IPV6_AUTOCONFIG */
+    }
+  }
+
+
+  /* Process our own addresses, if DAD configured. */
+  for (netif = netif_list; netif != NULL; netif = netif->next) {
+    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
+      if (ip6_addr_istentative(netif->ip6_addr_state[i])) {
+        if ((netif->ip6_addr_state[i] & 0x07) >= LWIP_IPV6_DUP_DETECT_ATTEMPTS) {
+          /* No NA received in response. Mark address as valid. */
+          netif->ip6_addr_state[i] = IP6_ADDR_PREFERRED;
+          /* TODO implement preferred and valid lifetimes. */
+        }
+        else if (netif->flags & NETIF_FLAG_UP) {
+#if LWIP_IPV6_MLD
+          if ((netif->ip6_addr_state[i] & 0x07) == 0) {
+            /* Join solicited node multicast group. */
+            ip6_addr_set_solicitednode(&multicast_address, netif_ip6_addr(netif, i)->addr[3]);
+            mld6_joingroup(netif_ip6_addr(netif, i), &multicast_address);
+          }
+#endif /* LWIP_IPV6_MLD */
+          /* Send a NS for this address. */
+          nd6_send_ns(netif, netif_ip6_addr(netif, i), ND6_SEND_FLAG_MULTICAST_DEST);
+          (netif->ip6_addr_state[i])++;
+          /* TODO send max 1 NS per tmr call? enable return*/
+          /*return;*/
+        }
+      }
+    }
+  }
+
+#if LWIP_IPV6_SEND_ROUTER_SOLICIT
+  /* Send router solicitation messages, if necessary. */
+  for (netif = netif_list; netif != NULL; netif = netif->next) {
+    if ((netif->rs_count > 0) && (netif->flags & NETIF_FLAG_UP)) {
+      nd6_send_rs(netif);
+      netif->rs_count--;
+    }
+  }
+#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
+
+}
+
+/**
+ * Send a neighbor solicitation message
+ *
+ * @param netif the netif on which to send the message
+ * @param target_addr the IPv6 target address for the ND message
+ * @param flags one of ND6_SEND_FLAG_*
+ */
+static void
+nd6_send_ns(struct netif * netif, ip6_addr_t * target_addr, u8_t flags)
+{
+  struct ns_header * ns_hdr;
+  struct lladdr_option * lladdr_opt;
+  struct pbuf * p;
+  ip6_addr_t * src_addr;
+
+  if (ip6_addr_isvalid(netif_ip6_addr_state(netif,0))) {
+    /* Use link-local address as source address. */
+    src_addr = netif_ip6_addr(netif, 0);
+  } else {
+    src_addr = IP6_ADDR_ANY;
+  }
+
+  /* Allocate a packet. */
+  p = pbuf_alloc(PBUF_IP, sizeof(struct ns_header) + sizeof(struct lladdr_option), PBUF_RAM);
+  if ((p == NULL) || (p->len < (sizeof(struct ns_header) + sizeof(struct lladdr_option)))) {
+    /* We couldn't allocate a suitable pbuf for the ns. drop it. */
+    if (p != NULL) {
+      pbuf_free(p);
+    }
+    ND6_STATS_INC(nd6.memerr);
+    return;
+  }
+
+  /* Set fields. */
+  ns_hdr = (struct ns_header *)p->payload;
+  lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct ns_header));
+
+  ns_hdr->type = ICMP6_TYPE_NS;
+  ns_hdr->code = 0;
+  ns_hdr->chksum = 0;
+  ns_hdr->reserved = 0;
+  ip6_addr_set(&(ns_hdr->target_address), target_addr);
+
+  lladdr_opt->type = ND6_OPTION_TYPE_SOURCE_LLADDR;
+  lladdr_opt->length = ((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0);
+  SMEMCPY(lladdr_opt->addr, netif->hwaddr, netif->hwaddr_len);
+
+  /* Generate the solicited node address for the target address. */
+  if (flags & ND6_SEND_FLAG_MULTICAST_DEST) {
+    ip6_addr_set_solicitednode(&multicast_address, target_addr->addr[3]);
+    target_addr = &multicast_address;
+  }
+
+  ns_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len, src_addr,
+    target_addr);
+
+  /* Send the packet out. */
+  ND6_STATS_INC(nd6.xmit);
+  ip6_output_if(p, (src_addr == IP6_ADDR_ANY) ? NULL : src_addr, target_addr,
+      LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, netif);
+  pbuf_free(p);
+}
+
+/**
+ * Send a neighbor advertisement message
+ *
+ * @param netif the netif on which to send the message
+ * @param target_addr the IPv6 target address for the ND message
+ * @param flags one of ND6_SEND_FLAG_*
+ */
+static void
+nd6_send_na(struct netif * netif, ip6_addr_t * target_addr, u8_t flags)
+{
+  struct na_header * na_hdr;
+  struct lladdr_option * lladdr_opt;
+  struct pbuf * p;
+  ip6_addr_t * src_addr;
+  ip6_addr_t * dest_addr;
+
+  /* Use link-local address as source address. */
+  /* src_addr = &(netif->ip6_addr[0]); */
+  /* Use target address as source address. */
+  src_addr = target_addr;
+
+  /* Allocate a packet. */
+  p = pbuf_alloc(PBUF_IP, sizeof(struct na_header) + sizeof(struct lladdr_option), PBUF_RAM);
+  if ((p == NULL) || (p->len < (sizeof(struct na_header) + sizeof(struct lladdr_option)))) {
+    /* We couldn't allocate a suitable pbuf for the ns. drop it. */
+    if (p != NULL) {
+      pbuf_free(p);
+    }
+    ND6_STATS_INC(nd6.memerr);
+    return;
+  }
+
+  /* Set fields. */
+  na_hdr = (struct na_header *)p->payload;
+  lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct na_header));
+
+  na_hdr->type = ICMP6_TYPE_NA;
+  na_hdr->code = 0;
+  na_hdr->chksum = 0;
+  na_hdr->flags = flags & 0xf0;
+  na_hdr->reserved[0] = 0;
+  na_hdr->reserved[1] = 0;
+  na_hdr->reserved[2] = 0;
+  ip6_addr_set(&(na_hdr->target_address), target_addr);
+
+  lladdr_opt->type = ND6_OPTION_TYPE_TARGET_LLADDR;
+  lladdr_opt->length = ((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0);
+  SMEMCPY(lladdr_opt->addr, netif->hwaddr, netif->hwaddr_len);
+
+  /* Generate the solicited node address for the target address. */
+  if (flags & ND6_SEND_FLAG_MULTICAST_DEST) {
+    ip6_addr_set_solicitednode(&multicast_address, target_addr->addr[3]);
+    dest_addr = &multicast_address;
+  }
+  else if (flags & ND6_SEND_FLAG_ALLNODES_DEST) {
+    ip6_addr_set_allnodes_linklocal(&multicast_address);
+    dest_addr = &multicast_address;
+  }
+  else {
+    dest_addr = ip6_current_src_addr();
+  }
+
+  na_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len, src_addr,
+    dest_addr);
+
+  /* Send the packet out. */
+  ND6_STATS_INC(nd6.xmit);
+  ip6_output_if(p, src_addr, dest_addr,
+      LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, netif);
+  pbuf_free(p);
+}
+
+#if LWIP_IPV6_SEND_ROUTER_SOLICIT
+/**
+ * Send a router solicitation message
+ *
+ * @param netif the netif on which to send the message
+ */
+static void
+nd6_send_rs(struct netif * netif)
+{
+  struct rs_header * rs_hdr;
+  struct lladdr_option * lladdr_opt;
+  struct pbuf * p;
+  ip6_addr_t * src_addr;
+  u16_t packet_len;
+
+  /* Link-local source address, or unspecified address? */
+  if (ip6_addr_isvalid(netif_ip6_addr_state(netif, 0))) {
+    src_addr = netif_ip6_addr(netif, 0);
+  }
+  else {
+    src_addr = IP6_ADDR_ANY;
+  }
+
+  /* Generate the all routers target address. */
+  ip6_addr_set_allrouters_linklocal(&multicast_address);
+
+  /* Allocate a packet. */
+  packet_len = sizeof(struct rs_header);
+  if (src_addr != IP6_ADDR_ANY) {
+    packet_len += sizeof(struct lladdr_option);
+  }
+  p = pbuf_alloc(PBUF_IP, packet_len, PBUF_RAM);
+  if ((p == NULL) || (p->len < packet_len)) {
+    /* We couldn't allocate a suitable pbuf for the ns. drop it. */
+    if (p != NULL) {
+      pbuf_free(p);
+    }
+    ND6_STATS_INC(nd6.memerr);
+    return;
+  }
+
+  /* Set fields. */
+  rs_hdr = (struct rs_header *)p->payload;
+
+  rs_hdr->type = ICMP6_TYPE_RS;
+  rs_hdr->code = 0;
+  rs_hdr->chksum = 0;
+  rs_hdr->reserved = 0;
+
+  if (src_addr != IP6_ADDR_ANY) {
+    /* Include our hw address. */
+    lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct rs_header));
+    lladdr_opt->type = ND6_OPTION_TYPE_SOURCE_LLADDR;
+    lladdr_opt->length = ((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0);
+    SMEMCPY(lladdr_opt->addr, netif->hwaddr, netif->hwaddr_len);
+  }
+
+  rs_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len, src_addr,
+    &multicast_address);
+
+  /* Send the packet out. */
+  ND6_STATS_INC(nd6.xmit);
+  ip6_output_if(p, src_addr, &multicast_address,
+      LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, netif);
+  pbuf_free(p);
+}
+#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
+
+/**
+ * Search for a neighbor cache entry
+ *
+ * @param ip6addr the IPv6 address of the neighbor
+ * @return The neighbor cache entry index that matched, -1 if no
+ * entry is found
+ */
+static s8_t
+nd6_find_neighbor_cache_entry(ip6_addr_t * ip6addr)
+{
+  s8_t i;
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    if (ip6_addr_cmp(ip6addr, &(neighbor_cache[i].next_hop_address))) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+/**
+ * Create a new neighbor cache entry.
+ *
+ * If no unused entry is found, will try to recycle an old entry
+ * according to ad-hoc "age" heuristic.
+ *
+ * @return The neighbor cache entry index that was created, -1 if no
+ * entry could be created
+ */
+static s8_t
+nd6_new_neighbor_cache_entry(void)
+{
+  s8_t i;
+  s8_t j;
+  u32_t time;
+
+
+  /* First, try to find an empty entry. */
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    if (neighbor_cache[i].state == ND6_NO_ENTRY) {
+      return i;
+    }
+  }
+
+  /* We need to recycle an entry. in general, do not recycle if it is a router. */
+
+  /* Next, try to find a Stale entry. */
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    if ((neighbor_cache[i].state == ND6_STALE) &&
+        (!neighbor_cache[i].isrouter)) {
+      nd6_free_neighbor_cache_entry(i);
+      return i;
+    }
+  }
+
+  /* Next, try to find a Probe entry. */
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    if ((neighbor_cache[i].state == ND6_PROBE) &&
+        (!neighbor_cache[i].isrouter)) {
+      nd6_free_neighbor_cache_entry(i);
+      return i;
+    }
+  }
+
+  /* Next, try to find a Delayed entry. */
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    if ((neighbor_cache[i].state == ND6_DELAY) &&
+        (!neighbor_cache[i].isrouter)) {
+      nd6_free_neighbor_cache_entry(i);
+      return i;
+    }
+  }
+
+  /* Next, try to find the oldest reachable entry. */
+  time = 0xfffffffful;
+  j = -1;
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    if ((neighbor_cache[i].state == ND6_REACHABLE) &&
+        (!neighbor_cache[i].isrouter)) {
+      if (neighbor_cache[i].counter.reachable_time < time) {
+        j = i;
+        time = neighbor_cache[i].counter.reachable_time;
+      }
+    }
+  }
+  if (j >= 0) {
+    nd6_free_neighbor_cache_entry(j);
+    return j;
+  }
+
+  /* Next, find oldest incomplete entry without queued packets. */
+  time = 0;
+  j = -1;
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    if (
+        (neighbor_cache[i].q == NULL) &&
+        (neighbor_cache[i].state == ND6_INCOMPLETE) &&
+        (!neighbor_cache[i].isrouter)) {
+      if (neighbor_cache[i].counter.probes_sent >= time) {
+        j = i;
+        time = neighbor_cache[i].counter.probes_sent;
+      }
+    }
+  }
+  if (j >= 0) {
+    nd6_free_neighbor_cache_entry(j);
+    return j;
+  }
+
+  /* Next, find oldest incomplete entry with queued packets. */
+  time = 0;
+  j = -1;
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    if ((neighbor_cache[i].state == ND6_INCOMPLETE) &&
+        (!neighbor_cache[i].isrouter)) {
+      if (neighbor_cache[i].counter.probes_sent >= time) {
+        j = i;
+        time = neighbor_cache[i].counter.probes_sent;
+      }
+    }
+  }
+  if (j >= 0) {
+    nd6_free_neighbor_cache_entry(j);
+    return j;
+  }
+
+  /* No more entries to try. */
+  return -1;
+}
+
+/**
+ * Will free any resources associated with a neighbor cache
+ * entry, and will mark it as unused.
+ *
+ * @param i the neighbor cache entry index to free
+ */
+static void
+nd6_free_neighbor_cache_entry(s8_t i)
+{
+  if ((i < 0) || (i >= LWIP_ND6_NUM_NEIGHBORS)) {
+    return;
+  }
+
+  /* Free any queued packets. */
+  if (neighbor_cache[i].q != NULL) {
+    nd6_free_q(neighbor_cache[i].q);
+    neighbor_cache[i].q = NULL;
+  }
+
+  neighbor_cache[i].state = ND6_NO_ENTRY;
+  neighbor_cache[i].isrouter = 0;
+  neighbor_cache[i].netif = NULL;
+  neighbor_cache[i].counter.reachable_time = 0;
+  ip6_addr_set_zero(&(neighbor_cache[i].next_hop_address));
+}
+
+/**
+ * Search for a destination cache entry
+ *
+ * @param ip6addr the IPv6 address of the destination
+ * @return The destination cache entry index that matched, -1 if no
+ * entry is found
+ */
+static s8_t
+nd6_find_destination_cache_entry(ip6_addr_t * ip6addr)
+{
+  s8_t i;
+  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
+    if (ip6_addr_cmp(ip6addr, &(destination_cache[i].destination_addr))) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+/**
+ * Create a new destination cache entry. If no unused entry is found,
+ * will recycle oldest entry.
+ *
+ * @return The destination cache entry index that was created, -1 if no
+ * entry was created
+ */
+static s8_t
+nd6_new_destination_cache_entry(void)
+{
+  s8_t i, j;
+  u32_t age;
+
+  /* Find an empty entry. */
+  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
+    if (ip6_addr_isany(&(destination_cache[i].destination_addr))) {
+      return i;
+    }
+  }
+
+  /* Find oldest entry. */
+  age = 0;
+  j = LWIP_ND6_NUM_DESTINATIONS - 1;
+  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
+    if (destination_cache[i].age > age) {
+      j = i;
+    }
+  }
+
+  return j;
+}
+
+/**
+ * Determine whether an address matches an on-link prefix.
+ *
+ * @param ip6addr the IPv6 address to match
+ * @return 1 if the address is on-link, 0 otherwise
+ */
+static s8_t
+nd6_is_prefix_in_netif(ip6_addr_t * ip6addr, struct netif * netif)
+{
+  s8_t i;
+  for (i = 0; i < LWIP_ND6_NUM_PREFIXES; i++) {
+    if ((prefix_list[i].netif == netif) &&
+        (prefix_list[i].invalidation_timer > 0) &&
+        ip6_addr_netcmp(ip6addr, &(prefix_list[i].prefix))) {
+      return 1;
+    }
+  }
+  /* Check to see if address prefix matches a (manually?) configured address. */
+  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+    if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+        ip6_addr_netcmp(ip6addr, netif_ip6_addr(netif, i))) {
+      return 1;
+    }
+  }
+  return 0;
+}
+
+/**
+ * Select a default router for a destination.
+ *
+ * @param ip6addr the destination address
+ * @param netif the netif for the outgoing packet, if known
+ * @return the default router entry index, or -1 if no suitable
+ *         router is found
+ */
+s8_t
+nd6_select_router(ip6_addr_t * ip6addr, struct netif * netif)
+{
+  s8_t i;
+  /* last_router is used for round-robin router selection (as recommended
+   * in RFC). This is more robust in case one router is not reachable,
+   * we are not stuck trying to resolve it. */
+  static s8_t last_router;
+  (void)ip6addr; /* TODO match preferred routes!! (must implement ND6_OPTION_TYPE_ROUTE_INFO) */
+
+  /* TODO: implement default router preference */
+
+  /* Look for reachable routers. */
+  for (i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
+    if (++last_router >= LWIP_ND6_NUM_ROUTERS) {
+      last_router = 0;
+    }
+    if ((default_router_list[i].neighbor_entry != NULL) &&
+        (netif != NULL ? netif == default_router_list[i].neighbor_entry->netif : 1) &&
+        (default_router_list[i].invalidation_timer > 0) &&
+        (default_router_list[i].neighbor_entry->state == ND6_REACHABLE)) {
+      return i;
+    }
+  }
+
+  /* Look for router in other reachability states, but still valid according to timer. */
+  for (i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
+    if (++last_router >= LWIP_ND6_NUM_ROUTERS) {
+      last_router = 0;
+    }
+    if ((default_router_list[i].neighbor_entry != NULL) &&
+        (netif != NULL ? netif == default_router_list[i].neighbor_entry->netif : 1) &&
+        (default_router_list[i].invalidation_timer > 0)) {
+      return i;
+    }
+  }
+
+  /* Look for any router for which we have any information at all. */
+  for (i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
+    if (++last_router >= LWIP_ND6_NUM_ROUTERS) {
+      last_router = 0;
+    }
+    if (default_router_list[i].neighbor_entry != NULL &&
+        (netif != NULL ? netif == default_router_list[i].neighbor_entry->netif : 1)) {
+      return i;
+    }
+  }
+
+  /* no suitable router found. */
+  return -1;
+}
+
+/**
+ * Find an entry for a default router.
+ *
+ * @param router_addr the IPv6 address of the router
+ * @param netif the netif on which the router is found, if known
+ * @return the index of the router entry, or -1 if not found
+ */
+static s8_t
+nd6_get_router(ip6_addr_t * router_addr, struct netif * netif)
+{
+  s8_t i;
+
+  /* Look for router. */
+  for (i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
+    if ((default_router_list[i].neighbor_entry != NULL) &&
+        ((netif != NULL) ? netif == default_router_list[i].neighbor_entry->netif : 1) &&
+        ip6_addr_cmp(router_addr, &(default_router_list[i].neighbor_entry->next_hop_address))) {
+      return i;
+    }
+  }
+
+  /* router not found. */
+  return -1;
+}
+
+/**
+ * Create a new entry for a default router.
+ *
+ * @param router_addr the IPv6 address of the router
+ * @param netif the netif on which the router is connected, if known
+ * @return the index on the router table, or -1 if could not be created
+ */
+static s8_t
+nd6_new_router(ip6_addr_t * router_addr, struct netif * netif)
+{
+  s8_t router_index;
+  s8_t neighbor_index;
+
+  /* Do we have a neighbor entry for this router? */
+  neighbor_index = nd6_find_neighbor_cache_entry(router_addr);
+  if (neighbor_index < 0) {
+    /* Create a neighbor entry for this router. */
+    neighbor_index = nd6_new_neighbor_cache_entry();
+    if (neighbor_index < 0) {
+      /* Could not create neighbor entry for this router. */
+      return -1;
+    }
+    ip6_addr_set(&(neighbor_cache[neighbor_index].next_hop_address), router_addr);
+    neighbor_cache[neighbor_index].netif = netif;
+    neighbor_cache[neighbor_index].q = NULL;
+    neighbor_cache[neighbor_index].state = ND6_INCOMPLETE;
+    neighbor_cache[neighbor_index].counter.probes_sent = 0;
+  }
+
+  /* Mark neighbor as router. */
+  neighbor_cache[neighbor_index].isrouter = 1;
+
+  /* Look for empty entry. */
+  for (router_index = 0; router_index < LWIP_ND6_NUM_ROUTERS; router_index++) {
+    if (default_router_list[router_index].neighbor_entry == NULL) {
+      default_router_list[router_index].neighbor_entry = &(neighbor_cache[neighbor_index]);
+      return router_index;
+    }
+  }
+
+  /* Could not create a router entry. */
+
+  /* Mark neighbor entry as not-router. Entry might be useful as neighbor still. */
+  neighbor_cache[neighbor_index].isrouter = 0;
+
+  /* router not found. */
+  return -1;
+}
+
+/**
+ * Find the cached entry for an on-link prefix.
+ *
+ * @param prefix the IPv6 prefix that is on-link
+ * @param netif the netif on which the prefix is on-link
+ * @return the index on the prefix table, or -1 if not found
+ */
+static s8_t
+nd6_get_onlink_prefix(ip6_addr_t * prefix, struct netif * netif)
+{
+  s8_t i;
+
+  /* Look for prefix in list. */
+  for (i = 0; i < LWIP_ND6_NUM_PREFIXES; ++i) {
+    if ((ip6_addr_netcmp(&(prefix_list[i].prefix), prefix)) &&
+        (prefix_list[i].netif == netif)) {
+      return i;
+    }
+  }
+
+  /* Entry not available. */
+  return -1;
+}
+
+/**
+ * Creates a new entry for an on-link prefix.
+ *
+ * @param prefix the IPv6 prefix that is on-link
+ * @param netif the netif on which the prefix is on-link
+ * @return the index on the prefix table, or -1 if not created
+ */
+static s8_t
+nd6_new_onlink_prefix(ip6_addr_t * prefix, struct netif * netif)
+{
+  s8_t i;
+
+  /* Create new entry. */
+  for (i = 0; i < LWIP_ND6_NUM_PREFIXES; ++i) {
+    if ((prefix_list[i].netif == NULL) ||
+        (prefix_list[i].invalidation_timer == 0)) {
+      /* Found empty prefix entry. */
+      prefix_list[i].netif = netif;
+      ip6_addr_set(&(prefix_list[i].prefix), prefix);
+#if LWIP_IPV6_AUTOCONFIG
+      prefix_list[i].flags = 0;
+#endif
+      return i;
+    }
+  }
+
+  /* Entry not available. */
+  return -1;
+}
+
+/**
+ * Determine the next hop for a destination. Will determine if the
+ * destination is on-link, else a suitable on-link router is selected.
+ *
+ * The last entry index is cached for fast entry search.
+ *
+ * @param ip6addr the destination address
+ * @param netif the netif on which the packet will be sent
+ * @return the neighbor cache entry for the next hop, ERR_RTE if no
+ *         suitable next hop was found, ERR_MEM if no cache entry
+ *         could be created
+ */
+s8_t
+nd6_get_next_hop_entry(ip6_addr_t * ip6addr, struct netif * netif)
+{
+  s8_t i;
+
+#if LWIP_NETIF_HWADDRHINT
+  if (netif->addr_hint != NULL) {
+    /* per-pcb cached entry was given */
+    u8_t addr_hint = *(netif->addr_hint);
+    if (addr_hint < LWIP_ND6_NUM_DESTINATIONS) {
+      nd6_cached_destination_index = addr_hint;
+    }
+  }
+#endif /* LWIP_NETIF_HWADDRHINT */
+
+  /* Look for ip6addr in destination cache. */
+  if (ip6_addr_cmp(ip6addr, &(destination_cache[nd6_cached_destination_index].destination_addr))) {
+    /* the cached entry index is the right one! */
+    /* do nothing. */
+    ND6_STATS_INC(nd6.cachehit);
+  } else {
+    /* Search destination cache. */
+    i = nd6_find_destination_cache_entry(ip6addr);
+    if (i >= 0) {
+      /* found destination entry. make it our new cached index. */
+      nd6_cached_destination_index = i;
+    }
+    else {
+      /* Not found. Create a new destination entry. */
+      i = nd6_new_destination_cache_entry();
+      if (i >= 0) {
+        /* got new destination entry. make it our new cached index. */
+        nd6_cached_destination_index = i;
+      } else {
+        /* Could not create a destination cache entry. */
+        return ERR_MEM;
+      }
+
+      /* Copy dest address to destination cache. */
+      ip6_addr_set(&(destination_cache[nd6_cached_destination_index].destination_addr), ip6addr);
+
+      /* Now find the next hop. is it a neighbor? */
+      if (ip6_addr_islinklocal(ip6addr) ||
+          nd6_is_prefix_in_netif(ip6addr, netif)) {
+        /* Destination in local link. */
+        destination_cache[nd6_cached_destination_index].pmtu = netif->mtu;
+        ip6_addr_copy(destination_cache[nd6_cached_destination_index].next_hop_addr, destination_cache[nd6_cached_destination_index].destination_addr);
+      }
+      else {
+        /* We need to select a router. */
+        i = nd6_select_router(ip6addr, netif);
+        if (i < 0) {
+          /* No router found. */
+          ip6_addr_set_any(&(destination_cache[nd6_cached_destination_index].destination_addr));
+          return ERR_RTE;
+        }
+        destination_cache[nd6_cached_destination_index].pmtu = netif->mtu; /* Start with netif mtu, correct through ICMPv6 if necessary */
+        ip6_addr_copy(destination_cache[nd6_cached_destination_index].next_hop_addr, default_router_list[i].neighbor_entry->next_hop_address);
+      }
+    }
+  }
+
+#if LWIP_NETIF_HWADDRHINT
+  if (netif->addr_hint != NULL) {
+    /* per-pcb cached entry was given */
+    *(netif->addr_hint) = nd6_cached_destination_index;
+  }
+#endif /* LWIP_NETIF_HWADDRHINT */
+
+  /* Look in neighbor cache for the next-hop address. */
+  if (ip6_addr_cmp(&(destination_cache[nd6_cached_destination_index].next_hop_addr),
+                   &(neighbor_cache[nd6_cached_neighbor_index].next_hop_address))) {
+    /* Cache hit. */
+    /* Do nothing. */
+    ND6_STATS_INC(nd6.cachehit);
+  } else {
+    i = nd6_find_neighbor_cache_entry(&(destination_cache[nd6_cached_destination_index].next_hop_addr));
+    if (i >= 0) {
+      /* Found a matching record, make it new cached entry. */
+      nd6_cached_neighbor_index = i;
+    }
+    else {
+      /* Neighbor not in cache. Make a new entry. */
+      i = nd6_new_neighbor_cache_entry();
+      if (i >= 0) {
+        /* got new neighbor entry. make it our new cached index. */
+        nd6_cached_neighbor_index = i;
+      } else {
+        /* Could not create a neighbor cache entry. */
+        return ERR_MEM;
+      }
+
+      /* Initialize fields. */
+      ip6_addr_copy(neighbor_cache[i].next_hop_address,
+                   destination_cache[nd6_cached_destination_index].next_hop_addr);
+      neighbor_cache[i].isrouter = 0;
+      neighbor_cache[i].netif = netif;
+      neighbor_cache[i].state = ND6_INCOMPLETE;
+      neighbor_cache[i].counter.probes_sent = 0;
+    }
+  }
+
+  /* Reset this destination's age. */
+  destination_cache[nd6_cached_destination_index].age = 0;
+
+  return nd6_cached_neighbor_index;
+}
+
+/**
+ * Queue a packet for a neighbor.
+ *
+ * @param neighbor_index the index in the neighbor cache table
+ * @param q packet to be queued
+ * @return ERR_OK if succeeded, ERR_MEM if out of memory
+ */
+err_t
+nd6_queue_packet(s8_t neighbor_index, struct pbuf * q)
+{
+  err_t result = ERR_MEM;
+  struct pbuf *p;
+  int copy_needed = 0;
+#if LWIP_ND6_QUEUEING
+  struct nd6_q_entry *new_entry, *r;
+#endif /* LWIP_ND6_QUEUEING */
+
+  if ((neighbor_index < 0) || (neighbor_index >= LWIP_ND6_NUM_NEIGHBORS)) {
+    return ERR_ARG;
+  }
+
+  /* IF q includes a PBUF_REF, PBUF_POOL or PBUF_RAM, we have no choice but
+   * to copy the whole queue into a new PBUF_RAM (see bug #11400)
+   * PBUF_ROMs can be left as they are, since ROM must not get changed. */
+  p = q;
+  while (p) {
+    if(p->type != PBUF_ROM) {
+      copy_needed = 1;
+      break;
+    }
+    p = p->next;
+  }
+  if(copy_needed) {
+    /* copy the whole packet into new pbufs */
+    p = pbuf_alloc(PBUF_LINK, q->tot_len, PBUF_RAM);
+    while ((p == NULL) && (neighbor_cache[neighbor_index].q != NULL)) {
+      /* Free oldest packet (as per RFC recommendation) */
+#if LWIP_ND6_QUEUEING
+      r = neighbor_cache[neighbor_index].q;
+      neighbor_cache[neighbor_index].q = r->next;
+      r->next = NULL;
+      nd6_free_q(r);
+#else /* LWIP_ND6_QUEUEING */
+      pbuf_free(neighbor_cache[neighbor_index].q);
+      neighbor_cache[neighbor_index].q = NULL;
+#endif /* LWIP_ND6_QUEUEING */
+      p = pbuf_alloc(PBUF_LINK, q->tot_len, PBUF_RAM);
+    }
+    if(p != NULL) {
+      if (pbuf_copy(p, q) != ERR_OK) {
+        pbuf_free(p);
+        p = NULL;
+      }
+    }
+  } else {
+    /* referencing the old pbuf is enough */
+    p = q;
+    pbuf_ref(p);
+  }
+  /* packet was copied/ref'd? */
+  if (p != NULL) {
+    /* queue packet ... */
+#if LWIP_ND6_QUEUEING
+    /* allocate a new nd6 queue entry */
+    new_entry = (struct nd6_q_entry *)memp_malloc(MEMP_ND6_QUEUE);
+    if ((new_entry == NULL) && (neighbor_cache[neighbor_index].q != NULL)) {
+      /* Free oldest packet (as per RFC recommendation) */
+      r = neighbor_cache[neighbor_index].q;
+      neighbor_cache[neighbor_index].q = r->next;
+      r->next = NULL;
+      nd6_free_q(r);
+      new_entry = (struct nd6_q_entry *)memp_malloc(MEMP_ND6_QUEUE);
+    }
+    if (new_entry != NULL) {
+      new_entry->next = NULL;
+      new_entry->p = p;
+      if(neighbor_cache[neighbor_index].q != NULL) {
+        /* queue was already existent, append the new entry to the end */
+        r = neighbor_cache[neighbor_index].q;
+        while (r->next != NULL) {
+          r = r->next;
+        }
+        r->next = new_entry;
+      } else {
+        /* queue did not exist, first item in queue */
+        neighbor_cache[neighbor_index].q = new_entry;
+      }
+      LWIP_DEBUGF(LWIP_DBG_TRACE, ("ipv6: queued packet %p on neighbor entry %"S16_F"\n", (void *)p, (s16_t)neighbor_index));
+      result = ERR_OK;
+    } else {
+      /* the pool MEMP_ND6_QUEUE is empty */
+      pbuf_free(p);
+      LWIP_DEBUGF(LWIP_DBG_TRACE, ("ipv6: could not queue a copy of packet %p (out of memory)\n", (void *)p));
+      /* { result == ERR_MEM } through initialization */
+    }
+#else /* LWIP_ND6_QUEUEING */
+    /* Queue a single packet. If an older packet is already queued, free it as per RFC. */
+    if (neighbor_cache[neighbor_index].q != NULL) {
+      pbuf_free(neighbor_cache[neighbor_index].q);
+    }
+    neighbor_cache[neighbor_index].q = p;
+    LWIP_DEBUGF(LWIP_DBG_TRACE, ("ipv6: queued packet %p on neighbor entry %"S16_F"\n", (void *)p, (s16_t)neighbor_index));
+    result = ERR_OK;
+#endif /* LWIP_ND6_QUEUEING */
+  } else {
+    LWIP_DEBUGF(LWIP_DBG_TRACE, ("ipv6: could not queue a copy of packet %p (out of memory)\n", (void *)q));
+    /* { result == ERR_MEM } through initialization */
+  }
+
+  return result;
+}
+
+#if LWIP_ND6_QUEUEING
+/**
+ * Free a complete queue of nd6 q entries
+ *
+ * @param q a queue of nd6_q_entry to free
+ */
+static void
+nd6_free_q(struct nd6_q_entry *q)
+{
+  struct nd6_q_entry *r;
+  LWIP_ASSERT("q != NULL", q != NULL);
+  LWIP_ASSERT("q->p != NULL", q->p != NULL);
+  while (q) {
+    r = q;
+    q = q->next;
+    LWIP_ASSERT("r->p != NULL", (r->p != NULL));
+    pbuf_free(r->p);
+    memp_free(MEMP_ND6_QUEUE, r);
+  }
+}
+#endif /* LWIP_ND6_QUEUEING */
+
+/**
+ * Send queued packets for a neighbor
+ *
+ * @param i the neighbor to send packets to
+ */
+static void
+nd6_send_q(s8_t i)
+{
+  struct ip6_hdr *ip6hdr;
+#if LWIP_ND6_QUEUEING
+  struct nd6_q_entry *q;
+#endif /* LWIP_ND6_QUEUEING */
+
+  if ((i < 0) || (i >= LWIP_ND6_NUM_NEIGHBORS)) {
+    return;
+  }
+
+#if LWIP_ND6_QUEUEING
+  while (neighbor_cache[i].q != NULL) {
+    /* remember first in queue */
+    q = neighbor_cache[i].q;
+    /* pop first item off the queue */
+    neighbor_cache[i].q = q->next;
+    /* Get ipv6 header. */
+    ip6hdr = (struct ip6_hdr *)(q->p->payload);
+    /* Override ip6_current_dest_addr() so that we have an aligned copy. */
+    ip6_addr_set(ip6_current_dest_addr(), &(ip6hdr->dest));
+    /* send the queued IPv6 packet */
+    (neighbor_cache[i].netif)->output_ip6(neighbor_cache[i].netif, q->p, ip6_current_dest_addr());
+    /* free the queued IP packet */
+    pbuf_free(q->p);
+    /* now queue entry can be freed */
+    memp_free(MEMP_ND6_QUEUE, q);
+  }
+#else /* LWIP_ND6_QUEUEING */
+  if (neighbor_cache[i].q != NULL) {
+    /* Get ipv6 header. */
+    ip6hdr = (struct ip6_hdr *)(neighbor_cache[i].q->payload);
+    /* Override ip6_current_dest_addr() so that we have an aligned copy. */
+    ip6_addr_set(ip6_current_dest_addr(), &(ip6hdr->dest));
+    /* send the queued IPv6 packet */
+    (neighbor_cache[i].netif)->output_ip6(neighbor_cache[i].netif, neighbor_cache[i].q, ip6_current_dest_addr());
+    /* free the queued IP packet */
+    pbuf_free(neighbor_cache[i].q);
+    neighbor_cache[i].q = NULL;
+  }
+#endif /* LWIP_ND6_QUEUEING */
+}
+
+
+/**
+ * Get the Path MTU for a destination.
+ *
+ * @param ip6addr the destination address
+ * @param netif the netif on which the packet will be sent
+ * @return the Path MTU, if known, or the netif default MTU
+ */
+u16_t
+nd6_get_destination_mtu(ip6_addr_t * ip6addr, struct netif * netif)
+{
+  s8_t i;
+
+  i = nd6_find_destination_cache_entry(ip6addr);
+  if (i >= 0) {
+    if (destination_cache[i].pmtu > 0) {
+      return destination_cache[i].pmtu;
+    }
+  }
+
+  if (netif != NULL) {
+    return netif->mtu;
+  }
+
+  return 1280; /* Minimum MTU */
+}
+
+
+#if LWIP_ND6_TCP_REACHABILITY_HINTS
+/**
+ * Provide the Neighbor discovery process with a hint that a
+ * destination is reachable. Called by tcp_receive when ACKs are
+ * received or sent (as per RFC). This is useful to avoid sending
+ * NS messages every 30 seconds.
+ *
+ * @param ip6addr the destination address which is know to be reachable
+ *                by an upper layer protocol (TCP)
+ */
+void
+nd6_reachability_hint(ip6_addr_t * ip6addr)
+{
+  s8_t i;
+
+  /* Find destination in cache. */
+  if (ip6_addr_cmp(ip6addr, &(destination_cache[nd6_cached_destination_index].destination_addr))) {
+    i = nd6_cached_destination_index;
+    ND6_STATS_INC(nd6.cachehit);
+  }
+  else {
+    i = nd6_find_destination_cache_entry(ip6addr);
+  }
+  if (i < 0) {
+    return;
+  }
+
+  /* Find next hop neighbor in cache. */
+  if (ip6_addr_cmp(&(destination_cache[i].next_hop_addr), &(neighbor_cache[nd6_cached_neighbor_index].next_hop_address))) {
+    i = nd6_cached_neighbor_index;
+    ND6_STATS_INC(nd6.cachehit);
+  }
+  else {
+    i = nd6_find_neighbor_cache_entry(&(destination_cache[i].next_hop_addr));
+  }
+  if (i < 0) {
+    return;
+  }
+
+  /* Set reachability state. */
+  neighbor_cache[i].state = ND6_REACHABLE;
+  neighbor_cache[i].counter.reachable_time = reachable_time;
+}
+#endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */
+
+#endif /* LWIP_IPV6 */
diff --git a/external/badvpn_dns/lwip/src/core/mem.c b/external/badvpn_dns/lwip/src/core/mem.c
new file mode 100644
index 0000000..1659a2c
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/mem.c
@@ -0,0 +1,659 @@
+/**
+ * @file
+ * Dynamic memory manager
+ *
+ * This is a lightweight replacement for the standard C library malloc().
+ *
+ * If you want to use the standard C library malloc() instead, define
+ * MEM_LIBC_MALLOC to 1 in your lwipopts.h
+ *
+ * To let mem_malloc() use pools (prevents fragmentation and is much faster than
+ * a heap but might waste some memory), define MEM_USE_POOLS to 1, define
+ * MEM_USE_CUSTOM_POOLS to 1 and create a file "lwippools.h" that includes a list
+ * of pools like this (more pools can be added between _START and _END):
+ *
+ * Define three pools with sizes 256, 512, and 1512 bytes
+ * LWIP_MALLOC_MEMPOOL_START
+ * LWIP_MALLOC_MEMPOOL(20, 256)
+ * LWIP_MALLOC_MEMPOOL(10, 512)
+ * LWIP_MALLOC_MEMPOOL(5, 1512)
+ * LWIP_MALLOC_MEMPOOL_END
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *         Simon Goldschmidt
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if !MEM_LIBC_MALLOC /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/sys.h"
+#include "lwip/stats.h"
+#include "lwip/err.h"
+
+#include <string.h>
+
+#if MEM_USE_POOLS
+/* lwIP head implemented with different sized pools */
+
+/**
+ * Allocate memory: determine the smallest pool that is big enough
+ * to contain an element of 'size' and get an element from that pool.
+ *
+ * @param size the size in bytes of the memory needed
+ * @return a pointer to the allocated memory or NULL if the pool is empty
+ */
+void *
+mem_malloc(mem_size_t size)
+{
+  void *ret;
+  struct memp_malloc_helper *element;
+  memp_t poolnr;
+  mem_size_t required_size = size + LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper));
+
+  for (poolnr = MEMP_POOL_FIRST; poolnr <= MEMP_POOL_LAST; poolnr = (memp_t)(poolnr + 1)) {
+#if MEM_USE_POOLS_TRY_BIGGER_POOL
+again:
+#endif /* MEM_USE_POOLS_TRY_BIGGER_POOL */
+    /* is this pool big enough to hold an element of the required size
+       plus a struct memp_malloc_helper that saves the pool this element came from? */
+    if (required_size <= memp_sizes[poolnr]) {
+      break;
+    }
+  }
+  if (poolnr > MEMP_POOL_LAST) {
+    LWIP_ASSERT("mem_malloc(): no pool is that big!", 0);
+    return NULL;
+  }
+  element = (struct memp_malloc_helper*)memp_malloc(poolnr);
+  if (element == NULL) {
+    /* No need to DEBUGF or ASSERT: This error is already
+       taken care of in memp.c */
+#if MEM_USE_POOLS_TRY_BIGGER_POOL
+    /** Try a bigger pool if this one is empty! */
+    if (poolnr < MEMP_POOL_LAST) {
+      poolnr++;
+      goto again;
+    }
+#endif /* MEM_USE_POOLS_TRY_BIGGER_POOL */
+    return NULL;
+  }
+
+  /* save the pool number this element came from */
+  element->poolnr = poolnr;
+  /* and return a pointer to the memory directly after the struct memp_malloc_helper */
+  ret = (u8_t*)element + LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper));
+
+  return ret;
+}
+
+/**
+ * Free memory previously allocated by mem_malloc. Loads the pool number
+ * and calls memp_free with that pool number to put the element back into
+ * its pool
+ *
+ * @param rmem the memory element to free
+ */
+void
+mem_free(void *rmem)
+{
+  struct memp_malloc_helper *hmem;
+
+  LWIP_ASSERT("rmem != NULL", (rmem != NULL));
+  LWIP_ASSERT("rmem == MEM_ALIGN(rmem)", (rmem == LWIP_MEM_ALIGN(rmem)));
+
+  /* get the original struct memp_malloc_helper */
+  hmem = (struct memp_malloc_helper*)(void*)((u8_t*)rmem - LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper)));
+
+  LWIP_ASSERT("hmem != NULL", (hmem != NULL));
+  LWIP_ASSERT("hmem == MEM_ALIGN(hmem)", (hmem == LWIP_MEM_ALIGN(hmem)));
+  LWIP_ASSERT("hmem->poolnr < MEMP_MAX", (hmem->poolnr < MEMP_MAX));
+
+  /* and put it in the pool we saved earlier */
+  memp_free(hmem->poolnr, hmem);
+}
+
+#else /* MEM_USE_POOLS */
+/* lwIP replacement for your libc malloc() */
+
+/**
+ * The heap is made up as a list of structs of this type.
+ * This does not have to be aligned since for getting its size,
+ * we only use the macro SIZEOF_STRUCT_MEM, which automatically alignes.
+ */
+struct mem {
+  /** index (-> ram[next]) of the next struct */
+  mem_size_t next;
+  /** index (-> ram[prev]) of the previous struct */
+  mem_size_t prev;
+  /** 1: this area is used; 0: this area is unused */
+  u8_t used;
+};
+
+/** All allocated blocks will be MIN_SIZE bytes big, at least!
+ * MIN_SIZE can be overridden to suit your needs. Smaller values save space,
+ * larger values could prevent too small blocks to fragment the RAM too much. */
+#ifndef MIN_SIZE
+#define MIN_SIZE             12
+#endif /* MIN_SIZE */
+/* some alignment macros: we define them here for better source code layout */
+#define MIN_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MIN_SIZE)
+#define SIZEOF_STRUCT_MEM    LWIP_MEM_ALIGN_SIZE(sizeof(struct mem))
+#define MEM_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MEM_SIZE)
+
+/** If you want to relocate the heap to external memory, simply define
+ * LWIP_RAM_HEAP_POINTER as a void-pointer to that location.
+ * If so, make sure the memory at that location is big enough (see below on
+ * how that space is calculated). */
+#ifndef LWIP_RAM_HEAP_POINTER
+/** the heap. we need one struct mem at the end and some room for alignment */
+u8_t ram_heap[MEM_SIZE_ALIGNED + (2*SIZEOF_STRUCT_MEM) + MEM_ALIGNMENT];
+#define LWIP_RAM_HEAP_POINTER ram_heap
+#endif /* LWIP_RAM_HEAP_POINTER */
+
+/** pointer to the heap (ram_heap): for alignment, ram is now a pointer instead of an array */
+static u8_t *ram;
+/** the last entry, always unused! */
+static struct mem *ram_end;
+/** pointer to the lowest free block, this is used for faster search */
+static struct mem *lfree;
+
+/** concurrent access protection */
+#if !NO_SYS
+static sys_mutex_t mem_mutex;
+#endif
+
+#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+
+static volatile u8_t mem_free_count;
+
+/* Allow mem_free from other (e.g. interrupt) context */
+#define LWIP_MEM_FREE_DECL_PROTECT()  SYS_ARCH_DECL_PROTECT(lev_free)
+#define LWIP_MEM_FREE_PROTECT()       SYS_ARCH_PROTECT(lev_free)
+#define LWIP_MEM_FREE_UNPROTECT()     SYS_ARCH_UNPROTECT(lev_free)
+#define LWIP_MEM_ALLOC_DECL_PROTECT() SYS_ARCH_DECL_PROTECT(lev_alloc)
+#define LWIP_MEM_ALLOC_PROTECT()      SYS_ARCH_PROTECT(lev_alloc)
+#define LWIP_MEM_ALLOC_UNPROTECT()    SYS_ARCH_UNPROTECT(lev_alloc)
+
+#else /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+
+/* Protect the heap only by using a semaphore */
+#define LWIP_MEM_FREE_DECL_PROTECT()
+#define LWIP_MEM_FREE_PROTECT()    sys_mutex_lock(&mem_mutex)
+#define LWIP_MEM_FREE_UNPROTECT()  sys_mutex_unlock(&mem_mutex)
+/* mem_malloc is protected using semaphore AND LWIP_MEM_ALLOC_PROTECT */
+#define LWIP_MEM_ALLOC_DECL_PROTECT()
+#define LWIP_MEM_ALLOC_PROTECT()
+#define LWIP_MEM_ALLOC_UNPROTECT()
+
+#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+
+
+/**
+ * "Plug holes" by combining adjacent empty struct mems.
+ * After this function is through, there should not exist
+ * one empty struct mem pointing to another empty struct mem.
+ *
+ * @param mem this points to a struct mem which just has been freed
+ * @internal this function is only called by mem_free() and mem_trim()
+ *
+ * This assumes access to the heap is protected by the calling function
+ * already.
+ */
+static void
+plug_holes(struct mem *mem)
+{
+  struct mem *nmem;
+  struct mem *pmem;
+
+  LWIP_ASSERT("plug_holes: mem >= ram", (u8_t *)mem >= ram);
+  LWIP_ASSERT("plug_holes: mem < ram_end", (u8_t *)mem < (u8_t *)ram_end);
+  LWIP_ASSERT("plug_holes: mem->used == 0", mem->used == 0);
+
+  /* plug hole forward */
+  LWIP_ASSERT("plug_holes: mem->next <= MEM_SIZE_ALIGNED", mem->next <= MEM_SIZE_ALIGNED);
+
+  nmem = (struct mem *)(void *)&ram[mem->next];
+  if (mem != nmem && nmem->used == 0 && (u8_t *)nmem != (u8_t *)ram_end) {
+    /* if mem->next is unused and not end of ram, combine mem and mem->next */
+    if (lfree == nmem) {
+      lfree = mem;
+    }
+    mem->next = nmem->next;
+    ((struct mem *)(void *)&ram[nmem->next])->prev = (mem_size_t)((u8_t *)mem - ram);
+  }
+
+  /* plug hole backward */
+  pmem = (struct mem *)(void *)&ram[mem->prev];
+  if (pmem != mem && pmem->used == 0) {
+    /* if mem->prev is unused, combine mem and mem->prev */
+    if (lfree == mem) {
+      lfree = pmem;
+    }
+    pmem->next = mem->next;
+    ((struct mem *)(void *)&ram[mem->next])->prev = (mem_size_t)((u8_t *)pmem - ram);
+  }
+}
+
+/**
+ * Zero the heap and initialize start, end and lowest-free
+ */
+void
+mem_init(void)
+{
+  struct mem *mem;
+
+  LWIP_ASSERT("Sanity check alignment",
+    (SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT-1)) == 0);
+
+  /* align the heap */
+  ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);
+  /* initialize the start of the heap */
+  mem = (struct mem *)(void *)ram;
+  mem->next = MEM_SIZE_ALIGNED;
+  mem->prev = 0;
+  mem->used = 0;
+  /* initialize the end of the heap */
+  ram_end = (struct mem *)(void *)&ram[MEM_SIZE_ALIGNED];
+  ram_end->used = 1;
+  ram_end->next = MEM_SIZE_ALIGNED;
+  ram_end->prev = MEM_SIZE_ALIGNED;
+
+  /* initialize the lowest-free pointer to the start of the heap */
+  lfree = (struct mem *)(void *)ram;
+
+  MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);
+
+  if(sys_mutex_new(&mem_mutex) != ERR_OK) {
+    LWIP_ASSERT("failed to create mem_mutex", 0);
+  }
+}
+
+/**
+ * Put a struct mem back on the heap
+ *
+ * @param rmem is the data portion of a struct mem as returned by a previous
+ *             call to mem_malloc()
+ */
+void
+mem_free(void *rmem)
+{
+  struct mem *mem;
+  LWIP_MEM_FREE_DECL_PROTECT();
+
+  if (rmem == NULL) {
+    LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("mem_free(p == NULL) was called.\n"));
+    return;
+  }
+  LWIP_ASSERT("mem_free: sanity check alignment", (((mem_ptr_t)rmem) & (MEM_ALIGNMENT-1)) == 0);
+
+  LWIP_ASSERT("mem_free: legal memory", (u8_t *)rmem >= (u8_t *)ram &&
+    (u8_t *)rmem < (u8_t *)ram_end);
+
+  if ((u8_t *)rmem < (u8_t *)ram || (u8_t *)rmem >= (u8_t *)ram_end) {
+    SYS_ARCH_DECL_PROTECT(lev);
+    LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mem_free: illegal memory\n"));
+    /* protect mem stats from concurrent access */
+    SYS_ARCH_PROTECT(lev);
+    MEM_STATS_INC(illegal);
+    SYS_ARCH_UNPROTECT(lev);
+    return;
+  }
+  /* protect the heap from concurrent access */
+  LWIP_MEM_FREE_PROTECT();
+  /* Get the corresponding struct mem ... */
+  mem = (struct mem *)(void *)((u8_t *)rmem - SIZEOF_STRUCT_MEM);
+  /* ... which has to be in a used state ... */
+  LWIP_ASSERT("mem_free: mem->used", mem->used);
+  /* ... and is now unused. */
+  mem->used = 0;
+
+  if (mem < lfree) {
+    /* the newly freed struct is now the lowest */
+    lfree = mem;
+  }
+
+  MEM_STATS_DEC_USED(used, mem->next - (mem_size_t)(((u8_t *)mem - ram)));
+
+  /* finally, see if prev or next are free also */
+  plug_holes(mem);
+#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+  mem_free_count = 1;
+#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+  LWIP_MEM_FREE_UNPROTECT();
+}
+
+/**
+ * Shrink memory returned by mem_malloc().
+ *
+ * @param rmem pointer to memory allocated by mem_malloc the is to be shrinked
+ * @param newsize required size after shrinking (needs to be smaller than or
+ *                equal to the previous size)
+ * @return for compatibility reasons: is always == rmem, at the moment
+ *         or NULL if newsize is > old size, in which case rmem is NOT touched
+ *         or freed!
+ */
+void *
+mem_trim(void *rmem, mem_size_t newsize)
+{
+  mem_size_t size;
+  mem_size_t ptr, ptr2;
+  struct mem *mem, *mem2;
+  /* use the FREE_PROTECT here: it protects with sem OR SYS_ARCH_PROTECT */
+  LWIP_MEM_FREE_DECL_PROTECT();
+
+  /* Expand the size of the allocated memory region so that we can
+     adjust for alignment. */
+  newsize = LWIP_MEM_ALIGN_SIZE(newsize);
+
+  if(newsize < MIN_SIZE_ALIGNED) {
+    /* every data block must be at least MIN_SIZE_ALIGNED long */
+    newsize = MIN_SIZE_ALIGNED;
+  }
+
+  if (newsize > MEM_SIZE_ALIGNED) {
+    return NULL;
+  }
+
+  LWIP_ASSERT("mem_trim: legal memory", (u8_t *)rmem >= (u8_t *)ram &&
+   (u8_t *)rmem < (u8_t *)ram_end);
+
+  if ((u8_t *)rmem < (u8_t *)ram || (u8_t *)rmem >= (u8_t *)ram_end) {
+    SYS_ARCH_DECL_PROTECT(lev);
+    LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mem_trim: illegal memory\n"));
+    /* protect mem stats from concurrent access */
+    SYS_ARCH_PROTECT(lev);
+    MEM_STATS_INC(illegal);
+    SYS_ARCH_UNPROTECT(lev);
+    return rmem;
+  }
+  /* Get the corresponding struct mem ... */
+  mem = (struct mem *)(void *)((u8_t *)rmem - SIZEOF_STRUCT_MEM);
+  /* ... and its offset pointer */
+  ptr = (mem_size_t)((u8_t *)mem - ram);
+
+  size = mem->next - ptr - SIZEOF_STRUCT_MEM;
+  LWIP_ASSERT("mem_trim can only shrink memory", newsize <= size);
+  if (newsize > size) {
+    /* not supported */
+    return NULL;
+  }
+  if (newsize == size) {
+    /* No change in size, simply return */
+    return rmem;
+  }
+
+  /* protect the heap from concurrent access */
+  LWIP_MEM_FREE_PROTECT();
+
+  mem2 = (struct mem *)(void *)&ram[mem->next];
+  if(mem2->used == 0) {
+    /* The next struct is unused, we can simply move it at little */
+    mem_size_t next;
+    /* remember the old next pointer */
+    next = mem2->next;
+    /* create new struct mem which is moved directly after the shrinked mem */
+    ptr2 = ptr + SIZEOF_STRUCT_MEM + newsize;
+    if (lfree == mem2) {
+      lfree = (struct mem *)(void *)&ram[ptr2];
+    }
+    mem2 = (struct mem *)(void *)&ram[ptr2];
+    mem2->used = 0;
+    /* restore the next pointer */
+    mem2->next = next;
+    /* link it back to mem */
+    mem2->prev = ptr;
+    /* link mem to it */
+    mem->next = ptr2;
+    /* last thing to restore linked list: as we have moved mem2,
+     * let 'mem2->next->prev' point to mem2 again. but only if mem2->next is not
+     * the end of the heap */
+    if (mem2->next != MEM_SIZE_ALIGNED) {
+      ((struct mem *)(void *)&ram[mem2->next])->prev = ptr2;
+    }
+    MEM_STATS_DEC_USED(used, (size - newsize));
+    /* no need to plug holes, we've already done that */
+  } else if (newsize + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED <= size) {
+    /* Next struct is used but there's room for another struct mem with
+     * at least MIN_SIZE_ALIGNED of data.
+     * Old size ('size') must be big enough to contain at least 'newsize' plus a struct mem
+     * ('SIZEOF_STRUCT_MEM') with some data ('MIN_SIZE_ALIGNED').
+     * @todo we could leave out MIN_SIZE_ALIGNED. We would create an empty
+     *       region that couldn't hold data, but when mem->next gets freed,
+     *       the 2 regions would be combined, resulting in more free memory */
+    ptr2 = ptr + SIZEOF_STRUCT_MEM + newsize;
+    mem2 = (struct mem *)(void *)&ram[ptr2];
+    if (mem2 < lfree) {
+      lfree = mem2;
+    }
+    mem2->used = 0;
+    mem2->next = mem->next;
+    mem2->prev = ptr;
+    mem->next = ptr2;
+    if (mem2->next != MEM_SIZE_ALIGNED) {
+      ((struct mem *)(void *)&ram[mem2->next])->prev = ptr2;
+    }
+    MEM_STATS_DEC_USED(used, (size - newsize));
+    /* the original mem->next is used, so no need to plug holes! */
+  }
+  /* else {
+    next struct mem is used but size between mem and mem2 is not big enough
+    to create another struct mem
+    -> don't do anyhting. 
+    -> the remaining space stays unused since it is too small
+  } */
+#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+  mem_free_count = 1;
+#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+  LWIP_MEM_FREE_UNPROTECT();
+  return rmem;
+}
+
+/**
+ * Adam's mem_malloc() plus solution for bug #17922
+ * Allocate a block of memory with a minimum of 'size' bytes.
+ *
+ * @param size is the minimum size of the requested block in bytes.
+ * @return pointer to allocated memory or NULL if no free memory was found.
+ *
+ * Note that the returned value will always be aligned (as defined by MEM_ALIGNMENT).
+ */
+void *
+mem_malloc(mem_size_t size)
+{
+  mem_size_t ptr, ptr2;
+  struct mem *mem, *mem2;
+#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+  u8_t local_mem_free_count = 0;
+#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+  LWIP_MEM_ALLOC_DECL_PROTECT();
+
+  if (size == 0) {
+    return NULL;
+  }
+
+  /* Expand the size of the allocated memory region so that we can
+     adjust for alignment. */
+  size = LWIP_MEM_ALIGN_SIZE(size);
+
+  if(size < MIN_SIZE_ALIGNED) {
+    /* every data block must be at least MIN_SIZE_ALIGNED long */
+    size = MIN_SIZE_ALIGNED;
+  }
+
+  if (size > MEM_SIZE_ALIGNED) {
+    return NULL;
+  }
+
+  /* protect the heap from concurrent access */
+  sys_mutex_lock(&mem_mutex);
+  LWIP_MEM_ALLOC_PROTECT();
+#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+  /* run as long as a mem_free disturbed mem_malloc or mem_trim */
+  do {
+    local_mem_free_count = 0;
+#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+
+    /* Scan through the heap searching for a free block that is big enough,
+     * beginning with the lowest free block.
+     */
+    for (ptr = (mem_size_t)((u8_t *)lfree - ram); ptr < MEM_SIZE_ALIGNED - size;
+         ptr = ((struct mem *)(void *)&ram[ptr])->next) {
+      mem = (struct mem *)(void *)&ram[ptr];
+#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+      mem_free_count = 0;
+      LWIP_MEM_ALLOC_UNPROTECT();
+      /* allow mem_free or mem_trim to run */
+      LWIP_MEM_ALLOC_PROTECT();
+      if (mem_free_count != 0) {
+        /* If mem_free or mem_trim have run, we have to restart since they
+           could have altered our current struct mem. */
+        local_mem_free_count = 1;
+        break;
+      }
+#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+
+      if ((!mem->used) &&
+          (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) {
+        /* mem is not used and at least perfect fit is possible:
+         * mem->next - (ptr + SIZEOF_STRUCT_MEM) gives us the 'user data size' of mem */
+
+        if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)) {
+          /* (in addition to the above, we test if another struct mem (SIZEOF_STRUCT_MEM) containing
+           * at least MIN_SIZE_ALIGNED of data also fits in the 'user data space' of 'mem')
+           * -> split large block, create empty remainder,
+           * remainder must be large enough to contain MIN_SIZE_ALIGNED data: if
+           * mem->next - (ptr + (2*SIZEOF_STRUCT_MEM)) == size,
+           * struct mem would fit in but no data between mem2 and mem2->next
+           * @todo we could leave out MIN_SIZE_ALIGNED. We would create an empty
+           *       region that couldn't hold data, but when mem->next gets freed,
+           *       the 2 regions would be combined, resulting in more free memory
+           */
+          ptr2 = ptr + SIZEOF_STRUCT_MEM + size;
+          /* create mem2 struct */
+          mem2 = (struct mem *)(void *)&ram[ptr2];
+          mem2->used = 0;
+          mem2->next = mem->next;
+          mem2->prev = ptr;
+          /* and insert it between mem and mem->next */
+          mem->next = ptr2;
+          mem->used = 1;
+
+          if (mem2->next != MEM_SIZE_ALIGNED) {
+            ((struct mem *)(void *)&ram[mem2->next])->prev = ptr2;
+          }
+          MEM_STATS_INC_USED(used, (size + SIZEOF_STRUCT_MEM));
+        } else {
+          /* (a mem2 struct does no fit into the user data space of mem and mem->next will always
+           * be used at this point: if not we have 2 unused structs in a row, plug_holes should have
+           * take care of this).
+           * -> near fit or excact fit: do not split, no mem2 creation
+           * also can't move mem->next directly behind mem, since mem->next
+           * will always be used at this point!
+           */
+          mem->used = 1;
+          MEM_STATS_INC_USED(used, mem->next - (mem_size_t)((u8_t *)mem - ram));
+        }
+#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+mem_malloc_adjust_lfree:
+#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+        if (mem == lfree) {
+          struct mem *cur = lfree;
+          /* Find next free block after mem and update lowest free pointer */
+          while (cur->used && cur != ram_end) {
+#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+            mem_free_count = 0;
+            LWIP_MEM_ALLOC_UNPROTECT();
+            /* prevent high interrupt latency... */
+            LWIP_MEM_ALLOC_PROTECT();
+            if (mem_free_count != 0) {
+              /* If mem_free or mem_trim have run, we have to restart since they
+                 could have altered our current struct mem or lfree. */
+              goto mem_malloc_adjust_lfree;
+            }
+#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+            cur = (struct mem *)(void *)&ram[cur->next];
+          }
+          lfree = cur;
+          LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree == ram_end) || (!lfree->used)));
+        }
+        LWIP_MEM_ALLOC_UNPROTECT();
+        sys_mutex_unlock(&mem_mutex);
+        LWIP_ASSERT("mem_malloc: allocated memory not above ram_end.",
+         (mem_ptr_t)mem + SIZEOF_STRUCT_MEM + size <= (mem_ptr_t)ram_end);
+        LWIP_ASSERT("mem_malloc: allocated memory properly aligned.",
+         ((mem_ptr_t)mem + SIZEOF_STRUCT_MEM) % MEM_ALIGNMENT == 0);
+        LWIP_ASSERT("mem_malloc: sanity check alignment",
+          (((mem_ptr_t)mem) & (MEM_ALIGNMENT-1)) == 0);
+
+        return (u8_t *)mem + SIZEOF_STRUCT_MEM;
+      }
+    }
+#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+    /* if we got interrupted by a mem_free, try again */
+  } while(local_mem_free_count != 0);
+#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
+  LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mem_malloc: could not allocate %"S16_F" bytes\n", (s16_t)size));
+  MEM_STATS_INC(err);
+  LWIP_MEM_ALLOC_UNPROTECT();
+  sys_mutex_unlock(&mem_mutex);
+  return NULL;
+}
+
+#endif /* MEM_USE_POOLS */
+/**
+ * Contiguously allocates enough space for count objects that are size bytes
+ * of memory each and returns a pointer to the allocated memory.
+ *
+ * The allocated memory is filled with bytes of value zero.
+ *
+ * @param count number of objects to allocate
+ * @param size size of the objects to allocate
+ * @return pointer to allocated memory / NULL pointer if there is an error
+ */
+void *mem_calloc(mem_size_t count, mem_size_t size)
+{
+  void *p;
+
+  /* allocate 'count' objects of size 'size' */
+  p = mem_malloc(count * size);
+  if (p) {
+    /* zero the memory */
+    memset(p, 0, count * size);
+  }
+  return p;
+}
+
+#endif /* !MEM_LIBC_MALLOC */
diff --git a/external/badvpn_dns/lwip/src/core/memp.c b/external/badvpn_dns/lwip/src/core/memp.c
new file mode 100644
index 0000000..1323463
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/memp.c
@@ -0,0 +1,485 @@
+/**
+ * @file
+ * Dynamic pool memory manager
+ *
+ * lwIP has dedicated pools for many structures (netconn, protocol control blocks,
+ * packet buffers, ...). All these pools are managed here.
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/memp.h"
+#include "lwip/pbuf.h"
+#include "lwip/udp.h"
+#include "lwip/raw.h"
+#include "lwip/tcp_impl.h"
+#include "lwip/igmp.h"
+#include "lwip/api.h"
+#include "lwip/api_msg.h"
+#include "lwip/tcpip.h"
+#include "lwip/sys.h"
+#include "lwip/timers.h"
+#include "lwip/stats.h"
+#include "netif/etharp.h"
+#include "lwip/ip_frag.h"
+#include "lwip/snmp_structs.h"
+#include "lwip/snmp_msg.h"
+#include "lwip/dns.h"
+#include "netif/ppp_oe.h"
+#include "lwip/nd6.h"
+#include "lwip/ip6_frag.h"
+#include "lwip/mld6.h"
+
+#include <string.h>
+
+#if !MEMP_MEM_MALLOC /* don't build if not configured for use in lwipopts.h */
+
+struct memp {
+  struct memp *next;
+#if MEMP_OVERFLOW_CHECK
+  const char *file;
+  int line;
+#endif /* MEMP_OVERFLOW_CHECK */
+};
+
+#if MEMP_OVERFLOW_CHECK
+/* if MEMP_OVERFLOW_CHECK is turned on, we reserve some bytes at the beginning
+ * and at the end of each element, initialize them as 0xcd and check
+ * them later. */
+/* If MEMP_OVERFLOW_CHECK is >= 2, on every call to memp_malloc or memp_free,
+ * every single element in each pool is checked!
+ * This is VERY SLOW but also very helpful. */
+/* MEMP_SANITY_REGION_BEFORE and MEMP_SANITY_REGION_AFTER can be overridden in
+ * lwipopts.h to change the amount reserved for checking. */
+#ifndef MEMP_SANITY_REGION_BEFORE
+#define MEMP_SANITY_REGION_BEFORE  16
+#endif /* MEMP_SANITY_REGION_BEFORE*/
+#if MEMP_SANITY_REGION_BEFORE > 0
+#define MEMP_SANITY_REGION_BEFORE_ALIGNED    LWIP_MEM_ALIGN_SIZE(MEMP_SANITY_REGION_BEFORE)
+#else
+#define MEMP_SANITY_REGION_BEFORE_ALIGNED    0
+#endif /* MEMP_SANITY_REGION_BEFORE*/
+#ifndef MEMP_SANITY_REGION_AFTER
+#define MEMP_SANITY_REGION_AFTER   16
+#endif /* MEMP_SANITY_REGION_AFTER*/
+#if MEMP_SANITY_REGION_AFTER > 0
+#define MEMP_SANITY_REGION_AFTER_ALIGNED     LWIP_MEM_ALIGN_SIZE(MEMP_SANITY_REGION_AFTER)
+#else
+#define MEMP_SANITY_REGION_AFTER_ALIGNED     0
+#endif /* MEMP_SANITY_REGION_AFTER*/
+
+/* MEMP_SIZE: save space for struct memp and for sanity check */
+#define MEMP_SIZE          (LWIP_MEM_ALIGN_SIZE(sizeof(struct memp)) + MEMP_SANITY_REGION_BEFORE_ALIGNED)
+#define MEMP_ALIGN_SIZE(x) (LWIP_MEM_ALIGN_SIZE(x) + MEMP_SANITY_REGION_AFTER_ALIGNED)
+
+#else /* MEMP_OVERFLOW_CHECK */
+
+/* No sanity checks
+ * We don't need to preserve the struct memp while not allocated, so we
+ * can save a little space and set MEMP_SIZE to 0.
+ */
+#define MEMP_SIZE           0
+#define MEMP_ALIGN_SIZE(x) (LWIP_MEM_ALIGN_SIZE(x))
+
+#endif /* MEMP_OVERFLOW_CHECK */
+
+/** This array holds the first free element of each pool.
+ *  Elements form a linked list. */
+static struct memp *memp_tab[MEMP_MAX];
+
+#else /* MEMP_MEM_MALLOC */
+
+#define MEMP_ALIGN_SIZE(x) (LWIP_MEM_ALIGN_SIZE(x))
+
+#endif /* MEMP_MEM_MALLOC */
+
+/** This array holds the element sizes of each pool. */
+#if !MEM_USE_POOLS && !MEMP_MEM_MALLOC
+static
+#endif
+const u16_t memp_sizes[MEMP_MAX] = {
+#define LWIP_MEMPOOL(name,num,size,desc)  LWIP_MEM_ALIGN_SIZE(size),
+#include "lwip/memp_std.h"
+};
+
+#if !MEMP_MEM_MALLOC /* don't build if not configured for use in lwipopts.h */
+
+/** This array holds the number of elements in each pool. */
+static const u16_t memp_num[MEMP_MAX] = {
+#define LWIP_MEMPOOL(name,num,size,desc)  (num),
+#include "lwip/memp_std.h"
+};
+
+/** This array holds a textual description of each pool. */
+#ifdef LWIP_DEBUG
+static const char *memp_desc[MEMP_MAX] = {
+#define LWIP_MEMPOOL(name,num,size,desc)  (desc),
+#include "lwip/memp_std.h"
+};
+#endif /* LWIP_DEBUG */
+
+#if MEMP_SEPARATE_POOLS
+
+/** This creates each memory pool. These are named memp_memory_XXX_base (where
+ * XXX is the name of the pool defined in memp_std.h).
+ * To relocate a pool, declare it as extern in cc.h. Example for GCC:
+ *   extern u8_t __attribute__((section(".onchip_mem"))) memp_memory_UDP_PCB_base[];
+ */
+#define LWIP_MEMPOOL(name,num,size,desc) u8_t memp_memory_ ## name ## _base \
+  [((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))];   
+#include "lwip/memp_std.h"
+
+/** This array holds the base of each memory pool. */
+static u8_t *const memp_bases[] = { 
+#define LWIP_MEMPOOL(name,num,size,desc) memp_memory_ ## name ## _base,   
+#include "lwip/memp_std.h"
+};
+
+#else /* MEMP_SEPARATE_POOLS */
+
+/** This is the actual memory used by the pools (all pools in one big block). */
+static u8_t memp_memory[MEM_ALIGNMENT - 1 
+#define LWIP_MEMPOOL(name,num,size,desc) + ( (num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size) ) )
+#include "lwip/memp_std.h"
+];
+
+#endif /* MEMP_SEPARATE_POOLS */
+
+#if MEMP_SANITY_CHECK
+/**
+ * Check that memp-lists don't form a circle, using "Floyd's cycle-finding algorithm".
+ */
+static int
+memp_sanity(void)
+{
+  s16_t i;
+  struct memp *t, *h;
+
+  for (i = 0; i < MEMP_MAX; i++) {
+    t = memp_tab[i];
+    if(t != NULL) {
+      for (h = t->next; (t != NULL) && (h != NULL); t = t->next,
+        h = (((h->next != NULL) && (h->next->next != NULL)) ? h->next->next : NULL)) {
+        if (t == h) {
+          return 0;
+        }
+      }
+    }
+  }
+  return 1;
+}
+#endif /* MEMP_SANITY_CHECK*/
+#if MEMP_OVERFLOW_CHECK
+#if defined(LWIP_DEBUG) && MEMP_STATS
+static const char * memp_overflow_names[] = {
+#define LWIP_MEMPOOL(name,num,size,desc) "/"desc,
+#include "lwip/memp_std.h"
+  };
+#endif
+
+/**
+ * Check if a memp element was victim of an overflow
+ * (e.g. the restricted area after it has been altered)
+ *
+ * @param p the memp element to check
+ * @param memp_type the pool p comes from
+ */
+static void
+memp_overflow_check_element_overflow(struct memp *p, u16_t memp_type)
+{
+  u16_t k;
+  u8_t *m;
+#if MEMP_SANITY_REGION_AFTER_ALIGNED > 0
+  m = (u8_t*)p + MEMP_SIZE + memp_sizes[memp_type];
+  for (k = 0; k < MEMP_SANITY_REGION_AFTER_ALIGNED; k++) {
+    if (m[k] != 0xcd) {
+      char errstr[128] = "detected memp overflow in pool ";
+      char digit[] = "0";
+      if(memp_type >= 10) {
+        digit[0] = '0' + (memp_type/10);
+        strcat(errstr, digit);
+      }
+      digit[0] = '0' + (memp_type%10);
+      strcat(errstr, digit);
+#if defined(LWIP_DEBUG) && MEMP_STATS
+      strcat(errstr, memp_overflow_names[memp_type]);
+#endif
+      LWIP_ASSERT(errstr, 0);
+    }
+  }
+#endif
+}
+
+/**
+ * Check if a memp element was victim of an underflow
+ * (e.g. the restricted area before it has been altered)
+ *
+ * @param p the memp element to check
+ * @param memp_type the pool p comes from
+ */
+static void
+memp_overflow_check_element_underflow(struct memp *p, u16_t memp_type)
+{
+  u16_t k;
+  u8_t *m;
+#if MEMP_SANITY_REGION_BEFORE_ALIGNED > 0
+  m = (u8_t*)p + MEMP_SIZE - MEMP_SANITY_REGION_BEFORE_ALIGNED;
+  for (k = 0; k < MEMP_SANITY_REGION_BEFORE_ALIGNED; k++) {
+    if (m[k] != 0xcd) {
+      char errstr[128] = "detected memp underflow in pool ";
+      char digit[] = "0";
+      if(memp_type >= 10) {
+        digit[0] = '0' + (memp_type/10);
+        strcat(errstr, digit);
+      }
+      digit[0] = '0' + (memp_type%10);
+      strcat(errstr, digit);
+#if defined(LWIP_DEBUG) && MEMP_STATS
+      strcat(errstr, memp_overflow_names[memp_type]);
+#endif
+      LWIP_ASSERT(errstr, 0);
+    }
+  }
+#endif
+}
+
+/**
+ * Do an overflow check for all elements in every pool.
+ *
+ * @see memp_overflow_check_element for a description of the check
+ */
+static void
+memp_overflow_check_all(void)
+{
+  u16_t i, j;
+  struct memp *p;
+
+#if !MEMP_SEPARATE_POOLS
+  p = (struct memp *)LWIP_MEM_ALIGN(memp_memory);
+#endif /* !MEMP_SEPARATE_POOLS */
+  for (i = 0; i < MEMP_MAX; ++i) {
+#if MEMP_SEPARATE_POOLS
+    p = (struct memp *)(memp_bases[i]);
+#endif /* MEMP_SEPARATE_POOLS */
+    for (j = 0; j < memp_num[i]; ++j) {
+      memp_overflow_check_element_overflow(p, i);
+      p = (struct memp*)((u8_t*)p + MEMP_SIZE + memp_sizes[i] + MEMP_SANITY_REGION_AFTER_ALIGNED);
+    }
+  }
+#if !MEMP_SEPARATE_POOLS
+  p = (struct memp *)LWIP_MEM_ALIGN(memp_memory);
+#endif /* !MEMP_SEPARATE_POOLS */
+  for (i = 0; i < MEMP_MAX; ++i) {
+#if MEMP_SEPARATE_POOLS
+    p = (struct memp *)(memp_bases[i]);
+#endif /* MEMP_SEPARATE_POOLS */
+    for (j = 0; j < memp_num[i]; ++j) {
+      memp_overflow_check_element_underflow(p, i);
+      p = (struct memp*)((u8_t*)p + MEMP_SIZE + memp_sizes[i] + MEMP_SANITY_REGION_AFTER_ALIGNED);
+    }
+  }
+}
+
+/**
+ * Initialize the restricted areas of all memp elements in every pool.
+ */
+static void
+memp_overflow_init(void)
+{
+  u16_t i, j;
+  struct memp *p;
+  u8_t *m;
+
+#if !MEMP_SEPARATE_POOLS
+  p = (struct memp *)LWIP_MEM_ALIGN(memp_memory);
+#endif /* !MEMP_SEPARATE_POOLS */
+  for (i = 0; i < MEMP_MAX; ++i) {
+#if MEMP_SEPARATE_POOLS
+    p = (struct memp *)(memp_bases[i]);
+#endif /* MEMP_SEPARATE_POOLS */
+    for (j = 0; j < memp_num[i]; ++j) {
+#if MEMP_SANITY_REGION_BEFORE_ALIGNED > 0
+      m = (u8_t*)p + MEMP_SIZE - MEMP_SANITY_REGION_BEFORE_ALIGNED;
+      memset(m, 0xcd, MEMP_SANITY_REGION_BEFORE_ALIGNED);
+#endif
+#if MEMP_SANITY_REGION_AFTER_ALIGNED > 0
+      m = (u8_t*)p + MEMP_SIZE + memp_sizes[i];
+      memset(m, 0xcd, MEMP_SANITY_REGION_AFTER_ALIGNED);
+#endif
+      p = (struct memp*)((u8_t*)p + MEMP_SIZE + memp_sizes[i] + MEMP_SANITY_REGION_AFTER_ALIGNED);
+    }
+  }
+}
+#endif /* MEMP_OVERFLOW_CHECK */
+
+/**
+ * Initialize this module.
+ * 
+ * Carves out memp_memory into linked lists for each pool-type.
+ */
+void
+memp_init(void)
+{
+  struct memp *memp;
+  u16_t i, j;
+
+  for (i = 0; i < MEMP_MAX; ++i) {
+    MEMP_STATS_AVAIL(used, i, 0);
+    MEMP_STATS_AVAIL(max, i, 0);
+    MEMP_STATS_AVAIL(err, i, 0);
+    MEMP_STATS_AVAIL(avail, i, memp_num[i]);
+  }
+
+#if !MEMP_SEPARATE_POOLS
+  memp = (struct memp *)LWIP_MEM_ALIGN(memp_memory);
+#endif /* !MEMP_SEPARATE_POOLS */
+  /* for every pool: */
+  for (i = 0; i < MEMP_MAX; ++i) {
+    memp_tab[i] = NULL;
+#if MEMP_SEPARATE_POOLS
+    memp = (struct memp*)memp_bases[i];
+#endif /* MEMP_SEPARATE_POOLS */
+    /* create a linked list of memp elements */
+    for (j = 0; j < memp_num[i]; ++j) {
+      memp->next = memp_tab[i];
+      memp_tab[i] = memp;
+      memp = (struct memp *)(void *)((u8_t *)memp + MEMP_SIZE + memp_sizes[i]
+#if MEMP_OVERFLOW_CHECK
+        + MEMP_SANITY_REGION_AFTER_ALIGNED
+#endif
+      );
+    }
+  }
+#if MEMP_OVERFLOW_CHECK
+  memp_overflow_init();
+  /* check everything a first time to see if it worked */
+  memp_overflow_check_all();
+#endif /* MEMP_OVERFLOW_CHECK */
+}
+
+/**
+ * Get an element from a specific pool.
+ *
+ * @param type the pool to get an element from
+ *
+ * the debug version has two more parameters:
+ * @param file file name calling this function
+ * @param line number of line where this function is called
+ *
+ * @return a pointer to the allocated memory or a NULL pointer on error
+ */
+void *
+#if !MEMP_OVERFLOW_CHECK
+memp_malloc(memp_t type)
+#else
+memp_malloc_fn(memp_t type, const char* file, const int line)
+#endif
+{
+  struct memp *memp;
+  SYS_ARCH_DECL_PROTECT(old_level);
+ 
+  LWIP_ERROR("memp_malloc: type < MEMP_MAX", (type < MEMP_MAX), return NULL;);
+
+  SYS_ARCH_PROTECT(old_level);
+#if MEMP_OVERFLOW_CHECK >= 2
+  memp_overflow_check_all();
+#endif /* MEMP_OVERFLOW_CHECK >= 2 */
+
+  memp = memp_tab[type];
+  
+  if (memp != NULL) {
+    memp_tab[type] = memp->next;
+#if MEMP_OVERFLOW_CHECK
+    memp->next = NULL;
+    memp->file = file;
+    memp->line = line;
+#endif /* MEMP_OVERFLOW_CHECK */
+    MEMP_STATS_INC_USED(used, type);
+    LWIP_ASSERT("memp_malloc: memp properly aligned",
+                ((mem_ptr_t)memp % MEM_ALIGNMENT) == 0);
+    memp = (struct memp*)(void *)((u8_t*)memp + MEMP_SIZE);
+  } else {
+    LWIP_DEBUGF(MEMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("memp_malloc: out of memory in pool %s\n", memp_desc[type]));
+    MEMP_STATS_INC(err, type);
+  }
+
+  SYS_ARCH_UNPROTECT(old_level);
+
+  return memp;
+}
+
+/**
+ * Put an element back into its pool.
+ *
+ * @param type the pool where to put mem
+ * @param mem the memp element to free
+ */
+void
+memp_free(memp_t type, void *mem)
+{
+  struct memp *memp;
+  SYS_ARCH_DECL_PROTECT(old_level);
+
+  if (mem == NULL) {
+    return;
+  }
+  LWIP_ASSERT("memp_free: mem properly aligned",
+                ((mem_ptr_t)mem % MEM_ALIGNMENT) == 0);
+
+  memp = (struct memp *)(void *)((u8_t*)mem - MEMP_SIZE);
+
+  SYS_ARCH_PROTECT(old_level);
+#if MEMP_OVERFLOW_CHECK
+#if MEMP_OVERFLOW_CHECK >= 2
+  memp_overflow_check_all();
+#else
+  memp_overflow_check_element_overflow(memp, type);
+  memp_overflow_check_element_underflow(memp, type);
+#endif /* MEMP_OVERFLOW_CHECK >= 2 */
+#endif /* MEMP_OVERFLOW_CHECK */
+
+  MEMP_STATS_DEC(used, type); 
+  
+  memp->next = memp_tab[type]; 
+  memp_tab[type] = memp;
+
+#if MEMP_SANITY_CHECK
+  LWIP_ASSERT("memp sanity", memp_sanity());
+#endif /* MEMP_SANITY_CHECK */
+
+  SYS_ARCH_UNPROTECT(old_level);
+}
+
+#endif /* MEMP_MEM_MALLOC */
diff --git a/external/badvpn_dns/lwip/src/core/netif.c b/external/badvpn_dns/lwip/src/core/netif.c
new file mode 100644
index 0000000..4df1a90
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/netif.c
@@ -0,0 +1,918 @@
+/**
+ * @file
+ * lwIP network interface abstraction
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/def.h"
+#include "lwip/ip_addr.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/netif.h"
+#include "lwip/tcp_impl.h"
+#include "lwip/snmp.h"
+#include "lwip/igmp.h"
+#include "netif/etharp.h"
+#include "lwip/stats.h"
+#if ENABLE_LOOPBACK
+#include "lwip/sys.h"
+#if LWIP_NETIF_LOOPBACK_MULTITHREADING
+#include "lwip/tcpip.h"
+#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
+#endif /* ENABLE_LOOPBACK */
+
+#if LWIP_AUTOIP
+#include "lwip/autoip.h"
+#endif /* LWIP_AUTOIP */
+#if LWIP_DHCP
+#include "lwip/dhcp.h"
+#endif /* LWIP_DHCP */
+#if LWIP_IPV6_DHCP6
+#include "lwip/dhcp6.h"
+#endif /* LWIP_IPV6_DHCP6 */
+#if LWIP_IPV6_MLD
+#include "lwip/mld6.h"
+#endif /* LWIP_IPV6_MLD */
+
+#if LWIP_NETIF_STATUS_CALLBACK
+#define NETIF_STATUS_CALLBACK(n) do{ if (n->status_callback) { (n->status_callback)(n); }}while(0)
+#else
+#define NETIF_STATUS_CALLBACK(n)
+#endif /* LWIP_NETIF_STATUS_CALLBACK */ 
+
+#if LWIP_NETIF_LINK_CALLBACK
+#define NETIF_LINK_CALLBACK(n) do{ if (n->link_callback) { (n->link_callback)(n); }}while(0)
+#else
+#define NETIF_LINK_CALLBACK(n)
+#endif /* LWIP_NETIF_LINK_CALLBACK */ 
+
+struct netif *netif_list;
+struct netif *netif_default;
+
+static u8_t netif_num;
+
+#if LWIP_IPV6
+static err_t netif_null_output_ip6(struct netif *netif, struct pbuf *p, ip6_addr_t *ipaddr);
+#endif /* LWIP_IPV6 */
+
+#if LWIP_HAVE_LOOPIF
+static struct netif loop_netif;
+
+/**
+ * Initialize a lwip network interface structure for a loopback interface
+ *
+ * @param netif the lwip network interface structure for this loopif
+ * @return ERR_OK if the loopif is initialized
+ *         ERR_MEM if private data couldn't be allocated
+ */
+static err_t
+netif_loopif_init(struct netif *netif)
+{
+  /* initialize the snmp variables and counters inside the struct netif
+   * ifSpeed: no assumption can be made!
+   */
+  NETIF_INIT_SNMP(netif, snmp_ifType_softwareLoopback, 0);
+
+  netif->name[0] = 'l';
+  netif->name[1] = 'o';
+  netif->output = netif_loop_output;
+  return ERR_OK;
+}
+#endif /* LWIP_HAVE_LOOPIF */
+
+void
+netif_init(void)
+{
+#if LWIP_HAVE_LOOPIF
+  ip_addr_t loop_ipaddr, loop_netmask, loop_gw;
+  IP4_ADDR(&loop_gw, 127,0,0,1);
+  IP4_ADDR(&loop_ipaddr, 127,0,0,1);
+  IP4_ADDR(&loop_netmask, 255,0,0,0);
+
+#if NO_SYS
+  netif_add(&loop_netif, &loop_ipaddr, &loop_netmask, &loop_gw, NULL, netif_loopif_init, ip_input);
+#else  /* NO_SYS */
+  netif_add(&loop_netif, &loop_ipaddr, &loop_netmask, &loop_gw, NULL, netif_loopif_init, tcpip_input);
+#endif /* NO_SYS */
+  netif_set_up(&loop_netif);
+
+#endif /* LWIP_HAVE_LOOPIF */
+}
+
+/**
+ * Add a network interface to the list of lwIP netifs.
+ *
+ * @param netif a pre-allocated netif structure
+ * @param ipaddr IP address for the new netif
+ * @param netmask network mask for the new netif
+ * @param gw default gateway IP address for the new netif
+ * @param state opaque data passed to the new netif
+ * @param init callback function that initializes the interface
+ * @param input callback function that is called to pass
+ * ingress packets up in the protocol layer stack.
+ *
+ * @return netif, or NULL if failed.
+ */
+struct netif *
+netif_add(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask,
+  ip_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input)
+{
+#if LWIP_IPV6
+  u32_t i;
+#endif
+
+  LWIP_ASSERT("No init function given", init != NULL);
+
+  /* reset new interface configuration state */
+  ip_addr_set_zero(&netif->ip_addr);
+  ip_addr_set_zero(&netif->netmask);
+  ip_addr_set_zero(&netif->gw);
+#if LWIP_IPV6
+  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+    ip6_addr_set_zero(&netif->ip6_addr[i]);
+    netif_ip6_addr_set_state(netif, i, IP6_ADDR_INVALID);
+  }
+  netif->output_ip6 = netif_null_output_ip6;
+#endif /* LWIP_IPV6 */
+  netif->flags = 0;
+#if LWIP_DHCP
+  /* netif not under DHCP control by default */
+  netif->dhcp = NULL;
+#endif /* LWIP_DHCP */
+#if LWIP_AUTOIP
+  /* netif not under AutoIP control by default */
+  netif->autoip = NULL;
+#endif /* LWIP_AUTOIP */
+#if LWIP_IPV6_AUTOCONFIG
+  /* IPv6 address autoconfiguration not enabled by default */
+  netif->ip6_autoconfig_enabled = 0;
+#endif /* LWIP_IPV6_AUTOCONFIG */
+#if LWIP_IPV6_SEND_ROUTER_SOLICIT
+  netif->rs_count = LWIP_ND6_MAX_MULTICAST_SOLICIT;
+#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
+#if LWIP_IPV6_DHCP6
+  /* netif not under DHCPv6 control by default */
+  netif->dhcp6 = NULL;
+#endif /* LWIP_IPV6_DHCP6 */
+#if LWIP_NETIF_STATUS_CALLBACK
+  netif->status_callback = NULL;
+#endif /* LWIP_NETIF_STATUS_CALLBACK */
+#if LWIP_NETIF_LINK_CALLBACK
+  netif->link_callback = NULL;
+#endif /* LWIP_NETIF_LINK_CALLBACK */
+#if LWIP_IGMP
+  netif->igmp_mac_filter = NULL;
+#endif /* LWIP_IGMP */
+#if LWIP_IPV6 && LWIP_IPV6_MLD
+  netif->mld_mac_filter = NULL;
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+#if ENABLE_LOOPBACK
+  netif->loop_first = NULL;
+  netif->loop_last = NULL;
+#endif /* ENABLE_LOOPBACK */
+
+  /* remember netif specific state information data */
+  netif->state = state;
+  #ifdef PSIPHON
+  /* tun2socks as a library, with a multi-run lifetime,
+     may invoke this multiple times */
+  netif->num = netif_num;
+#else
+  netif->num = netif_num++;
+#endif /* PSIPHON */
+  netif->input = input;
+  NETIF_SET_HWADDRHINT(netif, NULL);
+#if ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS
+  netif->loop_cnt_current = 0;
+#endif /* ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS */
+
+  netif_set_addr(netif, ipaddr, netmask, gw);
+
+  /* call user specified initialization function for netif */
+  if (init(netif) != ERR_OK) {
+    return NULL;
+  }
+
+  /* add this netif to the list */
+  netif->next = netif_list;
+  netif_list = netif;
+  snmp_inc_iflist();
+
+#if LWIP_IGMP
+  /* start IGMP processing */
+  if (netif->flags & NETIF_FLAG_IGMP) {
+    igmp_start(netif);
+  }
+#endif /* LWIP_IGMP */
+
+  LWIP_DEBUGF(NETIF_DEBUG, ("netif: added interface %c%c IP addr ",
+    netif->name[0], netif->name[1]));
+  ip_addr_debug_print(NETIF_DEBUG, ipaddr);
+  LWIP_DEBUGF(NETIF_DEBUG, (" netmask "));
+  ip_addr_debug_print(NETIF_DEBUG, netmask);
+  LWIP_DEBUGF(NETIF_DEBUG, (" gw "));
+  ip_addr_debug_print(NETIF_DEBUG, gw);
+  LWIP_DEBUGF(NETIF_DEBUG, ("\n"));
+  return netif;
+}
+
+/**
+ * Change IP address configuration for a network interface (including netmask
+ * and default gateway).
+ *
+ * @param netif the network interface to change
+ * @param ipaddr the new IP address
+ * @param netmask the new netmask
+ * @param gw the new default gateway
+ */
+void
+netif_set_addr(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask,
+    ip_addr_t *gw)
+{
+  netif_set_ipaddr(netif, ipaddr);
+  netif_set_netmask(netif, netmask);
+  netif_set_gw(netif, gw);
+}
+
+/**
+ * Remove a network interface from the list of lwIP netifs.
+ *
+ * @param netif the network interface to remove
+ */
+void
+netif_remove(struct netif *netif)
+{
+  if (netif == NULL) {
+    return;
+  }
+
+#if LWIP_IGMP
+  /* stop IGMP processing */
+  if (netif->flags & NETIF_FLAG_IGMP) {
+    igmp_stop(netif);
+  }
+#endif /* LWIP_IGMP */
+#if LWIP_IPV6 && LWIP_IPV6_MLD
+  /* stop MLD processing */
+  mld6_stop(netif);
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+  if (netif_is_up(netif)) {
+    /* set netif down before removing (call callback function) */
+    netif_set_down(netif);
+  }
+
+  snmp_delete_ipaddridx_tree(netif);
+
+  /*  is it the first netif? */
+  if (netif_list == netif) {
+    netif_list = netif->next;
+  } else {
+    /*  look for netif further down the list */
+    struct netif * tmpNetif;
+    for (tmpNetif = netif_list; tmpNetif != NULL; tmpNetif = tmpNetif->next) {
+      if (tmpNetif->next == netif) {
+        tmpNetif->next = netif->next;
+        break;
+      }
+    }
+    if (tmpNetif == NULL)
+      return; /*  we didn't find any netif today */
+  }
+  snmp_dec_iflist();
+  /* this netif is default? */
+  if (netif_default == netif) {
+    /* reset default netif */
+    netif_set_default(NULL);
+  }
+#if LWIP_NETIF_REMOVE_CALLBACK
+  if (netif->remove_callback) {
+    netif->remove_callback(netif);
+  }
+#endif /* LWIP_NETIF_REMOVE_CALLBACK */
+  LWIP_DEBUGF( NETIF_DEBUG, ("netif_remove: removed netif\n") );
+}
+
+/**
+ * Find a network interface by searching for its name
+ *
+ * @param name the name of the netif (like netif->name) plus concatenated number
+ * in ascii representation (e.g. 'en0')
+ */
+struct netif *
+netif_find(char *name)
+{
+  struct netif *netif;
+  u8_t num;
+
+  if (name == NULL) {
+    return NULL;
+  }
+
+  num = name[2] - '0';
+
+  for(netif = netif_list; netif != NULL; netif = netif->next) {
+    if (num == netif->num &&
+       name[0] == netif->name[0] &&
+       name[1] == netif->name[1]) {
+      LWIP_DEBUGF(NETIF_DEBUG, ("netif_find: found %c%c\n", name[0], name[1]));
+      return netif;
+    }
+  }
+  LWIP_DEBUGF(NETIF_DEBUG, ("netif_find: didn't find %c%c\n", name[0], name[1]));
+  return NULL;
+}
+
+int netif_is_named (struct netif *netif, const char name[3])
+{
+    u8_t num = name[2] - '0';
+    
+    return (!memcmp(netif->name, name, 2) && netif->num == num);
+}
+
+/**
+ * Change the IP address of a network interface
+ *
+ * @param netif the network interface to change
+ * @param ipaddr the new IP address
+ *
+ * @note call netif_set_addr() if you also want to change netmask and
+ * default gateway
+ */
+void
+netif_set_ipaddr(struct netif *netif, ip_addr_t *ipaddr)
+{
+  /* TODO: Handling of obsolete pcbs */
+  /* See:  http://mail.gnu.org/archive/html/lwip-users/2003-03/msg00118.html */
+#if LWIP_TCP
+  struct tcp_pcb *pcb;
+  struct tcp_pcb_listen *lpcb;
+
+  /* address is actually being changed? */
+  if (ipaddr && (ip_addr_cmp(ipaddr, &(netif->ip_addr))) == 0) {
+    /* extern struct tcp_pcb *tcp_active_pcbs; defined by tcp.h */
+    LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_STATE, ("netif_set_ipaddr: netif address being changed\n"));
+    pcb = tcp_active_pcbs;
+    while (pcb != NULL) {
+      /* PCB bound to current local interface address? */
+      if (ip_addr_cmp(ipX_2_ip(&pcb->local_ip), &(netif->ip_addr))
+#if LWIP_AUTOIP
+        /* connections to link-local addresses must persist (RFC3927 ch. 1.9) */
+        && !ip_addr_islinklocal(ipX_2_ip(&pcb->local_ip))
+#endif /* LWIP_AUTOIP */
+        ) {
+        /* this connection must be aborted */
+        struct tcp_pcb *next = pcb->next;
+        LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_STATE, ("netif_set_ipaddr: aborting TCP pcb %p\n", (void *)pcb));
+        tcp_abort(pcb);
+        pcb = next;
+      } else {
+        pcb = pcb->next;
+      }
+    }
+    for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
+      /* PCB bound to current local interface address? */
+      if ((!(ip_addr_isany(ipX_2_ip(&lpcb->local_ip)))) &&
+          (ip_addr_cmp(ipX_2_ip(&lpcb->local_ip), &(netif->ip_addr)))) {
+        /* The PCB is listening to the old ipaddr and
+         * is set to listen to the new one instead */
+        ip_addr_set(ipX_2_ip(&lpcb->local_ip), ipaddr);
+      }
+    }
+  }
+#endif
+  snmp_delete_ipaddridx_tree(netif);
+  snmp_delete_iprteidx_tree(0,netif);
+  /* set new IP address to netif */
+  ip_addr_set(&(netif->ip_addr), ipaddr);
+  snmp_insert_ipaddridx_tree(netif);
+  snmp_insert_iprteidx_tree(0,netif);
+
+  LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("netif: IP address of interface %c%c set to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+    netif->name[0], netif->name[1],
+    ip4_addr1_16(&netif->ip_addr),
+    ip4_addr2_16(&netif->ip_addr),
+    ip4_addr3_16(&netif->ip_addr),
+    ip4_addr4_16(&netif->ip_addr)));
+}
+
+/**
+ * Change the default gateway for a network interface
+ *
+ * @param netif the network interface to change
+ * @param gw the new default gateway
+ *
+ * @note call netif_set_addr() if you also want to change ip address and netmask
+ */
+void
+netif_set_gw(struct netif *netif, ip_addr_t *gw)
+{
+  ip_addr_set(&(netif->gw), gw);
+  LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("netif: GW address of interface %c%c set to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+    netif->name[0], netif->name[1],
+    ip4_addr1_16(&netif->gw),
+    ip4_addr2_16(&netif->gw),
+    ip4_addr3_16(&netif->gw),
+    ip4_addr4_16(&netif->gw)));
+}
+
+void netif_set_pretend_tcp (struct netif *netif, u8_t pretend)
+{
+    if (pretend) {
+        netif->flags |= NETIF_FLAG_PRETEND_TCP;
+    } else {
+        netif->flags &= ~NETIF_FLAG_PRETEND_TCP;
+    }
+}
+
+/**
+ * Change the netmask of a network interface
+ *
+ * @param netif the network interface to change
+ * @param netmask the new netmask
+ *
+ * @note call netif_set_addr() if you also want to change ip address and
+ * default gateway
+ */
+void
+netif_set_netmask(struct netif *netif, ip_addr_t *netmask)
+{
+  snmp_delete_iprteidx_tree(0, netif);
+  /* set new netmask to netif */
+  ip_addr_set(&(netif->netmask), netmask);
+  snmp_insert_iprteidx_tree(0, netif);
+  LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("netif: netmask of interface %c%c set to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+    netif->name[0], netif->name[1],
+    ip4_addr1_16(&netif->netmask),
+    ip4_addr2_16(&netif->netmask),
+    ip4_addr3_16(&netif->netmask),
+    ip4_addr4_16(&netif->netmask)));
+}
+
+/**
+ * Set a network interface as the default network interface
+ * (used to output all packets for which no specific route is found)
+ *
+ * @param netif the default network interface
+ */
+void
+netif_set_default(struct netif *netif)
+{
+  if (netif == NULL) {
+    /* remove default route */
+    snmp_delete_iprteidx_tree(1, netif);
+  } else {
+    /* install default route */
+    snmp_insert_iprteidx_tree(1, netif);
+  }
+  netif_default = netif;
+  LWIP_DEBUGF(NETIF_DEBUG, ("netif: setting default interface %c%c\n",
+           netif ? netif->name[0] : '\'', netif ? netif->name[1] : '\''));
+}
+
+/**
+ * Bring an interface up, available for processing
+ * traffic.
+ * 
+ * @note: Enabling DHCP on a down interface will make it come
+ * up once configured.
+ * 
+ * @see dhcp_start()
+ */ 
+void netif_set_up(struct netif *netif)
+{
+  if (!(netif->flags & NETIF_FLAG_UP)) {
+    netif->flags |= NETIF_FLAG_UP;
+    
+#if LWIP_SNMP
+    snmp_get_sysuptime(&netif->ts);
+#endif /* LWIP_SNMP */
+
+    NETIF_STATUS_CALLBACK(netif);
+
+    if (netif->flags & NETIF_FLAG_LINK_UP) {
+#if LWIP_ARP
+      /* For Ethernet network interfaces, we would like to send a "gratuitous ARP" */ 
+      if (netif->flags & (NETIF_FLAG_ETHARP)) {
+        etharp_gratuitous(netif);
+      }
+#endif /* LWIP_ARP */
+
+#if LWIP_IGMP
+      /* resend IGMP memberships */
+      if (netif->flags & NETIF_FLAG_IGMP) {
+        igmp_report_groups( netif);
+      }
+#endif /* LWIP_IGMP */
+#if LWIP_IPV6 && LWIP_IPV6_MLD
+      /* send mld memberships */
+      mld6_report_groups( netif);
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+
+#if LWIP_IPV6_SEND_ROUTER_SOLICIT
+      /* Send Router Solicitation messages. */
+      netif->rs_count = LWIP_ND6_MAX_MULTICAST_SOLICIT;
+#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
+
+    }
+  }
+}
+
+/**
+ * Bring an interface down, disabling any traffic processing.
+ *
+ * @note: Enabling DHCP on a down interface will make it come
+ * up once configured.
+ * 
+ * @see dhcp_start()
+ */ 
+void netif_set_down(struct netif *netif)
+{
+  if (netif->flags & NETIF_FLAG_UP) {
+    netif->flags &= ~NETIF_FLAG_UP;
+#if LWIP_SNMP
+    snmp_get_sysuptime(&netif->ts);
+#endif
+
+#if LWIP_ARP
+    if (netif->flags & NETIF_FLAG_ETHARP) {
+      etharp_cleanup_netif(netif);
+    }
+#endif /* LWIP_ARP */
+    NETIF_STATUS_CALLBACK(netif);
+  }
+}
+
+#if LWIP_NETIF_STATUS_CALLBACK
+/**
+ * Set callback to be called when interface is brought up/down
+ */
+void netif_set_status_callback(struct netif *netif, netif_status_callback_fn status_callback)
+{
+  if (netif) {
+    netif->status_callback = status_callback;
+  }
+}
+#endif /* LWIP_NETIF_STATUS_CALLBACK */
+
+#if LWIP_NETIF_REMOVE_CALLBACK
+/**
+ * Set callback to be called when the interface has been removed
+ */
+void
+netif_set_remove_callback(struct netif *netif, netif_status_callback_fn remove_callback)
+{
+  if (netif) {
+    netif->remove_callback = remove_callback;
+  }
+}
+#endif /* LWIP_NETIF_REMOVE_CALLBACK */
+
+/**
+ * Called by a driver when its link goes up
+ */
+void netif_set_link_up(struct netif *netif )
+{
+  if (!(netif->flags & NETIF_FLAG_LINK_UP)) {
+    netif->flags |= NETIF_FLAG_LINK_UP;
+
+#if LWIP_DHCP
+    if (netif->dhcp) {
+      dhcp_network_changed(netif);
+    }
+#endif /* LWIP_DHCP */
+
+#if LWIP_AUTOIP
+    if (netif->autoip) {
+      autoip_network_changed(netif);
+    }
+#endif /* LWIP_AUTOIP */
+
+    if (netif->flags & NETIF_FLAG_UP) {
+#if LWIP_ARP
+      /* For Ethernet network interfaces, we would like to send a "gratuitous ARP" */ 
+      if (netif->flags & NETIF_FLAG_ETHARP) {
+        etharp_gratuitous(netif);
+      }
+#endif /* LWIP_ARP */
+
+#if LWIP_IGMP
+      /* resend IGMP memberships */
+      if (netif->flags & NETIF_FLAG_IGMP) {
+        igmp_report_groups( netif);
+      }
+#endif /* LWIP_IGMP */
+#if LWIP_IPV6 && LWIP_IPV6_MLD
+      /* send mld memberships */
+      mld6_report_groups( netif);
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+    }
+    NETIF_LINK_CALLBACK(netif);
+  }
+}
+
+/**
+ * Called by a driver when its link goes down
+ */
+void netif_set_link_down(struct netif *netif )
+{
+  if (netif->flags & NETIF_FLAG_LINK_UP) {
+    netif->flags &= ~NETIF_FLAG_LINK_UP;
+    NETIF_LINK_CALLBACK(netif);
+  }
+}
+
+#if LWIP_NETIF_LINK_CALLBACK
+/**
+ * Set callback to be called when link is brought up/down
+ */
+void netif_set_link_callback(struct netif *netif, netif_status_callback_fn link_callback)
+{
+  if (netif) {
+    netif->link_callback = link_callback;
+  }
+}
+#endif /* LWIP_NETIF_LINK_CALLBACK */
+
+#if ENABLE_LOOPBACK
+/**
+ * Send an IP packet to be received on the same netif (loopif-like).
+ * The pbuf is simply copied and handed back to netif->input.
+ * In multithreaded mode, this is done directly since netif->input must put
+ * the packet on a queue.
+ * In callback mode, the packet is put on an internal queue and is fed to
+ * netif->input by netif_poll().
+ *
+ * @param netif the lwip network interface structure
+ * @param p the (IP) packet to 'send'
+ * @param ipaddr the ip address to send the packet to (not used)
+ * @return ERR_OK if the packet has been sent
+ *         ERR_MEM if the pbuf used to copy the packet couldn't be allocated
+ */
+err_t
+netif_loop_output(struct netif *netif, struct pbuf *p,
+       ip_addr_t *ipaddr)
+{
+  struct pbuf *r;
+  err_t err;
+  struct pbuf *last;
+#if LWIP_LOOPBACK_MAX_PBUFS
+  u8_t clen = 0;
+#endif /* LWIP_LOOPBACK_MAX_PBUFS */
+  /* If we have a loopif, SNMP counters are adjusted for it,
+   * if not they are adjusted for 'netif'. */
+#if LWIP_SNMP
+#if LWIP_HAVE_LOOPIF
+  struct netif *stats_if = &loop_netif;
+#else /* LWIP_HAVE_LOOPIF */
+  struct netif *stats_if = netif;
+#endif /* LWIP_HAVE_LOOPIF */
+#endif /* LWIP_SNMP */
+  SYS_ARCH_DECL_PROTECT(lev);
+  LWIP_UNUSED_ARG(ipaddr);
+
+  /* Allocate a new pbuf */
+  r = pbuf_alloc(PBUF_LINK, p->tot_len, PBUF_RAM);
+  if (r == NULL) {
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.drop);
+    snmp_inc_ifoutdiscards(stats_if);
+    return ERR_MEM;
+  }
+#if LWIP_LOOPBACK_MAX_PBUFS
+  clen = pbuf_clen(r);
+  /* check for overflow or too many pbuf on queue */
+  if(((netif->loop_cnt_current + clen) < netif->loop_cnt_current) ||
+     ((netif->loop_cnt_current + clen) > LWIP_LOOPBACK_MAX_PBUFS)) {
+    pbuf_free(r);
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.drop);
+    snmp_inc_ifoutdiscards(stats_if);
+    return ERR_MEM;
+  }
+  netif->loop_cnt_current += clen;
+#endif /* LWIP_LOOPBACK_MAX_PBUFS */
+
+  /* Copy the whole pbuf queue p into the single pbuf r */
+  if ((err = pbuf_copy(r, p)) != ERR_OK) {
+    pbuf_free(r);
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.drop);
+    snmp_inc_ifoutdiscards(stats_if);
+    return err;
+  }
+
+  /* Put the packet on a linked list which gets emptied through calling
+     netif_poll(). */
+
+  /* let last point to the last pbuf in chain r */
+  for (last = r; last->next != NULL; last = last->next);
+
+  SYS_ARCH_PROTECT(lev);
+  if(netif->loop_first != NULL) {
+    LWIP_ASSERT("if first != NULL, last must also be != NULL", netif->loop_last != NULL);
+    netif->loop_last->next = r;
+    netif->loop_last = last;
+  } else {
+    netif->loop_first = r;
+    netif->loop_last = last;
+  }
+  SYS_ARCH_UNPROTECT(lev);
+
+  LINK_STATS_INC(link.xmit);
+  snmp_add_ifoutoctets(stats_if, p->tot_len);
+  snmp_inc_ifoutucastpkts(stats_if);
+
+#if LWIP_NETIF_LOOPBACK_MULTITHREADING
+  /* For multithreading environment, schedule a call to netif_poll */
+  tcpip_callback((tcpip_callback_fn)netif_poll, netif);
+#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
+
+  return ERR_OK;
+}
+
+/**
+ * Call netif_poll() in the main loop of your application. This is to prevent
+ * reentering non-reentrant functions like tcp_input(). Packets passed to
+ * netif_loop_output() are put on a list that is passed to netif->input() by
+ * netif_poll().
+ */
+void
+netif_poll(struct netif *netif)
+{
+  struct pbuf *in;
+  /* If we have a loopif, SNMP counters are adjusted for it,
+   * if not they are adjusted for 'netif'. */
+#if LWIP_SNMP
+#if LWIP_HAVE_LOOPIF
+  struct netif *stats_if = &loop_netif;
+#else /* LWIP_HAVE_LOOPIF */
+  struct netif *stats_if = netif;
+#endif /* LWIP_HAVE_LOOPIF */
+#endif /* LWIP_SNMP */
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  do {
+    /* Get a packet from the list. With SYS_LIGHTWEIGHT_PROT=1, this is protected */
+    SYS_ARCH_PROTECT(lev);
+    in = netif->loop_first;
+    if (in != NULL) {
+      struct pbuf *in_end = in;
+#if LWIP_LOOPBACK_MAX_PBUFS
+      u8_t clen = pbuf_clen(in);
+      /* adjust the number of pbufs on queue */
+      LWIP_ASSERT("netif->loop_cnt_current underflow",
+        ((netif->loop_cnt_current - clen) < netif->loop_cnt_current));
+      netif->loop_cnt_current -= clen;
+#endif /* LWIP_LOOPBACK_MAX_PBUFS */
+      while (in_end->len != in_end->tot_len) {
+        LWIP_ASSERT("bogus pbuf: len != tot_len but next == NULL!", in_end->next != NULL);
+        in_end = in_end->next;
+      }
+      /* 'in_end' now points to the last pbuf from 'in' */
+      if (in_end == netif->loop_last) {
+        /* this was the last pbuf in the list */
+        netif->loop_first = netif->loop_last = NULL;
+      } else {
+        /* pop the pbuf off the list */
+        netif->loop_first = in_end->next;
+        LWIP_ASSERT("should not be null since first != last!", netif->loop_first != NULL);
+      }
+      /* De-queue the pbuf from its successors on the 'loop_' list. */
+      in_end->next = NULL;
+    }
+    SYS_ARCH_UNPROTECT(lev);
+
+    if (in != NULL) {
+      LINK_STATS_INC(link.recv);
+      snmp_add_ifinoctets(stats_if, in->tot_len);
+      snmp_inc_ifinucastpkts(stats_if);
+      /* loopback packets are always IP packets! */
+      if (ip_input(in, netif) != ERR_OK) {
+        pbuf_free(in);
+      }
+      /* Don't reference the packet any more! */
+      in = NULL;
+    }
+  /* go on while there is a packet on the list */
+  } while (netif->loop_first != NULL);
+}
+
+#if !LWIP_NETIF_LOOPBACK_MULTITHREADING
+/**
+ * Calls netif_poll() for every netif on the netif_list.
+ */
+void
+netif_poll_all(void)
+{
+  struct netif *netif = netif_list;
+  /* loop through netifs */
+  while (netif != NULL) {
+    netif_poll(netif);
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+}
+#endif /* !LWIP_NETIF_LOOPBACK_MULTITHREADING */
+#endif /* ENABLE_LOOPBACK */
+
+#if LWIP_IPV6
+s8_t
+netif_matches_ip6_addr(struct netif * netif, ip6_addr_t * ip6addr)
+{
+  s8_t i;
+  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+    if (ip6_addr_cmp(netif_ip6_addr(netif, i), ip6addr)) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+void
+netif_create_ip6_linklocal_address(struct netif * netif, u8_t from_mac_48bit)
+{
+  u8_t i, addr_index;
+
+  /* Link-local prefix. */
+  netif->ip6_addr[0].addr[0] = PP_HTONL(0xfe800000ul);
+  netif->ip6_addr[0].addr[1] = 0;
+
+  /* Generate interface ID. */
+  if (from_mac_48bit) {
+    /* Assume hwaddr is a 48-bit IEEE 802 MAC. Convert to EUI-64 address. Complement Group bit. */
+    netif->ip6_addr[0].addr[2] = htonl((((u32_t)(netif->hwaddr[0] ^ 0x02)) << 24) |
+        ((u32_t)(netif->hwaddr[1]) << 16) |
+        ((u32_t)(netif->hwaddr[2]) << 8) |
+        (0xff));
+    netif->ip6_addr[0].addr[3] = htonl((0xfeul << 24) |
+        ((u32_t)(netif->hwaddr[3]) << 16) |
+        ((u32_t)(netif->hwaddr[4]) << 8) |
+        (netif->hwaddr[5]));
+  }
+  else {
+    /* Use hwaddr directly as interface ID. */
+    netif->ip6_addr[0].addr[2] = 0;
+    netif->ip6_addr[0].addr[3] = 0;
+
+    addr_index = 3;
+    for (i = 0; i < 8; i++) {
+      if (i == 4) {
+        addr_index--;
+      }
+      netif->ip6_addr[0].addr[addr_index] |= ((u32_t)(netif->hwaddr[netif->hwaddr_len - i - 1])) << (8 * (i & 0x03));
+    }
+  }
+
+  /* Set address state. */
+#if LWIP_IPV6_DUP_DETECT_ATTEMPTS
+  /* Will perform duplicate address detection (DAD). */
+  netif->ip6_addr_state[0] = IP6_ADDR_TENTATIVE;
+#else
+  /* Consider address valid. */
+  netif->ip6_addr_state[0] = IP6_ADDR_PREFERRED;
+#endif /* LWIP_IPV6_AUTOCONFIG */
+}
+
+static err_t
+netif_null_output_ip6(struct netif *netif, struct pbuf *p, ip6_addr_t *ipaddr)
+{
+    (void)netif;
+    (void)p;
+    (void)ipaddr;
+
+    return ERR_IF;
+}
+#endif /* LWIP_IPV6 */
diff --git a/external/badvpn_dns/lwip/src/core/pbuf.c b/external/badvpn_dns/lwip/src/core/pbuf.c
new file mode 100644
index 0000000..1e5e53b
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/pbuf.c
@@ -0,0 +1,1179 @@
+/**
+ * @file
+ * Packet buffer management
+ *
+ * Packets are built from the pbuf data structure. It supports dynamic
+ * memory allocation for packet contents or can reference externally
+ * managed packet contents both in RAM and ROM. Quick allocation for
+ * incoming packets is provided through pools with fixed sized pbufs.
+ *
+ * A packet may span over multiple pbufs, chained as a singly linked
+ * list. This is called a "pbuf chain".
+ *
+ * Multiple packets may be queued, also using this singly linked list.
+ * This is called a "packet queue".
+ * 
+ * So, a packet queue consists of one or more pbuf chains, each of
+ * which consist of one or more pbufs. CURRENTLY, PACKET QUEUES ARE
+ * NOT SUPPORTED!!! Use helper structs to queue multiple packets.
+ * 
+ * The differences between a pbuf chain and a packet queue are very
+ * precise but subtle. 
+ *
+ * The last pbuf of a packet has a ->tot_len field that equals the
+ * ->len field. It can be found by traversing the list. If the last
+ * pbuf of a packet has a ->next field other than NULL, more packets
+ * are on the queue.
+ *
+ * Therefore, looping through a pbuf of a single packet, has an
+ * loop end condition (tot_len == p->len), NOT (next == NULL).
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/stats.h"
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/pbuf.h"
+#include "lwip/sys.h"
+#include "arch/perf.h"
+#if LWIP_TCP && TCP_QUEUE_OOSEQ
+#include "lwip/tcp_impl.h"
+#endif
+#if LWIP_CHECKSUM_ON_COPY
+#include "lwip/inet_chksum.h"
+#endif
+
+#include <string.h>
+
+#define SIZEOF_STRUCT_PBUF        LWIP_MEM_ALIGN_SIZE(sizeof(struct pbuf))
+/* Since the pool is created in memp, PBUF_POOL_BUFSIZE will be automatically
+   aligned there. Therefore, PBUF_POOL_BUFSIZE_ALIGNED can be used here. */
+#define PBUF_POOL_BUFSIZE_ALIGNED LWIP_MEM_ALIGN_SIZE(PBUF_POOL_BUFSIZE)
+
+#if !LWIP_TCP || !TCP_QUEUE_OOSEQ || !PBUF_POOL_FREE_OOSEQ
+#define PBUF_POOL_IS_EMPTY()
+#else /* !LWIP_TCP || !TCP_QUEUE_OOSEQ || !PBUF_POOL_FREE_OOSEQ */
+
+#if !NO_SYS
+#ifndef PBUF_POOL_FREE_OOSEQ_QUEUE_CALL
+#include "lwip/tcpip.h"
+#define PBUF_POOL_FREE_OOSEQ_QUEUE_CALL()  do { \
+  if(tcpip_callback_with_block(pbuf_free_ooseq_callback, NULL, 0) != ERR_OK) { \
+      SYS_ARCH_PROTECT(old_level); \
+      pbuf_free_ooseq_pending = 0; \
+      SYS_ARCH_UNPROTECT(old_level); \
+  } } while(0)
+#endif /* PBUF_POOL_FREE_OOSEQ_QUEUE_CALL */
+#endif /* !NO_SYS */
+
+volatile u8_t pbuf_free_ooseq_pending;
+#define PBUF_POOL_IS_EMPTY() pbuf_pool_is_empty()
+
+/**
+ * Attempt to reclaim some memory from queued out-of-sequence TCP segments
+ * if we run out of pool pbufs. It's better to give priority to new packets
+ * if we're running out.
+ *
+ * This must be done in the correct thread context therefore this function
+ * can only be used with NO_SYS=0 and through tcpip_callback.
+ */
+#if !NO_SYS
+static
+#endif /* !NO_SYS */
+void
+pbuf_free_ooseq(void)
+{
+  struct tcp_pcb* pcb;
+  SYS_ARCH_DECL_PROTECT(old_level);
+
+  SYS_ARCH_PROTECT(old_level);
+  pbuf_free_ooseq_pending = 0;
+  SYS_ARCH_UNPROTECT(old_level);
+
+  for (pcb = tcp_active_pcbs; NULL != pcb; pcb = pcb->next) {
+    if (NULL != pcb->ooseq) {
+      /** Free the ooseq pbufs of one PCB only */
+      LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free_ooseq: freeing out-of-sequence pbufs\n"));
+      tcp_segs_free(pcb->ooseq);
+      pcb->ooseq = NULL;
+      return;
+    }
+  }
+}
+
+#if !NO_SYS
+/**
+ * Just a callback function for tcpip_timeout() that calls pbuf_free_ooseq().
+ */
+static void
+pbuf_free_ooseq_callback(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  pbuf_free_ooseq();
+}
+#endif /* !NO_SYS */
+
+/** Queue a call to pbuf_free_ooseq if not already queued. */
+static void
+pbuf_pool_is_empty(void)
+{
+#ifndef PBUF_POOL_FREE_OOSEQ_QUEUE_CALL
+  SYS_ARCH_DECL_PROTECT(old_level);
+  SYS_ARCH_PROTECT(old_level);
+  pbuf_free_ooseq_pending = 1;
+  SYS_ARCH_UNPROTECT(old_level);
+#else /* PBUF_POOL_FREE_OOSEQ_QUEUE_CALL */
+  u8_t queued;
+  SYS_ARCH_DECL_PROTECT(old_level);
+  SYS_ARCH_PROTECT(old_level);
+  queued = pbuf_free_ooseq_pending;
+  pbuf_free_ooseq_pending = 1;
+  SYS_ARCH_UNPROTECT(old_level);
+
+  if(!queued) {
+    /* queue a call to pbuf_free_ooseq if not already queued */
+    PBUF_POOL_FREE_OOSEQ_QUEUE_CALL();
+  }
+#endif /* PBUF_POOL_FREE_OOSEQ_QUEUE_CALL */
+}
+#endif /* !LWIP_TCP || !TCP_QUEUE_OOSEQ || !PBUF_POOL_FREE_OOSEQ */
+
+/**
+ * Allocates a pbuf of the given type (possibly a chain for PBUF_POOL type).
+ *
+ * The actual memory allocated for the pbuf is determined by the
+ * layer at which the pbuf is allocated and the requested size
+ * (from the size parameter).
+ *
+ * @param layer flag to define header size
+ * @param length size of the pbuf's payload
+ * @param type this parameter decides how and where the pbuf
+ * should be allocated as follows:
+ *
+ * - PBUF_RAM: buffer memory for pbuf is allocated as one large
+ *             chunk. This includes protocol headers as well.
+ * - PBUF_ROM: no buffer memory is allocated for the pbuf, even for
+ *             protocol headers. Additional headers must be prepended
+ *             by allocating another pbuf and chain in to the front of
+ *             the ROM pbuf. It is assumed that the memory used is really
+ *             similar to ROM in that it is immutable and will not be
+ *             changed. Memory which is dynamic should generally not
+ *             be attached to PBUF_ROM pbufs. Use PBUF_REF instead.
+ * - PBUF_REF: no buffer memory is allocated for the pbuf, even for
+ *             protocol headers. It is assumed that the pbuf is only
+ *             being used in a single thread. If the pbuf gets queued,
+ *             then pbuf_take should be called to copy the buffer.
+ * - PBUF_POOL: the pbuf is allocated as a pbuf chain, with pbufs from
+ *              the pbuf pool that is allocated during pbuf_init().
+ *
+ * @return the allocated pbuf. If multiple pbufs where allocated, this
+ * is the first pbuf of a pbuf chain.
+ */
+struct pbuf *
+pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
+{
+  struct pbuf *p, *q, *r;
+  u16_t offset;
+  s32_t rem_len; /* remaining length */
+  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length));
+
+  /* determine header offset */
+  switch (layer) {
+  case PBUF_TRANSPORT:
+    /* add room for transport (often TCP) layer header */
+    offset = PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN;
+    break;
+  case PBUF_IP:
+    /* add room for IP layer header */
+    offset = PBUF_LINK_HLEN + PBUF_IP_HLEN;
+    break;
+  case PBUF_LINK:
+    /* add room for link layer header */
+    offset = PBUF_LINK_HLEN;
+    break;
+  case PBUF_RAW:
+    offset = 0;
+    break;
+  default:
+    LWIP_ASSERT("pbuf_alloc: bad pbuf layer", 0);
+    return NULL;
+  }
+
+  switch (type) {
+  case PBUF_POOL:
+    /* allocate head of pbuf chain into p */
+    p = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
+    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc: allocated pbuf %p\n", (void *)p));
+    if (p == NULL) {
+      PBUF_POOL_IS_EMPTY();
+      return NULL;
+    }
+    p->type = type;
+    p->next = NULL;
+
+    /* make the payload pointer point 'offset' bytes into pbuf data memory */
+    p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + (SIZEOF_STRUCT_PBUF + offset)));
+    LWIP_ASSERT("pbuf_alloc: pbuf p->payload properly aligned",
+            ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
+    /* the total length of the pbuf chain is the requested size */
+    p->tot_len = length;
+    /* set the length of the first pbuf in the chain */
+    p->len = LWIP_MIN(length, PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset));
+    LWIP_ASSERT("check p->payload + p->len does not overflow pbuf",
+                ((u8_t*)p->payload + p->len <=
+                 (u8_t*)p + SIZEOF_STRUCT_PBUF + PBUF_POOL_BUFSIZE_ALIGNED));
+    LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT",
+      (PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0 );
+    /* set reference count (needed here in case we fail) */
+    p->ref = 1;
+
+    /* now allocate the tail of the pbuf chain */
+
+    /* remember first pbuf for linkage in next iteration */
+    r = p;
+    /* remaining length to be allocated */
+    rem_len = length - p->len;
+    /* any remaining pbufs to be allocated? */
+    while (rem_len > 0) {
+      q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
+      if (q == NULL) {
+        PBUF_POOL_IS_EMPTY();
+        /* free chain so far allocated */
+        pbuf_free(p);
+        /* bail out unsuccesfully */
+        return NULL;
+      }
+      q->type = type;
+      q->flags = 0;
+      q->next = NULL;
+      /* make previous pbuf point to this pbuf */
+      r->next = q;
+      /* set total length of this pbuf and next in chain */
+      LWIP_ASSERT("rem_len < max_u16_t", rem_len < 0xffff);
+      q->tot_len = (u16_t)rem_len;
+      /* this pbuf length is pool size, unless smaller sized tail */
+      q->len = LWIP_MIN((u16_t)rem_len, PBUF_POOL_BUFSIZE_ALIGNED);
+      q->payload = (void *)((u8_t *)q + SIZEOF_STRUCT_PBUF);
+      LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned",
+              ((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0);
+      LWIP_ASSERT("check p->payload + p->len does not overflow pbuf",
+                  ((u8_t*)p->payload + p->len <=
+                   (u8_t*)p + SIZEOF_STRUCT_PBUF + PBUF_POOL_BUFSIZE_ALIGNED));
+      q->ref = 1;
+      /* calculate remaining length to be allocated */
+      rem_len -= q->len;
+      /* remember this pbuf for linkage in next iteration */
+      r = q;
+    }
+    /* end of chain */
+    /*r->next = NULL;*/
+
+    break;
+  case PBUF_RAM:
+    /* If pbuf is to be allocated in RAM, allocate memory for it. */
+    p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));
+    if (p == NULL) {
+      return NULL;
+    }
+    /* Set up internal structure of the pbuf. */
+    p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset));
+    p->len = p->tot_len = length;
+    p->next = NULL;
+    p->type = type;
+
+    LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned",
+           ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
+    break;
+  /* pbuf references existing (non-volatile static constant) ROM payload? */
+  case PBUF_ROM:
+  /* pbuf references existing (externally allocated) RAM payload? */
+  case PBUF_REF:
+    /* only allocate memory for the pbuf structure */
+    p = (struct pbuf *)memp_malloc(MEMP_PBUF);
+    if (p == NULL) {
+      LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+                  ("pbuf_alloc: Could not allocate MEMP_PBUF for PBUF_%s.\n",
+                  (type == PBUF_ROM) ? "ROM" : "REF"));
+      return NULL;
+    }
+    /* caller must set this field properly, afterwards */
+    p->payload = NULL;
+    p->len = p->tot_len = length;
+    p->next = NULL;
+    p->type = type;
+    break;
+  default:
+    LWIP_ASSERT("pbuf_alloc: erroneous type", 0);
+    return NULL;
+  }
+  /* set reference count */
+  p->ref = 1;
+  /* set flags */
+  p->flags = 0;
+  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F") == %p\n", length, (void *)p));
+  return p;
+}
+
+#if LWIP_SUPPORT_CUSTOM_PBUF
+/** Initialize a custom pbuf (already allocated).
+ *
+ * @param layer flag to define header size
+ * @param length size of the pbuf's payload
+ * @param type type of the pbuf (only used to treat the pbuf accordingly, as
+ *        this function allocates no memory)
+ * @param p pointer to the custom pbuf to initialize (already allocated)
+ * @param payload_mem pointer to the buffer that is used for payload and headers,
+ *        must be at least big enough to hold 'length' plus the header size,
+ *        may be NULL if set later.
+ *        ATTENTION: The caller is responsible for correct alignment of this buffer!!
+ * @param payload_mem_len the size of the 'payload_mem' buffer, must be at least
+ *        big enough to hold 'length' plus the header size
+ */
+struct pbuf*
+pbuf_alloced_custom(pbuf_layer l, u16_t length, pbuf_type type, struct pbuf_custom *p,
+                    void *payload_mem, u16_t payload_mem_len)
+{
+  u16_t offset;
+  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloced_custom(length=%"U16_F")\n", length));
+
+  /* determine header offset */
+  switch (l) {
+  case PBUF_TRANSPORT:
+    /* add room for transport (often TCP) layer header */
+    offset = PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN;
+    break;
+  case PBUF_IP:
+    /* add room for IP layer header */
+    offset = PBUF_LINK_HLEN + PBUF_IP_HLEN;
+    break;
+  case PBUF_LINK:
+    /* add room for link layer header */
+    offset = PBUF_LINK_HLEN;
+    break;
+  case PBUF_RAW:
+    offset = 0;
+    break;
+  default:
+    LWIP_ASSERT("pbuf_alloced_custom: bad pbuf layer", 0);
+    return NULL;
+  }
+
+  if (LWIP_MEM_ALIGN_SIZE(offset) + length > payload_mem_len) {
+    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_WARNING, ("pbuf_alloced_custom(length=%"U16_F") buffer too short\n", length));
+    return NULL;
+  }
+
+  p->pbuf.next = NULL;
+  if (payload_mem != NULL) {
+    p->pbuf.payload = (u8_t *)payload_mem + LWIP_MEM_ALIGN_SIZE(offset);
+  } else {
+    p->pbuf.payload = NULL;
+  }
+  p->pbuf.flags = PBUF_FLAG_IS_CUSTOM;
+  p->pbuf.len = p->pbuf.tot_len = length;
+  p->pbuf.type = type;
+  p->pbuf.ref = 1;
+  return &p->pbuf;
+}
+#endif /* LWIP_SUPPORT_CUSTOM_PBUF */
+
+/**
+ * Shrink a pbuf chain to a desired length.
+ *
+ * @param p pbuf to shrink.
+ * @param new_len desired new length of pbuf chain
+ *
+ * Depending on the desired length, the first few pbufs in a chain might
+ * be skipped and left unchanged. The new last pbuf in the chain will be
+ * resized, and any remaining pbufs will be freed.
+ *
+ * @note If the pbuf is ROM/REF, only the ->tot_len and ->len fields are adjusted.
+ * @note May not be called on a packet queue.
+ *
+ * @note Despite its name, pbuf_realloc cannot grow the size of a pbuf (chain).
+ */
+void
+pbuf_realloc(struct pbuf *p, u16_t new_len)
+{
+  struct pbuf *q;
+  u16_t rem_len; /* remaining length */
+  s32_t grow;
+
+  LWIP_ASSERT("pbuf_realloc: p != NULL", p != NULL);
+  LWIP_ASSERT("pbuf_realloc: sane p->type", p->type == PBUF_POOL ||
+              p->type == PBUF_ROM ||
+              p->type == PBUF_RAM ||
+              p->type == PBUF_REF);
+
+  /* desired length larger than current length? */
+  if (new_len >= p->tot_len) {
+    /* enlarging not yet supported */
+    return;
+  }
+
+  /* the pbuf chain grows by (new_len - p->tot_len) bytes
+   * (which may be negative in case of shrinking) */
+  grow = new_len - p->tot_len;
+
+  /* first, step over any pbufs that should remain in the chain */
+  rem_len = new_len;
+  q = p;
+  /* should this pbuf be kept? */
+  while (rem_len > q->len) {
+    /* decrease remaining length by pbuf length */
+    rem_len -= q->len;
+    /* decrease total length indicator */
+    LWIP_ASSERT("grow < max_u16_t", grow < 0xffff);
+    q->tot_len += (u16_t)grow;
+    /* proceed to next pbuf in chain */
+    q = q->next;
+    LWIP_ASSERT("pbuf_realloc: q != NULL", q != NULL);
+  }
+  /* we have now reached the new last pbuf (in q) */
+  /* rem_len == desired length for pbuf q */
+
+  /* shrink allocated memory for PBUF_RAM */
+  /* (other types merely adjust their length fields */
+  if ((q->type == PBUF_RAM) && (rem_len != q->len)) {
+    /* reallocate and adjust the length of the pbuf that will be split */
+    q = (struct pbuf *)mem_trim(q, (u16_t)((u8_t *)q->payload - (u8_t *)q) + rem_len);
+    LWIP_ASSERT("mem_trim returned q == NULL", q != NULL);
+  }
+  /* adjust length fields for new last pbuf */
+  q->len = rem_len;
+  q->tot_len = q->len;
+
+  /* any remaining pbufs in chain? */
+  if (q->next != NULL) {
+    /* free remaining pbufs in chain */
+    pbuf_free(q->next);
+  }
+  /* q is last packet in chain */
+  q->next = NULL;
+
+}
+
+/**
+ * Adjusts the payload pointer to hide or reveal headers in the payload.
+ *
+ * Adjusts the ->payload pointer so that space for a header
+ * (dis)appears in the pbuf payload.
+ *
+ * The ->payload, ->tot_len and ->len fields are adjusted.
+ *
+ * @param p pbuf to change the header size.
+ * @param header_size_increment Number of bytes to increment header size which
+ * increases the size of the pbuf. New space is on the front.
+ * (Using a negative value decreases the header size.)
+ * If hdr_size_inc is 0, this function does nothing and returns succesful.
+ *
+ * PBUF_ROM and PBUF_REF type buffers cannot have their sizes increased, so
+ * the call will fail. A check is made that the increase in header size does
+ * not move the payload pointer in front of the start of the buffer.
+ * @return non-zero on failure, zero on success.
+ *
+ */
+u8_t
+pbuf_header(struct pbuf *p, s16_t header_size_increment)
+{
+  u16_t type;
+  void *payload;
+  u16_t increment_magnitude;
+
+  LWIP_ASSERT("p != NULL", p != NULL);
+  if ((header_size_increment == 0) || (p == NULL)) {
+    return 0;
+  }
+ 
+  if (header_size_increment < 0){
+    increment_magnitude = -header_size_increment;
+    /* Check that we aren't going to move off the end of the pbuf */
+    LWIP_ERROR("increment_magnitude <= p->len", (increment_magnitude <= p->len), return 1;);
+  } else {
+    increment_magnitude = header_size_increment;
+#if 0
+    /* Can't assert these as some callers speculatively call
+         pbuf_header() to see if it's OK.  Will return 1 below instead. */
+    /* Check that we've got the correct type of pbuf to work with */
+    LWIP_ASSERT("p->type == PBUF_RAM || p->type == PBUF_POOL", 
+                p->type == PBUF_RAM || p->type == PBUF_POOL);
+    /* Check that we aren't going to move off the beginning of the pbuf */
+    LWIP_ASSERT("p->payload - increment_magnitude >= p + SIZEOF_STRUCT_PBUF",
+                (u8_t *)p->payload - increment_magnitude >= (u8_t *)p + SIZEOF_STRUCT_PBUF);
+#endif
+  }
+
+  type = p->type;
+  /* remember current payload pointer */
+  payload = p->payload;
+
+  /* pbuf types containing payloads? */
+  if (type == PBUF_RAM || type == PBUF_POOL) {
+    /* set new payload pointer */
+    p->payload = (u8_t *)p->payload - header_size_increment;
+    /* boundary check fails? */
+    if ((u8_t *)p->payload < (u8_t *)p + SIZEOF_STRUCT_PBUF) {
+      LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+        ("pbuf_header: failed as %p < %p (not enough space for new header size)\n",
+        (void *)p->payload, (void *)(p + 1)));
+      /* restore old payload pointer */
+      p->payload = payload;
+      /* bail out unsuccesfully */
+      return 1;
+    }
+  /* pbuf types refering to external payloads? */
+  } else if (type == PBUF_REF || type == PBUF_ROM) {
+    /* hide a header in the payload? */
+    if ((header_size_increment < 0) && (increment_magnitude <= p->len)) {
+      /* increase payload pointer */
+      p->payload = (u8_t *)p->payload - header_size_increment;
+    } else {
+      /* cannot expand payload to front (yet!)
+       * bail out unsuccesfully */
+      return 1;
+    }
+  } else {
+    /* Unknown type */
+    LWIP_ASSERT("bad pbuf type", 0);
+    return 1;
+  }
+  /* modify pbuf length fields */
+  p->len += header_size_increment;
+  p->tot_len += header_size_increment;
+
+  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_header: old %p new %p (%"S16_F")\n",
+    (void *)payload, (void *)p->payload, header_size_increment));
+
+  return 0;
+}
+
+/**
+ * Dereference a pbuf chain or queue and deallocate any no-longer-used
+ * pbufs at the head of this chain or queue.
+ *
+ * Decrements the pbuf reference count. If it reaches zero, the pbuf is
+ * deallocated.
+ *
+ * For a pbuf chain, this is repeated for each pbuf in the chain,
+ * up to the first pbuf which has a non-zero reference count after
+ * decrementing. So, when all reference counts are one, the whole
+ * chain is free'd.
+ *
+ * @param p The pbuf (chain) to be dereferenced.
+ *
+ * @return the number of pbufs that were de-allocated
+ * from the head of the chain.
+ *
+ * @note MUST NOT be called on a packet queue (Not verified to work yet).
+ * @note the reference counter of a pbuf equals the number of pointers
+ * that refer to the pbuf (or into the pbuf).
+ *
+ * @internal examples:
+ *
+ * Assuming existing chains a->b->c with the following reference
+ * counts, calling pbuf_free(a) results in:
+ * 
+ * 1->2->3 becomes ...1->3
+ * 3->3->3 becomes 2->3->3
+ * 1->1->2 becomes ......1
+ * 2->1->1 becomes 1->1->1
+ * 1->1->1 becomes .......
+ *
+ */
+u8_t
+pbuf_free(struct pbuf *p)
+{
+  u16_t type;
+  struct pbuf *q;
+  u8_t count;
+
+  if (p == NULL) {
+    LWIP_ASSERT("p != NULL", p != NULL);
+    /* if assertions are disabled, proceed with debug output */
+    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+      ("pbuf_free(p == NULL) was called.\n"));
+    return 0;
+  }
+  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free(%p)\n", (void *)p));
+
+  PERF_START;
+
+  LWIP_ASSERT("pbuf_free: sane type",
+    p->type == PBUF_RAM || p->type == PBUF_ROM ||
+    p->type == PBUF_REF || p->type == PBUF_POOL);
+
+  count = 0;
+  /* de-allocate all consecutive pbufs from the head of the chain that
+   * obtain a zero reference count after decrementing*/
+  while (p != NULL) {
+    u16_t ref;
+    SYS_ARCH_DECL_PROTECT(old_level);
+    /* Since decrementing ref cannot be guaranteed to be a single machine operation
+     * we must protect it. We put the new ref into a local variable to prevent
+     * further protection. */
+    SYS_ARCH_PROTECT(old_level);
+    /* all pbufs in a chain are referenced at least once */
+    LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0);
+    /* decrease reference count (number of pointers to pbuf) */
+    ref = --(p->ref);
+    SYS_ARCH_UNPROTECT(old_level);
+    /* this pbuf is no longer referenced to? */
+    if (ref == 0) {
+      /* remember next pbuf in chain for next iteration */
+      q = p->next;
+      LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void *)p));
+      type = p->type;
+#if LWIP_SUPPORT_CUSTOM_PBUF
+      /* is this a custom pbuf? */
+      if ((p->flags & PBUF_FLAG_IS_CUSTOM) != 0) {
+        struct pbuf_custom *pc = (struct pbuf_custom*)p;
+        LWIP_ASSERT("pc->custom_free_function != NULL", pc->custom_free_function != NULL);
+        pc->custom_free_function(p);
+      } else
+#endif /* LWIP_SUPPORT_CUSTOM_PBUF */
+      {
+        /* is this a pbuf from the pool? */
+        if (type == PBUF_POOL) {
+          memp_free(MEMP_PBUF_POOL, p);
+        /* is this a ROM or RAM referencing pbuf? */
+        } else if (type == PBUF_ROM || type == PBUF_REF) {
+          memp_free(MEMP_PBUF, p);
+        /* type == PBUF_RAM */
+        } else {
+          mem_free(p);
+        }
+      }
+      count++;
+      /* proceed to next pbuf */
+      p = q;
+    /* p->ref > 0, this pbuf is still referenced to */
+    /* (and so the remaining pbufs in chain as well) */
+    } else {
+      LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %"U16_F", ending here.\n", (void *)p, ref));
+      /* stop walking through the chain */
+      p = NULL;
+    }
+  }
+  PERF_STOP("pbuf_free");
+  /* return number of de-allocated pbufs */
+  return count;
+}
+
+/**
+ * Count number of pbufs in a chain
+ *
+ * @param p first pbuf of chain
+ * @return the number of pbufs in a chain
+ */
+
+u8_t
+pbuf_clen(struct pbuf *p)
+{
+  u8_t len;
+
+  len = 0;
+  while (p != NULL) {
+    ++len;
+    p = p->next;
+  }
+  return len;
+}
+
+/**
+ * Increment the reference count of the pbuf.
+ *
+ * @param p pbuf to increase reference counter of
+ *
+ */
+void
+pbuf_ref(struct pbuf *p)
+{
+  SYS_ARCH_DECL_PROTECT(old_level);
+  /* pbuf given? */
+  if (p != NULL) {
+    SYS_ARCH_PROTECT(old_level);
+    ++(p->ref);
+    SYS_ARCH_UNPROTECT(old_level);
+  }
+}
+
+/**
+ * Concatenate two pbufs (each may be a pbuf chain) and take over
+ * the caller's reference of the tail pbuf.
+ * 
+ * @note The caller MAY NOT reference the tail pbuf afterwards.
+ * Use pbuf_chain() for that purpose.
+ * 
+ * @see pbuf_chain()
+ */
+
+void
+pbuf_cat(struct pbuf *h, struct pbuf *t)
+{
+  struct pbuf *p;
+
+  LWIP_ERROR("(h != NULL) && (t != NULL) (programmer violates API)",
+             ((h != NULL) && (t != NULL)), return;);
+
+  /* proceed to last pbuf of chain */
+  for (p = h; p->next != NULL; p = p->next) {
+    /* add total length of second chain to all totals of first chain */
+    p->tot_len += t->tot_len;
+  }
+  /* { p is last pbuf of first h chain, p->next == NULL } */
+  LWIP_ASSERT("p->tot_len == p->len (of last pbuf in chain)", p->tot_len == p->len);
+  LWIP_ASSERT("p->next == NULL", p->next == NULL);
+  /* add total length of second chain to last pbuf total of first chain */
+  p->tot_len += t->tot_len;
+  /* chain last pbuf of head (p) with first of tail (t) */
+  p->next = t;
+  /* p->next now references t, but the caller will drop its reference to t,
+   * so netto there is no change to the reference count of t.
+   */
+}
+
+/**
+ * Chain two pbufs (or pbuf chains) together.
+ * 
+ * The caller MUST call pbuf_free(t) once it has stopped
+ * using it. Use pbuf_cat() instead if you no longer use t.
+ * 
+ * @param h head pbuf (chain)
+ * @param t tail pbuf (chain)
+ * @note The pbufs MUST belong to the same packet.
+ * @note MAY NOT be called on a packet queue.
+ *
+ * The ->tot_len fields of all pbufs of the head chain are adjusted.
+ * The ->next field of the last pbuf of the head chain is adjusted.
+ * The ->ref field of the first pbuf of the tail chain is adjusted.
+ *
+ */
+void
+pbuf_chain(struct pbuf *h, struct pbuf *t)
+{
+  pbuf_cat(h, t);
+  /* t is now referenced by h */
+  pbuf_ref(t);
+  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_chain: %p references %p\n", (void *)h, (void *)t));
+}
+
+/**
+ * Dechains the first pbuf from its succeeding pbufs in the chain.
+ *
+ * Makes p->tot_len field equal to p->len.
+ * @param p pbuf to dechain
+ * @return remainder of the pbuf chain, or NULL if it was de-allocated.
+ * @note May not be called on a packet queue.
+ */
+struct pbuf *
+pbuf_dechain(struct pbuf *p)
+{
+  struct pbuf *q;
+  u8_t tail_gone = 1;
+  /* tail */
+  q = p->next;
+  /* pbuf has successor in chain? */
+  if (q != NULL) {
+    /* assert tot_len invariant: (p->tot_len == p->len + (p->next? p->next->tot_len: 0) */
+    LWIP_ASSERT("p->tot_len == p->len + q->tot_len", q->tot_len == p->tot_len - p->len);
+    /* enforce invariant if assertion is disabled */
+    q->tot_len = p->tot_len - p->len;
+    /* decouple pbuf from remainder */
+    p->next = NULL;
+    /* total length of pbuf p is its own length only */
+    p->tot_len = p->len;
+    /* q is no longer referenced by p, free it */
+    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_dechain: unreferencing %p\n", (void *)q));
+    tail_gone = pbuf_free(q);
+    if (tail_gone > 0) {
+      LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE,
+                  ("pbuf_dechain: deallocated %p (as it is no longer referenced)\n", (void *)q));
+    }
+    /* return remaining tail or NULL if deallocated */
+  }
+  /* assert tot_len invariant: (p->tot_len == p->len + (p->next? p->next->tot_len: 0) */
+  LWIP_ASSERT("p->tot_len == p->len", p->tot_len == p->len);
+  return ((tail_gone > 0) ? NULL : q);
+}
+
+/**
+ *
+ * Create PBUF_RAM copies of pbufs.
+ *
+ * Used to queue packets on behalf of the lwIP stack, such as
+ * ARP based queueing.
+ *
+ * @note You MUST explicitly use p = pbuf_take(p);
+ *
+ * @note Only one packet is copied, no packet queue!
+ *
+ * @param p_to pbuf destination of the copy
+ * @param p_from pbuf source of the copy
+ *
+ * @return ERR_OK if pbuf was copied
+ *         ERR_ARG if one of the pbufs is NULL or p_to is not big
+ *                 enough to hold p_from
+ */
+err_t
+pbuf_copy(struct pbuf *p_to, struct pbuf *p_from)
+{
+  u16_t offset_to=0, offset_from=0, len;
+
+  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_copy(%p, %p)\n",
+    (void*)p_to, (void*)p_from));
+
+  /* is the target big enough to hold the source? */
+  LWIP_ERROR("pbuf_copy: target not big enough to hold source", ((p_to != NULL) &&
+             (p_from != NULL) && (p_to->tot_len >= p_from->tot_len)), return ERR_ARG;);
+
+  /* iterate through pbuf chain */
+  do
+  {
+    /* copy one part of the original chain */
+    if ((p_to->len - offset_to) >= (p_from->len - offset_from)) {
+      /* complete current p_from fits into current p_to */
+      len = p_from->len - offset_from;
+    } else {
+      /* current p_from does not fit into current p_to */
+      len = p_to->len - offset_to;
+    }
+    MEMCPY((u8_t*)p_to->payload + offset_to, (u8_t*)p_from->payload + offset_from, len);
+    offset_to += len;
+    offset_from += len;
+    LWIP_ASSERT("offset_to <= p_to->len", offset_to <= p_to->len);
+    LWIP_ASSERT("offset_from <= p_from->len", offset_from <= p_from->len);
+    if (offset_from >= p_from->len) {
+      /* on to next p_from (if any) */
+      offset_from = 0;
+      p_from = p_from->next;
+    }
+    if (offset_to == p_to->len) {
+      /* on to next p_to (if any) */
+      offset_to = 0;
+      p_to = p_to->next;
+      LWIP_ERROR("p_to != NULL", (p_to != NULL) || (p_from == NULL) , return ERR_ARG;);
+    }
+
+    if((p_from != NULL) && (p_from->len == p_from->tot_len)) {
+      /* don't copy more than one packet! */
+      LWIP_ERROR("pbuf_copy() does not allow packet queues!\n",
+                 (p_from->next == NULL), return ERR_VAL;);
+    }
+    if((p_to != NULL) && (p_to->len == p_to->tot_len)) {
+      /* don't copy more than one packet! */
+      LWIP_ERROR("pbuf_copy() does not allow packet queues!\n",
+                  (p_to->next == NULL), return ERR_VAL;);
+    }
+  } while (p_from);
+  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_copy: end of chain reached.\n"));
+  return ERR_OK;
+}
+
+/**
+ * Copy (part of) the contents of a packet buffer
+ * to an application supplied buffer.
+ *
+ * @param buf the pbuf from which to copy data
+ * @param dataptr the application supplied buffer
+ * @param len length of data to copy (dataptr must be big enough). No more 
+ * than buf->tot_len will be copied, irrespective of len
+ * @param offset offset into the packet buffer from where to begin copying len bytes
+ * @return the number of bytes copied, or 0 on failure
+ */
+u16_t
+pbuf_copy_partial(struct pbuf *buf, void *dataptr, u16_t len, u16_t offset)
+{
+  struct pbuf *p;
+  u16_t left;
+  u16_t buf_copy_len;
+  u16_t copied_total = 0;
+
+  LWIP_ERROR("pbuf_copy_partial: invalid buf", (buf != NULL), return 0;);
+  LWIP_ERROR("pbuf_copy_partial: invalid dataptr", (dataptr != NULL), return 0;);
+
+  left = 0;
+
+  if((buf == NULL) || (dataptr == NULL)) {
+    return 0;
+  }
+
+  /* Note some systems use byte copy if dataptr or one of the pbuf payload pointers are unaligned. */
+  for(p = buf; len != 0 && p != NULL; p = p->next) {
+    if ((offset != 0) && (offset >= p->len)) {
+      /* don't copy from this buffer -> on to the next */
+      offset -= p->len;
+    } else {
+      /* copy from this buffer. maybe only partially. */
+      buf_copy_len = p->len - offset;
+      if (buf_copy_len > len)
+          buf_copy_len = len;
+      /* copy the necessary parts of the buffer */
+      MEMCPY(&((char*)dataptr)[left], &((char*)p->payload)[offset], buf_copy_len);
+      copied_total += buf_copy_len;
+      left += buf_copy_len;
+      len -= buf_copy_len;
+      offset = 0;
+    }
+  }
+  return copied_total;
+}
+
+/**
+ * Copy application supplied data into a pbuf.
+ * This function can only be used to copy the equivalent of buf->tot_len data.
+ *
+ * @param buf pbuf to fill with data
+ * @param dataptr application supplied data buffer
+ * @param len length of the application supplied data buffer
+ *
+ * @return ERR_OK if successful, ERR_MEM if the pbuf is not big enough
+ */
+err_t
+pbuf_take(struct pbuf *buf, const void *dataptr, u16_t len)
+{
+  struct pbuf *p;
+  u16_t buf_copy_len;
+  u16_t total_copy_len = len;
+  u16_t copied_total = 0;
+
+  LWIP_ERROR("pbuf_take: invalid buf", (buf != NULL), return 0;);
+  LWIP_ERROR("pbuf_take: invalid dataptr", (dataptr != NULL), return 0;);
+
+  if ((buf == NULL) || (dataptr == NULL) || (buf->tot_len < len)) {
+    return ERR_ARG;
+  }
+
+  /* Note some systems use byte copy if dataptr or one of the pbuf payload pointers are unaligned. */
+  for(p = buf; total_copy_len != 0; p = p->next) {
+    LWIP_ASSERT("pbuf_take: invalid pbuf", p != NULL);
+    buf_copy_len = total_copy_len;
+    if (buf_copy_len > p->len) {
+      /* this pbuf cannot hold all remaining data */
+      buf_copy_len = p->len;
+    }
+    /* copy the necessary parts of the buffer */
+    MEMCPY(p->payload, &((char*)dataptr)[copied_total], buf_copy_len);
+    total_copy_len -= buf_copy_len;
+    copied_total += buf_copy_len;
+  }
+  LWIP_ASSERT("did not copy all data", total_copy_len == 0 && copied_total == len);
+  return ERR_OK;
+}
+
+/**
+ * Creates a single pbuf out of a queue of pbufs.
+ *
+ * @remark: Either the source pbuf 'p' is freed by this function or the original
+ *          pbuf 'p' is returned, therefore the caller has to check the result!
+ *
+ * @param p the source pbuf
+ * @param layer pbuf_layer of the new pbuf
+ *
+ * @return a new, single pbuf (p->next is NULL)
+ *         or the old pbuf if allocation fails
+ */
+struct pbuf*
+pbuf_coalesce(struct pbuf *p, pbuf_layer layer)
+{
+  struct pbuf *q;
+  err_t err;
+  if (p->next == NULL) {
+    return p;
+  }
+  q = pbuf_alloc(layer, p->tot_len, PBUF_RAM);
+  if (q == NULL) {
+    /* @todo: what do we do now? */
+    return p;
+  }
+  err = pbuf_copy(q, p);
+  LWIP_ASSERT("pbuf_copy failed", err == ERR_OK);
+  pbuf_free(p);
+  return q;
+}
+
+#if LWIP_CHECKSUM_ON_COPY
+/**
+ * Copies data into a single pbuf (*not* into a pbuf queue!) and updates
+ * the checksum while copying
+ *
+ * @param p the pbuf to copy data into
+ * @param start_offset offset of p->payload where to copy the data to
+ * @param dataptr data to copy into the pbuf
+ * @param len length of data to copy into the pbuf
+ * @param chksum pointer to the checksum which is updated
+ * @return ERR_OK if successful, another error if the data does not fit
+ *         within the (first) pbuf (no pbuf queues!)
+ */
+err_t
+pbuf_fill_chksum(struct pbuf *p, u16_t start_offset, const void *dataptr,
+                 u16_t len, u16_t *chksum)
+{
+  u32_t acc;
+  u16_t copy_chksum;
+  char *dst_ptr;
+  LWIP_ASSERT("p != NULL", p != NULL);
+  LWIP_ASSERT("dataptr != NULL", dataptr != NULL);
+  LWIP_ASSERT("chksum != NULL", chksum != NULL);
+  LWIP_ASSERT("len != 0", len != 0);
+
+  if ((start_offset >= p->len) || (start_offset + len > p->len)) {
+    return ERR_ARG;
+  }
+
+  dst_ptr = ((char*)p->payload) + start_offset;
+  copy_chksum = LWIP_CHKSUM_COPY(dst_ptr, dataptr, len);
+  if ((start_offset & 1) != 0) {
+    copy_chksum = SWAP_BYTES_IN_WORD(copy_chksum);
+  }
+  acc = *chksum;
+  acc += copy_chksum;
+  *chksum = FOLD_U32T(acc);
+  return ERR_OK;
+}
+#endif /* LWIP_CHECKSUM_ON_COPY */
+
+ /** Get one byte from the specified position in a pbuf
+ * WARNING: returns zero for offset >= p->tot_len
+ *
+ * @param p pbuf to parse
+ * @param offset offset into p of the byte to return
+ * @return byte at an offset into p OR ZERO IF 'offset' >= p->tot_len
+ */
+u8_t
+pbuf_get_at(struct pbuf* p, u16_t offset)
+{
+  u16_t copy_from = offset;
+  struct pbuf* q = p;
+
+  /* get the correct pbuf */
+  while ((q != NULL) && (q->len <= copy_from)) {
+    copy_from -= q->len;
+    q = q->next;
+  }
+  /* return requested data if pbuf is OK */
+  if ((q != NULL) && (q->len > copy_from)) {
+    return ((u8_t*)q->payload)[copy_from];
+  }
+  return 0;
+}
+
+/** Compare pbuf contents at specified offset with memory s2, both of length n
+ *
+ * @param p pbuf to compare
+ * @param offset offset into p at wich to start comparing
+ * @param s2 buffer to compare
+ * @param n length of buffer to compare
+ * @return zero if equal, nonzero otherwise
+ *         (0xffff if p is too short, diffoffset+1 otherwise)
+ */
+u16_t
+pbuf_memcmp(struct pbuf* p, u16_t offset, const void* s2, u16_t n)
+{
+  u16_t start = offset;
+  struct pbuf* q = p;
+
+  /* get the correct pbuf */
+  while ((q != NULL) && (q->len <= start)) {
+    start -= q->len;
+    q = q->next;
+  }
+  /* return requested data if pbuf is OK */
+  if ((q != NULL) && (q->len > start)) {
+    u16_t i;
+    for(i = 0; i < n; i++) {
+      u8_t a = pbuf_get_at(q, start + i);
+      u8_t b = ((u8_t*)s2)[i];
+      if (a != b) {
+        return i+1;
+      }
+    }
+    return 0;
+  }
+  return 0xffff;
+}
+
+/** Find occurrence of mem (with length mem_len) in pbuf p, starting at offset
+ * start_offset.
+ *
+ * @param p pbuf to search, maximum length is 0xFFFE since 0xFFFF is used as
+ *        return value 'not found'
+ * @param mem search for the contents of this buffer
+ * @param mem_len length of 'mem'
+ * @param start_offset offset into p at which to start searching
+ * @return 0xFFFF if substr was not found in p or the index where it was found
+ */
+u16_t
+pbuf_memfind(struct pbuf* p, const void* mem, u16_t mem_len, u16_t start_offset)
+{
+  u16_t i;
+  u16_t max = p->tot_len - mem_len;
+  if (p->tot_len >= mem_len + start_offset) {
+    for(i = start_offset; i <= max; ) {
+      u16_t plus = pbuf_memcmp(p, i, mem, mem_len);
+      if (plus == 0) {
+        return i;
+      } else {
+        i += plus;
+      }
+    }
+  }
+  return 0xFFFF;
+}
+
+/** Find occurrence of substr with length substr_len in pbuf p, start at offset
+ * start_offset
+ * WARNING: in contrast to strstr(), this one does not stop at the first \0 in
+ * the pbuf/source string!
+ *
+ * @param p pbuf to search, maximum length is 0xFFFE since 0xFFFF is used as
+ *        return value 'not found'
+ * @param substr string to search for in p, maximum length is 0xFFFE
+ * @return 0xFFFF if substr was not found in p or the index where it was found
+ */
+u16_t
+pbuf_strstr(struct pbuf* p, const char* substr)
+{
+  size_t substr_len;
+  if ((substr == NULL) || (substr[0] == 0) || (p->tot_len == 0xFFFF)) {
+    return 0xFFFF;
+  }
+  substr_len = strlen(substr);
+  if (substr_len >= 0xFFFF) {
+    return 0xFFFF;
+  }
+  return pbuf_memfind(p, substr, (u16_t)substr_len, 0);
+}
diff --git a/external/badvpn_dns/lwip/src/core/raw.c b/external/badvpn_dns/lwip/src/core/raw.c
new file mode 100644
index 0000000..68b23c6
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/raw.c
@@ -0,0 +1,422 @@
+/**
+ * @file
+ * Implementation of raw protocol PCBs for low-level handling of
+ * different types of protocols besides (or overriding) those
+ * already available in lwIP.
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_RAW /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/memp.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+#include "lwip/raw.h"
+#include "lwip/stats.h"
+#include "arch/perf.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+
+#include <string.h>
+
+/** The list of RAW PCBs */
+static struct raw_pcb *raw_pcbs;
+
+/**
+ * Determine if in incoming IP packet is covered by a RAW PCB
+ * and if so, pass it to a user-provided receive callback function.
+ *
+ * Given an incoming IP datagram (as a chain of pbufs) this function
+ * finds a corresponding RAW PCB and calls the corresponding receive
+ * callback function.
+ *
+ * @param p pbuf to be demultiplexed to a RAW PCB.
+ * @param inp network interface on which the datagram was received.
+ * @return - 1 if the packet has been eaten by a RAW PCB receive
+ *           callback function. The caller MAY NOT not reference the
+ *           packet any longer, and MAY NOT call pbuf_free().
+ * @return - 0 if packet is not eaten (pbuf is still referenced by the
+ *           caller).
+ *
+ */
+u8_t
+raw_input(struct pbuf *p, struct netif *inp)
+{
+  struct raw_pcb *pcb, *prev;
+  struct ip_hdr *iphdr;
+  s16_t proto;
+  u8_t eaten = 0;
+#if LWIP_IPV6
+  struct ip6_hdr *ip6hdr;
+#endif /* LWIP_IPV6 */
+
+
+  LWIP_UNUSED_ARG(inp);
+
+  iphdr = (struct ip_hdr *)p->payload;
+#if LWIP_IPV6
+  if (IPH_V(iphdr) == 6) {
+    ip6hdr = (struct ip6_hdr *)p->payload;
+    proto = IP6H_NEXTH(ip6hdr);
+  }
+  else
+#endif /* LWIP_IPV6 */
+  {
+    proto = IPH_PROTO(iphdr);
+  }
+
+  prev = NULL;
+  pcb = raw_pcbs;
+  /* loop through all raw pcbs until the packet is eaten by one */
+  /* this allows multiple pcbs to match against the packet by design */
+  while ((eaten == 0) && (pcb != NULL)) {
+    if ((pcb->protocol == proto) && IP_PCB_IPVER_INPUT_MATCH(pcb) &&
+        (ipX_addr_isany(PCB_ISIPV6(pcb), &pcb->local_ip) ||
+         ipX_addr_cmp(PCB_ISIPV6(pcb), &(pcb->local_ip), ipX_current_dest_addr()))) {
+#if IP_SOF_BROADCAST_RECV
+      /* broadcast filter? */
+      if ((ip_get_option(pcb, SOF_BROADCAST) || !ip_addr_isbroadcast(ip_current_dest_addr(), inp))
+#if LWIP_IPV6
+          && !PCB_ISIPV6(pcb)
+#endif /* LWIP_IPV6 */
+          )
+#endif /* IP_SOF_BROADCAST_RECV */
+      {
+        /* receive callback function available? */
+        if (pcb->recv.ip4 != NULL) {
+#ifndef LWIP_NOASSERT
+          void* old_payload = p->payload;
+#endif
+          /* the receive callback function did not eat the packet? */
+          eaten = pcb->recv.ip4(pcb->recv_arg, pcb, p, ip_current_src_addr());
+          if (eaten != 0) {
+            /* receive function ate the packet */
+            p = NULL;
+            eaten = 1;
+            if (prev != NULL) {
+            /* move the pcb to the front of raw_pcbs so that is
+               found faster next time */
+              prev->next = pcb->next;
+              pcb->next = raw_pcbs;
+              raw_pcbs = pcb;
+            }
+          } else {
+            /* sanity-check that the receive callback did not alter the pbuf */
+            LWIP_ASSERT("raw pcb recv callback altered pbuf payload pointer without eating packet",
+              p->payload == old_payload);
+          }
+        }
+        /* no receive callback function was set for this raw PCB */
+      }
+      /* drop the packet */
+    }
+    prev = pcb;
+    pcb = pcb->next;
+  }
+  return eaten;
+}
+
+/**
+ * Bind a RAW PCB.
+ *
+ * @param pcb RAW PCB to be bound with a local address ipaddr.
+ * @param ipaddr local IP address to bind with. Use IP_ADDR_ANY to
+ * bind to all local interfaces.
+ *
+ * @return lwIP error code.
+ * - ERR_OK. Successful. No error occured.
+ * - ERR_USE. The specified IP address is already bound to by
+ * another RAW PCB.
+ *
+ * @see raw_disconnect()
+ */
+err_t
+raw_bind(struct raw_pcb *pcb, ip_addr_t *ipaddr)
+{
+  ipX_addr_set_ipaddr(PCB_ISIPV6(pcb), &pcb->local_ip, ipaddr);
+  return ERR_OK;
+}
+
+/**
+ * Connect an RAW PCB. This function is required by upper layers
+ * of lwip. Using the raw api you could use raw_sendto() instead
+ *
+ * This will associate the RAW PCB with the remote address.
+ *
+ * @param pcb RAW PCB to be connected with remote address ipaddr and port.
+ * @param ipaddr remote IP address to connect with.
+ *
+ * @return lwIP error code
+ *
+ * @see raw_disconnect() and raw_sendto()
+ */
+err_t
+raw_connect(struct raw_pcb *pcb, ip_addr_t *ipaddr)
+{
+  ipX_addr_set_ipaddr(PCB_ISIPV6(pcb), &pcb->remote_ip, ipaddr);
+  return ERR_OK;
+}
+
+
+/**
+ * Set the callback function for received packets that match the
+ * raw PCB's protocol and binding. 
+ * 
+ * The callback function MUST either
+ * - eat the packet by calling pbuf_free() and returning non-zero. The
+ *   packet will not be passed to other raw PCBs or other protocol layers.
+ * - not free the packet, and return zero. The packet will be matched
+ *   against further PCBs and/or forwarded to another protocol layers.
+ * 
+ * @return non-zero if the packet was free()d, zero if the packet remains
+ * available for others.
+ */
+void
+raw_recv(struct raw_pcb *pcb, raw_recv_fn recv, void *recv_arg)
+{
+  /* remember recv() callback and user data */
+  pcb->recv.ip4 = recv;
+  pcb->recv_arg = recv_arg;
+}
+
+/**
+ * Send the raw IP packet to the given address. Note that actually you cannot
+ * modify the IP headers (this is inconsistent with the receive callback where
+ * you actually get the IP headers), you can only specify the IP payload here.
+ * It requires some more changes in lwIP. (there will be a raw_send() function
+ * then.)
+ *
+ * @param pcb the raw pcb which to send
+ * @param p the IP payload to send
+ * @param ipaddr the destination address of the IP packet
+ *
+ */
+err_t
+raw_sendto(struct raw_pcb *pcb, struct pbuf *p, ip_addr_t *ipaddr)
+{
+  err_t err;
+  struct netif *netif;
+  ipX_addr_t *src_ip;
+  struct pbuf *q; /* q will be sent down the stack */
+  s16_t header_size;
+  ipX_addr_t *dst_ip = ip_2_ipX(ipaddr);
+
+  LWIP_DEBUGF(RAW_DEBUG | LWIP_DBG_TRACE, ("raw_sendto\n"));
+
+  header_size = (
+#if LWIP_IPV6
+    PCB_ISIPV6(pcb) ? IP6_HLEN :
+#endif /* LWIP_IPV6 */
+    IP_HLEN);
+
+  /* not enough space to add an IP header to first pbuf in given p chain? */
+  if (pbuf_header(p, header_size)) {
+    /* allocate header in new pbuf */
+    q = pbuf_alloc(PBUF_IP, 0, PBUF_RAM);
+    /* new header pbuf could not be allocated? */
+    if (q == NULL) {
+      LWIP_DEBUGF(RAW_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("raw_sendto: could not allocate header\n"));
+      return ERR_MEM;
+    }
+    if (p->tot_len != 0) {
+      /* chain header q in front of given pbuf p */
+      pbuf_chain(q, p);
+    }
+    /* { first pbuf q points to header pbuf } */
+    LWIP_DEBUGF(RAW_DEBUG, ("raw_sendto: added header pbuf %p before given pbuf %p\n", (void *)q, (void *)p));
+  }  else {
+    /* first pbuf q equals given pbuf */
+    q = p;
+    if(pbuf_header(q, -header_size)) {
+      LWIP_ASSERT("Can't restore header we just removed!", 0);
+      return ERR_MEM;
+    }
+  }
+
+  netif = ipX_route(PCB_ISIPV6(pcb), &pcb->local_ip, dst_ip);
+  if (netif == NULL) {
+    LWIP_DEBUGF(RAW_DEBUG | LWIP_DBG_LEVEL_WARNING, ("raw_sendto: No route to "));
+    ipX_addr_debug_print(PCB_ISIPV6(pcb), RAW_DEBUG | LWIP_DBG_LEVEL_WARNING, dst_ip);
+    /* free any temporary header pbuf allocated by pbuf_header() */
+    if (q != p) {
+      pbuf_free(q);
+    }
+    return ERR_RTE;
+  }
+
+#if IP_SOF_BROADCAST
+#if LWIP_IPV6
+  /* @todo: why does IPv6 not filter broadcast with SOF_BROADCAST enabled? */
+  if (!PCB_ISIPV6(pcb))
+#endif /* LWIP_IPV6 */
+  {
+    /* broadcast filter? */
+    if (!ip_get_option(pcb, SOF_BROADCAST) && ip_addr_isbroadcast(ipaddr, netif)) {
+      LWIP_DEBUGF(RAW_DEBUG | LWIP_DBG_LEVEL_WARNING, ("raw_sendto: SOF_BROADCAST not enabled on pcb %p\n", (void *)pcb));
+      /* free any temporary header pbuf allocated by pbuf_header() */
+      if (q != p) {
+        pbuf_free(q);
+      }
+      return ERR_VAL;
+    }
+  }
+#endif /* IP_SOF_BROADCAST */
+
+  if (ipX_addr_isany(PCB_ISIPV6(pcb), &pcb->local_ip)) {
+    /* use outgoing network interface IP address as source address */
+    src_ip = ipX_netif_get_local_ipX(PCB_ISIPV6(pcb), netif, dst_ip);
+#if LWIP_IPV6
+    if (src_ip == NULL) {
+      if (q != p) {
+        pbuf_free(q);
+      }
+      return ERR_RTE;
+    }
+#endif /* LWIP_IPV6 */
+  } else {
+    /* use RAW PCB local IP address as source address */
+    src_ip = &pcb->local_ip;
+  }
+
+  NETIF_SET_HWADDRHINT(netif, &pcb->addr_hint);
+  err = ipX_output_if(PCB_ISIPV6(pcb), q, ipX_2_ip(src_ip), ipX_2_ip(dst_ip), pcb->ttl, pcb->tos, pcb->protocol, netif);
+  NETIF_SET_HWADDRHINT(netif, NULL);
+
+  /* did we chain a header earlier? */
+  if (q != p) {
+    /* free the header */
+    pbuf_free(q);
+  }
+  return err;
+}
+
+/**
+ * Send the raw IP packet to the address given by raw_connect()
+ *
+ * @param pcb the raw pcb which to send
+ * @param p the IP payload to send
+ *
+ */
+err_t
+raw_send(struct raw_pcb *pcb, struct pbuf *p)
+{
+  return raw_sendto(pcb, p, ipX_2_ip(&pcb->remote_ip));
+}
+
+/**
+ * Remove an RAW PCB.
+ *
+ * @param pcb RAW PCB to be removed. The PCB is removed from the list of
+ * RAW PCB's and the data structure is freed from memory.
+ *
+ * @see raw_new()
+ */
+void
+raw_remove(struct raw_pcb *pcb)
+{
+  struct raw_pcb *pcb2;
+  /* pcb to be removed is first in list? */
+  if (raw_pcbs == pcb) {
+    /* make list start at 2nd pcb */
+    raw_pcbs = raw_pcbs->next;
+    /* pcb not 1st in list */
+  } else {
+    for(pcb2 = raw_pcbs; pcb2 != NULL; pcb2 = pcb2->next) {
+      /* find pcb in raw_pcbs list */
+      if (pcb2->next != NULL && pcb2->next == pcb) {
+        /* remove pcb from list */
+        pcb2->next = pcb->next;
+      }
+    }
+  }
+  memp_free(MEMP_RAW_PCB, pcb);
+}
+
+/**
+ * Create a RAW PCB.
+ *
+ * @return The RAW PCB which was created. NULL if the PCB data structure
+ * could not be allocated.
+ *
+ * @param proto the protocol number of the IPs payload (e.g. IP_PROTO_ICMP)
+ *
+ * @see raw_remove()
+ */
+struct raw_pcb *
+raw_new(u8_t proto)
+{
+  struct raw_pcb *pcb;
+
+  LWIP_DEBUGF(RAW_DEBUG | LWIP_DBG_TRACE, ("raw_new\n"));
+
+  pcb = (struct raw_pcb *)memp_malloc(MEMP_RAW_PCB);
+  /* could allocate RAW PCB? */
+  if (pcb != NULL) {
+    /* initialize PCB to all zeroes */
+    memset(pcb, 0, sizeof(struct raw_pcb));
+    pcb->protocol = proto;
+    pcb->ttl = RAW_TTL;
+    pcb->next = raw_pcbs;
+    raw_pcbs = pcb;
+  }
+  return pcb;
+}
+
+#if LWIP_IPV6
+/**
+ * Create a RAW PCB for IPv6.
+ *
+ * @return The RAW PCB which was created. NULL if the PCB data structure
+ * could not be allocated.
+ *
+ * @param proto the protocol number (next header) of the IPv6 packet payload
+ *              (e.g. IP6_NEXTH_ICMP6)
+ *
+ * @see raw_remove()
+ */
+struct raw_pcb *
+raw_new_ip6(u8_t proto)
+{
+  struct raw_pcb *pcb;
+  pcb = raw_new(proto);
+  ip_set_v6(pcb, 1);
+  return pcb;
+}
+#endif /* LWIP_IPV6 */
+
+#endif /* LWIP_RAW */
diff --git a/external/badvpn_dns/lwip/src/core/snmp/asn1_dec.c b/external/badvpn_dns/lwip/src/core/snmp/asn1_dec.c
new file mode 100644
index 0000000..1d56582
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/snmp/asn1_dec.c
@@ -0,0 +1,657 @@
+/**
+ * @file
+ * Abstract Syntax Notation One (ISO 8824, 8825) decoding
+ *
+ * @todo not optimised (yet), favor correctness over speed, favor speed over size
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Christiaan Simons <christiaan.simons@xxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/snmp_asn1.h"
+
+/**
+ * Retrieves type field from incoming pbuf chain.
+ *
+ * @param p points to a pbuf holding an ASN1 coded type field
+ * @param ofs points to the offset within the pbuf chain of the ASN1 coded type field
+ * @param type return ASN1 type
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
+ */
+err_t
+snmp_asn1_dec_type(struct pbuf *p, u16_t ofs, u8_t *type)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+      *type = *msg_ptr;
+      return ERR_OK;
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Decodes length field from incoming pbuf chain into host length.
+ *
+ * @param p points to a pbuf holding an ASN1 coded length
+ * @param ofs points to the offset within the pbuf chain of the ASN1 coded length
+ * @param octets_used returns number of octets used by the length code
+ * @param length return host order length, upto 64k
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
+ */
+err_t
+snmp_asn1_dec_length(struct pbuf *p, u16_t ofs, u8_t *octets_used, u16_t *length)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+
+      if (*msg_ptr < 0x80)
+      {
+        /* primitive definite length format */
+        *octets_used = 1;
+        *length = *msg_ptr;
+        return ERR_OK;
+      }
+      else if (*msg_ptr == 0x80)
+      {
+        /* constructed indefinite length format, termination with two zero octets */
+        u8_t zeros;
+        u8_t i;
+
+        *length = 0;
+        zeros = 0;
+        while (zeros != 2)
+        {
+          i = 2;
+          while (i > 0)
+          {
+            i--;
+            (*length) += 1;
+            ofs += 1;
+            if (ofs >= plen)
+            {
+              /* next octet in next pbuf */
+              p = p->next;
+              if (p == NULL) { return ERR_ARG; }
+              msg_ptr = (u8_t*)p->payload;
+              plen += p->len;
+            }
+            else
+            {
+              /* next octet in same pbuf */
+              msg_ptr++;
+            }
+            if (*msg_ptr == 0)
+            {
+              zeros++;
+              if (zeros == 2)
+              {
+                /* stop while (i > 0) */
+                i = 0;
+              }
+            }
+            else
+            {
+              zeros = 0;
+            }
+          }
+        }
+        *octets_used = 1;
+        return ERR_OK;
+      }
+      else if (*msg_ptr == 0x81)
+      {
+        /* constructed definite length format, one octet */
+        ofs += 1;
+        if (ofs >= plen)
+        {
+          /* next octet in next pbuf */
+          p = p->next;
+          if (p == NULL) { return ERR_ARG; }
+          msg_ptr = (u8_t*)p->payload;
+        }
+        else
+        {
+          /* next octet in same pbuf */
+          msg_ptr++;
+        }
+        *length = *msg_ptr;
+        *octets_used = 2;
+        return ERR_OK;
+      }
+      else if (*msg_ptr == 0x82)
+      {
+        u8_t i;
+
+        /* constructed definite length format, two octets */
+        i = 2;
+        while (i > 0)
+        {
+          i--;
+          ofs += 1;
+          if (ofs >= plen)
+          {
+            /* next octet in next pbuf */
+            p = p->next;
+            if (p == NULL) { return ERR_ARG; }
+            msg_ptr = (u8_t*)p->payload;
+            plen += p->len;
+          }
+          else
+          {
+            /* next octet in same pbuf */
+            msg_ptr++;
+          }
+          if (i == 0)
+          {
+            /* least significant length octet */
+            *length |= *msg_ptr;
+          }
+          else
+          {
+            /* most significant length octet */
+            *length = (*msg_ptr) << 8;
+          }
+        }
+        *octets_used = 3;
+        return ERR_OK;
+      }
+      else
+      {
+        /* constructed definite length format 3..127 octets, this is too big (>64k) */
+        /**  @todo: do we need to accept inefficient codings with many leading zero's? */
+        *octets_used = 1 + ((*msg_ptr) & 0x7f);
+        return ERR_ARG;
+      }
+    }
+    p = p->next;
+  }
+
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Decodes positive integer (counter, gauge, timeticks) into u32_t.
+ *
+ * @param p points to a pbuf holding an ASN1 coded integer
+ * @param ofs points to the offset within the pbuf chain of the ASN1 coded integer
+ * @param len length of the coded integer field
+ * @param value return host order integer
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
+ *
+ * @note ASN coded integers are _always_ signed. E.g. +0xFFFF is coded
+ * as 0x00,0xFF,0xFF. Note the leading sign octet. A positive value
+ * of 0xFFFFFFFF is preceded with 0x00 and the length is 5 octets!!
+ */
+err_t
+snmp_asn1_dec_u32t(struct pbuf *p, u16_t ofs, u16_t len, u32_t *value)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+      if ((len > 0) && (len < 6))
+      {
+        /* start from zero */
+        *value = 0;
+        if (*msg_ptr & 0x80)
+        {
+          /* negative, expecting zero sign bit! */
+          return ERR_ARG;
+        }
+        else
+        {
+          /* positive */
+          if ((len > 1) && (*msg_ptr == 0))
+          {
+            /* skip leading "sign byte" octet 0x00 */
+            len--;
+            ofs += 1;
+            if (ofs >= plen)
+            {
+              /* next octet in next pbuf */
+              p = p->next;
+              if (p == NULL) { return ERR_ARG; }
+              msg_ptr = (u8_t*)p->payload;
+              plen += p->len;
+            }
+            else
+            {
+              /* next octet in same pbuf */
+              msg_ptr++;
+            }
+          }
+        }
+        /* OR octets with value */
+        while (len > 1)
+        {
+          len--;
+          *value |= *msg_ptr;
+          *value <<= 8;
+          ofs += 1;
+          if (ofs >= plen)
+          {
+            /* next octet in next pbuf */
+            p = p->next;
+            if (p == NULL) { return ERR_ARG; }
+            msg_ptr = (u8_t*)p->payload;
+            plen += p->len;
+          }
+          else
+          {
+            /* next octet in same pbuf */
+            msg_ptr++;
+          }
+        }
+        *value |= *msg_ptr;
+        return ERR_OK;
+      }
+      else
+      {
+        return ERR_ARG;
+      }
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Decodes integer into s32_t.
+ *
+ * @param p points to a pbuf holding an ASN1 coded integer
+ * @param ofs points to the offset within the pbuf chain of the ASN1 coded integer
+ * @param len length of the coded integer field
+ * @param value return host order integer
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
+ *
+ * @note ASN coded integers are _always_ signed!
+ */
+err_t
+snmp_asn1_dec_s32t(struct pbuf *p, u16_t ofs, u16_t len, s32_t *value)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+#if BYTE_ORDER == LITTLE_ENDIAN
+  u8_t *lsb_ptr = (u8_t*)value;
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+  u8_t *lsb_ptr = (u8_t*)value + sizeof(s32_t) - 1;
+#endif
+  u8_t sign;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+      if ((len > 0) && (len < 5))
+      {
+        if (*msg_ptr & 0x80)
+        {
+          /* negative, start from -1 */
+          *value = -1;
+          sign = 1;
+        }
+        else
+        {
+          /* positive, start from 0 */
+          *value = 0;
+          sign = 0;
+        }
+        /* OR/AND octets with value */
+        while (len > 1)
+        {
+          len--;
+          if (sign)
+          {
+            *lsb_ptr &= *msg_ptr;
+            *value <<= 8;
+            *lsb_ptr |= 255;
+          }
+          else
+          {
+            *lsb_ptr |= *msg_ptr;
+            *value <<= 8;
+          }
+          ofs += 1;
+          if (ofs >= plen)
+          {
+            /* next octet in next pbuf */
+            p = p->next;
+            if (p == NULL) { return ERR_ARG; }
+            msg_ptr = (u8_t*)p->payload;
+            plen += p->len;
+          }
+          else
+          {
+            /* next octet in same pbuf */
+            msg_ptr++;
+          }
+        }
+        if (sign)
+        {
+          *lsb_ptr &= *msg_ptr;
+        }
+        else
+        {
+          *lsb_ptr |= *msg_ptr;
+        }
+        return ERR_OK;
+      }
+      else
+      {
+        return ERR_ARG;
+      }
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Decodes object identifier from incoming message into array of s32_t.
+ *
+ * @param p points to a pbuf holding an ASN1 coded object identifier
+ * @param ofs points to the offset within the pbuf chain of the ASN1 coded object identifier
+ * @param len length of the coded object identifier
+ * @param oid return object identifier struct
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
+ */
+err_t
+snmp_asn1_dec_oid(struct pbuf *p, u16_t ofs, u16_t len, struct snmp_obj_id *oid)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+  s32_t *oid_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+
+      oid->len = 0;
+      oid_ptr = &oid->id[0];
+      if (len > 0)
+      {
+        /* first compressed octet */
+        if (*msg_ptr == 0x2B)
+        {
+          /* (most) common case 1.3 (iso.org) */
+          *oid_ptr = 1;
+          oid_ptr++;
+          *oid_ptr = 3;
+          oid_ptr++;
+        }
+        else if (*msg_ptr < 40)
+        {
+          *oid_ptr = 0;
+          oid_ptr++;
+          *oid_ptr = *msg_ptr;
+          oid_ptr++;
+        }
+        else if (*msg_ptr < 80)
+        {
+          *oid_ptr = 1;
+          oid_ptr++;
+          *oid_ptr = (*msg_ptr) - 40;
+          oid_ptr++;
+        }
+        else
+        {
+          *oid_ptr = 2;
+          oid_ptr++;
+          *oid_ptr = (*msg_ptr) - 80;
+          oid_ptr++;
+        }
+        oid->len = 2;
+      }
+      else
+      {
+        /* accepting zero length identifiers e.g. for
+           getnext operation. uncommon but valid */
+        return ERR_OK;
+      }
+      len--;
+      if (len > 0)
+      {
+        ofs += 1;
+        if (ofs >= plen)
+        {
+          /* next octet in next pbuf */
+          p = p->next;
+          if (p == NULL) { return ERR_ARG; }
+          msg_ptr = (u8_t*)p->payload;
+          plen += p->len;
+        }
+        else
+        {
+          /* next octet in same pbuf */
+          msg_ptr++;
+        }
+      }
+      while ((len > 0) && (oid->len < LWIP_SNMP_OBJ_ID_LEN))
+      {
+        /* sub-identifier uses multiple octets */
+        if (*msg_ptr & 0x80)
+        {
+          s32_t sub_id = 0;
+
+          while ((*msg_ptr & 0x80) && (len > 1))
+          {
+            len--;
+            sub_id = (sub_id << 7) + (*msg_ptr & ~0x80);
+            ofs += 1;
+            if (ofs >= plen)
+            {
+              /* next octet in next pbuf */
+              p = p->next;
+              if (p == NULL) { return ERR_ARG; }
+              msg_ptr = (u8_t*)p->payload;
+              plen += p->len;
+            }
+            else
+            {
+              /* next octet in same pbuf */
+              msg_ptr++;
+            }
+          }
+          if (!(*msg_ptr & 0x80) && (len > 0))
+          {
+            /* last octet sub-identifier */
+            len--;
+            sub_id = (sub_id << 7) + *msg_ptr;
+            *oid_ptr = sub_id;
+          }
+        }
+        else
+        {
+          /* !(*msg_ptr & 0x80) sub-identifier uses single octet */
+          len--;
+          *oid_ptr = *msg_ptr;
+        }
+        if (len > 0)
+        {
+          /* remaining oid bytes available ... */
+          ofs += 1;
+          if (ofs >= plen)
+          {
+            /* next octet in next pbuf */
+            p = p->next;
+            if (p == NULL) { return ERR_ARG; }
+            msg_ptr = (u8_t*)p->payload;
+            plen += p->len;
+          }
+          else
+          {
+            /* next octet in same pbuf */
+            msg_ptr++;
+          }
+        }
+        oid_ptr++;
+        oid->len++;
+      }
+      if (len == 0)
+      {
+        /* len == 0, end of oid */
+        return ERR_OK;
+      }
+      else
+      {
+        /* len > 0, oid->len == LWIP_SNMP_OBJ_ID_LEN or malformed encoding */
+        return ERR_ARG;
+      }
+
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Decodes (copies) raw data (ip-addresses, octet strings, opaque encoding)
+ * from incoming message into array.
+ *
+ * @param p points to a pbuf holding an ASN1 coded raw data
+ * @param ofs points to the offset within the pbuf chain of the ASN1 coded raw data
+ * @param len length of the coded raw data (zero is valid, e.g. empty string!)
+ * @param raw_len length of the raw return value
+ * @param raw return raw bytes
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) decode
+ */
+err_t
+snmp_asn1_dec_raw(struct pbuf *p, u16_t ofs, u16_t len, u16_t raw_len, u8_t *raw)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  if (len > 0)
+  {
+    plen = 0;
+    while (p != NULL)
+    {
+      base = plen;
+      plen += p->len;
+      if (ofs < plen)
+      {
+        msg_ptr = (u8_t*)p->payload;
+        msg_ptr += ofs - base;
+        if (raw_len >= len)
+        {
+          while (len > 1)
+          {
+            /* copy len - 1 octets */
+            len--;
+            *raw = *msg_ptr;
+            raw++;
+            ofs += 1;
+            if (ofs >= plen)
+            {
+              /* next octet in next pbuf */
+              p = p->next;
+              if (p == NULL) { return ERR_ARG; }
+              msg_ptr = (u8_t*)p->payload;
+              plen += p->len;
+            }
+            else
+            {
+              /* next octet in same pbuf */
+              msg_ptr++;
+            }
+          }
+          /* copy last octet */
+          *raw = *msg_ptr;
+          return ERR_OK;
+        }
+        else
+        {
+          /* raw_len < len, not enough dst space */
+          return ERR_ARG;
+        }
+      }
+      p = p->next;
+    }
+    /* p == NULL, ofs >= plen */
+    return ERR_ARG;
+  }
+  else
+  {
+    /* len == 0, empty string */
+    return ERR_OK;
+  }
+}
+
+#endif /* LWIP_SNMP */
diff --git a/external/badvpn_dns/lwip/src/core/snmp/asn1_enc.c b/external/badvpn_dns/lwip/src/core/snmp/asn1_enc.c
new file mode 100644
index 0000000..64dfc5f
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/snmp/asn1_enc.c
@@ -0,0 +1,611 @@
+/**
+ * @file
+ * Abstract Syntax Notation One (ISO 8824, 8825) encoding
+ *
+ * @todo not optimised (yet), favor correctness over speed, favor speed over size
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Christiaan Simons <christiaan.simons@xxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/snmp_asn1.h"
+
+/**
+ * Returns octet count for length.
+ *
+ * @param length
+ * @param octets_needed points to the return value
+ */
+void
+snmp_asn1_enc_length_cnt(u16_t length, u8_t *octets_needed)
+{
+  if (length < 0x80U)
+  {
+    *octets_needed = 1;
+  }
+  else if (length < 0x100U)
+  {
+    *octets_needed = 2;
+  }
+  else
+  {
+    *octets_needed = 3;
+  }
+}
+
+/**
+ * Returns octet count for an u32_t.
+ *
+ * @param value
+ * @param octets_needed points to the return value
+ *
+ * @note ASN coded integers are _always_ signed. E.g. +0xFFFF is coded
+ * as 0x00,0xFF,0xFF. Note the leading sign octet. A positive value
+ * of 0xFFFFFFFF is preceded with 0x00 and the length is 5 octets!!
+ */
+void
+snmp_asn1_enc_u32t_cnt(u32_t value, u16_t *octets_needed)
+{
+  if (value < 0x80UL)
+  {
+    *octets_needed = 1;
+  }
+  else if (value < 0x8000UL)
+  {
+    *octets_needed = 2;
+  }
+  else if (value < 0x800000UL)
+  {
+    *octets_needed = 3;
+  }
+  else if (value < 0x80000000UL)
+  {
+    *octets_needed = 4;
+  }
+  else
+  {
+    *octets_needed = 5;
+  }
+}
+
+/**
+ * Returns octet count for an s32_t.
+ *
+ * @param value
+ * @param octets_needed points to the return value
+ *
+ * @note ASN coded integers are _always_ signed.
+ */
+void
+snmp_asn1_enc_s32t_cnt(s32_t value, u16_t *octets_needed)
+{
+  if (value < 0)
+  {
+    value = ~value;
+  }
+  if (value < 0x80L)
+  {
+    *octets_needed = 1;
+  }
+  else if (value < 0x8000L)
+  {
+    *octets_needed = 2;
+  }
+  else if (value < 0x800000L)
+  {
+    *octets_needed = 3;
+  }
+  else
+  {
+    *octets_needed = 4;
+  }
+}
+
+/**
+ * Returns octet count for an object identifier.
+ *
+ * @param ident_len object identifier array length
+ * @param ident points to object identifier array
+ * @param octets_needed points to the return value
+ */
+void
+snmp_asn1_enc_oid_cnt(u8_t ident_len, s32_t *ident, u16_t *octets_needed)
+{
+  s32_t sub_id;
+  u8_t cnt;
+
+  cnt = 0;
+  if (ident_len > 1)
+  {
+    /* compressed prefix in one octet */
+    cnt++;
+    ident_len -= 2;
+    ident += 2;
+  }
+  while(ident_len > 0)
+  {
+    ident_len--;
+    sub_id = *ident;
+
+    sub_id >>= 7;
+    cnt++;
+    while(sub_id > 0)
+    {
+      sub_id >>= 7;
+      cnt++;
+    }
+    ident++;
+  }
+  *octets_needed = cnt;
+}
+
+/**
+ * Encodes ASN type field into a pbuf chained ASN1 msg.
+ *
+ * @param p points to output pbuf to encode value into
+ * @param ofs points to the offset within the pbuf chain
+ * @param type input ASN1 type
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_asn1_enc_type(struct pbuf *p, u16_t ofs, u8_t type)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+      *msg_ptr = type;
+      return ERR_OK;
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Encodes host order length field into a pbuf chained ASN1 msg.
+ *
+ * @param p points to output pbuf to encode length into
+ * @param ofs points to the offset within the pbuf chain
+ * @param length is the host order length to be encoded
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_asn1_enc_length(struct pbuf *p, u16_t ofs, u16_t length)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+
+      if (length < 0x80)
+      {
+        *msg_ptr = (u8_t)length;
+        return ERR_OK;
+      }
+      else if (length < 0x100)
+      {
+        *msg_ptr = 0x81;
+        ofs += 1;
+        if (ofs >= plen)
+        {
+          /* next octet in next pbuf */
+          p = p->next;
+          if (p == NULL) { return ERR_ARG; }
+          msg_ptr = (u8_t*)p->payload;
+        }
+        else
+        {
+          /* next octet in same pbuf */
+          msg_ptr++;
+        }
+        *msg_ptr = (u8_t)length;
+        return ERR_OK;
+      }
+      else
+      {
+        u8_t i;
+
+        /* length >= 0x100 && length <= 0xFFFF */
+        *msg_ptr = 0x82;
+        i = 2;
+        while (i > 0)
+        {
+          i--;
+          ofs += 1;
+          if (ofs >= plen)
+          {
+            /* next octet in next pbuf */
+            p = p->next;
+            if (p == NULL) { return ERR_ARG; }
+            msg_ptr = (u8_t*)p->payload;
+            plen += p->len;
+          }
+          else
+          {
+            /* next octet in same pbuf */
+            msg_ptr++;
+          }
+          if (i == 0)
+          {
+            /* least significant length octet */
+            *msg_ptr = (u8_t)length;
+          }
+          else
+          {
+            /* most significant length octet */
+            *msg_ptr = (u8_t)(length >> 8);
+          }
+        }
+        return ERR_OK;
+      }
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Encodes u32_t (counter, gauge, timeticks) into a pbuf chained ASN1 msg.
+ *
+ * @param p points to output pbuf to encode value into
+ * @param ofs points to the offset within the pbuf chain
+ * @param octets_needed encoding length (from snmp_asn1_enc_u32t_cnt())
+ * @param value is the host order u32_t value to be encoded
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) encode
+ *
+ * @see snmp_asn1_enc_u32t_cnt()
+ */
+err_t
+snmp_asn1_enc_u32t(struct pbuf *p, u16_t ofs, u16_t octets_needed, u32_t value)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+
+      if (octets_needed == 5)
+      {
+        /* not enough bits in 'value' add leading 0x00 */
+        octets_needed--;
+        *msg_ptr = 0x00;
+        ofs += 1;
+        if (ofs >= plen)
+        {
+          /* next octet in next pbuf */
+          p = p->next;
+          if (p == NULL) { return ERR_ARG; }
+          msg_ptr = (u8_t*)p->payload;
+          plen += p->len;
+        }
+        else
+        {
+          /* next octet in same pbuf */
+          msg_ptr++;
+        }
+      }
+      while (octets_needed > 1)
+      {
+        octets_needed--;
+        *msg_ptr = (u8_t)(value >> (octets_needed << 3));
+        ofs += 1;
+        if (ofs >= plen)
+        {
+          /* next octet in next pbuf */
+          p = p->next;
+          if (p == NULL) { return ERR_ARG; }
+          msg_ptr = (u8_t*)p->payload;
+          plen += p->len;
+        }
+        else
+        {
+          /* next octet in same pbuf */
+          msg_ptr++;
+        }
+      }
+      /* (only) one least significant octet */
+      *msg_ptr = (u8_t)value;
+      return ERR_OK;
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Encodes s32_t integer into a pbuf chained ASN1 msg.
+ *
+ * @param p points to output pbuf to encode value into
+ * @param ofs points to the offset within the pbuf chain
+ * @param octets_needed encoding length (from snmp_asn1_enc_s32t_cnt())
+ * @param value is the host order s32_t value to be encoded
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) encode
+ *
+ * @see snmp_asn1_enc_s32t_cnt()
+ */
+err_t
+snmp_asn1_enc_s32t(struct pbuf *p, u16_t ofs, u16_t octets_needed, s32_t value)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+
+      while (octets_needed > 1)
+      {
+        octets_needed--;
+        *msg_ptr = (u8_t)(value >> (octets_needed << 3));
+        ofs += 1;
+        if (ofs >= plen)
+        {
+          /* next octet in next pbuf */
+          p = p->next;
+          if (p == NULL) { return ERR_ARG; }
+          msg_ptr = (u8_t*)p->payload;
+          plen += p->len;
+        }
+        else
+        {
+          /* next octet in same pbuf */
+          msg_ptr++;
+        }
+      }
+      /* (only) one least significant octet */
+      *msg_ptr = (u8_t)value;
+      return ERR_OK;
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Encodes object identifier into a pbuf chained ASN1 msg.
+ *
+ * @param p points to output pbuf to encode oid into
+ * @param ofs points to the offset within the pbuf chain
+ * @param ident_len object identifier array length
+ * @param ident points to object identifier array
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_asn1_enc_oid(struct pbuf *p, u16_t ofs, u8_t ident_len, s32_t *ident)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+
+      if (ident_len > 1)
+      {
+        if ((ident[0] == 1) && (ident[1] == 3))
+        {
+          /* compressed (most common) prefix .iso.org */
+          *msg_ptr = 0x2b;
+        }
+        else
+        {
+          /* calculate prefix */
+          *msg_ptr = (u8_t)((ident[0] * 40) + ident[1]);
+        }
+        ofs += 1;
+        if (ofs >= plen)
+        {
+          /* next octet in next pbuf */
+          p = p->next;
+          if (p == NULL) { return ERR_ARG; }
+          msg_ptr = (u8_t*)p->payload;
+          plen += p->len;
+        }
+        else
+        {
+          /* next octet in same pbuf */
+          msg_ptr++;
+        }
+        ident_len -= 2;
+        ident += 2;
+      }
+      else
+      {
+/* @bug:  allow empty varbinds for symmetry (we must decode them for getnext), allow partial compression??  */
+        /* ident_len <= 1, at least we need zeroDotZero (0.0) (ident_len == 2) */
+        return ERR_ARG;
+      }
+      while (ident_len > 0)
+      {
+        s32_t sub_id;
+        u8_t shift, tail;
+
+        ident_len--;
+        sub_id = *ident;
+        tail = 0;
+        shift = 28;
+        while(shift > 0)
+        {
+          u8_t code;
+
+          code = (u8_t)(sub_id >> shift);
+          if ((code != 0) || (tail != 0))
+          {
+            tail = 1;
+            *msg_ptr = code | 0x80;
+            ofs += 1;
+            if (ofs >= plen)
+            {
+              /* next octet in next pbuf */
+              p = p->next;
+              if (p == NULL) { return ERR_ARG; }
+              msg_ptr = (u8_t*)p->payload;
+              plen += p->len;
+            }
+            else
+            {
+              /* next octet in same pbuf */
+              msg_ptr++;
+            }
+          }
+          shift -= 7;
+        }
+        *msg_ptr = (u8_t)sub_id & 0x7F;
+        if (ident_len > 0)
+        {
+          ofs += 1;
+          if (ofs >= plen)
+          {
+            /* next octet in next pbuf */
+            p = p->next;
+            if (p == NULL) { return ERR_ARG; }
+            msg_ptr = (u8_t*)p->payload;
+            plen += p->len;
+          }
+          else
+          {
+            /* next octet in same pbuf */
+            msg_ptr++;
+          }
+        }
+        /* proceed to next sub-identifier */
+        ident++;
+      }
+      return ERR_OK;
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+/**
+ * Encodes raw data (octet string, opaque) into a pbuf chained ASN1 msg.
+ *
+ * @param p points to output pbuf to encode raw data into
+ * @param ofs points to the offset within the pbuf chain
+ * @param raw_len raw data length
+ * @param raw points raw data
+ * @return ERR_OK if successfull, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_asn1_enc_raw(struct pbuf *p, u16_t ofs, u16_t raw_len, u8_t *raw)
+{
+  u16_t plen, base;
+  u8_t *msg_ptr;
+
+  plen = 0;
+  while (p != NULL)
+  {
+    base = plen;
+    plen += p->len;
+    if (ofs < plen)
+    {
+      msg_ptr = (u8_t*)p->payload;
+      msg_ptr += ofs - base;
+
+      while (raw_len > 1)
+      {
+        /* copy raw_len - 1 octets */
+        raw_len--;
+        *msg_ptr = *raw;
+        raw++;
+        ofs += 1;
+        if (ofs >= plen)
+        {
+          /* next octet in next pbuf */
+          p = p->next;
+          if (p == NULL) { return ERR_ARG; }
+          msg_ptr = (u8_t*)p->payload;
+          plen += p->len;
+        }
+        else
+        {
+          /* next octet in same pbuf */
+          msg_ptr++;
+        }
+      }
+      if (raw_len > 0)
+      {
+        /* copy last or single octet */
+        *msg_ptr = *raw;
+      }
+      return ERR_OK;
+    }
+    p = p->next;
+  }
+  /* p == NULL, ofs >= plen */
+  return ERR_ARG;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/external/badvpn_dns/lwip/src/core/snmp/mib2.c b/external/badvpn_dns/lwip/src/core/snmp/mib2.c
new file mode 100644
index 0000000..dcd3b62
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/snmp/mib2.c
@@ -0,0 +1,4146 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) objects and functions.
+ *
+ * @note the object identifiers for this MIB-2 and private MIB tree
+ * must be kept in sorted ascending order. This to ensure correct getnext operation.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Christiaan Simons <christiaan.simons@xxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/snmp.h"
+#include "lwip/netif.h"
+#include "lwip/ip.h"
+#include "lwip/ip_frag.h"
+#include "lwip/mem.h"
+#include "lwip/tcp_impl.h"
+#include "lwip/udp.h"
+#include "lwip/snmp_asn1.h"
+#include "lwip/snmp_structs.h"
+#include "lwip/sys.h"
+#include "netif/etharp.h"
+
+/**
+ * IANA assigned enterprise ID for lwIP is 26381
+ * @see http://www.iana.org/assignments/enterprise-numbers
+ *
+ * @note this enterprise ID is assigned to the lwIP project,
+ * all object identifiers living under this ID are assigned
+ * by the lwIP maintainers (contact Christiaan Simons)!
+ * @note don't change this define, use snmp_set_sysobjid()
+ *
+ * If you need to create your own private MIB you'll need
+ * to apply for your own enterprise ID with IANA:
+ * http://www.iana.org/numbers.html
+ */
+#define SNMP_ENTERPRISE_ID 26381
+#define SNMP_SYSOBJID_LEN 7
+#define SNMP_SYSOBJID {1, 3, 6, 1, 4, 1, SNMP_ENTERPRISE_ID}
+
+#ifndef SNMP_SYSSERVICES
+#define SNMP_SYSSERVICES ((1 << 6) | (1 << 3) | ((IP_FORWARD) << 2))
+#endif
+
+#ifndef SNMP_GET_SYSUPTIME
+#define SNMP_GET_SYSUPTIME(sysuptime)  (sysuptime = (sys_now() / 10))
+#endif
+
+static void system_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void system_get_value(struct obj_def *od, u16_t len, void *value);
+static u8_t system_set_test(struct obj_def *od, u16_t len, void *value);
+static void system_set_value(struct obj_def *od, u16_t len, void *value);
+static void interfaces_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void interfaces_get_value(struct obj_def *od, u16_t len, void *value);
+static void ifentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void ifentry_get_value(struct obj_def *od, u16_t len, void *value);
+#if !SNMP_SAFE_REQUESTS
+static u8_t ifentry_set_test (struct obj_def *od, u16_t len, void *value);
+static void ifentry_set_value (struct obj_def *od, u16_t len, void *value);
+#endif /* SNMP_SAFE_REQUESTS */
+static void atentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void atentry_get_value(struct obj_def *od, u16_t len, void *value);
+static void ip_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void ip_get_value(struct obj_def *od, u16_t len, void *value);
+static u8_t ip_set_test(struct obj_def *od, u16_t len, void *value);
+static void ip_addrentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void ip_addrentry_get_value(struct obj_def *od, u16_t len, void *value);
+static void ip_rteentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void ip_rteentry_get_value(struct obj_def *od, u16_t len, void *value);
+static void ip_ntomentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void ip_ntomentry_get_value(struct obj_def *od, u16_t len, void *value);
+static void icmp_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void icmp_get_value(struct obj_def *od, u16_t len, void *value);
+#if LWIP_TCP
+static void tcp_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void tcp_get_value(struct obj_def *od, u16_t len, void *value);
+#ifdef THIS_SEEMS_UNUSED
+static void tcpconnentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void tcpconnentry_get_value(struct obj_def *od, u16_t len, void *value);
+#endif
+#endif
+static void udp_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void udp_get_value(struct obj_def *od, u16_t len, void *value);
+static void udpentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void udpentry_get_value(struct obj_def *od, u16_t len, void *value);
+static void snmp_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+static void snmp_get_value(struct obj_def *od, u16_t len, void *value);
+static u8_t snmp_set_test(struct obj_def *od, u16_t len, void *value);
+static void snmp_set_value(struct obj_def *od, u16_t len, void *value);
+
+
+/* snmp .1.3.6.1.2.1.11 */
+const mib_scalar_node snmp_scalar = {
+  &snmp_get_object_def,
+  &snmp_get_value,
+  &snmp_set_test,
+  &snmp_set_value,
+  MIB_NODE_SC,
+  0
+};
+const s32_t snmp_ids[28] = {
+  1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+  17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30
+};
+struct mib_node* const snmp_nodes[28] = {
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar,
+  (struct mib_node*)&snmp_scalar, (struct mib_node*)&snmp_scalar
+};
+const struct mib_array_node snmp = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  28,
+  snmp_ids,
+  snmp_nodes
+};
+
+/* dot3 and EtherLike MIB not planned. (transmission .1.3.6.1.2.1.10) */
+/* historical (some say hysterical). (cmot .1.3.6.1.2.1.9) */
+/* lwIP has no EGP, thus may not implement it. (egp .1.3.6.1.2.1.8) */
+
+/* udp .1.3.6.1.2.1.7 */
+/** index root node for udpTable */
+struct mib_list_rootnode udp_root = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_LR,
+  0,
+  NULL,
+  NULL,
+  0
+};
+const s32_t udpentry_ids[2] = { 1, 2 };
+struct mib_node* const udpentry_nodes[2] = {
+  (struct mib_node*)&udp_root, (struct mib_node*)&udp_root,
+};
+const struct mib_array_node udpentry = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  2,
+  udpentry_ids,
+  udpentry_nodes
+};
+
+s32_t udptable_id = 1;
+struct mib_node* udptable_node = (struct mib_node*)&udpentry;
+struct mib_ram_array_node udptable = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_RA,
+  0,
+  &udptable_id,
+  &udptable_node
+};
+
+const mib_scalar_node udp_scalar = {
+  &udp_get_object_def,
+  &udp_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_SC,
+  0
+};
+const s32_t udp_ids[5] = { 1, 2, 3, 4, 5 };
+struct mib_node* const udp_nodes[5] = {
+  (struct mib_node*)&udp_scalar, (struct mib_node*)&udp_scalar,
+  (struct mib_node*)&udp_scalar, (struct mib_node*)&udp_scalar,
+  (struct mib_node*)&udptable
+};
+const struct mib_array_node udp = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  5,
+  udp_ids,
+  udp_nodes
+};
+
+/* tcp .1.3.6.1.2.1.6 */
+#if LWIP_TCP
+/* only if the TCP protocol is available may implement this group */
+/** index root node for tcpConnTable */
+struct mib_list_rootnode tcpconntree_root = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_LR,
+  0,
+  NULL,
+  NULL,
+  0
+};
+const s32_t tcpconnentry_ids[5] = { 1, 2, 3, 4, 5 };
+struct mib_node* const tcpconnentry_nodes[5] = {
+  (struct mib_node*)&tcpconntree_root, (struct mib_node*)&tcpconntree_root,
+  (struct mib_node*)&tcpconntree_root, (struct mib_node*)&tcpconntree_root,
+  (struct mib_node*)&tcpconntree_root
+};
+const struct mib_array_node tcpconnentry = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  5,
+  tcpconnentry_ids,
+  tcpconnentry_nodes
+};
+
+s32_t tcpconntable_id = 1;
+struct mib_node* tcpconntable_node = (struct mib_node*)&tcpconnentry;
+struct mib_ram_array_node tcpconntable = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_RA,
+/** @todo update maxlength when inserting / deleting from table
+   0 when table is empty, 1 when more than one entry */
+  0,
+  &tcpconntable_id,
+  &tcpconntable_node
+};
+
+const mib_scalar_node tcp_scalar = {
+  &tcp_get_object_def,
+  &tcp_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_SC,
+  0
+};
+const s32_t tcp_ids[15] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+struct mib_node* const tcp_nodes[15] = {
+  (struct mib_node*)&tcp_scalar, (struct mib_node*)&tcp_scalar,
+  (struct mib_node*)&tcp_scalar, (struct mib_node*)&tcp_scalar,
+  (struct mib_node*)&tcp_scalar, (struct mib_node*)&tcp_scalar,
+  (struct mib_node*)&tcp_scalar, (struct mib_node*)&tcp_scalar,
+  (struct mib_node*)&tcp_scalar, (struct mib_node*)&tcp_scalar,
+  (struct mib_node*)&tcp_scalar, (struct mib_node*)&tcp_scalar,
+  (struct mib_node*)&tcpconntable, (struct mib_node*)&tcp_scalar,
+  (struct mib_node*)&tcp_scalar
+};
+const struct mib_array_node tcp = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  15,
+  tcp_ids,
+  tcp_nodes
+};
+#endif
+
+/* icmp .1.3.6.1.2.1.5 */
+const mib_scalar_node icmp_scalar = {
+  &icmp_get_object_def,
+  &icmp_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_SC,
+  0
+};
+const s32_t icmp_ids[26] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 };
+struct mib_node* const icmp_nodes[26] = {
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar,
+  (struct mib_node*)&icmp_scalar, (struct mib_node*)&icmp_scalar
+};
+const struct mib_array_node icmp = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  26,
+  icmp_ids,
+  icmp_nodes
+};
+
+/** index root node for ipNetToMediaTable */
+struct mib_list_rootnode ipntomtree_root = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_LR,
+  0,
+  NULL,
+  NULL,
+  0
+};
+const s32_t ipntomentry_ids[4] = { 1, 2, 3, 4 };
+struct mib_node* const ipntomentry_nodes[4] = {
+  (struct mib_node*)&ipntomtree_root, (struct mib_node*)&ipntomtree_root,
+  (struct mib_node*)&ipntomtree_root, (struct mib_node*)&ipntomtree_root
+};
+const struct mib_array_node ipntomentry = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  4,
+  ipntomentry_ids,
+  ipntomentry_nodes
+};
+
+s32_t ipntomtable_id = 1;
+struct mib_node* ipntomtable_node = (struct mib_node*)&ipntomentry;
+struct mib_ram_array_node ipntomtable = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_RA,
+  0,
+  &ipntomtable_id,
+  &ipntomtable_node
+};
+
+/** index root node for ipRouteTable */
+struct mib_list_rootnode iprtetree_root = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_LR,
+  0,
+  NULL,
+  NULL,
+  0
+};
+const s32_t iprteentry_ids[13] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
+struct mib_node* const iprteentry_nodes[13] = {
+  (struct mib_node*)&iprtetree_root, (struct mib_node*)&iprtetree_root,
+  (struct mib_node*)&iprtetree_root, (struct mib_node*)&iprtetree_root,
+  (struct mib_node*)&iprtetree_root, (struct mib_node*)&iprtetree_root,
+  (struct mib_node*)&iprtetree_root, (struct mib_node*)&iprtetree_root,
+  (struct mib_node*)&iprtetree_root, (struct mib_node*)&iprtetree_root,
+  (struct mib_node*)&iprtetree_root, (struct mib_node*)&iprtetree_root,
+  (struct mib_node*)&iprtetree_root
+};
+const struct mib_array_node iprteentry = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  13,
+  iprteentry_ids,
+  iprteentry_nodes
+};
+
+s32_t iprtetable_id = 1;
+struct mib_node* iprtetable_node = (struct mib_node*)&iprteentry;
+struct mib_ram_array_node iprtetable = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_RA,
+  0,
+  &iprtetable_id,
+  &iprtetable_node
+};
+
+/** index root node for ipAddrTable */
+struct mib_list_rootnode ipaddrtree_root = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_LR,
+  0,
+  NULL,
+  NULL,
+  0
+};
+const s32_t ipaddrentry_ids[5] = { 1, 2, 3, 4, 5 };
+struct mib_node* const ipaddrentry_nodes[5] = {
+  (struct mib_node*)&ipaddrtree_root,
+  (struct mib_node*)&ipaddrtree_root,
+  (struct mib_node*)&ipaddrtree_root,
+  (struct mib_node*)&ipaddrtree_root,
+  (struct mib_node*)&ipaddrtree_root
+};
+const struct mib_array_node ipaddrentry = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  5,
+  ipaddrentry_ids,
+  ipaddrentry_nodes
+};
+
+s32_t ipaddrtable_id = 1;
+struct mib_node* ipaddrtable_node = (struct mib_node*)&ipaddrentry;
+struct mib_ram_array_node ipaddrtable = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_RA,
+  0,
+  &ipaddrtable_id,
+  &ipaddrtable_node
+};
+
+/* ip .1.3.6.1.2.1.4 */
+const mib_scalar_node ip_scalar = {
+  &ip_get_object_def,
+  &ip_get_value,
+  &ip_set_test,
+  &noleafs_set_value,
+  MIB_NODE_SC,
+  0
+};
+const s32_t ip_ids[23] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 };
+struct mib_node* const ip_nodes[23] = {
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ip_scalar,
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ip_scalar,
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ip_scalar,
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ip_scalar,
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ip_scalar,
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ip_scalar,
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ip_scalar,
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ip_scalar,
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ip_scalar,
+  (struct mib_node*)&ip_scalar, (struct mib_node*)&ipaddrtable,
+  (struct mib_node*)&iprtetable, (struct mib_node*)&ipntomtable,
+  (struct mib_node*)&ip_scalar
+};
+const struct mib_array_node mib2_ip = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  23,
+  ip_ids,
+  ip_nodes
+};
+
+/** index root node for atTable */
+struct mib_list_rootnode arptree_root = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_LR,
+  0,
+  NULL,
+  NULL,
+  0
+};
+const s32_t atentry_ids[3] = { 1, 2, 3 };
+struct mib_node* const atentry_nodes[3] = {
+  (struct mib_node*)&arptree_root,
+  (struct mib_node*)&arptree_root,
+  (struct mib_node*)&arptree_root
+};
+const struct mib_array_node atentry = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  3,
+  atentry_ids,
+  atentry_nodes
+};
+
+const s32_t attable_id = 1;
+struct mib_node* const attable_node = (struct mib_node*)&atentry;
+const struct mib_array_node attable = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  1,
+  &attable_id,
+  &attable_node
+};
+
+/* at .1.3.6.1.2.1.3 */
+s32_t at_id = 1;
+struct mib_node* mib2_at_node = (struct mib_node*)&attable;
+struct mib_ram_array_node at = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_RA,
+  0,
+  &at_id,
+  &mib2_at_node
+};
+
+/** index root node for ifTable */
+struct mib_list_rootnode iflist_root = {
+  &ifentry_get_object_def,
+  &ifentry_get_value,
+#if SNMP_SAFE_REQUESTS
+  &noleafs_set_test,
+  &noleafs_set_value,
+#else /* SNMP_SAFE_REQUESTS */
+  &ifentry_set_test,
+  &ifentry_set_value,
+#endif /* SNMP_SAFE_REQUESTS */
+  MIB_NODE_LR,
+  0,
+  NULL,
+  NULL,
+  0
+};
+const s32_t ifentry_ids[22] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 };
+struct mib_node* const ifentry_nodes[22] = {
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root,
+  (struct mib_node*)&iflist_root, (struct mib_node*)&iflist_root
+};
+const struct mib_array_node ifentry = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  22,
+  ifentry_ids,
+  ifentry_nodes
+};
+
+s32_t iftable_id = 1;
+struct mib_node* iftable_node = (struct mib_node*)&ifentry;
+struct mib_ram_array_node iftable = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_RA,
+  0,
+  &iftable_id,
+  &iftable_node
+};
+
+/* interfaces .1.3.6.1.2.1.2 */
+const mib_scalar_node interfaces_scalar = {
+  &interfaces_get_object_def,
+  &interfaces_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_SC,
+  0
+};
+const s32_t interfaces_ids[2] = { 1, 2 };
+struct mib_node* const interfaces_nodes[2] = {
+  (struct mib_node*)&interfaces_scalar, (struct mib_node*)&iftable
+};
+const struct mib_array_node interfaces = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  2,
+  interfaces_ids,
+  interfaces_nodes
+};
+
+
+/*             0 1 2 3 4 5 6 */
+/* system .1.3.6.1.2.1.1 */
+const mib_scalar_node sys_tem_scalar = {
+  &system_get_object_def,
+  &system_get_value,
+  &system_set_test,
+  &system_set_value,
+  MIB_NODE_SC,
+  0
+};
+const s32_t sys_tem_ids[7] = { 1, 2, 3, 4, 5, 6, 7 };
+struct mib_node* const sys_tem_nodes[7] = {
+  (struct mib_node*)&sys_tem_scalar, (struct mib_node*)&sys_tem_scalar,
+  (struct mib_node*)&sys_tem_scalar, (struct mib_node*)&sys_tem_scalar,
+  (struct mib_node*)&sys_tem_scalar, (struct mib_node*)&sys_tem_scalar,
+  (struct mib_node*)&sys_tem_scalar
+};
+/* work around name issue with 'sys_tem', some compiler(s?) seem to reserve 'system' */
+const struct mib_array_node sys_tem = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  7,
+  sys_tem_ids,
+  sys_tem_nodes
+};
+
+/* mib-2 .1.3.6.1.2.1 */
+#if LWIP_TCP
+#define MIB2_GROUPS 8
+#else
+#define MIB2_GROUPS 7
+#endif
+const s32_t mib2_ids[MIB2_GROUPS] =
+{
+  1,
+  2,
+  3,
+  4,
+  5,
+#if LWIP_TCP
+  6,
+#endif
+  7,
+  11
+};
+struct mib_node* const mib2_nodes[MIB2_GROUPS] = {
+  (struct mib_node*)&sys_tem,
+  (struct mib_node*)&interfaces,
+  (struct mib_node*)&at,
+  (struct mib_node*)&mib2_ip,
+  (struct mib_node*)&icmp,
+#if LWIP_TCP
+  (struct mib_node*)&tcp,
+#endif
+  (struct mib_node*)&udp,
+  (struct mib_node*)&snmp
+};
+
+const struct mib_array_node mib2 = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  MIB2_GROUPS,
+  mib2_ids,
+  mib2_nodes
+};
+
+/* mgmt .1.3.6.1.2 */
+const s32_t mgmt_ids[1] = { 1 };
+struct mib_node* const mgmt_nodes[1] = { (struct mib_node*)&mib2 };
+const struct mib_array_node mgmt = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  1,
+  mgmt_ids,
+  mgmt_nodes
+};
+
+/* internet .1.3.6.1 */
+#if SNMP_PRIVATE_MIB
+/* When using a private MIB, you have to create a file 'private_mib.h' that contains
+ * a 'struct mib_array_node mib_private' which contains your MIB. */
+s32_t internet_ids[2] = { 2, 4 };
+struct mib_node* const internet_nodes[2] = { (struct mib_node*)&mgmt, (struct mib_node*)&mib_private };
+const struct mib_array_node internet = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  2,
+  internet_ids,
+  internet_nodes
+};
+#else
+const s32_t internet_ids[1] = { 2 };
+struct mib_node* const internet_nodes[1] = { (struct mib_node*)&mgmt };
+const struct mib_array_node internet = {
+  &noleafs_get_object_def,
+  &noleafs_get_value,
+  &noleafs_set_test,
+  &noleafs_set_value,
+  MIB_NODE_AR,
+  1,
+  internet_ids,
+  internet_nodes
+};
+#endif
+
+/** mib-2.system.sysObjectID  */
+static struct snmp_obj_id sysobjid = {SNMP_SYSOBJID_LEN, SNMP_SYSOBJID};
+/** enterprise ID for generic TRAPs, .iso.org.dod.internet.mgmt.mib-2.snmp */
+static struct snmp_obj_id snmpgrp_id = {7,{1,3,6,1,2,1,11}};
+/** mib-2.system.sysServices */
+static const s32_t sysservices = SNMP_SYSSERVICES;
+
+/** mib-2.system.sysDescr */
+static const u8_t sysdescr_len_default = 4;
+static const u8_t sysdescr_default[] = "lwIP";
+static u8_t* sysdescr_len_ptr = (u8_t*)&sysdescr_len_default;
+static u8_t* sysdescr_ptr = (u8_t*)&sysdescr_default[0];
+/** mib-2.system.sysContact */
+static const u8_t syscontact_len_default = 0;
+static const u8_t syscontact_default[] = "";
+static u8_t* syscontact_len_ptr = (u8_t*)&syscontact_len_default;
+static u8_t* syscontact_ptr = (u8_t*)&syscontact_default[0];
+/** mib-2.system.sysName */
+static const u8_t sysname_len_default = 8;
+static const u8_t sysname_default[] = "FQDN-unk";
+static u8_t* sysname_len_ptr = (u8_t*)&sysname_len_default;
+static u8_t* sysname_ptr = (u8_t*)&sysname_default[0];
+/** mib-2.system.sysLocation */
+static const u8_t syslocation_len_default = 0;
+static const u8_t syslocation_default[] = "";
+static u8_t* syslocation_len_ptr = (u8_t*)&syslocation_len_default;
+static u8_t* syslocation_ptr = (u8_t*)&syslocation_default[0];
+/** mib-2.snmp.snmpEnableAuthenTraps */
+static const u8_t snmpenableauthentraps_default = 2; /* disabled */
+static u8_t* snmpenableauthentraps_ptr = (u8_t*)&snmpenableauthentraps_default;
+
+/** mib-2.interfaces.ifTable.ifEntry.ifSpecific (zeroDotZero) */
+static const struct snmp_obj_id ifspecific = {2, {0, 0}};
+/** mib-2.ip.ipRouteTable.ipRouteEntry.ipRouteInfo (zeroDotZero) */
+static const struct snmp_obj_id iprouteinfo = {2, {0, 0}};
+
+
+
+/* mib-2.system counter(s) */
+static u32_t sysuptime = 0;
+
+/* mib-2.ip counter(s) */
+static u32_t ipinreceives = 0,
+             ipinhdrerrors = 0,
+             ipinaddrerrors = 0,
+             ipforwdatagrams = 0,
+             ipinunknownprotos = 0,
+             ipindiscards = 0,
+             ipindelivers = 0,
+             ipoutrequests = 0,
+             ipoutdiscards = 0,
+             ipoutnoroutes = 0,
+             ipreasmreqds = 0,
+             ipreasmoks = 0,
+             ipreasmfails = 0,
+             ipfragoks = 0,
+             ipfragfails = 0,
+             ipfragcreates = 0,
+             iproutingdiscards = 0;
+/* mib-2.icmp counter(s) */
+static u32_t icmpinmsgs = 0,
+             icmpinerrors = 0,
+             icmpindestunreachs = 0,
+             icmpintimeexcds = 0,
+             icmpinparmprobs = 0,
+             icmpinsrcquenchs = 0,
+             icmpinredirects = 0,
+             icmpinechos = 0,
+             icmpinechoreps = 0,
+             icmpintimestamps = 0,
+             icmpintimestampreps = 0,
+             icmpinaddrmasks = 0,
+             icmpinaddrmaskreps = 0,
+             icmpoutmsgs = 0,
+             icmpouterrors = 0,
+             icmpoutdestunreachs = 0,
+             icmpouttimeexcds = 0,
+             icmpoutparmprobs = 0,
+             icmpoutsrcquenchs = 0,
+             icmpoutredirects = 0,
+             icmpoutechos = 0,
+             icmpoutechoreps = 0,
+             icmpouttimestamps = 0,
+             icmpouttimestampreps = 0,
+             icmpoutaddrmasks = 0,
+             icmpoutaddrmaskreps = 0;
+/* mib-2.tcp counter(s) */
+static u32_t tcpactiveopens = 0,
+             tcppassiveopens = 0,
+             tcpattemptfails = 0,
+             tcpestabresets = 0,
+             tcpinsegs = 0,
+             tcpoutsegs = 0,
+             tcpretranssegs = 0,
+             tcpinerrs = 0,
+             tcpoutrsts = 0;
+/* mib-2.udp counter(s) */
+static u32_t udpindatagrams = 0,
+             udpnoports = 0,
+             udpinerrors = 0,
+             udpoutdatagrams = 0;
+/* mib-2.snmp counter(s) */
+static u32_t snmpinpkts = 0,
+             snmpoutpkts = 0,
+             snmpinbadversions = 0,
+             snmpinbadcommunitynames = 0,
+             snmpinbadcommunityuses = 0,
+             snmpinasnparseerrs = 0,
+             snmpintoobigs = 0,
+             snmpinnosuchnames = 0,
+             snmpinbadvalues = 0,
+             snmpinreadonlys = 0,
+             snmpingenerrs = 0,
+             snmpintotalreqvars = 0,
+             snmpintotalsetvars = 0,
+             snmpingetrequests = 0,
+             snmpingetnexts = 0,
+             snmpinsetrequests = 0,
+             snmpingetresponses = 0,
+             snmpintraps = 0,
+             snmpouttoobigs = 0,
+             snmpoutnosuchnames = 0,
+             snmpoutbadvalues = 0,
+             snmpoutgenerrs = 0,
+             snmpoutgetrequests = 0,
+             snmpoutgetnexts = 0,
+             snmpoutsetrequests = 0,
+             snmpoutgetresponses = 0,
+             snmpouttraps = 0;
+
+
+
+/* prototypes of the following functions are in lwip/src/include/lwip/snmp.h */
+/**
+ * Copy octet string.
+ *
+ * @param dst points to destination
+ * @param src points to source
+ * @param n number of octets to copy.
+ */
+static void ocstrncpy(u8_t *dst, u8_t *src, u16_t n)
+{
+  u16_t i = n;
+  while (i > 0) {
+    i--;
+    *dst++ = *src++;
+  }
+}
+
+/**
+ * Copy object identifier (s32_t) array.
+ *
+ * @param dst points to destination
+ * @param src points to source
+ * @param n number of sub identifiers to copy.
+ */
+void objectidncpy(s32_t *dst, s32_t *src, u8_t n)
+{
+  u8_t i = n;
+  while(i > 0) {
+    i--;
+    *dst++ = *src++;
+  }
+}
+
+/**
+ * Initializes sysDescr pointers.
+ *
+ * @param str if non-NULL then copy str pointer
+ * @param len points to string length, excluding zero terminator
+ */
+void snmp_set_sysdesr(u8_t *str, u8_t *len)
+{
+  if (str != NULL)
+  {
+    sysdescr_ptr = str;
+    sysdescr_len_ptr = len;
+  }
+}
+
+void snmp_get_sysobjid_ptr(struct snmp_obj_id **oid)
+{
+  *oid = &sysobjid;
+}
+
+/**
+ * Initializes sysObjectID value.
+ *
+ * @param oid points to stuct snmp_obj_id to copy
+ */
+void snmp_set_sysobjid(struct snmp_obj_id *oid)
+{
+  sysobjid = *oid;
+}
+
+/**
+ * Must be called at regular 10 msec interval from a timer interrupt
+ * or signal handler depending on your runtime environment.
+ */
+void snmp_inc_sysuptime(void)
+{
+  sysuptime++;
+}
+
+void snmp_add_sysuptime(u32_t value)
+{
+  sysuptime+=value;
+}
+
+void snmp_get_sysuptime(u32_t *value)
+{
+  SNMP_GET_SYSUPTIME(sysuptime);
+  *value = sysuptime;
+}
+
+/**
+ * Initializes sysContact pointers,
+ * e.g. ptrs to non-volatile memory external to lwIP.
+ *
+ * @param ocstr if non-NULL then copy str pointer
+ * @param ocstrlen points to string length, excluding zero terminator
+ */
+void snmp_set_syscontact(u8_t *ocstr, u8_t *ocstrlen)
+{
+  if (ocstr != NULL)
+  {
+    syscontact_ptr = ocstr;
+    syscontact_len_ptr = ocstrlen;
+  }
+}
+
+/**
+ * Initializes sysName pointers,
+ * e.g. ptrs to non-volatile memory external to lwIP.
+ *
+ * @param ocstr if non-NULL then copy str pointer
+ * @param ocstrlen points to string length, excluding zero terminator
+ */
+void snmp_set_sysname(u8_t *ocstr, u8_t *ocstrlen)
+{
+  if (ocstr != NULL)
+  {
+    sysname_ptr = ocstr;
+    sysname_len_ptr = ocstrlen;
+  }
+}
+
+/**
+ * Initializes sysLocation pointers,
+ * e.g. ptrs to non-volatile memory external to lwIP.
+ *
+ * @param ocstr if non-NULL then copy str pointer
+ * @param ocstrlen points to string length, excluding zero terminator
+ */
+void snmp_set_syslocation(u8_t *ocstr, u8_t *ocstrlen)
+{
+  if (ocstr != NULL)
+  {
+    syslocation_ptr = ocstr;
+    syslocation_len_ptr = ocstrlen;
+  }
+}
+
+
+void snmp_add_ifinoctets(struct netif *ni, u32_t value)
+{
+  ni->ifinoctets += value;
+}
+
+void snmp_inc_ifinucastpkts(struct netif *ni)
+{
+  (ni->ifinucastpkts)++;
+}
+
+void snmp_inc_ifinnucastpkts(struct netif *ni)
+{
+  (ni->ifinnucastpkts)++;
+}
+
+void snmp_inc_ifindiscards(struct netif *ni)
+{
+  (ni->ifindiscards)++;
+}
+
+void snmp_add_ifoutoctets(struct netif *ni, u32_t value)
+{
+  ni->ifoutoctets += value;
+}
+
+void snmp_inc_ifoutucastpkts(struct netif *ni)
+{
+  (ni->ifoutucastpkts)++;
+}
+
+void snmp_inc_ifoutnucastpkts(struct netif *ni)
+{
+  (ni->ifoutnucastpkts)++;
+}
+
+void snmp_inc_ifoutdiscards(struct netif *ni)
+{
+  (ni->ifoutdiscards)++;
+}
+
+void snmp_inc_iflist(void)
+{
+  struct mib_list_node *if_node = NULL;
+
+  snmp_mib_node_insert(&iflist_root, iflist_root.count + 1, &if_node);
+  /* enable getnext traversal on filled table */
+  iftable.maxlength = 1;
+}
+
+void snmp_dec_iflist(void)
+{
+  snmp_mib_node_delete(&iflist_root, iflist_root.tail);
+  /* disable getnext traversal on empty table */
+  if(iflist_root.count == 0) iftable.maxlength = 0;
+}
+
+/**
+ * Inserts ARP table indexes (.xIfIndex.xNetAddress)
+ * into arp table index trees (both atTable and ipNetToMediaTable).
+ */
+void snmp_insert_arpidx_tree(struct netif *ni, ip_addr_t *ip)
+{
+  struct mib_list_rootnode *at_rn;
+  struct mib_list_node *at_node;
+  s32_t arpidx[5];
+  u8_t level, tree;
+
+  LWIP_ASSERT("ni != NULL", ni != NULL);
+  snmp_netiftoifindex(ni, &arpidx[0]);
+  snmp_iptooid(ip, &arpidx[1]);
+
+  for (tree = 0; tree < 2; tree++)
+  {
+    if (tree == 0)
+    {
+      at_rn = &arptree_root;
+    }
+    else
+    {
+      at_rn = &ipntomtree_root;
+    }
+    for (level = 0; level < 5; level++)
+    {
+      at_node = NULL;
+      snmp_mib_node_insert(at_rn, arpidx[level], &at_node);
+      if ((level != 4) && (at_node != NULL))
+      {
+        if (at_node->nptr == NULL)
+        {
+          at_rn = snmp_mib_lrn_alloc();
+          at_node->nptr = (struct mib_node*)at_rn;
+          if (at_rn != NULL)
+          {
+            if (level == 3)
+            {
+              if (tree == 0)
+              {
+                at_rn->get_object_def = atentry_get_object_def;
+                at_rn->get_value = atentry_get_value;
+              }
+              else
+              {
+                at_rn->get_object_def = ip_ntomentry_get_object_def;
+                at_rn->get_value = ip_ntomentry_get_value;
+              }
+              at_rn->set_test = noleafs_set_test;
+              at_rn->set_value = noleafs_set_value;
+            }
+          }
+          else
+          {
+            /* at_rn == NULL, malloc failure */
+            LWIP_DEBUGF(SNMP_MIB_DEBUG,("snmp_insert_arpidx_tree() insert failed, mem full"));
+            break;
+          }
+        }
+        else
+        {
+          at_rn = (struct mib_list_rootnode*)at_node->nptr;
+        }
+      }
+    }
+  }
+  /* enable getnext traversal on filled tables */
+  at.maxlength = 1;
+  ipntomtable.maxlength = 1;
+}
+
+/**
+ * Removes ARP table indexes (.xIfIndex.xNetAddress)
+ * from arp table index trees.
+ */
+void snmp_delete_arpidx_tree(struct netif *ni, ip_addr_t *ip)
+{
+  struct mib_list_rootnode *at_rn, *next, *del_rn[5];
+  struct mib_list_node *at_n, *del_n[5];
+  s32_t arpidx[5];
+  u8_t fc, tree, level, del_cnt;
+
+  snmp_netiftoifindex(ni, &arpidx[0]);
+  snmp_iptooid(ip, &arpidx[1]);
+
+  for (tree = 0; tree < 2; tree++)
+  {
+    /* mark nodes for deletion */
+    if (tree == 0)
+    {
+      at_rn = &arptree_root;
+    }
+    else
+    {
+      at_rn = &ipntomtree_root;
+    }
+    level = 0;
+    del_cnt = 0;
+    while ((level < 5) && (at_rn != NULL))
+    {
+      fc = snmp_mib_node_find(at_rn, arpidx[level], &at_n);
+      if (fc == 0)
+      {
+        /* arpidx[level] does not exist */
+        del_cnt = 0;
+        at_rn = NULL;
+      }
+      else if (fc == 1)
+      {
+        del_rn[del_cnt] = at_rn;
+        del_n[del_cnt] = at_n;
+        del_cnt++;
+        at_rn = (struct mib_list_rootnode*)(at_n->nptr);
+      }
+      else if (fc == 2)
+      {
+        /* reset delete (2 or more childs) */
+        del_cnt = 0;
+        at_rn = (struct mib_list_rootnode*)(at_n->nptr);
+      }
+      level++;
+    }
+    /* delete marked index nodes */
+    while (del_cnt > 0)
+    {
+      del_cnt--;
+
+      at_rn = del_rn[del_cnt];
+      at_n = del_n[del_cnt];
+
+      next = snmp_mib_node_delete(at_rn, at_n);
+      if (next != NULL)
+      {
+        LWIP_ASSERT("next_count == 0",next->count == 0);
+        snmp_mib_lrn_free(next);
+      }
+    }
+  }
+  /* disable getnext traversal on empty tables */
+  if(arptree_root.count == 0) at.maxlength = 0;
+  if(ipntomtree_root.count == 0) ipntomtable.maxlength = 0;
+}
+
+void snmp_inc_ipinreceives(void)
+{
+  ipinreceives++;
+}
+
+void snmp_inc_ipinhdrerrors(void)
+{
+  ipinhdrerrors++;
+}
+
+void snmp_inc_ipinaddrerrors(void)
+{
+  ipinaddrerrors++;
+}
+
+void snmp_inc_ipforwdatagrams(void)
+{
+  ipforwdatagrams++;
+}
+
+void snmp_inc_ipinunknownprotos(void)
+{
+  ipinunknownprotos++;
+}
+
+void snmp_inc_ipindiscards(void)
+{
+  ipindiscards++;
+}
+
+void snmp_inc_ipindelivers(void)
+{
+  ipindelivers++;
+}
+
+void snmp_inc_ipoutrequests(void)
+{
+  ipoutrequests++;
+}
+
+void snmp_inc_ipoutdiscards(void)
+{
+  ipoutdiscards++;
+}
+
+void snmp_inc_ipoutnoroutes(void)
+{
+  ipoutnoroutes++;
+}
+
+void snmp_inc_ipreasmreqds(void)
+{
+  ipreasmreqds++;
+}
+
+void snmp_inc_ipreasmoks(void)
+{
+  ipreasmoks++;
+}
+
+void snmp_inc_ipreasmfails(void)
+{
+  ipreasmfails++;
+}
+
+void snmp_inc_ipfragoks(void)
+{
+  ipfragoks++;
+}
+
+void snmp_inc_ipfragfails(void)
+{
+  ipfragfails++;
+}
+
+void snmp_inc_ipfragcreates(void)
+{
+  ipfragcreates++;
+}
+
+void snmp_inc_iproutingdiscards(void)
+{
+  iproutingdiscards++;
+}
+
+/**
+ * Inserts ipAddrTable indexes (.ipAdEntAddr)
+ * into index tree.
+ */
+void snmp_insert_ipaddridx_tree(struct netif *ni)
+{
+  struct mib_list_rootnode *ipa_rn;
+  struct mib_list_node *ipa_node;
+  s32_t ipaddridx[4];
+  u8_t level;
+
+  LWIP_ASSERT("ni != NULL", ni != NULL);
+  snmp_iptooid(&ni->ip_addr, &ipaddridx[0]);
+
+  level = 0;
+  ipa_rn = &ipaddrtree_root;
+  while (level < 4)
+  {
+    ipa_node = NULL;
+    snmp_mib_node_insert(ipa_rn, ipaddridx[level], &ipa_node);
+    if ((level != 3) && (ipa_node != NULL))
+    {
+      if (ipa_node->nptr == NULL)
+      {
+        ipa_rn = snmp_mib_lrn_alloc();
+        ipa_node->nptr = (struct mib_node*)ipa_rn;
+        if (ipa_rn != NULL)
+        {
+          if (level == 2)
+          {
+            ipa_rn->get_object_def = ip_addrentry_get_object_def;
+            ipa_rn->get_value = ip_addrentry_get_value;
+            ipa_rn->set_test = noleafs_set_test;
+            ipa_rn->set_value = noleafs_set_value;
+          }
+        }
+        else
+        {
+          /* ipa_rn == NULL, malloc failure */
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("snmp_insert_ipaddridx_tree() insert failed, mem full"));
+          break;
+        }
+      }
+      else
+      {
+        ipa_rn = (struct mib_list_rootnode*)ipa_node->nptr;
+      }
+    }
+    level++;
+  }
+  /* enable getnext traversal on filled table */
+  ipaddrtable.maxlength = 1;
+}
+
+/**
+ * Removes ipAddrTable indexes (.ipAdEntAddr)
+ * from index tree.
+ */
+void snmp_delete_ipaddridx_tree(struct netif *ni)
+{
+  struct mib_list_rootnode *ipa_rn, *next, *del_rn[4];
+  struct mib_list_node *ipa_n, *del_n[4];
+  s32_t ipaddridx[4];
+  u8_t fc, level, del_cnt;
+
+  LWIP_ASSERT("ni != NULL", ni != NULL);
+  snmp_iptooid(&ni->ip_addr, &ipaddridx[0]);
+
+  /* mark nodes for deletion */
+  level = 0;
+  del_cnt = 0;
+  ipa_rn = &ipaddrtree_root;
+  while ((level < 4) && (ipa_rn != NULL))
+  {
+    fc = snmp_mib_node_find(ipa_rn, ipaddridx[level], &ipa_n);
+    if (fc == 0)
+    {
+      /* ipaddridx[level] does not exist */
+      del_cnt = 0;
+      ipa_rn = NULL;
+    }
+    else if (fc == 1)
+    {
+      del_rn[del_cnt] = ipa_rn;
+      del_n[del_cnt] = ipa_n;
+      del_cnt++;
+      ipa_rn = (struct mib_list_rootnode*)(ipa_n->nptr);
+    }
+    else if (fc == 2)
+    {
+      /* reset delete (2 or more childs) */
+      del_cnt = 0;
+      ipa_rn = (struct mib_list_rootnode*)(ipa_n->nptr);
+    }
+    level++;
+  }
+  /* delete marked index nodes */
+  while (del_cnt > 0)
+  {
+    del_cnt--;
+
+    ipa_rn = del_rn[del_cnt];
+    ipa_n = del_n[del_cnt];
+
+    next = snmp_mib_node_delete(ipa_rn, ipa_n);
+    if (next != NULL)
+    {
+      LWIP_ASSERT("next_count == 0",next->count == 0);
+      snmp_mib_lrn_free(next);
+    }
+  }
+  /* disable getnext traversal on empty table */
+  if (ipaddrtree_root.count == 0) ipaddrtable.maxlength = 0;
+}
+
+/**
+ * Inserts ipRouteTable indexes (.ipRouteDest)
+ * into index tree.
+ *
+ * @param dflt non-zero for the default rte, zero for network rte
+ * @param ni points to network interface for this rte
+ *
+ * @todo record sysuptime for _this_ route when it is installed
+ *   (needed for ipRouteAge) in the netif.
+ */
+void snmp_insert_iprteidx_tree(u8_t dflt, struct netif *ni)
+{
+  u8_t insert = 0;
+  ip_addr_t dst;
+
+  if (dflt != 0)
+  {
+    /* the default route 0.0.0.0 */
+    ip_addr_set_any(&dst);
+    insert = 1;
+  }
+  else
+  {
+    /* route to the network address */
+    ip_addr_get_network(&dst, &ni->ip_addr, &ni->netmask);
+    /* exclude 0.0.0.0 network (reserved for default rte) */
+    if (!ip_addr_isany(&dst)) {
+      insert = 1;
+    }
+  }
+  if (insert)
+  {
+    struct mib_list_rootnode *iprte_rn;
+    struct mib_list_node *iprte_node;
+    s32_t iprteidx[4];
+    u8_t level;
+
+    snmp_iptooid(&dst, &iprteidx[0]);
+    level = 0;
+    iprte_rn = &iprtetree_root;
+    while (level < 4)
+    {
+      iprte_node = NULL;
+      snmp_mib_node_insert(iprte_rn, iprteidx[level], &iprte_node);
+      if ((level != 3) && (iprte_node != NULL))
+      {
+        if (iprte_node->nptr == NULL)
+        {
+          iprte_rn = snmp_mib_lrn_alloc();
+          iprte_node->nptr = (struct mib_node*)iprte_rn;
+          if (iprte_rn != NULL)
+          {
+            if (level == 2)
+            {
+              iprte_rn->get_object_def = ip_rteentry_get_object_def;
+              iprte_rn->get_value = ip_rteentry_get_value;
+              iprte_rn->set_test = noleafs_set_test;
+              iprte_rn->set_value = noleafs_set_value;
+            }
+          }
+          else
+          {
+            /* iprte_rn == NULL, malloc failure */
+            LWIP_DEBUGF(SNMP_MIB_DEBUG,("snmp_insert_iprteidx_tree() insert failed, mem full"));
+            break;
+          }
+        }
+        else
+        {
+          iprte_rn = (struct mib_list_rootnode*)iprte_node->nptr;
+        }
+      }
+      level++;
+    }
+  }
+  /* enable getnext traversal on filled table */
+  iprtetable.maxlength = 1;
+}
+
+/**
+ * Removes ipRouteTable indexes (.ipRouteDest)
+ * from index tree.
+ *
+ * @param dflt non-zero for the default rte, zero for network rte
+ * @param ni points to network interface for this rte or NULL
+ *   for default route to be removed.
+ */
+void snmp_delete_iprteidx_tree(u8_t dflt, struct netif *ni)
+{
+  u8_t del = 0;
+  ip_addr_t dst;
+
+  if (dflt != 0)
+  {
+    /* the default route 0.0.0.0 */
+    ip_addr_set_any(&dst);
+    del = 1;
+  }
+  else
+  {
+    /* route to the network address */
+    ip_addr_get_network(&dst, &ni->ip_addr, &ni->netmask);
+    /* exclude 0.0.0.0 network (reserved for default rte) */
+    if (!ip_addr_isany(&dst)) {
+      del = 1;
+    }
+  }
+  if (del)
+  {
+    struct mib_list_rootnode *iprte_rn, *next, *del_rn[4];
+    struct mib_list_node *iprte_n, *del_n[4];
+    s32_t iprteidx[4];
+    u8_t fc, level, del_cnt;
+
+    snmp_iptooid(&dst, &iprteidx[0]);
+    /* mark nodes for deletion */
+    level = 0;
+    del_cnt = 0;
+    iprte_rn = &iprtetree_root;
+    while ((level < 4) && (iprte_rn != NULL))
+    {
+      fc = snmp_mib_node_find(iprte_rn, iprteidx[level], &iprte_n);
+      if (fc == 0)
+      {
+        /* iprteidx[level] does not exist */
+        del_cnt = 0;
+        iprte_rn = NULL;
+      }
+      else if (fc == 1)
+      {
+        del_rn[del_cnt] = iprte_rn;
+        del_n[del_cnt] = iprte_n;
+        del_cnt++;
+        iprte_rn = (struct mib_list_rootnode*)(iprte_n->nptr);
+      }
+      else if (fc == 2)
+      {
+        /* reset delete (2 or more childs) */
+        del_cnt = 0;
+        iprte_rn = (struct mib_list_rootnode*)(iprte_n->nptr);
+      }
+      level++;
+    }
+    /* delete marked index nodes */
+    while (del_cnt > 0)
+    {
+      del_cnt--;
+
+      iprte_rn = del_rn[del_cnt];
+      iprte_n = del_n[del_cnt];
+
+      next = snmp_mib_node_delete(iprte_rn, iprte_n);
+      if (next != NULL)
+      {
+        LWIP_ASSERT("next_count == 0",next->count == 0);
+        snmp_mib_lrn_free(next);
+      }
+    }
+  }
+  /* disable getnext traversal on empty table */
+  if (iprtetree_root.count == 0) iprtetable.maxlength = 0;
+}
+
+
+void snmp_inc_icmpinmsgs(void)
+{
+  icmpinmsgs++;
+}
+
+void snmp_inc_icmpinerrors(void)
+{
+  icmpinerrors++;
+}
+
+void snmp_inc_icmpindestunreachs(void)
+{
+  icmpindestunreachs++;
+}
+
+void snmp_inc_icmpintimeexcds(void)
+{
+  icmpintimeexcds++;
+}
+
+void snmp_inc_icmpinparmprobs(void)
+{
+  icmpinparmprobs++;
+}
+
+void snmp_inc_icmpinsrcquenchs(void)
+{
+  icmpinsrcquenchs++;
+}
+
+void snmp_inc_icmpinredirects(void)
+{
+  icmpinredirects++;
+}
+
+void snmp_inc_icmpinechos(void)
+{
+  icmpinechos++;
+}
+
+void snmp_inc_icmpinechoreps(void)
+{
+  icmpinechoreps++;
+}
+
+void snmp_inc_icmpintimestamps(void)
+{
+  icmpintimestamps++;
+}
+
+void snmp_inc_icmpintimestampreps(void)
+{
+  icmpintimestampreps++;
+}
+
+void snmp_inc_icmpinaddrmasks(void)
+{
+  icmpinaddrmasks++;
+}
+
+void snmp_inc_icmpinaddrmaskreps(void)
+{
+  icmpinaddrmaskreps++;
+}
+
+void snmp_inc_icmpoutmsgs(void)
+{
+  icmpoutmsgs++;
+}
+
+void snmp_inc_icmpouterrors(void)
+{
+  icmpouterrors++;
+}
+
+void snmp_inc_icmpoutdestunreachs(void)
+{
+  icmpoutdestunreachs++;
+}
+
+void snmp_inc_icmpouttimeexcds(void)
+{
+  icmpouttimeexcds++;
+}
+
+void snmp_inc_icmpoutparmprobs(void)
+{
+  icmpoutparmprobs++;
+}
+
+void snmp_inc_icmpoutsrcquenchs(void)
+{
+  icmpoutsrcquenchs++;
+}
+
+void snmp_inc_icmpoutredirects(void)
+{
+  icmpoutredirects++;
+}
+
+void snmp_inc_icmpoutechos(void)
+{
+  icmpoutechos++;
+}
+
+void snmp_inc_icmpoutechoreps(void)
+{
+  icmpoutechoreps++;
+}
+
+void snmp_inc_icmpouttimestamps(void)
+{
+  icmpouttimestamps++;
+}
+
+void snmp_inc_icmpouttimestampreps(void)
+{
+  icmpouttimestampreps++;
+}
+
+void snmp_inc_icmpoutaddrmasks(void)
+{
+  icmpoutaddrmasks++;
+}
+
+void snmp_inc_icmpoutaddrmaskreps(void)
+{
+  icmpoutaddrmaskreps++;
+}
+
+void snmp_inc_tcpactiveopens(void)
+{
+  tcpactiveopens++;
+}
+
+void snmp_inc_tcppassiveopens(void)
+{
+  tcppassiveopens++;
+}
+
+void snmp_inc_tcpattemptfails(void)
+{
+  tcpattemptfails++;
+}
+
+void snmp_inc_tcpestabresets(void)
+{
+  tcpestabresets++;
+}
+
+void snmp_inc_tcpinsegs(void)
+{
+  tcpinsegs++;
+}
+
+void snmp_inc_tcpoutsegs(void)
+{
+  tcpoutsegs++;
+}
+
+void snmp_inc_tcpretranssegs(void)
+{
+  tcpretranssegs++;
+}
+
+void snmp_inc_tcpinerrs(void)
+{
+  tcpinerrs++;
+}
+
+void snmp_inc_tcpoutrsts(void)
+{
+  tcpoutrsts++;
+}
+
+void snmp_inc_udpindatagrams(void)
+{
+  udpindatagrams++;
+}
+
+void snmp_inc_udpnoports(void)
+{
+  udpnoports++;
+}
+
+void snmp_inc_udpinerrors(void)
+{
+  udpinerrors++;
+}
+
+void snmp_inc_udpoutdatagrams(void)
+{
+  udpoutdatagrams++;
+}
+
+/**
+ * Inserts udpTable indexes (.udpLocalAddress.udpLocalPort)
+ * into index tree.
+ */
+void snmp_insert_udpidx_tree(struct udp_pcb *pcb)
+{
+  struct mib_list_rootnode *udp_rn;
+  struct mib_list_node *udp_node;
+  s32_t udpidx[5];
+  u8_t level;
+
+  LWIP_ASSERT("pcb != NULL", pcb != NULL);
+  snmp_iptooid(ipX_2_ip(&pcb->local_ip), &udpidx[0]);
+  udpidx[4] = pcb->local_port;
+
+  udp_rn = &udp_root;
+  for (level = 0; level < 5; level++)
+  {
+    udp_node = NULL;
+    snmp_mib_node_insert(udp_rn, udpidx[level], &udp_node);
+    if ((level != 4) && (udp_node != NULL))
+    {
+      if (udp_node->nptr == NULL)
+      {
+        udp_rn = snmp_mib_lrn_alloc();
+        udp_node->nptr = (struct mib_node*)udp_rn;
+        if (udp_rn != NULL)
+        {
+          if (level == 3)
+          {
+            udp_rn->get_object_def = udpentry_get_object_def;
+            udp_rn->get_value = udpentry_get_value;
+            udp_rn->set_test = noleafs_set_test;
+            udp_rn->set_value = noleafs_set_value;
+          }
+        }
+        else
+        {
+          /* udp_rn == NULL, malloc failure */
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("snmp_insert_udpidx_tree() insert failed, mem full"));
+          break;
+        }
+      }
+      else
+      {
+        udp_rn = (struct mib_list_rootnode*)udp_node->nptr;
+      }
+    }
+  }
+  udptable.maxlength = 1;
+}
+
+/**
+ * Removes udpTable indexes (.udpLocalAddress.udpLocalPort)
+ * from index tree.
+ */
+void snmp_delete_udpidx_tree(struct udp_pcb *pcb)
+{
+  struct udp_pcb *npcb;
+  struct mib_list_rootnode *udp_rn, *next, *del_rn[5];
+  struct mib_list_node *udp_n, *del_n[5];
+  s32_t udpidx[5];
+  u8_t bindings, fc, level, del_cnt;
+
+  LWIP_ASSERT("pcb != NULL", pcb != NULL);
+  snmp_iptooid(ipX_2_ip(&pcb->local_ip), &udpidx[0]);
+  udpidx[4] = pcb->local_port;
+
+  /* count PCBs for a given binding
+     (e.g. when reusing ports or for temp output PCBs) */
+  bindings = 0;
+  npcb = udp_pcbs;
+  while ((npcb != NULL))
+  {
+    if (ipX_addr_cmp(0, &npcb->local_ip, &pcb->local_ip) &&
+        (npcb->local_port == udpidx[4]))
+    {
+      bindings++;
+    }
+    npcb = npcb->next;
+  }
+  if (bindings == 1)
+  {
+    /* selectively remove */
+    /* mark nodes for deletion */
+    level = 0;
+    del_cnt = 0;
+    udp_rn = &udp_root;
+    while ((level < 5) && (udp_rn != NULL))
+    {
+      fc = snmp_mib_node_find(udp_rn, udpidx[level], &udp_n);
+      if (fc == 0)
+      {
+        /* udpidx[level] does not exist */
+        del_cnt = 0;
+        udp_rn = NULL;
+      }
+      else if (fc == 1)
+      {
+        del_rn[del_cnt] = udp_rn;
+        del_n[del_cnt] = udp_n;
+        del_cnt++;
+        udp_rn = (struct mib_list_rootnode*)(udp_n->nptr);
+      }
+      else if (fc == 2)
+      {
+        /* reset delete (2 or more childs) */
+        del_cnt = 0;
+        udp_rn = (struct mib_list_rootnode*)(udp_n->nptr);
+      }
+      level++;
+    }
+    /* delete marked index nodes */
+    while (del_cnt > 0)
+    {
+      del_cnt--;
+
+      udp_rn = del_rn[del_cnt];
+      udp_n = del_n[del_cnt];
+
+      next = snmp_mib_node_delete(udp_rn, udp_n);
+      if (next != NULL)
+      {
+        LWIP_ASSERT("next_count == 0",next->count == 0);
+        snmp_mib_lrn_free(next);
+      }
+    }
+  }
+  /* disable getnext traversal on empty table */
+  if (udp_root.count == 0) udptable.maxlength = 0;
+}
+
+
+void snmp_inc_snmpinpkts(void)
+{
+  snmpinpkts++;
+}
+
+void snmp_inc_snmpoutpkts(void)
+{
+  snmpoutpkts++;
+}
+
+void snmp_inc_snmpinbadversions(void)
+{
+  snmpinbadversions++;
+}
+
+void snmp_inc_snmpinbadcommunitynames(void)
+{
+  snmpinbadcommunitynames++;
+}
+
+void snmp_inc_snmpinbadcommunityuses(void)
+{
+  snmpinbadcommunityuses++;
+}
+
+void snmp_inc_snmpinasnparseerrs(void)
+{
+  snmpinasnparseerrs++;
+}
+
+void snmp_inc_snmpintoobigs(void)
+{
+  snmpintoobigs++;
+}
+
+void snmp_inc_snmpinnosuchnames(void)
+{
+  snmpinnosuchnames++;
+}
+
+void snmp_inc_snmpinbadvalues(void)
+{
+  snmpinbadvalues++;
+}
+
+void snmp_inc_snmpinreadonlys(void)
+{
+  snmpinreadonlys++;
+}
+
+void snmp_inc_snmpingenerrs(void)
+{
+  snmpingenerrs++;
+}
+
+void snmp_add_snmpintotalreqvars(u8_t value)
+{
+  snmpintotalreqvars += value;
+}
+
+void snmp_add_snmpintotalsetvars(u8_t value)
+{
+  snmpintotalsetvars += value;
+}
+
+void snmp_inc_snmpingetrequests(void)
+{
+  snmpingetrequests++;
+}
+
+void snmp_inc_snmpingetnexts(void)
+{
+  snmpingetnexts++;
+}
+
+void snmp_inc_snmpinsetrequests(void)
+{
+  snmpinsetrequests++;
+}
+
+void snmp_inc_snmpingetresponses(void)
+{
+  snmpingetresponses++;
+}
+
+void snmp_inc_snmpintraps(void)
+{
+  snmpintraps++;
+}
+
+void snmp_inc_snmpouttoobigs(void)
+{
+  snmpouttoobigs++;
+}
+
+void snmp_inc_snmpoutnosuchnames(void)
+{
+  snmpoutnosuchnames++;
+}
+
+void snmp_inc_snmpoutbadvalues(void)
+{
+  snmpoutbadvalues++;
+}
+
+void snmp_inc_snmpoutgenerrs(void)
+{
+  snmpoutgenerrs++;
+}
+
+void snmp_inc_snmpoutgetrequests(void)
+{
+  snmpoutgetrequests++;
+}
+
+void snmp_inc_snmpoutgetnexts(void)
+{
+  snmpoutgetnexts++;
+}
+
+void snmp_inc_snmpoutsetrequests(void)
+{
+  snmpoutsetrequests++;
+}
+
+void snmp_inc_snmpoutgetresponses(void)
+{
+  snmpoutgetresponses++;
+}
+
+void snmp_inc_snmpouttraps(void)
+{
+  snmpouttraps++;
+}
+
+void snmp_get_snmpgrpid_ptr(struct snmp_obj_id **oid)
+{
+  *oid = &snmpgrp_id;
+}
+
+void snmp_set_snmpenableauthentraps(u8_t *value)
+{
+  if (value != NULL)
+  {
+    snmpenableauthentraps_ptr = value;
+  }
+}
+
+void snmp_get_snmpenableauthentraps(u8_t *value)
+{
+  *value = *snmpenableauthentraps_ptr;
+}
+
+void
+noleafs_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  LWIP_UNUSED_ARG(ident_len);
+  LWIP_UNUSED_ARG(ident);
+  od->instance = MIB_OBJECT_NONE;
+}
+
+void
+noleafs_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  LWIP_UNUSED_ARG(od);
+  LWIP_UNUSED_ARG(len);
+  LWIP_UNUSED_ARG(value);
+}
+
+u8_t
+noleafs_set_test(struct obj_def *od, u16_t len, void *value)
+{
+  LWIP_UNUSED_ARG(od);
+  LWIP_UNUSED_ARG(len);
+  LWIP_UNUSED_ARG(value);
+  /* can't set */
+  return 0;
+}
+
+void
+noleafs_set_value(struct obj_def *od, u16_t len, void *value)
+{
+  LWIP_UNUSED_ARG(od);
+  LWIP_UNUSED_ARG(len);
+  LWIP_UNUSED_ARG(value);
+}
+
+
+/**
+ * Returns systems object definitions.
+ *
+ * @param ident_len the address length (2)
+ * @param ident points to objectname.0 (object id trailer)
+ * @param od points to object definition.
+ */
+static void
+system_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  u8_t id;
+
+  /* return to object name, adding index depth (1) */
+  ident_len += 1;
+  ident -= 1;
+  if (ident_len == 2)
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    LWIP_ASSERT("invalid id", (ident[0] >= 0) && (ident[0] <= 0xff));
+    id = (u8_t)ident[0];
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("get_object_def system.%"U16_F".0\n",(u16_t)id));
+    switch (id)
+    {
+      case 1: /* sysDescr */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR);
+        od->v_len = *sysdescr_len_ptr;
+        break;
+      case 2: /* sysObjectID */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OBJ_ID);
+        od->v_len = sysobjid.len * sizeof(s32_t);
+        break;
+      case 3: /* sysUpTime */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_TIMETICKS);
+        od->v_len = sizeof(u32_t);
+        break;
+      case 4: /* sysContact */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR);
+        od->v_len = *syscontact_len_ptr;
+        break;
+      case 5: /* sysName */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR);
+        od->v_len = *sysname_len_ptr;
+        break;
+      case 6: /* sysLocation */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR);
+        od->v_len = *syslocation_len_ptr;
+        break;
+      case 7: /* sysServices */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("system_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    };
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("system_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+/**
+ * Returns system object value.
+ *
+ * @param ident_len the address length (2)
+ * @param ident points to objectname.0 (object id trailer)
+ * @param len return value space (in bytes)
+ * @param value points to (varbind) space to copy value into.
+ */
+static void
+system_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  u8_t id;
+
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 1: /* sysDescr */
+      ocstrncpy((u8_t*)value, sysdescr_ptr, len);
+      break;
+    case 2: /* sysObjectID */
+      objectidncpy((s32_t*)value, (s32_t*)sysobjid.id, (u8_t)(len / sizeof(s32_t)));
+      break;
+    case 3: /* sysUpTime */
+      {
+        snmp_get_sysuptime((u32_t*)value);
+      }
+      break;
+    case 4: /* sysContact */
+      ocstrncpy((u8_t*)value, syscontact_ptr, len);
+      break;
+    case 5: /* sysName */
+      ocstrncpy((u8_t*)value, sysname_ptr, len);
+      break;
+    case 6: /* sysLocation */
+      ocstrncpy((u8_t*)value, syslocation_ptr, len);
+      break;
+    case 7: /* sysServices */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+        *sint_ptr = sysservices;
+      }
+      break;
+  };
+}
+
+static u8_t
+system_set_test(struct obj_def *od, u16_t len, void *value)
+{
+  u8_t id, set_ok;
+
+  LWIP_UNUSED_ARG(value);
+  set_ok = 0;
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 4: /* sysContact */
+      if ((syscontact_ptr != syscontact_default) &&
+          (len <= 255))
+      {
+        set_ok = 1;
+      }
+      break;
+    case 5: /* sysName */
+      if ((sysname_ptr != sysname_default) &&
+          (len <= 255))
+      {
+        set_ok = 1;
+      }
+      break;
+    case 6: /* sysLocation */
+      if ((syslocation_ptr != syslocation_default) &&
+          (len <= 255))
+      {
+        set_ok = 1;
+      }
+      break;
+  };
+  return set_ok;
+}
+
+static void
+system_set_value(struct obj_def *od, u16_t len, void *value)
+{
+  u8_t id;
+
+  LWIP_ASSERT("invalid len", len <= 0xff);
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 4: /* sysContact */
+      ocstrncpy(syscontact_ptr, (u8_t*)value, len);
+      *syscontact_len_ptr = (u8_t)len;
+      break;
+    case 5: /* sysName */
+      ocstrncpy(sysname_ptr, (u8_t*)value, len);
+      *sysname_len_ptr = (u8_t)len;
+      break;
+    case 6: /* sysLocation */
+      ocstrncpy(syslocation_ptr, (u8_t*)value, len);
+      *syslocation_len_ptr = (u8_t)len;
+      break;
+  };
+}
+
+/**
+ * Returns interfaces.ifnumber object definition.
+ *
+ * @param ident_len the address length (2)
+ * @param ident points to objectname.index
+ * @param od points to object definition.
+ */
+static void
+interfaces_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  /* return to object name, adding index depth (1) */
+  ident_len += 1;
+  ident -= 1;
+  if (ident_len == 2)
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    od->instance = MIB_OBJECT_SCALAR;
+    od->access = MIB_OBJECT_READ_ONLY;
+    od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+    od->v_len = sizeof(s32_t);
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("interfaces_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+/**
+ * Returns interfaces.ifnumber object value.
+ *
+ * @param ident_len the address length (2)
+ * @param ident points to objectname.0 (object id trailer)
+ * @param len return value space (in bytes)
+ * @param value points to (varbind) space to copy value into.
+ */
+static void
+interfaces_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  LWIP_UNUSED_ARG(len);
+  if (od->id_inst_ptr[0] == 1)
+  {
+    s32_t *sint_ptr = (s32_t*)value;
+    *sint_ptr = iflist_root.count;
+  }
+}
+
+/**
+ * Returns ifentry object definitions.
+ *
+ * @param ident_len the address length (2)
+ * @param ident points to objectname.index
+ * @param od points to object definition.
+ */
+static void
+ifentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  u8_t id;
+
+  /* return to object name, adding index depth (1) */
+  ident_len += 1;
+  ident -= 1;
+  if (ident_len == 2)
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    LWIP_ASSERT("invalid id", (ident[0] >= 0) && (ident[0] <= 0xff));
+    id = (u8_t)ident[0];
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("get_object_def ifentry.%"U16_F"\n",(u16_t)id));
+    switch (id)
+    {
+      case 1: /* ifIndex */
+      case 3: /* ifType */
+      case 4: /* ifMtu */
+      case 8: /* ifOperStatus */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      case 2: /* ifDescr */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR);
+        /** @todo this should be some sort of sizeof(struct netif.name) */
+        od->v_len = 2;
+        break;
+      case 5: /* ifSpeed */
+      case 21: /* ifOutQLen */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_GAUGE);
+        od->v_len = sizeof(u32_t);
+        break;
+      case 6: /* ifPhysAddress */
+        {
+          struct netif *netif;
+
+          snmp_ifindextonetif(ident[1], &netif);
+          od->instance = MIB_OBJECT_TAB;
+          od->access = MIB_OBJECT_READ_ONLY;
+          od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR);
+          od->v_len = netif->hwaddr_len;
+        }
+        break;
+      case 7: /* ifAdminStatus */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      case 9: /* ifLastChange */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_TIMETICKS);
+        od->v_len = sizeof(u32_t);
+        break;
+      case 10: /* ifInOctets */
+      case 11: /* ifInUcastPkts */
+      case 12: /* ifInNUcastPkts */
+      case 13: /* ifInDiscarts */
+      case 14: /* ifInErrors */
+      case 15: /* ifInUnkownProtos */
+      case 16: /* ifOutOctets */
+      case 17: /* ifOutUcastPkts */
+      case 18: /* ifOutNUcastPkts */
+      case 19: /* ifOutDiscarts */
+      case 20: /* ifOutErrors */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_COUNTER);
+        od->v_len = sizeof(u32_t);
+        break;
+      case 22: /* ifSpecific */
+        /** @note returning zeroDotZero (0.0) no media specific MIB support */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OBJ_ID);
+        od->v_len = ifspecific.len * sizeof(s32_t);
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("ifentry_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    };
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("ifentry_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+/**
+ * Returns ifentry object value.
+ *
+ * @param ident_len the address length (2)
+ * @param ident points to objectname.0 (object id trailer)
+ * @param len return value space (in bytes)
+ * @param value points to (varbind) space to copy value into.
+ */
+static void
+ifentry_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  struct netif *netif;
+  u8_t id;
+
+  snmp_ifindextonetif(od->id_inst_ptr[1], &netif);
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 1: /* ifIndex */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+        *sint_ptr = od->id_inst_ptr[1];
+      }
+      break;
+    case 2: /* ifDescr */
+      ocstrncpy((u8_t*)value, (u8_t*)netif->name, len);
+      break;
+    case 3: /* ifType */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+        *sint_ptr = netif->link_type;
+      }
+      break;
+    case 4: /* ifMtu */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+        *sint_ptr = netif->mtu;
+      }
+      break;
+    case 5: /* ifSpeed */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->link_speed;
+      }
+      break;
+    case 6: /* ifPhysAddress */
+      ocstrncpy((u8_t*)value, netif->hwaddr, len);
+      break;
+    case 7: /* ifAdminStatus */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+        if (netif_is_up(netif))
+        {
+          if (netif_is_link_up(netif))
+          {
+            *sint_ptr = 1; /* up */
+          }
+          else
+          {
+            *sint_ptr = 7; /* lowerLayerDown */
+          }
+        }
+        else
+        {
+          *sint_ptr = 2; /* down */
+        }
+      }
+      break;
+    case 8: /* ifOperStatus */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+        if (netif_is_up(netif))
+        {
+          *sint_ptr = 1;
+        }
+        else
+        {
+          *sint_ptr = 2;
+        }
+      }
+      break;
+    case 9: /* ifLastChange */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->ts;
+      }
+      break;
+    case 10: /* ifInOctets */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->ifinoctets;
+      }
+      break;
+    case 11: /* ifInUcastPkts */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->ifinucastpkts;
+      }
+      break;
+    case 12: /* ifInNUcastPkts */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->ifinnucastpkts;
+      }
+      break;
+    case 13: /* ifInDiscarts */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->ifindiscards;
+      }
+      break;
+    case 14: /* ifInErrors */
+    case 15: /* ifInUnkownProtos */
+      /** @todo add these counters! */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = 0;
+      }
+      break;
+    case 16: /* ifOutOctets */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->ifoutoctets;
+      }
+      break;
+    case 17: /* ifOutUcastPkts */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->ifoutucastpkts;
+      }
+      break;
+    case 18: /* ifOutNUcastPkts */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->ifoutnucastpkts;
+      }
+      break;
+    case 19: /* ifOutDiscarts */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = netif->ifoutdiscards;
+      }
+      break;
+    case 20: /* ifOutErrors */
+       /** @todo add this counter! */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = 0;
+      }
+      break;
+    case 21: /* ifOutQLen */
+      /** @todo figure out if this must be 0 (no queue) or 1? */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = 0;
+      }
+      break;
+    case 22: /* ifSpecific */
+      objectidncpy((s32_t*)value, (s32_t*)ifspecific.id, (u8_t)(len / sizeof(s32_t)));
+      break;
+  };
+}
+
+#if !SNMP_SAFE_REQUESTS
+static u8_t
+ifentry_set_test(struct obj_def *od, u16_t len, void *value)
+{
+  struct netif *netif;
+  u8_t id, set_ok;
+  LWIP_UNUSED_ARG(len);
+
+  set_ok = 0;
+  snmp_ifindextonetif(od->id_inst_ptr[1], &netif);
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 7: /* ifAdminStatus */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+        if (*sint_ptr == 1 || *sint_ptr == 2)
+          set_ok = 1;
+      }
+      break;
+  }
+  return set_ok;
+}
+
+static void
+ifentry_set_value(struct obj_def *od, u16_t len, void *value)
+{
+  struct netif *netif;
+  u8_t id;
+  LWIP_UNUSED_ARG(len);
+
+  snmp_ifindextonetif(od->id_inst_ptr[1], &netif);
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 7: /* ifAdminStatus */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+        if (*sint_ptr == 1)
+        {
+          netif_set_up(netif);
+        }
+        else if (*sint_ptr == 2)
+        {
+          netif_set_down(netif);
+         }
+      }
+      break;
+  }
+}
+#endif /* SNMP_SAFE_REQUESTS */
+
+/**
+ * Returns atentry object definitions.
+ *
+ * @param ident_len the address length (6)
+ * @param ident points to objectname.atifindex.atnetaddress
+ * @param od points to object definition.
+ */
+static void
+atentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  /* return to object name, adding index depth (5) */
+  ident_len += 5;
+  ident -= 5;
+
+  if (ident_len == 6)
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    switch (ident[0])
+    {
+      case 1: /* atIfIndex */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      case 2: /* atPhysAddress */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR);
+        od->v_len = 6; /** @todo try to use netif::hwaddr_len */
+        break;
+      case 3: /* atNetAddress */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR);
+        od->v_len = 4;
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("atentry_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    }
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("atentry_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+atentry_get_value(struct obj_def *od, u16_t len, void *value)
+{
+#if LWIP_ARP
+  u8_t id;
+  struct eth_addr* ethaddr_ret;
+  ip_addr_t* ipaddr_ret;
+#endif /* LWIP_ARP */
+  ip_addr_t ip;
+  struct netif *netif;
+
+  LWIP_UNUSED_ARG(len);
+  LWIP_UNUSED_ARG(value);/* if !LWIP_ARP */
+
+  snmp_ifindextonetif(od->id_inst_ptr[1], &netif);
+  snmp_oidtoip(&od->id_inst_ptr[2], &ip);
+
+#if LWIP_ARP /** @todo implement a netif_find_addr */
+  if (etharp_find_addr(netif, &ip, &ethaddr_ret, &ipaddr_ret) > -1)
+  {
+    LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+    id = (u8_t)od->id_inst_ptr[0];
+    switch (id)
+    {
+      case 1: /* atIfIndex */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+          *sint_ptr = od->id_inst_ptr[1];
+        }
+        break;
+      case 2: /* atPhysAddress */
+        {
+          struct eth_addr *dst = (struct eth_addr*)value;
+
+          *dst = *ethaddr_ret;
+        }
+        break;
+      case 3: /* atNetAddress */
+        {
+          ip_addr_t *dst = (ip_addr_t*)value;
+
+          *dst = *ipaddr_ret;
+        }
+        break;
+    }
+  }
+#endif /* LWIP_ARP */
+}
+
+static void
+ip_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  u8_t id;
+
+  /* return to object name, adding index depth (1) */
+  ident_len += 1;
+  ident -= 1;
+  if (ident_len == 2)
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    LWIP_ASSERT("invalid id", (ident[0] >= 0) && (ident[0] <= 0xff));
+    id = (u8_t)ident[0];
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("get_object_def ip.%"U16_F".0\n",(u16_t)id));
+    switch (id)
+    {
+      case 1: /* ipForwarding */
+      case 2: /* ipDefaultTTL */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      case 3: /* ipInReceives */
+      case 4: /* ipInHdrErrors */
+      case 5: /* ipInAddrErrors */
+      case 6: /* ipForwDatagrams */
+      case 7: /* ipInUnknownProtos */
+      case 8: /* ipInDiscards */
+      case 9: /* ipInDelivers */
+      case 10: /* ipOutRequests */
+      case 11: /* ipOutDiscards */
+      case 12: /* ipOutNoRoutes */
+      case 14: /* ipReasmReqds */
+      case 15: /* ipReasmOKs */
+      case 16: /* ipReasmFails */
+      case 17: /* ipFragOKs */
+      case 18: /* ipFragFails */
+      case 19: /* ipFragCreates */
+      case 23: /* ipRoutingDiscards */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_COUNTER);
+        od->v_len = sizeof(u32_t);
+        break;
+      case 13: /* ipReasmTimeout */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    };
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+ip_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  u8_t id;
+
+  LWIP_UNUSED_ARG(len);
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 1: /* ipForwarding */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+#if IP_FORWARD
+        /* forwarding */
+        *sint_ptr = 1;
+#else
+        /* not-forwarding */
+        *sint_ptr = 2;
+#endif
+      }
+      break;
+    case 2: /* ipDefaultTTL */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+        *sint_ptr = IP_DEFAULT_TTL;
+      }
+      break;
+    case 3: /* ipInReceives */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipinreceives;
+      }
+      break;
+    case 4: /* ipInHdrErrors */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipinhdrerrors;
+      }
+      break;
+    case 5: /* ipInAddrErrors */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipinaddrerrors;
+      }
+      break;
+    case 6: /* ipForwDatagrams */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipforwdatagrams;
+      }
+      break;
+    case 7: /* ipInUnknownProtos */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipinunknownprotos;
+      }
+      break;
+    case 8: /* ipInDiscards */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipindiscards;
+      }
+      break;
+    case 9: /* ipInDelivers */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipindelivers;
+      }
+      break;
+    case 10: /* ipOutRequests */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipoutrequests;
+      }
+      break;
+    case 11: /* ipOutDiscards */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipoutdiscards;
+      }
+      break;
+    case 12: /* ipOutNoRoutes */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipoutnoroutes;
+      }
+      break;
+    case 13: /* ipReasmTimeout */
+      {
+        s32_t *sint_ptr = (s32_t*)value;
+#if IP_REASSEMBLY
+        *sint_ptr = IP_REASS_MAXAGE;
+#else
+        *sint_ptr = 0;
+#endif
+      }
+      break;
+    case 14: /* ipReasmReqds */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipreasmreqds;
+      }
+      break;
+    case 15: /* ipReasmOKs */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipreasmoks;
+      }
+      break;
+    case 16: /* ipReasmFails */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipreasmfails;
+      }
+      break;
+    case 17: /* ipFragOKs */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipfragoks;
+      }
+      break;
+    case 18: /* ipFragFails */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipfragfails;
+      }
+      break;
+    case 19: /* ipFragCreates */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = ipfragcreates;
+      }
+      break;
+    case 23: /* ipRoutingDiscards */
+      /** @todo can lwIP discard routes at all?? hardwire this to 0?? */
+      {
+        u32_t *uint_ptr = (u32_t*)value;
+        *uint_ptr = iproutingdiscards;
+      }
+      break;
+  };
+}
+
+/**
+ * Test ip object value before setting.
+ *
+ * @param od is the object definition
+ * @param len return value space (in bytes)
+ * @param value points to (varbind) space to copy value from.
+ *
+ * @note we allow set if the value matches the hardwired value,
+ *   otherwise return badvalue.
+ */
+static u8_t
+ip_set_test(struct obj_def *od, u16_t len, void *value)
+{
+  u8_t id, set_ok;
+  s32_t *sint_ptr = (s32_t*)value;
+
+  LWIP_UNUSED_ARG(len);
+  set_ok = 0;
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 1: /* ipForwarding */
+#if IP_FORWARD
+      /* forwarding */
+      if (*sint_ptr == 1)
+#else
+      /* not-forwarding */
+      if (*sint_ptr == 2)
+#endif
+      {
+        set_ok = 1;
+      }
+      break;
+    case 2: /* ipDefaultTTL */
+      if (*sint_ptr == IP_DEFAULT_TTL)
+      {
+        set_ok = 1;
+      }
+      break;
+  };
+  return set_ok;
+}
+
+static void
+ip_addrentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  /* return to object name, adding index depth (4) */
+  ident_len += 4;
+  ident -= 4;
+
+  if (ident_len == 5)
+  {
+    u8_t id;
+
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    LWIP_ASSERT("invalid id", (ident[0] >= 0) && (ident[0] <= 0xff));
+    id = (u8_t)ident[0];
+    switch (id)
+    {
+      case 1: /* ipAdEntAddr */
+      case 3: /* ipAdEntNetMask */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR);
+        od->v_len = 4;
+        break;
+      case 2: /* ipAdEntIfIndex */
+      case 4: /* ipAdEntBcastAddr */
+      case 5: /* ipAdEntReasmMaxSize */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_addrentry_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    }
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_addrentry_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+ip_addrentry_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  u8_t id;
+  u16_t ifidx;
+  ip_addr_t ip;
+  struct netif *netif = netif_list;
+
+  LWIP_UNUSED_ARG(len);
+  snmp_oidtoip(&od->id_inst_ptr[1], &ip);
+  ifidx = 0;
+  while ((netif != NULL) && !ip_addr_cmp(&ip, &netif->ip_addr))
+  {
+    netif = netif->next;
+    ifidx++;
+  }
+
+  if (netif != NULL)
+  {
+    LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+    id = (u8_t)od->id_inst_ptr[0];
+    switch (id)
+    {
+      case 1: /* ipAdEntAddr */
+        {
+          ip_addr_t *dst = (ip_addr_t*)value;
+          *dst = netif->ip_addr;
+        }
+        break;
+      case 2: /* ipAdEntIfIndex */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+          *sint_ptr = ifidx + 1;
+        }
+        break;
+      case 3: /* ipAdEntNetMask */
+        {
+          ip_addr_t *dst = (ip_addr_t*)value;
+          *dst = netif->netmask;
+        }
+        break;
+      case 4: /* ipAdEntBcastAddr */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+
+          /* lwIP oddity, there's no broadcast
+            address in the netif we can rely on */
+          *sint_ptr = IPADDR_BROADCAST & 1;
+        }
+        break;
+      case 5: /* ipAdEntReasmMaxSize */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+#if IP_REASSEMBLY
+          /* @todo The theoretical maximum is IP_REASS_MAX_PBUFS * size of the pbufs,
+           * but only if receiving one fragmented packet at a time.
+           * The current solution is to calculate for 2 simultaneous packets...
+           */
+          *sint_ptr = (IP_HLEN + ((IP_REASS_MAX_PBUFS/2) *
+            (PBUF_POOL_BUFSIZE - PBUF_LINK_HLEN - IP_HLEN)));
+#else
+          /** @todo returning MTU would be a bad thing and
+             returning a wild guess like '576' isn't good either */
+          *sint_ptr = 0;
+#endif
+        }
+        break;
+    }
+  }
+}
+
+/**
+ * @note
+ * lwIP IP routing is currently using the network addresses in netif_list.
+ * if no suitable network IP is found in netif_list, the default_netif is used.
+ */
+static void
+ip_rteentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  u8_t id;
+
+  /* return to object name, adding index depth (4) */
+  ident_len += 4;
+  ident -= 4;
+
+  if (ident_len == 5)
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    LWIP_ASSERT("invalid id", (ident[0] >= 0) && (ident[0] <= 0xff));
+    id = (u8_t)ident[0];
+    switch (id)
+    {
+      case 1: /* ipRouteDest */
+      case 7: /* ipRouteNextHop */
+      case 11: /* ipRouteMask */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR);
+        od->v_len = 4;
+        break;
+      case 2: /* ipRouteIfIndex */
+      case 3: /* ipRouteMetric1 */
+      case 4: /* ipRouteMetric2 */
+      case 5: /* ipRouteMetric3 */
+      case 6: /* ipRouteMetric4 */
+      case 8: /* ipRouteType */
+      case 10: /* ipRouteAge */
+      case 12: /* ipRouteMetric5 */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      case 9: /* ipRouteProto */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      case 13: /* ipRouteInfo */
+        /** @note returning zeroDotZero (0.0) no routing protocol specific MIB */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OBJ_ID);
+        od->v_len = iprouteinfo.len * sizeof(s32_t);
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_rteentry_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    }
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_rteentry_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+ip_rteentry_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  struct netif *netif;
+  ip_addr_t dest;
+  s32_t *ident;
+  u8_t id;
+
+  ident = od->id_inst_ptr;
+  snmp_oidtoip(&ident[1], &dest);
+
+  if (ip_addr_isany(&dest))
+  {
+    /* ip_route() uses default netif for default route */
+    netif = netif_default;
+  }
+  else
+  {
+    /* not using ip_route(), need exact match! */
+    netif = netif_list;
+    while ((netif != NULL) &&
+            !ip_addr_netcmp(&dest, &(netif->ip_addr), &(netif->netmask)) )
+    {
+      netif = netif->next;
+    }
+  }
+  if (netif != NULL)
+  {
+    LWIP_ASSERT("invalid id", (ident[0] >= 0) && (ident[0] <= 0xff));
+    id = (u8_t)ident[0];
+    switch (id)
+    {
+      case 1: /* ipRouteDest */
+        {
+          ip_addr_t *dst = (ip_addr_t*)value;
+
+          if (ip_addr_isany(&dest))
+          {
+            /* default rte has 0.0.0.0 dest */
+            ip_addr_set_zero(dst);
+          }
+          else
+          {
+            /* netifs have netaddress dest */
+            ip_addr_get_network(dst, &netif->ip_addr, &netif->netmask);
+          }
+        }
+        break;
+      case 2: /* ipRouteIfIndex */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+
+          snmp_netiftoifindex(netif, sint_ptr);
+        }
+        break;
+      case 3: /* ipRouteMetric1 */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+
+          if (ip_addr_isany(&dest))
+          {
+            /* default rte has metric 1 */
+            *sint_ptr = 1;
+          }
+          else
+          {
+            /* other rtes have metric 0 */
+            *sint_ptr = 0;
+          }
+        }
+        break;
+      case 4: /* ipRouteMetric2 */
+      case 5: /* ipRouteMetric3 */
+      case 6: /* ipRouteMetric4 */
+      case 12: /* ipRouteMetric5 */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+          /* not used */
+          *sint_ptr = -1;
+        }
+        break;
+      case 7: /* ipRouteNextHop */
+        {
+          ip_addr_t *dst = (ip_addr_t*)value;
+
+          if (ip_addr_isany(&dest))
+          {
+            /* default rte: gateway */
+            *dst = netif->gw;
+          }
+          else
+          {
+            /* other rtes: netif ip_addr  */
+            *dst = netif->ip_addr;
+          }
+        }
+        break;
+      case 8: /* ipRouteType */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+
+          if (ip_addr_isany(&dest))
+          {
+            /* default rte is indirect */
+            *sint_ptr = 4;
+          }
+          else
+          {
+            /* other rtes are direct */
+            *sint_ptr = 3;
+          }
+        }
+        break;
+      case 9: /* ipRouteProto */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+          /* locally defined routes */
+          *sint_ptr = 2;
+        }
+        break;
+      case 10: /* ipRouteAge */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+          /** @todo (sysuptime - timestamp last change) / 100
+              @see snmp_insert_iprteidx_tree() */
+          *sint_ptr = 0;
+        }
+        break;
+      case 11: /* ipRouteMask */
+        {
+          ip_addr_t *dst = (ip_addr_t*)value;
+
+          if (ip_addr_isany(&dest))
+          {
+            /* default rte use 0.0.0.0 mask */
+            ip_addr_set_zero(dst);
+          }
+          else
+          {
+            /* other rtes use netmask */
+            *dst = netif->netmask;
+          }
+        }
+        break;
+      case 13: /* ipRouteInfo */
+        objectidncpy((s32_t*)value, (s32_t*)iprouteinfo.id, (u8_t)(len / sizeof(s32_t)));
+        break;
+    }
+  }
+}
+
+static void
+ip_ntomentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  /* return to object name, adding index depth (5) */
+  ident_len += 5;
+  ident -= 5;
+
+  if (ident_len == 6)
+  {
+    u8_t id;
+
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    LWIP_ASSERT("invalid id", (ident[0] >= 0) && (ident[0] <= 0xff));
+    id = (u8_t)ident[0];
+    switch (id)
+    {
+      case 1: /* ipNetToMediaIfIndex */
+      case 4: /* ipNetToMediaType */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      case 2: /* ipNetToMediaPhysAddress */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR);
+        od->v_len = 6; /** @todo try to use netif::hwaddr_len */
+        break;
+      case 3: /* ipNetToMediaNetAddress */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR);
+        od->v_len = 4;
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_ntomentry_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    }
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_ntomentry_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+ip_ntomentry_get_value(struct obj_def *od, u16_t len, void *value)
+{
+#if LWIP_ARP
+  u8_t id;
+  struct eth_addr* ethaddr_ret;
+  ip_addr_t* ipaddr_ret;
+#endif /* LWIP_ARP */
+  ip_addr_t ip;
+  struct netif *netif;
+
+  LWIP_UNUSED_ARG(len);
+  LWIP_UNUSED_ARG(value);/* if !LWIP_ARP */
+
+  snmp_ifindextonetif(od->id_inst_ptr[1], &netif);
+  snmp_oidtoip(&od->id_inst_ptr[2], &ip);
+
+#if LWIP_ARP /** @todo implement a netif_find_addr */
+  if (etharp_find_addr(netif, &ip, &ethaddr_ret, &ipaddr_ret) > -1)
+  {
+    LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+    id = (u8_t)od->id_inst_ptr[0];
+    switch (id)
+    {
+      case 1: /* ipNetToMediaIfIndex */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+          *sint_ptr = od->id_inst_ptr[1];
+        }
+        break;
+      case 2: /* ipNetToMediaPhysAddress */
+        {
+          struct eth_addr *dst = (struct eth_addr*)value;
+
+          *dst = *ethaddr_ret;
+        }
+        break;
+      case 3: /* ipNetToMediaNetAddress */
+        {
+          ip_addr_t *dst = (ip_addr_t*)value;
+
+          *dst = *ipaddr_ret;
+        }
+        break;
+      case 4: /* ipNetToMediaType */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+          /* dynamic (?) */
+          *sint_ptr = 3;
+        }
+        break;
+    }
+  }
+#endif /* LWIP_ARP */
+}
+
+static void
+icmp_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  /* return to object name, adding index depth (1) */
+  ident_len += 1;
+  ident -= 1;
+  if ((ident_len == 2) &&
+      (ident[0] > 0) && (ident[0] < 27))
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    od->instance = MIB_OBJECT_SCALAR;
+    od->access = MIB_OBJECT_READ_ONLY;
+    od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_COUNTER);
+    od->v_len = sizeof(u32_t);
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("icmp_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+icmp_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  u32_t *uint_ptr = (u32_t*)value;
+  u8_t id;
+
+  LWIP_UNUSED_ARG(len);
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 1: /* icmpInMsgs */
+      *uint_ptr = icmpinmsgs;
+      break;
+    case 2: /* icmpInErrors */
+      *uint_ptr = icmpinerrors;
+      break;
+    case 3: /* icmpInDestUnreachs */
+      *uint_ptr = icmpindestunreachs;
+      break;
+    case 4: /* icmpInTimeExcds */
+      *uint_ptr = icmpintimeexcds;
+      break;
+    case 5: /* icmpInParmProbs */
+      *uint_ptr = icmpinparmprobs;
+      break;
+    case 6: /* icmpInSrcQuenchs */
+      *uint_ptr = icmpinsrcquenchs;
+      break;
+    case 7: /* icmpInRedirects */
+      *uint_ptr = icmpinredirects;
+      break;
+    case 8: /* icmpInEchos */
+      *uint_ptr = icmpinechos;
+      break;
+    case 9: /* icmpInEchoReps */
+      *uint_ptr = icmpinechoreps;
+      break;
+    case 10: /* icmpInTimestamps */
+      *uint_ptr = icmpintimestamps;
+      break;
+    case 11: /* icmpInTimestampReps */
+      *uint_ptr = icmpintimestampreps;
+      break;
+    case 12: /* icmpInAddrMasks */
+      *uint_ptr = icmpinaddrmasks;
+      break;
+    case 13: /* icmpInAddrMaskReps */
+      *uint_ptr = icmpinaddrmaskreps;
+      break;
+    case 14: /* icmpOutMsgs */
+      *uint_ptr = icmpoutmsgs;
+      break;
+    case 15: /* icmpOutErrors */
+      *uint_ptr = icmpouterrors;
+      break;
+    case 16: /* icmpOutDestUnreachs */
+      *uint_ptr = icmpoutdestunreachs;
+      break;
+    case 17: /* icmpOutTimeExcds */
+      *uint_ptr = icmpouttimeexcds;
+      break;
+    case 18: /* icmpOutParmProbs */
+      *uint_ptr = icmpoutparmprobs;
+      break;
+    case 19: /* icmpOutSrcQuenchs */
+      *uint_ptr = icmpoutsrcquenchs;
+      break;
+    case 20: /* icmpOutRedirects */
+      *uint_ptr = icmpoutredirects;
+      break;
+    case 21: /* icmpOutEchos */
+      *uint_ptr = icmpoutechos;
+      break;
+    case 22: /* icmpOutEchoReps */
+      *uint_ptr = icmpoutechoreps;
+      break;
+    case 23: /* icmpOutTimestamps */
+      *uint_ptr = icmpouttimestamps;
+      break;
+    case 24: /* icmpOutTimestampReps */
+      *uint_ptr = icmpouttimestampreps;
+      break;
+    case 25: /* icmpOutAddrMasks */
+      *uint_ptr = icmpoutaddrmasks;
+      break;
+    case 26: /* icmpOutAddrMaskReps */
+      *uint_ptr = icmpoutaddrmaskreps;
+      break;
+  }
+}
+
+#if LWIP_TCP
+/** @todo tcp grp */
+static void
+tcp_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  u8_t id;
+
+  /* return to object name, adding index depth (1) */
+  ident_len += 1;
+  ident -= 1;
+  if (ident_len == 2)
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    LWIP_ASSERT("invalid id", (ident[0] >= 0) && (ident[0] <= 0xff));
+    id = (u8_t)ident[0];
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("get_object_def tcp.%"U16_F".0\n",(u16_t)id));
+
+    switch (id)
+    {
+      case 1: /* tcpRtoAlgorithm */
+      case 2: /* tcpRtoMin */
+      case 3: /* tcpRtoMax */
+      case 4: /* tcpMaxConn */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      case 5: /* tcpActiveOpens */
+      case 6: /* tcpPassiveOpens */
+      case 7: /* tcpAttemptFails */
+      case 8: /* tcpEstabResets */
+      case 10: /* tcpInSegs */
+      case 11: /* tcpOutSegs */
+      case 12: /* tcpRetransSegs */
+      case 14: /* tcpInErrs */
+      case 15: /* tcpOutRsts */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_COUNTER);
+        od->v_len = sizeof(u32_t);
+        break;
+      case 9: /* tcpCurrEstab */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_GAUGE);
+        od->v_len = sizeof(u32_t);
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("tcp_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    };
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("tcp_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+tcp_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  u32_t *uint_ptr = (u32_t*)value;
+  s32_t *sint_ptr = (s32_t*)value;
+  u8_t id;
+
+  LWIP_UNUSED_ARG(len);
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 1: /* tcpRtoAlgorithm, vanj(4) */
+      *sint_ptr = 4;
+      break;
+    case 2: /* tcpRtoMin */
+      /* @todo not the actual value, a guess,
+          needs to be calculated */
+      *sint_ptr = 1000;
+      break;
+    case 3: /* tcpRtoMax */
+      /* @todo not the actual value, a guess,
+         needs to be calculated */
+      *sint_ptr = 60000;
+      break;
+    case 4: /* tcpMaxConn */
+      *sint_ptr = MEMP_NUM_TCP_PCB;
+      break;
+    case 5: /* tcpActiveOpens */
+      *uint_ptr = tcpactiveopens;
+      break;
+    case 6: /* tcpPassiveOpens */
+      *uint_ptr = tcppassiveopens;
+      break;
+    case 7: /* tcpAttemptFails */
+      *uint_ptr = tcpattemptfails;
+      break;
+    case 8: /* tcpEstabResets */
+      *uint_ptr = tcpestabresets;
+      break;
+    case 9: /* tcpCurrEstab */
+      {
+        u16_t tcpcurrestab = 0;
+        struct tcp_pcb *pcb = tcp_active_pcbs;
+        while (pcb != NULL)
+        {
+          if ((pcb->state == ESTABLISHED) ||
+              (pcb->state == CLOSE_WAIT))
+          {
+            tcpcurrestab++;
+          }
+          pcb = pcb->next;
+        }
+        *uint_ptr = tcpcurrestab;
+      }
+      break;
+    case 10: /* tcpInSegs */
+      *uint_ptr = tcpinsegs;
+      break;
+    case 11: /* tcpOutSegs */
+      *uint_ptr = tcpoutsegs;
+      break;
+    case 12: /* tcpRetransSegs */
+      *uint_ptr = tcpretranssegs;
+      break;
+    case 14: /* tcpInErrs */
+      *uint_ptr = tcpinerrs;
+      break;
+    case 15: /* tcpOutRsts */
+      *uint_ptr = tcpoutrsts;
+      break;
+  }
+}
+#ifdef THIS_SEEMS_UNUSED
+static void
+tcpconnentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  /* return to object name, adding index depth (10) */
+  ident_len += 10;
+  ident -= 10;
+
+  if (ident_len == 11)
+  {
+    u8_t id;
+
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    id = ident[0];
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("get_object_def tcp.%"U16_F".0\n",(u16_t)id));
+
+    switch (id)
+    {
+      case 1: /* tcpConnState */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      case 2: /* tcpConnLocalAddress */
+      case 4: /* tcpConnRemAddress */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR);
+        od->v_len = 4;
+        break;
+      case 3: /* tcpConnLocalPort */
+      case 5: /* tcpConnRemPort */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("tcpconnentry_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    };
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("tcpconnentry_get_object_def: no such object\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+tcpconnentry_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  ip_addr_t lip, rip;
+  u16_t lport, rport;
+  s32_t *ident;
+
+  ident = od->id_inst_ptr;
+  snmp_oidtoip(&ident[1], &lip);
+  lport = ident[5];
+  snmp_oidtoip(&ident[6], &rip);
+  rport = ident[10];
+
+  /** @todo find matching PCB */
+}
+#endif /* if 0 */
+#endif
+
+static void
+udp_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  /* return to object name, adding index depth (1) */
+  ident_len += 1;
+  ident -= 1;
+  if ((ident_len == 2) &&
+      (ident[0] > 0) && (ident[0] < 6))
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    od->instance = MIB_OBJECT_SCALAR;
+    od->access = MIB_OBJECT_READ_ONLY;
+    od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_COUNTER);
+    od->v_len = sizeof(u32_t);
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("udp_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+udp_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  u32_t *uint_ptr = (u32_t*)value;
+  u8_t id;
+
+  LWIP_UNUSED_ARG(len);
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+    case 1: /* udpInDatagrams */
+      *uint_ptr = udpindatagrams;
+      break;
+    case 2: /* udpNoPorts */
+      *uint_ptr = udpnoports;
+      break;
+    case 3: /* udpInErrors */
+      *uint_ptr = udpinerrors;
+      break;
+    case 4: /* udpOutDatagrams */
+      *uint_ptr = udpoutdatagrams;
+      break;
+  }
+}
+
+static void
+udpentry_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  /* return to object name, adding index depth (5) */
+  ident_len += 5;
+  ident -= 5;
+
+  if (ident_len == 6)
+  {
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    switch (ident[0])
+    {
+      case 1: /* udpLocalAddress */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR);
+        od->v_len = 4;
+        break;
+      case 2: /* udpLocalPort */
+        od->instance = MIB_OBJECT_TAB;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("udpentry_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    }
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("udpentry_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+udpentry_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  u8_t id;
+  struct udp_pcb *pcb;
+  ipX_addr_t ip;
+  u16_t port;
+
+  LWIP_UNUSED_ARG(len);
+  snmp_oidtoip(&od->id_inst_ptr[1], (ip_addr_t*)&ip);
+  LWIP_ASSERT("invalid port", (od->id_inst_ptr[5] >= 0) && (od->id_inst_ptr[5] <= 0xffff));
+  port = (u16_t)od->id_inst_ptr[5];
+
+  pcb = udp_pcbs;
+  while ((pcb != NULL) &&
+         !(ipX_addr_cmp(0, &pcb->local_ip, &ip) &&
+           (pcb->local_port == port)))
+  {
+    pcb = pcb->next;
+  }
+
+  if (pcb != NULL)
+  {
+    LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+    id = (u8_t)od->id_inst_ptr[0];
+    switch (id)
+    {
+      case 1: /* udpLocalAddress */
+        {
+          ipX_addr_t *dst = (ipX_addr_t*)value;
+          ipX_addr_copy(0, *dst, pcb->local_ip);
+        }
+        break;
+      case 2: /* udpLocalPort */
+        {
+          s32_t *sint_ptr = (s32_t*)value;
+          *sint_ptr = pcb->local_port;
+        }
+        break;
+    }
+  }
+}
+
+static void
+snmp_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od)
+{
+  /* return to object name, adding index depth (1) */
+  ident_len += 1;
+  ident -= 1;
+  if (ident_len == 2)
+  {
+    u8_t id;
+
+    od->id_inst_len = ident_len;
+    od->id_inst_ptr = ident;
+
+    LWIP_ASSERT("invalid id", (ident[0] >= 0) && (ident[0] <= 0xff));
+    id = (u8_t)ident[0];
+    switch (id)
+    {
+      case 1: /* snmpInPkts */
+      case 2: /* snmpOutPkts */
+      case 3: /* snmpInBadVersions */
+      case 4: /* snmpInBadCommunityNames */
+      case 5: /* snmpInBadCommunityUses */
+      case 6: /* snmpInASNParseErrs */
+      case 8: /* snmpInTooBigs */
+      case 9: /* snmpInNoSuchNames */
+      case 10: /* snmpInBadValues */
+      case 11: /* snmpInReadOnlys */
+      case 12: /* snmpInGenErrs */
+      case 13: /* snmpInTotalReqVars */
+      case 14: /* snmpInTotalSetVars */
+      case 15: /* snmpInGetRequests */
+      case 16: /* snmpInGetNexts */
+      case 17: /* snmpInSetRequests */
+      case 18: /* snmpInGetResponses */
+      case 19: /* snmpInTraps */
+      case 20: /* snmpOutTooBigs */
+      case 21: /* snmpOutNoSuchNames */
+      case 22: /* snmpOutBadValues */
+      case 24: /* snmpOutGenErrs */
+      case 25: /* snmpOutGetRequests */
+      case 26: /* snmpOutGetNexts */
+      case 27: /* snmpOutSetRequests */
+      case 28: /* snmpOutGetResponses */
+      case 29: /* snmpOutTraps */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_ONLY;
+        od->asn_type = (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_COUNTER);
+        od->v_len = sizeof(u32_t);
+        break;
+      case 30: /* snmpEnableAuthenTraps */
+        od->instance = MIB_OBJECT_SCALAR;
+        od->access = MIB_OBJECT_READ_WRITE;
+        od->asn_type = (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG);
+        od->v_len = sizeof(s32_t);
+        break;
+      default:
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("snmp_get_object_def: no such object\n"));
+        od->instance = MIB_OBJECT_NONE;
+        break;
+    };
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("snmp_get_object_def: no scalar\n"));
+    od->instance = MIB_OBJECT_NONE;
+  }
+}
+
+static void
+snmp_get_value(struct obj_def *od, u16_t len, void *value)
+{
+  u32_t *uint_ptr = (u32_t*)value;
+  u8_t id;
+
+  LWIP_UNUSED_ARG(len);
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  switch (id)
+  {
+      case 1: /* snmpInPkts */
+        *uint_ptr = snmpinpkts;
+        break;
+      case 2: /* snmpOutPkts */
+        *uint_ptr = snmpoutpkts;
+        break;
+      case 3: /* snmpInBadVersions */
+        *uint_ptr = snmpinbadversions;
+        break;
+      case 4: /* snmpInBadCommunityNames */
+        *uint_ptr = snmpinbadcommunitynames;
+        break;
+      case 5: /* snmpInBadCommunityUses */
+        *uint_ptr = snmpinbadcommunityuses;
+        break;
+      case 6: /* snmpInASNParseErrs */
+        *uint_ptr = snmpinasnparseerrs;
+        break;
+      case 8: /* snmpInTooBigs */
+        *uint_ptr = snmpintoobigs;
+        break;
+      case 9: /* snmpInNoSuchNames */
+        *uint_ptr = snmpinnosuchnames;
+        break;
+      case 10: /* snmpInBadValues */
+        *uint_ptr = snmpinbadvalues;
+        break;
+      case 11: /* snmpInReadOnlys */
+        *uint_ptr = snmpinreadonlys;
+        break;
+      case 12: /* snmpInGenErrs */
+        *uint_ptr = snmpingenerrs;
+        break;
+      case 13: /* snmpInTotalReqVars */
+        *uint_ptr = snmpintotalreqvars;
+        break;
+      case 14: /* snmpInTotalSetVars */
+        *uint_ptr = snmpintotalsetvars;
+        break;
+      case 15: /* snmpInGetRequests */
+        *uint_ptr = snmpingetrequests;
+        break;
+      case 16: /* snmpInGetNexts */
+        *uint_ptr = snmpingetnexts;
+        break;
+      case 17: /* snmpInSetRequests */
+        *uint_ptr = snmpinsetrequests;
+        break;
+      case 18: /* snmpInGetResponses */
+        *uint_ptr = snmpingetresponses;
+        break;
+      case 19: /* snmpInTraps */
+        *uint_ptr = snmpintraps;
+        break;
+      case 20: /* snmpOutTooBigs */
+        *uint_ptr = snmpouttoobigs;
+        break;
+      case 21: /* snmpOutNoSuchNames */
+        *uint_ptr = snmpoutnosuchnames;
+        break;
+      case 22: /* snmpOutBadValues */
+        *uint_ptr = snmpoutbadvalues;
+        break;
+      case 24: /* snmpOutGenErrs */
+        *uint_ptr = snmpoutgenerrs;
+        break;
+      case 25: /* snmpOutGetRequests */
+        *uint_ptr = snmpoutgetrequests;
+        break;
+      case 26: /* snmpOutGetNexts */
+        *uint_ptr = snmpoutgetnexts;
+        break;
+      case 27: /* snmpOutSetRequests */
+        *uint_ptr = snmpoutsetrequests;
+        break;
+      case 28: /* snmpOutGetResponses */
+        *uint_ptr = snmpoutgetresponses;
+        break;
+      case 29: /* snmpOutTraps */
+        *uint_ptr = snmpouttraps;
+        break;
+      case 30: /* snmpEnableAuthenTraps */
+        *uint_ptr = *snmpenableauthentraps_ptr;
+        break;
+  };
+}
+
+/**
+ * Test snmp object value before setting.
+ *
+ * @param od is the object definition
+ * @param len return value space (in bytes)
+ * @param value points to (varbind) space to copy value from.
+ */
+static u8_t
+snmp_set_test(struct obj_def *od, u16_t len, void *value)
+{
+  u8_t id, set_ok;
+
+  LWIP_UNUSED_ARG(len);
+  set_ok = 0;
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  if (id == 30)
+  {
+    /* snmpEnableAuthenTraps */
+    s32_t *sint_ptr = (s32_t*)value;
+
+    if (snmpenableauthentraps_ptr != &snmpenableauthentraps_default)
+    {
+      /* we should have writable non-volatile mem here */
+      if ((*sint_ptr == 1) || (*sint_ptr == 2))
+      {
+        set_ok = 1;
+      }
+    }
+    else
+    {
+      /* const or hardwired value */
+      if (*sint_ptr == snmpenableauthentraps_default)
+      {
+        set_ok = 1;
+      }
+    }
+  }
+  return set_ok;
+}
+
+static void
+snmp_set_value(struct obj_def *od, u16_t len, void *value)
+{
+  u8_t id;
+
+  LWIP_UNUSED_ARG(len);
+  LWIP_ASSERT("invalid id", (od->id_inst_ptr[0] >= 0) && (od->id_inst_ptr[0] <= 0xff));
+  id = (u8_t)od->id_inst_ptr[0];
+  if (id == 30)
+  {
+    /* snmpEnableAuthenTraps */
+    /* @todo @fixme: which kind of pointer is 'value'? s32_t or u8_t??? */
+    u8_t *ptr = (u8_t*)value;
+    *snmpenableauthentraps_ptr = *ptr;
+  }
+}
+
+#endif /* LWIP_SNMP */
diff --git a/external/badvpn_dns/lwip/src/core/snmp/mib_structs.c b/external/badvpn_dns/lwip/src/core/snmp/mib_structs.c
new file mode 100644
index 0000000..2f185cb
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/snmp/mib_structs.c
@@ -0,0 +1,1174 @@
+/**
+ * @file
+ * MIB tree access/construction functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Christiaan Simons <christiaan.simons@xxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/snmp_structs.h"
+#include "lwip/memp.h"
+#include "lwip/netif.h"
+
+/** .iso.org.dod.internet address prefix, @see snmp_iso_*() */
+const s32_t prefix[4] = {1, 3, 6, 1};
+
+#define NODE_STACK_SIZE (LWIP_SNMP_OBJ_ID_LEN)
+/** node stack entry (old news?) */
+struct nse
+{
+  /** right child */
+  struct mib_node* r_ptr;
+  /** right child identifier */
+  s32_t r_id;
+  /** right child next level */
+  u8_t r_nl;
+};
+static u8_t node_stack_cnt;
+static struct nse node_stack[NODE_STACK_SIZE];
+
+/**
+ * Pushes nse struct onto stack.
+ */
+static void
+push_node(struct nse* node)
+{
+  LWIP_ASSERT("node_stack_cnt < NODE_STACK_SIZE",node_stack_cnt < NODE_STACK_SIZE);
+  LWIP_DEBUGF(SNMP_MIB_DEBUG,("push_node() node=%p id=%"S32_F"\n",(void*)(node->r_ptr),node->r_id));
+  if (node_stack_cnt < NODE_STACK_SIZE)
+  {
+    node_stack[node_stack_cnt] = *node;
+    node_stack_cnt++;
+  }
+}
+
+/**
+ * Pops nse struct from stack.
+ */
+static void
+pop_node(struct nse* node)
+{
+  if (node_stack_cnt > 0)
+  {
+    node_stack_cnt--;
+    *node = node_stack[node_stack_cnt];
+  }
+  LWIP_DEBUGF(SNMP_MIB_DEBUG,("pop_node() node=%p id=%"S32_F"\n",(void *)(node->r_ptr),node->r_id));
+}
+
+/**
+ * Conversion from ifIndex to lwIP netif
+ * @param ifindex is a s32_t object sub-identifier
+ * @param netif points to returned netif struct pointer
+ */
+void
+snmp_ifindextonetif(s32_t ifindex, struct netif **netif)
+{
+  struct netif *nif = netif_list;
+  s32_t i, ifidx;
+
+  ifidx = ifindex - 1;
+  i = 0;
+  while ((nif != NULL) && (i < ifidx))
+  {
+    nif = nif->next;
+    i++;
+  }
+  *netif = nif;
+}
+
+/**
+ * Conversion from lwIP netif to ifIndex
+ * @param netif points to a netif struct
+ * @param ifidx points to s32_t object sub-identifier
+ */
+void
+snmp_netiftoifindex(struct netif *netif, s32_t *ifidx)
+{
+  struct netif *nif = netif_list;
+  u16_t i;
+
+  i = 0;
+  while ((nif != NULL) && (nif != netif))
+  {
+    nif = nif->next;
+    i++;
+  }
+  *ifidx = i+1;
+}
+
+/**
+ * Conversion from oid to lwIP ip_addr
+ * @param ident points to s32_t ident[4] input
+ * @param ip points to output struct
+ */
+void
+snmp_oidtoip(s32_t *ident, ip_addr_t *ip)
+{
+  IP4_ADDR(ip, ident[0], ident[1], ident[2], ident[3]);
+}
+
+/**
+ * Conversion from lwIP ip_addr to oid
+ * @param ip points to input struct
+ * @param ident points to s32_t ident[4] output
+ */
+void
+snmp_iptooid(ip_addr_t *ip, s32_t *ident)
+{
+  ident[0] = ip4_addr1(ip);
+  ident[1] = ip4_addr2(ip);
+  ident[2] = ip4_addr3(ip);
+  ident[3] = ip4_addr4(ip);
+}
+
+struct mib_list_node *
+snmp_mib_ln_alloc(s32_t id)
+{
+  struct mib_list_node *ln;
+
+  ln = (struct mib_list_node *)memp_malloc(MEMP_SNMP_NODE);
+  if (ln != NULL)
+  {
+    ln->prev = NULL;
+    ln->next = NULL;
+    ln->objid = id;
+    ln->nptr = NULL;
+  }
+  return ln;
+}
+
+void
+snmp_mib_ln_free(struct mib_list_node *ln)
+{
+  memp_free(MEMP_SNMP_NODE, ln);
+}
+
+struct mib_list_rootnode *
+snmp_mib_lrn_alloc(void)
+{
+  struct mib_list_rootnode *lrn;
+
+  lrn = (struct mib_list_rootnode*)memp_malloc(MEMP_SNMP_ROOTNODE);
+  if (lrn != NULL)
+  {
+    lrn->get_object_def = noleafs_get_object_def;
+    lrn->get_value = noleafs_get_value;
+    lrn->set_test = noleafs_set_test;
+    lrn->set_value = noleafs_set_value;
+    lrn->node_type = MIB_NODE_LR;
+    lrn->maxlength = 0;
+    lrn->head = NULL;
+    lrn->tail = NULL;
+    lrn->count = 0;
+  }
+  return lrn;
+}
+
+void
+snmp_mib_lrn_free(struct mib_list_rootnode *lrn)
+{
+  memp_free(MEMP_SNMP_ROOTNODE, lrn);
+}
+
+/**
+ * Inserts node in idx list in a sorted
+ * (ascending order) fashion and
+ * allocates the node if needed.
+ *
+ * @param rn points to the root node
+ * @param objid is the object sub identifier
+ * @param insn points to a pointer to the inserted node
+ *   used for constructing the tree.
+ * @return -1 if failed, 1 if inserted, 2 if present.
+ */
+s8_t
+snmp_mib_node_insert(struct mib_list_rootnode *rn, s32_t objid, struct mib_list_node **insn)
+{
+  struct mib_list_node *nn;
+  s8_t insert;
+
+  LWIP_ASSERT("rn != NULL",rn != NULL);
+
+  /* -1 = malloc failure, 0 = not inserted, 1 = inserted, 2 = was present */
+  insert = 0;
+  if (rn->head == NULL)
+  {
+    /* empty list, add first node */
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("alloc empty list objid==%"S32_F"\n",objid));
+    nn = snmp_mib_ln_alloc(objid);
+    if (nn != NULL)
+    {
+      rn->head = nn;
+      rn->tail = nn;
+      *insn = nn;
+      insert = 1;
+    }
+    else
+    {
+      insert = -1;
+    }
+  }
+  else
+  {
+    struct mib_list_node *n;
+    /* at least one node is present */
+    n = rn->head;
+    while ((n != NULL) && (insert == 0))
+    {
+      if (n->objid == objid)
+      {
+        /* node is already there */
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("node already there objid==%"S32_F"\n",objid));
+        *insn = n;
+        insert = 2;
+      }
+      else if (n->objid < objid)
+      {
+        if (n->next == NULL)
+        {
+          /* alloc and insert at the tail */
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("alloc ins tail objid==%"S32_F"\n",objid));
+          nn = snmp_mib_ln_alloc(objid);
+          if (nn != NULL)
+          {
+            nn->next = NULL;
+            nn->prev = n;
+            n->next = nn;
+            rn->tail = nn;
+            *insn = nn;
+            insert = 1;
+          }
+          else
+          {
+            /* insertion failure */
+            insert = -1;
+          }
+        }
+        else
+        {
+          /* there's more to explore: traverse list */
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("traverse list\n"));
+          n = n->next;
+        }
+      }
+      else
+      {
+        /* n->objid > objid */
+        /* alloc and insert between n->prev and n */
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("alloc ins n->prev, objid==%"S32_F", n\n",objid));
+        nn = snmp_mib_ln_alloc(objid);
+        if (nn != NULL)
+        {
+          if (n->prev == NULL)
+          {
+            /* insert at the head */
+            nn->next = n;
+            nn->prev = NULL;
+            rn->head = nn;
+            n->prev = nn;
+          }
+          else
+          {
+            /* insert in the middle */
+            nn->next = n;
+            nn->prev = n->prev;
+            n->prev->next = nn;
+            n->prev = nn;
+          }
+          *insn = nn;
+          insert = 1;
+        }
+        else
+        {
+          /* insertion failure */
+          insert = -1;
+        }
+      }
+    }
+  }
+  if (insert == 1)
+  {
+    rn->count += 1;
+  }
+  LWIP_ASSERT("insert != 0",insert != 0);
+  return insert;
+}
+
+/**
+ * Finds node in idx list and returns deletion mark.
+ *
+ * @param rn points to the root node
+ * @param objid  is the object sub identifier
+ * @param fn returns pointer to found node
+ * @return 0 if not found, 1 if deletable,
+ *   2 can't delete (2 or more children), 3 not a list_node
+ */
+s8_t
+snmp_mib_node_find(struct mib_list_rootnode *rn, s32_t objid, struct mib_list_node **fn)
+{
+  s8_t fc;
+  struct mib_list_node *n;
+
+  LWIP_ASSERT("rn != NULL",rn != NULL);
+  n = rn->head;
+  while ((n != NULL) && (n->objid != objid))
+  {
+    n = n->next;
+  }
+  if (n == NULL)
+  {
+    fc = 0;
+  }
+  else if (n->nptr == NULL)
+  {
+    /* leaf, can delete node */
+    fc = 1;
+  }
+  else
+  {
+    struct mib_list_rootnode *r;
+
+    if (n->nptr->node_type == MIB_NODE_LR)
+    {
+      r = (struct mib_list_rootnode *)n->nptr;
+      if (r->count > 1)
+      {
+        /* can't delete node */
+        fc = 2;
+      }
+      else
+      {
+        /* count <= 1, can delete node */
+        fc = 1;
+      }
+    }
+    else
+    {
+      /* other node type */
+      fc = 3;
+    }
+  }
+  *fn = n;
+  return fc;
+}
+
+/**
+ * Removes node from idx list
+ * if it has a single child left.
+ *
+ * @param rn points to the root node
+ * @param n points to the node to delete
+ * @return the nptr to be freed by caller
+ */
+struct mib_list_rootnode *
+snmp_mib_node_delete(struct mib_list_rootnode *rn, struct mib_list_node *n)
+{
+  struct mib_list_rootnode *next;
+
+  LWIP_ASSERT("rn != NULL",rn != NULL);
+  LWIP_ASSERT("n != NULL",n != NULL);
+
+  /* caller must remove this sub-tree */
+  next = (struct mib_list_rootnode*)(n->nptr);
+  rn->count -= 1;
+
+  if (n == rn->head)
+  {
+    rn->head = n->next;
+    if (n->next != NULL)
+    {
+      /* not last node, new list begin */
+      n->next->prev = NULL;
+    }
+  }
+  else if (n == rn->tail)
+  {
+    rn->tail = n->prev;
+    if (n->prev != NULL)
+    {
+      /* not last node, new list end */
+      n->prev->next = NULL;
+    }
+  }
+  else
+  {
+    /* node must be in the middle */
+    n->prev->next = n->next;
+    n->next->prev = n->prev;
+  }
+  LWIP_DEBUGF(SNMP_MIB_DEBUG,("free list objid==%"S32_F"\n",n->objid));
+  snmp_mib_ln_free(n);
+  if (rn->count == 0)
+  {
+    rn->head = NULL;
+    rn->tail = NULL;
+  }
+  return next;
+}
+
+
+
+/**
+ * Searches tree for the supplied (scalar?) object identifier.
+ *
+ * @param node points to the root of the tree ('.internet')
+ * @param ident_len the length of the supplied object identifier
+ * @param ident points to the array of sub identifiers
+ * @param np points to the found object instance (return)
+ * @return pointer to the requested parent (!) node if success, NULL otherwise
+ */
+struct mib_node *
+snmp_search_tree(struct mib_node *node, u8_t ident_len, s32_t *ident, struct snmp_name_ptr *np)
+{
+  u8_t node_type, ext_level;
+
+  ext_level = 0;
+  LWIP_DEBUGF(SNMP_MIB_DEBUG,("node==%p *ident==%"S32_F"\n",(void*)node,*ident));
+  while (node != NULL)
+  {
+    node_type = node->node_type;
+    if ((node_type == MIB_NODE_AR) || (node_type == MIB_NODE_RA))
+    {
+      struct mib_array_node *an;
+      u16_t i;
+
+      if (ident_len > 0)
+      {
+        /* array node (internal ROM or RAM, fixed length) */
+        an = (struct mib_array_node *)node;
+        i = 0;
+        while ((i < an->maxlength) && (an->objid[i] != *ident))
+        {
+          i++;
+        }
+        if (i < an->maxlength)
+        {
+          /* found it, if available proceed to child, otherwise inspect leaf */
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("an->objid[%"U16_F"]==%"S32_F" *ident==%"S32_F"\n",i,an->objid[i],*ident));
+          if (an->nptr[i] == NULL)
+          {
+            /* a scalar leaf OR table,
+               inspect remaining instance number / table index */
+            np->ident_len = ident_len;
+            np->ident = ident;
+            return (struct mib_node*)an;
+          }
+          else
+          {
+            /* follow next child pointer */
+            ident++;
+            ident_len--;
+            node = an->nptr[i];
+          }
+        }
+        else
+        {
+          /* search failed, identifier mismatch (nosuchname) */
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("an search failed *ident==%"S32_F"\n",*ident));
+          return NULL;
+        }
+      }
+      else
+      {
+        /* search failed, short object identifier (nosuchname) */
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("an search failed, short object identifier\n"));
+        return NULL;
+      }
+    }
+    else if(node_type == MIB_NODE_LR)
+    {
+      struct mib_list_rootnode *lrn;
+      struct mib_list_node *ln;
+
+      if (ident_len > 0)
+      {
+        /* list root node (internal 'RAM', variable length) */
+        lrn = (struct mib_list_rootnode *)node;
+        ln = lrn->head;
+        /* iterate over list, head to tail */
+        while ((ln != NULL) && (ln->objid != *ident))
+        {
+          ln = ln->next;
+        }
+        if (ln != NULL)
+        {
+          /* found it, proceed to child */;
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("ln->objid==%"S32_F" *ident==%"S32_F"\n",ln->objid,*ident));
+          if (ln->nptr == NULL)
+          {
+            np->ident_len = ident_len;
+            np->ident = ident;
+            return (struct mib_node*)lrn;
+          }
+          else
+          {
+            /* follow next child pointer */
+            ident_len--;
+            ident++;
+            node = ln->nptr;
+          }
+        }
+        else
+        {
+          /* search failed */
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("ln search failed *ident==%"S32_F"\n",*ident));
+          return NULL;
+        }
+      }
+      else
+      {
+        /* search failed, short object identifier (nosuchname) */
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("ln search failed, short object identifier\n"));
+        return NULL;
+      }
+    }
+    else if(node_type == MIB_NODE_EX)
+    {
+      struct mib_external_node *en;
+      u16_t i, len;
+
+      if (ident_len > 0)
+      {
+        /* external node (addressing and access via functions) */
+        en = (struct mib_external_node *)node;
+
+        i = 0;
+        len = en->level_length(en->addr_inf,ext_level);
+        while ((i < len) && (en->ident_cmp(en->addr_inf,ext_level,i,*ident) != 0))
+        {
+          i++;
+        }
+        if (i < len)
+        {
+          s32_t debug_id;
+
+          en->get_objid(en->addr_inf,ext_level,i,&debug_id);
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("en->objid==%"S32_F" *ident==%"S32_F"\n",debug_id,*ident));
+          if ((ext_level + 1) == en->tree_levels)
+          {
+            np->ident_len = ident_len;
+            np->ident = ident;
+            return (struct mib_node*)en;
+          }
+          else
+          {
+            /* found it, proceed to child */
+            ident_len--;
+            ident++;
+            ext_level++;
+          }
+        }
+        else
+        {
+          /* search failed */
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("en search failed *ident==%"S32_F"\n",*ident));
+          return NULL;
+        }
+      }
+      else
+      {
+        /* search failed, short object identifier (nosuchname) */
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("en search failed, short object identifier\n"));
+        return NULL;
+      }
+    }
+    else if (node_type == MIB_NODE_SC)
+    {
+      mib_scalar_node *sn;
+
+      sn = (mib_scalar_node *)node;
+      if ((ident_len == 1) && (*ident == 0))
+      {
+        np->ident_len = ident_len;
+        np->ident = ident;
+        return (struct mib_node*)sn;
+      }
+      else
+      {
+        /* search failed, short object identifier (nosuchname) */
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("search failed, invalid object identifier length\n"));
+        return NULL;
+      }
+    }
+    else
+    {
+      /* unknown node_type */
+      LWIP_DEBUGF(SNMP_MIB_DEBUG,("search failed node_type %"U16_F" unkown\n",(u16_t)node_type));
+      return NULL;
+    }
+  }
+  /* done, found nothing */
+  LWIP_DEBUGF(SNMP_MIB_DEBUG,("search failed node==%p\n",(void*)node));
+  return NULL;
+}
+
+/**
+ * Test table for presence of at least one table entry.
+ */
+static u8_t
+empty_table(struct mib_node *node)
+{
+  u8_t node_type;
+  u8_t empty = 0;
+
+  if (node != NULL)
+  {
+    node_type = node->node_type;
+    if (node_type == MIB_NODE_LR)
+    {
+      struct mib_list_rootnode *lrn;
+      lrn = (struct mib_list_rootnode *)node;
+      if ((lrn->count == 0) || (lrn->head == NULL))
+      {
+        empty = 1;
+      }
+    }
+    else if ((node_type == MIB_NODE_AR) || (node_type == MIB_NODE_RA))
+    {
+      struct mib_array_node *an;
+      an = (struct mib_array_node *)node;
+      if ((an->maxlength == 0) || (an->nptr == NULL))
+      {
+        empty = 1;
+      }
+    }
+    else if (node_type == MIB_NODE_EX)
+    {
+      struct mib_external_node *en;
+      en = (struct mib_external_node *)node;
+      if (en->tree_levels == 0)
+      {
+        empty = 1;
+      }
+    }
+  }
+  return empty;
+}
+
+/**
+ * Tree expansion.
+ */
+struct mib_node *
+snmp_expand_tree(struct mib_node *node, u8_t ident_len, s32_t *ident, struct snmp_obj_id *oidret)
+{
+  u8_t node_type, ext_level, climb_tree;
+
+  ext_level = 0;
+  /* reset node stack */
+  node_stack_cnt = 0;
+  while (node != NULL)
+  {
+    climb_tree = 0;
+    node_type = node->node_type;
+    if ((node_type == MIB_NODE_AR) || (node_type == MIB_NODE_RA))
+    {
+      struct mib_array_node *an;
+      u16_t i;
+
+      /* array node (internal ROM or RAM, fixed length) */
+      an = (struct mib_array_node *)node;
+      if (ident_len > 0)
+      {
+        i = 0;
+        while ((i < an->maxlength) && (an->objid[i] < *ident))
+        {
+          i++;
+        }
+        if (i < an->maxlength)
+        {
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("an->objid[%"U16_F"]==%"S32_F" *ident==%"S32_F"\n",i,an->objid[i],*ident));
+          /* add identifier to oidret */
+          oidret->id[oidret->len] = an->objid[i];
+          (oidret->len)++;
+
+          if (an->nptr[i] == NULL)
+          {
+            LWIP_DEBUGF(SNMP_MIB_DEBUG,("leaf node\n"));
+            /* leaf node (e.g. in a fixed size table) */
+            if (an->objid[i] > *ident)
+            {
+              return (struct mib_node*)an;
+            }
+            else if ((i + 1) < an->maxlength)
+            {
+              /* an->objid[i] == *ident */
+              (oidret->len)--;
+              oidret->id[oidret->len] = an->objid[i + 1];
+              (oidret->len)++;
+              return (struct mib_node*)an;
+            }
+            else
+            {
+              /* (i + 1) == an->maxlength */
+              (oidret->len)--;
+              climb_tree = 1;
+            }
+          }
+          else
+          {
+            u8_t j;
+            struct nse cur_node;
+
+            LWIP_DEBUGF(SNMP_MIB_DEBUG,("non-leaf node\n"));
+            /* non-leaf, store right child ptr and id */
+            LWIP_ASSERT("i < 0xff", i < 0xff);
+            j = (u8_t)i + 1;
+            while ((j < an->maxlength) && (empty_table(an->nptr[j])))
+            {
+              j++;
+            }
+            if (j < an->maxlength)
+            {
+              cur_node.r_ptr = an->nptr[j];
+              cur_node.r_id = an->objid[j];
+              cur_node.r_nl = 0;
+            }
+            else
+            {
+              cur_node.r_ptr = NULL;
+            }
+            push_node(&cur_node);
+            if (an->objid[i] == *ident)
+            {
+              ident_len--;
+              ident++;
+            }
+            else
+            {
+              /* an->objid[i] < *ident */
+              ident_len = 0;
+            }
+            /* follow next child pointer */
+            node = an->nptr[i];
+          }
+        }
+        else
+        {
+          /* i == an->maxlength */
+          climb_tree = 1;
+        }
+      }
+      else
+      {
+        u8_t j;
+        /* ident_len == 0, complete with leftmost '.thing' */
+        j = 0;
+        while ((j < an->maxlength) && empty_table(an->nptr[j]))
+        {
+          j++;
+        }
+        if (j < an->maxlength)
+        {
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("left an->objid[j]==%"S32_F"\n",an->objid[j]));
+          oidret->id[oidret->len] = an->objid[j];
+          (oidret->len)++;
+          if (an->nptr[j] == NULL)
+          {
+            /* leaf node */
+            return (struct mib_node*)an;
+          }
+          else
+          {
+            /* no leaf, continue */
+            node = an->nptr[j];
+          }
+        }
+        else
+        {
+          /* j == an->maxlength */
+          climb_tree = 1;
+        }
+      }
+    }
+    else if(node_type == MIB_NODE_LR)
+    {
+      struct mib_list_rootnode *lrn;
+      struct mib_list_node *ln;
+
+      /* list root node (internal 'RAM', variable length) */
+      lrn = (struct mib_list_rootnode *)node;
+      if (ident_len > 0)
+      {
+        ln = lrn->head;
+        /* iterate over list, head to tail */
+        while ((ln != NULL) && (ln->objid < *ident))
+        {
+          ln = ln->next;
+        }
+        if (ln != NULL)
+        {
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("ln->objid==%"S32_F" *ident==%"S32_F"\n",ln->objid,*ident));
+          oidret->id[oidret->len] = ln->objid;
+          (oidret->len)++;
+          if (ln->nptr == NULL)
+          {
+            /* leaf node */
+            if (ln->objid > *ident)
+            {
+              return (struct mib_node*)lrn;
+            }
+            else if (ln->next != NULL)
+            {
+              /* ln->objid == *ident */
+              (oidret->len)--;
+              oidret->id[oidret->len] = ln->next->objid;
+              (oidret->len)++;
+              return (struct mib_node*)lrn;
+            }
+            else
+            {
+              /* ln->next == NULL */
+              (oidret->len)--;
+              climb_tree = 1;
+            }
+          }
+          else
+          {
+            struct mib_list_node *jn;
+            struct nse cur_node;
+
+            /* non-leaf, store right child ptr and id */
+            jn = ln->next;
+            while ((jn != NULL) && empty_table(jn->nptr))
+            {
+              jn = jn->next;
+            }
+            if (jn != NULL)
+            {
+              cur_node.r_ptr = jn->nptr;
+              cur_node.r_id = jn->objid;
+              cur_node.r_nl = 0;
+            }
+            else
+            {
+              cur_node.r_ptr = NULL;
+            }
+            push_node(&cur_node);
+            if (ln->objid == *ident)
+            {
+              ident_len--;
+              ident++;
+            }
+            else
+            {
+              /* ln->objid < *ident */
+              ident_len = 0;
+            }
+            /* follow next child pointer */
+            node = ln->nptr;
+          }
+
+        }
+        else
+        {
+          /* ln == NULL */
+          climb_tree = 1;
+        }
+      }
+      else
+      {
+        struct mib_list_node *jn;
+        /* ident_len == 0, complete with leftmost '.thing' */
+        jn = lrn->head;
+        while ((jn != NULL) && empty_table(jn->nptr))
+        {
+          jn = jn->next;
+        }
+        if (jn != NULL)
+        {
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("left jn->objid==%"S32_F"\n",jn->objid));
+          oidret->id[oidret->len] = jn->objid;
+          (oidret->len)++;
+          if (jn->nptr == NULL)
+          {
+            /* leaf node */
+            LWIP_DEBUGF(SNMP_MIB_DEBUG,("jn->nptr == NULL\n"));
+            return (struct mib_node*)lrn;
+          }
+          else
+          {
+            /* no leaf, continue */
+            node = jn->nptr;
+          }
+        }
+        else
+        {
+          /* jn == NULL */
+          climb_tree = 1;
+        }
+      }
+    }
+    else if(node_type == MIB_NODE_EX)
+    {
+      struct mib_external_node *en;
+      s32_t ex_id;
+
+      /* external node (addressing and access via functions) */
+      en = (struct mib_external_node *)node;
+      if (ident_len > 0)
+      {
+        u16_t i, len;
+
+        i = 0;
+        len = en->level_length(en->addr_inf,ext_level);
+        while ((i < len) && (en->ident_cmp(en->addr_inf,ext_level,i,*ident) < 0))
+        {
+          i++;
+        }
+        if (i < len)
+        {
+          /* add identifier to oidret */
+          en->get_objid(en->addr_inf,ext_level,i,&ex_id);
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("en->objid[%"U16_F"]==%"S32_F" *ident==%"S32_F"\n",i,ex_id,*ident));
+          oidret->id[oidret->len] = ex_id;
+          (oidret->len)++;
+
+          if ((ext_level + 1) == en->tree_levels)
+          {
+            LWIP_DEBUGF(SNMP_MIB_DEBUG,("leaf node\n"));
+            /* leaf node */
+            if (ex_id > *ident)
+            {
+              return (struct mib_node*)en;
+            }
+            else if ((i + 1) < len)
+            {
+              /* ex_id == *ident */
+              en->get_objid(en->addr_inf,ext_level,i + 1,&ex_id);
+              (oidret->len)--;
+              oidret->id[oidret->len] = ex_id;
+              (oidret->len)++;
+              return (struct mib_node*)en;
+            }
+            else
+            {
+              /* (i + 1) == len */
+              (oidret->len)--;
+              climb_tree = 1;
+            }
+          }
+          else
+          {
+            u8_t j;
+            struct nse cur_node;
+
+            LWIP_DEBUGF(SNMP_MIB_DEBUG,("non-leaf node\n"));
+            /* non-leaf, store right child ptr and id */
+            LWIP_ASSERT("i < 0xff", i < 0xff);
+            j = (u8_t)i + 1;
+            if (j < len)
+            {
+              /* right node is the current external node */
+              cur_node.r_ptr = node;
+              en->get_objid(en->addr_inf,ext_level,j,&cur_node.r_id);
+              cur_node.r_nl = ext_level + 1;
+            }
+            else
+            {
+              cur_node.r_ptr = NULL;
+            }
+            push_node(&cur_node);
+            if (en->ident_cmp(en->addr_inf,ext_level,i,*ident) == 0)
+            {
+              ident_len--;
+              ident++;
+            }
+            else
+            {
+              /* external id < *ident */
+              ident_len = 0;
+            }
+            /* proceed to child */
+            ext_level++;
+          }
+        }
+        else
+        {
+          /* i == len (en->level_len()) */
+          climb_tree = 1;
+        }
+      }
+      else
+      {
+        /* ident_len == 0, complete with leftmost '.thing' */
+        en->get_objid(en->addr_inf,ext_level,0,&ex_id);
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("left en->objid==%"S32_F"\n",ex_id));
+        oidret->id[oidret->len] = ex_id;
+        (oidret->len)++;
+        if ((ext_level + 1) == en->tree_levels)
+        {
+          /* leaf node */
+          LWIP_DEBUGF(SNMP_MIB_DEBUG,("(ext_level + 1) == en->tree_levels\n"));
+          return (struct mib_node*)en;
+        }
+        else
+        {
+          /* no leaf, proceed to child */
+          ext_level++;
+        }
+      }
+    }
+    else if(node_type == MIB_NODE_SC)
+    {
+      mib_scalar_node *sn;
+
+      /* scalar node  */
+      sn = (mib_scalar_node *)node;
+      if (ident_len > 0)
+      {
+        /* at .0 */
+        climb_tree = 1;
+      }
+      else
+      {
+        /* ident_len == 0, complete object identifier */
+        oidret->id[oidret->len] = 0;
+        (oidret->len)++;
+        /* leaf node */
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("completed scalar leaf\n"));
+        return (struct mib_node*)sn;
+      }
+    }
+    else
+    {
+      /* unknown/unhandled node_type */
+      LWIP_DEBUGF(SNMP_MIB_DEBUG,("expand failed node_type %"U16_F" unkown\n",(u16_t)node_type));
+      return NULL;
+    }
+
+    if (climb_tree)
+    {
+      struct nse child;
+
+      /* find right child ptr */
+      child.r_ptr = NULL;
+      child.r_id = 0;
+      child.r_nl = 0;
+      while ((node_stack_cnt > 0) && (child.r_ptr == NULL))
+      {
+        pop_node(&child);
+        /* trim returned oid */
+        (oidret->len)--;
+      }
+      if (child.r_ptr != NULL)
+      {
+        /* incoming ident is useless beyond this point */
+        ident_len = 0;
+        oidret->id[oidret->len] = child.r_id;
+        oidret->len++;
+        node = child.r_ptr;
+        ext_level = child.r_nl;
+      }
+      else
+      {
+        /* tree ends here ... */
+        LWIP_DEBUGF(SNMP_MIB_DEBUG,("expand failed, tree ends here\n"));
+        return NULL;
+      }
+    }
+  }
+  /* done, found nothing */
+  LWIP_DEBUGF(SNMP_MIB_DEBUG,("expand failed node==%p\n",(void*)node));
+  return NULL;
+}
+
+/**
+ * Test object identifier for the iso.org.dod.internet prefix.
+ *
+ * @param ident_len the length of the supplied object identifier
+ * @param ident points to the array of sub identifiers
+ * @return 1 if it matches, 0 otherwise
+ */
+u8_t
+snmp_iso_prefix_tst(u8_t ident_len, s32_t *ident)
+{
+  if ((ident_len > 3) &&
+      (ident[0] == 1) && (ident[1] == 3) &&
+      (ident[2] == 6) && (ident[3] == 1))
+  {
+    return 1;
+  }
+  else
+  {
+    return 0;
+  }
+}
+
+/**
+ * Expands object identifier to the iso.org.dod.internet
+ * prefix for use in getnext operation.
+ *
+ * @param ident_len the length of the supplied object identifier
+ * @param ident points to the array of sub identifiers
+ * @param oidret points to returned expanded object identifier
+ * @return 1 if it matches, 0 otherwise
+ *
+ * @note ident_len 0 is allowed, expanding to the first known object id!!
+ */
+u8_t
+snmp_iso_prefix_expand(u8_t ident_len, s32_t *ident, struct snmp_obj_id *oidret)
+{
+  const s32_t *prefix_ptr;
+  s32_t *ret_ptr;
+  u8_t i;
+
+  i = 0;
+  prefix_ptr = &prefix[0];
+  ret_ptr = &oidret->id[0];
+  ident_len = ((ident_len < 4)?ident_len:4);
+  while ((i < ident_len) && ((*ident) <= (*prefix_ptr)))
+  {
+    *ret_ptr++ = *prefix_ptr++;
+    ident++;
+    i++;
+  }
+  if (i == ident_len)
+  {
+    /* match, complete missing bits */
+    while (i < 4)
+    {
+      *ret_ptr++ = *prefix_ptr++;
+      i++;
+    }
+    oidret->len = i;
+    return 1;
+  }
+  else
+  {
+    /* i != ident_len */
+    return 0;
+  }
+}
+
+#endif /* LWIP_SNMP */
diff --git a/external/badvpn_dns/lwip/src/core/snmp/msg_in.c b/external/badvpn_dns/lwip/src/core/snmp/msg_in.c
new file mode 100644
index 0000000..be940c6
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/snmp/msg_in.c
@@ -0,0 +1,1453 @@
+/**
+ * @file
+ * SNMP input message processing (RFC1157).
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Christiaan Simons <christiaan.simons@xxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/snmp.h"
+#include "lwip/snmp_asn1.h"
+#include "lwip/snmp_msg.h"
+#include "lwip/snmp_structs.h"
+#include "lwip/ip_addr.h"
+#include "lwip/memp.h"
+#include "lwip/udp.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+/* public (non-static) constants */
+/** SNMP v1 == 0 */
+const s32_t snmp_version = 0;
+/** default SNMP community string */
+const char snmp_publiccommunity[7] = "public";
+
+/* statically allocated buffers for SNMP_CONCURRENT_REQUESTS */
+struct snmp_msg_pstat msg_input_list[SNMP_CONCURRENT_REQUESTS];
+/* UDP Protocol Control Block */
+struct udp_pcb *snmp1_pcb;
+
+static void snmp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port);
+static err_t snmp_pdu_header_check(struct pbuf *p, u16_t ofs, u16_t pdu_len, u16_t *ofs_ret, struct snmp_msg_pstat *m_stat);
+static err_t snmp_pdu_dec_varbindlist(struct pbuf *p, u16_t ofs, u16_t *ofs_ret, struct snmp_msg_pstat *m_stat);
+
+
+/**
+ * Starts SNMP Agent.
+ * Allocates UDP pcb and binds it to IP_ADDR_ANY port 161.
+ */
+void
+snmp_init(void)
+{
+  struct snmp_msg_pstat *msg_ps;
+  u8_t i;
+
+  snmp1_pcb = udp_new();
+  if (snmp1_pcb != NULL)
+  {
+    udp_recv(snmp1_pcb, snmp_recv, (void *)SNMP_IN_PORT);
+    udp_bind(snmp1_pcb, IP_ADDR_ANY, SNMP_IN_PORT);
+  }
+  msg_ps = &msg_input_list[0];
+  for (i=0; i<SNMP_CONCURRENT_REQUESTS; i++)
+  {
+    msg_ps->state = SNMP_MSG_EMPTY;
+    msg_ps->error_index = 0;
+    msg_ps->error_status = SNMP_ES_NOERROR;
+    msg_ps++;
+  }
+  trap_msg.pcb = snmp1_pcb;
+
+#ifdef SNMP_PRIVATE_MIB_INIT
+  /* If defined, this must be a function-like define to initialize the
+   * private MIB after the stack has been initialized.
+   * The private MIB can also be initialized in tcpip_callback (or after
+   * the stack is initialized), this define is only for convenience. */
+  SNMP_PRIVATE_MIB_INIT();
+#endif /* SNMP_PRIVATE_MIB_INIT */
+
+  /* The coldstart trap will only be output
+     if our outgoing interface is up & configured  */
+  snmp_coldstart_trap();
+}
+
+static void
+snmp_error_response(struct snmp_msg_pstat *msg_ps, u8_t error)
+{
+  /* move names back from outvb to invb */
+  int v;
+  struct snmp_varbind *vbi = msg_ps->invb.head;
+  struct snmp_varbind *vbo = msg_ps->outvb.head;
+  for (v=0; v<msg_ps->vb_idx; v++) {
+    vbi->ident_len = vbo->ident_len;
+    vbo->ident_len = 0;
+    vbi->ident = vbo->ident;
+    vbo->ident = NULL;
+    vbi = vbi->next;
+    vbo = vbo->next;
+  }
+  /* free outvb */
+  snmp_varbind_list_free(&msg_ps->outvb);
+  /* we send invb back */
+  msg_ps->outvb = msg_ps->invb;
+  msg_ps->invb.head = NULL;
+  msg_ps->invb.tail = NULL;
+  msg_ps->invb.count = 0;
+  msg_ps->error_status = error;
+  /* error index must be 0 for error too big */
+  msg_ps->error_index = (error != SNMP_ES_TOOBIG) ? (1 + msg_ps->vb_idx) : 0;
+  snmp_send_response(msg_ps);
+  snmp_varbind_list_free(&msg_ps->outvb);
+  msg_ps->state = SNMP_MSG_EMPTY;
+}
+
+static void
+snmp_ok_response(struct snmp_msg_pstat *msg_ps)
+{
+  err_t err_ret;
+
+  err_ret = snmp_send_response(msg_ps);
+  if (err_ret == ERR_MEM)
+  {
+    /* serious memory problem, can't return tooBig */
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_event = %"S32_F"\n",msg_ps->error_status));
+  }
+  /* free varbinds (if available) */
+  snmp_varbind_list_free(&msg_ps->invb);
+  snmp_varbind_list_free(&msg_ps->outvb);
+  msg_ps->state = SNMP_MSG_EMPTY;
+}
+
+/**
+ * Service an internal or external event for SNMP GET.
+ *
+ * @param request_id identifies requests from 0 to (SNMP_CONCURRENT_REQUESTS-1)
+ * @param msg_ps points to the assosicated message process state
+ */
+static void
+snmp_msg_get_event(u8_t request_id, struct snmp_msg_pstat *msg_ps)
+{
+  LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_get_event: msg_ps->state==%"U16_F"\n",(u16_t)msg_ps->state));
+
+  if (msg_ps->state == SNMP_MSG_EXTERNAL_GET_OBJDEF)
+  {
+    struct mib_external_node *en;
+    struct snmp_name_ptr np;
+
+    /* get_object_def() answer*/
+    en = msg_ps->ext_mib_node;
+    np = msg_ps->ext_name_ptr;
+
+    /* translate answer into a known lifeform */
+    en->get_object_def_a(request_id, np.ident_len, np.ident, &msg_ps->ext_object_def);
+    if ((msg_ps->ext_object_def.instance != MIB_OBJECT_NONE) &&
+        (msg_ps->ext_object_def.access & MIB_ACCESS_READ))
+    {
+      msg_ps->state = SNMP_MSG_EXTERNAL_GET_VALUE;
+      en->get_value_q(request_id, &msg_ps->ext_object_def);
+    }
+    else
+    {
+      en->get_object_def_pc(request_id, np.ident_len, np.ident);
+      /* search failed, object id points to unknown object (nosuchname) */
+      snmp_error_response(msg_ps,SNMP_ES_NOSUCHNAME);
+    }
+  }
+  else if (msg_ps->state == SNMP_MSG_EXTERNAL_GET_VALUE)
+  {
+    struct mib_external_node *en;
+    struct snmp_varbind *vb;
+
+    /* get_value() answer */
+    en = msg_ps->ext_mib_node;
+
+    /* allocate output varbind */
+    vb = (struct snmp_varbind *)memp_malloc(MEMP_SNMP_VARBIND);
+    if (vb != NULL)
+    {
+      vb->next = NULL;
+      vb->prev = NULL;
+
+      /* move name from invb to outvb */
+      vb->ident = msg_ps->vb_ptr->ident;
+      vb->ident_len = msg_ps->vb_ptr->ident_len;
+      /* ensure this memory is refereced once only */
+      msg_ps->vb_ptr->ident = NULL;
+      msg_ps->vb_ptr->ident_len = 0;
+
+      vb->value_type = msg_ps->ext_object_def.asn_type;
+      LWIP_ASSERT("invalid length", msg_ps->ext_object_def.v_len <= 0xff);
+      vb->value_len = (u8_t)msg_ps->ext_object_def.v_len;
+      if (vb->value_len > 0)
+      {
+        LWIP_ASSERT("SNMP_MAX_OCTET_STRING_LEN is configured too low", vb->value_len <= SNMP_MAX_VALUE_SIZE);
+        vb->value = memp_malloc(MEMP_SNMP_VALUE);
+        if (vb->value != NULL)
+        {
+          en->get_value_a(request_id, &msg_ps->ext_object_def, vb->value_len, vb->value);
+          snmp_varbind_tail_add(&msg_ps->outvb, vb);
+          /* search again (if vb_idx < msg_ps->invb.count) */
+          msg_ps->state = SNMP_MSG_SEARCH_OBJ;
+          msg_ps->vb_idx += 1;
+        }
+        else
+        {
+          en->get_value_pc(request_id, &msg_ps->ext_object_def);
+          LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_event: no variable space\n"));
+          msg_ps->vb_ptr->ident = vb->ident;
+          msg_ps->vb_ptr->ident_len = vb->ident_len;
+          memp_free(MEMP_SNMP_VARBIND, vb);
+          snmp_error_response(msg_ps,SNMP_ES_TOOBIG);
+        }
+      }
+      else
+      {
+        /* vb->value_len == 0, empty value (e.g. empty string) */
+        en->get_value_a(request_id, &msg_ps->ext_object_def, 0, NULL);
+        vb->value = NULL;
+        snmp_varbind_tail_add(&msg_ps->outvb, vb);
+        /* search again (if vb_idx < msg_ps->invb.count) */
+        msg_ps->state = SNMP_MSG_SEARCH_OBJ;
+        msg_ps->vb_idx += 1;
+      }
+    }
+    else
+    {
+      en->get_value_pc(request_id, &msg_ps->ext_object_def);
+      LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_event: no outvb space\n"));
+      snmp_error_response(msg_ps,SNMP_ES_TOOBIG);
+    }
+  }
+
+  while ((msg_ps->state == SNMP_MSG_SEARCH_OBJ) &&
+         (msg_ps->vb_idx < msg_ps->invb.count))
+  {
+    struct mib_node *mn;
+    struct snmp_name_ptr np;
+
+    if (msg_ps->vb_idx == 0)
+    {
+      msg_ps->vb_ptr = msg_ps->invb.head;
+    }
+    else
+    {
+      msg_ps->vb_ptr = msg_ps->vb_ptr->next;
+    }
+    /** test object identifier for .iso.org.dod.internet prefix */
+    if (snmp_iso_prefix_tst(msg_ps->vb_ptr->ident_len,  msg_ps->vb_ptr->ident))
+    {
+      mn = snmp_search_tree((struct mib_node*)&internet, msg_ps->vb_ptr->ident_len - 4,
+                             msg_ps->vb_ptr->ident + 4, &np);
+      if (mn != NULL)
+      {
+        if (mn->node_type == MIB_NODE_EX)
+        {
+          /* external object */
+          struct mib_external_node *en = (struct mib_external_node*)mn;
+
+          msg_ps->state = SNMP_MSG_EXTERNAL_GET_OBJDEF;
+          /* save en && args in msg_ps!! */
+          msg_ps->ext_mib_node = en;
+          msg_ps->ext_name_ptr = np;
+
+          en->get_object_def_q(en->addr_inf, request_id, np.ident_len, np.ident);
+        }
+        else
+        {
+          /* internal object */
+          struct obj_def object_def;
+
+          msg_ps->state = SNMP_MSG_INTERNAL_GET_OBJDEF;
+          mn->get_object_def(np.ident_len, np.ident, &object_def);
+          if ((object_def.instance != MIB_OBJECT_NONE) &&
+            (object_def.access & MIB_ACCESS_READ))
+          {
+            mn = mn;
+          }
+          else
+          {
+            /* search failed, object id points to unknown object (nosuchname) */
+            mn =  NULL;
+          }
+          if (mn != NULL)
+          {
+            struct snmp_varbind *vb;
+
+            msg_ps->state = SNMP_MSG_INTERNAL_GET_VALUE;
+            /* allocate output varbind */
+            vb = (struct snmp_varbind *)memp_malloc(MEMP_SNMP_VARBIND);
+            if (vb != NULL)
+            {
+              vb->next = NULL;
+              vb->prev = NULL;
+
+              /* move name from invb to outvb */
+              vb->ident = msg_ps->vb_ptr->ident;
+              vb->ident_len = msg_ps->vb_ptr->ident_len;
+              /* ensure this memory is refereced once only */
+              msg_ps->vb_ptr->ident = NULL;
+              msg_ps->vb_ptr->ident_len = 0;
+
+              vb->value_type = object_def.asn_type;
+              LWIP_ASSERT("invalid length", object_def.v_len <= 0xff);
+              vb->value_len = (u8_t)object_def.v_len;
+              if (vb->value_len > 0)
+              {
+                LWIP_ASSERT("SNMP_MAX_OCTET_STRING_LEN is configured too low",
+                  vb->value_len <= SNMP_MAX_VALUE_SIZE);
+                vb->value = memp_malloc(MEMP_SNMP_VALUE);
+                if (vb->value != NULL)
+                {
+                  mn->get_value(&object_def, vb->value_len, vb->value);
+                  snmp_varbind_tail_add(&msg_ps->outvb, vb);
+                  msg_ps->state = SNMP_MSG_SEARCH_OBJ;
+                  msg_ps->vb_idx += 1;
+                }
+                else
+                {
+                  LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_event: couldn't allocate variable space\n"));
+                  msg_ps->vb_ptr->ident = vb->ident;
+                  msg_ps->vb_ptr->ident_len = vb->ident_len;
+                  vb->ident = NULL;
+                  vb->ident_len = 0;
+                  memp_free(MEMP_SNMP_VARBIND, vb);
+                  snmp_error_response(msg_ps,SNMP_ES_TOOBIG);
+                }
+              }
+              else
+              {
+                /* vb->value_len == 0, empty value (e.g. empty string) */
+                vb->value = NULL;
+                snmp_varbind_tail_add(&msg_ps->outvb, vb);
+                msg_ps->state = SNMP_MSG_SEARCH_OBJ;
+                msg_ps->vb_idx += 1;
+              }
+            }
+            else
+            {
+              LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_event: couldn't allocate outvb space\n"));
+              snmp_error_response(msg_ps,SNMP_ES_TOOBIG);
+            }
+          }
+        }
+      }
+    }
+    else
+    {
+      mn = NULL;
+    }
+    if (mn == NULL)
+    {
+      /* mn == NULL, noSuchName */
+      snmp_error_response(msg_ps,SNMP_ES_NOSUCHNAME);
+    }
+  }
+  if ((msg_ps->state == SNMP_MSG_SEARCH_OBJ) &&
+      (msg_ps->vb_idx == msg_ps->invb.count))
+  {
+    snmp_ok_response(msg_ps);
+  }
+}
+
+/**
+ * Service an internal or external event for SNMP GETNEXT.
+ *
+ * @param request_id identifies requests from 0 to (SNMP_CONCURRENT_REQUESTS-1)
+ * @param msg_ps points to the assosicated message process state
+ */
+static void
+snmp_msg_getnext_event(u8_t request_id, struct snmp_msg_pstat *msg_ps)
+{
+  LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_getnext_event: msg_ps->state==%"U16_F"\n",(u16_t)msg_ps->state));
+
+  if (msg_ps->state == SNMP_MSG_EXTERNAL_GET_OBJDEF)
+  {
+    struct mib_external_node *en;
+
+    /* get_object_def() answer*/
+    en = msg_ps->ext_mib_node;
+
+    /* translate answer into a known lifeform */
+    en->get_object_def_a(request_id, 1, &msg_ps->ext_oid.id[msg_ps->ext_oid.len - 1], &msg_ps->ext_object_def);
+    if (msg_ps->ext_object_def.instance != MIB_OBJECT_NONE)
+    {
+      msg_ps->state = SNMP_MSG_EXTERNAL_GET_VALUE;
+      en->get_value_q(request_id, &msg_ps->ext_object_def);
+    }
+    else
+    {
+      en->get_object_def_pc(request_id, 1, &msg_ps->ext_oid.id[msg_ps->ext_oid.len - 1]);
+      /* search failed, object id points to unknown object (nosuchname) */
+      snmp_error_response(msg_ps,SNMP_ES_NOSUCHNAME);
+    }
+  }
+  else if (msg_ps->state == SNMP_MSG_EXTERNAL_GET_VALUE)
+  {
+    struct mib_external_node *en;
+    struct snmp_varbind *vb;
+
+    /* get_value() answer */
+    en = msg_ps->ext_mib_node;
+
+    LWIP_ASSERT("invalid length", msg_ps->ext_object_def.v_len <= 0xff);
+    vb = snmp_varbind_alloc(&msg_ps->ext_oid,
+                            msg_ps->ext_object_def.asn_type,
+                            (u8_t)msg_ps->ext_object_def.v_len);
+    if (vb != NULL)
+    {
+      en->get_value_a(request_id, &msg_ps->ext_object_def, vb->value_len, vb->value);
+      snmp_varbind_tail_add(&msg_ps->outvb, vb);
+      msg_ps->state = SNMP_MSG_SEARCH_OBJ;
+      msg_ps->vb_idx += 1;
+    }
+    else
+    {
+      en->get_value_pc(request_id, &msg_ps->ext_object_def);
+      LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_getnext_event: couldn't allocate outvb space\n"));
+      snmp_error_response(msg_ps,SNMP_ES_TOOBIG);
+    }
+  }
+
+  while ((msg_ps->state == SNMP_MSG_SEARCH_OBJ) &&
+         (msg_ps->vb_idx < msg_ps->invb.count))
+  {
+    struct mib_node *mn;
+    struct snmp_obj_id oid;
+
+    if (msg_ps->vb_idx == 0)
+    {
+      msg_ps->vb_ptr = msg_ps->invb.head;
+    }
+    else
+    {
+      msg_ps->vb_ptr = msg_ps->vb_ptr->next;
+    }
+    if (snmp_iso_prefix_expand(msg_ps->vb_ptr->ident_len, msg_ps->vb_ptr->ident, &oid))
+    {
+      if (msg_ps->vb_ptr->ident_len > 3)
+      {
+        /* can offset ident_len and ident */
+        mn = snmp_expand_tree((struct mib_node*)&internet,
+                              msg_ps->vb_ptr->ident_len - 4,
+                              msg_ps->vb_ptr->ident + 4, &oid);
+      }
+      else
+      {
+        /* can't offset ident_len -4, ident + 4 */
+        mn = snmp_expand_tree((struct mib_node*)&internet, 0, NULL, &oid);
+      }
+    }
+    else
+    {
+      mn = NULL;
+    }
+    if (mn != NULL)
+    {
+      if (mn->node_type == MIB_NODE_EX)
+      {
+        /* external object */
+        struct mib_external_node *en = (struct mib_external_node*)mn;
+
+        msg_ps->state = SNMP_MSG_EXTERNAL_GET_OBJDEF;
+        /* save en && args in msg_ps!! */
+        msg_ps->ext_mib_node = en;
+        msg_ps->ext_oid = oid;
+
+        en->get_object_def_q(en->addr_inf, request_id, 1, &oid.id[oid.len - 1]);
+      }
+      else
+      {
+        /* internal object */
+        struct obj_def object_def;
+        struct snmp_varbind *vb;
+
+        msg_ps->state = SNMP_MSG_INTERNAL_GET_OBJDEF;
+        mn->get_object_def(1, &oid.id[oid.len - 1], &object_def);
+
+        LWIP_ASSERT("invalid length", object_def.v_len <= 0xff);
+        vb = snmp_varbind_alloc(&oid, object_def.asn_type, (u8_t)object_def.v_len);
+        if (vb != NULL)
+        {
+          msg_ps->state = SNMP_MSG_INTERNAL_GET_VALUE;
+          mn->get_value(&object_def, object_def.v_len, vb->value);
+          snmp_varbind_tail_add(&msg_ps->outvb, vb);
+          msg_ps->state = SNMP_MSG_SEARCH_OBJ;
+          msg_ps->vb_idx += 1;
+        }
+        else
+        {
+          LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_recv couldn't allocate outvb space\n"));
+          snmp_error_response(msg_ps,SNMP_ES_TOOBIG);
+        }
+      }
+    }
+    if (mn == NULL)
+    {
+      /* mn == NULL, noSuchName */
+      snmp_error_response(msg_ps,SNMP_ES_NOSUCHNAME);
+    }
+  }
+  if ((msg_ps->state == SNMP_MSG_SEARCH_OBJ) &&
+      (msg_ps->vb_idx == msg_ps->invb.count))
+  {
+    snmp_ok_response(msg_ps);
+  }
+}
+
+/**
+ * Service an internal or external event for SNMP SET.
+ *
+ * @param request_id identifies requests from 0 to (SNMP_CONCURRENT_REQUESTS-1)
+ * @param msg_ps points to the assosicated message process state
+ */
+static void
+snmp_msg_set_event(u8_t request_id, struct snmp_msg_pstat *msg_ps)
+{
+  LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_msg_set_event: msg_ps->state==%"U16_F"\n",(u16_t)msg_ps->state));
+
+  if (msg_ps->state == SNMP_MSG_EXTERNAL_GET_OBJDEF)
+  {
+    struct mib_external_node *en;
+    struct snmp_name_ptr np;
+
+    /* get_object_def() answer*/
+    en = msg_ps->ext_mib_node;
+    np = msg_ps->ext_name_ptr;
+
+    /* translate answer into a known lifeform */
+    en->get_object_def_a(request_id, np.ident_len, np.ident, &msg_ps->ext_object_def);
+    if (msg_ps->ext_object_def.instance != MIB_OBJECT_NONE)
+    {
+      msg_ps->state = SNMP_MSG_EXTERNAL_SET_TEST;
+      en->set_test_q(request_id, &msg_ps->ext_object_def);
+    }
+    else
+    {
+      en->get_object_def_pc(request_id, np.ident_len, np.ident);
+      /* search failed, object id points to unknown object (nosuchname) */
+      snmp_error_response(msg_ps,SNMP_ES_NOSUCHNAME);
+    }
+  }
+  else if (msg_ps->state == SNMP_MSG_EXTERNAL_SET_TEST)
+  {
+    struct mib_external_node *en;
+
+    /* set_test() answer*/
+    en = msg_ps->ext_mib_node;
+
+    if (msg_ps->ext_object_def.access & MIB_ACCESS_WRITE)
+    {
+       if ((msg_ps->ext_object_def.asn_type == msg_ps->vb_ptr->value_type) &&
+           (en->set_test_a(request_id,&msg_ps->ext_object_def,
+                           msg_ps->vb_ptr->value_len,msg_ps->vb_ptr->value) != 0))
+      {
+        msg_ps->state = SNMP_MSG_SEARCH_OBJ;
+        msg_ps->vb_idx += 1;
+      }
+      else
+      {
+        en->set_test_pc(request_id,&msg_ps->ext_object_def);
+        /* bad value */
+        snmp_error_response(msg_ps,SNMP_ES_BADVALUE);
+      }
+    }
+    else
+    {
+      en->set_test_pc(request_id,&msg_ps->ext_object_def);
+      /* object not available for set */
+      snmp_error_response(msg_ps,SNMP_ES_NOSUCHNAME);
+    }
+  }
+  else if (msg_ps->state == SNMP_MSG_EXTERNAL_GET_OBJDEF_S)
+  {
+    struct mib_external_node *en;
+    struct snmp_name_ptr np;
+
+    /* get_object_def() answer*/
+    en = msg_ps->ext_mib_node;
+    np = msg_ps->ext_name_ptr;
+
+    /* translate answer into a known lifeform */
+    en->get_object_def_a(request_id, np.ident_len, np.ident, &msg_ps->ext_object_def);
+    if (msg_ps->ext_object_def.instance != MIB_OBJECT_NONE)
+    {
+      msg_ps->state = SNMP_MSG_EXTERNAL_SET_VALUE;
+      en->set_value_q(request_id, &msg_ps->ext_object_def,
+                      msg_ps->vb_ptr->value_len,msg_ps->vb_ptr->value);
+    }
+    else
+    {
+      en->get_object_def_pc(request_id, np.ident_len, np.ident);
+      /* set_value failed, object has disappeared for some odd reason?? */
+      snmp_error_response(msg_ps,SNMP_ES_GENERROR);
+    }
+  }
+  else if (msg_ps->state == SNMP_MSG_EXTERNAL_SET_VALUE)
+  {
+    struct mib_external_node *en;
+
+    /** set_value_a() */
+    en = msg_ps->ext_mib_node;
+    en->set_value_a(request_id, &msg_ps->ext_object_def,
+      msg_ps->vb_ptr->value_len, msg_ps->vb_ptr->value);
+
+    /** @todo use set_value_pc() if toobig */
+    msg_ps->state = SNMP_MSG_INTERNAL_SET_VALUE;
+    msg_ps->vb_idx += 1;
+  }
+
+  /* test all values before setting */
+  while ((msg_ps->state == SNMP_MSG_SEARCH_OBJ) &&
+         (msg_ps->vb_idx < msg_ps->invb.count))
+  {
+    struct mib_node *mn;
+    struct snmp_name_ptr np;
+
+    if (msg_ps->vb_idx == 0)
+    {
+      msg_ps->vb_ptr = msg_ps->invb.head;
+    }
+    else
+    {
+      msg_ps->vb_ptr = msg_ps->vb_ptr->next;
+    }
+    /** test object identifier for .iso.org.dod.internet prefix */
+    if (snmp_iso_prefix_tst(msg_ps->vb_ptr->ident_len,  msg_ps->vb_ptr->ident))
+    {
+      mn = snmp_search_tree((struct mib_node*)&internet, msg_ps->vb_ptr->ident_len - 4,
+                             msg_ps->vb_ptr->ident + 4, &np);
+      if (mn != NULL)
+      {
+        if (mn->node_type == MIB_NODE_EX)
+        {
+          /* external object */
+          struct mib_external_node *en = (struct mib_external_node*)mn;
+
+          msg_ps->state = SNMP_MSG_EXTERNAL_GET_OBJDEF;
+          /* save en && args in msg_ps!! */
+          msg_ps->ext_mib_node = en;
+          msg_ps->ext_name_ptr = np;
+
+          en->get_object_def_q(en->addr_inf, request_id, np.ident_len, np.ident);
+        }
+        else
+        {
+          /* internal object */
+          struct obj_def object_def;
+
+          msg_ps->state = SNMP_MSG_INTERNAL_GET_OBJDEF;
+          mn->get_object_def(np.ident_len, np.ident, &object_def);
+          if (object_def.instance != MIB_OBJECT_NONE)
+          {
+            mn = mn;
+          }
+          else
+          {
+            /* search failed, object id points to unknown object (nosuchname) */
+            mn = NULL;
+          }
+          if (mn != NULL)
+          {
+            msg_ps->state = SNMP_MSG_INTERNAL_SET_TEST;
+
+            if (object_def.access & MIB_ACCESS_WRITE)
+            {
+              if ((object_def.asn_type == msg_ps->vb_ptr->value_type) &&
+                  (mn->set_test(&object_def,msg_ps->vb_ptr->value_len,msg_ps->vb_ptr->value) != 0))
+              {
+                msg_ps->state = SNMP_MSG_SEARCH_OBJ;
+                msg_ps->vb_idx += 1;
+              }
+              else
+              {
+                /* bad value */
+                snmp_error_response(msg_ps,SNMP_ES_BADVALUE);
+              }
+            }
+            else
+            {
+              /* object not available for set */
+              snmp_error_response(msg_ps,SNMP_ES_NOSUCHNAME);
+            }
+          }
+        }
+      }
+    }
+    else
+    {
+      mn = NULL;
+    }
+    if (mn == NULL)
+    {
+      /* mn == NULL, noSuchName */
+      snmp_error_response(msg_ps,SNMP_ES_NOSUCHNAME);
+    }
+  }
+
+  if ((msg_ps->state == SNMP_MSG_SEARCH_OBJ) &&
+      (msg_ps->vb_idx == msg_ps->invb.count))
+  {
+    msg_ps->vb_idx = 0;
+    msg_ps->state = SNMP_MSG_INTERNAL_SET_VALUE;
+  }
+
+  /* set all values "atomically" (be as "atomic" as possible) */
+  while ((msg_ps->state == SNMP_MSG_INTERNAL_SET_VALUE) &&
+         (msg_ps->vb_idx < msg_ps->invb.count))
+  {
+    struct mib_node *mn;
+    struct snmp_name_ptr np;
+
+    if (msg_ps->vb_idx == 0)
+    {
+      msg_ps->vb_ptr = msg_ps->invb.head;
+    }
+    else
+    {
+      msg_ps->vb_ptr = msg_ps->vb_ptr->next;
+    }
+    /* skip iso prefix test, was done previously while settesting() */
+    mn = snmp_search_tree((struct mib_node*)&internet, msg_ps->vb_ptr->ident_len - 4,
+                           msg_ps->vb_ptr->ident + 4, &np);
+    /* check if object is still available
+       (e.g. external hot-plug thingy present?) */
+    if (mn != NULL)
+    {
+      if (mn->node_type == MIB_NODE_EX)
+      {
+        /* external object */
+        struct mib_external_node *en = (struct mib_external_node*)mn;
+
+        msg_ps->state = SNMP_MSG_EXTERNAL_GET_OBJDEF_S;
+        /* save en && args in msg_ps!! */
+        msg_ps->ext_mib_node = en;
+        msg_ps->ext_name_ptr = np;
+
+        en->get_object_def_q(en->addr_inf, request_id, np.ident_len, np.ident);
+      }
+      else
+      {
+        /* internal object */
+        struct obj_def object_def;
+
+        msg_ps->state = SNMP_MSG_INTERNAL_GET_OBJDEF_S;
+        mn->get_object_def(np.ident_len, np.ident, &object_def);
+        msg_ps->state = SNMP_MSG_INTERNAL_SET_VALUE;
+        mn->set_value(&object_def,msg_ps->vb_ptr->value_len,msg_ps->vb_ptr->value);
+        msg_ps->vb_idx += 1;
+      }
+    }
+  }
+  if ((msg_ps->state == SNMP_MSG_INTERNAL_SET_VALUE) &&
+      (msg_ps->vb_idx == msg_ps->invb.count))
+  {
+    /* simply echo the input if we can set it
+       @todo do we need to return the actual value?
+       e.g. if value is silently modified or behaves sticky? */
+    msg_ps->outvb = msg_ps->invb;
+    msg_ps->invb.head = NULL;
+    msg_ps->invb.tail = NULL;
+    msg_ps->invb.count = 0;
+    snmp_ok_response(msg_ps);
+  }
+}
+
+
+/**
+ * Handle one internal or external event.
+ * Called for one async event. (recv external/private answer)
+ *
+ * @param request_id identifies requests from 0 to (SNMP_CONCURRENT_REQUESTS-1)
+ */
+void
+snmp_msg_event(u8_t request_id)
+{
+  struct snmp_msg_pstat *msg_ps;
+
+  if (request_id < SNMP_CONCURRENT_REQUESTS)
+  {
+    msg_ps = &msg_input_list[request_id];
+    if (msg_ps->rt == SNMP_ASN1_PDU_GET_NEXT_REQ)
+    {
+      snmp_msg_getnext_event(request_id, msg_ps);
+    }
+    else if (msg_ps->rt == SNMP_ASN1_PDU_GET_REQ)
+    {
+      snmp_msg_get_event(request_id, msg_ps);
+    }
+    else if(msg_ps->rt == SNMP_ASN1_PDU_SET_REQ)
+    {
+      snmp_msg_set_event(request_id, msg_ps);
+    }
+  }
+}
+
+
+/* lwIP UDP receive callback function */
+static void
+snmp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port)
+{
+  struct snmp_msg_pstat *msg_ps;
+  u8_t req_idx;
+  err_t err_ret;
+  u16_t payload_len = p->tot_len;
+  u16_t payload_ofs = 0;
+  u16_t varbind_ofs = 0;
+
+  /* suppress unused argument warning */
+  LWIP_UNUSED_ARG(arg);
+
+  /* traverse input message process list, look for SNMP_MSG_EMPTY */
+  msg_ps = &msg_input_list[0];
+  req_idx = 0;
+  while ((req_idx < SNMP_CONCURRENT_REQUESTS) && (msg_ps->state != SNMP_MSG_EMPTY))
+  {
+    req_idx++;
+    msg_ps++;
+  }
+  if (req_idx == SNMP_CONCURRENT_REQUESTS)
+  {
+    /* exceeding number of concurrent requests */
+    pbuf_free(p);
+    return;
+  }
+
+  /* accepting request */
+  snmp_inc_snmpinpkts();
+  /* record used 'protocol control block' */
+  msg_ps->pcb = pcb;
+  /* source address (network order) */
+  msg_ps->sip = *addr;
+  /* source port (host order (lwIP oddity)) */
+  msg_ps->sp = port;
+
+  /* check total length, version, community, pdu type */
+  err_ret = snmp_pdu_header_check(p, payload_ofs, payload_len, &varbind_ofs, msg_ps);
+  /* Only accept requests and requests without error (be robust) */
+  /* Reject response and trap headers or error requests as input! */
+  if ((err_ret != ERR_OK) ||
+      ((msg_ps->rt != SNMP_ASN1_PDU_GET_REQ) &&
+       (msg_ps->rt != SNMP_ASN1_PDU_GET_NEXT_REQ) &&
+       (msg_ps->rt != SNMP_ASN1_PDU_SET_REQ)) ||
+      ((msg_ps->error_status != SNMP_ES_NOERROR) ||
+       (msg_ps->error_index != 0)) )
+  {
+    /* header check failed drop request silently, do not return error! */
+    pbuf_free(p);
+    LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_pdu_header_check() failed\n"));
+    return;
+  }
+  LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_recv ok, community %s\n", msg_ps->community));
+
+  /* Builds a list of variable bindings. Copy the varbinds from the pbuf
+    chain to glue them when these are divided over two or more pbuf's. */
+  err_ret = snmp_pdu_dec_varbindlist(p, varbind_ofs, &varbind_ofs, msg_ps);
+  /* we've decoded the incoming message, release input msg now */
+  pbuf_free(p);
+  if ((err_ret != ERR_OK) || (msg_ps->invb.count == 0))
+  {
+    /* varbind-list decode failed, or varbind list empty.
+       drop request silently, do not return error!
+       (errors are only returned for a specific varbind failure) */
+    LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_pdu_dec_varbindlist() failed\n"));
+    return;
+  }
+
+  msg_ps->error_status = SNMP_ES_NOERROR;
+  msg_ps->error_index = 0;
+  /* find object for each variable binding */
+  msg_ps->state = SNMP_MSG_SEARCH_OBJ;
+  /* first variable binding from list to inspect */
+  msg_ps->vb_idx = 0;
+
+  LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_recv varbind cnt=%"U16_F"\n",(u16_t)msg_ps->invb.count));
+
+  /* handle input event and as much objects as possible in one go */
+  snmp_msg_event(req_idx);
+}
+
+/**
+ * Checks and decodes incoming SNMP message header, logs header errors.
+ *
+ * @param p points to pbuf chain of SNMP message (UDP payload)
+ * @param ofs points to first octet of SNMP message
+ * @param pdu_len the length of the UDP payload
+ * @param ofs_ret returns the ofset of the variable bindings
+ * @param m_stat points to the current message request state return
+ * @return
+ * - ERR_OK SNMP header is sane and accepted
+ * - ERR_ARG SNMP header is either malformed or rejected
+ */
+static err_t
+snmp_pdu_header_check(struct pbuf *p, u16_t ofs, u16_t pdu_len, u16_t *ofs_ret, struct snmp_msg_pstat *m_stat)
+{
+  err_t derr;
+  u16_t len, ofs_base;
+  u8_t  len_octets;
+  u8_t  type;
+  s32_t version;
+
+  ofs_base = ofs;
+  snmp_asn1_dec_type(p, ofs, &type);
+  derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+  if ((derr != ERR_OK) ||
+      (pdu_len != (1 + len_octets + len)) ||
+      (type != (SNMP_ASN1_UNIV | SNMP_ASN1_CONSTR | SNMP_ASN1_SEQ)))
+  {
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  ofs += (1 + len_octets);
+  snmp_asn1_dec_type(p, ofs, &type);
+  derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+  if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG)))
+  {
+    /* can't decode or no integer (version) */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  derr = snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &version);
+  if (derr != ERR_OK)
+  {
+    /* can't decode */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  if (version != 0)
+  {
+    /* not version 1 */
+    snmp_inc_snmpinbadversions();
+    return ERR_ARG;
+  }
+  ofs += (1 + len_octets + len);
+  snmp_asn1_dec_type(p, ofs, &type);
+  derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+  if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR)))
+  {
+    /* can't decode or no octet string (community) */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  derr = snmp_asn1_dec_raw(p, ofs + 1 + len_octets, len, SNMP_COMMUNITY_STR_LEN, m_stat->community);
+  if (derr != ERR_OK)
+  {
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  /* add zero terminator */
+  len = ((len < (SNMP_COMMUNITY_STR_LEN))?(len):(SNMP_COMMUNITY_STR_LEN));
+  m_stat->community[len] = 0;
+  m_stat->com_strlen = (u8_t)len;
+  if (strncmp(snmp_publiccommunity, (const char*)m_stat->community, SNMP_COMMUNITY_STR_LEN) != 0)
+  {
+    /** @todo: move this if we need to check more names */
+    snmp_inc_snmpinbadcommunitynames();
+    snmp_authfail_trap();
+    return ERR_ARG;
+  }
+  ofs += (1 + len_octets + len);
+  snmp_asn1_dec_type(p, ofs, &type);
+  derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+  if (derr != ERR_OK)
+  {
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  switch(type)
+  {
+    case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_GET_REQ):
+      /* GetRequest PDU */
+      snmp_inc_snmpingetrequests();
+      derr = ERR_OK;
+      break;
+    case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_GET_NEXT_REQ):
+      /* GetNextRequest PDU */
+      snmp_inc_snmpingetnexts();
+      derr = ERR_OK;
+      break;
+    case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_GET_RESP):
+      /* GetResponse PDU */
+      snmp_inc_snmpingetresponses();
+      derr = ERR_ARG;
+      break;
+    case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_SET_REQ):
+      /* SetRequest PDU */
+      snmp_inc_snmpinsetrequests();
+      derr = ERR_OK;
+      break;
+    case (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_TRAP):
+      /* Trap PDU */
+      snmp_inc_snmpintraps();
+      derr = ERR_ARG;
+      break;
+    default:
+      snmp_inc_snmpinasnparseerrs();
+      derr = ERR_ARG;
+      break;
+  }
+  if (derr != ERR_OK)
+  {
+    /* unsupported input PDU for this agent (no parse error) */
+    return ERR_ARG;
+  }
+  m_stat->rt = type & 0x1F;
+  ofs += (1 + len_octets);
+  if (len != (pdu_len - (ofs - ofs_base)))
+  {
+    /* decoded PDU length does not equal actual payload length */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  snmp_asn1_dec_type(p, ofs, &type);
+  derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+  if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG)))
+  {
+    /* can't decode or no integer (request ID) */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  derr = snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &m_stat->rid);
+  if (derr != ERR_OK)
+  {
+    /* can't decode */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  ofs += (1 + len_octets + len);
+  snmp_asn1_dec_type(p, ofs, &type);
+  derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+  if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG)))
+  {
+    /* can't decode or no integer (error-status) */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  /* must be noError (0) for incoming requests.
+     log errors for mib-2 completeness and for debug purposes */
+  derr = snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &m_stat->error_status);
+  if (derr != ERR_OK)
+  {
+    /* can't decode */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  switch (m_stat->error_status)
+  {
+    case SNMP_ES_TOOBIG:
+      snmp_inc_snmpintoobigs();
+      break;
+    case SNMP_ES_NOSUCHNAME:
+      snmp_inc_snmpinnosuchnames();
+      break;
+    case SNMP_ES_BADVALUE:
+      snmp_inc_snmpinbadvalues();
+      break;
+    case SNMP_ES_READONLY:
+      snmp_inc_snmpinreadonlys();
+      break;
+    case SNMP_ES_GENERROR:
+      snmp_inc_snmpingenerrs();
+      break;
+  }
+  ofs += (1 + len_octets + len);
+  snmp_asn1_dec_type(p, ofs, &type);
+  derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+  if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG)))
+  {
+    /* can't decode or no integer (error-index) */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  /* must be 0 for incoming requests.
+     decode anyway to catch bad integers (and dirty tricks) */
+  derr = snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, &m_stat->error_index);
+  if (derr != ERR_OK)
+  {
+    /* can't decode */
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  ofs += (1 + len_octets + len);
+  *ofs_ret = ofs;
+  return ERR_OK;
+}
+
+static err_t
+snmp_pdu_dec_varbindlist(struct pbuf *p, u16_t ofs, u16_t *ofs_ret, struct snmp_msg_pstat *m_stat)
+{
+  err_t derr;
+  u16_t len, vb_len;
+  u8_t  len_octets;
+  u8_t type;
+
+  /* variable binding list */
+  snmp_asn1_dec_type(p, ofs, &type);
+  derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &vb_len);
+  if ((derr != ERR_OK) ||
+      (type != (SNMP_ASN1_UNIV | SNMP_ASN1_CONSTR | SNMP_ASN1_SEQ)))
+  {
+    snmp_inc_snmpinasnparseerrs();
+    return ERR_ARG;
+  }
+  ofs += (1 + len_octets);
+
+  /* start with empty list */
+  m_stat->invb.count = 0;
+  m_stat->invb.head = NULL;
+  m_stat->invb.tail = NULL;
+
+  while (vb_len > 0)
+  {
+    struct snmp_obj_id oid, oid_value;
+    struct snmp_varbind *vb;
+
+    snmp_asn1_dec_type(p, ofs, &type);
+    derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+    if ((derr != ERR_OK) ||
+        (type != (SNMP_ASN1_UNIV | SNMP_ASN1_CONSTR | SNMP_ASN1_SEQ)) ||
+        (len == 0) || (len > vb_len))
+    {
+      snmp_inc_snmpinasnparseerrs();
+      /* free varbinds (if available) */
+      snmp_varbind_list_free(&m_stat->invb);
+      return ERR_ARG;
+    }
+    ofs += (1 + len_octets);
+    vb_len -= (1 + len_octets);
+
+    snmp_asn1_dec_type(p, ofs, &type);
+    derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+    if ((derr != ERR_OK) || (type != (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OBJ_ID)))
+    {
+      /* can't decode object name length */
+      snmp_inc_snmpinasnparseerrs();
+      /* free varbinds (if available) */
+      snmp_varbind_list_free(&m_stat->invb);
+      return ERR_ARG;
+    }
+    derr = snmp_asn1_dec_oid(p, ofs + 1 + len_octets, len, &oid);
+    if (derr != ERR_OK)
+    {
+      /* can't decode object name */
+      snmp_inc_snmpinasnparseerrs();
+      /* free varbinds (if available) */
+      snmp_varbind_list_free(&m_stat->invb);
+      return ERR_ARG;
+    }
+    ofs += (1 + len_octets + len);
+    vb_len -= (1 + len_octets + len);
+
+    snmp_asn1_dec_type(p, ofs, &type);
+    derr = snmp_asn1_dec_length(p, ofs+1, &len_octets, &len);
+    if (derr != ERR_OK)
+    {
+      /* can't decode object value length */
+      snmp_inc_snmpinasnparseerrs();
+      /* free varbinds (if available) */
+      snmp_varbind_list_free(&m_stat->invb);
+      return ERR_ARG;
+    }
+
+    switch (type)
+    {
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG):
+        vb = snmp_varbind_alloc(&oid, type, sizeof(s32_t));
+        if (vb != NULL)
+        {
+          s32_t *vptr = (s32_t*)vb->value;
+
+          derr = snmp_asn1_dec_s32t(p, ofs + 1 + len_octets, len, vptr);
+          snmp_varbind_tail_add(&m_stat->invb, vb);
+        }
+        else
+        {
+          derr = ERR_ARG;
+        }
+        break;
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_COUNTER):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_GAUGE):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_TIMETICKS):
+        vb = snmp_varbind_alloc(&oid, type, sizeof(u32_t));
+        if (vb != NULL)
+        {
+          u32_t *vptr = (u32_t*)vb->value;
+
+          derr = snmp_asn1_dec_u32t(p, ofs + 1 + len_octets, len, vptr);
+          snmp_varbind_tail_add(&m_stat->invb, vb);
+        }
+        else
+        {
+          derr = ERR_ARG;
+        }
+        break;
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_OPAQUE):
+        LWIP_ASSERT("invalid length", len <= 0xff);
+        vb = snmp_varbind_alloc(&oid, type, (u8_t)len);
+        if (vb != NULL)
+        {
+          derr = snmp_asn1_dec_raw(p, ofs + 1 + len_octets, len, vb->value_len, (u8_t*)vb->value);
+          snmp_varbind_tail_add(&m_stat->invb, vb);
+        }
+        else
+        {
+          derr = ERR_ARG;
+        }
+        break;
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_NUL):
+        vb = snmp_varbind_alloc(&oid, type, 0);
+        if (vb != NULL)
+        {
+          snmp_varbind_tail_add(&m_stat->invb, vb);
+          derr = ERR_OK;
+        }
+        else
+        {
+          derr = ERR_ARG;
+        }
+        break;
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OBJ_ID):
+        derr = snmp_asn1_dec_oid(p, ofs + 1 + len_octets, len, &oid_value);
+        if (derr == ERR_OK)
+        {
+          vb = snmp_varbind_alloc(&oid, type, oid_value.len * sizeof(s32_t));
+          if (vb != NULL)
+          {
+            u8_t i = oid_value.len;
+            s32_t *vptr = (s32_t*)vb->value;
+
+            while(i > 0)
+            {
+              i--;
+              vptr[i] = oid_value.id[i];
+            }
+            snmp_varbind_tail_add(&m_stat->invb, vb);
+            derr = ERR_OK;
+          }
+          else
+          {
+            derr = ERR_ARG;
+          }
+        }
+        break;
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR):
+        if (len == 4)
+        {
+          /* must be exactly 4 octets! */
+          vb = snmp_varbind_alloc(&oid, type, 4);
+          if (vb != NULL)
+          {
+            derr = snmp_asn1_dec_raw(p, ofs + 1 + len_octets, len, vb->value_len, (u8_t*)vb->value);
+            snmp_varbind_tail_add(&m_stat->invb, vb);
+          }
+          else
+          {
+            derr = ERR_ARG;
+          }
+        }
+        else
+        {
+          derr = ERR_ARG;
+        }
+        break;
+      default:
+        derr = ERR_ARG;
+        break;
+    }
+    if (derr != ERR_OK)
+    {
+      snmp_inc_snmpinasnparseerrs();
+      /* free varbinds (if available) */
+      snmp_varbind_list_free(&m_stat->invb);
+      return ERR_ARG;
+    }
+    ofs += (1 + len_octets + len);
+    vb_len -= (1 + len_octets + len);
+  }
+
+  if (m_stat->rt == SNMP_ASN1_PDU_SET_REQ)
+  {
+    snmp_add_snmpintotalsetvars(m_stat->invb.count);
+  }
+  else
+  {
+    snmp_add_snmpintotalreqvars(m_stat->invb.count);
+  }
+
+  *ofs_ret = ofs;
+  return ERR_OK;
+}
+
+struct snmp_varbind*
+snmp_varbind_alloc(struct snmp_obj_id *oid, u8_t type, u8_t len)
+{
+  struct snmp_varbind *vb;
+
+  vb = (struct snmp_varbind *)memp_malloc(MEMP_SNMP_VARBIND);
+  if (vb != NULL)
+  {
+    u8_t i;
+
+    vb->next = NULL;
+    vb->prev = NULL;
+    i = oid->len;
+    vb->ident_len = i;
+    if (i > 0)
+    {
+      LWIP_ASSERT("SNMP_MAX_TREE_DEPTH is configured too low", i <= SNMP_MAX_TREE_DEPTH);
+      /* allocate array of s32_t for our object identifier */
+      vb->ident = (s32_t*)memp_malloc(MEMP_SNMP_VALUE);
+      if (vb->ident == NULL)
+      {
+        LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_varbind_alloc: couldn't allocate ident value space\n"));
+        memp_free(MEMP_SNMP_VARBIND, vb);
+        return NULL;
+      }
+      while(i > 0)
+      {
+        i--;
+        vb->ident[i] = oid->id[i];
+      }
+    }
+    else
+    {
+      /* i == 0, pass zero length object identifier */
+      vb->ident = NULL;
+    }
+    vb->value_type = type;
+    vb->value_len = len;
+    if (len > 0)
+    {
+      LWIP_ASSERT("SNMP_MAX_OCTET_STRING_LEN is configured too low", vb->value_len <= SNMP_MAX_VALUE_SIZE);
+      /* allocate raw bytes for our object value */
+      vb->value = memp_malloc(MEMP_SNMP_VALUE);
+      if (vb->value == NULL)
+      {
+        LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_varbind_alloc: couldn't allocate value space\n"));
+        if (vb->ident != NULL)
+        {
+          memp_free(MEMP_SNMP_VALUE, vb->ident);
+        }
+        memp_free(MEMP_SNMP_VARBIND, vb);
+        return NULL;
+      }
+    }
+    else
+    {
+      /* ASN1_NUL type, or zero length ASN1_OC_STR */
+      vb->value = NULL;
+    }
+  }
+  else
+  {
+    LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_varbind_alloc: couldn't allocate varbind space\n"));
+  }
+  return vb;
+}
+
+void
+snmp_varbind_free(struct snmp_varbind *vb)
+{
+  if (vb->value != NULL )
+  {
+    memp_free(MEMP_SNMP_VALUE, vb->value);
+  }
+  if (vb->ident != NULL )
+  {
+    memp_free(MEMP_SNMP_VALUE, vb->ident);
+  }
+  memp_free(MEMP_SNMP_VARBIND, vb);
+}
+
+void
+snmp_varbind_list_free(struct snmp_varbind_root *root)
+{
+  struct snmp_varbind *vb, *prev;
+
+  vb = root->tail;
+  while ( vb != NULL )
+  {
+    prev = vb->prev;
+    snmp_varbind_free(vb);
+    vb = prev;
+  }
+  root->count = 0;
+  root->head = NULL;
+  root->tail = NULL;
+}
+
+void
+snmp_varbind_tail_add(struct snmp_varbind_root *root, struct snmp_varbind *vb)
+{
+  if (root->count == 0)
+  {
+    /* add first varbind to list */
+    root->head = vb;
+    root->tail = vb;
+  }
+  else
+  {
+    /* add nth varbind to list tail */
+    root->tail->next = vb;
+    vb->prev = root->tail;
+    root->tail = vb;
+  }
+  root->count += 1;
+}
+
+struct snmp_varbind*
+snmp_varbind_tail_remove(struct snmp_varbind_root *root)
+{
+  struct snmp_varbind* vb;
+
+  if (root->count > 0)
+  {
+    /* remove tail varbind */
+    vb = root->tail;
+    root->tail = vb->prev;
+    vb->prev->next = NULL;
+    root->count -= 1;
+  }
+  else
+  {
+    /* nothing to remove */
+    vb = NULL;
+  }
+  return vb;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/external/badvpn_dns/lwip/src/core/snmp/msg_out.c b/external/badvpn_dns/lwip/src/core/snmp/msg_out.c
new file mode 100644
index 0000000..fc0807c
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/snmp/msg_out.c
@@ -0,0 +1,678 @@
+/**
+ * @file
+ * SNMP output message processing (RFC1157).
+ *
+ * Output responses and traps are build in two passes:
+ *
+ * Pass 0: iterate over the output message backwards to determine encoding lengths
+ * Pass 1: the actual forward encoding of internal form into ASN1
+ *
+ * The single-pass encoding method described by Comer & Stevens
+ * requires extra buffer space and copying for reversal of the packet.
+ * The buffer requirement can be prohibitively large for big payloads
+ * (>= 484) therefore we use the two encoding passes.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Christiaan Simons <christiaan.simons@xxxxxxx>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/udp.h"
+#include "lwip/netif.h"
+#include "lwip/snmp.h"
+#include "lwip/snmp_asn1.h"
+#include "lwip/snmp_msg.h"
+
+struct snmp_trap_dst
+{
+  /* destination IP address in network order */
+  ip_addr_t dip;
+  /* set to 0 when disabled, >0 when enabled */
+  u8_t enable;
+};
+struct snmp_trap_dst trap_dst[SNMP_TRAP_DESTINATIONS];
+
+/** TRAP message structure */
+struct snmp_msg_trap trap_msg;
+
+static u16_t snmp_resp_header_sum(struct snmp_msg_pstat *m_stat, u16_t vb_len);
+static u16_t snmp_trap_header_sum(struct snmp_msg_trap *m_trap, u16_t vb_len);
+static u16_t snmp_varbind_list_sum(struct snmp_varbind_root *root);
+
+static u16_t snmp_resp_header_enc(struct snmp_msg_pstat *m_stat, struct pbuf *p);
+static u16_t snmp_trap_header_enc(struct snmp_msg_trap *m_trap, struct pbuf *p);
+static u16_t snmp_varbind_list_enc(struct snmp_varbind_root *root, struct pbuf *p, u16_t ofs);
+
+/**
+ * Sets enable switch for this trap destination.
+ * @param dst_idx index in 0 .. SNMP_TRAP_DESTINATIONS-1
+ * @param enable switch if 0 destination is disabled >0 enabled.
+ */
+void
+snmp_trap_dst_enable(u8_t dst_idx, u8_t enable)
+{
+  if (dst_idx < SNMP_TRAP_DESTINATIONS)
+  {
+    trap_dst[dst_idx].enable = enable;
+  }
+}
+
+/**
+ * Sets IPv4 address for this trap destination.
+ * @param dst_idx index in 0 .. SNMP_TRAP_DESTINATIONS-1
+ * @param dst IPv4 address in host order.
+ */
+void
+snmp_trap_dst_ip_set(u8_t dst_idx, ip_addr_t *dst)
+{
+  if (dst_idx < SNMP_TRAP_DESTINATIONS)
+  {
+    ip_addr_set(&trap_dst[dst_idx].dip, dst);
+  }
+}
+
+/**
+ * Sends a 'getresponse' message to the request originator.
+ *
+ * @param m_stat points to the current message request state source
+ * @return ERR_OK when success, ERR_MEM if we're out of memory
+ *
+ * @note the caller is responsible for filling in outvb in the m_stat
+ * and provide error-status and index (except for tooBig errors) ...
+ */
+err_t
+snmp_send_response(struct snmp_msg_pstat *m_stat)
+{
+  struct snmp_varbind_root emptyvb = {NULL, NULL, 0, 0, 0};
+  struct pbuf *p;
+  u16_t tot_len;
+  err_t err;
+
+  /* pass 0, calculate length fields */
+  tot_len = snmp_varbind_list_sum(&m_stat->outvb);
+  tot_len = snmp_resp_header_sum(m_stat, tot_len);
+
+  /* try allocating pbuf(s) for complete response */
+  p = pbuf_alloc(PBUF_TRANSPORT, tot_len, PBUF_POOL);
+  if (p == NULL)
+  {
+    LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_snd_response() tooBig\n"));
+
+    /* can't construct reply, return error-status tooBig */
+    m_stat->error_status = SNMP_ES_TOOBIG;
+    m_stat->error_index = 0;
+    /* pass 0, recalculate lengths, for empty varbind-list */
+    tot_len = snmp_varbind_list_sum(&emptyvb);
+    tot_len = snmp_resp_header_sum(m_stat, tot_len);
+    /* retry allocation once for header and empty varbind-list */
+    p = pbuf_alloc(PBUF_TRANSPORT, tot_len, PBUF_POOL);
+  }
+  if (p != NULL)
+  {
+    /* first pbuf alloc try or retry alloc success */
+    u16_t ofs;
+
+    LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_snd_response() p != NULL\n"));
+
+    /* pass 1, size error, encode packet ino the pbuf(s) */
+    ofs = snmp_resp_header_enc(m_stat, p);
+    snmp_varbind_list_enc(&m_stat->outvb, p, ofs);
+
+    switch (m_stat->error_status)
+    {
+      case SNMP_ES_TOOBIG:
+        snmp_inc_snmpouttoobigs();
+        break;
+      case SNMP_ES_NOSUCHNAME:
+        snmp_inc_snmpoutnosuchnames();
+        break;
+      case SNMP_ES_BADVALUE:
+        snmp_inc_snmpoutbadvalues();
+        break;
+      case SNMP_ES_GENERROR:
+        snmp_inc_snmpoutgenerrs();
+        break;
+    }
+    snmp_inc_snmpoutgetresponses();
+    snmp_inc_snmpoutpkts();
+
+    /** @todo do we need separate rx and tx pcbs for threaded case? */
+    /** connect to the originating source */
+    udp_connect(m_stat->pcb, &m_stat->sip, m_stat->sp);
+    err = udp_send(m_stat->pcb, p);
+    if (err == ERR_MEM)
+    {
+      /** @todo release some memory, retry and return tooBig? tooMuchHassle? */
+      err = ERR_MEM;
+    }
+    else
+    {
+      err = ERR_OK;
+    }
+    /** disassociate remote address and port with this pcb */
+    udp_disconnect(m_stat->pcb);
+
+    pbuf_free(p);
+    LWIP_DEBUGF(SNMP_MSG_DEBUG, ("snmp_snd_response() done\n"));
+    return err;
+  }
+  else
+  {
+    /* first pbuf alloc try or retry alloc failed
+       very low on memory, couldn't return tooBig */
+    return ERR_MEM;
+  }
+}
+
+
+/**
+ * Sends an generic or enterprise specific trap message.
+ *
+ * @param generic_trap is the trap code
+ * @param eoid points to enterprise object identifier
+ * @param specific_trap used for enterprise traps when generic_trap == 6
+ * @return ERR_OK when success, ERR_MEM if we're out of memory
+ *
+ * @note the caller is responsible for filling in outvb in the trap_msg
+ * @note the use of the enterpise identifier field
+ * is per RFC1215.
+ * Use .iso.org.dod.internet.mgmt.mib-2.snmp for generic traps
+ * and .iso.org.dod.internet.private.enterprises.yourenterprise
+ * (sysObjectID) for specific traps.
+ */
+err_t
+snmp_send_trap(s8_t generic_trap, struct snmp_obj_id *eoid, s32_t specific_trap)
+{
+  struct snmp_trap_dst *td;
+  struct netif *dst_if;
+  ip_addr_t dst_ip;
+  struct pbuf *p;
+  u16_t i,tot_len;
+  err_t err = ERR_OK;
+
+  for (i=0, td = &trap_dst[0]; i<SNMP_TRAP_DESTINATIONS; i++, td++)
+  {
+    if ((td->enable != 0) && !ip_addr_isany(&td->dip))
+    {
+      /* network order trap destination */
+      ip_addr_copy(trap_msg.dip, td->dip);
+      /* lookup current source address for this dst */
+      dst_if = ip_route(&td->dip);
+      if (dst_if != NULL) {
+        ip_addr_copy(dst_ip, dst_if->ip_addr);
+        /* @todo: what about IPv6? */
+        trap_msg.sip_raw[0] = ip4_addr1(&dst_ip);
+        trap_msg.sip_raw[1] = ip4_addr2(&dst_ip);
+        trap_msg.sip_raw[2] = ip4_addr3(&dst_ip);
+        trap_msg.sip_raw[3] = ip4_addr4(&dst_ip);
+        trap_msg.gen_trap = generic_trap;
+        trap_msg.spc_trap = specific_trap;
+        if (generic_trap == SNMP_GENTRAP_ENTERPRISESPC)
+        {
+          /* enterprise-Specific trap */
+          trap_msg.enterprise = eoid;
+        }
+        else
+        {
+          /* generic (MIB-II) trap */
+          snmp_get_snmpgrpid_ptr(&trap_msg.enterprise);
+        }
+        snmp_get_sysuptime(&trap_msg.ts);
+
+        /* pass 0, calculate length fields */
+        tot_len = snmp_varbind_list_sum(&trap_msg.outvb);
+        tot_len = snmp_trap_header_sum(&trap_msg, tot_len);
+
+        /* allocate pbuf(s) */
+        p = pbuf_alloc(PBUF_TRANSPORT, tot_len, PBUF_POOL);
+        if (p != NULL)
+        {
+          u16_t ofs;
+
+          /* pass 1, encode packet ino the pbuf(s) */
+          ofs = snmp_trap_header_enc(&trap_msg, p);
+          snmp_varbind_list_enc(&trap_msg.outvb, p, ofs);
+
+          snmp_inc_snmpouttraps();
+          snmp_inc_snmpoutpkts();
+
+          /** send to the TRAP destination */
+          udp_sendto(trap_msg.pcb, p, &trap_msg.dip, SNMP_TRAP_PORT);
+
+          pbuf_free(p);
+        } else {
+          err = ERR_MEM;
+        }
+      } else {
+        /* routing error */
+        err = ERR_RTE;
+      }
+    }
+  }
+  return err;
+}
+
+void
+snmp_coldstart_trap(void)
+{
+  trap_msg.outvb.head = NULL;
+  trap_msg.outvb.tail = NULL;
+  trap_msg.outvb.count = 0;
+  snmp_send_trap(SNMP_GENTRAP_COLDSTART, NULL, 0);
+}
+
+void
+snmp_authfail_trap(void)
+{
+  u8_t enable;
+  snmp_get_snmpenableauthentraps(&enable);
+  if (enable == 1)
+  {
+    trap_msg.outvb.head = NULL;
+    trap_msg.outvb.tail = NULL;
+    trap_msg.outvb.count = 0;
+    snmp_send_trap(SNMP_GENTRAP_AUTHFAIL, NULL, 0);
+  }
+}
+
+/**
+ * Sums response header field lengths from tail to head and
+ * returns resp_header_lengths for second encoding pass.
+ *
+ * @param vb_len varbind-list length
+ * @param rhl points to returned header lengths
+ * @return the required lenght for encoding the response header
+ */
+static u16_t
+snmp_resp_header_sum(struct snmp_msg_pstat *m_stat, u16_t vb_len)
+{
+  u16_t tot_len;
+  struct snmp_resp_header_lengths *rhl;
+
+  rhl = &m_stat->rhl;
+  tot_len = vb_len;
+  snmp_asn1_enc_s32t_cnt(m_stat->error_index, &rhl->erridxlen);
+  snmp_asn1_enc_length_cnt(rhl->erridxlen, &rhl->erridxlenlen);
+  tot_len += 1 + rhl->erridxlenlen + rhl->erridxlen;
+
+  snmp_asn1_enc_s32t_cnt(m_stat->error_status, &rhl->errstatlen);
+  snmp_asn1_enc_length_cnt(rhl->errstatlen, &rhl->errstatlenlen);
+  tot_len += 1 + rhl->errstatlenlen + rhl->errstatlen;
+
+  snmp_asn1_enc_s32t_cnt(m_stat->rid, &rhl->ridlen);
+  snmp_asn1_enc_length_cnt(rhl->ridlen, &rhl->ridlenlen);
+  tot_len += 1 + rhl->ridlenlen + rhl->ridlen;
+
+  rhl->pdulen = tot_len;
+  snmp_asn1_enc_length_cnt(rhl->pdulen, &rhl->pdulenlen);
+  tot_len += 1 + rhl->pdulenlen;
+
+  rhl->comlen = m_stat->com_strlen;
+  snmp_asn1_enc_length_cnt(rhl->comlen, &rhl->comlenlen);
+  tot_len += 1 + rhl->comlenlen + rhl->comlen;
+
+  snmp_asn1_enc_s32t_cnt(snmp_version, &rhl->verlen);
+  snmp_asn1_enc_length_cnt(rhl->verlen, &rhl->verlenlen);
+  tot_len += 1 + rhl->verlen + rhl->verlenlen;
+
+  rhl->seqlen = tot_len;
+  snmp_asn1_enc_length_cnt(rhl->seqlen, &rhl->seqlenlen);
+  tot_len += 1 + rhl->seqlenlen;
+
+  return tot_len;
+}
+
+/**
+ * Sums trap header field lengths from tail to head and
+ * returns trap_header_lengths for second encoding pass.
+ *
+ * @param vb_len varbind-list length
+ * @param thl points to returned header lengths
+ * @return the required lenght for encoding the trap header
+ */
+static u16_t
+snmp_trap_header_sum(struct snmp_msg_trap *m_trap, u16_t vb_len)
+{
+  u16_t tot_len;
+  struct snmp_trap_header_lengths *thl;
+
+  thl = &m_trap->thl;
+  tot_len = vb_len;
+
+  snmp_asn1_enc_u32t_cnt(m_trap->ts, &thl->tslen);
+  snmp_asn1_enc_length_cnt(thl->tslen, &thl->tslenlen);
+  tot_len += 1 + thl->tslen + thl->tslenlen;
+
+  snmp_asn1_enc_s32t_cnt(m_trap->spc_trap, &thl->strplen);
+  snmp_asn1_enc_length_cnt(thl->strplen, &thl->strplenlen);
+  tot_len += 1 + thl->strplen + thl->strplenlen;
+
+  snmp_asn1_enc_s32t_cnt(m_trap->gen_trap, &thl->gtrplen);
+  snmp_asn1_enc_length_cnt(thl->gtrplen, &thl->gtrplenlen);
+  tot_len += 1 + thl->gtrplen + thl->gtrplenlen;
+
+  thl->aaddrlen = 4;
+  snmp_asn1_enc_length_cnt(thl->aaddrlen, &thl->aaddrlenlen);
+  tot_len += 1 + thl->aaddrlen + thl->aaddrlenlen;
+
+  snmp_asn1_enc_oid_cnt(m_trap->enterprise->len, &m_trap->enterprise->id[0], &thl->eidlen);
+  snmp_asn1_enc_length_cnt(thl->eidlen, &thl->eidlenlen);
+  tot_len += 1 + thl->eidlen + thl->eidlenlen;
+
+  thl->pdulen = tot_len;
+  snmp_asn1_enc_length_cnt(thl->pdulen, &thl->pdulenlen);
+  tot_len += 1 + thl->pdulenlen;
+
+  thl->comlen = sizeof(snmp_publiccommunity) - 1;
+  snmp_asn1_enc_length_cnt(thl->comlen, &thl->comlenlen);
+  tot_len += 1 + thl->comlenlen + thl->comlen;
+
+  snmp_asn1_enc_s32t_cnt(snmp_version, &thl->verlen);
+  snmp_asn1_enc_length_cnt(thl->verlen, &thl->verlenlen);
+  tot_len += 1 + thl->verlen + thl->verlenlen;
+
+  thl->seqlen = tot_len;
+  snmp_asn1_enc_length_cnt(thl->seqlen, &thl->seqlenlen);
+  tot_len += 1 + thl->seqlenlen;
+
+  return tot_len;
+}
+
+/**
+ * Sums varbind lengths from tail to head and
+ * annotates lengths in varbind for second encoding pass.
+ *
+ * @param root points to the root of the variable binding list
+ * @return the required lenght for encoding the variable bindings
+ */
+static u16_t
+snmp_varbind_list_sum(struct snmp_varbind_root *root)
+{
+  struct snmp_varbind *vb;
+  u32_t *uint_ptr;
+  s32_t *sint_ptr;
+  u16_t tot_len;
+
+  tot_len = 0;
+  vb = root->tail;
+  while ( vb != NULL )
+  {
+    /* encoded value lenght depends on type */
+    switch (vb->value_type)
+    {
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG):
+        sint_ptr = (s32_t*)vb->value;
+        snmp_asn1_enc_s32t_cnt(*sint_ptr, &vb->vlen);
+        break;
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_COUNTER):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_GAUGE):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_TIMETICKS):
+        uint_ptr = (u32_t*)vb->value;
+        snmp_asn1_enc_u32t_cnt(*uint_ptr, &vb->vlen);
+        break;
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR):
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_NUL):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_OPAQUE):
+        vb->vlen = vb->value_len;
+        break;
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OBJ_ID):
+        sint_ptr = (s32_t*)vb->value;
+        snmp_asn1_enc_oid_cnt(vb->value_len / sizeof(s32_t), sint_ptr, &vb->vlen);
+        break;
+      default:
+        /* unsupported type */
+        vb->vlen = 0;
+        break;
+    };
+    /* encoding length of value length field */
+    snmp_asn1_enc_length_cnt(vb->vlen, &vb->vlenlen);
+    snmp_asn1_enc_oid_cnt(vb->ident_len, vb->ident, &vb->olen);
+    snmp_asn1_enc_length_cnt(vb->olen, &vb->olenlen);
+
+    vb->seqlen = 1 + vb->vlenlen + vb->vlen;
+    vb->seqlen += 1 + vb->olenlen + vb->olen;
+    snmp_asn1_enc_length_cnt(vb->seqlen, &vb->seqlenlen);
+
+    /* varbind seq */
+    tot_len += 1 + vb->seqlenlen + vb->seqlen;
+
+    vb = vb->prev;
+  }
+
+  /* varbind-list seq */
+  root->seqlen = tot_len;
+  snmp_asn1_enc_length_cnt(root->seqlen, &root->seqlenlen);
+  tot_len += 1 + root->seqlenlen;
+
+  return tot_len;
+}
+
+/**
+ * Encodes response header from head to tail.
+ */
+static u16_t
+snmp_resp_header_enc(struct snmp_msg_pstat *m_stat, struct pbuf *p)
+{
+  u16_t ofs;
+
+  ofs = 0;
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_CONSTR | SNMP_ASN1_SEQ));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_stat->rhl.seqlen);
+  ofs += m_stat->rhl.seqlenlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_stat->rhl.verlen);
+  ofs += m_stat->rhl.verlenlen;
+  snmp_asn1_enc_s32t(p, ofs, m_stat->rhl.verlen, snmp_version);
+  ofs += m_stat->rhl.verlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_stat->rhl.comlen);
+  ofs += m_stat->rhl.comlenlen;
+  snmp_asn1_enc_raw(p, ofs, m_stat->rhl.comlen, m_stat->community);
+  ofs += m_stat->rhl.comlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_GET_RESP));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_stat->rhl.pdulen);
+  ofs += m_stat->rhl.pdulenlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_stat->rhl.ridlen);
+  ofs += m_stat->rhl.ridlenlen;
+  snmp_asn1_enc_s32t(p, ofs, m_stat->rhl.ridlen, m_stat->rid);
+  ofs += m_stat->rhl.ridlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_stat->rhl.errstatlen);
+  ofs += m_stat->rhl.errstatlenlen;
+  snmp_asn1_enc_s32t(p, ofs, m_stat->rhl.errstatlen, m_stat->error_status);
+  ofs += m_stat->rhl.errstatlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_stat->rhl.erridxlen);
+  ofs += m_stat->rhl.erridxlenlen;
+  snmp_asn1_enc_s32t(p, ofs, m_stat->rhl.erridxlen, m_stat->error_index);
+  ofs += m_stat->rhl.erridxlen;
+
+  return ofs;
+}
+
+/**
+ * Encodes trap header from head to tail.
+ */
+static u16_t
+snmp_trap_header_enc(struct snmp_msg_trap *m_trap, struct pbuf *p)
+{
+  u16_t ofs;
+
+  ofs = 0;
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_CONSTR | SNMP_ASN1_SEQ));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_trap->thl.seqlen);
+  ofs += m_trap->thl.seqlenlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_trap->thl.verlen);
+  ofs += m_trap->thl.verlenlen;
+  snmp_asn1_enc_s32t(p, ofs, m_trap->thl.verlen, snmp_version);
+  ofs += m_trap->thl.verlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_trap->thl.comlen);
+  ofs += m_trap->thl.comlenlen;
+  snmp_asn1_enc_raw(p, ofs, m_trap->thl.comlen, (u8_t *)&snmp_publiccommunity[0]);
+  ofs += m_trap->thl.comlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_CONTXT | SNMP_ASN1_CONSTR | SNMP_ASN1_PDU_TRAP));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_trap->thl.pdulen);
+  ofs += m_trap->thl.pdulenlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OBJ_ID));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_trap->thl.eidlen);
+  ofs += m_trap->thl.eidlenlen;
+  snmp_asn1_enc_oid(p, ofs, m_trap->enterprise->len, &m_trap->enterprise->id[0]);
+  ofs += m_trap->thl.eidlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_trap->thl.aaddrlen);
+  ofs += m_trap->thl.aaddrlenlen;
+  snmp_asn1_enc_raw(p, ofs, m_trap->thl.aaddrlen, &m_trap->sip_raw[0]);
+  ofs += m_trap->thl.aaddrlen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_trap->thl.gtrplen);
+  ofs += m_trap->thl.gtrplenlen;
+  snmp_asn1_enc_u32t(p, ofs, m_trap->thl.gtrplen, m_trap->gen_trap);
+  ofs += m_trap->thl.gtrplen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_trap->thl.strplen);
+  ofs += m_trap->thl.strplenlen;
+  snmp_asn1_enc_u32t(p, ofs, m_trap->thl.strplen, m_trap->spc_trap);
+  ofs += m_trap->thl.strplen;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_TIMETICKS));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, m_trap->thl.tslen);
+  ofs += m_trap->thl.tslenlen;
+  snmp_asn1_enc_u32t(p, ofs, m_trap->thl.tslen, m_trap->ts);
+  ofs += m_trap->thl.tslen;
+
+  return ofs;
+}
+
+/**
+ * Encodes varbind list from head to tail.
+ */
+static u16_t
+snmp_varbind_list_enc(struct snmp_varbind_root *root, struct pbuf *p, u16_t ofs)
+{
+  struct snmp_varbind *vb;
+  s32_t *sint_ptr;
+  u32_t *uint_ptr;
+  u8_t *raw_ptr;
+
+  snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_CONSTR | SNMP_ASN1_SEQ));
+  ofs += 1;
+  snmp_asn1_enc_length(p, ofs, root->seqlen);
+  ofs += root->seqlenlen;
+
+  vb = root->head;
+  while ( vb != NULL )
+  {
+    snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_CONSTR | SNMP_ASN1_SEQ));
+    ofs += 1;
+    snmp_asn1_enc_length(p, ofs, vb->seqlen);
+    ofs += vb->seqlenlen;
+
+    snmp_asn1_enc_type(p, ofs, (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OBJ_ID));
+    ofs += 1;
+    snmp_asn1_enc_length(p, ofs, vb->olen);
+    ofs += vb->olenlen;
+    snmp_asn1_enc_oid(p, ofs, vb->ident_len, &vb->ident[0]);
+    ofs += vb->olen;
+
+    snmp_asn1_enc_type(p, ofs, vb->value_type);
+    ofs += 1;
+    snmp_asn1_enc_length(p, ofs, vb->vlen);
+    ofs += vb->vlenlen;
+
+    switch (vb->value_type)
+    {
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_INTEG):
+        sint_ptr = (s32_t*)vb->value;
+        snmp_asn1_enc_s32t(p, ofs, vb->vlen, *sint_ptr);
+        break;
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_COUNTER):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_GAUGE):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_TIMETICKS):
+        uint_ptr = (u32_t*)vb->value;
+        snmp_asn1_enc_u32t(p, ofs, vb->vlen, *uint_ptr);
+        break;
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OC_STR):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_IPADDR):
+      case (SNMP_ASN1_APPLIC | SNMP_ASN1_PRIMIT | SNMP_ASN1_OPAQUE):
+        raw_ptr = (u8_t*)vb->value;
+        snmp_asn1_enc_raw(p, ofs, vb->vlen, raw_ptr);
+        break;
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_NUL):
+        break;
+      case (SNMP_ASN1_UNIV | SNMP_ASN1_PRIMIT | SNMP_ASN1_OBJ_ID):
+        sint_ptr = (s32_t*)vb->value;
+        snmp_asn1_enc_oid(p, ofs, vb->value_len / sizeof(s32_t), sint_ptr);
+        break;
+      default:
+        /* unsupported type */
+        break;
+    };
+    ofs += vb->vlen;
+    vb = vb->next;
+  }
+  return ofs;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/external/badvpn_dns/lwip/src/core/stats.c b/external/badvpn_dns/lwip/src/core/stats.c
new file mode 100644
index 0000000..06fbe0f
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/stats.c
@@ -0,0 +1,181 @@
+/**
+ * @file
+ * Statistics module
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_STATS /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/stats.h"
+#include "lwip/mem.h"
+
+#include <string.h>
+
+struct stats_ lwip_stats;
+
+void stats_init(void)
+{
+#ifdef LWIP_DEBUG
+#if MEMP_STATS
+  const char * memp_names[] = {
+#define LWIP_MEMPOOL(name,num,size,desc) desc,
+#include "lwip/memp_std.h"
+  };
+  int i;
+  for (i = 0; i < MEMP_MAX; i++) {
+    lwip_stats.memp[i].name = memp_names[i];
+  }
+#endif /* MEMP_STATS */
+#if MEM_STATS
+  lwip_stats.mem.name = "MEM";
+#endif /* MEM_STATS */
+#endif /* LWIP_DEBUG */
+}
+
+#if LWIP_STATS_DISPLAY
+void
+stats_display_proto(struct stats_proto *proto, const char *name)
+{
+  LWIP_PLATFORM_DIAG(("\n%s\n\t", name));
+  LWIP_PLATFORM_DIAG(("xmit: %"STAT_COUNTER_F"\n\t", proto->xmit)); 
+  LWIP_PLATFORM_DIAG(("recv: %"STAT_COUNTER_F"\n\t", proto->recv)); 
+  LWIP_PLATFORM_DIAG(("fw: %"STAT_COUNTER_F"\n\t", proto->fw)); 
+  LWIP_PLATFORM_DIAG(("drop: %"STAT_COUNTER_F"\n\t", proto->drop)); 
+  LWIP_PLATFORM_DIAG(("chkerr: %"STAT_COUNTER_F"\n\t", proto->chkerr)); 
+  LWIP_PLATFORM_DIAG(("lenerr: %"STAT_COUNTER_F"\n\t", proto->lenerr)); 
+  LWIP_PLATFORM_DIAG(("memerr: %"STAT_COUNTER_F"\n\t", proto->memerr)); 
+  LWIP_PLATFORM_DIAG(("rterr: %"STAT_COUNTER_F"\n\t", proto->rterr)); 
+  LWIP_PLATFORM_DIAG(("proterr: %"STAT_COUNTER_F"\n\t", proto->proterr)); 
+  LWIP_PLATFORM_DIAG(("opterr: %"STAT_COUNTER_F"\n\t", proto->opterr)); 
+  LWIP_PLATFORM_DIAG(("err: %"STAT_COUNTER_F"\n\t", proto->err)); 
+  LWIP_PLATFORM_DIAG(("cachehit: %"STAT_COUNTER_F"\n", proto->cachehit)); 
+}
+
+#if IGMP_STATS
+void
+stats_display_igmp(struct stats_igmp *igmp, const char *name)
+{
+  LWIP_PLATFORM_DIAG(("\n%s\n\t", name));
+  LWIP_PLATFORM_DIAG(("xmit: %"STAT_COUNTER_F"\n\t", igmp->xmit)); 
+  LWIP_PLATFORM_DIAG(("recv: %"STAT_COUNTER_F"\n\t", igmp->recv)); 
+  LWIP_PLATFORM_DIAG(("drop: %"STAT_COUNTER_F"\n\t", igmp->drop)); 
+  LWIP_PLATFORM_DIAG(("chkerr: %"STAT_COUNTER_F"\n\t", igmp->chkerr)); 
+  LWIP_PLATFORM_DIAG(("lenerr: %"STAT_COUNTER_F"\n\t", igmp->lenerr)); 
+  LWIP_PLATFORM_DIAG(("memerr: %"STAT_COUNTER_F"\n\t", igmp->memerr)); 
+  LWIP_PLATFORM_DIAG(("proterr: %"STAT_COUNTER_F"\n\t", igmp->proterr)); 
+  LWIP_PLATFORM_DIAG(("rx_v1: %"STAT_COUNTER_F"\n\t", igmp->rx_v1)); 
+  LWIP_PLATFORM_DIAG(("rx_group: %"STAT_COUNTER_F"\n\t", igmp->rx_group));
+  LWIP_PLATFORM_DIAG(("rx_general: %"STAT_COUNTER_F"\n\t", igmp->rx_general));
+  LWIP_PLATFORM_DIAG(("rx_report: %"STAT_COUNTER_F"\n\t", igmp->rx_report)); 
+  LWIP_PLATFORM_DIAG(("tx_join: %"STAT_COUNTER_F"\n\t", igmp->tx_join)); 
+  LWIP_PLATFORM_DIAG(("tx_leave: %"STAT_COUNTER_F"\n\t", igmp->tx_leave)); 
+  LWIP_PLATFORM_DIAG(("tx_report: %"STAT_COUNTER_F"\n\t", igmp->tx_report)); 
+}
+#endif /* IGMP_STATS */
+
+#if MEM_STATS || MEMP_STATS
+void
+stats_display_mem(struct stats_mem *mem, const char *name)
+{
+  LWIP_PLATFORM_DIAG(("\nMEM %s\n\t", name));
+  LWIP_PLATFORM_DIAG(("avail: %"U32_F"\n\t", (u32_t)mem->avail)); 
+  LWIP_PLATFORM_DIAG(("used: %"U32_F"\n\t", (u32_t)mem->used)); 
+  LWIP_PLATFORM_DIAG(("max: %"U32_F"\n\t", (u32_t)mem->max)); 
+  LWIP_PLATFORM_DIAG(("err: %"U32_F"\n", (u32_t)mem->err));
+}
+
+#if MEMP_STATS
+void
+stats_display_memp(struct stats_mem *mem, int index)
+{
+  char * memp_names[] = {
+#define LWIP_MEMPOOL(name,num,size,desc) desc,
+#include "lwip/memp_std.h"
+  };
+  if(index < MEMP_MAX) {
+    stats_display_mem(mem, memp_names[index]);
+  }
+}
+#endif /* MEMP_STATS */
+#endif /* MEM_STATS || MEMP_STATS */
+
+#if SYS_STATS
+void
+stats_display_sys(struct stats_sys *sys)
+{
+  LWIP_PLATFORM_DIAG(("\nSYS\n\t"));
+  LWIP_PLATFORM_DIAG(("sem.used:  %"U32_F"\n\t", (u32_t)sys->sem.used)); 
+  LWIP_PLATFORM_DIAG(("sem.max:   %"U32_F"\n\t", (u32_t)sys->sem.max)); 
+  LWIP_PLATFORM_DIAG(("sem.err:   %"U32_F"\n\t", (u32_t)sys->sem.err)); 
+  LWIP_PLATFORM_DIAG(("mutex.used: %"U32_F"\n\t", (u32_t)sys->mutex.used)); 
+  LWIP_PLATFORM_DIAG(("mutex.max:  %"U32_F"\n\t", (u32_t)sys->mutex.max)); 
+  LWIP_PLATFORM_DIAG(("mutex.err:  %"U32_F"\n\t", (u32_t)sys->mutex.err)); 
+  LWIP_PLATFORM_DIAG(("mbox.used:  %"U32_F"\n\t", (u32_t)sys->mbox.used)); 
+  LWIP_PLATFORM_DIAG(("mbox.max:   %"U32_F"\n\t", (u32_t)sys->mbox.max)); 
+  LWIP_PLATFORM_DIAG(("mbox.err:   %"U32_F"\n\t", (u32_t)sys->mbox.err)); 
+}
+#endif /* SYS_STATS */
+
+void
+stats_display(void)
+{
+  s16_t i;
+
+  LINK_STATS_DISPLAY();
+  ETHARP_STATS_DISPLAY();
+  IPFRAG_STATS_DISPLAY();
+  IP6_FRAG_STATS_DISPLAY();
+  IP_STATS_DISPLAY();
+  ND6_STATS_DISPLAY();
+  IP6_STATS_DISPLAY();
+  IGMP_STATS_DISPLAY();
+  MLD6_STATS_DISPLAY();
+  ICMP_STATS_DISPLAY();
+  ICMP6_STATS_DISPLAY();
+  UDP_STATS_DISPLAY();
+  TCP_STATS_DISPLAY();
+  MEM_STATS_DISPLAY();
+  for (i = 0; i < MEMP_MAX; i++) {
+    MEMP_STATS_DISPLAY(i);
+  }
+  SYS_STATS_DISPLAY();
+}
+#endif /* LWIP_STATS_DISPLAY */
+
+#endif /* LWIP_STATS */
+
diff --git a/external/badvpn_dns/lwip/src/core/sys.c b/external/badvpn_dns/lwip/src/core/sys.c
new file mode 100644
index 0000000..f177737
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/sys.c
@@ -0,0 +1,68 @@
+/**
+ * @file
+ * lwIP Operating System abstraction
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/sys.h"
+
+/* Most of the functions defined in sys.h must be implemented in the
+ * architecture-dependent file sys_arch.c */
+
+#if !NO_SYS
+
+#ifndef sys_msleep
+/**
+ * Sleep for some ms. Timeouts are NOT processed while sleeping.
+ *
+ * @param ms number of milliseconds to sleep
+ */
+void
+sys_msleep(u32_t ms)
+{
+  if (ms > 0) {
+    sys_sem_t delaysem;
+    err_t err = sys_sem_new(&delaysem, 0);
+    if (err == ERR_OK) {
+      sys_arch_sem_wait(&delaysem, ms);
+      sys_sem_free(&delaysem);
+    }
+  }
+}
+#endif /* sys_msleep */
+
+#endif /* !NO_SYS */
diff --git a/external/badvpn_dns/lwip/src/core/tcp.c b/external/badvpn_dns/lwip/src/core/tcp.c
new file mode 100644
index 0000000..55496d0
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/tcp.c
@@ -0,0 +1,1852 @@
+/**
+ * @file
+ * Transmission Control Protocol for IP
+ *
+ * This file contains common functions for the TCP implementation, such as functinos
+ * for manipulating the data structures and the TCP timer functions. TCP functions
+ * related to input and output is found in tcp_in.c and tcp_out.c respectively.
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/snmp.h"
+#include "lwip/tcp.h"
+#include "lwip/tcp_impl.h"
+#include "lwip/debug.h"
+#include "lwip/stats.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/nd6.h"
+
+#include <string.h>
+
+#ifndef TCP_LOCAL_PORT_RANGE_START
+/* From http://www.iana.org/assignments/port-numbers:
+   "The Dynamic and/or Private Ports are those from 49152 through 65535" */
+#define TCP_LOCAL_PORT_RANGE_START        0xc000
+#define TCP_LOCAL_PORT_RANGE_END          0xffff
+#define TCP_ENSURE_LOCAL_PORT_RANGE(port) (((port) & ~TCP_LOCAL_PORT_RANGE_START) + TCP_LOCAL_PORT_RANGE_START)
+#endif
+
+#if LWIP_TCP_KEEPALIVE
+#define TCP_KEEP_DUR(pcb)   ((pcb)->keep_cnt * (pcb)->keep_intvl)
+#define TCP_KEEP_INTVL(pcb) ((pcb)->keep_intvl)
+#else /* LWIP_TCP_KEEPALIVE */
+#define TCP_KEEP_DUR(pcb)   TCP_MAXIDLE
+#define TCP_KEEP_INTVL(pcb) TCP_KEEPINTVL_DEFAULT
+#endif /* LWIP_TCP_KEEPALIVE */
+
+const char * const tcp_state_str[] = {
+  "CLOSED",      
+  "LISTEN",      
+  "SYN_SENT",    
+  "SYN_RCVD",    
+  "ESTABLISHED", 
+  "FIN_WAIT_1",  
+  "FIN_WAIT_2",  
+  "CLOSE_WAIT",  
+  "CLOSING",     
+  "LAST_ACK",    
+  "TIME_WAIT"   
+};
+
+/* last local TCP port */
+static u16_t tcp_port = TCP_LOCAL_PORT_RANGE_START;
+
+/* Incremented every coarse grained timer shot (typically every 500 ms). */
+u32_t tcp_ticks;
+const u8_t tcp_backoff[13] =
+    { 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7};
+ /* Times per slowtmr hits */
+const u8_t tcp_persist_backoff[7] = { 3, 6, 12, 24, 48, 96, 120 };
+
+/* The TCP PCB lists. */
+
+/** List of all TCP PCBs bound but not yet (connected || listening) */
+struct tcp_pcb *tcp_bound_pcbs;
+/** List of all TCP PCBs in LISTEN state */
+union tcp_listen_pcbs_t tcp_listen_pcbs;
+/** List of all TCP PCBs that are in a state in which
+ * they accept or send data. */
+struct tcp_pcb *tcp_active_pcbs;
+/** List of all TCP PCBs in TIME-WAIT state */
+struct tcp_pcb *tcp_tw_pcbs;
+
+#define NUM_TCP_PCB_LISTS               4
+#define NUM_TCP_PCB_LISTS_NO_TIME_WAIT  3
+/** An array with all (non-temporary) PCB lists, mainly used for smaller code size */
+struct tcp_pcb ** const tcp_pcb_lists[] = {&tcp_listen_pcbs.pcbs, &tcp_bound_pcbs,
+  &tcp_active_pcbs, &tcp_tw_pcbs};
+
+/** Only used for temporary storage. */
+struct tcp_pcb *tcp_tmp_pcb;
+
+u8_t tcp_active_pcbs_changed;
+
+/** Timer counter to handle calling slow-timer from tcp_tmr() */ 
+static u8_t tcp_timer;
+static u8_t tcp_timer_ctr;
+static u16_t tcp_new_port(void);
+
+/**
+ * Initialize this module.
+ */
+void
+tcp_init(void)
+{
+#if LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS && defined(LWIP_RAND)
+  tcp_port = TCP_ENSURE_LOCAL_PORT_RANGE(LWIP_RAND());
+#endif /* LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS && defined(LWIP_RAND) */
+}
+
+/**
+ * Called periodically to dispatch TCP timers.
+ */
+void
+tcp_tmr(void)
+{
+  /* Call tcp_fasttmr() every 250 ms */
+  tcp_fasttmr();
+
+  if (++tcp_timer & 1) {
+    /* Call tcp_tmr() every 500 ms, i.e., every other timer
+       tcp_tmr() is called. */
+    tcp_slowtmr();
+  }
+}
+
+/**
+ * Closes the TX side of a connection held by the PCB.
+ * For tcp_close(), a RST is sent if the application didn't receive all data
+ * (tcp_recved() not called for all data passed to recv callback).
+ *
+ * Listening pcbs are freed and may not be referenced any more.
+ * Connection pcbs are freed if not yet connected and may not be referenced
+ * any more. If a connection is established (at least SYN received or in
+ * a closing state), the connection is closed, and put in a closing state.
+ * The pcb is then automatically freed in tcp_slowtmr(). It is therefore
+ * unsafe to reference it.
+ *
+ * @param pcb the tcp_pcb to close
+ * @return ERR_OK if connection has been closed
+ *         another err_t if closing failed and pcb is not freed
+ */
+static err_t
+tcp_close_shutdown(struct tcp_pcb *pcb, u8_t rst_on_unacked_data)
+{
+  err_t err;
+
+  if (rst_on_unacked_data && ((pcb->state == ESTABLISHED) || (pcb->state == CLOSE_WAIT))) {
+    if ((pcb->refused_data != NULL) || (pcb->rcv_wnd != TCP_WND)) {
+      /* Not all data received by application, send RST to tell the remote
+         side about this. */
+      LWIP_ASSERT("pcb->flags & TF_RXCLOSED", pcb->flags & TF_RXCLOSED);
+
+      /* don't call tcp_abort here: we must not deallocate the pcb since
+         that might not be expected when calling tcp_close */
+      tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
+               pcb->local_port, pcb->remote_port, PCB_ISIPV6(pcb));
+
+      tcp_pcb_purge(pcb);
+      TCP_RMV_ACTIVE(pcb);
+      if (pcb->state == ESTABLISHED) {
+        /* move to TIME_WAIT since we close actively */
+        pcb->state = TIME_WAIT;
+        TCP_REG(&tcp_tw_pcbs, pcb);
+      } else {
+        /* CLOSE_WAIT: deallocate the pcb since we already sent a RST for it */
+        memp_free(MEMP_TCP_PCB, pcb);
+      }
+      return ERR_OK;
+    }
+  }
+
+  switch (pcb->state) {
+  case CLOSED:
+    /* Closing a pcb in the CLOSED state might seem erroneous,
+     * however, it is in this state once allocated and as yet unused
+     * and the user needs some way to free it should the need arise.
+     * Calling tcp_close() with a pcb that has already been closed, (i.e. twice)
+     * or for a pcb that has been used and then entered the CLOSED state 
+     * is erroneous, but this should never happen as the pcb has in those cases
+     * been freed, and so any remaining handles are bogus. */
+    err = ERR_OK;
+    if (pcb->local_port != 0 || pcb->bound_to_netif) {
+      TCP_RMV(&tcp_bound_pcbs, pcb);
+    }
+    memp_free(MEMP_TCP_PCB, pcb);
+    pcb = NULL;
+    break;
+  case LISTEN:
+    err = ERR_OK;
+    tcp_pcb_remove(&tcp_listen_pcbs.pcbs, pcb);
+    memp_free(MEMP_TCP_PCB_LISTEN, pcb);
+    pcb = NULL;
+    break;
+  case SYN_SENT:
+    err = ERR_OK;
+    TCP_PCB_REMOVE_ACTIVE(pcb);
+    memp_free(MEMP_TCP_PCB, pcb);
+    pcb = NULL;
+    snmp_inc_tcpattemptfails();
+    break;
+  case SYN_RCVD:
+    err = tcp_send_fin(pcb);
+    if (err == ERR_OK) {
+      snmp_inc_tcpattemptfails();
+      pcb->state = FIN_WAIT_1;
+    }
+    break;
+  case ESTABLISHED:
+    err = tcp_send_fin(pcb);
+    if (err == ERR_OK) {
+      snmp_inc_tcpestabresets();
+      pcb->state = FIN_WAIT_1;
+    }
+    break;
+  case CLOSE_WAIT:
+    err = tcp_send_fin(pcb);
+    if (err == ERR_OK) {
+      snmp_inc_tcpestabresets();
+      pcb->state = LAST_ACK;
+    }
+    break;
+  default:
+    /* Has already been closed, do nothing. */
+    err = ERR_OK;
+    pcb = NULL;
+    break;
+  }
+
+  if (pcb != NULL && err == ERR_OK) {
+    /* To ensure all data has been sent when tcp_close returns, we have
+       to make sure tcp_output doesn't fail.
+       Since we don't really have to ensure all data has been sent when tcp_close
+       returns (unsent data is sent from tcp timer functions, also), we don't care
+       for the return value of tcp_output for now. */
+    /* @todo: When implementing SO_LINGER, this must be changed somehow:
+       If SOF_LINGER is set, the data should be sent and acked before close returns.
+       This can only be valid for sequential APIs, not for the raw API. */
+    tcp_output(pcb);
+  }
+  return err;
+}
+
+/**
+ * Closes the connection held by the PCB.
+ *
+ * Listening pcbs are freed and may not be referenced any more.
+ * Connection pcbs are freed if not yet connected and may not be referenced
+ * any more. If a connection is established (at least SYN received or in
+ * a closing state), the connection is closed, and put in a closing state.
+ * The pcb is then automatically freed in tcp_slowtmr(). It is therefore
+ * unsafe to reference it (unless an error is returned).
+ *
+ * @param pcb the tcp_pcb to close
+ * @return ERR_OK if connection has been closed
+ *         another err_t if closing failed and pcb is not freed
+ */
+err_t
+tcp_close(struct tcp_pcb *pcb)
+{
+#if TCP_DEBUG
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_close: closing in "));
+  tcp_debug_print_state(pcb->state);
+#endif /* TCP_DEBUG */
+
+  if (pcb->state != LISTEN) {
+    /* Set a flag not to receive any more data... */
+    pcb->flags |= TF_RXCLOSED;
+  }
+  /* ... and close */
+  return tcp_close_shutdown(pcb, 1);
+}
+
+/**
+ * Causes all or part of a full-duplex connection of this PCB to be shut down.
+ * This doesn't deallocate the PCB unless shutting down both sides!
+ * Shutting down both sides is the same as calling tcp_close, so if it succeds,
+ * the PCB should not be referenced any more.
+ *
+ * @param pcb PCB to shutdown
+ * @param shut_rx shut down receive side if this is != 0
+ * @param shut_tx shut down send side if this is != 0
+ * @return ERR_OK if shutdown succeeded (or the PCB has already been shut down)
+ *         another err_t on error.
+ */
+err_t
+tcp_shutdown(struct tcp_pcb *pcb, int shut_rx, int shut_tx)
+{
+  if (pcb->state == LISTEN) {
+    return ERR_CONN;
+  }
+  if (shut_rx) {
+    /* shut down the receive side: set a flag not to receive any more data... */
+    pcb->flags |= TF_RXCLOSED;
+    if (shut_tx) {
+      /* shutting down the tx AND rx side is the same as closing for the raw API */
+      return tcp_close_shutdown(pcb, 1);
+    }
+    /* ... and free buffered data */
+    if (pcb->refused_data != NULL) {
+      pbuf_free(pcb->refused_data);
+      pcb->refused_data = NULL;
+    }
+  }
+  if (shut_tx) {
+    /* This can't happen twice since if it succeeds, the pcb's state is changed.
+       Only close in these states as the others directly deallocate the PCB */
+    switch (pcb->state) {
+    case SYN_RCVD:
+    case ESTABLISHED:
+    case CLOSE_WAIT:
+      return tcp_close_shutdown(pcb, shut_rx);
+    default:
+      /* Not (yet?) connected, cannot shutdown the TX side as that would bring us
+        into CLOSED state, where the PCB is deallocated. */
+      return ERR_CONN;
+    }
+  }
+  return ERR_OK;
+}
+
+/**
+ * Abandons a connection and optionally sends a RST to the remote
+ * host.  Deletes the local protocol control block. This is done when
+ * a connection is killed because of shortage of memory.
+ *
+ * @param pcb the tcp_pcb to abort
+ * @param reset boolean to indicate whether a reset should be sent
+ */
+void
+tcp_abandon(struct tcp_pcb *pcb, int reset)
+{
+  u32_t seqno, ackno;
+#if LWIP_CALLBACK_API  
+  tcp_err_fn errf;
+#endif /* LWIP_CALLBACK_API */
+  void *errf_arg;
+
+  /* pcb->state LISTEN not allowed here */
+  LWIP_ASSERT("don't call tcp_abort/tcp_abandon for listen-pcbs",
+    pcb->state != LISTEN);
+  /* Figure out on which TCP PCB list we are, and remove us. If we
+     are in an active state, call the receive function associated with
+     the PCB with a NULL argument, and send an RST to the remote end. */
+  if (pcb->state == TIME_WAIT) {
+    tcp_pcb_remove(&tcp_tw_pcbs, pcb);
+    memp_free(MEMP_TCP_PCB, pcb);
+  } else {
+    int send_rst = reset && (pcb->state != CLOSED);
+    seqno = pcb->snd_nxt;
+    ackno = pcb->rcv_nxt;
+#if LWIP_CALLBACK_API
+    errf = pcb->errf;
+#endif /* LWIP_CALLBACK_API */
+    errf_arg = pcb->callback_arg;
+    TCP_PCB_REMOVE_ACTIVE(pcb);
+    if (pcb->unacked != NULL) {
+      tcp_segs_free(pcb->unacked);
+    }
+    if (pcb->unsent != NULL) {
+      tcp_segs_free(pcb->unsent);
+    }
+#if TCP_QUEUE_OOSEQ    
+    if (pcb->ooseq != NULL) {
+      tcp_segs_free(pcb->ooseq);
+    }
+#endif /* TCP_QUEUE_OOSEQ */
+    if (send_rst) {
+      LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_abandon: sending RST\n"));
+      tcp_rst(seqno, ackno, &pcb->local_ip, &pcb->remote_ip, pcb->local_port, pcb->remote_port, PCB_ISIPV6(pcb));
+    }
+    memp_free(MEMP_TCP_PCB, pcb);
+    TCP_EVENT_ERR(errf, errf_arg, ERR_ABRT);
+  }
+}
+
+/**
+ * Aborts the connection by sending a RST (reset) segment to the remote
+ * host. The pcb is deallocated. This function never fails.
+ *
+ * ATTENTION: When calling this from one of the TCP callbacks, make
+ * sure you always return ERR_ABRT (and never return ERR_ABRT otherwise
+ * or you will risk accessing deallocated memory or memory leaks!
+ *
+ * @param pcb the tcp pcb to abort
+ */
+void
+tcp_abort(struct tcp_pcb *pcb)
+{
+  tcp_abandon(pcb, 1);
+}
+
+/**
+ * Binds the connection to a local portnumber and IP address. If the
+ * IP address is not given (i.e., ipaddr == NULL), the IP address of
+ * the outgoing network interface is used instead.
+ *
+ * @param pcb the tcp_pcb to bind (no check is done whether this pcb is
+ *        already bound!)
+ * @param ipaddr the local ip address to bind to (use IP_ADDR_ANY to bind
+ *        to any local address
+ * @param port the local port to bind to
+ * @return ERR_USE if the port is already in use
+ *         ERR_VAL if bind failed because the PCB is not in a valid state
+ *         ERR_OK if bound
+ */
+err_t
+tcp_bind(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
+{
+  int i;
+  int max_pcb_list = NUM_TCP_PCB_LISTS;
+  struct tcp_pcb *cpcb;
+
+  LWIP_ERROR("tcp_bind: can only bind in state CLOSED", pcb->state == CLOSED, return ERR_VAL);
+
+#if SO_REUSE
+  /* Unless the REUSEADDR flag is set,
+     we have to check the pcbs in TIME-WAIT state, also.
+     We do not dump TIME_WAIT pcb's; they can still be matched by incoming
+     packets using both local and remote IP addresses and ports to distinguish.
+   */
+  if (ip_get_option(pcb, SOF_REUSEADDR)) {
+    max_pcb_list = NUM_TCP_PCB_LISTS_NO_TIME_WAIT;
+  }
+#endif /* SO_REUSE */
+
+  if (port == 0) {
+    port = tcp_new_port();
+    if (port == 0) {
+      return ERR_BUF;
+    }
+  }
+
+  /* Check if the address already is in use (on all lists) */
+  for (i = 0; i < max_pcb_list; i++) {
+    for(cpcb = *tcp_pcb_lists[i]; cpcb != NULL; cpcb = cpcb->next) {
+      if (cpcb->local_port == port) {
+#if SO_REUSE
+        /* Omit checking for the same port if both pcbs have REUSEADDR set.
+           For SO_REUSEADDR, the duplicate-check for a 5-tuple is done in
+           tcp_connect. */
+        if (!ip_get_option(pcb, SOF_REUSEADDR) ||
+            !ip_get_option(cpcb, SOF_REUSEADDR))
+#endif /* SO_REUSE */
+        {
+          /* @todo: check accept_any_ip_version */
+          if (IP_PCB_IPVER_EQ(pcb, cpcb) &&
+              (ipX_addr_isany(PCB_ISIPV6(pcb), &cpcb->local_ip) ||
+              ipX_addr_isany(PCB_ISIPV6(pcb), ip_2_ipX(ipaddr)) ||
+              ipX_addr_cmp(PCB_ISIPV6(pcb), &cpcb->local_ip, ip_2_ipX(ipaddr)))) {
+            return ERR_USE;
+          }
+        }
+      }
+    }
+  }
+
+  pcb->bound_to_netif = 0;
+  if (!ipX_addr_isany(PCB_ISIPV6(pcb), ip_2_ipX(ipaddr))) {
+    ipX_addr_set(PCB_ISIPV6(pcb), &pcb->local_ip, ip_2_ipX(ipaddr));
+  }
+  pcb->local_port = port;
+  TCP_REG(&tcp_bound_pcbs, pcb);
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_bind: bind to port %"U16_F"\n", port));
+  return ERR_OK;
+}
+
+err_t
+tcp_bind_to_netif(struct tcp_pcb *pcb, const char ifname[3])
+{
+  LWIP_ERROR("tcp_bind_if: can only bind in state CLOSED", pcb->state == CLOSED, return ERR_ISCONN);
+  
+  /* Check if the interface is already in use */
+  for (int i = 0; i < NUM_TCP_PCB_LISTS; i++) {
+    for(struct tcp_pcb *cpcb = *tcp_pcb_lists[i]; cpcb != NULL; cpcb = cpcb->next) {
+      if (IP_PCB_IPVER_EQ(pcb, cpcb) &&
+          cpcb->bound_to_netif &&
+          !memcmp(cpcb->local_netif, ifname, sizeof(cpcb->local_netif))) {
+        return ERR_USE;
+      }
+    }
+  }
+
+  pcb->bound_to_netif = 1;
+  ipX_addr_set_any(PCB_ISIPV6(pcb), &pcb->local_ip);
+  pcb->local_port = 0;
+  memcpy(pcb->local_netif, ifname, sizeof(pcb->local_netif));
+  TCP_REG(&tcp_bound_pcbs, pcb);
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_bind_to_netif: bind to interface %c%c%c\n", ifname[0], ifname[1], ifname[2]));
+  return ERR_OK;
+}
+
+#if LWIP_CALLBACK_API
+/**
+ * Default accept callback if no accept callback is specified by the user.
+ */
+static err_t
+tcp_accept_null(void *arg, struct tcp_pcb *pcb, err_t err)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_UNUSED_ARG(pcb);
+  LWIP_UNUSED_ARG(err);
+
+  return ERR_ABRT;
+}
+#endif /* LWIP_CALLBACK_API */
+
+/**
+ * Set the state of the connection to be LISTEN, which means that it
+ * is able to accept incoming connections. The protocol control block
+ * is reallocated in order to consume less memory. Setting the
+ * connection to LISTEN is an irreversible process.
+ *
+ * @param pcb the original tcp_pcb
+ * @param backlog the incoming connections queue limit
+ * @return tcp_pcb used for listening, consumes less memory.
+ *
+ * @note The original tcp_pcb is freed. This function therefore has to be
+ *       called like this:
+ *             tpcb = tcp_listen(tpcb);
+ */
+struct tcp_pcb *
+tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog)
+{
+  struct tcp_pcb_listen *lpcb;
+
+  LWIP_UNUSED_ARG(backlog);
+  LWIP_ERROR("tcp_listen: pcb already connected", pcb->state == CLOSED, return NULL);
+
+  /* already listening? */
+  if (pcb->state == LISTEN) {
+    return pcb;
+  }
+#if SO_REUSE
+  if (ip_get_option(pcb, SOF_REUSEADDR) && !pcb->have_local_netif) {
+    /* Since SOF_REUSEADDR allows reusing a local address before the pcb's usage
+       is declared (listen-/connection-pcb), we have to make sure now that
+       this port is only used once for every local IP. */
+    for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
+      if ((lpcb->local_port == pcb->local_port) &&
+          IP_PCB_IPVER_EQ(pcb, lpcb)) {
+        if (ipX_addr_cmp(PCB_ISIPV6(pcb), &lpcb->local_ip, &pcb->local_ip)) {
+          /* this address/port is already used */
+          return NULL;
+        }
+      }
+    }
+  }
+#endif /* SO_REUSE */
+  lpcb = (struct tcp_pcb_listen *)memp_malloc(MEMP_TCP_PCB_LISTEN);
+  if (lpcb == NULL) {
+    return NULL;
+  }
+  lpcb->callback_arg = pcb->callback_arg;
+  lpcb->bound_to_netif = pcb->bound_to_netif;
+  lpcb->local_port = pcb->local_port;
+  memcpy(lpcb->local_netif, pcb->local_netif, sizeof(pcb->local_netif));
+  lpcb->state = LISTEN;
+  lpcb->prio = pcb->prio;
+  lpcb->so_options = pcb->so_options;
+  ip_set_option(lpcb, SOF_ACCEPTCONN);
+  lpcb->ttl = pcb->ttl;
+  lpcb->tos = pcb->tos;
+#if LWIP_IPV6
+  PCB_ISIPV6(lpcb) = PCB_ISIPV6(pcb);
+  lpcb->accept_any_ip_version = 0;
+#endif /* LWIP_IPV6 */
+  ipX_addr_copy(PCB_ISIPV6(pcb), lpcb->local_ip, pcb->local_ip);
+  if (pcb->local_port != 0 || pcb->bound_to_netif) {
+    TCP_RMV(&tcp_bound_pcbs, pcb);
+  }
+  memp_free(MEMP_TCP_PCB, pcb);
+#if LWIP_CALLBACK_API
+  lpcb->accept = tcp_accept_null;
+#endif /* LWIP_CALLBACK_API */
+#if TCP_LISTEN_BACKLOG
+  lpcb->accepts_pending = 0;
+  lpcb->backlog = (backlog ? backlog : 1);
+#endif /* TCP_LISTEN_BACKLOG */
+  TCP_REG(&tcp_listen_pcbs.pcbs, (struct tcp_pcb *)lpcb);
+  return (struct tcp_pcb *)lpcb;
+}
+
+#if LWIP_IPV6
+/**
+ * Same as tcp_listen_with_backlog, but allows to accept IPv4 and IPv6
+ * connections, if the pcb's local address is set to ANY.
+ */
+struct tcp_pcb *
+tcp_listen_dual_with_backlog(struct tcp_pcb *pcb, u8_t backlog)
+{
+  struct tcp_pcb *lpcb;
+
+  lpcb = tcp_listen_with_backlog(pcb, backlog);
+  if ((lpcb != NULL) &&
+      ipX_addr_isany(PCB_ISIPV6(pcb), &pcb->local_ip)) {
+    /* The default behavior is to accept connections on either
+     * IPv4 or IPv6, if not bound. */
+    /* @see NETCONN_FLAG_IPV6_V6ONLY for changing this behavior */
+    ((struct tcp_pcb_listen*)lpcb)->accept_any_ip_version = 1;
+  }
+  return lpcb;
+}
+#endif /* LWIP_IPV6 */
+
+/**
+ * Update the state that tracks the available window space to advertise.
+ *
+ * Returns how much extra window would be advertised if we sent an
+ * update now.
+ */
+u32_t tcp_update_rcv_ann_wnd(struct tcp_pcb *pcb)
+{
+  u32_t new_right_edge = pcb->rcv_nxt + pcb->rcv_wnd;
+
+  if (TCP_SEQ_GEQ(new_right_edge, pcb->rcv_ann_right_edge + LWIP_MIN((TCP_WND / 2), pcb->mss))) {
+    /* we can advertise more window */
+    pcb->rcv_ann_wnd = pcb->rcv_wnd;
+    return new_right_edge - pcb->rcv_ann_right_edge;
+  } else {
+    if (TCP_SEQ_GT(pcb->rcv_nxt, pcb->rcv_ann_right_edge)) {
+      /* Can happen due to other end sending out of advertised window,
+       * but within actual available (but not yet advertised) window */
+      pcb->rcv_ann_wnd = 0;
+    } else {
+      /* keep the right edge of window constant */
+      u32_t new_rcv_ann_wnd = pcb->rcv_ann_right_edge - pcb->rcv_nxt;
+      LWIP_ASSERT("new_rcv_ann_wnd <= 0xffff", new_rcv_ann_wnd <= 0xffff);
+      pcb->rcv_ann_wnd = (u16_t)new_rcv_ann_wnd;
+    }
+    return 0;
+  }
+}
+
+/**
+ * This function should be called by the application when it has
+ * processed the data. The purpose is to advertise a larger window
+ * when the data has been processed.
+ *
+ * @param pcb the tcp_pcb for which data is read
+ * @param len the amount of bytes that have been read by the application
+ */
+void
+tcp_recved(struct tcp_pcb *pcb, u16_t len)
+{
+  int wnd_inflation;
+
+  /* pcb->state LISTEN not allowed here */
+  LWIP_ASSERT("don't call tcp_recved for listen-pcbs",
+    pcb->state != LISTEN);
+  LWIP_ASSERT("tcp_recved: len would wrap rcv_wnd\n",
+              len <= 0xffff - pcb->rcv_wnd );
+
+  pcb->rcv_wnd += len;
+  if (pcb->rcv_wnd > TCP_WND) {
+    pcb->rcv_wnd = TCP_WND;
+  }
+
+  wnd_inflation = tcp_update_rcv_ann_wnd(pcb);
+
+  /* If the change in the right edge of window is significant (default
+   * watermark is TCP_WND/4), then send an explicit update now.
+   * Otherwise wait for a packet to be sent in the normal course of
+   * events (or more window to be available later) */
+  if (wnd_inflation >= TCP_WND_UPDATE_THRESHOLD) {
+    tcp_ack_now(pcb);
+    tcp_output(pcb);
+  }
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_recved: recveived %"U16_F" bytes, wnd %"U16_F" (%"U16_F").\n",
+         len, pcb->rcv_wnd, TCP_WND - pcb->rcv_wnd));
+}
+
+/**
+ * Allocate a new local TCP port.
+ *
+ * @return a new (free) local TCP port number
+ */
+static u16_t
+tcp_new_port(void)
+{
+  u8_t i;
+  u16_t n = 0;
+  struct tcp_pcb *pcb;
+  
+again:
+  if (tcp_port++ == TCP_LOCAL_PORT_RANGE_END) {
+    tcp_port = TCP_LOCAL_PORT_RANGE_START;
+  }
+  /* Check all PCB lists. */
+  for (i = 0; i < NUM_TCP_PCB_LISTS; i++) {
+    for(pcb = *tcp_pcb_lists[i]; pcb != NULL; pcb = pcb->next) {
+      if (pcb->local_port == tcp_port) {
+        if (++n > (TCP_LOCAL_PORT_RANGE_END - TCP_LOCAL_PORT_RANGE_START)) {
+          return 0;
+        }
+        goto again;
+      }
+    }
+  }
+  return tcp_port;
+}
+
+/**
+ * Connects to another host. The function given as the "connected"
+ * argument will be called when the connection has been established.
+ *
+ * @param pcb the tcp_pcb used to establish the connection
+ * @param ipaddr the remote ip address to connect to
+ * @param port the remote tcp port to connect to
+ * @param connected callback function to call when connected (or on error)
+ * @return ERR_VAL if invalid arguments are given
+ *         ERR_OK if connect request has been sent
+ *         other err_t values if connect request couldn't be sent
+ */
+err_t
+tcp_connect(struct tcp_pcb *pcb, ip_addr_t *ipaddr, u16_t port,
+      tcp_connected_fn connected)
+{
+  err_t ret;
+  u32_t iss;
+  u16_t old_local_port;
+
+  LWIP_ERROR("tcp_connect: can only connect from state CLOSED", pcb->state == CLOSED, return ERR_ISCONN);
+  LWIP_ERROR("tcp_connect: cannot connect pcb bound to netif", !pcb->bound_to_netif, return ERR_VAL);
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_connect to port %"U16_F"\n", port));
+  if (ipaddr != NULL) {
+    ipX_addr_set(PCB_ISIPV6(pcb), &pcb->remote_ip, ip_2_ipX(ipaddr));
+  } else {
+    return ERR_VAL;
+  }
+  pcb->remote_port = port;
+
+  /* check if we have a route to the remote host */
+  if (ipX_addr_isany(PCB_ISIPV6(pcb), &pcb->local_ip)) {
+    /* no local IP address set, yet. */
+    struct netif *netif;
+    ipX_addr_t *local_ip;
+    ipX_route_get_local_ipX(PCB_ISIPV6(pcb), &pcb->local_ip, &pcb->remote_ip, netif, local_ip);
+    if ((netif == NULL) || (local_ip == NULL)) {
+      /* Don't even try to send a SYN packet if we have no route
+         since that will fail. */
+      return ERR_RTE;
+    }
+    /* Use the address as local address of the pcb. */
+    ipX_addr_copy(PCB_ISIPV6(pcb), pcb->local_ip, *local_ip);
+  }
+
+  old_local_port = pcb->local_port;
+  if (pcb->local_port == 0) {
+    pcb->local_port = tcp_new_port();
+    if (pcb->local_port == 0) {
+      return ERR_BUF;
+    }
+  }
+#if SO_REUSE
+  if (ip_get_option(pcb, SOF_REUSEADDR)) {
+    /* Since SOF_REUSEADDR allows reusing a local address, we have to make sure
+       now that the 5-tuple is unique. */
+    struct tcp_pcb *cpcb;
+    int i;
+    /* Don't check listen- and bound-PCBs, check active- and TIME-WAIT PCBs. */
+    for (i = 2; i < NUM_TCP_PCB_LISTS; i++) {
+      for(cpcb = *tcp_pcb_lists[i]; cpcb != NULL; cpcb = cpcb->next) {
+        if ((cpcb->local_port == pcb->local_port) &&
+            (cpcb->remote_port == port) &&
+            IP_PCB_IPVER_EQ(cpcb, pcb) &&
+            ipX_addr_cmp(PCB_ISIPV6(pcb), &cpcb->local_ip, &pcb->local_ip) &&
+            ipX_addr_cmp(PCB_ISIPV6(pcb), &cpcb->remote_ip, ip_2_ipX(ipaddr))) {
+          /* linux returns EISCONN here, but ERR_USE should be OK for us */
+          return ERR_USE;
+        }
+      }
+    }
+  }
+#endif /* SO_REUSE */
+  iss = tcp_next_iss();
+  pcb->rcv_nxt = 0;
+  pcb->snd_nxt = iss;
+  pcb->lastack = iss - 1;
+  pcb->snd_lbb = iss - 1;
+  pcb->rcv_wnd = TCP_WND;
+  pcb->rcv_ann_wnd = TCP_WND;
+  pcb->rcv_ann_right_edge = pcb->rcv_nxt;
+  pcb->snd_wnd = TCP_WND;
+  /* As initial send MSS, we use TCP_MSS but limit it to 536.
+     The send MSS is updated when an MSS option is received. */
+  pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS;
+#if TCP_CALCULATE_EFF_SEND_MSS
+  pcb->mss = tcp_eff_send_mss(pcb->mss, &pcb->local_ip, &pcb->remote_ip, PCB_ISIPV6(pcb));
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+  pcb->cwnd = 1;
+  pcb->ssthresh = pcb->mss * 10;
+#if LWIP_CALLBACK_API
+  pcb->connected = connected;
+#else /* LWIP_CALLBACK_API */
+  LWIP_UNUSED_ARG(connected);
+#endif /* LWIP_CALLBACK_API */
+
+  /* Send a SYN together with the MSS option. */
+  ret = tcp_enqueue_flags(pcb, TCP_SYN);
+  if (ret == ERR_OK) {
+    /* SYN segment was enqueued, changed the pcbs state now */
+    pcb->state = SYN_SENT;
+    if (old_local_port != 0) {
+      TCP_RMV(&tcp_bound_pcbs, pcb);
+    }
+    TCP_REG_ACTIVE(pcb);
+    snmp_inc_tcpactiveopens();
+
+    tcp_output(pcb);
+  }
+  return ret;
+}
+
+/**
+ * Called every 500 ms and implements the retransmission timer and the timer that
+ * removes PCBs that have been in TIME-WAIT for enough time. It also increments
+ * various timers such as the inactivity timer in each PCB.
+ *
+ * Automatically called from tcp_tmr().
+ */
+void
+tcp_slowtmr(void)
+{
+  struct tcp_pcb *pcb, *prev;
+  u16_t eff_wnd;
+  u8_t pcb_remove;      /* flag if a PCB should be removed */
+  u8_t pcb_reset;       /* flag if a RST should be sent when removing */
+  err_t err;
+
+  err = ERR_OK;
+
+  ++tcp_ticks;
+  ++tcp_timer_ctr;
+
+tcp_slowtmr_start:
+  /* Steps through all of the active PCBs. */
+  prev = NULL;
+  pcb = tcp_active_pcbs;
+  if (pcb == NULL) {
+    LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: no active pcbs\n"));
+  }
+  while (pcb != NULL) {
+    LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: processing active pcb\n"));
+    LWIP_ASSERT("tcp_slowtmr: active pcb->state != CLOSED\n", pcb->state != CLOSED);
+    LWIP_ASSERT("tcp_slowtmr: active pcb->state != LISTEN\n", pcb->state != LISTEN);
+    LWIP_ASSERT("tcp_slowtmr: active pcb->state != TIME-WAIT\n", pcb->state != TIME_WAIT);
+    if (pcb->last_timer == tcp_timer_ctr) {
+      /* skip this pcb, we have already processed it */
+      pcb = pcb->next;
+      continue;
+    }
+    pcb->last_timer = tcp_timer_ctr;
+
+    pcb_remove = 0;
+    pcb_reset = 0;
+
+    if (pcb->state == SYN_SENT && pcb->nrtx == TCP_SYNMAXRTX) {
+      ++pcb_remove;
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max SYN retries reached\n"));
+    }
+    else if (pcb->nrtx == TCP_MAXRTX) {
+      ++pcb_remove;
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max DATA retries reached\n"));
+    } else {
+      if (pcb->persist_backoff > 0) {
+        /* If snd_wnd is zero, use persist timer to send 1 byte probes
+         * instead of using the standard retransmission mechanism. */
+        pcb->persist_cnt++;
+        if (pcb->persist_cnt >= tcp_persist_backoff[pcb->persist_backoff-1]) {
+          pcb->persist_cnt = 0;
+          if (pcb->persist_backoff < sizeof(tcp_persist_backoff)) {
+            pcb->persist_backoff++;
+          }
+          tcp_zero_window_probe(pcb);
+        }
+      } else {
+        /* Increase the retransmission timer if it is running */
+        if(pcb->rtime >= 0) {
+          ++pcb->rtime;
+        }
+
+        if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) {
+          /* Time for a retransmission. */
+          LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_slowtmr: rtime %"S16_F
+                                      " pcb->rto %"S16_F"\n",
+                                      pcb->rtime, pcb->rto));
+
+          /* Double retransmission time-out unless we are trying to
+           * connect to somebody (i.e., we are in SYN_SENT). */
+          if (pcb->state != SYN_SENT) {
+            pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx];
+          }
+
+          /* Reset the retransmission timer. */
+          pcb->rtime = 0;
+
+          /* Reduce congestion window and ssthresh. */
+          eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);
+          pcb->ssthresh = eff_wnd >> 1;
+          if (pcb->ssthresh < (pcb->mss << 1)) {
+            pcb->ssthresh = (pcb->mss << 1);
+          }
+          pcb->cwnd = pcb->mss;
+          LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %"U16_F
+                                       " ssthresh %"U16_F"\n",
+                                       pcb->cwnd, pcb->ssthresh));
+ 
+          /* The following needs to be called AFTER cwnd is set to one
+             mss - STJ */
+          tcp_rexmit_rto(pcb);
+        }
+      }
+    }
+    /* Check if this PCB has stayed too long in FIN-WAIT-2 */
+    if (pcb->state == FIN_WAIT_2) {
+      /* If this PCB is in FIN_WAIT_2 because of SHUT_WR don't let it time out. */
+      if (pcb->flags & TF_RXCLOSED) {
+        /* PCB was fully closed (either through close() or SHUT_RDWR):
+           normal FIN-WAIT timeout handling. */
+        if ((u32_t)(tcp_ticks - pcb->tmr) >
+            TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {
+          ++pcb_remove;
+          LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in FIN-WAIT-2\n"));
+        }
+      }
+    }
+
+    /* Check if KEEPALIVE should be sent */
+    if(ip_get_option(pcb, SOF_KEEPALIVE) &&
+       ((pcb->state == ESTABLISHED) ||
+        (pcb->state == CLOSE_WAIT))) {
+      if((u32_t)(tcp_ticks - pcb->tmr) >
+         (pcb->keep_idle + TCP_KEEP_DUR(pcb)) / TCP_SLOW_INTERVAL)
+      {
+        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: KEEPALIVE timeout. Aborting connection to "));
+        ipX_addr_debug_print(PCB_ISIPV6(pcb), TCP_DEBUG, &pcb->remote_ip);
+        LWIP_DEBUGF(TCP_DEBUG, ("\n"));
+        
+        ++pcb_remove;
+        ++pcb_reset;
+      }
+      else if((u32_t)(tcp_ticks - pcb->tmr) > 
+              (pcb->keep_idle + pcb->keep_cnt_sent * TCP_KEEP_INTVL(pcb))
+              / TCP_SLOW_INTERVAL)
+      {
+        tcp_keepalive(pcb);
+        pcb->keep_cnt_sent++;
+      }
+    }
+
+    /* If this PCB has queued out of sequence data, but has been
+       inactive for too long, will drop the data (it will eventually
+       be retransmitted). */
+#if TCP_QUEUE_OOSEQ
+    if (pcb->ooseq != NULL &&
+        (u32_t)tcp_ticks - pcb->tmr >= pcb->rto * TCP_OOSEQ_TIMEOUT) {
+      tcp_segs_free(pcb->ooseq);
+      pcb->ooseq = NULL;
+      LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: dropping OOSEQ queued data\n"));
+    }
+#endif /* TCP_QUEUE_OOSEQ */
+
+    /* Check if this PCB has stayed too long in SYN-RCVD */
+    if (pcb->state == SYN_RCVD) {
+      if ((u32_t)(tcp_ticks - pcb->tmr) >
+          TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {
+        ++pcb_remove;
+        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in SYN-RCVD\n"));
+      }
+    }
+
+    /* Check if this PCB has stayed too long in LAST-ACK */
+    if (pcb->state == LAST_ACK) {
+      if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
+        ++pcb_remove;
+        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in LAST-ACK\n"));
+      }
+    }
+
+    /* If the PCB should be removed, do it. */
+    if (pcb_remove) {
+      struct tcp_pcb *pcb2;
+      tcp_err_fn err_fn;
+      void *err_arg;
+      tcp_pcb_purge(pcb);
+      /* Remove PCB from tcp_active_pcbs list. */
+      if (prev != NULL) {
+        LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_active_pcbs", pcb != tcp_active_pcbs);
+        prev->next = pcb->next;
+      } else {
+        /* This PCB was the first. */
+        LWIP_ASSERT("tcp_slowtmr: first pcb == tcp_active_pcbs", tcp_active_pcbs == pcb);
+        tcp_active_pcbs = pcb->next;
+      }
+
+      if (pcb_reset) {
+        tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
+                 pcb->local_port, pcb->remote_port, PCB_ISIPV6(pcb));
+      }
+
+      err_fn = pcb->errf;
+      err_arg = pcb->callback_arg;
+      pcb2 = pcb;
+      pcb = pcb->next;
+      memp_free(MEMP_TCP_PCB, pcb2);
+
+      tcp_active_pcbs_changed = 0;
+      TCP_EVENT_ERR(err_fn, err_arg, ERR_ABRT);
+      if (tcp_active_pcbs_changed) {
+        goto tcp_slowtmr_start;
+      }
+    } else {
+      /* get the 'next' element now and work with 'prev' below (in case of abort) */
+      prev = pcb;
+      pcb = pcb->next;
+
+      /* We check if we should poll the connection. */
+      ++prev->polltmr;
+      if (prev->polltmr >= prev->pollinterval) {
+        prev->polltmr = 0;
+        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: polling application\n"));
+        tcp_active_pcbs_changed = 0;
+        TCP_EVENT_POLL(prev, err);
+        if (tcp_active_pcbs_changed) {
+          goto tcp_slowtmr_start;
+        }
+        /* if err == ERR_ABRT, 'prev' is already deallocated */
+        if (err == ERR_OK) {
+          tcp_output(prev);
+        }
+      }
+    }
+  }
+
+  
+  /* Steps through all of the TIME-WAIT PCBs. */
+  prev = NULL;
+  pcb = tcp_tw_pcbs;
+  while (pcb != NULL) {
+    LWIP_ASSERT("tcp_slowtmr: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);
+    pcb_remove = 0;
+
+    /* Check if this PCB has stayed long enough in TIME-WAIT */
+    if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
+      ++pcb_remove;
+    }
+    
+
+
+    /* If the PCB should be removed, do it. */
+    if (pcb_remove) {
+      struct tcp_pcb *pcb2;
+      tcp_pcb_purge(pcb);
+      /* Remove PCB from tcp_tw_pcbs list. */
+      if (prev != NULL) {
+        LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_tw_pcbs", pcb != tcp_tw_pcbs);
+        prev->next = pcb->next;
+      } else {
+        /* This PCB was the first. */
+        LWIP_ASSERT("tcp_slowtmr: first pcb == tcp_tw_pcbs", tcp_tw_pcbs == pcb);
+        tcp_tw_pcbs = pcb->next;
+      }
+      pcb2 = pcb;
+      pcb = pcb->next;
+      memp_free(MEMP_TCP_PCB, pcb2);
+    } else {
+      prev = pcb;
+      pcb = pcb->next;
+    }
+  }
+}
+
+/**
+ * Is called every TCP_FAST_INTERVAL (250 ms) and process data previously
+ * "refused" by upper layer (application) and sends delayed ACKs.
+ *
+ * Automatically called from tcp_tmr().
+ */
+void
+tcp_fasttmr(void)
+{
+  struct tcp_pcb *pcb;
+
+  ++tcp_timer_ctr;
+
+tcp_fasttmr_start:
+  pcb = tcp_active_pcbs;
+
+  while(pcb != NULL) {
+    if (pcb->last_timer != tcp_timer_ctr) {
+      struct tcp_pcb *next;
+      pcb->last_timer = tcp_timer_ctr;
+      /* send delayed ACKs */
+      if (pcb->flags & TF_ACK_DELAY) {
+        LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: delayed ACK\n"));
+        tcp_ack_now(pcb);
+        tcp_output(pcb);
+        pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
+      }
+
+      next = pcb->next;
+
+      /* If there is data which was previously "refused" by upper layer */
+      if (pcb->refused_data != NULL) {
+        tcp_active_pcbs_changed = 0;
+        tcp_process_refused_data(pcb);
+        if (tcp_active_pcbs_changed) {
+          /* application callback has changed the pcb list: restart the loop */
+          goto tcp_fasttmr_start;
+        }
+      }
+      pcb = next;
+    }
+  }
+}
+
+/** Pass pcb->refused_data to the recv callback */
+err_t
+tcp_process_refused_data(struct tcp_pcb *pcb)
+{
+  err_t err;
+  u8_t refused_flags = pcb->refused_data->flags;
+  /* set pcb->refused_data to NULL in case the callback frees it and then
+     closes the pcb */
+  struct pbuf *refused_data = pcb->refused_data;
+  pcb->refused_data = NULL;
+  /* Notify again application with data previously received. */
+  LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: notify kept packet\n"));
+  TCP_EVENT_RECV(pcb, refused_data, ERR_OK, err);
+  if (err == ERR_OK) {
+    /* did refused_data include a FIN? */
+    if (refused_flags & PBUF_FLAG_TCP_FIN) {
+      /* correct rcv_wnd as the application won't call tcp_recved()
+         for the FIN's seqno */
+      if (pcb->rcv_wnd != TCP_WND) {
+        pcb->rcv_wnd++;
+      }
+      TCP_EVENT_CLOSED(pcb, err);
+      if (err == ERR_ABRT) {
+        return ERR_ABRT;
+      }
+    }
+  } else if (err == ERR_ABRT) {
+    /* if err == ERR_ABRT, 'pcb' is already deallocated */
+    /* Drop incoming packets because pcb is "full" (only if the incoming
+       segment contains data). */
+    LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: drop incoming packets, because pcb is \"full\"\n"));
+    return ERR_ABRT;
+  } else {
+    /* data is still refused, pbuf is still valid (go on for ACK-only packets) */
+    pcb->refused_data = refused_data;
+  }
+  return ERR_OK;
+}
+
+/**
+ * Deallocates a list of TCP segments (tcp_seg structures).
+ *
+ * @param seg tcp_seg list of TCP segments to free
+ */
+void
+tcp_segs_free(struct tcp_seg *seg)
+{
+  while (seg != NULL) {
+    struct tcp_seg *next = seg->next;
+    tcp_seg_free(seg);
+    seg = next;
+  }
+}
+
+/**
+ * Frees a TCP segment (tcp_seg structure).
+ *
+ * @param seg single tcp_seg to free
+ */
+void
+tcp_seg_free(struct tcp_seg *seg)
+{
+  if (seg != NULL) {
+    if (seg->p != NULL) {
+      pbuf_free(seg->p);
+#if TCP_DEBUG
+      seg->p = NULL;
+#endif /* TCP_DEBUG */
+    }
+    memp_free(MEMP_TCP_SEG, seg);
+  }
+}
+
+/**
+ * Sets the priority of a connection.
+ *
+ * @param pcb the tcp_pcb to manipulate
+ * @param prio new priority
+ */
+void
+tcp_setprio(struct tcp_pcb *pcb, u8_t prio)
+{
+  pcb->prio = prio;
+}
+
+#if TCP_QUEUE_OOSEQ
+/**
+ * Returns a copy of the given TCP segment.
+ * The pbuf and data are not copied, only the pointers
+ *
+ * @param seg the old tcp_seg
+ * @return a copy of seg
+ */ 
+struct tcp_seg *
+tcp_seg_copy(struct tcp_seg *seg)
+{
+  struct tcp_seg *cseg;
+
+  cseg = (struct tcp_seg *)memp_malloc(MEMP_TCP_SEG);
+  if (cseg == NULL) {
+    return NULL;
+  }
+  SMEMCPY((u8_t *)cseg, (const u8_t *)seg, sizeof(struct tcp_seg)); 
+  pbuf_ref(cseg->p);
+  return cseg;
+}
+#endif /* TCP_QUEUE_OOSEQ */
+
+#if LWIP_CALLBACK_API
+/**
+ * Default receive callback that is called if the user didn't register
+ * a recv callback for the pcb.
+ */
+err_t
+tcp_recv_null(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+  LWIP_UNUSED_ARG(arg);
+  if (p != NULL) {
+    tcp_recved(pcb, p->tot_len);
+    pbuf_free(p);
+  } else if (err == ERR_OK) {
+    return tcp_close(pcb);
+  }
+  return ERR_OK;
+}
+#endif /* LWIP_CALLBACK_API */
+
+/**
+ * Kills the oldest active connection that has the same or lower priority than
+ * 'prio'.
+ *
+ * @param prio minimum priority
+ */
+static void
+tcp_kill_prio(u8_t prio)
+{
+  struct tcp_pcb *pcb, *inactive;
+  u32_t inactivity;
+  u8_t mprio;
+
+
+  mprio = TCP_PRIO_MAX;
+  
+  /* We kill the oldest active connection that has lower priority than prio. */
+  inactivity = 0;
+  inactive = NULL;
+  for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
+    if (pcb->prio <= prio &&
+       pcb->prio <= mprio &&
+       (u32_t)(tcp_ticks - pcb->tmr) >= inactivity) {
+      inactivity = tcp_ticks - pcb->tmr;
+      inactive = pcb;
+      mprio = pcb->prio;
+    }
+  }
+  if (inactive != NULL) {
+    LWIP_DEBUGF(TCP_DEBUG, ("tcp_kill_prio: killing oldest PCB %p (%"S32_F")\n",
+           (void *)inactive, inactivity));
+    tcp_abort(inactive);
+  }
+}
+
+/**
+ * Kills the oldest connection that is in TIME_WAIT state.
+ * Called from tcp_alloc() if no more connections are available.
+ */
+static void
+tcp_kill_timewait(void)
+{
+  struct tcp_pcb *pcb, *inactive;
+  u32_t inactivity;
+
+  inactivity = 0;
+  inactive = NULL;
+  /* Go through the list of TIME_WAIT pcbs and get the oldest pcb. */
+  for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
+    if ((u32_t)(tcp_ticks - pcb->tmr) >= inactivity) {
+      inactivity = tcp_ticks - pcb->tmr;
+      inactive = pcb;
+    }
+  }
+  if (inactive != NULL) {
+    LWIP_DEBUGF(TCP_DEBUG, ("tcp_kill_timewait: killing oldest TIME-WAIT PCB %p (%"S32_F")\n",
+           (void *)inactive, inactivity));
+    tcp_abort(inactive);
+  }
+}
+
+/**
+ * Allocate a new tcp_pcb structure.
+ *
+ * @param prio priority for the new pcb
+ * @return a new tcp_pcb that initially is in state CLOSED
+ */
+struct tcp_pcb *
+tcp_alloc(u8_t prio)
+{
+  struct tcp_pcb *pcb;
+  u32_t iss;
+  
+  pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
+  if (pcb == NULL) {
+    /* Try killing oldest connection in TIME-WAIT. */
+    LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing off oldest TIME-WAIT connection\n"));
+    tcp_kill_timewait();
+    /* Try to allocate a tcp_pcb again. */
+    pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
+    if (pcb == NULL) {
+      /* Try killing active connections with lower priority than the new one. */
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing connection with prio lower than %d\n", prio));
+      tcp_kill_prio(prio);
+      /* Try to allocate a tcp_pcb again. */
+      pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
+      if (pcb != NULL) {
+        /* adjust err stats: memp_malloc failed twice before */
+        MEMP_STATS_DEC(err, MEMP_TCP_PCB);
+      }
+    }
+    if (pcb != NULL) {
+      /* adjust err stats: timewait PCB was freed above */
+      MEMP_STATS_DEC(err, MEMP_TCP_PCB);
+    }
+  }
+  if (pcb != NULL) {
+    memset(pcb, 0, sizeof(struct tcp_pcb));
+    pcb->prio = prio;
+    pcb->snd_buf = TCP_SND_BUF;
+    pcb->snd_queuelen = 0;
+    pcb->rcv_wnd = TCP_WND;
+    pcb->rcv_ann_wnd = TCP_WND;
+    pcb->tos = 0;
+    pcb->ttl = TCP_TTL;
+    /* As initial send MSS, we use TCP_MSS but limit it to 536.
+       The send MSS is updated when an MSS option is received. */
+    pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS;
+    pcb->rto = 3000 / TCP_SLOW_INTERVAL;
+    pcb->sa = 0;
+    pcb->sv = 3000 / TCP_SLOW_INTERVAL;
+    pcb->rtime = -1;
+    pcb->cwnd = 1;
+    iss = tcp_next_iss();
+    pcb->snd_wl2 = iss;
+    pcb->snd_nxt = iss;
+    pcb->lastack = iss;
+    pcb->snd_lbb = iss;   
+    pcb->tmr = tcp_ticks;
+    pcb->last_timer = tcp_timer_ctr;
+
+    pcb->polltmr = 0;
+
+#if LWIP_CALLBACK_API
+    pcb->recv = tcp_recv_null;
+#endif /* LWIP_CALLBACK_API */  
+    
+    /* Init KEEPALIVE timer */
+    pcb->keep_idle  = TCP_KEEPIDLE_DEFAULT;
+    
+#if LWIP_TCP_KEEPALIVE
+    pcb->keep_intvl = TCP_KEEPINTVL_DEFAULT;
+    pcb->keep_cnt   = TCP_KEEPCNT_DEFAULT;
+#endif /* LWIP_TCP_KEEPALIVE */
+
+    pcb->keep_cnt_sent = 0;
+  }
+  return pcb;
+}
+
+/**
+ * Creates a new TCP protocol control block but doesn't place it on
+ * any of the TCP PCB lists.
+ * The pcb is not put on any list until binding using tcp_bind().
+ *
+ * @internal: Maybe there should be a idle TCP PCB list where these
+ * PCBs are put on. Port reservation using tcp_bind() is implemented but
+ * allocated pcbs that are not bound can't be killed automatically if wanting
+ * to allocate a pcb with higher prio (@see tcp_kill_prio())
+ *
+ * @return a new tcp_pcb that initially is in state CLOSED
+ */
+struct tcp_pcb *
+tcp_new(void)
+{
+  return tcp_alloc(TCP_PRIO_NORMAL);
+}
+
+#if LWIP_IPV6
+/**
+ * Creates a new TCP-over-IPv6 protocol control block but doesn't
+ * place it on any of the TCP PCB lists.
+ * The pcb is not put on any list until binding using tcp_bind().
+ *
+ * @return a new tcp_pcb that initially is in state CLOSED
+ */
+struct tcp_pcb *
+tcp_new_ip6(void)
+{
+  struct tcp_pcb * pcb;
+  pcb = tcp_alloc(TCP_PRIO_NORMAL);
+  ip_set_v6(pcb, 1);
+  return pcb;
+}
+#endif /* LWIP_IPV6 */
+
+/**
+ * Used to specify the argument that should be passed callback
+ * functions.
+ *
+ * @param pcb tcp_pcb to set the callback argument
+ * @param arg void pointer argument to pass to callback functions
+ */ 
+void
+tcp_arg(struct tcp_pcb *pcb, void *arg)
+{
+  /* This function is allowed to be called for both listen pcbs and
+     connection pcbs. */
+  pcb->callback_arg = arg;
+}
+#if LWIP_CALLBACK_API
+
+/**
+ * Used to specify the function that should be called when a TCP
+ * connection receives data.
+ *
+ * @param pcb tcp_pcb to set the recv callback
+ * @param recv callback function to call for this pcb when data is received
+ */ 
+void
+tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn recv)
+{
+  LWIP_ASSERT("invalid socket state for recv callback", pcb->state != LISTEN);
+  pcb->recv = recv;
+}
+
+/**
+ * Used to specify the function that should be called when TCP data
+ * has been successfully delivered to the remote host.
+ *
+ * @param pcb tcp_pcb to set the sent callback
+ * @param sent callback function to call for this pcb when data is successfully sent
+ */ 
+void
+tcp_sent(struct tcp_pcb *pcb, tcp_sent_fn sent)
+{
+  LWIP_ASSERT("invalid socket state for sent callback", pcb->state != LISTEN);
+  pcb->sent = sent;
+}
+
+/**
+ * Used to specify the function that should be called when a fatal error
+ * has occured on the connection.
+ *
+ * @param pcb tcp_pcb to set the err callback
+ * @param err callback function to call for this pcb when a fatal error
+ *        has occured on the connection
+ */ 
+void
+tcp_err(struct tcp_pcb *pcb, tcp_err_fn err)
+{
+  LWIP_ASSERT("invalid socket state for err callback", pcb->state != LISTEN);
+  pcb->errf = err;
+}
+
+/**
+ * Used for specifying the function that should be called when a
+ * LISTENing connection has been connected to another host.
+ *
+ * @param pcb tcp_pcb to set the accept callback
+ * @param accept callback function to call for this pcb when LISTENing
+ *        connection has been connected to another host
+ */ 
+void
+tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
+{
+  /* This function is allowed to be called for both listen pcbs and
+     connection pcbs. */
+  pcb->accept = accept;
+}
+#endif /* LWIP_CALLBACK_API */
+
+
+/**
+ * Used to specify the function that should be called periodically
+ * from TCP. The interval is specified in terms of the TCP coarse
+ * timer interval, which is called twice a second.
+ *
+ */ 
+void
+tcp_poll(struct tcp_pcb *pcb, tcp_poll_fn poll, u8_t interval)
+{
+  LWIP_ASSERT("invalid socket state for poll", pcb->state != LISTEN);
+#if LWIP_CALLBACK_API
+  pcb->poll = poll;
+#else /* LWIP_CALLBACK_API */  
+  LWIP_UNUSED_ARG(poll);
+#endif /* LWIP_CALLBACK_API */  
+  pcb->pollinterval = interval;
+}
+
+/**
+ * Purges a TCP PCB. Removes any buffered data and frees the buffer memory
+ * (pcb->ooseq, pcb->unsent and pcb->unacked are freed).
+ *
+ * @param pcb tcp_pcb to purge. The pcb itself is not deallocated!
+ */
+void
+tcp_pcb_purge(struct tcp_pcb *pcb)
+{
+  if (pcb->state != CLOSED &&
+     pcb->state != TIME_WAIT &&
+     pcb->state != LISTEN) {
+
+    LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge\n"));
+
+#if TCP_LISTEN_BACKLOG
+    if (pcb->state == SYN_RCVD) {
+      /* Need to find the corresponding listen_pcb and decrease its accepts_pending */
+      struct tcp_pcb_listen *lpcb;
+      LWIP_ASSERT("tcp_pcb_purge: pcb->state == SYN_RCVD but tcp_listen_pcbs is NULL",
+        tcp_listen_pcbs.listen_pcbs != NULL);
+      for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
+        if ((!lpcb->bound_to_netif && !pcb->bound_to_netif &&
+             (lpcb->local_port == pcb->local_port) &&
+             IP_PCB_IPVER_EQ(pcb, lpcb) &&
+             (ipX_addr_isany(PCB_ISIPV6(lpcb), &lpcb->local_ip) ||
+             ipX_addr_cmp(PCB_ISIPV6(lpcb), &pcb->local_ip, &lpcb->local_ip))) ||
+            (lpcb->bound_to_netif && pcb->bound_to_netif &&
+             !memcmp(lpcb->local_netif, pcb->local_netif, sizeof(pcb->local_netif)))) {
+            /* port and address of the listen pcb match the timed-out pcb */
+            LWIP_ASSERT("tcp_pcb_purge: listen pcb does not have accepts pending",
+              lpcb->accepts_pending > 0);
+            lpcb->accepts_pending--;
+            break;
+        }
+      }
+    }
+#endif /* TCP_LISTEN_BACKLOG */
+
+
+    if (pcb->refused_data != NULL) {
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge: data left on ->refused_data\n"));
+      pbuf_free(pcb->refused_data);
+      pcb->refused_data = NULL;
+    }
+    if (pcb->unsent != NULL) {
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge: not all data sent\n"));
+    }
+    if (pcb->unacked != NULL) {
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge: data left on ->unacked\n"));
+    }
+#if TCP_QUEUE_OOSEQ
+    if (pcb->ooseq != NULL) {
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_pcb_purge: data left on ->ooseq\n"));
+    }
+    tcp_segs_free(pcb->ooseq);
+    pcb->ooseq = NULL;
+#endif /* TCP_QUEUE_OOSEQ */
+
+    /* Stop the retransmission timer as it will expect data on unacked
+       queue if it fires */
+    pcb->rtime = -1;
+
+    tcp_segs_free(pcb->unsent);
+    tcp_segs_free(pcb->unacked);
+    pcb->unacked = pcb->unsent = NULL;
+#if TCP_OVERSIZE
+    pcb->unsent_oversize = 0;
+#endif /* TCP_OVERSIZE */
+  }
+}
+
+/**
+ * Purges the PCB and removes it from a PCB list. Any delayed ACKs are sent first.
+ *
+ * @param pcblist PCB list to purge.
+ * @param pcb tcp_pcb to purge. The pcb itself is NOT deallocated!
+ */
+void
+tcp_pcb_remove(struct tcp_pcb **pcblist, struct tcp_pcb *pcb)
+{
+  TCP_RMV(pcblist, pcb);
+
+  tcp_pcb_purge(pcb);
+  
+  /* if there is an outstanding delayed ACKs, send it */
+  if (pcb->state != TIME_WAIT &&
+     pcb->state != LISTEN &&
+     pcb->flags & TF_ACK_DELAY) {
+    pcb->flags |= TF_ACK_NOW;
+    tcp_output(pcb);
+  }
+
+  if (pcb->state != LISTEN) {
+    LWIP_ASSERT("unsent segments leaking", pcb->unsent == NULL);
+    LWIP_ASSERT("unacked segments leaking", pcb->unacked == NULL);
+#if TCP_QUEUE_OOSEQ
+    LWIP_ASSERT("ooseq segments leaking", pcb->ooseq == NULL);
+#endif /* TCP_QUEUE_OOSEQ */
+  }
+
+  pcb->state = CLOSED;
+
+  LWIP_ASSERT("tcp_pcb_remove: tcp_pcbs_sane()", tcp_pcbs_sane());
+}
+
+/**
+ * Calculates a new initial sequence number for new connections.
+ *
+ * @return u32_t pseudo random sequence number
+ */
+u32_t
+tcp_next_iss(void)
+{
+  static u32_t iss = 6510;
+  
+  iss += tcp_ticks;       /* XXX */
+  return iss;
+}
+
+#if TCP_CALCULATE_EFF_SEND_MSS
+/**
+ * Calcluates the effective send mss that can be used for a specific IP address
+ * by using ip_route to determin the netif used to send to the address and
+ * calculating the minimum of TCP_MSS and that netif's mtu (if set).
+ */
+u16_t
+tcp_eff_send_mss_impl(u16_t sendmss, ipX_addr_t *dest
+#if LWIP_IPV6
+                     , ipX_addr_t *src, u8_t isipv6
+#endif /* LWIP_IPV6 */
+                     )
+{
+  u16_t mss_s;
+  struct netif *outif;
+  s16_t mtu;
+
+  outif = ipX_route(isipv6, src, dest);
+#if LWIP_IPV6
+  if (isipv6) {
+    /* First look in destination cache, to see if there is a Path MTU. */
+    mtu = nd6_get_destination_mtu(ipX_2_ip6(dest), outif);
+  } else
+#endif /* LWIP_IPV6 */
+  {
+    if (outif == NULL) {
+      return sendmss;
+    }
+    mtu = outif->mtu;
+  }
+
+  if (mtu != 0) {
+    mss_s = mtu - IP_HLEN - TCP_HLEN;
+#if LWIP_IPV6
+    /* for IPv6, substract the difference in header size */
+    mss_s -= (IP6_HLEN - IP_HLEN);
+#endif /* LWIP_IPV6 */
+    /* RFC 1122, chap 4.2.2.6:
+     * Eff.snd.MSS = min(SendMSS+20, MMS_S) - TCPhdrsize - IPoptionsize
+     * We correct for TCP options in tcp_write(), and don't support IP options.
+     */
+    sendmss = LWIP_MIN(sendmss, mss_s);
+  }
+  return sendmss;
+}
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+
+const char*
+tcp_debug_state_str(enum tcp_state s)
+{
+  return tcp_state_str[s];
+}
+
+#if TCP_DEBUG || TCP_INPUT_DEBUG || TCP_OUTPUT_DEBUG
+/**
+ * Print a tcp header for debugging purposes.
+ *
+ * @param tcphdr pointer to a struct tcp_hdr
+ */
+void
+tcp_debug_print(struct tcp_hdr *tcphdr)
+{
+  LWIP_DEBUGF(TCP_DEBUG, ("TCP header:\n"));
+  LWIP_DEBUGF(TCP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(TCP_DEBUG, ("|    %5"U16_F"      |    %5"U16_F"      | (src port, dest port)\n",
+         ntohs(tcphdr->src), ntohs(tcphdr->dest)));
+  LWIP_DEBUGF(TCP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(TCP_DEBUG, ("|           %010"U32_F"          | (seq no)\n",
+          ntohl(tcphdr->seqno)));
+  LWIP_DEBUGF(TCP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(TCP_DEBUG, ("|           %010"U32_F"          | (ack no)\n",
+         ntohl(tcphdr->ackno)));
+  LWIP_DEBUGF(TCP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(TCP_DEBUG, ("| %2"U16_F" |   |%"U16_F"%"U16_F"%"U16_F"%"U16_F"%"U16_F"%"U16_F"|     %5"U16_F"     | (hdrlen, flags (",
+       TCPH_HDRLEN(tcphdr),
+         TCPH_FLAGS(tcphdr) >> 5 & 1,
+         TCPH_FLAGS(tcphdr) >> 4 & 1,
+         TCPH_FLAGS(tcphdr) >> 3 & 1,
+         TCPH_FLAGS(tcphdr) >> 2 & 1,
+         TCPH_FLAGS(tcphdr) >> 1 & 1,
+         TCPH_FLAGS(tcphdr) & 1,
+         ntohs(tcphdr->wnd)));
+  tcp_debug_print_flags(TCPH_FLAGS(tcphdr));
+  LWIP_DEBUGF(TCP_DEBUG, ("), win)\n"));
+  LWIP_DEBUGF(TCP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(TCP_DEBUG, ("|    0x%04"X16_F"     |     %5"U16_F"     | (chksum, urgp)\n",
+         ntohs(tcphdr->chksum), ntohs(tcphdr->urgp)));
+  LWIP_DEBUGF(TCP_DEBUG, ("+-------------------------------+\n"));
+}
+
+/**
+ * Print a tcp state for debugging purposes.
+ *
+ * @param s enum tcp_state to print
+ */
+void
+tcp_debug_print_state(enum tcp_state s)
+{
+  LWIP_DEBUGF(TCP_DEBUG, ("State: %s\n", tcp_state_str[s]));
+}
+
+/**
+ * Print tcp flags for debugging purposes.
+ *
+ * @param flags tcp flags, all active flags are printed
+ */
+void
+tcp_debug_print_flags(u8_t flags)
+{
+  if (flags & TCP_FIN) {
+    LWIP_DEBUGF(TCP_DEBUG, ("FIN "));
+  }
+  if (flags & TCP_SYN) {
+    LWIP_DEBUGF(TCP_DEBUG, ("SYN "));
+  }
+  if (flags & TCP_RST) {
+    LWIP_DEBUGF(TCP_DEBUG, ("RST "));
+  }
+  if (flags & TCP_PSH) {
+    LWIP_DEBUGF(TCP_DEBUG, ("PSH "));
+  }
+  if (flags & TCP_ACK) {
+    LWIP_DEBUGF(TCP_DEBUG, ("ACK "));
+  }
+  if (flags & TCP_URG) {
+    LWIP_DEBUGF(TCP_DEBUG, ("URG "));
+  }
+  if (flags & TCP_ECE) {
+    LWIP_DEBUGF(TCP_DEBUG, ("ECE "));
+  }
+  if (flags & TCP_CWR) {
+    LWIP_DEBUGF(TCP_DEBUG, ("CWR "));
+  }
+  LWIP_DEBUGF(TCP_DEBUG, ("\n"));
+}
+
+/**
+ * Print all tcp_pcbs in every list for debugging purposes.
+ */
+void
+tcp_debug_print_pcbs(void)
+{
+  struct tcp_pcb *pcb;
+  LWIP_DEBUGF(TCP_DEBUG, ("Active PCB states:\n"));
+  for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
+    LWIP_DEBUGF(TCP_DEBUG, ("Local port %"U16_F", foreign port %"U16_F" snd_nxt %"U32_F" rcv_nxt %"U32_F" ",
+                       pcb->local_port, pcb->remote_port,
+                       pcb->snd_nxt, pcb->rcv_nxt));
+    tcp_debug_print_state(pcb->state);
+  }    
+  LWIP_DEBUGF(TCP_DEBUG, ("Listen PCB states:\n"));
+  for(pcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs; pcb != NULL; pcb = pcb->next) {
+    LWIP_DEBUGF(TCP_DEBUG, ("Local port %"U16_F", foreign port %"U16_F" snd_nxt %"U32_F" rcv_nxt %"U32_F" ",
+                       pcb->local_port, pcb->remote_port,
+                       pcb->snd_nxt, pcb->rcv_nxt));
+    tcp_debug_print_state(pcb->state);
+  }    
+  LWIP_DEBUGF(TCP_DEBUG, ("TIME-WAIT PCB states:\n"));
+  for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
+    LWIP_DEBUGF(TCP_DEBUG, ("Local port %"U16_F", foreign port %"U16_F" snd_nxt %"U32_F" rcv_nxt %"U32_F" ",
+                       pcb->local_port, pcb->remote_port,
+                       pcb->snd_nxt, pcb->rcv_nxt));
+    tcp_debug_print_state(pcb->state);
+  }    
+}
+
+/**
+ * Check state consistency of the tcp_pcb lists.
+ */
+s16_t
+tcp_pcbs_sane(void)
+{
+  struct tcp_pcb *pcb;
+  for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
+    LWIP_ASSERT("tcp_pcbs_sane: active pcb->state != CLOSED", pcb->state != CLOSED);
+    LWIP_ASSERT("tcp_pcbs_sane: active pcb->state != LISTEN", pcb->state != LISTEN);
+    LWIP_ASSERT("tcp_pcbs_sane: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT);
+  }
+  for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
+    LWIP_ASSERT("tcp_pcbs_sane: tw pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);
+  }
+  return 1;
+}
+#endif /* TCP_DEBUG */
+
+#endif /* LWIP_TCP */
diff --git a/external/badvpn_dns/lwip/src/core/tcp_in.c b/external/badvpn_dns/lwip/src/core/tcp_in.c
new file mode 100644
index 0000000..92ee2b2
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/tcp_in.c
@@ -0,0 +1,1666 @@
+/**
+ * @file
+ * Transmission Control Protocol, incoming traffic
+ *
+ * The input processing functions of the TCP layer.
+ *
+ * These functions are generally called in the order (ip_input() ->)
+ * tcp_input() -> * tcp_process() -> tcp_receive() (-> application).
+ * 
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/tcp_impl.h"
+#include "lwip/def.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/stats.h"
+#include "lwip/snmp.h"
+#include "arch/perf.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/inet_chksum.h"
+#if LWIP_ND6_TCP_REACHABILITY_HINTS
+#include "lwip/nd6.h"
+#endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */
+
+/* These variables are global to all functions involved in the input
+   processing of TCP segments. They are set by the tcp_input()
+   function. */
+static struct tcp_seg inseg;
+static struct tcp_hdr *tcphdr;
+static u32_t seqno, ackno;
+static u8_t flags;
+static u16_t tcplen;
+
+static u8_t recv_flags;
+static struct pbuf *recv_data;
+
+struct tcp_pcb *tcp_input_pcb;
+
+/* Forward declarations. */
+static err_t tcp_process(struct tcp_pcb *pcb);
+static void tcp_receive(struct tcp_pcb *pcb);
+static void tcp_parseopt(struct tcp_pcb *pcb);
+
+static err_t tcp_listen_input(struct tcp_pcb_listen *pcb);
+static err_t tcp_timewait_input(struct tcp_pcb *pcb);
+
+/**
+ * The initial input processing of TCP. It verifies the TCP header, demultiplexes
+ * the segment between the PCBs and passes it on to tcp_process(), which implements
+ * the TCP finite state machine. This function is called by the IP layer (in
+ * ip_input()).
+ *
+ * @param p received TCP segment to process (p->payload pointing to the TCP header)
+ * @param inp network interface on which this segment was received
+ */
+void
+tcp_input(struct pbuf *p, struct netif *inp)
+{
+  struct tcp_pcb *pcb, *prev;
+  struct tcp_pcb_listen *lpcb;
+#if SO_REUSE
+  struct tcp_pcb *lpcb_prev = NULL;
+  struct tcp_pcb_listen *lpcb_any = NULL;
+#endif /* SO_REUSE */
+  u8_t hdrlen;
+  err_t err;
+#if CHECKSUM_CHECK_TCP
+  u16_t chksum;
+#endif /* CHECKSUM_CHECK_TCP */
+
+  PERF_START;
+
+  TCP_STATS_INC(tcp.recv);
+  snmp_inc_tcpinsegs();
+
+  tcphdr = (struct tcp_hdr *)p->payload;
+
+#if TCP_INPUT_DEBUG
+  tcp_debug_print(tcphdr);
+#endif
+
+  /* Check that TCP header fits in payload */
+  if (p->len < sizeof(struct tcp_hdr)) {
+    /* drop short packets */
+    LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: short packet (%"U16_F" bytes) discarded\n", p->tot_len));
+    TCP_STATS_INC(tcp.lenerr);
+    goto dropped;
+  }
+
+  /* Don't even process incoming broadcasts/multicasts. */
+  if ((!ip_current_is_v6() && ip_addr_isbroadcast(ip_current_dest_addr(), inp)) ||
+       ipX_addr_ismulticast(ip_current_is_v6(), ipX_current_dest_addr())) {
+    TCP_STATS_INC(tcp.proterr);
+    goto dropped;
+  }
+
+#if CHECKSUM_CHECK_TCP
+  /* Verify TCP checksum. */
+  chksum = ipX_chksum_pseudo(ip_current_is_v6(), p, IP_PROTO_TCP, p->tot_len,
+                             ipX_current_src_addr(), ipX_current_dest_addr());
+  if (chksum != 0) {
+      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packet discarded due to failing checksum 0x%04"X16_F"\n",
+        chksum));
+    tcp_debug_print(tcphdr);
+    TCP_STATS_INC(tcp.chkerr);
+    goto dropped;
+  }
+#endif /* CHECKSUM_CHECK_TCP */
+
+  /* Move the payload pointer in the pbuf so that it points to the
+     TCP data instead of the TCP header. */
+  hdrlen = TCPH_HDRLEN(tcphdr);
+  if(pbuf_header(p, -(hdrlen * 4))){
+    /* drop short packets */
+    LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: short packet\n"));
+    TCP_STATS_INC(tcp.lenerr);
+    goto dropped;
+  }
+
+  /* Convert fields in TCP header to host byte order. */
+  tcphdr->src = ntohs(tcphdr->src);
+  tcphdr->dest = ntohs(tcphdr->dest);
+  seqno = tcphdr->seqno = ntohl(tcphdr->seqno);
+  ackno = tcphdr->ackno = ntohl(tcphdr->ackno);
+  tcphdr->wnd = ntohs(tcphdr->wnd);
+
+  flags = TCPH_FLAGS(tcphdr);
+  tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0);
+
+  /* Demultiplex an incoming segment. First, we check if it is destined
+     for an active connection. */
+  prev = NULL;
+
+  
+  for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
+    LWIP_ASSERT("tcp_input: active pcb->state != CLOSED", pcb->state != CLOSED);
+    LWIP_ASSERT("tcp_input: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT);
+    LWIP_ASSERT("tcp_input: active pcb->state != LISTEN", pcb->state != LISTEN);
+    if (pcb->remote_port == tcphdr->src &&
+        pcb->local_port == tcphdr->dest &&
+        IP_PCB_IPVER_INPUT_MATCH(pcb) &&
+        ipX_addr_cmp(ip_current_is_v6(), &pcb->remote_ip, ipX_current_src_addr()) &&
+        ipX_addr_cmp(ip_current_is_v6(),&pcb->local_ip, ipX_current_dest_addr())) {
+      /* Move this PCB to the front of the list so that subsequent
+         lookups will be faster (we exploit locality in TCP segment
+         arrivals). */
+      LWIP_ASSERT("tcp_input: pcb->next != pcb (before cache)", pcb->next != pcb);
+      if (prev != NULL) {
+        prev->next = pcb->next;
+        pcb->next = tcp_active_pcbs;
+        tcp_active_pcbs = pcb;
+      }
+      LWIP_ASSERT("tcp_input: pcb->next != pcb (after cache)", pcb->next != pcb);
+      break;
+    }
+    prev = pcb;
+  }
+
+  if (pcb == NULL) {
+    /* If it did not go to an active connection, we check the connections
+       in the TIME-WAIT state. */
+    for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
+      LWIP_ASSERT("tcp_input: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);
+      if (pcb->remote_port == tcphdr->src &&
+          pcb->local_port == tcphdr->dest &&
+          IP_PCB_IPVER_INPUT_MATCH(pcb) &&
+          ipX_addr_cmp(ip_current_is_v6(), &pcb->remote_ip, ipX_current_src_addr()) &&
+          ipX_addr_cmp(ip_current_is_v6(),&pcb->local_ip, ipX_current_dest_addr())) {
+        /* We don't really care enough to move this PCB to the front
+           of the list since we are not very likely to receive that
+           many segments for connections in TIME-WAIT. */
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for TIME_WAITing connection.\n"));
+        tcp_timewait_input(pcb);
+        pbuf_free(p);
+        return;
+      }
+    }
+
+    /* Finally, if we still did not get a match, we check all PCBs that
+       are LISTENing for incoming connections. */
+    prev = NULL;
+    struct tcp_pcb_listen *netif_pcb = NULL;
+    struct tcp_pcb *netif_pcb_prev;
+    for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
+      if (lpcb->bound_to_netif) {
+        if (IP_PCB_IPVER_INPUT_MATCH(lpcb) && netif_is_named(inp, lpcb->local_netif)) {
+          netif_pcb = lpcb;
+          netif_pcb_prev = prev;
+        }
+      }
+      else if (lpcb->local_port == tcphdr->dest) {
+#if LWIP_IPV6
+        if (lpcb->accept_any_ip_version) {
+          /* found an ANY-match */
+#if SO_REUSE
+          lpcb_any = lpcb;
+          lpcb_prev = prev;
+#else /* SO_REUSE */
+          break;
+#endif /* SO_REUSE */
+        } else
+#endif /* LWIP_IPV6 */
+        if (IP_PCB_IPVER_INPUT_MATCH(lpcb)) {
+          if (ipX_addr_cmp(ip_current_is_v6(), &lpcb->local_ip, ipX_current_dest_addr())) {
+            /* found an exact match */
+            break;
+          } else if (ipX_addr_isany(ip_current_is_v6(), &lpcb->local_ip)) {
+            /* found an ANY-match */
+#if SO_REUSE
+            lpcb_any = lpcb;
+            lpcb_prev = prev;
+#else /* SO_REUSE */
+            break;
+ #endif /* SO_REUSE */
+          }
+        }
+      }
+      prev = (struct tcp_pcb *)lpcb;
+    }
+#if SO_REUSE
+    /* first try specific local IP */
+    if (lpcb == NULL) {
+      /* only pass to ANY if no specific local IP has been found */
+      lpcb = lpcb_any;
+      prev = lpcb_prev;
+    }
+#endif /* SO_REUSE */
+    if (lpcb == NULL && netif_pcb) {
+        lpcb = netif_pcb;
+        prev = netif_pcb_prev;
+    }
+    if (lpcb != NULL) {
+      /* Move this PCB to the front of the list so that subsequent
+         lookups will be faster (we exploit locality in TCP segment
+         arrivals). */
+      if (prev != NULL) {
+        ((struct tcp_pcb_listen *)prev)->next = lpcb->next;
+              /* our successor is the remainder of the listening list */
+        lpcb->next = tcp_listen_pcbs.listen_pcbs;
+              /* put this listening pcb at the head of the listening list */
+        tcp_listen_pcbs.listen_pcbs = lpcb;
+      }
+    
+      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for LISTENing connection.\n"));
+      tcp_listen_input(lpcb);
+      pbuf_free(p);
+      return;
+    }
+  }
+
+#if TCP_INPUT_DEBUG
+  LWIP_DEBUGF(TCP_INPUT_DEBUG, ("+-+-+-+-+-+-+-+-+-+-+-+-+-+- tcp_input: flags "));
+  tcp_debug_print_flags(TCPH_FLAGS(tcphdr));
+  LWIP_DEBUGF(TCP_INPUT_DEBUG, ("-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n"));
+#endif /* TCP_INPUT_DEBUG */
+
+
+  if (pcb != NULL) {
+    /* The incoming segment belongs to a connection. */
+#if TCP_INPUT_DEBUG
+#if TCP_DEBUG
+    tcp_debug_print_state(pcb->state);
+#endif /* TCP_DEBUG */
+#endif /* TCP_INPUT_DEBUG */
+
+    /* Set up a tcp_seg structure. */
+    inseg.next = NULL;
+    inseg.len = p->tot_len;
+    inseg.p = p;
+    inseg.tcphdr = tcphdr;
+
+    recv_data = NULL;
+    recv_flags = 0;
+
+    if (flags & TCP_PSH) {
+      p->flags |= PBUF_FLAG_PUSH;
+    }
+
+    /* If there is data which was previously "refused" by upper layer */
+    if (pcb->refused_data != NULL) {
+      if ((tcp_process_refused_data(pcb) == ERR_ABRT) ||
+        ((pcb->refused_data != NULL) && (tcplen > 0))) {
+        /* pcb has been aborted or refused data is still refused and the new
+           segment contains data */
+        TCP_STATS_INC(tcp.drop);
+        snmp_inc_tcpinerrs();
+        goto aborted;
+      }
+    }
+    tcp_input_pcb = pcb;
+    err = tcp_process(pcb);
+    /* A return value of ERR_ABRT means that tcp_abort() was called
+       and that the pcb has been freed. If so, we don't do anything. */
+    if (err != ERR_ABRT) {
+      if (recv_flags & TF_RESET) {
+        /* TF_RESET means that the connection was reset by the other
+           end. We then call the error callback to inform the
+           application that the connection is dead before we
+           deallocate the PCB. */
+        TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_RST);
+        tcp_pcb_remove(&tcp_active_pcbs, pcb);
+        memp_free(MEMP_TCP_PCB, pcb);
+      } else if (recv_flags & TF_CLOSED) {
+        /* The connection has been closed and we will deallocate the
+           PCB. */
+        if (!(pcb->flags & TF_RXCLOSED)) {
+          /* Connection closed although the application has only shut down the
+             tx side: call the PCB's err callback and indicate the closure to
+             ensure the application doesn't continue using the PCB. */
+          TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_CLSD);
+        }
+        tcp_pcb_remove(&tcp_active_pcbs, pcb);
+        memp_free(MEMP_TCP_PCB, pcb);
+      } else {
+        err = ERR_OK;
+        /* If the application has registered a "sent" function to be
+           called when new send buffer space is available, we call it
+           now. */
+        if (pcb->acked > 0) {
+          TCP_EVENT_SENT(pcb, pcb->acked, err);
+          if (err == ERR_ABRT) {
+            goto aborted;
+          }
+        }
+
+        if (recv_data != NULL) {
+          LWIP_ASSERT("pcb->refused_data == NULL", pcb->refused_data == NULL);
+          if (pcb->flags & TF_RXCLOSED) {
+            /* received data although already closed -> abort (send RST) to
+               notify the remote host that not all data has been processed */
+            pbuf_free(recv_data);
+            tcp_abort(pcb);
+            goto aborted;
+          }
+
+          /* Notify application that data has been received. */
+          TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
+          if (err == ERR_ABRT) {
+            goto aborted;
+          }
+
+          /* If the upper layer can't receive this data, store it */
+          if (err != ERR_OK) {
+            pcb->refused_data = recv_data;
+            LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n"));
+          }
+        }
+
+        /* If a FIN segment was received, we call the callback
+           function with a NULL buffer to indicate EOF. */
+        if (recv_flags & TF_GOT_FIN) {
+          if (pcb->refused_data != NULL) {
+            /* Delay this if we have refused data. */
+            pcb->refused_data->flags |= PBUF_FLAG_TCP_FIN;
+          } else {
+            /* correct rcv_wnd as the application won't call tcp_recved()
+               for the FIN's seqno */
+            if (pcb->rcv_wnd != TCP_WND) {
+              pcb->rcv_wnd++;
+            }
+            TCP_EVENT_CLOSED(pcb, err);
+            if (err == ERR_ABRT) {
+              goto aborted;
+            }
+          }
+        }
+
+        tcp_input_pcb = NULL;
+        /* Try to send something out. */
+        tcp_output(pcb);
+#if TCP_INPUT_DEBUG
+#if TCP_DEBUG
+        tcp_debug_print_state(pcb->state);
+#endif /* TCP_DEBUG */
+#endif /* TCP_INPUT_DEBUG */
+      }
+    }
+    /* Jump target if pcb has been aborted in a callback (by calling tcp_abort()).
+       Below this line, 'pcb' may not be dereferenced! */
+aborted:
+    tcp_input_pcb = NULL;
+    recv_data = NULL;
+
+    /* give up our reference to inseg.p */
+    if (inseg.p != NULL)
+    {
+      pbuf_free(inseg.p);
+      inseg.p = NULL;
+    }
+  } else {
+
+    /* If no matching PCB was found, send a TCP RST (reset) to the
+       sender. */
+    LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_input: no PCB match found, resetting.\n"));
+    if (!(TCPH_FLAGS(tcphdr) & TCP_RST)) {
+      TCP_STATS_INC(tcp.proterr);
+      TCP_STATS_INC(tcp.drop);
+      tcp_rst(ackno, seqno + tcplen, ipX_current_dest_addr(),
+        ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+    }
+    pbuf_free(p);
+  }
+
+  LWIP_ASSERT("tcp_input: tcp_pcbs_sane()", tcp_pcbs_sane());
+  PERF_STOP("tcp_input");
+  return;
+dropped:
+  TCP_STATS_INC(tcp.drop);
+  snmp_inc_tcpinerrs();
+  pbuf_free(p);
+}
+
+/**
+ * Called by tcp_input() when a segment arrives for a listening
+ * connection (from tcp_input()).
+ *
+ * @param pcb the tcp_pcb_listen for which a segment arrived
+ * @return ERR_OK if the segment was processed
+ *         another err_t on error
+ *
+ * @note the return value is not (yet?) used in tcp_input()
+ * @note the segment which arrived is saved in global variables, therefore only the pcb
+ *       involved is passed as a parameter to this function
+ */
+static err_t
+tcp_listen_input(struct tcp_pcb_listen *pcb)
+{
+  struct tcp_pcb *npcb;
+  err_t rc;
+
+  if (flags & TCP_RST) {
+    /* An incoming RST should be ignored. Return. */
+    return ERR_OK;
+  }
+
+  /* In the LISTEN state, we check for incoming SYN segments,
+     creates a new PCB, and responds with a SYN|ACK. */
+  if (flags & TCP_ACK) {
+    /* For incoming segments with the ACK flag set, respond with a
+       RST. */
+    LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_listen_input: ACK in LISTEN, sending reset\n"));
+    tcp_rst(ackno, seqno + tcplen, ipX_current_dest_addr(),
+      ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+  } else if (flags & TCP_SYN) {
+    LWIP_DEBUGF(TCP_DEBUG, ("TCP connection request %"U16_F" -> %"U16_F".\n", tcphdr->src, tcphdr->dest));
+#if TCP_LISTEN_BACKLOG
+    if (pcb->accepts_pending >= pcb->backlog) {
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: listen backlog exceeded for port %"U16_F"\n", tcphdr->dest));
+      return ERR_ABRT;
+    }
+#endif /* TCP_LISTEN_BACKLOG */
+    npcb = tcp_alloc(pcb->prio);
+    /* If a new PCB could not be created (probably due to lack of memory),
+       we don't do anything, but rely on the sender will retransmit the
+       SYN at a time when we have more memory available. */
+    if (npcb == NULL) {
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\n"));
+      TCP_STATS_INC(tcp.memerr);
+      return ERR_MEM;
+    }
+#if TCP_LISTEN_BACKLOG
+    pcb->accepts_pending++;
+#endif /* TCP_LISTEN_BACKLOG */
+    /* Set up the new PCB. */
+#if LWIP_IPV6
+    PCB_ISIPV6(npcb) = ip_current_is_v6();
+#endif /* LWIP_IPV6 */
+    ipX_addr_copy(ip_current_is_v6(), npcb->local_ip, *ipX_current_dest_addr());
+    ipX_addr_copy(ip_current_is_v6(), npcb->remote_ip, *ipX_current_src_addr());
+    npcb->bound_to_netif = pcb->bound_to_netif;
+    npcb->local_port = tcphdr->dest;
+    memcpy(npcb->local_netif, pcb->local_netif, sizeof(pcb->local_netif));
+    npcb->remote_port = tcphdr->src;
+    npcb->state = SYN_RCVD;
+    npcb->rcv_nxt = seqno + 1;
+    npcb->rcv_ann_right_edge = npcb->rcv_nxt;
+    npcb->snd_wnd = tcphdr->wnd;
+    npcb->snd_wnd_max = tcphdr->wnd;
+    npcb->ssthresh = npcb->snd_wnd;
+    npcb->snd_wl1 = seqno - 1;/* initialise to seqno-1 to force window update */
+    npcb->callback_arg = pcb->callback_arg;
+#if LWIP_CALLBACK_API
+    npcb->accept = pcb->accept;
+#endif /* LWIP_CALLBACK_API */
+    /* inherit socket options */
+    npcb->so_options = pcb->so_options & SOF_INHERITED;
+    /* Register the new PCB so that we can begin receiving segments
+       for it. */
+    TCP_REG_ACTIVE(npcb);
+
+    /* Parse any options in the SYN. */
+    tcp_parseopt(npcb);
+#if TCP_CALCULATE_EFF_SEND_MSS
+    npcb->mss = tcp_eff_send_mss(npcb->mss, &npcb->local_ip,
+      &npcb->remote_ip, PCB_ISIPV6(npcb));
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+
+    snmp_inc_tcppassiveopens();
+
+    /* Send a SYN|ACK together with the MSS option. */
+    rc = tcp_enqueue_flags(npcb, TCP_SYN | TCP_ACK);
+    if (rc != ERR_OK) {
+      tcp_abandon(npcb, 0);
+      return rc;
+    }
+    return tcp_output(npcb);
+  }
+  return ERR_OK;
+}
+
+/**
+ * Called by tcp_input() when a segment arrives for a connection in
+ * TIME_WAIT.
+ *
+ * @param pcb the tcp_pcb for which a segment arrived
+ *
+ * @note the segment which arrived is saved in global variables, therefore only the pcb
+ *       involved is passed as a parameter to this function
+ */
+static err_t
+tcp_timewait_input(struct tcp_pcb *pcb)
+{
+  /* RFC 1337: in TIME_WAIT, ignore RST and ACK FINs + any 'acceptable' segments */
+  /* RFC 793 3.9 Event Processing - Segment Arrives:
+   * - first check sequence number - we skip that one in TIME_WAIT (always
+   *   acceptable since we only send ACKs)
+   * - second check the RST bit (... return) */
+  if (flags & TCP_RST)  {
+    return ERR_OK;
+  }
+  /* - fourth, check the SYN bit, */
+  if (flags & TCP_SYN) {
+    /* If an incoming segment is not acceptable, an acknowledgment
+       should be sent in reply */
+    if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd)) {
+      /* If the SYN is in the window it is an error, send a reset */
+      tcp_rst(ackno, seqno + tcplen, ipX_current_dest_addr(),
+        ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+      return ERR_OK;
+    }
+  } else if (flags & TCP_FIN) {
+    /* - eighth, check the FIN bit: Remain in the TIME-WAIT state.
+         Restart the 2 MSL time-wait timeout.*/
+    pcb->tmr = tcp_ticks;
+  }
+
+  if ((tcplen > 0))  {
+    /* Acknowledge data, FIN or out-of-window SYN */
+    pcb->flags |= TF_ACK_NOW;
+    return tcp_output(pcb);
+  }
+  return ERR_OK;
+}
+
+/**
+ * Implements the TCP state machine. Called by tcp_input. In some
+ * states tcp_receive() is called to receive data. The tcp_seg
+ * argument will be freed by the caller (tcp_input()) unless the
+ * recv_data pointer in the pcb is set.
+ *
+ * @param pcb the tcp_pcb for which a segment arrived
+ *
+ * @note the segment which arrived is saved in global variables, therefore only the pcb
+ *       involved is passed as a parameter to this function
+ */
+static err_t
+tcp_process(struct tcp_pcb *pcb)
+{
+  struct tcp_seg *rseg;
+  u8_t acceptable = 0;
+  err_t err;
+
+  err = ERR_OK;
+
+  /* Process incoming RST segments. */
+  if (flags & TCP_RST) {
+    /* First, determine if the reset is acceptable. */
+    if (pcb->state == SYN_SENT) {
+      if (ackno == pcb->snd_nxt) {
+        acceptable = 1;
+      }
+    } else {
+      if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, 
+                          pcb->rcv_nxt+pcb->rcv_wnd)) {
+        acceptable = 1;
+      }
+    }
+
+    if (acceptable) {
+      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n"));
+      LWIP_ASSERT("tcp_input: pcb->state != CLOSED", pcb->state != CLOSED);
+      recv_flags |= TF_RESET;
+      pcb->flags &= ~TF_ACK_DELAY;
+      return ERR_RST;
+    } else {
+      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
+       seqno, pcb->rcv_nxt));
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
+       seqno, pcb->rcv_nxt));
+      return ERR_OK;
+    }
+  }
+
+  if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) { 
+    /* Cope with new connection attempt after remote end crashed */
+    tcp_ack_now(pcb);
+    return ERR_OK;
+  }
+  
+  if ((pcb->flags & TF_RXCLOSED) == 0) {
+    /* Update the PCB (in)activity timer unless rx is closed (see tcp_shutdown) */
+    pcb->tmr = tcp_ticks;
+  }
+  pcb->keep_cnt_sent = 0;
+
+  tcp_parseopt(pcb);
+
+  /* Do different things depending on the TCP state. */
+  switch (pcb->state) {
+  case SYN_SENT:
+    LWIP_DEBUGF(TCP_INPUT_DEBUG, ("SYN-SENT: ackno %"U32_F" pcb->snd_nxt %"U32_F" unacked %"U32_F"\n", ackno,
+     pcb->snd_nxt, ntohl(pcb->unacked->tcphdr->seqno)));
+    /* received SYN ACK with expected sequence number? */
+    if ((flags & TCP_ACK) && (flags & TCP_SYN)
+        && ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1) {
+      pcb->snd_buf++;
+      pcb->rcv_nxt = seqno + 1;
+      pcb->rcv_ann_right_edge = pcb->rcv_nxt;
+      pcb->lastack = ackno;
+      pcb->snd_wnd = tcphdr->wnd;
+      pcb->snd_wnd_max = tcphdr->wnd;
+      pcb->snd_wl1 = seqno - 1; /* initialise to seqno - 1 to force window update */
+      pcb->state = ESTABLISHED;
+
+#if TCP_CALCULATE_EFF_SEND_MSS
+      pcb->mss = tcp_eff_send_mss(pcb->mss, &pcb->local_ip, &pcb->remote_ip,
+        PCB_ISIPV6(pcb));
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+
+      /* Set ssthresh again after changing pcb->mss (already set in tcp_connect
+       * but for the default value of pcb->mss) */
+      pcb->ssthresh = pcb->mss * 10;
+
+      pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
+      LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0));
+      --pcb->snd_queuelen;
+      LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"U16_F"\n", (u16_t)pcb->snd_queuelen));
+      rseg = pcb->unacked;
+      pcb->unacked = rseg->next;
+      tcp_seg_free(rseg);
+
+      /* If there's nothing left to acknowledge, stop the retransmit
+         timer, otherwise reset it to start again */
+      if(pcb->unacked == NULL)
+        pcb->rtime = -1;
+      else {
+        pcb->rtime = 0;
+        pcb->nrtx = 0;
+      }
+
+      /* Call the user specified function to call when sucessfully
+       * connected. */
+      TCP_EVENT_CONNECTED(pcb, ERR_OK, err);
+      if (err == ERR_ABRT) {
+        return ERR_ABRT;
+      }
+      tcp_ack_now(pcb);
+    }
+    /* received ACK? possibly a half-open connection */
+    else if (flags & TCP_ACK) {
+      /* send a RST to bring the other side in a non-synchronized state. */
+      tcp_rst(ackno, seqno + tcplen, ipX_current_dest_addr(),
+        ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+    }
+    break;
+  case SYN_RCVD:
+    if (flags & TCP_ACK) {
+      /* expected ACK number? */
+      if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {
+        u16_t old_cwnd;
+        pcb->state = ESTABLISHED;
+        LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
+#if LWIP_CALLBACK_API
+        LWIP_ASSERT("pcb->accept != NULL", pcb->accept != NULL);
+#endif
+        /* Call the accept function. */
+        TCP_EVENT_ACCEPT(pcb, ERR_OK, err);
+        if (err != ERR_OK) {
+          /* If the accept function returns with an error, we abort
+           * the connection. */
+          /* Already aborted? */
+          if (err != ERR_ABRT) {
+            tcp_abort(pcb);
+          }
+          return ERR_ABRT;
+        }
+        old_cwnd = pcb->cwnd;
+        /* If there was any data contained within this ACK,
+         * we'd better pass it on to the application as well. */
+        tcp_receive(pcb);
+
+        /* Prevent ACK for SYN to generate a sent event */
+        if (pcb->acked != 0) {
+          pcb->acked--;
+        }
+
+        pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
+
+        if (recv_flags & TF_GOT_FIN) {
+          tcp_ack_now(pcb);
+          pcb->state = CLOSE_WAIT;
+        }
+      } else {
+        /* incorrect ACK number, send RST */
+        tcp_rst(ackno, seqno + tcplen, ipX_current_dest_addr(),
+          ipX_current_src_addr(), tcphdr->dest, tcphdr->src, ip_current_is_v6());
+      }
+    } else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) {
+      /* Looks like another copy of the SYN - retransmit our SYN-ACK */
+      tcp_rexmit(pcb);
+    }
+    break;
+  case CLOSE_WAIT:
+    /* FALLTHROUGH */
+  case ESTABLISHED:
+    tcp_receive(pcb);
+    if (recv_flags & TF_GOT_FIN) { /* passive close */
+      tcp_ack_now(pcb);
+      pcb->state = CLOSE_WAIT;
+    }
+    break;
+  case FIN_WAIT_1:
+    tcp_receive(pcb);
+    if (recv_flags & TF_GOT_FIN) {
+      if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {
+        LWIP_DEBUGF(TCP_DEBUG,
+          ("TCP connection closed: FIN_WAIT_1 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
+        tcp_ack_now(pcb);
+        tcp_pcb_purge(pcb);
+        TCP_RMV_ACTIVE(pcb);
+        pcb->state = TIME_WAIT;
+        TCP_REG(&tcp_tw_pcbs, pcb);
+      } else {
+        tcp_ack_now(pcb);
+        pcb->state = CLOSING;
+      }
+    } else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {
+      pcb->state = FIN_WAIT_2;
+    }
+    break;
+  case FIN_WAIT_2:
+    tcp_receive(pcb);
+    if (recv_flags & TF_GOT_FIN) {
+      LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: FIN_WAIT_2 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
+      tcp_ack_now(pcb);
+      tcp_pcb_purge(pcb);
+      TCP_RMV_ACTIVE(pcb);
+      pcb->state = TIME_WAIT;
+      TCP_REG(&tcp_tw_pcbs, pcb);
+    }
+    break;
+  case CLOSING:
+    tcp_receive(pcb);
+    if (flags & TCP_ACK && ackno == pcb->snd_nxt) {
+      LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: CLOSING %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
+      tcp_pcb_purge(pcb);
+      TCP_RMV_ACTIVE(pcb);
+      pcb->state = TIME_WAIT;
+      TCP_REG(&tcp_tw_pcbs, pcb);
+    }
+    break;
+  case LAST_ACK:
+    tcp_receive(pcb);
+    if (flags & TCP_ACK && ackno == pcb->snd_nxt) {
+      LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: LAST_ACK %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
+      /* bugfix #21699: don't set pcb->state to CLOSED here or we risk leaking segments */
+      recv_flags |= TF_CLOSED;
+    }
+    break;
+  default:
+    break;
+  }
+  return ERR_OK;
+}
+
+#if TCP_QUEUE_OOSEQ
+/**
+ * Insert segment into the list (segments covered with new one will be deleted)
+ *
+ * Called from tcp_receive()
+ */
+static void
+tcp_oos_insert_segment(struct tcp_seg *cseg, struct tcp_seg *next)
+{
+  struct tcp_seg *old_seg;
+
+  if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN) {
+    /* received segment overlaps all following segments */
+    tcp_segs_free(next);
+    next = NULL;
+  }
+  else {
+    /* delete some following segments
+       oos queue may have segments with FIN flag */
+    while (next &&
+           TCP_SEQ_GEQ((seqno + cseg->len),
+                      (next->tcphdr->seqno + next->len))) {
+      /* cseg with FIN already processed */
+      if (TCPH_FLAGS(next->tcphdr) & TCP_FIN) {
+        TCPH_SET_FLAG(cseg->tcphdr, TCP_FIN);
+      }
+      old_seg = next;
+      next = next->next;
+      tcp_seg_free(old_seg);
+    }
+    if (next &&
+        TCP_SEQ_GT(seqno + cseg->len, next->tcphdr->seqno)) {
+      /* We need to trim the incoming segment. */
+      cseg->len = (u16_t)(next->tcphdr->seqno - seqno);
+      pbuf_realloc(cseg->p, cseg->len);
+    }
+  }
+  cseg->next = next;
+}
+#endif /* TCP_QUEUE_OOSEQ */
+
+/**
+ * Called by tcp_process. Checks if the given segment is an ACK for outstanding
+ * data, and if so frees the memory of the buffered data. Next, is places the
+ * segment on any of the receive queues (pcb->recved or pcb->ooseq). If the segment
+ * is buffered, the pbuf is referenced by pbuf_ref so that it will not be freed until
+ * it has been removed from the buffer.
+ *
+ * If the incoming segment constitutes an ACK for a segment that was used for RTT
+ * estimation, the RTT is estimated here as well.
+ *
+ * Called from tcp_process().
+ */
+static void
+tcp_receive(struct tcp_pcb *pcb)
+{
+  struct tcp_seg *next;
+#if TCP_QUEUE_OOSEQ
+  struct tcp_seg *prev, *cseg;
+#endif /* TCP_QUEUE_OOSEQ */
+  struct pbuf *p;
+  s32_t off;
+  s16_t m;
+  u32_t right_wnd_edge;
+  u16_t new_tot_len;
+  int found_dupack = 0;
+#if TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS
+  u32_t ooseq_blen;
+  u16_t ooseq_qlen;
+#endif /* TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS */
+
+  LWIP_ASSERT("tcp_receive: wrong state", pcb->state >= ESTABLISHED);
+
+  if (flags & TCP_ACK) {
+    right_wnd_edge = pcb->snd_wnd + pcb->snd_wl2;
+
+    /* Update window. */
+    if (TCP_SEQ_LT(pcb->snd_wl1, seqno) ||
+       (pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno)) ||
+       (pcb->snd_wl2 == ackno && tcphdr->wnd > pcb->snd_wnd)) {
+      pcb->snd_wnd = tcphdr->wnd;
+      /* keep track of the biggest window announced by the remote host to calculate
+         the maximum segment size */
+      if (pcb->snd_wnd_max < tcphdr->wnd) {
+        pcb->snd_wnd_max = tcphdr->wnd;
+      }
+      pcb->snd_wl1 = seqno;
+      pcb->snd_wl2 = ackno;
+      if (pcb->snd_wnd == 0) {
+        if (pcb->persist_backoff == 0) {
+          /* start persist timer */
+          pcb->persist_cnt = 0;
+          pcb->persist_backoff = 1;
+        }
+      } else if (pcb->persist_backoff > 0) {
+        /* stop persist timer */
+          pcb->persist_backoff = 0;
+      }
+      LWIP_DEBUGF(TCP_WND_DEBUG, ("tcp_receive: window update %"U16_F"\n", pcb->snd_wnd));
+#if TCP_WND_DEBUG
+    } else {
+      if (pcb->snd_wnd != tcphdr->wnd) {
+        LWIP_DEBUGF(TCP_WND_DEBUG, 
+                    ("tcp_receive: no window update lastack %"U32_F" ackno %"
+                     U32_F" wl1 %"U32_F" seqno %"U32_F" wl2 %"U32_F"\n",
+                     pcb->lastack, ackno, pcb->snd_wl1, seqno, pcb->snd_wl2));
+      }
+#endif /* TCP_WND_DEBUG */
+    }
+
+    /* (From Stevens TCP/IP Illustrated Vol II, p970.) Its only a
+     * duplicate ack if:
+     * 1) It doesn't ACK new data 
+     * 2) length of received packet is zero (i.e. no payload) 
+     * 3) the advertised window hasn't changed 
+     * 4) There is outstanding unacknowledged data (retransmission timer running)
+     * 5) The ACK is == biggest ACK sequence number so far seen (snd_una)
+     * 
+     * If it passes all five, should process as a dupack: 
+     * a) dupacks < 3: do nothing 
+     * b) dupacks == 3: fast retransmit 
+     * c) dupacks > 3: increase cwnd 
+     * 
+     * If it only passes 1-3, should reset dupack counter (and add to
+     * stats, which we don't do in lwIP)
+     *
+     * If it only passes 1, should reset dupack counter
+     *
+     */
+
+    /* Clause 1 */
+    if (TCP_SEQ_LEQ(ackno, pcb->lastack)) {
+      pcb->acked = 0;
+      /* Clause 2 */
+      if (tcplen == 0) {
+        /* Clause 3 */
+        if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge){
+          /* Clause 4 */
+          if (pcb->rtime >= 0) {
+            /* Clause 5 */
+            if (pcb->lastack == ackno) {
+              found_dupack = 1;
+              if ((u8_t)(pcb->dupacks + 1) > pcb->dupacks) {
+                ++pcb->dupacks;
+              }
+              if (pcb->dupacks > 3) {
+                /* Inflate the congestion window, but not if it means that
+                   the value overflows. */
+                if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
+                  pcb->cwnd += pcb->mss;
+                }
+              } else if (pcb->dupacks == 3) {
+                /* Do fast retransmit */
+                tcp_rexmit_fast(pcb);
+              }
+            }
+          }
+        }
+      }
+      /* If Clause (1) or more is true, but not a duplicate ack, reset
+       * count of consecutive duplicate acks */
+      if (!found_dupack) {
+        pcb->dupacks = 0;
+      }
+    } else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)){
+      /* We come here when the ACK acknowledges new data. */
+
+      /* Reset the "IN Fast Retransmit" flag, since we are no longer
+         in fast retransmit. Also reset the congestion window to the
+         slow start threshold. */
+      if (pcb->flags & TF_INFR) {
+        pcb->flags &= ~TF_INFR;
+        pcb->cwnd = pcb->ssthresh;
+      }
+
+      /* Reset the number of retransmissions. */
+      pcb->nrtx = 0;
+
+      /* Reset the retransmission time-out. */
+      pcb->rto = (pcb->sa >> 3) + pcb->sv;
+
+      /* Update the send buffer space. Diff between the two can never exceed 64K? */
+      pcb->acked = (u16_t)(ackno - pcb->lastack);
+
+      pcb->snd_buf += pcb->acked;
+
+      /* Reset the fast retransmit variables. */
+      pcb->dupacks = 0;
+      pcb->lastack = ackno;
+
+      /* Update the congestion control variables (cwnd and
+         ssthresh). */
+      if (pcb->state >= ESTABLISHED) {
+        if (pcb->cwnd < pcb->ssthresh) {
+          if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
+            pcb->cwnd += pcb->mss;
+          }
+          LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"U16_F"\n", pcb->cwnd));
+        } else {
+          u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);
+          if (new_cwnd > pcb->cwnd) {
+            pcb->cwnd = new_cwnd;
+          }
+          LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: congestion avoidance cwnd %"U16_F"\n", pcb->cwnd));
+        }
+      }
+      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: ACK for %"U32_F", unacked->seqno %"U32_F":%"U32_F"\n",
+                                    ackno,
+                                    pcb->unacked != NULL?
+                                    ntohl(pcb->unacked->tcphdr->seqno): 0,
+                                    pcb->unacked != NULL?
+                                    ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked): 0));
+
+      /* Remove segment from the unacknowledged list if the incoming
+         ACK acknowlegdes them. */
+      while (pcb->unacked != NULL &&
+             TCP_SEQ_LEQ(ntohl(pcb->unacked->tcphdr->seqno) +
+                         TCP_TCPLEN(pcb->unacked), ackno)) {
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: removing %"U32_F":%"U32_F" from pcb->unacked\n",
+                                      ntohl(pcb->unacked->tcphdr->seqno),
+                                      ntohl(pcb->unacked->tcphdr->seqno) +
+                                      TCP_TCPLEN(pcb->unacked)));
+
+        next = pcb->unacked;
+        pcb->unacked = pcb->unacked->next;
+
+        LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"U16_F" ... ", (u16_t)pcb->snd_queuelen));
+        LWIP_ASSERT("pcb->snd_queuelen >= pbuf_clen(next->p)", (pcb->snd_queuelen >= pbuf_clen(next->p)));
+        /* Prevent ACK for FIN to generate a sent event */
+        if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)) {
+          pcb->acked--;
+        }
+
+        pcb->snd_queuelen -= pbuf_clen(next->p);
+        tcp_seg_free(next);
+
+        LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"U16_F" (after freeing unacked)\n", (u16_t)pcb->snd_queuelen));
+        if (pcb->snd_queuelen != 0) {
+          LWIP_ASSERT("tcp_receive: valid queue length", pcb->unacked != NULL ||
+                      pcb->unsent != NULL);
+        }
+      }
+
+      /* If there's nothing left to acknowledge, stop the retransmit
+         timer, otherwise reset it to start again */
+      if(pcb->unacked == NULL)
+        pcb->rtime = -1;
+      else
+        pcb->rtime = 0;
+
+      pcb->polltmr = 0;
+
+#if LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS
+      if (PCB_ISIPV6(pcb)) {
+        /* Inform neighbor reachability of forward progress. */
+        nd6_reachability_hint(ip6_current_src_addr());
+      }
+#endif /* LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS*/
+    } else {
+      /* Fix bug bug #21582: out of sequence ACK, didn't really ack anything */
+      pcb->acked = 0;
+    }
+
+    /* We go through the ->unsent list to see if any of the segments
+       on the list are acknowledged by the ACK. This may seem
+       strange since an "unsent" segment shouldn't be acked. The
+       rationale is that lwIP puts all outstanding segments on the
+       ->unsent list after a retransmission, so these segments may
+       in fact have been sent once. */
+    while (pcb->unsent != NULL &&
+           TCP_SEQ_BETWEEN(ackno, ntohl(pcb->unsent->tcphdr->seqno) + 
+                           TCP_TCPLEN(pcb->unsent), pcb->snd_nxt)) {
+      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: removing %"U32_F":%"U32_F" from pcb->unsent\n",
+                                    ntohl(pcb->unsent->tcphdr->seqno), ntohl(pcb->unsent->tcphdr->seqno) +
+                                    TCP_TCPLEN(pcb->unsent)));
+
+      next = pcb->unsent;
+      pcb->unsent = pcb->unsent->next;
+#if TCP_OVERSIZE
+      if (pcb->unsent == NULL) {
+        pcb->unsent_oversize = 0;
+      }
+#endif /* TCP_OVERSIZE */ 
+      LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"U16_F" ... ", (u16_t)pcb->snd_queuelen));
+      LWIP_ASSERT("pcb->snd_queuelen >= pbuf_clen(next->p)", (pcb->snd_queuelen >= pbuf_clen(next->p)));
+      /* Prevent ACK for FIN to generate a sent event */
+      if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)) {
+        pcb->acked--;
+      }
+      pcb->snd_queuelen -= pbuf_clen(next->p);
+      tcp_seg_free(next);
+      LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"U16_F" (after freeing unsent)\n", (u16_t)pcb->snd_queuelen));
+      if (pcb->snd_queuelen != 0) {
+        LWIP_ASSERT("tcp_receive: valid queue length",
+          pcb->unacked != NULL || pcb->unsent != NULL);
+      }
+    }
+    /* End of ACK for new data processing. */
+
+    LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_receive: pcb->rttest %"U32_F" rtseq %"U32_F" ackno %"U32_F"\n",
+                                pcb->rttest, pcb->rtseq, ackno));
+
+    /* RTT estimation calculations. This is done by checking if the
+       incoming segment acknowledges the segment we use to take a
+       round-trip time measurement. */
+    if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno)) {
+      /* diff between this shouldn't exceed 32K since this are tcp timer ticks
+         and a round-trip shouldn't be that long... */
+      m = (s16_t)(tcp_ticks - pcb->rttest);
+
+      LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_receive: experienced rtt %"U16_F" ticks (%"U16_F" msec).\n",
+                                  m, m * TCP_SLOW_INTERVAL));
+
+      /* This is taken directly from VJs original code in his paper */
+      m = m - (pcb->sa >> 3);
+      pcb->sa += m;
+      if (m < 0) {
+        m = -m;
+      }
+      m = m - (pcb->sv >> 2);
+      pcb->sv += m;
+      pcb->rto = (pcb->sa >> 3) + pcb->sv;
+
+      LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_receive: RTO %"U16_F" (%"U16_F" milliseconds)\n",
+                                  pcb->rto, pcb->rto * TCP_SLOW_INTERVAL));
+
+      pcb->rttest = 0;
+    }
+  }
+
+  /* If the incoming segment contains data, we must process it
+     further unless the pcb already received a FIN.
+     (RFC 793, chapeter 3.9, "SEGMENT ARRIVES" in states CLOSE-WAIT, CLOSING,
+     LAST-ACK and TIME-WAIT: "Ignore the segment text.") */
+  if ((tcplen > 0) && (pcb->state < CLOSE_WAIT)) {
+    /* This code basically does three things:
+
+    +) If the incoming segment contains data that is the next
+    in-sequence data, this data is passed to the application. This
+    might involve trimming the first edge of the data. The rcv_nxt
+    variable and the advertised window are adjusted.
+
+    +) If the incoming segment has data that is above the next
+    sequence number expected (->rcv_nxt), the segment is placed on
+    the ->ooseq queue. This is done by finding the appropriate
+    place in the ->ooseq queue (which is ordered by sequence
+    number) and trim the segment in both ends if needed. An
+    immediate ACK is sent to indicate that we received an
+    out-of-sequence segment.
+
+    +) Finally, we check if the first segment on the ->ooseq queue
+    now is in sequence (i.e., if rcv_nxt >= ooseq->seqno). If
+    rcv_nxt > ooseq->seqno, we must trim the first edge of the
+    segment on ->ooseq before we adjust rcv_nxt. The data in the
+    segments that are now on sequence are chained onto the
+    incoming segment so that we only need to call the application
+    once.
+    */
+
+    /* First, we check if we must trim the first edge. We have to do
+       this if the sequence number of the incoming segment is less
+       than rcv_nxt, and the sequence number plus the length of the
+       segment is larger than rcv_nxt. */
+    /*    if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)){
+          if (TCP_SEQ_LT(pcb->rcv_nxt, seqno + tcplen)) {*/
+    if (TCP_SEQ_BETWEEN(pcb->rcv_nxt, seqno + 1, seqno + tcplen - 1)){
+      /* Trimming the first edge is done by pushing the payload
+         pointer in the pbuf downwards. This is somewhat tricky since
+         we do not want to discard the full contents of the pbuf up to
+         the new starting point of the data since we have to keep the
+         TCP header which is present in the first pbuf in the chain.
+
+         What is done is really quite a nasty hack: the first pbuf in
+         the pbuf chain is pointed to by inseg.p. Since we need to be
+         able to deallocate the whole pbuf, we cannot change this
+         inseg.p pointer to point to any of the later pbufs in the
+         chain. Instead, we point the ->payload pointer in the first
+         pbuf to data in one of the later pbufs. We also set the
+         inseg.data pointer to point to the right place. This way, the
+         ->p pointer will still point to the first pbuf, but the
+         ->p->payload pointer will point to data in another pbuf.
+
+         After we are done with adjusting the pbuf pointers we must
+         adjust the ->data pointer in the seg and the segment
+         length.*/
+
+      off = pcb->rcv_nxt - seqno;
+      p = inseg.p;
+      LWIP_ASSERT("inseg.p != NULL", inseg.p);
+      LWIP_ASSERT("insane offset!", (off < 0x7fff));
+      if (inseg.p->len < off) {
+        LWIP_ASSERT("pbuf too short!", (((s32_t)inseg.p->tot_len) >= off));
+        new_tot_len = (u16_t)(inseg.p->tot_len - off);
+        while (p->len < off) {
+          off -= p->len;
+          /* KJM following line changed (with addition of new_tot_len var)
+             to fix bug #9076
+             inseg.p->tot_len -= p->len; */
+          p->tot_len = new_tot_len;
+          p->len = 0;
+          p = p->next;
+        }
+        if(pbuf_header(p, (s16_t)-off)) {
+          /* Do we need to cope with this failing?  Assert for now */
+          LWIP_ASSERT("pbuf_header failed", 0);
+        }
+      } else {
+        if(pbuf_header(inseg.p, (s16_t)-off)) {
+          /* Do we need to cope with this failing?  Assert for now */
+          LWIP_ASSERT("pbuf_header failed", 0);
+        }
+      }
+      inseg.len -= (u16_t)(pcb->rcv_nxt - seqno);
+      inseg.tcphdr->seqno = seqno = pcb->rcv_nxt;
+    }
+    else {
+      if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)){
+        /* the whole segment is < rcv_nxt */
+        /* must be a duplicate of a packet that has already been correctly handled */
+
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: duplicate seqno %"U32_F"\n", seqno));
+        tcp_ack_now(pcb);
+      }
+    }
+
+    /* The sequence number must be within the window (above rcv_nxt
+       and below rcv_nxt + rcv_wnd) in order to be further
+       processed. */
+    if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, 
+                        pcb->rcv_nxt + pcb->rcv_wnd - 1)){
+      if (pcb->rcv_nxt == seqno) {
+        /* The incoming segment is the next in sequence. We check if
+           we have to trim the end of the segment and update rcv_nxt
+           and pass the data to the application. */
+        tcplen = TCP_TCPLEN(&inseg);
+
+        if (tcplen > pcb->rcv_wnd) {
+          LWIP_DEBUGF(TCP_INPUT_DEBUG, 
+                      ("tcp_receive: other end overran receive window"
+                       "seqno %"U32_F" len %"U16_F" right edge %"U32_F"\n",
+                       seqno, tcplen, pcb->rcv_nxt + pcb->rcv_wnd));
+          if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {
+            /* Must remove the FIN from the header as we're trimming 
+             * that byte of sequence-space from the packet */
+            TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) &~ TCP_FIN);
+          }
+          /* Adjust length of segment to fit in the window. */
+          inseg.len = pcb->rcv_wnd;
+          if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {
+            inseg.len -= 1;
+          }
+          pbuf_realloc(inseg.p, inseg.len);
+          tcplen = TCP_TCPLEN(&inseg);
+          LWIP_ASSERT("tcp_receive: segment not trimmed correctly to rcv_wnd\n",
+                      (seqno + tcplen) == (pcb->rcv_nxt + pcb->rcv_wnd));
+        }
+#if TCP_QUEUE_OOSEQ
+        /* Received in-sequence data, adjust ooseq data if:
+           - FIN has been received or
+           - inseq overlaps with ooseq */
+        if (pcb->ooseq != NULL) {
+          if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {
+            LWIP_DEBUGF(TCP_INPUT_DEBUG, 
+                        ("tcp_receive: received in-order FIN, binning ooseq queue\n"));
+            /* Received in-order FIN means anything that was received
+             * out of order must now have been received in-order, so
+             * bin the ooseq queue */
+            while (pcb->ooseq != NULL) {
+              struct tcp_seg *old_ooseq = pcb->ooseq;
+              pcb->ooseq = pcb->ooseq->next;
+              tcp_seg_free(old_ooseq);
+            }
+          } else {
+            next = pcb->ooseq;
+            /* Remove all segments on ooseq that are covered by inseg already.
+             * FIN is copied from ooseq to inseg if present. */
+            while (next &&
+                   TCP_SEQ_GEQ(seqno + tcplen,
+                               next->tcphdr->seqno + next->len)) {
+              /* inseg cannot have FIN here (already processed above) */
+              if (TCPH_FLAGS(next->tcphdr) & TCP_FIN &&
+                  (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) == 0) {
+                TCPH_SET_FLAG(inseg.tcphdr, TCP_FIN);
+                tcplen = TCP_TCPLEN(&inseg);
+              }
+              prev = next;
+              next = next->next;
+              tcp_seg_free(prev);
+            }
+            /* Now trim right side of inseg if it overlaps with the first
+             * segment on ooseq */
+            if (next &&
+                TCP_SEQ_GT(seqno + tcplen,
+                           next->tcphdr->seqno)) {
+              /* inseg cannot have FIN here (already processed above) */
+              inseg.len = (u16_t)(next->tcphdr->seqno - seqno);
+              if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {
+                inseg.len -= 1;
+              }
+              pbuf_realloc(inseg.p, inseg.len);
+              tcplen = TCP_TCPLEN(&inseg);
+              LWIP_ASSERT("tcp_receive: segment not trimmed correctly to ooseq queue\n",
+                          (seqno + tcplen) == next->tcphdr->seqno);
+            }
+            pcb->ooseq = next;
+          }
+        }
+#endif /* TCP_QUEUE_OOSEQ */
+
+        pcb->rcv_nxt = seqno + tcplen;
+
+        /* Update the receiver's (our) window. */
+        LWIP_ASSERT("tcp_receive: tcplen > rcv_wnd\n", pcb->rcv_wnd >= tcplen);
+        pcb->rcv_wnd -= tcplen;
+
+        tcp_update_rcv_ann_wnd(pcb);
+
+        /* If there is data in the segment, we make preparations to
+           pass this up to the application. The ->recv_data variable
+           is used for holding the pbuf that goes to the
+           application. The code for reassembling out-of-sequence data
+           chains its data on this pbuf as well.
+
+           If the segment was a FIN, we set the TF_GOT_FIN flag that will
+           be used to indicate to the application that the remote side has
+           closed its end of the connection. */
+        if (inseg.p->tot_len > 0) {
+          recv_data = inseg.p;
+          /* Since this pbuf now is the responsibility of the
+             application, we delete our reference to it so that we won't
+             (mistakingly) deallocate it. */
+          inseg.p = NULL;
+        }
+        if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {
+          LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: received FIN.\n"));
+          recv_flags |= TF_GOT_FIN;
+        }
+
+#if TCP_QUEUE_OOSEQ
+        /* We now check if we have segments on the ->ooseq queue that
+           are now in sequence. */
+        while (pcb->ooseq != NULL &&
+               pcb->ooseq->tcphdr->seqno == pcb->rcv_nxt) {
+
+          cseg = pcb->ooseq;
+          seqno = pcb->ooseq->tcphdr->seqno;
+
+          pcb->rcv_nxt += TCP_TCPLEN(cseg);
+          LWIP_ASSERT("tcp_receive: ooseq tcplen > rcv_wnd\n",
+                      pcb->rcv_wnd >= TCP_TCPLEN(cseg));
+          pcb->rcv_wnd -= TCP_TCPLEN(cseg);
+
+          tcp_update_rcv_ann_wnd(pcb);
+
+          if (cseg->p->tot_len > 0) {
+            /* Chain this pbuf onto the pbuf that we will pass to
+               the application. */
+            if (recv_data) {
+              pbuf_cat(recv_data, cseg->p);
+            } else {
+              recv_data = cseg->p;
+            }
+            cseg->p = NULL;
+          }
+          if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN) {
+            LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: dequeued FIN.\n"));
+            recv_flags |= TF_GOT_FIN;
+            if (pcb->state == ESTABLISHED) { /* force passive close or we can move to active close */
+              pcb->state = CLOSE_WAIT;
+            } 
+          }
+
+          pcb->ooseq = cseg->next;
+          tcp_seg_free(cseg);
+        }
+#endif /* TCP_QUEUE_OOSEQ */
+
+
+        /* Acknowledge the segment(s). */
+        tcp_ack(pcb);
+
+#if LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS
+        if (PCB_ISIPV6(pcb)) {
+          /* Inform neighbor reachability of forward progress. */
+          nd6_reachability_hint(ip6_current_src_addr());
+        }
+#endif /* LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS*/
+
+      } else {
+        /* We get here if the incoming segment is out-of-sequence. */
+        tcp_send_empty_ack(pcb);
+#if TCP_QUEUE_OOSEQ
+        /* We queue the segment on the ->ooseq queue. */
+        if (pcb->ooseq == NULL) {
+          pcb->ooseq = tcp_seg_copy(&inseg);
+        } else {
+          /* If the queue is not empty, we walk through the queue and
+             try to find a place where the sequence number of the
+             incoming segment is between the sequence numbers of the
+             previous and the next segment on the ->ooseq queue. That is
+             the place where we put the incoming segment. If needed, we
+             trim the second edges of the previous and the incoming
+             segment so that it will fit into the sequence.
+
+             If the incoming segment has the same sequence number as a
+             segment on the ->ooseq queue, we discard the segment that
+             contains less data. */
+
+          prev = NULL;
+          for(next = pcb->ooseq; next != NULL; next = next->next) {
+            if (seqno == next->tcphdr->seqno) {
+              /* The sequence number of the incoming segment is the
+                 same as the sequence number of the segment on
+                 ->ooseq. We check the lengths to see which one to
+                 discard. */
+              if (inseg.len > next->len) {
+                /* The incoming segment is larger than the old
+                   segment. We replace some segments with the new
+                   one. */
+                cseg = tcp_seg_copy(&inseg);
+                if (cseg != NULL) {
+                  if (prev != NULL) {
+                    prev->next = cseg;
+                  } else {
+                    pcb->ooseq = cseg;
+                  }
+                  tcp_oos_insert_segment(cseg, next);
+                }
+                break;
+              } else {
+                /* Either the lenghts are the same or the incoming
+                   segment was smaller than the old one; in either
+                   case, we ditch the incoming segment. */
+                break;
+              }
+            } else {
+              if (prev == NULL) {
+                if (TCP_SEQ_LT(seqno, next->tcphdr->seqno)) {
+                  /* The sequence number of the incoming segment is lower
+                     than the sequence number of the first segment on the
+                     queue. We put the incoming segment first on the
+                     queue. */
+                  cseg = tcp_seg_copy(&inseg);
+                  if (cseg != NULL) {
+                    pcb->ooseq = cseg;
+                    tcp_oos_insert_segment(cseg, next);
+                  }
+                  break;
+                }
+              } else {
+                /*if (TCP_SEQ_LT(prev->tcphdr->seqno, seqno) &&
+                  TCP_SEQ_LT(seqno, next->tcphdr->seqno)) {*/
+                if (TCP_SEQ_BETWEEN(seqno, prev->tcphdr->seqno+1, next->tcphdr->seqno-1)) {
+                  /* The sequence number of the incoming segment is in
+                     between the sequence numbers of the previous and
+                     the next segment on ->ooseq. We trim trim the previous
+                     segment, delete next segments that included in received segment
+                     and trim received, if needed. */
+                  cseg = tcp_seg_copy(&inseg);
+                  if (cseg != NULL) {
+                    if (TCP_SEQ_GT(prev->tcphdr->seqno + prev->len, seqno)) {
+                      /* We need to trim the prev segment. */
+                      prev->len = (u16_t)(seqno - prev->tcphdr->seqno);
+                      pbuf_realloc(prev->p, prev->len);
+                    }
+                    prev->next = cseg;
+                    tcp_oos_insert_segment(cseg, next);
+                  }
+                  break;
+                }
+              }
+              /* If the "next" segment is the last segment on the
+                 ooseq queue, we add the incoming segment to the end
+                 of the list. */
+              if (next->next == NULL &&
+                  TCP_SEQ_GT(seqno, next->tcphdr->seqno)) {
+                if (TCPH_FLAGS(next->tcphdr) & TCP_FIN) {
+                  /* segment "next" already contains all data */
+                  break;
+                }
+                next->next = tcp_seg_copy(&inseg);
+                if (next->next != NULL) {
+                  if (TCP_SEQ_GT(next->tcphdr->seqno + next->len, seqno)) {
+                    /* We need to trim the last segment. */
+                    next->len = (u16_t)(seqno - next->tcphdr->seqno);
+                    pbuf_realloc(next->p, next->len);
+                  }
+                  /* check if the remote side overruns our receive window */
+                  if ((u32_t)tcplen + seqno > pcb->rcv_nxt + (u32_t)pcb->rcv_wnd) {
+                    LWIP_DEBUGF(TCP_INPUT_DEBUG, 
+                                ("tcp_receive: other end overran receive window"
+                                 "seqno %"U32_F" len %"U16_F" right edge %"U32_F"\n",
+                                 seqno, tcplen, pcb->rcv_nxt + pcb->rcv_wnd));
+                    if (TCPH_FLAGS(next->next->tcphdr) & TCP_FIN) {
+                      /* Must remove the FIN from the header as we're trimming 
+                       * that byte of sequence-space from the packet */
+                      TCPH_FLAGS_SET(next->next->tcphdr, TCPH_FLAGS(next->next->tcphdr) &~ TCP_FIN);
+                    }
+                    /* Adjust length of segment to fit in the window. */
+                    next->next->len = pcb->rcv_nxt + pcb->rcv_wnd - seqno;
+                    pbuf_realloc(next->next->p, next->next->len);
+                    tcplen = TCP_TCPLEN(next->next);
+                    LWIP_ASSERT("tcp_receive: segment not trimmed correctly to rcv_wnd\n",
+                                (seqno + tcplen) == (pcb->rcv_nxt + pcb->rcv_wnd));
+                  }
+                }
+                break;
+              }
+            }
+            prev = next;
+          }
+        }
+#if TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS
+        /* Check that the data on ooseq doesn't exceed one of the limits
+           and throw away everything above that limit. */
+        ooseq_blen = 0;
+        ooseq_qlen = 0;
+        prev = NULL;
+        for(next = pcb->ooseq; next != NULL; prev = next, next = next->next) {
+          struct pbuf *p = next->p;
+          ooseq_blen += p->tot_len;
+          ooseq_qlen += pbuf_clen(p);
+          if ((ooseq_blen > TCP_OOSEQ_MAX_BYTES) ||
+              (ooseq_qlen > TCP_OOSEQ_MAX_PBUFS)) {
+             /* too much ooseq data, dump this and everything after it */
+             tcp_segs_free(next);
+             if (prev == NULL) {
+               /* first ooseq segment is too much, dump the whole queue */
+               pcb->ooseq = NULL;
+             } else {
+               /* just dump 'next' and everything after it */
+               prev->next = NULL;
+             }
+             break;
+          }
+        }
+#endif /* TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS */
+#endif /* TCP_QUEUE_OOSEQ */
+      }
+    } else {
+      /* The incoming segment is not withing the window. */
+      tcp_send_empty_ack(pcb);
+    }
+  } else {
+    /* Segments with length 0 is taken care of here. Segments that
+       fall out of the window are ACKed. */
+    /*if (TCP_SEQ_GT(pcb->rcv_nxt, seqno) ||
+      TCP_SEQ_GEQ(seqno, pcb->rcv_nxt + pcb->rcv_wnd)) {*/
+    if(!TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd-1)){
+      tcp_ack_now(pcb);
+    }
+  }
+}
+
+/**
+ * Parses the options contained in the incoming segment. 
+ *
+ * Called from tcp_listen_input() and tcp_process().
+ * Currently, only the MSS option is supported!
+ *
+ * @param pcb the tcp_pcb for which a segment arrived
+ */
+static void
+tcp_parseopt(struct tcp_pcb *pcb)
+{
+  u16_t c, max_c;
+  u16_t mss;
+  u8_t *opts, opt;
+#if LWIP_TCP_TIMESTAMPS
+  u32_t tsval;
+#endif
+
+  opts = (u8_t *)tcphdr + TCP_HLEN;
+
+  /* Parse the TCP MSS option, if present. */
+  if(TCPH_HDRLEN(tcphdr) > 0x5) {
+    max_c = (TCPH_HDRLEN(tcphdr) - 5) << 2;
+    for (c = 0; c < max_c; ) {
+      opt = opts[c];
+      switch (opt) {
+      case 0x00:
+        /* End of options. */
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: EOL\n"));
+        return;
+      case 0x01:
+        /* NOP option. */
+        ++c;
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: NOP\n"));
+        break;
+      case 0x02:
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: MSS\n"));
+        if (opts[c + 1] != 0x04 || c + 0x04 > max_c) {
+          /* Bad length */
+          LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
+          return;
+        }
+        /* An MSS option with the right option length. */
+        mss = (opts[c + 2] << 8) | opts[c + 3];
+        /* Limit the mss to the configured TCP_MSS and prevent division by zero */
+        pcb->mss = ((mss > TCP_MSS) || (mss == 0)) ? TCP_MSS : mss;
+        /* Advance to next option */
+        c += 0x04;
+        break;
+#if LWIP_TCP_TIMESTAMPS
+      case 0x08:
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: TS\n"));
+        if (opts[c + 1] != 0x0A || c + 0x0A > max_c) {
+          /* Bad length */
+          LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
+          return;
+        }
+        /* TCP timestamp option with valid length */
+        tsval = (opts[c+2]) | (opts[c+3] << 8) | 
+          (opts[c+4] << 16) | (opts[c+5] << 24);
+        if (flags & TCP_SYN) {
+          pcb->ts_recent = ntohl(tsval);
+          pcb->flags |= TF_TIMESTAMP;
+        } else if (TCP_SEQ_BETWEEN(pcb->ts_lastacksent, seqno, seqno+tcplen)) {
+          pcb->ts_recent = ntohl(tsval);
+        }
+        /* Advance to next option */
+        c += 0x0A;
+        break;
+#endif
+      default:
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: other\n"));
+        if (opts[c + 1] == 0) {
+          LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
+          /* If the length field is zero, the options are malformed
+             and we don't process them further. */
+          return;
+        }
+        /* All other options have a length field, so that we easily
+           can skip past them. */
+        c += opts[c + 1];
+      }
+    }
+  }
+}
+
+#endif /* LWIP_TCP */
diff --git a/external/badvpn_dns/lwip/src/core/tcp_out.c b/external/badvpn_dns/lwip/src/core/tcp_out.c
new file mode 100644
index 0000000..b9fc339
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/tcp_out.c
@@ -0,0 +1,1499 @@
+/**
+ * @file
+ * Transmission Control Protocol, outgoing traffic
+ *
+ * The output functions of TCP.
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/tcp_impl.h"
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/stats.h"
+#include "lwip/snmp.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/inet_chksum.h"
+#if LWIP_TCP_TIMESTAMPS
+#include "lwip/sys.h"
+#endif
+
+#include <string.h>
+
+/* Define some copy-macros for checksum-on-copy so that the code looks
+   nicer by preventing too many ifdef's. */
+#if TCP_CHECKSUM_ON_COPY
+#define TCP_DATA_COPY(dst, src, len, seg) do { \
+  tcp_seg_add_chksum(LWIP_CHKSUM_COPY(dst, src, len), \
+                     len, &seg->chksum, &seg->chksum_swapped); \
+  seg->flags |= TF_SEG_DATA_CHECKSUMMED; } while(0)
+#define TCP_DATA_COPY2(dst, src, len, chksum, chksum_swapped)  \
+  tcp_seg_add_chksum(LWIP_CHKSUM_COPY(dst, src, len), len, chksum, chksum_swapped);
+#else /* TCP_CHECKSUM_ON_COPY*/
+#define TCP_DATA_COPY(dst, src, len, seg)                     MEMCPY(dst, src, len)
+#define TCP_DATA_COPY2(dst, src, len, chksum, chksum_swapped) MEMCPY(dst, src, len)
+#endif /* TCP_CHECKSUM_ON_COPY*/
+
+/** Define this to 1 for an extra check that the output checksum is valid
+ * (usefule when the checksum is generated by the application, not the stack) */
+#ifndef TCP_CHECKSUM_ON_COPY_SANITY_CHECK
+#define TCP_CHECKSUM_ON_COPY_SANITY_CHECK   0
+#endif
+
+/* Forward declarations.*/
+static void tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb);
+
+/** Allocate a pbuf and create a tcphdr at p->payload, used for output
+ * functions other than the default tcp_output -> tcp_output_segment
+ * (e.g. tcp_send_empty_ack, etc.)
+ *
+ * @param pcb tcp pcb for which to send a packet (used to initialize tcp_hdr)
+ * @param optlen length of header-options
+ * @param datalen length of tcp data to reserve in pbuf
+ * @param seqno_be seqno in network byte order (big-endian)
+ * @return pbuf with p->payload being the tcp_hdr
+ */
+static struct pbuf *
+tcp_output_alloc_header(struct tcp_pcb *pcb, u16_t optlen, u16_t datalen,
+                      u32_t seqno_be /* already in network byte order */)
+{
+  struct tcp_hdr *tcphdr;
+  struct pbuf *p = pbuf_alloc(PBUF_IP, TCP_HLEN + optlen + datalen, PBUF_RAM);
+  if (p != NULL) {
+    LWIP_ASSERT("check that first pbuf can hold struct tcp_hdr",
+                 (p->len >= TCP_HLEN + optlen));
+    tcphdr = (struct tcp_hdr *)p->payload;
+    tcphdr->src = htons(pcb->local_port);
+    tcphdr->dest = htons(pcb->remote_port);
+    tcphdr->seqno = seqno_be;
+    tcphdr->ackno = htonl(pcb->rcv_nxt);
+    TCPH_HDRLEN_FLAGS_SET(tcphdr, (5 + optlen / 4), TCP_ACK);
+    tcphdr->wnd = htons(pcb->rcv_ann_wnd);
+    tcphdr->chksum = 0;
+    tcphdr->urgp = 0;
+
+    /* If we're sending a packet, update the announced right window edge */
+    pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;
+  }
+  return p;
+}
+
+/**
+ * Called by tcp_close() to send a segment including FIN flag but not data.
+ *
+ * @param pcb the tcp_pcb over which to send a segment
+ * @return ERR_OK if sent, another err_t otherwise
+ */
+err_t
+tcp_send_fin(struct tcp_pcb *pcb)
+{
+  /* first, try to add the fin to the last unsent segment */
+  if (pcb->unsent != NULL) {
+    struct tcp_seg *last_unsent;
+    for (last_unsent = pcb->unsent; last_unsent->next != NULL;
+         last_unsent = last_unsent->next);
+
+    if ((TCPH_FLAGS(last_unsent->tcphdr) & (TCP_SYN | TCP_FIN | TCP_RST)) == 0) {
+      /* no SYN/FIN/RST flag in the header, we can add the FIN flag */
+      TCPH_SET_FLAG(last_unsent->tcphdr, TCP_FIN);
+      pcb->flags |= TF_FIN;
+      return ERR_OK;
+    }
+  }
+  /* no data, no length, flags, copy=1, no optdata */
+  return tcp_enqueue_flags(pcb, TCP_FIN);
+}
+
+/**
+ * Create a TCP segment with prefilled header.
+ *
+ * Called by tcp_write and tcp_enqueue_flags.
+ *
+ * @param pcb Protocol control block for the TCP connection.
+ * @param p pbuf that is used to hold the TCP header.
+ * @param flags TCP flags for header.
+ * @param seqno TCP sequence number of this packet
+ * @param optflags options to include in TCP header
+ * @return a new tcp_seg pointing to p, or NULL.
+ * The TCP header is filled in except ackno and wnd.
+ * p is freed on failure.
+ */
+static struct tcp_seg *
+tcp_create_segment(struct tcp_pcb *pcb, struct pbuf *p, u8_t flags, u32_t seqno, u8_t optflags)
+{
+  struct tcp_seg *seg;
+  u8_t optlen = LWIP_TCP_OPT_LENGTH(optflags);
+
+  if ((seg = (struct tcp_seg *)memp_malloc(MEMP_TCP_SEG)) == NULL) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_create_segment: no memory.\n"));
+    pbuf_free(p);
+    return NULL;
+  }
+  seg->flags = optflags;
+  seg->next = NULL;
+  seg->p = p;
+  seg->len = p->tot_len - optlen;
+#if TCP_OVERSIZE_DBGCHECK
+  seg->oversize_left = 0;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+#if TCP_CHECKSUM_ON_COPY
+  seg->chksum = 0;
+  seg->chksum_swapped = 0;
+  /* check optflags */
+  LWIP_ASSERT("invalid optflags passed: TF_SEG_DATA_CHECKSUMMED",
+              (optflags & TF_SEG_DATA_CHECKSUMMED) == 0);
+#endif /* TCP_CHECKSUM_ON_COPY */
+
+  /* build TCP header */
+  if (pbuf_header(p, TCP_HLEN)) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_create_segment: no room for TCP header in pbuf.\n"));
+    TCP_STATS_INC(tcp.err);
+    tcp_seg_free(seg);
+    return NULL;
+  }
+  seg->tcphdr = (struct tcp_hdr *)seg->p->payload;
+  seg->tcphdr->src = htons(pcb->local_port);
+  seg->tcphdr->dest = htons(pcb->remote_port);
+  seg->tcphdr->seqno = htonl(seqno);
+  /* ackno is set in tcp_output */
+  TCPH_HDRLEN_FLAGS_SET(seg->tcphdr, (5 + optlen / 4), flags);
+  /* wnd and chksum are set in tcp_output */
+  seg->tcphdr->urgp = 0;
+  return seg;
+} 
+
+/**
+ * Allocate a PBUF_RAM pbuf, perhaps with extra space at the end.
+ *
+ * This function is like pbuf_alloc(layer, length, PBUF_RAM) except
+ * there may be extra bytes available at the end.
+ *
+ * @param layer flag to define header size.
+ * @param length size of the pbuf's payload.
+ * @param max_length maximum usable size of payload+oversize.
+ * @param oversize pointer to a u16_t that will receive the number of usable tail bytes.
+ * @param pcb The TCP connection that willo enqueue the pbuf.
+ * @param apiflags API flags given to tcp_write.
+ * @param first_seg true when this pbuf will be used in the first enqueued segment.
+ * @param 
+ */
+#if TCP_OVERSIZE
+static struct pbuf *
+tcp_pbuf_prealloc(pbuf_layer layer, u16_t length, u16_t max_length,
+                  u16_t *oversize, struct tcp_pcb *pcb, u8_t apiflags,
+                  u8_t first_seg)
+{
+  struct pbuf *p;
+  u16_t alloc = length;
+
+#if LWIP_NETIF_TX_SINGLE_PBUF
+  LWIP_UNUSED_ARG(max_length);
+  LWIP_UNUSED_ARG(pcb);
+  LWIP_UNUSED_ARG(apiflags);
+  LWIP_UNUSED_ARG(first_seg);
+  /* always create MSS-sized pbufs */
+  alloc = max_length;
+#else /* LWIP_NETIF_TX_SINGLE_PBUF */
+  if (length < max_length) {
+    /* Should we allocate an oversized pbuf, or just the minimum
+     * length required? If tcp_write is going to be called again
+     * before this segment is transmitted, we want the oversized
+     * buffer. If the segment will be transmitted immediately, we can
+     * save memory by allocating only length. We use a simple
+     * heuristic based on the following information:
+     *
+     * Did the user set TCP_WRITE_FLAG_MORE?
+     *
+     * Will the Nagle algorithm defer transmission of this segment?
+     */
+    if ((apiflags & TCP_WRITE_FLAG_MORE) ||
+        (!(pcb->flags & TF_NODELAY) &&
+         (!first_seg ||
+          pcb->unsent != NULL ||
+          pcb->unacked != NULL))) {
+      alloc = LWIP_MIN(max_length, LWIP_MEM_ALIGN_SIZE(length + TCP_OVERSIZE));
+    }
+  }
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+  p = pbuf_alloc(layer, alloc, PBUF_RAM);
+  if (p == NULL) {
+    return NULL;
+  }
+  LWIP_ASSERT("need unchained pbuf", p->next == NULL);
+  *oversize = p->len - length;
+  /* trim p->len to the currently used size */
+  p->len = p->tot_len = length;
+  return p;
+}
+#else /* TCP_OVERSIZE */
+#define tcp_pbuf_prealloc(layer, length, mx, os, pcb, api, fst) pbuf_alloc((layer), (length), PBUF_RAM)
+#endif /* TCP_OVERSIZE */
+
+#if TCP_CHECKSUM_ON_COPY
+/** Add a checksum of newly added data to the segment */
+static void
+tcp_seg_add_chksum(u16_t chksum, u16_t len, u16_t *seg_chksum,
+                   u8_t *seg_chksum_swapped)
+{
+  u32_t helper;
+  /* add chksum to old chksum and fold to u16_t */
+  helper = chksum + *seg_chksum;
+  chksum = FOLD_U32T(helper);
+  if ((len & 1) != 0) {
+    *seg_chksum_swapped = 1 - *seg_chksum_swapped;
+    chksum = SWAP_BYTES_IN_WORD(chksum);
+  }
+  *seg_chksum = chksum;
+}
+#endif /* TCP_CHECKSUM_ON_COPY */
+
+/** Checks if tcp_write is allowed or not (checks state, snd_buf and snd_queuelen).
+ *
+ * @param pcb the tcp pcb to check for
+ * @param len length of data to send (checked agains snd_buf)
+ * @return ERR_OK if tcp_write is allowed to proceed, another err_t otherwise
+ */
+static err_t
+tcp_write_checks(struct tcp_pcb *pcb, u16_t len)
+{
+  /* connection is in invalid state for data transmission? */
+  if ((pcb->state != ESTABLISHED) &&
+      (pcb->state != CLOSE_WAIT) &&
+      (pcb->state != SYN_SENT) &&
+      (pcb->state != SYN_RCVD)) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_STATE | LWIP_DBG_LEVEL_SEVERE, ("tcp_write() called in invalid state\n"));
+    return ERR_CONN;
+  } else if (len == 0) {
+    return ERR_OK;
+  }
+
+  /* fail on too much data */
+  if (len > pcb->snd_buf) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_write: too much data (len=%"U16_F" > snd_buf=%"U16_F")\n",
+      len, pcb->snd_buf));
+    pcb->flags |= TF_NAGLEMEMERR;
+    return ERR_MEM;
+  }
+
+  LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: queuelen: %"U16_F"\n", (u16_t)pcb->snd_queuelen));
+
+  /* If total number of pbufs on the unsent/unacked queues exceeds the
+   * configured maximum, return an error */
+  /* check for configured max queuelen and possible overflow */
+  if ((pcb->snd_queuelen >= TCP_SND_QUEUELEN) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_write: too long queue %"U16_F" (max %"U16_F")\n",
+      pcb->snd_queuelen, TCP_SND_QUEUELEN));
+    TCP_STATS_INC(tcp.memerr);
+    pcb->flags |= TF_NAGLEMEMERR;
+    return ERR_MEM;
+  }
+  if (pcb->snd_queuelen != 0) {
+    LWIP_ASSERT("tcp_write: pbufs on queue => at least one queue non-empty",
+      pcb->unacked != NULL || pcb->unsent != NULL);
+  } else {
+    LWIP_ASSERT("tcp_write: no pbufs on queue => both queues empty",
+      pcb->unacked == NULL && pcb->unsent == NULL);
+  }
+  return ERR_OK;
+}
+
+/**
+ * Write data for sending (but does not send it immediately).
+ *
+ * It waits in the expectation of more data being sent soon (as
+ * it can send them more efficiently by combining them together).
+ * To prompt the system to send data now, call tcp_output() after
+ * calling tcp_write().
+ *
+ * @param pcb Protocol control block for the TCP connection to enqueue data for.
+ * @param arg Pointer to the data to be enqueued for sending.
+ * @param len Data length in bytes
+ * @param apiflags combination of following flags :
+ * - TCP_WRITE_FLAG_COPY (0x01) data will be copied into memory belonging to the stack
+ * - TCP_WRITE_FLAG_MORE (0x02) for TCP connection, PSH flag will be set on last segment sent,
+ * @return ERR_OK if enqueued, another err_t on error
+ */
+err_t
+tcp_write(struct tcp_pcb *pcb, const void *arg, u16_t len, u8_t apiflags)
+{
+  struct pbuf *concat_p = NULL;
+  struct tcp_seg *last_unsent = NULL, *seg = NULL, *prev_seg = NULL, *queue = NULL;
+  u16_t pos = 0; /* position in 'arg' data */
+  u16_t queuelen;
+  u8_t optlen = 0;
+  u8_t optflags = 0;
+#if TCP_OVERSIZE
+  u16_t oversize = 0;
+  u16_t oversize_used = 0;
+#endif /* TCP_OVERSIZE */
+#if TCP_CHECKSUM_ON_COPY
+  u16_t concat_chksum = 0;
+  u8_t concat_chksum_swapped = 0;
+  u16_t concat_chksummed = 0;
+#endif /* TCP_CHECKSUM_ON_COPY */
+  err_t err;
+  /* don't allocate segments bigger than half the maximum window we ever received */
+  u16_t mss_local = LWIP_MIN(pcb->mss, pcb->snd_wnd_max/2);
+
+#if LWIP_NETIF_TX_SINGLE_PBUF
+  /* Always copy to try to create single pbufs for TX */
+  apiflags |= TCP_WRITE_FLAG_COPY;
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+
+  LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_write(pcb=%p, data=%p, len=%"U16_F", apiflags=%"U16_F")\n",
+    (void *)pcb, arg, len, (u16_t)apiflags));
+  LWIP_ERROR("tcp_write: arg == NULL (programmer violates API)", 
+             arg != NULL, return ERR_ARG;);
+
+  err = tcp_write_checks(pcb, len);
+  if (err != ERR_OK) {
+    return err;
+  }
+  queuelen = pcb->snd_queuelen;
+
+#if LWIP_TCP_TIMESTAMPS
+  if ((pcb->flags & TF_TIMESTAMP)) {
+    optflags = TF_SEG_OPTS_TS;
+    optlen = LWIP_TCP_OPT_LENGTH(TF_SEG_OPTS_TS);
+  }
+#endif /* LWIP_TCP_TIMESTAMPS */
+
+
+  /*
+   * TCP segmentation is done in three phases with increasing complexity:
+   *
+   * 1. Copy data directly into an oversized pbuf.
+   * 2. Chain a new pbuf to the end of pcb->unsent.
+   * 3. Create new segments.
+   *
+   * We may run out of memory at any point. In that case we must
+   * return ERR_MEM and not change anything in pcb. Therefore, all
+   * changes are recorded in local variables and committed at the end
+   * of the function. Some pcb fields are maintained in local copies:
+   *
+   * queuelen = pcb->snd_queuelen
+   * oversize = pcb->unsent_oversize
+   *
+   * These variables are set consistently by the phases:
+   *
+   * seg points to the last segment tampered with.
+   *
+   * pos records progress as data is segmented.
+   */
+
+  /* Find the tail of the unsent queue. */
+  if (pcb->unsent != NULL) {
+    u16_t space;
+    u16_t unsent_optlen;
+
+    /* @todo: this could be sped up by keeping last_unsent in the pcb */
+    for (last_unsent = pcb->unsent; last_unsent->next != NULL;
+         last_unsent = last_unsent->next);
+
+    /* Usable space at the end of the last unsent segment */
+    unsent_optlen = LWIP_TCP_OPT_LENGTH(last_unsent->flags);
+    space = mss_local - (last_unsent->len + unsent_optlen);
+
+    /*
+     * Phase 1: Copy data directly into an oversized pbuf.
+     *
+     * The number of bytes copied is recorded in the oversize_used
+     * variable. The actual copying is done at the bottom of the
+     * function.
+     */
+#if TCP_OVERSIZE
+#if TCP_OVERSIZE_DBGCHECK
+    /* check that pcb->unsent_oversize matches last_unsent->unsent_oversize */
+    LWIP_ASSERT("unsent_oversize mismatch (pcb vs. last_unsent)",
+                pcb->unsent_oversize == last_unsent->oversize_left);
+#endif /* TCP_OVERSIZE_DBGCHECK */
+    oversize = pcb->unsent_oversize;
+    if (oversize > 0) {
+      LWIP_ASSERT("inconsistent oversize vs. space", oversize_used <= space);
+      seg = last_unsent;
+      oversize_used = oversize < len ? oversize : len;
+      pos += oversize_used;
+      oversize -= oversize_used;
+      space -= oversize_used;
+    }
+    /* now we are either finished or oversize is zero */
+    LWIP_ASSERT("inconsistend oversize vs. len", (oversize == 0) || (pos == len));
+#endif /* TCP_OVERSIZE */
+
+    /*
+     * Phase 2: Chain a new pbuf to the end of pcb->unsent.
+     *
+     * We don't extend segments containing SYN/FIN flags or options
+     * (len==0). The new pbuf is kept in concat_p and pbuf_cat'ed at
+     * the end.
+     */
+    if ((pos < len) && (space > 0) && (last_unsent->len > 0)) {
+      u16_t seglen = space < len - pos ? space : len - pos;
+      seg = last_unsent;
+
+      /* Create a pbuf with a copy or reference to seglen bytes. We
+       * can use PBUF_RAW here since the data appears in the middle of
+       * a segment. A header will never be prepended. */
+      if (apiflags & TCP_WRITE_FLAG_COPY) {
+        /* Data is copied */
+        if ((concat_p = tcp_pbuf_prealloc(PBUF_RAW, seglen, space, &oversize, pcb, apiflags, 1)) == NULL) {
+          LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
+                      ("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n",
+                       seglen));
+          goto memerr;
+        }
+#if TCP_OVERSIZE_DBGCHECK
+        last_unsent->oversize_left += oversize;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+        TCP_DATA_COPY2(concat_p->payload, (u8_t*)arg + pos, seglen, &concat_chksum, &concat_chksum_swapped);
+#if TCP_CHECKSUM_ON_COPY
+        concat_chksummed += seglen;
+#endif /* TCP_CHECKSUM_ON_COPY */
+      } else {
+        /* Data is not copied */
+        if ((concat_p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) {
+          LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
+                      ("tcp_write: could not allocate memory for zero-copy pbuf\n"));
+          goto memerr;
+        }
+#if TCP_CHECKSUM_ON_COPY
+        /* calculate the checksum of nocopy-data */
+        tcp_seg_add_chksum(~inet_chksum((u8_t*)arg + pos, seglen), seglen,
+          &concat_chksum, &concat_chksum_swapped);
+        concat_chksummed += seglen;
+#endif /* TCP_CHECKSUM_ON_COPY */
+        /* reference the non-volatile payload data */
+        concat_p->payload = (u8_t*)arg + pos;
+      }
+
+      pos += seglen;
+      queuelen += pbuf_clen(concat_p);
+    }
+  } else {
+#if TCP_OVERSIZE
+    LWIP_ASSERT("unsent_oversize mismatch (pcb->unsent is NULL)",
+                pcb->unsent_oversize == 0);
+#endif /* TCP_OVERSIZE */
+  }
+
+  /*
+   * Phase 3: Create new segments.
+   *
+   * The new segments are chained together in the local 'queue'
+   * variable, ready to be appended to pcb->unsent.
+   */
+  while (pos < len) {
+    struct pbuf *p;
+    u16_t left = len - pos;
+    u16_t max_len = mss_local - optlen;
+    u16_t seglen = left > max_len ? max_len : left;
+#if TCP_CHECKSUM_ON_COPY
+    u16_t chksum = 0;
+    u8_t chksum_swapped = 0;
+#endif /* TCP_CHECKSUM_ON_COPY */
+
+    if (apiflags & TCP_WRITE_FLAG_COPY) {
+      /* If copy is set, memory should be allocated and data copied
+       * into pbuf */
+      if ((p = tcp_pbuf_prealloc(PBUF_TRANSPORT, seglen + optlen, mss_local, &oversize, pcb, apiflags, queue == NULL)) == NULL) {
+        LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n", seglen));
+        goto memerr;
+      }
+      LWIP_ASSERT("tcp_write: check that first pbuf can hold the complete seglen",
+                  (p->len >= seglen));
+      TCP_DATA_COPY2((char *)p->payload + optlen, (u8_t*)arg + pos, seglen, &chksum, &chksum_swapped);
+    } else {
+      /* Copy is not set: First allocate a pbuf for holding the data.
+       * Since the referenced data is available at least until it is
+       * sent out on the link (as it has to be ACKed by the remote
+       * party) we can safely use PBUF_ROM instead of PBUF_REF here.
+       */
+      struct pbuf *p2;
+#if TCP_OVERSIZE
+      LWIP_ASSERT("oversize == 0", oversize == 0);
+#endif /* TCP_OVERSIZE */
+      if ((p2 = pbuf_alloc(PBUF_TRANSPORT, seglen, PBUF_ROM)) == NULL) {
+        LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write: could not allocate memory for zero-copy pbuf\n"));
+        goto memerr;
+      }
+#if TCP_CHECKSUM_ON_COPY
+      /* calculate the checksum of nocopy-data */
+      chksum = ~inet_chksum((u8_t*)arg + pos, seglen);
+#endif /* TCP_CHECKSUM_ON_COPY */
+      /* reference the non-volatile payload data */
+      p2->payload = (u8_t*)arg + pos;
+
+      /* Second, allocate a pbuf for the headers. */
+      if ((p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
+        /* If allocation fails, we have to deallocate the data pbuf as
+         * well. */
+        pbuf_free(p2);
+        LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write: could not allocate memory for header pbuf\n"));
+        goto memerr;
+      }
+      /* Concatenate the headers and data pbufs together. */
+      pbuf_cat(p/*header*/, p2/*data*/);
+    }
+
+    queuelen += pbuf_clen(p);
+
+    /* Now that there are more segments queued, we check again if the
+     * length of the queue exceeds the configured maximum or
+     * overflows. */
+    if ((queuelen > TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
+      LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write: queue too long %"U16_F" (%"U16_F")\n", queuelen, TCP_SND_QUEUELEN));
+      pbuf_free(p);
+      goto memerr;
+    }
+
+    if ((seg = tcp_create_segment(pcb, p, 0, pcb->snd_lbb + pos, optflags)) == NULL) {
+      goto memerr;
+    }
+#if TCP_OVERSIZE_DBGCHECK
+    seg->oversize_left = oversize;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+#if TCP_CHECKSUM_ON_COPY
+    seg->chksum = chksum;
+    seg->chksum_swapped = chksum_swapped;
+    seg->flags |= TF_SEG_DATA_CHECKSUMMED;
+#endif /* TCP_CHECKSUM_ON_COPY */
+
+    /* first segment of to-be-queued data? */
+    if (queue == NULL) {
+      queue = seg;
+    } else {
+      /* Attach the segment to the end of the queued segments */
+      LWIP_ASSERT("prev_seg != NULL", prev_seg != NULL);
+      prev_seg->next = seg;
+    }
+    /* remember last segment of to-be-queued data for next iteration */
+    prev_seg = seg;
+
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE, ("tcp_write: queueing %"U32_F":%"U32_F"\n",
+      ntohl(seg->tcphdr->seqno),
+      ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg)));
+
+    pos += seglen;
+  }
+
+  /*
+   * All three segmentation phases were successful. We can commit the
+   * transaction.
+   */
+
+  /*
+   * Phase 1: If data has been added to the preallocated tail of
+   * last_unsent, we update the length fields of the pbuf chain.
+   */
+#if TCP_OVERSIZE
+  if (oversize_used > 0) {
+    struct pbuf *p;
+    /* Bump tot_len of whole chain, len of tail */
+    for (p = last_unsent->p; p; p = p->next) {
+      p->tot_len += oversize_used;
+      if (p->next == NULL) {
+        TCP_DATA_COPY((char *)p->payload + p->len, arg, oversize_used, last_unsent);
+        p->len += oversize_used;
+      }
+    }
+    last_unsent->len += oversize_used;
+#if TCP_OVERSIZE_DBGCHECK
+    LWIP_ASSERT("last_unsent->oversize_left >= oversize_used",
+                last_unsent->oversize_left >= oversize_used);
+    last_unsent->oversize_left -= oversize_used;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+  }
+  pcb->unsent_oversize = oversize;
+#endif /* TCP_OVERSIZE */
+
+  /*
+   * Phase 2: concat_p can be concatenated onto last_unsent->p
+   */
+  if (concat_p != NULL) {
+    LWIP_ASSERT("tcp_write: cannot concatenate when pcb->unsent is empty",
+      (last_unsent != NULL));
+    pbuf_cat(last_unsent->p, concat_p);
+    last_unsent->len += concat_p->tot_len;
+#if TCP_CHECKSUM_ON_COPY
+    if (concat_chksummed) {
+      tcp_seg_add_chksum(concat_chksum, concat_chksummed, &last_unsent->chksum,
+        &last_unsent->chksum_swapped);
+      last_unsent->flags |= TF_SEG_DATA_CHECKSUMMED;
+    }
+#endif /* TCP_CHECKSUM_ON_COPY */
+  }
+
+  /*
+   * Phase 3: Append queue to pcb->unsent. Queue may be NULL, but that
+   * is harmless
+   */
+  if (last_unsent == NULL) {
+    pcb->unsent = queue;
+  } else {
+    last_unsent->next = queue;
+  }
+
+  /*
+   * Finally update the pcb state.
+   */
+  pcb->snd_lbb += len;
+  pcb->snd_buf -= len;
+  pcb->snd_queuelen = queuelen;
+
+  LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: %"S16_F" (after enqueued)\n",
+    pcb->snd_queuelen));
+  if (pcb->snd_queuelen != 0) {
+    LWIP_ASSERT("tcp_write: valid queue length",
+                pcb->unacked != NULL || pcb->unsent != NULL);
+  }
+
+  /* Set the PSH flag in the last segment that we enqueued. */
+  if (seg != NULL && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE)==0)) {
+    TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);
+  }
+
+  return ERR_OK;
+memerr:
+  pcb->flags |= TF_NAGLEMEMERR;
+  TCP_STATS_INC(tcp.memerr);
+
+  if (concat_p != NULL) {
+    pbuf_free(concat_p);
+  }
+  if (queue != NULL) {
+    tcp_segs_free(queue);
+  }
+  if (pcb->snd_queuelen != 0) {
+    LWIP_ASSERT("tcp_write: valid queue length", pcb->unacked != NULL ||
+      pcb->unsent != NULL);
+  }
+  LWIP_DEBUGF(TCP_QLEN_DEBUG | LWIP_DBG_STATE, ("tcp_write: %"S16_F" (with mem err)\n", pcb->snd_queuelen));
+  return ERR_MEM;
+}
+
+/**
+ * Enqueue TCP options for transmission.
+ *
+ * Called by tcp_connect(), tcp_listen_input(), and tcp_send_ctrl().
+ *
+ * @param pcb Protocol control block for the TCP connection.
+ * @param flags TCP header flags to set in the outgoing segment.
+ * @param optdata pointer to TCP options, or NULL.
+ * @param optlen length of TCP options in bytes.
+ */
+err_t
+tcp_enqueue_flags(struct tcp_pcb *pcb, u8_t flags)
+{
+  struct pbuf *p;
+  struct tcp_seg *seg;
+  u8_t optflags = 0;
+  u8_t optlen = 0;
+
+  LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue_flags: queuelen: %"U16_F"\n", (u16_t)pcb->snd_queuelen));
+
+  LWIP_ASSERT("tcp_enqueue_flags: need either TCP_SYN or TCP_FIN in flags (programmer violates API)",
+              (flags & (TCP_SYN | TCP_FIN)) != 0);
+
+  /* check for configured max queuelen and possible overflow */
+  if ((pcb->snd_queuelen >= TCP_SND_QUEUELEN) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_enqueue_flags: too long queue %"U16_F" (max %"U16_F")\n",
+                                       pcb->snd_queuelen, TCP_SND_QUEUELEN));
+    TCP_STATS_INC(tcp.memerr);
+    pcb->flags |= TF_NAGLEMEMERR;
+    return ERR_MEM;
+  }
+
+  if (flags & TCP_SYN) {
+    optflags = TF_SEG_OPTS_MSS;
+  }
+#if LWIP_TCP_TIMESTAMPS
+  if ((pcb->flags & TF_TIMESTAMP)) {
+    optflags |= TF_SEG_OPTS_TS;
+  }
+#endif /* LWIP_TCP_TIMESTAMPS */
+  optlen = LWIP_TCP_OPT_LENGTH(optflags);
+
+  /* tcp_enqueue_flags is always called with either SYN or FIN in flags.
+   * We need one available snd_buf byte to do that.
+   * This means we can't send FIN while snd_buf==0. A better fix would be to
+   * not include SYN and FIN sequence numbers in the snd_buf count. */
+  if (pcb->snd_buf == 0) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_enqueue_flags: no send buffer available\n"));
+    TCP_STATS_INC(tcp.memerr);
+    return ERR_MEM;
+  }
+
+  /* Allocate pbuf with room for TCP header + options */
+  if ((p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
+    pcb->flags |= TF_NAGLEMEMERR;
+    TCP_STATS_INC(tcp.memerr);
+    return ERR_MEM;
+  }
+  LWIP_ASSERT("tcp_enqueue_flags: check that first pbuf can hold optlen",
+              (p->len >= optlen));
+
+  /* Allocate memory for tcp_seg, and fill in fields. */
+  if ((seg = tcp_create_segment(pcb, p, flags, pcb->snd_lbb, optflags)) == NULL) {
+    pcb->flags |= TF_NAGLEMEMERR;
+    TCP_STATS_INC(tcp.memerr);
+    return ERR_MEM;
+  }
+  LWIP_ASSERT("seg->tcphdr not aligned", ((mem_ptr_t)seg->tcphdr % MEM_ALIGNMENT) == 0);
+  LWIP_ASSERT("tcp_enqueue_flags: invalid segment length", seg->len == 0);
+
+  LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE,
+              ("tcp_enqueue_flags: queueing %"U32_F":%"U32_F" (0x%"X16_F")\n",
+               ntohl(seg->tcphdr->seqno),
+               ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg),
+               (u16_t)flags));
+
+  /* Now append seg to pcb->unsent queue */
+  if (pcb->unsent == NULL) {
+    pcb->unsent = seg;
+  } else {
+    struct tcp_seg *useg;
+    for (useg = pcb->unsent; useg->next != NULL; useg = useg->next);
+    useg->next = seg;
+  }
+#if TCP_OVERSIZE
+  /* The new unsent tail has no space */
+  pcb->unsent_oversize = 0;
+#endif /* TCP_OVERSIZE */
+
+  /* SYN and FIN bump the sequence number */
+  if ((flags & TCP_SYN) || (flags & TCP_FIN)) {
+    pcb->snd_lbb++;
+    /* optlen does not influence snd_buf */
+    pcb->snd_buf--;
+  }
+  if (flags & TCP_FIN) {
+    pcb->flags |= TF_FIN;
+  }
+
+  /* update number of segments on the queues */
+  pcb->snd_queuelen += pbuf_clen(seg->p);
+  LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue_flags: %"S16_F" (after enqueued)\n", pcb->snd_queuelen));
+  if (pcb->snd_queuelen != 0) {
+    LWIP_ASSERT("tcp_enqueue_flags: invalid queue length",
+      pcb->unacked != NULL || pcb->unsent != NULL);
+  }
+
+  return ERR_OK;
+}
+
+#if LWIP_TCP_TIMESTAMPS
+/* Build a timestamp option (12 bytes long) at the specified options pointer)
+ *
+ * @param pcb tcp_pcb
+ * @param opts option pointer where to store the timestamp option
+ */
+static void
+tcp_build_timestamp_option(struct tcp_pcb *pcb, u32_t *opts)
+{
+  /* Pad with two NOP options to make everything nicely aligned */
+  opts[0] = PP_HTONL(0x0101080A);
+  opts[1] = htonl(sys_now());
+  opts[2] = htonl(pcb->ts_recent);
+}
+#endif
+
+/** Send an ACK without data.
+ *
+ * @param pcb Protocol control block for the TCP connection to send the ACK
+ */
+err_t
+tcp_send_empty_ack(struct tcp_pcb *pcb)
+{
+  struct pbuf *p;
+  u8_t optlen = 0;
+#if LWIP_TCP_TIMESTAMPS || CHECKSUM_GEN_TCP
+  struct tcp_hdr *tcphdr;
+#endif /* LWIP_TCP_TIMESTAMPS || CHECKSUM_GEN_TCP */
+
+#if LWIP_TCP_TIMESTAMPS
+  if (pcb->flags & TF_TIMESTAMP) {
+    optlen = LWIP_TCP_OPT_LENGTH(TF_SEG_OPTS_TS);
+  }
+#endif
+
+  p = tcp_output_alloc_header(pcb, optlen, 0, htonl(pcb->snd_nxt));
+  if (p == NULL) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output: (ACK) could not allocate pbuf\n"));
+    return ERR_BUF;
+  }
+#if LWIP_TCP_TIMESTAMPS || CHECKSUM_GEN_TCP
+  tcphdr = (struct tcp_hdr *)p->payload;
+#endif /* LWIP_TCP_TIMESTAMPS || CHECKSUM_GEN_TCP */
+  LWIP_DEBUGF(TCP_OUTPUT_DEBUG, 
+              ("tcp_output: sending ACK for %"U32_F"\n", pcb->rcv_nxt));
+  /* remove ACK flags from the PCB, as we send an empty ACK now */
+  pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
+
+  /* NB. MSS option is only sent on SYNs, so ignore it here */
+#if LWIP_TCP_TIMESTAMPS
+  pcb->ts_lastacksent = pcb->rcv_nxt;
+
+  if (pcb->flags & TF_TIMESTAMP) {
+    tcp_build_timestamp_option(pcb, (u32_t *)(tcphdr + 1));
+  }
+#endif 
+
+#if CHECKSUM_GEN_TCP
+  tcphdr->chksum = ipX_chksum_pseudo(PCB_ISIPV6(pcb), p, IP_PROTO_TCP, p->tot_len,
+    &pcb->local_ip, &pcb->remote_ip);
+#endif
+#if LWIP_NETIF_HWADDRHINT
+  ipX_output_hinted(PCB_ISIPV6(pcb), p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, pcb->tos,
+      IP_PROTO_TCP, &pcb->addr_hint);
+#else /* LWIP_NETIF_HWADDRHINT*/
+  ipX_output(PCB_ISIPV6(pcb), p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, pcb->tos,
+      IP_PROTO_TCP);
+#endif /* LWIP_NETIF_HWADDRHINT*/
+  pbuf_free(p);
+
+  return ERR_OK;
+}
+
+/**
+ * Find out what we can send and send it
+ *
+ * @param pcb Protocol control block for the TCP connection to send data
+ * @return ERR_OK if data has been sent or nothing to send
+ *         another err_t on error
+ */
+err_t
+tcp_output(struct tcp_pcb *pcb)
+{
+  struct tcp_seg *seg, *useg;
+  u32_t wnd, snd_nxt;
+#if TCP_CWND_DEBUG
+  s16_t i = 0;
+#endif /* TCP_CWND_DEBUG */
+
+  /* pcb->state LISTEN not allowed here */
+  LWIP_ASSERT("don't call tcp_output for listen-pcbs",
+    pcb->state != LISTEN);
+
+  /* First, check if we are invoked by the TCP input processing
+     code. If so, we do not output anything. Instead, we rely on the
+     input processing code to call us when input processing is done
+     with. */
+  if (tcp_input_pcb == pcb) {
+    return ERR_OK;
+  }
+
+  wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd);
+
+  seg = pcb->unsent;
+
+  /* If the TF_ACK_NOW flag is set and no data will be sent (either
+   * because the ->unsent queue is empty or because the window does
+   * not allow it), construct an empty ACK segment and send it.
+   *
+   * If data is to be sent, we will just piggyback the ACK (see below).
+   */
+  if (pcb->flags & TF_ACK_NOW &&
+     (seg == NULL ||
+      ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd)) {
+     return tcp_send_empty_ack(pcb);
+  }
+
+  /* useg should point to last segment on unacked queue */
+  useg = pcb->unacked;
+  if (useg != NULL) {
+    for (; useg->next != NULL; useg = useg->next);
+  }
+
+#if TCP_OUTPUT_DEBUG
+  if (seg == NULL) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output: nothing to send (%p)\n",
+                                   (void*)pcb->unsent));
+  }
+#endif /* TCP_OUTPUT_DEBUG */
+#if TCP_CWND_DEBUG
+  if (seg == NULL) {
+    LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"U16_F
+                                 ", cwnd %"U16_F", wnd %"U32_F
+                                 ", seg == NULL, ack %"U32_F"\n",
+                                 pcb->snd_wnd, pcb->cwnd, wnd, pcb->lastack));
+  } else {
+    LWIP_DEBUGF(TCP_CWND_DEBUG, 
+                ("tcp_output: snd_wnd %"U16_F", cwnd %"U16_F", wnd %"U32_F
+                 ", effwnd %"U32_F", seq %"U32_F", ack %"U32_F"\n",
+                 pcb->snd_wnd, pcb->cwnd, wnd,
+                 ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len,
+                 ntohl(seg->tcphdr->seqno), pcb->lastack));
+  }
+#endif /* TCP_CWND_DEBUG */
+  /* data available and window allows it to be sent? */
+  while (seg != NULL &&
+         ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd) {
+    LWIP_ASSERT("RST not expected here!", 
+                (TCPH_FLAGS(seg->tcphdr) & TCP_RST) == 0);
+    /* Stop sending if the nagle algorithm would prevent it
+     * Don't stop:
+     * - if tcp_write had a memory error before (prevent delayed ACK timeout) or
+     * - if FIN was already enqueued for this PCB (SYN is always alone in a segment -
+     *   either seg->next != NULL or pcb->unacked == NULL;
+     *   RST is no sent using tcp_write/tcp_output.
+     */
+    if((tcp_do_output_nagle(pcb) == 0) &&
+      ((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)){
+      break;
+    }
+#if TCP_CWND_DEBUG
+    LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"U16_F", cwnd %"U16_F", wnd %"U32_F", effwnd %"U32_F", seq %"U32_F", ack %"U32_F", i %"S16_F"\n",
+                            pcb->snd_wnd, pcb->cwnd, wnd,
+                            ntohl(seg->tcphdr->seqno) + seg->len -
+                            pcb->lastack,
+                            ntohl(seg->tcphdr->seqno), pcb->lastack, i));
+    ++i;
+#endif /* TCP_CWND_DEBUG */
+
+    pcb->unsent = seg->next;
+
+    if (pcb->state != SYN_SENT) {
+      TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);
+      pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
+    }
+
+#if TCP_OVERSIZE_DBGCHECK
+    seg->oversize_left = 0;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+    tcp_output_segment(seg, pcb);
+    snd_nxt = ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg);
+    if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) {
+      pcb->snd_nxt = snd_nxt;
+    }
+    /* put segment on unacknowledged list if length > 0 */
+    if (TCP_TCPLEN(seg) > 0) {
+      seg->next = NULL;
+      /* unacked list is empty? */
+      if (pcb->unacked == NULL) {
+        pcb->unacked = seg;
+        useg = seg;
+      /* unacked list is not empty? */
+      } else {
+        /* In the case of fast retransmit, the packet should not go to the tail
+         * of the unacked queue, but rather somewhere before it. We need to check for
+         * this case. -STJ Jul 27, 2004 */
+        if (TCP_SEQ_LT(ntohl(seg->tcphdr->seqno), ntohl(useg->tcphdr->seqno))) {
+          /* add segment to before tail of unacked list, keeping the list sorted */
+          struct tcp_seg **cur_seg = &(pcb->unacked);
+          while (*cur_seg &&
+            TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) {
+              cur_seg = &((*cur_seg)->next );
+          }
+          seg->next = (*cur_seg);
+          (*cur_seg) = seg;
+        } else {
+          /* add segment to tail of unacked list */
+          useg->next = seg;
+          useg = useg->next;
+        }
+      }
+    /* do not queue empty segments on the unacked list */
+    } else {
+      tcp_seg_free(seg);
+    }
+    seg = pcb->unsent;
+  }
+#if TCP_OVERSIZE
+  if (pcb->unsent == NULL) {
+    /* last unsent has been removed, reset unsent_oversize */
+    pcb->unsent_oversize = 0;
+  }
+#endif /* TCP_OVERSIZE */
+
+  pcb->flags &= ~TF_NAGLEMEMERR;
+  return ERR_OK;
+}
+
+/**
+ * Called by tcp_output() to actually send a TCP segment over IP.
+ *
+ * @param seg the tcp_seg to send
+ * @param pcb the tcp_pcb for the TCP connection used to send the segment
+ */
+static void
+tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
+{
+  u16_t len;
+  u32_t *opts;
+
+  /** @bug Exclude retransmitted segments from this count. */
+  snmp_inc_tcpoutsegs();
+
+  /* The TCP header has already been constructed, but the ackno and
+   wnd fields remain. */
+  seg->tcphdr->ackno = htonl(pcb->rcv_nxt);
+
+  /* advertise our receive window size in this TCP segment */
+  seg->tcphdr->wnd = htons(pcb->rcv_ann_wnd);
+
+  pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;
+
+  /* Add any requested options.  NB MSS option is only set on SYN
+     packets, so ignore it here */
+  opts = (u32_t *)(void *)(seg->tcphdr + 1);
+  if (seg->flags & TF_SEG_OPTS_MSS) {
+    u16_t mss;
+#if TCP_CALCULATE_EFF_SEND_MSS
+    mss = tcp_eff_send_mss(TCP_MSS, &pcb->local_ip, &pcb->remote_ip, PCB_ISIPV6(pcb));
+#else /* TCP_CALCULATE_EFF_SEND_MSS */
+    mss = TCP_MSS;
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+    *opts = TCP_BUILD_MSS_OPTION(mss);
+    opts += 1;
+  }
+#if LWIP_TCP_TIMESTAMPS
+  pcb->ts_lastacksent = pcb->rcv_nxt;
+
+  if (seg->flags & TF_SEG_OPTS_TS) {
+    tcp_build_timestamp_option(pcb, opts);
+    opts += 3;
+  }
+#endif
+
+  /* Set retransmission timer running if it is not currently enabled 
+     This must be set before checking the route. */
+  if (pcb->rtime == -1) {
+    pcb->rtime = 0;
+  }
+
+  /* If we don't have a local IP address, we get one by
+     calling ip_route(). */
+  if (ipX_addr_isany(PCB_ISIPV6(pcb), &pcb->local_ip)) {
+    struct netif *netif;
+    ipX_addr_t *local_ip;
+    ipX_route_get_local_ipX(PCB_ISIPV6(pcb), &pcb->local_ip, &pcb->remote_ip, netif, local_ip);
+    if ((netif == NULL) || (local_ip == NULL)) {
+      return;
+    }
+    ipX_addr_copy(PCB_ISIPV6(pcb), pcb->local_ip, *local_ip);
+  }
+
+  if (pcb->rttest == 0) {
+    pcb->rttest = tcp_ticks;
+    pcb->rtseq = ntohl(seg->tcphdr->seqno);
+
+    LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_output_segment: rtseq %"U32_F"\n", pcb->rtseq));
+  }
+  LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output_segment: %"U32_F":%"U32_F"\n",
+          htonl(seg->tcphdr->seqno), htonl(seg->tcphdr->seqno) +
+          seg->len));
+
+  len = (u16_t)((u8_t *)seg->tcphdr - (u8_t *)seg->p->payload);
+
+  seg->p->len -= len;
+  seg->p->tot_len -= len;
+
+  seg->p->payload = seg->tcphdr;
+
+  seg->tcphdr->chksum = 0;
+#if TCP_CHECKSUM_ON_COPY
+  {
+    u32_t acc;
+#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
+    u16_t chksum_slow = ipX_chksum_pseudo(PCB_ISIPV6(pcb), seg->p, IP_PROTO_TCP,
+      seg->p->tot_len, &pcb->local_ip, &pcb->remote_ip);
+#endif /* TCP_CHECKSUM_ON_COPY_SANITY_CHECK */
+    if ((seg->flags & TF_SEG_DATA_CHECKSUMMED) == 0) {
+      LWIP_ASSERT("data included but not checksummed",
+        seg->p->tot_len == (TCPH_HDRLEN(seg->tcphdr) * 4));
+    }
+
+    /* rebuild TCP header checksum (TCP header changes for retransmissions!) */
+    acc = ipX_chksum_pseudo_partial(PCB_ISIPV6(pcb), seg->p, IP_PROTO_TCP,
+      seg->p->tot_len, TCPH_HDRLEN(seg->tcphdr) * 4, &pcb->local_ip, &pcb->remote_ip);
+    /* add payload checksum */
+    if (seg->chksum_swapped) {
+      seg->chksum = SWAP_BYTES_IN_WORD(seg->chksum);
+      seg->chksum_swapped = 0;
+    }
+    acc += (u16_t)~(seg->chksum);
+    seg->tcphdr->chksum = FOLD_U32T(acc);
+#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
+    if (chksum_slow != seg->tcphdr->chksum) {
+      LWIP_DEBUGF(TCP_DEBUG | LWIP_DBG_LEVEL_WARNING,
+                  ("tcp_output_segment: calculated checksum is %"X16_F" instead of %"X16_F"\n",
+                  seg->tcphdr->chksum, chksum_slow));
+      seg->tcphdr->chksum = chksum_slow;
+    }
+#endif /* TCP_CHECKSUM_ON_COPY_SANITY_CHECK */
+  }
+#else /* TCP_CHECKSUM_ON_COPY */
+#if CHECKSUM_GEN_TCP
+  seg->tcphdr->chksum = ipX_chksum_pseudo(PCB_ISIPV6(pcb), seg->p, IP_PROTO_TCP,
+    seg->p->tot_len, &pcb->local_ip, &pcb->remote_ip);
+#endif /* CHECKSUM_GEN_TCP */
+#endif /* TCP_CHECKSUM_ON_COPY */
+  TCP_STATS_INC(tcp.xmit);
+
+#if LWIP_NETIF_HWADDRHINT
+  ipX_output_hinted(PCB_ISIPV6(pcb), seg->p, &pcb->local_ip, &pcb->remote_ip,
+    pcb->ttl, pcb->tos, IP_PROTO_TCP, &pcb->addr_hint);
+#else /* LWIP_NETIF_HWADDRHINT*/
+  ipX_output(PCB_ISIPV6(pcb), seg->p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl,
+    pcb->tos, IP_PROTO_TCP);
+#endif /* LWIP_NETIF_HWADDRHINT*/
+}
+
+/**
+ * Send a TCP RESET packet (empty segment with RST flag set) either to
+ * abort a connection or to show that there is no matching local connection
+ * for a received segment.
+ *
+ * Called by tcp_abort() (to abort a local connection), tcp_input() (if no
+ * matching local pcb was found), tcp_listen_input() (if incoming segment
+ * has ACK flag set) and tcp_process() (received segment in the wrong state)
+ *
+ * Since a RST segment is in most cases not sent for an active connection,
+ * tcp_rst() has a number of arguments that are taken from a tcp_pcb for
+ * most other segment output functions.
+ *
+ * @param seqno the sequence number to use for the outgoing segment
+ * @param ackno the acknowledge number to use for the outgoing segment
+ * @param local_ip the local IP address to send the segment from
+ * @param remote_ip the remote IP address to send the segment to
+ * @param local_port the local TCP port to send the segment from
+ * @param remote_port the remote TCP port to send the segment to
+ */
+void
+tcp_rst_impl(u32_t seqno, u32_t ackno,
+  ipX_addr_t *local_ip, ipX_addr_t *remote_ip,
+  u16_t local_port, u16_t remote_port
+#if LWIP_IPV6
+  , u8_t isipv6
+#endif /* LWIP_IPV6 */
+  )
+{
+  struct pbuf *p;
+  struct tcp_hdr *tcphdr;
+  p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM);
+  if (p == NULL) {
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_rst: could not allocate memory for pbuf\n"));
+      return;
+  }
+  LWIP_ASSERT("check that first pbuf can hold struct tcp_hdr",
+              (p->len >= sizeof(struct tcp_hdr)));
+
+  tcphdr = (struct tcp_hdr *)p->payload;
+  tcphdr->src = htons(local_port);
+  tcphdr->dest = htons(remote_port);
+  tcphdr->seqno = htonl(seqno);
+  tcphdr->ackno = htonl(ackno);
+  TCPH_HDRLEN_FLAGS_SET(tcphdr, TCP_HLEN/4, TCP_RST | TCP_ACK);
+  tcphdr->wnd = PP_HTONS(TCP_WND);
+  tcphdr->chksum = 0;
+  tcphdr->urgp = 0;
+
+  TCP_STATS_INC(tcp.xmit);
+  snmp_inc_tcpoutrsts();
+
+#if CHECKSUM_GEN_TCP
+  tcphdr->chksum = ipX_chksum_pseudo(isipv6, p, IP_PROTO_TCP, p->tot_len,
+                                     local_ip, remote_ip);
+#endif
+  /* Send output with hardcoded TTL/HL since we have no access to the pcb */
+  ipX_output(isipv6, p, local_ip, remote_ip, TCP_TTL, 0, IP_PROTO_TCP);
+  pbuf_free(p);
+  LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_rst: seqno %"U32_F" ackno %"U32_F".\n", seqno, ackno));
+}
+
+/**
+ * Requeue all unacked segments for retransmission
+ *
+ * Called by tcp_slowtmr() for slow retransmission.
+ *
+ * @param pcb the tcp_pcb for which to re-enqueue all unacked segments
+ */
+void
+tcp_rexmit_rto(struct tcp_pcb *pcb)
+{
+  struct tcp_seg *seg;
+
+  if (pcb->unacked == NULL) {
+    return;
+  }
+
+  /* Move all unacked segments to the head of the unsent queue */
+  for (seg = pcb->unacked; seg->next != NULL; seg = seg->next);
+  /* concatenate unsent queue after unacked queue */
+  seg->next = pcb->unsent;
+  /* unsent queue is the concatenated queue (of unacked, unsent) */
+  pcb->unsent = pcb->unacked;
+  /* unacked queue is now empty */
+  pcb->unacked = NULL;
+  /* last unsent hasn't changed, no need to reset unsent_oversize */
+
+  /* increment number of retransmissions */
+  ++pcb->nrtx;
+
+  /* Don't take any RTT measurements after retransmitting. */
+  pcb->rttest = 0;
+
+  /* Do the actual retransmission */
+  tcp_output(pcb);
+}
+
+/**
+ * Requeue the first unacked segment for retransmission
+ *
+ * Called by tcp_receive() for fast retramsmit.
+ *
+ * @param pcb the tcp_pcb for which to retransmit the first unacked segment
+ */
+void
+tcp_rexmit(struct tcp_pcb *pcb)
+{
+  struct tcp_seg *seg;
+  struct tcp_seg **cur_seg;
+
+  if (pcb->unacked == NULL) {
+    return;
+  }
+
+  /* Move the first unacked segment to the unsent queue */
+  /* Keep the unsent queue sorted. */
+  seg = pcb->unacked;
+  pcb->unacked = seg->next;
+
+  cur_seg = &(pcb->unsent);
+  while (*cur_seg &&
+    TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) {
+      cur_seg = &((*cur_seg)->next );
+  }
+  seg->next = *cur_seg;
+  *cur_seg = seg;
+#if TCP_OVERSIZE
+  if (seg->next == NULL) {
+    /* the retransmitted segment is last in unsent, so reset unsent_oversize */
+    pcb->unsent_oversize = 0;
+  }
+#endif /* TCP_OVERSIZE */
+
+  ++pcb->nrtx;
+
+  /* Don't take any rtt measurements after retransmitting. */
+  pcb->rttest = 0;
+
+  /* Do the actual retransmission. */
+  snmp_inc_tcpretranssegs();
+  /* No need to call tcp_output: we are always called from tcp_input()
+     and thus tcp_output directly returns. */
+}
+
+
+/**
+ * Handle retransmission after three dupacks received
+ *
+ * @param pcb the tcp_pcb for which to retransmit the first unacked segment
+ */
+void 
+tcp_rexmit_fast(struct tcp_pcb *pcb)
+{
+  if (pcb->unacked != NULL && !(pcb->flags & TF_INFR)) {
+    /* This is fast retransmit. Retransmit the first unacked segment. */
+    LWIP_DEBUGF(TCP_FR_DEBUG, 
+                ("tcp_receive: dupacks %"U16_F" (%"U32_F
+                 "), fast retransmit %"U32_F"\n",
+                 (u16_t)pcb->dupacks, pcb->lastack,
+                 ntohl(pcb->unacked->tcphdr->seqno)));
+    tcp_rexmit(pcb);
+
+    /* Set ssthresh to half of the minimum of the current
+     * cwnd and the advertised window */
+    if (pcb->cwnd > pcb->snd_wnd) {
+      pcb->ssthresh = pcb->snd_wnd / 2;
+    } else {
+      pcb->ssthresh = pcb->cwnd / 2;
+    }
+    
+    /* The minimum value for ssthresh should be 2 MSS */
+    if (pcb->ssthresh < 2*pcb->mss) {
+      LWIP_DEBUGF(TCP_FR_DEBUG, 
+                  ("tcp_receive: The minimum value for ssthresh %"U16_F
+                   " should be min 2 mss %"U16_F"...\n",
+                   pcb->ssthresh, 2*pcb->mss));
+      pcb->ssthresh = 2*pcb->mss;
+    }
+    
+    pcb->cwnd = pcb->ssthresh + 3 * pcb->mss;
+    pcb->flags |= TF_INFR;
+  } 
+}
+
+
+/**
+ * Send keepalive packets to keep a connection active although
+ * no data is sent over it.
+ *
+ * Called by tcp_slowtmr()
+ *
+ * @param pcb the tcp_pcb for which to send a keepalive packet
+ */
+void
+tcp_keepalive(struct tcp_pcb *pcb)
+{
+  struct pbuf *p;
+#if CHECKSUM_GEN_TCP
+  struct tcp_hdr *tcphdr;
+#endif /* CHECKSUM_GEN_TCP */
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: sending KEEPALIVE probe to "));
+  ipX_addr_debug_print(PCB_ISIPV6(pcb), TCP_DEBUG, &pcb->remote_ip);
+  LWIP_DEBUGF(TCP_DEBUG, ("\n"));
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: tcp_ticks %"U32_F"   pcb->tmr %"U32_F" pcb->keep_cnt_sent %"U16_F"\n", 
+                          tcp_ticks, pcb->tmr, pcb->keep_cnt_sent));
+   
+  p = tcp_output_alloc_header(pcb, 0, 0, htonl(pcb->snd_nxt - 1));
+  if(p == NULL) {
+    LWIP_DEBUGF(TCP_DEBUG, 
+                ("tcp_keepalive: could not allocate memory for pbuf\n"));
+    return;
+  }
+#if CHECKSUM_GEN_TCP
+  tcphdr = (struct tcp_hdr *)p->payload;
+
+  tcphdr->chksum = ipX_chksum_pseudo(PCB_ISIPV6(pcb), p, IP_PROTO_TCP, p->tot_len,
+      &pcb->local_ip, &pcb->remote_ip);
+#endif /* CHECKSUM_GEN_TCP */
+  TCP_STATS_INC(tcp.xmit);
+
+  /* Send output to IP */
+#if LWIP_NETIF_HWADDRHINT
+  ipX_output_hinted(PCB_ISIPV6(pcb), p, &pcb->local_ip, &pcb->remote_ip,
+    pcb->ttl, 0, IP_PROTO_TCP, &pcb->addr_hint);
+#else /* LWIP_NETIF_HWADDRHINT*/
+  ipX_output(PCB_ISIPV6(pcb), p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl,
+    0, IP_PROTO_TCP);
+#endif /* LWIP_NETIF_HWADDRHINT*/
+
+  pbuf_free(p);
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: seqno %"U32_F" ackno %"U32_F".\n",
+                          pcb->snd_nxt - 1, pcb->rcv_nxt));
+}
+
+
+/**
+ * Send persist timer zero-window probes to keep a connection active
+ * when a window update is lost.
+ *
+ * Called by tcp_slowtmr()
+ *
+ * @param pcb the tcp_pcb for which to send a zero-window probe packet
+ */
+void
+tcp_zero_window_probe(struct tcp_pcb *pcb)
+{
+  struct pbuf *p;
+  struct tcp_hdr *tcphdr;
+  struct tcp_seg *seg;
+  u16_t len;
+  u8_t is_fin;
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_zero_window_probe: sending ZERO WINDOW probe to "));
+  ipX_addr_debug_print(PCB_ISIPV6(pcb), TCP_DEBUG, &pcb->remote_ip);
+  LWIP_DEBUGF(TCP_DEBUG, ("\n"));
+
+  LWIP_DEBUGF(TCP_DEBUG, 
+              ("tcp_zero_window_probe: tcp_ticks %"U32_F
+               "   pcb->tmr %"U32_F" pcb->keep_cnt_sent %"U16_F"\n", 
+               tcp_ticks, pcb->tmr, pcb->keep_cnt_sent));
+
+  seg = pcb->unacked;
+
+  if(seg == NULL) {
+    seg = pcb->unsent;
+  }
+  if(seg == NULL) {
+    return;
+  }
+
+  is_fin = ((TCPH_FLAGS(seg->tcphdr) & TCP_FIN) != 0) && (seg->len == 0);
+  /* we want to send one seqno: either FIN or data (no options) */
+  len = is_fin ? 0 : 1;
+
+  p = tcp_output_alloc_header(pcb, 0, len, seg->tcphdr->seqno);
+  if(p == NULL) {
+    LWIP_DEBUGF(TCP_DEBUG, ("tcp_zero_window_probe: no memory for pbuf\n"));
+    return;
+  }
+  tcphdr = (struct tcp_hdr *)p->payload;
+
+  if (is_fin) {
+    /* FIN segment, no data */
+    TCPH_FLAGS_SET(tcphdr, TCP_ACK | TCP_FIN);
+  } else {
+    /* Data segment, copy in one byte from the head of the unacked queue */
+    char *d = ((char *)p->payload + TCP_HLEN);
+    /* Depending on whether the segment has already been sent (unacked) or not
+       (unsent), seg->p->payload points to the IP header or TCP header.
+       Ensure we copy the first TCP data byte: */
+    pbuf_copy_partial(seg->p, d, 1, seg->p->tot_len - seg->len);
+  }
+
+#if CHECKSUM_GEN_TCP
+  tcphdr->chksum = ipX_chksum_pseudo(PCB_ISIPV6(pcb), p, IP_PROTO_TCP, p->tot_len,
+      &pcb->local_ip, &pcb->remote_ip);
+#endif
+  TCP_STATS_INC(tcp.xmit);
+
+  /* Send output to IP */
+#if LWIP_NETIF_HWADDRHINT
+  ipX_output_hinted(PCB_ISIPV6(pcb), p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl,
+    0, IP_PROTO_TCP, &pcb->addr_hint);
+#else /* LWIP_NETIF_HWADDRHINT*/
+  ipX_output(PCB_ISIPV6(pcb), p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, 0, IP_PROTO_TCP);
+#endif /* LWIP_NETIF_HWADDRHINT*/
+
+  pbuf_free(p);
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_zero_window_probe: seqno %"U32_F
+                          " ackno %"U32_F".\n",
+                          pcb->snd_nxt - 1, pcb->rcv_nxt));
+}
+#endif /* LWIP_TCP */
diff --git a/external/badvpn_dns/lwip/src/core/timers.c b/external/badvpn_dns/lwip/src/core/timers.c
new file mode 100644
index 0000000..c8ead4e
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/timers.c
@@ -0,0 +1,546 @@
+/**
+ * @file
+ * Stack-internal timers implementation.
+ * This file includes timer callbacks for stack-internal timers as well as
+ * functions to set up or stop timers and check for expired timers.
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *         Simon Goldschmidt
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/timers.h"
+#include "lwip/tcp_impl.h"
+
+#if LWIP_TIMERS
+
+#include "lwip/def.h"
+#include "lwip/memp.h"
+#include "lwip/tcpip.h"
+
+#include "lwip/ip_frag.h"
+#include "netif/etharp.h"
+#include "lwip/dhcp.h"
+#include "lwip/autoip.h"
+#include "lwip/igmp.h"
+#include "lwip/dns.h"
+#include "lwip/nd6.h"
+#include "lwip/ip6_frag.h"
+#include "lwip/mld6.h"
+#include "lwip/sys.h"
+#include "lwip/pbuf.h"
+
+/** The one and only timeout list */
+static struct sys_timeo *next_timeout;
+#if NO_SYS
+static u32_t timeouts_last_time;
+#endif /* NO_SYS */
+
+#if LWIP_TCP
+/** global variable that shows if the tcp timer is currently scheduled or not */
+static int tcpip_tcp_timer_active;
+
+/**
+ * Timer callback function that calls tcp_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+tcpip_tcp_timer(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+
+  /* call TCP timer handler */
+  tcp_tmr();
+  /* timer still needed? */
+  if (tcp_active_pcbs || tcp_tw_pcbs) {
+    /* restart timer */
+    sys_timeout(TCP_TMR_INTERVAL, tcpip_tcp_timer, NULL);
+  } else {
+    /* disable timer */
+    tcpip_tcp_timer_active = 0;
+  }
+}
+
+/**
+ * Called from TCP_REG when registering a new PCB:
+ * the reason is to have the TCP timer only running when
+ * there are active (or time-wait) PCBs.
+ */
+void
+tcp_timer_needed(void)
+{
+  /* timer is off but needed again? */
+  if (!tcpip_tcp_timer_active && (tcp_active_pcbs || tcp_tw_pcbs)) {
+    /* enable and start timer */
+    tcpip_tcp_timer_active = 1;
+    sys_timeout(TCP_TMR_INTERVAL, tcpip_tcp_timer, NULL);
+  }
+}
+#endif /* LWIP_TCP */
+
+#if IP_REASSEMBLY
+/**
+ * Timer callback function that calls ip_reass_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+ip_reass_timer(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: ip_reass_tmr()\n"));
+  ip_reass_tmr();
+  sys_timeout(IP_TMR_INTERVAL, ip_reass_timer, NULL);
+}
+#endif /* IP_REASSEMBLY */
+
+#if LWIP_ARP
+/**
+ * Timer callback function that calls etharp_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+arp_timer(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: etharp_tmr()\n"));
+  etharp_tmr();
+  sys_timeout(ARP_TMR_INTERVAL, arp_timer, NULL);
+}
+#endif /* LWIP_ARP */
+
+#if LWIP_DHCP
+/**
+ * Timer callback function that calls dhcp_coarse_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+dhcp_timer_coarse(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: dhcp_coarse_tmr()\n"));
+  dhcp_coarse_tmr();
+  sys_timeout(DHCP_COARSE_TIMER_MSECS, dhcp_timer_coarse, NULL);
+}
+
+/**
+ * Timer callback function that calls dhcp_fine_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+dhcp_timer_fine(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: dhcp_fine_tmr()\n"));
+  dhcp_fine_tmr();
+  sys_timeout(DHCP_FINE_TIMER_MSECS, dhcp_timer_fine, NULL);
+}
+#endif /* LWIP_DHCP */
+
+#if LWIP_AUTOIP
+/**
+ * Timer callback function that calls autoip_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+autoip_timer(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: autoip_tmr()\n"));
+  autoip_tmr();
+  sys_timeout(AUTOIP_TMR_INTERVAL, autoip_timer, NULL);
+}
+#endif /* LWIP_AUTOIP */
+
+#if LWIP_IGMP
+/**
+ * Timer callback function that calls igmp_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+igmp_timer(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: igmp_tmr()\n"));
+  igmp_tmr();
+  sys_timeout(IGMP_TMR_INTERVAL, igmp_timer, NULL);
+}
+#endif /* LWIP_IGMP */
+
+#if LWIP_DNS
+/**
+ * Timer callback function that calls dns_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+dns_timer(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: dns_tmr()\n"));
+  dns_tmr();
+  sys_timeout(DNS_TMR_INTERVAL, dns_timer, NULL);
+}
+#endif /* LWIP_DNS */
+
+#if LWIP_IPV6
+/**
+ * Timer callback function that calls nd6_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+nd6_timer(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: nd6_tmr()\n"));
+  nd6_tmr();
+  sys_timeout(ND6_TMR_INTERVAL, nd6_timer, NULL);
+}
+
+#if LWIP_IPV6_REASS
+/**
+ * Timer callback function that calls ip6_reass_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+ip6_reass_timer(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: ip6_reass_tmr()\n"));
+  ip6_reass_tmr();
+  sys_timeout(IP6_REASS_TMR_INTERVAL, ip6_reass_timer, NULL);
+}
+#endif /* LWIP_IPV6_REASS */
+
+#if LWIP_IPV6_MLD
+/**
+ * Timer callback function that calls mld6_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+mld6_timer(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: mld6_tmr()\n"));
+  mld6_tmr();
+  sys_timeout(MLD6_TMR_INTERVAL, mld6_timer, NULL);
+}
+#endif /* LWIP_IPV6_MLD */
+#endif /* LWIP_IPV6 */
+
+/** Initialize this module */
+void sys_timeouts_init(void)
+{
+#if IP_REASSEMBLY
+  sys_timeout(IP_TMR_INTERVAL, ip_reass_timer, NULL);
+#endif /* IP_REASSEMBLY */
+#if LWIP_ARP
+  sys_timeout(ARP_TMR_INTERVAL, arp_timer, NULL);
+#endif /* LWIP_ARP */
+#if LWIP_DHCP
+  sys_timeout(DHCP_COARSE_TIMER_MSECS, dhcp_timer_coarse, NULL);
+  sys_timeout(DHCP_FINE_TIMER_MSECS, dhcp_timer_fine, NULL);
+#endif /* LWIP_DHCP */
+#if LWIP_AUTOIP
+  sys_timeout(AUTOIP_TMR_INTERVAL, autoip_timer, NULL);
+#endif /* LWIP_AUTOIP */
+#if LWIP_IGMP
+  sys_timeout(IGMP_TMR_INTERVAL, igmp_timer, NULL);
+#endif /* LWIP_IGMP */
+#if LWIP_DNS
+  sys_timeout(DNS_TMR_INTERVAL, dns_timer, NULL);
+#endif /* LWIP_DNS */
+#if LWIP_IPV6
+  sys_timeout(ND6_TMR_INTERVAL, nd6_timer, NULL);
+#if LWIP_IPV6_REASS
+  sys_timeout(IP6_REASS_TMR_INTERVAL, ip6_reass_timer, NULL);
+#endif /* LWIP_IPV6_REASS */
+#if LWIP_IPV6_MLD
+  sys_timeout(MLD6_TMR_INTERVAL, mld6_timer, NULL);
+#endif /* LWIP_IPV6_MLD */
+#endif /* LWIP_IPV6 */
+
+#if NO_SYS
+  /* Initialise timestamp for sys_check_timeouts */
+  timeouts_last_time = sys_now();
+#endif
+}
+
+/**
+ * Create a one-shot timer (aka timeout). Timeouts are processed in the
+ * following cases:
+ * - while waiting for a message using sys_timeouts_mbox_fetch()
+ * - by calling sys_check_timeouts() (NO_SYS==1 only)
+ *
+ * @param msecs time in milliseconds after that the timer should expire
+ * @param handler callback function to call when msecs have elapsed
+ * @param arg argument to pass to the callback function
+ */
+#if LWIP_DEBUG_TIMERNAMES
+void
+sys_timeout_debug(u32_t msecs, sys_timeout_handler handler, void *arg, const char* handler_name)
+#else /* LWIP_DEBUG_TIMERNAMES */
+void
+sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)
+#endif /* LWIP_DEBUG_TIMERNAMES */
+{
+  struct sys_timeo *timeout, *t;
+
+  timeout = (struct sys_timeo *)memp_malloc(MEMP_SYS_TIMEOUT);
+  if (timeout == NULL) {
+    LWIP_ASSERT("sys_timeout: timeout != NULL, pool MEMP_SYS_TIMEOUT is empty", timeout != NULL);
+    return;
+  }
+  timeout->next = NULL;
+  timeout->h = handler;
+  timeout->arg = arg;
+  timeout->time = msecs;
+#if LWIP_DEBUG_TIMERNAMES
+  timeout->handler_name = handler_name;
+  LWIP_DEBUGF(TIMERS_DEBUG, ("sys_timeout: %p msecs=%"U32_F" handler=%s arg=%p\n",
+    (void *)timeout, msecs, handler_name, (void *)arg));
+#endif /* LWIP_DEBUG_TIMERNAMES */
+
+  if (next_timeout == NULL) {
+    next_timeout = timeout;
+    return;
+  }
+
+  if (next_timeout->time > msecs) {
+    next_timeout->time -= msecs;
+    timeout->next = next_timeout;
+    next_timeout = timeout;
+  } else {
+    for(t = next_timeout; t != NULL; t = t->next) {
+      timeout->time -= t->time;
+      if (t->next == NULL || t->next->time > timeout->time) {
+        if (t->next != NULL) {
+          t->next->time -= timeout->time;
+        }
+        timeout->next = t->next;
+        t->next = timeout;
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * Go through timeout list (for this task only) and remove the first matching
+ * entry, even though the timeout has not triggered yet.
+ *
+ * @note This function only works as expected if there is only one timeout
+ * calling 'handler' in the list of timeouts.
+ *
+ * @param handler callback function that would be called by the timeout
+ * @param arg callback argument that would be passed to handler
+*/
+void
+sys_untimeout(sys_timeout_handler handler, void *arg)
+{
+  struct sys_timeo *prev_t, *t;
+
+  if (next_timeout == NULL) {
+    return;
+  }
+
+  for (t = next_timeout, prev_t = NULL; t != NULL; prev_t = t, t = t->next) {
+    if ((t->h == handler) && (t->arg == arg)) {
+      /* We have a match */
+      /* Unlink from previous in list */
+      if (prev_t == NULL) {
+        next_timeout = t->next;
+      } else {
+        prev_t->next = t->next;
+      }
+      /* If not the last one, add time of this one back to next */
+      if (t->next != NULL) {
+        t->next->time += t->time;
+      }
+      memp_free(MEMP_SYS_TIMEOUT, t);
+      return;
+    }
+  }
+  return;
+}
+
+#if NO_SYS
+
+/** Handle timeouts for NO_SYS==1 (i.e. without using
+ * tcpip_thread/sys_timeouts_mbox_fetch(). Uses sys_now() to call timeout
+ * handler functions when timeouts expire.
+ *
+ * Must be called periodically from your main loop.
+ */
+void
+sys_check_timeouts(void)
+{
+  if (next_timeout) {
+    struct sys_timeo *tmptimeout;
+    u32_t diff;
+    sys_timeout_handler handler;
+    void *arg;
+    u8_t had_one;
+    u32_t now;
+
+    now = sys_now();
+    /* this cares for wraparounds */
+    diff = now - timeouts_last_time;
+    do
+    {
+#if PBUF_POOL_FREE_OOSEQ
+      PBUF_CHECK_FREE_OOSEQ();
+#endif /* PBUF_POOL_FREE_OOSEQ */
+      had_one = 0;
+      tmptimeout = next_timeout;
+      if (tmptimeout && (tmptimeout->time <= diff)) {
+        /* timeout has expired */
+        had_one = 1;
+        timeouts_last_time = now;
+        diff -= tmptimeout->time;
+        next_timeout = tmptimeout->next;
+        handler = tmptimeout->h;
+        arg = tmptimeout->arg;
+#if LWIP_DEBUG_TIMERNAMES
+        if (handler != NULL) {
+          LWIP_DEBUGF(TIMERS_DEBUG, ("sct calling h=%s arg=%p\n",
+            tmptimeout->handler_name, arg));
+        }
+#endif /* LWIP_DEBUG_TIMERNAMES */
+        memp_free(MEMP_SYS_TIMEOUT, tmptimeout);
+        if (handler != NULL) {
+          handler(arg);
+        }
+      }
+    /* repeat until all expired timers have been called */
+    }while(had_one);
+  }
+}
+
+/** Set back the timestamp of the last call to sys_check_timeouts()
+ * This is necessary if sys_check_timeouts() hasn't been called for a long
+ * time (e.g. while saving energy) to prevent all timer functions of that
+ * period being called.
+ */
+void
+sys_restart_timeouts(void)
+{
+  timeouts_last_time = sys_now();
+}
+
+#else /* NO_SYS */
+
+/**
+ * Wait (forever) for a message to arrive in an mbox.
+ * While waiting, timeouts are processed.
+ *
+ * @param mbox the mbox to fetch the message from
+ * @param msg the place to store the message
+ */
+void
+sys_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg)
+{
+  u32_t time_needed;
+  struct sys_timeo *tmptimeout;
+  sys_timeout_handler handler;
+  void *arg;
+
+ again:
+  if (!next_timeout) {
+    time_needed = sys_arch_mbox_fetch(mbox, msg, 0);
+  } else {
+    if (next_timeout->time > 0) {
+      time_needed = sys_arch_mbox_fetch(mbox, msg, next_timeout->time);
+    } else {
+      time_needed = SYS_ARCH_TIMEOUT;
+    }
+
+    if (time_needed == SYS_ARCH_TIMEOUT) {
+      /* If time == SYS_ARCH_TIMEOUT, a timeout occured before a message
+         could be fetched. We should now call the timeout handler and
+         deallocate the memory allocated for the timeout. */
+      tmptimeout = next_timeout;
+      next_timeout = tmptimeout->next;
+      handler = tmptimeout->h;
+      arg = tmptimeout->arg;
+#if LWIP_DEBUG_TIMERNAMES
+      if (handler != NULL) {
+        LWIP_DEBUGF(TIMERS_DEBUG, ("stmf calling h=%s arg=%p\n",
+          tmptimeout->handler_name, arg));
+      }
+#endif /* LWIP_DEBUG_TIMERNAMES */
+      memp_free(MEMP_SYS_TIMEOUT, tmptimeout);
+      if (handler != NULL) {
+        /* For LWIP_TCPIP_CORE_LOCKING, lock the core before calling the
+           timeout handler function. */
+        LOCK_TCPIP_CORE();
+        handler(arg);
+        UNLOCK_TCPIP_CORE();
+      }
+      LWIP_TCPIP_THREAD_ALIVE();
+
+      /* We try again to fetch a message from the mbox. */
+      goto again;
+    } else {
+      /* If time != SYS_ARCH_TIMEOUT, a message was received before the timeout
+         occured. The time variable is set to the number of
+         milliseconds we waited for the message. */
+      if (time_needed < next_timeout->time) {
+        next_timeout->time -= time_needed;
+      } else {
+        next_timeout->time = 0;
+      }
+    }
+  }
+}
+
+#endif /* NO_SYS */
+
+#else /* LWIP_TIMERS */
+/* Satisfy the TCP code which calls this function */
+void
+tcp_timer_needed(void)
+{
+}
+#endif /* LWIP_TIMERS */
diff --git a/external/badvpn_dns/lwip/src/core/udp.c b/external/badvpn_dns/lwip/src/core/udp.c
new file mode 100644
index 0000000..ac0e56d
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/core/udp.c
@@ -0,0 +1,1151 @@
+/**
+ * @file
+ * User Datagram Protocol module
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+
+/* udp.c
+ *
+ * The code for the User Datagram Protocol UDP & UDPLite (RFC 3828).
+ *
+ */
+
+/* @todo Check the use of '(struct udp_pcb).chksum_len_rx'!
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_UDP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/udp.h"
+#include "lwip/def.h"
+#include "lwip/memp.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/ip_addr.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/netif.h"
+#include "lwip/icmp.h"
+#include "lwip/icmp6.h"
+#include "lwip/stats.h"
+#include "lwip/snmp.h"
+#include "arch/perf.h"
+#include "lwip/dhcp.h"
+
+#include <string.h>
+
+#ifndef UDP_LOCAL_PORT_RANGE_START
+/* From http://www.iana.org/assignments/port-numbers:
+   "The Dynamic and/or Private Ports are those from 49152 through 65535" */
+#define UDP_LOCAL_PORT_RANGE_START  0xc000
+#define UDP_LOCAL_PORT_RANGE_END    0xffff
+#define UDP_ENSURE_LOCAL_PORT_RANGE(port) (((port) & ~UDP_LOCAL_PORT_RANGE_START) + UDP_LOCAL_PORT_RANGE_START)
+#endif
+
+/* last local UDP port */
+static u16_t udp_port = UDP_LOCAL_PORT_RANGE_START;
+
+/* The list of UDP PCBs */
+/* exported in udp.h (was static) */
+struct udp_pcb *udp_pcbs;
+
+/**
+ * Initialize this module.
+ */
+void
+udp_init(void)
+{
+#if LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS && defined(LWIP_RAND)
+  udp_port = UDP_ENSURE_LOCAL_PORT_RANGE(LWIP_RAND());
+#endif /* LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS && defined(LWIP_RAND) */
+}
+
+/**
+ * Allocate a new local UDP port.
+ *
+ * @return a new (free) local UDP port number
+ */
+static u16_t
+udp_new_port(void)
+{
+  u16_t n = 0;
+  struct udp_pcb *pcb;
+  
+again:
+  if (udp_port++ == UDP_LOCAL_PORT_RANGE_END) {
+    udp_port = UDP_LOCAL_PORT_RANGE_START;
+  }
+  /* Check all PCBs. */
+  for(pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
+    if (pcb->local_port == udp_port) {
+      if (++n > (UDP_LOCAL_PORT_RANGE_END - UDP_LOCAL_PORT_RANGE_START)) {
+        return 0;
+      }
+      goto again;
+    }
+  }
+  return udp_port;
+#if 0
+  struct udp_pcb *ipcb = udp_pcbs;
+  while ((ipcb != NULL) && (udp_port != UDP_LOCAL_PORT_RANGE_END)) {
+    if (ipcb->local_port == udp_port) {
+      /* port is already used by another udp_pcb */
+      udp_port++;
+      /* restart scanning all udp pcbs */
+      ipcb = udp_pcbs;
+    } else {
+      /* go on with next udp pcb */
+      ipcb = ipcb->next;
+    }
+  }
+  if (ipcb != NULL) {
+    return 0;
+  }
+  return udp_port;
+#endif
+}
+
+/**
+ * Process an incoming UDP datagram.
+ *
+ * Given an incoming UDP datagram (as a chain of pbufs) this function
+ * finds a corresponding UDP PCB and hands over the pbuf to the pcbs
+ * recv function. If no pcb is found or the datagram is incorrect, the
+ * pbuf is freed.
+ *
+ * @param p pbuf to be demultiplexed to a UDP PCB (p->payload pointing to the UDP header)
+ * @param inp network interface on which the datagram was received.
+ *
+ */
+void
+udp_input(struct pbuf *p, struct netif *inp)
+{
+  struct udp_hdr *udphdr;
+  struct udp_pcb *pcb, *prev;
+  struct udp_pcb *uncon_pcb;
+  u16_t src, dest;
+  u8_t local_match;
+  u8_t broadcast;
+  u8_t for_us;
+
+  PERF_START;
+
+  UDP_STATS_INC(udp.recv);
+
+  /* Check minimum length (UDP header) */
+  if (p->len < UDP_HLEN) {
+    /* drop short packets */
+    LWIP_DEBUGF(UDP_DEBUG,
+                ("udp_input: short UDP datagram (%"U16_F" bytes) discarded\n", p->tot_len));
+    UDP_STATS_INC(udp.lenerr);
+    UDP_STATS_INC(udp.drop);
+    snmp_inc_udpinerrors();
+    pbuf_free(p);
+    goto end;
+  }
+
+  udphdr = (struct udp_hdr *)p->payload;
+
+  /* is broadcast packet ? */
+#if LWIP_IPV6
+  broadcast = !ip_current_is_v6() && ip_addr_isbroadcast(ip_current_dest_addr(), inp);
+#else /* LWIP_IPV6 */
+  broadcast = ip_addr_isbroadcast(ip_current_dest_addr(), inp);
+#endif /* LWIP_IPV6 */
+
+  LWIP_DEBUGF(UDP_DEBUG, ("udp_input: received datagram of length %"U16_F"\n", p->tot_len));
+
+  /* convert src and dest ports to host byte order */
+  src = ntohs(udphdr->src);
+  dest = ntohs(udphdr->dest);
+
+  udp_debug_print(udphdr);
+
+  /* print the UDP source and destination */
+  LWIP_DEBUGF(UDP_DEBUG, ("udp ("));
+  ipX_addr_debug_print(ip_current_is_v6(), UDP_DEBUG, ipX_current_dest_addr());
+  LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F") <-- (", ntohs(udphdr->dest)));
+  ipX_addr_debug_print(ip_current_is_v6(), UDP_DEBUG, ipX_current_src_addr());
+  LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F")\n", ntohs(udphdr->src)));
+
+#if LWIP_DHCP
+  pcb = NULL;
+  /* when LWIP_DHCP is active, packets to DHCP_CLIENT_PORT may only be processed by
+     the dhcp module, no other UDP pcb may use the local UDP port DHCP_CLIENT_PORT */
+  if (dest == DHCP_CLIENT_PORT) {
+    /* all packets for DHCP_CLIENT_PORT not coming from DHCP_SERVER_PORT are dropped! */
+    if (src == DHCP_SERVER_PORT) {
+      if ((inp->dhcp != NULL) && (inp->dhcp->pcb != NULL)) {
+        /* accept the packe if 
+           (- broadcast or directed to us) -> DHCP is link-layer-addressed, local ip is always ANY!
+           - inp->dhcp->pcb->remote == ANY or iphdr->src
+           (no need to check for IPv6 since the dhcp struct always uses IPv4) */
+        if (ipX_addr_isany(0, &inp->dhcp->pcb->remote_ip) ||
+            ip_addr_cmp(ipX_2_ip(&(inp->dhcp->pcb->remote_ip)), ip_current_src_addr())) {
+          pcb = inp->dhcp->pcb;
+        }
+      }
+    }
+  } else
+#endif /* LWIP_DHCP */
+  {
+    prev = NULL;
+    local_match = 0;
+    uncon_pcb = NULL;
+    /* Iterate through the UDP pcb list for a matching pcb.
+     * 'Perfect match' pcbs (connected to the remote port & ip address) are
+     * preferred. If no perfect match is found, the first unconnected pcb that
+     * matches the local port and ip address gets the datagram. */
+    for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
+      local_match = 0;
+      /* print the PCB local and remote address */
+      LWIP_DEBUGF(UDP_DEBUG, ("pcb ("));
+      ipX_addr_debug_print(PCB_ISIPV6(pcb), UDP_DEBUG, &pcb->local_ip);
+      LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F") <-- (", pcb->local_port));
+      ipX_addr_debug_print(PCB_ISIPV6(pcb), UDP_DEBUG, &pcb->remote_ip);
+      LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F")\n", pcb->remote_port));
+
+      /* compare PCB local addr+port to UDP destination addr+port */
+      if (pcb->local_port == dest) {
+        if (
+#if LWIP_IPV6
+          ((PCB_ISIPV6(pcb) && (ip_current_is_v6()) &&
+            (ip6_addr_isany(ipX_2_ip6(&pcb->local_ip)) ||
+#if LWIP_IPV6_MLD
+            ip6_addr_ismulticast(ip6_current_dest_addr()) ||
+#endif /* LWIP_IPV6_MLD */
+            ip6_addr_cmp(ipX_2_ip6(&pcb->local_ip), ip6_current_dest_addr()))) ||
+           (!PCB_ISIPV6(pcb) &&
+            (ip_current_header() != NULL) &&
+#else /* LWIP_IPV6 */
+           ((
+#endif /* LWIP_IPV6 */
+            ((!broadcast && ipX_addr_isany(0, &pcb->local_ip)) ||
+            ip_addr_cmp(ipX_2_ip(&pcb->local_ip), ip_current_dest_addr()) ||
+#if LWIP_IGMP
+            ip_addr_ismulticast(ip_current_dest_addr()) ||
+#endif /* LWIP_IGMP */
+#if IP_SOF_BROADCAST_RECV
+            (broadcast && ip_get_option(pcb, SOF_BROADCAST) &&
+             (ipX_addr_isany(0, &pcb->local_ip) ||
+              ip_addr_netcmp(ipX_2_ip(&pcb->local_ip), ip_current_dest_addr(), &inp->netmask))))))) {
+#else /* IP_SOF_BROADCAST_RECV */
+            (broadcast &&
+             (ipX_addr_isany(0, &pcb->local_ip) ||
+              ip_addr_netcmp(ipX_2_ip(&pcb->local_ip), ip_current_dest_addr(), &inp->netmask))))))) {
+#endif /* IP_SOF_BROADCAST_RECV */ 
+          local_match = 1;
+          if ((uncon_pcb == NULL) && 
+              ((pcb->flags & UDP_FLAGS_CONNECTED) == 0)) {
+            /* the first unconnected matching PCB */
+            uncon_pcb = pcb;
+          }
+        }
+      }
+      /* compare PCB remote addr+port to UDP source addr+port */
+      if ((local_match != 0) &&
+          (pcb->remote_port == src) && IP_PCB_IPVER_INPUT_MATCH(pcb) &&
+            (ipX_addr_isany(PCB_ISIPV6(pcb), &pcb->remote_ip) ||
+              ipX_addr_cmp(PCB_ISIPV6(pcb), &pcb->remote_ip, ipX_current_src_addr()))) {
+        /* the first fully matching PCB */
+        if (prev != NULL) {
+          /* move the pcb to the front of udp_pcbs so that is
+             found faster next time */
+          prev->next = pcb->next;
+          pcb->next = udp_pcbs;
+          udp_pcbs = pcb;
+        } else {
+          UDP_STATS_INC(udp.cachehit);
+        }
+        break;
+      }
+      prev = pcb;
+    }
+    /* no fully matching pcb found? then look for an unconnected pcb */
+    if (pcb == NULL) {
+      pcb = uncon_pcb;
+    }
+  }
+
+  /* Check checksum if this is a match or if it was directed at us. */
+  if (pcb != NULL) {
+    for_us = 1;
+  } else {
+#if LWIP_IPV6
+    if (ip_current_is_v6()) {
+      for_us = netif_matches_ip6_addr(inp, ip6_current_dest_addr());
+    } else
+#endif /* LWIP_IPV6 */
+    {
+      for_us = ip_addr_cmp(&inp->ip_addr, ip_current_dest_addr());
+    }
+  }
+  if (for_us) {
+    LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_input: calculating checksum\n"));
+#if CHECKSUM_CHECK_UDP
+#if LWIP_UDPLITE
+    if (ip_current_header_proto() == IP_PROTO_UDPLITE) {
+      /* Do the UDP Lite checksum */
+      u16_t chklen = ntohs(udphdr->len);
+      if (chklen < sizeof(struct udp_hdr)) {
+        if (chklen == 0) {
+          /* For UDP-Lite, checksum length of 0 means checksum
+             over the complete packet (See RFC 3828 chap. 3.1) */
+          chklen = p->tot_len;
+        } else {
+          /* At least the UDP-Lite header must be covered by the
+             checksum! (Again, see RFC 3828 chap. 3.1) */
+          goto chkerr;
+        }
+      }
+      if (ipX_chksum_pseudo_partial(ip_current_is_v6(), p, IP_PROTO_UDPLITE,
+                   p->tot_len, chklen,
+                   ipX_current_src_addr(), ipX_current_dest_addr()) != 0) {
+        goto chkerr;
+      }
+    } else
+#endif /* LWIP_UDPLITE */
+    {
+      if (udphdr->chksum != 0) {
+        if (ipX_chksum_pseudo(ip_current_is_v6(), p, IP_PROTO_UDP, p->tot_len,
+                              ipX_current_src_addr(),
+                              ipX_current_dest_addr()) != 0) {
+          goto chkerr;
+        }
+      }
+    }
+#endif /* CHECKSUM_CHECK_UDP */
+    if(pbuf_header(p, -UDP_HLEN)) {
+      /* Can we cope with this failing? Just assert for now */
+      LWIP_ASSERT("pbuf_header failed\n", 0);
+      UDP_STATS_INC(udp.drop);
+      snmp_inc_udpinerrors();
+      pbuf_free(p);
+      goto end;
+    }
+    if (pcb != NULL) {
+      snmp_inc_udpindatagrams();
+#if SO_REUSE && SO_REUSE_RXTOALL
+      if ((broadcast ||
+#if LWIP_IPV6
+          ip6_addr_ismulticast(ip6_current_dest_addr()) ||
+#endif /* LWIP_IPV6 */
+           ip_addr_ismulticast(ip_current_dest_addr())) &&
+          ip_get_option(pcb, SOF_REUSEADDR)) {
+        /* pass broadcast- or multicast packets to all multicast pcbs
+           if SOF_REUSEADDR is set on the first match */
+        struct udp_pcb *mpcb;
+        u8_t p_header_changed = 0;
+        s16_t hdrs_len = (s16_t)(ip_current_header_tot_len() + UDP_HLEN);
+        for (mpcb = udp_pcbs; mpcb != NULL; mpcb = mpcb->next) {
+          if (mpcb != pcb) {
+            /* compare PCB local addr+port to UDP destination addr+port */
+            if ((mpcb->local_port == dest) &&
+#if LWIP_IPV6
+                ((PCB_ISIPV6(mpcb) &&
+                  (ip6_addr_ismulticast(ip6_current_dest_addr()) ||
+                   ip6_addr_cmp(ipX_2_ip6(&mpcb->local_ip), ip6_current_dest_addr()))) ||
+                 (!PCB_ISIPV6(mpcb) &&
+#else /* LWIP_IPV6 */
+                ((
+#endif /* LWIP_IPV6 */
+                  ((!broadcast && ipX_addr_isany(0, &mpcb->local_ip)) ||
+                   ip_addr_cmp(ipX_2_ip(&mpcb->local_ip), ip_current_dest_addr()) ||
+#if LWIP_IGMP
+                   ip_addr_ismulticast(ip_current_dest_addr()) ||
+#endif /* LWIP_IGMP */
+#if IP_SOF_BROADCAST_RECV
+                   (broadcast && ip_get_option(mpcb, SOF_BROADCAST)))))) {
+#else  /* IP_SOF_BROADCAST_RECV */
+                   (broadcast))))) {
+#endif /* IP_SOF_BROADCAST_RECV */
+              /* pass a copy of the packet to all local matches */
+              if (mpcb->recv.ip4 != NULL) {
+                struct pbuf *q;
+                /* for that, move payload to IP header again */
+                if (p_header_changed == 0) {
+                  pbuf_header(p, hdrs_len);
+                  p_header_changed = 1;
+                }
+                q = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
+                if (q != NULL) {
+                  err_t err = pbuf_copy(q, p);
+                  if (err == ERR_OK) {
+                    /* move payload to UDP data */
+                    pbuf_header(q, -hdrs_len);
+#if LWIP_IPV6
+                    if (PCB_ISIPV6(mpcb)) {
+                      mpcb->recv.ip6(mpcb->recv_arg, mpcb, q, ip6_current_src_addr(), src);
+                    }
+                    else
+#endif /* LWIP_IPV6 */
+                    {
+                      mpcb->recv.ip4(mpcb->recv_arg, mpcb, q, ip_current_src_addr(), src);
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+        if (p_header_changed) {
+          /* and move payload to UDP data again */
+          pbuf_header(p, -hdrs_len);
+        }
+      }
+#endif /* SO_REUSE && SO_REUSE_RXTOALL */
+      /* callback */
+      if (pcb->recv.ip4 != NULL) {
+        /* now the recv function is responsible for freeing p */
+#if LWIP_IPV6
+        if (PCB_ISIPV6(pcb)) {
+          pcb->recv.ip6(pcb->recv_arg, pcb, p, ip6_current_src_addr(), src);
+        }
+        else
+#endif /* LWIP_IPV6 */
+        {
+          pcb->recv.ip4(pcb->recv_arg, pcb, p, ip_current_src_addr(), src);
+        }
+      } else {
+        /* no recv function registered? then we have to free the pbuf! */
+        pbuf_free(p);
+        goto end;
+      }
+    } else {
+      LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_input: not for us.\n"));
+
+#if LWIP_ICMP || LWIP_ICMP6
+      /* No match was found, send ICMP destination port unreachable unless
+         destination address was broadcast/multicast. */
+      if (!broadcast &&
+#if LWIP_IPV6
+          !ip6_addr_ismulticast(ip6_current_dest_addr()) &&
+#endif /* LWIP_IPV6 */
+          !ip_addr_ismulticast(ip_current_dest_addr())) {
+        /* move payload pointer back to ip header */
+        pbuf_header(p, ip_current_header_tot_len() + UDP_HLEN);
+        icmp_port_unreach(ip_current_is_v6(), p);
+      }
+#endif /* LWIP_ICMP || LWIP_ICMP6 */
+      UDP_STATS_INC(udp.proterr);
+      UDP_STATS_INC(udp.drop);
+      snmp_inc_udpnoports();
+      pbuf_free(p);
+    }
+  } else {
+    pbuf_free(p);
+  }
+end:
+  PERF_STOP("udp_input");
+  return;
+#if CHECKSUM_CHECK_UDP
+chkerr:
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+              ("udp_input: UDP (or UDP Lite) datagram discarded due to failing checksum\n"));
+  UDP_STATS_INC(udp.chkerr);
+  UDP_STATS_INC(udp.drop);
+  snmp_inc_udpinerrors();
+  pbuf_free(p);
+  PERF_STOP("udp_input");
+#endif /* CHECKSUM_CHECK_UDP */
+}
+
+/**
+ * Send data using UDP.
+ *
+ * @param pcb UDP PCB used to send the data.
+ * @param p chain of pbuf's to be sent.
+ *
+ * The datagram will be sent to the current remote_ip & remote_port
+ * stored in pcb. If the pcb is not bound to a port, it will
+ * automatically be bound to a random port.
+ *
+ * @return lwIP error code.
+ * - ERR_OK. Successful. No error occured.
+ * - ERR_MEM. Out of memory.
+ * - ERR_RTE. Could not find route to destination address.
+ * - More errors could be returned by lower protocol layers.
+ *
+ * @see udp_disconnect() udp_sendto()
+ */
+err_t
+udp_send(struct udp_pcb *pcb, struct pbuf *p)
+{
+  /* send to the packet using remote ip and port stored in the pcb */
+  return udp_sendto(pcb, p, ipX_2_ip(&pcb->remote_ip), pcb->remote_port);
+}
+
+#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
+/** Same as udp_send() but with checksum
+ */
+err_t
+udp_send_chksum(struct udp_pcb *pcb, struct pbuf *p,
+                u8_t have_chksum, u16_t chksum)
+{
+  /* send to the packet using remote ip and port stored in the pcb */
+  return udp_sendto_chksum(pcb, p, ipX_2_ip(&pcb->remote_ip), pcb->remote_port,
+    have_chksum, chksum);
+}
+#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+
+/**
+ * Send data to a specified address using UDP.
+ *
+ * @param pcb UDP PCB used to send the data.
+ * @param p chain of pbuf's to be sent.
+ * @param dst_ip Destination IP address.
+ * @param dst_port Destination UDP port.
+ *
+ * dst_ip & dst_port are expected to be in the same byte order as in the pcb.
+ *
+ * If the PCB already has a remote address association, it will
+ * be restored after the data is sent.
+ * 
+ * @return lwIP error code (@see udp_send for possible error codes)
+ *
+ * @see udp_disconnect() udp_send()
+ */
+err_t
+udp_sendto(struct udp_pcb *pcb, struct pbuf *p,
+  ip_addr_t *dst_ip, u16_t dst_port)
+{
+#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
+  return udp_sendto_chksum(pcb, p, dst_ip, dst_port, 0, 0);
+}
+
+/** Same as udp_sendto(), but with checksum */
+err_t
+udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *dst_ip,
+                  u16_t dst_port, u8_t have_chksum, u16_t chksum)
+{
+#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+  struct netif *netif;
+  ipX_addr_t *dst_ip_route = ip_2_ipX(dst_ip);
+
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send\n"));
+
+#if LWIP_IPV6 || LWIP_IGMP
+  if (ipX_addr_ismulticast(PCB_ISIPV6(pcb), dst_ip_route)) {
+    /* For multicast, find a netif based on source address. */
+#if LWIP_IPV6
+    if (PCB_ISIPV6(pcb)) {
+      dst_ip_route = &pcb->local_ip;
+    } else
+#endif /* LWIP_IPV6 */
+    {
+#if LWIP_IGMP
+      dst_ip_route = ip_2_ipX(&pcb->multicast_ip);
+#endif /* LWIP_IGMP */
+    }
+  }
+#endif /* LWIP_IPV6 || LWIP_IGMP */
+
+  /* find the outgoing network interface for this packet */
+  netif = ipX_route(PCB_ISIPV6(pcb), &pcb->local_ip, dst_ip_route);
+
+  /* no outgoing network interface could be found? */
+  if (netif == NULL) {
+    LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: No route to "));
+    ipX_addr_debug_print(PCB_ISIPV6(pcb), UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ip_2_ipX(dst_ip));
+    LWIP_DEBUGF(UDP_DEBUG, ("\n"));
+    UDP_STATS_INC(udp.rterr);
+    return ERR_RTE;
+  }
+#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
+  return udp_sendto_if_chksum(pcb, p, dst_ip, dst_port, netif, have_chksum, chksum);
+#else /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+  return udp_sendto_if(pcb, p, dst_ip, dst_port, netif);
+#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+}
+
+/**
+ * Send data to a specified address using UDP.
+ * The netif used for sending can be specified.
+ *
+ * This function exists mainly for DHCP, to be able to send UDP packets
+ * on a netif that is still down.
+ *
+ * @param pcb UDP PCB used to send the data.
+ * @param p chain of pbuf's to be sent.
+ * @param dst_ip Destination IP address.
+ * @param dst_port Destination UDP port.
+ * @param netif the netif used for sending.
+ *
+ * dst_ip & dst_port are expected to be in the same byte order as in the pcb.
+ *
+ * @return lwIP error code (@see udp_send for possible error codes)
+ *
+ * @see udp_disconnect() udp_send()
+ */
+err_t
+udp_sendto_if(struct udp_pcb *pcb, struct pbuf *p,
+  ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif)
+{
+#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
+  return udp_sendto_if_chksum(pcb, p, dst_ip, dst_port, netif, 0, 0);
+}
+
+/** Same as udp_sendto_if(), but with checksum */
+err_t
+udp_sendto_if_chksum(struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *dst_ip,
+                     u16_t dst_port, struct netif *netif, u8_t have_chksum,
+                     u16_t chksum)
+{
+#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+  struct udp_hdr *udphdr;
+  ip_addr_t *src_ip;
+  err_t err;
+  struct pbuf *q; /* q will be sent down the stack */
+  u8_t ip_proto;
+
+#if IP_SOF_BROADCAST
+  /* broadcast filter? */
+  if (!ip_get_option(pcb, SOF_BROADCAST) &&
+#if LWIP_IPV6
+      !PCB_ISIPV6(pcb) &&
+#endif /* LWIP_IPV6 */
+      ip_addr_isbroadcast(dst_ip, netif) ) {
+    LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+      ("udp_sendto_if: SOF_BROADCAST not enabled on pcb %p\n", (void *)pcb));
+    return ERR_VAL;
+  }
+#endif /* IP_SOF_BROADCAST */
+
+  /* if the PCB is not yet bound to a port, bind it here */
+  if (pcb->local_port == 0) {
+    LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send: not yet bound to a port, binding now\n"));
+    err = udp_bind(pcb, ipX_2_ip(&pcb->local_ip), pcb->local_port);
+    if (err != ERR_OK) {
+      LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: forced port bind failed\n"));
+      return err;
+    }
+  }
+
+  /* not enough space to add an UDP header to first pbuf in given p chain? */
+  if (pbuf_header(p, UDP_HLEN)) {
+    /* allocate header in a separate new pbuf */
+    q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
+    /* new header pbuf could not be allocated? */
+    if (q == NULL) {
+      LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: could not allocate header\n"));
+      return ERR_MEM;
+    }
+    if (p->tot_len != 0) {
+      /* chain header q in front of given pbuf p (only if p contains data) */
+      pbuf_chain(q, p);
+    }
+    /* first pbuf q points to header pbuf */
+    LWIP_DEBUGF(UDP_DEBUG,
+                ("udp_send: added header pbuf %p before given pbuf %p\n", (void *)q, (void *)p));
+  } else {
+    /* adding space for header within p succeeded */
+    /* first pbuf q equals given pbuf */
+    q = p;
+    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: added header in given pbuf %p\n", (void *)p));
+  }
+  LWIP_ASSERT("check that first pbuf can hold struct udp_hdr",
+              (q->len >= sizeof(struct udp_hdr)));
+  /* q now represents the packet to be sent */
+  udphdr = (struct udp_hdr *)q->payload;
+  udphdr->src = htons(pcb->local_port);
+  udphdr->dest = htons(dst_port);
+  /* in UDP, 0 checksum means 'no checksum' */
+  udphdr->chksum = 0x0000; 
+
+  /* Multicast Loop? */
+#if LWIP_IGMP
+  if (((pcb->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) &&
+#if LWIP_IPV6
+      (
+#if LWIP_IPV6_MLD
+       (PCB_ISIPV6(pcb) &&
+        ip6_addr_ismulticast(ip_2_ip6(dst_ip))) ||
+#endif /* LWIP_IPV6_MLD */
+       (!PCB_ISIPV6(pcb) &&
+#else /* LWIP_IPV6 */
+      ((
+#endif /* LWIP_IPV6 */
+        ip_addr_ismulticast(dst_ip)))) {
+    q->flags |= PBUF_FLAG_MCASTLOOP;
+  }
+#endif /* LWIP_IGMP */
+
+
+  /* PCB local address is IP_ANY_ADDR? */
+#if LWIP_IPV6
+  if (PCB_ISIPV6(pcb)) {
+    if (ip6_addr_isany(ipX_2_ip6(&pcb->local_ip))) {
+      src_ip = ip6_2_ip(ip6_select_source_address(netif, ip_2_ip6(dst_ip)));
+      if (src_ip == NULL) {
+        /* No suitable source address was found. */
+        if (q != p) {
+          /* free the header pbuf */
+          pbuf_free(q);
+          /* p is still referenced by the caller, and will live on */
+        }
+        return ERR_RTE;
+      }
+    } else {
+      /* use UDP PCB local IPv6 address as source address, if still valid. */
+      if (netif_matches_ip6_addr(netif, ipX_2_ip6(&pcb->local_ip)) < 0) {
+        /* Address isn't valid anymore. */
+        if (q != p) {
+          /* free the header pbuf */
+          pbuf_free(q);
+          /* p is still referenced by the caller, and will live on */
+        }
+        return ERR_RTE;
+      }
+      src_ip = ipX_2_ip(&pcb->local_ip);
+    }
+  }
+  else
+#endif /* LWIP_IPV6 */
+  if (ip_addr_isany(ipX_2_ip(&pcb->local_ip))) {
+    /* use outgoing network interface IP address as source address */
+    src_ip = &(netif->ip_addr);
+  } else {
+    /* check if UDP PCB local IP address is correct
+     * this could be an old address if netif->ip_addr has changed */
+    if (!ip_addr_cmp(ipX_2_ip(&(pcb->local_ip)), &(netif->ip_addr))) {
+      /* local_ip doesn't match, drop the packet */
+      if (q != p) {
+        /* free the header pbuf */
+        pbuf_free(q);
+        q = NULL;
+        /* p is still referenced by the caller, and will live on */
+      }
+      return ERR_VAL;
+    }
+    /* use UDP PCB local IP address as source address */
+    src_ip = ipX_2_ip(&(pcb->local_ip));
+  }
+
+  LWIP_DEBUGF(UDP_DEBUG, ("udp_send: sending datagram of length %"U16_F"\n", q->tot_len));
+
+#if LWIP_UDPLITE
+  /* UDP Lite protocol? */
+  if (pcb->flags & UDP_FLAGS_UDPLITE) {
+    u16_t chklen, chklen_hdr;
+    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP LITE packet length %"U16_F"\n", q->tot_len));
+    /* set UDP message length in UDP header */
+    chklen_hdr = chklen = pcb->chksum_len_tx;
+    if ((chklen < sizeof(struct udp_hdr)) || (chklen > q->tot_len)) {
+      if (chklen != 0) {
+        LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP LITE pcb->chksum_len is illegal: %"U16_F"\n", chklen));
+      }
+      /* For UDP-Lite, checksum length of 0 means checksum
+         over the complete packet. (See RFC 3828 chap. 3.1)
+         At least the UDP-Lite header must be covered by the
+         checksum, therefore, if chksum_len has an illegal
+         value, we generate the checksum over the complete
+         packet to be safe. */
+      chklen_hdr = 0;
+      chklen = q->tot_len;
+    }
+    udphdr->len = htons(chklen_hdr);
+    /* calculate checksum */
+#if CHECKSUM_GEN_UDP
+#if LWIP_CHECKSUM_ON_COPY
+    if (have_chksum) {
+      chklen = UDP_HLEN;
+    }
+#endif /* LWIP_CHECKSUM_ON_COPY */
+    udphdr->chksum = ipX_chksum_pseudo_partial(PCB_ISIPV6(pcb), q, IP_PROTO_UDPLITE,
+      q->tot_len, chklen, ip_2_ipX(src_ip), ip_2_ipX(dst_ip));
+#if LWIP_CHECKSUM_ON_COPY
+    if (have_chksum) {
+      u32_t acc;
+      acc = udphdr->chksum + (u16_t)~(chksum);
+      udphdr->chksum = FOLD_U32T(acc);
+    }
+#endif /* LWIP_CHECKSUM_ON_COPY */
+
+    /* chksum zero must become 0xffff, as zero means 'no checksum' */
+    if (udphdr->chksum == 0x0000) {
+      udphdr->chksum = 0xffff;
+    }
+#endif /* CHECKSUM_GEN_UDP */
+
+    ip_proto = IP_PROTO_UDPLITE;
+  } else
+#endif /* LWIP_UDPLITE */
+  {      /* UDP */
+    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP packet length %"U16_F"\n", q->tot_len));
+    udphdr->len = htons(q->tot_len);
+    /* calculate checksum */
+#if CHECKSUM_GEN_UDP
+    /* Checksum is mandatory over IPv6. */
+    if (PCB_ISIPV6(pcb) || (pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
+      u16_t udpchksum;
+#if LWIP_CHECKSUM_ON_COPY
+      if (have_chksum) {
+        u32_t acc;
+        udpchksum = ipX_chksum_pseudo_partial(PCB_ISIPV6(pcb), q, IP_PROTO_UDP,
+          q->tot_len, UDP_HLEN, ip_2_ipX(src_ip), ip_2_ipX(dst_ip));
+        acc = udpchksum + (u16_t)~(chksum);
+        udpchksum = FOLD_U32T(acc);
+      } else
+#endif /* LWIP_CHECKSUM_ON_COPY */
+      {
+        udpchksum = ipX_chksum_pseudo(PCB_ISIPV6(pcb), q, IP_PROTO_UDP, q->tot_len,
+          ip_2_ipX(src_ip), ip_2_ipX(dst_ip));
+      }
+
+      /* chksum zero must become 0xffff, as zero means 'no checksum' */
+      if (udpchksum == 0x0000) {
+        udpchksum = 0xffff;
+      }
+      udphdr->chksum = udpchksum;
+    }
+#endif /* CHECKSUM_GEN_UDP */
+    ip_proto = IP_PROTO_UDP;
+  }
+
+  LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP checksum 0x%04"X16_F"\n", udphdr->chksum));
+  LWIP_DEBUGF(UDP_DEBUG, ("udp_send: ip_output_if (,,,,0x%02"X16_F",)\n", (u16_t)ip_proto));
+  /* output to IP */
+  NETIF_SET_HWADDRHINT(netif, &(pcb->addr_hint));
+  err = ipX_output_if(PCB_ISIPV6(pcb), q, src_ip, dst_ip, pcb->ttl, pcb->tos, ip_proto, netif);
+  NETIF_SET_HWADDRHINT(netif, NULL);
+
+  /* TODO: must this be increased even if error occured? */
+  snmp_inc_udpoutdatagrams();
+
+  /* did we chain a separate header pbuf earlier? */
+  if (q != p) {
+    /* free the header pbuf */
+    pbuf_free(q);
+    q = NULL;
+    /* p is still referenced by the caller, and will live on */
+  }
+
+  UDP_STATS_INC(udp.xmit);
+  return err;
+}
+
+/**
+ * Bind an UDP PCB.
+ *
+ * @param pcb UDP PCB to be bound with a local address ipaddr and port.
+ * @param ipaddr local IP address to bind with. Use IP_ADDR_ANY to
+ * bind to all local interfaces.
+ * @param port local UDP port to bind with. Use 0 to automatically bind
+ * to a random port between UDP_LOCAL_PORT_RANGE_START and
+ * UDP_LOCAL_PORT_RANGE_END.
+ *
+ * ipaddr & port are expected to be in the same byte order as in the pcb.
+ *
+ * @return lwIP error code.
+ * - ERR_OK. Successful. No error occured.
+ * - ERR_USE. The specified ipaddr and port are already bound to by
+ * another UDP PCB.
+ *
+ * @see udp_disconnect()
+ */
+err_t
+udp_bind(struct udp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
+{
+  struct udp_pcb *ipcb;
+  u8_t rebind;
+
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_bind(ipaddr = "));
+  ipX_addr_debug_print(PCB_ISIPV6(pcb), UDP_DEBUG | LWIP_DBG_TRACE, ip_2_ipX(ipaddr));
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, (", port = %"U16_F")\n", port));
+
+  rebind = 0;
+  /* Check for double bind and rebind of the same pcb */
+  for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
+    /* is this UDP PCB already on active list? */
+    if (pcb == ipcb) {
+      /* pcb may occur at most once in active list */
+      LWIP_ASSERT("rebind == 0", rebind == 0);
+      /* pcb already in list, just rebind */
+      rebind = 1;
+    }
+
+    /* By default, we don't allow to bind to a port that any other udp
+       PCB is alread bound to, unless *all* PCBs with that port have tha
+       REUSEADDR flag set. */
+#if SO_REUSE
+    else if (!ip_get_option(pcb, SOF_REUSEADDR) &&
+             !ip_get_option(ipcb, SOF_REUSEADDR)) {
+#else /* SO_REUSE */
+    /* port matches that of PCB in list and REUSEADDR not set -> reject */
+    else {
+#endif /* SO_REUSE */
+      if ((ipcb->local_port == port) && IP_PCB_IPVER_EQ(pcb, ipcb) &&
+          /* IP address matches, or one is IP_ADDR_ANY? */
+            (ipX_addr_isany(PCB_ISIPV6(ipcb), &(ipcb->local_ip)) ||
+             ipX_addr_isany(PCB_ISIPV6(ipcb), ip_2_ipX(ipaddr)) ||
+             ipX_addr_cmp(PCB_ISIPV6(ipcb), &(ipcb->local_ip), ip_2_ipX(ipaddr)))) {
+        /* other PCB already binds to this local IP and port */
+        LWIP_DEBUGF(UDP_DEBUG,
+                    ("udp_bind: local port %"U16_F" already bound by another pcb\n", port));
+        return ERR_USE;
+      }
+    }
+  }
+
+  ipX_addr_set_ipaddr(PCB_ISIPV6(pcb), &pcb->local_ip, ipaddr);
+
+  /* no port specified? */
+  if (port == 0) {
+    port = udp_new_port();
+    if (port == 0) {
+      /* no more ports available in local range */
+      LWIP_DEBUGF(UDP_DEBUG, ("udp_bind: out of free UDP ports\n"));
+      return ERR_USE;
+    }
+  }
+  pcb->local_port = port;
+  snmp_insert_udpidx_tree(pcb);
+  /* pcb not active yet? */
+  if (rebind == 0) {
+    /* place the PCB on the active list if not already there */
+    pcb->next = udp_pcbs;
+    udp_pcbs = pcb;
+  }
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("udp_bind: bound to "));
+  ipX_addr_debug_print(PCB_ISIPV6(pcb), UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, &pcb->local_ip);
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, (", port %"U16_F")\n", pcb->local_port));
+  return ERR_OK;
+}
+
+/**
+ * Connect an UDP PCB.
+ *
+ * This will associate the UDP PCB with the remote address.
+ *
+ * @param pcb UDP PCB to be connected with remote address ipaddr and port.
+ * @param ipaddr remote IP address to connect with.
+ * @param port remote UDP port to connect with.
+ *
+ * @return lwIP error code
+ *
+ * ipaddr & port are expected to be in the same byte order as in the pcb.
+ *
+ * The udp pcb is bound to a random local port if not already bound.
+ *
+ * @see udp_disconnect()
+ */
+err_t
+udp_connect(struct udp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
+{
+  struct udp_pcb *ipcb;
+
+  if (pcb->local_port == 0) {
+    err_t err = udp_bind(pcb, ipX_2_ip(&pcb->local_ip), pcb->local_port);
+    if (err != ERR_OK) {
+      return err;
+    }
+  }
+
+  ipX_addr_set_ipaddr(PCB_ISIPV6(pcb), &pcb->remote_ip, ipaddr);
+  pcb->remote_port = port;
+  pcb->flags |= UDP_FLAGS_CONNECTED;
+/** TODO: this functionality belongs in upper layers */
+#ifdef LWIP_UDP_TODO
+#if LWIP_IPV6
+  if (!PCB_ISIPV6(pcb))
+#endif /* LWIP_IPV6 */
+  {
+    /* Nail down local IP for netconn_addr()/getsockname() */
+    if (ip_addr_isany(ipX_2_ip(&pcb->local_ip)) && !ip_addr_isany(ipX_2_ip(&pcb->remote_ip))) {
+      struct netif *netif;
+
+      if ((netif = ip_route(ipX_2_ip(&pcb->remote_ip))) == NULL) {
+        LWIP_DEBUGF(UDP_DEBUG, ("udp_connect: No route to 0x%lx\n",
+                    ip4_addr_get_u32(ipX_2_ip(&pcb->remote_ip))));
+        UDP_STATS_INC(udp.rterr);
+        return ERR_RTE;
+      }
+      /** TODO: this will bind the udp pcb locally, to the interface which
+          is used to route output packets to the remote address. However, we
+          might want to accept incoming packets on any interface! */
+      ipX_addr_copy(0, pcb->local_ip, netif->ip_addr);
+    } else if (ip_addr_isany(ipX_2_ip(&pcb->remote_ip))) {
+      ipX_addr_set_any(0, &pcb->local_ip);
+    }
+  }
+#endif
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("udp_connect: connected to "));
+  ipX_addr_debug_print(PCB_ISIPV6(pcb), UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE,
+                       &pcb->remote_ip);
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, (", port %"U16_F")\n", pcb->remote_port));
+
+  /* Insert UDP PCB into the list of active UDP PCBs. */
+  for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
+    if (pcb == ipcb) {
+      /* already on the list, just return */
+      return ERR_OK;
+    }
+  }
+  /* PCB not yet on the list, add PCB now */
+  pcb->next = udp_pcbs;
+  udp_pcbs = pcb;
+  return ERR_OK;
+}
+
+/**
+ * Disconnect a UDP PCB
+ *
+ * @param pcb the udp pcb to disconnect.
+ */
+void
+udp_disconnect(struct udp_pcb *pcb)
+{
+  /* reset remote address association */
+  ipX_addr_set_any(PCB_ISIPV6(pcb), &pcb->remote_ip);
+  pcb->remote_port = 0;
+  /* mark PCB as unconnected */
+  pcb->flags &= ~UDP_FLAGS_CONNECTED;
+}
+
+/**
+ * Set a receive callback for a UDP PCB
+ *
+ * This callback will be called when receiving a datagram for the pcb.
+ *
+ * @param pcb the pcb for wich to set the recv callback
+ * @param recv function pointer of the callback function
+ * @param recv_arg additional argument to pass to the callback function
+ */
+void
+udp_recv(struct udp_pcb *pcb, udp_recv_fn recv, void *recv_arg)
+{
+  /* remember recv() callback and user data */
+  pcb->recv.ip4 = recv;
+  pcb->recv_arg = recv_arg;
+}
+
+/**
+ * Remove an UDP PCB.
+ *
+ * @param pcb UDP PCB to be removed. The PCB is removed from the list of
+ * UDP PCB's and the data structure is freed from memory.
+ *
+ * @see udp_new()
+ */
+void
+udp_remove(struct udp_pcb *pcb)
+{
+  struct udp_pcb *pcb2;
+
+  snmp_delete_udpidx_tree(pcb);
+  /* pcb to be removed is first in list? */
+  if (udp_pcbs == pcb) {
+    /* make list start at 2nd pcb */
+    udp_pcbs = udp_pcbs->next;
+    /* pcb not 1st in list */
+  } else {
+    for (pcb2 = udp_pcbs; pcb2 != NULL; pcb2 = pcb2->next) {
+      /* find pcb in udp_pcbs list */
+      if (pcb2->next != NULL && pcb2->next == pcb) {
+        /* remove pcb from list */
+        pcb2->next = pcb->next;
+      }
+    }
+  }
+  memp_free(MEMP_UDP_PCB, pcb);
+}
+
+/**
+ * Create a UDP PCB.
+ *
+ * @return The UDP PCB which was created. NULL if the PCB data structure
+ * could not be allocated.
+ *
+ * @see udp_remove()
+ */
+struct udp_pcb *
+udp_new(void)
+{
+  struct udp_pcb *pcb;
+  pcb = (struct udp_pcb *)memp_malloc(MEMP_UDP_PCB);
+  /* could allocate UDP PCB? */
+  if (pcb != NULL) {
+    /* UDP Lite: by initializing to all zeroes, chksum_len is set to 0
+     * which means checksum is generated over the whole datagram per default
+     * (recommended as default by RFC 3828). */
+    /* initialize PCB to all zeroes */
+    memset(pcb, 0, sizeof(struct udp_pcb));
+    pcb->ttl = UDP_TTL;
+  }
+  return pcb;
+}
+
+#if LWIP_IPV6
+/**
+ * Create a UDP PCB for IPv6.
+ *
+ * @return The UDP PCB which was created. NULL if the PCB data structure
+ * could not be allocated.
+ *
+ * @see udp_remove()
+ */
+struct udp_pcb *
+udp_new_ip6(void)
+{
+  struct udp_pcb *pcb;
+  pcb = udp_new();
+  ip_set_v6(pcb, 1);
+  return pcb;
+}
+#endif /* LWIP_IPV6 */
+
+#if UDP_DEBUG
+/**
+ * Print UDP header information for debug purposes.
+ *
+ * @param udphdr pointer to the udp header in memory.
+ */
+void
+udp_debug_print(struct udp_hdr *udphdr)
+{
+  LWIP_DEBUGF(UDP_DEBUG, ("UDP header:\n"));
+  LWIP_DEBUGF(UDP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(UDP_DEBUG, ("|     %5"U16_F"     |     %5"U16_F"     | (src port, dest port)\n",
+                          ntohs(udphdr->src), ntohs(udphdr->dest)));
+  LWIP_DEBUGF(UDP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(UDP_DEBUG, ("|     %5"U16_F"     |     0x%04"X16_F"    | (len, chksum)\n",
+                          ntohs(udphdr->len), ntohs(udphdr->chksum)));
+  LWIP_DEBUGF(UDP_DEBUG, ("+-------------------------------+\n"));
+}
+#endif /* UDP_DEBUG */
+
+#endif /* LWIP_UDP */
diff --git a/external/badvpn_dns/lwip/src/include/ipv4/lwip/autoip.h b/external/badvpn_dns/lwip/src/include/ipv4/lwip/autoip.h
new file mode 100644
index 0000000..e62b72e
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv4/lwip/autoip.h
@@ -0,0 +1,118 @@
+/**
+ * @file
+ *
+ * AutoIP Automatic LinkLocal IP Configuration
+ */
+
+/*
+ *
+ * Copyright (c) 2007 Dominik Spies <kontakt@xxxxxxxxx>
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Dominik Spies <kontakt@xxxxxxxxx>
+ *
+ * This is a AutoIP implementation for the lwIP TCP/IP stack. It aims to conform
+ * with RFC 3927.
+ *
+ *
+ * Please coordinate changes and requests with Dominik Spies
+ * <kontakt@xxxxxxxxx>
+ */
+ 
+#ifndef __LWIP_AUTOIP_H__
+#define __LWIP_AUTOIP_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_AUTOIP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/netif.h"
+#include "lwip/udp.h"
+#include "netif/etharp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* AutoIP Timing */
+#define AUTOIP_TMR_INTERVAL      100
+#define AUTOIP_TICKS_PER_SECOND (1000 / AUTOIP_TMR_INTERVAL)
+
+/* RFC 3927 Constants */
+#define PROBE_WAIT               1   /* second   (initial random delay)                 */
+#define PROBE_MIN                1   /* second   (minimum delay till repeated probe)    */
+#define PROBE_MAX                2   /* seconds  (maximum delay till repeated probe)    */
+#define PROBE_NUM                3   /*          (number of probe packets)              */
+#define ANNOUNCE_NUM             2   /*          (number of announcement packets)       */
+#define ANNOUNCE_INTERVAL        2   /* seconds  (time between announcement packets)    */
+#define ANNOUNCE_WAIT            2   /* seconds  (delay before announcing)              */
+#define MAX_CONFLICTS            10  /*          (max conflicts before rate limiting)   */
+#define RATE_LIMIT_INTERVAL      60  /* seconds  (delay between successive attempts)    */
+#define DEFEND_INTERVAL          10  /* seconds  (min. wait between defensive ARPs)     */
+
+/* AutoIP client states */
+#define AUTOIP_STATE_OFF         0
+#define AUTOIP_STATE_PROBING     1
+#define AUTOIP_STATE_ANNOUNCING  2
+#define AUTOIP_STATE_BOUND       3
+
+struct autoip
+{
+  ip_addr_t llipaddr;       /* the currently selected, probed, announced or used LL IP-Address */
+  u8_t state;               /* current AutoIP state machine state */
+  u8_t sent_num;            /* sent number of probes or announces, dependent on state */
+  u16_t ttw;                /* ticks to wait, tick is AUTOIP_TMR_INTERVAL long */
+  u8_t lastconflict;        /* ticks until a conflict can be solved by defending */
+  u8_t tried_llipaddr;      /* total number of probed/used Link Local IP-Addresses */
+};
+
+
+#define autoip_init() /* Compatibility define, no init needed. */
+
+/** Set a struct autoip allocated by the application to work with */
+void autoip_set_struct(struct netif *netif, struct autoip *autoip);
+
+/** Start AutoIP client */
+err_t autoip_start(struct netif *netif);
+
+/** Stop AutoIP client */
+err_t autoip_stop(struct netif *netif);
+
+/** Handles every incoming ARP Packet, called by etharp_arp_input */
+void autoip_arp_reply(struct netif *netif, struct etharp_hdr *hdr);
+
+/** Has to be called in loop every AUTOIP_TMR_INTERVAL milliseconds */
+void autoip_tmr(void);
+
+/** Handle a possible change in the network configuration */
+void autoip_network_changed(struct netif *netif);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_AUTOIP */
+
+#endif /* __LWIP_AUTOIP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv4/lwip/icmp.h b/external/badvpn_dns/lwip/src/include/ipv4/lwip/icmp.h
new file mode 100644
index 0000000..fa893b6
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv4/lwip/icmp.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_ICMP_H__
+#define __LWIP_ICMP_H__
+
+#include "lwip/opt.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+
+#if LWIP_IPV6 && LWIP_ICMP6
+#include "lwip/icmp6.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ICMP_ER   0    /* echo reply */
+#define ICMP_DUR  3    /* destination unreachable */
+#define ICMP_SQ   4    /* source quench */
+#define ICMP_RD   5    /* redirect */
+#define ICMP_ECHO 8    /* echo */
+#define ICMP_TE  11    /* time exceeded */
+#define ICMP_PP  12    /* parameter problem */
+#define ICMP_TS  13    /* timestamp */
+#define ICMP_TSR 14    /* timestamp reply */
+#define ICMP_IRQ 15    /* information request */
+#define ICMP_IR  16    /* information reply */
+
+enum icmp_dur_type {
+  ICMP_DUR_NET   = 0,  /* net unreachable */
+  ICMP_DUR_HOST  = 1,  /* host unreachable */
+  ICMP_DUR_PROTO = 2,  /* protocol unreachable */
+  ICMP_DUR_PORT  = 3,  /* port unreachable */
+  ICMP_DUR_FRAG  = 4,  /* fragmentation needed and DF set */
+  ICMP_DUR_SR    = 5   /* source route failed */
+};
+
+enum icmp_te_type {
+  ICMP_TE_TTL  = 0,    /* time to live exceeded in transit */
+  ICMP_TE_FRAG = 1     /* fragment reassembly time exceeded */
+};
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+/** This is the standard ICMP header only that the u32_t data
+ *  is splitted to two u16_t like ICMP echo needs it.
+ *  This header is also used for other ICMP types that do not
+ *  use the data part.
+ */
+PACK_STRUCT_BEGIN
+struct icmp_echo_hdr {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u16_t id);
+  PACK_STRUCT_FIELD(u16_t seqno);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define ICMPH_TYPE(hdr) ((hdr)->type)
+#define ICMPH_CODE(hdr) ((hdr)->code)
+
+/** Combines type and code to an u16_t */
+#define ICMPH_TYPE_SET(hdr, t) ((hdr)->type = (t))
+#define ICMPH_CODE_SET(hdr, c) ((hdr)->code = (c))
+
+
+#if LWIP_ICMP /* don't build if not configured for use in lwipopts.h */
+
+void icmp_input(struct pbuf *p, struct netif *inp);
+void icmp_dest_unreach(struct pbuf *p, enum icmp_dur_type t);
+void icmp_time_exceeded(struct pbuf *p, enum icmp_te_type t);
+
+#endif /* LWIP_ICMP */
+
+#if (LWIP_IPV6 && LWIP_ICMP6)
+#define icmp_port_unreach(isipv6, pbuf) ((isipv6) ? \
+                                         icmp6_dest_unreach(pbuf, ICMP6_DUR_PORT) : \
+                                         icmp_dest_unreach(pbuf, ICMP_DUR_PORT))
+#elif LWIP_ICMP
+#define icmp_port_unreach(isipv6, pbuf) icmp_dest_unreach(pbuf, ICMP_DUR_PORT)
+#else /* (LWIP_IPV6 && LWIP_ICMP6) || LWIP_ICMP*/
+#define icmp_port_unreach(isipv6, pbuf)
+#endif /* (LWIP_IPV6 && LWIP_ICMP6) || LWIP_ICMP*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_ICMP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv4/lwip/igmp.h b/external/badvpn_dns/lwip/src/include/ipv4/lwip/igmp.h
new file mode 100644
index 0000000..8aabac2
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv4/lwip/igmp.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2002 CITEL Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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 CITEL Technologies Ltd 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 CITEL TECHNOLOGIES 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 CITEL TECHNOLOGIES OR CONTRIBUTORS 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. 
+ *
+ * This file is a contribution to the lwIP TCP/IP stack.
+ * The Swedish Institute of Computer Science and Adam Dunkels
+ * are specifically granted permission to redistribute this
+ * source code.
+*/
+
+#ifndef __LWIP_IGMP_H__
+#define __LWIP_IGMP_H__
+
+#include "lwip/opt.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+#include "lwip/pbuf.h"
+
+#if LWIP_IGMP /* don't build if not configured for use in lwipopts.h */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* IGMP timer */
+#define IGMP_TMR_INTERVAL              100 /* Milliseconds */
+#define IGMP_V1_DELAYING_MEMBER_TMR   (1000/IGMP_TMR_INTERVAL)
+#define IGMP_JOIN_DELAYING_MEMBER_TMR (500 /IGMP_TMR_INTERVAL)
+
+/* MAC Filter Actions, these are passed to a netif's
+ * igmp_mac_filter callback function. */
+#define IGMP_DEL_MAC_FILTER            0
+#define IGMP_ADD_MAC_FILTER            1
+
+
+/**
+ * igmp group structure - there is
+ * a list of groups for each interface
+ * these should really be linked from the interface, but
+ * if we keep them separate we will not affect the lwip original code
+ * too much
+ * 
+ * There will be a group for the all systems group address but this 
+ * will not run the state machine as it is used to kick off reports
+ * from all the other groups
+ */
+struct igmp_group {
+  /** next link */
+  struct igmp_group *next;
+  /** interface on which the group is active */
+  struct netif      *netif;
+  /** multicast address */
+  ip_addr_t          group_address;
+  /** signifies we were the last person to report */
+  u8_t               last_reporter_flag;
+  /** current state of the group */
+  u8_t               group_state;
+  /** timer for reporting, negative is OFF */
+  u16_t              timer;
+  /** counter of simultaneous uses */
+  u8_t               use;
+};
+
+/*  Prototypes */
+void   igmp_init(void);
+err_t  igmp_start(struct netif *netif);
+err_t  igmp_stop(struct netif *netif);
+void   igmp_report_groups(struct netif *netif);
+struct igmp_group *igmp_lookfor_group(struct netif *ifp, ip_addr_t *addr);
+void   igmp_input(struct pbuf *p, struct netif *inp, ip_addr_t *dest);
+err_t  igmp_joingroup(ip_addr_t *ifaddr, ip_addr_t *groupaddr);
+err_t  igmp_leavegroup(ip_addr_t *ifaddr, ip_addr_t *groupaddr);
+void   igmp_tmr(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IGMP */
+
+#endif /* __LWIP_IGMP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv4/lwip/inet.h b/external/badvpn_dns/lwip/src/include/ipv4/lwip/inet.h
new file mode 100644
index 0000000..7bff49b
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv4/lwip/inet.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_INET_H__
+#define __LWIP_INET_H__
+
+#include "lwip/opt.h"
+#include "lwip/def.h"
+#include "lwip/ip_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** For compatibility with BSD code */
+struct in_addr {
+  u32_t s_addr;
+};
+
+/** 255.255.255.255 */
+#define INADDR_NONE         IPADDR_NONE
+/** 127.0.0.1 */
+#define INADDR_LOOPBACK     IPADDR_LOOPBACK
+/** 0.0.0.0 */
+#define INADDR_ANY          IPADDR_ANY
+/** 255.255.255.255 */
+#define INADDR_BROADCAST    IPADDR_BROADCAST
+
+/* Definitions of the bits in an Internet address integer.
+
+   On subnets, host and network parts are found according to
+   the subnet mask, not these masks.  */
+#define IN_CLASSA(a)        IP_CLASSA(a)
+#define IN_CLASSA_NET       IP_CLASSA_NET
+#define IN_CLASSA_NSHIFT    IP_CLASSA_NSHIFT
+#define IN_CLASSA_HOST      IP_CLASSA_HOST
+#define IN_CLASSA_MAX       IP_CLASSA_MAX
+
+#define IN_CLASSB(b)        IP_CLASSB(b)
+#define IN_CLASSB_NET       IP_CLASSB_NET
+#define IN_CLASSB_NSHIFT    IP_CLASSB_NSHIFT
+#define IN_CLASSB_HOST      IP_CLASSB_HOST
+#define IN_CLASSB_MAX       IP_CLASSB_MAX
+
+#define IN_CLASSC(c)        IP_CLASSC(c)
+#define IN_CLASSC_NET       IP_CLASSC_NET
+#define IN_CLASSC_NSHIFT    IP_CLASSC_NSHIFT
+#define IN_CLASSC_HOST      IP_CLASSC_HOST
+#define IN_CLASSC_MAX       IP_CLASSC_MAX
+
+#define IN_CLASSD(d)        IP_CLASSD(d)
+#define IN_CLASSD_NET       IP_CLASSD_NET     /* These ones aren't really */
+#define IN_CLASSD_NSHIFT    IP_CLASSD_NSHIFT  /*   net and host fields, but */
+#define IN_CLASSD_HOST      IP_CLASSD_HOST    /*   routing needn't know. */
+#define IN_CLASSD_MAX       IP_CLASSD_MAX
+
+#define IN_MULTICAST(a)     IP_MULTICAST(a)
+
+#define IN_EXPERIMENTAL(a)  IP_EXPERIMENTAL(a)
+#define IN_BADCLASS(a)      IP_BADCLASS(a)
+
+#define IN_LOOPBACKNET      IP_LOOPBACKNET
+
+#define inet_addr_from_ipaddr(target_inaddr, source_ipaddr) ((target_inaddr)->s_addr = ip4_addr_get_u32(source_ipaddr))
+#define inet_addr_to_ipaddr(target_ipaddr, source_inaddr)   (ip4_addr_set_u32(target_ipaddr, (source_inaddr)->s_addr))
+/* ATTENTION: the next define only works because both s_addr and ip_addr_t are an u32_t effectively! */
+#define inet_addr_to_ipaddr_p(target_ipaddr_p, source_inaddr)   ((target_ipaddr_p) = (ip_addr_t*)&((source_inaddr)->s_addr))
+
+/* directly map this to the lwip internal functions */
+#define inet_addr(cp)         ipaddr_addr(cp)
+#define inet_aton(cp, addr)   ipaddr_aton(cp, (ip_addr_t*)addr)
+#define inet_ntoa(addr)       ipaddr_ntoa((ip_addr_t*)&(addr))
+#define inet_ntoa_r(addr, buf, buflen) ipaddr_ntoa_r((ip_addr_t*)&(addr), buf, buflen)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_INET_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv4/lwip/ip4.h b/external/badvpn_dns/lwip/src/include/ipv4/lwip/ip4.h
new file mode 100644
index 0000000..04b5b8a
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv4/lwip/ip4.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_IP4_H__
+#define __LWIP_IP4_H__
+
+#include "lwip/opt.h"
+
+#include "lwip/def.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip_addr.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/err.h"
+#include "lwip/netif.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Currently, the function ip_output_if_opt() is only used with IGMP */
+#define IP_OPTIONS_SEND   LWIP_IGMP
+
+#define IP_HLEN 20
+
+#define IP_PROTO_ICMP    1
+#define IP_PROTO_IGMP    2
+#define IP_PROTO_UDP     17
+#define IP_PROTO_UDPLITE 136
+#define IP_PROTO_TCP     6
+
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ip_hdr {
+  /* version / header length */
+  PACK_STRUCT_FIELD(u8_t _v_hl);
+  /* type of service */
+  PACK_STRUCT_FIELD(u8_t _tos);
+  /* total length */
+  PACK_STRUCT_FIELD(u16_t _len);
+  /* identification */
+  PACK_STRUCT_FIELD(u16_t _id);
+  /* fragment offset field */
+  PACK_STRUCT_FIELD(u16_t _offset);
+#define IP_RF 0x8000U        /* reserved fragment flag */
+#define IP_DF 0x4000U        /* dont fragment flag */
+#define IP_MF 0x2000U        /* more fragments flag */
+#define IP_OFFMASK 0x1fffU   /* mask for fragmenting bits */
+  /* time to live */
+  PACK_STRUCT_FIELD(u8_t _ttl);
+  /* protocol*/
+  PACK_STRUCT_FIELD(u8_t _proto);
+  /* checksum */
+  PACK_STRUCT_FIELD(u16_t _chksum);
+  /* source and destination IP addresses */
+  PACK_STRUCT_FIELD(ip_addr_p_t src);
+  PACK_STRUCT_FIELD(ip_addr_p_t dest); 
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define IPH_V(hdr)  ((hdr)->_v_hl >> 4)
+#define IPH_HL(hdr) ((hdr)->_v_hl & 0x0f)
+#define IPH_TOS(hdr) ((hdr)->_tos)
+#define IPH_LEN(hdr) ((hdr)->_len)
+#define IPH_ID(hdr) ((hdr)->_id)
+#define IPH_OFFSET(hdr) ((hdr)->_offset)
+#define IPH_TTL(hdr) ((hdr)->_ttl)
+#define IPH_PROTO(hdr) ((hdr)->_proto)
+#define IPH_CHKSUM(hdr) ((hdr)->_chksum)
+
+#define IPH_VHL_SET(hdr, v, hl) (hdr)->_v_hl = (((v) << 4) | (hl))
+#define IPH_TOS_SET(hdr, tos) (hdr)->_tos = (tos)
+#define IPH_LEN_SET(hdr, len) (hdr)->_len = (len)
+#define IPH_ID_SET(hdr, id) (hdr)->_id = (id)
+#define IPH_OFFSET_SET(hdr, off) (hdr)->_offset = (off)
+#define IPH_TTL_SET(hdr, ttl) (hdr)->_ttl = (u8_t)(ttl)
+#define IPH_PROTO_SET(hdr, proto) (hdr)->_proto = (u8_t)(proto)
+#define IPH_CHKSUM_SET(hdr, chksum) (hdr)->_chksum = (chksum)
+
+
+#define ip_init() /* Compatibility define, no init needed. */
+struct netif *ip_route(ip_addr_t *dest);
+err_t ip_input(struct pbuf *p, struct netif *inp);
+err_t ip_output(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
+       u8_t ttl, u8_t tos, u8_t proto);
+err_t ip_output_if(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
+       u8_t ttl, u8_t tos, u8_t proto,
+       struct netif *netif);
+#if LWIP_NETIF_HWADDRHINT
+err_t ip_output_hinted(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
+       u8_t ttl, u8_t tos, u8_t proto, u8_t *addr_hint);
+#endif /* LWIP_NETIF_HWADDRHINT */
+#if IP_OPTIONS_SEND
+err_t ip_output_if_opt(struct pbuf *p, ip_addr_t *src, ip_addr_t *dest,
+       u8_t ttl, u8_t tos, u8_t proto, struct netif *netif, void *ip_options,
+       u16_t optlen);
+#endif /* IP_OPTIONS_SEND */
+
+#define ip_netif_get_local_ipX(netif) (((netif) != NULL) ? ip_2_ipX(&((netif)->ip_addr)) : NULL)
+
+#if IP_DEBUG
+void ip_debug_print(struct pbuf *p);
+#else
+#define ip_debug_print(p)
+#endif /* IP_DEBUG */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_IP_H__ */
+
+
diff --git a/external/badvpn_dns/lwip/src/include/ipv4/lwip/ip4_addr.h b/external/badvpn_dns/lwip/src/include/ipv4/lwip/ip4_addr.h
new file mode 100644
index 0000000..b05ae53
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv4/lwip/ip4_addr.h
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_IP4_ADDR_H__
+#define __LWIP_IP4_ADDR_H__
+
+#include "lwip/opt.h"
+#include "lwip/def.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is the aligned version of ip_addr_t,
+   used as local variable, on the stack, etc. */
+struct ip_addr {
+  u32_t addr;
+};
+
+/* This is the packed version of ip_addr_t,
+   used in network headers that are itself packed */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ip_addr_packed {
+  PACK_STRUCT_FIELD(u32_t addr);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** ip_addr_t uses a struct for convenience only, so that the same defines can
+ * operate both on ip_addr_t as well as on ip_addr_p_t. */
+typedef struct ip_addr ip_addr_t;
+typedef struct ip_addr_packed ip_addr_p_t;
+
+/*
+ * struct ipaddr2 is used in the definition of the ARP packet format in
+ * order to support compilers that don't have structure packing.
+ */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ip_addr2 {
+  PACK_STRUCT_FIELD(u16_t addrw[2]);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/* Forward declaration to not include netif.h */
+struct netif;
+
+extern const ip_addr_t ip_addr_any;
+extern const ip_addr_t ip_addr_broadcast;
+
+/** IP_ADDR_ can be used as a fixed IP address
+ *  for the wildcard and the broadcast address
+ */
+#define IP_ADDR_ANY         ((ip_addr_t *)&ip_addr_any)
+#define IP_ADDR_BROADCAST   ((ip_addr_t *)&ip_addr_broadcast)
+
+/** 255.255.255.255 */
+#define IPADDR_NONE         ((u32_t)0xffffffffUL)
+/** 127.0.0.1 */
+#define IPADDR_LOOPBACK     ((u32_t)0x7f000001UL)
+/** 0.0.0.0 */
+#define IPADDR_ANY          ((u32_t)0x00000000UL)
+/** 255.255.255.255 */
+#define IPADDR_BROADCAST    ((u32_t)0xffffffffUL)
+
+/* Definitions of the bits in an Internet address integer.
+
+   On subnets, host and network parts are found according to
+   the subnet mask, not these masks.  */
+#define IP_CLASSA(a)        ((((u32_t)(a)) & 0x80000000UL) == 0)
+#define IP_CLASSA_NET       0xff000000
+#define IP_CLASSA_NSHIFT    24
+#define IP_CLASSA_HOST      (0xffffffff & ~IP_CLASSA_NET)
+#define IP_CLASSA_MAX       128
+
+#define IP_CLASSB(a)        ((((u32_t)(a)) & 0xc0000000UL) == 0x80000000UL)
+#define IP_CLASSB_NET       0xffff0000
+#define IP_CLASSB_NSHIFT    16
+#define IP_CLASSB_HOST      (0xffffffff & ~IP_CLASSB_NET)
+#define IP_CLASSB_MAX       65536
+
+#define IP_CLASSC(a)        ((((u32_t)(a)) & 0xe0000000UL) == 0xc0000000UL)
+#define IP_CLASSC_NET       0xffffff00
+#define IP_CLASSC_NSHIFT    8
+#define IP_CLASSC_HOST      (0xffffffff & ~IP_CLASSC_NET)
+
+#define IP_CLASSD(a)        (((u32_t)(a) & 0xf0000000UL) == 0xe0000000UL)
+#define IP_CLASSD_NET       0xf0000000          /* These ones aren't really */
+#define IP_CLASSD_NSHIFT    28                  /*   net and host fields, but */
+#define IP_CLASSD_HOST      0x0fffffff          /*   routing needn't know. */
+#define IP_MULTICAST(a)     IP_CLASSD(a)
+
+#define IP_EXPERIMENTAL(a)  (((u32_t)(a) & 0xf0000000UL) == 0xf0000000UL)
+#define IP_BADCLASS(a)      (((u32_t)(a) & 0xf0000000UL) == 0xf0000000UL)
+
+#define IP_LOOPBACKNET      127                 /* official! */
+
+
+#if BYTE_ORDER == BIG_ENDIAN
+/** Set an IP address given by the four byte-parts */
+#define IP4_ADDR(ipaddr, a,b,c,d) \
+        (ipaddr)->addr = ((u32_t)((a) & 0xff) << 24) | \
+                         ((u32_t)((b) & 0xff) << 16) | \
+                         ((u32_t)((c) & 0xff) << 8)  | \
+                          (u32_t)((d) & 0xff)
+#else
+/** Set an IP address given by the four byte-parts.
+    Little-endian version that prevents the use of htonl. */
+#define IP4_ADDR(ipaddr, a,b,c,d) \
+        (ipaddr)->addr = ((u32_t)((d) & 0xff) << 24) | \
+                         ((u32_t)((c) & 0xff) << 16) | \
+                         ((u32_t)((b) & 0xff) << 8)  | \
+                          (u32_t)((a) & 0xff)
+#endif
+
+/** MEMCPY-like copying of IP addresses where addresses are known to be
+ * 16-bit-aligned if the port is correctly configured (so a port could define
+ * this to copying 2 u16_t's) - no NULL-pointer-checking needed. */
+#ifndef IPADDR2_COPY
+#define IPADDR2_COPY(dest, src) SMEMCPY(dest, src, sizeof(ip_addr_t))
+#endif
+
+/** Copy IP address - faster than ip_addr_set: no NULL check */
+#define ip_addr_copy(dest, src) ((dest).addr = (src).addr)
+/** Safely copy one IP address to another (src may be NULL) */
+#define ip_addr_set(dest, src) ((dest)->addr = \
+                                    ((src) == NULL ? 0 : \
+                                    (src)->addr))
+/** Set complete address to zero */
+#define ip_addr_set_zero(ipaddr)      ((ipaddr)->addr = 0)
+/** Set address to IPADDR_ANY (no need for htonl()) */
+#define ip_addr_set_any(ipaddr)       ((ipaddr)->addr = IPADDR_ANY)
+/** Set address to loopback address */
+#define ip_addr_set_loopback(ipaddr)  ((ipaddr)->addr = PP_HTONL(IPADDR_LOOPBACK))
+/** Safely copy one IP address to another and change byte order
+ * from host- to network-order. */
+#define ip_addr_set_hton(dest, src) ((dest)->addr = \
+                               ((src) == NULL ? 0:\
+                               htonl((src)->addr)))
+/** IPv4 only: set the IP address given as an u32_t */
+#define ip4_addr_set_u32(dest_ipaddr, src_u32) ((dest_ipaddr)->addr = (src_u32))
+/** IPv4 only: get the IP address as an u32_t */
+#define ip4_addr_get_u32(src_ipaddr) ((src_ipaddr)->addr)
+
+/** Get the network address by combining host address with netmask */
+#define ip_addr_get_network(target, host, netmask) ((target)->addr = ((host)->addr) & ((netmask)->addr))
+
+/**
+ * Determine if two address are on the same network.
+ *
+ * @arg addr1 IP address 1
+ * @arg addr2 IP address 2
+ * @arg mask network identifier mask
+ * @return !0 if the network identifiers of both address match
+ */
+#define ip_addr_netcmp(addr1, addr2, mask) (((addr1)->addr & \
+                                              (mask)->addr) == \
+                                             ((addr2)->addr & \
+                                              (mask)->addr))
+#define ip_addr_cmp(addr1, addr2) ((addr1)->addr == (addr2)->addr)
+
+#define ip_addr_isany(addr1) ((addr1) == NULL || (addr1)->addr == IPADDR_ANY)
+
+#define ip_addr_isbroadcast(ipaddr, netif) ip4_addr_isbroadcast((ipaddr)->addr, (netif))
+u8_t ip4_addr_isbroadcast(u32_t addr, const struct netif *netif);
+
+#define ip_addr_netmask_valid(netmask) ip4_addr_netmask_valid((netmask)->addr)
+u8_t ip4_addr_netmask_valid(u32_t netmask);
+
+#define ip_addr_ismulticast(addr1) (((addr1)->addr & PP_HTONL(0xf0000000UL)) == PP_HTONL(0xe0000000UL))
+
+#define ip_addr_islinklocal(addr1) (((addr1)->addr & PP_HTONL(0xffff0000UL)) == PP_HTONL(0xa9fe0000UL))
+
+#define ip_addr_debug_print(debug, ipaddr) \
+  LWIP_DEBUGF(debug, ("%"U16_F".%"U16_F".%"U16_F".%"U16_F,             \
+                      ipaddr != NULL ? ip4_addr1_16(ipaddr) : 0,       \
+                      ipaddr != NULL ? ip4_addr2_16(ipaddr) : 0,       \
+                      ipaddr != NULL ? ip4_addr3_16(ipaddr) : 0,       \
+                      ipaddr != NULL ? ip4_addr4_16(ipaddr) : 0))
+
+/* Get one byte from the 4-byte address */
+#define ip4_addr1(ipaddr) (((u8_t*)(ipaddr))[0])
+#define ip4_addr2(ipaddr) (((u8_t*)(ipaddr))[1])
+#define ip4_addr3(ipaddr) (((u8_t*)(ipaddr))[2])
+#define ip4_addr4(ipaddr) (((u8_t*)(ipaddr))[3])
+/* These are cast to u16_t, with the intent that they are often arguments
+ * to printf using the U16_F format from cc.h. */
+#define ip4_addr1_16(ipaddr) ((u16_t)ip4_addr1(ipaddr))
+#define ip4_addr2_16(ipaddr) ((u16_t)ip4_addr2(ipaddr))
+#define ip4_addr3_16(ipaddr) ((u16_t)ip4_addr3(ipaddr))
+#define ip4_addr4_16(ipaddr) ((u16_t)ip4_addr4(ipaddr))
+
+/** For backwards compatibility */
+#define ip_ntoa(ipaddr)  ipaddr_ntoa(ipaddr)
+
+u32_t ipaddr_addr(const char *cp);
+int ipaddr_aton(const char *cp, ip_addr_t *addr);
+/** returns ptr to static buffer; not reentrant! */
+char *ipaddr_ntoa(const ip_addr_t *addr);
+char *ipaddr_ntoa_r(const ip_addr_t *addr, char *buf, int buflen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_IP_ADDR_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv4/lwip/ip_frag.h b/external/badvpn_dns/lwip/src/include/ipv4/lwip/ip_frag.h
new file mode 100644
index 0000000..47eca9f
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv4/lwip/ip_frag.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Jani Monoses <jani@xxxxx>
+ *
+ */
+
+#ifndef __LWIP_IP_FRAG_H__
+#define __LWIP_IP_FRAG_H__
+
+#include "lwip/opt.h"
+#include "lwip/err.h"
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+#include "lwip/ip_addr.h"
+#include "lwip/ip.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if IP_REASSEMBLY
+/* The IP reassembly timer interval in milliseconds. */
+#define IP_TMR_INTERVAL 1000
+
+/* IP reassembly helper struct.
+ * This is exported because memp needs to know the size.
+ */
+struct ip_reassdata {
+  struct ip_reassdata *next;
+  struct pbuf *p;
+  struct ip_hdr iphdr;
+  u16_t datagram_len;
+  u8_t flags;
+  u8_t timer;
+};
+
+void ip_reass_init(void);
+void ip_reass_tmr(void);
+struct pbuf * ip_reass(struct pbuf *p);
+#endif /* IP_REASSEMBLY */
+
+#if IP_FRAG
+#if !IP_FRAG_USES_STATIC_BUF && !LWIP_NETIF_TX_SINGLE_PBUF
+/** A custom pbuf that holds a reference to another pbuf, which is freed
+ * when this custom pbuf is freed. This is used to create a custom PBUF_REF
+ * that points into the original pbuf. */
+#ifndef __LWIP_PBUF_CUSTOM_REF__
+#define __LWIP_PBUF_CUSTOM_REF__
+struct pbuf_custom_ref {
+  /** 'base class' */
+  struct pbuf_custom pc;
+  /** pointer to the original pbuf that is referenced */
+  struct pbuf *original;
+};
+#endif /* __LWIP_PBUF_CUSTOM_REF__ */
+#endif /* !IP_FRAG_USES_STATIC_BUF && !LWIP_NETIF_TX_SINGLE_PBUF */
+
+err_t ip_frag(struct pbuf *p, struct netif *netif, ip_addr_t *dest);
+#endif /* IP_FRAG */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_IP_FRAG_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv6/lwip/dhcp6.h b/external/badvpn_dns/lwip/src/include/ipv6/lwip/dhcp6.h
new file mode 100644
index 0000000..4b905c5
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv6/lwip/dhcp6.h
@@ -0,0 +1,58 @@
+/**
+ * @file
+ *
+ * IPv6 address autoconfiguration as per RFC 4862.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ * IPv6 address autoconfiguration as per RFC 4862.
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#ifndef __LWIP_IP6_DHCP6_H__
+#define __LWIP_IP6_DHCP6_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6_DHCP6  /* don't build if not configured for use in lwipopts.h */
+
+
+struct dhcp6
+{
+  /*TODO: implement DHCP6*/
+};
+
+#endif /* LWIP_IPV6_DHCP6 */
+
+#endif /* __LWIP_IP6_DHCP6_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv6/lwip/ethip6.h b/external/badvpn_dns/lwip/src/include/ipv6/lwip/ethip6.h
new file mode 100644
index 0000000..e7f412b
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv6/lwip/ethip6.h
@@ -0,0 +1,68 @@
+/**
+ * @file
+ *
+ * Ethernet output for IPv6. Uses ND tables for link-layer addressing.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#ifndef __LWIP_ETHIP6_H__
+#define __LWIP_ETHIP6_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6 && LWIP_ETHERNET /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/pbuf.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/netif.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+err_t ethip6_output(struct netif *netif, struct pbuf *q, ip6_addr_t *ip6addr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV6 && LWIP_ETHERNET */
+
+#endif /* __LWIP_ETHIP6_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv6/lwip/icmp6.h b/external/badvpn_dns/lwip/src/include/ipv6/lwip/icmp6.h
new file mode 100644
index 0000000..74bfdbe
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv6/lwip/icmp6.h
@@ -0,0 +1,152 @@
+/**
+ * @file
+ *
+ * IPv6 version of ICMP, as per RFC 4443.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+#ifndef __LWIP_ICMP6_H__
+#define __LWIP_ICMP6_H__
+
+#include "lwip/opt.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/netif.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum icmp6_type {
+  ICMP6_TYPE_DUR = 1,  /* Destination unreachable */
+  ICMP6_TYPE_PTB = 2,  /* Packet too big */
+  ICMP6_TYPE_TE = 3,   /* Time exceeded */
+  ICMP6_TYPE_PP = 4,   /* Parameter problem */
+  ICMP6_TYPE_PE1 = 100,  /* Private experimentation */
+  ICMP6_TYPE_PE2 = 101,  /* Private experimentation */
+  ICMP6_TYPE_RSV_ERR = 127,  /* Reserved for expansion of error messages */
+
+  ICMP6_TYPE_EREQ = 128,  /* Echo request */
+  ICMP6_TYPE_EREP = 129,  /* Echo reply */
+  ICMP6_TYPE_MLQ = 130,  /* Multicast listener query */
+  ICMP6_TYPE_MLR = 131,  /* Multicast listener report */
+  ICMP6_TYPE_MLD = 132,  /* Multicast listener done */
+  ICMP6_TYPE_RS = 133,  /* Router solicitation */
+  ICMP6_TYPE_RA = 134,  /* Router advertisement */
+  ICMP6_TYPE_NS = 135,  /* Neighbor solicitation */
+  ICMP6_TYPE_NA = 136,  /* Neighbor advertisement */
+  ICMP6_TYPE_RD = 137,  /* Redirect */
+  ICMP6_TYPE_MRA = 151,  /* Multicast router advertisement */
+  ICMP6_TYPE_MRS = 152,  /* Multicast router solicitation */
+  ICMP6_TYPE_MRT = 153,  /* Multicast router termination */
+  ICMP6_TYPE_PE3 = 200,  /* Private experimentation */
+  ICMP6_TYPE_PE4 = 201,  /* Private experimentation */
+  ICMP6_TYPE_RSV_INF = 255  /* Reserved for expansion of informational messages */
+};
+
+enum icmp6_dur_code {
+  ICMP6_DUR_NO_ROUTE = 0,     /* No route to destination */
+  ICMP6_DUR_PROHIBITED = 1,   /* Communication with destination administratively prohibited */
+  ICMP6_DUR_SCOPE = 2,        /* Beyond scope of source address */
+  ICMP6_DUR_ADDRESS = 3,      /* Address unreachable */
+  ICMP6_DUR_PORT = 4,         /* Port unreachable */
+  ICMP6_DUR_POLICY = 5,       /* Source address failed ingress/egress policy */
+  ICMP6_DUR_REJECT_ROUTE = 6  /* Reject route to destination */
+};
+
+enum icmp6_te_code {
+  ICMP6_TE_HL = 0,   /* Hop limit exceeded in transit */
+  ICMP6_TE_FRAG = 1  /* Fragment reassembly time exceeded */
+};
+
+enum icmp6_pp_code {
+  ICMP6_PP_FIELD = 0,   /* Erroneous header field encountered */
+  ICMP6_PP_HEADER = 1,  /* Unrecognized next header type encountered */
+  ICMP6_PP_OPTION = 2   /* Unrecognized IPv6 option encountered */
+};
+
+/** This is the standard ICMP6 header. */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct icmp6_hdr {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u32_t data);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** This is the ICMP6 header adapted for echo req/resp. */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct icmp6_echo_hdr {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u16_t id);
+  PACK_STRUCT_FIELD(u16_t seqno);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+
+#if LWIP_ICMP6 && LWIP_IPV6 /* don't build if not configured for use in lwipopts.h */
+
+void icmp6_input(struct pbuf *p, struct netif *inp);
+void icmp6_dest_unreach(struct pbuf *p, enum icmp6_dur_code c);
+void icmp6_packet_too_big(struct pbuf *p, u32_t mtu);
+void icmp6_time_exceeded(struct pbuf *p, enum icmp6_te_code c);
+void icmp6_param_problem(struct pbuf *p, enum icmp6_pp_code c, u32_t pointer);
+
+#endif /* LWIP_ICMP6 && LWIP_IPV6 */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* __LWIP_ICMP6_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv6/lwip/inet6.h b/external/badvpn_dns/lwip/src/include/ipv6/lwip/inet6.h
new file mode 100644
index 0000000..dbf98df
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv6/lwip/inet6.h
@@ -0,0 +1,92 @@
+/**
+ * @file
+ *
+ * INET v6 addresses.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+#ifndef __LWIP_INET6_H__
+#define __LWIP_INET6_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6 && LWIP_SOCKET /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/ip6_addr.h"
+#include "lwip/def.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** For compatibility with BSD code */
+struct in6_addr {
+  union {
+    u8_t  u8_addr[16];
+    u32_t u32_addr[4];
+  } un;
+#define s6_addr  un.u32_addr
+};
+
+#define IN6ADDR_ANY_INIT {0,0,0,0}
+#define IN6ADDR_LOOPBACK_INIT {0,0,0,PP_HTONL(1)}
+
+
+#define inet6_addr_from_ip6addr(target_in6addr, source_ip6addr) {(target_in6addr)->un.u32_addr[0] = (source_ip6addr)->addr[0]; \
+                                                                 (target_in6addr)->un.u32_addr[1] = (source_ip6addr)->addr[1]; \
+                                                                 (target_in6addr)->un.u32_addr[2] = (source_ip6addr)->addr[2]; \
+                                                                 (target_in6addr)->un.u32_addr[3] = (source_ip6addr)->addr[3];}
+#define inet6_addr_to_ip6addr(target_ip6addr, source_in6addr)   {(target_ip6addr)->addr[0] = (source_in6addr)->un.u32_addr[0]; \
+                                                                 (target_ip6addr)->addr[1] = (source_in6addr)->un.u32_addr[1]; \
+                                                                 (target_ip6addr)->addr[2] = (source_in6addr)->un.u32_addr[2]; \
+                                                                 (target_ip6addr)->addr[3] = (source_in6addr)->un.u32_addr[3];}
+/* ATTENTION: the next define only works because both in6_addr and ip6_addr_t are an u32_t[4] effectively! */
+#define inet6_addr_to_ip6addr_p(target_ip6addr_p, source_in6addr)   ((target_ip6addr_p) = (ip6_addr_t*)(source_in6addr))
+
+/* directly map this to the lwip internal functions */
+#define inet6_aton(cp, addr)   ip6addr_aton(cp, (ip6_addr_t*)addr)
+#define inet6_ntoa(addr)       ip6addr_ntoa((ip6_addr_t*)&(addr))
+#define inet6_ntoa_r(addr, buf, buflen) ip6addr_ntoa_r((ip6_addr_t*)&(addr), buf, buflen)
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV6 */
+
+#endif /* __LWIP_INET6_H__ */
+
diff --git a/external/badvpn_dns/lwip/src/include/ipv6/lwip/ip6.h b/external/badvpn_dns/lwip/src/include/ipv6/lwip/ip6.h
new file mode 100644
index 0000000..b199c95
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv6/lwip/ip6.h
@@ -0,0 +1,197 @@
+/**
+ * @file
+ *
+ * IPv6 layer.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+#ifndef __LWIP_IP6_H__
+#define __LWIP_IP6_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/ip.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/def.h"
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+
+#include "lwip/err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define IP6_HLEN 40
+
+#define IP6_NEXTH_HOPBYHOP  0
+#define IP6_NEXTH_TCP       6
+#define IP6_NEXTH_UDP       17
+#define IP6_NEXTH_ENCAPS    41
+#define IP6_NEXTH_ROUTING   43
+#define IP6_NEXTH_FRAGMENT  44
+#define IP6_NEXTH_ICMP6     58
+#define IP6_NEXTH_NONE      59
+#define IP6_NEXTH_DESTOPTS  60
+#define IP6_NEXTH_UDPLITE   136
+
+
+/* The IPv6 header. */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ip6_hdr {
+  /* version / traffic class / flow label */
+  PACK_STRUCT_FIELD(u32_t _v_tc_fl);
+  /* payload length */
+  PACK_STRUCT_FIELD(u16_t _plen);
+  /* next header */
+  PACK_STRUCT_FIELD(u8_t _nexth);
+  /* hop limit */
+  PACK_STRUCT_FIELD(u8_t _hoplim);
+  /* source and destination IP addresses */
+  PACK_STRUCT_FIELD(ip6_addr_p_t src);
+  PACK_STRUCT_FIELD(ip6_addr_p_t dest);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/* Hop-by-hop router alert option. */
+#define IP6_HBH_HLEN    8
+#define IP6_PAD1_OPTION         0
+#define IP6_PADN_ALERT_OPTION   1
+#define IP6_ROUTER_ALERT_OPTION 5
+#define IP6_ROUTER_ALERT_VALUE_MLD 0
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ip6_hbh_hdr {
+  /* next header */
+  PACK_STRUCT_FIELD(u8_t _nexth);
+  /* header length */
+  PACK_STRUCT_FIELD(u8_t _hlen);
+  /* router alert option type */
+  PACK_STRUCT_FIELD(u8_t _ra_opt_type);
+  /* router alert option data len */
+  PACK_STRUCT_FIELD(u8_t _ra_opt_dlen);
+  /* router alert option data */
+  PACK_STRUCT_FIELD(u16_t _ra_opt_data);
+  /* PadN option type */
+  PACK_STRUCT_FIELD(u8_t _padn_opt_type);
+  /* PadN option data len */
+  PACK_STRUCT_FIELD(u8_t _padn_opt_dlen);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/* Fragment header. */
+#define IP6_FRAG_HLEN    8
+#define IP6_FRAG_OFFSET_MASK    0xfff8
+#define IP6_FRAG_MORE_FLAG      0x0001
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ip6_frag_hdr {
+  /* next header */
+  PACK_STRUCT_FIELD(u8_t _nexth);
+  /* reserved */
+  PACK_STRUCT_FIELD(u8_t reserved);
+  /* fragment offset */
+  PACK_STRUCT_FIELD(u16_t _fragment_offset);
+  /* fragmented packet identification */
+  PACK_STRUCT_FIELD(u32_t _identification);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define IP6H_V(hdr)  ((ntohl((hdr)->_v_tc_fl) >> 28) & 0x0f)
+#define IP6H_TC(hdr) ((ntohl((hdr)->_v_tc_fl) >> 20) & 0xff)
+#define IP6H_FL(hdr) (ntohl((hdr)->_v_tc_fl) & 0x000fffff)
+#define IP6H_PLEN(hdr) (ntohs((hdr)->_plen))
+#define IP6H_NEXTH(hdr) ((hdr)->_nexth)
+#define IP6H_NEXTH_P(hdr) ((u8_t *)(hdr) + 6)
+#define IP6H_HOPLIM(hdr) ((hdr)->_hoplim)
+
+#define IP6H_VTCFL_SET(hdr, v, tc, fl) (hdr)->_v_tc_fl = (htonl(((v) << 28) | ((tc) << 20) | (fl)))
+#define IP6H_PLEN_SET(hdr, plen) (hdr)->_plen = htons(plen)
+#define IP6H_NEXTH_SET(hdr, nexth) (hdr)->_nexth = (nexth)
+#define IP6H_HOPLIM_SET(hdr, hl) (hdr)->_hoplim = (u8_t)(hl)
+
+
+#define ip6_init() /* TODO should we init current addresses and header pointer? */
+struct netif *ip6_route(struct ip6_addr *src, struct ip6_addr *dest);
+ip6_addr_t   *ip6_select_source_address(struct netif *netif, ip6_addr_t * dest);
+err_t         ip6_input(struct pbuf *p, struct netif *inp);
+err_t         ip6_output(struct pbuf *p, struct ip6_addr *src, struct ip6_addr *dest,
+                         u8_t hl, u8_t tc, u8_t nexth);
+err_t         ip6_output_if(struct pbuf *p, struct ip6_addr *src, struct ip6_addr *dest,
+                            u8_t hl, u8_t tc, u8_t nexth, struct netif *netif);
+#if LWIP_NETIF_HWADDRHINT
+err_t         ip6_output_hinted(struct pbuf *p, ip6_addr_t *src, ip6_addr_t *dest,
+                                u8_t hl, u8_t tc, u8_t nexth, u8_t *addr_hint);
+#endif /* LWIP_NETIF_HWADDRHINT */
+#if LWIP_IPV6_MLD
+err_t         ip6_options_add_hbh_ra(struct pbuf * p, u8_t nexth, u8_t value);
+#endif /* LWIP_IPV6_MLD */
+
+#define ip6_netif_get_local_ipX(netif, dest) (((netif) != NULL) ? \
+  ip6_2_ipX(ip6_select_source_address(netif, dest)) : NULL)
+
+#if IP6_DEBUG
+void ip6_debug_print(struct pbuf *p);
+#else
+#define ip6_debug_print(p)
+#endif /* IP6_DEBUG */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV6 */
+
+#endif /* __LWIP_IP6_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv6/lwip/ip6_addr.h b/external/badvpn_dns/lwip/src/include/ipv6/lwip/ip6_addr.h
new file mode 100644
index 0000000..89b5b81
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv6/lwip/ip6_addr.h
@@ -0,0 +1,286 @@
+/**
+ * @file
+ *
+ * IPv6 addresses.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ * Structs and macros for handling IPv6 addresses.
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+#ifndef __LWIP_IP6_ADDR_H__
+#define __LWIP_IP6_ADDR_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* This is the aligned version of ip6_addr_t,
+   used as local variable, on the stack, etc. */
+struct ip6_addr {
+  u32_t addr[4];
+};
+
+/* This is the packed version of ip6_addr_t,
+   used in network headers that are itself packed */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ip6_addr_packed {
+  PACK_STRUCT_FIELD(u32_t addr[4]);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** ip6_addr_t uses a struct for convenience only, so that the same defines can
+ * operate both on ip6_addr_t as well as on ip6_addr_p_t. */
+typedef struct ip6_addr ip6_addr_t;
+typedef struct ip6_addr_packed ip6_addr_p_t;
+
+
+/** IP6_ADDR_ANY can be used as a fixed IPv6 address
+ *  for the wildcard
+ */
+extern const ip6_addr_t ip6_addr_any;
+#define IP6_ADDR_ANY         ((ip6_addr_t *)&ip6_addr_any)
+
+
+
+
+#if BYTE_ORDER == BIG_ENDIAN
+/** Set an IPv6 partial address given by byte-parts. */
+#define IP6_ADDR(ip6addr, index, a,b,c,d) \
+  (ip6addr)->addr[index] = ((u32_t)((a) & 0xff) << 24) | \
+                           ((u32_t)((b) & 0xff) << 16) | \
+                           ((u32_t)((c) & 0xff) << 8)  | \
+                            (u32_t)((d) & 0xff)
+#else
+/** Set an IPv6 partial address given by byte-parts.
+Little-endian version, stored in network order (no htonl). */
+#define IP6_ADDR(ip6addr, index, a,b,c,d) \
+  (ip6addr)->addr[index] = ((u32_t)((d) & 0xff) << 24) | \
+                           ((u32_t)((c) & 0xff) << 16) | \
+                           ((u32_t)((b) & 0xff) << 8)  | \
+                            (u32_t)((a) & 0xff)
+#endif
+
+/** Access address in 16-bit block */
+#define IP6_ADDR_BLOCK1(ip6addr) ((u16_t)(htonl((ip6addr)->addr[0]) >> 16) & 0xffff)
+#define IP6_ADDR_BLOCK2(ip6addr) ((u16_t)(htonl((ip6addr)->addr[0])) & 0xffff)
+#define IP6_ADDR_BLOCK3(ip6addr) ((u16_t)(htonl((ip6addr)->addr[1]) >> 16) & 0xffff)
+#define IP6_ADDR_BLOCK4(ip6addr) ((u16_t)(htonl((ip6addr)->addr[1])) & 0xffff)
+#define IP6_ADDR_BLOCK5(ip6addr) ((u16_t)(htonl((ip6addr)->addr[2]) >> 16) & 0xffff)
+#define IP6_ADDR_BLOCK6(ip6addr) ((u16_t)(htonl((ip6addr)->addr[2])) & 0xffff)
+#define IP6_ADDR_BLOCK7(ip6addr) ((u16_t)(htonl((ip6addr)->addr[3]) >> 16) & 0xffff)
+#define IP6_ADDR_BLOCK8(ip6addr) ((u16_t)(htonl((ip6addr)->addr[3])) & 0xffff)
+
+/** Copy IPv6 address - faster than ip6_addr_set: no NULL check */
+#define ip6_addr_copy(dest, src) do{(dest).addr[0] = (src).addr[0]; \
+                                    (dest).addr[1] = (src).addr[1]; \
+                                    (dest).addr[2] = (src).addr[2]; \
+                                    (dest).addr[3] = (src).addr[3];}while(0)
+/** Safely copy one IPv6 address to another (src may be NULL) */
+#define ip6_addr_set(dest, src) do{(dest)->addr[0] = (src) == NULL ? 0 : (src)->addr[0]; \
+                                   (dest)->addr[1] = (src) == NULL ? 0 : (src)->addr[1]; \
+                                   (dest)->addr[2] = (src) == NULL ? 0 : (src)->addr[2]; \
+                                   (dest)->addr[3] = (src) == NULL ? 0 : (src)->addr[3];}while(0)
+
+/** Set complete address to zero */
+#define ip6_addr_set_zero(ip6addr)    do{(ip6addr)->addr[0] = 0; \
+                                         (ip6addr)->addr[1] = 0; \
+                                         (ip6addr)->addr[2] = 0; \
+                                         (ip6addr)->addr[3] = 0;}while(0)
+
+/** Set address to ipv6 'any' (no need for htonl()) */
+#define ip6_addr_set_any(ip6addr)       ip6_addr_set_zero(ip6addr)
+/** Set address to ipv6 loopback address */
+#define ip6_addr_set_loopback(ip6addr) do{(ip6addr)->addr[0] = 0; \
+                                          (ip6addr)->addr[1] = 0; \
+                                          (ip6addr)->addr[2] = 0; \
+                                          (ip6addr)->addr[3] = PP_HTONL(0x00000001UL);}while(0)
+/** Safely copy one IPv6 address to another and change byte order
+ * from host- to network-order. */
+#define ip6_addr_set_hton(dest, src) do{(dest)->addr[0] = (src) == NULL ? 0 : htonl((src)->addr[0]); \
+                                        (dest)->addr[1] = (src) == NULL ? 0 : htonl((src)->addr[1]); \
+                                        (dest)->addr[2] = (src) == NULL ? 0 : htonl((src)->addr[2]); \
+                                        (dest)->addr[3] = (src) == NULL ? 0 : htonl((src)->addr[3]);}while(0)
+
+
+
+/**
+ * Determine if two IPv6 address are on the same network.
+ *
+ * @arg addr1 IPv6 address 1
+ * @arg addr2 IPv6 address 2
+ * @return !0 if the network identifiers of both address match
+ */
+#define ip6_addr_netcmp(addr1, addr2) (((addr1)->addr[0] == (addr2)->addr[0]) && \
+                                       ((addr1)->addr[1] == (addr2)->addr[1]))
+
+#define ip6_addr_cmp(addr1, addr2) (((addr1)->addr[0] == (addr2)->addr[0]) && \
+                                    ((addr1)->addr[1] == (addr2)->addr[1]) && \
+                                    ((addr1)->addr[2] == (addr2)->addr[2]) && \
+                                    ((addr1)->addr[3] == (addr2)->addr[3]))
+
+#define ip6_get_subnet_id(ip6addr)   (htonl((ip6addr)->addr[2]) & 0x0000ffffUL)
+
+#define ip6_addr_isany(ip6addr) (((ip6addr) == NULL) || \
+                             (((ip6addr)->addr[0] == 0) && \
+                             ((ip6addr)->addr[1] == 0) && \
+                             ((ip6addr)->addr[2] == 0) && \
+                             ((ip6addr)->addr[3] == 0)))
+
+
+#define ip6_addr_isglobal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xe0000000UL)) == PP_HTONL(0x20000000UL))
+
+#define ip6_addr_islinklocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xffc00000UL)) == PP_HTONL(0xfe800000UL))
+
+#define ip6_addr_issitelocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xffc00000UL)) == PP_HTONL(0xfec00000UL))
+
+#define ip6_addr_isuniquelocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xfe000000UL)) == PP_HTONL(0xfc000000UL))
+
+#define ip6_addr_ismulticast(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xff000000UL)) == PP_HTONL(0xff000000UL))
+#define ip6_addr_multicast_transient_flag(ip6addr)  ((ip6addr)->addr[0] & PP_HTONL(0x00100000UL))
+#define ip6_addr_multicast_prefix_flag(ip6addr)     ((ip6addr)->addr[0] & PP_HTONL(0x00200000UL))
+#define ip6_addr_multicast_rendezvous_flag(ip6addr) ((ip6addr)->addr[0] & PP_HTONL(0x00400000UL))
+#define ip6_addr_multicast_scope(ip6addr) ((htonl((ip6addr)->addr[0]) >> 16) & 0xf)
+#define IP6_MULTICAST_SCOPE_RESERVED            0x0
+#define IP6_MULTICAST_SCOPE_RESERVED0           0x0
+#define IP6_MULTICAST_SCOPE_INTERFACE_LOCAL     0x1
+#define IP6_MULTICAST_SCOPE_LINK_LOCAL          0x2
+#define IP6_MULTICAST_SCOPE_RESERVED3           0x3
+#define IP6_MULTICAST_SCOPE_ADMIN_LOCAL         0x4
+#define IP6_MULTICAST_SCOPE_SITE_LOCAL          0x5
+#define IP6_MULTICAST_SCOPE_ORGANIZATION_LOCAL  0x8
+#define IP6_MULTICAST_SCOPE_GLOBAL              0xe
+#define IP6_MULTICAST_SCOPE_RESERVEDF           0xf
+#define ip6_addr_ismulticast_iflocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xffff0000UL)) == PP_HTONL(0xff010000UL))
+#define ip6_addr_ismulticast_linklocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xffff0000UL)) == PP_HTONL(0xff020000UL))
+#define ip6_addr_ismulticast_adminlocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xffff0000UL)) == PP_HTONL(0xff040000UL))
+#define ip6_addr_ismulticast_sitelocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xffff0000UL)) == PP_HTONL(0xff050000UL))
+#define ip6_addr_ismulticast_orglocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xffff0000UL)) == PP_HTONL(0xff080000UL))
+#define ip6_addr_ismulticast_global(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xffff0000UL)) == PP_HTONL(0xff0e0000UL))
+
+/* TODO define get/set for well-know multicast addresses, e.g. ff02::1 */
+#define ip6_addr_isallnodes_iflocal(ip6addr) (((ip6addr)->addr[0] == PP_HTONL(0xff010000UL)) && \
+    ((ip6addr)->addr[1] == 0UL) && \
+    ((ip6addr)->addr[2] == 0UL) && \
+    ((ip6addr)->addr[3] == PP_HTONL(0x00000001UL)))
+
+#define ip6_addr_isallnodes_linklocal(ip6addr) (((ip6addr)->addr[0] == PP_HTONL(0xff020000UL)) && \
+    ((ip6addr)->addr[1] == 0UL) && \
+    ((ip6addr)->addr[2] == 0UL) && \
+    ((ip6addr)->addr[3] == PP_HTONL(0x00000001UL)))
+#define ip6_addr_set_allnodes_linklocal(ip6addr) do{(ip6addr)->addr[0] = PP_HTONL(0xff020000UL); \
+                (ip6addr)->addr[1] = 0; \
+                (ip6addr)->addr[2] = 0; \
+                (ip6addr)->addr[3] = PP_HTONL(0x00000001UL);}while(0)
+
+#define ip6_addr_isallrouters_linklocal(ip6addr) (((ip6addr)->addr[0] == PP_HTONL(0xff020000UL)) && \
+    ((ip6addr)->addr[1] == 0UL) && \
+    ((ip6addr)->addr[2] == 0UL) && \
+    ((ip6addr)->addr[3] == PP_HTONL(0x00000002UL)))
+#define ip6_addr_set_allrouters_linklocal(ip6addr) do{(ip6addr)->addr[0] = PP_HTONL(0xff020000UL); \
+                (ip6addr)->addr[1] = 0; \
+                (ip6addr)->addr[2] = 0; \
+                (ip6addr)->addr[3] = PP_HTONL(0x00000002UL);}while(0)
+
+#define ip6_addr_issolicitednode(ip6addr) ( ((ip6addr)->addr[0] == PP_HTONL(0xff020000UL)) && \
+        ((ip6addr)->addr[2] == PP_HTONL(0x00000001UL)) && \
+        (((ip6addr)->addr[3] & PP_HTONL(0xff000000UL)) == PP_HTONL(0xff000000UL)) )
+
+#define ip6_addr_set_solicitednode(ip6addr, if_id) do{(ip6addr)->addr[0] = PP_HTONL(0xff020000UL); \
+                (ip6addr)->addr[1] = 0; \
+                (ip6addr)->addr[2] = PP_HTONL(0x00000001UL); \
+                (ip6addr)->addr[3] = htonl(0xff000000UL | (htonl(if_id) & 0x00ffffffUL));}while(0)
+
+#define ip6_addr_cmp_solicitednode(ip6addr, sn_addr) (((ip6addr)->addr[0] == PP_HTONL(0xff020000UL)) && \
+                                    ((ip6addr)->addr[1] == 0) && \
+                                    ((ip6addr)->addr[2] == PP_HTONL(0x00000001UL)) && \
+                                    ((ip6addr)->addr[3] == htonl(0xff000000UL | (htonl((sn_addr)->addr[3]) & 0x00ffffffUL))))
+
+/* IPv6 address states. */
+#define IP6_ADDR_INVALID      0x00
+#define IP6_ADDR_TENTATIVE    0x08
+#define IP6_ADDR_TENTATIVE_1  0x09 /* 1 probe sent */
+#define IP6_ADDR_TENTATIVE_2  0x0a /* 2 probes sent */
+#define IP6_ADDR_TENTATIVE_3  0x0b /* 3 probes sent */
+#define IP6_ADDR_TENTATIVE_4  0x0c /* 4 probes sent */
+#define IP6_ADDR_TENTATIVE_5  0x0d /* 5 probes sent */
+#define IP6_ADDR_TENTATIVE_6  0x0e /* 6 probes sent */
+#define IP6_ADDR_TENTATIVE_7  0x0f /* 7 probes sent */
+#define IP6_ADDR_VALID        0x10
+#define IP6_ADDR_PREFERRED    0x30
+#define IP6_ADDR_DEPRECATED   0x50
+
+#define ip6_addr_isinvalid(addr_state) (addr_state == IP6_ADDR_INVALID)
+#define ip6_addr_istentative(addr_state) (addr_state & IP6_ADDR_TENTATIVE)
+#define ip6_addr_isvalid(addr_state) (addr_state & IP6_ADDR_VALID) /* Include valid, preferred, and deprecated. */
+#define ip6_addr_ispreferred(addr_state) (addr_state == IP6_ADDR_PREFERRED)
+#define ip6_addr_isdeprecated(addr_state) (addr_state == IP6_ADDR_DEPRECATED)
+
+#define ip6_addr_debug_print(debug, ipaddr) \
+  LWIP_DEBUGF(debug, ("%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F":%"X16_F, \
+                      ipaddr != NULL ? IP6_ADDR_BLOCK1(ipaddr) : 0,    \
+                      ipaddr != NULL ? IP6_ADDR_BLOCK2(ipaddr) : 0,    \
+                      ipaddr != NULL ? IP6_ADDR_BLOCK3(ipaddr) : 0,    \
+                      ipaddr != NULL ? IP6_ADDR_BLOCK4(ipaddr) : 0,    \
+                      ipaddr != NULL ? IP6_ADDR_BLOCK5(ipaddr) : 0,    \
+                      ipaddr != NULL ? IP6_ADDR_BLOCK6(ipaddr) : 0,    \
+                      ipaddr != NULL ? IP6_ADDR_BLOCK7(ipaddr) : 0,    \
+                      ipaddr != NULL ? IP6_ADDR_BLOCK8(ipaddr) : 0))
+
+int ip6addr_aton(const char *cp, ip6_addr_t *addr);
+/** returns ptr to static buffer; not reentrant! */
+char *ip6addr_ntoa(const ip6_addr_t *addr);
+char *ip6addr_ntoa_r(const ip6_addr_t *addr, char *buf, int buflen);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV6 */
+
+#endif /* __LWIP_IP6_ADDR_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv6/lwip/ip6_frag.h b/external/badvpn_dns/lwip/src/include/ipv6/lwip/ip6_frag.h
new file mode 100644
index 0000000..75898b8
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv6/lwip/ip6_frag.h
@@ -0,0 +1,102 @@
+/**
+ * @file
+ *
+ * IPv6 fragmentation and reassembly.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+#ifndef __LWIP_IP6_FRAG_H__
+#define __LWIP_IP6_FRAG_H__
+
+#include "lwip/opt.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/netif.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#if LWIP_IPV6 && LWIP_IPV6_REASS  /* don't build if not configured for use in lwipopts.h */
+
+/* The IPv6 reassembly timer interval in milliseconds. */
+#define IP6_REASS_TMR_INTERVAL 1000
+
+/* IPv6 reassembly helper struct.
+ * This is exported because memp needs to know the size.
+ */
+struct ip6_reassdata {
+  struct ip6_reassdata *next;
+  struct pbuf *p;
+  struct ip6_hdr * iphdr;
+  u32_t identification;
+  u16_t datagram_len;
+  u8_t nexth;
+  u8_t timer;
+};
+
+#define ip6_reass_init() /* Compatibility define */
+void ip6_reass_tmr(void);
+struct pbuf * ip6_reass(struct pbuf *p);
+
+#endif /* LWIP_IPV6 && LWIP_IPV6_REASS */
+
+#if LWIP_IPV6 && LWIP_IPV6_FRAG  /* don't build if not configured for use in lwipopts.h */
+
+/** A custom pbuf that holds a reference to another pbuf, which is freed
+ * when this custom pbuf is freed. This is used to create a custom PBUF_REF
+ * that points into the original pbuf. */
+#ifndef __LWIP_PBUF_CUSTOM_REF__
+#define __LWIP_PBUF_CUSTOM_REF__
+struct pbuf_custom_ref {
+  /** 'base class' */
+  struct pbuf_custom pc;
+  /** pointer to the original pbuf that is referenced */
+  struct pbuf *original;
+};
+#endif /* __LWIP_PBUF_CUSTOM_REF__ */
+
+err_t ip6_frag(struct pbuf *p, struct netif *netif, ip6_addr_t *dest);
+
+#endif /* LWIP_IPV6 && LWIP_IPV6_FRAG */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_IP6_FRAG_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv6/lwip/mld6.h b/external/badvpn_dns/lwip/src/include/ipv6/lwip/mld6.h
new file mode 100644
index 0000000..abd86e5
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv6/lwip/mld6.h
@@ -0,0 +1,118 @@
+/**
+ * @file
+ *
+ * Multicast listener discovery for IPv6. Aims to be compliant with RFC 2710.
+ * No support for MLDv2.
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#ifndef __LWIP_MLD6_H__
+#define __LWIP_MLD6_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6_MLD && LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct mld_group {
+  /** next link */
+  struct mld_group *next;
+  /** interface on which the group is active */
+  struct netif      *netif;
+  /** multicast address */
+  ip6_addr_t         group_address;
+  /** signifies we were the last person to report */
+  u8_t               last_reporter_flag;
+  /** current state of the group */
+  u8_t               group_state;
+  /** timer for reporting */
+  u16_t              timer;
+  /** counter of simultaneous uses */
+  u8_t               use;
+};
+
+/** Multicast listener report/query/done message header. */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct mld_header {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u16_t max_resp_delay);
+  PACK_STRUCT_FIELD(u16_t reserved);
+  PACK_STRUCT_FIELD(ip6_addr_p_t multicast_address);
+  /* Options follow. */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define MLD6_TMR_INTERVAL              100 /* Milliseconds */
+
+/* MAC Filter Actions, these are passed to a netif's
+ * mld_mac_filter callback function. */
+#define MLD6_DEL_MAC_FILTER            0
+#define MLD6_ADD_MAC_FILTER            1
+
+
+#define mld6_init() /* TODO should we init tables? */
+err_t  mld6_stop(struct netif *netif);
+void   mld6_report_groups(struct netif *netif);
+void   mld6_tmr(void);
+struct mld_group *mld6_lookfor_group(struct netif *ifp, ip6_addr_t *addr);
+void   mld6_input(struct pbuf *p, struct netif *inp);
+err_t  mld6_joingroup(ip6_addr_t *srcaddr, ip6_addr_t *groupaddr);
+err_t  mld6_leavegroup(ip6_addr_t *srcaddr, ip6_addr_t *groupaddr);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV6_MLD && LWIP_IPV6 */
+
+#endif /* __LWIP_MLD6_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/ipv6/lwip/nd6.h b/external/badvpn_dns/lwip/src/include/ipv6/lwip/nd6.h
new file mode 100644
index 0000000..28636e8
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/ipv6/lwip/nd6.h
@@ -0,0 +1,369 @@
+/**
+ * @file
+ *
+ * Neighbor discovery and stateless address autoconfiguration for IPv6.
+ * Aims to be compliant with RFC 4861 (Neighbor discovery) and RFC 4862
+ * (Address autoconfiguration).
+ */
+
+/*
+ * Copyright (c) 2010 Inico Technologies Ltd.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Ivan Delamer <delamer@xxxxxxxxxxxxx>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@xxxxxxxxxxxxx>
+ */
+
+#ifndef __LWIP_ND6_H__
+#define __LWIP_ND6_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/pbuf.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/netif.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Struct for tables. */
+struct nd6_neighbor_cache_entry {
+  ip6_addr_t next_hop_address;
+  struct netif * netif;
+  u8_t lladdr[NETIF_MAX_HWADDR_LEN];
+  /*u32_t pmtu;*/
+#if LWIP_ND6_QUEUEING
+  /** Pointer to queue of pending outgoing packets on this entry. */
+  struct nd6_q_entry *q;
+#else /* LWIP_ND6_QUEUEING */
+  /** Pointer to a single pending outgoing packet on this entry. */
+  struct pbuf *q;
+#endif /* LWIP_ND6_QUEUEING */
+  u8_t state;
+  u8_t isrouter;
+  union {
+    u32_t reachable_time;
+    u32_t delay_time;
+    u32_t probes_sent;
+    u32_t stale_time;
+  } counter;
+};
+
+struct nd6_destination_cache_entry {
+  ip6_addr_t destination_addr;
+  ip6_addr_t next_hop_addr;
+  u32_t pmtu;
+  u32_t age;
+};
+
+struct nd6_prefix_list_entry {
+  ip6_addr_t prefix;
+  struct netif * netif;
+  u32_t invalidation_timer;
+#if LWIP_IPV6_AUTOCONFIG
+  u8_t flags;
+#define ND6_PREFIX_AUTOCONFIG_AUTONOMOUS 0x01
+#define ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED 0x02
+#define ND6_PREFIX_AUTOCONFIG_ADDRESS_DUPLICATE 0x04
+#endif /* LWIP_IPV6_AUTOCONFIG */
+};
+
+struct nd6_router_list_entry {
+  struct nd6_neighbor_cache_entry * neighbor_entry;
+  u32_t invalidation_timer;
+  u8_t flags;
+};
+
+
+enum nd6_neighbor_cache_entry_state {
+  ND6_NO_ENTRY = 0,
+  ND6_INCOMPLETE,
+  ND6_REACHABLE,
+  ND6_STALE,
+  ND6_DELAY,
+  ND6_PROBE
+};
+
+#if LWIP_ND6_QUEUEING
+/** struct for queueing outgoing packets for unknown address
+  * defined here to be accessed by memp.h
+  */
+struct nd6_q_entry {
+  struct nd6_q_entry *next;
+  struct pbuf *p;
+};
+#endif /* LWIP_ND6_QUEUEING */
+
+/** Neighbor solicitation message header. */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ns_header {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u32_t reserved);
+  PACK_STRUCT_FIELD(ip6_addr_p_t target_address);
+  /* Options follow. */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** Neighbor advertisement message header. */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct na_header {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u8_t flags);
+  PACK_STRUCT_FIELD(u8_t reserved[3]);
+  PACK_STRUCT_FIELD(ip6_addr_p_t target_address);
+  /* Options follow. */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+#define ND6_FLAG_ROUTER      (0x80)
+#define ND6_FLAG_SOLICITED   (0x40)
+#define ND6_FLAG_OVERRIDE    (0x20)
+
+/** Router solicitation message header. */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct rs_header {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u32_t reserved);
+  /* Options follow. */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** Router advertisement message header. */
+#define ND6_RA_FLAG_MANAGED_ADDR_CONFIG (0x80)
+#define ND6_RA_FLAG_OTHER_STATEFUL_CONFIG (0x40)
+#define ND6_RA_FLAG_HOME_AGENT (0x20)
+#define ND6_RA_PREFERENCE_MASK (0x18)
+#define ND6_RA_PREFERENCE_HIGH (0x08)
+#define ND6_RA_PREFERENCE_MEDIUM (0x00)
+#define ND6_RA_PREFERENCE_LOW (0x18)
+#define ND6_RA_PREFERENCE_DISABLED (0x10)
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct ra_header {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u8_t current_hop_limit);
+  PACK_STRUCT_FIELD(u8_t flags);
+  PACK_STRUCT_FIELD(u16_t router_lifetime);
+  PACK_STRUCT_FIELD(u32_t reachable_time);
+  PACK_STRUCT_FIELD(u32_t retrans_timer);
+  /* Options follow. */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** Redirect message header. */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct redirect_header {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u32_t reserved);
+  PACK_STRUCT_FIELD(ip6_addr_p_t target_address);
+  PACK_STRUCT_FIELD(ip6_addr_p_t destination_address);
+  /* Options follow. */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** Link-layer address option. */
+#define ND6_OPTION_TYPE_SOURCE_LLADDR (0x01)
+#define ND6_OPTION_TYPE_TARGET_LLADDR (0x02)
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct lladdr_option {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t length);
+  PACK_STRUCT_FIELD(u8_t addr[NETIF_MAX_HWADDR_LEN]);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** Prefix information option. */
+#define ND6_OPTION_TYPE_PREFIX_INFO (0x03)
+#define ND6_PREFIX_FLAG_ON_LINK (0x80)
+#define ND6_PREFIX_FLAG_AUTONOMOUS (0x40)
+#define ND6_PREFIX_FLAG_ROUTER_ADDRESS (0x20)
+#define ND6_PREFIX_FLAG_SITE_PREFIX (0x10)
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct prefix_option {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t length);
+  PACK_STRUCT_FIELD(u8_t prefix_length);
+  PACK_STRUCT_FIELD(u8_t flags);
+  PACK_STRUCT_FIELD(u32_t valid_lifetime);
+  PACK_STRUCT_FIELD(u32_t preferred_lifetime);
+  PACK_STRUCT_FIELD(u8_t reserved2[3]);
+  PACK_STRUCT_FIELD(u8_t site_prefix_length);
+  PACK_STRUCT_FIELD(ip6_addr_p_t prefix);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** Redirected header option. */
+#define ND6_OPTION_TYPE_REDIR_HDR (0x04)
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct redirected_header_option {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t length);
+  PACK_STRUCT_FIELD(u8_t reserved[6]);
+  /* Portion of redirected packet follows. */
+  /* PACK_STRUCT_FIELD(u8_t redirected[8]); */
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** MTU option. */
+#define ND6_OPTION_TYPE_MTU (0x05)
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct mtu_option {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t length);
+  PACK_STRUCT_FIELD(u16_t reserved);
+  PACK_STRUCT_FIELD(u32_t mtu);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** Route information option. */
+#define ND6_OPTION_TYPE_ROUTE_INFO (24)
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct route_option {
+  PACK_STRUCT_FIELD(u8_t type);
+  PACK_STRUCT_FIELD(u8_t length);
+  PACK_STRUCT_FIELD(u8_t prefix_length);
+  PACK_STRUCT_FIELD(u8_t preference);
+  PACK_STRUCT_FIELD(u32_t route_lifetime);
+  PACK_STRUCT_FIELD(ip6_addr_p_t prefix);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/* the possible states of an IP address */
+#define IP6_ADDRESS_STATE_INVALID     (0)
+#define IP6_ADDRESS_STATE_VALID       (0x4)
+#define IP6_ADDRESS_STATE_PREFERRED   (0x5) /* includes valid */
+#define IP6_ADDRESS_STATE_DEPRECATED  (0x6) /* includes valid */
+#define IP6_ADDRESS_STATE_TENTATIV    (0x8)
+
+/** 1 second period */
+#define ND6_TMR_INTERVAL 1000
+
+/* Router tables. */
+/* TODO make these static? and entries accessible through API? */
+extern struct nd6_neighbor_cache_entry neighbor_cache[];
+extern struct nd6_destination_cache_entry destination_cache[];
+extern struct nd6_prefix_list_entry prefix_list[];
+extern struct nd6_router_list_entry default_router_list[];
+
+/* Default values, can be updated by a RA message. */
+extern u32_t reachable_time;
+extern u32_t retrans_timer;
+
+#define nd6_init() /* TODO should we init tables? */
+void nd6_tmr(void);
+void nd6_input(struct pbuf *p, struct netif *inp);
+s8_t nd6_get_next_hop_entry(ip6_addr_t * ip6addr, struct netif * netif);
+s8_t nd6_select_router(ip6_addr_t * ip6addr, struct netif * netif);
+u16_t nd6_get_destination_mtu(ip6_addr_t * ip6addr, struct netif * netif);
+err_t nd6_queue_packet(s8_t neighbor_index, struct pbuf * p);
+#if LWIP_ND6_TCP_REACHABILITY_HINTS
+void nd6_reachability_hint(ip6_addr_t * ip6addr);
+#endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV6 */
+
+#endif /* __LWIP_ND6_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/api.h b/external/badvpn_dns/lwip/src/include/lwip/api.h
new file mode 100644
index 0000000..ac58eeb
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/api.h
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_API_H__
+#define __LWIP_API_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_NETCONN /* don't build if not configured for use in lwipopts.h */
+
+#include <stddef.h> /* for size_t */
+
+#include "lwip/netbuf.h"
+#include "lwip/sys.h"
+#include "lwip/ip_addr.h"
+#include "lwip/err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Throughout this file, IP addresses and port numbers are expected to be in
+ * the same byte order as in the corresponding pcb.
+ */
+
+/* Flags for netconn_write (u8_t) */
+#define NETCONN_NOFLAG    0x00
+#define NETCONN_NOCOPY    0x00 /* Only for source code compatibility */
+#define NETCONN_COPY      0x01
+#define NETCONN_MORE      0x02
+#define NETCONN_DONTBLOCK 0x04
+
+/* Flags for struct netconn.flags (u8_t) */
+/** TCP: when data passed to netconn_write doesn't fit into the send buffer,
+    this temporarily stores whether to wake up the original application task
+    if data couldn't be sent in the first try. */
+#define NETCONN_FLAG_WRITE_DELAYED            0x01
+/** Should this netconn avoid blocking? */
+#define NETCONN_FLAG_NON_BLOCKING             0x02
+/** Was the last connect action a non-blocking one? */
+#define NETCONN_FLAG_IN_NONBLOCKING_CONNECT   0x04
+/** If this is set, a TCP netconn must call netconn_recved() to update
+    the TCP receive window (done automatically if not set). */
+#define NETCONN_FLAG_NO_AUTO_RECVED           0x08
+/** If a nonblocking write has been rejected before, poll_tcp needs to
+    check if the netconn is writable again */
+#define NETCONN_FLAG_CHECK_WRITESPACE         0x10
+#if LWIP_IPV6
+/** If this flag is set then only IPv6 communication is allowed on the
+    netconn. As per RFC#3493 this features defaults to OFF allowing
+    dual-stack usage by default. */
+#define NETCONN_FLAG_IPV6_V6ONLY              0x20
+#endif /* LWIP_IPV6 */
+
+
+/* Helpers to process several netconn_types by the same code */
+#define NETCONNTYPE_GROUP(t)         ((t)&0xF0)
+#define NETCONNTYPE_DATAGRAM(t)      ((t)&0xE0)
+#if LWIP_IPV6
+#define NETCONN_TYPE_IPV6            0x08
+#define NETCONNTYPE_ISIPV6(t)        ((t)&0x08)
+#define NETCONNTYPE_ISUDPLITE(t)     (((t)&0xF7) == NETCONN_UDPLITE)
+#define NETCONNTYPE_ISUDPNOCHKSUM(t) (((t)&0xF7) == NETCONN_UDPNOCHKSUM)
+#else /* LWIP_IPV6 */
+#define NETCONNTYPE_ISUDPLITE(t)     ((t) == NETCONN_UDPLITE)
+#define NETCONNTYPE_ISUDPNOCHKSUM(t) ((t) == NETCONN_UDPNOCHKSUM)
+#endif /* LWIP_IPV6 */
+
+/** Protocol family and type of the netconn */
+enum netconn_type {
+  NETCONN_INVALID     = 0,
+  /* NETCONN_TCP Group */
+  NETCONN_TCP         = 0x10,
+#if LWIP_IPV6
+  NETCONN_TCP_IPV6    = NETCONN_TCP | NETCONN_TYPE_IPV6 /* 0x18 */,
+#endif /* LWIP_IPV6 */
+  /* NETCONN_UDP Group */
+  NETCONN_UDP         = 0x20,
+  NETCONN_UDPLITE     = 0x21,
+  NETCONN_UDPNOCHKSUM = 0x22,
+#if LWIP_IPV6
+  NETCONN_UDP_IPV6         = NETCONN_UDP | NETCONN_TYPE_IPV6 /* 0x28 */,
+  NETCONN_UDPLITE_IPV6     = NETCONN_UDPLITE | NETCONN_TYPE_IPV6 /* 0x29 */,
+  NETCONN_UDPNOCHKSUM_IPV6 = NETCONN_UDPNOCHKSUM | NETCONN_TYPE_IPV6 /* 0x2a */,
+#endif /* LWIP_IPV6 */
+  /* NETCONN_RAW Group */
+  NETCONN_RAW         = 0x40
+#if LWIP_IPV6
+  ,
+  NETCONN_RAW_IPV6    = NETCONN_RAW | NETCONN_TYPE_IPV6 /* 0x48 */
+#endif /* LWIP_IPV6 */
+};
+
+/** Current state of the netconn. Non-TCP netconns are always
+ * in state NETCONN_NONE! */
+enum netconn_state {
+  NETCONN_NONE,
+  NETCONN_WRITE,
+  NETCONN_LISTEN,
+  NETCONN_CONNECT,
+  NETCONN_CLOSE
+};
+
+/** Use to inform the callback function about changes */
+enum netconn_evt {
+  NETCONN_EVT_RCVPLUS,
+  NETCONN_EVT_RCVMINUS,
+  NETCONN_EVT_SENDPLUS,
+  NETCONN_EVT_SENDMINUS,
+  NETCONN_EVT_ERROR
+};
+
+#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
+/** Used for netconn_join_leave_group() */
+enum netconn_igmp {
+  NETCONN_JOIN,
+  NETCONN_LEAVE
+};
+#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+
+/* forward-declare some structs to avoid to include their headers */
+struct ip_pcb;
+struct tcp_pcb;
+struct udp_pcb;
+struct raw_pcb;
+struct netconn;
+struct api_msg_msg;
+
+/** A callback prototype to inform about events for a netconn */
+typedef void (* netconn_callback)(struct netconn *, enum netconn_evt, u16_t len);
+
+/** A netconn descriptor */
+struct netconn {
+  /** type of the netconn (TCP, UDP or RAW) */
+  enum netconn_type type;
+  /** current state of the netconn */
+  enum netconn_state state;
+  /** the lwIP internal protocol control block */
+  union {
+    struct ip_pcb  *ip;
+    struct tcp_pcb *tcp;
+    struct udp_pcb *udp;
+    struct raw_pcb *raw;
+  } pcb;
+  /** the last error this netconn had */
+  err_t last_err;
+  /** sem that is used to synchroneously execute functions in the core context */
+  sys_sem_t op_completed;
+  /** mbox where received packets are stored until they are fetched
+      by the netconn application thread (can grow quite big) */
+  sys_mbox_t recvmbox;
+#if LWIP_TCP
+  /** mbox where new connections are stored until processed
+      by the application thread */
+  sys_mbox_t acceptmbox;
+#endif /* LWIP_TCP */
+  /** only used for socket layer */
+#if LWIP_SOCKET
+  int socket;
+#endif /* LWIP_SOCKET */
+#if LWIP_SO_SNDTIMEO
+  /** timeout to wait for sending data (which means enqueueing data for sending
+      in internal buffers) */
+  s32_t send_timeout;
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVTIMEO
+  /** timeout to wait for new data to be received
+      (or connections to arrive for listening netconns) */
+  int recv_timeout;
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVBUF
+  /** maximum amount of bytes queued in recvmbox
+      not used for TCP: adjust TCP_WND instead! */
+  int recv_bufsize;
+  /** number of bytes currently in recvmbox to be received,
+      tested against recv_bufsize to limit bytes on recvmbox
+      for UDP and RAW, used for FIONREAD */
+  s16_t recv_avail;
+#endif /* LWIP_SO_RCVBUF */
+  /** flags holding more netconn-internal state, see NETCONN_FLAG_* defines */
+  u8_t flags;
+#if LWIP_TCP
+  /** TCP: when data passed to netconn_write doesn't fit into the send buffer,
+      this temporarily stores how much is already sent. */
+  size_t write_offset;
+  /** TCP: when data passed to netconn_write doesn't fit into the send buffer,
+      this temporarily stores the message.
+      Also used during connect and close. */
+  struct api_msg_msg *current_msg;
+#endif /* LWIP_TCP */
+  /** A callback function that is informed about events for this netconn */
+  netconn_callback callback;
+};
+
+/** Register an Network connection event */
+#define API_EVENT(c,e,l) if (c->callback) {         \
+                           (*c->callback)(c, e, l); \
+                         }
+
+/** Set conn->last_err to err but don't overwrite fatal errors */
+#define NETCONN_SET_SAFE_ERR(conn, err) do { \
+  SYS_ARCH_DECL_PROTECT(lev); \
+  SYS_ARCH_PROTECT(lev); \
+  if (!ERR_IS_FATAL((conn)->last_err)) { \
+    (conn)->last_err = err; \
+  } \
+  SYS_ARCH_UNPROTECT(lev); \
+} while(0);
+
+/* Network connection functions: */
+#define netconn_new(t)                  netconn_new_with_proto_and_callback(t, 0, NULL)
+#define netconn_new_with_callback(t, c) netconn_new_with_proto_and_callback(t, 0, c)
+struct
+netconn *netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto,
+                                             netconn_callback callback);
+err_t   netconn_delete(struct netconn *conn);
+/** Get the type of a netconn (as enum netconn_type). */
+#define netconn_type(conn) (conn->type)
+
+err_t   netconn_getaddr(struct netconn *conn, ip_addr_t *addr,
+                        u16_t *port, u8_t local);
+#define netconn_peer(c,i,p) netconn_getaddr(c,i,p,0)
+#define netconn_addr(c,i,p) netconn_getaddr(c,i,p,1)
+
+err_t   netconn_bind(struct netconn *conn, ip_addr_t *addr, u16_t port);
+err_t   netconn_connect(struct netconn *conn, ip_addr_t *addr, u16_t port);
+err_t   netconn_disconnect (struct netconn *conn);
+err_t   netconn_listen_with_backlog(struct netconn *conn, u8_t backlog);
+#define netconn_listen(conn) netconn_listen_with_backlog(conn, TCP_DEFAULT_LISTEN_BACKLOG)
+err_t   netconn_accept(struct netconn *conn, struct netconn **new_conn);
+err_t   netconn_recv(struct netconn *conn, struct netbuf **new_buf);
+err_t   netconn_recv_tcp_pbuf(struct netconn *conn, struct pbuf **new_buf);
+void    netconn_recved(struct netconn *conn, u32_t length);
+err_t   netconn_sendto(struct netconn *conn, struct netbuf *buf,
+                       ip_addr_t *addr, u16_t port);
+err_t   netconn_send(struct netconn *conn, struct netbuf *buf);
+err_t   netconn_write_partly(struct netconn *conn, const void *dataptr, size_t size,
+                             u8_t apiflags, size_t *bytes_written);
+#define netconn_write(conn, dataptr, size, apiflags) \
+          netconn_write_partly(conn, dataptr, size, apiflags, NULL)
+err_t   netconn_close(struct netconn *conn);
+err_t   netconn_shutdown(struct netconn *conn, u8_t shut_rx, u8_t shut_tx);
+
+#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
+err_t   netconn_join_leave_group(struct netconn *conn, ip_addr_t *multiaddr,
+                                 ip_addr_t *netif_addr, enum netconn_igmp join_or_leave);
+#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+#if LWIP_DNS
+err_t   netconn_gethostbyname(const char *name, ip_addr_t *addr);
+#endif /* LWIP_DNS */
+#if LWIP_IPV6
+
+#define netconn_bind_ip6(conn, ip6addr, port) (NETCONNTYPE_ISIPV6((conn)->type) ? \
+        netconn_bind(conn, ip6_2_ip(ip6addr), port) : ERR_VAL)
+#define netconn_connect_ip6(conn, ip6addr, port) (NETCONNTYPE_ISIPV6((conn)->type) ? \
+        netconn_connect(conn, ip6_2_ip(ip6addr), port) : ERR_VAL)
+#define netconn_sendto_ip6(conn, buf, ip6addr, port) (NETCONNTYPE_ISIPV6((conn)->type) ? \
+        netconn_sendto(conn, buf, ip6_2_ip(ip6addr), port) : ERR_VAL)
+#if LWIP_IPV6_MLD
+#define netconn_join_leave_group_ip6(conn, multiaddr, srcaddr, join_or_leave) (NETCONNTYPE_ISIPV6((conn)->type) ? \
+        netconn_join_leave_group(conn, ip6_2_ip(multiaddr), ip6_2_ip(srcaddr), join_or_leave) :\
+        ERR_VAL)
+#endif /* LWIP_IPV6_MLD*/
+#endif /* LWIP_IPV6 */
+
+#define netconn_err(conn)               ((conn)->last_err)
+#define netconn_recv_bufsize(conn)      ((conn)->recv_bufsize)
+
+/** Set the blocking status of netconn calls (@todo: write/send is missing) */
+#define netconn_set_nonblocking(conn, val)  do { if(val) { \
+  (conn)->flags |= NETCONN_FLAG_NON_BLOCKING; \
+} else { \
+  (conn)->flags &= ~ NETCONN_FLAG_NON_BLOCKING; }} while(0)
+/** Get the blocking status of netconn calls (@todo: write/send is missing) */
+#define netconn_is_nonblocking(conn)        (((conn)->flags & NETCONN_FLAG_NON_BLOCKING) != 0)
+
+/** TCP: Set the no-auto-recved status of netconn calls (see NETCONN_FLAG_NO_AUTO_RECVED) */
+#define netconn_set_noautorecved(conn, val)  do { if(val) { \
+  (conn)->flags |= NETCONN_FLAG_NO_AUTO_RECVED; \
+} else { \
+  (conn)->flags &= ~ NETCONN_FLAG_NO_AUTO_RECVED; }} while(0)
+/** TCP: Get the no-auto-recved status of netconn calls (see NETCONN_FLAG_NO_AUTO_RECVED) */
+#define netconn_get_noautorecved(conn)        (((conn)->flags & NETCONN_FLAG_NO_AUTO_RECVED) != 0)
+
+#if LWIP_SO_SNDTIMEO
+/** Set the send timeout in milliseconds */
+#define netconn_set_sendtimeout(conn, timeout)      ((conn)->send_timeout = (timeout))
+/** Get the send timeout in milliseconds */
+#define netconn_get_sendtimeout(conn)               ((conn)->send_timeout)
+#endif /* LWIP_SO_SNDTIMEO */
+#if LWIP_SO_RCVTIMEO
+/** Set the receive timeout in milliseconds */
+#define netconn_set_recvtimeout(conn, timeout)      ((conn)->recv_timeout = (timeout))
+/** Get the receive timeout in milliseconds */
+#define netconn_get_recvtimeout(conn)               ((conn)->recv_timeout)
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVBUF
+/** Set the receive buffer in bytes */
+#define netconn_set_recvbufsize(conn, recvbufsize)  ((conn)->recv_bufsize = (recvbufsize))
+/** Get the receive buffer in bytes */
+#define netconn_get_recvbufsize(conn)               ((conn)->recv_bufsize)
+#endif /* LWIP_SO_RCVBUF*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_NETCONN */
+
+#endif /* __LWIP_API_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/api_msg.h b/external/badvpn_dns/lwip/src/include/lwip/api_msg.h
new file mode 100644
index 0000000..8268036
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/api_msg.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_API_MSG_H__
+#define __LWIP_API_MSG_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_NETCONN /* don't build if not configured for use in lwipopts.h */
+
+#include <stddef.h> /* for size_t */
+
+#include "lwip/ip_addr.h"
+#include "lwip/err.h"
+#include "lwip/sys.h"
+#include "lwip/igmp.h"
+#include "lwip/api.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For the netconn API, these values are use as a bitmask! */
+#define NETCONN_SHUT_RD   1
+#define NETCONN_SHUT_WR   2
+#define NETCONN_SHUT_RDWR (NETCONN_SHUT_RD | NETCONN_SHUT_WR)
+
+/* IP addresses and port numbers are expected to be in
+ * the same byte order as in the corresponding pcb.
+ */
+/** This struct includes everything that is necessary to execute a function
+    for a netconn in another thread context (mainly used to process netconns
+    in the tcpip_thread context to be thread safe). */
+struct api_msg_msg {
+  /** The netconn which to process - always needed: it includes the semaphore
+      which is used to block the application thread until the function finished. */
+  struct netconn *conn;
+  /** The return value of the function executed in tcpip_thread. */
+  err_t err;
+  /** Depending on the executed function, one of these union members is used */
+  union {
+    /** used for lwip_netconn_do_send */
+    struct netbuf *b;
+    /** used for lwip_netconn_do_newconn */
+    struct {
+      u8_t proto;
+    } n;
+    /** used for lwip_netconn_do_bind and lwip_netconn_do_connect */
+    struct {
+      ip_addr_t *ipaddr;
+      u16_t port;
+    } bc;
+    /** used for lwip_netconn_do_getaddr */
+    struct {
+      ipX_addr_t *ipaddr;
+      u16_t *port;
+      u8_t local;
+    } ad;
+    /** used for lwip_netconn_do_write */
+    struct {
+      const void *dataptr;
+      size_t len;
+      u8_t apiflags;
+#if LWIP_SO_SNDTIMEO
+      u32_t time_started;
+#endif /* LWIP_SO_SNDTIMEO */
+    } w;
+    /** used for lwip_netconn_do_recv */
+    struct {
+      u32_t len;
+    } r;
+    /** used for lwip_netconn_do_close (/shutdown) */
+    struct {
+      u8_t shut;
+    } sd;
+#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
+    /** used for lwip_netconn_do_join_leave_group */
+    struct {
+      ipX_addr_t *multiaddr;
+      ipX_addr_t *netif_addr;
+      enum netconn_igmp join_or_leave;
+    } jl;
+#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+#if TCP_LISTEN_BACKLOG
+    struct {
+      u8_t backlog;
+    } lb;
+#endif /* TCP_LISTEN_BACKLOG */
+  } msg;
+};
+
+/** This struct contains a function to execute in another thread context and
+    a struct api_msg_msg that serves as an argument for this function.
+    This is passed to tcpip_apimsg to execute functions in tcpip_thread context. */
+struct api_msg {
+  /** function to execute in tcpip_thread context */
+  void (* function)(struct api_msg_msg *msg);
+  /** arguments for this function */
+  struct api_msg_msg msg;
+};
+
+#if LWIP_DNS
+/** As lwip_netconn_do_gethostbyname requires more arguments but doesn't require a netconn,
+    it has its own struct (to avoid struct api_msg getting bigger than necessary).
+    lwip_netconn_do_gethostbyname must be called using tcpip_callback instead of tcpip_apimsg
+    (see netconn_gethostbyname). */
+struct dns_api_msg {
+  /** Hostname to query or dotted IP address string */
+  const char *name;
+  /** Rhe resolved address is stored here */
+  ip_addr_t *addr;
+  /** This semaphore is posted when the name is resolved, the application thread
+      should wait on it. */
+  sys_sem_t *sem;
+  /** Errors are given back here */
+  err_t *err;
+};
+#endif /* LWIP_DNS */
+
+void lwip_netconn_do_newconn         ( struct api_msg_msg *msg);
+void lwip_netconn_do_delconn         ( struct api_msg_msg *msg);
+void lwip_netconn_do_bind            ( struct api_msg_msg *msg);
+void lwip_netconn_do_connect         ( struct api_msg_msg *msg);
+void lwip_netconn_do_disconnect      ( struct api_msg_msg *msg);
+void lwip_netconn_do_listen          ( struct api_msg_msg *msg);
+void lwip_netconn_do_send            ( struct api_msg_msg *msg);
+void lwip_netconn_do_recv            ( struct api_msg_msg *msg);
+void lwip_netconn_do_write           ( struct api_msg_msg *msg);
+void lwip_netconn_do_getaddr         ( struct api_msg_msg *msg);
+void lwip_netconn_do_close           ( struct api_msg_msg *msg);
+void lwip_netconn_do_shutdown        ( struct api_msg_msg *msg);
+#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
+void lwip_netconn_do_join_leave_group( struct api_msg_msg *msg);
+#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+
+#if LWIP_DNS
+void lwip_netconn_do_gethostbyname(void *arg);
+#endif /* LWIP_DNS */
+
+struct netconn* netconn_alloc(enum netconn_type t, netconn_callback callback);
+void netconn_free(struct netconn *conn);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_NETCONN */
+
+#endif /* __LWIP_API_MSG_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/arch.h b/external/badvpn_dns/lwip/src/include/lwip/arch.h
new file mode 100644
index 0000000..4d6df77
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/arch.h
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_ARCH_H__
+#define __LWIP_ARCH_H__
+
+#ifndef LITTLE_ENDIAN
+#define LITTLE_ENDIAN 1234
+#endif
+
+#ifndef BIG_ENDIAN
+#define BIG_ENDIAN 4321
+#endif
+
+#include "arch/cc.h"
+
+/** Temporary: define format string for size_t if not defined in cc.h */
+#ifndef SZT_F
+#define SZT_F U32_F
+#endif /* SZT_F */
+/** Temporary upgrade helper: define format string for u8_t as hex if not
+    defined in cc.h */
+#ifndef X8_F
+#define X8_F  "02x"
+#endif /* X8_F */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef PACK_STRUCT_BEGIN
+#define PACK_STRUCT_BEGIN
+#endif /* PACK_STRUCT_BEGIN */
+
+#ifndef PACK_STRUCT_END
+#define PACK_STRUCT_END
+#endif /* PACK_STRUCT_END */
+
+#ifndef PACK_STRUCT_FIELD
+#define PACK_STRUCT_FIELD(x) x
+#endif /* PACK_STRUCT_FIELD */
+
+
+#ifndef LWIP_UNUSED_ARG
+#define LWIP_UNUSED_ARG(x) (void)x
+#endif /* LWIP_UNUSED_ARG */ 
+
+
+#ifdef LWIP_PROVIDE_ERRNO
+
+#define  EPERM         1  /* Operation not permitted */
+#define  ENOENT        2  /* No such file or directory */
+#define  ESRCH         3  /* No such process */
+#define  EINTR         4  /* Interrupted system call */
+#define  EIO           5  /* I/O error */
+#define  ENXIO         6  /* No such device or address */
+#define  E2BIG         7  /* Arg list too long */
+#define  ENOEXEC       8  /* Exec format error */
+#define  EBADF         9  /* Bad file number */
+#define  ECHILD       10  /* No child processes */
+#define  EAGAIN       11  /* Try again */
+#define  ENOMEM       12  /* Out of memory */
+#define  EACCES       13  /* Permission denied */
+#define  EFAULT       14  /* Bad address */
+#define  ENOTBLK      15  /* Block device required */
+#define  EBUSY        16  /* Device or resource busy */
+#define  EEXIST       17  /* File exists */
+#define  EXDEV        18  /* Cross-device link */
+#define  ENODEV       19  /* No such device */
+#define  ENOTDIR      20  /* Not a directory */
+#define  EISDIR       21  /* Is a directory */
+#define  EINVAL       22  /* Invalid argument */
+#define  ENFILE       23  /* File table overflow */
+#define  EMFILE       24  /* Too many open files */
+#define  ENOTTY       25  /* Not a typewriter */
+#define  ETXTBSY      26  /* Text file busy */
+#define  EFBIG        27  /* File too large */
+#define  ENOSPC       28  /* No space left on device */
+#define  ESPIPE       29  /* Illegal seek */
+#define  EROFS        30  /* Read-only file system */
+#define  EMLINK       31  /* Too many links */
+#define  EPIPE        32  /* Broken pipe */
+#define  EDOM         33  /* Math argument out of domain of func */
+#define  ERANGE       34  /* Math result not representable */
+#define  EDEADLK      35  /* Resource deadlock would occur */
+#define  ENAMETOOLONG 36  /* File name too long */
+#define  ENOLCK       37  /* No record locks available */
+#define  ENOSYS       38  /* Function not implemented */
+#define  ENOTEMPTY    39  /* Directory not empty */
+#define  ELOOP        40  /* Too many symbolic links encountered */
+#define  EWOULDBLOCK  EAGAIN  /* Operation would block */
+#define  ENOMSG       42  /* No message of desired type */
+#define  EIDRM        43  /* Identifier removed */
+#define  ECHRNG       44  /* Channel number out of range */
+#define  EL2NSYNC     45  /* Level 2 not synchronized */
+#define  EL3HLT       46  /* Level 3 halted */
+#define  EL3RST       47  /* Level 3 reset */
+#define  ELNRNG       48  /* Link number out of range */
+#define  EUNATCH      49  /* Protocol driver not attached */
+#define  ENOCSI       50  /* No CSI structure available */
+#define  EL2HLT       51  /* Level 2 halted */
+#define  EBADE        52  /* Invalid exchange */
+#define  EBADR        53  /* Invalid request descriptor */
+#define  EXFULL       54  /* Exchange full */
+#define  ENOANO       55  /* No anode */
+#define  EBADRQC      56  /* Invalid request code */
+#define  EBADSLT      57  /* Invalid slot */
+
+#define  EDEADLOCK    EDEADLK
+
+#define  EBFONT       59  /* Bad font file format */
+#define  ENOSTR       60  /* Device not a stream */
+#define  ENODATA      61  /* No data available */
+#define  ETIME        62  /* Timer expired */
+#define  ENOSR        63  /* Out of streams resources */
+#define  ENONET       64  /* Machine is not on the network */
+#define  ENOPKG       65  /* Package not installed */
+#define  EREMOTE      66  /* Object is remote */
+#define  ENOLINK      67  /* Link has been severed */
+#define  EADV         68  /* Advertise error */
+#define  ESRMNT       69  /* Srmount error */
+#define  ECOMM        70  /* Communication error on send */
+#define  EPROTO       71  /* Protocol error */
+#define  EMULTIHOP    72  /* Multihop attempted */
+#define  EDOTDOT      73  /* RFS specific error */
+#define  EBADMSG      74  /* Not a data message */
+#define  EOVERFLOW    75  /* Value too large for defined data type */
+#define  ENOTUNIQ     76  /* Name not unique on network */
+#define  EBADFD       77  /* File descriptor in bad state */
+#define  EREMCHG      78  /* Remote address changed */
+#define  ELIBACC      79  /* Can not access a needed shared library */
+#define  ELIBBAD      80  /* Accessing a corrupted shared library */
+#define  ELIBSCN      81  /* .lib section in a.out corrupted */
+#define  ELIBMAX      82  /* Attempting to link in too many shared libraries */
+#define  ELIBEXEC     83  /* Cannot exec a shared library directly */
+#define  EILSEQ       84  /* Illegal byte sequence */
+#define  ERESTART     85  /* Interrupted system call should be restarted */
+#define  ESTRPIPE     86  /* Streams pipe error */
+#define  EUSERS       87  /* Too many users */
+#define  ENOTSOCK     88  /* Socket operation on non-socket */
+#define  EDESTADDRREQ 89  /* Destination address required */
+#define  EMSGSIZE     90  /* Message too long */
+#define  EPROTOTYPE   91  /* Protocol wrong type for socket */
+#define  ENOPROTOOPT  92  /* Protocol not available */
+#define  EPROTONOSUPPORT 93  /* Protocol not supported */
+#define  ESOCKTNOSUPPORT 94  /* Socket type not supported */
+#define  EOPNOTSUPP      95  /* Operation not supported on transport endpoint */
+#define  EPFNOSUPPORT    96  /* Protocol family not supported */
+#define  EAFNOSUPPORT    97  /* Address family not supported by protocol */
+#define  EADDRINUSE      98  /* Address already in use */
+#define  EADDRNOTAVAIL   99  /* Cannot assign requested address */
+#define  ENETDOWN       100  /* Network is down */
+#define  ENETUNREACH    101  /* Network is unreachable */
+#define  ENETRESET      102  /* Network dropped connection because of reset */
+#define  ECONNABORTED   103  /* Software caused connection abort */
+#define  ECONNRESET     104  /* Connection reset by peer */
+#define  ENOBUFS        105  /* No buffer space available */
+#define  EISCONN        106  /* Transport endpoint is already connected */
+#define  ENOTCONN       107  /* Transport endpoint is not connected */
+#define  ESHUTDOWN      108  /* Cannot send after transport endpoint shutdown */
+#define  ETOOMANYREFS   109  /* Too many references: cannot splice */
+#define  ETIMEDOUT      110  /* Connection timed out */
+#define  ECONNREFUSED   111  /* Connection refused */
+#define  EHOSTDOWN      112  /* Host is down */
+#define  EHOSTUNREACH   113  /* No route to host */
+#define  EALREADY       114  /* Operation already in progress */
+#define  EINPROGRESS    115  /* Operation now in progress */
+#define  ESTALE         116  /* Stale NFS file handle */
+#define  EUCLEAN        117  /* Structure needs cleaning */
+#define  ENOTNAM        118  /* Not a XENIX named type file */
+#define  ENAVAIL        119  /* No XENIX semaphores available */
+#define  EISNAM         120  /* Is a named type file */
+#define  EREMOTEIO      121  /* Remote I/O error */
+#define  EDQUOT         122  /* Quota exceeded */
+
+#define  ENOMEDIUM      123  /* No medium found */
+#define  EMEDIUMTYPE    124  /* Wrong medium type */
+
+#ifndef errno
+extern int errno;
+#endif
+
+#endif /* LWIP_PROVIDE_ERRNO */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_ARCH_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/debug.h b/external/badvpn_dns/lwip/src/include/lwip/debug.h
new file mode 100644
index 0000000..0fe0413
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/debug.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_DEBUG_H__
+#define __LWIP_DEBUG_H__
+
+#include "lwip/arch.h"
+#include "lwip/opt.h"
+
+/** lower two bits indicate debug level
+ * - 0 all
+ * - 1 warning
+ * - 2 serious
+ * - 3 severe
+ */
+#define LWIP_DBG_LEVEL_ALL     0x00
+#define LWIP_DBG_LEVEL_OFF     LWIP_DBG_LEVEL_ALL /* compatibility define only */
+#define LWIP_DBG_LEVEL_WARNING 0x01 /* bad checksums, dropped packets, ... */
+#define LWIP_DBG_LEVEL_SERIOUS 0x02 /* memory allocation failures, ... */
+#define LWIP_DBG_LEVEL_SEVERE  0x03
+#define LWIP_DBG_MASK_LEVEL    0x03
+
+/** flag for LWIP_DEBUGF to enable that debug message */
+#define LWIP_DBG_ON            0x80U
+/** flag for LWIP_DEBUGF to disable that debug message */
+#define LWIP_DBG_OFF           0x00U
+
+/** flag for LWIP_DEBUGF indicating a tracing message (to follow program flow) */
+#define LWIP_DBG_TRACE         0x40U
+/** flag for LWIP_DEBUGF indicating a state debug message (to follow module states) */
+#define LWIP_DBG_STATE         0x20U
+/** flag for LWIP_DEBUGF indicating newly added code, not thoroughly tested yet */
+#define LWIP_DBG_FRESH         0x10U
+/** flag for LWIP_DEBUGF to halt after printing this debug message */
+#define LWIP_DBG_HALT          0x08U
+
+#ifndef LWIP_NOASSERT
+#define LWIP_ASSERT(message, assertion) do { if(!(assertion)) \
+  LWIP_PLATFORM_ASSERT(message); } while(0)
+#else  /* LWIP_NOASSERT */
+#define LWIP_ASSERT(message, assertion) 
+#endif /* LWIP_NOASSERT */
+
+/** if "expression" isn't true, then print "message" and execute "handler" expression */
+#ifndef LWIP_ERROR
+#define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \
+  LWIP_PLATFORM_ASSERT(message); handler;}} while(0)
+#endif /* LWIP_ERROR */
+
+#ifdef LWIP_DEBUG
+/** print debug message only if debug message type is enabled...
+ *  AND is of correct type AND is at least LWIP_DBG_LEVEL
+ */
+#define LWIP_DEBUGF(debug, message) do { \
+                               if ( \
+                                   ((debug) & LWIP_DBG_ON) && \
+                                   ((debug) & LWIP_DBG_TYPES_ON) && \
+                                   ((s16_t)((debug) & LWIP_DBG_MASK_LEVEL) >= LWIP_DBG_MIN_LEVEL)) { \
+                                 LWIP_PLATFORM_DIAG(message); \
+                                 if ((debug) & LWIP_DBG_HALT) { \
+                                   while(1); \
+                                 } \
+                               } \
+                             } while(0)
+
+#else  /* LWIP_DEBUG */
+#define LWIP_DEBUGF(debug, message) 
+#endif /* LWIP_DEBUG */
+
+#endif /* __LWIP_DEBUG_H__ */
+
diff --git a/external/badvpn_dns/lwip/src/include/lwip/def.h b/external/badvpn_dns/lwip/src/include/lwip/def.h
new file mode 100644
index 0000000..73a1b56
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/def.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_DEF_H__
+#define __LWIP_DEF_H__
+
+/* arch.h might define NULL already */
+#include "lwip/arch.h"
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LWIP_MAX(x , y)  (((x) > (y)) ? (x) : (y))
+#define LWIP_MIN(x , y)  (((x) < (y)) ? (x) : (y))
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+/* Endianess-optimized shifting of two u8_t to create one u16_t */
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define LWIP_MAKE_U16(a, b) ((a << 8) | b)
+#else
+#define LWIP_MAKE_U16(a, b) ((b << 8) | a)
+#endif 
+
+#ifndef LWIP_PLATFORM_BYTESWAP
+#define LWIP_PLATFORM_BYTESWAP 0
+#endif
+
+#ifndef LWIP_PREFIX_BYTEORDER_FUNCS
+/* workaround for naming collisions on some platforms */
+
+#ifdef htons
+#undef htons
+#endif /* htons */
+#ifdef htonl
+#undef htonl
+#endif /* htonl */
+#ifdef ntohs
+#undef ntohs
+#endif /* ntohs */
+#ifdef ntohl
+#undef ntohl
+#endif /* ntohl */
+
+#define htons(x) lwip_htons(x)
+#define ntohs(x) lwip_ntohs(x)
+#define htonl(x) lwip_htonl(x)
+#define ntohl(x) lwip_ntohl(x)
+#endif /* LWIP_PREFIX_BYTEORDER_FUNCS */
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define lwip_htons(x) (x)
+#define lwip_ntohs(x) (x)
+#define lwip_htonl(x) (x)
+#define lwip_ntohl(x) (x)
+#define PP_HTONS(x) (x)
+#define PP_NTOHS(x) (x)
+#define PP_HTONL(x) (x)
+#define PP_NTOHL(x) (x)
+#else /* BYTE_ORDER != BIG_ENDIAN */
+#if LWIP_PLATFORM_BYTESWAP
+#define lwip_htons(x) LWIP_PLATFORM_HTONS(x)
+#define lwip_ntohs(x) LWIP_PLATFORM_HTONS(x)
+#define lwip_htonl(x) LWIP_PLATFORM_HTONL(x)
+#define lwip_ntohl(x) LWIP_PLATFORM_HTONL(x)
+#else /* LWIP_PLATFORM_BYTESWAP */
+u16_t lwip_htons(u16_t x);
+u16_t lwip_ntohs(u16_t x);
+u32_t lwip_htonl(u32_t x);
+u32_t lwip_ntohl(u32_t x);
+#endif /* LWIP_PLATFORM_BYTESWAP */
+
+/* These macros should be calculated by the preprocessor and are used
+   with compile-time constants only (so that there is no little-endian
+   overhead at runtime). */
+#define PP_HTONS(x) ((((x) & 0xff) << 8) | (((x) & 0xff00) >> 8))
+#define PP_NTOHS(x) PP_HTONS(x)
+#define PP_HTONL(x) ((((x) & 0xff) << 24) | \
+                     (((x) & 0xff00) << 8) | \
+                     (((x) & 0xff0000UL) >> 8) | \
+                     (((x) & 0xff000000UL) >> 24))
+#define PP_NTOHL(x) PP_HTONL(x)
+
+#endif /* BYTE_ORDER == BIG_ENDIAN */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_DEF_H__ */
+
diff --git a/external/badvpn_dns/lwip/src/include/lwip/dhcp.h b/external/badvpn_dns/lwip/src/include/lwip/dhcp.h
new file mode 100644
index 0000000..32d9338
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/dhcp.h
@@ -0,0 +1,242 @@
+/** @file
+ */
+
+#ifndef __LWIP_DHCP_H__
+#define __LWIP_DHCP_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_DHCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/netif.h"
+#include "lwip/udp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** period (in seconds) of the application calling dhcp_coarse_tmr() */
+#define DHCP_COARSE_TIMER_SECS 60 
+/** period (in milliseconds) of the application calling dhcp_coarse_tmr() */
+#define DHCP_COARSE_TIMER_MSECS (DHCP_COARSE_TIMER_SECS * 1000UL)
+/** period (in milliseconds) of the application calling dhcp_fine_tmr() */
+#define DHCP_FINE_TIMER_MSECS 500 
+
+#define DHCP_CHADDR_LEN 16U
+#define DHCP_SNAME_LEN  64U
+#define DHCP_FILE_LEN   128U
+
+struct dhcp
+{
+  /** transaction identifier of last sent request */ 
+  u32_t xid;
+  /** our connection to the DHCP server */ 
+  struct udp_pcb *pcb;
+  /** incoming msg */
+  struct dhcp_msg *msg_in;
+  /** current DHCP state machine state */
+  u8_t state;
+  /** retries of current request */
+  u8_t tries;
+#if LWIP_DHCP_AUTOIP_COOP
+  u8_t autoip_coop_state;
+#endif
+  u8_t subnet_mask_given;
+
+  struct pbuf *p_out; /* pbuf of outcoming msg */
+  struct dhcp_msg *msg_out; /* outgoing msg */
+  u16_t options_out_len; /* outgoing msg options length */
+  u16_t request_timeout; /* #ticks with period DHCP_FINE_TIMER_SECS for request timeout */
+  u16_t t1_timeout;  /* #ticks with period DHCP_COARSE_TIMER_SECS for renewal time */
+  u16_t t2_timeout;  /* #ticks with period DHCP_COARSE_TIMER_SECS for rebind time */
+  ip_addr_t server_ip_addr; /* dhcp server address that offered this lease */
+  ip_addr_t offered_ip_addr;
+  ip_addr_t offered_sn_mask;
+  ip_addr_t offered_gw_addr;
+ 
+  u32_t offered_t0_lease; /* lease period (in seconds) */
+  u32_t offered_t1_renew; /* recommended renew time (usually 50% of lease period) */
+  u32_t offered_t2_rebind; /* recommended rebind time (usually 66% of lease period)  */
+  /* @todo: LWIP_DHCP_BOOTP_FILE configuration option?
+     integrate with possible TFTP-client for booting? */
+#if LWIP_DHCP_BOOTP_FILE
+  ip_addr_t offered_si_addr;
+  char boot_file_name[DHCP_FILE_LEN];
+#endif /* LWIP_DHCP_BOOTPFILE */
+};
+
+/* MUST be compiled with "pack structs" or equivalent! */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+/** minimum set of fields of any DHCP message */
+struct dhcp_msg
+{
+  PACK_STRUCT_FIELD(u8_t op);
+  PACK_STRUCT_FIELD(u8_t htype);
+  PACK_STRUCT_FIELD(u8_t hlen);
+  PACK_STRUCT_FIELD(u8_t hops);
+  PACK_STRUCT_FIELD(u32_t xid);
+  PACK_STRUCT_FIELD(u16_t secs);
+  PACK_STRUCT_FIELD(u16_t flags);
+  PACK_STRUCT_FIELD(ip_addr_p_t ciaddr);
+  PACK_STRUCT_FIELD(ip_addr_p_t yiaddr);
+  PACK_STRUCT_FIELD(ip_addr_p_t siaddr);
+  PACK_STRUCT_FIELD(ip_addr_p_t giaddr);
+  PACK_STRUCT_FIELD(u8_t chaddr[DHCP_CHADDR_LEN]);
+  PACK_STRUCT_FIELD(u8_t sname[DHCP_SNAME_LEN]);
+  PACK_STRUCT_FIELD(u8_t file[DHCP_FILE_LEN]);
+  PACK_STRUCT_FIELD(u32_t cookie);
+#define DHCP_MIN_OPTIONS_LEN 68U
+/** make sure user does not configure this too small */
+#if ((defined(DHCP_OPTIONS_LEN)) && (DHCP_OPTIONS_LEN < DHCP_MIN_OPTIONS_LEN))
+#  undef DHCP_OPTIONS_LEN
+#endif
+/** allow this to be configured in lwipopts.h, but not too small */
+#if (!defined(DHCP_OPTIONS_LEN))
+/** set this to be sufficient for your options in outgoing DHCP msgs */
+#  define DHCP_OPTIONS_LEN DHCP_MIN_OPTIONS_LEN
+#endif
+  PACK_STRUCT_FIELD(u8_t options[DHCP_OPTIONS_LEN]);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+void dhcp_set_struct(struct netif *netif, struct dhcp *dhcp);
+/** Remove a struct dhcp previously set to the netif using dhcp_set_struct() */
+#define dhcp_remove_struct(netif) do { (netif)->dhcp = NULL; } while(0)
+void dhcp_cleanup(struct netif *netif);
+/** start DHCP configuration */
+err_t dhcp_start(struct netif *netif);
+/** enforce early lease renewal (not needed normally)*/
+err_t dhcp_renew(struct netif *netif);
+/** release the DHCP lease, usually called before dhcp_stop()*/
+err_t dhcp_release(struct netif *netif);
+/** stop DHCP configuration */
+void dhcp_stop(struct netif *netif);
+/** inform server of our manual IP address */
+void dhcp_inform(struct netif *netif);
+/** Handle a possible change in the network configuration */
+void dhcp_network_changed(struct netif *netif);
+
+/** if enabled, check whether the offered IP address is not in use, using ARP */
+#if DHCP_DOES_ARP_CHECK
+void dhcp_arp_reply(struct netif *netif, ip_addr_t *addr);
+#endif
+
+/** to be called every minute */
+void dhcp_coarse_tmr(void);
+/** to be called every half second */
+void dhcp_fine_tmr(void);
+ 
+/** DHCP message item offsets and length */
+#define DHCP_OP_OFS       0
+#define DHCP_HTYPE_OFS    1
+#define DHCP_HLEN_OFS     2
+#define DHCP_HOPS_OFS     3
+#define DHCP_XID_OFS      4
+#define DHCP_SECS_OFS     8
+#define DHCP_FLAGS_OFS    10
+#define DHCP_CIADDR_OFS   12
+#define DHCP_YIADDR_OFS   16
+#define DHCP_SIADDR_OFS   20
+#define DHCP_GIADDR_OFS   24
+#define DHCP_CHADDR_OFS   28
+#define DHCP_SNAME_OFS    44
+#define DHCP_FILE_OFS     108
+#define DHCP_MSG_LEN      236
+
+#define DHCP_COOKIE_OFS   DHCP_MSG_LEN
+#define DHCP_OPTIONS_OFS  (DHCP_MSG_LEN + 4)
+
+#define DHCP_CLIENT_PORT  68  
+#define DHCP_SERVER_PORT  67
+
+/** DHCP client states */
+#define DHCP_OFF          0
+#define DHCP_REQUESTING   1
+#define DHCP_INIT         2
+#define DHCP_REBOOTING    3
+#define DHCP_REBINDING    4
+#define DHCP_RENEWING     5
+#define DHCP_SELECTING    6
+#define DHCP_INFORMING    7
+#define DHCP_CHECKING     8
+#define DHCP_PERMANENT    9
+#define DHCP_BOUND        10
+/** not yet implemented #define DHCP_RELEASING 11 */
+#define DHCP_BACKING_OFF  12
+
+/** AUTOIP cooperatation flags */
+#define DHCP_AUTOIP_COOP_STATE_OFF  0
+#define DHCP_AUTOIP_COOP_STATE_ON   1
+ 
+#define DHCP_BOOTREQUEST  1
+#define DHCP_BOOTREPLY    2
+
+/** DHCP message types */
+#define DHCP_DISCOVER 1
+#define DHCP_OFFER    2
+#define DHCP_REQUEST  3
+#define DHCP_DECLINE  4
+#define DHCP_ACK      5
+#define DHCP_NAK      6
+#define DHCP_RELEASE  7
+#define DHCP_INFORM   8
+
+/** DHCP hardware type, currently only ethernet is supported */
+#define DHCP_HTYPE_ETH 1
+
+#define DHCP_MAGIC_COOKIE 0x63825363UL
+
+/* This is a list of options for BOOTP and DHCP, see RFC 2132 for descriptions */
+
+/** BootP options */
+#define DHCP_OPTION_PAD 0
+#define DHCP_OPTION_SUBNET_MASK 1 /* RFC 2132 3.3 */
+#define DHCP_OPTION_ROUTER 3
+#define DHCP_OPTION_DNS_SERVER 6 
+#define DHCP_OPTION_HOSTNAME 12
+#define DHCP_OPTION_IP_TTL 23
+#define DHCP_OPTION_MTU 26
+#define DHCP_OPTION_BROADCAST 28
+#define DHCP_OPTION_TCP_TTL 37
+#define DHCP_OPTION_END 255
+
+/** DHCP options */
+#define DHCP_OPTION_REQUESTED_IP 50 /* RFC 2132 9.1, requested IP address */
+#define DHCP_OPTION_LEASE_TIME 51 /* RFC 2132 9.2, time in seconds, in 4 bytes */
+#define DHCP_OPTION_OVERLOAD 52 /* RFC2132 9.3, use file and/or sname field for options */
+
+#define DHCP_OPTION_MESSAGE_TYPE 53 /* RFC 2132 9.6, important for DHCP */
+#define DHCP_OPTION_MESSAGE_TYPE_LEN 1
+
+#define DHCP_OPTION_SERVER_ID 54 /* RFC 2132 9.7, server IP address */
+#define DHCP_OPTION_PARAMETER_REQUEST_LIST 55 /* RFC 2132 9.8, requested option types */
+
+#define DHCP_OPTION_MAX_MSG_SIZE 57 /* RFC 2132 9.10, message size accepted >= 576 */
+#define DHCP_OPTION_MAX_MSG_SIZE_LEN 2
+
+#define DHCP_OPTION_T1 58 /* T1 renewal time */
+#define DHCP_OPTION_T2 59 /* T2 rebinding time */
+#define DHCP_OPTION_US 60
+#define DHCP_OPTION_CLIENT_ID 61
+#define DHCP_OPTION_TFTP_SERVERNAME 66
+#define DHCP_OPTION_BOOTFILE 67
+
+/** possible combinations of overloading the file and sname fields with options */
+#define DHCP_OVERLOAD_NONE 0
+#define DHCP_OVERLOAD_FILE 1
+#define DHCP_OVERLOAD_SNAME  2
+#define DHCP_OVERLOAD_SNAME_FILE 3
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_DHCP */
+
+#endif /*__LWIP_DHCP_H__*/
diff --git a/external/badvpn_dns/lwip/src/include/lwip/dns.h b/external/badvpn_dns/lwip/src/include/lwip/dns.h
new file mode 100644
index 0000000..6c7d9b0
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/dns.h
@@ -0,0 +1,124 @@
+/**
+ * lwip DNS resolver header file.
+
+ * Author: Jim Pettinato 
+ *   April 2007
+
+ * ported from uIP resolv.c Copyright (c) 2002-2003, Adam Dunkels.
+ *
+ * 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. 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.
+ */
+
+#ifndef __LWIP_DNS_H__
+#define __LWIP_DNS_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_DNS /* don't build if not configured for use in lwipopts.h */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** DNS timer period */
+#define DNS_TMR_INTERVAL          1000
+
+/** DNS field TYPE used for "Resource Records" */
+#define DNS_RRTYPE_A              1     /* a host address */
+#define DNS_RRTYPE_NS             2     /* an authoritative name server */
+#define DNS_RRTYPE_MD             3     /* a mail destination (Obsolete - use MX) */
+#define DNS_RRTYPE_MF             4     /* a mail forwarder (Obsolete - use MX) */
+#define DNS_RRTYPE_CNAME          5     /* the canonical name for an alias */
+#define DNS_RRTYPE_SOA            6     /* marks the start of a zone of authority */
+#define DNS_RRTYPE_MB             7     /* a mailbox domain name (EXPERIMENTAL) */
+#define DNS_RRTYPE_MG             8     /* a mail group member (EXPERIMENTAL) */
+#define DNS_RRTYPE_MR             9     /* a mail rename domain name (EXPERIMENTAL) */
+#define DNS_RRTYPE_NULL           10    /* a null RR (EXPERIMENTAL) */
+#define DNS_RRTYPE_WKS            11    /* a well known service description */
+#define DNS_RRTYPE_PTR            12    /* a domain name pointer */
+#define DNS_RRTYPE_HINFO          13    /* host information */
+#define DNS_RRTYPE_MINFO          14    /* mailbox or mail list information */
+#define DNS_RRTYPE_MX             15    /* mail exchange */
+#define DNS_RRTYPE_TXT            16    /* text strings */
+
+/** DNS field CLASS used for "Resource Records" */
+#define DNS_RRCLASS_IN            1     /* the Internet */
+#define DNS_RRCLASS_CS            2     /* the CSNET class (Obsolete - used only for examples in some obsolete RFCs) */
+#define DNS_RRCLASS_CH            3     /* the CHAOS class */
+#define DNS_RRCLASS_HS            4     /* Hesiod [Dyer 87] */
+#define DNS_RRCLASS_FLUSH         0x800 /* Flush bit */
+
+/* The size used for the next line is rather a hack, but it prevents including socket.h in all files
+   that include memp.h, and that would possibly break portability (since socket.h defines some types
+   and constants possibly already define by the OS).
+   Calculation rule:
+   sizeof(struct addrinfo) + sizeof(struct sockaddr_in) + DNS_MAX_NAME_LENGTH + 1 byte zero-termination */
+#define NETDB_ELEM_SIZE           (32 + 16 + DNS_MAX_NAME_LENGTH + 1)
+
+#if DNS_LOCAL_HOSTLIST
+/** struct used for local host-list */
+struct local_hostlist_entry {
+  /** static hostname */
+  const char *name;
+  /** static host address in network byteorder */
+  ip_addr_t addr;
+  struct local_hostlist_entry *next;
+};
+#if DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+#ifndef DNS_LOCAL_HOSTLIST_MAX_NAMELEN
+#define DNS_LOCAL_HOSTLIST_MAX_NAMELEN  DNS_MAX_NAME_LENGTH
+#endif
+#define LOCALHOSTLIST_ELEM_SIZE ((sizeof(struct local_hostlist_entry) + DNS_LOCAL_HOSTLIST_MAX_NAMELEN + 1))
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+#endif /* DNS_LOCAL_HOSTLIST */
+
+/** Callback which is invoked when a hostname is found.
+ * A function of this type must be implemented by the application using the DNS resolver.
+ * @param name pointer to the name that was looked up.
+ * @param ipaddr pointer to an ip_addr_t containing the IP address of the hostname,
+ *        or NULL if the name could not be found (or on any other error).
+ * @param callback_arg a user-specified callback argument passed to dns_gethostbyname
+*/
+typedef void (*dns_found_callback)(const char *name, ip_addr_t *ipaddr, void *callback_arg);
+
+void           dns_init(void);
+void           dns_tmr(void);
+void           dns_setserver(u8_t numdns, ip_addr_t *dnsserver);
+ip_addr_t      dns_getserver(u8_t numdns);
+err_t          dns_gethostbyname(const char *hostname, ip_addr_t *addr,
+                                 dns_found_callback found, void *callback_arg);
+
+#if DNS_LOCAL_HOSTLIST && DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+int            dns_local_removehost(const char *hostname, const ip_addr_t *addr);
+err_t          dns_local_addhost(const char *hostname, const ip_addr_t *addr);
+#endif /* DNS_LOCAL_HOSTLIST && DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_DNS */
+
+#endif /* __LWIP_DNS_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/err.h b/external/badvpn_dns/lwip/src/include/lwip/err.h
new file mode 100644
index 0000000..ac90772
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/err.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_ERR_H__
+#define __LWIP_ERR_H__
+
+#include "lwip/opt.h"
+#include "lwip/arch.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Define LWIP_ERR_T in cc.h if you want to use
+ *  a different type for your platform (must be signed). */
+#ifdef LWIP_ERR_T
+typedef LWIP_ERR_T err_t;
+#else /* LWIP_ERR_T */
+typedef s8_t err_t;
+#endif /* LWIP_ERR_T*/
+
+/* Definitions for error constants. */
+
+#define ERR_OK          0    /* No error, everything OK. */
+#define ERR_MEM        -1    /* Out of memory error.     */
+#define ERR_BUF        -2    /* Buffer error.            */
+#define ERR_TIMEOUT    -3    /* Timeout.                 */
+#define ERR_RTE        -4    /* Routing problem.         */
+#define ERR_INPROGRESS -5    /* Operation in progress    */
+#define ERR_VAL        -6    /* Illegal value.           */
+#define ERR_WOULDBLOCK -7    /* Operation would block.   */
+#define ERR_USE        -8    /* Address in use.          */
+#define ERR_ISCONN     -9    /* Already connected.       */
+
+#define ERR_IS_FATAL(e) ((e) < ERR_ISCONN)
+
+#define ERR_ABRT       -10   /* Connection aborted.      */
+#define ERR_RST        -11   /* Connection reset.        */
+#define ERR_CLSD       -12   /* Connection closed.       */
+#define ERR_CONN       -13   /* Not connected.           */
+
+#define ERR_ARG        -14   /* Illegal argument.        */
+
+#define ERR_IF         -15   /* Low-level netif error    */
+
+
+#ifdef LWIP_DEBUG
+extern const char *lwip_strerr(err_t err);
+#else
+#define lwip_strerr(x) ""
+#endif /* LWIP_DEBUG */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_ERR_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/inet_chksum.h b/external/badvpn_dns/lwip/src/include/lwip/inet_chksum.h
new file mode 100644
index 0000000..e168788
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/inet_chksum.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_INET_CHKSUM_H__
+#define __LWIP_INET_CHKSUM_H__
+
+#include "lwip/opt.h"
+
+#include "lwip/pbuf.h"
+#include "lwip/ip_addr.h"
+
+/** Swap the bytes in an u16_t: much like htons() for little-endian */
+#ifndef SWAP_BYTES_IN_WORD
+#if LWIP_PLATFORM_BYTESWAP && (BYTE_ORDER == LITTLE_ENDIAN)
+/* little endian and PLATFORM_BYTESWAP defined */
+#define SWAP_BYTES_IN_WORD(w) LWIP_PLATFORM_HTONS(w)
+#else /* LWIP_PLATFORM_BYTESWAP && (BYTE_ORDER == LITTLE_ENDIAN) */
+/* can't use htons on big endian (or PLATFORM_BYTESWAP not defined)... */
+#define SWAP_BYTES_IN_WORD(w) (((w) & 0xff) << 8) | (((w) & 0xff00) >> 8)
+#endif /* LWIP_PLATFORM_BYTESWAP && (BYTE_ORDER == LITTLE_ENDIAN)*/
+#endif /* SWAP_BYTES_IN_WORD */
+
+/** Split an u32_t in two u16_ts and add them up */
+#ifndef FOLD_U32T
+#define FOLD_U32T(u)          (((u) >> 16) + ((u) & 0x0000ffffUL))
+#endif
+
+#if LWIP_CHECKSUM_ON_COPY
+/** Function-like macro: same as MEMCPY but returns the checksum of copied data
+    as u16_t */
+#ifndef LWIP_CHKSUM_COPY
+#define LWIP_CHKSUM_COPY(dst, src, len) lwip_chksum_copy(dst, src, len)
+#ifndef LWIP_CHKSUM_COPY_ALGORITHM
+#define LWIP_CHKSUM_COPY_ALGORITHM 1
+#endif /* LWIP_CHKSUM_COPY_ALGORITHM */
+#endif /* LWIP_CHKSUM_COPY */
+#else /* LWIP_CHECKSUM_ON_COPY */
+#define LWIP_CHKSUM_COPY_ALGORITHM 0
+#endif /* LWIP_CHECKSUM_ON_COPY */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+u16_t inet_chksum(void *dataptr, u16_t len);
+u16_t inet_chksum_pbuf(struct pbuf *p);
+u16_t inet_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len,
+       ip_addr_t *src, ip_addr_t *dest);
+u16_t inet_chksum_pseudo_partial(struct pbuf *p, u8_t proto,
+       u16_t proto_len, u16_t chksum_len, ip_addr_t *src, ip_addr_t *dest);
+#if LWIP_CHKSUM_COPY_ALGORITHM
+u16_t lwip_chksum_copy(void *dst, const void *src, u16_t len);
+#endif /* LWIP_CHKSUM_COPY_ALGORITHM */
+
+#if LWIP_IPV6
+u16_t ip6_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len,
+       ip6_addr_t *src, ip6_addr_t *dest);
+u16_t ip6_chksum_pseudo_partial(struct pbuf *p, u8_t proto, u16_t proto_len,
+       u16_t chksum_len, ip6_addr_t *src, ip6_addr_t *dest);
+
+#define ipX_chksum_pseudo(isipv6, p, proto, proto_len, src, dest) \
+  ((isipv6) ? \
+  ip6_chksum_pseudo(p, proto, proto_len, ipX_2_ip6(src), ipX_2_ip6(dest)) :\
+  inet_chksum_pseudo(p, proto, proto_len, ipX_2_ip(src), ipX_2_ip(dest)))
+#define ipX_chksum_pseudo_partial(isipv6, p, proto, proto_len, chksum_len, src, dest) \
+  ((isipv6) ? \
+  ip6_chksum_pseudo_partial(p, proto, proto_len, chksum_len, ipX_2_ip6(src), ipX_2_ip6(dest)) :\
+  inet_chksum_pseudo_partial(p, proto, proto_len, chksum_len, ipX_2_ip(src), ipX_2_ip(dest)))
+
+#else /* LWIP_IPV6 */
+
+#define ipX_chksum_pseudo(isipv6, p, proto, proto_len, src, dest) \
+  inet_chksum_pseudo(p, proto, proto_len, src, dest)
+#define ipX_chksum_pseudo_partial(isipv6, p, proto, proto_len, chksum_len, src, dest) \
+  inet_chksum_pseudo_partial(p, proto, proto_len, chksum_len, src, dest)
+
+#endif /* LWIP_IPV6 */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_INET_H__ */
+
diff --git a/external/badvpn_dns/lwip/src/include/lwip/init.h b/external/badvpn_dns/lwip/src/include/lwip/init.h
new file mode 100644
index 0000000..4e2e285
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/init.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_INIT_H__
+#define __LWIP_INIT_H__
+
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** X.x.x: Major version of the stack */
+#define LWIP_VERSION_MAJOR      1U
+/** x.X.x: Minor version of the stack */
+#define LWIP_VERSION_MINOR      4U
+/** x.x.X: Revision of the stack */
+#define LWIP_VERSION_REVISION   1U
+/** For release candidates, this is set to 1..254
+  * For official releases, this is set to 255 (LWIP_RC_RELEASE)
+  * For development versions (CVS), this is set to 0 (LWIP_RC_DEVELOPMENT) */
+#define LWIP_VERSION_RC         0U
+
+/** LWIP_VERSION_RC is set to LWIP_RC_RELEASE for official releases */
+#define LWIP_RC_RELEASE         255U
+/** LWIP_VERSION_RC is set to LWIP_RC_DEVELOPMENT for CVS versions */
+#define LWIP_RC_DEVELOPMENT     0U
+
+#define LWIP_VERSION_IS_RELEASE     (LWIP_VERSION_RC == LWIP_RC_RELEASE)
+#define LWIP_VERSION_IS_DEVELOPMENT (LWIP_VERSION_RC == LWIP_RC_DEVELOPMENT)
+#define LWIP_VERSION_IS_RC          ((LWIP_VERSION_RC != LWIP_RC_RELEASE) && (LWIP_VERSION_RC != LWIP_RC_DEVELOPMENT))
+
+/** Provides the version of the stack */
+#define LWIP_VERSION   (LWIP_VERSION_MAJOR << 24   | LWIP_VERSION_MINOR << 16 | \
+                        LWIP_VERSION_REVISION << 8 | LWIP_VERSION_RC)
+
+/* Modules initialization */
+void lwip_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_INIT_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/ip.h b/external/badvpn_dns/lwip/src/include/lwip/ip.h
new file mode 100644
index 0000000..a0cd1d4
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/ip.h
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_IP_H__
+#define __LWIP_IP_H__
+
+#include "lwip/opt.h"
+
+#include "lwip/def.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip_addr.h"
+#include "lwip/err.h"
+#include "lwip/netif.h"
+#include "lwip/ip4.h"
+#include "lwip/ip6.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is passed as the destination address to ip_output_if (not
+   to ip_output), meaning that an IP header already is constructed
+   in the pbuf. This is used when TCP retransmits. */
+#ifdef IP_HDRINCL
+#undef IP_HDRINCL
+#endif /* IP_HDRINCL */
+#define IP_HDRINCL  NULL
+
+#if LWIP_NETIF_HWADDRHINT
+#define IP_PCB_ADDRHINT ;u8_t addr_hint
+#else
+#define IP_PCB_ADDRHINT
+#endif /* LWIP_NETIF_HWADDRHINT */
+
+#if LWIP_IPV6
+#define IP_PCB_ISIPV6_MEMBER  u8_t isipv6;
+#define IP_PCB_IPVER_EQ(pcb1, pcb2)   ((pcb1)->isipv6 == (pcb2)->isipv6)
+#define IP_PCB_IPVER_INPUT_MATCH(pcb) (ip_current_is_v6() ? \
+                                       ((pcb)->isipv6 != 0) : \
+                                       ((pcb)->isipv6 == 0))
+#define PCB_ISIPV6(pcb) ((pcb)->isipv6)
+#else
+#define IP_PCB_ISIPV6_MEMBER
+#define IP_PCB_IPVER_EQ(pcb1, pcb2)   1
+#define IP_PCB_IPVER_INPUT_MATCH(pcb) 1
+#define PCB_ISIPV6(pcb)            0
+#endif /* LWIP_IPV6 */
+
+/* This is the common part of all PCB types. It needs to be at the
+   beginning of a PCB type definition. It is located here so that
+   changes to this common part are made in one location instead of
+   having to change all PCB structs. */
+#define IP_PCB \
+  IP_PCB_ISIPV6_MEMBER \
+  /* ip addresses in network byte order */ \
+  ipX_addr_t local_ip; \
+  ipX_addr_t remote_ip; \
+   /* Socket options */  \
+  u8_t so_options;      \
+   /* Type Of Service */ \
+  u8_t tos;              \
+  /* Time To Live */     \
+  u8_t ttl               \
+  /* link layer address resolution hint */ \
+  IP_PCB_ADDRHINT
+
+struct ip_pcb {
+/* Common members of all PCB types */
+  IP_PCB;
+};
+
+/*
+ * Option flags per-socket. These are the same like SO_XXX.
+ */
+/*#define SOF_DEBUG       0x01U     Unimplemented: turn on debugging info recording */
+#define SOF_ACCEPTCONN    0x02U  /* socket has had listen() */
+#define SOF_REUSEADDR     0x04U  /* allow local address reuse */
+#define SOF_KEEPALIVE     0x08U  /* keep connections alive */
+/*#define SOF_DONTROUTE   0x10U     Unimplemented: just use interface addresses */
+#define SOF_BROADCAST     0x20U  /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */
+/*#define SOF_USELOOPBACK 0x40U     Unimplemented: bypass hardware when possible */
+#define SOF_LINGER        0x80U  /* linger on close if data present */
+/*#define SOF_OOBINLINE   0x0100U     Unimplemented: leave received OOB data in line */
+/*#define SOF_REUSEPORT   0x0200U     Unimplemented: allow local address & port reuse */
+
+/* These flags are inherited (e.g. from a listen-pcb to a connection-pcb): */
+#define SOF_INHERITED   (SOF_REUSEADDR|SOF_KEEPALIVE|SOF_LINGER/*|SOF_DEBUG|SOF_DONTROUTE|SOF_OOBINLINE*/)
+
+/* Global variables of this module, kept in a struct for efficient access using base+index. */
+struct ip_globals
+{
+  /** The interface that provided the packet for the current callback invocation. */
+  struct netif *current_netif;
+  /** Header of the input packet currently being processed. */
+  const struct ip_hdr *current_ip4_header;
+#if LWIP_IPV6
+  /** Header of the input IPv6 packet currently being processed. */
+  const struct ip6_hdr *current_ip6_header;
+#endif /* LWIP_IPV6 */
+  /** Total header length of current_ip4/6_header (i.e. after this, the UDP/TCP header starts) */
+  u16_t current_ip_header_tot_len;
+  /** Source IP address of current_header */
+  ipX_addr_t current_iphdr_src;
+  /** Destination IP address of current_header */
+  ipX_addr_t current_iphdr_dest;
+};
+extern struct ip_globals ip_data;
+
+
+/** Get the interface that received the current packet.
+ * This function must only be called from a receive callback (udp_recv,
+ * raw_recv, tcp_accept). It will return NULL otherwise. */
+#define ip_current_netif()      (ip_data.current_netif)
+/** Get the IP header of the current packet.
+ * This function must only be called from a receive callback (udp_recv,
+ * raw_recv, tcp_accept). It will return NULL otherwise. */
+#define ip_current_header()     (ip_data.current_ip4_header)
+/** Total header length of ip(6)_current_header() (i.e. after this, the UDP/TCP header starts) */
+#define ip_current_header_tot_len() (ip_data.current_ip_header_tot_len)
+/** Source IP address of current_header */
+#define ipX_current_src_addr()   (&ip_data.current_iphdr_src)
+/** Destination IP address of current_header */
+#define ipX_current_dest_addr()  (&ip_data.current_iphdr_dest)
+
+#if LWIP_IPV6
+/** Get the IPv6 header of the current packet.
+ * This function must only be called from a receive callback (udp_recv,
+ * raw_recv, tcp_accept). It will return NULL otherwise. */
+#define ip6_current_header()      (ip_data.current_ip6_header)
+/** Returns TRUE if the current IP input packet is IPv6, FALSE if it is IPv4 */
+#define ip_current_is_v6()        (ip6_current_header() != NULL)
+/** Source IPv6 address of current_header */
+#define ip6_current_src_addr()    (ipX_2_ip6(&ip_data.current_iphdr_src))
+/** Destination IPv6 address of current_header */
+#define ip6_current_dest_addr()   (ipX_2_ip6(&ip_data.current_iphdr_dest))
+/** Get the transport layer protocol */
+#define ip_current_header_proto() (ip_current_is_v6() ? \
+                                   IP6H_NEXTH(ip6_current_header()) :\
+                                   IPH_PROTO(ip_current_header()))
+/** Get the transport layer header */
+#define ipX_next_header_ptr()     ((void*)((ip_current_is_v6() ? \
+  (u8_t*)ip6_current_header() : (u8_t*)ip_current_header())  + ip_current_header_tot_len()))
+
+/** Set an IP_PCB to IPv6 (IPv4 is the default) */
+#define ip_set_v6(pcb, val)       do{if(pcb != NULL) { pcb->isipv6 = val; }}while(0)
+
+/** Source IP4 address of current_header */
+#define ip_current_src_addr()     (ipX_2_ip(&ip_data.current_iphdr_src))
+/** Destination IP4 address of current_header */
+#define ip_current_dest_addr()    (ipX_2_ip(&ip_data.current_iphdr_dest))
+
+#else /* LWIP_IPV6 */
+
+/** Always returns FALSE when only supporting IPv4 */
+#define ip_current_is_v6()        0
+/** Get the transport layer protocol */
+#define ip_current_header_proto() IPH_PROTO(ip_current_header())
+/** Get the transport layer header */
+#define ipX_next_header_ptr()     ((void*)((u8_t*)ip_current_header() + ip_current_header_tot_len()))
+/** Source IP4 address of current_header */
+#define ip_current_src_addr()     (&ip_data.current_iphdr_src)
+/** Destination IP4 address of current_header */
+#define ip_current_dest_addr()    (&ip_data.current_iphdr_dest)
+
+#endif /* LWIP_IPV6 */
+
+/** Union source address of current_header */
+#define ipX_current_src_addr()    (&ip_data.current_iphdr_src)
+/** Union destination address of current_header */
+#define ipX_current_dest_addr()   (&ip_data.current_iphdr_dest)
+
+/** Gets an IP pcb option (SOF_* flags) */
+#define ip_get_option(pcb, opt)   ((pcb)->so_options & (opt))
+/** Sets an IP pcb option (SOF_* flags) */
+#define ip_set_option(pcb, opt)   ((pcb)->so_options |= (opt))
+/** Resets an IP pcb option (SOF_* flags) */
+#define ip_reset_option(pcb, opt) ((pcb)->so_options &= ~(opt))
+
+#if LWIP_IPV6
+#define ipX_output(isipv6, p, src, dest, ttl, tos, proto) \
+        ((isipv6) ? \
+        ip6_output(p, ipX_2_ip6(src), ipX_2_ip6(dest), ttl, tos, proto) : \
+        ip_output(p, ipX_2_ip(src), ipX_2_ip(dest), ttl, tos, proto))
+#define ipX_output_if(isipv6, p, src, dest, ttl, tos, proto, netif) \
+        ((isipv6) ? \
+        ip6_output_if(p, ip_2_ip6(src), ip_2_ip6(dest), ttl, tos, proto, netif) : \
+        ip_output_if(p, (src), (dest), ttl, tos, proto, netif))
+#define ipX_output_hinted(isipv6, p, src, dest, ttl, tos, proto, addr_hint) \
+        ((isipv6) ? \
+        ip6_output_hinted(p, ipX_2_ip6(src), ipX_2_ip6(dest), ttl, tos, proto, addr_hint) : \
+        ip_output_hinted(p, ipX_2_ip(src), ipX_2_ip(dest), ttl, tos, proto, addr_hint))
+#define ipX_route(isipv6, src, dest) \
+        ((isipv6) ? \
+        ip6_route(ipX_2_ip6(src), ipX_2_ip6(dest)) : \
+        ip_route(ipX_2_ip(dest)))
+#define ipX_netif_get_local_ipX(isipv6, netif, dest) \
+        ((isipv6) ? \
+        ip6_netif_get_local_ipX(netif, ipX_2_ip6(dest)) : \
+        ip_netif_get_local_ipX(netif))
+#define ipX_debug_print(is_ipv6, p) ((is_ipv6) ? ip6_debug_print(p) : ip_debug_print(p))
+#else /* LWIP_IPV6 */
+#define ipX_output(isipv6, p, src, dest, ttl, tos, proto) \
+        ip_output(p, src, dest, ttl, tos, proto)
+#define ipX_output_if(isipv6, p, src, dest, ttl, tos, proto, netif) \
+        ip_output_if(p, src, dest, ttl, tos, proto, netif)
+#define ipX_output_hinted(isipv6, p, src, dest, ttl, tos, proto, addr_hint) \
+        ip_output_hinted(p, src, dest, ttl, tos, proto, addr_hint)
+#define ipX_route(isipv6, src, dest) \
+        ip_route(ipX_2_ip(dest))
+#define ipX_netif_get_local_ipX(isipv6, netif, dest) \
+        ip_netif_get_local_ipX(netif)
+#define ipX_debug_print(is_ipv6, p) ip_debug_print(p)
+#endif /* LWIP_IPV6 */
+
+#define ipX_route_get_local_ipX(isipv6, src, dest, netif, ipXaddr) do { \
+  (netif) = ipX_route(isipv6, src, dest); \
+  (ipXaddr) = ipX_netif_get_local_ipX(isipv6, netif, dest); \
+}while(0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_IP_H__ */
+
+
diff --git a/external/badvpn_dns/lwip/src/include/lwip/ip_addr.h b/external/badvpn_dns/lwip/src/include/lwip/ip_addr.h
new file mode 100644
index 0000000..7bd03cb
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/ip_addr.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_IP_ADDR_H__
+#define __LWIP_IP_ADDR_H__
+
+#include "lwip/opt.h"
+#include "lwip/def.h"
+
+#include "lwip/ip4_addr.h"
+#include "lwip/ip6_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_IPV6
+/* A union struct for both IP version's addresses. */
+typedef union {
+  ip_addr_t ip4;
+  ip6_addr_t ip6;
+} ipX_addr_t;
+
+/** These functions only exist for type-safe conversion from ip_addr_t to
+    ip6_addr_t and back */
+#ifdef LWIP_ALLOW_STATIC_FN_IN_HEADER
+static ip6_addr_t* ip_2_ip6(ip_addr_t *ipaddr)
+{ return (ip6_addr_t*)ipaddr;}
+static ip_addr_t* ip6_2_ip(ip6_addr_t *ip6addr)
+{ return (ip_addr_t*)ip6addr; }
+static ipX_addr_t* ip_2_ipX(ip_addr_t *ipaddr)
+{ return (ipX_addr_t*)ipaddr; }
+static ipX_addr_t* ip6_2_ipX(ip6_addr_t *ip6addr)
+{ return (ipX_addr_t*)ip6addr; }
+#else /* LWIP_ALLOW_STATIC_FN_IN_HEADER */
+#define ip_2_ip6(ipaddr)   ((ip6_addr_t*)(ipaddr))
+#define ip6_2_ip(ip6addr)  ((ip_addr_t*)(ip6addr))
+#define ip_2_ipX(ipaddr)   ((ipX_addr_t*)ipaddr)
+#define ip6_2_ipX(ip6addr) ((ipX_addr_t*)ip6addr)
+#endif /* LWIP_ALLOW_STATIC_FN_IN_HEADER*/
+#define ipX_2_ip6(ip6addr) (&((ip6addr)->ip6))
+#define ipX_2_ip(ipaddr)   (&((ipaddr)->ip4))
+
+#define ipX_addr_copy(is_ipv6, dest, src)      do{if(is_ipv6){ \
+  ip6_addr_copy((dest).ip6, (src).ip6); }else{ \
+  ip_addr_copy((dest).ip4, (src).ip4); }}while(0)
+#define ipX_addr_set(is_ipv6, dest, src) do{if(is_ipv6){ \
+  ip6_addr_set(ipX_2_ip6(dest), ipX_2_ip6(src)); }else{ \
+  ip_addr_set(ipX_2_ip(dest), ipX_2_ip(src)); }}while(0)
+#define ipX_addr_set_ipaddr(is_ipv6, dest, src) do{if(is_ipv6){ \
+  ip6_addr_set(ipX_2_ip6(dest), ip_2_ip6(src)); }else{ \
+  ip_addr_set(ipX_2_ip(dest), src); }}while(0)
+#define ipX_addr_set_zero(is_ipv6, ipaddr)     do{if(is_ipv6){ \
+  ip6_addr_set_zero(ipX_2_ip6(ipaddr)); }else{ \
+  ip_addr_set_zero(ipX_2_ip(ipaddr)); }}while(0)
+#define ipX_addr_set_any(is_ipv6, ipaddr)      do{if(is_ipv6){ \
+  ip6_addr_set_any(ipX_2_ip6(ipaddr)); }else{ \
+  ip_addr_set_any(ipX_2_ip(ipaddr)); }}while(0)
+#define ipX_addr_set_loopback(is_ipv6, ipaddr) do{if(is_ipv6){ \
+  ip6_addr_set_loopback(ipX_2_ip6(ipaddr)); }else{ \
+  ip_addr_set_loopback(ipX_2_ip(ipaddr)); }}while(0)
+#define ipX_addr_set_hton(is_ipv6, dest, src)  do{if(is_ipv6){ \
+  ip6_addr_set_hton(ipX_2_ip6(ipaddr), (src)) ;}else{ \
+  ip_addr_set_hton(ipX_2_ip(ipaddr), (src));}}while(0)
+#define ipX_addr_cmp(is_ipv6, addr1, addr2)    ((is_ipv6) ? \
+  ip6_addr_cmp(ipX_2_ip6(addr1), ipX_2_ip6(addr2)) : \
+  ip_addr_cmp(ipX_2_ip(addr1), ipX_2_ip(addr2)))
+#define ipX_addr_isany(is_ipv6, ipaddr)        ((is_ipv6) ? \
+  ip6_addr_isany(ipX_2_ip6(ipaddr)) : \
+  ip_addr_isany(ipX_2_ip(ipaddr)))
+#define ipX_addr_ismulticast(is_ipv6, ipaddr)  ((is_ipv6) ? \
+  ip6_addr_ismulticast(ipX_2_ip6(ipaddr)) : \
+  ip_addr_ismulticast(ipX_2_ip(ipaddr)))
+#define ipX_addr_debug_print(is_ipv6, debug, ipaddr) do { if(is_ipv6) { \
+  ip6_addr_debug_print(debug, ipX_2_ip6(ipaddr)); } else { \
+  ip_addr_debug_print(debug, ipX_2_ip(ipaddr)); }}while(0)
+
+#else /* LWIP_IPV6 */
+
+typedef ip_addr_t ipX_addr_t;
+#define ipX_2_ip(ipaddr) (ipaddr)
+#define ip_2_ipX(ipaddr) (ipaddr)
+
+#define ipX_addr_copy(is_ipv6, dest, src)       ip_addr_copy(dest, src)
+#define ipX_addr_set(is_ipv6, dest, src)        ip_addr_set(dest, src)
+#define ipX_addr_set_ipaddr(is_ipv6, dest, src) ip_addr_set(dest, src)
+#define ipX_addr_set_zero(is_ipv6, ipaddr)      ip_addr_set_zero(ipaddr)
+#define ipX_addr_set_any(is_ipv6, ipaddr)       ip_addr_set_any(ipaddr)
+#define ipX_addr_set_loopback(is_ipv6, ipaddr)  ip_addr_set_loopback(ipaddr)
+#define ipX_addr_set_hton(is_ipv6, dest, src)   ip_addr_set_hton(dest, src)
+#define ipX_addr_cmp(is_ipv6, addr1, addr2)     ip_addr_cmp(addr1, addr2)
+#define ipX_addr_isany(is_ipv6, ipaddr)         ip_addr_isany(ipaddr)
+#define ipX_addr_ismulticast(is_ipv6, ipaddr)   ip_addr_ismulticast(ipaddr)
+#define ipX_addr_debug_print(is_ipv6, debug, ipaddr) ip_addr_debug_print(debug, ipaddr)
+
+#endif /* LWIP_IPV6 */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_IP_ADDR_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/mem.h b/external/badvpn_dns/lwip/src/include/lwip/mem.h
new file mode 100644
index 0000000..5bb906b
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/mem.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_MEM_H__
+#define __LWIP_MEM_H__
+
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if MEM_LIBC_MALLOC
+
+#include <stddef.h> /* for size_t */
+
+typedef size_t mem_size_t;
+#define MEM_SIZE_F SZT_F
+
+/* aliases for C library malloc() */
+#define mem_init()
+/* in case C library malloc() needs extra protection,
+ * allow these defines to be overridden.
+ */
+#ifndef mem_free
+#define mem_free free
+#endif
+#ifndef mem_malloc
+#define mem_malloc malloc
+#endif
+#ifndef mem_calloc
+#define mem_calloc calloc
+#endif
+/* Since there is no C library allocation function to shrink memory without
+   moving it, define this to nothing. */
+#ifndef mem_trim
+#define mem_trim(mem, size) (mem)
+#endif
+#else /* MEM_LIBC_MALLOC */
+
+/* MEM_SIZE would have to be aligned, but using 64000 here instead of
+ * 65535 leaves some room for alignment...
+ */
+#if MEM_SIZE > 64000L
+typedef u32_t mem_size_t;
+#define MEM_SIZE_F U32_F
+#else
+typedef u16_t mem_size_t;
+#define MEM_SIZE_F U16_F
+#endif /* MEM_SIZE > 64000 */
+
+#if MEM_USE_POOLS
+/** mem_init is not used when using pools instead of a heap */
+#define mem_init()
+/** mem_trim is not used when using pools instead of a heap:
+    we can't free part of a pool element and don't want to copy the rest */
+#define mem_trim(mem, size) (mem)
+#else /* MEM_USE_POOLS */
+/* lwIP alternative malloc */
+void  mem_init(void);
+void *mem_trim(void *mem, mem_size_t size);
+#endif /* MEM_USE_POOLS */
+void *mem_malloc(mem_size_t size);
+void *mem_calloc(mem_size_t count, mem_size_t size);
+void  mem_free(void *mem);
+#endif /* MEM_LIBC_MALLOC */
+
+/** Calculate memory size for an aligned buffer - returns the next highest
+ * multiple of MEM_ALIGNMENT (e.g. LWIP_MEM_ALIGN_SIZE(3) and
+ * LWIP_MEM_ALIGN_SIZE(4) will both yield 4 for MEM_ALIGNMENT == 4).
+ */
+#ifndef LWIP_MEM_ALIGN_SIZE
+#define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1) & ~(MEM_ALIGNMENT-1))
+#endif
+
+/** Calculate safe memory size for an aligned buffer when using an unaligned
+ * type as storage. This includes a safety-margin on (MEM_ALIGNMENT - 1) at the
+ * start (e.g. if buffer is u8_t[] and actual data will be u32_t*)
+ */
+#ifndef LWIP_MEM_ALIGN_BUFFER
+#define LWIP_MEM_ALIGN_BUFFER(size) (((size) + MEM_ALIGNMENT - 1))
+#endif
+
+/** Align a memory pointer to the alignment defined by MEM_ALIGNMENT
+ * so that ADDR % MEM_ALIGNMENT == 0
+ */
+#ifndef LWIP_MEM_ALIGN
+#define LWIP_MEM_ALIGN(addr) ((void *)(((mem_ptr_t)(addr) + MEM_ALIGNMENT - 1) & ~(mem_ptr_t)(MEM_ALIGNMENT-1)))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_MEM_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/memp.h b/external/badvpn_dns/lwip/src/include/lwip/memp.h
new file mode 100644
index 0000000..f0d0739
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/memp.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#ifndef __LWIP_MEMP_H__
+#define __LWIP_MEMP_H__
+
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Create the list of all memory pools managed by memp. MEMP_MAX represents a NULL pool at the end */
+typedef enum {
+#define LWIP_MEMPOOL(name,num,size,desc)  MEMP_##name,
+#include "lwip/memp_std.h"
+  MEMP_MAX
+} memp_t;
+
+#if MEM_USE_POOLS
+/* Use a helper type to get the start and end of the user "memory pools" for mem_malloc */
+typedef enum {
+    /* Get the first (via:
+       MEMP_POOL_HELPER_START = ((u8_t) 1*MEMP_POOL_A + 0*MEMP_POOL_B + 0*MEMP_POOL_C + 0)*/
+    MEMP_POOL_HELPER_FIRST = ((u8_t)
+#define LWIP_MEMPOOL(name,num,size,desc)
+#define LWIP_MALLOC_MEMPOOL_START 1
+#define LWIP_MALLOC_MEMPOOL(num, size) * MEMP_POOL_##size + 0
+#define LWIP_MALLOC_MEMPOOL_END
+#include "lwip/memp_std.h"
+    ) ,
+    /* Get the last (via:
+       MEMP_POOL_HELPER_END = ((u8_t) 0 + MEMP_POOL_A*0 + MEMP_POOL_B*0 + MEMP_POOL_C*1) */
+    MEMP_POOL_HELPER_LAST = ((u8_t)
+#define LWIP_MEMPOOL(name,num,size,desc)
+#define LWIP_MALLOC_MEMPOOL_START
+#define LWIP_MALLOC_MEMPOOL(num, size) 0 + MEMP_POOL_##size *
+#define LWIP_MALLOC_MEMPOOL_END 1
+#include "lwip/memp_std.h"
+    )
+} memp_pool_helper_t;
+
+/* The actual start and stop values are here (cast them over)
+   We use this helper type and these defines so we can avoid using const memp_t values */
+#define MEMP_POOL_FIRST ((memp_t) MEMP_POOL_HELPER_FIRST)
+#define MEMP_POOL_LAST   ((memp_t) MEMP_POOL_HELPER_LAST)
+#endif /* MEM_USE_POOLS */
+
+#if MEMP_MEM_MALLOC || MEM_USE_POOLS
+extern const u16_t memp_sizes[MEMP_MAX];
+#endif /* MEMP_MEM_MALLOC || MEM_USE_POOLS */
+
+#if MEMP_MEM_MALLOC
+
+#include "mem.h"
+
+#define memp_init()
+#define memp_malloc(type)     mem_malloc(memp_sizes[type])
+#define memp_free(type, mem)  mem_free(mem)
+
+#else /* MEMP_MEM_MALLOC */
+
+#if MEM_USE_POOLS
+/** This structure is used to save the pool one element came from. */
+struct memp_malloc_helper
+{
+   memp_t poolnr;
+};
+#endif /* MEM_USE_POOLS */
+
+void  memp_init(void);
+
+#if MEMP_OVERFLOW_CHECK
+void *memp_malloc_fn(memp_t type, const char* file, const int line);
+#define memp_malloc(t) memp_malloc_fn((t), __FILE__, __LINE__)
+#else
+void *memp_malloc(memp_t type);
+#endif
+void  memp_free(memp_t type, void *mem);
+
+#endif /* MEMP_MEM_MALLOC */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_MEMP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/memp_std.h b/external/badvpn_dns/lwip/src/include/lwip/memp_std.h
new file mode 100644
index 0000000..592a282
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/memp_std.h
@@ -0,0 +1,135 @@
+/*
+ * SETUP: Make sure we define everything we will need.
+ *
+ * We have create three types of pools:
+ *   1) MEMPOOL - standard pools
+ *   2) MALLOC_MEMPOOL - to be used by mem_malloc in mem.c
+ *   3) PBUF_MEMPOOL - a mempool of pbuf's, so include space for the pbuf struct
+ *
+ * If the include'r doesn't require any special treatment of each of the types
+ * above, then will declare #2 & #3 to be just standard mempools.
+ */
+#ifndef LWIP_MALLOC_MEMPOOL
+/* This treats "malloc pools" just like any other pool.
+   The pools are a little bigger to provide 'size' as the amount of user data. */
+#define LWIP_MALLOC_MEMPOOL(num, size) LWIP_MEMPOOL(POOL_##size, num, (size + sizeof(struct memp_malloc_helper)), "MALLOC_"#size)
+#define LWIP_MALLOC_MEMPOOL_START
+#define LWIP_MALLOC_MEMPOOL_END
+#endif /* LWIP_MALLOC_MEMPOOL */ 
+
+#ifndef LWIP_PBUF_MEMPOOL
+/* This treats "pbuf pools" just like any other pool.
+ * Allocates buffers for a pbuf struct AND a payload size */
+#define LWIP_PBUF_MEMPOOL(name, num, payload, desc) LWIP_MEMPOOL(name, num, (MEMP_ALIGN_SIZE(sizeof(struct pbuf)) + MEMP_ALIGN_SIZE(payload)), desc)
+#endif /* LWIP_PBUF_MEMPOOL */
+
+
+/*
+ * A list of internal pools used by LWIP.
+ *
+ * LWIP_MEMPOOL(pool_name, number_elements, element_size, pool_description)
+ *     creates a pool name MEMP_pool_name. description is used in stats.c
+ */
+#if LWIP_RAW
+LWIP_MEMPOOL(RAW_PCB,        MEMP_NUM_RAW_PCB,         sizeof(struct raw_pcb),        "RAW_PCB")
+#endif /* LWIP_RAW */
+
+#if LWIP_UDP
+LWIP_MEMPOOL(UDP_PCB,        MEMP_NUM_UDP_PCB,         sizeof(struct udp_pcb),        "UDP_PCB")
+#endif /* LWIP_UDP */
+
+#if LWIP_TCP
+LWIP_MEMPOOL(TCP_PCB,        MEMP_NUM_TCP_PCB,         sizeof(struct tcp_pcb),        "TCP_PCB")
+LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN,  sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN")
+LWIP_MEMPOOL(TCP_SEG,        MEMP_NUM_TCP_SEG,         sizeof(struct tcp_seg),        "TCP_SEG")
+#endif /* LWIP_TCP */
+
+#if IP_REASSEMBLY
+LWIP_MEMPOOL(REASSDATA,      MEMP_NUM_REASSDATA,       sizeof(struct ip_reassdata),   "REASSDATA")
+#endif /* IP_REASSEMBLY */
+#if (IP_FRAG && !IP_FRAG_USES_STATIC_BUF && !LWIP_NETIF_TX_SINGLE_PBUF) || LWIP_IPV6_FRAG
+LWIP_MEMPOOL(FRAG_PBUF,      MEMP_NUM_FRAG_PBUF,       sizeof(struct pbuf_custom_ref),"FRAG_PBUF")
+#endif /* IP_FRAG && !IP_FRAG_USES_STATIC_BUF && !LWIP_NETIF_TX_SINGLE_PBUF */
+
+#if LWIP_NETCONN
+LWIP_MEMPOOL(NETBUF,         MEMP_NUM_NETBUF,          sizeof(struct netbuf),         "NETBUF")
+LWIP_MEMPOOL(NETCONN,        MEMP_NUM_NETCONN,         sizeof(struct netconn),        "NETCONN")
+#endif /* LWIP_NETCONN */
+
+#if NO_SYS==0
+LWIP_MEMPOOL(TCPIP_MSG_API,  MEMP_NUM_TCPIP_MSG_API,   sizeof(struct tcpip_msg),      "TCPIP_MSG_API")
+#if !LWIP_TCPIP_CORE_LOCKING_INPUT
+LWIP_MEMPOOL(TCPIP_MSG_INPKT,MEMP_NUM_TCPIP_MSG_INPKT, sizeof(struct tcpip_msg),      "TCPIP_MSG_INPKT")
+#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
+#endif /* NO_SYS==0 */
+
+#if LWIP_ARP && ARP_QUEUEING
+LWIP_MEMPOOL(ARP_QUEUE,      MEMP_NUM_ARP_QUEUE,       sizeof(struct etharp_q_entry), "ARP_QUEUE")
+#endif /* LWIP_ARP && ARP_QUEUEING */
+
+#if LWIP_IGMP
+LWIP_MEMPOOL(IGMP_GROUP,     MEMP_NUM_IGMP_GROUP,      sizeof(struct igmp_group),     "IGMP_GROUP")
+#endif /* LWIP_IGMP */
+
+#if (!NO_SYS || (NO_SYS && !NO_SYS_NO_TIMERS)) /* LWIP_TIMERS */
+LWIP_MEMPOOL(SYS_TIMEOUT,    MEMP_NUM_SYS_TIMEOUT,     sizeof(struct sys_timeo),      "SYS_TIMEOUT")
+#endif /* LWIP_TIMERS */
+
+#if LWIP_SNMP
+LWIP_MEMPOOL(SNMP_ROOTNODE,  MEMP_NUM_SNMP_ROOTNODE,   sizeof(struct mib_list_rootnode), "SNMP_ROOTNODE")
+LWIP_MEMPOOL(SNMP_NODE,      MEMP_NUM_SNMP_NODE,       sizeof(struct mib_list_node),     "SNMP_NODE")
+LWIP_MEMPOOL(SNMP_VARBIND,   MEMP_NUM_SNMP_VARBIND,    sizeof(struct snmp_varbind),      "SNMP_VARBIND")
+LWIP_MEMPOOL(SNMP_VALUE,     MEMP_NUM_SNMP_VALUE,      SNMP_MAX_VALUE_SIZE,              "SNMP_VALUE")
+#endif /* LWIP_SNMP */
+#if LWIP_DNS && LWIP_SOCKET
+LWIP_MEMPOOL(NETDB,          MEMP_NUM_NETDB,           NETDB_ELEM_SIZE,               "NETDB")
+#endif /* LWIP_DNS && LWIP_SOCKET */
+#if LWIP_DNS && DNS_LOCAL_HOSTLIST && DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+LWIP_MEMPOOL(LOCALHOSTLIST,  MEMP_NUM_LOCALHOSTLIST,   LOCALHOSTLIST_ELEM_SIZE,       "LOCALHOSTLIST")
+#endif /* LWIP_DNS && DNS_LOCAL_HOSTLIST && DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+#if PPP_SUPPORT && PPPOE_SUPPORT
+LWIP_MEMPOOL(PPPOE_IF,      MEMP_NUM_PPPOE_INTERFACES, sizeof(struct pppoe_softc),    "PPPOE_IF")
+#endif /* PPP_SUPPORT && PPPOE_SUPPORT */
+
+#if LWIP_IPV6 && LWIP_ND6_QUEUEING
+LWIP_MEMPOOL(ND6_QUEUE,      MEMP_NUM_ND6_QUEUE,       sizeof(struct nd6_q_entry), "ND6_QUEUE")
+#endif /* LWIP_IPV6 && LWIP_ND6_QUEUEING */
+
+#if LWIP_IPV6 && LWIP_IPV6_REASS
+LWIP_MEMPOOL(IP6_REASSDATA,      MEMP_NUM_REASSDATA,       sizeof(struct ip6_reassdata),   "IP6_REASSDATA")
+#endif /* LWIP_IPV6 && LWIP_IPV6_REASS */
+
+#if LWIP_IPV6 && LWIP_IPV6_MLD
+LWIP_MEMPOOL(MLD6_GROUP,     MEMP_NUM_MLD6_GROUP,      sizeof(struct mld_group),     "MLD6_GROUP")
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+
+
+/*
+ * A list of pools of pbuf's used by LWIP.
+ *
+ * LWIP_PBUF_MEMPOOL(pool_name, number_elements, pbuf_payload_size, pool_description)
+ *     creates a pool name MEMP_pool_name. description is used in stats.c
+ *     This allocates enough space for the pbuf struct and a payload.
+ *     (Example: pbuf_payload_size=0 allocates only size for the struct)
+ */
+LWIP_PBUF_MEMPOOL(PBUF,      MEMP_NUM_PBUF,            0,                             "PBUF_REF/ROM")
+LWIP_PBUF_MEMPOOL(PBUF_POOL, PBUF_POOL_SIZE,           PBUF_POOL_BUFSIZE,             "PBUF_POOL")
+
+
+/*
+ * Allow for user-defined pools; this must be explicitly set in lwipopts.h
+ * since the default is to NOT look for lwippools.h
+ */
+#if MEMP_USE_CUSTOM_POOLS
+#include "lwippools.h"
+#endif /* MEMP_USE_CUSTOM_POOLS */
+
+/*
+ * REQUIRED CLEANUP: Clear up so we don't get "multiply defined" error later
+ * (#undef is ignored for something that is not defined)
+ */
+#undef LWIP_MEMPOOL
+#undef LWIP_MALLOC_MEMPOOL
+#undef LWIP_MALLOC_MEMPOOL_START
+#undef LWIP_MALLOC_MEMPOOL_END
+#undef LWIP_PBUF_MEMPOOL
diff --git a/external/badvpn_dns/lwip/src/include/lwip/netbuf.h b/external/badvpn_dns/lwip/src/include/lwip/netbuf.h
new file mode 100644
index 0000000..d12fe27
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/netbuf.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_NETBUF_H__
+#define __LWIP_NETBUF_H__
+
+#include "lwip/opt.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip_addr.h"
+#include "lwip/ip6_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** This netbuf has dest-addr/port set */
+#define NETBUF_FLAG_DESTADDR    0x01
+/** This netbuf includes a checksum */
+#define NETBUF_FLAG_CHKSUM      0x02
+
+struct netbuf {
+  struct pbuf *p, *ptr;
+  ipX_addr_t addr;
+  u16_t port;
+#if LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY
+#if LWIP_CHECKSUM_ON_COPY
+  u8_t flags;
+#endif /* LWIP_CHECKSUM_ON_COPY */
+  u16_t toport_chksum;
+#if LWIP_NETBUF_RECVINFO
+  ipX_addr_t toaddr;
+#endif /* LWIP_NETBUF_RECVINFO */
+#endif /* LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY */
+};
+
+/* Network buffer functions: */
+struct netbuf *   netbuf_new      (void);
+void              netbuf_delete   (struct netbuf *buf);
+void *            netbuf_alloc    (struct netbuf *buf, u16_t size);
+void              netbuf_free     (struct netbuf *buf);
+err_t             netbuf_ref      (struct netbuf *buf,
+                                   const void *dataptr, u16_t size);
+void              netbuf_chain    (struct netbuf *head,
+           struct netbuf *tail);
+
+err_t             netbuf_data     (struct netbuf *buf,
+                                   void **dataptr, u16_t *len);
+s8_t              netbuf_next     (struct netbuf *buf);
+void              netbuf_first    (struct netbuf *buf);
+
+
+#define netbuf_copy_partial(buf, dataptr, len, offset) \
+  pbuf_copy_partial((buf)->p, (dataptr), (len), (offset))
+#define netbuf_copy(buf,dataptr,len) netbuf_copy_partial(buf, dataptr, len, 0)
+#define netbuf_take(buf, dataptr, len) pbuf_take((buf)->p, dataptr, len)
+#define netbuf_len(buf)              ((buf)->p->tot_len)
+#define netbuf_fromaddr(buf)         (ipX_2_ip(&((buf)->addr)))
+#define netbuf_set_fromaddr(buf, fromaddr) ip_addr_set(ipX_2_ip(&((buf)->addr)), fromaddr)
+#define netbuf_fromport(buf)         ((buf)->port)
+#if LWIP_NETBUF_RECVINFO
+#define netbuf_destaddr(buf)         (ipX_2_ip(&((buf)->toaddr)))
+#define netbuf_set_destaddr(buf, destaddr) ip_addr_set(ipX_2_ip(&((buf)->toaddr)), destaddr)
+#define netbuf_destport(buf)         (((buf)->flags & NETBUF_FLAG_DESTADDR) ? (buf)->toport_chksum : 0)
+#endif /* LWIP_NETBUF_RECVINFO */
+#if LWIP_CHECKSUM_ON_COPY
+#define netbuf_set_chksum(buf, chksum) do { (buf)->flags = NETBUF_FLAG_CHKSUM; \
+                                            (buf)->toport_chksum = chksum; } while(0)
+#endif /* LWIP_CHECKSUM_ON_COPY */
+
+#if LWIP_IPV6
+#define netbuf_fromaddr_ip6(buf)         (ipX_2_ip6(&((buf)->addr)))
+#define netbuf_set_fromaddr_ip6(buf, fromaddr) ip6_addr_set(ipX_2_ip6(&((buf)->addr)), fromaddr)
+#define netbuf_destaddr_ip6(buf)         (ipX_2_ip6(&((buf)->toaddr)))
+#define netbuf_set_destaddr_ip6(buf, destaddr) ip6_addr_set(ipX_2_ip6(&((buf)->toaddr)), destaddr)
+#endif /* LWIP_IPV6 */
+
+#define netbuf_fromaddr_ipX(buf)         (&((buf)->addr))
+#define netbuf_destaddr_ipX(buf)         (&((buf)->toaddr))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_NETBUF_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/netdb.h b/external/badvpn_dns/lwip/src/include/lwip/netdb.h
new file mode 100644
index 0000000..7587e2f
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/netdb.h
@@ -0,0 +1,124 @@
+/*
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Simon Goldschmidt
+ *
+ */
+#ifndef __LWIP_NETDB_H__
+#define __LWIP_NETDB_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_DNS && LWIP_SOCKET
+
+#include <stddef.h> /* for size_t */
+
+#include "lwip/inet.h"
+#include "lwip/sockets.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* some rarely used options */
+#ifndef LWIP_DNS_API_DECLARE_H_ERRNO
+#define LWIP_DNS_API_DECLARE_H_ERRNO 1
+#endif
+
+#ifndef LWIP_DNS_API_DEFINE_ERRORS
+#define LWIP_DNS_API_DEFINE_ERRORS 1
+#endif
+
+#ifndef LWIP_DNS_API_DECLARE_STRUCTS
+#define LWIP_DNS_API_DECLARE_STRUCTS 1
+#endif
+
+#if LWIP_DNS_API_DEFINE_ERRORS
+/** Errors used by the DNS API functions, h_errno can be one of them */
+#define EAI_NONAME      200
+#define EAI_SERVICE     201
+#define EAI_FAIL        202
+#define EAI_MEMORY      203
+
+#define HOST_NOT_FOUND  210
+#define NO_DATA         211
+#define NO_RECOVERY     212
+#define TRY_AGAIN       213
+#endif /* LWIP_DNS_API_DEFINE_ERRORS */
+
+#if LWIP_DNS_API_DECLARE_STRUCTS
+struct hostent {
+    char  *h_name;      /* Official name of the host. */
+    char **h_aliases;   /* A pointer to an array of pointers to alternative host names,
+                           terminated by a null pointer. */
+    int    h_addrtype;  /* Address type. */
+    int    h_length;    /* The length, in bytes, of the address. */
+    char **h_addr_list; /* A pointer to an array of pointers to network addresses (in
+                           network byte order) for the host, terminated by a null pointer. */
+#define h_addr h_addr_list[0] /* for backward compatibility */
+};
+
+struct addrinfo {
+    int               ai_flags;      /* Input flags. */
+    int               ai_family;     /* Address family of socket. */
+    int               ai_socktype;   /* Socket type. */
+    int               ai_protocol;   /* Protocol of socket. */
+    socklen_t         ai_addrlen;    /* Length of socket address. */
+    struct sockaddr  *ai_addr;       /* Socket address of socket. */
+    char             *ai_canonname;  /* Canonical name of service location. */
+    struct addrinfo  *ai_next;       /* Pointer to next in list. */
+};
+#endif /* LWIP_DNS_API_DECLARE_STRUCTS */
+
+#if LWIP_DNS_API_DECLARE_H_ERRNO
+/* application accessable error code set by the DNS API functions */
+extern int h_errno;
+#endif /* LWIP_DNS_API_DECLARE_H_ERRNO*/
+
+struct hostent *lwip_gethostbyname(const char *name);
+int lwip_gethostbyname_r(const char *name, struct hostent *ret, char *buf,
+                size_t buflen, struct hostent **result, int *h_errnop);
+void lwip_freeaddrinfo(struct addrinfo *ai);
+int lwip_getaddrinfo(const char *nodename,
+       const char *servname,
+       const struct addrinfo *hints,
+       struct addrinfo **res);
+
+#if LWIP_COMPAT_SOCKETS
+#define gethostbyname(name) lwip_gethostbyname(name)
+#define gethostbyname_r(name, ret, buf, buflen, result, h_errnop) \
+       lwip_gethostbyname_r(name, ret, buf, buflen, result, h_errnop)
+#define freeaddrinfo(addrinfo) lwip_freeaddrinfo(addrinfo)
+#define getaddrinfo(nodname, servname, hints, res) \
+       lwip_getaddrinfo(nodname, servname, hints, res)
+#endif /* LWIP_COMPAT_SOCKETS */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_DNS && LWIP_SOCKET */
+
+#endif /* __LWIP_NETDB_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/netif.h b/external/badvpn_dns/lwip/src/include/lwip/netif.h
new file mode 100644
index 0000000..f977032
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/netif.h
@@ -0,0 +1,393 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_NETIF_H__
+#define __LWIP_NETIF_H__
+
+#include "lwip/opt.h"
+
+#define ENABLE_LOOPBACK (LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF)
+
+#include "lwip/err.h"
+
+#include "lwip/ip_addr.h"
+#include "lwip/ip6_addr.h"
+
+#include "lwip/def.h"
+#include "lwip/pbuf.h"
+#if LWIP_DHCP
+struct dhcp;
+#endif
+#if LWIP_AUTOIP
+struct autoip;
+#endif
+#if LWIP_IPV6_DHCP6
+#include "lwip/dhcp6.h"
+#endif /* LWIP_IPV6_DHCP6 */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Throughout this file, IP addresses are expected to be in
+ * the same byte order as in IP_PCB. */
+
+/** must be the maximum of all used hardware address lengths
+    across all types of interfaces in use */
+#define NETIF_MAX_HWADDR_LEN 6U
+
+/** Whether the network interface is 'up'. This is
+ * a software flag used to control whether this network
+ * interface is enabled and processes traffic.
+ * It is set by the startup code (for static IP configuration) or
+ * by dhcp/autoip when an address has been assigned.
+ */
+#define NETIF_FLAG_UP           0x01U
+/** If set, the netif has broadcast capability.
+ * Set by the netif driver in its init function. */
+#define NETIF_FLAG_BROADCAST    0x02U
+/** If set, the netif is one end of a point-to-point connection.
+ * Set by the netif driver in its init function. */
+#define NETIF_FLAG_POINTTOPOINT 0x04U
+/** If set, the interface is configured using DHCP.
+ * Set by the DHCP code when starting or stopping DHCP. */
+#define NETIF_FLAG_DHCP         0x08U
+/** If set, the interface has an active link
+ *  (set by the network interface driver).
+ * Either set by the netif driver in its init function (if the link
+ * is up at that time) or at a later point once the link comes up
+ * (if link detection is supported by the hardware). */
+#define NETIF_FLAG_LINK_UP      0x10U
+/** If set, the netif is an ethernet device using ARP.
+ * Set by the netif driver in its init function.
+ * Used to check input packet types and use of DHCP. */
+#define NETIF_FLAG_ETHARP       0x20U
+/** If set, the netif is an ethernet device. It might not use
+ * ARP or TCP/IP if it is used for PPPoE only.
+ */
+#define NETIF_FLAG_ETHERNET     0x40U
+/** If set, the netif has IGMP capability.
+ * Set by the netif driver in its init function. */
+#define NETIF_FLAG_IGMP         0x80U
+/** Whether to pretend that we are every host for TCP packets.
+ * Set by netif_set_pretend_tcp. */
+#define NETIF_FLAG_PRETEND_TCP  0x100U
+
+/** Function prototype for netif init functions. Set up flags and output/linkoutput
+ * callback functions in this function.
+ *
+ * @param netif The netif to initialize
+ */
+typedef err_t (*netif_init_fn)(struct netif *netif);
+/** Function prototype for netif->input functions. This function is saved as 'input'
+ * callback function in the netif struct. Call it when a packet has been received.
+ *
+ * @param p The received packet, copied into a pbuf
+ * @param inp The netif which received the packet
+ */
+typedef err_t (*netif_input_fn)(struct pbuf *p, struct netif *inp);
+/** Function prototype for netif->output functions. Called by lwIP when a packet
+ * shall be sent. For ethernet netif, set this to 'etharp_output' and set
+ * 'linkoutput'.
+ *
+ * @param netif The netif which shall send a packet
+ * @param p The packet to send (p->payload points to IP header)
+ * @param ipaddr The IP address to which the packet shall be sent
+ */
+typedef err_t (*netif_output_fn)(struct netif *netif, struct pbuf *p,
+       ip_addr_t *ipaddr);
+#if LWIP_IPV6
+/** Function prototype for netif->output_ip6 functions. Called by lwIP when a packet
+ * shall be sent. For ethernet netif, set this to 'nd_output' and set
+ * 'linkoutput'.
+ *
+ * @param netif The netif which shall send a packet
+ * @param p The packet to send (p->payload points to IP header)
+ * @param ipaddr The IPv6 address to which the packet shall be sent
+ */
+typedef err_t (*netif_output_ip6_fn)(struct netif *netif, struct pbuf *p,
+       ip6_addr_t *ipaddr);
+#endif /* LWIP_IPV6 */
+/** Function prototype for netif->linkoutput functions. Only used for ethernet
+ * netifs. This function is called by ARP when a packet shall be sent.
+ *
+ * @param netif The netif which shall send a packet
+ * @param p The packet to send (raw ethernet packet)
+ */
+typedef err_t (*netif_linkoutput_fn)(struct netif *netif, struct pbuf *p);
+/** Function prototype for netif status- or link-callback functions. */
+typedef void (*netif_status_callback_fn)(struct netif *netif);
+/** Function prototype for netif igmp_mac_filter functions */
+typedef err_t (*netif_igmp_mac_filter_fn)(struct netif *netif,
+       ip_addr_t *group, u8_t action);
+#if LWIP_IPV6 && LWIP_IPV6_MLD
+/** Function prototype for netif mld_mac_filter functions */
+typedef err_t (*netif_mld_mac_filter_fn)(struct netif *netif,
+       ip6_addr_t *group, u8_t action);
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+
+/** Generic data structure used for all lwIP network interfaces.
+ *  The following fields should be filled in by the initialization
+ *  function for the device driver: hwaddr_len, hwaddr[], mtu, flags */
+struct netif {
+  /** pointer to next in linked list */
+  struct netif *next;
+
+  /** IP address configuration in network byte order */
+  ip_addr_t ip_addr;
+  ip_addr_t netmask;
+  ip_addr_t gw;
+
+#if LWIP_IPV6
+  /** Array of IPv6 addresses for this netif. */
+  ip6_addr_t ip6_addr[LWIP_IPV6_NUM_ADDRESSES];
+  /** The state of each IPv6 address (Tentative, Preferred, etc).
+   * @see ip6_addr.h */
+  u8_t ip6_addr_state[LWIP_IPV6_NUM_ADDRESSES];
+#endif /* LWIP_IPV6 */
+  /** This function is called by the network device driver
+   *  to pass a packet up the TCP/IP stack. */
+  netif_input_fn input;
+  /** This function is called by the IP module when it wants
+   *  to send a packet on the interface. This function typically
+   *  first resolves the hardware address, then sends the packet. */
+  netif_output_fn output;
+  /** This function is called by the ARP module when it wants
+   *  to send a packet on the interface. This function outputs
+   *  the pbuf as-is on the link medium. */
+  netif_linkoutput_fn linkoutput;
+#if LWIP_IPV6
+  /** This function is called by the IPv6 module when it wants
+   *  to send a packet on the interface. This function typically
+   *  first resolves the hardware address, then sends the packet. */
+  netif_output_ip6_fn output_ip6;
+#endif /* LWIP_IPV6 */
+#if LWIP_NETIF_STATUS_CALLBACK
+  /** This function is called when the netif state is set to up or down
+   */
+  netif_status_callback_fn status_callback;
+#endif /* LWIP_NETIF_STATUS_CALLBACK */
+#if LWIP_NETIF_LINK_CALLBACK
+  /** This function is called when the netif link is set to up or down
+   */
+  netif_status_callback_fn link_callback;
+#endif /* LWIP_NETIF_LINK_CALLBACK */
+#if LWIP_NETIF_REMOVE_CALLBACK
+  /** This function is called when the netif has been removed */
+  netif_status_callback_fn remove_callback;
+#endif /* LWIP_NETIF_REMOVE_CALLBACK */
+  /** This field can be set by the device driver and could point
+   *  to state information for the device. */
+  void *state;
+#if LWIP_DHCP
+  /** the DHCP client state information for this netif */
+  struct dhcp *dhcp;
+#endif /* LWIP_DHCP */
+#if LWIP_AUTOIP
+  /** the AutoIP client state information for this netif */
+  struct autoip *autoip;
+#endif
+#if LWIP_IPV6_AUTOCONFIG
+  /** is this netif enabled for IPv6 autoconfiguration */
+  u8_t ip6_autoconfig_enabled;
+#endif /* LWIP_IPV6_AUTOCONFIG */
+#if LWIP_IPV6_SEND_ROUTER_SOLICIT
+  /** Number of Router Solicitation messages that remain to be sent. */
+  u8_t rs_count;
+#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
+#if LWIP_IPV6_DHCP6
+  /** the DHCPv6 client state information for this netif */
+  struct dhcp6 *dhcp6;
+#endif /* LWIP_IPV6_DHCP6 */
+#if LWIP_NETIF_HOSTNAME
+  /* the hostname for this netif, NULL is a valid value */
+  char*  hostname;
+#endif /* LWIP_NETIF_HOSTNAME */
+  /** maximum transfer unit (in bytes) */
+  u16_t mtu;
+  /** number of bytes used in hwaddr */
+  u8_t hwaddr_len;
+  /** link level hardware address of this interface */
+  u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
+  /** flags (see NETIF_FLAG_ above) */
+  u16_t flags;
+  /** descriptive abbreviation */
+  char name[2];
+  /** number of this interface */
+  u8_t num;
+#if LWIP_SNMP
+  /** link type (from "snmp_ifType" enum from snmp.h) */
+  u8_t link_type;
+  /** (estimate) link speed */
+  u32_t link_speed;
+  /** timestamp at last change made (up/down) */
+  u32_t ts;
+  /** counters */
+  u32_t ifinoctets;
+  u32_t ifinucastpkts;
+  u32_t ifinnucastpkts;
+  u32_t ifindiscards;
+  u32_t ifoutoctets;
+  u32_t ifoutucastpkts;
+  u32_t ifoutnucastpkts;
+  u32_t ifoutdiscards;
+#endif /* LWIP_SNMP */
+#if LWIP_IGMP
+  /** This function could be called to add or delete an entry in the multicast
+      filter table of the ethernet MAC.*/
+  netif_igmp_mac_filter_fn igmp_mac_filter;
+#endif /* LWIP_IGMP */
+#if LWIP_IPV6 && LWIP_IPV6_MLD
+  /** This function could be called to add or delete an entry in the IPv6 multicast
+      filter table of the ethernet MAC. */
+  netif_mld_mac_filter_fn mld_mac_filter;
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+#if LWIP_NETIF_HWADDRHINT
+  u8_t *addr_hint;
+#endif /* LWIP_NETIF_HWADDRHINT */
+#if ENABLE_LOOPBACK
+  /* List of packets to be queued for ourselves. */
+  struct pbuf *loop_first;
+  struct pbuf *loop_last;
+#if LWIP_LOOPBACK_MAX_PBUFS
+  u16_t loop_cnt_current;
+#endif /* LWIP_LOOPBACK_MAX_PBUFS */
+#endif /* ENABLE_LOOPBACK */
+};
+
+#if LWIP_SNMP
+#define NETIF_INIT_SNMP(netif, type, speed) \
+  /* use "snmp_ifType" enum from snmp.h for "type", snmp_ifType_ethernet_csmacd by example */ \
+  (netif)->link_type = (type);    \
+  /* your link speed here (units: bits per second) */  \
+  (netif)->link_speed = (speed);  \
+  (netif)->ts = 0;              \
+  (netif)->ifinoctets = 0;      \
+  (netif)->ifinucastpkts = 0;   \
+  (netif)->ifinnucastpkts = 0;  \
+  (netif)->ifindiscards = 0;    \
+  (netif)->ifoutoctets = 0;     \
+  (netif)->ifoutucastpkts = 0;  \
+  (netif)->ifoutnucastpkts = 0; \
+  (netif)->ifoutdiscards = 0
+#else /* LWIP_SNMP */
+#define NETIF_INIT_SNMP(netif, type, speed)
+#endif /* LWIP_SNMP */
+
+
+/** The list of network interfaces. */
+extern struct netif *netif_list;
+/** The default network interface. */
+extern struct netif *netif_default;
+
+void netif_init(void);
+
+struct netif *netif_add(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask,
+      ip_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input);
+
+void
+netif_set_addr(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask,
+      ip_addr_t *gw);
+void netif_remove(struct netif * netif);
+
+/* Returns a network interface given its name. The name is of the form
+   "et0", where the first two letters are the "name" field in the
+   netif structure, and the digit is in the num field in the same
+   structure. */
+struct netif *netif_find(char *name);
+
+int netif_is_named (struct netif *netif, const char name[3]);
+
+void netif_set_default(struct netif *netif);
+
+void netif_set_ipaddr(struct netif *netif, ip_addr_t *ipaddr);
+void netif_set_netmask(struct netif *netif, ip_addr_t *netmask);
+void netif_set_gw(struct netif *netif, ip_addr_t *gw);
+void netif_set_pretend_tcp(struct netif *netif, u8_t pretend);
+
+void netif_set_up(struct netif *netif);
+void netif_set_down(struct netif *netif);
+/** Ask if an interface is up */
+#define netif_is_up(netif) (((netif)->flags & NETIF_FLAG_UP) ? (u8_t)1 : (u8_t)0)
+
+#if LWIP_NETIF_STATUS_CALLBACK
+void netif_set_status_callback(struct netif *netif, netif_status_callback_fn status_callback);
+#endif /* LWIP_NETIF_STATUS_CALLBACK */
+#if LWIP_NETIF_REMOVE_CALLBACK
+void netif_set_remove_callback(struct netif *netif, netif_status_callback_fn remove_callback);
+#endif /* LWIP_NETIF_REMOVE_CALLBACK */
+
+void netif_set_link_up(struct netif *netif);
+void netif_set_link_down(struct netif *netif);
+/** Ask if a link is up */ 
+#define netif_is_link_up(netif) (((netif)->flags & NETIF_FLAG_LINK_UP) ? (u8_t)1 : (u8_t)0)
+
+#if LWIP_NETIF_LINK_CALLBACK
+void netif_set_link_callback(struct netif *netif, netif_status_callback_fn link_callback);
+#endif /* LWIP_NETIF_LINK_CALLBACK */
+
+#if LWIP_NETIF_HOSTNAME
+#define netif_set_hostname(netif, name) do { if((netif) != NULL) { (netif)->hostname = name; }}while(0)
+#define netif_get_hostname(netif) (((netif) != NULL) ? ((netif)->hostname) : NULL)
+#endif /* LWIP_NETIF_HOSTNAME */
+
+#if LWIP_IGMP
+#define netif_set_igmp_mac_filter(netif, function) do { if((netif) != NULL) { (netif)->igmp_mac_filter = function; }}while(0)
+#define netif_get_igmp_mac_filter(netif) (((netif) != NULL) ? ((netif)->igmp_mac_filter) : NULL)
+#endif /* LWIP_IGMP */
+
+#if ENABLE_LOOPBACK
+err_t netif_loop_output(struct netif *netif, struct pbuf *p, ip_addr_t *dest_ip);
+void netif_poll(struct netif *netif);
+#if !LWIP_NETIF_LOOPBACK_MULTITHREADING
+void netif_poll_all(void);
+#endif /* !LWIP_NETIF_LOOPBACK_MULTITHREADING */
+#endif /* ENABLE_LOOPBACK */
+
+#if LWIP_IPV6
+#define netif_ip6_addr(netif, i)  (&((netif)->ip6_addr[(i)]))
+#define netif_ip6_addr_state(netif, i)  ((netif)->ip6_addr_state[(i)])
+#define netif_ip6_addr_set_state(netif, i, state)  ((netif)->ip6_addr_state[(i)] = (state))
+s8_t netif_matches_ip6_addr(struct netif * netif, ip6_addr_t * ip6addr);
+void netif_create_ip6_linklocal_address(struct netif * netif, u8_t from_mac_48bit);
+#endif /* LWIP_IPV6 */
+
+#if LWIP_NETIF_HWADDRHINT
+#define NETIF_SET_HWADDRHINT(netif, hint) ((netif)->addr_hint = (hint))
+#else /* LWIP_NETIF_HWADDRHINT */
+#define NETIF_SET_HWADDRHINT(netif, hint)
+#endif /* LWIP_NETIF_HWADDRHINT */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_NETIF_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/netifapi.h b/external/badvpn_dns/lwip/src/include/lwip/netifapi.h
new file mode 100644
index 0000000..33318ef
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/netifapi.h
@@ -0,0 +1,108 @@
+/*
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ */
+ 
+#ifndef __LWIP_NETIFAPI_H__
+#define __LWIP_NETIFAPI_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_NETIF_API /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/sys.h"
+#include "lwip/netif.h"
+#include "lwip/dhcp.h"
+#include "lwip/autoip.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*netifapi_void_fn)(struct netif *netif);
+typedef err_t (*netifapi_errt_fn)(struct netif *netif);
+
+struct netifapi_msg_msg {
+#if !LWIP_TCPIP_CORE_LOCKING
+  sys_sem_t sem;
+#endif /* !LWIP_TCPIP_CORE_LOCKING */
+  err_t err;
+  struct netif *netif;
+  union {
+    struct {
+      ip_addr_t *ipaddr;
+      ip_addr_t *netmask;
+      ip_addr_t *gw;
+      void *state;
+      netif_init_fn init;
+      netif_input_fn input;
+    } add;
+    struct {
+      netifapi_void_fn voidfunc;
+      netifapi_errt_fn errtfunc;
+    } common;
+  } msg;
+};
+
+struct netifapi_msg {
+  void (* function)(struct netifapi_msg_msg *msg);
+  struct netifapi_msg_msg msg;
+};
+
+
+/* API for application */
+err_t netifapi_netif_add       ( struct netif *netif,
+                                 ip_addr_t *ipaddr,
+                                 ip_addr_t *netmask,
+                                 ip_addr_t *gw,
+                                 void *state,
+                                 netif_init_fn init,
+                                 netif_input_fn input);
+
+err_t netifapi_netif_set_addr  ( struct netif *netif,
+                                 ip_addr_t *ipaddr,
+                                 ip_addr_t *netmask,
+                                 ip_addr_t *gw );
+
+err_t netifapi_netif_common    ( struct netif *netif,
+                                 netifapi_void_fn voidfunc,
+                                 netifapi_errt_fn errtfunc);
+
+#define netifapi_netif_remove(n)      netifapi_netif_common(n, netif_remove, NULL)
+#define netifapi_netif_set_up(n)      netifapi_netif_common(n, netif_set_up, NULL)
+#define netifapi_netif_set_down(n)    netifapi_netif_common(n, netif_set_down, NULL)
+#define netifapi_netif_set_default(n) netifapi_netif_common(n, netif_set_default, NULL)
+#define netifapi_dhcp_start(n)        netifapi_netif_common(n, NULL, dhcp_start)
+#define netifapi_dhcp_stop(n)         netifapi_netif_common(n, dhcp_stop, NULL)
+#define netifapi_autoip_start(n)      netifapi_netif_common(n, NULL, autoip_start)
+#define netifapi_autoip_stop(n)       netifapi_netif_common(n, NULL, autoip_stop)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_NETIF_API */
+
+#endif /* __LWIP_NETIFAPI_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/opt.h b/external/badvpn_dns/lwip/src/include/lwip/opt.h
new file mode 100644
index 0000000..e51f8e5
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/opt.h
@@ -0,0 +1,2417 @@
+/**
+ * @file
+ *
+ * lwIP Options Configuration
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_OPT_H__
+#define __LWIP_OPT_H__
+
+/*
+ * Include user defined options first. Anything not defined in these files
+ * will be set to standard values. Override anything you dont like!
+ */
+#include "lwipopts.h"
+#include "lwip/debug.h"
+
+/*
+   -----------------------------------------------
+   ---------- Platform specific locking ----------
+   -----------------------------------------------
+*/
+
+/**
+ * SYS_LIGHTWEIGHT_PROT==1: if you want inter-task protection for certain
+ * critical regions during buffer allocation, deallocation and memory
+ * allocation and deallocation.
+ */
+#ifndef SYS_LIGHTWEIGHT_PROT
+#define SYS_LIGHTWEIGHT_PROT            0
+#endif
+
+/** 
+ * NO_SYS==1: Provides VERY minimal functionality. Otherwise,
+ * use lwIP facilities.
+ */
+#ifndef NO_SYS
+#define NO_SYS                          0
+#endif
+
+/**
+ * NO_SYS_NO_TIMERS==1: Drop support for sys_timeout when NO_SYS==1
+ * Mainly for compatibility to old versions.
+ */
+#ifndef NO_SYS_NO_TIMERS
+#define NO_SYS_NO_TIMERS                0
+#endif
+
+/**
+ * MEMCPY: override this if you have a faster implementation at hand than the
+ * one included in your C library
+ */
+#ifndef MEMCPY
+#define MEMCPY(dst,src,len)             memcpy(dst,src,len)
+#endif
+
+/**
+ * SMEMCPY: override this with care! Some compilers (e.g. gcc) can inline a
+ * call to memcpy() if the length is known at compile time and is small.
+ */
+#ifndef SMEMCPY
+#define SMEMCPY(dst,src,len)            memcpy(dst,src,len)
+#endif
+
+/*
+   ------------------------------------
+   ---------- Memory options ----------
+   ------------------------------------
+*/
+/**
+ * MEM_LIBC_MALLOC==1: Use malloc/free/realloc provided by your C-library
+ * instead of the lwip internal allocator. Can save code size if you
+ * already use it.
+ */
+#ifndef MEM_LIBC_MALLOC
+#define MEM_LIBC_MALLOC                 0
+#endif
+
+/**
+* MEMP_MEM_MALLOC==1: Use mem_malloc/mem_free instead of the lwip pool allocator.
+* Especially useful with MEM_LIBC_MALLOC but handle with care regarding execution
+* speed and usage from interrupts!
+*/
+#ifndef MEMP_MEM_MALLOC
+#define MEMP_MEM_MALLOC                 0
+#endif
+
+/**
+ * MEM_ALIGNMENT: should be set to the alignment of the CPU
+ *    4 byte alignment -> #define MEM_ALIGNMENT 4
+ *    2 byte alignment -> #define MEM_ALIGNMENT 2
+ */
+#ifndef MEM_ALIGNMENT
+#define MEM_ALIGNMENT                   1
+#endif
+
+/**
+ * MEM_SIZE: the size of the heap memory. If the application will send
+ * a lot of data that needs to be copied, this should be set high.
+ */
+#ifndef MEM_SIZE
+#define MEM_SIZE                        1600
+#endif
+
+/**
+ * MEMP_SEPARATE_POOLS: if defined to 1, each pool is placed in its own array.
+ * This can be used to individually change the location of each pool.
+ * Default is one big array for all pools
+ */
+#ifndef MEMP_SEPARATE_POOLS
+#define MEMP_SEPARATE_POOLS             0
+#endif
+
+/**
+ * MEMP_OVERFLOW_CHECK: memp overflow protection reserves a configurable
+ * amount of bytes before and after each memp element in every pool and fills
+ * it with a prominent default value.
+ *    MEMP_OVERFLOW_CHECK == 0 no checking
+ *    MEMP_OVERFLOW_CHECK == 1 checks each element when it is freed
+ *    MEMP_OVERFLOW_CHECK >= 2 checks each element in every pool every time
+ *      memp_malloc() or memp_free() is called (useful but slow!)
+ */
+#ifndef MEMP_OVERFLOW_CHECK
+#define MEMP_OVERFLOW_CHECK             0
+#endif
+
+/**
+ * MEMP_SANITY_CHECK==1: run a sanity check after each memp_free() to make
+ * sure that there are no cycles in the linked lists.
+ */
+#ifndef MEMP_SANITY_CHECK
+#define MEMP_SANITY_CHECK               0
+#endif
+
+/**
+ * MEM_USE_POOLS==1: Use an alternative to malloc() by allocating from a set
+ * of memory pools of various sizes. When mem_malloc is called, an element of
+ * the smallest pool that can provide the length needed is returned.
+ * To use this, MEMP_USE_CUSTOM_POOLS also has to be enabled.
+ */
+#ifndef MEM_USE_POOLS
+#define MEM_USE_POOLS                   0
+#endif
+
+/**
+ * MEM_USE_POOLS_TRY_BIGGER_POOL==1: if one malloc-pool is empty, try the next
+ * bigger pool - WARNING: THIS MIGHT WASTE MEMORY but it can make a system more
+ * reliable. */
+#ifndef MEM_USE_POOLS_TRY_BIGGER_POOL
+#define MEM_USE_POOLS_TRY_BIGGER_POOL   0
+#endif
+
+/**
+ * MEMP_USE_CUSTOM_POOLS==1: whether to include a user file lwippools.h
+ * that defines additional pools beyond the "standard" ones required
+ * by lwIP. If you set this to 1, you must have lwippools.h in your 
+ * inlude path somewhere. 
+ */
+#ifndef MEMP_USE_CUSTOM_POOLS
+#define MEMP_USE_CUSTOM_POOLS           0
+#endif
+
+/**
+ * Set this to 1 if you want to free PBUF_RAM pbufs (or call mem_free()) from
+ * interrupt context (or another context that doesn't allow waiting for a
+ * semaphore).
+ * If set to 1, mem_malloc will be protected by a semaphore and SYS_ARCH_PROTECT,
+ * while mem_free will only use SYS_ARCH_PROTECT. mem_malloc SYS_ARCH_UNPROTECTs
+ * with each loop so that mem_free can run.
+ *
+ * ATTENTION: As you can see from the above description, this leads to dis-/
+ * enabling interrupts often, which can be slow! Also, on low memory, mem_malloc
+ * can need longer.
+ *
+ * If you don't want that, at least for NO_SYS=0, you can still use the following
+ * functions to enqueue a deallocation call which then runs in the tcpip_thread
+ * context:
+ * - pbuf_free_callback(p);
+ * - mem_free_callback(m);
+ */
+#ifndef LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
+#define LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT 0
+#endif
+
+/*
+   ------------------------------------------------
+   ---------- Internal Memory Pool Sizes ----------
+   ------------------------------------------------
+*/
+/**
+ * MEMP_NUM_PBUF: the number of memp struct pbufs (used for PBUF_ROM and PBUF_REF).
+ * If the application sends a lot of data out of ROM (or other static memory),
+ * this should be set high.
+ */
+#ifndef MEMP_NUM_PBUF
+#define MEMP_NUM_PBUF                   16
+#endif
+
+/**
+ * MEMP_NUM_RAW_PCB: Number of raw connection PCBs
+ * (requires the LWIP_RAW option)
+ */
+#ifndef MEMP_NUM_RAW_PCB
+#define MEMP_NUM_RAW_PCB                4
+#endif
+
+/**
+ * MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
+ * per active UDP "connection".
+ * (requires the LWIP_UDP option)
+ */
+#ifndef MEMP_NUM_UDP_PCB
+#define MEMP_NUM_UDP_PCB                4
+#endif
+
+/**
+ * MEMP_NUM_TCP_PCB: the number of simulatenously active TCP connections.
+ * (requires the LWIP_TCP option)
+ */
+#ifndef MEMP_NUM_TCP_PCB
+#define MEMP_NUM_TCP_PCB                5
+#endif
+
+/**
+ * MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP connections.
+ * (requires the LWIP_TCP option)
+ */
+#ifndef MEMP_NUM_TCP_PCB_LISTEN
+#define MEMP_NUM_TCP_PCB_LISTEN         8
+#endif
+
+/**
+ * MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP segments.
+ * (requires the LWIP_TCP option)
+ */
+#ifndef MEMP_NUM_TCP_SEG
+#define MEMP_NUM_TCP_SEG                16
+#endif
+
+/**
+ * MEMP_NUM_REASSDATA: the number of IP packets simultaneously queued for
+ * reassembly (whole packets, not fragments!)
+ */
+#ifndef MEMP_NUM_REASSDATA
+#define MEMP_NUM_REASSDATA              5
+#endif
+
+/**
+ * MEMP_NUM_FRAG_PBUF: the number of IP fragments simultaneously sent
+ * (fragments, not whole packets!).
+ * This is only used with IP_FRAG_USES_STATIC_BUF==0 and
+ * LWIP_NETIF_TX_SINGLE_PBUF==0 and only has to be > 1 with DMA-enabled MACs
+ * where the packet is not yet sent when netif->output returns.
+ */
+#ifndef MEMP_NUM_FRAG_PBUF
+#define MEMP_NUM_FRAG_PBUF              15
+#endif
+
+/**
+ * MEMP_NUM_ARP_QUEUE: the number of simulateously queued outgoing
+ * packets (pbufs) that are waiting for an ARP request (to resolve
+ * their destination address) to finish.
+ * (requires the ARP_QUEUEING option)
+ */
+#ifndef MEMP_NUM_ARP_QUEUE
+#define MEMP_NUM_ARP_QUEUE              30
+#endif
+
+/**
+ * MEMP_NUM_IGMP_GROUP: The number of multicast groups whose network interfaces
+ * can be members et the same time (one per netif - allsystems group -, plus one
+ * per netif membership).
+ * (requires the LWIP_IGMP option)
+ */
+#ifndef MEMP_NUM_IGMP_GROUP
+#define MEMP_NUM_IGMP_GROUP             8
+#endif
+
+/**
+ * MEMP_NUM_SYS_TIMEOUT: the number of simulateously active timeouts.
+ * (requires NO_SYS==0)
+ * The default number of timeouts is calculated here for all enabled modules.
+ * The formula expects settings to be either '0' or '1'.
+ */
+#ifndef MEMP_NUM_SYS_TIMEOUT
+#define MEMP_NUM_SYS_TIMEOUT            (LWIP_TCP + IP_REASSEMBLY + LWIP_ARP + (2*LWIP_DHCP) + LWIP_AUTOIP + LWIP_IGMP + LWIP_DNS + PPP_SUPPORT + (LWIP_IPV6 ? (1 + LWIP_IPV6_REASS + LWIP_IPV6_MLD) : 0))
+#endif
+
+/**
+ * MEMP_NUM_NETBUF: the number of struct netbufs.
+ * (only needed if you use the sequential API, like api_lib.c)
+ */
+#ifndef MEMP_NUM_NETBUF
+#define MEMP_NUM_NETBUF                 2
+#endif
+
+/**
+ * MEMP_NUM_NETCONN: the number of struct netconns.
+ * (only needed if you use the sequential API, like api_lib.c)
+ */
+#ifndef MEMP_NUM_NETCONN
+#define MEMP_NUM_NETCONN                4
+#endif
+
+/**
+ * MEMP_NUM_TCPIP_MSG_API: the number of struct tcpip_msg, which are used
+ * for callback/timeout API communication. 
+ * (only needed if you use tcpip.c)
+ */
+#ifndef MEMP_NUM_TCPIP_MSG_API
+#define MEMP_NUM_TCPIP_MSG_API          8
+#endif
+
+/**
+ * MEMP_NUM_TCPIP_MSG_INPKT: the number of struct tcpip_msg, which are used
+ * for incoming packets. 
+ * (only needed if you use tcpip.c)
+ */
+#ifndef MEMP_NUM_TCPIP_MSG_INPKT
+#define MEMP_NUM_TCPIP_MSG_INPKT        8
+#endif
+
+/**
+ * MEMP_NUM_SNMP_NODE: the number of leafs in the SNMP tree.
+ */
+#ifndef MEMP_NUM_SNMP_NODE
+#define MEMP_NUM_SNMP_NODE              50
+#endif
+
+/**
+ * MEMP_NUM_SNMP_ROOTNODE: the number of branches in the SNMP tree.
+ * Every branch has one leaf (MEMP_NUM_SNMP_NODE) at least!
+ */
+#ifndef MEMP_NUM_SNMP_ROOTNODE
+#define MEMP_NUM_SNMP_ROOTNODE          30
+#endif
+
+/**
+ * MEMP_NUM_SNMP_VARBIND: the number of concurrent requests (does not have to
+ * be changed normally) - 2 of these are used per request (1 for input,
+ * 1 for output)
+ */
+#ifndef MEMP_NUM_SNMP_VARBIND
+#define MEMP_NUM_SNMP_VARBIND           2
+#endif
+
+/**
+ * MEMP_NUM_SNMP_VALUE: the number of OID or values concurrently used
+ * (does not have to be changed normally) - 3 of these are used per request
+ * (1 for the value read and 2 for OIDs - input and output)
+ */
+#ifndef MEMP_NUM_SNMP_VALUE
+#define MEMP_NUM_SNMP_VALUE             3
+#endif
+
+/**
+ * MEMP_NUM_NETDB: the number of concurrently running lwip_addrinfo() calls
+ * (before freeing the corresponding memory using lwip_freeaddrinfo()).
+ */
+#ifndef MEMP_NUM_NETDB
+#define MEMP_NUM_NETDB                  1
+#endif
+
+/**
+ * MEMP_NUM_LOCALHOSTLIST: the number of host entries in the local host list
+ * if DNS_LOCAL_HOSTLIST_IS_DYNAMIC==1.
+ */
+#ifndef MEMP_NUM_LOCALHOSTLIST
+#define MEMP_NUM_LOCALHOSTLIST          1
+#endif
+
+/**
+ * MEMP_NUM_PPPOE_INTERFACES: the number of concurrently active PPPoE
+ * interfaces (only used with PPPOE_SUPPORT==1)
+ */
+#ifndef MEMP_NUM_PPPOE_INTERFACES
+#define MEMP_NUM_PPPOE_INTERFACES       1
+#endif
+
+/**
+ * PBUF_POOL_SIZE: the number of buffers in the pbuf pool. 
+ */
+#ifndef PBUF_POOL_SIZE
+#define PBUF_POOL_SIZE                  16
+#endif
+
+/*
+   ---------------------------------
+   ---------- ARP options ----------
+   ---------------------------------
+*/
+/**
+ * LWIP_ARP==1: Enable ARP functionality.
+ */
+#ifndef LWIP_ARP
+#define LWIP_ARP                        1
+#endif
+
+/**
+ * ARP_TABLE_SIZE: Number of active MAC-IP address pairs cached.
+ */
+#ifndef ARP_TABLE_SIZE
+#define ARP_TABLE_SIZE                  10
+#endif
+
+/**
+ * ARP_QUEUEING==1: Multiple outgoing packets are queued during hardware address
+ * resolution. By default, only the most recent packet is queued per IP address.
+ * This is sufficient for most protocols and mainly reduces TCP connection
+ * startup time. Set this to 1 if you know your application sends more than one
+ * packet in a row to an IP address that is not in the ARP cache.
+ */
+#ifndef ARP_QUEUEING
+#define ARP_QUEUEING                    0
+#endif
+
+/**
+ * ETHARP_TRUST_IP_MAC==1: Incoming IP packets cause the ARP table to be
+ * updated with the source MAC and IP addresses supplied in the packet.
+ * You may want to disable this if you do not trust LAN peers to have the
+ * correct addresses, or as a limited approach to attempt to handle
+ * spoofing. If disabled, lwIP will need to make a new ARP request if
+ * the peer is not already in the ARP table, adding a little latency.
+ * The peer *is* in the ARP table if it requested our address before.
+ * Also notice that this slows down input processing of every IP packet!
+ */
+#ifndef ETHARP_TRUST_IP_MAC
+#define ETHARP_TRUST_IP_MAC             0
+#endif
+
+/**
+ * ETHARP_SUPPORT_VLAN==1: support receiving ethernet packets with VLAN header.
+ * Additionally, you can define ETHARP_VLAN_CHECK to an u16_t VLAN ID to check.
+ * If ETHARP_VLAN_CHECK is defined, only VLAN-traffic for this VLAN is accepted.
+ * If ETHARP_VLAN_CHECK is not defined, all traffic is accepted.
+ * Alternatively, define a function/define ETHARP_VLAN_CHECK_FN(eth_hdr, vlan)
+ * that returns 1 to accept a packet or 0 to drop a packet.
+ */
+#ifndef ETHARP_SUPPORT_VLAN
+#define ETHARP_SUPPORT_VLAN             0
+#endif
+
+/** LWIP_ETHERNET==1: enable ethernet support for PPPoE even though ARP
+ * might be disabled
+ */
+#ifndef LWIP_ETHERNET
+#define LWIP_ETHERNET                   (LWIP_ARP || PPPOE_SUPPORT)
+#endif
+
+/** ETH_PAD_SIZE: number of bytes added before the ethernet header to ensure
+ * alignment of payload after that header. Since the header is 14 bytes long,
+ * without this padding e.g. addresses in the IP header will not be aligned
+ * on a 32-bit boundary, so setting this to 2 can speed up 32-bit-platforms.
+ */
+#ifndef ETH_PAD_SIZE
+#define ETH_PAD_SIZE                    0
+#endif
+
+/** ETHARP_SUPPORT_STATIC_ENTRIES==1: enable code to support static ARP table
+ * entries (using etharp_add_static_entry/etharp_remove_static_entry).
+ */
+#ifndef ETHARP_SUPPORT_STATIC_ENTRIES
+#define ETHARP_SUPPORT_STATIC_ENTRIES   0
+#endif
+
+
+/*
+   --------------------------------
+   ---------- IP options ----------
+   --------------------------------
+*/
+/**
+ * IP_FORWARD==1: Enables the ability to forward IP packets across network
+ * interfaces. If you are going to run lwIP on a device with only one network
+ * interface, define this to 0.
+ */
+#ifndef IP_FORWARD
+#define IP_FORWARD                      0
+#endif
+
+/**
+ * IP_OPTIONS_ALLOWED: Defines the behavior for IP options.
+ *      IP_OPTIONS_ALLOWED==0: All packets with IP options are dropped.
+ *      IP_OPTIONS_ALLOWED==1: IP options are allowed (but not parsed).
+ */
+#ifndef IP_OPTIONS_ALLOWED
+#define IP_OPTIONS_ALLOWED              1
+#endif
+
+/**
+ * IP_REASSEMBLY==1: Reassemble incoming fragmented IP packets. Note that
+ * this option does not affect outgoing packet sizes, which can be controlled
+ * via IP_FRAG.
+ */
+#ifndef IP_REASSEMBLY
+#define IP_REASSEMBLY                   1
+#endif
+
+/**
+ * IP_FRAG==1: Fragment outgoing IP packets if their size exceeds MTU. Note
+ * that this option does not affect incoming packet sizes, which can be
+ * controlled via IP_REASSEMBLY.
+ */
+#ifndef IP_FRAG
+#define IP_FRAG                         1
+#endif
+
+/**
+ * IP_REASS_MAXAGE: Maximum time (in multiples of IP_TMR_INTERVAL - so seconds, normally)
+ * a fragmented IP packet waits for all fragments to arrive. If not all fragments arrived
+ * in this time, the whole packet is discarded.
+ */
+#ifndef IP_REASS_MAXAGE
+#define IP_REASS_MAXAGE                 3
+#endif
+
+/**
+ * IP_REASS_MAX_PBUFS: Total maximum amount of pbufs waiting to be reassembled.
+ * Since the received pbufs are enqueued, be sure to configure
+ * PBUF_POOL_SIZE > IP_REASS_MAX_PBUFS so that the stack is still able to receive
+ * packets even if the maximum amount of fragments is enqueued for reassembly!
+ */
+#ifndef IP_REASS_MAX_PBUFS
+#define IP_REASS_MAX_PBUFS              10
+#endif
+
+/**
+ * IP_FRAG_USES_STATIC_BUF==1: Use a static MTU-sized buffer for IP
+ * fragmentation. Otherwise pbufs are allocated and reference the original
+ * packet data to be fragmented (or with LWIP_NETIF_TX_SINGLE_PBUF==1,
+ * new PBUF_RAM pbufs are used for fragments).
+ * ATTENTION: IP_FRAG_USES_STATIC_BUF==1 may not be used for DMA-enabled MACs!
+ */
+#ifndef IP_FRAG_USES_STATIC_BUF
+#define IP_FRAG_USES_STATIC_BUF         0
+#endif
+
+/**
+ * IP_FRAG_MAX_MTU: Assumed max MTU on any interface for IP frag buffer
+ * (requires IP_FRAG_USES_STATIC_BUF==1)
+ */
+#if IP_FRAG_USES_STATIC_BUF && !defined(IP_FRAG_MAX_MTU)
+#define IP_FRAG_MAX_MTU                 1500
+#endif
+
+/**
+ * IP_DEFAULT_TTL: Default value for Time-To-Live used by transport layers.
+ */
+#ifndef IP_DEFAULT_TTL
+#define IP_DEFAULT_TTL                  255
+#endif
+
+/**
+ * IP_SOF_BROADCAST=1: Use the SOF_BROADCAST field to enable broadcast
+ * filter per pcb on udp and raw send operations. To enable broadcast filter
+ * on recv operations, you also have to set IP_SOF_BROADCAST_RECV=1.
+ */
+#ifndef IP_SOF_BROADCAST
+#define IP_SOF_BROADCAST                0
+#endif
+
+/**
+ * IP_SOF_BROADCAST_RECV (requires IP_SOF_BROADCAST=1) enable the broadcast
+ * filter on recv operations.
+ */
+#ifndef IP_SOF_BROADCAST_RECV
+#define IP_SOF_BROADCAST_RECV           0
+#endif
+
+/**
+ * IP_FORWARD_ALLOW_TX_ON_RX_NETIF==1: allow ip_forward() to send packets back
+ * out on the netif where it was received. This should only be used for
+ * wireless networks.
+ * ATTENTION: When this is 1, make sure your netif driver correctly marks incoming
+ * link-layer-broadcast/multicast packets as such using the corresponding pbuf flags!
+ */
+#ifndef IP_FORWARD_ALLOW_TX_ON_RX_NETIF
+#define IP_FORWARD_ALLOW_TX_ON_RX_NETIF 0
+#endif
+
+/**
+ * LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS==1: randomize the local port for the first
+ * local TCP/UDP pcb (default==0). This can prevent creating predictable port
+ * numbers after booting a device.
+ */
+#ifndef LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS
+#define LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS 0
+#endif
+
+/*
+   ----------------------------------
+   ---------- ICMP options ----------
+   ----------------------------------
+*/
+/**
+ * LWIP_ICMP==1: Enable ICMP module inside the IP stack.
+ * Be careful, disable that make your product non-compliant to RFC1122
+ */
+#ifndef LWIP_ICMP
+#define LWIP_ICMP                       1
+#endif
+
+/**
+ * ICMP_TTL: Default value for Time-To-Live used by ICMP packets.
+ */
+#ifndef ICMP_TTL
+#define ICMP_TTL                       (IP_DEFAULT_TTL)
+#endif
+
+/**
+ * LWIP_BROADCAST_PING==1: respond to broadcast pings (default is unicast only)
+ */
+#ifndef LWIP_BROADCAST_PING
+#define LWIP_BROADCAST_PING             0
+#endif
+
+/**
+ * LWIP_MULTICAST_PING==1: respond to multicast pings (default is unicast only)
+ */
+#ifndef LWIP_MULTICAST_PING
+#define LWIP_MULTICAST_PING             0
+#endif
+
+/*
+   ---------------------------------
+   ---------- RAW options ----------
+   ---------------------------------
+*/
+/**
+ * LWIP_RAW==1: Enable application layer to hook into the IP layer itself.
+ */
+#ifndef LWIP_RAW
+#define LWIP_RAW                        1
+#endif
+
+/**
+ * LWIP_RAW==1: Enable application layer to hook into the IP layer itself.
+ */
+#ifndef RAW_TTL
+#define RAW_TTL                        (IP_DEFAULT_TTL)
+#endif
+
+/*
+   ----------------------------------
+   ---------- DHCP options ----------
+   ----------------------------------
+*/
+/**
+ * LWIP_DHCP==1: Enable DHCP module.
+ */
+#ifndef LWIP_DHCP
+#define LWIP_DHCP                       0
+#endif
+
+/**
+ * DHCP_DOES_ARP_CHECK==1: Do an ARP check on the offered address.
+ */
+#ifndef DHCP_DOES_ARP_CHECK
+#define DHCP_DOES_ARP_CHECK             ((LWIP_DHCP) && (LWIP_ARP))
+#endif
+
+/*
+   ------------------------------------
+   ---------- AUTOIP options ----------
+   ------------------------------------
+*/
+/**
+ * LWIP_AUTOIP==1: Enable AUTOIP module.
+ */
+#ifndef LWIP_AUTOIP
+#define LWIP_AUTOIP                     0
+#endif
+
+/**
+ * LWIP_DHCP_AUTOIP_COOP==1: Allow DHCP and AUTOIP to be both enabled on
+ * the same interface at the same time.
+ */
+#ifndef LWIP_DHCP_AUTOIP_COOP
+#define LWIP_DHCP_AUTOIP_COOP           0
+#endif
+
+/**
+ * LWIP_DHCP_AUTOIP_COOP_TRIES: Set to the number of DHCP DISCOVER probes
+ * that should be sent before falling back on AUTOIP. This can be set
+ * as low as 1 to get an AutoIP address very quickly, but you should
+ * be prepared to handle a changing IP address when DHCP overrides
+ * AutoIP.
+ */
+#ifndef LWIP_DHCP_AUTOIP_COOP_TRIES
+#define LWIP_DHCP_AUTOIP_COOP_TRIES     9
+#endif
+
+/*
+   ----------------------------------
+   ---------- SNMP options ----------
+   ----------------------------------
+*/
+/**
+ * LWIP_SNMP==1: Turn on SNMP module. UDP must be available for SNMP
+ * transport.
+ */
+#ifndef LWIP_SNMP
+#define LWIP_SNMP                       0
+#endif
+
+/**
+ * SNMP_CONCURRENT_REQUESTS: Number of concurrent requests the module will
+ * allow. At least one request buffer is required.
+ * Does not have to be changed unless external MIBs answer request asynchronously
+ */
+#ifndef SNMP_CONCURRENT_REQUESTS
+#define SNMP_CONCURRENT_REQUESTS        1
+#endif
+
+/**
+ * SNMP_TRAP_DESTINATIONS: Number of trap destinations. At least one trap
+ * destination is required
+ */
+#ifndef SNMP_TRAP_DESTINATIONS
+#define SNMP_TRAP_DESTINATIONS          1
+#endif
+
+/**
+ * SNMP_PRIVATE_MIB: 
+ * When using a private MIB, you have to create a file 'private_mib.h' that contains
+ * a 'struct mib_array_node mib_private' which contains your MIB.
+ */
+#ifndef SNMP_PRIVATE_MIB
+#define SNMP_PRIVATE_MIB                0
+#endif
+
+/**
+ * Only allow SNMP write actions that are 'safe' (e.g. disabeling netifs is not
+ * a safe action and disabled when SNMP_SAFE_REQUESTS = 1).
+ * Unsafe requests are disabled by default!
+ */
+#ifndef SNMP_SAFE_REQUESTS
+#define SNMP_SAFE_REQUESTS              1
+#endif
+
+/**
+ * The maximum length of strings used. This affects the size of
+ * MEMP_SNMP_VALUE elements.
+ */
+#ifndef SNMP_MAX_OCTET_STRING_LEN
+#define SNMP_MAX_OCTET_STRING_LEN       127
+#endif
+
+/**
+ * The maximum depth of the SNMP tree.
+ * With private MIBs enabled, this depends on your MIB!
+ * This affects the size of MEMP_SNMP_VALUE elements.
+ */
+#ifndef SNMP_MAX_TREE_DEPTH
+#define SNMP_MAX_TREE_DEPTH             15
+#endif
+
+/**
+ * The size of the MEMP_SNMP_VALUE elements, normally calculated from
+ * SNMP_MAX_OCTET_STRING_LEN and SNMP_MAX_TREE_DEPTH.
+ */
+#ifndef SNMP_MAX_VALUE_SIZE
+#define SNMP_MAX_VALUE_SIZE             LWIP_MAX((SNMP_MAX_OCTET_STRING_LEN)+1, sizeof(s32_t)*(SNMP_MAX_TREE_DEPTH))
+#endif
+
+/*
+   ----------------------------------
+   ---------- IGMP options ----------
+   ----------------------------------
+*/
+/**
+ * LWIP_IGMP==1: Turn on IGMP module. 
+ */
+#ifndef LWIP_IGMP
+#define LWIP_IGMP                       0
+#endif
+
+/*
+   ----------------------------------
+   ---------- DNS options -----------
+   ----------------------------------
+*/
+/**
+ * LWIP_DNS==1: Turn on DNS module. UDP must be available for DNS
+ * transport.
+ */
+#ifndef LWIP_DNS
+#define LWIP_DNS                        0
+#endif
+
+/** DNS maximum number of entries to maintain locally. */
+#ifndef DNS_TABLE_SIZE
+#define DNS_TABLE_SIZE                  4
+#endif
+
+/** DNS maximum host name length supported in the name table. */
+#ifndef DNS_MAX_NAME_LENGTH
+#define DNS_MAX_NAME_LENGTH             256
+#endif
+
+/** The maximum of DNS servers */
+#ifndef DNS_MAX_SERVERS
+#define DNS_MAX_SERVERS                 2
+#endif
+
+/** DNS do a name checking between the query and the response. */
+#ifndef DNS_DOES_NAME_CHECK
+#define DNS_DOES_NAME_CHECK             1
+#endif
+
+/** DNS message max. size. Default value is RFC compliant. */
+#ifndef DNS_MSG_SIZE
+#define DNS_MSG_SIZE                    512
+#endif
+
+/** DNS_LOCAL_HOSTLIST: Implements a local host-to-address list. If enabled,
+ *  you have to define
+ *    #define DNS_LOCAL_HOSTLIST_INIT {{"host1", 0x123}, {"host2", 0x234}}
+ *  (an array of structs name/address, where address is an u32_t in network
+ *  byte order).
+ *
+ *  Instead, you can also use an external function:
+ *  #define DNS_LOOKUP_LOCAL_EXTERN(x) extern u32_t my_lookup_function(const char *name)
+ *  that returns the IP address or INADDR_NONE if not found.
+ */
+#ifndef DNS_LOCAL_HOSTLIST
+#define DNS_LOCAL_HOSTLIST              0
+#endif /* DNS_LOCAL_HOSTLIST */
+
+/** If this is turned on, the local host-list can be dynamically changed
+ *  at runtime. */
+#ifndef DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+#define DNS_LOCAL_HOSTLIST_IS_DYNAMIC   0
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+
+/*
+   ---------------------------------
+   ---------- UDP options ----------
+   ---------------------------------
+*/
+/**
+ * LWIP_UDP==1: Turn on UDP.
+ */
+#ifndef LWIP_UDP
+#define LWIP_UDP                        1
+#endif
+
+/**
+ * LWIP_UDPLITE==1: Turn on UDP-Lite. (Requires LWIP_UDP)
+ */
+#ifndef LWIP_UDPLITE
+#define LWIP_UDPLITE                    0
+#endif
+
+/**
+ * UDP_TTL: Default Time-To-Live value.
+ */
+#ifndef UDP_TTL
+#define UDP_TTL                         (IP_DEFAULT_TTL)
+#endif
+
+/**
+ * LWIP_NETBUF_RECVINFO==1: append destination addr and port to every netbuf.
+ */
+#ifndef LWIP_NETBUF_RECVINFO
+#define LWIP_NETBUF_RECVINFO            0
+#endif
+
+/*
+   ---------------------------------
+   ---------- TCP options ----------
+   ---------------------------------
+*/
+/**
+ * LWIP_TCP==1: Turn on TCP.
+ */
+#ifndef LWIP_TCP
+#define LWIP_TCP                        1
+#endif
+
+/**
+ * TCP_TTL: Default Time-To-Live value.
+ */
+#ifndef TCP_TTL
+#define TCP_TTL                         (IP_DEFAULT_TTL)
+#endif
+
+/**
+ * TCP_WND: The size of a TCP window.  This must be at least 
+ * (2 * TCP_MSS) for things to work well
+ */
+#ifndef TCP_WND
+#define TCP_WND                         (4 * TCP_MSS)
+#endif 
+
+/**
+ * TCP_MAXRTX: Maximum number of retransmissions of data segments.
+ */
+#ifndef TCP_MAXRTX
+#define TCP_MAXRTX                      12
+#endif
+
+/**
+ * TCP_SYNMAXRTX: Maximum number of retransmissions of SYN segments.
+ */
+#ifndef TCP_SYNMAXRTX
+#define TCP_SYNMAXRTX                   6
+#endif
+
+/**
+ * TCP_QUEUE_OOSEQ==1: TCP will queue segments that arrive out of order.
+ * Define to 0 if your device is low on memory.
+ */
+#ifndef TCP_QUEUE_OOSEQ
+#define TCP_QUEUE_OOSEQ                 (LWIP_TCP)
+#endif
+
+/**
+ * TCP_MSS: TCP Maximum segment size. (default is 536, a conservative default,
+ * you might want to increase this.)
+ * For the receive side, this MSS is advertised to the remote side
+ * when opening a connection. For the transmit size, this MSS sets
+ * an upper limit on the MSS advertised by the remote host.
+ */
+#ifndef TCP_MSS
+#define TCP_MSS                         536
+#endif
+
+/**
+ * TCP_CALCULATE_EFF_SEND_MSS: "The maximum size of a segment that TCP really
+ * sends, the 'effective send MSS,' MUST be the smaller of the send MSS (which
+ * reflects the available reassembly buffer size at the remote host) and the
+ * largest size permitted by the IP layer" (RFC 1122)
+ * Setting this to 1 enables code that checks TCP_MSS against the MTU of the
+ * netif used for a connection and limits the MSS if it would be too big otherwise.
+ */
+#ifndef TCP_CALCULATE_EFF_SEND_MSS
+#define TCP_CALCULATE_EFF_SEND_MSS      1
+#endif
+
+
+/**
+ * TCP_SND_BUF: TCP sender buffer space (bytes).
+ * To achieve good performance, this should be at least 2 * TCP_MSS.
+ */
+#ifndef TCP_SND_BUF
+#define TCP_SND_BUF                     (2 * TCP_MSS)
+#endif
+
+/**
+ * TCP_SND_QUEUELEN: TCP sender buffer space (pbufs). This must be at least
+ * as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work.
+ */
+#ifndef TCP_SND_QUEUELEN
+#define TCP_SND_QUEUELEN                ((4 * (TCP_SND_BUF) + (TCP_MSS - 1))/(TCP_MSS))
+#endif
+
+/**
+ * TCP_SNDLOWAT: TCP writable space (bytes). This must be less than
+ * TCP_SND_BUF. It is the amount of space which must be available in the
+ * TCP snd_buf for select to return writable (combined with TCP_SNDQUEUELOWAT).
+ */
+#ifndef TCP_SNDLOWAT
+#define TCP_SNDLOWAT                    LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1)
+#endif
+
+/**
+ * TCP_SNDQUEUELOWAT: TCP writable bufs (pbuf count). This must be less
+ * than TCP_SND_QUEUELEN. If the number of pbufs queued on a pcb drops below
+ * this number, select returns writable (combined with TCP_SNDLOWAT).
+ */
+#ifndef TCP_SNDQUEUELOWAT
+#define TCP_SNDQUEUELOWAT               LWIP_MAX(((TCP_SND_QUEUELEN)/2), 5)
+#endif
+
+/**
+ * TCP_OOSEQ_MAX_BYTES: The maximum number of bytes queued on ooseq per pcb.
+ * Default is 0 (no limit). Only valid for TCP_QUEUE_OOSEQ==0.
+ */
+#ifndef TCP_OOSEQ_MAX_BYTES
+#define TCP_OOSEQ_MAX_BYTES             0
+#endif
+
+/**
+ * TCP_OOSEQ_MAX_PBUFS: The maximum number of pbufs queued on ooseq per pcb.
+ * Default is 0 (no limit). Only valid for TCP_QUEUE_OOSEQ==0.
+ */
+#ifndef TCP_OOSEQ_MAX_PBUFS
+#define TCP_OOSEQ_MAX_PBUFS             0
+#endif
+
+/**
+ * TCP_LISTEN_BACKLOG: Enable the backlog option for tcp listen pcb.
+ */
+#ifndef TCP_LISTEN_BACKLOG
+#define TCP_LISTEN_BACKLOG              0
+#endif
+
+/**
+ * The maximum allowed backlog for TCP listen netconns.
+ * This backlog is used unless another is explicitly specified.
+ * 0xff is the maximum (u8_t).
+ */
+#ifndef TCP_DEFAULT_LISTEN_BACKLOG
+#define TCP_DEFAULT_LISTEN_BACKLOG      0xff
+#endif
+
+/**
+ * TCP_OVERSIZE: The maximum number of bytes that tcp_write may
+ * allocate ahead of time in an attempt to create shorter pbuf chains
+ * for transmission. The meaningful range is 0 to TCP_MSS. Some
+ * suggested values are:
+ *
+ * 0:         Disable oversized allocation. Each tcp_write() allocates a new
+              pbuf (old behaviour).
+ * 1:         Allocate size-aligned pbufs with minimal excess. Use this if your
+ *            scatter-gather DMA requires aligned fragments.
+ * 128:       Limit the pbuf/memory overhead to 20%.
+ * TCP_MSS:   Try to create unfragmented TCP packets.
+ * TCP_MSS/4: Try to create 4 fragments or less per TCP packet.
+ */
+#ifndef TCP_OVERSIZE
+#define TCP_OVERSIZE                    TCP_MSS
+#endif
+
+/**
+ * LWIP_TCP_TIMESTAMPS==1: support the TCP timestamp option.
+ */
+#ifndef LWIP_TCP_TIMESTAMPS
+#define LWIP_TCP_TIMESTAMPS             0
+#endif
+
+/**
+ * TCP_WND_UPDATE_THRESHOLD: difference in window to trigger an
+ * explicit window update
+ */
+#ifndef TCP_WND_UPDATE_THRESHOLD
+#define TCP_WND_UPDATE_THRESHOLD   (TCP_WND / 4)
+#endif
+
+/**
+ * LWIP_EVENT_API and LWIP_CALLBACK_API: Only one of these should be set to 1.
+ *     LWIP_EVENT_API==1: The user defines lwip_tcp_event() to receive all
+ *         events (accept, sent, etc) that happen in the system.
+ *     LWIP_CALLBACK_API==1: The PCB callback function is called directly
+ *         for the event. This is the default.
+ */
+#if !defined(LWIP_EVENT_API) && !defined(LWIP_CALLBACK_API)
+#define LWIP_EVENT_API                  0
+#define LWIP_CALLBACK_API               1
+#endif
+
+
+/*
+   ----------------------------------
+   ---------- Pbuf options ----------
+   ----------------------------------
+*/
+/**
+ * PBUF_LINK_HLEN: the number of bytes that should be allocated for a
+ * link level header. The default is 14, the standard value for
+ * Ethernet.
+ */
+#ifndef PBUF_LINK_HLEN
+#define PBUF_LINK_HLEN                  (14 + ETH_PAD_SIZE)
+#endif
+
+/**
+ * PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. The default is
+ * designed to accomodate single full size TCP frame in one pbuf, including
+ * TCP_MSS, IP header, and link header.
+ */
+#ifndef PBUF_POOL_BUFSIZE
+#define PBUF_POOL_BUFSIZE               LWIP_MEM_ALIGN_SIZE(TCP_MSS+40+PBUF_LINK_HLEN)
+#endif
+
+/*
+   ------------------------------------------------
+   ---------- Network Interfaces options ----------
+   ------------------------------------------------
+*/
+/**
+ * LWIP_NETIF_HOSTNAME==1: use DHCP_OPTION_HOSTNAME with netif's hostname
+ * field.
+ */
+#ifndef LWIP_NETIF_HOSTNAME
+#define LWIP_NETIF_HOSTNAME             0
+#endif
+
+/**
+ * LWIP_NETIF_API==1: Support netif api (in netifapi.c)
+ */
+#ifndef LWIP_NETIF_API
+#define LWIP_NETIF_API                  0
+#endif
+
+/**
+ * LWIP_NETIF_STATUS_CALLBACK==1: Support a callback function whenever an interface
+ * changes its up/down status (i.e., due to DHCP IP acquistion)
+ */
+#ifndef LWIP_NETIF_STATUS_CALLBACK
+#define LWIP_NETIF_STATUS_CALLBACK      0
+#endif
+
+/**
+ * LWIP_NETIF_LINK_CALLBACK==1: Support a callback function from an interface
+ * whenever the link changes (i.e., link down)
+ */
+#ifndef LWIP_NETIF_LINK_CALLBACK
+#define LWIP_NETIF_LINK_CALLBACK        0
+#endif
+
+/**
+ * LWIP_NETIF_REMOVE_CALLBACK==1: Support a callback function that is called
+ * when a netif has been removed
+ */
+#ifndef LWIP_NETIF_REMOVE_CALLBACK
+#define LWIP_NETIF_REMOVE_CALLBACK      0
+#endif
+
+/**
+ * LWIP_NETIF_HWADDRHINT==1: Cache link-layer-address hints (e.g. table
+ * indices) in struct netif. TCP and UDP can make use of this to prevent
+ * scanning the ARP table for every sent packet. While this is faster for big
+ * ARP tables or many concurrent connections, it might be counterproductive
+ * if you have a tiny ARP table or if there never are concurrent connections.
+ */
+#ifndef LWIP_NETIF_HWADDRHINT
+#define LWIP_NETIF_HWADDRHINT           0
+#endif
+
+/**
+ * LWIP_NETIF_LOOPBACK==1: Support sending packets with a destination IP
+ * address equal to the netif IP address, looping them back up the stack.
+ */
+#ifndef LWIP_NETIF_LOOPBACK
+#define LWIP_NETIF_LOOPBACK             0
+#endif
+
+/**
+ * LWIP_LOOPBACK_MAX_PBUFS: Maximum number of pbufs on queue for loopback
+ * sending for each netif (0 = disabled)
+ */
+#ifndef LWIP_LOOPBACK_MAX_PBUFS
+#define LWIP_LOOPBACK_MAX_PBUFS         0
+#endif
+
+/**
+ * LWIP_NETIF_LOOPBACK_MULTITHREADING: Indicates whether threading is enabled in
+ * the system, as netifs must change how they behave depending on this setting
+ * for the LWIP_NETIF_LOOPBACK option to work.
+ * Setting this is needed to avoid reentering non-reentrant functions like
+ * tcp_input().
+ *    LWIP_NETIF_LOOPBACK_MULTITHREADING==1: Indicates that the user is using a
+ *       multithreaded environment like tcpip.c. In this case, netif->input()
+ *       is called directly.
+ *    LWIP_NETIF_LOOPBACK_MULTITHREADING==0: Indicates a polling (or NO_SYS) setup.
+ *       The packets are put on a list and netif_poll() must be called in
+ *       the main application loop.
+ */
+#ifndef LWIP_NETIF_LOOPBACK_MULTITHREADING
+#define LWIP_NETIF_LOOPBACK_MULTITHREADING    (!NO_SYS)
+#endif
+
+/**
+ * LWIP_NETIF_TX_SINGLE_PBUF: if this is set to 1, lwIP tries to put all data
+ * to be sent into one single pbuf. This is for compatibility with DMA-enabled
+ * MACs that do not support scatter-gather.
+ * Beware that this might involve CPU-memcpy before transmitting that would not
+ * be needed without this flag! Use this only if you need to!
+ *
+ * @todo: TCP and IP-frag do not work with this, yet:
+ */
+#ifndef LWIP_NETIF_TX_SINGLE_PBUF
+#define LWIP_NETIF_TX_SINGLE_PBUF             0
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+
+/*
+   ------------------------------------
+   ---------- LOOPIF options ----------
+   ------------------------------------
+*/
+/**
+ * LWIP_HAVE_LOOPIF==1: Support loop interface (127.0.0.1) and loopif.c
+ */
+#ifndef LWIP_HAVE_LOOPIF
+#define LWIP_HAVE_LOOPIF                0
+#endif
+
+/*
+   ------------------------------------
+   ---------- SLIPIF options ----------
+   ------------------------------------
+*/
+/**
+ * LWIP_HAVE_SLIPIF==1: Support slip interface and slipif.c
+ */
+#ifndef LWIP_HAVE_SLIPIF
+#define LWIP_HAVE_SLIPIF                0
+#endif
+
+/*
+   ------------------------------------
+   ---------- Thread options ----------
+   ------------------------------------
+*/
+/**
+ * TCPIP_THREAD_NAME: The name assigned to the main tcpip thread.
+ */
+#ifndef TCPIP_THREAD_NAME
+#define TCPIP_THREAD_NAME              "tcpip_thread"
+#endif
+
+/**
+ * TCPIP_THREAD_STACKSIZE: The stack size used by the main tcpip thread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#ifndef TCPIP_THREAD_STACKSIZE
+#define TCPIP_THREAD_STACKSIZE          0
+#endif
+
+/**
+ * TCPIP_THREAD_PRIO: The priority assigned to the main tcpip thread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#ifndef TCPIP_THREAD_PRIO
+#define TCPIP_THREAD_PRIO               1
+#endif
+
+/**
+ * TCPIP_MBOX_SIZE: The mailbox size for the tcpip thread messages
+ * The queue size value itself is platform-dependent, but is passed to
+ * sys_mbox_new() when tcpip_init is called.
+ */
+#ifndef TCPIP_MBOX_SIZE
+#define TCPIP_MBOX_SIZE                 0
+#endif
+
+/**
+ * SLIPIF_THREAD_NAME: The name assigned to the slipif_loop thread.
+ */
+#ifndef SLIPIF_THREAD_NAME
+#define SLIPIF_THREAD_NAME             "slipif_loop"
+#endif
+
+/**
+ * SLIP_THREAD_STACKSIZE: The stack size used by the slipif_loop thread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#ifndef SLIPIF_THREAD_STACKSIZE
+#define SLIPIF_THREAD_STACKSIZE         0
+#endif
+
+/**
+ * SLIPIF_THREAD_PRIO: The priority assigned to the slipif_loop thread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#ifndef SLIPIF_THREAD_PRIO
+#define SLIPIF_THREAD_PRIO              1
+#endif
+
+/**
+ * PPP_THREAD_NAME: The name assigned to the pppInputThread.
+ */
+#ifndef PPP_THREAD_NAME
+#define PPP_THREAD_NAME                "pppInputThread"
+#endif
+
+/**
+ * PPP_THREAD_STACKSIZE: The stack size used by the pppInputThread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#ifndef PPP_THREAD_STACKSIZE
+#define PPP_THREAD_STACKSIZE            0
+#endif
+
+/**
+ * PPP_THREAD_PRIO: The priority assigned to the pppInputThread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#ifndef PPP_THREAD_PRIO
+#define PPP_THREAD_PRIO                 1
+#endif
+
+/**
+ * DEFAULT_THREAD_NAME: The name assigned to any other lwIP thread.
+ */
+#ifndef DEFAULT_THREAD_NAME
+#define DEFAULT_THREAD_NAME            "lwIP"
+#endif
+
+/**
+ * DEFAULT_THREAD_STACKSIZE: The stack size used by any other lwIP thread.
+ * The stack size value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#ifndef DEFAULT_THREAD_STACKSIZE
+#define DEFAULT_THREAD_STACKSIZE        0
+#endif
+
+/**
+ * DEFAULT_THREAD_PRIO: The priority assigned to any other lwIP thread.
+ * The priority value itself is platform-dependent, but is passed to
+ * sys_thread_new() when the thread is created.
+ */
+#ifndef DEFAULT_THREAD_PRIO
+#define DEFAULT_THREAD_PRIO             1
+#endif
+
+/**
+ * DEFAULT_RAW_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
+ * NETCONN_RAW. The queue size value itself is platform-dependent, but is passed
+ * to sys_mbox_new() when the recvmbox is created.
+ */
+#ifndef DEFAULT_RAW_RECVMBOX_SIZE
+#define DEFAULT_RAW_RECVMBOX_SIZE       0
+#endif
+
+/**
+ * DEFAULT_UDP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
+ * NETCONN_UDP. The queue size value itself is platform-dependent, but is passed
+ * to sys_mbox_new() when the recvmbox is created.
+ */
+#ifndef DEFAULT_UDP_RECVMBOX_SIZE
+#define DEFAULT_UDP_RECVMBOX_SIZE       0
+#endif
+
+/**
+ * DEFAULT_TCP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a
+ * NETCONN_TCP. The queue size value itself is platform-dependent, but is passed
+ * to sys_mbox_new() when the recvmbox is created.
+ */
+#ifndef DEFAULT_TCP_RECVMBOX_SIZE
+#define DEFAULT_TCP_RECVMBOX_SIZE       0
+#endif
+
+/**
+ * DEFAULT_ACCEPTMBOX_SIZE: The mailbox size for the incoming connections.
+ * The queue size value itself is platform-dependent, but is passed to
+ * sys_mbox_new() when the acceptmbox is created.
+ */
+#ifndef DEFAULT_ACCEPTMBOX_SIZE
+#define DEFAULT_ACCEPTMBOX_SIZE         0
+#endif
+
+/*
+   ----------------------------------------------
+   ---------- Sequential layer options ----------
+   ----------------------------------------------
+*/
+/**
+ * LWIP_TCPIP_CORE_LOCKING: (EXPERIMENTAL!)
+ * Don't use it if you're not an active lwIP project member
+ */
+#ifndef LWIP_TCPIP_CORE_LOCKING
+#define LWIP_TCPIP_CORE_LOCKING         0
+#endif
+
+/**
+ * LWIP_TCPIP_CORE_LOCKING_INPUT: (EXPERIMENTAL!)
+ * Don't use it if you're not an active lwIP project member
+ */
+#ifndef LWIP_TCPIP_CORE_LOCKING_INPUT
+#define LWIP_TCPIP_CORE_LOCKING_INPUT   0
+#endif
+
+/**
+ * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
+ */
+#ifndef LWIP_NETCONN
+#define LWIP_NETCONN                    1
+#endif
+
+/** LWIP_TCPIP_TIMEOUT==1: Enable tcpip_timeout/tcpip_untimeout tod create
+ * timers running in tcpip_thread from another thread.
+ */
+#ifndef LWIP_TCPIP_TIMEOUT
+#define LWIP_TCPIP_TIMEOUT              1
+#endif
+
+/*
+   ------------------------------------
+   ---------- Socket options ----------
+   ------------------------------------
+*/
+/**
+ * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
+ */
+#ifndef LWIP_SOCKET
+#define LWIP_SOCKET                     1
+#endif
+
+/**
+ * LWIP_COMPAT_SOCKETS==1: Enable BSD-style sockets functions names.
+ * (only used if you use sockets.c)
+ */
+#ifndef LWIP_COMPAT_SOCKETS
+#define LWIP_COMPAT_SOCKETS             1
+#endif
+
+/**
+ * LWIP_POSIX_SOCKETS_IO_NAMES==1: Enable POSIX-style sockets functions names.
+ * Disable this option if you use a POSIX operating system that uses the same
+ * names (read, write & close). (only used if you use sockets.c)
+ */
+#ifndef LWIP_POSIX_SOCKETS_IO_NAMES
+#define LWIP_POSIX_SOCKETS_IO_NAMES     1
+#endif
+
+/**
+ * LWIP_TCP_KEEPALIVE==1: Enable TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT
+ * options processing. Note that TCP_KEEPIDLE and TCP_KEEPINTVL have to be set
+ * in seconds. (does not require sockets.c, and will affect tcp.c)
+ */
+#ifndef LWIP_TCP_KEEPALIVE
+#define LWIP_TCP_KEEPALIVE              0
+#endif
+
+/**
+ * LWIP_SO_SNDTIMEO==1: Enable send timeout for sockets/netconns and
+ * SO_SNDTIMEO processing.
+ */
+#ifndef LWIP_SO_SNDTIMEO
+#define LWIP_SO_SNDTIMEO                0
+#endif
+
+/**
+ * LWIP_SO_RCVTIMEO==1: Enable receive timeout for sockets/netconns and
+ * SO_RCVTIMEO processing.
+ */
+#ifndef LWIP_SO_RCVTIMEO
+#define LWIP_SO_RCVTIMEO                0
+#endif
+
+/**
+ * LWIP_SO_RCVBUF==1: Enable SO_RCVBUF processing.
+ */
+#ifndef LWIP_SO_RCVBUF
+#define LWIP_SO_RCVBUF                  0
+#endif
+
+/**
+ * If LWIP_SO_RCVBUF is used, this is the default value for recv_bufsize.
+ */
+#ifndef RECV_BUFSIZE_DEFAULT
+#define RECV_BUFSIZE_DEFAULT            INT_MAX
+#endif
+
+/**
+ * SO_REUSE==1: Enable SO_REUSEADDR option.
+ */
+#ifndef SO_REUSE
+#define SO_REUSE                        0
+#endif
+
+/**
+ * SO_REUSE_RXTOALL==1: Pass a copy of incoming broadcast/multicast packets
+ * to all local matches if SO_REUSEADDR is turned on.
+ * WARNING: Adds a memcpy for every packet if passing to more than one pcb!
+ */
+#ifndef SO_REUSE_RXTOALL
+#define SO_REUSE_RXTOALL                0
+#endif
+
+/**
+ * LWIP_FIONREAD_LINUXMODE==0 (default): ioctl/FIONREAD returns the amount of
+ * pending data in the network buffer. This is the way windows does it. It's
+ * the default for lwIP since it is smaller.
+ * LWIP_FIONREAD_LINUXMODE==1: ioctl/FIONREAD returns the size of the next
+ * pending datagram in bytes. This is the way linux does it. This code is only
+ * here for compatibility.
+ */
+#ifndef LWIP_FIONREAD_LINUXMODE
+#define LWIP_FIONREAD_LINUXMODE         0
+#endif
+
+/*
+   ----------------------------------------
+   ---------- Statistics options ----------
+   ----------------------------------------
+*/
+/**
+ * LWIP_STATS==1: Enable statistics collection in lwip_stats.
+ */
+#ifndef LWIP_STATS
+#define LWIP_STATS                      1
+#endif
+
+#if LWIP_STATS
+
+/**
+ * LWIP_STATS_DISPLAY==1: Compile in the statistics output functions.
+ */
+#ifndef LWIP_STATS_DISPLAY
+#define LWIP_STATS_DISPLAY              0
+#endif
+
+/**
+ * LINK_STATS==1: Enable link stats.
+ */
+#ifndef LINK_STATS
+#define LINK_STATS                      1
+#endif
+
+/**
+ * ETHARP_STATS==1: Enable etharp stats.
+ */
+#ifndef ETHARP_STATS
+#define ETHARP_STATS                    (LWIP_ARP)
+#endif
+
+/**
+ * IP_STATS==1: Enable IP stats.
+ */
+#ifndef IP_STATS
+#define IP_STATS                        1
+#endif
+
+/**
+ * IPFRAG_STATS==1: Enable IP fragmentation stats. Default is
+ * on if using either frag or reass.
+ */
+#ifndef IPFRAG_STATS
+#define IPFRAG_STATS                    (IP_REASSEMBLY || IP_FRAG)
+#endif
+
+/**
+ * ICMP_STATS==1: Enable ICMP stats.
+ */
+#ifndef ICMP_STATS
+#define ICMP_STATS                      1
+#endif
+
+/**
+ * IGMP_STATS==1: Enable IGMP stats.
+ */
+#ifndef IGMP_STATS
+#define IGMP_STATS                      (LWIP_IGMP)
+#endif
+
+/**
+ * UDP_STATS==1: Enable UDP stats. Default is on if
+ * UDP enabled, otherwise off.
+ */
+#ifndef UDP_STATS
+#define UDP_STATS                       (LWIP_UDP)
+#endif
+
+/**
+ * TCP_STATS==1: Enable TCP stats. Default is on if TCP
+ * enabled, otherwise off.
+ */
+#ifndef TCP_STATS
+#define TCP_STATS                       (LWIP_TCP)
+#endif
+
+/**
+ * MEM_STATS==1: Enable mem.c stats.
+ */
+#ifndef MEM_STATS
+#define MEM_STATS                       ((MEM_LIBC_MALLOC == 0) && (MEM_USE_POOLS == 0))
+#endif
+
+/**
+ * MEMP_STATS==1: Enable memp.c pool stats.
+ */
+#ifndef MEMP_STATS
+#define MEMP_STATS                      (MEMP_MEM_MALLOC == 0)
+#endif
+
+/**
+ * SYS_STATS==1: Enable system stats (sem and mbox counts, etc).
+ */
+#ifndef SYS_STATS
+#define SYS_STATS                       (NO_SYS == 0)
+#endif
+
+/**
+ * IP6_STATS==1: Enable IPv6 stats.
+ */
+#ifndef IP6_STATS
+#define IP6_STATS                       (LWIP_IPV6)
+#endif
+
+/**
+ * ICMP6_STATS==1: Enable ICMP for IPv6 stats.
+ */
+#ifndef ICMP6_STATS
+#define ICMP6_STATS                     (LWIP_IPV6 && LWIP_ICMP6)
+#endif
+
+/**
+ * IP6_FRAG_STATS==1: Enable IPv6 fragmentation stats.
+ */
+#ifndef IP6_FRAG_STATS
+#define IP6_FRAG_STATS                  (LWIP_IPV6 && (LWIP_IPV6_FRAG || LWIP_IPV6_REASS))
+#endif
+
+/**
+ * MLD6_STATS==1: Enable MLD for IPv6 stats.
+ */
+#ifndef MLD6_STATS
+#define MLD6_STATS                      (LWIP_IPV6 && LWIP_IPV6_MLD)
+#endif
+
+/**
+ * ND6_STATS==1: Enable Neighbor discovery for IPv6 stats.
+ */
+#ifndef ND6_STATS
+#define ND6_STATS                       (LWIP_IPV6)
+#endif
+
+#else
+
+#define LINK_STATS                      0
+#define IP_STATS                        0
+#define IPFRAG_STATS                    0
+#define ICMP_STATS                      0
+#define IGMP_STATS                      0
+#define UDP_STATS                       0
+#define TCP_STATS                       0
+#define MEM_STATS                       0
+#define MEMP_STATS                      0
+#define SYS_STATS                       0
+#define LWIP_STATS_DISPLAY              0
+#define IP6_STATS                       0
+#define ICMP6_STATS                     0
+#define IP6_FRAG_STATS                  0
+#define MLD6_STATS                      0
+#define ND6_STATS                       0
+
+#endif /* LWIP_STATS */
+
+/*
+   ---------------------------------
+   ---------- PPP options ----------
+   ---------------------------------
+*/
+/**
+ * PPP_SUPPORT==1: Enable PPP.
+ */
+#ifndef PPP_SUPPORT
+#define PPP_SUPPORT                     0
+#endif
+
+/**
+ * PPPOE_SUPPORT==1: Enable PPP Over Ethernet
+ */
+#ifndef PPPOE_SUPPORT
+#define PPPOE_SUPPORT                   0
+#endif
+
+/**
+ * PPPOS_SUPPORT==1: Enable PPP Over Serial
+ */
+#ifndef PPPOS_SUPPORT
+#define PPPOS_SUPPORT                   PPP_SUPPORT
+#endif
+
+#if PPP_SUPPORT
+
+/**
+ * NUM_PPP: Max PPP sessions.
+ */
+#ifndef NUM_PPP
+#define NUM_PPP                         1
+#endif
+
+/**
+ * PAP_SUPPORT==1: Support PAP.
+ */
+#ifndef PAP_SUPPORT
+#define PAP_SUPPORT                     0
+#endif
+
+/**
+ * CHAP_SUPPORT==1: Support CHAP.
+ */
+#ifndef CHAP_SUPPORT
+#define CHAP_SUPPORT                    0
+#endif
+
+/**
+ * MSCHAP_SUPPORT==1: Support MSCHAP. CURRENTLY NOT SUPPORTED! DO NOT SET!
+ */
+#ifndef MSCHAP_SUPPORT
+#define MSCHAP_SUPPORT                  0
+#endif
+
+/**
+ * CBCP_SUPPORT==1: Support CBCP. CURRENTLY NOT SUPPORTED! DO NOT SET!
+ */
+#ifndef CBCP_SUPPORT
+#define CBCP_SUPPORT                    0
+#endif
+
+/**
+ * CCP_SUPPORT==1: Support CCP. CURRENTLY NOT SUPPORTED! DO NOT SET!
+ */
+#ifndef CCP_SUPPORT
+#define CCP_SUPPORT                     0
+#endif
+
+/**
+ * VJ_SUPPORT==1: Support VJ header compression.
+ */
+#ifndef VJ_SUPPORT
+#define VJ_SUPPORT                      0
+#endif
+
+/**
+ * MD5_SUPPORT==1: Support MD5 (see also CHAP).
+ */
+#ifndef MD5_SUPPORT
+#define MD5_SUPPORT                     0
+#endif
+
+/*
+ * Timeouts
+ */
+#ifndef FSM_DEFTIMEOUT
+#define FSM_DEFTIMEOUT                  6       /* Timeout time in seconds */
+#endif
+
+#ifndef FSM_DEFMAXTERMREQS
+#define FSM_DEFMAXTERMREQS              2       /* Maximum Terminate-Request transmissions */
+#endif
+
+#ifndef FSM_DEFMAXCONFREQS
+#define FSM_DEFMAXCONFREQS              10      /* Maximum Configure-Request transmissions */
+#endif
+
+#ifndef FSM_DEFMAXNAKLOOPS
+#define FSM_DEFMAXNAKLOOPS              5       /* Maximum number of nak loops */
+#endif
+
+#ifndef UPAP_DEFTIMEOUT
+#define UPAP_DEFTIMEOUT                 6       /* Timeout (seconds) for retransmitting req */
+#endif
+
+#ifndef UPAP_DEFREQTIME
+#define UPAP_DEFREQTIME                 30      /* Time to wait for auth-req from peer */
+#endif
+
+#ifndef CHAP_DEFTIMEOUT
+#define CHAP_DEFTIMEOUT                 6       /* Timeout time in seconds */
+#endif
+
+#ifndef CHAP_DEFTRANSMITS
+#define CHAP_DEFTRANSMITS               10      /* max # times to send challenge */
+#endif
+
+/* Interval in seconds between keepalive echo requests, 0 to disable. */
+#ifndef LCP_ECHOINTERVAL
+#define LCP_ECHOINTERVAL                0
+#endif
+
+/* Number of unanswered echo requests before failure. */
+#ifndef LCP_MAXECHOFAILS
+#define LCP_MAXECHOFAILS                3
+#endif
+
+/* Max Xmit idle time (in jiffies) before resend flag char. */
+#ifndef PPP_MAXIDLEFLAG
+#define PPP_MAXIDLEFLAG                 100
+#endif
+
+/*
+ * Packet sizes
+ *
+ * Note - lcp shouldn't be allowed to negotiate stuff outside these
+ *    limits.  See lcp.h in the pppd directory.
+ * (XXX - these constants should simply be shared by lcp.c instead
+ *    of living in lcp.h)
+ */
+#define PPP_MTU                         1500     /* Default MTU (size of Info field) */
+#ifndef PPP_MAXMTU
+/* #define PPP_MAXMTU  65535 - (PPP_HDRLEN + PPP_FCSLEN) */
+#define PPP_MAXMTU                      1500 /* Largest MTU we allow */
+#endif
+#define PPP_MINMTU                      64
+#define PPP_MRU                         1500     /* default MRU = max length of info field */
+#define PPP_MAXMRU                      1500     /* Largest MRU we allow */
+#ifndef PPP_DEFMRU
+#define PPP_DEFMRU                      296             /* Try for this */
+#endif
+#define PPP_MINMRU                      128             /* No MRUs below this */
+
+#ifndef MAXNAMELEN
+#define MAXNAMELEN                      256     /* max length of hostname or name for auth */
+#endif
+#ifndef MAXSECRETLEN
+#define MAXSECRETLEN                    256     /* max length of password or secret */
+#endif
+
+#endif /* PPP_SUPPORT */
+
+/*
+   --------------------------------------
+   ---------- Checksum options ----------
+   --------------------------------------
+*/
+/**
+ * CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.
+ */
+#ifndef CHECKSUM_GEN_IP
+#define CHECKSUM_GEN_IP                 1
+#endif
+ 
+/**
+ * CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.
+ */
+#ifndef CHECKSUM_GEN_UDP
+#define CHECKSUM_GEN_UDP                1
+#endif
+ 
+/**
+ * CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.
+ */
+#ifndef CHECKSUM_GEN_TCP
+#define CHECKSUM_GEN_TCP                1
+#endif
+
+/**
+ * CHECKSUM_GEN_ICMP==1: Generate checksums in software for outgoing ICMP packets.
+ */
+#ifndef CHECKSUM_GEN_ICMP
+#define CHECKSUM_GEN_ICMP               1
+#endif
+ 
+/**
+ * CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.
+ */
+#ifndef CHECKSUM_CHECK_IP
+#define CHECKSUM_CHECK_IP               1
+#endif
+ 
+/**
+ * CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.
+ */
+#ifndef CHECKSUM_CHECK_UDP
+#define CHECKSUM_CHECK_UDP              1
+#endif
+
+/**
+ * CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.
+ */
+#ifndef CHECKSUM_CHECK_TCP
+#define CHECKSUM_CHECK_TCP              1
+#endif
+
+/**
+ * LWIP_CHECKSUM_ON_COPY==1: Calculate checksum when copying data from
+ * application buffers to pbufs.
+ */
+#ifndef LWIP_CHECKSUM_ON_COPY
+#define LWIP_CHECKSUM_ON_COPY           0
+#endif
+
+/*
+   ---------------------------------------
+   ---------- IPv6 options ---------------
+   ---------------------------------------
+*/
+/**
+ * LWIP_IPV6==1: Enable IPv6
+ */
+#ifndef LWIP_IPV6
+#define LWIP_IPV6                       0
+#endif
+
+/**
+ * LWIP_IPV6_NUM_ADDRESSES: Number of IPv6 addresses per netif.
+ */
+#ifndef LWIP_IPV6_NUM_ADDRESSES
+#define LWIP_IPV6_NUM_ADDRESSES         3
+#endif
+
+/**
+ * LWIP_IPV6_FORWARD==1: Forward IPv6 packets across netifs
+ */
+#ifndef LWIP_IPV6_FORWARD
+#define LWIP_IPV6_FORWARD               0
+#endif
+
+/**
+ * LWIP_ICMP6==1: Enable ICMPv6 (mandatory per RFC)
+ */
+#ifndef LWIP_ICMP6
+#define LWIP_ICMP6                      (LWIP_IPV6)
+#endif
+
+/**
+ * LWIP_ICMP6_DATASIZE: bytes from original packet to send back in
+ * ICMPv6 error messages.
+ */
+#ifndef LWIP_ICMP6_DATASIZE
+#define LWIP_ICMP6_DATASIZE             8
+#endif
+
+/**
+ * LWIP_ICMP6_HL: default hop limit for ICMPv6 messages
+ */
+#ifndef LWIP_ICMP6_HL
+#define LWIP_ICMP6_HL                   255
+#endif
+
+/**
+ * LWIP_ICMP6_CHECKSUM_CHECK==1: verify checksum on ICMPv6 packets
+ */
+#ifndef LWIP_ICMP6_CHECKSUM_CHECK
+#define LWIP_ICMP6_CHECKSUM_CHECK       1
+#endif
+
+/**
+ * LWIP_IPV6_MLD==1: Enable multicast listener discovery protocol.
+ */
+#ifndef LWIP_IPV6_MLD
+#define LWIP_IPV6_MLD                   (LWIP_IPV6)
+#endif
+
+/**
+ * MEMP_NUM_MLD6_GROUP: Max number of IPv6 multicast that can be joined.
+ */
+#ifndef MEMP_NUM_MLD6_GROUP
+#define MEMP_NUM_MLD6_GROUP             4
+#endif
+
+/**
+ * LWIP_IPV6_FRAG==1: Fragment outgoing IPv6 packets that are too big.
+ */
+#ifndef LWIP_IPV6_FRAG
+#define LWIP_IPV6_FRAG                  0
+#endif
+
+/**
+ * LWIP_IPV6_REASS==1: reassemble incoming IPv6 packets that fragmented
+ */
+#ifndef LWIP_IPV6_REASS
+#define LWIP_IPV6_REASS                 (LWIP_IPV6)
+#endif
+
+/**
+ * LWIP_ND6_QUEUEING==1: queue outgoing IPv6 packets while MAC address
+ * is being resolved.
+ */
+#ifndef LWIP_ND6_QUEUEING
+#define LWIP_ND6_QUEUEING               (LWIP_IPV6)
+#endif
+
+/**
+ * MEMP_NUM_ND6_QUEUE: Max number of IPv6 packets to queue during MAC resolution.
+ */
+#ifndef MEMP_NUM_ND6_QUEUE
+#define MEMP_NUM_ND6_QUEUE              20
+#endif
+
+/**
+ * LWIP_ND6_NUM_NEIGHBORS: Number of entries in IPv6 neighbor cache
+ */
+#ifndef LWIP_ND6_NUM_NEIGHBORS
+#define LWIP_ND6_NUM_NEIGHBORS          10
+#endif
+
+/**
+ * LWIP_ND6_NUM_DESTINATIONS: number of entries in IPv6 destination cache
+ */
+#ifndef LWIP_ND6_NUM_DESTINATIONS
+#define LWIP_ND6_NUM_DESTINATIONS       10
+#endif
+
+/**
+ * LWIP_ND6_NUM_PREFIXES: number of entries in IPv6 on-link prefixes cache
+ */
+#ifndef LWIP_ND6_NUM_PREFIXES
+#define LWIP_ND6_NUM_PREFIXES           5
+#endif
+
+/**
+ * LWIP_ND6_NUM_ROUTERS: number of entries in IPv6 default router cache
+ */
+#ifndef LWIP_ND6_NUM_ROUTERS
+#define LWIP_ND6_NUM_ROUTERS            3
+#endif
+
+/**
+ * LWIP_ND6_MAX_MULTICAST_SOLICIT: max number of multicast solicit messages to send
+ * (neighbor solicit and router solicit)
+ */
+#ifndef LWIP_ND6_MAX_MULTICAST_SOLICIT
+#define LWIP_ND6_MAX_MULTICAST_SOLICIT  3
+#endif
+
+/**
+ * LWIP_ND6_MAX_UNICAST_SOLICIT: max number of unicast neighbor solicitation messages
+ * to send during neighbor reachability detection.
+ */
+#ifndef LWIP_ND6_MAX_UNICAST_SOLICIT
+#define LWIP_ND6_MAX_UNICAST_SOLICIT    3
+#endif
+
+/**
+ * Unused: See ND RFC (time in milliseconds).
+ */
+#ifndef LWIP_ND6_MAX_ANYCAST_DELAY_TIME
+#define LWIP_ND6_MAX_ANYCAST_DELAY_TIME 1000
+#endif
+
+/**
+ * Unused: See ND RFC
+ */
+#ifndef LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT
+#define LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT  3
+#endif
+
+/**
+ * LWIP_ND6_REACHABLE_TIME: default neighbor reachable time (in milliseconds).
+ * May be updated by router advertisement messages.
+ */
+#ifndef LWIP_ND6_REACHABLE_TIME
+#define LWIP_ND6_REACHABLE_TIME         30000
+#endif
+
+/**
+ * LWIP_ND6_RETRANS_TIMER: default retransmission timer for solicitation messages
+ */
+#ifndef LWIP_ND6_RETRANS_TIMER
+#define LWIP_ND6_RETRANS_TIMER          1000
+#endif
+
+/**
+ * LWIP_ND6_DELAY_FIRST_PROBE_TIME: Delay before first unicast neighbor solicitation
+ * message is sent, during neighbor reachability detection.
+ */
+#ifndef LWIP_ND6_DELAY_FIRST_PROBE_TIME
+#define LWIP_ND6_DELAY_FIRST_PROBE_TIME 5000
+#endif
+
+/**
+ * LWIP_ND6_ALLOW_RA_UPDATES==1: Allow Router Advertisement messages to update
+ * Reachable time and retransmission timers, and netif MTU.
+ */
+#ifndef LWIP_ND6_ALLOW_RA_UPDATES
+#define LWIP_ND6_ALLOW_RA_UPDATES       1
+#endif
+
+/**
+ * LWIP_IPV6_SEND_ROUTER_SOLICIT==1: Send router solicitation messages during
+ * network startup.
+ */
+#ifndef LWIP_IPV6_SEND_ROUTER_SOLICIT
+#define LWIP_IPV6_SEND_ROUTER_SOLICIT   1
+#endif
+
+/**
+ * LWIP_ND6_TCP_REACHABILITY_HINTS==1: Allow TCP to provide Neighbor Discovery
+ * with reachability hints for connected destinations. This helps avoid sending
+ * unicast neighbor solicitation messages.
+ */
+#ifndef LWIP_ND6_TCP_REACHABILITY_HINTS
+#define LWIP_ND6_TCP_REACHABILITY_HINTS 1
+#endif
+
+/**
+ * LWIP_IPV6_AUTOCONFIG==1: Enable stateless address autoconfiguration as per RFC 4862.
+ */
+#ifndef LWIP_IPV6_AUTOCONFIG
+#define LWIP_IPV6_AUTOCONFIG            (LWIP_IPV6)
+#endif
+
+/**
+ * LWIP_IPV6_DUP_DETECT_ATTEMPTS: Number of duplicate address detection attempts.
+ */
+#ifndef LWIP_IPV6_DUP_DETECT_ATTEMPTS
+#define LWIP_IPV6_DUP_DETECT_ATTEMPTS   1
+#endif
+
+/**
+ * LWIP_IPV6_DHCP6==1: enable DHCPv6 stateful address autoconfiguration.
+ */
+#ifndef LWIP_IPV6_DHCP6
+#define LWIP_IPV6_DHCP6                 0
+#endif
+
+/*
+   ---------------------------------------
+   ---------- Hook options ---------------
+   ---------------------------------------
+*/
+
+/* Hooks are undefined by default, define them to a function if you need them. */
+
+/**
+ * LWIP_HOOK_IP4_INPUT(pbuf, input_netif):
+ * - called from ip_input() (IPv4)
+ * - pbuf: received struct pbuf passed to ip_input()
+ * - input_netif: struct netif on which the packet has been received
+ * Return values:
+ * - 0: Hook has not consumed the packet, packet is processed as normal
+ * - != 0: Hook has consumed the packet.
+ * If the hook consumed the packet, 'pbuf' is in the responsibility of the hook
+ * (i.e. free it when done).
+ */
+
+/**
+ * LWIP_HOOK_IP4_ROUTE(dest):
+ * - called from ip_route() (IPv4)
+ * - dest: destination IPv4 address
+ * Returns the destination netif or NULL if no destination netif is found. In
+ * that case, ip_route() continues as normal.
+ */
+
+/*
+   ---------------------------------------
+   ---------- Debugging options ----------
+   ---------------------------------------
+*/
+/**
+ * LWIP_DBG_MIN_LEVEL: After masking, the value of the debug is
+ * compared against this value. If it is smaller, then debugging
+ * messages are written.
+ */
+#ifndef LWIP_DBG_MIN_LEVEL
+#define LWIP_DBG_MIN_LEVEL              LWIP_DBG_LEVEL_ALL
+#endif
+
+/**
+ * LWIP_DBG_TYPES_ON: A mask that can be used to globally enable/disable
+ * debug messages of certain types.
+ */
+#ifndef LWIP_DBG_TYPES_ON
+#define LWIP_DBG_TYPES_ON               LWIP_DBG_ON
+#endif
+
+/**
+ * ETHARP_DEBUG: Enable debugging in etharp.c.
+ */
+#ifndef ETHARP_DEBUG
+#define ETHARP_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * NETIF_DEBUG: Enable debugging in netif.c.
+ */
+#ifndef NETIF_DEBUG
+#define NETIF_DEBUG                     LWIP_DBG_OFF
+#endif
+
+/**
+ * PBUF_DEBUG: Enable debugging in pbuf.c.
+ */
+#ifndef PBUF_DEBUG
+#define PBUF_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * API_LIB_DEBUG: Enable debugging in api_lib.c.
+ */
+#ifndef API_LIB_DEBUG
+#define API_LIB_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * API_MSG_DEBUG: Enable debugging in api_msg.c.
+ */
+#ifndef API_MSG_DEBUG
+#define API_MSG_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * SOCKETS_DEBUG: Enable debugging in sockets.c.
+ */
+#ifndef SOCKETS_DEBUG
+#define SOCKETS_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * ICMP_DEBUG: Enable debugging in icmp.c.
+ */
+#ifndef ICMP_DEBUG
+#define ICMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * IGMP_DEBUG: Enable debugging in igmp.c.
+ */
+#ifndef IGMP_DEBUG
+#define IGMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * INET_DEBUG: Enable debugging in inet.c.
+ */
+#ifndef INET_DEBUG
+#define INET_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * IP_DEBUG: Enable debugging for IP.
+ */
+#ifndef IP_DEBUG
+#define IP_DEBUG                        LWIP_DBG_OFF
+#endif
+
+/**
+ * IP_REASS_DEBUG: Enable debugging in ip_frag.c for both frag & reass.
+ */
+#ifndef IP_REASS_DEBUG
+#define IP_REASS_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * RAW_DEBUG: Enable debugging in raw.c.
+ */
+#ifndef RAW_DEBUG
+#define RAW_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * MEM_DEBUG: Enable debugging in mem.c.
+ */
+#ifndef MEM_DEBUG
+#define MEM_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * MEMP_DEBUG: Enable debugging in memp.c.
+ */
+#ifndef MEMP_DEBUG
+#define MEMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * SYS_DEBUG: Enable debugging in sys.c.
+ */
+#ifndef SYS_DEBUG
+#define SYS_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * TIMERS_DEBUG: Enable debugging in timers.c.
+ */
+#ifndef TIMERS_DEBUG
+#define TIMERS_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_DEBUG: Enable debugging for TCP.
+ */
+#ifndef TCP_DEBUG
+#define TCP_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_INPUT_DEBUG: Enable debugging in tcp_in.c for incoming debug.
+ */
+#ifndef TCP_INPUT_DEBUG
+#define TCP_INPUT_DEBUG                 LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_FR_DEBUG: Enable debugging in tcp_in.c for fast retransmit.
+ */
+#ifndef TCP_FR_DEBUG
+#define TCP_FR_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_RTO_DEBUG: Enable debugging in TCP for retransmit
+ * timeout.
+ */
+#ifndef TCP_RTO_DEBUG
+#define TCP_RTO_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_CWND_DEBUG: Enable debugging for TCP congestion window.
+ */
+#ifndef TCP_CWND_DEBUG
+#define TCP_CWND_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_WND_DEBUG: Enable debugging in tcp_in.c for window updating.
+ */
+#ifndef TCP_WND_DEBUG
+#define TCP_WND_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_OUTPUT_DEBUG: Enable debugging in tcp_out.c output functions.
+ */
+#ifndef TCP_OUTPUT_DEBUG
+#define TCP_OUTPUT_DEBUG                LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_RST_DEBUG: Enable debugging for TCP with the RST message.
+ */
+#ifndef TCP_RST_DEBUG
+#define TCP_RST_DEBUG                   LWIP_DBG_OFF
+#endif
+
+/**
+ * TCP_QLEN_DEBUG: Enable debugging for TCP queue lengths.
+ */
+#ifndef TCP_QLEN_DEBUG
+#define TCP_QLEN_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * UDP_DEBUG: Enable debugging in UDP.
+ */
+#ifndef UDP_DEBUG
+#define UDP_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * TCPIP_DEBUG: Enable debugging in tcpip.c.
+ */
+#ifndef TCPIP_DEBUG
+#define TCPIP_DEBUG                     LWIP_DBG_OFF
+#endif
+
+/**
+ * PPP_DEBUG: Enable debugging for PPP.
+ */
+#ifndef PPP_DEBUG
+#define PPP_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * SLIP_DEBUG: Enable debugging in slipif.c.
+ */
+#ifndef SLIP_DEBUG
+#define SLIP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * DHCP_DEBUG: Enable debugging in dhcp.c.
+ */
+#ifndef DHCP_DEBUG
+#define DHCP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * AUTOIP_DEBUG: Enable debugging in autoip.c.
+ */
+#ifndef AUTOIP_DEBUG
+#define AUTOIP_DEBUG                    LWIP_DBG_OFF
+#endif
+
+/**
+ * SNMP_MSG_DEBUG: Enable debugging for SNMP messages.
+ */
+#ifndef SNMP_MSG_DEBUG
+#define SNMP_MSG_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * SNMP_MIB_DEBUG: Enable debugging for SNMP MIBs.
+ */
+#ifndef SNMP_MIB_DEBUG
+#define SNMP_MIB_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * DNS_DEBUG: Enable debugging for DNS.
+ */
+#ifndef DNS_DEBUG
+#define DNS_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * IP6_DEBUG: Enable debugging for IPv6.
+ */
+#ifndef IP6_DEBUG
+#define IP6_DEBUG                       LWIP_DBG_OFF
+#endif
+
+#endif /* __LWIP_OPT_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/pbuf.h b/external/badvpn_dns/lwip/src/include/lwip/pbuf.h
new file mode 100644
index 0000000..4f8dca8
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/pbuf.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#ifndef __LWIP_PBUF_H__
+#define __LWIP_PBUF_H__
+
+#include "lwip/opt.h"
+#include "lwip/err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Currently, the pbuf_custom code is only needed for one specific configuration
+ * of IP_FRAG */
+#define LWIP_SUPPORT_CUSTOM_PBUF (IP_FRAG && !IP_FRAG_USES_STATIC_BUF && !LWIP_NETIF_TX_SINGLE_PBUF)
+
+/* @todo: We need a mechanism to prevent wasting memory in every pbuf
+   (TCP vs. UDP, IPv4 vs. IPv6: UDP/IPv4 packets may waste up to 28 bytes) */
+
+#define PBUF_TRANSPORT_HLEN 20
+#if LWIP_IPV6
+#define PBUF_IP_HLEN        40
+#else
+#define PBUF_IP_HLEN        20
+#endif
+
+typedef enum {
+  PBUF_TRANSPORT,
+  PBUF_IP,
+  PBUF_LINK,
+  PBUF_RAW
+} pbuf_layer;
+
+typedef enum {
+  PBUF_RAM, /* pbuf data is stored in RAM */
+  PBUF_ROM, /* pbuf data is stored in ROM */
+  PBUF_REF, /* pbuf comes from the pbuf pool */
+  PBUF_POOL /* pbuf payload refers to RAM */
+} pbuf_type;
+
+
+/** indicates this packet's data should be immediately passed to the application */
+#define PBUF_FLAG_PUSH      0x01U
+/** indicates this is a custom pbuf: pbuf_free and pbuf_header handle such a
+    a pbuf differently */
+#define PBUF_FLAG_IS_CUSTOM 0x02U
+/** indicates this pbuf is UDP multicast to be looped back */
+#define PBUF_FLAG_MCASTLOOP 0x04U
+/** indicates this pbuf was received as link-level broadcast */
+#define PBUF_FLAG_LLBCAST   0x08U
+/** indicates this pbuf was received as link-level multicast */
+#define PBUF_FLAG_LLMCAST   0x10U
+/** indicates this pbuf includes a TCP FIN flag */
+#define PBUF_FLAG_TCP_FIN   0x20U
+
+struct pbuf {
+  /** next pbuf in singly linked pbuf chain */
+  struct pbuf *next;
+
+  /** pointer to the actual data in the buffer */
+  void *payload;
+
+  /**
+   * total length of this buffer and all next buffers in chain
+   * belonging to the same packet.
+   *
+   * For non-queue packet chains this is the invariant:
+   * p->tot_len == p->len + (p->next? p->next->tot_len: 0)
+   */
+  u16_t tot_len;
+
+  /** length of this buffer */
+  u16_t len;
+
+  /** pbuf_type as u8_t instead of enum to save space */
+  u8_t /*pbuf_type*/ type;
+
+  /** misc flags */
+  u8_t flags;
+
+  /**
+   * the reference count always equals the number of pointers
+   * that refer to this pbuf. This can be pointers from an application,
+   * the stack itself, or pbuf->next pointers from a chain.
+   */
+  u16_t ref;
+};
+
+#if LWIP_SUPPORT_CUSTOM_PBUF
+/** Prototype for a function to free a custom pbuf */
+typedef void (*pbuf_free_custom_fn)(struct pbuf *p);
+
+/** A custom pbuf: like a pbuf, but following a function pointer to free it. */
+struct pbuf_custom {
+  /** The actual pbuf */
+  struct pbuf pbuf;
+  /** This function is called when pbuf_free deallocates this pbuf(_custom) */
+  pbuf_free_custom_fn custom_free_function;
+};
+#endif /* LWIP_SUPPORT_CUSTOM_PBUF */
+
+#if LWIP_TCP && TCP_QUEUE_OOSEQ
+/** Define this to 0 to prevent freeing ooseq pbufs when the PBUF_POOL is empty */
+#ifndef PBUF_POOL_FREE_OOSEQ
+#define PBUF_POOL_FREE_OOSEQ 1
+#endif /* PBUF_POOL_FREE_OOSEQ */
+#if NO_SYS && PBUF_POOL_FREE_OOSEQ
+extern volatile u8_t pbuf_free_ooseq_pending;
+void pbuf_free_ooseq(void);
+/** When not using sys_check_timeouts(), call PBUF_CHECK_FREE_OOSEQ()
+    at regular intervals from main level to check if ooseq pbufs need to be
+    freed! */
+#define PBUF_CHECK_FREE_OOSEQ() do { if(pbuf_free_ooseq_pending) { \
+  /* pbuf_alloc() reported PBUF_POOL to be empty -> try to free some \
+     ooseq queued pbufs now */ \
+  pbuf_free_ooseq(); }}while(0)
+#endif /* NO_SYS && PBUF_POOL_FREE_OOSEQ*/
+#endif /* LWIP_TCP && TCP_QUEUE_OOSEQ */
+
+/* Initializes the pbuf module. This call is empty for now, but may not be in future. */
+#define pbuf_init()
+
+struct pbuf *pbuf_alloc(pbuf_layer l, u16_t length, pbuf_type type);
+#if LWIP_SUPPORT_CUSTOM_PBUF
+struct pbuf *pbuf_alloced_custom(pbuf_layer l, u16_t length, pbuf_type type,
+                                 struct pbuf_custom *p, void *payload_mem,
+                                 u16_t payload_mem_len);
+#endif /* LWIP_SUPPORT_CUSTOM_PBUF */
+void pbuf_realloc(struct pbuf *p, u16_t size); 
+u8_t pbuf_header(struct pbuf *p, s16_t header_size);
+void pbuf_ref(struct pbuf *p);
+u8_t pbuf_free(struct pbuf *p);
+u8_t pbuf_clen(struct pbuf *p);  
+void pbuf_cat(struct pbuf *head, struct pbuf *tail);
+void pbuf_chain(struct pbuf *head, struct pbuf *tail);
+struct pbuf *pbuf_dechain(struct pbuf *p);
+err_t pbuf_copy(struct pbuf *p_to, struct pbuf *p_from);
+u16_t pbuf_copy_partial(struct pbuf *p, void *dataptr, u16_t len, u16_t offset);
+err_t pbuf_take(struct pbuf *buf, const void *dataptr, u16_t len);
+struct pbuf *pbuf_coalesce(struct pbuf *p, pbuf_layer layer);
+#if LWIP_CHECKSUM_ON_COPY
+err_t pbuf_fill_chksum(struct pbuf *p, u16_t start_offset, const void *dataptr,
+                       u16_t len, u16_t *chksum);
+#endif /* LWIP_CHECKSUM_ON_COPY */
+
+u8_t pbuf_get_at(struct pbuf* p, u16_t offset);
+u16_t pbuf_memcmp(struct pbuf* p, u16_t offset, const void* s2, u16_t n);
+u16_t pbuf_memfind(struct pbuf* p, const void* mem, u16_t mem_len, u16_t start_offset);
+u16_t pbuf_strstr(struct pbuf* p, const char* substr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_PBUF_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/raw.h b/external/badvpn_dns/lwip/src/include/lwip/raw.h
new file mode 100644
index 0000000..f0c8ed4
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/raw.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_RAW_H__
+#define __LWIP_RAW_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_RAW /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/pbuf.h"
+#include "lwip/def.h"
+#include "lwip/ip.h"
+#include "lwip/ip_addr.h"
+#include "lwip/ip6_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct raw_pcb;
+
+/** Function prototype for raw pcb receive callback functions.
+ * @param arg user supplied argument (raw_pcb.recv_arg)
+ * @param pcb the raw_pcb which received data
+ * @param p the packet buffer that was received
+ * @param addr the remote IP address from which the packet was received
+ * @return 1 if the packet was 'eaten' (aka. deleted),
+ *         0 if the packet lives on
+ * If returning 1, the callback is responsible for freeing the pbuf
+ * if it's not used any more.
+ */
+typedef u8_t (*raw_recv_fn)(void *arg, struct raw_pcb *pcb, struct pbuf *p,
+    ip_addr_t *addr);
+
+#if LWIP_IPV6
+/** Function prototype for raw pcb IPv6 receive callback functions.
+ * @param arg user supplied argument (raw_pcb.recv_arg)
+ * @param pcb the raw_pcb which received data
+ * @param p the packet buffer that was received
+ * @param addr the remote IPv6 address from which the packet was received
+ * @return 1 if the packet was 'eaten' (aka. deleted),
+ *         0 if the packet lives on
+ * If returning 1, the callback is responsible for freeing the pbuf
+ * if it's not used any more.
+ */
+typedef u8_t (*raw_recv_ip6_fn)(void *arg, struct raw_pcb *pcb, struct pbuf *p,
+    ip6_addr_t *addr);
+#endif /* LWIP_IPV6 */
+
+#if LWIP_IPV6
+#define RAW_PCB_RECV_IP6  raw_recv_ip6_fn ip6;
+#else
+#define RAW_PCB_RECV_IP6
+#endif /* LWIP_IPV6 */
+
+struct raw_pcb {
+  /* Common members of all PCB types */
+  IP_PCB;
+
+  struct raw_pcb *next;
+
+  u8_t protocol;
+
+  /** receive callback function */
+  union {
+    raw_recv_fn ip4;
+    RAW_PCB_RECV_IP6
+  } recv;
+  /* user-supplied argument for the recv callback */
+  void *recv_arg;
+};
+
+/* The following functions is the application layer interface to the
+   RAW code. */
+struct raw_pcb * raw_new        (u8_t proto);
+void             raw_remove     (struct raw_pcb *pcb);
+err_t            raw_bind       (struct raw_pcb *pcb, ip_addr_t *ipaddr);
+err_t            raw_connect    (struct raw_pcb *pcb, ip_addr_t *ipaddr);
+
+void             raw_recv       (struct raw_pcb *pcb, raw_recv_fn recv, void *recv_arg);
+err_t            raw_sendto     (struct raw_pcb *pcb, struct pbuf *p, ip_addr_t *ipaddr);
+err_t            raw_send       (struct raw_pcb *pcb, struct pbuf *p);
+
+#if LWIP_IPV6
+struct raw_pcb * raw_new_ip6   (u8_t proto);
+#define          raw_bind_ip6(pcb, ip6addr) raw_bind(pcb, ip6_2_ip(ip6addr))
+#define          raw_connect_ip6(pcb, ip6addr) raw_connect(pcb, ip6_2_ip(ip6addr))
+#define          raw_recv_ip6(pcb, recv_ip6_fn, recv_arg) raw_recv(pcb, (raw_recv_fn)recv_ip6_fn, recv_arg)
+#define          raw_sendto_ip6(pcb, pbuf, ip6addr) raw_sendto(pcb, pbuf, ip6_2_ip(ip6addr))
+#endif /* LWIP_IPV6 */
+
+/* The following functions are the lower layer interface to RAW. */
+u8_t             raw_input      (struct pbuf *p, struct netif *inp);
+#define raw_init() /* Compatibility define, not init needed. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_RAW */
+
+#endif /* __LWIP_RAW_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/sio.h b/external/badvpn_dns/lwip/src/include/lwip/sio.h
new file mode 100644
index 0000000..28ae2f2
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/sio.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ */
+
+/*
+ * This is the interface to the platform specific serial IO module
+ * It needs to be implemented by those platforms which need SLIP or PPP
+ */
+
+#ifndef __SIO_H__
+#define __SIO_H__
+
+#include "lwip/arch.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* If you want to define sio_fd_t elsewhere or differently,
+   define this in your cc.h file. */
+#ifndef __sio_fd_t_defined
+typedef void * sio_fd_t;
+#endif
+
+/* The following functions can be defined to something else in your cc.h file
+   or be implemented in your custom sio.c file. */
+
+#ifndef sio_open
+/**
+ * Opens a serial device for communication.
+ * 
+ * @param devnum device number
+ * @return handle to serial device if successful, NULL otherwise
+ */
+sio_fd_t sio_open(u8_t devnum);
+#endif
+
+#ifndef sio_send
+/**
+ * Sends a single character to the serial device.
+ * 
+ * @param c character to send
+ * @param fd serial device handle
+ * 
+ * @note This function will block until the character can be sent.
+ */
+void sio_send(u8_t c, sio_fd_t fd);
+#endif
+
+#ifndef sio_recv
+/**
+ * Receives a single character from the serial device.
+ * 
+ * @param fd serial device handle
+ * 
+ * @note This function will block until a character is received.
+ */
+u8_t sio_recv(sio_fd_t fd);
+#endif
+
+#ifndef sio_read
+/**
+ * Reads from the serial device.
+ * 
+ * @param fd serial device handle
+ * @param data pointer to data buffer for receiving
+ * @param len maximum length (in bytes) of data to receive
+ * @return number of bytes actually received - may be 0 if aborted by sio_read_abort
+ * 
+ * @note This function will block until data can be received. The blocking
+ * can be cancelled by calling sio_read_abort().
+ */
+u32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len);
+#endif
+
+#ifndef sio_tryread
+/**
+ * Tries to read from the serial device. Same as sio_read but returns
+ * immediately if no data is available and never blocks.
+ * 
+ * @param fd serial device handle
+ * @param data pointer to data buffer for receiving
+ * @param len maximum length (in bytes) of data to receive
+ * @return number of bytes actually received
+ */
+u32_t sio_tryread(sio_fd_t fd, u8_t *data, u32_t len);
+#endif
+
+#ifndef sio_write
+/**
+ * Writes to the serial device.
+ * 
+ * @param fd serial device handle
+ * @param data pointer to data to send
+ * @param len length (in bytes) of data to send
+ * @return number of bytes actually sent
+ * 
+ * @note This function will block until all data can be sent.
+ */
+u32_t sio_write(sio_fd_t fd, u8_t *data, u32_t len);
+#endif
+
+#ifndef sio_read_abort
+/**
+ * Aborts a blocking sio_read() call.
+ * 
+ * @param fd serial device handle
+ */
+void sio_read_abort(sio_fd_t fd);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SIO_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/snmp.h b/external/badvpn_dns/lwip/src/include/lwip/snmp.h
new file mode 100644
index 0000000..2ed043d
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/snmp.h
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2001, 2002 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+ * Copyright (c) 2001, 2002 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Leon Woestenberg <leon.woestenberg@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_SNMP_H__
+#define __LWIP_SNMP_H__
+
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lwip/ip_addr.h"
+
+struct udp_pcb;
+struct netif;
+
+/**
+ * @see RFC1213, "MIB-II, 6. Definitions"
+ */
+enum snmp_ifType {
+  snmp_ifType_other=1,                /* none of the following */
+  snmp_ifType_regular1822,
+  snmp_ifType_hdh1822,
+  snmp_ifType_ddn_x25,
+  snmp_ifType_rfc877_x25,
+  snmp_ifType_ethernet_csmacd,
+  snmp_ifType_iso88023_csmacd,
+  snmp_ifType_iso88024_tokenBus,
+  snmp_ifType_iso88025_tokenRing,
+  snmp_ifType_iso88026_man,
+  snmp_ifType_starLan,
+  snmp_ifType_proteon_10Mbit,
+  snmp_ifType_proteon_80Mbit,
+  snmp_ifType_hyperchannel,
+  snmp_ifType_fddi,
+  snmp_ifType_lapb,
+  snmp_ifType_sdlc,
+  snmp_ifType_ds1,                    /* T-1 */
+  snmp_ifType_e1,                     /* european equiv. of T-1 */
+  snmp_ifType_basicISDN,
+  snmp_ifType_primaryISDN,            /* proprietary serial */
+  snmp_ifType_propPointToPointSerial,
+  snmp_ifType_ppp,
+  snmp_ifType_softwareLoopback,
+  snmp_ifType_eon,                    /* CLNP over IP [11] */
+  snmp_ifType_ethernet_3Mbit,
+  snmp_ifType_nsip,                   /* XNS over IP */
+  snmp_ifType_slip,                   /* generic SLIP */
+  snmp_ifType_ultra,                  /* ULTRA technologies */
+  snmp_ifType_ds3,                    /* T-3 */
+  snmp_ifType_sip,                    /* SMDS */
+  snmp_ifType_frame_relay
+};
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+/** SNMP "sysuptime" Interval */
+#define SNMP_SYSUPTIME_INTERVAL 10
+
+/** fixed maximum length for object identifier type */
+#define LWIP_SNMP_OBJ_ID_LEN 32
+
+/** internal object identifier representation */
+struct snmp_obj_id
+{
+  u8_t len;
+  s32_t id[LWIP_SNMP_OBJ_ID_LEN];
+};
+
+/* system */
+void snmp_set_sysdesr(u8_t* str, u8_t* len);
+void snmp_set_sysobjid(struct snmp_obj_id *oid);
+void snmp_get_sysobjid_ptr(struct snmp_obj_id **oid);
+void snmp_inc_sysuptime(void);
+void snmp_add_sysuptime(u32_t value);
+void snmp_get_sysuptime(u32_t *value);
+void snmp_set_syscontact(u8_t *ocstr, u8_t *ocstrlen);
+void snmp_set_sysname(u8_t *ocstr, u8_t *ocstrlen);
+void snmp_set_syslocation(u8_t *ocstr, u8_t *ocstrlen);
+
+/* network interface */
+void snmp_add_ifinoctets(struct netif *ni, u32_t value); 
+void snmp_inc_ifinucastpkts(struct netif *ni);
+void snmp_inc_ifinnucastpkts(struct netif *ni);
+void snmp_inc_ifindiscards(struct netif *ni);
+void snmp_add_ifoutoctets(struct netif *ni, u32_t value);
+void snmp_inc_ifoutucastpkts(struct netif *ni);
+void snmp_inc_ifoutnucastpkts(struct netif *ni);
+void snmp_inc_ifoutdiscards(struct netif *ni);
+void snmp_inc_iflist(void);
+void snmp_dec_iflist(void);
+
+/* ARP (for atTable and ipNetToMediaTable) */
+void snmp_insert_arpidx_tree(struct netif *ni, ip_addr_t *ip);
+void snmp_delete_arpidx_tree(struct netif *ni, ip_addr_t *ip);
+
+/* IP */
+void snmp_inc_ipinreceives(void);
+void snmp_inc_ipinhdrerrors(void);
+void snmp_inc_ipinaddrerrors(void);
+void snmp_inc_ipforwdatagrams(void);
+void snmp_inc_ipinunknownprotos(void);
+void snmp_inc_ipindiscards(void);
+void snmp_inc_ipindelivers(void);
+void snmp_inc_ipoutrequests(void);
+void snmp_inc_ipoutdiscards(void);
+void snmp_inc_ipoutnoroutes(void);
+void snmp_inc_ipreasmreqds(void);
+void snmp_inc_ipreasmoks(void);
+void snmp_inc_ipreasmfails(void);
+void snmp_inc_ipfragoks(void);
+void snmp_inc_ipfragfails(void);
+void snmp_inc_ipfragcreates(void);
+void snmp_inc_iproutingdiscards(void);
+void snmp_insert_ipaddridx_tree(struct netif *ni);
+void snmp_delete_ipaddridx_tree(struct netif *ni);
+void snmp_insert_iprteidx_tree(u8_t dflt, struct netif *ni);
+void snmp_delete_iprteidx_tree(u8_t dflt, struct netif *ni);
+
+/* ICMP */
+void snmp_inc_icmpinmsgs(void);
+void snmp_inc_icmpinerrors(void);
+void snmp_inc_icmpindestunreachs(void);
+void snmp_inc_icmpintimeexcds(void);
+void snmp_inc_icmpinparmprobs(void);
+void snmp_inc_icmpinsrcquenchs(void);
+void snmp_inc_icmpinredirects(void);
+void snmp_inc_icmpinechos(void);
+void snmp_inc_icmpinechoreps(void);
+void snmp_inc_icmpintimestamps(void);
+void snmp_inc_icmpintimestampreps(void);
+void snmp_inc_icmpinaddrmasks(void);
+void snmp_inc_icmpinaddrmaskreps(void);
+void snmp_inc_icmpoutmsgs(void);
+void snmp_inc_icmpouterrors(void);
+void snmp_inc_icmpoutdestunreachs(void);
+void snmp_inc_icmpouttimeexcds(void);
+void snmp_inc_icmpoutparmprobs(void);
+void snmp_inc_icmpoutsrcquenchs(void);
+void snmp_inc_icmpoutredirects(void); 
+void snmp_inc_icmpoutechos(void);
+void snmp_inc_icmpoutechoreps(void);
+void snmp_inc_icmpouttimestamps(void);
+void snmp_inc_icmpouttimestampreps(void);
+void snmp_inc_icmpoutaddrmasks(void);
+void snmp_inc_icmpoutaddrmaskreps(void);
+
+/* TCP */
+void snmp_inc_tcpactiveopens(void);
+void snmp_inc_tcppassiveopens(void);
+void snmp_inc_tcpattemptfails(void);
+void snmp_inc_tcpestabresets(void);
+void snmp_inc_tcpinsegs(void);
+void snmp_inc_tcpoutsegs(void);
+void snmp_inc_tcpretranssegs(void);
+void snmp_inc_tcpinerrs(void);
+void snmp_inc_tcpoutrsts(void);
+
+/* UDP */
+void snmp_inc_udpindatagrams(void);
+void snmp_inc_udpnoports(void);
+void snmp_inc_udpinerrors(void);
+void snmp_inc_udpoutdatagrams(void);
+void snmp_insert_udpidx_tree(struct udp_pcb *pcb);
+void snmp_delete_udpidx_tree(struct udp_pcb *pcb);
+
+/* SNMP */
+void snmp_inc_snmpinpkts(void);
+void snmp_inc_snmpoutpkts(void);
+void snmp_inc_snmpinbadversions(void);
+void snmp_inc_snmpinbadcommunitynames(void);
+void snmp_inc_snmpinbadcommunityuses(void);
+void snmp_inc_snmpinasnparseerrs(void);
+void snmp_inc_snmpintoobigs(void);
+void snmp_inc_snmpinnosuchnames(void);
+void snmp_inc_snmpinbadvalues(void);
+void snmp_inc_snmpinreadonlys(void);
+void snmp_inc_snmpingenerrs(void);
+void snmp_add_snmpintotalreqvars(u8_t value);
+void snmp_add_snmpintotalsetvars(u8_t value);
+void snmp_inc_snmpingetrequests(void);
+void snmp_inc_snmpingetnexts(void);
+void snmp_inc_snmpinsetrequests(void);
+void snmp_inc_snmpingetresponses(void);
+void snmp_inc_snmpintraps(void);
+void snmp_inc_snmpouttoobigs(void);
+void snmp_inc_snmpoutnosuchnames(void);
+void snmp_inc_snmpoutbadvalues(void);
+void snmp_inc_snmpoutgenerrs(void);
+void snmp_inc_snmpoutgetrequests(void);
+void snmp_inc_snmpoutgetnexts(void);
+void snmp_inc_snmpoutsetrequests(void);
+void snmp_inc_snmpoutgetresponses(void);
+void snmp_inc_snmpouttraps(void);
+void snmp_get_snmpgrpid_ptr(struct snmp_obj_id **oid);
+void snmp_set_snmpenableauthentraps(u8_t *value);
+void snmp_get_snmpenableauthentraps(u8_t *value);
+
+/* LWIP_SNMP support not available */
+/* define everything to be empty */
+#else
+
+/* system */
+#define snmp_set_sysdesr(str, len)
+#define snmp_set_sysobjid(oid);
+#define snmp_get_sysobjid_ptr(oid)
+#define snmp_inc_sysuptime()
+#define snmp_add_sysuptime(value)
+#define snmp_get_sysuptime(value)
+#define snmp_set_syscontact(ocstr, ocstrlen);
+#define snmp_set_sysname(ocstr, ocstrlen);
+#define snmp_set_syslocation(ocstr, ocstrlen);
+
+/* network interface */
+#define snmp_add_ifinoctets(ni,value) 
+#define snmp_inc_ifinucastpkts(ni)
+#define snmp_inc_ifinnucastpkts(ni)
+#define snmp_inc_ifindiscards(ni)
+#define snmp_add_ifoutoctets(ni,value)
+#define snmp_inc_ifoutucastpkts(ni)
+#define snmp_inc_ifoutnucastpkts(ni)
+#define snmp_inc_ifoutdiscards(ni)
+#define snmp_inc_iflist()
+#define snmp_dec_iflist()
+
+/* ARP */
+#define snmp_insert_arpidx_tree(ni,ip)
+#define snmp_delete_arpidx_tree(ni,ip)
+
+/* IP */
+#define snmp_inc_ipinreceives()
+#define snmp_inc_ipinhdrerrors()
+#define snmp_inc_ipinaddrerrors()
+#define snmp_inc_ipforwdatagrams()
+#define snmp_inc_ipinunknownprotos()
+#define snmp_inc_ipindiscards()
+#define snmp_inc_ipindelivers()
+#define snmp_inc_ipoutrequests()
+#define snmp_inc_ipoutdiscards()
+#define snmp_inc_ipoutnoroutes()
+#define snmp_inc_ipreasmreqds()
+#define snmp_inc_ipreasmoks()
+#define snmp_inc_ipreasmfails()
+#define snmp_inc_ipfragoks()
+#define snmp_inc_ipfragfails()
+#define snmp_inc_ipfragcreates()
+#define snmp_inc_iproutingdiscards()
+#define snmp_insert_ipaddridx_tree(ni)
+#define snmp_delete_ipaddridx_tree(ni)
+#define snmp_insert_iprteidx_tree(dflt, ni)
+#define snmp_delete_iprteidx_tree(dflt, ni)
+
+/* ICMP */
+#define snmp_inc_icmpinmsgs()
+#define snmp_inc_icmpinerrors() 
+#define snmp_inc_icmpindestunreachs() 
+#define snmp_inc_icmpintimeexcds()
+#define snmp_inc_icmpinparmprobs() 
+#define snmp_inc_icmpinsrcquenchs() 
+#define snmp_inc_icmpinredirects() 
+#define snmp_inc_icmpinechos() 
+#define snmp_inc_icmpinechoreps()
+#define snmp_inc_icmpintimestamps() 
+#define snmp_inc_icmpintimestampreps()
+#define snmp_inc_icmpinaddrmasks()
+#define snmp_inc_icmpinaddrmaskreps()
+#define snmp_inc_icmpoutmsgs()
+#define snmp_inc_icmpouterrors()
+#define snmp_inc_icmpoutdestunreachs() 
+#define snmp_inc_icmpouttimeexcds() 
+#define snmp_inc_icmpoutparmprobs()
+#define snmp_inc_icmpoutsrcquenchs()
+#define snmp_inc_icmpoutredirects() 
+#define snmp_inc_icmpoutechos() 
+#define snmp_inc_icmpoutechoreps()
+#define snmp_inc_icmpouttimestamps()
+#define snmp_inc_icmpouttimestampreps()
+#define snmp_inc_icmpoutaddrmasks()
+#define snmp_inc_icmpoutaddrmaskreps()
+/* TCP */
+#define snmp_inc_tcpactiveopens()
+#define snmp_inc_tcppassiveopens()
+#define snmp_inc_tcpattemptfails()
+#define snmp_inc_tcpestabresets()
+#define snmp_inc_tcpinsegs()
+#define snmp_inc_tcpoutsegs()
+#define snmp_inc_tcpretranssegs()
+#define snmp_inc_tcpinerrs()
+#define snmp_inc_tcpoutrsts()
+
+/* UDP */
+#define snmp_inc_udpindatagrams()
+#define snmp_inc_udpnoports()
+#define snmp_inc_udpinerrors()
+#define snmp_inc_udpoutdatagrams()
+#define snmp_insert_udpidx_tree(pcb)
+#define snmp_delete_udpidx_tree(pcb)
+
+/* SNMP */
+#define snmp_inc_snmpinpkts()
+#define snmp_inc_snmpoutpkts()
+#define snmp_inc_snmpinbadversions()
+#define snmp_inc_snmpinbadcommunitynames()
+#define snmp_inc_snmpinbadcommunityuses()
+#define snmp_inc_snmpinasnparseerrs()
+#define snmp_inc_snmpintoobigs()
+#define snmp_inc_snmpinnosuchnames()
+#define snmp_inc_snmpinbadvalues()
+#define snmp_inc_snmpinreadonlys()
+#define snmp_inc_snmpingenerrs()
+#define snmp_add_snmpintotalreqvars(value)
+#define snmp_add_snmpintotalsetvars(value)
+#define snmp_inc_snmpingetrequests()
+#define snmp_inc_snmpingetnexts()
+#define snmp_inc_snmpinsetrequests()
+#define snmp_inc_snmpingetresponses()
+#define snmp_inc_snmpintraps()
+#define snmp_inc_snmpouttoobigs()
+#define snmp_inc_snmpoutnosuchnames()
+#define snmp_inc_snmpoutbadvalues()
+#define snmp_inc_snmpoutgenerrs()
+#define snmp_inc_snmpoutgetrequests()
+#define snmp_inc_snmpoutgetnexts()
+#define snmp_inc_snmpoutsetrequests()
+#define snmp_inc_snmpoutgetresponses()
+#define snmp_inc_snmpouttraps()
+#define snmp_get_snmpgrpid_ptr(oid)
+#define snmp_set_snmpenableauthentraps(value)
+#define snmp_get_snmpenableauthentraps(value)
+
+#endif /* LWIP_SNMP */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_SNMP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/snmp_asn1.h b/external/badvpn_dns/lwip/src/include/lwip/snmp_asn1.h
new file mode 100644
index 0000000..605fa3f
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/snmp_asn1.h
@@ -0,0 +1,101 @@
+/**
+ * @file
+ * Abstract Syntax Notation One (ISO 8824, 8825) codec.
+ */
+ 
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Christiaan Simons <christiaan.simons@xxxxxxx>
+ */
+
+#ifndef __LWIP_SNMP_ASN1_H__
+#define __LWIP_SNMP_ASN1_H__
+
+#include "lwip/opt.h"
+#include "lwip/err.h"
+#include "lwip/pbuf.h"
+#include "lwip/snmp.h"
+
+#if LWIP_SNMP
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SNMP_ASN1_UNIV   (0)    /* (!0x80 | !0x40) */
+#define SNMP_ASN1_APPLIC (0x40) /* (!0x80 |  0x40) */
+#define SNMP_ASN1_CONTXT (0x80) /* ( 0x80 | !0x40) */
+
+#define SNMP_ASN1_CONSTR (0x20) /* ( 0x20) */
+#define SNMP_ASN1_PRIMIT (0)    /* (!0x20) */
+
+/* universal tags */
+#define SNMP_ASN1_INTEG  2
+#define SNMP_ASN1_OC_STR 4
+#define SNMP_ASN1_NUL    5
+#define SNMP_ASN1_OBJ_ID 6
+#define SNMP_ASN1_SEQ    16
+
+/* application specific (SNMP) tags */
+#define SNMP_ASN1_IPADDR 0    /* octet string size(4) */
+#define SNMP_ASN1_COUNTER 1   /* u32_t */
+#define SNMP_ASN1_GAUGE 2     /* u32_t */
+#define SNMP_ASN1_TIMETICKS 3 /* u32_t */
+#define SNMP_ASN1_OPAQUE 4    /* octet string */
+
+/* context specific (SNMP) tags */
+#define SNMP_ASN1_PDU_GET_REQ 0
+#define SNMP_ASN1_PDU_GET_NEXT_REQ 1
+#define SNMP_ASN1_PDU_GET_RESP 2
+#define SNMP_ASN1_PDU_SET_REQ 3
+#define SNMP_ASN1_PDU_TRAP 4
+
+err_t snmp_asn1_dec_type(struct pbuf *p, u16_t ofs, u8_t *type);
+err_t snmp_asn1_dec_length(struct pbuf *p, u16_t ofs, u8_t *octets_used, u16_t *length);
+err_t snmp_asn1_dec_u32t(struct pbuf *p, u16_t ofs, u16_t len, u32_t *value);
+err_t snmp_asn1_dec_s32t(struct pbuf *p, u16_t ofs, u16_t len, s32_t *value);
+err_t snmp_asn1_dec_oid(struct pbuf *p, u16_t ofs, u16_t len, struct snmp_obj_id *oid);
+err_t snmp_asn1_dec_raw(struct pbuf *p, u16_t ofs, u16_t len, u16_t raw_len, u8_t *raw);
+
+void snmp_asn1_enc_length_cnt(u16_t length, u8_t *octets_needed);
+void snmp_asn1_enc_u32t_cnt(u32_t value, u16_t *octets_needed);
+void snmp_asn1_enc_s32t_cnt(s32_t value, u16_t *octets_needed);
+void snmp_asn1_enc_oid_cnt(u8_t ident_len, s32_t *ident, u16_t *octets_needed);
+err_t snmp_asn1_enc_type(struct pbuf *p, u16_t ofs, u8_t type);
+err_t snmp_asn1_enc_length(struct pbuf *p, u16_t ofs, u16_t length);
+err_t snmp_asn1_enc_u32t(struct pbuf *p, u16_t ofs, u16_t octets_needed, u32_t value);
+err_t snmp_asn1_enc_s32t(struct pbuf *p, u16_t ofs, u16_t octets_needed, s32_t value);
+err_t snmp_asn1_enc_oid(struct pbuf *p, u16_t ofs, u8_t ident_len, s32_t *ident);
+err_t snmp_asn1_enc_raw(struct pbuf *p, u16_t ofs, u16_t raw_len, u8_t *raw);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* __LWIP_SNMP_ASN1_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/snmp_msg.h b/external/badvpn_dns/lwip/src/include/lwip/snmp_msg.h
new file mode 100644
index 0000000..1183e3a
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/snmp_msg.h
@@ -0,0 +1,315 @@
+/**
+ * @file
+ * SNMP Agent message handling structures.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Christiaan Simons <christiaan.simons@xxxxxxx>
+ */
+
+#ifndef __LWIP_SNMP_MSG_H__
+#define __LWIP_SNMP_MSG_H__
+
+#include "lwip/opt.h"
+#include "lwip/snmp.h"
+#include "lwip/snmp_structs.h"
+#include "lwip/ip_addr.h"
+#include "lwip/err.h"
+
+#if LWIP_SNMP
+
+#if SNMP_PRIVATE_MIB
+/* When using a private MIB, you have to create a file 'private_mib.h' that contains
+ * a 'struct mib_array_node mib_private' which contains your MIB. */
+#include "private_mib.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The listen port of the SNMP agent. Clients have to make their requests to
+   this port. Most standard clients won't work if you change this! */
+#ifndef SNMP_IN_PORT
+#define SNMP_IN_PORT 161
+#endif
+/* The remote port the SNMP agent sends traps to. Most standard trap sinks won't
+   work if you change this! */
+#ifndef SNMP_TRAP_PORT
+#define SNMP_TRAP_PORT 162
+#endif
+
+#define SNMP_ES_NOERROR 0
+#define SNMP_ES_TOOBIG 1
+#define SNMP_ES_NOSUCHNAME 2
+#define SNMP_ES_BADVALUE 3
+#define SNMP_ES_READONLY 4
+#define SNMP_ES_GENERROR 5
+
+#define SNMP_GENTRAP_COLDSTART 0
+#define SNMP_GENTRAP_WARMSTART 1
+#define SNMP_GENTRAP_AUTHFAIL 4
+#define SNMP_GENTRAP_ENTERPRISESPC 6
+
+struct snmp_varbind
+{
+  /* next pointer, NULL for last in list */
+  struct snmp_varbind *next;
+  /* previous pointer, NULL for first in list */
+  struct snmp_varbind *prev;
+
+  /* object identifier length (in s32_t) */
+  u8_t ident_len;
+  /* object identifier array */
+  s32_t *ident;
+
+  /* object value ASN1 type */
+  u8_t value_type;
+  /* object value length (in u8_t) */
+  u8_t value_len;
+  /* object value */
+  void *value;
+
+  /* encoding varbind seq length length */
+  u8_t seqlenlen;
+  /* encoding object identifier length length */
+  u8_t olenlen;
+  /* encoding object value length length */
+  u8_t vlenlen;
+  /* encoding varbind seq length */
+  u16_t seqlen;
+  /* encoding object identifier length */
+  u16_t olen;
+  /* encoding object value length */
+  u16_t vlen;
+};
+
+struct snmp_varbind_root
+{
+  struct snmp_varbind *head;
+  struct snmp_varbind *tail;
+  /* number of variable bindings in list */
+  u8_t count;
+  /* encoding varbind-list seq length length */
+  u8_t seqlenlen;
+  /* encoding varbind-list seq length */
+  u16_t seqlen;
+};
+
+/** output response message header length fields */
+struct snmp_resp_header_lengths
+{
+  /* encoding error-index length length */
+  u8_t erridxlenlen;
+  /* encoding error-status length length */
+  u8_t errstatlenlen;
+  /* encoding request id length length */
+  u8_t ridlenlen;
+  /* encoding pdu length length */
+  u8_t pdulenlen;
+  /* encoding community length length */
+  u8_t comlenlen;
+  /* encoding version length length */
+  u8_t verlenlen;
+  /* encoding sequence length length */
+  u8_t seqlenlen;
+
+  /* encoding error-index length */
+  u16_t erridxlen;
+  /* encoding error-status length */
+  u16_t errstatlen;
+  /* encoding request id length */
+  u16_t ridlen;
+  /* encoding pdu length */
+  u16_t pdulen;
+  /* encoding community length */
+  u16_t comlen;
+  /* encoding version length */
+  u16_t verlen;
+  /* encoding sequence length */
+  u16_t seqlen;
+};
+
+/** output response message header length fields */
+struct snmp_trap_header_lengths
+{
+  /* encoding timestamp length length */
+  u8_t tslenlen;
+  /* encoding specific-trap length length */
+  u8_t strplenlen;
+  /* encoding generic-trap length length */
+  u8_t gtrplenlen;
+  /* encoding agent-addr length length */
+  u8_t aaddrlenlen;
+  /* encoding enterprise-id length length */
+  u8_t eidlenlen;
+  /* encoding pdu length length */
+  u8_t pdulenlen;
+  /* encoding community length length */
+  u8_t comlenlen;
+  /* encoding version length length */
+  u8_t verlenlen;
+  /* encoding sequence length length */
+  u8_t seqlenlen;
+
+  /* encoding timestamp length */
+  u16_t tslen;
+  /* encoding specific-trap length */
+  u16_t strplen;
+  /* encoding generic-trap length */
+  u16_t gtrplen;
+  /* encoding agent-addr length */
+  u16_t aaddrlen;
+  /* encoding enterprise-id length */
+  u16_t eidlen;
+  /* encoding pdu length */
+  u16_t pdulen;
+  /* encoding community length */
+  u16_t comlen;
+  /* encoding version length */
+  u16_t verlen;
+  /* encoding sequence length */
+  u16_t seqlen;
+};
+
+/* Accepting new SNMP messages. */
+#define SNMP_MSG_EMPTY                 0
+/* Search for matching object for variable binding. */
+#define SNMP_MSG_SEARCH_OBJ            1
+/* Perform SNMP operation on in-memory object.
+   Pass-through states, for symmetry only. */
+#define SNMP_MSG_INTERNAL_GET_OBJDEF   2
+#define SNMP_MSG_INTERNAL_GET_VALUE    3
+#define SNMP_MSG_INTERNAL_SET_TEST     4
+#define SNMP_MSG_INTERNAL_GET_OBJDEF_S 5
+#define SNMP_MSG_INTERNAL_SET_VALUE    6
+/* Perform SNMP operation on object located externally.
+   In theory this could be used for building a proxy agent.
+   Practical use is for an enterprise spc. app. gateway. */
+#define SNMP_MSG_EXTERNAL_GET_OBJDEF   7
+#define SNMP_MSG_EXTERNAL_GET_VALUE    8
+#define SNMP_MSG_EXTERNAL_SET_TEST     9
+#define SNMP_MSG_EXTERNAL_GET_OBJDEF_S 10
+#define SNMP_MSG_EXTERNAL_SET_VALUE    11
+
+#define SNMP_COMMUNITY_STR_LEN 64
+struct snmp_msg_pstat
+{
+  /* lwIP local port (161) binding */
+  struct udp_pcb *pcb;
+  /* source IP address */
+  ip_addr_t sip;
+  /* source UDP port */
+  u16_t sp;
+  /* request type */
+  u8_t rt;
+  /* request ID */
+  s32_t rid;
+  /* error status */
+  s32_t error_status;
+  /* error index */
+  s32_t error_index;
+  /* community name (zero terminated) */
+  u8_t community[SNMP_COMMUNITY_STR_LEN + 1];
+  /* community string length (exclusive zero term) */
+  u8_t com_strlen;
+  /* one out of MSG_EMPTY, MSG_DEMUX, MSG_INTERNAL, MSG_EXTERNAL_x */
+  u8_t state;
+  /* saved arguments for MSG_EXTERNAL_x */
+  struct mib_external_node *ext_mib_node;
+  struct snmp_name_ptr ext_name_ptr;
+  struct obj_def ext_object_def;
+  struct snmp_obj_id ext_oid;
+  /* index into input variable binding list */
+  u8_t vb_idx;
+  /* ptr into input variable binding list */
+  struct snmp_varbind *vb_ptr;
+  /* list of variable bindings from input */
+  struct snmp_varbind_root invb;
+  /* list of variable bindings to output */
+  struct snmp_varbind_root outvb;
+  /* output response lengths used in ASN encoding */
+  struct snmp_resp_header_lengths rhl;
+};
+
+struct snmp_msg_trap
+{
+  /* lwIP local port (161) binding */
+  struct udp_pcb *pcb;
+  /* destination IP address in network order */
+  ip_addr_t dip;
+
+  /* source enterprise ID (sysObjectID) */
+  struct snmp_obj_id *enterprise;
+  /* source IP address, raw network order format */
+  u8_t sip_raw[4];
+  /* generic trap code */
+  u32_t gen_trap;
+  /* specific trap code */
+  u32_t spc_trap;
+  /* timestamp */
+  u32_t ts;
+  /* list of variable bindings to output */
+  struct snmp_varbind_root outvb;
+  /* output trap lengths used in ASN encoding */
+  struct snmp_trap_header_lengths thl;
+};
+
+/** Agent Version constant, 0 = v1 oddity */
+extern const s32_t snmp_version;
+/** Agent default "public" community string */
+extern const char snmp_publiccommunity[7];
+
+extern struct snmp_msg_trap trap_msg;
+
+/** Agent setup, start listening to port 161. */
+void snmp_init(void);
+void snmp_trap_dst_enable(u8_t dst_idx, u8_t enable);
+void snmp_trap_dst_ip_set(u8_t dst_idx, ip_addr_t *dst);
+
+/** Varbind-list functions. */
+struct snmp_varbind* snmp_varbind_alloc(struct snmp_obj_id *oid, u8_t type, u8_t len);
+void snmp_varbind_free(struct snmp_varbind *vb);
+void snmp_varbind_list_free(struct snmp_varbind_root *root);
+void snmp_varbind_tail_add(struct snmp_varbind_root *root, struct snmp_varbind *vb);
+struct snmp_varbind* snmp_varbind_tail_remove(struct snmp_varbind_root *root);
+
+/** Handle an internal (recv) or external (private response) event. */
+void snmp_msg_event(u8_t request_id);
+err_t snmp_send_response(struct snmp_msg_pstat *m_stat);
+err_t snmp_send_trap(s8_t generic_trap, struct snmp_obj_id *eoid, s32_t specific_trap);
+void snmp_coldstart_trap(void);
+void snmp_authfail_trap(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* __LWIP_SNMP_MSG_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/snmp_structs.h b/external/badvpn_dns/lwip/src/include/lwip/snmp_structs.h
new file mode 100644
index 0000000..0d3b46a
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/snmp_structs.h
@@ -0,0 +1,268 @@
+/**
+ * @file
+ * Generic MIB tree structures.
+ *
+ * @todo namespace prefixes
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * Author: Christiaan Simons <christiaan.simons@xxxxxxx>
+ */
+
+#ifndef __LWIP_SNMP_STRUCTS_H__
+#define __LWIP_SNMP_STRUCTS_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/snmp.h"
+
+#if SNMP_PRIVATE_MIB
+/* When using a private MIB, you have to create a file 'private_mib.h' that contains
+ * a 'struct mib_array_node mib_private' which contains your MIB. */
+#include "private_mib.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* MIB object instance */
+#define MIB_OBJECT_NONE 0 
+#define MIB_OBJECT_SCALAR 1
+#define MIB_OBJECT_TAB 2
+
+/* MIB access types */
+#define MIB_ACCESS_READ   1
+#define MIB_ACCESS_WRITE  2
+
+/* MIB object access */
+#define MIB_OBJECT_READ_ONLY      MIB_ACCESS_READ
+#define MIB_OBJECT_READ_WRITE     (MIB_ACCESS_READ | MIB_ACCESS_WRITE)
+#define MIB_OBJECT_WRITE_ONLY     MIB_ACCESS_WRITE
+#define MIB_OBJECT_NOT_ACCESSIBLE 0
+
+/** object definition returned by (get_object_def)() */
+struct obj_def
+{
+  /* MIB_OBJECT_NONE (0), MIB_OBJECT_SCALAR (1), MIB_OBJECT_TAB (2) */
+  u8_t instance;
+  /* 0 read-only, 1 read-write, 2 write-only, 3 not-accessible */
+  u8_t access;
+  /* ASN type for this object */
+  u8_t asn_type;
+  /* value length (host length) */
+  u16_t v_len;
+  /* length of instance part of supplied object identifier */
+  u8_t  id_inst_len;
+  /* instance part of supplied object identifier */
+  s32_t *id_inst_ptr;
+};
+
+struct snmp_name_ptr
+{
+  u8_t ident_len;
+  s32_t *ident;
+};
+
+/** MIB const scalar (.0) node */
+#define MIB_NODE_SC 0x01
+/** MIB const array node */
+#define MIB_NODE_AR 0x02
+/** MIB array node (mem_malloced from RAM) */
+#define MIB_NODE_RA 0x03
+/** MIB list root node (mem_malloced from RAM) */
+#define MIB_NODE_LR 0x04
+/** MIB node for external objects */
+#define MIB_NODE_EX 0x05
+
+/** node "base class" layout, the mandatory fields for a node  */
+struct mib_node
+{
+  /** returns struct obj_def for the given object identifier */
+  void (*get_object_def)(u8_t ident_len, s32_t *ident, struct obj_def *od);
+  /** returns object value for the given object identifier,
+     @note the caller must allocate at least len bytes for the value */
+  void (*get_value)(struct obj_def *od, u16_t len, void *value);
+  /** tests length and/or range BEFORE setting */
+  u8_t (*set_test)(struct obj_def *od, u16_t len, void *value);
+  /** sets object value, only to be called when set_test()  */
+  void (*set_value)(struct obj_def *od, u16_t len, void *value);  
+  /** One out of MIB_NODE_AR, MIB_NODE_LR or MIB_NODE_EX */
+  u8_t node_type;
+  /* array or max list length */
+  u16_t maxlength;
+};
+
+/** derived node for scalars .0 index */
+typedef struct mib_node mib_scalar_node;
+
+/** derived node, points to a fixed size const array
+    of sub-identifiers plus a 'child' pointer */
+struct mib_array_node
+{
+  /* inherited "base class" members */
+  void (*get_object_def)(u8_t ident_len, s32_t *ident, struct obj_def *od);
+  void (*get_value)(struct obj_def *od, u16_t len, void *value);
+  u8_t (*set_test)(struct obj_def *od, u16_t len, void *value);
+  void (*set_value)(struct obj_def *od, u16_t len, void *value);
+
+  u8_t node_type;
+  u16_t maxlength;
+
+  /* additional struct members */
+  const s32_t *objid;
+  struct mib_node* const *nptr;
+};
+
+/** derived node, points to a fixed size mem_malloced array
+    of sub-identifiers plus a 'child' pointer */
+struct mib_ram_array_node
+{
+  /* inherited "base class" members */
+  void (*get_object_def)(u8_t ident_len, s32_t *ident, struct obj_def *od);
+  void (*get_value)(struct obj_def *od, u16_t len, void *value);
+  u8_t (*set_test)(struct obj_def *od, u16_t len, void *value);
+  void (*set_value)(struct obj_def *od, u16_t len, void *value);
+
+  u8_t node_type;
+  u16_t maxlength;
+
+  /* aditional struct members */
+  s32_t *objid;
+  struct mib_node **nptr;
+};
+
+struct mib_list_node
+{
+  struct mib_list_node *prev;  
+  struct mib_list_node *next;
+  s32_t objid;
+  struct mib_node *nptr;
+};
+
+/** derived node, points to a doubly linked list
+    of sub-identifiers plus a 'child' pointer */
+struct mib_list_rootnode
+{
+  /* inherited "base class" members */
+  void (*get_object_def)(u8_t ident_len, s32_t *ident, struct obj_def *od);
+  void (*get_value)(struct obj_def *od, u16_t len, void *value);
+  u8_t (*set_test)(struct obj_def *od, u16_t len, void *value);
+  void (*set_value)(struct obj_def *od, u16_t len, void *value);
+
+  u8_t node_type;
+  u16_t maxlength;
+
+  /* additional struct members */
+  struct mib_list_node *head;
+  struct mib_list_node *tail;
+  /* counts list nodes in list  */
+  u16_t count;
+};
+
+/** derived node, has access functions for mib object in external memory or device
+    using 'tree_level' and 'idx', with a range 0 .. (level_length() - 1) */
+struct mib_external_node
+{
+  /* inherited "base class" members */
+  void (*get_object_def)(u8_t ident_len, s32_t *ident, struct obj_def *od);
+  void (*get_value)(struct obj_def *od, u16_t len, void *value);
+  u8_t (*set_test)(struct obj_def *od, u16_t len, void *value);
+  void (*set_value)(struct obj_def *od, u16_t len, void *value);
+
+  u8_t node_type;
+  u16_t maxlength;
+
+  /* additional struct members */
+  /** points to an external (in memory) record of some sort of addressing
+      information, passed to and interpreted by the funtions below */
+  void* addr_inf;
+  /** tree levels under this node */
+  u8_t tree_levels;
+  /** number of objects at this level */
+  u16_t (*level_length)(void* addr_inf, u8_t level);
+  /** compares object sub identifier with external id
+      return zero when equal, nonzero when unequal */
+  s32_t (*ident_cmp)(void* addr_inf, u8_t level, u16_t idx, s32_t sub_id);
+  void (*get_objid)(void* addr_inf, u8_t level, u16_t idx, s32_t *sub_id);
+
+  /** async Questions */
+  void (*get_object_def_q)(void* addr_inf, u8_t rid, u8_t ident_len, s32_t *ident);
+  void (*get_value_q)(u8_t rid, struct obj_def *od);
+  void (*set_test_q)(u8_t rid, struct obj_def *od);
+  void (*set_value_q)(u8_t rid, struct obj_def *od, u16_t len, void *value);
+  /** async Answers */
+  void (*get_object_def_a)(u8_t rid, u8_t ident_len, s32_t *ident, struct obj_def *od);
+  void (*get_value_a)(u8_t rid, struct obj_def *od, u16_t len, void *value);
+  u8_t (*set_test_a)(u8_t rid, struct obj_def *od, u16_t len, void *value);
+  void (*set_value_a)(u8_t rid, struct obj_def *od, u16_t len, void *value);
+  /** async Panic Close (agent returns error reply, 
+      e.g. used for external transaction cleanup) */
+  void (*get_object_def_pc)(u8_t rid, u8_t ident_len, s32_t *ident);
+  void (*get_value_pc)(u8_t rid, struct obj_def *od);
+  void (*set_test_pc)(u8_t rid, struct obj_def *od);
+  void (*set_value_pc)(u8_t rid, struct obj_def *od);
+};
+
+/** export MIB tree from mib2.c */
+extern const struct mib_array_node internet;
+
+/** dummy function pointers for non-leaf MIB nodes from mib2.c */
+void noleafs_get_object_def(u8_t ident_len, s32_t *ident, struct obj_def *od);
+void noleafs_get_value(struct obj_def *od, u16_t len, void *value);
+u8_t noleafs_set_test(struct obj_def *od, u16_t len, void *value);
+void noleafs_set_value(struct obj_def *od, u16_t len, void *value);
+
+void snmp_oidtoip(s32_t *ident, ip_addr_t *ip);
+void snmp_iptooid(ip_addr_t *ip, s32_t *ident);
+void snmp_ifindextonetif(s32_t ifindex, struct netif **netif);
+void snmp_netiftoifindex(struct netif *netif, s32_t *ifidx);
+
+struct mib_list_node* snmp_mib_ln_alloc(s32_t id);
+void snmp_mib_ln_free(struct mib_list_node *ln);
+struct mib_list_rootnode* snmp_mib_lrn_alloc(void);
+void snmp_mib_lrn_free(struct mib_list_rootnode *lrn);
+
+s8_t snmp_mib_node_insert(struct mib_list_rootnode *rn, s32_t objid, struct mib_list_node **insn);
+s8_t snmp_mib_node_find(struct mib_list_rootnode *rn, s32_t objid, struct mib_list_node **fn);
+struct mib_list_rootnode *snmp_mib_node_delete(struct mib_list_rootnode *rn, struct mib_list_node *n);
+
+struct mib_node* snmp_search_tree(struct mib_node *node, u8_t ident_len, s32_t *ident, struct snmp_name_ptr *np);
+struct mib_node* snmp_expand_tree(struct mib_node *node, u8_t ident_len, s32_t *ident, struct snmp_obj_id *oidret);
+u8_t snmp_iso_prefix_tst(u8_t ident_len, s32_t *ident);
+u8_t snmp_iso_prefix_expand(u8_t ident_len, s32_t *ident, struct snmp_obj_id *oidret);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* __LWIP_SNMP_STRUCTS_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/sockets.h b/external/badvpn_dns/lwip/src/include/lwip/sockets.h
new file mode 100644
index 0000000..7346137
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/sockets.h
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+
+#ifndef __LWIP_SOCKETS_H__
+#define __LWIP_SOCKETS_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_SOCKET /* don't build if not configured for use in lwipopts.h */
+
+#include <stddef.h> /* for size_t */
+
+#include "lwip/ip_addr.h"
+#include "lwip/inet.h"
+#include "lwip/inet6.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* members are in network byte order */
+struct sockaddr_in {
+  u8_t sin_len;
+  u8_t sin_family;
+  u16_t sin_port;
+  struct in_addr sin_addr;
+#define SIN_ZERO_LEN 8
+  char sin_zero[SIN_ZERO_LEN];
+};
+
+#if LWIP_IPV6
+struct sockaddr_in6 {
+  u8_t sin6_len;             /* length of this structure */
+  u8_t sin6_family;          /* AF_INET6                 */
+  u16_t sin6_port;           /* Transport layer port #   */
+  u32_t sin6_flowinfo;       /* IPv6 flow information    */
+  struct in6_addr sin6_addr; /* IPv6 address             */
+};
+#endif /* LWIP_IPV6 */
+
+struct sockaddr {
+  u8_t sa_len;
+  u8_t sa_family;
+#if LWIP_IPV6
+  u8_t sa_data[22];
+#else /* LWIP_IPV6 */
+  u8_t sa_data[14];
+#endif /* LWIP_IPV6 */
+};
+
+/* If your port already typedef's socklen_t, define SOCKLEN_T_DEFINED
+   to prevent this code from redefining it. */
+#if !defined(socklen_t) && !defined(SOCKLEN_T_DEFINED)
+typedef u32_t socklen_t;
+#endif
+
+/* Socket protocol types (TCP/UDP/RAW) */
+#define SOCK_STREAM     1
+#define SOCK_DGRAM      2
+#define SOCK_RAW        3
+
+/*
+ * Option flags per-socket. These must match the SOF_ flags in ip.h (checked in init.c)
+ */
+#define  SO_DEBUG       0x0001 /* Unimplemented: turn on debugging info recording */
+#define  SO_ACCEPTCONN  0x0002 /* socket has had listen() */
+#define  SO_REUSEADDR   0x0004 /* Allow local address reuse */
+#define  SO_KEEPALIVE   0x0008 /* keep connections alive */
+#define  SO_DONTROUTE   0x0010 /* Unimplemented: just use interface addresses */
+#define  SO_BROADCAST   0x0020 /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */
+#define  SO_USELOOPBACK 0x0040 /* Unimplemented: bypass hardware when possible */
+#define  SO_LINGER      0x0080 /* linger on close if data present */
+#define  SO_OOBINLINE   0x0100 /* Unimplemented: leave received OOB data in line */
+#define  SO_REUSEPORT   0x0200 /* Unimplemented: allow local address & port reuse */
+
+#define SO_DONTLINGER   ((int)(~SO_LINGER))
+
+/*
+ * Additional options, not kept in so_options.
+ */
+#define SO_SNDBUF    0x1001    /* Unimplemented: send buffer size */
+#define SO_RCVBUF    0x1002    /* receive buffer size */
+#define SO_SNDLOWAT  0x1003    /* Unimplemented: send low-water mark */
+#define SO_RCVLOWAT  0x1004    /* Unimplemented: receive low-water mark */
+#define SO_SNDTIMEO  0x1005    /* Unimplemented: send timeout */
+#define SO_RCVTIMEO  0x1006    /* receive timeout */
+#define SO_ERROR     0x1007    /* get error status and clear */
+#define SO_TYPE      0x1008    /* get socket type */
+#define SO_CONTIMEO  0x1009    /* Unimplemented: connect timeout */
+#define SO_NO_CHECK  0x100a    /* don't create UDP checksum */
+
+
+/*
+ * Structure used for manipulating linger option.
+ */
+struct linger {
+       int l_onoff;                /* option on/off */
+       int l_linger;               /* linger time */
+};
+
+/*
+ * Level number for (get/set)sockopt() to apply to socket itself.
+ */
+#define  SOL_SOCKET  0xfff    /* options for socket level */
+
+
+#define AF_UNSPEC       0
+#define AF_INET         2
+#if LWIP_IPV6
+#define AF_INET6        10
+#else /* LWIP_IPV6 */
+#define AF_INET6        AF_UNSPEC
+#endif /* LWIP_IPV6 */
+#define PF_INET         AF_INET
+#define PF_INET6        AF_INET6
+#define PF_UNSPEC       AF_UNSPEC
+
+#define IPPROTO_IP      0
+#define IPPROTO_TCP     6
+#define IPPROTO_UDP     17
+#if LWIP_IPV6
+#define IPPROTO_IPV6    41
+#endif /* LWIP_IPV6 */
+#define IPPROTO_UDPLITE 136
+
+/* Flags we can use with send and recv. */
+#define MSG_PEEK       0x01    /* Peeks at an incoming message */
+#define MSG_WAITALL    0x02    /* Unimplemented: Requests that the function block until the full amount of data requested can be returned */
+#define MSG_OOB        0x04    /* Unimplemented: Requests out-of-band data. The significance and semantics of out-of-band data are protocol-specific */
+#define MSG_DONTWAIT   0x08    /* Nonblocking i/o for this operation only */
+#define MSG_MORE       0x10    /* Sender will send more */
+
+
+/*
+ * Options for level IPPROTO_IP
+ */
+#define IP_TOS             1
+#define IP_TTL             2
+
+#if LWIP_TCP
+/*
+ * Options for level IPPROTO_TCP
+ */
+#define TCP_NODELAY    0x01    /* don't delay send to coalesce packets */
+#define TCP_KEEPALIVE  0x02    /* send KEEPALIVE probes when idle for pcb->keep_idle milliseconds */
+#define TCP_KEEPIDLE   0x03    /* set pcb->keep_idle  - Same as TCP_KEEPALIVE, but use seconds for get/setsockopt */
+#define TCP_KEEPINTVL  0x04    /* set pcb->keep_intvl - Use seconds for get/setsockopt */
+#define TCP_KEEPCNT    0x05    /* set pcb->keep_cnt   - Use number of probes sent for get/setsockopt */
+#endif /* LWIP_TCP */
+
+#if LWIP_IPV6
+/*
+ * Options for level IPPROTO_IPV6
+ */
+#define IPV6_V6ONLY 27 /* RFC3493: boolean control to restrict AF_INET6 sockets to IPv6 communications only. */
+#endif /* LWIP_IPV6 */
+
+#if LWIP_UDP && LWIP_UDPLITE
+/*
+ * Options for level IPPROTO_UDPLITE
+ */
+#define UDPLITE_SEND_CSCOV 0x01 /* sender checksum coverage */
+#define UDPLITE_RECV_CSCOV 0x02 /* minimal receiver checksum coverage */
+#endif /* LWIP_UDP && LWIP_UDPLITE*/
+
+
+#if LWIP_IGMP
+/*
+ * Options and types for UDP multicast traffic handling
+ */
+#define IP_ADD_MEMBERSHIP  3
+#define IP_DROP_MEMBERSHIP 4
+#define IP_MULTICAST_TTL   5
+#define IP_MULTICAST_IF    6
+#define IP_MULTICAST_LOOP  7
+
+typedef struct ip_mreq {
+    struct in_addr imr_multiaddr; /* IP multicast address of group */
+    struct in_addr imr_interface; /* local IP address of interface */
+} ip_mreq;
+#endif /* LWIP_IGMP */
+
+/*
+ * The Type of Service provides an indication of the abstract
+ * parameters of the quality of service desired.  These parameters are
+ * to be used to guide the selection of the actual service parameters
+ * when transmitting a datagram through a particular network.  Several
+ * networks offer service precedence, which somehow treats high
+ * precedence traffic as more important than other traffic (generally
+ * by accepting only traffic above a certain precedence at time of high
+ * load).  The major choice is a three way tradeoff between low-delay,
+ * high-reliability, and high-throughput.
+ * The use of the Delay, Throughput, and Reliability indications may
+ * increase the cost (in some sense) of the service.  In many networks
+ * better performance for one of these parameters is coupled with worse
+ * performance on another.  Except for very unusual cases at most two
+ * of these three indications should be set.
+ */
+#define IPTOS_TOS_MASK          0x1E
+#define IPTOS_TOS(tos)          ((tos) & IPTOS_TOS_MASK)
+#define IPTOS_LOWDELAY          0x10
+#define IPTOS_THROUGHPUT        0x08
+#define IPTOS_RELIABILITY       0x04
+#define IPTOS_LOWCOST           0x02
+#define IPTOS_MINCOST           IPTOS_LOWCOST
+
+/*
+ * The Network Control precedence designation is intended to be used
+ * within a network only.  The actual use and control of that
+ * designation is up to each network. The Internetwork Control
+ * designation is intended for use by gateway control originators only.
+ * If the actual use of these precedence designations is of concern to
+ * a particular network, it is the responsibility of that network to
+ * control the access to, and use of, those precedence designations.
+ */
+#define IPTOS_PREC_MASK                 0xe0
+#define IPTOS_PREC(tos)                ((tos) & IPTOS_PREC_MASK)
+#define IPTOS_PREC_NETCONTROL           0xe0
+#define IPTOS_PREC_INTERNETCONTROL      0xc0
+#define IPTOS_PREC_CRITIC_ECP           0xa0
+#define IPTOS_PREC_FLASHOVERRIDE        0x80
+#define IPTOS_PREC_FLASH                0x60
+#define IPTOS_PREC_IMMEDIATE            0x40
+#define IPTOS_PREC_PRIORITY             0x20
+#define IPTOS_PREC_ROUTINE              0x00
+
+
+/*
+ * Commands for ioctlsocket(),  taken from the BSD file fcntl.h.
+ * lwip_ioctl only supports FIONREAD and FIONBIO, for now
+ *
+ * Ioctl's have the command encoded in the lower word,
+ * and the size of any in or out parameters in the upper
+ * word.  The high 2 bits of the upper word are used
+ * to encode the in/out status of the parameter; for now
+ * we restrict parameters to at most 128 bytes.
+ */
+#if !defined(FIONREAD) || !defined(FIONBIO)
+#define IOCPARM_MASK    0x7fU           /* parameters must be < 128 bytes */
+#define IOC_VOID        0x20000000UL    /* no parameters */
+#define IOC_OUT         0x40000000UL    /* copy out parameters */
+#define IOC_IN          0x80000000UL    /* copy in parameters */
+#define IOC_INOUT       (IOC_IN|IOC_OUT)
+                                        /* 0x20000000 distinguishes new &
+                                           old ioctl's */
+#define _IO(x,y)        (IOC_VOID|((x)<<8)|(y))
+
+#define _IOR(x,y,t)     (IOC_OUT|(((long)sizeof(t)&IOCPARM_MASK)<<16)|((x)<<8)|(y))
+
+#define _IOW(x,y,t)     (IOC_IN|(((long)sizeof(t)&IOCPARM_MASK)<<16)|((x)<<8)|(y))
+#endif /* !defined(FIONREAD) || !defined(FIONBIO) */
+
+#ifndef FIONREAD
+#define FIONREAD    _IOR('f', 127, unsigned long) /* get # bytes to read */
+#endif
+#ifndef FIONBIO
+#define FIONBIO     _IOW('f', 126, unsigned long) /* set/clear non-blocking i/o */
+#endif
+
+/* Socket I/O Controls: unimplemented */
+#ifndef SIOCSHIWAT
+#define SIOCSHIWAT  _IOW('s',  0, unsigned long)  /* set high watermark */
+#define SIOCGHIWAT  _IOR('s',  1, unsigned long)  /* get high watermark */
+#define SIOCSLOWAT  _IOW('s',  2, unsigned long)  /* set low watermark */
+#define SIOCGLOWAT  _IOR('s',  3, unsigned long)  /* get low watermark */
+#define SIOCATMARK  _IOR('s',  7, unsigned long)  /* at oob mark? */
+#endif
+
+/* commands for fnctl */
+#ifndef F_GETFL
+#define F_GETFL 3
+#endif
+#ifndef F_SETFL
+#define F_SETFL 4
+#endif
+
+/* File status flags and file access modes for fnctl,
+   these are bits in an int. */
+#ifndef O_NONBLOCK
+#define O_NONBLOCK  1 /* nonblocking I/O */
+#endif
+#ifndef O_NDELAY
+#define O_NDELAY    1 /* same as O_NONBLOCK, for compatibility */
+#endif
+
+#ifndef SHUT_RD
+  #define SHUT_RD   0
+  #define SHUT_WR   1
+  #define SHUT_RDWR 2
+#endif
+
+/* FD_SET used for lwip_select */
+#ifndef FD_SET
+  #undef  FD_SETSIZE
+  /* Make FD_SETSIZE match NUM_SOCKETS in socket.c */
+  #define FD_SETSIZE    MEMP_NUM_NETCONN
+  #define FD_SET(n, p)  ((p)->fd_bits[(n)/8] |=  (1 << ((n) & 7)))
+  #define FD_CLR(n, p)  ((p)->fd_bits[(n)/8] &= ~(1 << ((n) & 7)))
+  #define FD_ISSET(n,p) ((p)->fd_bits[(n)/8] &   (1 << ((n) & 7)))
+  #define FD_ZERO(p)    memset((void*)(p),0,sizeof(*(p)))
+
+  typedef struct fd_set {
+          unsigned char fd_bits [(FD_SETSIZE+7)/8];
+        } fd_set;
+
+#endif /* FD_SET */
+
+/** LWIP_TIMEVAL_PRIVATE: if you want to use the struct timeval provided
+ * by your system, set this to 0 and include <sys/time.h> in cc.h */ 
+#ifndef LWIP_TIMEVAL_PRIVATE
+#define LWIP_TIMEVAL_PRIVATE 1
+#endif
+
+#if LWIP_TIMEVAL_PRIVATE
+struct timeval {
+  long    tv_sec;         /* seconds */
+  long    tv_usec;        /* and microseconds */
+};
+#endif /* LWIP_TIMEVAL_PRIVATE */
+
+void lwip_socket_init(void);
+
+int lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen);
+int lwip_bind(int s, const struct sockaddr *name, socklen_t namelen);
+int lwip_shutdown(int s, int how);
+int lwip_getpeername (int s, struct sockaddr *name, socklen_t *namelen);
+int lwip_getsockname (int s, struct sockaddr *name, socklen_t *namelen);
+int lwip_getsockopt (int s, int level, int optname, void *optval, socklen_t *optlen);
+int lwip_setsockopt (int s, int level, int optname, const void *optval, socklen_t optlen);
+int lwip_close(int s);
+int lwip_connect(int s, const struct sockaddr *name, socklen_t namelen);
+int lwip_listen(int s, int backlog);
+int lwip_recv(int s, void *mem, size_t len, int flags);
+int lwip_read(int s, void *mem, size_t len);
+int lwip_recvfrom(int s, void *mem, size_t len, int flags,
+      struct sockaddr *from, socklen_t *fromlen);
+int lwip_send(int s, const void *dataptr, size_t size, int flags);
+int lwip_sendto(int s, const void *dataptr, size_t size, int flags,
+    const struct sockaddr *to, socklen_t tolen);
+int lwip_socket(int domain, int type, int protocol);
+int lwip_write(int s, const void *dataptr, size_t size);
+int lwip_select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
+                struct timeval *timeout);
+int lwip_ioctl(int s, long cmd, void *argp);
+int lwip_fcntl(int s, int cmd, int val);
+
+#if LWIP_COMPAT_SOCKETS
+#define accept(a,b,c)         lwip_accept(a,b,c)
+#define bind(a,b,c)           lwip_bind(a,b,c)
+#define shutdown(a,b)         lwip_shutdown(a,b)
+#define closesocket(s)        lwip_close(s)
+#define connect(a,b,c)        lwip_connect(a,b,c)
+#define getsockname(a,b,c)    lwip_getsockname(a,b,c)
+#define getpeername(a,b,c)    lwip_getpeername(a,b,c)
+#define setsockopt(a,b,c,d,e) lwip_setsockopt(a,b,c,d,e)
+#define getsockopt(a,b,c,d,e) lwip_getsockopt(a,b,c,d,e)
+#define listen(a,b)           lwip_listen(a,b)
+#define recv(a,b,c,d)         lwip_recv(a,b,c,d)
+#define recvfrom(a,b,c,d,e,f) lwip_recvfrom(a,b,c,d,e,f)
+#define send(a,b,c,d)         lwip_send(a,b,c,d)
+#define sendto(a,b,c,d,e,f)   lwip_sendto(a,b,c,d,e,f)
+#define socket(a,b,c)         lwip_socket(a,b,c)
+#define select(a,b,c,d,e)     lwip_select(a,b,c,d,e)
+#define ioctlsocket(a,b,c)    lwip_ioctl(a,b,c)
+
+#if LWIP_POSIX_SOCKETS_IO_NAMES
+#define read(a,b,c)           lwip_read(a,b,c)
+#define write(a,b,c)          lwip_write(a,b,c)
+#define close(s)              lwip_close(s)
+#define fcntl(a,b,c)          lwip_fcntl(a,b,c)
+#endif /* LWIP_POSIX_SOCKETS_IO_NAMES */
+
+#endif /* LWIP_COMPAT_SOCKETS */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SOCKET */
+
+#endif /* __LWIP_SOCKETS_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/stats.h b/external/badvpn_dns/lwip/src/include/lwip/stats.h
new file mode 100644
index 0000000..d911216
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/stats.h
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_STATS_H__
+#define __LWIP_STATS_H__
+
+#include "lwip/opt.h"
+
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_STATS
+
+#ifndef LWIP_STATS_LARGE
+#define LWIP_STATS_LARGE 0
+#endif
+
+#if LWIP_STATS_LARGE
+#define STAT_COUNTER     u32_t
+#define STAT_COUNTER_F   U32_F
+#else
+#define STAT_COUNTER     u16_t
+#define STAT_COUNTER_F   U16_F
+#endif 
+
+struct stats_proto {
+  STAT_COUNTER xmit;             /* Transmitted packets. */
+  STAT_COUNTER recv;             /* Received packets. */
+  STAT_COUNTER fw;               /* Forwarded packets. */
+  STAT_COUNTER drop;             /* Dropped packets. */
+  STAT_COUNTER chkerr;           /* Checksum error. */
+  STAT_COUNTER lenerr;           /* Invalid length error. */
+  STAT_COUNTER memerr;           /* Out of memory error. */
+  STAT_COUNTER rterr;            /* Routing error. */
+  STAT_COUNTER proterr;          /* Protocol error. */
+  STAT_COUNTER opterr;           /* Error in options. */
+  STAT_COUNTER err;              /* Misc error. */
+  STAT_COUNTER cachehit;
+};
+
+struct stats_igmp {
+  STAT_COUNTER xmit;             /* Transmitted packets. */
+  STAT_COUNTER recv;             /* Received packets. */
+  STAT_COUNTER drop;             /* Dropped packets. */
+  STAT_COUNTER chkerr;           /* Checksum error. */
+  STAT_COUNTER lenerr;           /* Invalid length error. */
+  STAT_COUNTER memerr;           /* Out of memory error. */
+  STAT_COUNTER proterr;          /* Protocol error. */
+  STAT_COUNTER rx_v1;            /* Received v1 frames. */
+  STAT_COUNTER rx_group;         /* Received group-specific queries. */
+  STAT_COUNTER rx_general;       /* Received general queries. */
+  STAT_COUNTER rx_report;        /* Received reports. */
+  STAT_COUNTER tx_join;          /* Sent joins. */
+  STAT_COUNTER tx_leave;         /* Sent leaves. */
+  STAT_COUNTER tx_report;        /* Sent reports. */
+};
+
+struct stats_mem {
+#ifdef LWIP_DEBUG
+  const char *name;
+#endif /* LWIP_DEBUG */
+  mem_size_t avail;
+  mem_size_t used;
+  mem_size_t max;
+  STAT_COUNTER err;
+  STAT_COUNTER illegal;
+};
+
+struct stats_syselem {
+  STAT_COUNTER used;
+  STAT_COUNTER max;
+  STAT_COUNTER err;
+};
+
+struct stats_sys {
+  struct stats_syselem sem;
+  struct stats_syselem mutex;
+  struct stats_syselem mbox;
+};
+
+struct stats_ {
+#if LINK_STATS
+  struct stats_proto link;
+#endif
+#if ETHARP_STATS
+  struct stats_proto etharp;
+#endif
+#if IPFRAG_STATS
+  struct stats_proto ip_frag;
+#endif
+#if IP_STATS
+  struct stats_proto ip;
+#endif
+#if ICMP_STATS
+  struct stats_proto icmp;
+#endif
+#if IGMP_STATS
+  struct stats_igmp igmp;
+#endif
+#if UDP_STATS
+  struct stats_proto udp;
+#endif
+#if TCP_STATS
+  struct stats_proto tcp;
+#endif
+#if MEM_STATS
+  struct stats_mem mem;
+#endif
+#if MEMP_STATS
+  struct stats_mem memp[MEMP_MAX];
+#endif
+#if SYS_STATS
+  struct stats_sys sys;
+#endif
+#if IP6_STATS
+  struct stats_proto ip6;
+#endif
+#if ICMP6_STATS
+  struct stats_proto icmp6;
+#endif
+#if IP6_FRAG_STATS
+  struct stats_proto ip6_frag;
+#endif
+#if MLD6_STATS
+  struct stats_igmp mld6;
+#endif
+#if ND6_STATS
+  struct stats_proto nd6;
+#endif
+};
+
+extern struct stats_ lwip_stats;
+
+void stats_init(void);
+
+#define STATS_INC(x) ++lwip_stats.x
+#define STATS_DEC(x) --lwip_stats.x
+#define STATS_INC_USED(x, y) do { lwip_stats.x.used += y; \
+                                if (lwip_stats.x.max < lwip_stats.x.used) { \
+                                    lwip_stats.x.max = lwip_stats.x.used; \
+                                } \
+                             } while(0)
+#else /* LWIP_STATS */
+#define stats_init()
+#define STATS_INC(x)
+#define STATS_DEC(x)
+#define STATS_INC_USED(x)
+#endif /* LWIP_STATS */
+
+#if TCP_STATS
+#define TCP_STATS_INC(x) STATS_INC(x)
+#define TCP_STATS_DISPLAY() stats_display_proto(&lwip_stats.tcp, "TCP")
+#else
+#define TCP_STATS_INC(x)
+#define TCP_STATS_DISPLAY()
+#endif
+
+#if UDP_STATS
+#define UDP_STATS_INC(x) STATS_INC(x)
+#define UDP_STATS_DISPLAY() stats_display_proto(&lwip_stats.udp, "UDP")
+#else
+#define UDP_STATS_INC(x)
+#define UDP_STATS_DISPLAY()
+#endif
+
+#if ICMP_STATS
+#define ICMP_STATS_INC(x) STATS_INC(x)
+#define ICMP_STATS_DISPLAY() stats_display_proto(&lwip_stats.icmp, "ICMP")
+#else
+#define ICMP_STATS_INC(x)
+#define ICMP_STATS_DISPLAY()
+#endif
+
+#if IGMP_STATS
+#define IGMP_STATS_INC(x) STATS_INC(x)
+#define IGMP_STATS_DISPLAY() stats_display_igmp(&lwip_stats.igmp, "IGMP")
+#else
+#define IGMP_STATS_INC(x)
+#define IGMP_STATS_DISPLAY()
+#endif
+
+#if IP_STATS
+#define IP_STATS_INC(x) STATS_INC(x)
+#define IP_STATS_DISPLAY() stats_display_proto(&lwip_stats.ip, "IP")
+#else
+#define IP_STATS_INC(x)
+#define IP_STATS_DISPLAY()
+#endif
+
+#if IPFRAG_STATS
+#define IPFRAG_STATS_INC(x) STATS_INC(x)
+#define IPFRAG_STATS_DISPLAY() stats_display_proto(&lwip_stats.ip_frag, "IP_FRAG")
+#else
+#define IPFRAG_STATS_INC(x)
+#define IPFRAG_STATS_DISPLAY()
+#endif
+
+#if ETHARP_STATS
+#define ETHARP_STATS_INC(x) STATS_INC(x)
+#define ETHARP_STATS_DISPLAY() stats_display_proto(&lwip_stats.etharp, "ETHARP")
+#else
+#define ETHARP_STATS_INC(x)
+#define ETHARP_STATS_DISPLAY()
+#endif
+
+#if LINK_STATS
+#define LINK_STATS_INC(x) STATS_INC(x)
+#define LINK_STATS_DISPLAY() stats_display_proto(&lwip_stats.link, "LINK")
+#else
+#define LINK_STATS_INC(x)
+#define LINK_STATS_DISPLAY()
+#endif
+
+#if MEM_STATS
+#define MEM_STATS_AVAIL(x, y) lwip_stats.mem.x = y
+#define MEM_STATS_INC(x) STATS_INC(mem.x)
+#define MEM_STATS_INC_USED(x, y) STATS_INC_USED(mem, y)
+#define MEM_STATS_DEC_USED(x, y) lwip_stats.mem.x -= y
+#define MEM_STATS_DISPLAY() stats_display_mem(&lwip_stats.mem, "HEAP")
+#else
+#define MEM_STATS_AVAIL(x, y)
+#define MEM_STATS_INC(x)
+#define MEM_STATS_INC_USED(x, y)
+#define MEM_STATS_DEC_USED(x, y)
+#define MEM_STATS_DISPLAY()
+#endif
+
+#if MEMP_STATS
+#define MEMP_STATS_AVAIL(x, i, y) lwip_stats.memp[i].x = y
+#define MEMP_STATS_INC(x, i) STATS_INC(memp[i].x)
+#define MEMP_STATS_DEC(x, i) STATS_DEC(memp[i].x)
+#define MEMP_STATS_INC_USED(x, i) STATS_INC_USED(memp[i], 1)
+#define MEMP_STATS_DISPLAY(i) stats_display_memp(&lwip_stats.memp[i], i)
+#else
+#define MEMP_STATS_AVAIL(x, i, y)
+#define MEMP_STATS_INC(x, i)
+#define MEMP_STATS_DEC(x, i)
+#define MEMP_STATS_INC_USED(x, i)
+#define MEMP_STATS_DISPLAY(i)
+#endif
+
+#if SYS_STATS
+#define SYS_STATS_INC(x) STATS_INC(sys.x)
+#define SYS_STATS_DEC(x) STATS_DEC(sys.x)
+#define SYS_STATS_INC_USED(x) STATS_INC_USED(sys.x, 1)
+#define SYS_STATS_DISPLAY() stats_display_sys(&lwip_stats.sys)
+#else
+#define SYS_STATS_INC(x)
+#define SYS_STATS_DEC(x)
+#define SYS_STATS_INC_USED(x)
+#define SYS_STATS_DISPLAY()
+#endif
+
+#if IP6_STATS
+#define IP6_STATS_INC(x) STATS_INC(x)
+#define IP6_STATS_DISPLAY() stats_display_proto(&lwip_stats.ip6, "IPv6")
+#else
+#define IP6_STATS_INC(x)
+#define IP6_STATS_DISPLAY()
+#endif
+
+#if ICMP6_STATS
+#define ICMP6_STATS_INC(x) STATS_INC(x)
+#define ICMP6_STATS_DISPLAY() stats_display_proto(&lwip_stats.icmp6, "ICMPv6")
+#else
+#define ICMP6_STATS_INC(x)
+#define ICMP6_STATS_DISPLAY()
+#endif
+
+#if IP6_FRAG_STATS
+#define IP6_FRAG_STATS_INC(x) STATS_INC(x)
+#define IP6_FRAG_STATS_DISPLAY() stats_display_proto(&lwip_stats.ip6_frag, "IPv6 FRAG")
+#else
+#define IP6_FRAG_STATS_INC(x)
+#define IP6_FRAG_STATS_DISPLAY()
+#endif
+
+#if MLD6_STATS
+#define MLD6_STATS_INC(x) STATS_INC(x)
+#define MLD6_STATS_DISPLAY() stats_display_igmp(&lwip_stats.mld6, "MLDv1")
+#else
+#define MLD6_STATS_INC(x)
+#define MLD6_STATS_DISPLAY()
+#endif
+
+#if ND6_STATS
+#define ND6_STATS_INC(x) STATS_INC(x)
+#define ND6_STATS_DISPLAY() stats_display_proto(&lwip_stats.nd6, "ND")
+#else
+#define ND6_STATS_INC(x)
+#define ND6_STATS_DISPLAY()
+#endif
+
+/* Display of statistics */
+#if LWIP_STATS_DISPLAY
+void stats_display(void);
+void stats_display_proto(struct stats_proto *proto, const char *name);
+void stats_display_igmp(struct stats_igmp *igmp, const char *name);
+void stats_display_mem(struct stats_mem *mem, const char *name);
+void stats_display_memp(struct stats_mem *mem, int index);
+void stats_display_sys(struct stats_sys *sys);
+#else /* LWIP_STATS_DISPLAY */
+#define stats_display()
+#define stats_display_proto(proto, name)
+#define stats_display_igmp(igmp, name)
+#define stats_display_mem(mem, name)
+#define stats_display_memp(mem, index)
+#define stats_display_sys(sys)
+#endif /* LWIP_STATS_DISPLAY */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_STATS_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/sys.h b/external/badvpn_dns/lwip/src/include/lwip/sys.h
new file mode 100644
index 0000000..fd45ee8
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/sys.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_SYS_H__
+#define __LWIP_SYS_H__
+
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if NO_SYS
+
+/* For a totally minimal and standalone system, we provide null
+   definitions of the sys_ functions. */
+typedef u8_t sys_sem_t;
+typedef u8_t sys_mutex_t;
+typedef u8_t sys_mbox_t;
+
+#define sys_sem_new(s, c) ERR_OK
+#define sys_sem_signal(s)
+#define sys_sem_wait(s)
+#define sys_arch_sem_wait(s,t)
+#define sys_sem_free(s)
+#define sys_sem_valid(s) 0
+#define sys_sem_set_invalid(s)
+#define sys_mutex_new(mu) ERR_OK
+#define sys_mutex_lock(mu)
+#define sys_mutex_unlock(mu)
+#define sys_mutex_free(mu)
+#define sys_mutex_valid(mu) 0
+#define sys_mutex_set_invalid(mu)
+#define sys_mbox_new(m, s) ERR_OK
+#define sys_mbox_fetch(m,d)
+#define sys_mbox_tryfetch(m,d)
+#define sys_mbox_post(m,d)
+#define sys_mbox_trypost(m,d)
+#define sys_mbox_free(m)
+#define sys_mbox_valid(m)
+#define sys_mbox_set_invalid(m)
+
+#define sys_thread_new(n,t,a,s,p)
+
+#define sys_msleep(t)
+
+#else /* NO_SYS */
+
+/** Return code for timeouts from sys_arch_mbox_fetch and sys_arch_sem_wait */
+#define SYS_ARCH_TIMEOUT 0xffffffffUL
+
+/** sys_mbox_tryfetch() returns SYS_MBOX_EMPTY if appropriate.
+ * For now we use the same magic value, but we allow this to change in future.
+ */
+#define SYS_MBOX_EMPTY SYS_ARCH_TIMEOUT 
+
+#include "lwip/err.h"
+#include "arch/sys_arch.h"
+
+/** Function prototype for thread functions */
+typedef void (*lwip_thread_fn)(void *arg);
+
+/* Function prototypes for functions to be implemented by platform ports
+   (in sys_arch.c) */
+
+/* Mutex functions: */
+
+/** Define LWIP_COMPAT_MUTEX if the port has no mutexes and binary semaphores
+    should be used instead */
+#if LWIP_COMPAT_MUTEX
+/* for old ports that don't have mutexes: define them to binary semaphores */
+#define sys_mutex_t                   sys_sem_t
+#define sys_mutex_new(mutex)          sys_sem_new(mutex, 1)
+#define sys_mutex_lock(mutex)         sys_sem_wait(mutex)
+#define sys_mutex_unlock(mutex)       sys_sem_signal(mutex)
+#define sys_mutex_free(mutex)         sys_sem_free(mutex)
+#define sys_mutex_valid(mutex)        sys_sem_valid(mutex)
+#define sys_mutex_set_invalid(mutex)  sys_sem_set_invalid(mutex)
+
+#else /* LWIP_COMPAT_MUTEX */
+
+/** Create a new mutex
+ * @param mutex pointer to the mutex to create
+ * @return a new mutex */
+err_t sys_mutex_new(sys_mutex_t *mutex);
+/** Lock a mutex
+ * @param mutex the mutex to lock */
+void sys_mutex_lock(sys_mutex_t *mutex);
+/** Unlock a mutex
+ * @param mutex the mutex to unlock */
+void sys_mutex_unlock(sys_mutex_t *mutex);
+/** Delete a semaphore
+ * @param mutex the mutex to delete */
+void sys_mutex_free(sys_mutex_t *mutex); 
+#ifndef sys_mutex_valid
+/** Check if a mutex is valid/allocated: return 1 for valid, 0 for invalid */
+int sys_mutex_valid(sys_mutex_t *mutex);
+#endif
+#ifndef sys_mutex_set_invalid
+/** Set a mutex invalid so that sys_mutex_valid returns 0 */
+void sys_mutex_set_invalid(sys_mutex_t *mutex);
+#endif
+#endif /* LWIP_COMPAT_MUTEX */
+
+/* Semaphore functions: */
+
+/** Create a new semaphore
+ * @param sem pointer to the semaphore to create
+ * @param count initial count of the semaphore
+ * @return ERR_OK if successful, another err_t otherwise */
+err_t sys_sem_new(sys_sem_t *sem, u8_t count);
+/** Signals a semaphore
+ * @param sem the semaphore to signal */
+void sys_sem_signal(sys_sem_t *sem);
+/** Wait for a semaphore for the specified timeout
+ * @param sem the semaphore to wait for
+ * @param timeout timeout in milliseconds to wait (0 = wait forever)
+ * @return time (in milliseconds) waited for the semaphore
+ *         or SYS_ARCH_TIMEOUT on timeout */
+u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout);
+/** Delete a semaphore
+ * @param sem semaphore to delete */
+void sys_sem_free(sys_sem_t *sem);
+/** Wait for a semaphore - forever/no timeout */
+#define sys_sem_wait(sem)                  sys_arch_sem_wait(sem, 0)
+#ifndef sys_sem_valid
+/** Check if a sempahore is valid/allocated: return 1 for valid, 0 for invalid */
+int sys_sem_valid(sys_sem_t *sem);
+#endif
+#ifndef sys_sem_set_invalid
+/** Set a semaphore invalid so that sys_sem_valid returns 0 */
+void sys_sem_set_invalid(sys_sem_t *sem);
+#endif
+
+/* Time functions. */
+#ifndef sys_msleep
+void sys_msleep(u32_t ms); /* only has a (close to) 1 jiffy resolution. */
+#endif
+
+/* Mailbox functions. */
+
+/** Create a new mbox of specified size
+ * @param mbox pointer to the mbox to create
+ * @param size (miminum) number of messages in this mbox
+ * @return ERR_OK if successful, another err_t otherwise */
+err_t sys_mbox_new(sys_mbox_t *mbox, int size);
+/** Post a message to an mbox - may not fail
+ * -> blocks if full, only used from tasks not from ISR
+ * @param mbox mbox to posts the message
+ * @param msg message to post (ATTENTION: can be NULL) */
+void sys_mbox_post(sys_mbox_t *mbox, void *msg);
+/** Try to post a message to an mbox - may fail if full or ISR
+ * @param mbox mbox to posts the message
+ * @param msg message to post (ATTENTION: can be NULL) */
+err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg);
+/** Wait for a new message to arrive in the mbox
+ * @param mbox mbox to get a message from
+ * @param msg pointer where the message is stored
+ * @param timeout maximum time (in milliseconds) to wait for a message (0 = wait forever)
+ * @return time (in milliseconds) waited for a message, may be 0 if not waited
+           or SYS_ARCH_TIMEOUT on timeout
+ *         The returned time has to be accurate to prevent timer jitter! */
+u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout);
+/* Allow port to override with a macro, e.g. special timout for sys_arch_mbox_fetch() */
+#ifndef sys_arch_mbox_tryfetch
+/** Wait for a new message to arrive in the mbox
+ * @param mbox mbox to get a message from
+ * @param msg pointer where the message is stored
+ * @return 0 (milliseconds) if a message has been received
+ *         or SYS_MBOX_EMPTY if the mailbox is empty */
+u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg);
+#endif
+/** For now, we map straight to sys_arch implementation. */
+#define sys_mbox_tryfetch(mbox, msg) sys_arch_mbox_tryfetch(mbox, msg)
+/** Delete an mbox
+ * @param mbox mbox to delete */
+void sys_mbox_free(sys_mbox_t *mbox);
+#define sys_mbox_fetch(mbox, msg) sys_arch_mbox_fetch(mbox, msg, 0)
+#ifndef sys_mbox_valid
+/** Check if an mbox is valid/allocated: return 1 for valid, 0 for invalid */
+int sys_mbox_valid(sys_mbox_t *mbox);
+#endif
+#ifndef sys_mbox_set_invalid
+/** Set an mbox invalid so that sys_mbox_valid returns 0 */
+void sys_mbox_set_invalid(sys_mbox_t *mbox);
+#endif
+
+/** The only thread function:
+ * Creates a new thread
+ * @param name human-readable name for the thread (used for debugging purposes)
+ * @param thread thread-function
+ * @param arg parameter passed to 'thread'
+ * @param stacksize stack size in bytes for the new thread (may be ignored by ports)
+ * @param prio priority of the new thread (may be ignored by ports) */
+sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio);
+
+#endif /* NO_SYS */
+
+/* sys_init() must be called before anthing else. */
+void sys_init(void);
+
+#ifndef sys_jiffies
+/** Ticks/jiffies since power up. */
+u32_t sys_jiffies(void);
+#endif
+
+/** Returns the current time in milliseconds,
+ * may be the same as sys_jiffies or at least based on it. */
+u32_t sys_now(void);
+
+/* Critical Region Protection */
+/* These functions must be implemented in the sys_arch.c file.
+   In some implementations they can provide a more light-weight protection
+   mechanism than using semaphores. Otherwise semaphores can be used for
+   implementation */
+#ifndef SYS_ARCH_PROTECT
+/** SYS_LIGHTWEIGHT_PROT
+ * define SYS_LIGHTWEIGHT_PROT in lwipopts.h if you want inter-task protection
+ * for certain critical regions during buffer allocation, deallocation and memory
+ * allocation and deallocation.
+ */
+#if SYS_LIGHTWEIGHT_PROT
+
+/** SYS_ARCH_DECL_PROTECT
+ * declare a protection variable. This macro will default to defining a variable of
+ * type sys_prot_t. If a particular port needs a different implementation, then
+ * this macro may be defined in sys_arch.h.
+ */
+#define SYS_ARCH_DECL_PROTECT(lev) sys_prot_t lev
+/** SYS_ARCH_PROTECT
+ * Perform a "fast" protect. This could be implemented by
+ * disabling interrupts for an embedded system or by using a semaphore or
+ * mutex. The implementation should allow calling SYS_ARCH_PROTECT when
+ * already protected. The old protection level is returned in the variable
+ * "lev". This macro will default to calling the sys_arch_protect() function
+ * which should be implemented in sys_arch.c. If a particular port needs a
+ * different implementation, then this macro may be defined in sys_arch.h
+ */
+#define SYS_ARCH_PROTECT(lev) lev = sys_arch_protect()
+/** SYS_ARCH_UNPROTECT
+ * Perform a "fast" set of the protection level to "lev". This could be
+ * implemented by setting the interrupt level to "lev" within the MACRO or by
+ * using a semaphore or mutex.  This macro will default to calling the
+ * sys_arch_unprotect() function which should be implemented in
+ * sys_arch.c. If a particular port needs a different implementation, then
+ * this macro may be defined in sys_arch.h
+ */
+#define SYS_ARCH_UNPROTECT(lev) sys_arch_unprotect(lev)
+sys_prot_t sys_arch_protect(void);
+void sys_arch_unprotect(sys_prot_t pval);
+
+#else
+
+#define SYS_ARCH_DECL_PROTECT(lev)
+#define SYS_ARCH_PROTECT(lev)
+#define SYS_ARCH_UNPROTECT(lev)
+
+#endif /* SYS_LIGHTWEIGHT_PROT */
+
+#endif /* SYS_ARCH_PROTECT */
+
+/*
+ * Macros to set/get and increase/decrease variables in a thread-safe way.
+ * Use these for accessing variable that are used from more than one thread.
+ */
+
+#ifndef SYS_ARCH_INC
+#define SYS_ARCH_INC(var, val) do { \
+                                SYS_ARCH_DECL_PROTECT(old_level); \
+                                SYS_ARCH_PROTECT(old_level); \
+                                var += val; \
+                                SYS_ARCH_UNPROTECT(old_level); \
+                              } while(0)
+#endif /* SYS_ARCH_INC */
+
+#ifndef SYS_ARCH_DEC
+#define SYS_ARCH_DEC(var, val) do { \
+                                SYS_ARCH_DECL_PROTECT(old_level); \
+                                SYS_ARCH_PROTECT(old_level); \
+                                var -= val; \
+                                SYS_ARCH_UNPROTECT(old_level); \
+                              } while(0)
+#endif /* SYS_ARCH_DEC */
+
+#ifndef SYS_ARCH_GET
+#define SYS_ARCH_GET(var, ret) do { \
+                                SYS_ARCH_DECL_PROTECT(old_level); \
+                                SYS_ARCH_PROTECT(old_level); \
+                                ret = var; \
+                                SYS_ARCH_UNPROTECT(old_level); \
+                              } while(0)
+#endif /* SYS_ARCH_GET */
+
+#ifndef SYS_ARCH_SET
+#define SYS_ARCH_SET(var, val) do { \
+                                SYS_ARCH_DECL_PROTECT(old_level); \
+                                SYS_ARCH_PROTECT(old_level); \
+                                var = val; \
+                                SYS_ARCH_UNPROTECT(old_level); \
+                              } while(0)
+#endif /* SYS_ARCH_SET */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LWIP_SYS_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/tcp.h b/external/badvpn_dns/lwip/src/include/lwip/tcp.h
new file mode 100644
index 0000000..535b6a3
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/tcp.h
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_TCP_H__
+#define __LWIP_TCP_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/mem.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip.h"
+#include "lwip/icmp.h"
+#include "lwip/err.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct tcp_pcb;
+
+/** Function prototype for tcp accept callback functions. Called when a new
+ * connection can be accepted on a listening pcb.
+ *
+ * @param arg Additional argument to pass to the callback function (@see tcp_arg())
+ * @param newpcb The new connection pcb
+ * @param err An error code if there has been an error accepting.
+ *            Only return ERR_ABRT if you have called tcp_abort from within the
+ *            callback function!
+ */
+typedef err_t (*tcp_accept_fn)(void *arg, struct tcp_pcb *newpcb, err_t err);
+
+/** Function prototype for tcp receive callback functions. Called when data has
+ * been received.
+ *
+ * @param arg Additional argument to pass to the callback function (@see tcp_arg())
+ * @param tpcb The connection pcb which received data
+ * @param p The received data (or NULL when the connection has been closed!)
+ * @param err An error code if there has been an error receiving
+ *            Only return ERR_ABRT if you have called tcp_abort from within the
+ *            callback function!
+ */
+typedef err_t (*tcp_recv_fn)(void *arg, struct tcp_pcb *tpcb,
+                             struct pbuf *p, err_t err);
+
+/** Function prototype for tcp sent callback functions. Called when sent data has
+ * been acknowledged by the remote side. Use it to free corresponding resources.
+ * This also means that the pcb has now space available to send new data.
+ *
+ * @param arg Additional argument to pass to the callback function (@see tcp_arg())
+ * @param tpcb The connection pcb for which data has been acknowledged
+ * @param len The amount of bytes acknowledged
+ * @return ERR_OK: try to send some data by calling tcp_output
+ *            Only return ERR_ABRT if you have called tcp_abort from within the
+ *            callback function!
+ */
+typedef err_t (*tcp_sent_fn)(void *arg, struct tcp_pcb *tpcb,
+                              u16_t len);
+
+/** Function prototype for tcp poll callback functions. Called periodically as
+ * specified by @see tcp_poll.
+ *
+ * @param arg Additional argument to pass to the callback function (@see tcp_arg())
+ * @param tpcb tcp pcb
+ * @return ERR_OK: try to send some data by calling tcp_output
+ *            Only return ERR_ABRT if you have called tcp_abort from within the
+ *            callback function!
+ */
+typedef err_t (*tcp_poll_fn)(void *arg, struct tcp_pcb *tpcb);
+
+/** Function prototype for tcp error callback functions. Called when the pcb
+ * receives a RST or is unexpectedly closed for any other reason.
+ *
+ * @note The corresponding pcb is already freed when this callback is called!
+ *
+ * @param arg Additional argument to pass to the callback function (@see tcp_arg())
+ * @param err Error code to indicate why the pcb has been closed
+ *            ERR_ABRT: aborted through tcp_abort or by a TCP timer
+ *            ERR_RST: the connection was reset by the remote host
+ */
+typedef void  (*tcp_err_fn)(void *arg, err_t err);
+
+/** Function prototype for tcp connected callback functions. Called when a pcb
+ * is connected to the remote side after initiating a connection attempt by
+ * calling tcp_connect().
+ *
+ * @param arg Additional argument to pass to the callback function (@see tcp_arg())
+ * @param tpcb The connection pcb which is connected
+ * @param err An unused error code, always ERR_OK currently ;-) TODO!
+ *            Only return ERR_ABRT if you have called tcp_abort from within the
+ *            callback function!
+ *
+ * @note When a connection attempt fails, the error callback is currently called!
+ */
+typedef err_t (*tcp_connected_fn)(void *arg, struct tcp_pcb *tpcb, err_t err);
+
+enum tcp_state {
+  CLOSED      = 0,
+  LISTEN      = 1,
+  SYN_SENT    = 2,
+  SYN_RCVD    = 3,
+  ESTABLISHED = 4,
+  FIN_WAIT_1  = 5,
+  FIN_WAIT_2  = 6,
+  CLOSE_WAIT  = 7,
+  CLOSING     = 8,
+  LAST_ACK    = 9,
+  TIME_WAIT   = 10
+};
+
+#if LWIP_CALLBACK_API
+  /* Function to call when a listener has been connected.
+   * @param arg user-supplied argument (tcp_pcb.callback_arg)
+   * @param pcb a new tcp_pcb that now is connected
+   * @param err an error argument (TODO: that is current always ERR_OK?)
+   * @return ERR_OK: accept the new connection,
+   *                 any other err_t abortsthe new connection
+   */
+#define DEF_ACCEPT_CALLBACK  tcp_accept_fn accept;
+#else /* LWIP_CALLBACK_API */
+#define DEF_ACCEPT_CALLBACK
+#endif /* LWIP_CALLBACK_API */
+
+/**
+ * members common to struct tcp_pcb and struct tcp_listen_pcb
+ */
+#define TCP_PCB_COMMON(type) \
+  type *next; /* for the linked list */ \
+  void *callback_arg; \
+  /* the accept callback for listen- and normal pcbs, if LWIP_CALLBACK_API */ \
+  DEF_ACCEPT_CALLBACK \
+  enum tcp_state state; /* TCP state */ \
+  u8_t prio; \
+  /* ports are in host byte order */ \
+  int bound_to_netif; \
+  u16_t local_port; \
+  char local_netif[3]
+
+
+/* the TCP protocol control block */
+struct tcp_pcb {
+/** common PCB members */
+  IP_PCB;
+/** protocol specific PCB members */
+  TCP_PCB_COMMON(struct tcp_pcb);
+
+  /* ports are in host byte order */
+  u16_t remote_port;
+  
+  u8_t flags;
+#define TF_ACK_DELAY   ((u8_t)0x01U)   /* Delayed ACK. */
+#define TF_ACK_NOW     ((u8_t)0x02U)   /* Immediate ACK. */
+#define TF_INFR        ((u8_t)0x04U)   /* In fast recovery. */
+#define TF_TIMESTAMP   ((u8_t)0x08U)   /* Timestamp option enabled */
+#define TF_RXCLOSED    ((u8_t)0x10U)   /* rx closed by tcp_shutdown */
+#define TF_FIN         ((u8_t)0x20U)   /* Connection was closed locally (FIN segment enqueued). */
+#define TF_NODELAY     ((u8_t)0x40U)   /* Disable Nagle algorithm */
+#define TF_NAGLEMEMERR ((u8_t)0x80U)   /* nagle enabled, memerr, try to output to prevent delayed ACK to happen */
+
+  /* the rest of the fields are in host byte order
+     as we have to do some math with them */
+
+  /* Timers */
+  u8_t polltmr, pollinterval;
+  u8_t last_timer;
+  u32_t tmr;
+
+  /* receiver variables */
+  u32_t rcv_nxt;   /* next seqno expected */
+  u16_t rcv_wnd;   /* receiver window available */
+  u16_t rcv_ann_wnd; /* receiver window to announce */
+  u32_t rcv_ann_right_edge; /* announced right edge of window */
+
+  /* Retransmission timer. */
+  s16_t rtime;
+
+  u16_t mss;   /* maximum segment size */
+
+  /* RTT (round trip time) estimation variables */
+  u32_t rttest; /* RTT estimate in 500ms ticks */
+  u32_t rtseq;  /* sequence number being timed */
+  s16_t sa, sv; /* @todo document this */
+
+  s16_t rto;    /* retransmission time-out */
+  u8_t nrtx;    /* number of retransmissions */
+
+  /* fast retransmit/recovery */
+  u8_t dupacks;
+  u32_t lastack; /* Highest acknowledged seqno. */
+
+  /* congestion avoidance/control variables */
+  u16_t cwnd;
+  u16_t ssthresh;
+
+  /* sender variables */
+  u32_t snd_nxt;   /* next new seqno to be sent */
+  u32_t snd_wl1, snd_wl2; /* Sequence and acknowledgement numbers of last
+                             window update. */
+  u32_t snd_lbb;       /* Sequence number of next byte to be buffered. */
+  u16_t snd_wnd;   /* sender window */
+  u16_t snd_wnd_max; /* the maximum sender window announced by the remote host */
+
+  u16_t acked;
+
+  u16_t snd_buf;   /* Available buffer space for sending (in bytes). */
+#define TCP_SNDQUEUELEN_OVERFLOW (0xffffU-3)
+  u16_t snd_queuelen; /* Available buffer space for sending (in tcp_segs). */
+
+#if TCP_OVERSIZE
+  /* Extra bytes available at the end of the last pbuf in unsent. */
+  u16_t unsent_oversize;
+#endif /* TCP_OVERSIZE */ 
+
+  /* These are ordered by sequence number: */
+  struct tcp_seg *unsent;   /* Unsent (queued) segments. */
+  struct tcp_seg *unacked;  /* Sent but unacknowledged segments. */
+#if TCP_QUEUE_OOSEQ  
+  struct tcp_seg *ooseq;    /* Received out of sequence segments. */
+#endif /* TCP_QUEUE_OOSEQ */
+
+  struct pbuf *refused_data; /* Data previously received but not yet taken by upper layer */
+
+#if LWIP_CALLBACK_API
+  /* Function to be called when more send buffer space is available. */
+  tcp_sent_fn sent;
+  /* Function to be called when (in-sequence) data has arrived. */
+  tcp_recv_fn recv;
+  /* Function to be called when a connection has been set up. */
+  tcp_connected_fn connected;
+  /* Function which is called periodically. */
+  tcp_poll_fn poll;
+  /* Function to be called whenever a fatal error occurs. */
+  tcp_err_fn errf;
+#endif /* LWIP_CALLBACK_API */
+
+#if LWIP_TCP_TIMESTAMPS
+  u32_t ts_lastacksent;
+  u32_t ts_recent;
+#endif /* LWIP_TCP_TIMESTAMPS */
+
+  /* idle time before KEEPALIVE is sent */
+  u32_t keep_idle;
+#if LWIP_TCP_KEEPALIVE
+  u32_t keep_intvl;
+  u32_t keep_cnt;
+#endif /* LWIP_TCP_KEEPALIVE */
+  
+  /* Persist timer counter */
+  u8_t persist_cnt;
+  /* Persist timer back-off */
+  u8_t persist_backoff;
+
+  /* KEEPALIVE counter */
+  u8_t keep_cnt_sent;
+};
+
+struct tcp_pcb_listen {
+/* Common members of all PCB types */
+  IP_PCB;
+/* Protocol specific PCB members */
+  TCP_PCB_COMMON(struct tcp_pcb_listen);
+
+#if TCP_LISTEN_BACKLOG
+  u8_t backlog;
+  u8_t accepts_pending;
+#endif /* TCP_LISTEN_BACKLOG */
+#if LWIP_IPV6
+  u8_t accept_any_ip_version;
+#endif /* LWIP_IPV6 */
+};
+
+#if LWIP_EVENT_API
+
+enum lwip_event {
+  LWIP_EVENT_ACCEPT,
+  LWIP_EVENT_SENT,
+  LWIP_EVENT_RECV,
+  LWIP_EVENT_CONNECTED,
+  LWIP_EVENT_POLL,
+  LWIP_EVENT_ERR
+};
+
+err_t lwip_tcp_event(void *arg, struct tcp_pcb *pcb,
+         enum lwip_event,
+         struct pbuf *p,
+         u16_t size,
+         err_t err);
+
+#endif /* LWIP_EVENT_API */
+
+/* Application program's interface: */
+struct tcp_pcb * tcp_new     (void);
+
+void             tcp_arg     (struct tcp_pcb *pcb, void *arg);
+void             tcp_accept  (struct tcp_pcb *pcb, tcp_accept_fn accept);
+void             tcp_recv    (struct tcp_pcb *pcb, tcp_recv_fn recv);
+void             tcp_sent    (struct tcp_pcb *pcb, tcp_sent_fn sent);
+void             tcp_poll    (struct tcp_pcb *pcb, tcp_poll_fn poll, u8_t interval);
+void             tcp_err     (struct tcp_pcb *pcb, tcp_err_fn err);
+
+#define          tcp_mss(pcb)             (((pcb)->flags & TF_TIMESTAMP) ? ((pcb)->mss - 12)  : (pcb)->mss)
+#define          tcp_sndbuf(pcb)          ((pcb)->snd_buf)
+#define          tcp_sndqueuelen(pcb)     ((pcb)->snd_queuelen)
+#define          tcp_nagle_disable(pcb)   ((pcb)->flags |= TF_NODELAY)
+#define          tcp_nagle_enable(pcb)    ((pcb)->flags &= ~TF_NODELAY)
+#define          tcp_nagle_disabled(pcb)  (((pcb)->flags & TF_NODELAY) != 0)
+
+#if TCP_LISTEN_BACKLOG
+#define          tcp_accepted(pcb) do { \
+  LWIP_ASSERT("pcb->state == LISTEN (called for wrong pcb?)", pcb->state == LISTEN); \
+  (((struct tcp_pcb_listen *)(pcb))->accepts_pending--); } while(0)
+#else  /* TCP_LISTEN_BACKLOG */
+#define          tcp_accepted(pcb) LWIP_ASSERT("pcb->state == LISTEN (called for wrong pcb?)", \
+                                               (pcb)->state == LISTEN)
+#endif /* TCP_LISTEN_BACKLOG */
+
+void             tcp_recved  (struct tcp_pcb *pcb, u16_t len);
+err_t            tcp_bind    (struct tcp_pcb *pcb, ip_addr_t *ipaddr,
+                              u16_t port);
+err_t            tcp_bind_to_netif (struct tcp_pcb *pcb, const char ifname[3]);
+err_t            tcp_connect (struct tcp_pcb *pcb, ip_addr_t *ipaddr,
+                              u16_t port, tcp_connected_fn connected);
+
+struct tcp_pcb * tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog);
+#define          tcp_listen(pcb) tcp_listen_with_backlog(pcb, TCP_DEFAULT_LISTEN_BACKLOG)
+
+void             tcp_abort (struct tcp_pcb *pcb);
+err_t            tcp_close   (struct tcp_pcb *pcb);
+err_t            tcp_shutdown(struct tcp_pcb *pcb, int shut_rx, int shut_tx);
+
+/* Flags for "apiflags" parameter in tcp_write */
+#define TCP_WRITE_FLAG_COPY 0x01
+#define TCP_WRITE_FLAG_MORE 0x02
+
+err_t            tcp_write   (struct tcp_pcb *pcb, const void *dataptr, u16_t len,
+                              u8_t apiflags);
+
+void             tcp_setprio (struct tcp_pcb *pcb, u8_t prio);
+
+#define TCP_PRIO_MIN    1
+#define TCP_PRIO_NORMAL 64
+#define TCP_PRIO_MAX    127
+
+err_t            tcp_output  (struct tcp_pcb *pcb);
+
+
+const char* tcp_debug_state_str(enum tcp_state s);
+
+#if LWIP_IPV6
+struct tcp_pcb * tcp_new_ip6 (void);
+#define          tcp_bind_ip6(pcb, ip6addr, port) \
+                   tcp_bind(pcb, ip6_2_ip(ip6addr), port)
+#define          tcp_connect_ip6(pcb, ip6addr, port, connected) \
+                   tcp_connect(pcb, ip6_2_ip(ip6addr), port, connected)
+struct tcp_pcb * tcp_listen_dual_with_backlog(struct tcp_pcb *pcb, u8_t backlog);
+#define          tcp_listen_dual(pcb) tcp_listen_dual_with_backlog(pcb, TCP_DEFAULT_LISTEN_BACKLOG)
+#else /* LWIP_IPV6 */
+#define          tcp_listen_dual_with_backlog(pcb, backlog) tcp_listen_with_backlog(pcb, backlog)
+#define          tcp_listen_dual(pcb) tcp_listen(pcb)
+#endif /* LWIP_IPV6 */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_TCP */
+
+#endif /* __LWIP_TCP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/tcp_impl.h b/external/badvpn_dns/lwip/src/include/lwip/tcp_impl.h
new file mode 100644
index 0000000..2afc20d
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/tcp_impl.h
@@ -0,0 +1,508 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_TCP_IMPL_H__
+#define __LWIP_TCP_IMPL_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/tcp.h"
+#include "lwip/mem.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip.h"
+#include "lwip/icmp.h"
+#include "lwip/err.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Functions for interfacing with TCP: */
+
+/* Lower layer interface to TCP: */
+void             tcp_init    (void);  /* Initialize this module. */
+void             tcp_tmr     (void);  /* Must be called every
+                                         TCP_TMR_INTERVAL
+                                         ms. (Typically 250 ms). */
+/* It is also possible to call these two functions at the right
+   intervals (instead of calling tcp_tmr()). */
+void             tcp_slowtmr (void);
+void             tcp_fasttmr (void);
+
+
+/* Only used by IP to pass a TCP segment to TCP: */
+void             tcp_input   (struct pbuf *p, struct netif *inp);
+/* Used within the TCP code only: */
+struct tcp_pcb * tcp_alloc   (u8_t prio);
+void             tcp_abandon (struct tcp_pcb *pcb, int reset);
+err_t            tcp_send_empty_ack(struct tcp_pcb *pcb);
+void             tcp_rexmit  (struct tcp_pcb *pcb);
+void             tcp_rexmit_rto  (struct tcp_pcb *pcb);
+void             tcp_rexmit_fast (struct tcp_pcb *pcb);
+u32_t            tcp_update_rcv_ann_wnd(struct tcp_pcb *pcb);
+err_t            tcp_process_refused_data(struct tcp_pcb *pcb);
+
+/**
+ * This is the Nagle algorithm: try to combine user data to send as few TCP
+ * segments as possible. Only send if
+ * - no previously transmitted data on the connection remains unacknowledged or
+ * - the TF_NODELAY flag is set (nagle algorithm turned off for this pcb) or
+ * - the only unsent segment is at least pcb->mss bytes long (or there is more
+ *   than one unsent segment - with lwIP, this can happen although unsent->len < mss)
+ * - or if we are in fast-retransmit (TF_INFR)
+ */
+#define tcp_do_output_nagle(tpcb) ((((tpcb)->unacked == NULL) || \
+                            ((tpcb)->flags & (TF_NODELAY | TF_INFR)) || \
+                            (((tpcb)->unsent != NULL) && (((tpcb)->unsent->next != NULL) || \
+                              ((tpcb)->unsent->len >= (tpcb)->mss))) || \
+                            ((tcp_sndbuf(tpcb) == 0) || (tcp_sndqueuelen(tpcb) >= TCP_SND_QUEUELEN)) \
+                            ) ? 1 : 0)
+#define tcp_output_nagle(tpcb) (tcp_do_output_nagle(tpcb) ? tcp_output(tpcb) : ERR_OK)
+
+
+#define TCP_SEQ_LT(a,b)     ((s32_t)((u32_t)(a) - (u32_t)(b)) < 0)
+#define TCP_SEQ_LEQ(a,b)    ((s32_t)((u32_t)(a) - (u32_t)(b)) <= 0)
+#define TCP_SEQ_GT(a,b)     ((s32_t)((u32_t)(a) - (u32_t)(b)) > 0)
+#define TCP_SEQ_GEQ(a,b)    ((s32_t)((u32_t)(a) - (u32_t)(b)) >= 0)
+/* is b<=a<=c? */
+#if 0 /* see bug #10548 */
+#define TCP_SEQ_BETWEEN(a,b,c) ((c)-(b) >= (a)-(b))
+#endif
+#define TCP_SEQ_BETWEEN(a,b,c) (TCP_SEQ_GEQ(a,b) && TCP_SEQ_LEQ(a,c))
+#define TCP_FIN 0x01U
+#define TCP_SYN 0x02U
+#define TCP_RST 0x04U
+#define TCP_PSH 0x08U
+#define TCP_ACK 0x10U
+#define TCP_URG 0x20U
+#define TCP_ECE 0x40U
+#define TCP_CWR 0x80U
+
+#define TCP_FLAGS 0x3fU
+
+/* Length of the TCP header, excluding options. */
+#define TCP_HLEN 20
+
+#ifndef TCP_TMR_INTERVAL
+#define TCP_TMR_INTERVAL       250  /* The TCP timer interval in milliseconds. */
+#endif /* TCP_TMR_INTERVAL */
+
+#ifndef TCP_FAST_INTERVAL
+#define TCP_FAST_INTERVAL      TCP_TMR_INTERVAL /* the fine grained timeout in milliseconds */
+#endif /* TCP_FAST_INTERVAL */
+
+#ifndef TCP_SLOW_INTERVAL
+#define TCP_SLOW_INTERVAL      (2*TCP_TMR_INTERVAL)  /* the coarse grained timeout in milliseconds */
+#endif /* TCP_SLOW_INTERVAL */
+
+#define TCP_FIN_WAIT_TIMEOUT 20000 /* milliseconds */
+#define TCP_SYN_RCVD_TIMEOUT 20000 /* milliseconds */
+
+#define TCP_OOSEQ_TIMEOUT        6U /* x RTO */
+
+#ifndef TCP_MSL
+#define TCP_MSL 60000UL /* The maximum segment lifetime in milliseconds */
+#endif
+
+/* Keepalive values, compliant with RFC 1122. Don't change this unless you know what you're doing */
+#ifndef  TCP_KEEPIDLE_DEFAULT
+#define  TCP_KEEPIDLE_DEFAULT     7200000UL /* Default KEEPALIVE timer in milliseconds */
+#endif
+
+#ifndef  TCP_KEEPINTVL_DEFAULT
+#define  TCP_KEEPINTVL_DEFAULT    75000UL   /* Default Time between KEEPALIVE probes in milliseconds */
+#endif
+
+#ifndef  TCP_KEEPCNT_DEFAULT
+#define  TCP_KEEPCNT_DEFAULT      9U        /* Default Counter for KEEPALIVE probes */
+#endif
+
+#define  TCP_MAXIDLE              TCP_KEEPCNT_DEFAULT * TCP_KEEPINTVL_DEFAULT  /* Maximum KEEPALIVE probe time */
+
+/* Fields are (of course) in network byte order.
+ * Some fields are converted to host byte order in tcp_input().
+ */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct tcp_hdr {
+  PACK_STRUCT_FIELD(u16_t src);
+  PACK_STRUCT_FIELD(u16_t dest);
+  PACK_STRUCT_FIELD(u32_t seqno);
+  PACK_STRUCT_FIELD(u32_t ackno);
+  PACK_STRUCT_FIELD(u16_t _hdrlen_rsvd_flags);
+  PACK_STRUCT_FIELD(u16_t wnd);
+  PACK_STRUCT_FIELD(u16_t chksum);
+  PACK_STRUCT_FIELD(u16_t urgp);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define TCPH_HDRLEN(phdr) (ntohs((phdr)->_hdrlen_rsvd_flags) >> 12)
+#define TCPH_FLAGS(phdr)  (ntohs((phdr)->_hdrlen_rsvd_flags) & TCP_FLAGS)
+
+#define TCPH_HDRLEN_SET(phdr, len) (phdr)->_hdrlen_rsvd_flags = htons(((len) << 12) | TCPH_FLAGS(phdr))
+#define TCPH_FLAGS_SET(phdr, flags) (phdr)->_hdrlen_rsvd_flags = (((phdr)->_hdrlen_rsvd_flags & PP_HTONS((u16_t)(~(u16_t)(TCP_FLAGS)))) | htons(flags))
+#define TCPH_HDRLEN_FLAGS_SET(phdr, len, flags) (phdr)->_hdrlen_rsvd_flags = htons(((len) << 12) | (flags))
+
+#define TCPH_SET_FLAG(phdr, flags ) (phdr)->_hdrlen_rsvd_flags = ((phdr)->_hdrlen_rsvd_flags | htons(flags))
+#define TCPH_UNSET_FLAG(phdr, flags) (phdr)->_hdrlen_rsvd_flags = htons(ntohs((phdr)->_hdrlen_rsvd_flags) | (TCPH_FLAGS(phdr) & ~(flags)) )
+
+#define TCP_TCPLEN(seg) ((seg)->len + ((TCPH_FLAGS((seg)->tcphdr) & (TCP_FIN | TCP_SYN)) != 0))
+
+/** Flags used on input processing, not on pcb->flags
+*/
+#define TF_RESET     (u8_t)0x08U   /* Connection was reset. */
+#define TF_CLOSED    (u8_t)0x10U   /* Connection was sucessfully closed. */
+#define TF_GOT_FIN   (u8_t)0x20U   /* Connection was closed by the remote end. */
+
+
+#if LWIP_EVENT_API
+
+#define TCP_EVENT_ACCEPT(pcb,err,ret)    ret = lwip_tcp_event((pcb)->callback_arg, (pcb),\
+                LWIP_EVENT_ACCEPT, NULL, 0, err)
+#define TCP_EVENT_SENT(pcb,space,ret) ret = lwip_tcp_event((pcb)->callback_arg, (pcb),\
+                   LWIP_EVENT_SENT, NULL, space, ERR_OK)
+#define TCP_EVENT_RECV(pcb,p,err,ret) ret = lwip_tcp_event((pcb)->callback_arg, (pcb),\
+                LWIP_EVENT_RECV, (p), 0, (err))
+#define TCP_EVENT_CLOSED(pcb,ret) ret = lwip_tcp_event((pcb)->callback_arg, (pcb),\
+                LWIP_EVENT_RECV, NULL, 0, ERR_OK)
+#define TCP_EVENT_CONNECTED(pcb,err,ret) ret = lwip_tcp_event((pcb)->callback_arg, (pcb),\
+                LWIP_EVENT_CONNECTED, NULL, 0, (err))
+#define TCP_EVENT_POLL(pcb,ret)       ret = lwip_tcp_event((pcb)->callback_arg, (pcb),\
+                LWIP_EVENT_POLL, NULL, 0, ERR_OK)
+#define TCP_EVENT_ERR(errf,arg,err)  lwip_tcp_event((arg), NULL, \
+                LWIP_EVENT_ERR, NULL, 0, (err))
+
+#else /* LWIP_EVENT_API */
+
+#define TCP_EVENT_ACCEPT(pcb,err,ret)                          \
+  do {                                                         \
+    if((pcb)->accept != NULL)                                  \
+      (ret) = (pcb)->accept((pcb)->callback_arg,(pcb),(err));  \
+    else (ret) = ERR_ARG;                                      \
+  } while (0)
+
+#define TCP_EVENT_SENT(pcb,space,ret)                          \
+  do {                                                         \
+    if((pcb)->sent != NULL)                                    \
+      (ret) = (pcb)->sent((pcb)->callback_arg,(pcb),(space));  \
+    else (ret) = ERR_OK;                                       \
+  } while (0)
+
+#define TCP_EVENT_RECV(pcb,p,err,ret)                          \
+  do {                                                         \
+    if((pcb)->recv != NULL) {                                  \
+      (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),(p),(err));\
+    } else {                                                   \
+      (ret) = tcp_recv_null(NULL, (pcb), (p), (err));          \
+    }                                                          \
+  } while (0)
+
+#define TCP_EVENT_CLOSED(pcb,ret)                                \
+  do {                                                           \
+    if(((pcb)->recv != NULL)) {                                  \
+      (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),NULL,ERR_OK);\
+    } else {                                                     \
+      (ret) = ERR_OK;                                            \
+    }                                                            \
+  } while (0)
+
+#define TCP_EVENT_CONNECTED(pcb,err,ret)                         \
+  do {                                                           \
+    if((pcb)->connected != NULL)                                 \
+      (ret) = (pcb)->connected((pcb)->callback_arg,(pcb),(err)); \
+    else (ret) = ERR_OK;                                         \
+  } while (0)
+
+#define TCP_EVENT_POLL(pcb,ret)                                \
+  do {                                                         \
+    if((pcb)->poll != NULL)                                    \
+      (ret) = (pcb)->poll((pcb)->callback_arg,(pcb));          \
+    else (ret) = ERR_OK;                                       \
+  } while (0)
+
+#define TCP_EVENT_ERR(errf,arg,err)                            \
+  do {                                                         \
+    if((errf) != NULL)                                         \
+      (errf)((arg),(err));                                     \
+  } while (0)
+
+#endif /* LWIP_EVENT_API */
+
+/** Enabled extra-check for TCP_OVERSIZE if LWIP_DEBUG is enabled */
+#if TCP_OVERSIZE && defined(LWIP_DEBUG)
+#define TCP_OVERSIZE_DBGCHECK 1
+#else
+#define TCP_OVERSIZE_DBGCHECK 0
+#endif
+
+/** Don't generate checksum on copy if CHECKSUM_GEN_TCP is disabled */
+#define TCP_CHECKSUM_ON_COPY  (LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_TCP)
+
+/* This structure represents a TCP segment on the unsent, unacked and ooseq queues */
+struct tcp_seg {
+  struct tcp_seg *next;    /* used when putting segements on a queue */
+  struct pbuf *p;          /* buffer containing data + TCP header */
+  u16_t len;               /* the TCP length of this segment */
+#if TCP_OVERSIZE_DBGCHECK
+  u16_t oversize_left;     /* Extra bytes available at the end of the last
+                              pbuf in unsent (used for asserting vs.
+                              tcp_pcb.unsent_oversized only) */
+#endif /* TCP_OVERSIZE_DBGCHECK */ 
+#if TCP_CHECKSUM_ON_COPY
+  u16_t chksum;
+  u8_t  chksum_swapped;
+#endif /* TCP_CHECKSUM_ON_COPY */
+  u8_t  flags;
+#define TF_SEG_OPTS_MSS         (u8_t)0x01U /* Include MSS option. */
+#define TF_SEG_OPTS_TS          (u8_t)0x02U /* Include timestamp option. */
+#define TF_SEG_DATA_CHECKSUMMED (u8_t)0x04U /* ALL data (not the header) is
+                                               checksummed into 'chksum' */
+  struct tcp_hdr *tcphdr;  /* the TCP header */
+};
+
+#define LWIP_TCP_OPT_LENGTH(flags)              \
+  (flags & TF_SEG_OPTS_MSS ? 4  : 0) +          \
+  (flags & TF_SEG_OPTS_TS  ? 12 : 0)
+
+/** This returns a TCP header option for MSS in an u32_t */
+#define TCP_BUILD_MSS_OPTION(mss) htonl(0x02040000 | ((mss) & 0xFFFF))
+
+/* Global variables: */
+extern struct tcp_pcb *tcp_input_pcb;
+extern u32_t tcp_ticks;
+extern u8_t tcp_active_pcbs_changed;
+
+/* The TCP PCB lists. */
+union tcp_listen_pcbs_t { /* List of all TCP PCBs in LISTEN state. */
+  struct tcp_pcb_listen *listen_pcbs; 
+  struct tcp_pcb *pcbs;
+};
+extern struct tcp_pcb *tcp_bound_pcbs;
+extern union tcp_listen_pcbs_t tcp_listen_pcbs;
+extern struct tcp_pcb *tcp_active_pcbs;  /* List of all TCP PCBs that are in a
+              state in which they accept or send
+              data. */
+extern struct tcp_pcb *tcp_tw_pcbs;      /* List of all TCP PCBs in TIME-WAIT. */
+
+extern struct tcp_pcb *tcp_tmp_pcb;      /* Only used for temporary storage. */
+
+/* Axioms about the above lists:   
+   1) Every TCP PCB that is not CLOSED is in one of the lists.
+   2) A PCB is only in one of the lists.
+   3) All PCBs in the tcp_listen_pcbs list is in LISTEN state.
+   4) All PCBs in the tcp_tw_pcbs list is in TIME-WAIT state.
+*/
+/* Define two macros, TCP_REG and TCP_RMV that registers a TCP PCB
+   with a PCB list or removes a PCB from a list, respectively. */
+#ifndef TCP_DEBUG_PCB_LISTS
+#define TCP_DEBUG_PCB_LISTS 0
+#endif
+#if TCP_DEBUG_PCB_LISTS
+#define TCP_REG(pcbs, npcb) do {\
+                            LWIP_DEBUGF(TCP_DEBUG, ("TCP_REG %p local port %d\n", (npcb), (npcb)->local_port)); \
+                            for(tcp_tmp_pcb = *(pcbs); \
+          tcp_tmp_pcb != NULL; \
+        tcp_tmp_pcb = tcp_tmp_pcb->next) { \
+                                LWIP_ASSERT("TCP_REG: already registered\n", tcp_tmp_pcb != (npcb)); \
+                            } \
+                            LWIP_ASSERT("TCP_REG: pcb->state != CLOSED", ((pcbs) == &tcp_bound_pcbs) || ((npcb)->state != CLOSED)); \
+                            (npcb)->next = *(pcbs); \
+                            LWIP_ASSERT("TCP_REG: npcb->next != npcb", (npcb)->next != (npcb)); \
+                            *(pcbs) = (npcb); \
+                            LWIP_ASSERT("TCP_RMV: tcp_pcbs sane", tcp_pcbs_sane()); \
+              tcp_timer_needed(); \
+                            } while(0)
+#define TCP_RMV(pcbs, npcb) do { \
+                            LWIP_ASSERT("TCP_RMV: pcbs != NULL", *(pcbs) != NULL); \
+                            LWIP_DEBUGF(TCP_DEBUG, ("TCP_RMV: removing %p from %p\n", (npcb), *(pcbs))); \
+                            if(*(pcbs) == (npcb)) { \
+                               *(pcbs) = (*pcbs)->next; \
+                            } else for(tcp_tmp_pcb = *(pcbs); tcp_tmp_pcb != NULL; tcp_tmp_pcb = tcp_tmp_pcb->next) { \
+                               if(tcp_tmp_pcb->next == (npcb)) { \
+                                  tcp_tmp_pcb->next = (npcb)->next; \
+                                  break; \
+                               } \
+                            } \
+                            (npcb)->next = NULL; \
+                            LWIP_ASSERT("TCP_RMV: tcp_pcbs sane", tcp_pcbs_sane()); \
+                            LWIP_DEBUGF(TCP_DEBUG, ("TCP_RMV: removed %p from %p\n", (npcb), *(pcbs))); \
+                            } while(0)
+
+#else /* LWIP_DEBUG */
+
+#define TCP_REG(pcbs, npcb)                        \
+  do {                                             \
+    (npcb)->next = *pcbs;                          \
+    *(pcbs) = (npcb);                              \
+    tcp_timer_needed();                            \
+  } while (0)
+
+#define TCP_RMV(pcbs, npcb)                        \
+  do {                                             \
+    if(*(pcbs) == (npcb)) {                        \
+      (*(pcbs)) = (*pcbs)->next;                   \
+    }                                              \
+    else {                                         \
+      for(tcp_tmp_pcb = *pcbs;                     \
+          tcp_tmp_pcb != NULL;                     \
+          tcp_tmp_pcb = tcp_tmp_pcb->next) {       \
+        if(tcp_tmp_pcb->next == (npcb)) {          \
+          tcp_tmp_pcb->next = (npcb)->next;        \
+          break;                                   \
+        }                                          \
+      }                                            \
+    }                                              \
+    (npcb)->next = NULL;                           \
+  } while(0)
+
+#endif /* LWIP_DEBUG */
+
+#define TCP_REG_ACTIVE(npcb)                       \
+  do {                                             \
+    TCP_REG(&tcp_active_pcbs, npcb);               \
+    tcp_active_pcbs_changed = 1;                   \
+  } while (0)
+
+#define TCP_RMV_ACTIVE(npcb)                       \
+  do {                                             \
+    TCP_RMV(&tcp_active_pcbs, npcb);               \
+    tcp_active_pcbs_changed = 1;                   \
+  } while (0)
+
+#define TCP_PCB_REMOVE_ACTIVE(pcb)                 \
+  do {                                             \
+    tcp_pcb_remove(&tcp_active_pcbs, pcb);         \
+    tcp_active_pcbs_changed = 1;                   \
+  } while (0)
+
+
+/* Internal functions: */
+struct tcp_pcb *tcp_pcb_copy(struct tcp_pcb *pcb);
+void tcp_pcb_purge(struct tcp_pcb *pcb);
+void tcp_pcb_remove(struct tcp_pcb **pcblist, struct tcp_pcb *pcb);
+
+void tcp_segs_free(struct tcp_seg *seg);
+void tcp_seg_free(struct tcp_seg *seg);
+struct tcp_seg *tcp_seg_copy(struct tcp_seg *seg);
+
+#define tcp_ack(pcb)                               \
+  do {                                             \
+    if((pcb)->flags & TF_ACK_DELAY) {              \
+      (pcb)->flags &= ~TF_ACK_DELAY;               \
+      (pcb)->flags |= TF_ACK_NOW;                  \
+    }                                              \
+    else {                                         \
+      (pcb)->flags |= TF_ACK_DELAY;                \
+    }                                              \
+  } while (0)
+
+#define tcp_ack_now(pcb)                           \
+  do {                                             \
+    (pcb)->flags |= TF_ACK_NOW;                    \
+  } while (0)
+
+err_t tcp_send_fin(struct tcp_pcb *pcb);
+err_t tcp_enqueue_flags(struct tcp_pcb *pcb, u8_t flags);
+
+void tcp_rexmit_seg(struct tcp_pcb *pcb, struct tcp_seg *seg);
+
+void tcp_rst_impl(u32_t seqno, u32_t ackno,
+       ipX_addr_t *local_ip, ipX_addr_t *remote_ip,
+       u16_t local_port, u16_t remote_port
+#if LWIP_IPV6
+       , u8_t isipv6
+#endif /* LWIP_IPV6 */
+       );
+#if LWIP_IPV6
+#define tcp_rst(seqno, ackno, local_ip, remote_ip, local_port, remote_port, isipv6) \
+  tcp_rst_impl(seqno, ackno, local_ip, remote_ip, local_port, remote_port, isipv6)
+#else /* LWIP_IPV6 */
+#define tcp_rst(seqno, ackno, local_ip, remote_ip, local_port, remote_port, isipv6) \
+  tcp_rst_impl(seqno, ackno, local_ip, remote_ip, local_port, remote_port)
+#endif /* LWIP_IPV6 */
+
+u32_t tcp_next_iss(void);
+
+void tcp_keepalive(struct tcp_pcb *pcb);
+void tcp_zero_window_probe(struct tcp_pcb *pcb);
+
+#if TCP_CALCULATE_EFF_SEND_MSS
+u16_t tcp_eff_send_mss_impl(u16_t sendmss, ipX_addr_t *dest
+#if LWIP_IPV6
+                           , ipX_addr_t *src, u8_t isipv6
+#endif /* LWIP_IPV6 */
+                           );
+#if LWIP_IPV6
+#define tcp_eff_send_mss(sendmss, src, dest, isipv6) tcp_eff_send_mss_impl(sendmss, dest, src, isipv6)
+#else /* LWIP_IPV6 */
+#define tcp_eff_send_mss(sendmss, src, dest, isipv6) tcp_eff_send_mss_impl(sendmss, dest)
+#endif /* LWIP_IPV6 */
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+
+#if LWIP_CALLBACK_API
+err_t tcp_recv_null(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
+#endif /* LWIP_CALLBACK_API */
+
+#if TCP_DEBUG || TCP_INPUT_DEBUG || TCP_OUTPUT_DEBUG
+void tcp_debug_print(struct tcp_hdr *tcphdr);
+void tcp_debug_print_flags(u8_t flags);
+void tcp_debug_print_state(enum tcp_state s);
+void tcp_debug_print_pcbs(void);
+s16_t tcp_pcbs_sane(void);
+#else
+#  define tcp_debug_print(tcphdr)
+#  define tcp_debug_print_flags(flags)
+#  define tcp_debug_print_state(s)
+#  define tcp_debug_print_pcbs()
+#  define tcp_pcbs_sane() 1
+#endif /* TCP_DEBUG */
+
+/** External function (implemented in timers.c), called when TCP detects
+ * that a timer is needed (i.e. active- or time-wait-pcb found). */
+void tcp_timer_needed(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_TCP */
+
+#endif /* __LWIP_TCP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/tcpip.h b/external/badvpn_dns/lwip/src/include/lwip/tcpip.h
new file mode 100644
index 0000000..04567f2
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/tcpip.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_TCPIP_H__
+#define __LWIP_TCPIP_H__
+
+#include "lwip/opt.h"
+
+#if !NO_SYS /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/api_msg.h"
+#include "lwip/netifapi.h"
+#include "lwip/pbuf.h"
+#include "lwip/api.h"
+#include "lwip/sys.h"
+#include "lwip/timers.h"
+#include "lwip/netif.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Define this to something that triggers a watchdog. This is called from
+ * tcpip_thread after processing a message. */
+#ifndef LWIP_TCPIP_THREAD_ALIVE
+#define LWIP_TCPIP_THREAD_ALIVE()
+#endif
+
+#if LWIP_TCPIP_CORE_LOCKING
+/** The global semaphore to lock the stack. */
+extern sys_mutex_t lock_tcpip_core;
+#define LOCK_TCPIP_CORE()     sys_mutex_lock(&lock_tcpip_core)
+#define UNLOCK_TCPIP_CORE()   sys_mutex_unlock(&lock_tcpip_core)
+#ifdef LWIP_DEBUG
+#define TCIP_APIMSG_SET_ERR(m, e) (m)->msg.err = e  /* catch functions that don't set err */
+#else
+#define TCIP_APIMSG_SET_ERR(m, e)
+#endif
+#define TCPIP_APIMSG_NOERR(m,f) do { \
+  TCIP_APIMSG_SET_ERR(m, ERR_VAL); \
+  LOCK_TCPIP_CORE(); \
+  f(&((m)->msg)); \
+  UNLOCK_TCPIP_CORE(); \
+} while(0)
+#define TCPIP_APIMSG(m,f,e)   do { \
+  TCPIP_APIMSG_NOERR(m,f); \
+  (e) = (m)->msg.err; \
+} while(0)
+#define TCPIP_APIMSG_ACK(m)
+#define TCPIP_NETIFAPI(m)     tcpip_netifapi_lock(m)
+#define TCPIP_NETIFAPI_ACK(m)
+#else /* LWIP_TCPIP_CORE_LOCKING */
+#define LOCK_TCPIP_CORE()
+#define UNLOCK_TCPIP_CORE()
+#define TCPIP_APIMSG_NOERR(m,f) do { (m)->function = f; tcpip_apimsg(m); } while(0)
+#define TCPIP_APIMSG(m,f,e)   do { (m)->function = f; (e) = tcpip_apimsg(m); } while(0)
+#define TCPIP_APIMSG_ACK(m)   sys_sem_signal(&m->conn->op_completed)
+#define TCPIP_NETIFAPI(m)     tcpip_netifapi(m)
+#define TCPIP_NETIFAPI_ACK(m) sys_sem_signal(&m->sem)
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+
+/** Function prototype for the init_done function passed to tcpip_init */
+typedef void (*tcpip_init_done_fn)(void *arg);
+/** Function prototype for functions passed to tcpip_callback() */
+typedef void (*tcpip_callback_fn)(void *ctx);
+
+/* Forward declarations */
+struct tcpip_callback_msg;
+
+void tcpip_init(tcpip_init_done_fn tcpip_init_done, void *arg);
+
+#if LWIP_NETCONN
+err_t tcpip_apimsg(struct api_msg *apimsg);
+#endif /* LWIP_NETCONN */
+
+err_t tcpip_input(struct pbuf *p, struct netif *inp);
+
+#if LWIP_NETIF_API
+err_t tcpip_netifapi(struct netifapi_msg *netifapimsg);
+#if LWIP_TCPIP_CORE_LOCKING
+err_t tcpip_netifapi_lock(struct netifapi_msg *netifapimsg);
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+#endif /* LWIP_NETIF_API */
+
+err_t tcpip_callback_with_block(tcpip_callback_fn function, void *ctx, u8_t block);
+#define tcpip_callback(f, ctx)              tcpip_callback_with_block(f, ctx, 1)
+
+struct tcpip_callback_msg* tcpip_callbackmsg_new(tcpip_callback_fn function, void *ctx);
+void   tcpip_callbackmsg_delete(struct tcpip_callback_msg* msg);
+err_t  tcpip_trycallback(struct tcpip_callback_msg* msg);
+
+/* free pbufs or heap memory from another context without blocking */
+err_t pbuf_free_callback(struct pbuf *p);
+err_t mem_free_callback(void *m);
+
+#if LWIP_TCPIP_TIMEOUT
+err_t tcpip_timeout(u32_t msecs, sys_timeout_handler h, void *arg);
+err_t tcpip_untimeout(sys_timeout_handler h, void *arg);
+#endif /* LWIP_TCPIP_TIMEOUT */
+
+enum tcpip_msg_type {
+#if LWIP_NETCONN
+  TCPIP_MSG_API,
+#endif /* LWIP_NETCONN */
+  TCPIP_MSG_INPKT,
+#if LWIP_NETIF_API
+  TCPIP_MSG_NETIFAPI,
+#endif /* LWIP_NETIF_API */
+#if LWIP_TCPIP_TIMEOUT
+  TCPIP_MSG_TIMEOUT,
+  TCPIP_MSG_UNTIMEOUT,
+#endif /* LWIP_TCPIP_TIMEOUT */
+  TCPIP_MSG_CALLBACK,
+  TCPIP_MSG_CALLBACK_STATIC
+};
+
+struct tcpip_msg {
+  enum tcpip_msg_type type;
+  sys_sem_t *sem;
+  union {
+#if LWIP_NETCONN
+    struct api_msg *apimsg;
+#endif /* LWIP_NETCONN */
+#if LWIP_NETIF_API
+    struct netifapi_msg *netifapimsg;
+#endif /* LWIP_NETIF_API */
+    struct {
+      struct pbuf *p;
+      struct netif *netif;
+    } inp;
+    struct {
+      tcpip_callback_fn function;
+      void *ctx;
+    } cb;
+#if LWIP_TCPIP_TIMEOUT
+    struct {
+      u32_t msecs;
+      sys_timeout_handler h;
+      void *arg;
+    } tmo;
+#endif /* LWIP_TCPIP_TIMEOUT */
+  } msg;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !NO_SYS */
+
+#endif /* __LWIP_TCPIP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/timers.h b/external/badvpn_dns/lwip/src/include/lwip/timers.h
new file mode 100644
index 0000000..04e78e0
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/timers.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *         Simon Goldschmidt
+ *
+ */
+#ifndef __LWIP_TIMERS_H__
+#define __LWIP_TIMERS_H__
+
+#include "lwip/opt.h"
+
+/* Timers are not supported when NO_SYS==1 and NO_SYS_NO_TIMERS==1 */
+#define LWIP_TIMERS (!NO_SYS || (NO_SYS && !NO_SYS_NO_TIMERS))
+
+#if LWIP_TIMERS
+
+#include "lwip/err.h"
+#if !NO_SYS
+#include "lwip/sys.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef LWIP_DEBUG_TIMERNAMES
+#ifdef LWIP_DEBUG
+#define LWIP_DEBUG_TIMERNAMES SYS_DEBUG
+#else /* LWIP_DEBUG */
+#define LWIP_DEBUG_TIMERNAMES 0
+#endif /* LWIP_DEBUG*/
+#endif
+
+/** Function prototype for a timeout callback function. Register such a function
+ * using sys_timeout().
+ *
+ * @param arg Additional argument to pass to the function - set up by sys_timeout()
+ */
+typedef void (* sys_timeout_handler)(void *arg);
+
+struct sys_timeo {
+  struct sys_timeo *next;
+  u32_t time;
+  sys_timeout_handler h;
+  void *arg;
+#if LWIP_DEBUG_TIMERNAMES
+  const char* handler_name;
+#endif /* LWIP_DEBUG_TIMERNAMES */
+};
+
+void sys_timeouts_init(void);
+
+#if LWIP_DEBUG_TIMERNAMES
+void sys_timeout_debug(u32_t msecs, sys_timeout_handler handler, void *arg, const char* handler_name);
+#define sys_timeout(msecs, handler, arg) sys_timeout_debug(msecs, handler, arg, #handler)
+#else /* LWIP_DEBUG_TIMERNAMES */
+void sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg);
+#endif /* LWIP_DEBUG_TIMERNAMES */
+
+void sys_untimeout(sys_timeout_handler handler, void *arg);
+#if NO_SYS
+void sys_check_timeouts(void);
+void sys_restart_timeouts(void);
+#else /* NO_SYS */
+void sys_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg);
+#endif /* NO_SYS */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_TIMERS */
+#endif /* __LWIP_TIMERS_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/lwip/udp.h b/external/badvpn_dns/lwip/src/include/lwip/udp.h
new file mode 100644
index 0000000..14d5c0a
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/lwip/udp.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __LWIP_UDP_H__
+#define __LWIP_UDP_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_UDP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+#include "lwip/ip_addr.h"
+#include "lwip/ip.h"
+#include "lwip/ip6_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UDP_HLEN 8
+
+/* Fields are (of course) in network byte order. */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct udp_hdr {
+  PACK_STRUCT_FIELD(u16_t src);
+  PACK_STRUCT_FIELD(u16_t dest);  /* src/dest UDP ports */
+  PACK_STRUCT_FIELD(u16_t len);
+  PACK_STRUCT_FIELD(u16_t chksum);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define UDP_FLAGS_NOCHKSUM       0x01U
+#define UDP_FLAGS_UDPLITE        0x02U
+#define UDP_FLAGS_CONNECTED      0x04U
+#define UDP_FLAGS_MULTICAST_LOOP 0x08U
+
+struct udp_pcb;
+
+/** Function prototype for udp pcb receive callback functions
+ * addr and port are in same byte order as in the pcb
+ * The callback is responsible for freeing the pbuf
+ * if it's not used any more.
+ *
+ * ATTENTION: Be aware that 'addr' points into the pbuf 'p' so freeing this pbuf
+ *            makes 'addr' invalid, too.
+ *
+ * @param arg user supplied argument (udp_pcb.recv_arg)
+ * @param pcb the udp_pcb which received data
+ * @param p the packet buffer that was received
+ * @param addr the remote IP address from which the packet was received
+ * @param port the remote port from which the packet was received
+ */
+typedef void (*udp_recv_fn)(void *arg, struct udp_pcb *pcb, struct pbuf *p,
+    ip_addr_t *addr, u16_t port);
+
+#if LWIP_IPV6
+/** Function prototype for udp pcb IPv6 receive callback functions
+ * The callback is responsible for freeing the pbuf
+ * if it's not used any more.
+ *
+ * @param arg user supplied argument (udp_pcb.recv_arg)
+ * @param pcb the udp_pcb which received data
+ * @param p the packet buffer that was received
+ * @param addr the remote IPv6 address from which the packet was received
+ * @param port the remote port from which the packet was received
+ */
+typedef void (*udp_recv_ip6_fn)(void *arg, struct udp_pcb *pcb, struct pbuf *p,
+    ip6_addr_t *addr, u16_t port);
+#endif /* LWIP_IPV6 */
+
+#if LWIP_IPV6
+#define UDP_PCB_RECV_IP6  udp_recv_ip6_fn ip6;
+#else
+#define UDP_PCB_RECV_IP6
+#endif /* LWIP_IPV6 */
+
+struct udp_pcb {
+/* Common members of all PCB types */
+  IP_PCB;
+
+/* Protocol specific PCB members */
+
+  struct udp_pcb *next;
+
+  u8_t flags;
+  /** ports are in host byte order */
+  u16_t local_port, remote_port;
+
+#if LWIP_IGMP
+  /** outgoing network interface for multicast packets */
+  ip_addr_t multicast_ip;
+#endif /* LWIP_IGMP */
+
+#if LWIP_UDPLITE
+  /** used for UDP_LITE only */
+  u16_t chksum_len_rx, chksum_len_tx;
+#endif /* LWIP_UDPLITE */
+
+  /** receive callback function */
+  union {
+    udp_recv_fn ip4;
+    UDP_PCB_RECV_IP6
+  }recv;
+  /** user-supplied argument for the recv callback */
+  void *recv_arg;  
+};
+/* udp_pcbs export for exernal reference (e.g. SNMP agent) */
+extern struct udp_pcb *udp_pcbs;
+
+/* The following functions is the application layer interface to the
+   UDP code. */
+struct udp_pcb * udp_new        (void);
+void             udp_remove     (struct udp_pcb *pcb);
+err_t            udp_bind       (struct udp_pcb *pcb, ip_addr_t *ipaddr,
+                                 u16_t port);
+err_t            udp_connect    (struct udp_pcb *pcb, ip_addr_t *ipaddr,
+                                 u16_t port);
+void             udp_disconnect (struct udp_pcb *pcb);
+void             udp_recv       (struct udp_pcb *pcb, udp_recv_fn recv,
+                                 void *recv_arg);
+err_t            udp_sendto_if  (struct udp_pcb *pcb, struct pbuf *p,
+                                 ip_addr_t *dst_ip, u16_t dst_port,
+                                 struct netif *netif);
+err_t            udp_sendto     (struct udp_pcb *pcb, struct pbuf *p,
+                                 ip_addr_t *dst_ip, u16_t dst_port);
+err_t            udp_send       (struct udp_pcb *pcb, struct pbuf *p);
+
+#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
+err_t            udp_sendto_if_chksum(struct udp_pcb *pcb, struct pbuf *p,
+                                 ip_addr_t *dst_ip, u16_t dst_port,
+                                 struct netif *netif, u8_t have_chksum,
+                                 u16_t chksum);
+err_t            udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p,
+                                 ip_addr_t *dst_ip, u16_t dst_port,
+                                 u8_t have_chksum, u16_t chksum);
+err_t            udp_send_chksum(struct udp_pcb *pcb, struct pbuf *p,
+                                 u8_t have_chksum, u16_t chksum);
+#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+
+#define          udp_flags(pcb) ((pcb)->flags)
+#define          udp_setflags(pcb, f)  ((pcb)->flags = (f))
+
+/* The following functions are the lower layer interface to UDP. */
+void             udp_input      (struct pbuf *p, struct netif *inp);
+
+void             udp_init       (void);
+
+#if LWIP_IPV6
+struct udp_pcb * udp_new_ip6(void);
+#define          udp_bind_ip6(pcb, ip6addr, port) \
+                   udp_bind(pcb, ip6_2_ip(ip6addr), port)
+#define          udp_connect_ip6(pcb, ip6addr, port) \
+                   udp_connect(pcb, ip6_2_ip(ip6addr), port)
+#define          udp_recv_ip6(pcb, recv_ip6_fn, recv_arg) \
+                   udp_recv(pcb, (udp_recv_fn)recv_ip6_fn, recv_arg)
+#define          udp_sendto_ip6(pcb, pbuf, ip6addr, port) \
+                   udp_sendto(pcb, pbuf, ip6_2_ip(ip6addr), port)
+#define          udp_sendto_if_ip6(pcb, pbuf, ip6addr, port, netif) \
+                   udp_sendto_if(pcb, pbuf, ip6_2_ip(ip6addr), port, netif)
+#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
+#define          udp_sendto_chksum_ip6(pcb, pbuf, ip6addr, port, have_chk, chksum) \
+                   udp_sendto_chksum(pcb, pbuf, ip6_2_ip(ip6addr), port, have_chk, chksum)
+#define          udp_sendto_if_chksum_ip6(pcb, pbuf, ip6addr, port, netif, have_chk, chksum) \
+                   udp_sendto_if_chksum(pcb, pbuf, ip6_2_ip(ip6addr), port, netif, have_chk, chksum)
+#endif /*LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+#endif /* LWIP_IPV6 */
+
+#if UDP_DEBUG
+void udp_debug_print(struct udp_hdr *udphdr);
+#else
+#define udp_debug_print(udphdr)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_UDP */
+
+#endif /* __LWIP_UDP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/netif/etharp.h b/external/badvpn_dns/lwip/src/include/netif/etharp.h
new file mode 100644
index 0000000..8275a28
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/netif/etharp.h
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
+ * Copyright (c) 2003-2004 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+ * Copyright (c) 2003-2004 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+#ifndef __NETIF_ETHARP_H__
+#define __NETIF_ETHARP_H__
+
+#include "lwip/opt.h"
+
+#if LWIP_ARP || LWIP_ETHERNET /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/pbuf.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+#include "lwip/ip.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef ETHARP_HWADDR_LEN
+#define ETHARP_HWADDR_LEN     6
+#endif
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct eth_addr {
+  PACK_STRUCT_FIELD(u8_t addr[ETHARP_HWADDR_LEN]);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+/** Ethernet header */
+struct eth_hdr {
+#if ETH_PAD_SIZE
+  PACK_STRUCT_FIELD(u8_t padding[ETH_PAD_SIZE]);
+#endif
+  PACK_STRUCT_FIELD(struct eth_addr dest);
+  PACK_STRUCT_FIELD(struct eth_addr src);
+  PACK_STRUCT_FIELD(u16_t type);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define SIZEOF_ETH_HDR (14 + ETH_PAD_SIZE)
+
+#if ETHARP_SUPPORT_VLAN
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+/** VLAN header inserted between ethernet header and payload
+ * if 'type' in ethernet header is ETHTYPE_VLAN.
+ * See IEEE802.Q */
+struct eth_vlan_hdr {
+  PACK_STRUCT_FIELD(u16_t prio_vid);
+  PACK_STRUCT_FIELD(u16_t tpid);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define SIZEOF_VLAN_HDR 4
+#define VLAN_ID(vlan_hdr) (htons((vlan_hdr)->prio_vid) & 0xFFF)
+
+#endif /* ETHARP_SUPPORT_VLAN */
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+/** the ARP message, see RFC 826 ("Packet format") */
+struct etharp_hdr {
+  PACK_STRUCT_FIELD(u16_t hwtype);
+  PACK_STRUCT_FIELD(u16_t proto);
+  PACK_STRUCT_FIELD(u8_t  hwlen);
+  PACK_STRUCT_FIELD(u8_t  protolen);
+  PACK_STRUCT_FIELD(u16_t opcode);
+  PACK_STRUCT_FIELD(struct eth_addr shwaddr);
+  PACK_STRUCT_FIELD(struct ip_addr2 sipaddr);
+  PACK_STRUCT_FIELD(struct eth_addr dhwaddr);
+  PACK_STRUCT_FIELD(struct ip_addr2 dipaddr);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#define SIZEOF_ETHARP_HDR 28
+#define SIZEOF_ETHARP_PACKET (SIZEOF_ETH_HDR + SIZEOF_ETHARP_HDR)
+
+/** 5 seconds period */
+#define ARP_TMR_INTERVAL 5000
+
+#define ETHTYPE_ARP       0x0806U
+#define ETHTYPE_IP        0x0800U
+#define ETHTYPE_VLAN      0x8100U
+#define ETHTYPE_IPV6      0x86DDU
+#define ETHTYPE_PPPOEDISC 0x8863U  /* PPP Over Ethernet Discovery Stage */
+#define ETHTYPE_PPPOE     0x8864U  /* PPP Over Ethernet Session Stage */
+
+/** MEMCPY-like macro to copy to/from struct eth_addr's that are local variables
+ * or known to be 32-bit aligned within the protocol header. */
+#ifndef ETHADDR32_COPY
+#define ETHADDR32_COPY(src, dst)  SMEMCPY(src, dst, ETHARP_HWADDR_LEN)
+#endif
+
+/** MEMCPY-like macro to copy to/from struct eth_addr's that are no local
+ * variables and known to be 16-bit aligned within the protocol header. */
+#ifndef ETHADDR16_COPY
+#define ETHADDR16_COPY(src, dst)  SMEMCPY(src, dst, ETHARP_HWADDR_LEN)
+#endif
+
+#if LWIP_ARP /* don't build if not configured for use in lwipopts.h */
+
+/** ARP message types (opcodes) */
+#define ARP_REQUEST 1
+#define ARP_REPLY   2
+
+/** Define this to 1 and define LWIP_ARP_FILTER_NETIF_FN(pbuf, netif, type)
+ * to a filter function that returns the correct netif when using multiple
+ * netifs on one hardware interface where the netif's low-level receive
+ * routine cannot decide for the correct netif (e.g. when mapping multiple
+ * IP addresses to one hardware interface).
+ */
+#ifndef LWIP_ARP_FILTER_NETIF
+#define LWIP_ARP_FILTER_NETIF 0
+#endif
+
+#if ARP_QUEUEING
+/** struct for queueing outgoing packets for unknown address
+  * defined here to be accessed by memp.h
+  */
+struct etharp_q_entry {
+  struct etharp_q_entry *next;
+  struct pbuf *p;
+};
+#endif /* ARP_QUEUEING */
+
+#define etharp_init() /* Compatibility define, not init needed. */
+void etharp_tmr(void);
+s8_t etharp_find_addr(struct netif *netif, ip_addr_t *ipaddr,
+         struct eth_addr **eth_ret, ip_addr_t **ip_ret);
+err_t etharp_output(struct netif *netif, struct pbuf *q, ip_addr_t *ipaddr);
+err_t etharp_query(struct netif *netif, ip_addr_t *ipaddr, struct pbuf *q);
+err_t etharp_request(struct netif *netif, ip_addr_t *ipaddr);
+/** For Ethernet network interfaces, we might want to send "gratuitous ARP";
+ *  this is an ARP packet sent by a node in order to spontaneously cause other
+ *  nodes to update an entry in their ARP cache.
+ *  From RFC 3220 "IP Mobility Support for IPv4" section 4.6. */
+#define etharp_gratuitous(netif) etharp_request((netif), &(netif)->ip_addr)
+void etharp_cleanup_netif(struct netif *netif);
+
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+err_t etharp_add_static_entry(ip_addr_t *ipaddr, struct eth_addr *ethaddr);
+err_t etharp_remove_static_entry(ip_addr_t *ipaddr);
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+#if LWIP_AUTOIP
+err_t etharp_raw(struct netif *netif, const struct eth_addr *ethsrc_addr,
+                 const struct eth_addr *ethdst_addr,
+                 const struct eth_addr *hwsrc_addr, const ip_addr_t *ipsrc_addr,
+                 const struct eth_addr *hwdst_addr, const ip_addr_t *ipdst_addr,
+                 const u16_t opcode);
+#endif /* LWIP_AUTOIP */
+
+#endif /* LWIP_ARP */
+
+err_t ethernet_input(struct pbuf *p, struct netif *netif);
+
+#define eth_addr_cmp(addr1, addr2) (memcmp((addr1)->addr, (addr2)->addr, ETHARP_HWADDR_LEN) == 0)
+
+extern const struct eth_addr ethbroadcast, ethzero;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_ARP || LWIP_ETHERNET */
+
+#endif /* __NETIF_ARP_H__ */
diff --git a/external/badvpn_dns/lwip/src/include/netif/ppp_oe.h b/external/badvpn_dns/lwip/src/include/netif/ppp_oe.h
new file mode 100644
index 0000000..e1cdfa5
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/netif/ppp_oe.h
@@ -0,0 +1,190 @@
+/*****************************************************************************
+* ppp_oe.h - PPP Over Ethernet implementation for lwIP.
+*
+* Copyright (c) 2006 by Marc Boucher, Services Informatiques (MBSI) inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 06-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+*****************************************************************************/
+
+
+
+/* based on NetBSD: if_pppoe.c,v 1.64 2006/01/31 23:50:15 martin Exp */
+
+/*-
+ * Copyright (c) 2002 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Martin Husemann <martin@xxxxxxxxxx>.
+ *
+ * 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. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *        This product includes software developed by the NetBSD
+ *        Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS
+ * 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 PPP_OE_H
+#define PPP_OE_H
+
+#include "lwip/opt.h"
+
+#if PPPOE_SUPPORT > 0
+
+#include "netif/etharp.h"
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct pppoehdr {
+  PACK_STRUCT_FIELD(u8_t vertype);
+  PACK_STRUCT_FIELD(u8_t code);
+  PACK_STRUCT_FIELD(u16_t session);
+  PACK_STRUCT_FIELD(u16_t plen);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct pppoetag {
+  PACK_STRUCT_FIELD(u16_t tag);
+  PACK_STRUCT_FIELD(u16_t len);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+
+#define PPPOE_STATE_INITIAL   0
+#define PPPOE_STATE_PADI_SENT 1
+#define PPPOE_STATE_PADR_SENT 2
+#define PPPOE_STATE_SESSION   3
+#define PPPOE_STATE_CLOSING   4
+/* passive */
+#define PPPOE_STATE_PADO_SENT 1
+
+#define PPPOE_HEADERLEN       sizeof(struct pppoehdr)
+#define PPPOE_VERTYPE         0x11    /* VER=1, TYPE = 1 */
+
+#define PPPOE_TAG_EOL         0x0000  /* end of list */
+#define PPPOE_TAG_SNAME       0x0101  /* service name */
+#define PPPOE_TAG_ACNAME      0x0102  /* access concentrator name */
+#define PPPOE_TAG_HUNIQUE     0x0103  /* host unique */
+#define PPPOE_TAG_ACCOOKIE    0x0104  /* AC cookie */
+#define PPPOE_TAG_VENDOR      0x0105  /* vendor specific */
+#define PPPOE_TAG_RELAYSID    0x0110  /* relay session id */
+#define PPPOE_TAG_SNAME_ERR   0x0201  /* service name error */
+#define PPPOE_TAG_ACSYS_ERR   0x0202  /* AC system error */
+#define PPPOE_TAG_GENERIC_ERR 0x0203  /* gerneric error */
+
+#define PPPOE_CODE_PADI       0x09    /* Active Discovery Initiation */
+#define PPPOE_CODE_PADO       0x07    /* Active Discovery Offer */
+#define PPPOE_CODE_PADR       0x19    /* Active Discovery Request */
+#define PPPOE_CODE_PADS       0x65    /* Active Discovery Session confirmation */
+#define PPPOE_CODE_PADT       0xA7    /* Active Discovery Terminate */
+
+#ifndef ETHERMTU
+#define ETHERMTU 1500
+#endif
+
+/* two byte PPP protocol discriminator, then IP data */
+#define PPPOE_MAXMTU          (ETHERMTU-PPPOE_HEADERLEN-2)
+
+#ifndef PPPOE_MAX_AC_COOKIE_LEN
+#define PPPOE_MAX_AC_COOKIE_LEN   64
+#endif
+
+struct pppoe_softc {
+  struct pppoe_softc *next;
+  struct netif *sc_ethif;      /* ethernet interface we are using */
+  int sc_pd;                   /* ppp unit number */
+  void (*sc_linkStatusCB)(int pd, int up);
+
+  int sc_state;                /* discovery phase or session connected */
+  struct eth_addr sc_dest;     /* hardware address of concentrator */
+  u16_t sc_session;            /* PPPoE session id */
+
+#ifdef PPPOE_TODO
+  char *sc_service_name;       /* if != NULL: requested name of service */
+  char *sc_concentrator_name;  /* if != NULL: requested concentrator id */
+#endif /* PPPOE_TODO */
+  u8_t sc_ac_cookie[PPPOE_MAX_AC_COOKIE_LEN]; /* content of AC cookie we must echo back */
+  size_t sc_ac_cookie_len;     /* length of cookie data */
+#ifdef PPPOE_SERVER
+  u8_t *sc_hunique;            /* content of host unique we must echo back */
+  size_t sc_hunique_len;       /* length of host unique */
+#endif
+  int sc_padi_retried;         /* number of PADI retries already done */
+  int sc_padr_retried;         /* number of PADR retries already done */
+};
+
+
+#define pppoe_init() /* compatibility define, no initialization needed */
+
+err_t pppoe_create(struct netif *ethif, int pd, void (*linkStatusCB)(int pd, int up), struct pppoe_softc **scptr);
+err_t pppoe_destroy(struct netif *ifp);
+
+int pppoe_connect(struct pppoe_softc *sc);
+void pppoe_disconnect(struct pppoe_softc *sc);
+
+void pppoe_disc_input(struct netif *netif, struct pbuf *p);
+void pppoe_data_input(struct netif *netif, struct pbuf *p);
+
+err_t pppoe_xmit(struct pppoe_softc *sc, struct pbuf *pb);
+
+/** used in ppp.c */
+#define PPPOE_HDRLEN (sizeof(struct eth_hdr) + PPPOE_HEADERLEN)
+
+#endif /* PPPOE_SUPPORT */
+
+#endif /* PPP_OE_H */
diff --git a/external/badvpn_dns/lwip/src/include/netif/slipif.h b/external/badvpn_dns/lwip/src/include/netif/slipif.h
new file mode 100644
index 0000000..7b6ce5e
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/netif/slipif.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2001, Swedish Institute of Computer Science.
+ * All rights reserved. 
+ *
+ * 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 Institute 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 INSTITUTE 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 INSTITUTE OR CONTRIBUTORS 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. 
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+#ifndef __NETIF_SLIPIF_H__
+#define __NETIF_SLIPIF_H__
+
+#include "lwip/opt.h"
+#include "lwip/netif.h"
+
+/** Set this to 1 to start a thread that blocks reading on the serial line
+ * (using sio_read()).
+ */
+#ifndef SLIP_USE_RX_THREAD
+#define SLIP_USE_RX_THREAD !NO_SYS
+#endif
+
+/** Set this to 1 to enable functions to pass in RX bytes from ISR context.
+ * If enabled, slipif_received_byte[s]() process incoming bytes and put assembled
+ * packets on a queue, which is fed into lwIP from slipif_poll().
+ * If disabled, slipif_poll() polls the serila line (using sio_tryread()).
+ */
+#ifndef SLIP_RX_FROM_ISR
+#define SLIP_RX_FROM_ISR 0
+#endif
+
+/** Set this to 1 (default for SLIP_RX_FROM_ISR) to queue incoming packets
+ * received by slipif_received_byte[s]() as long as PBUF_POOL pbufs are available.
+ * If disabled, packets will be dropped if more than one packet is received.
+ */
+#ifndef SLIP_RX_QUEUE
+#define SLIP_RX_QUEUE SLIP_RX_FROM_ISR
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+err_t slipif_init(struct netif * netif);
+void slipif_poll(struct netif *netif);
+#if SLIP_RX_FROM_ISR
+void slipif_process_rxqueue(struct netif *netif);
+void slipif_received_byte(struct netif *netif, u8_t data);
+void slipif_received_bytes(struct netif *netif, u8_t *data, u8_t len);
+#endif /* SLIP_RX_FROM_ISR */
+
+#ifdef __cplusplus
+}
+#endif
+ 
+#endif 
+
diff --git a/external/badvpn_dns/lwip/src/include/posix/netdb.h b/external/badvpn_dns/lwip/src/include/posix/netdb.h
new file mode 100644
index 0000000..7134032
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/posix/netdb.h
@@ -0,0 +1,33 @@
+/**
+ * @file
+ * This file is a posix wrapper for lwip/netdb.h.
+ */
+
+/*
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ */
+
+#include "lwip/netdb.h"
diff --git a/external/badvpn_dns/lwip/src/include/posix/sys/socket.h b/external/badvpn_dns/lwip/src/include/posix/sys/socket.h
new file mode 100644
index 0000000..f7c7066
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/include/posix/sys/socket.h
@@ -0,0 +1,33 @@
+/**
+ * @file
+ * This file is a posix wrapper for lwip/sockets.h.
+ */
+
+/*
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ */
+
+#include "lwip/sockets.h"
diff --git a/external/badvpn_dns/lwip/src/netif/FILES b/external/badvpn_dns/lwip/src/netif/FILES
new file mode 100644
index 0000000..099dbf3
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/FILES
@@ -0,0 +1,29 @@
+This directory contains generic network interface device drivers that
+do not contain any hardware or architecture specific code. The files
+are:
+
+etharp.c
+          Implements the ARP (Address Resolution Protocol) over
+          Ethernet. The code in this file should be used together with
+          Ethernet device drivers. Note that this module has been
+          largely made Ethernet independent so you should be able to
+          adapt this for other link layers (such as Firewire).
+
+ethernetif.c
+          An example of how an Ethernet device driver could look. This
+          file can be used as a "skeleton" for developing new Ethernet
+          network device drivers. It uses the etharp.c ARP code.
+
+loopif.c
+          A "loopback" network interface driver. It requires configuration
+          through the define LWIP_LOOPIF_MULTITHREADING (see opt.h).
+
+slipif.c
+          A generic implementation of the SLIP (Serial Line IP)
+          protocol. It requires a sio (serial I/O) module to work.
+
+ppp/      Point-to-Point Protocol stack
+          The PPP stack has been ported from ucip (http://ucip.sourceforge.net).
+          It matches quite well to pppd 2.3.1 (http://ppp.samba.org), although
+          compared to that, it has some modifications for embedded systems and
+          the source code has been reordered a bit.
\ No newline at end of file
diff --git a/external/badvpn_dns/lwip/src/netif/etharp.c b/external/badvpn_dns/lwip/src/netif/etharp.c
new file mode 100644
index 0000000..1b7eb98
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/etharp.c
@@ -0,0 +1,1413 @@
+/**
+ * @file
+ * Address Resolution Protocol module for IP over Ethernet
+ *
+ * Functionally, ARP is divided into two parts. The first maps an IP address
+ * to a physical address when sending a packet, and the second part answers
+ * requests from other machines for our physical address.
+ *
+ * This implementation complies with RFC 826 (Ethernet ARP). It supports
+ * Gratuitious ARP from RFC3220 (IP Mobility Support for IPv4) section 4.6
+ * if an interface calls etharp_gratuitous(our_netif) upon address change.
+ */
+
+/*
+ * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
+ * Copyright (c) 2003-2004 Leon Woestenberg <leon.woestenberg@xxxxxxx>
+ * Copyright (c) 2003-2004 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ */
+ 
+#include "lwip/opt.h"
+
+#if LWIP_ARP || LWIP_ETHERNET
+
+#include "lwip/ip_addr.h"
+#include "lwip/def.h"
+#include "lwip/ip.h"
+#include "lwip/stats.h"
+#include "lwip/snmp.h"
+#include "lwip/dhcp.h"
+#include "lwip/autoip.h"
+#include "netif/etharp.h"
+#include "lwip/ip6.h"
+
+#if PPPOE_SUPPORT
+#include "netif/ppp_oe.h"
+#endif /* PPPOE_SUPPORT */
+
+#include <string.h>
+
+const struct eth_addr ethbroadcast = {{0xff,0xff,0xff,0xff,0xff,0xff}};
+const struct eth_addr ethzero = {{0,0,0,0,0,0}};
+
+/** The 24-bit IANA multicast OUI is 01-00-5e: */
+#define LL_MULTICAST_ADDR_0 0x01
+#define LL_MULTICAST_ADDR_1 0x00
+#define LL_MULTICAST_ADDR_2 0x5e
+
+#if LWIP_ARP /* don't build if not configured for use in lwipopts.h */
+
+/** the time an ARP entry stays valid after its last update,
+ *  for ARP_TMR_INTERVAL = 5000, this is
+ *  (240 * 5) seconds = 20 minutes.
+ */
+#define ARP_MAXAGE              240
+/** Re-request a used ARP entry 1 minute before it would expire to prevent
+ *  breaking a steadily used connection because the ARP entry timed out. */
+#define ARP_AGE_REREQUEST_USED  (ARP_MAXAGE - 12)
+
+/** the time an ARP entry stays pending after first request,
+ *  for ARP_TMR_INTERVAL = 5000, this is
+ *  (2 * 5) seconds = 10 seconds.
+ * 
+ *  @internal Keep this number at least 2, otherwise it might
+ *  run out instantly if the timeout occurs directly after a request.
+ */
+#define ARP_MAXPENDING 2
+
+#define HWTYPE_ETHERNET 1
+
+enum etharp_state {
+  ETHARP_STATE_EMPTY = 0,
+  ETHARP_STATE_PENDING,
+  ETHARP_STATE_STABLE,
+  ETHARP_STATE_STABLE_REREQUESTING
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+  ,ETHARP_STATE_STATIC
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+};
+
+struct etharp_entry {
+#if ARP_QUEUEING
+  /** Pointer to queue of pending outgoing packets on this ARP entry. */
+  struct etharp_q_entry *q;
+#else /* ARP_QUEUEING */
+  /** Pointer to a single pending outgoing packet on this ARP entry. */
+  struct pbuf *q;
+#endif /* ARP_QUEUEING */
+  ip_addr_t ipaddr;
+  struct netif *netif;
+  struct eth_addr ethaddr;
+  u8_t state;
+  u8_t ctime;
+};
+
+static struct etharp_entry arp_table[ARP_TABLE_SIZE];
+
+#if !LWIP_NETIF_HWADDRHINT
+static u8_t etharp_cached_entry;
+#endif /* !LWIP_NETIF_HWADDRHINT */
+
+/** Try hard to create a new entry - we want the IP address to appear in
+    the cache (even if this means removing an active entry or so). */
+#define ETHARP_FLAG_TRY_HARD     1
+#define ETHARP_FLAG_FIND_ONLY    2
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+#define ETHARP_FLAG_STATIC_ENTRY 4
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+#if LWIP_NETIF_HWADDRHINT
+#define ETHARP_SET_HINT(netif, hint)  if (((netif) != NULL) && ((netif)->addr_hint != NULL))  \
+                                      *((netif)->addr_hint) = (hint);
+#else /* LWIP_NETIF_HWADDRHINT */
+#define ETHARP_SET_HINT(netif, hint)  (etharp_cached_entry = (hint))
+#endif /* LWIP_NETIF_HWADDRHINT */
+
+
+/* Some checks, instead of etharp_init(): */
+#if (LWIP_ARP && (ARP_TABLE_SIZE > 0x7f))
+  #error "ARP_TABLE_SIZE must fit in an s8_t, you have to reduce it in your lwipopts.h"
+#endif
+
+
+#if ARP_QUEUEING
+/**
+ * Free a complete queue of etharp entries
+ *
+ * @param q a qeueue of etharp_q_entry's to free
+ */
+static void
+free_etharp_q(struct etharp_q_entry *q)
+{
+  struct etharp_q_entry *r;
+  LWIP_ASSERT("q != NULL", q != NULL);
+  LWIP_ASSERT("q->p != NULL", q->p != NULL);
+  while (q) {
+    r = q;
+    q = q->next;
+    LWIP_ASSERT("r->p != NULL", (r->p != NULL));
+    pbuf_free(r->p);
+    memp_free(MEMP_ARP_QUEUE, r);
+  }
+}
+#else /* ARP_QUEUEING */
+
+/** Compatibility define: free the queued pbuf */
+#define free_etharp_q(q) pbuf_free(q)
+
+#endif /* ARP_QUEUEING */
+
+/** Clean up ARP table entries */
+static void
+etharp_free_entry(int i)
+{
+  /* remove from SNMP ARP index tree */
+  snmp_delete_arpidx_tree(arp_table[i].netif, &arp_table[i].ipaddr);
+  /* and empty packet queue */
+  if (arp_table[i].q != NULL) {
+    /* remove all queued packets */
+    LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_free_entry: freeing entry %"U16_F", packet queue %p.\n", (u16_t)i, (void *)(arp_table[i].q)));
+    free_etharp_q(arp_table[i].q);
+    arp_table[i].q = NULL;
+  }
+  /* recycle entry for re-use */
+  arp_table[i].state = ETHARP_STATE_EMPTY;
+#ifdef LWIP_DEBUG
+  /* for debugging, clean out the complete entry */
+  arp_table[i].ctime = 0;
+  arp_table[i].netif = NULL;
+  ip_addr_set_zero(&arp_table[i].ipaddr);
+  arp_table[i].ethaddr = ethzero;
+#endif /* LWIP_DEBUG */
+}
+
+/**
+ * Clears expired entries in the ARP table.
+ *
+ * This function should be called every ETHARP_TMR_INTERVAL milliseconds (5 seconds),
+ * in order to expire entries in the ARP table.
+ */
+void
+etharp_tmr(void)
+{
+  u8_t i;
+
+  LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer\n"));
+  /* remove expired entries from the ARP table */
+  for (i = 0; i < ARP_TABLE_SIZE; ++i) {
+    u8_t state = arp_table[i].state;
+    if (state != ETHARP_STATE_EMPTY
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+      && (state != ETHARP_STATE_STATIC)
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+      ) {
+      arp_table[i].ctime++;
+      if ((arp_table[i].ctime >= ARP_MAXAGE) ||
+          ((arp_table[i].state == ETHARP_STATE_PENDING)  &&
+           (arp_table[i].ctime >= ARP_MAXPENDING))) {
+        /* pending or stable entry has become old! */
+        LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer: expired %s entry %"U16_F".\n",
+             arp_table[i].state >= ETHARP_STATE_STABLE ? "stable" : "pending", (u16_t)i));
+        /* clean up entries that have just been expired */
+        etharp_free_entry(i);
+      }
+      else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING) {
+        /* Reset state to stable, so that the next transmitted packet will
+           re-send an ARP request. */
+        arp_table[i].state = ETHARP_STATE_STABLE;
+      }
+#if ARP_QUEUEING
+      /* still pending entry? (not expired) */
+      if (arp_table[i].state == ETHARP_STATE_PENDING) {
+        /* resend an ARP query here? */
+      }
+#endif /* ARP_QUEUEING */
+    }
+  }
+}
+
+/**
+ * Search the ARP table for a matching or new entry.
+ * 
+ * If an IP address is given, return a pending or stable ARP entry that matches
+ * the address. If no match is found, create a new entry with this address set,
+ * but in state ETHARP_EMPTY. The caller must check and possibly change the
+ * state of the returned entry.
+ * 
+ * If ipaddr is NULL, return a initialized new entry in state ETHARP_EMPTY.
+ * 
+ * In all cases, attempt to create new entries from an empty entry. If no
+ * empty entries are available and ETHARP_FLAG_TRY_HARD flag is set, recycle
+ * old entries. Heuristic choose the least important entry for recycling.
+ *
+ * @param ipaddr IP address to find in ARP cache, or to add if not found.
+ * @param flags @see definition of ETHARP_FLAG_*
+ * @param netif netif related to this address (used for NETIF_HWADDRHINT)
+ *  
+ * @return The ARP entry index that matched or is created, ERR_MEM if no
+ * entry is found or could be recycled.
+ */
+static s8_t
+etharp_find_entry(ip_addr_t *ipaddr, u8_t flags)
+{
+  s8_t old_pending = ARP_TABLE_SIZE, old_stable = ARP_TABLE_SIZE;
+  s8_t empty = ARP_TABLE_SIZE;
+  u8_t i = 0, age_pending = 0, age_stable = 0;
+  /* oldest entry with packets on queue */
+  s8_t old_queue = ARP_TABLE_SIZE;
+  /* its age */
+  u8_t age_queue = 0;
+
+  /**
+   * a) do a search through the cache, remember candidates
+   * b) select candidate entry
+   * c) create new entry
+   */
+
+  /* a) in a single search sweep, do all of this
+   * 1) remember the first empty entry (if any)
+   * 2) remember the oldest stable entry (if any)
+   * 3) remember the oldest pending entry without queued packets (if any)
+   * 4) remember the oldest pending entry with queued packets (if any)
+   * 5) search for a matching IP entry, either pending or stable
+   *    until 5 matches, or all entries are searched for.
+   */
+
+  for (i = 0; i < ARP_TABLE_SIZE; ++i) {
+    u8_t state = arp_table[i].state;
+    /* no empty entry found yet and now we do find one? */
+    if ((empty == ARP_TABLE_SIZE) && (state == ETHARP_STATE_EMPTY)) {
+      LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_find_entry: found empty entry %"U16_F"\n", (u16_t)i));
+      /* remember first empty entry */
+      empty = i;
+    } else if (state != ETHARP_STATE_EMPTY) {
+      LWIP_ASSERT("state == ETHARP_STATE_PENDING || state >= ETHARP_STATE_STABLE",
+        state == ETHARP_STATE_PENDING || state >= ETHARP_STATE_STABLE);
+      /* if given, does IP address match IP address in ARP entry? */
+      if (ipaddr && ip_addr_cmp(ipaddr, &arp_table[i].ipaddr)) {
+        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: found matching entry %"U16_F"\n", (u16_t)i));
+        /* found exact IP address match, simply bail out */
+        return i;
+      }
+      /* pending entry? */
+      if (state == ETHARP_STATE_PENDING) {
+        /* pending with queued packets? */
+        if (arp_table[i].q != NULL) {
+          if (arp_table[i].ctime >= age_queue) {
+            old_queue = i;
+            age_queue = arp_table[i].ctime;
+          }
+        } else
+        /* pending without queued packets? */
+        {
+          if (arp_table[i].ctime >= age_pending) {
+            old_pending = i;
+            age_pending = arp_table[i].ctime;
+          }
+        }
+      /* stable entry? */
+      } else if (state >= ETHARP_STATE_STABLE) {
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+        /* don't record old_stable for static entries since they never expire */
+        if (state < ETHARP_STATE_STATIC)
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+        {
+          /* remember entry with oldest stable entry in oldest, its age in maxtime */
+          if (arp_table[i].ctime >= age_stable) {
+            old_stable = i;
+            age_stable = arp_table[i].ctime;
+          }
+        }
+      }
+    }
+  }
+  /* { we have no match } => try to create a new entry */
+   
+  /* don't create new entry, only search? */
+  if (((flags & ETHARP_FLAG_FIND_ONLY) != 0) ||
+      /* or no empty entry found and not allowed to recycle? */
+      ((empty == ARP_TABLE_SIZE) && ((flags & ETHARP_FLAG_TRY_HARD) == 0))) {
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: no empty entry found and not allowed to recycle\n"));
+    return (s8_t)ERR_MEM;
+  }
+  
+  /* b) choose the least destructive entry to recycle:
+   * 1) empty entry
+   * 2) oldest stable entry
+   * 3) oldest pending entry without queued packets
+   * 4) oldest pending entry with queued packets
+   * 
+   * { ETHARP_FLAG_TRY_HARD is set at this point }
+   */ 
+
+  /* 1) empty entry available? */
+  if (empty < ARP_TABLE_SIZE) {
+    i = empty;
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting empty entry %"U16_F"\n", (u16_t)i));
+  } else {
+    /* 2) found recyclable stable entry? */
+    if (old_stable < ARP_TABLE_SIZE) {
+      /* recycle oldest stable*/
+      i = old_stable;
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest stable entry %"U16_F"\n", (u16_t)i));
+      /* no queued packets should exist on stable entries */
+      LWIP_ASSERT("arp_table[i].q == NULL", arp_table[i].q == NULL);
+    /* 3) found recyclable pending entry without queued packets? */
+    } else if (old_pending < ARP_TABLE_SIZE) {
+      /* recycle oldest pending */
+      i = old_pending;
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest pending entry %"U16_F" (without queue)\n", (u16_t)i));
+    /* 4) found recyclable pending entry with queued packets? */
+    } else if (old_queue < ARP_TABLE_SIZE) {
+      /* recycle oldest pending (queued packets are free in etharp_free_entry) */
+      i = old_queue;
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: selecting oldest pending entry %"U16_F", freeing packet queue %p\n", (u16_t)i, (void *)(arp_table[i].q)));
+      /* no empty or recyclable entries found */
+    } else {
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_find_entry: no empty or recyclable entries found\n"));
+      return (s8_t)ERR_MEM;
+    }
+
+    /* { empty or recyclable entry found } */
+    LWIP_ASSERT("i < ARP_TABLE_SIZE", i < ARP_TABLE_SIZE);
+    etharp_free_entry(i);
+  }
+
+  LWIP_ASSERT("i < ARP_TABLE_SIZE", i < ARP_TABLE_SIZE);
+  LWIP_ASSERT("arp_table[i].state == ETHARP_STATE_EMPTY",
+    arp_table[i].state == ETHARP_STATE_EMPTY);
+
+  /* IP address given? */
+  if (ipaddr != NULL) {
+    /* set IP address */
+    ip_addr_copy(arp_table[i].ipaddr, *ipaddr);
+  }
+  arp_table[i].ctime = 0;
+  return (err_t)i;
+}
+
+/**
+ * Send an IP packet on the network using netif->linkoutput
+ * The ethernet header is filled in before sending.
+ *
+ * @params netif the lwIP network interface on which to send the packet
+ * @params p the packet to send, p->payload pointing to the (uninitialized) ethernet header
+ * @params src the source MAC address to be copied into the ethernet header
+ * @params dst the destination MAC address to be copied into the ethernet header
+ * @return ERR_OK if the packet was sent, any other err_t on failure
+ */
+static err_t
+etharp_send_ip(struct netif *netif, struct pbuf *p, struct eth_addr *src, struct eth_addr *dst)
+{
+  struct eth_hdr *ethhdr = (struct eth_hdr *)p->payload;
+
+  LWIP_ASSERT("netif->hwaddr_len must be the same as ETHARP_HWADDR_LEN for etharp!",
+              (netif->hwaddr_len == ETHARP_HWADDR_LEN));
+  ETHADDR32_COPY(&ethhdr->dest, dst);
+  ETHADDR16_COPY(&ethhdr->src, src);
+  ethhdr->type = PP_HTONS(ETHTYPE_IP);
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_send_ip: sending packet %p\n", (void *)p));
+  /* send the packet */
+  return netif->linkoutput(netif, p);
+}
+
+/**
+ * Update (or insert) a IP/MAC address pair in the ARP cache.
+ *
+ * If a pending entry is resolved, any queued packets will be sent
+ * at this point.
+ * 
+ * @param netif netif related to this entry (used for NETIF_ADDRHINT)
+ * @param ipaddr IP address of the inserted ARP entry.
+ * @param ethaddr Ethernet address of the inserted ARP entry.
+ * @param flags @see definition of ETHARP_FLAG_*
+ *
+ * @return
+ * - ERR_OK Succesfully updated ARP cache.
+ * - ERR_MEM If we could not add a new ARP entry when ETHARP_FLAG_TRY_HARD was set.
+ * - ERR_ARG Non-unicast address given, those will not appear in ARP cache.
+ *
+ * @see pbuf_free()
+ */
+static err_t
+etharp_update_arp_entry(struct netif *netif, ip_addr_t *ipaddr, struct eth_addr *ethaddr, u8_t flags)
+{
+  s8_t i;
+  LWIP_ASSERT("netif->hwaddr_len == ETHARP_HWADDR_LEN", netif->hwaddr_len == ETHARP_HWADDR_LEN);
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: %"U16_F".%"U16_F".%"U16_F".%"U16_F" - %02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F"\n",
+    ip4_addr1_16(ipaddr), ip4_addr2_16(ipaddr), ip4_addr3_16(ipaddr), ip4_addr4_16(ipaddr),
+    ethaddr->addr[0], ethaddr->addr[1], ethaddr->addr[2],
+    ethaddr->addr[3], ethaddr->addr[4], ethaddr->addr[5]));
+  /* non-unicast address? */
+  if (ip_addr_isany(ipaddr) ||
+      ip_addr_isbroadcast(ipaddr, netif) ||
+      ip_addr_ismulticast(ipaddr)) {
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: will not add non-unicast IP address to ARP cache\n"));
+    return ERR_ARG;
+  }
+  /* find or create ARP entry */
+  i = etharp_find_entry(ipaddr, flags);
+  /* bail out if no entry could be found */
+  if (i < 0) {
+    return (err_t)i;
+  }
+
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+  if (flags & ETHARP_FLAG_STATIC_ENTRY) {
+    /* record static type */
+    arp_table[i].state = ETHARP_STATE_STATIC;
+  } else
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+  {
+    /* mark it stable */
+    arp_table[i].state = ETHARP_STATE_STABLE;
+  }
+
+  /* record network interface */
+  arp_table[i].netif = netif;
+  /* insert in SNMP ARP index tree */
+  snmp_insert_arpidx_tree(netif, &arp_table[i].ipaddr);
+
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: updating stable entry %"S16_F"\n", (s16_t)i));
+  /* update address */
+  ETHADDR32_COPY(&arp_table[i].ethaddr, ethaddr);
+  /* reset time stamp */
+  arp_table[i].ctime = 0;
+  /* this is where we will send out queued packets! */
+#if ARP_QUEUEING
+  while (arp_table[i].q != NULL) {
+    struct pbuf *p;
+    /* remember remainder of queue */
+    struct etharp_q_entry *q = arp_table[i].q;
+    /* pop first item off the queue */
+    arp_table[i].q = q->next;
+    /* get the packet pointer */
+    p = q->p;
+    /* now queue entry can be freed */
+    memp_free(MEMP_ARP_QUEUE, q);
+#else /* ARP_QUEUEING */
+  if (arp_table[i].q != NULL) {
+    struct pbuf *p = arp_table[i].q;
+    arp_table[i].q = NULL;
+#endif /* ARP_QUEUEING */
+    /* send the queued IP packet */
+    etharp_send_ip(netif, p, (struct eth_addr*)(netif->hwaddr), ethaddr);
+    /* free the queued IP packet */
+    pbuf_free(p);
+  }
+  return ERR_OK;
+}
+
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+/** Add a new static entry to the ARP table. If an entry exists for the
+ * specified IP address, this entry is overwritten.
+ * If packets are queued for the specified IP address, they are sent out.
+ *
+ * @param ipaddr IP address for the new static entry
+ * @param ethaddr ethernet address for the new static entry
+ * @return @see return values of etharp_add_static_entry
+ */
+err_t
+etharp_add_static_entry(ip_addr_t *ipaddr, struct eth_addr *ethaddr)
+{
+  struct netif *netif;
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_add_static_entry: %"U16_F".%"U16_F".%"U16_F".%"U16_F" - %02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F"\n",
+    ip4_addr1_16(ipaddr), ip4_addr2_16(ipaddr), ip4_addr3_16(ipaddr), ip4_addr4_16(ipaddr),
+    ethaddr->addr[0], ethaddr->addr[1], ethaddr->addr[2],
+    ethaddr->addr[3], ethaddr->addr[4], ethaddr->addr[5]));
+
+  netif = ip_route(ipaddr);
+  if (netif == NULL) {
+    return ERR_RTE;
+  }
+
+  return etharp_update_arp_entry(netif, ipaddr, ethaddr, ETHARP_FLAG_TRY_HARD | ETHARP_FLAG_STATIC_ENTRY);
+}
+
+/** Remove a static entry from the ARP table previously added with a call to
+ * etharp_add_static_entry.
+ *
+ * @param ipaddr IP address of the static entry to remove
+ * @return ERR_OK: entry removed
+ *         ERR_MEM: entry wasn't found
+ *         ERR_ARG: entry wasn't a static entry but a dynamic one
+ */
+err_t
+etharp_remove_static_entry(ip_addr_t *ipaddr)
+{
+  s8_t i;
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_remove_static_entry: %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+    ip4_addr1_16(ipaddr), ip4_addr2_16(ipaddr), ip4_addr3_16(ipaddr), ip4_addr4_16(ipaddr)));
+
+  /* find or create ARP entry */
+  i = etharp_find_entry(ipaddr, ETHARP_FLAG_FIND_ONLY);
+  /* bail out if no entry could be found */
+  if (i < 0) {
+    return (err_t)i;
+  }
+
+  if (arp_table[i].state != ETHARP_STATE_STATIC) {
+    /* entry wasn't a static entry, cannot remove it */
+    return ERR_ARG;
+  }
+  /* entry found, free it */
+  etharp_free_entry(i);
+  return ERR_OK;
+}
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+/**
+ * Remove all ARP table entries of the specified netif.
+ *
+ * @param netif points to a network interface
+ */
+void etharp_cleanup_netif(struct netif *netif)
+{
+  u8_t i;
+
+  for (i = 0; i < ARP_TABLE_SIZE; ++i) {
+    u8_t state = arp_table[i].state;
+    if ((state != ETHARP_STATE_EMPTY) && (arp_table[i].netif == netif)) {
+      etharp_free_entry(i);
+    }
+  }
+}
+
+/**
+ * Finds (stable) ethernet/IP address pair from ARP table
+ * using interface and IP address index.
+ * @note the addresses in the ARP table are in network order!
+ *
+ * @param netif points to interface index
+ * @param ipaddr points to the (network order) IP address index
+ * @param eth_ret points to return pointer
+ * @param ip_ret points to return pointer
+ * @return table index if found, -1 otherwise
+ */
+s8_t
+etharp_find_addr(struct netif *netif, ip_addr_t *ipaddr,
+         struct eth_addr **eth_ret, ip_addr_t **ip_ret)
+{
+  s8_t i;
+
+  LWIP_ASSERT("eth_ret != NULL && ip_ret != NULL",
+    eth_ret != NULL && ip_ret != NULL);
+
+  LWIP_UNUSED_ARG(netif);
+
+  i = etharp_find_entry(ipaddr, ETHARP_FLAG_FIND_ONLY);
+  if((i >= 0) && (arp_table[i].state >= ETHARP_STATE_STABLE)) {
+      *eth_ret = &arp_table[i].ethaddr;
+      *ip_ret = &arp_table[i].ipaddr;
+      return i;
+  }
+  return -1;
+}
+
+#if ETHARP_TRUST_IP_MAC
+/**
+ * Updates the ARP table using the given IP packet.
+ *
+ * Uses the incoming IP packet's source address to update the
+ * ARP cache for the local network. The function does not alter
+ * or free the packet. This function must be called before the
+ * packet p is passed to the IP layer.
+ *
+ * @param netif The lwIP network interface on which the IP packet pbuf arrived.
+ * @param p The IP packet that arrived on netif.
+ *
+ * @return NULL
+ *
+ * @see pbuf_free()
+ */
+static void
+etharp_ip_input(struct netif *netif, struct pbuf *p)
+{
+  struct eth_hdr *ethhdr;
+  struct ip_hdr *iphdr;
+  ip_addr_t iphdr_src;
+  LWIP_ERROR("netif != NULL", (netif != NULL), return;);
+
+  /* Only insert an entry if the source IP address of the
+     incoming IP packet comes from a host on the local network. */
+  ethhdr = (struct eth_hdr *)p->payload;
+  iphdr = (struct ip_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);
+#if ETHARP_SUPPORT_VLAN
+  if (ethhdr->type == PP_HTONS(ETHTYPE_VLAN)) {
+    iphdr = (struct ip_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR);
+  }
+#endif /* ETHARP_SUPPORT_VLAN */
+
+  ip_addr_copy(iphdr_src, iphdr->src);
+
+  /* source is not on the local network? */
+  if (!ip_addr_netcmp(&iphdr_src, &(netif->ip_addr), &(netif->netmask))) {
+    /* do nothing */
+    return;
+  }
+
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_ip_input: updating ETHARP table.\n"));
+  /* update the source IP address in the cache, if present */
+  /* @todo We could use ETHARP_FLAG_TRY_HARD if we think we are going to talk
+   * back soon (for example, if the destination IP address is ours. */
+  etharp_update_arp_entry(netif, &iphdr_src, &(ethhdr->src), ETHARP_FLAG_FIND_ONLY);
+}
+#endif /* ETHARP_TRUST_IP_MAC */
+
+/**
+ * Responds to ARP requests to us. Upon ARP replies to us, add entry to cache  
+ * send out queued IP packets. Updates cache with snooped address pairs.
+ *
+ * Should be called for incoming ARP packets. The pbuf in the argument
+ * is freed by this function.
+ *
+ * @param netif The lwIP network interface on which the ARP packet pbuf arrived.
+ * @param ethaddr Ethernet address of netif.
+ * @param p The ARP packet that arrived on netif. Is freed by this function.
+ *
+ * @return NULL
+ *
+ * @see pbuf_free()
+ */
+static void
+etharp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p)
+{
+  struct etharp_hdr *hdr;
+  struct eth_hdr *ethhdr;
+  /* these are aligned properly, whereas the ARP header fields might not be */
+  ip_addr_t sipaddr, dipaddr;
+  u8_t for_us;
+#if LWIP_AUTOIP
+  const u8_t * ethdst_hwaddr;
+#endif /* LWIP_AUTOIP */
+
+  LWIP_ERROR("netif != NULL", (netif != NULL), return;);
+
+  /* drop short ARP packets: we have to check for p->len instead of p->tot_len here
+     since a struct etharp_hdr is pointed to p->payload, so it musn't be chained! */
+  if (p->len < SIZEOF_ETHARP_PACKET) {
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
+      ("etharp_arp_input: packet dropped, too short (%"S16_F"/%"S16_F")\n", p->tot_len,
+      (s16_t)SIZEOF_ETHARP_PACKET));
+    ETHARP_STATS_INC(etharp.lenerr);
+    ETHARP_STATS_INC(etharp.drop);
+    pbuf_free(p);
+    return;
+  }
+
+  ethhdr = (struct eth_hdr *)p->payload;
+  hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);
+#if ETHARP_SUPPORT_VLAN
+  if (ethhdr->type == PP_HTONS(ETHTYPE_VLAN)) {
+    hdr = (struct etharp_hdr *)(((u8_t*)ethhdr) + SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR);
+  }
+#endif /* ETHARP_SUPPORT_VLAN */
+
+  /* RFC 826 "Packet Reception": */
+  if ((hdr->hwtype != PP_HTONS(HWTYPE_ETHERNET)) ||
+      (hdr->hwlen != ETHARP_HWADDR_LEN) ||
+      (hdr->protolen != sizeof(ip_addr_t)) ||
+      (hdr->proto != PP_HTONS(ETHTYPE_IP)))  {
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
+      ("etharp_arp_input: packet dropped, wrong hw type, hwlen, proto, protolen or ethernet type (%"U16_F"/%"U16_F"/%"U16_F"/%"U16_F")\n",
+      hdr->hwtype, hdr->hwlen, hdr->proto, hdr->protolen));
+    ETHARP_STATS_INC(etharp.proterr);
+    ETHARP_STATS_INC(etharp.drop);
+    pbuf_free(p);
+    return;
+  }
+  ETHARP_STATS_INC(etharp.recv);
+
+#if LWIP_AUTOIP
+  /* We have to check if a host already has configured our random
+   * created link local address and continously check if there is
+   * a host with this IP-address so we can detect collisions */
+  autoip_arp_reply(netif, hdr);
+#endif /* LWIP_AUTOIP */
+
+  /* Copy struct ip_addr2 to aligned ip_addr, to support compilers without
+   * structure packing (not using structure copy which breaks strict-aliasing rules). */
+  IPADDR2_COPY(&sipaddr, &hdr->sipaddr);
+  IPADDR2_COPY(&dipaddr, &hdr->dipaddr);
+
+  /* this interface is not configured? */
+  if (ip_addr_isany(&netif->ip_addr)) {
+    for_us = 0;
+  } else {
+    /* ARP packet directed to us? */
+    for_us = (u8_t)ip_addr_cmp(&dipaddr, &(netif->ip_addr));
+  }
+
+  /* ARP message directed to us?
+      -> add IP address in ARP cache; assume requester wants to talk to us,
+         can result in directly sending the queued packets for this host.
+     ARP message not directed to us?
+      ->  update the source IP address in the cache, if present */
+  etharp_update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
+                   for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY);
+
+  /* now act on the message itself */
+  switch (hdr->opcode) {
+  /* ARP request? */
+  case PP_HTONS(ARP_REQUEST):
+    /* ARP request. If it asked for our address, we send out a
+     * reply. In any case, we time-stamp any existing ARP entry,
+     * and possiby send out an IP packet that was queued on it. */
+
+    LWIP_DEBUGF (ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: incoming ARP request\n"));
+    /* ARP request for our address? */
+    if (for_us) {
+
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: replying to ARP request for our IP address\n"));
+      /* Re-use pbuf to send ARP reply.
+         Since we are re-using an existing pbuf, we can't call etharp_raw since
+         that would allocate a new pbuf. */
+      hdr->opcode = htons(ARP_REPLY);
+
+      IPADDR2_COPY(&hdr->dipaddr, &hdr->sipaddr);
+      IPADDR2_COPY(&hdr->sipaddr, &netif->ip_addr);
+
+      LWIP_ASSERT("netif->hwaddr_len must be the same as ETHARP_HWADDR_LEN for etharp!",
+                  (netif->hwaddr_len == ETHARP_HWADDR_LEN));
+#if LWIP_AUTOIP
+      /* If we are using Link-Local, all ARP packets that contain a Link-Local
+       * 'sender IP address' MUST be sent using link-layer broadcast instead of
+       * link-layer unicast. (See RFC3927 Section 2.5, last paragraph) */
+      ethdst_hwaddr = ip_addr_islinklocal(&netif->ip_addr) ? (u8_t*)(ethbroadcast.addr) : hdr->shwaddr.addr;
+#endif /* LWIP_AUTOIP */
+
+      ETHADDR16_COPY(&hdr->dhwaddr, &hdr->shwaddr);
+#if LWIP_AUTOIP
+      ETHADDR16_COPY(&ethhdr->dest, ethdst_hwaddr);
+#else  /* LWIP_AUTOIP */
+      ETHADDR16_COPY(&ethhdr->dest, &hdr->shwaddr);
+#endif /* LWIP_AUTOIP */
+      ETHADDR16_COPY(&hdr->shwaddr, ethaddr);
+      ETHADDR16_COPY(&ethhdr->src, ethaddr);
+
+      /* hwtype, hwaddr_len, proto, protolen and the type in the ethernet header
+         are already correct, we tested that before */
+
+      /* return ARP reply */
+      netif->linkoutput(netif, p);
+    /* we are not configured? */
+    } else if (ip_addr_isany(&netif->ip_addr)) {
+      /* { for_us == 0 and netif->ip_addr.addr == 0 } */
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: we are unconfigured, ARP request ignored.\n"));
+    /* request was not directed to us */
+    } else {
+      /* { for_us == 0 and netif->ip_addr.addr != 0 } */
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: ARP request was not for us.\n"));
+    }
+    break;
+  case PP_HTONS(ARP_REPLY):
+    /* ARP reply. We already updated the ARP cache earlier. */
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: incoming ARP reply\n"));
+#if (LWIP_DHCP && DHCP_DOES_ARP_CHECK)
+    /* DHCP wants to know about ARP replies from any host with an
+     * IP address also offered to us by the DHCP server. We do not
+     * want to take a duplicate IP address on a single network.
+     * @todo How should we handle redundant (fail-over) interfaces? */
+    dhcp_arp_reply(netif, &sipaddr);
+#endif /* (LWIP_DHCP && DHCP_DOES_ARP_CHECK) */
+    break;
+  default:
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_arp_input: ARP unknown opcode type %"S16_F"\n", htons(hdr->opcode)));
+    ETHARP_STATS_INC(etharp.err);
+    break;
+  }
+  /* free ARP packet */
+  pbuf_free(p);
+}
+
+/** Just a small helper function that sends a pbuf to an ethernet address
+ * in the arp_table specified by the index 'arp_idx'.
+ */
+static err_t
+etharp_output_to_arp_index(struct netif *netif, struct pbuf *q, u8_t arp_idx)
+{
+  LWIP_ASSERT("arp_table[arp_idx].state >= ETHARP_STATE_STABLE",
+              arp_table[arp_idx].state >= ETHARP_STATE_STABLE);
+  /* if arp table entry is about to expire: re-request it,
+     but only if its state is ETHARP_STATE_STABLE to prevent flooding the
+     network with ARP requests if this address is used frequently. */
+  if ((arp_table[arp_idx].state == ETHARP_STATE_STABLE) && 
+      (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED)) {
+    if (etharp_request(netif, &arp_table[arp_idx].ipaddr) == ERR_OK) {
+      arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING;
+    }
+  }
+  
+  return etharp_send_ip(netif, q, (struct eth_addr*)(netif->hwaddr),
+    &arp_table[arp_idx].ethaddr);
+}
+
+/**
+ * Resolve and fill-in Ethernet address header for outgoing IP packet.
+ *
+ * For IP multicast and broadcast, corresponding Ethernet addresses
+ * are selected and the packet is transmitted on the link.
+ *
+ * For unicast addresses, the packet is submitted to etharp_query(). In
+ * case the IP address is outside the local network, the IP address of
+ * the gateway is used.
+ *
+ * @param netif The lwIP network interface which the IP packet will be sent on.
+ * @param q The pbuf(s) containing the IP packet to be sent.
+ * @param ipaddr The IP address of the packet destination.
+ *
+ * @return
+ * - ERR_RTE No route to destination (no gateway to external networks),
+ * or the return type of either etharp_query() or etharp_send_ip().
+ */
+err_t
+etharp_output(struct netif *netif, struct pbuf *q, ip_addr_t *ipaddr)
+{
+  struct eth_addr *dest;
+  struct eth_addr mcastaddr;
+  ip_addr_t *dst_addr = ipaddr;
+
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+  LWIP_ASSERT("q != NULL", q != NULL);
+  LWIP_ASSERT("ipaddr != NULL", ipaddr != NULL);
+
+  /* make room for Ethernet header - should not fail */
+  if (pbuf_header(q, sizeof(struct eth_hdr)) != 0) {
+    /* bail out */
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+      ("etharp_output: could not allocate room for header.\n"));
+    LINK_STATS_INC(link.lenerr);
+    return ERR_BUF;
+  }
+
+  /* Determine on destination hardware address. Broadcasts and multicasts
+   * are special, other IP addresses are looked up in the ARP table. */
+
+  /* broadcast destination IP address? */
+  if (ip_addr_isbroadcast(ipaddr, netif)) {
+    /* broadcast on Ethernet also */
+    dest = (struct eth_addr *)&ethbroadcast;
+  /* multicast destination IP address? */
+  } else if (ip_addr_ismulticast(ipaddr)) {
+    /* Hash IP multicast address to MAC address.*/
+    mcastaddr.addr[0] = LL_MULTICAST_ADDR_0;
+    mcastaddr.addr[1] = LL_MULTICAST_ADDR_1;
+    mcastaddr.addr[2] = LL_MULTICAST_ADDR_2;
+    mcastaddr.addr[3] = ip4_addr2(ipaddr) & 0x7f;
+    mcastaddr.addr[4] = ip4_addr3(ipaddr);
+    mcastaddr.addr[5] = ip4_addr4(ipaddr);
+    /* destination Ethernet address is multicast */
+    dest = &mcastaddr;
+  /* unicast destination IP address? */
+  } else {
+    s8_t i;
+    /* outside local network? if so, this can neither be a global broadcast nor
+       a subnet broadcast. */
+    if (!ip_addr_netcmp(ipaddr, &(netif->ip_addr), &(netif->netmask)) &&
+        !ip_addr_islinklocal(ipaddr)) {
+#if LWIP_AUTOIP
+      struct ip_hdr *iphdr = (struct ip_hdr*)((u8_t*)q->payload +
+        sizeof(struct eth_hdr));
+      /* According to RFC 3297, chapter 2.6.2 (Forwarding Rules), a packet with
+         a link-local source address must always be "directly to its destination
+         on the same physical link. The host MUST NOT send the packet to any
+         router for forwarding". */
+      if (!ip_addr_islinklocal(&iphdr->src))
+#endif /* LWIP_AUTOIP */
+      {
+        /* interface has default gateway? */
+        if (!ip_addr_isany(&netif->gw)) {
+          /* send to hardware address of default gateway IP address */
+          dst_addr = &(netif->gw);
+        /* no default gateway available */
+        } else {
+          /* no route to destination error (default gateway missing) */
+          return ERR_RTE;
+        }
+      }
+    }
+#if LWIP_NETIF_HWADDRHINT
+    if (netif->addr_hint != NULL) {
+      /* per-pcb cached entry was given */
+      u8_t etharp_cached_entry = *(netif->addr_hint);
+      if (etharp_cached_entry < ARP_TABLE_SIZE) {
+#endif /* LWIP_NETIF_HWADDRHINT */
+        if ((arp_table[etharp_cached_entry].state >= ETHARP_STATE_STABLE) &&
+            (ip_addr_cmp(dst_addr, &arp_table[etharp_cached_entry].ipaddr))) {
+          /* the per-pcb-cached entry is stable and the right one! */
+          ETHARP_STATS_INC(etharp.cachehit);
+          return etharp_output_to_arp_index(netif, q, etharp_cached_entry);
+        }
+#if LWIP_NETIF_HWADDRHINT
+      }
+    }
+#endif /* LWIP_NETIF_HWADDRHINT */
+
+    /* find stable entry: do this here since this is a critical path for
+       throughput and etharp_find_entry() is kind of slow */
+    for (i = 0; i < ARP_TABLE_SIZE; i++) {
+      if ((arp_table[i].state >= ETHARP_STATE_STABLE) &&
+          (ip_addr_cmp(dst_addr, &arp_table[i].ipaddr))) {
+        /* found an existing, stable entry */
+        ETHARP_SET_HINT(netif, i);
+        return etharp_output_to_arp_index(netif, q, i);
+      }
+    }
+    /* no stable entry found, use the (slower) query function:
+       queue on destination Ethernet address belonging to ipaddr */
+    return etharp_query(netif, dst_addr, q);
+  }
+
+  /* continuation for multicast/broadcast destinations */
+  /* obtain source Ethernet address of the given interface */
+  /* send packet directly on the link */
+  return etharp_send_ip(netif, q, (struct eth_addr*)(netif->hwaddr), dest);
+}
+
+/**
+ * Send an ARP request for the given IP address and/or queue a packet.
+ *
+ * If the IP address was not yet in the cache, a pending ARP cache entry
+ * is added and an ARP request is sent for the given address. The packet
+ * is queued on this entry.
+ *
+ * If the IP address was already pending in the cache, a new ARP request
+ * is sent for the given address. The packet is queued on this entry.
+ *
+ * If the IP address was already stable in the cache, and a packet is
+ * given, it is directly sent and no ARP request is sent out. 
+ * 
+ * If the IP address was already stable in the cache, and no packet is
+ * given, an ARP request is sent out.
+ * 
+ * @param netif The lwIP network interface on which ipaddr
+ * must be queried for.
+ * @param ipaddr The IP address to be resolved.
+ * @param q If non-NULL, a pbuf that must be delivered to the IP address.
+ * q is not freed by this function.
+ *
+ * @note q must only be ONE packet, not a packet queue!
+ *
+ * @return
+ * - ERR_BUF Could not make room for Ethernet header.
+ * - ERR_MEM Hardware address unknown, and no more ARP entries available
+ *   to query for address or queue the packet.
+ * - ERR_MEM Could not queue packet due to memory shortage.
+ * - ERR_RTE No route to destination (no gateway to external networks).
+ * - ERR_ARG Non-unicast address given, those will not appear in ARP cache.
+ *
+ */
+err_t
+etharp_query(struct netif *netif, ip_addr_t *ipaddr, struct pbuf *q)
+{
+  struct eth_addr * srcaddr = (struct eth_addr *)netif->hwaddr;
+  err_t result = ERR_MEM;
+  s8_t i; /* ARP entry index */
+
+  /* non-unicast address? */
+  if (ip_addr_isbroadcast(ipaddr, netif) ||
+      ip_addr_ismulticast(ipaddr) ||
+      ip_addr_isany(ipaddr)) {
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: will not add non-unicast IP address to ARP cache\n"));
+    return ERR_ARG;
+  }
+
+  /* find entry in ARP cache, ask to create entry if queueing packet */
+  i = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD);
+
+  /* could not find or create entry? */
+  if (i < 0) {
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not create ARP entry\n"));
+    if (q) {
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: packet dropped\n"));
+      ETHARP_STATS_INC(etharp.memerr);
+    }
+    return (err_t)i;
+  }
+
+  /* mark a fresh entry as pending (we just sent a request) */
+  if (arp_table[i].state == ETHARP_STATE_EMPTY) {
+    arp_table[i].state = ETHARP_STATE_PENDING;
+  }
+
+  /* { i is either a STABLE or (new or existing) PENDING entry } */
+  LWIP_ASSERT("arp_table[i].state == PENDING or STABLE",
+  ((arp_table[i].state == ETHARP_STATE_PENDING) ||
+   (arp_table[i].state >= ETHARP_STATE_STABLE)));
+
+  /* do we have a pending entry? or an implicit query request? */
+  if ((arp_table[i].state == ETHARP_STATE_PENDING) || (q == NULL)) {
+    /* try to resolve it; send out ARP request */
+    result = etharp_request(netif, ipaddr);
+    if (result != ERR_OK) {
+      /* ARP request couldn't be sent */
+      /* We don't re-send arp request in etharp_tmr, but we still queue packets,
+         since this failure could be temporary, and the next packet calling
+         etharp_query again could lead to sending the queued packets. */
+    }
+    if (q == NULL) {
+      return result;
+    }
+  }
+
+  /* packet given? */
+  LWIP_ASSERT("q != NULL", q != NULL);
+  /* stable entry? */
+  if (arp_table[i].state >= ETHARP_STATE_STABLE) {
+    /* we have a valid IP->Ethernet address mapping */
+    ETHARP_SET_HINT(netif, i);
+    /* send the packet */
+    result = etharp_send_ip(netif, q, srcaddr, &(arp_table[i].ethaddr));
+  /* pending entry? (either just created or already pending */
+  } else if (arp_table[i].state == ETHARP_STATE_PENDING) {
+    /* entry is still pending, queue the given packet 'q' */
+    struct pbuf *p;
+    int copy_needed = 0;
+    /* IF q includes a PBUF_REF, PBUF_POOL or PBUF_RAM, we have no choice but
+     * to copy the whole queue into a new PBUF_RAM (see bug #11400) 
+     * PBUF_ROMs can be left as they are, since ROM must not get changed. */
+    p = q;
+    while (p) {
+      LWIP_ASSERT("no packet queues allowed!", (p->len != p->tot_len) || (p->next == 0));
+      if(p->type != PBUF_ROM) {
+        copy_needed = 1;
+        break;
+      }
+      p = p->next;
+    }
+    if(copy_needed) {
+      /* copy the whole packet into new pbufs */
+      p = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
+      if(p != NULL) {
+        if (pbuf_copy(p, q) != ERR_OK) {
+          pbuf_free(p);
+          p = NULL;
+        }
+      }
+    } else {
+      /* referencing the old pbuf is enough */
+      p = q;
+      pbuf_ref(p);
+    }
+    /* packet could be taken over? */
+    if (p != NULL) {
+      /* queue packet ... */
+#if ARP_QUEUEING
+      struct etharp_q_entry *new_entry;
+      /* allocate a new arp queue entry */
+      new_entry = (struct etharp_q_entry *)memp_malloc(MEMP_ARP_QUEUE);
+      if (new_entry != NULL) {
+        new_entry->next = 0;
+        new_entry->p = p;
+        if(arp_table[i].q != NULL) {
+          /* queue was already existent, append the new entry to the end */
+          struct etharp_q_entry *r;
+          r = arp_table[i].q;
+          while (r->next != NULL) {
+            r = r->next;
+          }
+          r->next = new_entry;
+        } else {
+          /* queue did not exist, first item in queue */
+          arp_table[i].q = new_entry;
+        }
+        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: queued packet %p on ARP entry %"S16_F"\n", (void *)q, (s16_t)i));
+        result = ERR_OK;
+      } else {
+        /* the pool MEMP_ARP_QUEUE is empty */
+        pbuf_free(p);
+        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not queue a copy of PBUF_REF packet %p (out of memory)\n", (void *)q));
+        result = ERR_MEM;
+      }
+#else /* ARP_QUEUEING */
+      /* always queue one packet per ARP request only, freeing a previously queued packet */
+      if (arp_table[i].q != NULL) {
+        LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: dropped previously queued packet %p for ARP entry %"S16_F"\n", (void *)q, (s16_t)i));
+        pbuf_free(arp_table[i].q);
+      }
+      arp_table[i].q = p;
+      result = ERR_OK;
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: queued packet %p on ARP entry %"S16_F"\n", (void *)q, (s16_t)i));
+#endif /* ARP_QUEUEING */
+    } else {
+      ETHARP_STATS_INC(etharp.memerr);
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not queue a copy of PBUF_REF packet %p (out of memory)\n", (void *)q));
+      result = ERR_MEM;
+    }
+  }
+  return result;
+}
+
+/**
+ * Send a raw ARP packet (opcode and all addresses can be modified)
+ *
+ * @param netif the lwip network interface on which to send the ARP packet
+ * @param ethsrc_addr the source MAC address for the ethernet header
+ * @param ethdst_addr the destination MAC address for the ethernet header
+ * @param hwsrc_addr the source MAC address for the ARP protocol header
+ * @param ipsrc_addr the source IP address for the ARP protocol header
+ * @param hwdst_addr the destination MAC address for the ARP protocol header
+ * @param ipdst_addr the destination IP address for the ARP protocol header
+ * @param opcode the type of the ARP packet
+ * @return ERR_OK if the ARP packet has been sent
+ *         ERR_MEM if the ARP packet couldn't be allocated
+ *         any other err_t on failure
+ */
+#if !LWIP_AUTOIP
+static
+#endif /* LWIP_AUTOIP */
+err_t
+etharp_raw(struct netif *netif, const struct eth_addr *ethsrc_addr,
+           const struct eth_addr *ethdst_addr,
+           const struct eth_addr *hwsrc_addr, const ip_addr_t *ipsrc_addr,
+           const struct eth_addr *hwdst_addr, const ip_addr_t *ipdst_addr,
+           const u16_t opcode)
+{
+  struct pbuf *p;
+  err_t result = ERR_OK;
+  struct eth_hdr *ethhdr;
+  struct etharp_hdr *hdr;
+#if LWIP_AUTOIP
+  const u8_t * ethdst_hwaddr;
+#endif /* LWIP_AUTOIP */
+
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+
+  /* allocate a pbuf for the outgoing ARP request packet */
+  p = pbuf_alloc(PBUF_RAW, SIZEOF_ETHARP_PACKET, PBUF_RAM);
+  /* could allocate a pbuf for an ARP request? */
+  if (p == NULL) {
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
+      ("etharp_raw: could not allocate pbuf for ARP request.\n"));
+    ETHARP_STATS_INC(etharp.memerr);
+    return ERR_MEM;
+  }
+  LWIP_ASSERT("check that first pbuf can hold struct etharp_hdr",
+              (p->len >= SIZEOF_ETHARP_PACKET));
+
+  ethhdr = (struct eth_hdr *)p->payload;
+  hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR);
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_raw: sending raw ARP packet.\n"));
+  hdr->opcode = htons(opcode);
+
+  LWIP_ASSERT("netif->hwaddr_len must be the same as ETHARP_HWADDR_LEN for etharp!",
+              (netif->hwaddr_len == ETHARP_HWADDR_LEN));
+#if LWIP_AUTOIP
+  /* If we are using Link-Local, all ARP packets that contain a Link-Local
+   * 'sender IP address' MUST be sent using link-layer broadcast instead of
+   * link-layer unicast. (See RFC3927 Section 2.5, last paragraph) */
+  ethdst_hwaddr = ip_addr_islinklocal(ipsrc_addr) ? (u8_t*)(ethbroadcast.addr) : ethdst_addr->addr;
+#endif /* LWIP_AUTOIP */
+  /* Write the ARP MAC-Addresses */
+  ETHADDR16_COPY(&hdr->shwaddr, hwsrc_addr);
+  ETHADDR16_COPY(&hdr->dhwaddr, hwdst_addr);
+  /* Write the Ethernet MAC-Addresses */
+#if LWIP_AUTOIP
+  ETHADDR16_COPY(&ethhdr->dest, ethdst_hwaddr);
+#else  /* LWIP_AUTOIP */
+  ETHADDR16_COPY(&ethhdr->dest, ethdst_addr);
+#endif /* LWIP_AUTOIP */
+  ETHADDR16_COPY(&ethhdr->src, ethsrc_addr);
+  /* Copy struct ip_addr2 to aligned ip_addr, to support compilers without
+   * structure packing. */ 
+  IPADDR2_COPY(&hdr->sipaddr, ipsrc_addr);
+  IPADDR2_COPY(&hdr->dipaddr, ipdst_addr);
+
+  hdr->hwtype = PP_HTONS(HWTYPE_ETHERNET);
+  hdr->proto = PP_HTONS(ETHTYPE_IP);
+  /* set hwlen and protolen */
+  hdr->hwlen = ETHARP_HWADDR_LEN;
+  hdr->protolen = sizeof(ip_addr_t);
+
+  ethhdr->type = PP_HTONS(ETHTYPE_ARP);
+  /* send ARP query */
+  result = netif->linkoutput(netif, p);
+  ETHARP_STATS_INC(etharp.xmit);
+  /* free ARP query packet */
+  pbuf_free(p);
+  p = NULL;
+  /* could not allocate pbuf for ARP request */
+
+  return result;
+}
+
+/**
+ * Send an ARP request packet asking for ipaddr.
+ *
+ * @param netif the lwip network interface on which to send the request
+ * @param ipaddr the IP address for which to ask
+ * @return ERR_OK if the request has been sent
+ *         ERR_MEM if the ARP packet couldn't be allocated
+ *         any other err_t on failure
+ */
+err_t
+etharp_request(struct netif *netif, ip_addr_t *ipaddr)
+{
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_request: sending ARP request.\n"));
+  return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, &ethbroadcast,
+                    (struct eth_addr *)netif->hwaddr, &netif->ip_addr, &ethzero,
+                    ipaddr, ARP_REQUEST);
+}
+#endif /* LWIP_ARP */
+
+/**
+ * Process received ethernet frames. Using this function instead of directly
+ * calling ip_input and passing ARP frames through etharp in ethernetif_input,
+ * the ARP cache is protected from concurrent access.
+ *
+ * @param p the recevied packet, p->payload pointing to the ethernet header
+ * @param netif the network interface on which the packet was received
+ */
+err_t
+ethernet_input(struct pbuf *p, struct netif *netif)
+{
+  struct eth_hdr* ethhdr;
+  u16_t type;
+#if LWIP_ARP || ETHARP_SUPPORT_VLAN
+  s16_t ip_hdr_offset = SIZEOF_ETH_HDR;
+#endif /* LWIP_ARP || ETHARP_SUPPORT_VLAN */
+
+  if (p->len <= SIZEOF_ETH_HDR) {
+    /* a packet with only an ethernet header (or less) is not valid for us */
+    ETHARP_STATS_INC(etharp.proterr);
+    ETHARP_STATS_INC(etharp.drop);
+    goto free_and_return;
+  }
+
+  /* points to packet payload, which starts with an Ethernet header */
+  ethhdr = (struct eth_hdr *)p->payload;
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE,
+    ("ethernet_input: dest:%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F", src:%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F":%"X8_F", type:%"X16_F"\n",
+     (unsigned)ethhdr->dest.addr[0], (unsigned)ethhdr->dest.addr[1], (unsigned)ethhdr->dest.addr[2],
+     (unsigned)ethhdr->dest.addr[3], (unsigned)ethhdr->dest.addr[4], (unsigned)ethhdr->dest.addr[5],
+     (unsigned)ethhdr->src.addr[0], (unsigned)ethhdr->src.addr[1], (unsigned)ethhdr->src.addr[2],
+     (unsigned)ethhdr->src.addr[3], (unsigned)ethhdr->src.addr[4], (unsigned)ethhdr->src.addr[5],
+     (unsigned)htons(ethhdr->type)));
+
+  type = ethhdr->type;
+#if ETHARP_SUPPORT_VLAN
+  if (type == PP_HTONS(ETHTYPE_VLAN)) {
+    struct eth_vlan_hdr *vlan = (struct eth_vlan_hdr*)(((char*)ethhdr) + SIZEOF_ETH_HDR);
+    if (p->len <= SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR) {
+      /* a packet with only an ethernet/vlan header (or less) is not valid for us */
+      ETHARP_STATS_INC(etharp.proterr);
+      ETHARP_STATS_INC(etharp.drop);
+      goto free_and_return;
+    }
+#if defined(ETHARP_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK_FN) /* if not, allow all VLANs */
+#ifdef ETHARP_VLAN_CHECK_FN
+    if (!ETHARP_VLAN_CHECK_FN(ethhdr, vlan)) {
+#elif defined(ETHARP_VLAN_CHECK)
+    if (VLAN_ID(vlan) != ETHARP_VLAN_CHECK) {
+#endif
+      /* silently ignore this packet: not for our VLAN */
+      pbuf_free(p);
+      return ERR_OK;
+    }
+#endif /* defined(ETHARP_VLAN_CHECK) || defined(ETHARP_VLAN_CHECK_FN) */
+    type = vlan->tpid;
+    ip_hdr_offset = SIZEOF_ETH_HDR + SIZEOF_VLAN_HDR;
+  }
+#endif /* ETHARP_SUPPORT_VLAN */
+
+#if LWIP_ARP_FILTER_NETIF
+  netif = LWIP_ARP_FILTER_NETIF_FN(p, netif, htons(type));
+#endif /* LWIP_ARP_FILTER_NETIF*/
+
+  if (ethhdr->dest.addr[0] & 1) {
+    /* this might be a multicast or broadcast packet */
+    if (ethhdr->dest.addr[0] == LL_MULTICAST_ADDR_0) {
+      if ((ethhdr->dest.addr[1] == LL_MULTICAST_ADDR_1) &&
+          (ethhdr->dest.addr[2] == LL_MULTICAST_ADDR_2)) {
+        /* mark the pbuf as link-layer multicast */
+        p->flags |= PBUF_FLAG_LLMCAST;
+      }
+    } else if (eth_addr_cmp(&ethhdr->dest, &ethbroadcast)) {
+      /* mark the pbuf as link-layer broadcast */
+      p->flags |= PBUF_FLAG_LLBCAST;
+    }
+  }
+
+  switch (type) {
+#if LWIP_ARP
+    /* IP packet? */
+    case PP_HTONS(ETHTYPE_IP):
+      if (!(netif->flags & NETIF_FLAG_ETHARP)) {
+        goto free_and_return;
+      }
+#if ETHARP_TRUST_IP_MAC
+      /* update ARP table */
+      etharp_ip_input(netif, p);
+#endif /* ETHARP_TRUST_IP_MAC */
+      /* skip Ethernet header */
+      if(pbuf_header(p, -ip_hdr_offset)) {
+        LWIP_ASSERT("Can't move over header in packet", 0);
+        goto free_and_return;
+      } else {
+        /* pass to IP layer */
+        ip_input(p, netif);
+      }
+      break;
+      
+    case PP_HTONS(ETHTYPE_ARP):
+      if (!(netif->flags & NETIF_FLAG_ETHARP)) {
+        goto free_and_return;
+      }
+      /* pass p to ARP module */
+      etharp_arp_input(netif, (struct eth_addr*)(netif->hwaddr), p);
+      break;
+#endif /* LWIP_ARP */
+#if PPPOE_SUPPORT
+    case PP_HTONS(ETHTYPE_PPPOEDISC): /* PPP Over Ethernet Discovery Stage */
+      pppoe_disc_input(netif, p);
+      break;
+
+    case PP_HTONS(ETHTYPE_PPPOE): /* PPP Over Ethernet Session Stage */
+      pppoe_data_input(netif, p);
+      break;
+#endif /* PPPOE_SUPPORT */
+
+#if LWIP_IPV6
+    case PP_HTONS(ETHTYPE_IPV6): /* IPv6 */
+      /* skip Ethernet header */
+      if(pbuf_header(p, -(s16_t)SIZEOF_ETH_HDR)) {
+        LWIP_ASSERT("Can't move over header in packet", 0);
+        goto free_and_return;
+      } else {
+        /* pass to IPv6 layer */
+        ip6_input(p, netif);
+      }
+      break;
+#endif /* LWIP_IPV6 */
+
+    default:
+      ETHARP_STATS_INC(etharp.proterr);
+      ETHARP_STATS_INC(etharp.drop);
+      goto free_and_return;
+  }
+
+  /* This means the pbuf is freed or consumed,
+     so the caller doesn't have to free it again */
+  return ERR_OK;
+
+free_and_return:
+  pbuf_free(p);
+  return ERR_OK;
+}
+#endif /* LWIP_ARP || LWIP_ETHERNET */
diff --git a/external/badvpn_dns/lwip/src/netif/ethernetif.c b/external/badvpn_dns/lwip/src/netif/ethernetif.c
new file mode 100644
index 0000000..46900bd
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ethernetif.c
@@ -0,0 +1,322 @@
+/**
+ * @file
+ * Ethernet Interface Skeleton
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Adam Dunkels <adam@xxxxxxx>
+ *
+ */
+
+/*
+ * This file is a skeleton for developing Ethernet network interface
+ * drivers for lwIP. Add code to the low_level functions and do a
+ * search-and-replace for the word "ethernetif" to replace it with
+ * something that better describes your network interface.
+ */
+
+#include "lwip/opt.h"
+
+#if 0 /* don't build, this is only a skeleton, see previous comment */
+
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/pbuf.h"
+#include "lwip/stats.h"
+#include "lwip/snmp.h"
+#include "lwip/ethip6.h"
+#include "netif/etharp.h"
+#include "netif/ppp_oe.h"
+
+/* Define those to better describe your network interface. */
+#define IFNAME0 'e'
+#define IFNAME1 'n'
+
+/**
+ * Helper struct to hold private data used to operate your ethernet interface.
+ * Keeping the ethernet address of the MAC in this struct is not necessary
+ * as it is already kept in the struct netif.
+ * But this is only an example, anyway...
+ */
+struct ethernetif {
+  struct eth_addr *ethaddr;
+  /* Add whatever per-interface state that is needed here. */
+};
+
+/* Forward declarations. */
+static void  ethernetif_input(struct netif *netif);
+
+/**
+ * In this function, the hardware should be initialized.
+ * Called from ethernetif_init().
+ *
+ * @param netif the already initialized lwip network interface structure
+ *        for this ethernetif
+ */
+static void
+low_level_init(struct netif *netif)
+{
+  struct ethernetif *ethernetif = netif->state;
+  
+  /* set MAC hardware address length */
+  netif->hwaddr_len = ETHARP_HWADDR_LEN;
+
+  /* set MAC hardware address */
+  netif->hwaddr[0] = ;
+  ...
+  netif->hwaddr[5] = ;
+
+  /* maximum transfer unit */
+  netif->mtu = 1500;
+  
+  /* device capabilities */
+  /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
+  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
+ 
+  /* Do whatever else is needed to initialize interface. */  
+}
+
+/**
+ * This function should do the actual transmission of the packet. The packet is
+ * contained in the pbuf that is passed to the function. This pbuf
+ * might be chained.
+ *
+ * @param netif the lwip network interface structure for this ethernetif
+ * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
+ * @return ERR_OK if the packet could be sent
+ *         an err_t value if the packet couldn't be sent
+ *
+ * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
+ *       strange results. You might consider waiting for space in the DMA queue
+ *       to become availale since the stack doesn't retry to send a packet
+ *       dropped because of memory failure (except for the TCP timers).
+ */
+
+static err_t
+low_level_output(struct netif *netif, struct pbuf *p)
+{
+  struct ethernetif *ethernetif = netif->state;
+  struct pbuf *q;
+
+  initiate transfer();
+  
+#if ETH_PAD_SIZE
+  pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
+#endif
+
+  for(q = p; q != NULL; q = q->next) {
+    /* Send the data from the pbuf to the interface, one pbuf at a
+       time. The size of the data in each pbuf is kept in the ->len
+       variable. */
+    send data from(q->payload, q->len);
+  }
+
+  signal that packet should be sent();
+
+#if ETH_PAD_SIZE
+  pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
+#endif
+  
+  LINK_STATS_INC(link.xmit);
+
+  return ERR_OK;
+}
+
+/**
+ * Should allocate a pbuf and transfer the bytes of the incoming
+ * packet from the interface into the pbuf.
+ *
+ * @param netif the lwip network interface structure for this ethernetif
+ * @return a pbuf filled with the received packet (including MAC header)
+ *         NULL on memory error
+ */
+static struct pbuf *
+low_level_input(struct netif *netif)
+{
+  struct ethernetif *ethernetif = netif->state;
+  struct pbuf *p, *q;
+  u16_t len;
+
+  /* Obtain the size of the packet and put it into the "len"
+     variable. */
+  len = ;
+
+#if ETH_PAD_SIZE
+  len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
+#endif
+
+  /* We allocate a pbuf chain of pbufs from the pool. */
+  p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
+  
+  if (p != NULL) {
+
+#if ETH_PAD_SIZE
+    pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
+#endif
+
+    /* We iterate over the pbuf chain until we have read the entire
+     * packet into the pbuf. */
+    for(q = p; q != NULL; q = q->next) {
+      /* Read enough bytes to fill this pbuf in the chain. The
+       * available data in the pbuf is given by the q->len
+       * variable.
+       * This does not necessarily have to be a memcpy, you can also preallocate
+       * pbufs for a DMA-enabled MAC and after receiving truncate it to the
+       * actually received size. In this case, ensure the tot_len member of the
+       * pbuf is the sum of the chained pbuf len members.
+       */
+      read data into(q->payload, q->len);
+    }
+    acknowledge that packet has been read();
+
+#if ETH_PAD_SIZE
+    pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
+#endif
+
+    LINK_STATS_INC(link.recv);
+  } else {
+    drop packet();
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.drop);
+  }
+
+  return p;  
+}
+
+/**
+ * This function should be called when a packet is ready to be read
+ * from the interface. It uses the function low_level_input() that
+ * should handle the actual reception of bytes from the network
+ * interface. Then the type of the received packet is determined and
+ * the appropriate input function is called.
+ *
+ * @param netif the lwip network interface structure for this ethernetif
+ */
+static void
+ethernetif_input(struct netif *netif)
+{
+  struct ethernetif *ethernetif;
+  struct eth_hdr *ethhdr;
+  struct pbuf *p;
+
+  ethernetif = netif->state;
+
+  /* move received packet into a new pbuf */
+  p = low_level_input(netif);
+  /* no packet could be read, silently ignore this */
+  if (p == NULL) return;
+  /* points to packet payload, which starts with an Ethernet header */
+  ethhdr = p->payload;
+
+  switch (htons(ethhdr->type)) {
+  /* IP or ARP packet? */
+  case ETHTYPE_IP:
+  case ETHTYPE_IPV6:
+  case ETHTYPE_ARP:
+#if PPPOE_SUPPORT
+  /* PPPoE packet? */
+  case ETHTYPE_PPPOEDISC:
+  case ETHTYPE_PPPOE:
+#endif /* PPPOE_SUPPORT */
+    /* full packet send to tcpip_thread to process */
+    if (netif->input(p, netif)!=ERR_OK)
+     { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
+       pbuf_free(p);
+       p = NULL;
+     }
+    break;
+
+  default:
+    pbuf_free(p);
+    p = NULL;
+    break;
+  }
+}
+
+/**
+ * Should be called at the beginning of the program to set up the
+ * network interface. It calls the function low_level_init() to do the
+ * actual setup of the hardware.
+ *
+ * This function should be passed as a parameter to netif_add().
+ *
+ * @param netif the lwip network interface structure for this ethernetif
+ * @return ERR_OK if the loopif is initialized
+ *         ERR_MEM if private data couldn't be allocated
+ *         any other err_t on error
+ */
+err_t
+ethernetif_init(struct netif *netif)
+{
+  struct ethernetif *ethernetif;
+
+  LWIP_ASSERT("netif != NULL", (netif != NULL));
+    
+  ethernetif = mem_malloc(sizeof(struct ethernetif));
+  if (ethernetif == NULL) {
+    LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
+    return ERR_MEM;
+  }
+
+#if LWIP_NETIF_HOSTNAME
+  /* Initialize interface hostname */
+  netif->hostname = "lwip";
+#endif /* LWIP_NETIF_HOSTNAME */
+
+  /*
+   * Initialize the snmp variables and counters inside the struct netif.
+   * The last argument should be replaced with your link speed, in units
+   * of bits per second.
+   */
+  NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);
+
+  netif->state = ethernetif;
+  netif->name[0] = IFNAME0;
+  netif->name[1] = IFNAME1;
+  /* We directly use etharp_output() here to save a function call.
+   * You can instead declare your own function an call etharp_output()
+   * from it if you have to do some checks before sending (e.g. if link
+   * is available...) */
+  netif->output = etharp_output;
+#if LWIP_IPV6
+  netif->output_ip6 = ethip6_output;
+#endif /* LWIP_IPV6 */
+  netif->linkoutput = low_level_output;
+  
+  ethernetif->ethaddr = (struct eth_addr *)&(netif->hwaddr[0]);
+  
+  /* initialize the hardware */
+  low_level_init(netif);
+
+  return ERR_OK;
+}
+
+#endif /* 0 */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/auth.c b/external/badvpn_dns/lwip/src/netif/ppp/auth.c
new file mode 100644
index 0000000..0fd87a3
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/auth.c
@@ -0,0 +1,1334 @@
+/*****************************************************************************
+* auth.c - Network Authentication and Phase Control program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* Copyright (c) 1997 by Global Election Systems Inc.  All rights reserved.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-08 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Ported from public pppd code.
+*****************************************************************************/
+/*
+ * auth.c - PPP authentication and phase control.
+ *
+ * Copyright (c) 1993 The Australian National University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the Australian National University.  The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "fsm.h"
+#include "lcp.h"
+#include "pap.h"
+#include "chap.h"
+#include "auth.h"
+#include "ipcp.h"
+
+#if CBCP_SUPPORT
+#include "cbcp.h"
+#endif /* CBCP_SUPPORT */
+
+#include "lwip/inet.h"
+
+#include <string.h>
+
+#if 0 /* UNUSED */
+/* Bits in scan_authfile return value */
+#define NONWILD_SERVER  1
+#define NONWILD_CLIENT  2
+
+#define ISWILD(word)  (word[0] == '*' && word[1] == 0)
+#endif /* UNUSED */
+
+#if PAP_SUPPORT || CHAP_SUPPORT
+/* The name by which the peer authenticated itself to us. */
+static char peer_authname[MAXNAMELEN];
+#endif /* PAP_SUPPORT || CHAP_SUPPORT */
+
+/* Records which authentication operations haven't completed yet. */
+static int auth_pending[NUM_PPP];
+
+/* Set if we have successfully called plogin() */
+static int logged_in;
+
+/* Set if we have run the /etc/ppp/auth-up script. */
+static int did_authup; /* @todo, we don't need this in lwip*/
+
+/* List of addresses which the peer may use. */
+static struct wordlist *addresses[NUM_PPP];
+
+#if 0 /* UNUSED */
+/* Wordlist giving addresses which the peer may use
+   without authenticating itself. */
+static struct wordlist *noauth_addrs;
+
+/* Extra options to apply, from the secrets file entry for the peer. */
+static struct wordlist *extra_options;
+#endif /* UNUSED */
+
+/* Number of network protocols which we have opened. */
+static int num_np_open;
+
+/* Number of network protocols which have come up. */
+static int num_np_up;
+
+#if PAP_SUPPORT || CHAP_SUPPORT
+/* Set if we got the contents of passwd[] from the pap-secrets file. */
+static int passwd_from_file;
+#endif /* PAP_SUPPORT || CHAP_SUPPORT */
+
+#if 0 /* UNUSED */
+/* Set if we require authentication only because we have a default route. */
+static bool default_auth;
+
+/* Hook to enable a plugin to control the idle time limit */
+int (*idle_time_hook) __P((struct ppp_idle *)) = NULL;
+
+/* Hook for a plugin to say whether we can possibly authenticate any peer */
+int (*pap_check_hook) __P((void)) = NULL;
+
+/* Hook for a plugin to check the PAP user and password */
+int (*pap_auth_hook) __P((char *user, char *passwd, char **msgp,
+        struct wordlist **paddrs,
+        struct wordlist **popts)) = NULL;
+
+/* Hook for a plugin to know about the PAP user logout */
+void (*pap_logout_hook) __P((void)) = NULL;
+
+/* Hook for a plugin to get the PAP password for authenticating us */
+int (*pap_passwd_hook) __P((char *user, char *passwd)) = NULL;
+
+/*
+ * This is used to ensure that we don't start an auth-up/down
+ * script while one is already running.
+ */
+enum script_state {
+    s_down,
+    s_up
+};
+
+static enum script_state auth_state = s_down;
+static enum script_state auth_script_state = s_down;
+static pid_t auth_script_pid = 0;
+
+/*
+ * Option variables.
+ * lwip: some of these are present in the ppp_settings structure
+ */
+bool uselogin = 0;            /* Use /etc/passwd for checking PAP */
+bool cryptpap = 0;            /* Passwords in pap-secrets are encrypted */
+bool refuse_pap = 0;          /* Don't wanna auth. ourselves with PAP */
+bool refuse_chap = 0;         /* Don't wanna auth. ourselves with CHAP */
+bool usehostname = 0;         /* Use hostname for our_name */
+bool auth_required = 0;       /* Always require authentication from peer */
+bool allow_any_ip = 0;        /* Allow peer to use any IP address */
+bool explicit_remote = 0;     /* User specified explicit remote name */
+char remote_name[MAXNAMELEN]; /* Peer's name for authentication */
+
+#endif /* UNUSED */
+
+/* Bits in auth_pending[] */
+#define PAP_WITHPEER    1
+#define PAP_PEER        2
+#define CHAP_WITHPEER   4
+#define CHAP_PEER       8
+
+/* @todo, move this somewhere */
+/* Used for storing a sequence of words.  Usually malloced. */
+struct wordlist {
+  struct wordlist *next;
+  char        word[1];
+};
+
+
+extern char *crypt (const char *, const char *);
+
+/* Prototypes for procedures local to this file. */
+
+static void network_phase (int);
+static void check_idle (void *);
+static void connect_time_expired (void *);
+#if 0
+static int  plogin (char *, char *, char **, int *);
+#endif
+static void plogout (void);
+static int  null_login (int);
+static int  get_pap_passwd (int, char *, char *);
+static int  have_pap_secret (void);
+static int  have_chap_secret (char *, char *, u32_t);
+static int  ip_addr_check (u32_t, struct wordlist *);
+
+#if 0 /* PAP_SUPPORT || CHAP_SUPPORT */
+static int  scan_authfile (FILE *, char *, char *, char *,
+             struct wordlist **, struct wordlist **,
+             char *);
+static void free_wordlist (struct wordlist *);
+static void auth_script (char *);
+static void auth_script_done (void *);
+static void set_allowed_addrs (int unit, struct wordlist *addrs);
+static int  some_ip_ok (struct wordlist *);
+static int  setupapfile (char **);
+static int  privgroup (char **);
+static int  set_noauth_addr (char **);
+static void check_access (FILE *, char *);
+#endif /* 0 */ /* PAP_SUPPORT || CHAP_SUPPORT */
+
+#if 0 /* UNUSED */
+/*
+ * Authentication-related options.
+ */
+option_t auth_options[] = {
+    { "require-pap", o_bool, &lcp_wantoptions[0].neg_upap,
+      "Require PAP authentication from peer", 1, &auth_required },
+    { "+pap", o_bool, &lcp_wantoptions[0].neg_upap,
+      "Require PAP authentication from peer", 1, &auth_required },
+    { "refuse-pap", o_bool, &refuse_pap,
+      "Don't agree to auth to peer with PAP", 1 },
+    { "-pap", o_bool, &refuse_pap,
+      "Don't allow PAP authentication with peer", 1 },
+    { "require-chap", o_bool, &lcp_wantoptions[0].neg_chap,
+      "Require CHAP authentication from peer", 1, &auth_required },
+    { "+chap", o_bool, &lcp_wantoptions[0].neg_chap,
+      "Require CHAP authentication from peer", 1, &auth_required },
+    { "refuse-chap", o_bool, &refuse_chap,
+      "Don't agree to auth to peer with CHAP", 1 },
+    { "-chap", o_bool, &refuse_chap,
+      "Don't allow CHAP authentication with peer", 1 },
+    { "name", o_string, our_name,
+      "Set local name for authentication",
+      OPT_PRIV|OPT_STATIC, NULL, MAXNAMELEN },
+    { "user", o_string, user,
+      "Set name for auth with peer", OPT_STATIC, NULL, MAXNAMELEN },
+    { "usehostname", o_bool, &usehostname,
+      "Must use hostname for authentication", 1 },
+    { "remotename", o_string, remote_name,
+      "Set remote name for authentication", OPT_STATIC,
+      &explicit_remote, MAXNAMELEN },
+    { "auth", o_bool, &auth_required,
+      "Require authentication from peer", 1 },
+    { "noauth", o_bool, &auth_required,
+      "Don't require peer to authenticate", OPT_PRIV, &allow_any_ip },
+    {  "login", o_bool, &uselogin,
+      "Use system password database for PAP", 1 },
+    { "papcrypt", o_bool, &cryptpap,
+      "PAP passwords are encrypted", 1 },
+    { "+ua", o_special, (void *)setupapfile,
+      "Get PAP user and password from file" },
+    { "password", o_string, passwd,
+      "Password for authenticating us to the peer", OPT_STATIC,
+      NULL, MAXSECRETLEN },
+    { "privgroup", o_special, (void *)privgroup,
+      "Allow group members to use privileged options", OPT_PRIV },
+    { "allow-ip", o_special, (void *)set_noauth_addr,
+      "Set IP address(es) which can be used without authentication",
+      OPT_PRIV },
+    { NULL }
+};
+#endif /* UNUSED */
+#if 0 /* UNUSED */
+/*
+ * setupapfile - specifies UPAP info for authenticating with peer.
+ */
+static int
+setupapfile(char **argv)
+{
+    FILE * ufile;
+    int l;
+
+    lcp_allowoptions[0].neg_upap = 1;
+
+    /* open user info file */
+    seteuid(getuid());
+    ufile = fopen(*argv, "r");
+    seteuid(0);
+    if (ufile == NULL) {
+      option_error("unable to open user login data file %s", *argv);
+      return 0;
+    }
+    check_access(ufile, *argv);
+
+    /* get username */
+    if (fgets(user, MAXNAMELEN - 1, ufile) == NULL
+        || fgets(passwd, MAXSECRETLEN - 1, ufile) == NULL){
+      option_error("unable to read user login data file %s", *argv);
+      return 0;
+    }
+    fclose(ufile);
+
+    /* get rid of newlines */
+    l = strlen(user);
+    if (l > 0 && user[l-1] == '\n')
+      user[l-1] = 0;
+    l = strlen(passwd);
+    if (l > 0 && passwd[l-1] == '\n')
+      passwd[l-1] = 0;
+
+    return (1);
+}
+#endif /* UNUSED */
+
+#if 0 /* UNUSED */
+/*
+ * privgroup - allow members of the group to have privileged access.
+ */
+static int
+privgroup(char **argv)
+{
+    struct group *g;
+    int i;
+
+    g = getgrnam(*argv);
+    if (g == 0) {
+      option_error("group %s is unknown", *argv);
+      return 0;
+    }
+    for (i = 0; i < ngroups; ++i) {
+      if (groups[i] == g->gr_gid) {
+        privileged = 1;
+        break;
+      }
+    }
+    return 1;
+}
+#endif
+
+#if 0 /* UNUSED */
+/*
+ * set_noauth_addr - set address(es) that can be used without authentication.
+ * Equivalent to specifying an entry like `"" * "" addr' in pap-secrets.
+ */
+static int
+set_noauth_addr(char **argv)
+{
+    char *addr = *argv;
+    int l = strlen(addr);
+    struct wordlist *wp;
+
+    wp = (struct wordlist *) malloc(sizeof(struct wordlist) + l + 1);
+    if (wp == NULL)
+      novm("allow-ip argument");
+    wp->word = (char *) (wp + 1);
+    wp->next = noauth_addrs;
+    BCOPY(addr, wp->word, l);
+    noauth_addrs = wp;
+    return 1;
+}
+#endif /* UNUSED */
+
+/*
+ * An Open on LCP has requested a change from Dead to Establish phase.
+ * Do what's necessary to bring the physical layer up.
+ */
+void
+link_required(int unit)
+{
+  LWIP_UNUSED_ARG(unit);
+
+  AUTHDEBUG(LOG_INFO, ("link_required: %d\n", unit));
+}
+
+/*
+ * LCP has terminated the link; go to the Dead phase and take the
+ * physical layer down.
+ */
+void
+link_terminated(int unit)
+{
+  AUTHDEBUG(LOG_INFO, ("link_terminated: %d\n", unit));
+  if (lcp_phase[unit] == PHASE_DEAD) {
+    return;
+  }
+  if (logged_in) {
+    plogout();
+  }
+  lcp_phase[unit] = PHASE_DEAD;
+  AUTHDEBUG(LOG_NOTICE, ("Connection terminated.\n"));
+  pppLinkTerminated(unit);
+}
+
+/*
+ * LCP has gone down; it will either die or try to re-establish.
+ */
+void
+link_down(int unit)
+{
+  int i;
+  struct protent *protp;
+
+  AUTHDEBUG(LOG_INFO, ("link_down: %d\n", unit));
+
+  if (did_authup) {
+    /* XXX Do link down processing. */
+    did_authup = 0;
+  }
+  for (i = 0; (protp = ppp_protocols[i]) != NULL; ++i) {
+    if (!protp->enabled_flag) {
+      continue;
+    }
+    if (protp->protocol != PPP_LCP && protp->lowerdown != NULL) {
+      (*protp->lowerdown)(unit);
+    }
+    if (protp->protocol < 0xC000 && protp->close != NULL) {
+      (*protp->close)(unit, "LCP down");
+    }
+  }
+  num_np_open = 0;  /* number of network protocols we have opened */
+  num_np_up = 0;    /* Number of network protocols which have come up */
+
+  if (lcp_phase[unit] != PHASE_DEAD) {
+    lcp_phase[unit] = PHASE_TERMINATE;
+  }
+  pppLinkDown(unit);
+}
+
+/*
+ * The link is established.
+ * Proceed to the Dead, Authenticate or Network phase as appropriate.
+ */
+void
+link_established(int unit)
+{
+  int auth;
+  int i;
+  struct protent *protp;
+  lcp_options *wo = &lcp_wantoptions[unit];
+  lcp_options *go = &lcp_gotoptions[unit];
+#if PAP_SUPPORT || CHAP_SUPPORT
+  lcp_options *ho = &lcp_hisoptions[unit];
+#endif /* PAP_SUPPORT || CHAP_SUPPORT */
+
+  AUTHDEBUG(LOG_INFO, ("link_established: unit %d; Lowering up all protocols...\n", unit));
+  /*
+   * Tell higher-level protocols that LCP is up.
+   */
+  for (i = 0; (protp = ppp_protocols[i]) != NULL; ++i) {
+    if (protp->protocol != PPP_LCP && protp->enabled_flag && protp->lowerup != NULL) {
+      (*protp->lowerup)(unit);
+    }
+  }
+  if (ppp_settings.auth_required && !(go->neg_chap || go->neg_upap)) {
+    /*
+     * We wanted the peer to authenticate itself, and it refused:
+     * treat it as though it authenticated with PAP using a username
+     * of "" and a password of "".  If that's not OK, boot it out.
+     */
+    if (!wo->neg_upap || !null_login(unit)) {
+      AUTHDEBUG(LOG_WARNING, ("peer refused to authenticate\n"));
+      lcp_close(unit, "peer refused to authenticate");
+      return;
+    }
+  }
+
+  lcp_phase[unit] = PHASE_AUTHENTICATE;
+  auth = 0;
+#if CHAP_SUPPORT
+  if (go->neg_chap) {
+    ChapAuthPeer(unit, ppp_settings.our_name, go->chap_mdtype);
+    auth |= CHAP_PEER;
+  } 
+#endif /* CHAP_SUPPORT */
+#if PAP_SUPPORT && CHAP_SUPPORT
+  else
+#endif /* PAP_SUPPORT && CHAP_SUPPORT */
+#if PAP_SUPPORT
+  if (go->neg_upap) {
+    upap_authpeer(unit);
+    auth |= PAP_PEER;
+  }
+#endif /* PAP_SUPPORT */
+#if CHAP_SUPPORT
+  if (ho->neg_chap) {
+    ChapAuthWithPeer(unit, ppp_settings.user, ho->chap_mdtype);
+    auth |= CHAP_WITHPEER;
+  }
+#endif /* CHAP_SUPPORT */
+#if PAP_SUPPORT && CHAP_SUPPORT
+  else
+#endif /* PAP_SUPPORT && CHAP_SUPPORT */
+#if PAP_SUPPORT
+  if (ho->neg_upap) {
+    if (ppp_settings.passwd[0] == 0) {
+      passwd_from_file = 1;
+      if (!get_pap_passwd(unit, ppp_settings.user, ppp_settings.passwd)) {
+        AUTHDEBUG(LOG_ERR, ("No secret found for PAP login\n"));
+      }
+    }
+    upap_authwithpeer(unit, ppp_settings.user, ppp_settings.passwd);
+    auth |= PAP_WITHPEER;
+  }
+#endif /* PAP_SUPPORT */
+  auth_pending[unit] = auth;
+
+  if (!auth) {
+    network_phase(unit);
+  }
+}
+
+/*
+ * Proceed to the network phase.
+ */
+static void
+network_phase(int unit)
+{
+  int i;
+  struct protent *protp;
+  lcp_options *go = &lcp_gotoptions[unit];
+
+  /*
+   * If the peer had to authenticate, run the auth-up script now.
+   */
+  if ((go->neg_chap || go->neg_upap) && !did_authup) {
+    /* XXX Do setup for peer authentication. */
+    did_authup = 1;
+  }
+
+#if CBCP_SUPPORT
+  /*
+   * If we negotiated callback, do it now.
+   */
+  if (go->neg_cbcp) {
+    lcp_phase[unit] = PHASE_CALLBACK;
+    (*cbcp_protent.open)(unit);
+    return;
+  }
+#endif /* CBCP_SUPPORT */
+
+  lcp_phase[unit] = PHASE_NETWORK;
+  for (i = 0; (protp = ppp_protocols[i]) != NULL; ++i) {
+    if (protp->protocol < 0xC000 && protp->enabled_flag && protp->open != NULL) {
+      (*protp->open)(unit);
+      if (protp->protocol != PPP_CCP) {
+        ++num_np_open;
+      }
+    }
+  }
+
+  if (num_np_open == 0) {
+    /* nothing to do */
+    lcp_close(0, "No network protocols running");
+  }
+}
+/* @todo: add void start_networks(void) here (pppd 2.3.11) */
+
+/*
+ * The peer has failed to authenticate himself using `protocol'.
+ */
+void
+auth_peer_fail(int unit, u16_t protocol)
+{
+  LWIP_UNUSED_ARG(protocol);
+
+  AUTHDEBUG(LOG_INFO, ("auth_peer_fail: %d proto=%X\n", unit, protocol));
+  /*
+   * Authentication failure: take the link down
+   */
+  lcp_close(unit, "Authentication failed");
+}
+
+
+#if PAP_SUPPORT || CHAP_SUPPORT
+/*
+ * The peer has been successfully authenticated using `protocol'.
+ */
+void
+auth_peer_success(int unit, u16_t protocol, char *name, int namelen)
+{
+  int pbit;
+
+  AUTHDEBUG(LOG_INFO, ("auth_peer_success: %d proto=%X\n", unit, protocol));
+  switch (protocol) {
+    case PPP_CHAP:
+      pbit = CHAP_PEER;
+      break;
+    case PPP_PAP:
+      pbit = PAP_PEER;
+      break;
+    default:
+      AUTHDEBUG(LOG_WARNING, ("auth_peer_success: unknown protocol %x\n", protocol));
+      return;
+  }
+
+  /*
+   * Save the authenticated name of the peer for later.
+   */
+  if (namelen > (int)sizeof(peer_authname) - 1) {
+    namelen = sizeof(peer_authname) - 1;
+  }
+  BCOPY(name, peer_authname, namelen);
+  peer_authname[namelen] = 0;
+  
+  /*
+   * If there is no more authentication still to be done,
+   * proceed to the network (or callback) phase.
+   */
+  if ((auth_pending[unit] &= ~pbit) == 0) {
+    network_phase(unit);
+  }
+}
+
+/*
+ * We have failed to authenticate ourselves to the peer using `protocol'.
+ */
+void
+auth_withpeer_fail(int unit, u16_t protocol)
+{
+  int errCode = PPPERR_AUTHFAIL;
+
+  LWIP_UNUSED_ARG(protocol);
+
+  AUTHDEBUG(LOG_INFO, ("auth_withpeer_fail: %d proto=%X\n", unit, protocol));
+  if (passwd_from_file) {
+    BZERO(ppp_settings.passwd, MAXSECRETLEN);
+  }
+
+  /*
+   * We've failed to authenticate ourselves to our peer.
+   * He'll probably take the link down, and there's not much
+   * we can do except wait for that.
+   */
+  pppIOCtl(unit, PPPCTLS_ERRCODE, &errCode);
+  lcp_close(unit, "Failed to authenticate ourselves to peer");
+}
+
+/*
+ * We have successfully authenticated ourselves with the peer using `protocol'.
+ */
+void
+auth_withpeer_success(int unit, u16_t protocol)
+{
+  int pbit;
+
+  AUTHDEBUG(LOG_INFO, ("auth_withpeer_success: %d proto=%X\n", unit, protocol));
+  switch (protocol) {
+    case PPP_CHAP:
+      pbit = CHAP_WITHPEER;
+      break;
+    case PPP_PAP:
+      if (passwd_from_file) {
+        BZERO(ppp_settings.passwd, MAXSECRETLEN);
+      }
+      pbit = PAP_WITHPEER;
+      break;
+    default:
+      AUTHDEBUG(LOG_WARNING, ("auth_peer_success: unknown protocol %x\n", protocol));
+      pbit = 0;
+  }
+
+  /*
+   * If there is no more authentication still being done,
+   * proceed to the network (or callback) phase.
+   */
+  if ((auth_pending[unit] &= ~pbit) == 0) {
+    network_phase(unit);
+  }
+}
+#endif /* PAP_SUPPORT || CHAP_SUPPORT */
+
+
+/*
+ * np_up - a network protocol has come up.
+ */
+void
+np_up(int unit, u16_t proto)
+{
+  LWIP_UNUSED_ARG(unit);
+  LWIP_UNUSED_ARG(proto);
+
+  AUTHDEBUG(LOG_INFO, ("np_up: %d proto=%X\n", unit, proto));
+  if (num_np_up == 0) {
+    AUTHDEBUG(LOG_INFO, ("np_up: maxconnect=%d idle_time_limit=%d\n",ppp_settings.maxconnect,ppp_settings.idle_time_limit));
+    /*
+     * At this point we consider that the link has come up successfully.
+     */
+    if (ppp_settings.idle_time_limit > 0) {
+      TIMEOUT(check_idle, NULL, ppp_settings.idle_time_limit);
+    }
+
+    /*
+     * Set a timeout to close the connection once the maximum
+     * connect time has expired.
+     */
+    if (ppp_settings.maxconnect > 0) {
+      TIMEOUT(connect_time_expired, 0, ppp_settings.maxconnect);
+    }
+  }
+  ++num_np_up;
+}
+
+/*
+ * np_down - a network protocol has gone down.
+ */
+void
+np_down(int unit, u16_t proto)
+{
+  LWIP_UNUSED_ARG(unit);
+  LWIP_UNUSED_ARG(proto);
+
+  AUTHDEBUG(LOG_INFO, ("np_down: %d proto=%X\n", unit, proto));
+  if (--num_np_up == 0 && ppp_settings.idle_time_limit > 0) {
+    UNTIMEOUT(check_idle, NULL);
+  }
+}
+
+/*
+ * np_finished - a network protocol has finished using the link.
+ */
+void
+np_finished(int unit, u16_t proto)
+{
+  LWIP_UNUSED_ARG(unit);
+  LWIP_UNUSED_ARG(proto);
+
+  AUTHDEBUG(LOG_INFO, ("np_finished: %d proto=%X\n", unit, proto));
+  if (--num_np_open <= 0) {
+    /* no further use for the link: shut up shop. */
+    lcp_close(0, "No network protocols running");
+  }
+}
+
+/*
+ * check_idle - check whether the link has been idle for long
+ * enough that we can shut it down.
+ */
+static void
+check_idle(void *arg)
+{
+  struct ppp_idle idle;
+  u_short itime;
+  
+  LWIP_UNUSED_ARG(arg);
+  if (!get_idle_time(0, &idle)) {
+    return;
+  }
+  itime = LWIP_MIN(idle.xmit_idle, idle.recv_idle);
+  if (itime >= ppp_settings.idle_time_limit) {
+    /* link is idle: shut it down. */
+    AUTHDEBUG(LOG_INFO, ("Terminating connection due to lack of activity.\n"));
+    lcp_close(0, "Link inactive");
+  } else {
+    TIMEOUT(check_idle, NULL, ppp_settings.idle_time_limit - itime);
+  }
+}
+
+/*
+ * connect_time_expired - log a message and close the connection.
+ */
+static void
+connect_time_expired(void *arg)
+{
+  LWIP_UNUSED_ARG(arg);
+
+  AUTHDEBUG(LOG_INFO, ("Connect time expired\n"));
+  lcp_close(0, "Connect time expired");   /* Close connection */
+}
+
+#if 0 /* UNUSED */
+/*
+ * auth_check_options - called to check authentication options.
+ */
+void
+auth_check_options(void)
+{
+  lcp_options *wo = &lcp_wantoptions[0];
+  int can_auth;
+  ipcp_options *ipwo = &ipcp_wantoptions[0];
+  u32_t remote;
+
+  /* Default our_name to hostname, and user to our_name */
+  if (ppp_settings.our_name[0] == 0 || ppp_settings.usehostname) {
+      strcpy(ppp_settings.our_name, ppp_settings.hostname);
+  }
+
+  if (ppp_settings.user[0] == 0) {
+    strcpy(ppp_settings.user, ppp_settings.our_name);
+  }
+
+  /* If authentication is required, ask peer for CHAP or PAP. */
+  if (ppp_settings.auth_required && !wo->neg_chap && !wo->neg_upap) {
+    wo->neg_chap = 1;
+    wo->neg_upap = 1;
+  }
+  
+  /*
+   * Check whether we have appropriate secrets to use
+   * to authenticate the peer.
+   */
+  can_auth = wo->neg_upap && have_pap_secret();
+  if (!can_auth && wo->neg_chap) {
+    remote = ipwo->accept_remote? 0: ipwo->hisaddr;
+    can_auth = have_chap_secret(ppp_settings.remote_name, ppp_settings.our_name, remote);
+  }
+
+  if (ppp_settings.auth_required && !can_auth) {
+    ppp_panic("No auth secret");
+  }
+}
+#endif /* UNUSED */
+
+/*
+ * auth_reset - called when LCP is starting negotiations to recheck
+ * authentication options, i.e. whether we have appropriate secrets
+ * to use for authenticating ourselves and/or the peer.
+ */
+void
+auth_reset(int unit)
+{
+  lcp_options *go = &lcp_gotoptions[unit];
+  lcp_options *ao = &lcp_allowoptions[0];
+  ipcp_options *ipwo = &ipcp_wantoptions[0];
+  u32_t remote;
+
+  AUTHDEBUG(LOG_INFO, ("auth_reset: %d\n", unit));
+  ao->neg_upap = !ppp_settings.refuse_pap && (ppp_settings.passwd[0] != 0 || get_pap_passwd(unit, NULL, NULL));
+  ao->neg_chap = !ppp_settings.refuse_chap && ppp_settings.passwd[0] != 0 /*have_chap_secret(ppp_settings.user, ppp_settings.remote_name, (u32_t)0)*/;
+
+  if (go->neg_upap && !have_pap_secret()) {
+    go->neg_upap = 0;
+  }
+  if (go->neg_chap) {
+    remote = ipwo->accept_remote? 0: ipwo->hisaddr;
+    if (!have_chap_secret(ppp_settings.remote_name, ppp_settings.our_name, remote)) {
+      go->neg_chap = 0;
+    }
+  }
+}
+
+#if PAP_SUPPORT
+/*
+ * check_passwd - Check the user name and passwd against the PAP secrets
+ * file.  If requested, also check against the system password database,
+ * and login the user if OK.
+ *
+ * returns:
+ *  UPAP_AUTHNAK: Authentication failed.
+ *  UPAP_AUTHACK: Authentication succeeded.
+ * In either case, msg points to an appropriate message.
+ */
+u_char
+check_passwd( int unit, char *auser, int userlen, char *apasswd, int passwdlen, char **msg, int *msglen)
+{
+#if 1 /* XXX Assume all entries OK. */
+  LWIP_UNUSED_ARG(unit);
+  LWIP_UNUSED_ARG(auser);
+  LWIP_UNUSED_ARG(userlen);
+  LWIP_UNUSED_ARG(apasswd);
+  LWIP_UNUSED_ARG(passwdlen);
+  LWIP_UNUSED_ARG(msglen);
+  *msg = (char *) 0;
+  return UPAP_AUTHACK;     /* XXX Assume all entries OK. */
+#else
+  u_char ret = 0;
+  struct wordlist *addrs = NULL;
+  char passwd[256], user[256];
+  char secret[MAXWORDLEN];
+  static u_short attempts = 0;
+  
+  /*
+   * Make copies of apasswd and auser, then null-terminate them.
+   */
+  BCOPY(apasswd, passwd, passwdlen);
+  passwd[passwdlen] = '\0';
+  BCOPY(auser, user, userlen);
+  user[userlen] = '\0';
+  *msg = (char *) 0;
+
+  /* XXX Validate user name and password. */
+  ret = UPAP_AUTHACK;     /* XXX Assume all entries OK. */
+      
+  if (ret == UPAP_AUTHNAK) {
+    if (*msg == (char *) 0) {
+      *msg = "Login incorrect";
+    }
+    *msglen = strlen(*msg);
+    /*
+     * Frustrate passwd stealer programs.
+     * Allow 10 tries, but start backing off after 3 (stolen from login).
+     * On 10'th, drop the connection.
+     */
+    if (attempts++ >= 10) {
+      AUTHDEBUG(LOG_WARNING, ("%d LOGIN FAILURES BY %s\n", attempts, user));
+      /*ppp_panic("Excess Bad Logins");*/
+    }
+    if (attempts > 3) {
+      /* @todo: this was sleep(), i.e. seconds, not milliseconds
+       * I don't think we really need this in lwIP - we would block tcpip_thread!
+       */
+      /*sys_msleep((attempts - 3) * 5);*/
+    }
+    if (addrs != NULL) {
+      free_wordlist(addrs);
+    }
+  } else {
+    attempts = 0; /* Reset count */
+    if (*msg == (char *) 0) {
+      *msg = "Login ok";
+    }
+    *msglen = strlen(*msg);
+    set_allowed_addrs(unit, addrs);
+  }
+
+  BZERO(passwd, sizeof(passwd));
+  BZERO(secret, sizeof(secret));
+
+  return ret;
+#endif
+}
+#endif /* PAP_SUPPORT */
+
+#if 0 /* UNUSED */
+/*
+ * This function is needed for PAM.
+ */
+
+#ifdef USE_PAM
+
+/* lwip does not support PAM*/
+
+#endif  /* USE_PAM */
+
+#endif /* UNUSED */
+
+
+#if 0 /* UNUSED */
+/*
+ * plogin - Check the user name and password against the system
+ * password database, and login the user if OK.
+ *
+ * returns:
+ *  UPAP_AUTHNAK: Login failed.
+ *  UPAP_AUTHACK: Login succeeded.
+ * In either case, msg points to an appropriate message.
+ */
+static int
+plogin(char *user, char *passwd, char **msg, int *msglen)
+{
+
+  LWIP_UNUSED_ARG(user);
+  LWIP_UNUSED_ARG(passwd);
+  LWIP_UNUSED_ARG(msg);
+  LWIP_UNUSED_ARG(msglen);
+
+
+ /* The new lines are here align the file when 
+  * compared against the pppd 2.3.11 code */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  /* XXX Fail until we decide that we want to support logins. */
+  return (UPAP_AUTHNAK);
+}
+#endif
+
+
+
+/*
+ * plogout - Logout the user.
+ */
+static void
+plogout(void)
+{
+  logged_in = 0;
+}
+
+/*
+ * null_login - Check if a username of "" and a password of "" are
+ * acceptable, and iff so, set the list of acceptable IP addresses
+ * and return 1.
+ */
+static int
+null_login(int unit)
+{
+  LWIP_UNUSED_ARG(unit);
+  /* XXX Fail until we decide that we want to support logins. */
+  return 0;
+}
+
+
+/*
+ * get_pap_passwd - get a password for authenticating ourselves with
+ * our peer using PAP.  Returns 1 on success, 0 if no suitable password
+ * could be found.
+ */
+static int
+get_pap_passwd(int unit, char *user, char *passwd)
+{
+  LWIP_UNUSED_ARG(unit);
+/* normally we would reject PAP if no password is provided,
+   but this causes problems with some providers (like CHT in Taiwan)
+   who incorrectly request PAP and expect a bogus/empty password, so
+   always provide a default user/passwd of "none"/"none"
+
+   @todo: This should be configured by the user, instead of being hardcoded here!
+*/
+  if(user) {
+    strcpy(user, "none");
+  }
+  if(passwd) {
+    strcpy(passwd, "none");
+  }
+  return 1;
+}
+
+/*
+ * have_pap_secret - check whether we have a PAP file with any
+ * secrets that we could possibly use for authenticating the peer.
+ */
+static int
+have_pap_secret(void)
+{
+  /* XXX Fail until we set up our passwords. */
+  return 0;
+}
+
+/*
+ * have_chap_secret - check whether we have a CHAP file with a
+ * secret that we could possibly use for authenticating `client'
+ * on `server'.  Either can be the null string, meaning we don't
+ * know the identity yet.
+ */
+static int
+have_chap_secret(char *client, char *server, u32_t remote)
+{
+  LWIP_UNUSED_ARG(client);
+  LWIP_UNUSED_ARG(server);
+  LWIP_UNUSED_ARG(remote);
+
+  /* XXX Fail until we set up our passwords. */
+  return 0;
+}
+#if CHAP_SUPPORT
+
+/*
+ * get_secret - open the CHAP secret file and return the secret
+ * for authenticating the given client on the given server.
+ * (We could be either client or server).
+ */
+int
+get_secret(int unit, char *client, char *server, char *secret, int *secret_len, int save_addrs)
+{
+#if 1
+  int len;
+  struct wordlist *addrs;
+
+  LWIP_UNUSED_ARG(unit);
+  LWIP_UNUSED_ARG(server);
+  LWIP_UNUSED_ARG(save_addrs);
+
+  addrs = NULL;
+
+  if(!client || !client[0] || strcmp(client, ppp_settings.user)) {
+    return 0;
+  }
+
+  len = (int)strlen(ppp_settings.passwd);
+  if (len > MAXSECRETLEN) {
+    AUTHDEBUG(LOG_ERR, ("Secret for %s on %s is too long\n", client, server));
+    len = MAXSECRETLEN;
+  }
+
+  BCOPY(ppp_settings.passwd, secret, len);
+  *secret_len = len;
+
+  return 1;
+#else
+  int ret = 0, len;
+  struct wordlist *addrs;
+  char secbuf[MAXWORDLEN];
+  
+  addrs = NULL;
+  secbuf[0] = 0;
+
+  /* XXX Find secret. */
+  if (ret < 0) {
+    return 0;
+  }
+
+  if (save_addrs) {
+    set_allowed_addrs(unit, addrs);
+  }
+
+  len = strlen(secbuf);
+  if (len > MAXSECRETLEN) {
+    AUTHDEBUG(LOG_ERR, ("Secret for %s on %s is too long\n", client, server));
+    len = MAXSECRETLEN;
+  }
+
+  BCOPY(secbuf, secret, len);
+  BZERO(secbuf, sizeof(secbuf));
+  *secret_len = len;
+
+  return 1;
+#endif
+}
+#endif /* CHAP_SUPPORT */
+
+
+#if 0 /* PAP_SUPPORT || CHAP_SUPPORT */
+/*
+ * set_allowed_addrs() - set the list of allowed addresses.
+ */
+static void
+set_allowed_addrs(int unit, struct wordlist *addrs)
+{
+  if (addresses[unit] != NULL) {
+    free_wordlist(addresses[unit]);
+  }
+  addresses[unit] = addrs;
+
+#if 0
+  /*
+   * If there's only one authorized address we might as well
+   * ask our peer for that one right away
+   */
+  if (addrs != NULL && addrs->next == NULL) {
+    char *p = addrs->word;
+    struct ipcp_options *wo = &ipcp_wantoptions[unit];
+    u32_t a;
+    struct hostent *hp;
+    
+    if (wo->hisaddr == 0 && *p != '!' && *p != '-' && strchr(p, '/') == NULL) {
+      hp = gethostbyname(p);
+      if (hp != NULL && hp->h_addrtype == AF_INET) {
+        a = *(u32_t *)hp->h_addr;
+      } else {
+        a = inet_addr(p);
+      }
+      if (a != (u32_t) -1) {
+        wo->hisaddr = a;
+      }
+    }
+  }
+#endif
+}
+#endif /* 0 */ /* PAP_SUPPORT || CHAP_SUPPORT */
+
+/*
+ * auth_ip_addr - check whether the peer is authorized to use
+ * a given IP address.  Returns 1 if authorized, 0 otherwise.
+ */
+int
+auth_ip_addr(int unit, u32_t addr)
+{
+  return ip_addr_check(addr, addresses[unit]);
+}
+
+static int /* @todo: integrate this funtion into auth_ip_addr()*/
+ip_addr_check(u32_t addr, struct wordlist *addrs)
+{
+  /* don't allow loopback or multicast address */
+  if (bad_ip_adrs(addr)) {
+    return 0;
+  }
+
+  if (addrs == NULL) {
+    return !ppp_settings.auth_required; /* no addresses authorized */
+  }
+
+  /* XXX All other addresses allowed. */
+  return 1;
+}
+
+/*
+ * bad_ip_adrs - return 1 if the IP address is one we don't want
+ * to use, such as an address in the loopback net or a multicast address.
+ * addr is in network byte order.
+ */
+int
+bad_ip_adrs(u32_t addr)
+{
+  addr = ntohl(addr);
+  return (addr >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET
+      || IN_MULTICAST(addr) || IN_BADCLASS(addr);
+}
+
+#if 0 /* UNUSED */ /* PAP_SUPPORT || CHAP_SUPPORT */
+/*
+ * some_ip_ok - check a wordlist to see if it authorizes any
+ * IP address(es).
+ */
+static int
+some_ip_ok(struct wordlist *addrs)
+{
+    for (; addrs != 0; addrs = addrs->next) {
+      if (addrs->word[0] == '-')
+        break;
+      if (addrs->word[0] != '!')
+        return 1; /* some IP address is allowed */
+    }
+    return 0;
+}
+
+/*
+ * check_access - complain if a secret file has too-liberal permissions.
+ */
+static void
+check_access(FILE *f, char *filename)
+{
+    struct stat sbuf;
+
+    if (fstat(fileno(f), &sbuf) < 0) {
+      warn("cannot stat secret file %s: %m", filename);
+    } else if ((sbuf.st_mode & (S_IRWXG | S_IRWXO)) != 0) {
+      warn("Warning - secret file %s has world and/or group access",
+            filename);
+    }
+}
+
+
+/*
+ * scan_authfile - Scan an authorization file for a secret suitable
+ * for authenticating `client' on `server'.  The return value is -1
+ * if no secret is found, otherwise >= 0.  The return value has
+ * NONWILD_CLIENT set if the secret didn't have "*" for the client, and
+ * NONWILD_SERVER set if the secret didn't have "*" for the server.
+ * Any following words on the line up to a "--" (i.e. address authorization
+ * info) are placed in a wordlist and returned in *addrs.  Any
+ * following words (extra options) are placed in a wordlist and
+ * returned in *opts.
+ * We assume secret is NULL or points to MAXWORDLEN bytes of space.
+ */
+static int
+scan_authfile(FILE *f, char *client, char *server, char *secret, struct wordlist **addrs, struct wordlist **opts, char *filename)
+{
+  /* We do not (currently) need this in lwip  */
+  return 0; /* dummy */
+}
+/*
+ * free_wordlist - release memory allocated for a wordlist.
+ */
+static void
+free_wordlist(struct wordlist *wp)
+{
+  struct wordlist *next;
+
+  while (wp != NULL) {
+    next = wp->next;
+    free(wp);
+    wp = next;
+  }
+}
+
+/*
+ * auth_script_done - called when the auth-up or auth-down script
+ * has finished.
+ */
+static void
+auth_script_done(void *arg)
+{
+    auth_script_pid = 0;
+    switch (auth_script_state) {
+    case s_up:
+      if (auth_state == s_down) {
+        auth_script_state = s_down;
+        auth_script(_PATH_AUTHDOWN);
+      }
+      break;
+    case s_down:
+      if (auth_state == s_up) {
+        auth_script_state = s_up;
+        auth_script(_PATH_AUTHUP);
+      }
+      break;
+    }
+}
+
+/*
+ * auth_script - execute a script with arguments
+ * interface-name peer-name real-user tty speed
+ */
+static void
+auth_script(char *script)
+{
+    char strspeed[32];
+    struct passwd *pw;
+    char struid[32];
+    char *user_name;
+    char *argv[8];
+
+    if ((pw = getpwuid(getuid())) != NULL && pw->pw_name != NULL)
+      user_name = pw->pw_name;
+    else {
+      slprintf(struid, sizeof(struid), "%d", getuid());
+      user_name = struid;
+    }
+    slprintf(strspeed, sizeof(strspeed), "%d", baud_rate);
+
+    argv[0] = script;
+    argv[1] = ifname;
+    argv[2] = peer_authname;
+    argv[3] = user_name;
+    argv[4] = devnam;
+    argv[5] = strspeed;
+    argv[6] = NULL;
+
+    auth_script_pid = run_program(script, argv, 0, auth_script_done, NULL);
+}
+#endif  /* 0 */ /* PAP_SUPPORT || CHAP_SUPPORT */
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/auth.h b/external/badvpn_dns/lwip/src/netif/ppp/auth.h
new file mode 100644
index 0000000..a8069ec
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/auth.h
@@ -0,0 +1,111 @@
+/*****************************************************************************
+* auth.h -  PPP Authentication and phase control header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1998 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-04 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original derived from BSD pppd.h.
+*****************************************************************************/
+/*
+ * pppd.h - PPP daemon global declarations.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#ifndef AUTH_H
+#define AUTH_H
+
+/***********************
+*** PUBLIC FUNCTIONS ***
+***********************/
+
+/* we are starting to use the link */
+void link_required (int);
+
+/* we are finished with the link */
+void link_terminated (int);
+
+/* the LCP layer has left the Opened state */
+void link_down (int);
+
+/* the link is up; authenticate now */
+void link_established (int);
+
+/* a network protocol has come up */
+void np_up (int, u16_t);
+
+/* a network protocol has gone down */
+void np_down (int, u16_t);
+
+/* a network protocol no longer needs link */
+void np_finished (int, u16_t);
+
+/* peer failed to authenticate itself */
+void auth_peer_fail (int, u16_t);
+
+/* peer successfully authenticated itself */
+void auth_peer_success (int, u16_t, char *, int);
+
+/* we failed to authenticate ourselves */
+void auth_withpeer_fail (int, u16_t);
+
+/* we successfully authenticated ourselves */
+void auth_withpeer_success (int, u16_t);
+
+/* check authentication options supplied */
+void auth_check_options (void);
+
+/* check what secrets we have */
+void auth_reset (int);
+
+/* Check peer-supplied username/password */
+u_char check_passwd (int, char *, int, char *, int, char **, int *);
+
+/* get "secret" for chap */
+int  get_secret (int, char *, char *, char *, int *, int);
+
+/* check if IP address is authorized */
+int  auth_ip_addr (int, u32_t);
+
+/* check if IP address is unreasonable */
+int  bad_ip_adrs (u32_t);
+
+#endif /* AUTH_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/chap.c b/external/badvpn_dns/lwip/src/netif/ppp/chap.c
new file mode 100644
index 0000000..f10e27d
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/chap.c
@@ -0,0 +1,908 @@
+/*** WARNING - THIS HAS NEVER BEEN FINISHED ***/
+/*****************************************************************************
+* chap.c - Network Challenge Handshake Authentication Protocol program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 by Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-04 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original based on BSD chap.c.
+*****************************************************************************/
+/*
+ * chap.c - Challenge Handshake Authentication Protocol.
+ *
+ * Copyright (c) 1993 The Australian National University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the Australian National University.  The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Copyright (c) 1991 Gregory M. Christy.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Gregory M. Christy.  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 ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT  /* don't build if not configured for use in lwipopts.h */
+
+#if CHAP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "magic.h"
+#include "randm.h"
+#include "auth.h"
+#include "md5.h"
+#include "chap.h"
+#include "chpms.h"
+
+#include <string.h>
+
+#if 0 /* UNUSED */
+/*
+ * Command-line options.
+ */
+static option_t chap_option_list[] = {
+    { "chap-restart", o_int, &chap[0].timeouttime,
+      "Set timeout for CHAP" },
+    { "chap-max-challenge", o_int, &chap[0].max_transmits,
+      "Set max #xmits for challenge" },
+    { "chap-interval", o_int, &chap[0].chal_interval,
+      "Set interval for rechallenge" },
+#ifdef MSLANMAN
+    { "ms-lanman", o_bool, &ms_lanman,
+      "Use LanMan passwd when using MS-CHAP", 1 },
+#endif
+    { NULL }
+};
+#endif /* UNUSED */
+
+/*
+ * Protocol entry points.
+ */
+static void ChapInit (int);
+static void ChapLowerUp (int);
+static void ChapLowerDown (int);
+static void ChapInput (int, u_char *, int);
+static void ChapProtocolReject (int);
+#if PPP_ADDITIONAL_CALLBACKS
+static int  ChapPrintPkt (u_char *, int, void (*) (void *, char *, ...), void *);
+#endif
+
+struct protent chap_protent = {
+  PPP_CHAP,
+  ChapInit,
+  ChapInput,
+  ChapProtocolReject,
+  ChapLowerUp,
+  ChapLowerDown,
+  NULL,
+  NULL,
+#if PPP_ADDITIONAL_CALLBACKS
+  ChapPrintPkt,
+  NULL,
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+  1,
+  "CHAP",
+#if PPP_ADDITIONAL_CALLBACKS
+  NULL,
+  NULL,
+  NULL
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+};
+
+chap_state chap[NUM_PPP]; /* CHAP state; one for each unit */
+
+static void ChapChallengeTimeout (void *);
+static void ChapResponseTimeout (void *);
+static void ChapReceiveChallenge (chap_state *, u_char *, u_char, int);
+static void ChapRechallenge (void *);
+static void ChapReceiveResponse (chap_state *, u_char *, int, int);
+static void ChapReceiveSuccess(chap_state *cstate, u_char *inp, u_char id, int len);
+static void ChapReceiveFailure(chap_state *cstate, u_char *inp, u_char id, int len);
+static void ChapSendStatus (chap_state *, int);
+static void ChapSendChallenge (chap_state *);
+static void ChapSendResponse (chap_state *);
+static void ChapGenChallenge (chap_state *);
+
+/*
+ * ChapInit - Initialize a CHAP unit.
+ */
+static void
+ChapInit(int unit)
+{
+  chap_state *cstate = &chap[unit];
+
+  BZERO(cstate, sizeof(*cstate));
+  cstate->unit = unit;
+  cstate->clientstate = CHAPCS_INITIAL;
+  cstate->serverstate = CHAPSS_INITIAL;
+  cstate->timeouttime = CHAP_DEFTIMEOUT;
+  cstate->max_transmits = CHAP_DEFTRANSMITS;
+  /* random number generator is initialized in magic_init */
+}
+
+
+/*
+ * ChapAuthWithPeer - Authenticate us with our peer (start client).
+ *
+ */
+void
+ChapAuthWithPeer(int unit, char *our_name, u_char digest)
+{
+  chap_state *cstate = &chap[unit];
+
+  cstate->resp_name = our_name;
+  cstate->resp_type = digest;
+
+  if (cstate->clientstate == CHAPCS_INITIAL ||
+      cstate->clientstate == CHAPCS_PENDING) {
+    /* lower layer isn't up - wait until later */
+    cstate->clientstate = CHAPCS_PENDING;
+    return;
+  }
+
+  /*
+   * We get here as a result of LCP coming up.
+   * So even if CHAP was open before, we will 
+   * have to re-authenticate ourselves.
+   */
+  cstate->clientstate = CHAPCS_LISTEN;
+}
+
+
+/*
+ * ChapAuthPeer - Authenticate our peer (start server).
+ */
+void
+ChapAuthPeer(int unit, char *our_name, u_char digest)
+{
+  chap_state *cstate = &chap[unit];
+
+  cstate->chal_name = our_name;
+  cstate->chal_type = digest;
+  
+  if (cstate->serverstate == CHAPSS_INITIAL ||
+      cstate->serverstate == CHAPSS_PENDING) {
+    /* lower layer isn't up - wait until later */
+    cstate->serverstate = CHAPSS_PENDING;
+    return;
+  }
+
+  ChapGenChallenge(cstate);
+  ChapSendChallenge(cstate);    /* crank it up dude! */
+  cstate->serverstate = CHAPSS_INITIAL_CHAL;
+}
+
+
+/*
+ * ChapChallengeTimeout - Timeout expired on sending challenge.
+ */
+static void
+ChapChallengeTimeout(void *arg)
+{
+  chap_state *cstate = (chap_state *) arg;
+
+  /* if we aren't sending challenges, don't worry.  then again we */
+  /* probably shouldn't be here either */
+  if (cstate->serverstate != CHAPSS_INITIAL_CHAL &&
+      cstate->serverstate != CHAPSS_RECHALLENGE) {
+    return;
+  }
+
+  if (cstate->chal_transmits >= cstate->max_transmits) {
+    /* give up on peer */
+    CHAPDEBUG(LOG_ERR, ("Peer failed to respond to CHAP challenge\n"));
+    cstate->serverstate = CHAPSS_BADAUTH;
+    auth_peer_fail(cstate->unit, PPP_CHAP);
+    return;
+  }
+
+  ChapSendChallenge(cstate); /* Re-send challenge */
+}
+
+
+/*
+ * ChapResponseTimeout - Timeout expired on sending response.
+ */
+static void
+ChapResponseTimeout(void *arg)
+{
+  chap_state *cstate = (chap_state *) arg;
+
+  /* if we aren't sending a response, don't worry. */
+  if (cstate->clientstate != CHAPCS_RESPONSE) {
+    return;
+  }
+
+  ChapSendResponse(cstate);    /* re-send response */
+}
+
+
+/*
+ * ChapRechallenge - Time to challenge the peer again.
+ */
+static void
+ChapRechallenge(void *arg)
+{
+  chap_state *cstate = (chap_state *) arg;
+  
+  /* if we aren't sending a response, don't worry. */
+  if (cstate->serverstate != CHAPSS_OPEN) {
+    return;
+  }
+
+  ChapGenChallenge(cstate);
+  ChapSendChallenge(cstate);
+  cstate->serverstate = CHAPSS_RECHALLENGE;
+}
+
+
+/*
+ * ChapLowerUp - The lower layer is up.
+ *
+ * Start up if we have pending requests.
+ */
+static void
+ChapLowerUp(int unit)
+{
+  chap_state *cstate = &chap[unit];
+
+  if (cstate->clientstate == CHAPCS_INITIAL) {
+    cstate->clientstate = CHAPCS_CLOSED;
+  } else if (cstate->clientstate == CHAPCS_PENDING) {
+    cstate->clientstate = CHAPCS_LISTEN;
+  }
+
+  if (cstate->serverstate == CHAPSS_INITIAL) {
+    cstate->serverstate = CHAPSS_CLOSED;
+  } else if (cstate->serverstate == CHAPSS_PENDING) {
+    ChapGenChallenge(cstate);
+    ChapSendChallenge(cstate);
+    cstate->serverstate = CHAPSS_INITIAL_CHAL;
+  }
+}
+
+
+/*
+ * ChapLowerDown - The lower layer is down.
+ *
+ * Cancel all timeouts.
+ */
+static void
+ChapLowerDown(int unit)
+{
+  chap_state *cstate = &chap[unit];
+
+  /* Timeout(s) pending?  Cancel if so. */
+  if (cstate->serverstate == CHAPSS_INITIAL_CHAL ||
+      cstate->serverstate == CHAPSS_RECHALLENGE) {
+    UNTIMEOUT(ChapChallengeTimeout, cstate);
+  } else if (cstate->serverstate == CHAPSS_OPEN
+      && cstate->chal_interval != 0) {
+    UNTIMEOUT(ChapRechallenge, cstate);
+  }
+  if (cstate->clientstate == CHAPCS_RESPONSE) {
+    UNTIMEOUT(ChapResponseTimeout, cstate);
+  }
+  cstate->clientstate = CHAPCS_INITIAL;
+  cstate->serverstate = CHAPSS_INITIAL;
+}
+
+
+/*
+ * ChapProtocolReject - Peer doesn't grok CHAP.
+ */
+static void
+ChapProtocolReject(int unit)
+{
+  chap_state *cstate = &chap[unit];
+  
+  if (cstate->serverstate != CHAPSS_INITIAL &&
+      cstate->serverstate != CHAPSS_CLOSED) {
+    auth_peer_fail(unit, PPP_CHAP);
+  }
+  if (cstate->clientstate != CHAPCS_INITIAL &&
+      cstate->clientstate != CHAPCS_CLOSED) {
+    auth_withpeer_fail(unit, PPP_CHAP); /* lwip: just sets the PPP error code on this unit to PPPERR_AUTHFAIL */
+  }
+  ChapLowerDown(unit); /* shutdown chap */
+}
+
+
+/*
+ * ChapInput - Input CHAP packet.
+ */
+static void
+ChapInput(int unit, u_char *inpacket, int packet_len)
+{
+  chap_state *cstate = &chap[unit];
+  u_char *inp;
+  u_char code, id;
+  int len;
+  
+  /*
+   * Parse header (code, id and length).
+   * If packet too short, drop it.
+   */
+  inp = inpacket;
+  if (packet_len < CHAP_HEADERLEN) {
+    CHAPDEBUG(LOG_INFO, ("ChapInput: rcvd short header.\n"));
+    return;
+  }
+  GETCHAR(code, inp);
+  GETCHAR(id, inp);
+  GETSHORT(len, inp);
+  if (len < CHAP_HEADERLEN) {
+    CHAPDEBUG(LOG_INFO, ("ChapInput: rcvd illegal length.\n"));
+    return;
+  }
+  if (len > packet_len) {
+    CHAPDEBUG(LOG_INFO, ("ChapInput: rcvd short packet.\n"));
+    return;
+  }
+  len -= CHAP_HEADERLEN;
+  
+  /*
+   * Action depends on code (as in fact it usually does :-).
+   */
+  switch (code) {
+    case CHAP_CHALLENGE:
+      ChapReceiveChallenge(cstate, inp, id, len);
+      break;
+    
+    case CHAP_RESPONSE:
+      ChapReceiveResponse(cstate, inp, id, len);
+      break;
+    
+    case CHAP_FAILURE:
+      ChapReceiveFailure(cstate, inp, id, len);
+      break;
+    
+    case CHAP_SUCCESS:
+      ChapReceiveSuccess(cstate, inp, id, len);
+      break;
+    
+    default:        /* Need code reject? */
+      CHAPDEBUG(LOG_WARNING, ("Unknown CHAP code (%d) received.\n", code));
+      break;
+  }
+}
+
+
+/*
+ * ChapReceiveChallenge - Receive Challenge and send Response.
+ */
+static void
+ChapReceiveChallenge(chap_state *cstate, u_char *inp, u_char id, int len)
+{
+  int rchallenge_len;
+  u_char *rchallenge;
+  int secret_len;
+  char secret[MAXSECRETLEN];
+  char rhostname[256];
+  MD5_CTX mdContext;
+  u_char hash[MD5_SIGNATURE_SIZE];
+
+  CHAPDEBUG(LOG_INFO, ("ChapReceiveChallenge: Rcvd id %d.\n", id));
+  if (cstate->clientstate == CHAPCS_CLOSED ||
+    cstate->clientstate == CHAPCS_PENDING) {
+    CHAPDEBUG(LOG_INFO, ("ChapReceiveChallenge: in state %d\n",
+         cstate->clientstate));
+    return;
+  }
+
+  if (len < 2) {
+    CHAPDEBUG(LOG_INFO, ("ChapReceiveChallenge: rcvd short packet.\n"));
+    return;
+  }
+
+  GETCHAR(rchallenge_len, inp);
+  len -= sizeof (u_char) + rchallenge_len;  /* now name field length */
+  if (len < 0) {
+    CHAPDEBUG(LOG_INFO, ("ChapReceiveChallenge: rcvd short packet.\n"));
+    return;
+  }
+  rchallenge = inp;
+  INCPTR(rchallenge_len, inp);
+
+  if (len >= (int)sizeof(rhostname)) {
+    len = sizeof(rhostname) - 1;
+  }
+  BCOPY(inp, rhostname, len);
+  rhostname[len] = '\000';
+
+  CHAPDEBUG(LOG_INFO, ("ChapReceiveChallenge: received name field '%s'\n",
+             rhostname));
+
+  /* Microsoft doesn't send their name back in the PPP packet */
+  if (ppp_settings.remote_name[0] != 0 && (ppp_settings.explicit_remote || rhostname[0] == 0)) {
+    strncpy(rhostname, ppp_settings.remote_name, sizeof(rhostname));
+    rhostname[sizeof(rhostname) - 1] = 0;
+    CHAPDEBUG(LOG_INFO, ("ChapReceiveChallenge: using '%s' as remote name\n",
+               rhostname));
+  }
+
+  /* get secret for authenticating ourselves with the specified host */
+  if (!get_secret(cstate->unit, cstate->resp_name, rhostname,
+                  secret, &secret_len, 0)) {
+    secret_len = 0;    /* assume null secret if can't find one */
+    CHAPDEBUG(LOG_WARNING, ("No CHAP secret found for authenticating us to %s\n",
+               rhostname));
+  }
+
+  /* cancel response send timeout if necessary */
+  if (cstate->clientstate == CHAPCS_RESPONSE) {
+    UNTIMEOUT(ChapResponseTimeout, cstate);
+  }
+
+  cstate->resp_id = id;
+  cstate->resp_transmits = 0;
+
+  /*  generate MD based on negotiated type */
+  switch (cstate->resp_type) { 
+
+  case CHAP_DIGEST_MD5:
+    MD5Init(&mdContext);
+    MD5Update(&mdContext, &cstate->resp_id, 1);
+    MD5Update(&mdContext, (u_char*)secret, secret_len);
+    MD5Update(&mdContext, rchallenge, rchallenge_len);
+    MD5Final(hash, &mdContext);
+    BCOPY(hash, cstate->response, MD5_SIGNATURE_SIZE);
+    cstate->resp_length = MD5_SIGNATURE_SIZE;
+    break;
+  
+#if MSCHAP_SUPPORT
+  case CHAP_MICROSOFT:
+    ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len);
+    break;
+#endif
+
+  default:
+    CHAPDEBUG(LOG_INFO, ("unknown digest type %d\n", cstate->resp_type));
+    return;
+  }
+
+  BZERO(secret, sizeof(secret));
+  ChapSendResponse(cstate);
+}
+
+
+/*
+ * ChapReceiveResponse - Receive and process response.
+ */
+static void
+ChapReceiveResponse(chap_state *cstate, u_char *inp, int id, int len)
+{
+  u_char *remmd, remmd_len;
+  int secret_len, old_state;
+  int code;
+  char rhostname[256];
+  MD5_CTX mdContext;
+  char secret[MAXSECRETLEN];
+  u_char hash[MD5_SIGNATURE_SIZE];
+
+  CHAPDEBUG(LOG_INFO, ("ChapReceiveResponse: Rcvd id %d.\n", id));
+  
+  if (cstate->serverstate == CHAPSS_CLOSED ||
+      cstate->serverstate == CHAPSS_PENDING) {
+    CHAPDEBUG(LOG_INFO, ("ChapReceiveResponse: in state %d\n",
+    cstate->serverstate));
+    return;
+  }
+
+  if (id != cstate->chal_id) {
+    return;      /* doesn't match ID of last challenge */
+  }
+
+  /*
+  * If we have received a duplicate or bogus Response,
+  * we have to send the same answer (Success/Failure)
+  * as we did for the first Response we saw.
+  */
+  if (cstate->serverstate == CHAPSS_OPEN) {
+    ChapSendStatus(cstate, CHAP_SUCCESS);
+    return;
+  }
+  if (cstate->serverstate == CHAPSS_BADAUTH) {
+    ChapSendStatus(cstate, CHAP_FAILURE);
+    return;
+  }
+  
+  if (len < 2) {
+    CHAPDEBUG(LOG_INFO, ("ChapReceiveResponse: rcvd short packet.\n"));
+    return;
+  }
+  GETCHAR(remmd_len, inp); /* get length of MD */
+  remmd = inp;             /* get pointer to MD */
+  INCPTR(remmd_len, inp);
+  
+  len -= sizeof (u_char) + remmd_len;
+  if (len < 0) {
+    CHAPDEBUG(LOG_INFO, ("ChapReceiveResponse: rcvd short packet.\n"));
+    return;
+  }
+
+  UNTIMEOUT(ChapChallengeTimeout, cstate);
+  
+  if (len >= (int)sizeof(rhostname)) {
+    len = sizeof(rhostname) - 1;
+  }
+  BCOPY(inp, rhostname, len);
+  rhostname[len] = '\000';
+
+  CHAPDEBUG(LOG_INFO, ("ChapReceiveResponse: received name field: %s\n",
+             rhostname));
+
+  /*
+  * Get secret for authenticating them with us,
+  * do the hash ourselves, and compare the result.
+  */
+  code = CHAP_FAILURE;
+  if (!get_secret(cstate->unit, rhostname, cstate->chal_name,
+                  secret, &secret_len, 1)) {
+    CHAPDEBUG(LOG_WARNING, ("No CHAP secret found for authenticating %s\n",
+               rhostname));
+  } else {
+    /*  generate MD based on negotiated type */
+    switch (cstate->chal_type) {
+
+      case CHAP_DIGEST_MD5:    /* only MD5 is defined for now */
+        if (remmd_len != MD5_SIGNATURE_SIZE) {
+          break;      /* it's not even the right length */
+        }
+        MD5Init(&mdContext);
+        MD5Update(&mdContext, &cstate->chal_id, 1);
+        MD5Update(&mdContext, (u_char*)secret, secret_len);
+        MD5Update(&mdContext, cstate->challenge, cstate->chal_len);
+        MD5Final(hash, &mdContext); 
+        
+        /* compare local and remote MDs and send the appropriate status */
+        if (memcmp (hash, remmd, MD5_SIGNATURE_SIZE) == 0) {
+          code = CHAP_SUCCESS;  /* they are the same! */
+        }
+        break;
+      
+      default:
+        CHAPDEBUG(LOG_INFO, ("unknown digest type %d\n", cstate->chal_type));
+    }
+  }
+  
+  BZERO(secret, sizeof(secret));
+  ChapSendStatus(cstate, code);
+
+  if (code == CHAP_SUCCESS) {
+    old_state = cstate->serverstate;
+    cstate->serverstate = CHAPSS_OPEN;
+    if (old_state == CHAPSS_INITIAL_CHAL) {
+      auth_peer_success(cstate->unit, PPP_CHAP, rhostname, len);
+    }
+    if (cstate->chal_interval != 0) {
+      TIMEOUT(ChapRechallenge, cstate, cstate->chal_interval);
+    }
+  } else {
+    CHAPDEBUG(LOG_ERR, ("CHAP peer authentication failed\n"));
+    cstate->serverstate = CHAPSS_BADAUTH;
+    auth_peer_fail(cstate->unit, PPP_CHAP);
+  }
+}
+
+/*
+ * ChapReceiveSuccess - Receive Success
+ */
+static void
+ChapReceiveSuccess(chap_state *cstate, u_char *inp, u_char id, int len)
+{
+  LWIP_UNUSED_ARG(id);
+  LWIP_UNUSED_ARG(inp);
+
+  CHAPDEBUG(LOG_INFO, ("ChapReceiveSuccess: Rcvd id %d.\n", id));
+
+  if (cstate->clientstate == CHAPCS_OPEN) {
+    /* presumably an answer to a duplicate response */
+    return;
+  }
+
+  if (cstate->clientstate != CHAPCS_RESPONSE) {
+    /* don't know what this is */
+    CHAPDEBUG(LOG_INFO, ("ChapReceiveSuccess: in state %d\n",
+               cstate->clientstate));
+    return;
+  }
+  
+  UNTIMEOUT(ChapResponseTimeout, cstate);
+  
+  /*
+   * Print message.
+   */
+  if (len > 0) {
+    PRINTMSG(inp, len);
+  }
+
+  cstate->clientstate = CHAPCS_OPEN;
+
+  auth_withpeer_success(cstate->unit, PPP_CHAP);
+}
+
+
+/*
+ * ChapReceiveFailure - Receive failure.
+ */
+static void
+ChapReceiveFailure(chap_state *cstate, u_char *inp, u_char id, int len)
+{
+  LWIP_UNUSED_ARG(id);
+  LWIP_UNUSED_ARG(inp);
+
+  CHAPDEBUG(LOG_INFO, ("ChapReceiveFailure: Rcvd id %d.\n", id));
+
+  if (cstate->clientstate != CHAPCS_RESPONSE) {
+    /* don't know what this is */
+    CHAPDEBUG(LOG_INFO, ("ChapReceiveFailure: in state %d\n",
+               cstate->clientstate));
+    return;
+  }
+
+  UNTIMEOUT(ChapResponseTimeout, cstate);
+
+  /*
+   * Print message.
+   */
+  if (len > 0) {
+    PRINTMSG(inp, len);
+  }
+
+  CHAPDEBUG(LOG_ERR, ("CHAP authentication failed\n"));
+  auth_withpeer_fail(cstate->unit, PPP_CHAP); /* lwip: just sets the PPP error code on this unit to PPPERR_AUTHFAIL */
+}
+
+
+/*
+ * ChapSendChallenge - Send an Authenticate challenge.
+ */
+static void
+ChapSendChallenge(chap_state *cstate)
+{
+  u_char *outp;
+  int chal_len, name_len;
+  int outlen;
+  
+  chal_len = cstate->chal_len;
+  name_len = (int)strlen(cstate->chal_name);
+  outlen = CHAP_HEADERLEN + sizeof (u_char) + chal_len + name_len;
+  outp = outpacket_buf[cstate->unit];
+
+  MAKEHEADER(outp, PPP_CHAP);    /* paste in a CHAP header */
+
+  PUTCHAR(CHAP_CHALLENGE, outp);
+  PUTCHAR(cstate->chal_id, outp);
+  PUTSHORT(outlen, outp);
+
+  PUTCHAR(chal_len, outp);    /* put length of challenge */
+  BCOPY(cstate->challenge, outp, chal_len);
+  INCPTR(chal_len, outp);
+
+  BCOPY(cstate->chal_name, outp, name_len);  /* append hostname */
+  
+  pppWrite(cstate->unit, outpacket_buf[cstate->unit], outlen + PPP_HDRLEN);
+
+  CHAPDEBUG(LOG_INFO, ("ChapSendChallenge: Sent id %d.\n", cstate->chal_id));
+  
+  TIMEOUT(ChapChallengeTimeout, cstate, cstate->timeouttime);
+  ++cstate->chal_transmits;
+}
+
+
+/*
+ * ChapSendStatus - Send a status response (ack or nak).
+ */
+static void
+ChapSendStatus(chap_state *cstate, int code)
+{
+  u_char *outp;
+  int outlen, msglen;
+  char msg[256]; /* @todo: this can be a char*, no strcpy needed */
+
+  if (code == CHAP_SUCCESS) {
+    strcpy(msg, "Welcome!");
+  } else {
+    strcpy(msg, "I don't like you.  Go 'way.");
+  }
+  msglen = (int)strlen(msg);
+
+  outlen = CHAP_HEADERLEN + msglen;
+  outp = outpacket_buf[cstate->unit];
+
+  MAKEHEADER(outp, PPP_CHAP);    /* paste in a header */
+  
+  PUTCHAR(code, outp);
+  PUTCHAR(cstate->chal_id, outp);
+  PUTSHORT(outlen, outp);
+  BCOPY(msg, outp, msglen);
+  pppWrite(cstate->unit, outpacket_buf[cstate->unit], outlen + PPP_HDRLEN);
+
+  CHAPDEBUG(LOG_INFO, ("ChapSendStatus: Sent code %d, id %d.\n", code,
+             cstate->chal_id));
+}
+
+/*
+ * ChapGenChallenge is used to generate a pseudo-random challenge string of
+ * a pseudo-random length between min_len and max_len.  The challenge
+ * string and its length are stored in *cstate, and various other fields of
+ * *cstate are initialized.
+ */
+
+static void
+ChapGenChallenge(chap_state *cstate)
+{
+  int chal_len;
+  u_char *ptr = cstate->challenge;
+  int i;
+
+  /* pick a random challenge length between MIN_CHALLENGE_LENGTH and 
+     MAX_CHALLENGE_LENGTH */  
+  chal_len = (unsigned)
+        ((((magic() >> 16) *
+              (MAX_CHALLENGE_LENGTH - MIN_CHALLENGE_LENGTH)) >> 16)
+           + MIN_CHALLENGE_LENGTH);
+  LWIP_ASSERT("chal_len <= 0xff", chal_len <= 0xffff);
+  cstate->chal_len = (u_char)chal_len;
+  cstate->chal_id = ++cstate->id;
+  cstate->chal_transmits = 0;
+
+  /* generate a random string */
+  for (i = 0; i < chal_len; i++ ) {
+    *ptr++ = (char) (magic() & 0xff);
+  }
+}
+
+/*
+ * ChapSendResponse - send a response packet with values as specified
+ * in *cstate.
+ */
+/* ARGSUSED */
+static void
+ChapSendResponse(chap_state *cstate)
+{
+  u_char *outp;
+  int outlen, md_len, name_len;
+
+  md_len = cstate->resp_length;
+  name_len = (int)strlen(cstate->resp_name);
+  outlen = CHAP_HEADERLEN + sizeof (u_char) + md_len + name_len;
+  outp = outpacket_buf[cstate->unit];
+
+  MAKEHEADER(outp, PPP_CHAP);
+  
+  PUTCHAR(CHAP_RESPONSE, outp);  /* we are a response */
+  PUTCHAR(cstate->resp_id, outp);  /* copy id from challenge packet */
+  PUTSHORT(outlen, outp);      /* packet length */
+  
+  PUTCHAR(md_len, outp);      /* length of MD */
+  BCOPY(cstate->response, outp, md_len);    /* copy MD to buffer */
+  INCPTR(md_len, outp);
+
+  BCOPY(cstate->resp_name, outp, name_len);  /* append our name */
+
+  /* send the packet */
+  pppWrite(cstate->unit, outpacket_buf[cstate->unit], outlen + PPP_HDRLEN);
+
+  cstate->clientstate = CHAPCS_RESPONSE;
+  TIMEOUT(ChapResponseTimeout, cstate, cstate->timeouttime);
+  ++cstate->resp_transmits;
+}
+
+#if PPP_ADDITIONAL_CALLBACKS
+static char *ChapCodenames[] = {
+  "Challenge", "Response", "Success", "Failure"
+};
+/*
+ * ChapPrintPkt - print the contents of a CHAP packet.
+ */
+static int
+ChapPrintPkt( u_char *p, int plen, void (*printer) (void *, char *, ...), void *arg)
+{
+  int code, id, len;
+  int clen, nlen;
+  u_char x;
+
+  if (plen < CHAP_HEADERLEN) {
+    return 0;
+  }
+  GETCHAR(code, p);
+  GETCHAR(id, p);
+  GETSHORT(len, p);
+  if (len < CHAP_HEADERLEN || len > plen) {
+    return 0;
+  }
+
+  if (code >= 1 && code <= sizeof(ChapCodenames) / sizeof(char *)) {
+    printer(arg, " %s", ChapCodenames[code-1]);
+  } else {
+    printer(arg, " code=0x%x", code);
+  }
+  printer(arg, " id=0x%x", id);
+  len -= CHAP_HEADERLEN;
+  switch (code) {
+    case CHAP_CHALLENGE:
+    case CHAP_RESPONSE:
+      if (len < 1) {
+        break;
+      }
+      clen = p[0];
+      if (len < clen + 1) {
+        break;
+      }
+      ++p;
+      nlen = len - clen - 1;
+      printer(arg, " <");
+      for (; clen > 0; --clen) {
+        GETCHAR(x, p);
+        printer(arg, "%.2x", x);
+      }
+      printer(arg, ">, name = %.*Z", nlen, p);
+      break;
+    case CHAP_FAILURE:
+    case CHAP_SUCCESS:
+      printer(arg, " %.*Z", len, p);
+      break;
+    default:
+      for (clen = len; clen > 0; --clen) {
+        GETCHAR(x, p);
+        printer(arg, " %.2x", x);
+      }
+  }
+
+  return len + CHAP_HEADERLEN;
+}
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+
+#endif /* CHAP_SUPPORT */
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/chap.h b/external/badvpn_dns/lwip/src/netif/ppp/chap.h
new file mode 100644
index 0000000..fedcab8
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/chap.h
@@ -0,0 +1,150 @@
+/*****************************************************************************
+* chap.h - Network Challenge Handshake Authentication Protocol header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1998 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-03 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original built from BSD network code.
+******************************************************************************/
+/*
+ * chap.h - Challenge Handshake Authentication Protocol definitions.
+ *
+ * Copyright (c) 1993 The Australian National University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the Australian National University.  The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Copyright (c) 1991 Gregory M. Christy
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the author.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $Id: chap.h,v 1.6 2010/01/24 13:19:34 goldsimon Exp $
+ */
+
+#ifndef CHAP_H
+#define CHAP_H
+
+/* Code + ID + length */
+#define CHAP_HEADERLEN 4
+
+/*
+ * CHAP codes.
+ */
+
+#define CHAP_DIGEST_MD5      5    /* use MD5 algorithm */
+#define MD5_SIGNATURE_SIZE   16   /* 16 bytes in a MD5 message digest */
+#define CHAP_MICROSOFT       0x80 /* use Microsoft-compatible alg. */
+#define MS_CHAP_RESPONSE_LEN 49   /* Response length for MS-CHAP */
+
+#define CHAP_CHALLENGE       1
+#define CHAP_RESPONSE        2
+#define CHAP_SUCCESS         3
+#define CHAP_FAILURE         4
+
+/*
+ *  Challenge lengths (for challenges we send) and other limits.
+ */
+#define MIN_CHALLENGE_LENGTH 32
+#define MAX_CHALLENGE_LENGTH 64
+#define MAX_RESPONSE_LENGTH  64 /* sufficient for MD5 or MS-CHAP */
+
+/*
+ * Each interface is described by a chap structure.
+ */
+
+typedef struct chap_state {
+  int unit;                               /* Interface unit number */
+  int clientstate;                        /* Client state */
+  int serverstate;                        /* Server state */
+  u_char challenge[MAX_CHALLENGE_LENGTH]; /* last challenge string sent */
+  u_char chal_len;                        /* challenge length */
+  u_char chal_id;                         /* ID of last challenge */
+  u_char chal_type;                       /* hash algorithm for challenges */
+  u_char id;                              /* Current id */
+  char *chal_name;                        /* Our name to use with challenge */
+  int chal_interval;                      /* Time until we challenge peer again */
+  int timeouttime;                        /* Timeout time in seconds */
+  int max_transmits;                      /* Maximum # of challenge transmissions */
+  int chal_transmits;                     /* Number of transmissions of challenge */
+  int resp_transmits;                     /* Number of transmissions of response */
+  u_char response[MAX_RESPONSE_LENGTH];   /* Response to send */
+  u_char resp_length;                     /* length of response */
+  u_char resp_id;                         /* ID for response messages */
+  u_char resp_type;                       /* hash algorithm for responses */
+  char *resp_name;                        /* Our name to send with response */
+} chap_state;
+
+
+/*
+ * Client (peer) states.
+ */
+#define CHAPCS_INITIAL       0 /* Lower layer down, not opened */
+#define CHAPCS_CLOSED        1 /* Lower layer up, not opened */
+#define CHAPCS_PENDING       2 /* Auth us to peer when lower up */
+#define CHAPCS_LISTEN        3 /* Listening for a challenge */
+#define CHAPCS_RESPONSE      4 /* Sent response, waiting for status */
+#define CHAPCS_OPEN          5 /* We've received Success */
+
+/*
+ * Server (authenticator) states.
+ */
+#define CHAPSS_INITIAL       0 /* Lower layer down, not opened */
+#define CHAPSS_CLOSED        1 /* Lower layer up, not opened */
+#define CHAPSS_PENDING       2 /* Auth peer when lower up */
+#define CHAPSS_INITIAL_CHAL  3 /* We've sent the first challenge */
+#define CHAPSS_OPEN          4 /* We've sent a Success msg */
+#define CHAPSS_RECHALLENGE   5 /* We've sent another challenge */
+#define CHAPSS_BADAUTH       6 /* We've sent a Failure msg */
+
+extern chap_state chap[];
+
+void ChapAuthWithPeer (int, char *, u_char);
+void ChapAuthPeer (int, char *, u_char);
+
+extern struct protent chap_protent;
+
+#endif /* CHAP_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/chpms.c b/external/badvpn_dns/lwip/src/netif/ppp/chpms.c
new file mode 100644
index 0000000..81a887b
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/chpms.c
@@ -0,0 +1,396 @@
+/*** WARNING - THIS CODE HAS NOT BEEN FINISHED! ***/
+/*** The original PPPD code is written in a way to require either the UNIX DES
+     encryption functions encrypt(3) and setkey(3) or the DES library libdes.
+     Since both is not included in lwIP, MSCHAP currently does not work! */
+/*****************************************************************************
+* chpms.c - Network MicroSoft Challenge Handshake Authentication Protocol program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* Copyright (c) 1997 by Global Election Systems Inc.  All rights reserved.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-08 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original based on BSD chap_ms.c.
+*****************************************************************************/
+/*
+ * chap_ms.c - Microsoft MS-CHAP compatible implementation.
+ *
+ * Copyright (c) 1995 Eric Rosenquist, Strata Software Limited.
+ * http://www.strataware.com/
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Eric Rosenquist.  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 ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/*
+ * Modifications by Lauri Pesonen / lpesonen@xxxxxxxxx, april 1997
+ *
+ *   Implemented LANManager type password response to MS-CHAP challenges.
+ *   Now pppd provides both NT style and LANMan style blocks, and the
+ *   prefered is set by option "ms-lanman". Default is to use NT.
+ *   The hash text (StdText) was taken from Win95 RASAPI32.DLL.
+ *
+ *   You should also use DOMAIN\\USERNAME as described in README.MSCHAP80
+ */
+
+#define USE_CRYPT
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#if MSCHAP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "md4.h"
+#ifndef USE_CRYPT
+#include "des.h"
+#endif
+#include "chap.h"
+#include "chpms.h"
+
+#include <string.h>
+
+
+/*************************/
+/*** LOCAL DEFINITIONS ***/
+/*************************/
+
+
+/************************/
+/*** LOCAL DATA TYPES ***/
+/************************/
+typedef struct {
+    u_char LANManResp[24];
+    u_char NTResp[24];
+    u_char UseNT; /* If 1, ignore the LANMan response field */
+} MS_ChapResponse;
+/* We use MS_CHAP_RESPONSE_LEN, rather than sizeof(MS_ChapResponse),
+   in case this struct gets padded. */
+
+
+
+/***********************************/
+/*** LOCAL FUNCTION DECLARATIONS ***/
+/***********************************/
+
+/* XXX Don't know what to do with these. */
+extern void setkey(const char *);
+extern void encrypt(char *, int);
+
+static void DesEncrypt (u_char *, u_char *, u_char *);
+static void MakeKey (u_char *, u_char *);
+
+#ifdef USE_CRYPT
+static void Expand (u_char *, u_char *);
+static void Collapse (u_char *, u_char *);
+#endif
+
+static void ChallengeResponse(
+  u_char *challenge, /* IN   8 octets */
+  u_char *pwHash,    /* IN  16 octets */
+  u_char *response   /* OUT 24 octets */
+);
+static void ChapMS_NT(
+  char *rchallenge,
+  int rchallenge_len,
+  char *secret,
+  int secret_len,
+  MS_ChapResponse *response
+);
+static u_char Get7Bits(
+  u_char *input,
+  int startBit
+);
+
+static void
+ChallengeResponse( u_char *challenge, /* IN   8 octets */
+                   u_char *pwHash,    /* IN  16 octets */
+                   u_char *response   /* OUT 24 octets */)
+{
+  u_char    ZPasswordHash[21];
+
+  BZERO(ZPasswordHash, sizeof(ZPasswordHash));
+  BCOPY(pwHash, ZPasswordHash, 16);
+
+#if 0
+  log_packet(ZPasswordHash, sizeof(ZPasswordHash), "ChallengeResponse - ZPasswordHash", LOG_DEBUG);
+#endif
+
+  DesEncrypt(challenge, ZPasswordHash +  0, response + 0);
+  DesEncrypt(challenge, ZPasswordHash +  7, response + 8);
+  DesEncrypt(challenge, ZPasswordHash + 14, response + 16);
+
+#if 0
+  log_packet(response, 24, "ChallengeResponse - response", LOG_DEBUG);
+#endif
+}
+
+
+#ifdef USE_CRYPT
+static void
+DesEncrypt( u_char *clear, /* IN  8 octets */
+            u_char *key,   /* IN  7 octets */
+            u_char *cipher /* OUT 8 octets */)
+{
+  u_char des_key[8];
+  u_char crypt_key[66];
+  u_char des_input[66];
+
+  MakeKey(key, des_key);
+
+  Expand(des_key, crypt_key);
+  setkey((char*)crypt_key);
+
+#if 0
+  CHAPDEBUG(LOG_INFO, ("DesEncrypt: 8 octet input : %02X%02X%02X%02X%02X%02X%02X%02X\n",
+             clear[0], clear[1], clear[2], clear[3], clear[4], clear[5], clear[6], clear[7]));
+#endif
+
+  Expand(clear, des_input);
+  encrypt((char*)des_input, 0);
+  Collapse(des_input, cipher);
+
+#if 0
+  CHAPDEBUG(LOG_INFO, ("DesEncrypt: 8 octet output: %02X%02X%02X%02X%02X%02X%02X%02X\n",
+             cipher[0], cipher[1], cipher[2], cipher[3], cipher[4], cipher[5], cipher[6], cipher[7]));
+#endif
+}
+
+#else /* USE_CRYPT */
+
+static void
+DesEncrypt( u_char *clear, /* IN  8 octets */
+            u_char *key,   /* IN  7 octets */
+            u_char *cipher /* OUT 8 octets */)
+{
+  des_cblock    des_key;
+  des_key_schedule  key_schedule;
+
+  MakeKey(key, des_key);
+
+  des_set_key(&des_key, key_schedule);
+
+#if 0
+  CHAPDEBUG(LOG_INFO, ("DesEncrypt: 8 octet input : %02X%02X%02X%02X%02X%02X%02X%02X\n",
+             clear[0], clear[1], clear[2], clear[3], clear[4], clear[5], clear[6], clear[7]));
+#endif
+
+  des_ecb_encrypt((des_cblock *)clear, (des_cblock *)cipher, key_schedule, 1);
+
+#if 0
+  CHAPDEBUG(LOG_INFO, ("DesEncrypt: 8 octet output: %02X%02X%02X%02X%02X%02X%02X%02X\n",
+             cipher[0], cipher[1], cipher[2], cipher[3], cipher[4], cipher[5], cipher[6], cipher[7]));
+#endif
+}
+
+#endif /* USE_CRYPT */
+
+
+static u_char
+Get7Bits( u_char *input, int startBit)
+{
+  register unsigned int  word;
+
+  word  = (unsigned)input[startBit / 8] << 8;
+  word |= (unsigned)input[startBit / 8 + 1];
+
+  word >>= 15 - (startBit % 8 + 7);
+
+  return word & 0xFE;
+}
+
+#ifdef USE_CRYPT
+
+/* in == 8-byte string (expanded version of the 56-bit key)
+ * out == 64-byte string where each byte is either 1 or 0
+ * Note that the low-order "bit" is always ignored by by setkey()
+ */
+static void
+Expand(u_char *in, u_char *out)
+{
+  int j, c;
+  int i;
+
+  for(i = 0; i < 64; in++){
+    c = *in;
+    for(j = 7; j >= 0; j--) {
+      *out++ = (c >> j) & 01;
+    }
+    i += 8;
+  }
+}
+
+/* The inverse of Expand
+ */
+static void
+Collapse(u_char *in, u_char *out)
+{
+  int j;
+  int i;
+  unsigned int c;
+
+  for (i = 0; i < 64; i += 8, out++) {
+    c = 0;
+    for (j = 7; j >= 0; j--, in++) {
+      c |= *in << j;
+    }
+    *out = c & 0xff;
+  }
+}
+#endif
+
+static void
+MakeKey( u_char *key,    /* IN  56 bit DES key missing parity bits */
+         u_char *des_key /* OUT 64 bit DES key with parity bits added */)
+{
+  des_key[0] = Get7Bits(key,  0);
+  des_key[1] = Get7Bits(key,  7);
+  des_key[2] = Get7Bits(key, 14);
+  des_key[3] = Get7Bits(key, 21);
+  des_key[4] = Get7Bits(key, 28);
+  des_key[5] = Get7Bits(key, 35);
+  des_key[6] = Get7Bits(key, 42);
+  des_key[7] = Get7Bits(key, 49);
+  
+#ifndef USE_CRYPT
+  des_set_odd_parity((des_cblock *)des_key);
+#endif
+  
+#if 0
+  CHAPDEBUG(LOG_INFO, ("MakeKey: 56-bit input : %02X%02X%02X%02X%02X%02X%02X\n",
+             key[0], key[1], key[2], key[3], key[4], key[5], key[6]));
+  CHAPDEBUG(LOG_INFO, ("MakeKey: 64-bit output: %02X%02X%02X%02X%02X%02X%02X%02X\n",
+             des_key[0], des_key[1], des_key[2], des_key[3], des_key[4], des_key[5], des_key[6], des_key[7]));
+#endif
+}
+
+static void
+ChapMS_NT( char *rchallenge,
+           int rchallenge_len,
+           char *secret,
+           int secret_len,
+           MS_ChapResponse *response)
+{
+  int      i;
+  MDstruct  md4Context;
+  u_char    unicodePassword[MAX_NT_PASSWORD * 2];
+  static int  low_byte_first = -1;
+
+  LWIP_UNUSED_ARG(rchallenge_len);
+
+  /* Initialize the Unicode version of the secret (== password). */
+  /* This implicitly supports 8-bit ISO8859/1 characters. */
+  BZERO(unicodePassword, sizeof(unicodePassword));
+  for (i = 0; i < secret_len; i++) {
+    unicodePassword[i * 2] = (u_char)secret[i];
+  }
+  MDbegin(&md4Context);
+  MDupdate(&md4Context, unicodePassword, secret_len * 2 * 8);  /* Unicode is 2 bytes/char, *8 for bit count */
+
+  if (low_byte_first == -1) {
+    low_byte_first = (PP_HTONS((unsigned short int)1) != 1);
+  }
+  if (low_byte_first == 0) {
+    /* @todo: arg type - u_long* or u_int* ? */
+    MDreverse((unsigned int*)&md4Context);  /*  sfb 961105 */
+  }
+
+  MDupdate(&md4Context, NULL, 0);  /* Tell MD4 we're done */
+
+  ChallengeResponse((u_char*)rchallenge, (u_char*)md4Context.buffer, response->NTResp);
+}
+
+#ifdef MSLANMAN
+static u_char *StdText = (u_char *)"KGS!@#$%"; /* key from rasapi32.dll */
+
+static void
+ChapMS_LANMan( char *rchallenge,
+               int rchallenge_len,
+               char *secret,
+               int secret_len,
+               MS_ChapResponse  *response)
+{
+  int      i;
+  u_char    UcasePassword[MAX_NT_PASSWORD]; /* max is actually 14 */
+  u_char    PasswordHash[16];
+  
+  /* LANMan password is case insensitive */
+  BZERO(UcasePassword, sizeof(UcasePassword));
+  for (i = 0; i < secret_len; i++) {
+    UcasePassword[i] = (u_char)toupper(secret[i]);
+  }
+  DesEncrypt( StdText, UcasePassword + 0, PasswordHash + 0 );
+  DesEncrypt( StdText, UcasePassword + 7, PasswordHash + 8 );
+  ChallengeResponse(rchallenge, PasswordHash, response->LANManResp);
+}
+#endif
+
+void
+ChapMS( chap_state *cstate, char *rchallenge, int rchallenge_len, char *secret, int secret_len)
+{
+  MS_ChapResponse response;
+#ifdef MSLANMAN
+  extern int ms_lanman;
+#endif
+
+#if 0
+  CHAPDEBUG(LOG_INFO, ("ChapMS: secret is '%.*s'\n", secret_len, secret));
+#endif
+  BZERO(&response, sizeof(response));
+
+  /* Calculate both always */
+  ChapMS_NT(rchallenge, rchallenge_len, secret, secret_len, &response);
+
+#ifdef MSLANMAN
+  ChapMS_LANMan(rchallenge, rchallenge_len, secret, secret_len, &response);
+
+  /* prefered method is set by option  */
+  response.UseNT = !ms_lanman;
+#else
+  response.UseNT = 1;
+#endif
+
+  BCOPY(&response, cstate->response, MS_CHAP_RESPONSE_LEN);
+  cstate->resp_length = MS_CHAP_RESPONSE_LEN;
+}
+
+#endif /* MSCHAP_SUPPORT */
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/chpms.h b/external/badvpn_dns/lwip/src/netif/ppp/chpms.h
new file mode 100644
index 0000000..df070fb
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/chpms.h
@@ -0,0 +1,64 @@
+/*****************************************************************************
+* chpms.h - Network Microsoft Challenge Handshake Protocol header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1998 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 98-01-30 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original built from BSD network code.
+******************************************************************************/
+/*
+ * chap.h - Challenge Handshake Authentication Protocol definitions.
+ *
+ * Copyright (c) 1995 Eric Rosenquist, Strata Software Limited.
+ * http://www.strataware.com/
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Eric Rosenquist.  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 ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $Id: chpms.h,v 1.5 2007/12/19 20:47:23 fbernon Exp $
+ */
+
+#ifndef CHPMS_H
+#define CHPMS_H
+
+#define MAX_NT_PASSWORD 256 /* Maximum number of (Unicode) chars in an NT password */
+
+void ChapMS (chap_state *, char *, int, char *, int);
+
+#endif /* CHPMS_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/fsm.c b/external/badvpn_dns/lwip/src/netif/ppp/fsm.c
new file mode 100644
index 0000000..e8a254e
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/fsm.c
@@ -0,0 +1,890 @@
+/*****************************************************************************
+* fsm.c - Network Control Protocol Finite State Machine program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 by Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-01 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original based on BSD fsm.c.
+*****************************************************************************/
+/*
+ * fsm.c - {Link, IP} Control Protocol Finite State Machine.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/*
+ * TODO:
+ * Randomize fsm id on link/init.
+ * Deal with variable outgoing MTU.
+ */
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "fsm.h"
+
+#include <string.h>
+
+#if PPP_DEBUG
+static const char *ppperr_strerr[] = {
+           "LS_INITIAL",  /* LS_INITIAL  0 */
+           "LS_STARTING", /* LS_STARTING 1 */
+           "LS_CLOSED",   /* LS_CLOSED   2 */
+           "LS_STOPPED",  /* LS_STOPPED  3 */
+           "LS_CLOSING",  /* LS_CLOSING  4 */
+           "LS_STOPPING", /* LS_STOPPING 5 */
+           "LS_REQSENT",  /* LS_REQSENT  6 */
+           "LS_ACKRCVD",  /* LS_ACKRCVD  7 */
+           "LS_ACKSENT",  /* LS_ACKSENT  8 */
+           "LS_OPENED"    /* LS_OPENED   9 */
+};
+#endif /* PPP_DEBUG */
+
+static void fsm_timeout (void *);
+static void fsm_rconfreq (fsm *, u_char, u_char *, int);
+static void fsm_rconfack (fsm *, int, u_char *, int);
+static void fsm_rconfnakrej (fsm *, int, int, u_char *, int);
+static void fsm_rtermreq (fsm *, int, u_char *, int);
+static void fsm_rtermack (fsm *);
+static void fsm_rcoderej (fsm *, u_char *, int);
+static void fsm_sconfreq (fsm *, int);
+
+#define PROTO_NAME(f) ((f)->callbacks->proto_name)
+
+int peer_mru[NUM_PPP];
+
+
+/*
+ * fsm_init - Initialize fsm.
+ *
+ * Initialize fsm state.
+ */
+void
+fsm_init(fsm *f)
+{
+  f->state = LS_INITIAL;
+  f->flags = 0;
+  f->id = 0;        /* XXX Start with random id? */
+  f->timeouttime = FSM_DEFTIMEOUT;
+  f->maxconfreqtransmits = FSM_DEFMAXCONFREQS;
+  f->maxtermtransmits = FSM_DEFMAXTERMREQS;
+  f->maxnakloops = FSM_DEFMAXNAKLOOPS;
+  f->term_reason_len = 0;
+}
+
+
+/*
+ * fsm_lowerup - The lower layer is up.
+ */
+void
+fsm_lowerup(fsm *f)
+{
+  int oldState = f->state;
+
+  LWIP_UNUSED_ARG(oldState);
+
+  switch( f->state ) {
+    case LS_INITIAL:
+      f->state = LS_CLOSED;
+      break;
+
+    case LS_STARTING:
+      if( f->flags & OPT_SILENT ) {
+        f->state = LS_STOPPED;
+      } else {
+        /* Send an initial configure-request */
+        fsm_sconfreq(f, 0);
+        f->state = LS_REQSENT;
+      }
+    break;
+
+    default:
+      FSMDEBUG(LOG_INFO, ("%s: Up event in state %d (%s)!\n",
+          PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+  }
+
+  FSMDEBUG(LOG_INFO, ("%s: lowerup state %d (%s) -> %d (%s)\n",
+      PROTO_NAME(f), oldState, ppperr_strerr[oldState], f->state, ppperr_strerr[f->state]));
+}
+
+
+/*
+ * fsm_lowerdown - The lower layer is down.
+ *
+ * Cancel all timeouts and inform upper layers.
+ */
+void
+fsm_lowerdown(fsm *f)
+{
+  int oldState = f->state;
+
+  LWIP_UNUSED_ARG(oldState);
+
+  switch( f->state ) {
+    case LS_CLOSED:
+      f->state = LS_INITIAL;
+      break;
+
+    case LS_STOPPED:
+      f->state = LS_STARTING;
+      if( f->callbacks->starting ) {
+        (*f->callbacks->starting)(f);
+      }
+      break;
+
+    case LS_CLOSING:
+      f->state = LS_INITIAL;
+      UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      break;
+
+    case LS_STOPPING:
+    case LS_REQSENT:
+    case LS_ACKRCVD:
+    case LS_ACKSENT:
+      f->state = LS_STARTING;
+      UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      break;
+
+    case LS_OPENED:
+      if( f->callbacks->down ) {
+        (*f->callbacks->down)(f);
+      }
+      f->state = LS_STARTING;
+      break;
+
+    default:
+      FSMDEBUG(LOG_INFO, ("%s: Down event in state %d (%s)!\n",
+          PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+  }
+
+  FSMDEBUG(LOG_INFO, ("%s: lowerdown state %d (%s) -> %d (%s)\n",
+      PROTO_NAME(f), oldState, ppperr_strerr[oldState], f->state, ppperr_strerr[f->state]));
+}
+
+
+/*
+ * fsm_open - Link is allowed to come up.
+ */
+void
+fsm_open(fsm *f)
+{
+  int oldState = f->state;
+
+  LWIP_UNUSED_ARG(oldState);
+
+  switch( f->state ) {
+    case LS_INITIAL:
+      f->state = LS_STARTING;
+      if( f->callbacks->starting ) {
+        (*f->callbacks->starting)(f);
+      }
+      break;
+
+    case LS_CLOSED:
+      if( f->flags & OPT_SILENT ) {
+        f->state = LS_STOPPED;
+      } else {
+        /* Send an initial configure-request */
+        fsm_sconfreq(f, 0);
+        f->state = LS_REQSENT;
+      }
+      break;
+  
+    case LS_CLOSING:
+      f->state = LS_STOPPING;
+      /* fall through */
+    case LS_STOPPED:
+    case LS_OPENED:
+      if( f->flags & OPT_RESTART ) {
+        fsm_lowerdown(f);
+        fsm_lowerup(f);
+      }
+      break;
+  }
+
+  FSMDEBUG(LOG_INFO, ("%s: open state %d (%s) -> %d (%s)\n",
+      PROTO_NAME(f), oldState, ppperr_strerr[oldState], f->state, ppperr_strerr[f->state]));
+}
+
+#if 0 /* backport pppd 2.4.4b1; */
+/*
+ * terminate_layer - Start process of shutting down the FSM
+ *
+ * Cancel any timeout running, notify upper layers we're done, and
+ * send a terminate-request message as configured.
+ */
+static void
+terminate_layer(fsm *f, int nextstate)
+{
+  /* @todo */
+}
+#endif
+
+/*
+ * fsm_close - Start closing connection.
+ *
+ * Cancel timeouts and either initiate close or possibly go directly to
+ * the LS_CLOSED state.
+ */
+void
+fsm_close(fsm *f, char *reason)
+{
+  int oldState = f->state;
+
+  LWIP_UNUSED_ARG(oldState);
+
+  f->term_reason = reason;
+  f->term_reason_len = (reason == NULL ? 0 : (int)strlen(reason));
+  switch( f->state ) {
+    case LS_STARTING:
+      f->state = LS_INITIAL;
+      break;
+    case LS_STOPPED:
+      f->state = LS_CLOSED;
+      break;
+    case LS_STOPPING:
+      f->state = LS_CLOSING;
+      break;
+
+    case LS_REQSENT:
+    case LS_ACKRCVD:
+    case LS_ACKSENT:
+    case LS_OPENED:
+      if( f->state != LS_OPENED ) {
+        UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      } else if( f->callbacks->down ) {
+        (*f->callbacks->down)(f);  /* Inform upper layers we're down */
+      }
+      /* Init restart counter, send Terminate-Request */
+      f->retransmits = f->maxtermtransmits;
+      fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
+            (u_char *) f->term_reason, f->term_reason_len);
+      TIMEOUT(fsm_timeout, f, f->timeouttime);
+      --f->retransmits;
+
+      f->state = LS_CLOSING;
+      break;
+  }
+
+  FSMDEBUG(LOG_INFO, ("%s: close reason=%s state %d (%s) -> %d (%s)\n",
+      PROTO_NAME(f), reason, oldState, ppperr_strerr[oldState], f->state, ppperr_strerr[f->state]));
+}
+
+
+/*
+ * fsm_timeout - Timeout expired.
+ */
+static void
+fsm_timeout(void *arg)
+{
+  fsm *f = (fsm *) arg;
+
+  switch (f->state) {
+    case LS_CLOSING:
+    case LS_STOPPING:
+      if( f->retransmits <= 0 ) {
+        FSMDEBUG(LOG_WARNING, ("%s: timeout sending Terminate-Request state=%d (%s)\n",
+             PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+        /*
+         * We've waited for an ack long enough.  Peer probably heard us.
+         */
+        f->state = (f->state == LS_CLOSING)? LS_CLOSED: LS_STOPPED;
+        if( f->callbacks->finished ) {
+          (*f->callbacks->finished)(f);
+        }
+      } else {
+        FSMDEBUG(LOG_WARNING, ("%s: timeout resending Terminate-Requests state=%d (%s)\n",
+             PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+        /* Send Terminate-Request */
+        fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
+            (u_char *) f->term_reason, f->term_reason_len);
+        TIMEOUT(fsm_timeout, f, f->timeouttime);
+        --f->retransmits;
+      }
+      break;
+
+    case LS_REQSENT:
+    case LS_ACKRCVD:
+    case LS_ACKSENT:
+      if (f->retransmits <= 0) {
+        FSMDEBUG(LOG_WARNING, ("%s: timeout sending Config-Requests state=%d (%s)\n",
+         PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+        f->state = LS_STOPPED;
+        if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished ) {
+          (*f->callbacks->finished)(f);
+        }
+      } else {
+        FSMDEBUG(LOG_WARNING, ("%s: timeout resending Config-Request state=%d (%s)\n",
+         PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+        /* Retransmit the configure-request */
+        if (f->callbacks->retransmit) {
+          (*f->callbacks->retransmit)(f);
+        }
+        fsm_sconfreq(f, 1);    /* Re-send Configure-Request */
+        if( f->state == LS_ACKRCVD ) {
+          f->state = LS_REQSENT;
+        }
+      }
+      break;
+
+    default:
+      FSMDEBUG(LOG_INFO, ("%s: UNHANDLED timeout event in state %d (%s)!\n",
+          PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+  }
+}
+
+
+/*
+ * fsm_input - Input packet.
+ */
+void
+fsm_input(fsm *f, u_char *inpacket, int l)
+{
+  u_char *inp = inpacket;
+  u_char code, id;
+  int len;
+
+  /*
+  * Parse header (code, id and length).
+  * If packet too short, drop it.
+  */
+  if (l < HEADERLEN) {
+    FSMDEBUG(LOG_WARNING, ("fsm_input(%x): Rcvd short header.\n",
+          f->protocol));
+    return;
+  }
+  GETCHAR(code, inp);
+  GETCHAR(id, inp);
+  GETSHORT(len, inp);
+  if (len < HEADERLEN) {
+    FSMDEBUG(LOG_INFO, ("fsm_input(%x): Rcvd illegal length.\n",
+        f->protocol));
+    return;
+  }
+  if (len > l) {
+    FSMDEBUG(LOG_INFO, ("fsm_input(%x): Rcvd short packet.\n",
+        f->protocol));
+    return;
+  }
+  len -= HEADERLEN;    /* subtract header length */
+
+  if( f->state == LS_INITIAL || f->state == LS_STARTING ) {
+    FSMDEBUG(LOG_INFO, ("fsm_input(%x): Rcvd packet in state %d (%s).\n",
+        f->protocol, f->state, ppperr_strerr[f->state]));
+    return;
+  }
+  FSMDEBUG(LOG_INFO, ("fsm_input(%s):%d,%d,%d\n", PROTO_NAME(f), code, id, l));
+  /*
+   * Action depends on code.
+   */
+  switch (code) {
+    case CONFREQ:
+      fsm_rconfreq(f, id, inp, len);
+      break;
+    
+    case CONFACK:
+      fsm_rconfack(f, id, inp, len);
+      break;
+    
+    case CONFNAK:
+    case CONFREJ:
+      fsm_rconfnakrej(f, code, id, inp, len);
+      break;
+    
+    case TERMREQ:
+      fsm_rtermreq(f, id, inp, len);
+      break;
+    
+    case TERMACK:
+      fsm_rtermack(f);
+      break;
+    
+    case CODEREJ:
+      fsm_rcoderej(f, inp, len);
+      break;
+    
+    default:
+      FSMDEBUG(LOG_INFO, ("fsm_input(%s): default: \n", PROTO_NAME(f)));
+      if( !f->callbacks->extcode ||
+          !(*f->callbacks->extcode)(f, code, id, inp, len) ) {
+        fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
+      }
+      break;
+  }
+}
+
+
+/*
+ * fsm_rconfreq - Receive Configure-Request.
+ */
+static void
+fsm_rconfreq(fsm *f, u_char id, u_char *inp, int len)
+{
+  int code, reject_if_disagree;
+
+  FSMDEBUG(LOG_INFO, ("fsm_rconfreq(%s): Rcvd id %d state=%d (%s)\n", 
+        PROTO_NAME(f), id, f->state, ppperr_strerr[f->state]));
+  switch( f->state ) {
+    case LS_CLOSED:
+      /* Go away, we're closed */
+      fsm_sdata(f, TERMACK, id, NULL, 0);
+      return;
+    case LS_CLOSING:
+    case LS_STOPPING:
+      return;
+
+    case LS_OPENED:
+      /* Go down and restart negotiation */
+      if( f->callbacks->down ) {
+        (*f->callbacks->down)(f);  /* Inform upper layers */
+      }
+      fsm_sconfreq(f, 0);    /* Send initial Configure-Request */
+      break;
+
+    case LS_STOPPED:
+      /* Negotiation started by our peer */
+      fsm_sconfreq(f, 0);    /* Send initial Configure-Request */
+      f->state = LS_REQSENT;
+      break;
+  }
+  
+  /*
+  * Pass the requested configuration options
+  * to protocol-specific code for checking.
+  */
+  if (f->callbacks->reqci) {    /* Check CI */
+    reject_if_disagree = (f->nakloops >= f->maxnakloops);
+    code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree);
+  } else if (len) {
+    code = CONFREJ;      /* Reject all CI */
+  } else {
+    code = CONFACK;
+  }
+  
+  /* send the Ack, Nak or Rej to the peer */
+  fsm_sdata(f, (u_char)code, id, inp, len);
+  
+  if (code == CONFACK) {
+    if (f->state == LS_ACKRCVD) {
+      UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      f->state = LS_OPENED;
+      if (f->callbacks->up) {
+        (*f->callbacks->up)(f);  /* Inform upper layers */
+      }
+    } else {
+      f->state = LS_ACKSENT;
+    }
+    f->nakloops = 0;
+  } else {
+    /* we sent CONFACK or CONFREJ */
+    if (f->state != LS_ACKRCVD) {
+      f->state = LS_REQSENT;
+    }
+    if( code == CONFNAK ) {
+      ++f->nakloops;
+    }
+  }
+}
+
+
+/*
+ * fsm_rconfack - Receive Configure-Ack.
+ */
+static void
+fsm_rconfack(fsm *f, int id, u_char *inp, int len)
+{
+  FSMDEBUG(LOG_INFO, ("fsm_rconfack(%s): Rcvd id %d state=%d (%s)\n",
+        PROTO_NAME(f), id, f->state, ppperr_strerr[f->state]));
+  
+  if (id != f->reqid || f->seen_ack) {   /* Expected id? */
+    return; /* Nope, toss... */
+  }
+  if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len): (len == 0)) ) {
+    /* Ack is bad - ignore it */
+    FSMDEBUG(LOG_INFO, ("%s: received bad Ack (length %d)\n",
+          PROTO_NAME(f), len));
+    return;
+  }
+  f->seen_ack = 1;
+  
+  switch (f->state) {
+    case LS_CLOSED:
+    case LS_STOPPED:
+      fsm_sdata(f, TERMACK, (u_char)id, NULL, 0);
+      break;
+    
+    case LS_REQSENT:
+      f->state = LS_ACKRCVD;
+      f->retransmits = f->maxconfreqtransmits;
+      break;
+    
+    case LS_ACKRCVD:
+      /* Huh? an extra valid Ack? oh well... */
+      UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      fsm_sconfreq(f, 0);
+      f->state = LS_REQSENT;
+      break;
+    
+    case LS_ACKSENT:
+      UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      f->state = LS_OPENED;
+      f->retransmits = f->maxconfreqtransmits;
+      if (f->callbacks->up) {
+        (*f->callbacks->up)(f);  /* Inform upper layers */
+      }
+      break;
+    
+    case LS_OPENED:
+      /* Go down and restart negotiation */
+      if (f->callbacks->down) {
+        (*f->callbacks->down)(f);  /* Inform upper layers */
+      }
+      fsm_sconfreq(f, 0);    /* Send initial Configure-Request */
+      f->state = LS_REQSENT;
+      break;
+  }
+}
+
+
+/*
+ * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject.
+ */
+static void
+fsm_rconfnakrej(fsm *f, int code, int id, u_char *inp, int len)
+{
+  int (*proc) (fsm *, u_char *, int);
+  int ret;
+
+  FSMDEBUG(LOG_INFO, ("fsm_rconfnakrej(%s): Rcvd id %d state=%d (%s)\n",
+        PROTO_NAME(f), id, f->state, ppperr_strerr[f->state]));
+
+  if (id != f->reqid || f->seen_ack) { /* Expected id? */
+    return;        /* Nope, toss... */
+  }
+  proc = (code == CONFNAK)? f->callbacks->nakci: f->callbacks->rejci;
+  if (!proc || !((ret = proc(f, inp, len)))) {
+    /* Nak/reject is bad - ignore it */
+    FSMDEBUG(LOG_INFO, ("%s: received bad %s (length %d)\n",
+          PROTO_NAME(f), (code==CONFNAK? "Nak": "reject"), len));
+    return;
+  }
+  f->seen_ack = 1;
+
+  switch (f->state) {
+    case LS_CLOSED:
+    case LS_STOPPED:
+      fsm_sdata(f, TERMACK, (u_char)id, NULL, 0);
+      break;
+    
+    case LS_REQSENT:
+    case LS_ACKSENT:
+      /* They didn't agree to what we wanted - try another request */
+      UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      if (ret < 0) {
+        f->state = LS_STOPPED;    /* kludge for stopping CCP */
+      } else {
+        fsm_sconfreq(f, 0);    /* Send Configure-Request */
+      }
+      break;
+    
+    case LS_ACKRCVD:
+      /* Got a Nak/reject when we had already had an Ack?? oh well... */
+      UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      fsm_sconfreq(f, 0);
+      f->state = LS_REQSENT;
+      break;
+    
+    case LS_OPENED:
+      /* Go down and restart negotiation */
+      if (f->callbacks->down) {
+        (*f->callbacks->down)(f);  /* Inform upper layers */
+      }
+      fsm_sconfreq(f, 0);    /* Send initial Configure-Request */
+      f->state = LS_REQSENT;
+      break;
+  }
+}
+
+
+/*
+ * fsm_rtermreq - Receive Terminate-Req.
+ */
+static void
+fsm_rtermreq(fsm *f, int id, u_char *p, int len)
+{
+  LWIP_UNUSED_ARG(p);
+
+  FSMDEBUG(LOG_INFO, ("fsm_rtermreq(%s): Rcvd id %d state=%d (%s)\n",
+        PROTO_NAME(f), id, f->state, ppperr_strerr[f->state]));
+
+  switch (f->state) {
+    case LS_ACKRCVD:
+    case LS_ACKSENT:
+      f->state = LS_REQSENT;    /* Start over but keep trying */
+      break;
+
+    case LS_OPENED:
+      if (len > 0) {
+        FSMDEBUG(LOG_INFO, ("%s terminated by peer (%p)\n", PROTO_NAME(f), p));
+      } else {
+        FSMDEBUG(LOG_INFO, ("%s terminated by peer\n", PROTO_NAME(f)));
+      }
+      if (f->callbacks->down) {
+        (*f->callbacks->down)(f);  /* Inform upper layers */
+      }
+      f->retransmits = 0;
+      f->state = LS_STOPPING;
+      TIMEOUT(fsm_timeout, f, f->timeouttime);
+      break;
+  }
+
+  fsm_sdata(f, TERMACK, (u_char)id, NULL, 0);
+}
+
+
+/*
+ * fsm_rtermack - Receive Terminate-Ack.
+ */
+static void
+fsm_rtermack(fsm *f)
+{
+  FSMDEBUG(LOG_INFO, ("fsm_rtermack(%s): state=%d (%s)\n", 
+        PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+  
+  switch (f->state) {
+    case LS_CLOSING:
+      UNTIMEOUT(fsm_timeout, f);
+      f->state = LS_CLOSED;
+      if( f->callbacks->finished ) {
+        (*f->callbacks->finished)(f);
+      }
+      break;
+
+    case LS_STOPPING:
+      UNTIMEOUT(fsm_timeout, f);
+      f->state = LS_STOPPED;
+      if( f->callbacks->finished ) {
+        (*f->callbacks->finished)(f);
+      }
+      break;
+    
+    case LS_ACKRCVD:
+      f->state = LS_REQSENT;
+      break;
+    
+    case LS_OPENED:
+      if (f->callbacks->down) {
+        (*f->callbacks->down)(f);  /* Inform upper layers */
+      }
+      fsm_sconfreq(f, 0);
+      break;
+    default:
+      FSMDEBUG(LOG_INFO, ("fsm_rtermack(%s): UNHANDLED state=%d (%s)!!!\n", 
+                PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+  }
+}
+
+
+/*
+ * fsm_rcoderej - Receive an Code-Reject.
+ */
+static void
+fsm_rcoderej(fsm *f, u_char *inp, int len)
+{
+  u_char code, id;
+  
+  FSMDEBUG(LOG_INFO, ("fsm_rcoderej(%s): state=%d (%s)\n", 
+        PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+  
+  if (len < HEADERLEN) {
+    FSMDEBUG(LOG_INFO, ("fsm_rcoderej: Rcvd short Code-Reject packet!\n"));
+    return;
+  }
+  GETCHAR(code, inp);
+  GETCHAR(id, inp);
+  FSMDEBUG(LOG_WARNING, ("%s: Rcvd Code-Reject for code %d, id %d\n",
+        PROTO_NAME(f), code, id));
+  
+  if( f->state == LS_ACKRCVD ) {
+    f->state = LS_REQSENT;
+  }
+}
+
+
+/*
+ * fsm_protreject - Peer doesn't speak this protocol.
+ *
+ * Treat this as a catastrophic error (RXJ-).
+ */
+void
+fsm_protreject(fsm *f)
+{
+  switch( f->state ) {
+    case LS_CLOSING:
+      UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      /* fall through */
+    case LS_CLOSED:
+      f->state = LS_CLOSED;
+      if( f->callbacks->finished ) {
+        (*f->callbacks->finished)(f);
+      }
+      break;
+
+    case LS_STOPPING:
+    case LS_REQSENT:
+    case LS_ACKRCVD:
+    case LS_ACKSENT:
+      UNTIMEOUT(fsm_timeout, f);  /* Cancel timeout */
+      /* fall through */
+    case LS_STOPPED:
+      f->state = LS_STOPPED;
+      if( f->callbacks->finished ) {
+        (*f->callbacks->finished)(f);
+      }
+      break;
+    
+    case LS_OPENED:
+      if( f->callbacks->down ) {
+        (*f->callbacks->down)(f);
+      }
+      /* Init restart counter, send Terminate-Request */
+      f->retransmits = f->maxtermtransmits;
+      fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
+            (u_char *) f->term_reason, f->term_reason_len);
+      TIMEOUT(fsm_timeout, f, f->timeouttime);
+      --f->retransmits;
+
+      f->state = LS_STOPPING;
+      break;
+    
+    default:
+      FSMDEBUG(LOG_INFO, ("%s: Protocol-reject event in state %d (%s)!\n",
+            PROTO_NAME(f), f->state, ppperr_strerr[f->state]));
+    }
+}
+
+
+/*
+ * fsm_sconfreq - Send a Configure-Request.
+ */
+static void
+fsm_sconfreq(fsm *f, int retransmit)
+{
+  u_char *outp;
+  int cilen;
+  
+  if( f->state != LS_REQSENT && f->state != LS_ACKRCVD && f->state != LS_ACKSENT ) {
+    /* Not currently negotiating - reset options */
+    if( f->callbacks->resetci ) {
+      (*f->callbacks->resetci)(f);
+    }
+    f->nakloops = 0;
+  }
+  
+  if( !retransmit ) {
+    /* New request - reset retransmission counter, use new ID */
+    f->retransmits = f->maxconfreqtransmits;
+    f->reqid = ++f->id;
+  }
+  
+  f->seen_ack = 0;
+  
+  /*
+   * Make up the request packet
+   */
+  outp = outpacket_buf[f->unit] + PPP_HDRLEN + HEADERLEN;
+  if( f->callbacks->cilen && f->callbacks->addci ) {
+    cilen = (*f->callbacks->cilen)(f);
+    if( cilen > peer_mru[f->unit] - (int)HEADERLEN ) {
+      cilen = peer_mru[f->unit] - HEADERLEN;
+    }
+    if (f->callbacks->addci) {
+      (*f->callbacks->addci)(f, outp, &cilen);
+    }
+  } else {
+    cilen = 0;
+  }
+
+  /* send the request to our peer */
+  fsm_sdata(f, CONFREQ, f->reqid, outp, cilen);
+  
+  /* start the retransmit timer */
+  --f->retransmits;
+  TIMEOUT(fsm_timeout, f, f->timeouttime);
+  
+  FSMDEBUG(LOG_INFO, ("%s: sending Configure-Request, id %d\n",
+        PROTO_NAME(f), f->reqid));
+}
+
+
+/*
+ * fsm_sdata - Send some data.
+ *
+ * Used for all packets sent to our peer by this module.
+ */
+void
+fsm_sdata( fsm *f, u_char code, u_char id, u_char *data, int datalen)
+{
+  u_char *outp;
+  int outlen;
+
+  /* Adjust length to be smaller than MTU */
+  outp = outpacket_buf[f->unit];
+  if (datalen > peer_mru[f->unit] - (int)HEADERLEN) {
+    datalen = peer_mru[f->unit] - HEADERLEN;
+  }
+  if (datalen && data != outp + PPP_HDRLEN + HEADERLEN) {
+    BCOPY(data, outp + PPP_HDRLEN + HEADERLEN, datalen);
+  }
+  outlen = datalen + HEADERLEN;
+  MAKEHEADER(outp, f->protocol);
+  PUTCHAR(code, outp);
+  PUTCHAR(id, outp);
+  PUTSHORT(outlen, outp);
+  pppWrite(f->unit, outpacket_buf[f->unit], outlen + PPP_HDRLEN);
+  FSMDEBUG(LOG_INFO, ("fsm_sdata(%s): Sent code %d,%d,%d.\n",
+        PROTO_NAME(f), code, id, outlen));
+}
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/fsm.h b/external/badvpn_dns/lwip/src/netif/ppp/fsm.h
new file mode 100644
index 0000000..8d41b5f
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/fsm.h
@@ -0,0 +1,157 @@
+/*****************************************************************************
+* fsm.h - Network Control Protocol Finite State Machine header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* Copyright (c) 1997 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-11-05 Guy Lancaster <glanca@xxxxxxxx>, Global Election Systems Inc.
+*   Original based on BSD code.
+*****************************************************************************/
+/*
+ * fsm.h - {Link, IP} Control Protocol Finite State Machine definitions.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $Id: fsm.h,v 1.5 2009/12/31 17:08:08 goldsimon Exp $
+ */
+
+#ifndef FSM_H
+#define FSM_H
+
+/*
+ * LCP Packet header = Code, id, length.
+ */
+#define HEADERLEN (sizeof (u_char) + sizeof (u_char) + sizeof (u_short))
+
+
+/*
+ *  CP (LCP, IPCP, etc.) codes.
+ */
+#define CONFREQ     1 /* Configuration Request */
+#define CONFACK     2 /* Configuration Ack */
+#define CONFNAK     3 /* Configuration Nak */
+#define CONFREJ     4 /* Configuration Reject */
+#define TERMREQ     5 /* Termination Request */
+#define TERMACK     6 /* Termination Ack */
+#define CODEREJ     7 /* Code Reject */
+
+
+/*
+ * Each FSM is described by an fsm structure and fsm callbacks.
+ */
+typedef struct fsm {
+  int unit;                        /* Interface unit number */
+  u_short protocol;                /* Data Link Layer Protocol field value */
+  int state;                       /* State */
+  int flags;                       /* Contains option bits */
+  u_char id;                       /* Current id */
+  u_char reqid;                    /* Current request id */
+  u_char seen_ack;                 /* Have received valid Ack/Nak/Rej to Req */
+  int timeouttime;                 /* Timeout time in milliseconds */
+  int maxconfreqtransmits;         /* Maximum Configure-Request transmissions */
+  int retransmits;                 /* Number of retransmissions left */
+  int maxtermtransmits;            /* Maximum Terminate-Request transmissions */
+  int nakloops;                    /* Number of nak loops since last ack */
+  int maxnakloops;                 /* Maximum number of nak loops tolerated */
+  struct fsm_callbacks* callbacks; /* Callback routines */
+  char* term_reason;               /* Reason for closing protocol */
+  int term_reason_len;             /* Length of term_reason */
+} fsm;
+
+
+typedef struct fsm_callbacks {
+  void (*resetci)(fsm*);                            /* Reset our Configuration Information */
+  int  (*cilen)(fsm*);                              /* Length of our Configuration Information */
+  void (*addci)(fsm*, u_char*, int*);               /* Add our Configuration Information */
+  int  (*ackci)(fsm*, u_char*, int);                /* ACK our Configuration Information */
+  int  (*nakci)(fsm*, u_char*, int);                /* NAK our Configuration Information */
+  int  (*rejci)(fsm*, u_char*, int);                /* Reject our Configuration Information */
+  int  (*reqci)(fsm*, u_char*, int*, int);          /* Request peer's Configuration Information */
+  void (*up)(fsm*);                                 /* Called when fsm reaches LS_OPENED state */
+  void (*down)(fsm*);                               /* Called when fsm leaves LS_OPENED state */
+  void (*starting)(fsm*);                           /* Called when we want the lower layer */
+  void (*finished)(fsm*);                           /* Called when we don't want the lower layer */
+  void (*protreject)(int);                          /* Called when Protocol-Reject received */
+  void (*retransmit)(fsm*);                         /* Retransmission is necessary */
+  int  (*extcode)(fsm*, int, u_char, u_char*, int); /* Called when unknown code received */
+  char *proto_name;                                 /* String name for protocol (for messages) */
+} fsm_callbacks;
+
+
+/*
+ * Link states.
+ */
+#define LS_INITIAL  0 /* Down, hasn't been opened */
+#define LS_STARTING 1 /* Down, been opened */
+#define LS_CLOSED   2 /* Up, hasn't been opened */
+#define LS_STOPPED  3 /* Open, waiting for down event */
+#define LS_CLOSING  4 /* Terminating the connection, not open */
+#define LS_STOPPING 5 /* Terminating, but open */
+#define LS_REQSENT  6 /* We've sent a Config Request */
+#define LS_ACKRCVD  7 /* We've received a Config Ack */
+#define LS_ACKSENT  8 /* We've sent a Config Ack */
+#define LS_OPENED   9 /* Connection available */
+
+/*
+ * Flags - indicate options controlling FSM operation
+ */
+#define OPT_PASSIVE 1 /* Don't die if we don't get a response */
+#define OPT_RESTART 2 /* Treat 2nd OPEN as DOWN, UP */
+#define OPT_SILENT  4 /* Wait for peer to speak first */
+
+
+/*
+ * Prototypes
+ */
+void fsm_init (fsm*);
+void fsm_lowerup (fsm*);
+void fsm_lowerdown (fsm*);
+void fsm_open (fsm*);
+void fsm_close (fsm*, char*);
+void fsm_input (fsm*, u_char*, int);
+void fsm_protreject (fsm*);
+void fsm_sdata (fsm*, u_char, u_char, u_char*, int);
+
+
+/*
+ * Variables
+ */
+extern int peer_mru[]; /* currently negotiated peer MRU (per unit) */
+
+#endif /* FSM_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/ipcp.c b/external/badvpn_dns/lwip/src/netif/ppp/ipcp.c
new file mode 100644
index 0000000..f0ab2e0
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/ipcp.c
@@ -0,0 +1,1411 @@
+/** In contrast to pppd 2.3.1, DNS support has been added, proxy-ARP and
+    dial-on-demand has been stripped. */
+/*****************************************************************************
+* ipcp.c - Network PPP IP Control Protocol program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 by Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-08 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original.
+*****************************************************************************/
+/*
+ * ipcp.c - PPP IP Control Protocol.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "auth.h"
+#include "fsm.h"
+#include "vj.h"
+#include "ipcp.h"
+
+#include "lwip/inet.h"
+
+#include <string.h>
+
+/* #define OLD_CI_ADDRS 1 */ /* Support deprecated address negotiation. */
+
+/* global vars */
+ipcp_options ipcp_wantoptions[NUM_PPP];  /* Options that we want to request */
+ipcp_options ipcp_gotoptions[NUM_PPP];   /* Options that peer ack'd */
+ipcp_options ipcp_allowoptions[NUM_PPP]; /* Options we allow peer to request */
+ipcp_options ipcp_hisoptions[NUM_PPP];   /* Options that we ack'd */
+
+/* local vars */
+static int default_route_set[NUM_PPP]; /* Have set up a default route */
+static int cis_received[NUM_PPP];      /* # Conf-Reqs received */
+
+
+/*
+ * Callbacks for fsm code.  (CI = Configuration Information)
+ */
+static void ipcp_resetci (fsm *);                     /* Reset our CI */
+static int  ipcp_cilen (fsm *);                       /* Return length of our CI */
+static void ipcp_addci (fsm *, u_char *, int *);      /* Add our CI */
+static int  ipcp_ackci (fsm *, u_char *, int);        /* Peer ack'd our CI */
+static int  ipcp_nakci (fsm *, u_char *, int);        /* Peer nak'd our CI */
+static int  ipcp_rejci (fsm *, u_char *, int);        /* Peer rej'd our CI */
+static int  ipcp_reqci (fsm *, u_char *, int *, int); /* Rcv CI */
+static void ipcp_up (fsm *);                          /* We're UP */
+static void ipcp_down (fsm *);                        /* We're DOWN */
+#if PPP_ADDITIONAL_CALLBACKS
+static void ipcp_script (fsm *, char *); /* Run an up/down script */
+#endif
+static void ipcp_finished (fsm *);                    /* Don't need lower layer */
+
+
+fsm ipcp_fsm[NUM_PPP]; /* IPCP fsm structure */
+
+
+static fsm_callbacks ipcp_callbacks = { /* IPCP callback routines */
+  ipcp_resetci,  /* Reset our Configuration Information */
+  ipcp_cilen,    /* Length of our Configuration Information */
+  ipcp_addci,    /* Add our Configuration Information */
+  ipcp_ackci,    /* ACK our Configuration Information */
+  ipcp_nakci,    /* NAK our Configuration Information */
+  ipcp_rejci,    /* Reject our Configuration Information */
+  ipcp_reqci,    /* Request peer's Configuration Information */
+  ipcp_up,       /* Called when fsm reaches LS_OPENED state */
+  ipcp_down,     /* Called when fsm leaves LS_OPENED state */
+  NULL,          /* Called when we want the lower layer up */
+  ipcp_finished, /* Called when we want the lower layer down */
+  NULL,          /* Called when Protocol-Reject received */
+  NULL,          /* Retransmission is necessary */
+  NULL,          /* Called to handle protocol-specific codes */
+  "IPCP"         /* String name of protocol */
+};
+
+/*
+ * Protocol entry points from main code.
+ */
+static void ipcp_init (int);
+static void ipcp_open (int);
+static void ipcp_close (int, char *);
+static void ipcp_lowerup (int);
+static void ipcp_lowerdown (int);
+static void ipcp_input (int, u_char *, int);
+static void ipcp_protrej (int);
+
+
+struct protent ipcp_protent = {
+  PPP_IPCP,
+  ipcp_init,
+  ipcp_input,
+  ipcp_protrej,
+  ipcp_lowerup,
+  ipcp_lowerdown,
+  ipcp_open,
+  ipcp_close,
+#if PPP_ADDITIONAL_CALLBACKS
+  ipcp_printpkt,
+  NULL,
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+  1,
+  "IPCP",
+#if PPP_ADDITIONAL_CALLBACKS
+  ip_check_options,
+  NULL,
+  ip_active_pkt
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+};
+
+static void ipcp_clear_addrs (int);
+
+/*
+ * Lengths of configuration options.
+ */
+#define CILEN_VOID     2
+#define CILEN_COMPRESS 4  /* min length for compression protocol opt. */
+#define CILEN_VJ       6  /* length for RFC1332 Van-Jacobson opt. */
+#define CILEN_ADDR     6  /* new-style single address option */
+#define CILEN_ADDRS    10 /* old-style dual address option */
+
+
+#define CODENAME(x) ((x) == CONFACK ? "ACK" : \
+                     (x) == CONFNAK ? "NAK" : "REJ")
+
+
+/*
+ * ipcp_init - Initialize IPCP.
+ */
+static void
+ipcp_init(int unit)
+{
+  fsm           *f = &ipcp_fsm[unit];
+  ipcp_options *wo = &ipcp_wantoptions[unit];
+  ipcp_options *ao = &ipcp_allowoptions[unit];
+
+  f->unit      = unit;
+  f->protocol  = PPP_IPCP;
+  f->callbacks = &ipcp_callbacks;
+  fsm_init(&ipcp_fsm[unit]);
+
+  memset(wo, 0, sizeof(*wo));
+  memset(ao, 0, sizeof(*ao));
+
+  wo->neg_addr      = 1;
+  wo->ouraddr       = 0;
+#if VJ_SUPPORT
+  wo->neg_vj        = 1;
+#else  /* VJ_SUPPORT */
+  wo->neg_vj        = 0;
+#endif /* VJ_SUPPORT */
+  wo->vj_protocol   = IPCP_VJ_COMP;
+  wo->maxslotindex  = MAX_SLOTS - 1;
+  wo->cflag         = 0;
+  wo->default_route = 1;
+
+  ao->neg_addr      = 1;
+#if VJ_SUPPORT
+  ao->neg_vj        = 1;
+#else  /* VJ_SUPPORT */
+  ao->neg_vj        = 0;
+#endif /* VJ_SUPPORT */
+  ao->maxslotindex  = MAX_SLOTS - 1;
+  ao->cflag         = 1;
+  ao->default_route = 1;
+}
+
+
+/*
+ * ipcp_open - IPCP is allowed to come up.
+ */
+static void
+ipcp_open(int unit)
+{
+  fsm_open(&ipcp_fsm[unit]);
+}
+
+
+/*
+ * ipcp_close - Take IPCP down.
+ */
+static void
+ipcp_close(int unit, char *reason)
+{
+  fsm_close(&ipcp_fsm[unit], reason);
+}
+
+
+/*
+ * ipcp_lowerup - The lower layer is up.
+ */
+static void
+ipcp_lowerup(int unit)
+{
+  fsm_lowerup(&ipcp_fsm[unit]);
+}
+
+
+/*
+ * ipcp_lowerdown - The lower layer is down.
+ */
+static void
+ipcp_lowerdown(int unit)
+{
+  fsm_lowerdown(&ipcp_fsm[unit]);
+}
+
+
+/*
+ * ipcp_input - Input IPCP packet.
+ */
+static void
+ipcp_input(int unit, u_char *p, int len)
+{
+  fsm_input(&ipcp_fsm[unit], p, len);
+}
+
+
+/*
+ * ipcp_protrej - A Protocol-Reject was received for IPCP.
+ *
+ * Pretend the lower layer went down, so we shut up.
+ */
+static void
+ipcp_protrej(int unit)
+{
+  fsm_lowerdown(&ipcp_fsm[unit]);
+}
+
+
+/*
+ * ipcp_resetci - Reset our CI.
+ */
+static void
+ipcp_resetci(fsm *f)
+{
+  ipcp_options *wo = &ipcp_wantoptions[f->unit];
+  
+  wo->req_addr = wo->neg_addr && ipcp_allowoptions[f->unit].neg_addr;
+  if (wo->ouraddr == 0) {
+    wo->accept_local = 1;
+  }
+  if (wo->hisaddr == 0) {
+    wo->accept_remote = 1;
+  }
+  /* Request DNS addresses from the peer */
+  wo->req_dns1 = ppp_settings.usepeerdns;
+  wo->req_dns2 = ppp_settings.usepeerdns;
+  ipcp_gotoptions[f->unit] = *wo;
+  cis_received[f->unit] = 0;
+}
+
+
+/*
+ * ipcp_cilen - Return length of our CI.
+ */
+static int
+ipcp_cilen(fsm *f)
+{
+  ipcp_options *go = &ipcp_gotoptions[f->unit];
+  ipcp_options *wo = &ipcp_wantoptions[f->unit];
+  ipcp_options *ho = &ipcp_hisoptions[f->unit];
+
+#define LENCIVJ(neg, old)   (neg ? (old? CILEN_COMPRESS : CILEN_VJ) : 0)
+#define LENCIADDR(neg, old) (neg ? (old? CILEN_ADDRS : CILEN_ADDR) : 0)
+#define LENCIDNS(neg)       (neg ? (CILEN_ADDR) : 0)
+
+  /*
+   * First see if we want to change our options to the old
+   * forms because we have received old forms from the peer.
+   */
+  if (wo->neg_addr && !go->neg_addr && !go->old_addrs) {
+    /* use the old style of address negotiation */
+    go->neg_addr = 1;
+    go->old_addrs = 1;
+  }
+  if (wo->neg_vj && !go->neg_vj && !go->old_vj) {
+    /* try an older style of VJ negotiation */
+    if (cis_received[f->unit] == 0) {
+      /* keep trying the new style until we see some CI from the peer */
+      go->neg_vj = 1;
+    } else {
+      /* use the old style only if the peer did */
+      if (ho->neg_vj && ho->old_vj) {
+        go->neg_vj = 1;
+        go->old_vj = 1;
+        go->vj_protocol = ho->vj_protocol;
+      }
+    }
+  }
+
+  return (LENCIADDR(go->neg_addr, go->old_addrs) +
+          LENCIVJ(go->neg_vj, go->old_vj) +
+          LENCIDNS(go->req_dns1) +
+          LENCIDNS(go->req_dns2));
+}
+
+
+/*
+ * ipcp_addci - Add our desired CIs to a packet.
+ */
+static void
+ipcp_addci(fsm *f, u_char *ucp, int *lenp)
+{
+  ipcp_options *go = &ipcp_gotoptions[f->unit];
+  int len = *lenp;
+
+#define ADDCIVJ(opt, neg, val, old, maxslotindex, cflag) \
+  if (neg) { \
+    int vjlen = old? CILEN_COMPRESS : CILEN_VJ; \
+    if (len >= vjlen) { \
+      PUTCHAR(opt, ucp); \
+      PUTCHAR(vjlen, ucp); \
+      PUTSHORT(val, ucp); \
+      if (!old) { \
+        PUTCHAR(maxslotindex, ucp); \
+        PUTCHAR(cflag, ucp); \
+      } \
+      len -= vjlen; \
+    } else { \
+      neg = 0; \
+    } \
+  }
+
+#define ADDCIADDR(opt, neg, old, val1, val2) \
+  if (neg) { \
+    int addrlen = (old? CILEN_ADDRS: CILEN_ADDR); \
+    if (len >= addrlen) { \
+      u32_t l; \
+      PUTCHAR(opt, ucp); \
+      PUTCHAR(addrlen, ucp); \
+      l = ntohl(val1); \
+      PUTLONG(l, ucp); \
+      if (old) { \
+        l = ntohl(val2); \
+        PUTLONG(l, ucp); \
+      } \
+      len -= addrlen; \
+    } else { \
+      neg = 0; \
+    } \
+  }
+
+#define ADDCIDNS(opt, neg, addr) \
+  if (neg) { \
+    if (len >= CILEN_ADDR) { \
+      u32_t l; \
+      PUTCHAR(opt, ucp); \
+      PUTCHAR(CILEN_ADDR, ucp); \
+      l = ntohl(addr); \
+      PUTLONG(l, ucp); \
+      len -= CILEN_ADDR; \
+    } else { \
+      neg = 0; \
+    } \
+  }
+
+  ADDCIADDR((go->old_addrs? CI_ADDRS: CI_ADDR), go->neg_addr,
+      go->old_addrs, go->ouraddr, go->hisaddr);
+
+  ADDCIVJ(CI_COMPRESSTYPE, go->neg_vj, go->vj_protocol, go->old_vj,
+      go->maxslotindex, go->cflag);
+
+  ADDCIDNS(CI_MS_DNS1, go->req_dns1, go->dnsaddr[0]);
+
+  ADDCIDNS(CI_MS_DNS2, go->req_dns2, go->dnsaddr[1]);
+
+  *lenp -= len;
+}
+
+
+/*
+ * ipcp_ackci - Ack our CIs.
+ *
+ * Returns:
+ *  0 - Ack was bad.
+ *  1 - Ack was good.
+ */
+static int
+ipcp_ackci(fsm *f, u_char *p, int len)
+{
+  ipcp_options *go = &ipcp_gotoptions[f->unit];
+  u_short cilen, citype, cishort;
+  u32_t cilong;
+  u_char cimaxslotindex, cicflag;
+
+  /*
+   * CIs must be in exactly the same order that we sent...
+   * Check packet length and CI length at each step.
+   * If we find any deviations, then this packet is bad.
+   */
+
+#define ACKCIVJ(opt, neg, val, old, maxslotindex, cflag) \
+  if (neg) { \
+    int vjlen = old? CILEN_COMPRESS : CILEN_VJ; \
+    if ((len -= vjlen) < 0) { \
+      goto bad; \
+    } \
+    GETCHAR(citype, p); \
+    GETCHAR(cilen, p); \
+    if (cilen != vjlen || \
+        citype != opt) { \
+      goto bad; \
+    } \
+    GETSHORT(cishort, p); \
+    if (cishort != val) { \
+      goto bad; \
+    } \
+    if (!old) { \
+      GETCHAR(cimaxslotindex, p); \
+      if (cimaxslotindex != maxslotindex) { \
+        goto bad; \
+      } \
+      GETCHAR(cicflag, p); \
+      if (cicflag != cflag) { \
+        goto bad; \
+      } \
+    } \
+  }
+  
+#define ACKCIADDR(opt, neg, old, val1, val2) \
+  if (neg) { \
+    int addrlen = (old? CILEN_ADDRS: CILEN_ADDR); \
+    u32_t l; \
+    if ((len -= addrlen) < 0) { \
+      goto bad; \
+    } \
+    GETCHAR(citype, p); \
+    GETCHAR(cilen, p); \
+    if (cilen != addrlen || \
+        citype != opt) { \
+      goto bad; \
+    } \
+    GETLONG(l, p); \
+    cilong = htonl(l); \
+    if (val1 != cilong) { \
+      goto bad; \
+    } \
+    if (old) { \
+      GETLONG(l, p); \
+      cilong = htonl(l); \
+      if (val2 != cilong) { \
+        goto bad; \
+      } \
+    } \
+  }
+
+#define ACKCIDNS(opt, neg, addr) \
+  if (neg) { \
+    u32_t l; \
+    if ((len -= CILEN_ADDR) < 0) { \
+      goto bad; \
+    } \
+    GETCHAR(citype, p); \
+    GETCHAR(cilen, p); \
+    if (cilen != CILEN_ADDR || \
+        citype != opt) { \
+      goto bad; \
+    } \
+    GETLONG(l, p); \
+    cilong = htonl(l); \
+    if (addr != cilong) { \
+      goto bad; \
+    } \
+  }
+
+  ACKCIADDR((go->old_addrs? CI_ADDRS: CI_ADDR), go->neg_addr,
+        go->old_addrs, go->ouraddr, go->hisaddr);
+
+  ACKCIVJ(CI_COMPRESSTYPE, go->neg_vj, go->vj_protocol, go->old_vj,
+      go->maxslotindex, go->cflag);
+
+  ACKCIDNS(CI_MS_DNS1, go->req_dns1, go->dnsaddr[0]);
+
+  ACKCIDNS(CI_MS_DNS2, go->req_dns2, go->dnsaddr[1]);
+
+  /*
+   * If there are any remaining CIs, then this packet is bad.
+   */
+  if (len != 0) {
+    goto bad;
+  }
+  return (1);
+
+bad:
+  IPCPDEBUG(LOG_INFO, ("ipcp_ackci: received bad Ack!\n"));
+  return (0);
+}
+
+/*
+ * ipcp_nakci - Peer has sent a NAK for some of our CIs.
+ * This should not modify any state if the Nak is bad
+ * or if IPCP is in the LS_OPENED state.
+ *
+ * Returns:
+ *  0 - Nak was bad.
+ *  1 - Nak was good.
+ */
+static int
+ipcp_nakci(fsm *f, u_char *p, int len)
+{
+  ipcp_options *go = &ipcp_gotoptions[f->unit];
+  u_char cimaxslotindex, cicflag;
+  u_char citype, cilen, *next;
+  u_short cishort;
+  u32_t ciaddr1, ciaddr2, l, cidnsaddr;
+  ipcp_options no;    /* options we've seen Naks for */
+  ipcp_options try;    /* options to request next time */
+
+  BZERO(&no, sizeof(no));
+  try = *go;
+
+  /*
+   * Any Nak'd CIs must be in exactly the same order that we sent.
+   * Check packet length and CI length at each step.
+   * If we find any deviations, then this packet is bad.
+   */
+#define NAKCIADDR(opt, neg, old, code) \
+  if (go->neg && \
+      len >= (cilen = (old? CILEN_ADDRS: CILEN_ADDR)) && \
+      p[1] == cilen && \
+      p[0] == opt) { \
+    len -= cilen; \
+    INCPTR(2, p); \
+    GETLONG(l, p); \
+    ciaddr1 = htonl(l); \
+    if (old) { \
+      GETLONG(l, p); \
+      ciaddr2 = htonl(l); \
+      no.old_addrs = 1; \
+    } else { \
+      ciaddr2 = 0; \
+    } \
+    no.neg = 1; \
+    code \
+  }
+
+#define NAKCIVJ(opt, neg, code) \
+  if (go->neg && \
+      ((cilen = p[1]) == CILEN_COMPRESS || cilen == CILEN_VJ) && \
+      len >= cilen && \
+      p[0] == opt) { \
+    len -= cilen; \
+    INCPTR(2, p); \
+    GETSHORT(cishort, p); \
+    no.neg = 1; \
+    code \
+  }
+  
+#define NAKCIDNS(opt, neg, code) \
+  if (go->neg && \
+      ((cilen = p[1]) == CILEN_ADDR) && \
+      len >= cilen && \
+      p[0] == opt) { \
+    len -= cilen; \
+    INCPTR(2, p); \
+    GETLONG(l, p); \
+    cidnsaddr = htonl(l); \
+    no.neg = 1; \
+    code \
+  }
+
+  /*
+   * Accept the peer's idea of {our,his} address, if different
+   * from our idea, only if the accept_{local,remote} flag is set.
+   */
+  NAKCIADDR((go->old_addrs? CI_ADDRS: CI_ADDR), neg_addr, go->old_addrs,
+    if (go->accept_local && ciaddr1) { /* Do we know our address? */
+      try.ouraddr = ciaddr1;
+      IPCPDEBUG(LOG_INFO, ("local IP address %s\n",
+           inet_ntoa(ciaddr1)));
+    }
+    if (go->accept_remote && ciaddr2) { /* Does he know his? */
+      try.hisaddr = ciaddr2;
+      IPCPDEBUG(LOG_INFO, ("remote IP address %s\n",
+           inet_ntoa(ciaddr2)));
+    }
+  );
+
+  /*
+   * Accept the peer's value of maxslotindex provided that it
+   * is less than what we asked for.  Turn off slot-ID compression
+   * if the peer wants.  Send old-style compress-type option if
+   * the peer wants.
+   */
+  NAKCIVJ(CI_COMPRESSTYPE, neg_vj,
+    if (cilen == CILEN_VJ) {
+      GETCHAR(cimaxslotindex, p);
+      GETCHAR(cicflag, p);
+      if (cishort == IPCP_VJ_COMP) {
+        try.old_vj = 0;
+        if (cimaxslotindex < go->maxslotindex) {
+          try.maxslotindex = cimaxslotindex;
+        }
+        if (!cicflag) {
+          try.cflag = 0;
+        }
+      } else {
+        try.neg_vj = 0;
+      }
+    } else {
+      if (cishort == IPCP_VJ_COMP || cishort == IPCP_VJ_COMP_OLD) {
+        try.old_vj = 1;
+        try.vj_protocol = cishort;
+      } else {
+        try.neg_vj = 0;
+      }
+    }
+  );
+
+  NAKCIDNS(CI_MS_DNS1, req_dns1,
+      try.dnsaddr[0] = cidnsaddr;
+        IPCPDEBUG(LOG_INFO, ("primary DNS address %s\n", inet_ntoa(cidnsaddr)));
+      );
+
+  NAKCIDNS(CI_MS_DNS2, req_dns2,
+      try.dnsaddr[1] = cidnsaddr;
+        IPCPDEBUG(LOG_INFO, ("secondary DNS address %s\n", inet_ntoa(cidnsaddr)));
+      );
+
+  /*
+  * There may be remaining CIs, if the peer is requesting negotiation
+  * on an option that we didn't include in our request packet.
+  * If they want to negotiate about IP addresses, we comply.
+  * If they want us to ask for compression, we refuse.
+  */
+  while (len > CILEN_VOID) {
+    GETCHAR(citype, p);
+    GETCHAR(cilen, p);
+    if( (len -= cilen) < 0 ) {
+      goto bad;
+    }
+    next = p + cilen - 2;
+
+    switch (citype) {
+      case CI_COMPRESSTYPE:
+        if (go->neg_vj || no.neg_vj ||
+            (cilen != CILEN_VJ && cilen != CILEN_COMPRESS)) {
+          goto bad;
+        }
+        no.neg_vj = 1;
+        break;
+      case CI_ADDRS:
+        if ((go->neg_addr && go->old_addrs) || no.old_addrs
+            || cilen != CILEN_ADDRS) {
+          goto bad;
+        }
+        try.neg_addr = 1;
+        try.old_addrs = 1;
+        GETLONG(l, p);
+        ciaddr1 = htonl(l);
+        if (ciaddr1 && go->accept_local) {
+          try.ouraddr = ciaddr1;
+        }
+        GETLONG(l, p);
+        ciaddr2 = htonl(l);
+        if (ciaddr2 && go->accept_remote) {
+          try.hisaddr = ciaddr2;
+        }
+        no.old_addrs = 1;
+        break;
+      case CI_ADDR:
+        if (go->neg_addr || no.neg_addr || cilen != CILEN_ADDR) {
+          goto bad;
+        }
+        try.old_addrs = 0;
+        GETLONG(l, p);
+        ciaddr1 = htonl(l);
+        if (ciaddr1 && go->accept_local) {
+          try.ouraddr = ciaddr1;
+        }
+        if (try.ouraddr != 0) {
+          try.neg_addr = 1;
+        }
+        no.neg_addr = 1;
+        break;
+    }
+    p = next;
+  }
+
+  /* If there is still anything left, this packet is bad. */
+  if (len != 0) {
+    goto bad;
+  }
+
+  /*
+   * OK, the Nak is good.  Now we can update state.
+   */
+  if (f->state != LS_OPENED) {
+    *go = try;
+  }
+
+  return 1;
+
+bad:
+  IPCPDEBUG(LOG_INFO, ("ipcp_nakci: received bad Nak!\n"));
+  return 0;
+}
+
+
+/*
+ * ipcp_rejci - Reject some of our CIs.
+ */
+static int
+ipcp_rejci(fsm *f, u_char *p, int len)
+{
+  ipcp_options *go = &ipcp_gotoptions[f->unit];
+  u_char cimaxslotindex, ciflag, cilen;
+  u_short cishort;
+  u32_t cilong;
+  ipcp_options try;    /* options to request next time */
+
+  try = *go;
+  /*
+   * Any Rejected CIs must be in exactly the same order that we sent.
+   * Check packet length and CI length at each step.
+   * If we find any deviations, then this packet is bad.
+   */
+#define REJCIADDR(opt, neg, old, val1, val2) \
+  if (go->neg && \
+      len >= (cilen = old? CILEN_ADDRS: CILEN_ADDR) && \
+      p[1] == cilen && \
+      p[0] == opt) { \
+    u32_t l; \
+    len -= cilen; \
+    INCPTR(2, p); \
+    GETLONG(l, p); \
+    cilong = htonl(l); \
+    /* Check rejected value. */ \
+    if (cilong != val1) { \
+      goto bad; \
+    } \
+    if (old) { \
+      GETLONG(l, p); \
+      cilong = htonl(l); \
+      /* Check rejected value. */ \
+      if (cilong != val2) { \
+        goto bad; \
+      } \
+    } \
+    try.neg = 0; \
+  }
+
+#define REJCIVJ(opt, neg, val, old, maxslot, cflag) \
+  if (go->neg && \
+      p[1] == (old? CILEN_COMPRESS : CILEN_VJ) && \
+      len >= p[1] && \
+      p[0] == opt) { \
+    len -= p[1]; \
+    INCPTR(2, p); \
+    GETSHORT(cishort, p); \
+    /* Check rejected value. */  \
+    if (cishort != val) { \
+      goto bad; \
+    } \
+    if (!old) { \
+      GETCHAR(cimaxslotindex, p); \
+      if (cimaxslotindex != maxslot) { \
+        goto bad; \
+      } \
+      GETCHAR(ciflag, p); \
+      if (ciflag != cflag) { \
+        goto bad; \
+      } \
+    } \
+    try.neg = 0; \
+  }
+
+#define REJCIDNS(opt, neg, dnsaddr) \
+  if (go->neg && \
+      ((cilen = p[1]) == CILEN_ADDR) && \
+      len >= cilen && \
+      p[0] == opt) { \
+    u32_t l; \
+    len -= cilen; \
+    INCPTR(2, p); \
+    GETLONG(l, p); \
+    cilong = htonl(l); \
+    /* Check rejected value. */ \
+    if (cilong != dnsaddr) { \
+      goto bad; \
+    } \
+    try.neg = 0; \
+  }
+
+  REJCIADDR((go->old_addrs? CI_ADDRS: CI_ADDR), neg_addr,
+        go->old_addrs, go->ouraddr, go->hisaddr);
+
+  REJCIVJ(CI_COMPRESSTYPE, neg_vj, go->vj_protocol, go->old_vj,
+      go->maxslotindex, go->cflag);
+
+  REJCIDNS(CI_MS_DNS1, req_dns1, go->dnsaddr[0]);
+
+  REJCIDNS(CI_MS_DNS2, req_dns2, go->dnsaddr[1]);
+
+  /*
+   * If there are any remaining CIs, then this packet is bad.
+   */
+  if (len != 0) {
+    goto bad;
+  }
+  /*
+   * Now we can update state.
+   */
+  if (f->state != LS_OPENED) {
+    *go = try;
+  }
+  return 1;
+
+bad:
+  IPCPDEBUG(LOG_INFO, ("ipcp_rejci: received bad Reject!\n"));
+  return 0;
+}
+
+
+/*
+ * ipcp_reqci - Check the peer's requested CIs and send appropriate response.
+ *
+ * Returns: CONFACK, CONFNAK or CONFREJ and input packet modified
+ * appropriately.  If reject_if_disagree is non-zero, doesn't return
+ * CONFNAK; returns CONFREJ if it can't return CONFACK.
+ */
+static int
+ipcp_reqci(fsm *f, u_char *inp/* Requested CIs */,int *len/* Length of requested CIs */,int reject_if_disagree)
+{
+  ipcp_options *wo = &ipcp_wantoptions[f->unit];
+  ipcp_options *ho = &ipcp_hisoptions[f->unit];
+  ipcp_options *ao = &ipcp_allowoptions[f->unit];
+#ifdef OLD_CI_ADDRS
+  ipcp_options *go = &ipcp_gotoptions[f->unit];
+#endif
+  u_char *cip, *next;     /* Pointer to current and next CIs */
+  u_short cilen, citype;  /* Parsed len, type */
+  u_short cishort;        /* Parsed short value */
+  u32_t tl, ciaddr1;      /* Parsed address values */
+#ifdef OLD_CI_ADDRS
+  u32_t ciaddr2;          /* Parsed address values */
+#endif
+  int rc = CONFACK;       /* Final packet return code */
+  int orc;                /* Individual option return code */
+  u_char *p;              /* Pointer to next char to parse */
+  u_char *ucp = inp;      /* Pointer to current output char */
+  int l = *len;           /* Length left */
+  u_char maxslotindex, cflag;
+  int d;
+
+  cis_received[f->unit] = 1;
+
+  /*
+   * Reset all his options.
+   */
+  BZERO(ho, sizeof(*ho));
+
+  /*
+   * Process all his options.
+   */
+  next = inp;
+  while (l) {
+    orc = CONFACK;       /* Assume success */
+    cip = p = next;      /* Remember begining of CI */
+    if (l < 2 ||         /* Not enough data for CI header or */
+        p[1] < 2 ||      /*  CI length too small or */
+        p[1] > l) {      /*  CI length too big? */
+      IPCPDEBUG(LOG_INFO, ("ipcp_reqci: bad CI length!\n"));
+      orc = CONFREJ;     /* Reject bad CI */
+      cilen = (u_short)l;/* Reject till end of packet */
+      l = 0;             /* Don't loop again */
+      goto endswitch;
+    }
+    GETCHAR(citype, p);  /* Parse CI type */
+    GETCHAR(cilen, p);   /* Parse CI length */
+    l -= cilen;          /* Adjust remaining length */
+    next += cilen;       /* Step to next CI */
+
+    switch (citype) {      /* Check CI type */
+#ifdef OLD_CI_ADDRS /* Need to save space... */
+      case CI_ADDRS:
+        IPCPDEBUG(LOG_INFO, ("ipcp_reqci: received ADDRS\n"));
+        if (!ao->neg_addr ||
+            cilen != CILEN_ADDRS) {  /* Check CI length */
+          orc = CONFREJ;    /* Reject CI */
+          break;
+        }
+
+        /*
+         * If he has no address, or if we both have his address but
+         * disagree about it, then NAK it with our idea.
+         * In particular, if we don't know his address, but he does,
+         * then accept it.
+         */
+        GETLONG(tl, p);    /* Parse source address (his) */
+        ciaddr1 = htonl(tl);
+        IPCPDEBUG(LOG_INFO, ("his addr %s\n", inet_ntoa(ciaddr1)));
+        if (ciaddr1 != wo->hisaddr
+            && (ciaddr1 == 0 || !wo->accept_remote)) {
+          orc = CONFNAK;
+          if (!reject_if_disagree) {
+            DECPTR(sizeof(u32_t), p);
+            tl = ntohl(wo->hisaddr);
+            PUTLONG(tl, p);
+          }
+        } else if (ciaddr1 == 0 && wo->hisaddr == 0) {
+          /*
+           * If neither we nor he knows his address, reject the option.
+           */
+          orc = CONFREJ;
+          wo->req_addr = 0;  /* don't NAK with 0.0.0.0 later */
+          break;
+        }
+
+        /*
+         * If he doesn't know our address, or if we both have our address
+         * but disagree about it, then NAK it with our idea.
+         */
+        GETLONG(tl, p);    /* Parse desination address (ours) */
+        ciaddr2 = htonl(tl);
+        IPCPDEBUG(LOG_INFO, ("our addr %s\n", inet_ntoa(ciaddr2)));
+        if (ciaddr2 != wo->ouraddr) {
+          if (ciaddr2 == 0 || !wo->accept_local) {
+            orc = CONFNAK;
+            if (!reject_if_disagree) {
+              DECPTR(sizeof(u32_t), p);
+              tl = ntohl(wo->ouraddr);
+              PUTLONG(tl, p);
+            }
+          } else {
+            go->ouraddr = ciaddr2;  /* accept peer's idea */
+          }
+        }
+
+        ho->neg_addr = 1;
+        ho->old_addrs = 1;
+        ho->hisaddr = ciaddr1;
+        ho->ouraddr = ciaddr2;
+        break;
+#endif
+
+      case CI_ADDR:
+        if (!ao->neg_addr) {
+          IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Reject ADDR not allowed\n"));
+          orc = CONFREJ;        /* Reject CI */
+          break;
+        } else if (cilen != CILEN_ADDR) {  /* Check CI length */
+          IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Reject ADDR bad len\n"));
+          orc = CONFREJ;        /* Reject CI */
+          break;
+        }
+
+        /*
+         * If he has no address, or if we both have his address but
+         * disagree about it, then NAK it with our idea.
+         * In particular, if we don't know his address, but he does,
+         * then accept it.
+         */
+        GETLONG(tl, p);  /* Parse source address (his) */
+        ciaddr1 = htonl(tl);
+        if (ciaddr1 != wo->hisaddr
+            && (ciaddr1 == 0 || !wo->accept_remote)) {
+          orc = CONFNAK;
+          if (!reject_if_disagree) {
+            DECPTR(sizeof(u32_t), p);
+            tl = ntohl(wo->hisaddr);
+            PUTLONG(tl, p);
+          }
+          IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Nak ADDR %s\n", inet_ntoa(ciaddr1)));
+        } else if (ciaddr1 == 0 && wo->hisaddr == 0) {
+          /*
+           * Don't ACK an address of 0.0.0.0 - reject it instead.
+           */
+          IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Reject ADDR %s\n", inet_ntoa(ciaddr1)));
+          orc = CONFREJ;
+          wo->req_addr = 0;  /* don't NAK with 0.0.0.0 later */
+          break;
+        }
+
+        ho->neg_addr = 1;
+        ho->hisaddr = ciaddr1;
+        IPCPDEBUG(LOG_INFO, ("ipcp_reqci: ADDR %s\n", inet_ntoa(ciaddr1)));
+        break;
+
+      case CI_MS_DNS1:
+      case CI_MS_DNS2:
+        /* Microsoft primary or secondary DNS request */
+        d = citype == CI_MS_DNS2;
+
+        /* If we do not have a DNS address then we cannot send it */
+        if (ao->dnsaddr[d] == 0 ||
+            cilen != CILEN_ADDR) {  /* Check CI length */
+          IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Rejecting DNS%d Request\n", d+1));
+          orc = CONFREJ;        /* Reject CI */
+          break;
+        }
+        GETLONG(tl, p);
+        if (htonl(tl) != ao->dnsaddr[d]) {
+          IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Naking DNS%d Request %s\n",
+                d+1, inet_ntoa(tl)));
+          DECPTR(sizeof(u32_t), p);
+          tl = ntohl(ao->dnsaddr[d]);
+          PUTLONG(tl, p);
+          orc = CONFNAK;
+        }
+        IPCPDEBUG(LOG_INFO, ("ipcp_reqci: received DNS%d Request\n", d+1));
+        break;
+
+      case CI_MS_WINS1:
+      case CI_MS_WINS2:
+        /* Microsoft primary or secondary WINS request */
+        d = citype == CI_MS_WINS2;
+        IPCPDEBUG(LOG_INFO, ("ipcp_reqci: received WINS%d Request\n", d+1));
+
+        /* If we do not have a DNS address then we cannot send it */
+        if (ao->winsaddr[d] == 0 ||
+          cilen != CILEN_ADDR) {  /* Check CI length */
+          orc = CONFREJ;      /* Reject CI */
+          break;
+        }
+        GETLONG(tl, p);
+        if (htonl(tl) != ao->winsaddr[d]) {
+          DECPTR(sizeof(u32_t), p);
+          tl = ntohl(ao->winsaddr[d]);
+          PUTLONG(tl, p);
+          orc = CONFNAK;
+        }
+        break;
+
+      case CI_COMPRESSTYPE:
+        if (!ao->neg_vj) {
+          IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Rejecting COMPRESSTYPE not allowed\n"));
+          orc = CONFREJ;
+          break;
+        } else if (cilen != CILEN_VJ && cilen != CILEN_COMPRESS) {
+          IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Rejecting COMPRESSTYPE len=%d\n", cilen));
+          orc = CONFREJ;
+          break;
+        }
+        GETSHORT(cishort, p);
+
+        if (!(cishort == IPCP_VJ_COMP ||
+            (cishort == IPCP_VJ_COMP_OLD && cilen == CILEN_COMPRESS))) {
+          IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Rejecting COMPRESSTYPE %d\n", cishort));
+          orc = CONFREJ;
+          break;
+        }
+
+        ho->neg_vj = 1;
+        ho->vj_protocol = cishort;
+        if (cilen == CILEN_VJ) {
+          GETCHAR(maxslotindex, p);
+          if (maxslotindex > ao->maxslotindex) { 
+            IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Naking VJ max slot %d\n", maxslotindex));
+            orc = CONFNAK;
+            if (!reject_if_disagree) {
+              DECPTR(1, p);
+              PUTCHAR(ao->maxslotindex, p);
+            }
+          }
+          GETCHAR(cflag, p);
+          if (cflag && !ao->cflag) {
+            IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Naking VJ cflag %d\n", cflag));
+            orc = CONFNAK;
+            if (!reject_if_disagree) {
+              DECPTR(1, p);
+              PUTCHAR(wo->cflag, p);
+            }
+          }
+          ho->maxslotindex = maxslotindex;
+          ho->cflag = cflag;
+        } else {
+          ho->old_vj = 1;
+          ho->maxslotindex = MAX_SLOTS - 1;
+          ho->cflag = 1;
+        }
+        IPCPDEBUG(LOG_INFO, (
+              "ipcp_reqci: received COMPRESSTYPE p=%d old=%d maxslot=%d cflag=%d\n",
+              ho->vj_protocol, ho->old_vj, ho->maxslotindex, ho->cflag));
+        break;
+
+      default:
+        IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Rejecting unknown CI type %d\n", citype));
+        orc = CONFREJ;
+        break;
+    }
+
+endswitch:
+    if (orc == CONFACK &&    /* Good CI */
+        rc != CONFACK) {     /*  but prior CI wasnt? */
+      continue;              /* Don't send this one */
+    }
+
+    if (orc == CONFNAK) {    /* Nak this CI? */
+      if (reject_if_disagree) {  /* Getting fed up with sending NAKs? */
+        IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Rejecting too many naks\n"));
+        orc = CONFREJ;       /* Get tough if so */
+      } else {
+        if (rc == CONFREJ) { /* Rejecting prior CI? */
+          continue;          /* Don't send this one */
+        }
+        if (rc == CONFACK) { /* Ack'd all prior CIs? */
+          rc = CONFNAK;      /* Not anymore... */
+          ucp = inp;         /* Backup */
+        }
+      }
+    }
+
+    if (orc == CONFREJ &&    /* Reject this CI */
+        rc != CONFREJ) {  /*  but no prior ones? */
+      rc = CONFREJ;
+      ucp = inp;        /* Backup */
+    }
+    
+    /* Need to move CI? */
+    if (ucp != cip) {
+      BCOPY(cip, ucp, cilen);  /* Move it */
+    }
+
+    /* Update output pointer */
+    INCPTR(cilen, ucp);
+  }
+
+  /*
+   * If we aren't rejecting this packet, and we want to negotiate
+   * their address, and they didn't send their address, then we
+   * send a NAK with a CI_ADDR option appended.  We assume the
+   * input buffer is long enough that we can append the extra
+   * option safely.
+   */
+  if (rc != CONFREJ && !ho->neg_addr &&
+      wo->req_addr && !reject_if_disagree) {
+    IPCPDEBUG(LOG_INFO, ("ipcp_reqci: Requesting peer address\n"));
+    if (rc == CONFACK) {
+      rc = CONFNAK;
+      ucp = inp;        /* reset pointer */
+      wo->req_addr = 0;    /* don't ask again */
+    }
+    PUTCHAR(CI_ADDR, ucp);
+    PUTCHAR(CILEN_ADDR, ucp);
+    tl = ntohl(wo->hisaddr);
+    PUTLONG(tl, ucp);
+  }
+
+  *len = (int)(ucp - inp);    /* Compute output length */
+  IPCPDEBUG(LOG_INFO, ("ipcp_reqci: returning Configure-%s\n", CODENAME(rc)));
+  return (rc);      /* Return final code */
+}
+
+
+#if 0
+/*
+ * ip_check_options - check that any IP-related options are OK,
+ * and assign appropriate defaults.
+ */
+static void
+ip_check_options(u_long localAddr)
+{
+  ipcp_options *wo = &ipcp_wantoptions[0];
+
+  /*
+   * Load our default IP address but allow the remote host to give us
+   * a new address.
+   */
+  if (wo->ouraddr == 0 && !ppp_settings.disable_defaultip) {
+    wo->accept_local = 1;  /* don't insist on this default value */
+    wo->ouraddr = htonl(localAddr);
+  }
+}
+#endif
+
+
+/*
+ * ipcp_up - IPCP has come UP.
+ *
+ * Configure the IP network interface appropriately and bring it up.
+ */
+static void
+ipcp_up(fsm *f)
+{
+  u32_t mask;
+  ipcp_options *ho = &ipcp_hisoptions[f->unit];
+  ipcp_options *go = &ipcp_gotoptions[f->unit];
+  ipcp_options *wo = &ipcp_wantoptions[f->unit];
+
+  np_up(f->unit, PPP_IP);
+  IPCPDEBUG(LOG_INFO, ("ipcp: up\n"));
+
+  /*
+   * We must have a non-zero IP address for both ends of the link.
+   */
+  if (!ho->neg_addr) {
+    ho->hisaddr = wo->hisaddr;
+  }
+
+  if (ho->hisaddr == 0) {
+    IPCPDEBUG(LOG_ERR, ("Could not determine remote IP address\n"));
+    ipcp_close(f->unit, "Could not determine remote IP address");
+    return;
+  }
+  if (go->ouraddr == 0) {
+    IPCPDEBUG(LOG_ERR, ("Could not determine local IP address\n"));
+    ipcp_close(f->unit, "Could not determine local IP address");
+    return;
+  }
+
+  if (ppp_settings.usepeerdns && (go->dnsaddr[0] || go->dnsaddr[1])) {
+    /*pppGotDNSAddrs(go->dnsaddr[0], go->dnsaddr[1]);*/
+  }
+
+  /*
+   * Check that the peer is allowed to use the IP address it wants.
+   */
+  if (!auth_ip_addr(f->unit, ho->hisaddr)) {
+    IPCPDEBUG(LOG_ERR, ("Peer is not authorized to use remote address %s\n",
+        inet_ntoa(ho->hisaddr)));
+    ipcp_close(f->unit, "Unauthorized remote IP address");
+    return;
+  }
+
+  /* set tcp compression */
+  sifvjcomp(f->unit, ho->neg_vj, ho->cflag, ho->maxslotindex);
+
+  /*
+   * Set IP addresses and (if specified) netmask.
+   */
+  mask = GetMask(go->ouraddr);
+
+  if (!sifaddr(f->unit, go->ouraddr, ho->hisaddr, mask, go->dnsaddr[0], go->dnsaddr[1])) {
+    IPCPDEBUG(LOG_WARNING, ("sifaddr failed\n"));
+    ipcp_close(f->unit, "Interface configuration failed");
+    return;
+  }
+
+  /* bring the interface up for IP */
+  if (!sifup(f->unit)) {
+    IPCPDEBUG(LOG_WARNING, ("sifup failed\n"));
+    ipcp_close(f->unit, "Interface configuration failed");
+    return;
+  }
+
+  sifnpmode(f->unit, PPP_IP, NPMODE_PASS);
+
+  /* assign a default route through the interface if required */
+  if (ipcp_wantoptions[f->unit].default_route) {
+    if (sifdefaultroute(f->unit, go->ouraddr, ho->hisaddr)) {
+      default_route_set[f->unit] = 1;
+    }
+  }
+
+  IPCPDEBUG(LOG_NOTICE, ("local  IP address %s\n", inet_ntoa(go->ouraddr)));
+  IPCPDEBUG(LOG_NOTICE, ("remote IP address %s\n", inet_ntoa(ho->hisaddr)));
+  if (go->dnsaddr[0]) {
+    IPCPDEBUG(LOG_NOTICE, ("primary   DNS address %s\n", inet_ntoa(go->dnsaddr[0])));
+  }
+  if (go->dnsaddr[1]) {
+    IPCPDEBUG(LOG_NOTICE, ("secondary DNS address %s\n", inet_ntoa(go->dnsaddr[1])));
+  }
+}
+
+
+/*
+ * ipcp_down - IPCP has gone DOWN.
+ *
+ * Take the IP network interface down, clear its addresses
+ * and delete routes through it.
+ */
+static void
+ipcp_down(fsm *f)
+{
+  IPCPDEBUG(LOG_INFO, ("ipcp: down\n"));
+  np_down(f->unit, PPP_IP);
+  sifvjcomp(f->unit, 0, 0, 0);
+
+  sifdown(f->unit);
+  ipcp_clear_addrs(f->unit);
+}
+
+
+/*
+ * ipcp_clear_addrs() - clear the interface addresses, routes, etc.
+ */
+static void
+ipcp_clear_addrs(int unit)
+{
+  u32_t ouraddr, hisaddr;
+
+  ouraddr = ipcp_gotoptions[unit].ouraddr;
+  hisaddr = ipcp_hisoptions[unit].hisaddr;
+  if (default_route_set[unit]) {
+    cifdefaultroute(unit, ouraddr, hisaddr);
+    default_route_set[unit] = 0;
+  }
+  cifaddr(unit, ouraddr, hisaddr);
+}
+
+
+/*
+ * ipcp_finished - possibly shut down the lower layers.
+ */
+static void
+ipcp_finished(fsm *f)
+{
+  np_finished(f->unit, PPP_IP);
+}
+
+#if PPP_ADDITIONAL_CALLBACKS
+static int
+ipcp_printpkt(u_char *p, int plen, void (*printer) (void *, char *, ...), void *arg)
+{
+  LWIP_UNUSED_ARG(p);
+  LWIP_UNUSED_ARG(plen);
+  LWIP_UNUSED_ARG(printer);
+  LWIP_UNUSED_ARG(arg);
+  return 0;
+}
+
+/*
+ * ip_active_pkt - see if this IP packet is worth bringing the link up for.
+ * We don't bring the link up for IP fragments or for TCP FIN packets
+ * with no data.
+ */
+#define IP_HDRLEN   20  /* bytes */
+#define IP_OFFMASK  0x1fff
+#define IPPROTO_TCP 6
+#define TCP_HDRLEN  20
+#define TH_FIN      0x01
+
+/*
+ * We use these macros because the IP header may be at an odd address,
+ * and some compilers might use word loads to get th_off or ip_hl.
+ */
+
+#define net_short(x)    (((x)[0] << 8) + (x)[1])
+#define get_iphl(x)     (((unsigned char *)(x))[0] & 0xF)
+#define get_ipoff(x)    net_short((unsigned char *)(x) + 6)
+#define get_ipproto(x)  (((unsigned char *)(x))[9])
+#define get_tcpoff(x)   (((unsigned char *)(x))[12] >> 4)
+#define get_tcpflags(x) (((unsigned char *)(x))[13])
+
+static int
+ip_active_pkt(u_char *pkt, int len)
+{
+  u_char *tcp;
+  int hlen;
+
+  len -= PPP_HDRLEN;
+  pkt += PPP_HDRLEN;
+  if (len < IP_HDRLEN) {
+    return 0;
+  }
+  if ((get_ipoff(pkt) & IP_OFFMASK) != 0) {
+    return 0;
+  }
+  if (get_ipproto(pkt) != IPPROTO_TCP) {
+    return 1;
+  }
+  hlen = get_iphl(pkt) * 4;
+  if (len < hlen + TCP_HDRLEN) {
+    return 0;
+  }
+  tcp = pkt + hlen;
+  if ((get_tcpflags(tcp) & TH_FIN) != 0 && len == hlen + get_tcpoff(tcp) * 4) {
+    return 0;
+  }
+  return 1;
+}
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/ipcp.h b/external/badvpn_dns/lwip/src/netif/ppp/ipcp.h
new file mode 100644
index 0000000..de03f46
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/ipcp.h
@@ -0,0 +1,106 @@
+/*****************************************************************************
+* ipcp.h -  PPP IP NCP: Internet Protocol Network Control Protocol header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-04 Guy Lancaster <glanca@xxxxxxxx>, Global Election Systems Inc.
+*   Original derived from BSD codes.
+*****************************************************************************/
+/*
+ * ipcp.h - IP Control Protocol definitions.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $Id: ipcp.h,v 1.4 2010/01/18 20:49:43 goldsimon Exp $
+ */
+
+#ifndef IPCP_H
+#define IPCP_H
+
+/*
+ * Options.
+ */
+#define CI_ADDRS            1      /* IP Addresses */
+#define CI_COMPRESSTYPE     2      /* Compression Type */
+#define CI_ADDR             3
+
+#define CI_MS_DNS1          129    /* Primary DNS value */
+#define CI_MS_WINS1         128    /* Primary WINS value */
+#define CI_MS_DNS2          131    /* Secondary DNS value */
+#define CI_MS_WINS2         130    /* Secondary WINS value */
+
+#define IPCP_VJMODE_OLD     1      /* "old" mode (option # = 0x0037) */
+#define IPCP_VJMODE_RFC1172 2      /* "old-rfc"mode (option # = 0x002d) */
+#define IPCP_VJMODE_RFC1332 3      /* "new-rfc"mode (option # = 0x002d, */
+                                   /*  maxslot and slot number compression) */
+
+#define IPCP_VJ_COMP        0x002d /* current value for VJ compression option */
+#define IPCP_VJ_COMP_OLD    0x0037 /* "old" (i.e, broken) value for VJ */
+                                   /* compression option */ 
+
+typedef struct ipcp_options {
+  u_int   neg_addr      : 1; /* Negotiate IP Address? */
+  u_int   old_addrs     : 1; /* Use old (IP-Addresses) option? */
+  u_int   req_addr      : 1; /* Ask peer to send IP address? */
+  u_int   default_route : 1; /* Assign default route through interface? */
+  u_int   proxy_arp     : 1; /* Make proxy ARP entry for peer? */
+  u_int   neg_vj        : 1; /* Van Jacobson Compression? */
+  u_int   old_vj        : 1; /* use old (short) form of VJ option? */
+  u_int   accept_local  : 1; /* accept peer's value for ouraddr */
+  u_int   accept_remote : 1; /* accept peer's value for hisaddr */
+  u_int   req_dns1      : 1; /* Ask peer to send primary DNS address? */
+  u_int   req_dns2      : 1; /* Ask peer to send secondary DNS address? */
+  u_short vj_protocol;       /* protocol value to use in VJ option */
+  u_char  maxslotindex;      /* VJ slots - 1. */
+  u_char  cflag;             /* VJ slot compression flag. */
+  u32_t   ouraddr, hisaddr;  /* Addresses in NETWORK BYTE ORDER */
+  u32_t   dnsaddr[2];        /* Primary and secondary MS DNS entries */
+  u32_t   winsaddr[2];       /* Primary and secondary MS WINS entries */
+} ipcp_options;
+
+extern fsm ipcp_fsm[];
+extern ipcp_options ipcp_wantoptions[];
+extern ipcp_options ipcp_gotoptions[];
+extern ipcp_options ipcp_allowoptions[];
+extern ipcp_options ipcp_hisoptions[];
+
+extern struct protent ipcp_protent;
+
+#endif /* IPCP_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/lcp.c b/external/badvpn_dns/lwip/src/netif/ppp/lcp.c
new file mode 100644
index 0000000..54f758a
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/lcp.c
@@ -0,0 +1,2066 @@
+/*****************************************************************************
+* lcp.c - Network Link Control Protocol program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 by Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-01 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original.
+*****************************************************************************/
+
+/*
+ * lcp.c - PPP Link Control Protocol.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+ 
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "fsm.h"
+#include "chap.h"
+#include "magic.h"
+#include "auth.h"
+#include "lcp.h"
+
+#include <string.h>
+
+#if PPPOE_SUPPORT
+#include "netif/ppp_oe.h"
+#else
+#define PPPOE_MAXMTU PPP_MAXMRU
+#endif
+
+#if 0 /* UNUSED */
+/*
+ * LCP-related command-line options.
+ */
+int lcp_echo_interval = 0;  /* Interval between LCP echo-requests */
+int lcp_echo_fails = 0;     /* Tolerance to unanswered echo-requests */
+bool  lax_recv = 0;         /* accept control chars in asyncmap */
+
+static int setescape (char **);
+
+static option_t lcp_option_list[] = {
+    /* LCP options */
+    /* list stripped for simplicity */
+    {NULL}
+};
+#endif /* UNUSED */
+
+/* options */
+LinkPhase lcp_phase[NUM_PPP];          /* Phase of link session (RFC 1661) */
+static u_int lcp_echo_interval      = LCP_ECHOINTERVAL; /* Interval between LCP echo-requests */
+static u_int lcp_echo_fails         = LCP_MAXECHOFAILS; /* Tolerance to unanswered echo-requests */
+
+/* global vars */
+static fsm lcp_fsm[NUM_PPP];                            /* LCP fsm structure (global)*/
+lcp_options lcp_wantoptions[NUM_PPP];  /* Options that we want to request */
+lcp_options lcp_gotoptions[NUM_PPP];   /* Options that peer ack'd */
+lcp_options lcp_allowoptions[NUM_PPP]; /* Options we allow peer to request */
+lcp_options lcp_hisoptions[NUM_PPP];   /* Options that we ack'd */
+ext_accm xmit_accm[NUM_PPP];           /* extended transmit ACCM */
+
+static u32_t lcp_echos_pending      = 0;                /* Number of outstanding echo msgs */
+static u32_t lcp_echo_number        = 0;                /* ID number of next echo frame */
+static u32_t lcp_echo_timer_running = 0;                /* TRUE if a timer is running */
+
+/* @todo: do we really need such a large buffer? The typical 1500 bytes seem too much. */
+static u_char nak_buffer[PPP_MRU]; /* where we construct a nak packet */ 
+
+/*
+ * Callbacks for fsm code.  (CI = Configuration Information)
+ */
+static void lcp_resetci (fsm*);                   /* Reset our CI */
+static int  lcp_cilen (fsm*);                     /* Return length of our CI */
+static void lcp_addci (fsm*, u_char*, int*);      /* Add our CI to pkt */
+static int  lcp_ackci (fsm*, u_char*, int);       /* Peer ack'd our CI */
+static int  lcp_nakci (fsm*, u_char*, int);       /* Peer nak'd our CI */
+static int  lcp_rejci (fsm*, u_char*, int);       /* Peer rej'd our CI */
+static int  lcp_reqci (fsm*, u_char*, int*, int); /* Rcv peer CI */
+static void lcp_up (fsm*);                        /* We're UP */
+static void lcp_down (fsm*);                      /* We're DOWN */
+static void lcp_starting (fsm*);                  /* We need lower layer up */
+static void lcp_finished (fsm*);                  /* We need lower layer down */
+static int  lcp_extcode (fsm*, int, u_char, u_char*, int);
+static void lcp_rprotrej (fsm*, u_char*, int);
+
+/*
+ * routines to send LCP echos to peer
+ */
+
+static void lcp_echo_lowerup (int);
+static void lcp_echo_lowerdown (int);
+static void LcpEchoTimeout (void*);
+static void lcp_received_echo_reply (fsm*, int, u_char*, int);
+static void LcpSendEchoRequest (fsm*);
+static void LcpLinkFailure (fsm*);
+static void LcpEchoCheck (fsm*);
+
+static fsm_callbacks lcp_callbacks = { /* LCP callback routines */
+  lcp_resetci,  /* Reset our Configuration Information */
+  lcp_cilen,    /* Length of our Configuration Information */
+  lcp_addci,    /* Add our Configuration Information */
+  lcp_ackci,    /* ACK our Configuration Information */
+  lcp_nakci,    /* NAK our Configuration Information */
+  lcp_rejci,    /* Reject our Configuration Information */
+  lcp_reqci,    /* Request peer's Configuration Information */
+  lcp_up,       /* Called when fsm reaches LS_OPENED state */
+  lcp_down,     /* Called when fsm leaves LS_OPENED state */
+  lcp_starting, /* Called when we want the lower layer up */
+  lcp_finished, /* Called when we want the lower layer down */
+  NULL,         /* Called when Protocol-Reject received */
+  NULL,         /* Retransmission is necessary */
+  lcp_extcode,  /* Called to handle LCP-specific codes */
+  "LCP"         /* String name of protocol */
+};
+
+/*
+ * Protocol entry points.
+ * Some of these are called directly.
+ */
+
+static void lcp_input (int, u_char *, int);
+static void lcp_protrej (int);
+
+struct protent lcp_protent = {
+    PPP_LCP,
+    lcp_init,
+    lcp_input,
+    lcp_protrej,
+    lcp_lowerup,
+    lcp_lowerdown,
+    lcp_open,
+    lcp_close,
+#if PPP_ADDITIONAL_CALLBACKS
+    lcp_printpkt,
+    NULL,
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+    1,
+    "LCP",
+#if PPP_ADDITIONAL_CALLBACKS
+    NULL,
+    NULL,
+    NULL
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+};
+
+int lcp_loopbackfail = DEFLOOPBACKFAIL;
+
+/*
+ * Length of each type of configuration option (in octets)
+ */
+#define CILEN_VOID  2
+#define CILEN_CHAR  3
+#define CILEN_SHORT 4 /* CILEN_VOID + sizeof(short) */
+#define CILEN_CHAP  5 /* CILEN_VOID + sizeof(short) + 1 */
+#define CILEN_LONG  6 /* CILEN_VOID + sizeof(long) */
+#define CILEN_LQR   8 /* CILEN_VOID + sizeof(short) + sizeof(long) */
+#define CILEN_CBCP  3
+
+#define CODENAME(x)  ((x) == CONFACK ? "ACK" : (x) == CONFNAK ? "NAK" : "REJ")
+
+#if 0 /* UNUSED */
+/*
+ * setescape - add chars to the set we escape on transmission.
+ */
+static int
+setescape(argv)
+    char **argv;
+{
+    int n, ret;
+    char *p, *endp;
+
+    p = *argv;
+    ret = 1;
+    while (*p) {
+      n = strtol(p, &endp, 16);
+      if (p == endp) {
+        option_error("escape parameter contains invalid hex number '%s'", p);
+        return 0;
+      }
+      p = endp;
+      if (n < 0 || n == 0x5E || n > 0xFF) {
+        option_error("can't escape character 0x%x", n);
+        ret = 0;
+      } else
+        xmit_accm[0][n >> 5] |= 1 << (n & 0x1F);
+      while (*p == ',' || *p == ' ')
+        ++p;
+    }
+    return ret;
+}
+#endif /* UNUSED */
+
+/*
+ * lcp_init - Initialize LCP.
+ */
+void
+lcp_init(int unit)
+{
+  fsm         *f  = &lcp_fsm[unit];
+  lcp_options *wo = &lcp_wantoptions[unit];
+  lcp_options *ao = &lcp_allowoptions[unit];
+
+  f->unit      = unit;
+  f->protocol  = PPP_LCP;
+  f->callbacks = &lcp_callbacks;
+
+  fsm_init(f);
+
+  wo->passive           = 0;
+  wo->silent            = 0;
+  wo->restart           = 0;               /* Set to 1 in kernels or multi-line implementations */
+  wo->neg_mru           = 1;
+  wo->mru               = PPP_DEFMRU;
+  wo->neg_asyncmap      = 1;
+  wo->asyncmap          = 0x00000000l;     /* Assume don't need to escape any ctl chars. */
+  wo->neg_chap          = 0;               /* Set to 1 on server */
+  wo->neg_upap          = 0;               /* Set to 1 on server */
+  wo->chap_mdtype       = CHAP_DIGEST_MD5;
+  wo->neg_magicnumber   = 1;
+  wo->neg_pcompression  = 1;
+  wo->neg_accompression = 1;
+  wo->neg_lqr           = 0;               /* no LQR implementation yet */
+  wo->neg_cbcp          = 0;
+
+  ao->neg_mru           = 1;
+  ao->mru               = PPP_MAXMRU;
+  ao->neg_asyncmap      = 1;
+  ao->asyncmap          = 0x00000000l;     /* Assume don't need to escape any ctl chars. */
+  ao->neg_chap          = (CHAP_SUPPORT != 0);
+  ao->chap_mdtype       = CHAP_DIGEST_MD5;
+  ao->neg_upap          = (PAP_SUPPORT != 0);
+  ao->neg_magicnumber   = 1;
+  ao->neg_pcompression  = 1;
+  ao->neg_accompression = 1;
+  ao->neg_lqr           = 0;               /* no LQR implementation yet */
+  ao->neg_cbcp          = (CBCP_SUPPORT != 0);
+
+  /* 
+   * Set transmit escape for the flag and escape characters plus anything
+   * set for the allowable options.
+   */
+  memset(xmit_accm[unit], 0, sizeof(xmit_accm[0]));
+  xmit_accm[unit][15] = 0x60;
+  xmit_accm[unit][0]  = (u_char)((ao->asyncmap        & 0xFF));
+  xmit_accm[unit][1]  = (u_char)((ao->asyncmap >> 8)  & 0xFF);
+  xmit_accm[unit][2]  = (u_char)((ao->asyncmap >> 16) & 0xFF);
+  xmit_accm[unit][3]  = (u_char)((ao->asyncmap >> 24) & 0xFF);
+  LCPDEBUG(LOG_INFO, ("lcp_init: xmit_accm=%X %X %X %X\n",
+        xmit_accm[unit][0],
+        xmit_accm[unit][1],
+        xmit_accm[unit][2],
+        xmit_accm[unit][3]));
+  
+  lcp_phase[unit] = PHASE_INITIALIZE;
+}
+
+
+/*
+ * lcp_open - LCP is allowed to come up.
+ */
+void
+lcp_open(int unit)
+{
+  fsm         *f  = &lcp_fsm[unit];
+  lcp_options *wo = &lcp_wantoptions[unit];
+
+  f->flags = 0;
+  if (wo->passive) {
+    f->flags |= OPT_PASSIVE;
+  }
+  if (wo->silent) {
+    f->flags |= OPT_SILENT;
+  }
+  fsm_open(f);
+
+  lcp_phase[unit] = PHASE_ESTABLISH;
+}
+
+
+/*
+ * lcp_close - Take LCP down.
+ */
+void
+lcp_close(int unit, char *reason)
+{
+  fsm *f = &lcp_fsm[unit];
+
+  if (lcp_phase[unit] != PHASE_DEAD) {
+    lcp_phase[unit] = PHASE_TERMINATE;
+  }
+  if (f->state == LS_STOPPED && f->flags & (OPT_PASSIVE|OPT_SILENT)) {
+    /*
+     * This action is not strictly according to the FSM in RFC1548,
+     * but it does mean that the program terminates if you do an
+     * lcp_close() in passive/silent mode when a connection hasn't
+     * been established.
+     */
+    f->state = LS_CLOSED;
+    lcp_finished(f);
+  } else {
+    fsm_close(f, reason);
+  }
+}
+
+
+/*
+ * lcp_lowerup - The lower layer is up.
+ */
+void
+lcp_lowerup(int unit)
+{
+  lcp_options *wo = &lcp_wantoptions[unit];
+
+  /*
+   * Don't use A/C or protocol compression on transmission,
+   * but accept A/C and protocol compressed packets
+   * if we are going to ask for A/C and protocol compression.
+   */
+  ppp_set_xaccm(unit, &xmit_accm[unit]);
+  ppp_send_config(unit, PPP_MRU, 0xffffffffl, 0, 0);
+  ppp_recv_config(unit, PPP_MRU, 0x00000000l,
+  wo->neg_pcompression, wo->neg_accompression);
+  peer_mru[unit] = PPP_MRU;
+  lcp_allowoptions[unit].asyncmap = (u_long)xmit_accm[unit][0]
+                                 | ((u_long)xmit_accm[unit][1] << 8)
+                                 | ((u_long)xmit_accm[unit][2] << 16)
+                                 | ((u_long)xmit_accm[unit][3] << 24);
+  LCPDEBUG(LOG_INFO, ("lcp_lowerup: asyncmap=%X %X %X %X\n",
+            xmit_accm[unit][3],
+            xmit_accm[unit][2],
+            xmit_accm[unit][1],
+            xmit_accm[unit][0]));
+
+  fsm_lowerup(&lcp_fsm[unit]);
+}
+
+
+/*
+ * lcp_lowerdown - The lower layer is down.
+ */
+void
+lcp_lowerdown(int unit)
+{
+  fsm_lowerdown(&lcp_fsm[unit]);
+}
+
+
+/*
+ * lcp_input - Input LCP packet.
+ */
+static void
+lcp_input(int unit, u_char *p, int len)
+{
+  fsm *f = &lcp_fsm[unit];
+
+  fsm_input(f, p, len);
+}
+
+
+/*
+ * lcp_extcode - Handle a LCP-specific code.
+ */
+static int
+lcp_extcode(fsm *f, int code, u_char id, u_char *inp, int len)
+{
+  u_char *magp;
+
+  switch( code ){
+    case PROTREJ:
+      lcp_rprotrej(f, inp, len);
+      break;
+  
+    case ECHOREQ:
+      if (f->state != LS_OPENED) {
+        break;
+      }
+      LCPDEBUG(LOG_INFO, ("lcp: Echo-Request, Rcvd id %d\n", id));
+      magp = inp;
+      PUTLONG(lcp_gotoptions[f->unit].magicnumber, magp);
+      fsm_sdata(f, ECHOREP, id, inp, len);
+      break;
+
+    case ECHOREP:
+      lcp_received_echo_reply(f, id, inp, len);
+      break;
+
+    case DISCREQ:
+      break;
+
+    default:
+      return 0;
+  }
+  return 1;
+}
+
+
+/*
+ * lcp_rprotrej - Receive an Protocol-Reject.
+ *
+ * Figure out which protocol is rejected and inform it.
+ */
+static void
+lcp_rprotrej(fsm *f, u_char *inp, int len)
+{
+  int i;
+  struct protent *protp;
+  u_short prot;
+
+  if (len < (int)sizeof (u_short)) {
+    LCPDEBUG(LOG_INFO, ("lcp_rprotrej: Rcvd short Protocol-Reject packet!\n"));
+    return;
+  }
+
+  GETSHORT(prot, inp);
+
+  LCPDEBUG(LOG_INFO, ("lcp_rprotrej: Rcvd Protocol-Reject packet for %x!\n", prot));
+
+  /*
+   * Protocol-Reject packets received in any state other than the LCP
+   * LS_OPENED state SHOULD be silently discarded.
+   */
+  if( f->state != LS_OPENED ) {
+    LCPDEBUG(LOG_INFO, ("Protocol-Reject discarded: LCP in state %d\n", f->state));
+    return;
+  }
+
+  /*
+   * Upcall the proper Protocol-Reject routine.
+   */
+  for (i = 0; (protp = ppp_protocols[i]) != NULL; ++i) {
+    if (protp->protocol == prot && protp->enabled_flag) {
+      (*protp->protrej)(f->unit);
+      return;
+    }
+  }
+
+  LCPDEBUG(LOG_WARNING, ("Protocol-Reject for unsupported protocol 0x%x\n", prot));
+}
+
+
+/*
+ * lcp_protrej - A Protocol-Reject was received.
+ */
+static void
+lcp_protrej(int unit)
+{
+  LWIP_UNUSED_ARG(unit);
+  /*
+   * Can't reject LCP!
+   */
+  LCPDEBUG(LOG_WARNING, ("lcp_protrej: Received Protocol-Reject for LCP!\n"));
+  fsm_protreject(&lcp_fsm[unit]);
+}
+
+
+/*
+ * lcp_sprotrej - Send a Protocol-Reject for some protocol.
+ */
+void
+lcp_sprotrej(int unit, u_char *p, int len)
+{
+  /*
+   * Send back the protocol and the information field of the
+   * rejected packet.  We only get here if LCP is in the LS_OPENED state.
+   */
+
+  fsm_sdata(&lcp_fsm[unit], PROTREJ, ++lcp_fsm[unit].id, p, len);
+}
+
+
+/*
+ * lcp_resetci - Reset our CI.
+ */
+static void
+lcp_resetci(fsm *f)
+{
+  lcp_wantoptions[f->unit].magicnumber = magic();
+  lcp_wantoptions[f->unit].numloops = 0;
+  lcp_gotoptions[f->unit] = lcp_wantoptions[f->unit];
+  peer_mru[f->unit] = PPP_MRU;
+  auth_reset(f->unit);
+}
+
+
+/*
+ * lcp_cilen - Return length of our CI.
+ */
+static int
+lcp_cilen(fsm *f)
+{
+  lcp_options *go = &lcp_gotoptions[f->unit];
+
+#define LENCIVOID(neg)  ((neg) ? CILEN_VOID : 0)
+#define LENCICHAP(neg)  ((neg) ? CILEN_CHAP : 0)
+#define LENCISHORT(neg) ((neg) ? CILEN_SHORT : 0)
+#define LENCILONG(neg)  ((neg) ? CILEN_LONG : 0)
+#define LENCILQR(neg)   ((neg) ? CILEN_LQR: 0)
+#define LENCICBCP(neg)  ((neg) ? CILEN_CBCP: 0)
+  /*
+   * NB: we only ask for one of CHAP and UPAP, even if we will
+   * accept either.
+   */
+  return (LENCISHORT(go->neg_mru && go->mru != PPP_DEFMRU) +
+          LENCILONG(go->neg_asyncmap && go->asyncmap != 0xFFFFFFFFl) +
+          LENCICHAP(go->neg_chap) +
+          LENCISHORT(!go->neg_chap && go->neg_upap) +
+          LENCILQR(go->neg_lqr) +
+          LENCICBCP(go->neg_cbcp) +
+          LENCILONG(go->neg_magicnumber) +
+          LENCIVOID(go->neg_pcompression) +
+          LENCIVOID(go->neg_accompression));
+}
+
+
+/*
+ * lcp_addci - Add our desired CIs to a packet.
+ */
+static void
+lcp_addci(fsm *f, u_char *ucp, int *lenp)
+{
+  lcp_options *go = &lcp_gotoptions[f->unit];
+  u_char *start_ucp = ucp;
+
+#define ADDCIVOID(opt, neg) \
+  if (neg) { \
+    LCPDEBUG(LOG_INFO, ("lcp_addci: opt=%d\n", opt)); \
+    PUTCHAR(opt, ucp); \
+    PUTCHAR(CILEN_VOID, ucp); \
+  }
+#define ADDCISHORT(opt, neg, val) \
+  if (neg) { \
+    LCPDEBUG(LOG_INFO, ("lcp_addci: INT opt=%d %X\n", opt, val)); \
+    PUTCHAR(opt, ucp); \
+    PUTCHAR(CILEN_SHORT, ucp); \
+    PUTSHORT(val, ucp); \
+  }
+#define ADDCICHAP(opt, neg, val, digest) \
+  if (neg) { \
+    LCPDEBUG(LOG_INFO, ("lcp_addci: CHAP opt=%d %X\n", opt, val)); \
+    PUTCHAR(opt, ucp); \
+    PUTCHAR(CILEN_CHAP, ucp); \
+    PUTSHORT(val, ucp); \
+    PUTCHAR(digest, ucp); \
+  }
+#define ADDCILONG(opt, neg, val) \
+  if (neg) { \
+    LCPDEBUG(LOG_INFO, ("lcp_addci: L opt=%d %lX\n", opt, val)); \
+    PUTCHAR(opt, ucp); \
+    PUTCHAR(CILEN_LONG, ucp); \
+    PUTLONG(val, ucp); \
+  }
+#define ADDCILQR(opt, neg, val) \
+  if (neg) { \
+    LCPDEBUG(LOG_INFO, ("lcp_addci: LQR opt=%d %lX\n", opt, val)); \
+    PUTCHAR(opt, ucp); \
+    PUTCHAR(CILEN_LQR, ucp); \
+    PUTSHORT(PPP_LQR, ucp); \
+    PUTLONG(val, ucp); \
+  }
+#define ADDCICHAR(opt, neg, val) \
+  if (neg) { \
+    LCPDEBUG(LOG_INFO, ("lcp_addci: CHAR opt=%d %X '%z'\n", opt, val, val)); \
+    PUTCHAR(opt, ucp); \
+    PUTCHAR(CILEN_CHAR, ucp); \
+    PUTCHAR(val, ucp); \
+  }
+
+  ADDCISHORT(CI_MRU, go->neg_mru && go->mru != PPP_DEFMRU, go->mru);
+  ADDCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFFl, go->asyncmap);
+  ADDCICHAP(CI_AUTHTYPE, go->neg_chap, PPP_CHAP, go->chap_mdtype);
+  ADDCISHORT(CI_AUTHTYPE, !go->neg_chap && go->neg_upap, PPP_PAP);
+  ADDCILQR(CI_QUALITY, go->neg_lqr, go->lqr_period);
+  ADDCICHAR(CI_CALLBACK, go->neg_cbcp, CBCP_OPT);
+  ADDCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber);
+  ADDCIVOID(CI_PCOMPRESSION, go->neg_pcompression);
+  ADDCIVOID(CI_ACCOMPRESSION, go->neg_accompression);
+
+  if (ucp - start_ucp != *lenp) {
+    /* this should never happen, because peer_mtu should be 1500 */
+    LCPDEBUG(LOG_ERR, ("Bug in lcp_addci: wrong length\n"));
+  }
+}
+
+
+/*
+ * lcp_ackci - Ack our CIs.
+ * This should not modify any state if the Ack is bad.
+ *
+ * Returns:
+ *  0 - Ack was bad.
+ *  1 - Ack was good.
+ */
+static int
+lcp_ackci(fsm *f, u_char *p, int len)
+{
+  lcp_options *go = &lcp_gotoptions[f->unit];
+  u_char cilen, citype, cichar;
+  u_short cishort;
+  u32_t cilong;
+
+  /*
+   * CIs must be in exactly the same order that we sent.
+   * Check packet length and CI length at each step.
+   * If we find any deviations, then this packet is bad.
+   */
+#define ACKCIVOID(opt, neg) \
+  if (neg) { \
+    if ((len -= CILEN_VOID) < 0) \
+      goto bad; \
+    GETCHAR(citype, p); \
+    GETCHAR(cilen, p); \
+    if (cilen != CILEN_VOID || citype != opt) \
+      goto bad; \
+  }
+#define ACKCISHORT(opt, neg, val) \
+  if (neg) { \
+    if ((len -= CILEN_SHORT) < 0) \
+      goto bad; \
+    GETCHAR(citype, p); \
+    GETCHAR(cilen, p); \
+    if (cilen != CILEN_SHORT || citype != opt) \
+      goto bad; \
+    GETSHORT(cishort, p); \
+    if (cishort != val) \
+      goto bad; \
+  }
+#define ACKCICHAR(opt, neg, val) \
+  if (neg) { \
+    if ((len -= CILEN_CHAR) < 0) \
+      goto bad; \
+    GETCHAR(citype, p); \
+    GETCHAR(cilen, p); \
+    if (cilen != CILEN_CHAR || citype != opt) \
+      goto bad; \
+    GETCHAR(cichar, p); \
+    if (cichar != val) \
+      goto bad; \
+  }
+#define ACKCICHAP(opt, neg, val, digest) \
+  if (neg) { \
+    if ((len -= CILEN_CHAP) < 0) \
+      goto bad; \
+    GETCHAR(citype, p); \
+    GETCHAR(cilen, p); \
+    if (cilen != CILEN_CHAP || citype != opt) \
+      goto bad; \
+    GETSHORT(cishort, p); \
+    if (cishort != val) \
+      goto bad; \
+    GETCHAR(cichar, p); \
+    if (cichar != digest) \
+      goto bad; \
+  }
+#define ACKCILONG(opt, neg, val) \
+  if (neg) { \
+    if ((len -= CILEN_LONG) < 0) \
+      goto bad; \
+    GETCHAR(citype, p); \
+    GETCHAR(cilen, p); \
+    if (cilen != CILEN_LONG ||  citype != opt) \
+      goto bad; \
+    GETLONG(cilong, p); \
+    if (cilong != val) \
+      goto bad; \
+  }
+#define ACKCILQR(opt, neg, val) \
+  if (neg) { \
+    if ((len -= CILEN_LQR) < 0) \
+      goto bad; \
+    GETCHAR(citype, p); \
+    GETCHAR(cilen, p); \
+    if (cilen != CILEN_LQR || citype != opt) \
+      goto bad; \
+    GETSHORT(cishort, p); \
+    if (cishort != PPP_LQR) \
+      goto bad; \
+    GETLONG(cilong, p); \
+    if (cilong != val) \
+      goto bad; \
+  }
+
+  ACKCISHORT(CI_MRU, go->neg_mru && go->mru != PPP_DEFMRU, go->mru);
+  ACKCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFFl, go->asyncmap);
+  ACKCICHAP(CI_AUTHTYPE, go->neg_chap, PPP_CHAP, go->chap_mdtype);
+  ACKCISHORT(CI_AUTHTYPE, !go->neg_chap && go->neg_upap, PPP_PAP);
+  ACKCILQR(CI_QUALITY, go->neg_lqr, go->lqr_period);
+  ACKCICHAR(CI_CALLBACK, go->neg_cbcp, CBCP_OPT);
+  ACKCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber);
+  ACKCIVOID(CI_PCOMPRESSION, go->neg_pcompression);
+  ACKCIVOID(CI_ACCOMPRESSION, go->neg_accompression);
+
+  /*
+   * If there are any remaining CIs, then this packet is bad.
+   */
+  if (len != 0) {
+    goto bad;
+  }
+  LCPDEBUG(LOG_INFO, ("lcp_acki: Ack\n"));
+  return (1);
+bad:
+  LCPDEBUG(LOG_WARNING, ("lcp_acki: received bad Ack!\n"));
+  return (0);
+}
+
+
+/*
+ * lcp_nakci - Peer has sent a NAK for some of our CIs.
+ * This should not modify any state if the Nak is bad
+ * or if LCP is in the LS_OPENED state.
+ *
+ * Returns:
+ *  0 - Nak was bad.
+ *  1 - Nak was good.
+ */
+static int
+lcp_nakci(fsm *f, u_char *p, int len)
+{
+  lcp_options *go = &lcp_gotoptions[f->unit];
+  lcp_options *wo = &lcp_wantoptions[f->unit];
+  u_char citype, cichar, *next;
+  u_short cishort;
+  u32_t cilong;
+  lcp_options no;     /* options we've seen Naks for */
+  lcp_options try;    /* options to request next time */
+  int looped_back = 0;
+  int cilen;
+
+  BZERO(&no, sizeof(no));
+  try = *go;
+
+  /*
+   * Any Nak'd CIs must be in exactly the same order that we sent.
+   * Check packet length and CI length at each step.
+   * If we find any deviations, then this packet is bad.
+   */
+#define NAKCIVOID(opt, neg, code) \
+  if (go->neg && \
+      len >= CILEN_VOID && \
+      p[1] == CILEN_VOID && \
+      p[0] == opt) { \
+    len -= CILEN_VOID; \
+    INCPTR(CILEN_VOID, p); \
+    no.neg = 1; \
+    code \
+  }
+#define NAKCICHAP(opt, neg, code) \
+  if (go->neg && \
+      len >= CILEN_CHAP && \
+      p[1] == CILEN_CHAP && \
+      p[0] == opt) { \
+    len -= CILEN_CHAP; \
+    INCPTR(2, p); \
+    GETSHORT(cishort, p); \
+    GETCHAR(cichar, p); \
+    no.neg = 1; \
+    code \
+  }
+#define NAKCICHAR(opt, neg, code) \
+  if (go->neg && \
+      len >= CILEN_CHAR && \
+      p[1] == CILEN_CHAR && \
+      p[0] == opt) { \
+    len -= CILEN_CHAR; \
+    INCPTR(2, p); \
+    GETCHAR(cichar, p); \
+    no.neg = 1; \
+    code \
+  }
+#define NAKCISHORT(opt, neg, code) \
+  if (go->neg && \
+      len >= CILEN_SHORT && \
+      p[1] == CILEN_SHORT && \
+      p[0] == opt) { \
+    len -= CILEN_SHORT; \
+    INCPTR(2, p); \
+    GETSHORT(cishort, p); \
+    no.neg = 1; \
+    code \
+  }
+#define NAKCILONG(opt, neg, code) \
+  if (go->neg && \
+      len >= CILEN_LONG && \
+      p[1] == CILEN_LONG && \
+      p[0] == opt) { \
+    len -= CILEN_LONG; \
+    INCPTR(2, p); \
+    GETLONG(cilong, p); \
+    no.neg = 1; \
+    code \
+  }
+#define NAKCILQR(opt, neg, code) \
+  if (go->neg && \
+      len >= CILEN_LQR && \
+      p[1] == CILEN_LQR && \
+      p[0] == opt) { \
+    len -= CILEN_LQR; \
+    INCPTR(2, p); \
+    GETSHORT(cishort, p); \
+    GETLONG(cilong, p); \
+    no.neg = 1; \
+    code \
+  }
+
+  /*
+   * We don't care if they want to send us smaller packets than
+   * we want.  Therefore, accept any MRU less than what we asked for,
+   * but then ignore the new value when setting the MRU in the kernel.
+   * If they send us a bigger MRU than what we asked, accept it, up to
+   * the limit of the default MRU we'd get if we didn't negotiate.
+   */
+  if (go->neg_mru && go->mru != PPP_DEFMRU) {
+    NAKCISHORT(CI_MRU, neg_mru,
+      if (cishort <= wo->mru || cishort < PPP_DEFMRU) {
+        try.mru = cishort;
+      }
+    );
+  }
+
+  /*
+   * Add any characters they want to our (receive-side) asyncmap.
+   */
+  if (go->neg_asyncmap && go->asyncmap != 0xFFFFFFFFl) {
+    NAKCILONG(CI_ASYNCMAP, neg_asyncmap,
+      try.asyncmap = go->asyncmap | cilong;
+    );
+  }
+
+  /*
+   * If they've nak'd our authentication-protocol, check whether
+   * they are proposing a different protocol, or a different
+   * hash algorithm for CHAP.
+   */
+  if ((go->neg_chap || go->neg_upap)
+      && len >= CILEN_SHORT
+      && p[0] == CI_AUTHTYPE && p[1] >= CILEN_SHORT && p[1] <= len) {
+    cilen = p[1];
+    len -= cilen;
+    no.neg_chap = go->neg_chap;
+    no.neg_upap = go->neg_upap;
+    INCPTR(2, p);
+    GETSHORT(cishort, p);
+    if (cishort == PPP_PAP && cilen == CILEN_SHORT) {
+      /*
+       * If we were asking for CHAP, they obviously don't want to do it.
+       * If we weren't asking for CHAP, then we were asking for PAP,
+       * in which case this Nak is bad.
+       */
+      if (!go->neg_chap) {
+        goto bad;
+      }
+      try.neg_chap = 0;
+    
+    } else if (cishort == PPP_CHAP && cilen == CILEN_CHAP) {
+      GETCHAR(cichar, p);
+      if (go->neg_chap) {
+        /*
+         * We were asking for CHAP/MD5; they must want a different
+         * algorithm.  If they can't do MD5, we'll have to stop
+         * asking for CHAP.
+         */
+        if (cichar != go->chap_mdtype) {
+          try.neg_chap = 0;
+        }
+      } else {
+        /*
+         * Stop asking for PAP if we were asking for it.
+         */
+        try.neg_upap = 0;
+      }
+    
+    } else {
+      /*
+       * We don't recognize what they're suggesting.
+       * Stop asking for what we were asking for.
+       */
+      if (go->neg_chap) {
+        try.neg_chap = 0;
+      } else {
+        try.neg_upap = 0;
+      }
+      p += cilen - CILEN_SHORT;
+    }
+  }
+
+  /*
+   * If they can't cope with our link quality protocol, we'll have
+   * to stop asking for LQR.  We haven't got any other protocol.
+   * If they Nak the reporting period, take their value XXX ?
+   */
+  NAKCILQR(CI_QUALITY, neg_lqr,
+    if (cishort != PPP_LQR) {
+      try.neg_lqr = 0;
+    } else {
+      try.lqr_period = cilong;
+    }
+  );
+
+  /*
+   * Only implementing CBCP...not the rest of the callback options
+   */
+  NAKCICHAR(CI_CALLBACK, neg_cbcp,
+    try.neg_cbcp = 0;
+  );
+
+  /*
+   * Check for a looped-back line.
+   */
+  NAKCILONG(CI_MAGICNUMBER, neg_magicnumber,
+    try.magicnumber = magic();
+    looped_back = 1;
+  );
+
+  /*
+   * Peer shouldn't send Nak for protocol compression or
+   * address/control compression requests; they should send
+   * a Reject instead.  If they send a Nak, treat it as a Reject.
+   */
+  NAKCIVOID(CI_PCOMPRESSION, neg_pcompression,
+    try.neg_pcompression = 0;
+  );
+  NAKCIVOID(CI_ACCOMPRESSION, neg_accompression,
+    try.neg_accompression = 0;
+  );
+
+  /*
+   * There may be remaining CIs, if the peer is requesting negotiation
+   * on an option that we didn't include in our request packet.
+   * If we see an option that we requested, or one we've already seen
+   * in this packet, then this packet is bad.
+   * If we wanted to respond by starting to negotiate on the requested
+   * option(s), we could, but we don't, because except for the
+   * authentication type and quality protocol, if we are not negotiating
+   * an option, it is because we were told not to.
+   * For the authentication type, the Nak from the peer means
+   * `let me authenticate myself with you' which is a bit pointless.
+   * For the quality protocol, the Nak means `ask me to send you quality
+   * reports', but if we didn't ask for them, we don't want them.
+   * An option we don't recognize represents the peer asking to
+   * negotiate some option we don't support, so ignore it.
+   */
+  while (len > CILEN_VOID) {
+    GETCHAR(citype, p);
+    GETCHAR(cilen, p);
+    if (cilen < CILEN_VOID || (len -= cilen) < 0) {
+      goto bad;
+    }
+    next = p + cilen - 2;
+
+    switch (citype) {
+      case CI_MRU:
+        if ((go->neg_mru && go->mru != PPP_DEFMRU)
+            || no.neg_mru || cilen != CILEN_SHORT) {
+          goto bad;
+        }
+        GETSHORT(cishort, p);
+        if (cishort < PPP_DEFMRU) {
+          try.mru = cishort;
+        }
+        break;
+      case CI_ASYNCMAP:
+        if ((go->neg_asyncmap && go->asyncmap != 0xFFFFFFFFl)
+            || no.neg_asyncmap || cilen != CILEN_LONG) {
+          goto bad;
+        }
+        break;
+      case CI_AUTHTYPE:
+        if (go->neg_chap || no.neg_chap || go->neg_upap || no.neg_upap) {
+          goto bad;
+        }
+        break;
+      case CI_MAGICNUMBER:
+        if (go->neg_magicnumber || no.neg_magicnumber ||
+            cilen != CILEN_LONG) {
+          goto bad;
+        }
+        break;
+      case CI_PCOMPRESSION:
+        if (go->neg_pcompression || no.neg_pcompression
+            || cilen != CILEN_VOID) {
+          goto bad;
+        }
+        break;
+      case CI_ACCOMPRESSION:
+        if (go->neg_accompression || no.neg_accompression
+            || cilen != CILEN_VOID) {
+          goto bad;
+        }
+        break;
+      case CI_QUALITY:
+        if (go->neg_lqr || no.neg_lqr || cilen != CILEN_LQR) {
+          goto bad;
+        }
+        break;
+    }
+    p = next;
+  }
+
+  /* If there is still anything left, this packet is bad. */
+  if (len != 0) {
+    goto bad;
+  }
+
+  /*
+  * OK, the Nak is good.  Now we can update state.
+  */
+  if (f->state != LS_OPENED) {
+    if (looped_back) {
+      if (++try.numloops >= lcp_loopbackfail) {
+        LCPDEBUG(LOG_NOTICE, ("Serial line is looped back.\n"));
+        lcp_close(f->unit, "Loopback detected");
+      }
+    } else {
+      try.numloops = 0;
+    }
+    *go = try;
+  }
+
+  return 1;
+
+bad:
+  LCPDEBUG(LOG_WARNING, ("lcp_nakci: received bad Nak!\n"));
+  return 0;
+}
+
+
+/*
+ * lcp_rejci - Peer has Rejected some of our CIs.
+ * This should not modify any state if the Reject is bad
+ * or if LCP is in the LS_OPENED state.
+ *
+ * Returns:
+ *  0 - Reject was bad.
+ *  1 - Reject was good.
+ */
+static int
+lcp_rejci(fsm *f, u_char *p, int len)
+{
+  lcp_options *go = &lcp_gotoptions[f->unit];
+  u_char cichar;
+  u_short cishort;
+  u32_t cilong;
+  lcp_options try; /* options to request next time */
+
+  try = *go;
+
+  /*
+   * Any Rejected CIs must be in exactly the same order that we sent.
+   * Check packet length and CI length at each step.
+   * If we find any deviations, then this packet is bad.
+   */
+#define REJCIVOID(opt, neg) \
+  if (go->neg && \
+      len >= CILEN_VOID && \
+      p[1] == CILEN_VOID && \
+      p[0] == opt) { \
+    len -= CILEN_VOID; \
+    INCPTR(CILEN_VOID, p); \
+    try.neg = 0; \
+    LCPDEBUG(LOG_INFO, ("lcp_rejci: void opt %d rejected\n", opt)); \
+  }
+#define REJCISHORT(opt, neg, val) \
+  if (go->neg && \
+      len >= CILEN_SHORT && \
+      p[1] == CILEN_SHORT && \
+      p[0] == opt) { \
+    len -= CILEN_SHORT; \
+    INCPTR(2, p); \
+    GETSHORT(cishort, p); \
+    /* Check rejected value. */ \
+    if (cishort != val) { \
+      goto bad; \
+    } \
+    try.neg = 0; \
+    LCPDEBUG(LOG_INFO, ("lcp_rejci: short opt %d rejected\n", opt)); \
+  }
+#define REJCICHAP(opt, neg, val, digest) \
+  if (go->neg && \
+      len >= CILEN_CHAP && \
+      p[1] == CILEN_CHAP && \
+      p[0] == opt) { \
+    len -= CILEN_CHAP; \
+    INCPTR(2, p); \
+    GETSHORT(cishort, p); \
+    GETCHAR(cichar, p); \
+    /* Check rejected value. */ \
+    if (cishort != val || cichar != digest) { \
+      goto bad; \
+    } \
+    try.neg = 0; \
+    try.neg_upap = 0; \
+    LCPDEBUG(LOG_INFO, ("lcp_rejci: chap opt %d rejected\n", opt)); \
+  }
+#define REJCILONG(opt, neg, val) \
+  if (go->neg && \
+      len >= CILEN_LONG && \
+      p[1] == CILEN_LONG && \
+      p[0] == opt) { \
+    len -= CILEN_LONG; \
+    INCPTR(2, p); \
+    GETLONG(cilong, p); \
+    /* Check rejected value. */ \
+    if (cilong != val) { \
+      goto bad; \
+    } \
+    try.neg = 0; \
+    LCPDEBUG(LOG_INFO, ("lcp_rejci: long opt %d rejected\n", opt)); \
+  }
+#define REJCILQR(opt, neg, val) \
+  if (go->neg && \
+      len >= CILEN_LQR && \
+      p[1] == CILEN_LQR && \
+      p[0] == opt) { \
+    len -= CILEN_LQR; \
+    INCPTR(2, p); \
+    GETSHORT(cishort, p); \
+    GETLONG(cilong, p); \
+    /* Check rejected value. */ \
+    if (cishort != PPP_LQR || cilong != val) { \
+      goto bad; \
+    } \
+    try.neg = 0; \
+    LCPDEBUG(LOG_INFO, ("lcp_rejci: LQR opt %d rejected\n", opt)); \
+  }
+#define REJCICBCP(opt, neg, val) \
+  if (go->neg && \
+      len >= CILEN_CBCP && \
+      p[1] == CILEN_CBCP && \
+      p[0] == opt) { \
+    len -= CILEN_CBCP; \
+    INCPTR(2, p); \
+    GETCHAR(cichar, p); \
+    /* Check rejected value. */ \
+    if (cichar != val) { \
+      goto bad; \
+    } \
+    try.neg = 0; \
+    LCPDEBUG(LOG_INFO, ("lcp_rejci: Callback opt %d rejected\n", opt)); \
+  }
+  
+  REJCISHORT(CI_MRU, neg_mru, go->mru);
+  REJCILONG(CI_ASYNCMAP, neg_asyncmap, go->asyncmap);
+  REJCICHAP(CI_AUTHTYPE, neg_chap, PPP_CHAP, go->chap_mdtype);
+  if (!go->neg_chap) {
+    REJCISHORT(CI_AUTHTYPE, neg_upap, PPP_PAP);
+  }
+  REJCILQR(CI_QUALITY, neg_lqr, go->lqr_period);
+  REJCICBCP(CI_CALLBACK, neg_cbcp, CBCP_OPT);
+  REJCILONG(CI_MAGICNUMBER, neg_magicnumber, go->magicnumber);
+  REJCIVOID(CI_PCOMPRESSION, neg_pcompression);
+  REJCIVOID(CI_ACCOMPRESSION, neg_accompression);
+  
+  /*
+   * If there are any remaining CIs, then this packet is bad.
+   */
+  if (len != 0) {
+    goto bad;
+  }
+  /*
+   * Now we can update state.
+   */
+  if (f->state != LS_OPENED) {
+    *go = try;
+  }
+  return 1;
+  
+bad:
+  LCPDEBUG(LOG_WARNING, ("lcp_rejci: received bad Reject!\n"));
+  return 0;
+}
+
+
+/*
+ * lcp_reqci - Check the peer's requested CIs and send appropriate response.
+ *
+ * Returns: CONFACK, CONFNAK or CONFREJ and input packet modified
+ * appropriately.  If reject_if_disagree is non-zero, doesn't return
+ * CONFNAK; returns CONFREJ if it can't return CONFACK.
+ */
+static int
+lcp_reqci(fsm *f, 
+          u_char *inp,    /* Requested CIs */
+          int *lenp,      /* Length of requested CIs */
+          int reject_if_disagree)
+{
+  lcp_options *go = &lcp_gotoptions[f->unit];
+  lcp_options *ho = &lcp_hisoptions[f->unit];
+  lcp_options *ao = &lcp_allowoptions[f->unit];
+  u_char *cip, *next;         /* Pointer to current and next CIs */
+  int cilen, citype;          /* Parsed len, type */
+  u_char cichar;              /* Parsed char value */
+  u_short cishort;            /* Parsed short value */
+  u32_t cilong;               /* Parse long value */
+  int rc = CONFACK;           /* Final packet return code */
+  int orc;                    /* Individual option return code */
+  u_char *p;                  /* Pointer to next char to parse */
+  u_char *rejp;               /* Pointer to next char in reject frame */
+  u_char *nakp;               /* Pointer to next char in Nak frame */
+  int l = *lenp;              /* Length left */
+#if TRACELCP > 0
+  char traceBuf[80];
+  size_t traceNdx = 0;
+#endif
+
+  /*
+   * Reset all his options.
+   */
+  BZERO(ho, sizeof(*ho));
+
+  /*
+   * Process all his options.
+   */
+  next = inp;
+  nakp = nak_buffer;
+  rejp = inp;
+  while (l) {
+    orc = CONFACK;      /* Assume success */
+    cip = p = next;     /* Remember begining of CI */
+    if (l < 2 ||        /* Not enough data for CI header or */
+        p[1] < 2 ||     /*  CI length too small or */
+        p[1] > l) {     /*  CI length too big? */
+      LCPDEBUG(LOG_WARNING, ("lcp_reqci: bad CI length!\n"));
+      orc = CONFREJ;    /* Reject bad CI */
+      cilen = l;        /* Reject till end of packet */
+      l = 0;            /* Don't loop again */
+      citype = 0;
+      goto endswitch;
+    }
+    GETCHAR(citype, p); /* Parse CI type */
+    GETCHAR(cilen, p);  /* Parse CI length */
+    l -= cilen;         /* Adjust remaining length */
+    next += cilen;      /* Step to next CI */
+
+    switch (citype) {   /* Check CI type */
+      case CI_MRU:
+        if (!ao->neg_mru) {    /* Allow option? */
+          LCPDEBUG(LOG_INFO, ("lcp_reqci: Reject MRU - not allowed\n"));
+          orc = CONFREJ;    /* Reject CI */
+          break;
+        } else if (cilen != CILEN_SHORT) {  /* Check CI length */
+          LCPDEBUG(LOG_INFO, ("lcp_reqci: Reject MRU - bad length\n"));
+          orc = CONFREJ;    /* Reject CI */
+          break;
+        }
+        GETSHORT(cishort, p);  /* Parse MRU */
+
+        /*
+         * He must be able to receive at least our minimum.
+         * No need to check a maximum.  If he sends a large number,
+         * we'll just ignore it.
+         */
+        if (cishort < PPP_MINMRU) {
+          LCPDEBUG(LOG_INFO, ("lcp_reqci: Nak - MRU too small\n"));
+          orc = CONFNAK;    /* Nak CI */
+          PUTCHAR(CI_MRU, nakp);
+          PUTCHAR(CILEN_SHORT, nakp);
+          PUTSHORT(PPP_MINMRU, nakp);  /* Give him a hint */
+          break;
+        }
+        ho->neg_mru = 1;    /* Remember he sent MRU */
+        ho->mru = cishort;    /* And remember value */
+#if TRACELCP > 0
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " MRU %d", cishort);
+        traceNdx = strlen(traceBuf);
+#endif
+        break;
+
+      case CI_ASYNCMAP:
+        if (!ao->neg_asyncmap) {
+          LCPDEBUG(LOG_INFO, ("lcp_reqci: Reject ASYNCMAP not allowed\n"));
+          orc = CONFREJ;
+          break;
+        } else if (cilen != CILEN_LONG) {
+          LCPDEBUG(LOG_INFO, ("lcp_reqci: Reject ASYNCMAP bad length\n"));
+          orc = CONFREJ;
+          break;
+        }
+        GETLONG(cilong, p);
+        
+        /*
+         * Asyncmap must have set at least the bits
+         * which are set in lcp_allowoptions[unit].asyncmap.
+         */
+        if ((ao->asyncmap & ~cilong) != 0) {
+          LCPDEBUG(LOG_INFO, ("lcp_reqci: Nak ASYNCMAP %lX missing %lX\n", 
+                    cilong, ao->asyncmap));
+          orc = CONFNAK;
+          PUTCHAR(CI_ASYNCMAP, nakp);
+          PUTCHAR(CILEN_LONG, nakp);
+          PUTLONG(ao->asyncmap | cilong, nakp);
+          break;
+        }
+        ho->neg_asyncmap = 1;
+        ho->asyncmap = cilong;
+#if TRACELCP > 0
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " ASYNCMAP=%lX", cilong);
+        traceNdx = strlen(traceBuf);
+#endif
+        break;
+
+      case CI_AUTHTYPE:
+        if (cilen < CILEN_SHORT) {
+          LCPDEBUG(LOG_INFO, ("lcp_reqci: Reject AUTHTYPE missing arg\n"));
+          orc = CONFREJ;
+          break;
+        } else if (!(ao->neg_upap || ao->neg_chap)) {
+          /*
+           * Reject the option if we're not willing to authenticate.
+           */
+          LCPDEBUG(LOG_INFO, ("lcp_reqci: Reject AUTHTYPE not allowed\n"));
+          orc = CONFREJ;
+          break;
+        }
+        GETSHORT(cishort, p);
+        
+        /*
+         * Authtype must be UPAP or CHAP.
+         *
+         * Note: if both ao->neg_upap and ao->neg_chap are set,
+         * and the peer sends a Configure-Request with two
+         * authenticate-protocol requests, one for CHAP and one
+         * for UPAP, then we will reject the second request.
+         * Whether we end up doing CHAP or UPAP depends then on
+         * the ordering of the CIs in the peer's Configure-Request.
+         */
+        
+        if (cishort == PPP_PAP) {
+          if (ho->neg_chap) {  /* we've already accepted CHAP */
+            LCPDEBUG(LOG_WARNING, ("lcp_reqci: Reject AUTHTYPE PAP already accepted\n"));
+            orc = CONFREJ;
+            break;
+          } else if (cilen != CILEN_SHORT) {
+            LCPDEBUG(LOG_WARNING, ("lcp_reqci: Reject AUTHTYPE PAP bad len\n"));
+            orc = CONFREJ;
+            break;
+          }
+          if (!ao->neg_upap) {  /* we don't want to do PAP */
+            LCPDEBUG(LOG_WARNING, ("lcp_reqci: Nak AUTHTYPE PAP not allowed\n"));
+            orc = CONFNAK;  /* NAK it and suggest CHAP */
+            PUTCHAR(CI_AUTHTYPE, nakp);
+            PUTCHAR(CILEN_CHAP, nakp);
+            PUTSHORT(PPP_CHAP, nakp);
+            PUTCHAR(ao->chap_mdtype, nakp);
+            break;
+          }
+          ho->neg_upap = 1;
+#if TRACELCP > 0
+          snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " PAP (%X)", cishort);
+          traceNdx = strlen(traceBuf);
+#endif
+          break;
+        }
+        if (cishort == PPP_CHAP) {
+          if (ho->neg_upap) {  /* we've already accepted PAP */
+            LCPDEBUG(LOG_WARNING, ("lcp_reqci: Reject AUTHTYPE CHAP accepted PAP\n"));
+            orc = CONFREJ;
+            break;
+          } else if (cilen != CILEN_CHAP) {
+            LCPDEBUG(LOG_WARNING, ("lcp_reqci: Reject AUTHTYPE CHAP bad len\n"));
+            orc = CONFREJ;
+            break;
+          }
+          if (!ao->neg_chap) {  /* we don't want to do CHAP */
+            LCPDEBUG(LOG_WARNING, ("lcp_reqci: Nak AUTHTYPE CHAP not allowed\n"));
+            orc = CONFNAK;  /* NAK it and suggest PAP */
+            PUTCHAR(CI_AUTHTYPE, nakp);
+            PUTCHAR(CILEN_SHORT, nakp);
+            PUTSHORT(PPP_PAP, nakp);
+            break;
+          }
+          GETCHAR(cichar, p);  /* get digest type*/
+          if (cichar != CHAP_DIGEST_MD5
+#if MSCHAP_SUPPORT
+              && cichar != CHAP_MICROSOFT
+#endif
+          ) {
+            LCPDEBUG(LOG_WARNING, ("lcp_reqci: Nak AUTHTYPE CHAP digest=%d\n", (int)cichar));
+            orc = CONFNAK;
+            PUTCHAR(CI_AUTHTYPE, nakp);
+            PUTCHAR(CILEN_CHAP, nakp);
+            PUTSHORT(PPP_CHAP, nakp);
+            PUTCHAR(ao->chap_mdtype, nakp);
+            break;
+          }
+#if TRACELCP > 0
+          snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " CHAP %X,%d", cishort, (int)cichar);
+          traceNdx = strlen(traceBuf);
+#endif
+          ho->chap_mdtype = cichar; /* save md type */
+          ho->neg_chap = 1;
+          break;
+        }
+        
+        /*
+         * We don't recognize the protocol they're asking for.
+         * Nak it with something we're willing to do.
+         * (At this point we know ao->neg_upap || ao->neg_chap.)
+         */
+        orc = CONFNAK;
+        PUTCHAR(CI_AUTHTYPE, nakp);
+        if (ao->neg_chap) {
+          LCPDEBUG(LOG_WARNING, ("lcp_reqci: Nak AUTHTYPE %d req CHAP\n", cishort));
+          PUTCHAR(CILEN_CHAP, nakp);
+          PUTSHORT(PPP_CHAP, nakp);
+          PUTCHAR(ao->chap_mdtype, nakp);
+        } else {
+          LCPDEBUG(LOG_WARNING, ("lcp_reqci: Nak AUTHTYPE %d req PAP\n", cishort));
+          PUTCHAR(CILEN_SHORT, nakp);
+          PUTSHORT(PPP_PAP, nakp);
+        }
+        break;
+      
+      case CI_QUALITY:
+        GETSHORT(cishort, p);
+        GETLONG(cilong, p);
+#if TRACELCP > 0
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " QUALITY (%x %x)", cishort, (unsigned int) cilong);
+        traceNdx = strlen(traceBuf);
+#endif
+
+        if (!ao->neg_lqr ||
+            cilen != CILEN_LQR) {
+          orc = CONFREJ;
+          break;
+        }
+        
+        /*
+         * Check the protocol and the reporting period.
+         * XXX When should we Nak this, and what with?
+         */
+        if (cishort != PPP_LQR) {
+          orc = CONFNAK;
+          PUTCHAR(CI_QUALITY, nakp);
+          PUTCHAR(CILEN_LQR, nakp);
+          PUTSHORT(PPP_LQR, nakp);
+          PUTLONG(ao->lqr_period, nakp);
+          break;
+        }
+        break;
+      
+      case CI_MAGICNUMBER:
+        if (!(ao->neg_magicnumber || go->neg_magicnumber) ||
+            cilen != CILEN_LONG) {
+          orc = CONFREJ;
+          break;
+        }
+        GETLONG(cilong, p);
+#if TRACELCP > 0
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " MAGICNUMBER (%lX)", cilong);
+        traceNdx = strlen(traceBuf);
+#endif
+
+        /*
+         * He must have a different magic number.
+         */
+        if (go->neg_magicnumber &&
+            cilong == go->magicnumber) {
+          cilong = magic();  /* Don't put magic() inside macro! */
+          orc = CONFNAK;
+          PUTCHAR(CI_MAGICNUMBER, nakp);
+          PUTCHAR(CILEN_LONG, nakp);
+          PUTLONG(cilong, nakp);
+          break;
+        }
+        ho->neg_magicnumber = 1;
+        ho->magicnumber = cilong;
+        break;
+      
+      
+      case CI_PCOMPRESSION:
+#if TRACELCP > 0
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " PCOMPRESSION");
+        traceNdx = strlen(traceBuf);
+#endif
+        if (!ao->neg_pcompression ||
+            cilen != CILEN_VOID) {
+          orc = CONFREJ;
+          break;
+        }
+        ho->neg_pcompression = 1;
+        break;
+      
+      case CI_ACCOMPRESSION:
+#if TRACELCP > 0
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " ACCOMPRESSION");
+        traceNdx = strlen(traceBuf);
+#endif
+        if (!ao->neg_accompression ||
+            cilen != CILEN_VOID) {
+          orc = CONFREJ;
+          break;
+        }
+        ho->neg_accompression = 1;
+        break;
+      
+      case CI_MRRU:
+#if TRACELCP > 0
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " CI_MRRU");
+        traceNdx = strlen(traceBuf);
+#endif
+        orc = CONFREJ;
+        break;
+      
+      case CI_SSNHF:
+#if TRACELCP > 0
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " CI_SSNHF");
+        traceNdx = strlen(traceBuf);
+#endif
+        orc = CONFREJ;
+        break;
+      
+      case CI_EPDISC:
+#if TRACELCP > 0
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " CI_EPDISC");
+        traceNdx = strlen(traceBuf);
+#endif
+        orc = CONFREJ;
+        break;
+      
+      default:
+#if TRACELCP
+        snprintf(&traceBuf[traceNdx], sizeof(traceBuf), " unknown %d", citype);
+        traceNdx = strlen(traceBuf);
+#endif
+        orc = CONFREJ;
+        break;
+    }
+
+  endswitch:
+#if TRACELCP
+    if (traceNdx >= 80 - 32) {
+      LCPDEBUG(LOG_INFO, ("lcp_reqci: rcvd%s\n", traceBuf));
+      traceNdx = 0;
+    }
+#endif
+    if (orc == CONFACK && /* Good CI */
+        rc != CONFACK) {  /*  but prior CI wasnt? */
+      continue;           /* Don't send this one */
+    }
+
+    if (orc == CONFNAK) {     /* Nak this CI? */
+      if (reject_if_disagree  /* Getting fed up with sending NAKs? */
+          && citype != CI_MAGICNUMBER) {
+        orc = CONFREJ;        /* Get tough if so */
+      } else {
+        if (rc == CONFREJ) {  /* Rejecting prior CI? */
+          continue;           /* Don't send this one */
+        }
+        rc = CONFNAK;
+      }
+    }
+    if (orc == CONFREJ) {        /* Reject this CI */
+      rc = CONFREJ;
+      if (cip != rejp) {         /* Need to move rejected CI? */
+        BCOPY(cip, rejp, cilen); /* Move it */
+      }
+      INCPTR(cilen, rejp);       /* Update output pointer */
+    }
+  }
+
+  /*
+   * If we wanted to send additional NAKs (for unsent CIs), the
+   * code would go here.  The extra NAKs would go at *nakp.
+   * At present there are no cases where we want to ask the
+   * peer to negotiate an option.
+   */
+
+  switch (rc) {
+    case CONFACK:
+      *lenp = (int)(next - inp);
+      break;
+    case CONFNAK:
+      /*
+       * Copy the Nak'd options from the nak_buffer to the caller's buffer.
+       */
+      *lenp = (int)(nakp - nak_buffer);
+      BCOPY(nak_buffer, inp, *lenp);
+      break;
+    case CONFREJ:
+      *lenp = (int)(rejp - inp);
+      break;
+  }
+
+#if TRACELCP > 0
+  if (traceNdx > 0) {
+    LCPDEBUG(LOG_INFO, ("lcp_reqci: %s\n", traceBuf));
+  }
+#endif
+  LCPDEBUG(LOG_INFO, ("lcp_reqci: returning CONF%s.\n", CODENAME(rc)));
+  return (rc);      /* Return final code */
+}
+
+
+/*
+ * lcp_up - LCP has come UP.
+ */
+static void
+lcp_up(fsm *f)
+{
+  lcp_options *wo = &lcp_wantoptions[f->unit];
+  lcp_options *ho = &lcp_hisoptions[f->unit];
+  lcp_options *go = &lcp_gotoptions[f->unit];
+  lcp_options *ao = &lcp_allowoptions[f->unit];
+
+  if (!go->neg_magicnumber) {
+    go->magicnumber = 0;
+  }
+  if (!ho->neg_magicnumber) {
+    ho->magicnumber = 0;
+  }
+
+  /*
+   * Set our MTU to the smaller of the MTU we wanted and
+   * the MRU our peer wanted.  If we negotiated an MRU,
+   * set our MRU to the larger of value we wanted and
+   * the value we got in the negotiation.
+   */
+  ppp_send_config(f->unit, LWIP_MIN(ao->mru, (ho->neg_mru? ho->mru: PPP_MRU)),
+                 (ho->neg_asyncmap? ho->asyncmap: 0xffffffffl),
+                  ho->neg_pcompression, ho->neg_accompression);
+  /*
+   * If the asyncmap hasn't been negotiated, we really should
+   * set the receive asyncmap to ffffffff, but we set it to 0
+   * for backwards contemptibility.
+   */
+  ppp_recv_config(f->unit, (go->neg_mru? LWIP_MAX(wo->mru, go->mru): PPP_MRU),
+                 (go->neg_asyncmap? go->asyncmap: 0x00000000),
+                  go->neg_pcompression, go->neg_accompression);
+
+  if (ho->neg_mru) {
+    peer_mru[f->unit] = ho->mru;
+  }
+
+  lcp_echo_lowerup(f->unit); /* Enable echo messages */
+
+  link_established(f->unit); /* The link is up; authenticate now */
+}
+
+
+/*
+ * lcp_down - LCP has gone DOWN.
+ *
+ * Alert other protocols.
+ */
+static void
+lcp_down(fsm *f)
+{
+  lcp_options *go = &lcp_gotoptions[f->unit];
+
+  lcp_echo_lowerdown(f->unit);
+
+  link_down(f->unit);
+
+  ppp_send_config(f->unit, PPP_MRU, 0xffffffffl, 0, 0);
+  ppp_recv_config(f->unit, PPP_MRU,
+                  (go->neg_asyncmap? go->asyncmap: 0x00000000),
+                   go->neg_pcompression, go->neg_accompression);
+  peer_mru[f->unit] = PPP_MRU;
+}
+
+
+/*
+ * lcp_starting - LCP needs the lower layer up.
+ */
+static void
+lcp_starting(fsm *f)
+{
+  link_required(f->unit); /* lwip: currently does nothing */
+}
+
+
+/*
+ * lcp_finished - LCP has finished with the lower layer.
+ */
+static void
+lcp_finished(fsm *f)
+{
+  link_terminated(f->unit); /* we are finished with the link */
+}
+
+
+#if PPP_ADDITIONAL_CALLBACKS
+/*
+ * print_string - print a readable representation of a string using
+ * printer.
+ */
+static void
+print_string( char *p, int len, void (*printer) (void *, char *, ...), void *arg)
+{
+  int c;
+  
+  printer(arg, "\"");
+  for (; len > 0; --len) {
+    c = *p++;
+    if (' ' <= c && c <= '~') {
+        if (c == '\\' || c == '"') {
+          printer(arg, "\\");
+        }
+        printer(arg, "%c", c);
+    } else {
+      switch (c) {
+        case '\n':
+          printer(arg, "\\n");
+          break;
+        case '\r':
+          printer(arg, "\\r");
+          break;
+        case '\t':
+          printer(arg, "\\t");
+          break;
+        default:
+          printer(arg, "\\%.3o", c);
+        }
+    }
+  }
+  printer(arg, "\"");
+}
+
+
+/*
+ * lcp_printpkt - print the contents of an LCP packet.
+ */
+static char *lcp_codenames[] = {
+  "ConfReq", "ConfAck", "ConfNak", "ConfRej",
+  "TermReq", "TermAck", "CodeRej", "ProtRej",
+  "EchoReq", "EchoRep", "DiscReq"
+};
+
+static int
+lcp_printpkt( u_char *p, int plen, void (*printer) (void *, char *, ...), void *arg)
+{
+  int code, id, len, olen;
+  u_char *pstart, *optend;
+  u_short cishort;
+  u32_t cilong;
+
+  if (plen < HEADERLEN) {
+    return 0;
+  }
+  pstart = p;
+  GETCHAR(code, p);
+  GETCHAR(id, p);
+  GETSHORT(len, p);
+  if (len < HEADERLEN || len > plen) {
+    return 0;
+  }
+
+  if (code >= 1 && code <= sizeof(lcp_codenames) / sizeof(char *)) {
+    printer(arg, " %s", lcp_codenames[code-1]);
+  } else {
+    printer(arg, " code=0x%x", code);
+  }
+  printer(arg, " id=0x%x", id);
+  len -= HEADERLEN;
+  switch (code) {
+    case CONFREQ:
+    case CONFACK:
+    case CONFNAK:
+    case CONFREJ:
+      /* print option list */
+      while (len >= 2) {
+        GETCHAR(code, p);
+        GETCHAR(olen, p);
+        p -= 2;
+        if (olen < 2 || olen > len) {
+          break;
+        }
+        printer(arg, " <");
+        len -= olen;
+        optend = p + olen;
+        switch (code) {
+          case CI_MRU:
+            if (olen == CILEN_SHORT) {
+              p += 2;
+              GETSHORT(cishort, p);
+              printer(arg, "mru %d", cishort);
+            }
+            break;
+          case CI_ASYNCMAP:
+            if (olen == CILEN_LONG) {
+              p += 2;
+              GETLONG(cilong, p);
+              printer(arg, "asyncmap 0x%lx", cilong);
+            }
+            break;
+          case CI_AUTHTYPE:
+            if (olen >= CILEN_SHORT) {
+              p += 2;
+              printer(arg, "auth ");
+              GETSHORT(cishort, p);
+              switch (cishort) {
+                case PPP_PAP:
+                  printer(arg, "pap");
+                  break;
+                case PPP_CHAP:
+                  printer(arg, "chap");
+                  break;
+                default:
+                  printer(arg, "0x%x", cishort);
+              }
+            }
+            break;
+          case CI_QUALITY:
+            if (olen >= CILEN_SHORT) {
+              p += 2;
+              printer(arg, "quality ");
+              GETSHORT(cishort, p);
+              switch (cishort) {
+                case PPP_LQR:
+                  printer(arg, "lqr");
+                  break;
+                default:
+                  printer(arg, "0x%x", cishort);
+              }
+            }
+            break;
+          case CI_CALLBACK:
+            if (olen >= CILEN_CHAR) {
+              p += 2;
+              printer(arg, "callback ");
+              GETSHORT(cishort, p);
+              switch (cishort) {
+                case CBCP_OPT:
+                  printer(arg, "CBCP");
+                  break;
+                default:
+                  printer(arg, "0x%x", cishort);
+              }
+            }
+            break;
+          case CI_MAGICNUMBER:
+            if (olen == CILEN_LONG) {
+              p += 2;
+              GETLONG(cilong, p);
+              printer(arg, "magic 0x%x", cilong);
+            }
+            break;
+          case CI_PCOMPRESSION:
+            if (olen == CILEN_VOID) {
+              p += 2;
+              printer(arg, "pcomp");
+            }
+            break;
+          case CI_ACCOMPRESSION:
+            if (olen == CILEN_VOID) {
+              p += 2;
+              printer(arg, "accomp");
+            }
+            break;
+        }
+        while (p < optend) {
+          GETCHAR(code, p);
+          printer(arg, " %.2x", code);
+        }
+        printer(arg, ">");
+      }
+      break;
+    
+    case TERMACK:
+    case TERMREQ:
+      if (len > 0 && *p >= ' ' && *p < 0x7f) {
+        printer(arg, " ");
+        print_string((char*)p, len, printer, arg);
+        p += len;
+        len = 0;
+      }
+      break;
+    
+    case ECHOREQ:
+    case ECHOREP:
+    case DISCREQ:
+      if (len >= 4) {
+        GETLONG(cilong, p);
+        printer(arg, " magic=0x%x", cilong);
+        p += 4;
+        len -= 4;
+      }
+      break;
+  }
+
+  /* print the rest of the bytes in the packet */
+  for (; len > 0; --len) {
+    GETCHAR(code, p);
+    printer(arg, " %.2x", code);
+  }
+
+  return (int)(p - pstart);
+}
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+
+/*
+ * Time to shut down the link because there is nothing out there.
+ */
+static void
+LcpLinkFailure (fsm *f)
+{
+  if (f->state == LS_OPENED) {
+    LCPDEBUG(LOG_INFO, ("No response to %d echo-requests\n", lcp_echos_pending));
+    LCPDEBUG(LOG_NOTICE, ("Serial link appears to be disconnected.\n"));
+    lcp_close(f->unit, "Peer not responding");
+  }
+}
+
+/*
+ * Timer expired for the LCP echo requests from this process.
+ */
+static void
+LcpEchoCheck (fsm *f)
+{
+  LcpSendEchoRequest (f);
+
+  /*
+   * Start the timer for the next interval.
+   */
+  LWIP_ASSERT("lcp_echo_timer_running == 0", lcp_echo_timer_running == 0);
+
+  TIMEOUT (LcpEchoTimeout, f, lcp_echo_interval);
+  lcp_echo_timer_running = 1;
+}
+
+/*
+ * LcpEchoTimeout - Timer expired on the LCP echo
+ */
+static void
+LcpEchoTimeout (void *arg)
+{
+  if (lcp_echo_timer_running != 0) {
+    lcp_echo_timer_running = 0;
+    LcpEchoCheck ((fsm *) arg);
+  }
+}
+
+/*
+ * LcpEchoReply - LCP has received a reply to the echo
+ */
+static void
+lcp_received_echo_reply (fsm *f, int id, u_char *inp, int len)
+{
+  u32_t magic;
+
+  LWIP_UNUSED_ARG(id);
+
+  /* Check the magic number - don't count replies from ourselves. */
+  if (len < 4) {
+    LCPDEBUG(LOG_WARNING, ("lcp: received short Echo-Reply, length %d\n", len));
+    return;
+  }
+  GETLONG(magic, inp);
+  if (lcp_gotoptions[f->unit].neg_magicnumber && magic == lcp_gotoptions[f->unit].magicnumber) {
+    LCPDEBUG(LOG_WARNING, ("appear to have received our own echo-reply!\n"));
+    return;
+  }
+
+  /* Reset the number of outstanding echo frames */
+  lcp_echos_pending = 0;
+}
+
+/*
+ * LcpSendEchoRequest - Send an echo request frame to the peer
+ */
+static void
+LcpSendEchoRequest (fsm *f)
+{
+  u32_t lcp_magic;
+  u_char pkt[4], *pktp;
+
+  /*
+   * Detect the failure of the peer at this point.
+   */
+  if (lcp_echo_fails != 0) {
+    if (lcp_echos_pending >= lcp_echo_fails) {
+      LcpLinkFailure(f);
+      lcp_echos_pending = 0;
+    }
+  }
+
+  /*
+   * Make and send the echo request frame.
+   */
+  if (f->state == LS_OPENED) {
+    lcp_magic = lcp_gotoptions[f->unit].magicnumber;
+    pktp = pkt;
+    PUTLONG(lcp_magic, pktp);
+    fsm_sdata(f, ECHOREQ, (u_char)(lcp_echo_number++ & 0xFF), pkt, (int)(pktp - pkt));
+    ++lcp_echos_pending;
+  }
+}
+
+/*
+ * lcp_echo_lowerup - Start the timer for the LCP frame
+ */
+
+static void
+lcp_echo_lowerup (int unit)
+{
+  fsm *f = &lcp_fsm[unit];
+
+  /* Clear the parameters for generating echo frames */
+  lcp_echos_pending      = 0;
+  lcp_echo_number        = 0;
+  lcp_echo_timer_running = 0;
+
+  /* If a timeout interval is specified then start the timer */
+  if (lcp_echo_interval != 0) {
+    LcpEchoCheck (f);
+  }
+}
+
+/*
+ * lcp_echo_lowerdown - Stop the timer for the LCP frame
+ */
+
+static void
+lcp_echo_lowerdown (int unit)
+{
+  fsm *f = &lcp_fsm[unit];
+
+  if (lcp_echo_timer_running != 0) {
+    UNTIMEOUT (LcpEchoTimeout, f);
+    lcp_echo_timer_running = 0;
+  }
+}
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/lcp.h b/external/badvpn_dns/lwip/src/netif/ppp/lcp.h
new file mode 100644
index 0000000..b9201ee
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/lcp.h
@@ -0,0 +1,151 @@
+/*****************************************************************************
+* lcp.h - Network Link Control Protocol header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-11-05 Guy Lancaster <glanca@xxxxxxxx>, Global Election Systems Inc.
+*   Original derived from BSD codes.
+*****************************************************************************/
+/*
+ * lcp.h - Link Control Protocol definitions.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $Id: lcp.h,v 1.4 2010/01/18 20:49:43 goldsimon Exp $
+ */
+
+#ifndef LCP_H
+#define LCP_H
+/*
+ * Options.
+ */
+#define CI_MRU           1  /* Maximum Receive Unit */
+#define CI_ASYNCMAP      2  /* Async Control Character Map */
+#define CI_AUTHTYPE      3  /* Authentication Type */
+#define CI_QUALITY       4  /* Quality Protocol */
+#define CI_MAGICNUMBER   5  /* Magic Number */
+#define CI_PCOMPRESSION  7  /* Protocol Field Compression */
+#define CI_ACCOMPRESSION 8  /* Address/Control Field Compression */
+#define CI_CALLBACK      13 /* callback */
+#define CI_MRRU          17 /* max reconstructed receive unit; multilink */
+#define CI_SSNHF         18 /* short sequence numbers for multilink */
+#define CI_EPDISC        19 /* endpoint discriminator */
+
+/*
+ * LCP-specific packet types (code numbers).
+ */
+#define PROTREJ          8  /* Protocol Reject */
+#define ECHOREQ          9  /* Echo Request */
+#define ECHOREP          10 /* Echo Reply */
+#define DISCREQ          11 /* Discard Request */
+#define CBCP_OPT         6  /* Use callback control protocol */
+
+/*
+ * The state of options is described by an lcp_options structure.
+ */
+typedef struct lcp_options {
+    u_int passive           : 1; /* Don't die if we don't get a response */
+    u_int silent            : 1; /* Wait for the other end to start first */
+    u_int restart           : 1; /* Restart vs. exit after close */
+    u_int neg_mru           : 1; /* Negotiate the MRU? */
+    u_int neg_asyncmap      : 1; /* Negotiate the async map? */
+    u_int neg_upap          : 1; /* Ask for UPAP authentication? */
+    u_int neg_chap          : 1; /* Ask for CHAP authentication? */
+    u_int neg_magicnumber   : 1; /* Ask for magic number? */
+    u_int neg_pcompression  : 1; /* HDLC Protocol Field Compression? */
+    u_int neg_accompression : 1; /* HDLC Address/Control Field Compression? */
+    u_int neg_lqr           : 1; /* Negotiate use of Link Quality Reports */
+    u_int neg_cbcp          : 1; /* Negotiate use of CBCP */
+#ifdef PPP_MULTILINK
+    u_int neg_mrru          : 1; /* Negotiate multilink MRRU */
+    u_int neg_ssnhf         : 1; /* Negotiate short sequence numbers */
+    u_int neg_endpoint      : 1; /* Negotiate endpoint discriminator */
+#endif
+    u_short mru;                 /* Value of MRU */
+#ifdef PPP_MULTILINK
+    u_short mrru;                /* Value of MRRU, and multilink enable */
+#endif
+    u_char chap_mdtype;          /* which MD type (hashing algorithm) */
+    u32_t asyncmap;              /* Value of async map */
+    u32_t magicnumber;
+    int numloops;                /* Number of loops during magic number neg. */
+    u32_t lqr_period;            /* Reporting period for LQR 1/100ths second */
+#ifdef PPP_MULTILINK
+    struct epdisc endpoint;      /* endpoint discriminator */
+#endif
+} lcp_options;
+
+/*
+ * Values for phase from BSD pppd.h based on RFC 1661.
+ */
+typedef enum {
+  PHASE_DEAD = 0,
+  PHASE_INITIALIZE,
+  PHASE_ESTABLISH,
+  PHASE_AUTHENTICATE,
+  PHASE_CALLBACK,
+  PHASE_NETWORK,
+  PHASE_TERMINATE
+} LinkPhase;
+
+
+
+extern LinkPhase lcp_phase[NUM_PPP]; /* Phase of link session (RFC 1661) */
+extern lcp_options lcp_wantoptions[];
+extern lcp_options lcp_gotoptions[];
+extern lcp_options lcp_allowoptions[];
+extern lcp_options lcp_hisoptions[];
+extern ext_accm xmit_accm[];
+
+
+void lcp_init     (int);
+void lcp_open     (int);
+void lcp_close    (int, char *);
+void lcp_lowerup  (int);
+void lcp_lowerdown(int);
+void lcp_sprotrej (int, u_char *, int); /* send protocol reject */
+
+extern struct protent lcp_protent;
+
+/* Default number of times we receive our magic number from the peer
+   before deciding the link is looped-back. */
+#define DEFLOOPBACKFAIL 10
+
+#endif /* LCP_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/magic.c b/external/badvpn_dns/lwip/src/netif/ppp/magic.c
new file mode 100644
index 0000000..3732a42
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/magic.c
@@ -0,0 +1,80 @@
+/*****************************************************************************
+* magic.c - Network Random Number Generator program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 by Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-04 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original based on BSD magic.c.
+*****************************************************************************/
+/*
+ * magic.c - PPP Magic Number routines.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT
+
+#include "ppp_impl.h"
+#include "randm.h"
+#include "magic.h"
+
+
+/*
+ * magicInit - Initialize the magic number generator.
+ *
+ * Since we use another random number generator that has its own
+ * initialization, we do nothing here.
+ */
+void magicInit()
+{
+  return;
+}
+
+/*
+ * magic - Returns the next magic number.
+ */
+u32_t magic()
+{
+  return avRandom();
+}
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/magic.h b/external/badvpn_dns/lwip/src/netif/ppp/magic.h
new file mode 100644
index 0000000..eba70d2
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/magic.h
@@ -0,0 +1,63 @@
+/*****************************************************************************
+* magic.h - Network Random Number Generator header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-04 Guy Lancaster <glanca@xxxxxxxx>, Global Election Systems Inc.
+*   Original derived from BSD codes.
+*****************************************************************************/
+/*
+ * magic.h - PPP Magic Number definitions.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $Id: magic.h,v 1.3 2010/01/18 20:49:43 goldsimon Exp $
+ */
+
+#ifndef MAGIC_H
+#define MAGIC_H
+
+/* Initialize the magic number generator */
+void  magicInit(void);
+
+/* Returns the next magic number */
+u32_t magic(void);
+
+#endif /* MAGIC_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/md5.c b/external/badvpn_dns/lwip/src/netif/ppp/md5.c
new file mode 100644
index 0000000..dc3cc75
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/md5.c
@@ -0,0 +1,320 @@
+/*
+ ***********************************************************************
+ ** md5.c -- the source code for MD5 routines                         **
+ ** RSA Data Security, Inc. MD5 Message-Digest Algorithm              **
+ ** Created: 2/17/90 RLR                                              **
+ ** Revised: 1/91 SRD,AJ,BSK,JT Reference C ver., 7/10 constant corr. **
+ ***********************************************************************
+ */
+
+/*
+ ***********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.  **
+ **                                                                   **
+ ** License to copy and use this software is granted provided that    **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message-     **
+ ** Digest Algorithm" in all material mentioning or referencing this  **
+ ** software or this function.                                        **
+ **                                                                   **
+ ** License is also granted to make and use derivative works          **
+ ** provided that such works are identified as "derived from the RSA  **
+ ** Data Security, Inc. MD5 Message-Digest Algorithm" in all          **
+ ** material mentioning or referencing the derived work.              **
+ **                                                                   **
+ ** RSA Data Security, Inc. makes no representations concerning       **
+ ** either the merchantability of this software or the suitability    **
+ ** of this software for any particular purpose.  It is provided "as  **
+ ** is" without express or implied warranty of any kind.              **
+ **                                                                   **
+ ** These notices must be retained in any copies of any part of this  **
+ ** documentation and/or software.                                    **
+ ***********************************************************************
+ */
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#if CHAP_SUPPORT || MD5_SUPPORT
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "md5.h"
+
+#include <string.h>
+
+/*
+ ***********************************************************************
+ **  Message-digest routines:                                         **
+ **  To form the message digest for a message M                       **
+ **    (1) Initialize a context buffer mdContext using MD5Init        **
+ **    (2) Call MD5Update on mdContext and M                          **
+ **    (3) Call MD5Final on mdContext                                 **
+ **  The message digest is now in mdContext->digest[0...15]           **
+ ***********************************************************************
+ */
+
+/* forward declaration */
+static void Transform (u32_t *buf, u32_t *in);
+
+static unsigned char PADDING[64] = {
+  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* F, G, H and I are basic MD5 functions */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */
+/* Rotation is separate from addition to prevent recomputation */
+#define FF(a, b, c, d, x, s, ac) \
+  {(a) += F ((b), (c), (d)) + (x) + (u32_t)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define GG(a, b, c, d, x, s, ac) \
+  {(a) += G ((b), (c), (d)) + (x) + (u32_t)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define HH(a, b, c, d, x, s, ac) \
+  {(a) += H ((b), (c), (d)) + (x) + (u32_t)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define II(a, b, c, d, x, s, ac) \
+  {(a) += I ((b), (c), (d)) + (x) + (u32_t)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+
+#ifdef __STDC__
+#define UL(x) x##UL
+#else
+#ifdef WIN32
+#define UL(x) x##UL
+#else
+#define UL(x) x
+#endif
+#endif
+
+/* The routine MD5Init initializes the message-digest context
+   mdContext. All fields are set to zero.
+ */
+void
+MD5Init (MD5_CTX *mdContext)
+{
+  mdContext->i[0] = mdContext->i[1] = (u32_t)0;
+
+  /* Load magic initialization constants. */
+  mdContext->buf[0] = (u32_t)0x67452301UL;
+  mdContext->buf[1] = (u32_t)0xefcdab89UL;
+  mdContext->buf[2] = (u32_t)0x98badcfeUL;
+  mdContext->buf[3] = (u32_t)0x10325476UL;
+}
+
+/* The routine MD5Update updates the message-digest context to
+   account for the presence of each of the characters inBuf[0..inLen-1]
+   in the message whose digest is being computed.
+ */
+void
+MD5Update(MD5_CTX *mdContext, unsigned char *inBuf, unsigned int inLen)
+{
+  u32_t in[16];
+  int mdi;
+  unsigned int i, ii;
+
+#if 0
+  PPPDEBUG(LOG_INFO, ("MD5Update: %u:%.*H\n", inLen, LWIP_MIN(inLen, 20) * 2, inBuf));
+  PPPDEBUG(LOG_INFO, ("MD5Update: %u:%s\n", inLen, inBuf));
+#endif
+  
+  /* compute number of bytes mod 64 */
+  mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+  /* update number of bits */
+  if ((mdContext->i[0] + ((u32_t)inLen << 3)) < mdContext->i[0]) {
+    mdContext->i[1]++;
+  }
+  mdContext->i[0] += ((u32_t)inLen << 3);
+  mdContext->i[1] += ((u32_t)inLen >> 29);
+
+  while (inLen--) {
+    /* add new character to buffer, increment mdi */
+    mdContext->in[mdi++] = *inBuf++;
+
+    /* transform if necessary */
+    if (mdi == 0x40) {
+      for (i = 0, ii = 0; i < 16; i++, ii += 4) {
+        in[i] = (((u32_t)mdContext->in[ii+3]) << 24) |
+                (((u32_t)mdContext->in[ii+2]) << 16) |
+                (((u32_t)mdContext->in[ii+1]) << 8)  |
+                ((u32_t)mdContext->in[ii]);
+      }
+      Transform (mdContext->buf, in);
+      mdi = 0;
+    }
+  }
+}
+
+/* The routine MD5Final terminates the message-digest computation and
+   ends with the desired message digest in mdContext->digest[0...15].
+ */
+void
+MD5Final (unsigned char hash[], MD5_CTX *mdContext)
+{
+  u32_t in[16];
+  int mdi;
+  unsigned int i, ii;
+  unsigned int padLen;
+
+  /* save number of bits */
+  in[14] = mdContext->i[0];
+  in[15] = mdContext->i[1];
+
+  /* compute number of bytes mod 64 */
+  mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+  /* pad out to 56 mod 64 */
+  padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi);
+  MD5Update (mdContext, PADDING, padLen);
+
+  /* append length in bits and transform */
+  for (i = 0, ii = 0; i < 14; i++, ii += 4) {
+    in[i] = (((u32_t)mdContext->in[ii+3]) << 24) |
+            (((u32_t)mdContext->in[ii+2]) << 16) |
+            (((u32_t)mdContext->in[ii+1]) << 8)  |
+            ((u32_t)mdContext->in[ii]);
+  }
+  Transform (mdContext->buf, in);
+
+  /* store buffer in digest */
+  for (i = 0, ii = 0; i < 4; i++, ii += 4) {
+    mdContext->digest[ii]   = (unsigned char)(mdContext->buf[i] & 0xFF);
+    mdContext->digest[ii+1] =
+      (unsigned char)((mdContext->buf[i] >> 8)  & 0xFF);
+    mdContext->digest[ii+2] =
+      (unsigned char)((mdContext->buf[i] >> 16) & 0xFF);
+    mdContext->digest[ii+3] =
+      (unsigned char)((mdContext->buf[i] >> 24) & 0xFF);
+  }
+  SMEMCPY(hash, mdContext->digest, 16);
+}
+
+/* Basic MD5 step. Transforms buf based on in.
+ */
+static void
+Transform (u32_t *buf, u32_t *in)
+{
+  u32_t a = buf[0], b = buf[1], c = buf[2], d = buf[3];
+
+  /* Round 1 */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+  FF ( a, b, c, d, in[ 0], S11, UL(3614090360)); /* 1 */
+  FF ( d, a, b, c, in[ 1], S12, UL(3905402710)); /* 2 */
+  FF ( c, d, a, b, in[ 2], S13, UL( 606105819)); /* 3 */
+  FF ( b, c, d, a, in[ 3], S14, UL(3250441966)); /* 4 */
+  FF ( a, b, c, d, in[ 4], S11, UL(4118548399)); /* 5 */
+  FF ( d, a, b, c, in[ 5], S12, UL(1200080426)); /* 6 */
+  FF ( c, d, a, b, in[ 6], S13, UL(2821735955)); /* 7 */
+  FF ( b, c, d, a, in[ 7], S14, UL(4249261313)); /* 8 */
+  FF ( a, b, c, d, in[ 8], S11, UL(1770035416)); /* 9 */
+  FF ( d, a, b, c, in[ 9], S12, UL(2336552879)); /* 10 */
+  FF ( c, d, a, b, in[10], S13, UL(4294925233)); /* 11 */
+  FF ( b, c, d, a, in[11], S14, UL(2304563134)); /* 12 */
+  FF ( a, b, c, d, in[12], S11, UL(1804603682)); /* 13 */
+  FF ( d, a, b, c, in[13], S12, UL(4254626195)); /* 14 */
+  FF ( c, d, a, b, in[14], S13, UL(2792965006)); /* 15 */
+  FF ( b, c, d, a, in[15], S14, UL(1236535329)); /* 16 */
+
+  /* Round 2 */
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+  GG ( a, b, c, d, in[ 1], S21, UL(4129170786)); /* 17 */
+  GG ( d, a, b, c, in[ 6], S22, UL(3225465664)); /* 18 */
+  GG ( c, d, a, b, in[11], S23, UL( 643717713)); /* 19 */
+  GG ( b, c, d, a, in[ 0], S24, UL(3921069994)); /* 20 */
+  GG ( a, b, c, d, in[ 5], S21, UL(3593408605)); /* 21 */
+  GG ( d, a, b, c, in[10], S22, UL(  38016083)); /* 22 */
+  GG ( c, d, a, b, in[15], S23, UL(3634488961)); /* 23 */
+  GG ( b, c, d, a, in[ 4], S24, UL(3889429448)); /* 24 */
+  GG ( a, b, c, d, in[ 9], S21, UL( 568446438)); /* 25 */
+  GG ( d, a, b, c, in[14], S22, UL(3275163606)); /* 26 */
+  GG ( c, d, a, b, in[ 3], S23, UL(4107603335)); /* 27 */
+  GG ( b, c, d, a, in[ 8], S24, UL(1163531501)); /* 28 */
+  GG ( a, b, c, d, in[13], S21, UL(2850285829)); /* 29 */
+  GG ( d, a, b, c, in[ 2], S22, UL(4243563512)); /* 30 */
+  GG ( c, d, a, b, in[ 7], S23, UL(1735328473)); /* 31 */
+  GG ( b, c, d, a, in[12], S24, UL(2368359562)); /* 32 */
+
+  /* Round 3 */
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+  HH ( a, b, c, d, in[ 5], S31, UL(4294588738)); /* 33 */
+  HH ( d, a, b, c, in[ 8], S32, UL(2272392833)); /* 34 */
+  HH ( c, d, a, b, in[11], S33, UL(1839030562)); /* 35 */
+  HH ( b, c, d, a, in[14], S34, UL(4259657740)); /* 36 */
+  HH ( a, b, c, d, in[ 1], S31, UL(2763975236)); /* 37 */
+  HH ( d, a, b, c, in[ 4], S32, UL(1272893353)); /* 38 */
+  HH ( c, d, a, b, in[ 7], S33, UL(4139469664)); /* 39 */
+  HH ( b, c, d, a, in[10], S34, UL(3200236656)); /* 40 */
+  HH ( a, b, c, d, in[13], S31, UL( 681279174)); /* 41 */
+  HH ( d, a, b, c, in[ 0], S32, UL(3936430074)); /* 42 */
+  HH ( c, d, a, b, in[ 3], S33, UL(3572445317)); /* 43 */
+  HH ( b, c, d, a, in[ 6], S34, UL(  76029189)); /* 44 */
+  HH ( a, b, c, d, in[ 9], S31, UL(3654602809)); /* 45 */
+  HH ( d, a, b, c, in[12], S32, UL(3873151461)); /* 46 */
+  HH ( c, d, a, b, in[15], S33, UL( 530742520)); /* 47 */
+  HH ( b, c, d, a, in[ 2], S34, UL(3299628645)); /* 48 */
+
+  /* Round 4 */
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+  II ( a, b, c, d, in[ 0], S41, UL(4096336452)); /* 49 */
+  II ( d, a, b, c, in[ 7], S42, UL(1126891415)); /* 50 */
+  II ( c, d, a, b, in[14], S43, UL(2878612391)); /* 51 */
+  II ( b, c, d, a, in[ 5], S44, UL(4237533241)); /* 52 */
+  II ( a, b, c, d, in[12], S41, UL(1700485571)); /* 53 */
+  II ( d, a, b, c, in[ 3], S42, UL(2399980690)); /* 54 */
+  II ( c, d, a, b, in[10], S43, UL(4293915773)); /* 55 */
+  II ( b, c, d, a, in[ 1], S44, UL(2240044497)); /* 56 */
+  II ( a, b, c, d, in[ 8], S41, UL(1873313359)); /* 57 */
+  II ( d, a, b, c, in[15], S42, UL(4264355552)); /* 58 */
+  II ( c, d, a, b, in[ 6], S43, UL(2734768916)); /* 59 */
+  II ( b, c, d, a, in[13], S44, UL(1309151649)); /* 60 */
+  II ( a, b, c, d, in[ 4], S41, UL(4149444226)); /* 61 */
+  II ( d, a, b, c, in[11], S42, UL(3174756917)); /* 62 */
+  II ( c, d, a, b, in[ 2], S43, UL( 718787259)); /* 63 */
+  II ( b, c, d, a, in[ 9], S44, UL(3951481745)); /* 64 */
+
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
+
+#endif /* CHAP_SUPPORT || MD5_SUPPORT */
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/md5.h b/external/badvpn_dns/lwip/src/netif/ppp/md5.h
new file mode 100644
index 0000000..e129533
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/md5.h
@@ -0,0 +1,55 @@
+/*
+ ***********************************************************************
+ ** md5.h -- header file for implementation of MD5                    **
+ ** RSA Data Security, Inc. MD5 Message-Digest Algorithm              **
+ ** Created: 2/17/90 RLR                                              **
+ ** Revised: 12/27/90 SRD,AJ,BSK,JT Reference C version               **
+ ** Revised (for MD5): RLR 4/27/91                                    **
+ **   -- G modified to have y&~z instead of y&z                       **
+ **   -- FF, GG, HH modified to add in last register done             **
+ **   -- Access pattern: round 2 works mod 5, round 3 works mod 3     **
+ **   -- distinct additive constant for each step                     **
+ **   -- round 4 added, working mod 7                                 **
+ ***********************************************************************
+ */
+
+/*
+ ***********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.  **
+ **                                                                   **
+ ** License to copy and use this software is granted provided that    **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message-     **
+ ** Digest Algorithm" in all material mentioning or referencing this  **
+ ** software or this function.                                        **
+ **                                                                   **
+ ** License is also granted to make and use derivative works          **
+ ** provided that such works are identified as "derived from the RSA  **
+ ** Data Security, Inc. MD5 Message-Digest Algorithm" in all          **
+ ** material mentioning or referencing the derived work.              **
+ **                                                                   **
+ ** RSA Data Security, Inc. makes no representations concerning       **
+ ** either the merchantability of this software or the suitability    **
+ ** of this software for any particular purpose.  It is provided "as  **
+ ** is" without express or implied warranty of any kind.              **
+ **                                                                   **
+ ** These notices must be retained in any copies of any part of this  **
+ ** documentation and/or software.                                    **
+ ***********************************************************************
+ */
+
+#ifndef MD5_H
+#define MD5_H
+
+/* Data structure for MD5 (Message-Digest) computation */
+typedef struct {
+  u32_t i[2];               /* number of _bits_ handled mod 2^64 */
+  u32_t buf[4];             /* scratch buffer */
+  unsigned char in[64];     /* input buffer */
+  unsigned char digest[16]; /* actual digest after MD5Final call */
+} MD5_CTX;
+
+void MD5Init  ( MD5_CTX *mdContext);
+void MD5Update( MD5_CTX *mdContext, unsigned char *inBuf, unsigned int inLen);
+void MD5Final ( unsigned char hash[], MD5_CTX *mdContext);
+
+#endif /* MD5_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/pap.c b/external/badvpn_dns/lwip/src/netif/ppp/pap.c
new file mode 100644
index 0000000..5fb9f88
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/pap.c
@@ -0,0 +1,628 @@
+/*****************************************************************************
+* pap.c - Network Password Authentication Protocol program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 by Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-12 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original.
+*****************************************************************************/
+/*
+ * upap.c - User/Password Authentication Protocol.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#if PAP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "auth.h"
+#include "pap.h"
+
+#include <string.h>
+
+#if 0 /* UNUSED */
+static bool hide_password = 1;
+
+/*
+ * Command-line options.
+ */
+static option_t pap_option_list[] = {
+    { "hide-password", o_bool, &hide_password,
+      "Don't output passwords to log", 1 },
+    { "show-password", o_bool, &hide_password,
+      "Show password string in debug log messages", 0 },
+    { "pap-restart", o_int, &upap[0].us_timeouttime,
+      "Set retransmit timeout for PAP" },
+    { "pap-max-authreq", o_int, &upap[0].us_maxtransmits,
+      "Set max number of transmissions for auth-reqs" },
+    { "pap-timeout", o_int, &upap[0].us_reqtimeout,
+      "Set time limit for peer PAP authentication" },
+    { NULL }
+};
+#endif
+
+/*
+ * Protocol entry points.
+ */
+static void upap_init      (int);
+static void upap_lowerup   (int);
+static void upap_lowerdown (int);
+static void upap_input     (int, u_char *, int);
+static void upap_protrej   (int);
+#if PPP_ADDITIONAL_CALLBACKS
+static int  upap_printpkt (u_char *, int, void (*)(void *, char *, ...), void *);
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+
+struct protent pap_protent = {
+  PPP_PAP,
+  upap_init,
+  upap_input,
+  upap_protrej,
+  upap_lowerup,
+  upap_lowerdown,
+  NULL,
+  NULL,
+#if PPP_ADDITIONAL_CALLBACKS
+  upap_printpkt,
+  NULL,
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+  1,
+  "PAP",
+#if PPP_ADDITIONAL_CALLBACKS
+  NULL,
+  NULL,
+  NULL
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+};
+
+upap_state upap[NUM_PPP]; /* UPAP state; one for each unit */
+
+static void upap_timeout   (void *);
+static void upap_reqtimeout(void *);
+static void upap_rauthreq  (upap_state *, u_char *, u_char, int);
+static void upap_rauthack  (upap_state *, u_char *, int, int);
+static void upap_rauthnak  (upap_state *, u_char *, int, int);
+static void upap_sauthreq  (upap_state *);
+static void upap_sresp     (upap_state *, u_char, u_char, char *, int);
+
+
+/*
+ * upap_init - Initialize a UPAP unit.
+ */
+static void
+upap_init(int unit)
+{
+  upap_state *u = &upap[unit];
+
+  UPAPDEBUG(LOG_INFO, ("upap_init: %d\n", unit));
+  u->us_unit         = unit;
+  u->us_user         = NULL;
+  u->us_userlen      = 0;
+  u->us_passwd       = NULL;
+  u->us_passwdlen    = 0;
+  u->us_clientstate  = UPAPCS_INITIAL;
+  u->us_serverstate  = UPAPSS_INITIAL;
+  u->us_id           = 0;
+  u->us_timeouttime  = UPAP_DEFTIMEOUT;
+  u->us_maxtransmits = 10;
+  u->us_reqtimeout   = UPAP_DEFREQTIME;
+}
+
+/*
+ * upap_authwithpeer - Authenticate us with our peer (start client).
+ *
+ * Set new state and send authenticate's.
+ */
+void
+upap_authwithpeer(int unit, char *user, char *password)
+{
+  upap_state *u = &upap[unit];
+
+  UPAPDEBUG(LOG_INFO, ("upap_authwithpeer: %d user=%s password=%s s=%d\n",
+             unit, user, password, u->us_clientstate));
+
+  /* Save the username and password we're given */
+  u->us_user = user;
+  u->us_userlen = (int)strlen(user);
+  u->us_passwd = password;
+  u->us_passwdlen = (int)strlen(password);
+
+  u->us_transmits = 0;
+
+  /* Lower layer up yet? */
+  if (u->us_clientstate == UPAPCS_INITIAL ||
+      u->us_clientstate == UPAPCS_PENDING) {
+    u->us_clientstate = UPAPCS_PENDING;
+    return;
+  }
+
+  upap_sauthreq(u);      /* Start protocol */
+}
+
+
+/*
+ * upap_authpeer - Authenticate our peer (start server).
+ *
+ * Set new state.
+ */
+void
+upap_authpeer(int unit)
+{
+  upap_state *u = &upap[unit];
+
+  /* Lower layer up yet? */
+  if (u->us_serverstate == UPAPSS_INITIAL ||
+      u->us_serverstate == UPAPSS_PENDING) {
+    u->us_serverstate = UPAPSS_PENDING;
+    return;
+  }
+
+  u->us_serverstate = UPAPSS_LISTEN;
+  if (u->us_reqtimeout > 0) {
+    TIMEOUT(upap_reqtimeout, u, u->us_reqtimeout);
+  }
+}
+
+/*
+ * upap_timeout - Retransmission timer for sending auth-reqs expired.
+ */
+static void
+upap_timeout(void *arg)
+{
+  upap_state *u = (upap_state *) arg;
+
+  UPAPDEBUG(LOG_INFO, ("upap_timeout: %d timeout %d expired s=%d\n", 
+        u->us_unit, u->us_timeouttime, u->us_clientstate));
+
+  if (u->us_clientstate != UPAPCS_AUTHREQ) {
+    UPAPDEBUG(LOG_INFO, ("upap_timeout: not in AUTHREQ state!\n"));
+    return;
+  }
+
+  if (u->us_transmits >= u->us_maxtransmits) {
+    /* give up in disgust */
+    UPAPDEBUG(LOG_ERR, ("No response to PAP authenticate-requests\n"));
+    u->us_clientstate = UPAPCS_BADAUTH;
+    auth_withpeer_fail(u->us_unit, PPP_PAP);
+    return;
+  }
+
+  upap_sauthreq(u);    /* Send Authenticate-Request and set upap timeout*/
+}
+
+
+/*
+ * upap_reqtimeout - Give up waiting for the peer to send an auth-req.
+ */
+static void
+upap_reqtimeout(void *arg)
+{
+  upap_state *u = (upap_state *) arg;
+
+  if (u->us_serverstate != UPAPSS_LISTEN) {
+    return; /* huh?? */
+  }
+
+  auth_peer_fail(u->us_unit, PPP_PAP);
+  u->us_serverstate = UPAPSS_BADAUTH;
+}
+
+
+/*
+ * upap_lowerup - The lower layer is up.
+ *
+ * Start authenticating if pending.
+ */
+static void
+upap_lowerup(int unit)
+{
+  upap_state *u = &upap[unit];
+
+  UPAPDEBUG(LOG_INFO, ("upap_lowerup: init %d clientstate s=%d\n", unit, u->us_clientstate));
+
+  if (u->us_clientstate == UPAPCS_INITIAL) {
+    u->us_clientstate = UPAPCS_CLOSED;
+  } else if (u->us_clientstate == UPAPCS_PENDING) {
+    upap_sauthreq(u);  /* send an auth-request */
+    /* now client state is UPAPCS__AUTHREQ */
+  }
+
+  if (u->us_serverstate == UPAPSS_INITIAL) {
+    u->us_serverstate = UPAPSS_CLOSED;
+  } else if (u->us_serverstate == UPAPSS_PENDING) {
+    u->us_serverstate = UPAPSS_LISTEN;
+    if (u->us_reqtimeout > 0) {
+      TIMEOUT(upap_reqtimeout, u, u->us_reqtimeout);
+    }
+  }
+}
+
+
+/*
+ * upap_lowerdown - The lower layer is down.
+ *
+ * Cancel all timeouts.
+ */
+static void
+upap_lowerdown(int unit)
+{
+  upap_state *u = &upap[unit];
+
+  UPAPDEBUG(LOG_INFO, ("upap_lowerdown: %d s=%d\n", unit, u->us_clientstate));
+
+  if (u->us_clientstate == UPAPCS_AUTHREQ) { /* Timeout pending? */
+    UNTIMEOUT(upap_timeout, u);    /* Cancel timeout */
+  }
+  if (u->us_serverstate == UPAPSS_LISTEN && u->us_reqtimeout > 0) {
+    UNTIMEOUT(upap_reqtimeout, u);
+  }
+
+  u->us_clientstate = UPAPCS_INITIAL;
+  u->us_serverstate = UPAPSS_INITIAL;
+}
+
+
+/*
+ * upap_protrej - Peer doesn't speak this protocol.
+ *
+ * This shouldn't happen.  In any case, pretend lower layer went down.
+ */
+static void
+upap_protrej(int unit)
+{
+  upap_state *u = &upap[unit];
+
+  if (u->us_clientstate == UPAPCS_AUTHREQ) {
+    UPAPDEBUG(LOG_ERR, ("PAP authentication failed due to protocol-reject\n"));
+    auth_withpeer_fail(unit, PPP_PAP);
+  }
+  if (u->us_serverstate == UPAPSS_LISTEN) {
+    UPAPDEBUG(LOG_ERR, ("PAP authentication of peer failed (protocol-reject)\n"));
+    auth_peer_fail(unit, PPP_PAP);
+  }
+  upap_lowerdown(unit);
+}
+
+
+/*
+ * upap_input - Input UPAP packet.
+ */
+static void
+upap_input(int unit, u_char *inpacket, int l)
+{
+  upap_state *u = &upap[unit];
+  u_char *inp;
+  u_char code, id;
+  int len;
+
+  /*
+   * Parse header (code, id and length).
+   * If packet too short, drop it.
+   */
+  inp = inpacket;
+  if (l < (int)UPAP_HEADERLEN) {
+    UPAPDEBUG(LOG_INFO, ("pap_input: rcvd short header.\n"));
+    return;
+  }
+  GETCHAR(code, inp);
+  GETCHAR(id, inp);
+  GETSHORT(len, inp);
+  if (len < (int)UPAP_HEADERLEN) {
+    UPAPDEBUG(LOG_INFO, ("pap_input: rcvd illegal length.\n"));
+    return;
+  }
+  if (len > l) {
+    UPAPDEBUG(LOG_INFO, ("pap_input: rcvd short packet.\n"));
+    return;
+  }
+  len -= UPAP_HEADERLEN;
+
+  /*
+   * Action depends on code.
+   */
+  switch (code) {
+    case UPAP_AUTHREQ:
+      upap_rauthreq(u, inp, id, len);
+      break;
+
+    case UPAP_AUTHACK:
+      upap_rauthack(u, inp, id, len);
+      break;
+
+    case UPAP_AUTHNAK:
+      upap_rauthnak(u, inp, id, len);
+      break;
+
+    default:        /* XXX Need code reject */
+      UPAPDEBUG(LOG_INFO, ("pap_input: UNHANDLED default: code: %d, id: %d, len: %d.\n", code, id, len));
+      break;
+  }
+}
+
+
+/*
+ * upap_rauth - Receive Authenticate.
+ */
+static void
+upap_rauthreq(upap_state *u, u_char *inp, u_char id, int len)
+{
+  u_char ruserlen, rpasswdlen;
+  char *ruser, *rpasswd;
+  u_char retcode;
+  char *msg;
+  int msglen;
+
+  UPAPDEBUG(LOG_INFO, ("pap_rauth: Rcvd id %d.\n", id));
+
+  if (u->us_serverstate < UPAPSS_LISTEN) {
+    return;
+  }
+
+  /*
+   * If we receive a duplicate authenticate-request, we are
+   * supposed to return the same status as for the first request.
+   */
+  if (u->us_serverstate == UPAPSS_OPEN) {
+    upap_sresp(u, UPAP_AUTHACK, id, "", 0);  /* return auth-ack */
+    return;
+  }
+  if (u->us_serverstate == UPAPSS_BADAUTH) {
+    upap_sresp(u, UPAP_AUTHNAK, id, "", 0);  /* return auth-nak */
+    return;
+  }
+
+  /*
+   * Parse user/passwd.
+   */
+  if (len < (int)sizeof (u_char)) {
+    UPAPDEBUG(LOG_INFO, ("pap_rauth: rcvd short packet.\n"));
+    return;
+  }
+  GETCHAR(ruserlen, inp);
+  len -= sizeof (u_char) + ruserlen + sizeof (u_char);
+  if (len < 0) {
+    UPAPDEBUG(LOG_INFO, ("pap_rauth: rcvd short packet.\n"));
+    return;
+  }
+  ruser = (char *) inp;
+  INCPTR(ruserlen, inp);
+  GETCHAR(rpasswdlen, inp);
+  if (len < rpasswdlen) {
+    UPAPDEBUG(LOG_INFO, ("pap_rauth: rcvd short packet.\n"));
+    return;
+  }
+  rpasswd = (char *) inp;
+
+  /*
+   * Check the username and password given.
+   */
+  retcode = check_passwd(u->us_unit, ruser, ruserlen, rpasswd, rpasswdlen, &msg, &msglen);
+  /* lwip: currently retcode is always UPAP_AUTHACK */
+  BZERO(rpasswd, rpasswdlen);
+
+  upap_sresp(u, retcode, id, msg, msglen);
+
+  if (retcode == UPAP_AUTHACK) {
+    u->us_serverstate = UPAPSS_OPEN;
+    auth_peer_success(u->us_unit, PPP_PAP, ruser, ruserlen);
+  } else {
+    u->us_serverstate = UPAPSS_BADAUTH;
+    auth_peer_fail(u->us_unit, PPP_PAP);
+  }
+
+  if (u->us_reqtimeout > 0) {
+    UNTIMEOUT(upap_reqtimeout, u);
+  }
+}
+
+
+/*
+ * upap_rauthack - Receive Authenticate-Ack.
+ */
+static void
+upap_rauthack(upap_state *u, u_char *inp, int id, int len)
+{
+  u_char msglen;
+  char *msg;
+
+  LWIP_UNUSED_ARG(id);
+
+  UPAPDEBUG(LOG_INFO, ("pap_rauthack: Rcvd id %d s=%d\n", id, u->us_clientstate));
+
+  if (u->us_clientstate != UPAPCS_AUTHREQ) { /* XXX */
+    UPAPDEBUG(LOG_INFO, ("pap_rauthack: us_clientstate != UPAPCS_AUTHREQ\n"));
+    return;
+  }
+
+  /*
+   * Parse message.
+   */
+  if (len < (int)sizeof (u_char)) {
+    UPAPDEBUG(LOG_INFO, ("pap_rauthack: ignoring missing msg-length.\n"));
+  } else {
+    GETCHAR(msglen, inp);
+    if (msglen > 0) {
+      len -= sizeof (u_char);
+      if (len < msglen) {
+        UPAPDEBUG(LOG_INFO, ("pap_rauthack: rcvd short packet.\n"));
+        return;
+      }
+      msg = (char *) inp;
+      PRINTMSG(msg, msglen);
+    }
+  }
+  UNTIMEOUT(upap_timeout, u);    /* Cancel timeout */
+  u->us_clientstate = UPAPCS_OPEN;
+
+  auth_withpeer_success(u->us_unit, PPP_PAP);
+}
+
+
+/*
+ * upap_rauthnak - Receive Authenticate-Nak.
+ */
+static void
+upap_rauthnak(upap_state *u, u_char *inp, int id, int len)
+{
+  u_char msglen;
+  char *msg;
+
+  LWIP_UNUSED_ARG(id);
+
+  UPAPDEBUG(LOG_INFO, ("pap_rauthnak: Rcvd id %d s=%d\n", id, u->us_clientstate));
+
+  if (u->us_clientstate != UPAPCS_AUTHREQ) { /* XXX */
+    return;
+  }
+
+  /*
+   * Parse message.
+   */
+  if (len < sizeof (u_char)) {
+    UPAPDEBUG(LOG_INFO, ("pap_rauthnak: ignoring missing msg-length.\n"));
+  } else {
+    GETCHAR(msglen, inp);
+    if(msglen > 0) {
+      len -= sizeof (u_char);
+      if (len < msglen) {
+        UPAPDEBUG(LOG_INFO, ("pap_rauthnak: rcvd short packet.\n"));
+        return;
+      }
+      msg = (char *) inp;
+      PRINTMSG(msg, msglen);
+    }
+  }
+
+  u->us_clientstate = UPAPCS_BADAUTH;
+
+  UPAPDEBUG(LOG_ERR, ("PAP authentication failed\n"));
+  auth_withpeer_fail(u->us_unit, PPP_PAP);
+}
+
+
+/*
+ * upap_sauthreq - Send an Authenticate-Request.
+ */
+static void
+upap_sauthreq(upap_state *u)
+{
+  u_char *outp;
+  int outlen;
+
+  outlen = UPAP_HEADERLEN + 2 * sizeof (u_char) 
+         + u->us_userlen + u->us_passwdlen;
+  outp = outpacket_buf[u->us_unit];
+
+  MAKEHEADER(outp, PPP_PAP);
+
+  PUTCHAR(UPAP_AUTHREQ, outp);
+  PUTCHAR(++u->us_id, outp);
+  PUTSHORT(outlen, outp);
+  PUTCHAR(u->us_userlen, outp);
+  BCOPY(u->us_user, outp, u->us_userlen);
+  INCPTR(u->us_userlen, outp);
+  PUTCHAR(u->us_passwdlen, outp);
+  BCOPY(u->us_passwd, outp, u->us_passwdlen);
+
+  pppWrite(u->us_unit, outpacket_buf[u->us_unit], outlen + PPP_HDRLEN);
+
+  UPAPDEBUG(LOG_INFO, ("pap_sauth: Sent id %d\n", u->us_id));
+
+  TIMEOUT(upap_timeout, u, u->us_timeouttime);
+  ++u->us_transmits;
+  u->us_clientstate = UPAPCS_AUTHREQ;
+}
+
+
+/*
+ * upap_sresp - Send a response (ack or nak).
+ */
+static void
+upap_sresp(upap_state *u, u_char code, u_char id, char *msg, int msglen)
+{
+  u_char *outp;
+  int outlen;
+
+  outlen = UPAP_HEADERLEN + sizeof (u_char) + msglen;
+  outp = outpacket_buf[u->us_unit];
+  MAKEHEADER(outp, PPP_PAP);
+
+  PUTCHAR(code, outp);
+  PUTCHAR(id, outp);
+  PUTSHORT(outlen, outp);
+  PUTCHAR(msglen, outp);
+  BCOPY(msg, outp, msglen);
+  pppWrite(u->us_unit, outpacket_buf[u->us_unit], outlen + PPP_HDRLEN);
+
+  UPAPDEBUG(LOG_INFO, ("pap_sresp: Sent code %d, id %d s=%d\n", code, id, u->us_clientstate));
+}
+
+#if PPP_ADDITIONAL_CALLBACKS
+static char *upap_codenames[] = {
+    "AuthReq", "AuthAck", "AuthNak"
+};
+
+/*
+ * upap_printpkt - print the contents of a PAP packet.
+ */
+static int upap_printpkt(
+  u_char *p,
+  int plen,
+  void (*printer) (void *, char *, ...),
+  void *arg
+)
+{
+  LWIP_UNUSED_ARG(p);
+  LWIP_UNUSED_ARG(plen);
+  LWIP_UNUSED_ARG(printer);
+  LWIP_UNUSED_ARG(arg);
+  return 0;
+}
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+
+#endif /* PAP_SUPPORT */
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/pap.h b/external/badvpn_dns/lwip/src/netif/ppp/pap.h
new file mode 100644
index 0000000..c99a204
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/pap.h
@@ -0,0 +1,118 @@
+/*****************************************************************************
+* pap.h -  PPP Password Authentication Protocol header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-12-04 Guy Lancaster <glanca@xxxxxxxx>, Global Election Systems Inc.
+*   Original derived from BSD codes.
+*****************************************************************************/
+/*
+ * upap.h - User/Password Authentication Protocol definitions.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#ifndef PAP_H
+#define PAP_H
+
+#if PAP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+/*
+ * Packet header = Code, id, length.
+ */
+#define UPAP_HEADERLEN (sizeof (u_char) + sizeof (u_char) + sizeof (u_short))
+
+
+/*
+ * UPAP codes.
+ */
+#define UPAP_AUTHREQ 1 /* Authenticate-Request */
+#define UPAP_AUTHACK 2 /* Authenticate-Ack */
+#define UPAP_AUTHNAK 3 /* Authenticate-Nak */
+
+/*
+ * Each interface is described by upap structure.
+ */
+typedef struct upap_state {
+  int us_unit;           /* Interface unit number */
+  const char *us_user;   /* User */
+  int us_userlen;        /* User length */
+  const char *us_passwd; /* Password */
+  int us_passwdlen;      /* Password length */
+  int us_clientstate;    /* Client state */
+  int us_serverstate;    /* Server state */
+  u_char us_id;          /* Current id */
+  int us_timeouttime;    /* Timeout (seconds) for auth-req retrans. */
+  int us_transmits;      /* Number of auth-reqs sent */
+  int us_maxtransmits;   /* Maximum number of auth-reqs to send */
+  int us_reqtimeout;     /* Time to wait for auth-req from peer */
+} upap_state;
+
+/*
+ * Client states.
+ */
+#define UPAPCS_INITIAL 0 /* Connection down */
+#define UPAPCS_CLOSED  1 /* Connection up, haven't requested auth */
+#define UPAPCS_PENDING 2 /* Connection down, have requested auth */
+#define UPAPCS_AUTHREQ 3 /* We've sent an Authenticate-Request */
+#define UPAPCS_OPEN    4 /* We've received an Ack */
+#define UPAPCS_BADAUTH 5 /* We've received a Nak */
+
+/*
+ * Server states.
+ */
+#define UPAPSS_INITIAL 0 /* Connection down */
+#define UPAPSS_CLOSED  1 /* Connection up, haven't requested auth */
+#define UPAPSS_PENDING 2 /* Connection down, have requested auth */
+#define UPAPSS_LISTEN  3 /* Listening for an Authenticate */
+#define UPAPSS_OPEN    4 /* We've sent an Ack */
+#define UPAPSS_BADAUTH 5 /* We've sent a Nak */
+
+
+extern upap_state upap[];
+
+void upap_authwithpeer  (int, char *, char *);
+void upap_authpeer      (int);
+
+extern struct protent pap_protent;
+
+#endif /* PAP_SUPPORT */
+
+#endif /* PAP_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/ppp.c b/external/badvpn_dns/lwip/src/netif/ppp/ppp.c
new file mode 100644
index 0000000..2a34657
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/ppp.c
@@ -0,0 +1,2052 @@
+/*****************************************************************************
+* ppp.c - Network Point to Point Protocol program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 by Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-11-05 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original.
+*****************************************************************************/
+
+/*
+ * ppp_defs.h - PPP definitions.
+ *
+ * if_pppvar.h - private structures and declarations for PPP.
+ *
+ * Copyright (c) 1994 The Australian National University.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation is hereby granted, provided that the above copyright
+ * notice appears in all copies.  This software is provided without any
+ * warranty, express or implied. The Australian National University
+ * makes no representations about the suitability of this software for
+ * any purpose.
+ *
+ * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY
+ * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
+ * THE AUSTRALIAN NATIONAL UNIVERSITY HAVE BEEN ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO
+ * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
+ * OR MODIFICATIONS.
+ */
+
+/*
+ * if_ppp.h - Point-to-Point Protocol definitions.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp_impl.h"
+#include "lwip/ip.h" /* for ip_input() */
+
+#include "pppdebug.h"
+
+#include "randm.h"
+#include "fsm.h"
+#if PAP_SUPPORT
+#include "pap.h"
+#endif /* PAP_SUPPORT */
+#if CHAP_SUPPORT
+#include "chap.h"
+#endif /* CHAP_SUPPORT */
+#include "ipcp.h"
+#include "lcp.h"
+#include "magic.h"
+#include "auth.h"
+#if VJ_SUPPORT
+#include "vj.h"
+#endif /* VJ_SUPPORT */
+#if PPPOE_SUPPORT
+#include "netif/ppp_oe.h"
+#endif /* PPPOE_SUPPORT */
+
+#include "lwip/tcpip.h"
+#include "lwip/api.h"
+#include "lwip/snmp.h"
+
+#include <string.h>
+
+/*************************/
+/*** LOCAL DEFINITIONS ***/
+/*************************/
+
+/** PPP_INPROC_MULTITHREADED==1 call pppInput using tcpip_callback().
+ * Set this to 0 if pppInProc is called inside tcpip_thread or with NO_SYS==1.
+ * Default is 1 for NO_SYS==0 (multithreaded) and 0 for NO_SYS==1 (single-threaded).
+ */
+#ifndef PPP_INPROC_MULTITHREADED
+#define PPP_INPROC_MULTITHREADED (NO_SYS==0)
+#endif
+
+/** PPP_INPROC_OWNTHREAD==1: start a dedicated RX thread per PPP session.
+ * Default is 0: call pppos_input() for received raw characters, charcater
+ * reception is up to the port */
+#ifndef PPP_INPROC_OWNTHREAD
+#define PPP_INPROC_OWNTHREAD      PPP_INPROC_MULTITHREADED
+#endif
+
+#if PPP_INPROC_OWNTHREAD && !PPP_INPROC_MULTITHREADED
+  #error "PPP_INPROC_OWNTHREAD needs PPP_INPROC_MULTITHREADED==1"
+#endif
+
+/*
+ * The basic PPP frame.
+ */
+#define PPP_ADDRESS(p)  (((u_char *)(p))[0])
+#define PPP_CONTROL(p)  (((u_char *)(p))[1])
+#define PPP_PROTOCOL(p) ((((u_char *)(p))[2] << 8) + ((u_char *)(p))[3])
+
+/* PPP packet parser states.  Current state indicates operation yet to be
+ * completed. */
+typedef enum {
+  PDIDLE = 0,  /* Idle state - waiting. */
+  PDSTART,     /* Process start flag. */
+  PDADDRESS,   /* Process address field. */
+  PDCONTROL,   /* Process control field. */
+  PDPROTOCOL1, /* Process protocol field 1. */
+  PDPROTOCOL2, /* Process protocol field 2. */
+  PDDATA       /* Process data byte. */
+} PPPDevStates;
+
+#define ESCAPE_P(accm, c) ((accm)[(c) >> 3] & pppACCMMask[c & 0x07])
+
+/************************/
+/*** LOCAL DATA TYPES ***/
+/************************/
+
+/** RX buffer size: this may be configured smaller! */
+#ifndef PPPOS_RX_BUFSIZE
+#define PPPOS_RX_BUFSIZE    (PPP_MRU + PPP_HDRLEN)
+#endif
+
+typedef struct PPPControlRx_s {
+  /** unit number / ppp descriptor */
+  int pd;
+  /** the rx file descriptor */
+  sio_fd_t fd;
+  /** receive buffer - encoded data is stored here */
+#if PPP_INPROC_OWNTHREAD
+  u_char rxbuf[PPPOS_RX_BUFSIZE];
+#endif /* PPP_INPROC_OWNTHREAD */
+
+  /* The input packet. */
+  struct pbuf *inHead, *inTail;
+
+#if PPPOS_SUPPORT
+  u16_t inProtocol;             /* The input protocol code. */
+  u16_t inFCS;                  /* Input Frame Check Sequence value. */
+#endif /* PPPOS_SUPPORT */
+  PPPDevStates inState;         /* The input process state. */
+  char inEscaped;               /* Escape next character. */
+  ext_accm inACCM;              /* Async-Ctl-Char-Map for input. */
+} PPPControlRx;
+
+/*
+ * PPP interface control block.
+ */
+typedef struct PPPControl_s {
+  PPPControlRx rx;
+  char openFlag;                /* True when in use. */
+#if PPPOE_SUPPORT
+  struct netif *ethif;
+  struct pppoe_softc *pppoe_sc;
+#endif /* PPPOE_SUPPORT */
+  int  if_up;                   /* True when the interface is up. */
+  int  errCode;                 /* Code indicating why interface is down. */
+#if PPPOS_SUPPORT
+  sio_fd_t fd;                  /* File device ID of port. */
+#endif /* PPPOS_SUPPORT */
+  u16_t mtu;                    /* Peer's mru */
+  int  pcomp;                   /* Does peer accept protocol compression? */
+  int  accomp;                  /* Does peer accept addr/ctl compression? */
+  u_long lastXMit;              /* Time of last transmission. */
+  ext_accm outACCM;             /* Async-Ctl-Char-Map for output. */
+#if PPPOS_SUPPORT && VJ_SUPPORT
+  int  vjEnabled;               /* Flag indicating VJ compression enabled. */
+  struct vjcompress vjComp;     /* Van Jacobson compression header. */
+#endif /* PPPOS_SUPPORT && VJ_SUPPORT */
+
+  struct netif netif;
+
+  struct ppp_addrs addrs;
+
+  void (*linkStatusCB)(void *ctx, int errCode, void *arg);
+  void *linkStatusCtx;
+
+} PPPControl;
+
+
+/*
+ * Ioctl definitions.
+ */
+
+struct npioctl {
+  int         protocol; /* PPP procotol, e.g. PPP_IP */
+  enum NPmode mode;
+};
+
+
+
+/***********************************/
+/*** LOCAL FUNCTION DECLARATIONS ***/
+/***********************************/
+#if PPPOS_SUPPORT
+#if PPP_INPROC_OWNTHREAD
+static void pppInputThread(void *arg);
+#endif /* PPP_INPROC_OWNTHREAD */
+static void pppDrop(PPPControlRx *pcrx);
+static void pppInProc(PPPControlRx *pcrx, u_char *s, int l);
+static void pppFreeCurrentInputPacket(PPPControlRx *pcrx);
+#endif /* PPPOS_SUPPORT */
+
+
+/******************************/
+/*** PUBLIC DATA STRUCTURES ***/
+/******************************/
+static PPPControl pppControl[NUM_PPP]; /* The PPP interface control blocks. */
+
+/*
+ * PPP Data Link Layer "protocol" table.
+ * One entry per supported protocol.
+ * The last entry must be NULL.
+ */
+struct protent *ppp_protocols[] = {
+  &lcp_protent,
+#if PAP_SUPPORT
+  &pap_protent,
+#endif /* PAP_SUPPORT */
+#if CHAP_SUPPORT
+  &chap_protent,
+#endif /* CHAP_SUPPORT */
+#if CBCP_SUPPORT
+  &cbcp_protent,
+#endif /* CBCP_SUPPORT */
+  &ipcp_protent,
+#if CCP_SUPPORT
+  &ccp_protent,
+#endif /* CCP_SUPPORT */
+  NULL
+};
+
+
+/*
+ * Buffers for outgoing packets.  This must be accessed only from the appropriate
+ * PPP task so that it doesn't need to be protected to avoid collisions.
+ */
+u_char outpacket_buf[NUM_PPP][PPP_MRU+PPP_HDRLEN];
+
+
+/*****************************/
+/*** LOCAL DATA STRUCTURES ***/
+/*****************************/
+
+#if PPPOS_SUPPORT
+/*
+ * FCS lookup table as calculated by genfcstab.
+ * @todo: smaller, slower implementation for lower memory footprint?
+ */
+static const u_short fcstab[256] = {
+  0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+  0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+  0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+  0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+  0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+  0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+  0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+  0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+  0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+  0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+  0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+  0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+  0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+  0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+  0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+  0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+  0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+  0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+  0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+  0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+  0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+  0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+  0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+  0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+  0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+  0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+  0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+  0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+  0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+  0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+  0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+  0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+
+/* PPP's Asynchronous-Control-Character-Map.  The mask array is used
+ * to select the specific bit for a character. */
+static u_char pppACCMMask[] = {
+  0x01,
+  0x02,
+  0x04,
+  0x08,
+  0x10,
+  0x20,
+  0x40,
+  0x80
+};
+
+#if PPP_INPROC_OWNTHREAD
+/** Wake up the task blocked in reading from serial line (if any) */
+static void
+pppRecvWakeup(int pd)
+{
+  PPPDEBUG(LOG_DEBUG, ("pppRecvWakeup: unit %d\n", pd));
+  if (pppControl[pd].openFlag != 0) {
+    sio_read_abort(pppControl[pd].fd);
+  }
+}
+#endif /* PPP_INPROC_OWNTHREAD */
+#endif /* PPPOS_SUPPORT */
+
+void
+pppLinkTerminated(int pd)
+{
+  PPPDEBUG(LOG_DEBUG, ("pppLinkTerminated: unit %d\n", pd));
+
+#if PPPOE_SUPPORT
+  if (pppControl[pd].ethif) {
+    pppoe_disconnect(pppControl[pd].pppoe_sc);
+  } else
+#endif /* PPPOE_SUPPORT */
+  {
+#if PPPOS_SUPPORT
+    PPPControl* pc;
+#if PPP_INPROC_OWNTHREAD
+    pppRecvWakeup(pd);
+#endif /* PPP_INPROC_OWNTHREAD */
+    pc = &pppControl[pd];
+
+    PPPDEBUG(LOG_DEBUG, ("pppLinkTerminated: unit %d: linkStatusCB=%p errCode=%d\n", pd, pc->linkStatusCB, pc->errCode));
+    if (pc->linkStatusCB) {
+      pc->linkStatusCB(pc->linkStatusCtx, pc->errCode ? pc->errCode : PPPERR_PROTOCOL, NULL);
+    }
+
+    pc->openFlag = 0;/**/
+#endif /* PPPOS_SUPPORT */
+  }
+  PPPDEBUG(LOG_DEBUG, ("pppLinkTerminated: finished.\n"));
+}
+
+void
+pppLinkDown(int pd)
+{
+  PPPDEBUG(LOG_DEBUG, ("pppLinkDown: unit %d\n", pd));
+
+#if PPPOE_SUPPORT
+  if (pppControl[pd].ethif) {
+    pppoe_disconnect(pppControl[pd].pppoe_sc);
+  } else
+#endif /* PPPOE_SUPPORT */
+  {
+#if PPPOS_SUPPORT && PPP_INPROC_OWNTHREAD
+    pppRecvWakeup(pd);
+#endif /* PPPOS_SUPPORT && PPP_INPROC_OWNTHREAD*/
+  }
+}
+
+/** Initiate LCP open request */
+static void
+pppStart(int pd)
+{
+  PPPDEBUG(LOG_DEBUG, ("pppStart: unit %d\n", pd));
+  lcp_lowerup(pd);
+  lcp_open(pd); /* Start protocol */
+  PPPDEBUG(LOG_DEBUG, ("pppStart: finished\n"));
+}
+
+/** LCP close request */
+static void
+pppStop(int pd)
+{
+  PPPDEBUG(LOG_DEBUG, ("pppStop: unit %d\n", pd));
+  lcp_close(pd, "User request");
+}
+
+/** Called when carrier/link is lost */
+static void
+pppHup(int pd)
+{
+  PPPDEBUG(LOG_DEBUG, ("pppHupCB: unit %d\n", pd));
+  lcp_lowerdown(pd);
+  link_terminated(pd);
+}
+
+/***********************************/
+/*** PUBLIC FUNCTION DEFINITIONS ***/
+/***********************************/
+/* Initialize the PPP subsystem. */
+
+struct ppp_settings ppp_settings;
+
+void
+pppInit(void)
+{
+  struct protent *protp;
+  int i, j;
+
+  memset(&ppp_settings, 0, sizeof(ppp_settings));
+  ppp_settings.usepeerdns = 1;
+  pppSetAuth(PPPAUTHTYPE_NONE, NULL, NULL);
+
+  magicInit();
+
+  for (i = 0; i < NUM_PPP; i++) {
+    /* Initialize each protocol to the standard option set. */
+    for (j = 0; (protp = ppp_protocols[j]) != NULL; ++j) {
+      (*protp->init)(i);
+    }
+  }
+}
+
+void
+pppSetAuth(enum pppAuthType authType, const char *user, const char *passwd)
+{
+  switch(authType) {
+    case PPPAUTHTYPE_NONE:
+    default:
+#ifdef LWIP_PPP_STRICT_PAP_REJECT
+      ppp_settings.refuse_pap = 1;
+#else  /* LWIP_PPP_STRICT_PAP_REJECT */
+      /* some providers request pap and accept an empty login/pw */
+      ppp_settings.refuse_pap = 0;
+#endif /* LWIP_PPP_STRICT_PAP_REJECT */
+      ppp_settings.refuse_chap = 1;
+      break;
+
+    case PPPAUTHTYPE_ANY:
+      /* Warning: Using PPPAUTHTYPE_ANY might have security consequences.
+       * RFC 1994 says:
+       *
+       * In practice, within or associated with each PPP server, there is a
+       * database which associates "user" names with authentication
+       * information ("secrets").  It is not anticipated that a particular
+       * named user would be authenticated by multiple methods.  This would
+       * make the user vulnerable to attacks which negotiate the least secure
+       * method from among a set (such as PAP rather than CHAP).  If the same
+       * secret was used, PAP would reveal the secret to be used later with
+       * CHAP.
+       *
+       * Instead, for each user name there should be an indication of exactly
+       * one method used to authenticate that user name.  If a user needs to
+       * make use of different authentication methods under different
+       * circumstances, then distinct user names SHOULD be employed, each of
+       * which identifies exactly one authentication method.
+       *
+       */
+      ppp_settings.refuse_pap = 0;
+      ppp_settings.refuse_chap = 0;
+      break;
+
+    case PPPAUTHTYPE_PAP:
+      ppp_settings.refuse_pap = 0;
+      ppp_settings.refuse_chap = 1;
+      break;
+
+    case PPPAUTHTYPE_CHAP:
+      ppp_settings.refuse_pap = 1;
+      ppp_settings.refuse_chap = 0;
+      break;
+  }
+
+  if(user) {
+    strncpy(ppp_settings.user, user, sizeof(ppp_settings.user)-1);
+    ppp_settings.user[sizeof(ppp_settings.user)-1] = '\0';
+  } else {
+    ppp_settings.user[0] = '\0';
+  }
+
+  if(passwd) {
+    strncpy(ppp_settings.passwd, passwd, sizeof(ppp_settings.passwd)-1);
+    ppp_settings.passwd[sizeof(ppp_settings.passwd)-1] = '\0';
+  } else {
+    ppp_settings.passwd[0] = '\0';
+  }
+}
+
+#if PPPOS_SUPPORT
+/** Open a new PPP connection using the given I/O device.
+ * This initializes the PPP control block but does not
+ * attempt to negotiate the LCP session.  If this port
+ * connects to a modem, the modem connection must be
+ * established before calling this.
+ * Return a new PPP connection descriptor on success or
+ * an error code (negative) on failure.
+ *
+ * pppOpen() is directly defined to this function.
+ */
+int
+pppOverSerialOpen(sio_fd_t fd, pppLinkStatusCB_fn linkStatusCB, void *linkStatusCtx)
+{
+  PPPControl *pc;
+  int pd;
+
+  if (linkStatusCB == NULL) {
+    /* PPP is single-threaded: without a callback,
+     * there is no way to know when the link is up. */
+    return PPPERR_PARAM;
+  }
+
+  /* Find a free PPP session descriptor. */
+  for (pd = 0; pd < NUM_PPP && pppControl[pd].openFlag != 0; pd++);
+
+  if (pd >= NUM_PPP) {
+    pd = PPPERR_OPEN;
+  } else {
+    pc = &pppControl[pd];
+    /* input pbuf left over from last session? */
+    pppFreeCurrentInputPacket(&pc->rx);
+    /* @todo: is this correct or do I overwrite something? */
+    memset(pc, 0, sizeof(PPPControl));
+    pc->rx.pd = pd;
+    pc->rx.fd = fd;
+
+    pc->openFlag = 1;
+    pc->fd = fd;
+
+#if VJ_SUPPORT
+    vj_compress_init(&pc->vjComp);
+#endif /* VJ_SUPPORT */
+
+    /* 
+     * Default the in and out accm so that escape and flag characters
+     * are always escaped. 
+     */
+    pc->rx.inACCM[15] = 0x60; /* no need to protect since RX is not running */
+    pc->outACCM[15] = 0x60;
+
+    pc->linkStatusCB = linkStatusCB;
+    pc->linkStatusCtx = linkStatusCtx;
+
+    /*
+     * Start the connection and handle incoming events (packet or timeout).
+     */
+    PPPDEBUG(LOG_INFO, ("pppOverSerialOpen: unit %d: Connecting\n", pd));
+    pppStart(pd);
+#if PPP_INPROC_OWNTHREAD
+    sys_thread_new(PPP_THREAD_NAME, pppInputThread, (void*)&pc->rx, PPP_THREAD_STACKSIZE, PPP_THREAD_PRIO);
+#endif /* PPP_INPROC_OWNTHREAD */
+  }
+
+  return pd;
+}
+#endif /* PPPOS_SUPPORT */
+
+#if PPPOE_SUPPORT
+static void pppOverEthernetLinkStatusCB(int pd, int up);
+
+void
+pppOverEthernetClose(int pd)
+{
+  PPPControl* pc = &pppControl[pd];
+
+  /* *TJL* There's no lcp_deinit */
+  lcp_close(pd, NULL);
+
+  pppoe_destroy(&pc->netif);
+}
+
+int pppOverEthernetOpen(struct netif *ethif, const char *service_name, const char *concentrator_name,
+                        pppLinkStatusCB_fn linkStatusCB, void *linkStatusCtx)
+{
+  PPPControl *pc;
+  int pd;
+
+  LWIP_UNUSED_ARG(service_name);
+  LWIP_UNUSED_ARG(concentrator_name);
+
+  if (linkStatusCB == NULL) {
+    /* PPP is single-threaded: without a callback,
+     * there is no way to know when the link is up. */
+    return PPPERR_PARAM;
+  }
+
+  /* Find a free PPP session descriptor. Critical region? */
+  for (pd = 0; pd < NUM_PPP && pppControl[pd].openFlag != 0; pd++);
+  if (pd >= NUM_PPP) {
+    pd = PPPERR_OPEN;
+  } else {
+    pc = &pppControl[pd];
+    memset(pc, 0, sizeof(PPPControl));
+    pc->openFlag = 1;
+    pc->ethif = ethif;
+
+    pc->linkStatusCB  = linkStatusCB;
+    pc->linkStatusCtx = linkStatusCtx;
+
+    lcp_wantoptions[pd].mru = PPPOE_MAXMTU;
+    lcp_wantoptions[pd].neg_asyncmap = 0;
+    lcp_wantoptions[pd].neg_pcompression = 0;
+    lcp_wantoptions[pd].neg_accompression = 0;
+
+    lcp_allowoptions[pd].mru = PPPOE_MAXMTU;
+    lcp_allowoptions[pd].neg_asyncmap = 0;
+    lcp_allowoptions[pd].neg_pcompression = 0;
+    lcp_allowoptions[pd].neg_accompression = 0;
+
+    if(pppoe_create(ethif, pd, pppOverEthernetLinkStatusCB, &pc->pppoe_sc) != ERR_OK) {
+      pc->openFlag = 0;
+      return PPPERR_OPEN;
+    }
+
+    pppoe_connect(pc->pppoe_sc);
+  }
+
+  return pd;
+}
+#endif /* PPPOE_SUPPORT */
+
+
+/* Close a PPP connection and release the descriptor. 
+ * Any outstanding packets in the queues are dropped.
+ * Return 0 on success, an error code on failure. */
+int
+pppClose(int pd)
+{
+  PPPControl *pc = &pppControl[pd];
+  int st = 0;
+
+  PPPDEBUG(LOG_DEBUG, ("pppClose() called\n"));
+
+  /* Disconnect */
+#if PPPOE_SUPPORT
+  if(pc->ethif) {
+    PPPDEBUG(LOG_DEBUG, ("pppClose: unit %d kill_link -> pppStop\n", pd));
+    pc->errCode = PPPERR_USER;
+    /* This will leave us at PHASE_DEAD. */
+    pppStop(pd);
+  } else
+#endif /* PPPOE_SUPPORT */
+  {
+#if PPPOS_SUPPORT
+    PPPDEBUG(LOG_DEBUG, ("pppClose: unit %d kill_link -> pppStop\n", pd));
+    pc->errCode = PPPERR_USER;
+    /* This will leave us at PHASE_DEAD. */
+    pppStop(pd);
+#if PPP_INPROC_OWNTHREAD
+    pppRecvWakeup(pd);
+#endif /* PPP_INPROC_OWNTHREAD */
+#endif /* PPPOS_SUPPORT */
+  }
+
+  return st;
+}
+
+/* This function is called when carrier is lost on the PPP channel. */
+void
+pppSigHUP(int pd)
+{
+  PPPDEBUG(LOG_DEBUG, ("pppSigHUP: unit %d sig_hup -> pppHupCB\n", pd));
+  pppHup(pd);
+}
+
+#if PPPOS_SUPPORT
+static void
+nPut(PPPControl *pc, struct pbuf *nb)
+{
+  struct pbuf *b;
+  int c;
+
+  for(b = nb; b != NULL; b = b->next) {
+    if((c = sio_write(pc->fd, b->payload, b->len)) != b->len) {
+      PPPDEBUG(LOG_WARNING,
+               ("PPP nPut: incomplete sio_write(fd:%"SZT_F", len:%d, c: 0x%"X8_F") c = %d\n", (size_t)pc->fd, b->len, c, c));
+      LINK_STATS_INC(link.err);
+      pc->lastXMit = 0; /* prepend PPP_FLAG to next packet */
+      snmp_inc_ifoutdiscards(&pc->netif);
+      pbuf_free(nb);
+      return;
+    }
+  }
+
+  snmp_add_ifoutoctets(&pc->netif, nb->tot_len);
+  snmp_inc_ifoutucastpkts(&pc->netif);
+  pbuf_free(nb);
+  LINK_STATS_INC(link.xmit);
+}
+
+/* 
+ * pppAppend - append given character to end of given pbuf.  If outACCM
+ * is not NULL and the character needs to be escaped, do so.
+ * If pbuf is full, append another.
+ * Return the current pbuf.
+ */
+static struct pbuf *
+pppAppend(u_char c, struct pbuf *nb, ext_accm *outACCM)
+{
+  struct pbuf *tb = nb;
+  
+  /* Make sure there is room for the character and an escape code.
+   * Sure we don't quite fill the buffer if the character doesn't
+   * get escaped but is one character worth complicating this? */
+  /* Note: We assume no packet header. */
+  if (nb && (PBUF_POOL_BUFSIZE - nb->len) < 2) {
+    tb = pbuf_alloc(PBUF_RAW, 0, PBUF_POOL);
+    if (tb) {
+      nb->next = tb;
+    } else {
+      LINK_STATS_INC(link.memerr);
+    }
+    nb = tb;
+  }
+
+  if (nb) {
+    if (outACCM && ESCAPE_P(*outACCM, c)) {
+      *((u_char*)nb->payload + nb->len++) = PPP_ESCAPE;
+      *((u_char*)nb->payload + nb->len++) = c ^ PPP_TRANS;
+    } else {
+      *((u_char*)nb->payload + nb->len++) = c;
+    }
+  }
+
+  return tb;
+}
+#endif /* PPPOS_SUPPORT */
+
+#if PPPOE_SUPPORT
+static err_t
+pppifOutputOverEthernet(int pd, struct pbuf *p)
+{
+  PPPControl *pc = &pppControl[pd];
+  struct pbuf *pb;
+  u_short protocol = PPP_IP;
+  int i=0;
+  u16_t tot_len;
+
+  /* @todo: try to use pbuf_header() here! */
+  pb = pbuf_alloc(PBUF_LINK, PPPOE_HDRLEN + sizeof(protocol), PBUF_RAM);
+  if(!pb) {
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.proterr);
+    snmp_inc_ifoutdiscards(&pc->netif);
+    return ERR_MEM;
+  }
+
+  pbuf_header(pb, -(s16_t)PPPOE_HDRLEN);
+
+  pc->lastXMit = sys_jiffies();
+
+  if (!pc->pcomp || protocol > 0xFF) {
+    *((u_char*)pb->payload + i++) = (protocol >> 8) & 0xFF;
+  }
+  *((u_char*)pb->payload + i) = protocol & 0xFF;
+
+  pbuf_chain(pb, p);
+  tot_len = pb->tot_len;
+
+  if(pppoe_xmit(pc->pppoe_sc, pb) != ERR_OK) {
+    LINK_STATS_INC(link.err);
+    snmp_inc_ifoutdiscards(&pc->netif);
+    return PPPERR_DEVICE;
+  }
+
+  snmp_add_ifoutoctets(&pc->netif, tot_len);
+  snmp_inc_ifoutucastpkts(&pc->netif);
+  LINK_STATS_INC(link.xmit);
+  return ERR_OK;
+}
+#endif /* PPPOE_SUPPORT */
+
+/* Send a packet on the given connection. */
+static err_t
+pppifOutput(struct netif *netif, struct pbuf *pb, ip_addr_t *ipaddr)
+{
+  int pd = (int)(size_t)netif->state;
+  PPPControl *pc = &pppControl[pd];
+#if PPPOS_SUPPORT
+  u_short protocol = PPP_IP;
+  u_int fcsOut = PPP_INITFCS;
+  struct pbuf *headMB = NULL, *tailMB = NULL, *p;
+  u_char c;
+#endif /* PPPOS_SUPPORT */
+
+  LWIP_UNUSED_ARG(ipaddr);
+
+  /* Validate parameters. */
+  /* We let any protocol value go through - it can't hurt us
+   * and the peer will just drop it if it's not accepting it. */
+  if (pd < 0 || pd >= NUM_PPP || !pc->openFlag || !pb) {
+    PPPDEBUG(LOG_WARNING, ("pppifOutput[%d]: bad parms prot=%d pb=%p\n",
+              pd, PPP_IP, pb));
+    LINK_STATS_INC(link.opterr);
+    LINK_STATS_INC(link.drop);
+    snmp_inc_ifoutdiscards(netif);
+    return ERR_ARG;
+  }
+
+  /* Check that the link is up. */
+  if (lcp_phase[pd] == PHASE_DEAD) {
+    PPPDEBUG(LOG_ERR, ("pppifOutput[%d]: link not up\n", pd));
+    LINK_STATS_INC(link.rterr);
+    LINK_STATS_INC(link.drop);
+    snmp_inc_ifoutdiscards(netif);
+    return ERR_RTE;
+  }
+
+#if PPPOE_SUPPORT
+  if(pc->ethif) {
+    return pppifOutputOverEthernet(pd, pb);
+  }
+#endif /* PPPOE_SUPPORT */
+
+#if PPPOS_SUPPORT
+  /* Grab an output buffer. */
+  headMB = pbuf_alloc(PBUF_RAW, 0, PBUF_POOL);
+  if (headMB == NULL) {
+    PPPDEBUG(LOG_WARNING, ("pppifOutput[%d]: first alloc fail\n", pd));
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.drop);
+    snmp_inc_ifoutdiscards(netif);
+    return ERR_MEM;
+  }
+
+#if VJ_SUPPORT
+  /* 
+   * Attempt Van Jacobson header compression if VJ is configured and
+   * this is an IP packet. 
+   */
+  if (protocol == PPP_IP && pc->vjEnabled) {
+    switch (vj_compress_tcp(&pc->vjComp, pb)) {
+      case TYPE_IP:
+        /* No change...
+           protocol = PPP_IP_PROTOCOL; */
+        break;
+      case TYPE_COMPRESSED_TCP:
+        protocol = PPP_VJC_COMP;
+        break;
+      case TYPE_UNCOMPRESSED_TCP:
+        protocol = PPP_VJC_UNCOMP;
+        break;
+      default:
+        PPPDEBUG(LOG_WARNING, ("pppifOutput[%d]: bad IP packet\n", pd));
+        LINK_STATS_INC(link.proterr);
+        LINK_STATS_INC(link.drop);
+        snmp_inc_ifoutdiscards(netif);
+        pbuf_free(headMB);
+        return ERR_VAL;
+    }
+  }
+#endif /* VJ_SUPPORT */
+
+  tailMB = headMB;
+
+  /* Build the PPP header. */
+  if ((sys_jiffies() - pc->lastXMit) >= PPP_MAXIDLEFLAG) {
+    tailMB = pppAppend(PPP_FLAG, tailMB, NULL);
+  }
+
+  pc->lastXMit = sys_jiffies();
+  if (!pc->accomp) {
+    fcsOut = PPP_FCS(fcsOut, PPP_ALLSTATIONS);
+    tailMB = pppAppend(PPP_ALLSTATIONS, tailMB, &pc->outACCM);
+    fcsOut = PPP_FCS(fcsOut, PPP_UI);
+    tailMB = pppAppend(PPP_UI, tailMB, &pc->outACCM);
+  }
+  if (!pc->pcomp || protocol > 0xFF) {
+    c = (protocol >> 8) & 0xFF;
+    fcsOut = PPP_FCS(fcsOut, c);
+    tailMB = pppAppend(c, tailMB, &pc->outACCM);
+  }
+  c = protocol & 0xFF;
+  fcsOut = PPP_FCS(fcsOut, c);
+  tailMB = pppAppend(c, tailMB, &pc->outACCM);
+
+  /* Load packet. */
+  for(p = pb; p; p = p->next) {
+    int n;
+    u_char *sPtr;
+
+    sPtr = (u_char*)p->payload;
+    n = p->len;
+    while (n-- > 0) {
+      c = *sPtr++;
+
+      /* Update FCS before checking for special characters. */
+      fcsOut = PPP_FCS(fcsOut, c);
+      
+      /* Copy to output buffer escaping special characters. */
+      tailMB = pppAppend(c, tailMB, &pc->outACCM);
+    }
+  }
+
+  /* Add FCS and trailing flag. */
+  c = ~fcsOut & 0xFF;
+  tailMB = pppAppend(c, tailMB, &pc->outACCM);
+  c = (~fcsOut >> 8) & 0xFF;
+  tailMB = pppAppend(c, tailMB, &pc->outACCM);
+  tailMB = pppAppend(PPP_FLAG, tailMB, NULL);
+
+  /* If we failed to complete the packet, throw it away. */
+  if (!tailMB) {
+    PPPDEBUG(LOG_WARNING,
+             ("pppifOutput[%d]: Alloc err - dropping proto=%d\n", 
+              pd, protocol));
+    pbuf_free(headMB);
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.drop);
+    snmp_inc_ifoutdiscards(netif);
+    return ERR_MEM;
+  }
+
+  /* Send it. */
+  PPPDEBUG(LOG_INFO, ("pppifOutput[%d]: proto=0x%"X16_F"\n", pd, protocol));
+
+  nPut(pc, headMB);
+#endif /* PPPOS_SUPPORT */
+
+  return ERR_OK;
+}
+
+/* Get and set parameters for the given connection.
+ * Return 0 on success, an error code on failure. */
+int
+pppIOCtl(int pd, int cmd, void *arg)
+{
+  PPPControl *pc = &pppControl[pd];
+  int st = 0;
+
+  if (pd < 0 || pd >= NUM_PPP) {
+    st = PPPERR_PARAM;
+  } else {
+    switch(cmd) {
+    case PPPCTLG_UPSTATUS:      /* Get the PPP up status. */
+      if (arg) {
+        *(int *)arg = (int)(pc->if_up);
+      } else {
+        st = PPPERR_PARAM;
+      }
+      break;
+    case PPPCTLS_ERRCODE:       /* Set the PPP error code. */
+      if (arg) {
+        pc->errCode = *(int *)arg;
+      } else {
+        st = PPPERR_PARAM;
+      }
+      break;
+    case PPPCTLG_ERRCODE:       /* Get the PPP error code. */
+      if (arg) {
+        *(int *)arg = (int)(pc->errCode);
+      } else {
+        st = PPPERR_PARAM;
+      }
+      break;
+#if PPPOS_SUPPORT
+    case PPPCTLG_FD:            /* Get the fd associated with the ppp */
+      if (arg) {
+        *(sio_fd_t *)arg = pc->fd;
+      } else {
+        st = PPPERR_PARAM;
+      }
+      break;
+#endif /* PPPOS_SUPPORT */
+    default:
+      st = PPPERR_PARAM;
+      break;
+    }
+  }
+
+  return st;
+}
+
+/*
+ * Return the Maximum Transmission Unit for the given PPP connection.
+ */
+u_short
+pppMTU(int pd)
+{
+  PPPControl *pc = &pppControl[pd];
+  u_short st;
+
+  /* Validate parameters. */
+  if (pd < 0 || pd >= NUM_PPP || !pc->openFlag) {
+    st = 0;
+  } else {
+    st = pc->mtu;
+  }
+
+  return st;
+}
+
+#if PPPOE_SUPPORT
+int
+pppWriteOverEthernet(int pd, const u_char *s, int n)
+{
+  PPPControl *pc = &pppControl[pd];
+  struct pbuf *pb;
+
+  /* skip address & flags */
+  s += 2;
+  n -= 2;
+
+  LWIP_ASSERT("PPPOE_HDRLEN + n <= 0xffff", PPPOE_HDRLEN + n <= 0xffff);
+  pb = pbuf_alloc(PBUF_LINK, (u16_t)(PPPOE_HDRLEN + n), PBUF_RAM);
+  if(!pb) {
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.proterr);
+    snmp_inc_ifoutdiscards(&pc->netif);
+    return PPPERR_ALLOC;
+  }
+
+  pbuf_header(pb, -(s16_t)PPPOE_HDRLEN);
+
+  pc->lastXMit = sys_jiffies();
+
+  MEMCPY(pb->payload, s, n);
+
+  if(pppoe_xmit(pc->pppoe_sc, pb) != ERR_OK) {
+    LINK_STATS_INC(link.err);
+    snmp_inc_ifoutdiscards(&pc->netif);
+    return PPPERR_DEVICE;
+  }
+
+  snmp_add_ifoutoctets(&pc->netif, (u16_t)n);
+  snmp_inc_ifoutucastpkts(&pc->netif);
+  LINK_STATS_INC(link.xmit);
+  return PPPERR_NONE;
+}
+#endif /* PPPOE_SUPPORT */
+
+/*
+ * Write n characters to a ppp link.
+ *  RETURN: >= 0 Number of characters written
+ *           -1 Failed to write to device
+ */
+int
+pppWrite(int pd, const u_char *s, int n)
+{
+  PPPControl *pc = &pppControl[pd];
+#if PPPOS_SUPPORT
+  u_char c;
+  u_int fcsOut;
+  struct pbuf *headMB, *tailMB;
+#endif /* PPPOS_SUPPORT */
+
+#if PPPOE_SUPPORT
+  if(pc->ethif) {
+    return pppWriteOverEthernet(pd, s, n);
+  }
+#endif /* PPPOE_SUPPORT */
+
+#if PPPOS_SUPPORT
+  headMB = pbuf_alloc(PBUF_RAW, 0, PBUF_POOL);
+  if (headMB == NULL) {
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.proterr);
+    snmp_inc_ifoutdiscards(&pc->netif);
+    return PPPERR_ALLOC;
+  }
+
+  tailMB = headMB;
+
+  /* If the link has been idle, we'll send a fresh flag character to
+   * flush any noise. */
+  if ((sys_jiffies() - pc->lastXMit) >= PPP_MAXIDLEFLAG) {
+    tailMB = pppAppend(PPP_FLAG, tailMB, NULL);
+  }
+  pc->lastXMit = sys_jiffies();
+
+  fcsOut = PPP_INITFCS;
+  /* Load output buffer. */
+  while (n-- > 0) {
+    c = *s++;
+
+    /* Update FCS before checking for special characters. */
+    fcsOut = PPP_FCS(fcsOut, c);
+
+    /* Copy to output buffer escaping special characters. */
+    tailMB = pppAppend(c, tailMB, &pc->outACCM);
+  }
+    
+  /* Add FCS and trailing flag. */
+  c = ~fcsOut & 0xFF;
+  tailMB = pppAppend(c, tailMB, &pc->outACCM);
+  c = (~fcsOut >> 8) & 0xFF;
+  tailMB = pppAppend(c, tailMB, &pc->outACCM);
+  tailMB = pppAppend(PPP_FLAG, tailMB, NULL);
+
+  /* If we failed to complete the packet, throw it away.
+   * Otherwise send it. */
+  if (!tailMB) {
+    PPPDEBUG(LOG_WARNING,
+             ("pppWrite[%d]: Alloc err - dropping pbuf len=%d\n", pd, headMB->len));
+           /*"pppWrite[%d]: Alloc err - dropping %d:%.*H", pd, headMB->len, LWIP_MIN(headMB->len * 2, 40), headMB->payload)); */
+    pbuf_free(headMB);
+    LINK_STATS_INC(link.memerr);
+    LINK_STATS_INC(link.proterr);
+    snmp_inc_ifoutdiscards(&pc->netif);
+    return PPPERR_ALLOC;
+  }
+
+  PPPDEBUG(LOG_INFO, ("pppWrite[%d]: len=%d\n", pd, headMB->len));
+                   /* "pppWrite[%d]: %d:%.*H", pd, headMB->len, LWIP_MIN(headMB->len * 2, 40), headMB->payload)); */
+  nPut(pc, headMB);
+#endif /* PPPOS_SUPPORT */
+
+  return PPPERR_NONE;
+}
+
+/*
+ * ppp_send_config - configure the transmit characteristics of
+ * the ppp interface.
+ */
+void
+ppp_send_config( int unit, u16_t mtu, u32_t asyncmap, int pcomp, int accomp)
+{
+  PPPControl *pc = &pppControl[unit];
+  int i;
+  
+  pc->mtu = mtu;
+  pc->pcomp = pcomp;
+  pc->accomp = accomp;
+  
+  /* Load the ACCM bits for the 32 control codes. */
+  for (i = 0; i < 32/8; i++) {
+    pc->outACCM[i] = (u_char)((asyncmap >> (8 * i)) & 0xFF);
+  }
+  PPPDEBUG(LOG_INFO, ("ppp_send_config[%d]: outACCM=%X %X %X %X\n",
+            unit,
+            pc->outACCM[0], pc->outACCM[1], pc->outACCM[2], pc->outACCM[3]));
+}
+
+
+/*
+ * ppp_set_xaccm - set the extended transmit ACCM for the interface.
+ */
+void
+ppp_set_xaccm(int unit, ext_accm *accm)
+{
+  SMEMCPY(pppControl[unit].outACCM, accm, sizeof(ext_accm));
+  PPPDEBUG(LOG_INFO, ("ppp_set_xaccm[%d]: outACCM=%X %X %X %X\n",
+            unit,
+            pppControl[unit].outACCM[0],
+            pppControl[unit].outACCM[1],
+            pppControl[unit].outACCM[2],
+            pppControl[unit].outACCM[3]));
+}
+
+
+/*
+ * ppp_recv_config - configure the receive-side characteristics of
+ * the ppp interface.
+ */
+void
+ppp_recv_config( int unit, int mru, u32_t asyncmap, int pcomp, int accomp)
+{
+  PPPControl *pc = &pppControl[unit];
+  int i;
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  LWIP_UNUSED_ARG(accomp);
+  LWIP_UNUSED_ARG(pcomp);
+  LWIP_UNUSED_ARG(mru);
+
+  /* Load the ACCM bits for the 32 control codes. */
+  SYS_ARCH_PROTECT(lev);
+  for (i = 0; i < 32 / 8; i++) {
+    /* @todo: does this work? ext_accm has been modified from pppd! */
+    pc->rx.inACCM[i] = (u_char)(asyncmap >> (i * 8));
+  }
+  SYS_ARCH_UNPROTECT(lev);
+  PPPDEBUG(LOG_INFO, ("ppp_recv_config[%d]: inACCM=%X %X %X %X\n",
+            unit,
+            pc->rx.inACCM[0], pc->rx.inACCM[1], pc->rx.inACCM[2], pc->rx.inACCM[3]));
+}
+
+#if 0
+/*
+ * ccp_test - ask kernel whether a given compression method
+ * is acceptable for use.  Returns 1 if the method and parameters
+ * are OK, 0 if the method is known but the parameters are not OK
+ * (e.g. code size should be reduced), or -1 if the method is unknown.
+ */
+int
+ccp_test( int unit, int opt_len,  int for_transmit, u_char *opt_ptr)
+{
+  return 0; /* XXX Currently no compression. */
+}
+
+/*
+ * ccp_flags_set - inform kernel about the current state of CCP.
+ */
+void
+ccp_flags_set(int unit, int isopen, int isup)
+{
+  /* XXX */
+}
+
+/*
+ * ccp_fatal_error - returns 1 if decompression was disabled as a
+ * result of an error detected after decompression of a packet,
+ * 0 otherwise.  This is necessary because of patent nonsense.
+ */
+int
+ccp_fatal_error(int unit)
+{
+  /* XXX */
+  return 0;
+}
+#endif
+
+/*
+ * get_idle_time - return how long the link has been idle.
+ */
+int
+get_idle_time(int u, struct ppp_idle *ip)
+{
+  /* XXX */
+  LWIP_UNUSED_ARG(u);
+  LWIP_UNUSED_ARG(ip);
+
+  return 0;
+}
+
+
+/*
+ * Return user specified netmask, modified by any mask we might determine
+ * for address `addr' (in network byte order).
+ * Here we scan through the system's list of interfaces, looking for
+ * any non-point-to-point interfaces which might appear to be on the same
+ * network as `addr'.  If we find any, we OR in their netmask to the
+ * user-specified netmask.
+ */
+u32_t
+GetMask(u32_t addr)
+{
+  u32_t mask, nmask;
+
+  addr = htonl(addr);
+  if (IP_CLASSA(addr)) { /* determine network mask for address class */
+    nmask = IP_CLASSA_NET;
+  } else if (IP_CLASSB(addr)) {
+    nmask = IP_CLASSB_NET;
+  } else { 
+    nmask = IP_CLASSC_NET;
+  }
+
+  /* class D nets are disallowed by bad_ip_adrs */
+  mask = PP_HTONL(0xffffff00UL) | htonl(nmask);
+  
+  /* XXX
+   * Scan through the system's network interfaces.
+   * Get each netmask and OR them into our mask.
+   */
+
+  return mask;
+}
+
+/*
+ * sifvjcomp - config tcp header compression
+ */
+int
+sifvjcomp(int pd, int vjcomp, u8_t cidcomp, u8_t maxcid)
+{
+#if PPPOS_SUPPORT && VJ_SUPPORT
+  PPPControl *pc = &pppControl[pd];
+  
+  pc->vjEnabled = vjcomp;
+  pc->vjComp.compressSlot = cidcomp;
+  pc->vjComp.maxSlotIndex = maxcid;
+  PPPDEBUG(LOG_INFO, ("sifvjcomp: VJ compress enable=%d slot=%d max slot=%d\n",
+            vjcomp, cidcomp, maxcid));
+#else /* PPPOS_SUPPORT && VJ_SUPPORT */
+  LWIP_UNUSED_ARG(pd);
+  LWIP_UNUSED_ARG(vjcomp);
+  LWIP_UNUSED_ARG(cidcomp);
+  LWIP_UNUSED_ARG(maxcid);
+#endif /* PPPOS_SUPPORT && VJ_SUPPORT */
+
+  return 0;
+}
+
+/*
+ * pppifNetifInit - netif init callback
+ */
+static err_t
+pppifNetifInit(struct netif *netif)
+{
+  netif->name[0] = 'p';
+  netif->name[1] = 'p';
+  netif->output = pppifOutput;
+  netif->mtu = pppMTU((int)(size_t)netif->state);
+  netif->flags = NETIF_FLAG_POINTTOPOINT | NETIF_FLAG_LINK_UP;
+#if LWIP_NETIF_HOSTNAME
+  /* @todo: Initialize interface hostname */
+  /* netif_set_hostname(netif, "lwip"); */
+#endif /* LWIP_NETIF_HOSTNAME */
+  return ERR_OK;
+}
+
+
+/*
+ * sifup - Config the interface up and enable IP packets to pass.
+ */
+int
+sifup(int pd)
+{
+  PPPControl *pc = &pppControl[pd];
+  int st = 1;
+  
+  if (pd < 0 || pd >= NUM_PPP || !pc->openFlag) {
+    st = 0;
+    PPPDEBUG(LOG_WARNING, ("sifup[%d]: bad parms\n", pd));
+  } else {
+    netif_remove(&pc->netif);
+    if (netif_add(&pc->netif, &pc->addrs.our_ipaddr, &pc->addrs.netmask,
+                  &pc->addrs.his_ipaddr, (void *)(size_t)pd, pppifNetifInit, ip_input)) {
+      netif_set_up(&pc->netif);
+      pc->if_up = 1;
+      pc->errCode = PPPERR_NONE;
+
+      PPPDEBUG(LOG_DEBUG, ("sifup: unit %d: linkStatusCB=%p errCode=%d\n", pd, pc->linkStatusCB, pc->errCode));
+      if (pc->linkStatusCB) {
+        pc->linkStatusCB(pc->linkStatusCtx, pc->errCode, &pc->addrs);
+      }
+    } else {
+      st = 0;
+      PPPDEBUG(LOG_ERR, ("sifup[%d]: netif_add failed\n", pd));
+    }
+  }
+
+  return st;
+}
+
+/*
+ * sifnpmode - Set the mode for handling packets for a given NP.
+ */
+int
+sifnpmode(int u, int proto, enum NPmode mode)
+{
+  LWIP_UNUSED_ARG(u);
+  LWIP_UNUSED_ARG(proto);
+  LWIP_UNUSED_ARG(mode);
+  return 0;
+}
+
+/*
+ * sifdown - Config the interface down and disable IP.
+ */
+int
+sifdown(int pd)
+{
+  PPPControl *pc = &pppControl[pd];
+  int st = 1;
+  
+  if (pd < 0 || pd >= NUM_PPP || !pc->openFlag) {
+    st = 0;
+    PPPDEBUG(LOG_WARNING, ("sifdown[%d]: bad parms\n", pd));
+  } else {
+    pc->if_up = 0;
+    /* make sure the netif status callback is called */
+    netif_set_down(&pc->netif);
+    netif_remove(&pc->netif);
+    PPPDEBUG(LOG_DEBUG, ("sifdown: unit %d: linkStatusCB=%p errCode=%d\n", pd, pc->linkStatusCB, pc->errCode));
+    if (pc->linkStatusCB) {
+      pc->linkStatusCB(pc->linkStatusCtx, PPPERR_CONNECT, NULL);
+    }
+  }
+  return st;
+}
+
+/**
+ * sifaddr - Config the interface IP addresses and netmask.
+ * @param pd Interface unit ???
+ * @param o Our IP address ???
+ * @param h His IP address ???
+ * @param m IP subnet mask ???
+ * @param ns1 Primary DNS
+ * @param ns2 Secondary DNS
+ */
+int
+sifaddr( int pd, u32_t o, u32_t h, u32_t m, u32_t ns1, u32_t ns2)
+{
+  PPPControl *pc = &pppControl[pd];
+  int st = 1;
+  
+  if (pd < 0 || pd >= NUM_PPP || !pc->openFlag) {
+    st = 0;
+    PPPDEBUG(LOG_WARNING, ("sifup[%d]: bad parms\n", pd));
+  } else {
+    SMEMCPY(&pc->addrs.our_ipaddr, &o, sizeof(o));
+    SMEMCPY(&pc->addrs.his_ipaddr, &h, sizeof(h));
+    SMEMCPY(&pc->addrs.netmask, &m, sizeof(m));
+    SMEMCPY(&pc->addrs.dns1, &ns1, sizeof(ns1));
+    SMEMCPY(&pc->addrs.dns2, &ns2, sizeof(ns2));
+  }
+  return st;
+}
+
+/**
+ * cifaddr - Clear the interface IP addresses, and delete routes
+ * through the interface if possible.
+ * @param pd Interface unit ???
+ * @param o Our IP address ???
+ * @param h IP broadcast address ???
+ */
+int
+cifaddr( int pd, u32_t o, u32_t h)
+{
+  PPPControl *pc = &pppControl[pd];
+  int st = 1;
+  
+  LWIP_UNUSED_ARG(o);
+  LWIP_UNUSED_ARG(h);
+  if (pd < 0 || pd >= NUM_PPP || !pc->openFlag) {
+    st = 0;
+    PPPDEBUG(LOG_WARNING, ("sifup[%d]: bad parms\n", pd));
+  } else {
+    IP4_ADDR(&pc->addrs.our_ipaddr, 0,0,0,0);
+    IP4_ADDR(&pc->addrs.his_ipaddr, 0,0,0,0);
+    IP4_ADDR(&pc->addrs.netmask, 255,255,255,0);
+    IP4_ADDR(&pc->addrs.dns1, 0,0,0,0);
+    IP4_ADDR(&pc->addrs.dns2, 0,0,0,0);
+  }
+  return st;
+}
+
+/*
+ * sifdefaultroute - assign a default route through the address given.
+ */
+int
+sifdefaultroute(int pd, u32_t l, u32_t g)
+{
+  PPPControl *pc = &pppControl[pd];
+  int st = 1;
+
+  LWIP_UNUSED_ARG(l);
+  LWIP_UNUSED_ARG(g);
+
+  if (pd < 0 || pd >= NUM_PPP || !pc->openFlag) {
+    st = 0;
+    PPPDEBUG(LOG_WARNING, ("sifup[%d]: bad parms\n", pd));
+  } else {
+    netif_set_default(&pc->netif);
+  }
+
+  /* TODO: check how PPP handled the netMask, previously not set by ipSetDefault */
+
+  return st;
+}
+
+/*
+ * cifdefaultroute - delete a default route through the address given.
+ */
+int
+cifdefaultroute(int pd, u32_t l, u32_t g)
+{
+  PPPControl *pc = &pppControl[pd];
+  int st = 1;
+
+  LWIP_UNUSED_ARG(l);
+  LWIP_UNUSED_ARG(g);
+
+  if (pd < 0 || pd >= NUM_PPP || !pc->openFlag) {
+    st = 0;
+    PPPDEBUG(LOG_WARNING, ("sifup[%d]: bad parms\n", pd));
+  } else {
+    netif_set_default(NULL);
+  }
+
+  return st;
+}
+
+/**********************************/
+/*** LOCAL FUNCTION DEFINITIONS ***/
+/**********************************/
+
+#if PPPOS_SUPPORT && PPP_INPROC_OWNTHREAD
+/* The main PPP process function.  This implements the state machine according
+ * to section 4 of RFC 1661: The Point-To-Point Protocol. */
+static void
+pppInputThread(void *arg)
+{
+  int count;
+  PPPControlRx *pcrx = arg;
+
+  while (lcp_phase[pcrx->pd] != PHASE_DEAD) {
+    count = sio_read(pcrx->fd, pcrx->rxbuf, PPPOS_RX_BUFSIZE);
+    if(count > 0) {
+      pppInProc(pcrx, pcrx->rxbuf, count);
+    } else {
+      /* nothing received, give other tasks a chance to run */
+      sys_msleep(1);
+    }
+  }
+}
+#endif /* PPPOS_SUPPORT && PPP_INPROC_OWNTHREAD */
+
+#if PPPOE_SUPPORT
+
+void
+pppOverEthernetInitFailed(int pd)
+{
+  PPPControl* pc;
+
+  pppHup(pd);
+  pppStop(pd);
+
+  pc = &pppControl[pd];
+  pppoe_destroy(&pc->netif);
+  pc->openFlag = 0;
+
+  if(pc->linkStatusCB) {
+    pc->linkStatusCB(pc->linkStatusCtx, pc->errCode ? pc->errCode : PPPERR_PROTOCOL, NULL);
+  }
+}
+
+static void
+pppOverEthernetLinkStatusCB(int pd, int up)
+{
+  if(up) {
+    PPPDEBUG(LOG_INFO, ("pppOverEthernetLinkStatusCB: unit %d: Connecting\n", pd));
+    pppStart(pd);
+  } else {
+    pppOverEthernetInitFailed(pd);
+  }
+}
+#endif /* PPPOE_SUPPORT */
+
+struct pbuf *
+pppSingleBuf(struct pbuf *p)
+{
+  struct pbuf *q, *b;
+  u_char *pl;
+
+  if(p->tot_len == p->len) {
+    return p;
+  }
+
+  q = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
+  if(!q) {
+    PPPDEBUG(LOG_ERR,
+             ("pppSingleBuf: unable to alloc new buf (%d)\n", p->tot_len));
+    return p; /* live dangerously */
+  }
+
+  for(b = p, pl = q->payload; b != NULL; b = b->next) {
+    MEMCPY(pl, b->payload, b->len);
+    pl += b->len;
+  }
+
+  pbuf_free(p);
+
+  return q;
+}
+
+/** Input helper struct, must be packed since it is stored to pbuf->payload,
+ * which might be unaligned.
+ */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct pppInputHeader {
+  PACK_STRUCT_FIELD(int unit);
+  PACK_STRUCT_FIELD(u16_t proto);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/*
+ * Pass the processed input packet to the appropriate handler.
+ * This function and all handlers run in the context of the tcpip_thread
+ */
+static void
+pppInput(void *arg)
+{
+  struct pbuf *nb = (struct pbuf *)arg;
+  u16_t protocol;
+  int pd;
+
+  pd = ((struct pppInputHeader *)nb->payload)->unit;
+  protocol = ((struct pppInputHeader *)nb->payload)->proto;
+    
+  if(pbuf_header(nb, -(int)sizeof(struct pppInputHeader))) {
+    LWIP_ASSERT("pbuf_header failed\n", 0);
+    goto drop;
+  }
+
+  LINK_STATS_INC(link.recv);
+  snmp_inc_ifinucastpkts(&pppControl[pd].netif);
+  snmp_add_ifinoctets(&pppControl[pd].netif, nb->tot_len);
+
+  /*
+   * Toss all non-LCP packets unless LCP is OPEN.
+   * Until we get past the authentication phase, toss all packets
+   * except LCP, LQR and authentication packets.
+   */
+  if((lcp_phase[pd] <= PHASE_AUTHENTICATE) && (protocol != PPP_LCP)) {
+    if(!((protocol == PPP_LQR) || (protocol == PPP_PAP) || (protocol == PPP_CHAP)) ||
+        (lcp_phase[pd] != PHASE_AUTHENTICATE)) {
+      PPPDEBUG(LOG_INFO, ("pppInput: discarding proto 0x%"X16_F" in phase %d\n", protocol, lcp_phase[pd]));
+      goto drop;
+    }
+  }
+
+  switch(protocol) {
+    case PPP_VJC_COMP:      /* VJ compressed TCP */
+#if PPPOS_SUPPORT && VJ_SUPPORT
+      PPPDEBUG(LOG_INFO, ("pppInput[%d]: vj_comp in pbuf len=%d\n", pd, nb->len));
+      /*
+       * Clip off the VJ header and prepend the rebuilt TCP/IP header and
+       * pass the result to IP.
+       */
+      if ((vj_uncompress_tcp(&nb, &pppControl[pd].vjComp) >= 0) && (pppControl[pd].netif.input)) {
+        pppControl[pd].netif.input(nb, &pppControl[pd].netif);
+        return;
+      }
+      /* Something's wrong so drop it. */
+      PPPDEBUG(LOG_WARNING, ("pppInput[%d]: Dropping VJ compressed\n", pd));
+#else  /* PPPOS_SUPPORT && VJ_SUPPORT */
+      /* No handler for this protocol so drop the packet. */
+      PPPDEBUG(LOG_INFO, ("pppInput[%d]: drop VJ Comp in %d:%s\n", pd, nb->len, nb->payload));
+#endif /* PPPOS_SUPPORT && VJ_SUPPORT */
+      break;
+
+    case PPP_VJC_UNCOMP:    /* VJ uncompressed TCP */
+#if PPPOS_SUPPORT && VJ_SUPPORT
+      PPPDEBUG(LOG_INFO, ("pppInput[%d]: vj_un in pbuf len=%d\n", pd, nb->len));
+      /*
+       * Process the TCP/IP header for VJ header compression and then pass
+       * the packet to IP.
+       */
+      if ((vj_uncompress_uncomp(nb, &pppControl[pd].vjComp) >= 0) && pppControl[pd].netif.input) {
+        pppControl[pd].netif.input(nb, &pppControl[pd].netif);
+        return;
+      }
+      /* Something's wrong so drop it. */
+      PPPDEBUG(LOG_WARNING, ("pppInput[%d]: Dropping VJ uncompressed\n", pd));
+#else  /* PPPOS_SUPPORT && VJ_SUPPORT */
+      /* No handler for this protocol so drop the packet. */
+      PPPDEBUG(LOG_INFO,
+               ("pppInput[%d]: drop VJ UnComp in %d:.*H\n", 
+                pd, nb->len, LWIP_MIN(nb->len * 2, 40), nb->payload));
+#endif /* PPPOS_SUPPORT && VJ_SUPPORT */
+      break;
+
+    case PPP_IP:            /* Internet Protocol */
+      PPPDEBUG(LOG_INFO, ("pppInput[%d]: ip in pbuf len=%d\n", pd, nb->len));
+      if (pppControl[pd].netif.input) {
+        pppControl[pd].netif.input(nb, &pppControl[pd].netif);
+        return;
+      }
+      break;
+
+    default: {
+      struct protent *protp;
+      int i;
+
+      /*
+       * Upcall the proper protocol input routine.
+       */
+      for (i = 0; (protp = ppp_protocols[i]) != NULL; ++i) {
+        if (protp->protocol == protocol && protp->enabled_flag) {
+          PPPDEBUG(LOG_INFO, ("pppInput[%d]: %s len=%d\n", pd, protp->name, nb->len));
+          nb = pppSingleBuf(nb);
+          (*protp->input)(pd, nb->payload, nb->len);
+          PPPDEBUG(LOG_DETAIL, ("pppInput[%d]: packet processed\n", pd));
+          goto out;
+        }
+      }
+
+      /* No handler for this protocol so reject the packet. */
+      PPPDEBUG(LOG_INFO, ("pppInput[%d]: rejecting unsupported proto 0x%"X16_F" len=%d\n", pd, protocol, nb->len));
+      if (pbuf_header(nb, sizeof(protocol))) {
+        LWIP_ASSERT("pbuf_header failed\n", 0);
+        goto drop;
+      }
+#if BYTE_ORDER == LITTLE_ENDIAN
+      protocol = htons(protocol);
+#endif /* BYTE_ORDER == LITTLE_ENDIAN */
+      SMEMCPY(nb->payload, &protocol, sizeof(protocol));
+      lcp_sprotrej(pd, nb->payload, nb->len);
+    }
+    break;
+  }
+
+drop:
+  LINK_STATS_INC(link.drop);
+  snmp_inc_ifindiscards(&pppControl[pd].netif);
+
+out:
+  pbuf_free(nb);
+  return;
+}
+
+#if PPPOS_SUPPORT
+/*
+ * Drop the input packet.
+ */
+static void
+pppFreeCurrentInputPacket(PPPControlRx *pcrx)
+{
+  if (pcrx->inHead != NULL) {
+    if (pcrx->inTail && (pcrx->inTail != pcrx->inHead)) {
+      pbuf_free(pcrx->inTail);
+    }
+    pbuf_free(pcrx->inHead);
+    pcrx->inHead = NULL;
+  }
+  pcrx->inTail = NULL;
+}
+
+/*
+ * Drop the input packet and increase error counters.
+ */
+static void
+pppDrop(PPPControlRx *pcrx)
+{
+  if (pcrx->inHead != NULL) {
+#if 0
+    PPPDEBUG(LOG_INFO, ("pppDrop: %d:%.*H\n", pcrx->inHead->len, min(60, pcrx->inHead->len * 2), pcrx->inHead->payload));
+#endif
+    PPPDEBUG(LOG_INFO, ("pppDrop: pbuf len=%d, addr %p\n", pcrx->inHead->len, (void*)pcrx->inHead));
+  }
+  pppFreeCurrentInputPacket(pcrx);
+#if VJ_SUPPORT
+  vj_uncompress_err(&pppControl[pcrx->pd].vjComp);
+#endif /* VJ_SUPPORT */
+
+  LINK_STATS_INC(link.drop);
+  snmp_inc_ifindiscards(&pppControl[pcrx->pd].netif);
+}
+
+#if !PPP_INPROC_OWNTHREAD
+/** Pass received raw characters to PPPoS to be decoded. This function is
+ * thread-safe and can be called from a dedicated RX-thread or from a main-loop.
+ *
+ * @param pd PPP descriptor index, returned by pppOpen()
+ * @param data received data
+ * @param len length of received data
+ */
+void
+pppos_input(int pd, u_char* data, int len)
+{
+  pppInProc(&pppControl[pd].rx, data, len);
+}
+#endif
+
+/**
+ * Process a received octet string.
+ */
+static void
+pppInProc(PPPControlRx *pcrx, u_char *s, int l)
+{
+  struct pbuf *nextNBuf;
+  u_char curChar;
+  u_char escaped;
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  PPPDEBUG(LOG_DEBUG, ("pppInProc[%d]: got %d bytes\n", pcrx->pd, l));
+  while (l-- > 0) {
+    curChar = *s++;
+
+    SYS_ARCH_PROTECT(lev);
+    escaped = ESCAPE_P(pcrx->inACCM, curChar);
+    SYS_ARCH_UNPROTECT(lev);
+    /* Handle special characters. */
+    if (escaped) {
+      /* Check for escape sequences. */
+      /* XXX Note that this does not handle an escaped 0x5d character which
+       * would appear as an escape character.  Since this is an ASCII ']'
+       * and there is no reason that I know of to escape it, I won't complicate
+       * the code to handle this case. GLL */
+      if (curChar == PPP_ESCAPE) {
+        pcrx->inEscaped = 1;
+      /* Check for the flag character. */
+      } else if (curChar == PPP_FLAG) {
+        /* If this is just an extra flag character, ignore it. */
+        if (pcrx->inState <= PDADDRESS) {
+          /* ignore it */;
+        /* If we haven't received the packet header, drop what has come in. */
+        } else if (pcrx->inState < PDDATA) {
+          PPPDEBUG(LOG_WARNING,
+                   ("pppInProc[%d]: Dropping incomplete packet %d\n", 
+                    pcrx->pd, pcrx->inState));
+          LINK_STATS_INC(link.lenerr);
+          pppDrop(pcrx);
+        /* If the fcs is invalid, drop the packet. */
+        } else if (pcrx->inFCS != PPP_GOODFCS) {
+          PPPDEBUG(LOG_INFO,
+                   ("pppInProc[%d]: Dropping bad fcs 0x%"X16_F" proto=0x%"X16_F"\n", 
+                    pcrx->pd, pcrx->inFCS, pcrx->inProtocol));
+          /* Note: If you get lots of these, check for UART frame errors or try different baud rate */
+          LINK_STATS_INC(link.chkerr);
+          pppDrop(pcrx);
+        /* Otherwise it's a good packet so pass it on. */
+        } else {
+          struct pbuf *inp;
+          /* Trim off the checksum. */
+          if(pcrx->inTail->len > 2) {
+            pcrx->inTail->len -= 2;
+
+            pcrx->inTail->tot_len = pcrx->inTail->len;
+            if (pcrx->inTail != pcrx->inHead) {
+              pbuf_cat(pcrx->inHead, pcrx->inTail);
+            }
+          } else {
+            pcrx->inTail->tot_len = pcrx->inTail->len;
+            if (pcrx->inTail != pcrx->inHead) {
+              pbuf_cat(pcrx->inHead, pcrx->inTail);
+            }
+
+            pbuf_realloc(pcrx->inHead, pcrx->inHead->tot_len - 2);
+          }
+
+          /* Dispatch the packet thereby consuming it. */
+          inp = pcrx->inHead;
+          /* Packet consumed, release our references. */
+          pcrx->inHead = NULL;
+          pcrx->inTail = NULL;
+#if PPP_INPROC_MULTITHREADED
+          if(tcpip_callback_with_block(pppInput, inp, 0) != ERR_OK) {
+            PPPDEBUG(LOG_ERR, ("pppInProc[%d]: tcpip_callback() failed, dropping packet\n", pcrx->pd));
+            pbuf_free(inp);
+            LINK_STATS_INC(link.drop);
+            snmp_inc_ifindiscards(&pppControl[pcrx->pd].netif);
+          }
+#else /* PPP_INPROC_MULTITHREADED */
+          pppInput(inp);
+#endif /* PPP_INPROC_MULTITHREADED */
+        }
+
+        /* Prepare for a new packet. */
+        pcrx->inFCS = PPP_INITFCS;
+        pcrx->inState = PDADDRESS;
+        pcrx->inEscaped = 0;
+      /* Other characters are usually control characters that may have
+       * been inserted by the physical layer so here we just drop them. */
+      } else {
+        PPPDEBUG(LOG_WARNING,
+                 ("pppInProc[%d]: Dropping ACCM char <%d>\n", pcrx->pd, curChar));
+      }
+    /* Process other characters. */
+    } else {
+      /* Unencode escaped characters. */
+      if (pcrx->inEscaped) {
+        pcrx->inEscaped = 0;
+        curChar ^= PPP_TRANS;
+      }
+
+      /* Process character relative to current state. */
+      switch(pcrx->inState) {
+        case PDIDLE:                    /* Idle state - waiting. */
+          /* Drop the character if it's not 0xff
+           * we would have processed a flag character above. */
+          if (curChar != PPP_ALLSTATIONS) {
+            break;
+          }
+
+        /* Fall through */
+        case PDSTART:                   /* Process start flag. */
+          /* Prepare for a new packet. */
+          pcrx->inFCS = PPP_INITFCS;
+
+        /* Fall through */
+        case PDADDRESS:                 /* Process address field. */
+          if (curChar == PPP_ALLSTATIONS) {
+            pcrx->inState = PDCONTROL;
+            break;
+          }
+          /* Else assume compressed address and control fields so
+           * fall through to get the protocol... */
+        case PDCONTROL:                 /* Process control field. */
+          /* If we don't get a valid control code, restart. */
+          if (curChar == PPP_UI) {
+            pcrx->inState = PDPROTOCOL1;
+            break;
+          }
+#if 0
+          else {
+            PPPDEBUG(LOG_WARNING,
+                     ("pppInProc[%d]: Invalid control <%d>\n", pcrx->pd, curChar));
+            pcrx->inState = PDSTART;
+          }
+#endif
+        case PDPROTOCOL1:               /* Process protocol field 1. */
+          /* If the lower bit is set, this is the end of the protocol
+           * field. */
+          if (curChar & 1) {
+            pcrx->inProtocol = curChar;
+            pcrx->inState = PDDATA;
+          } else {
+            pcrx->inProtocol = (u_int)curChar << 8;
+            pcrx->inState = PDPROTOCOL2;
+          }
+          break;
+        case PDPROTOCOL2:               /* Process protocol field 2. */
+          pcrx->inProtocol |= curChar;
+          pcrx->inState = PDDATA;
+          break;
+        case PDDATA:                    /* Process data byte. */
+          /* Make space to receive processed data. */
+          if (pcrx->inTail == NULL || pcrx->inTail->len == PBUF_POOL_BUFSIZE) {
+            if (pcrx->inTail != NULL) {
+              pcrx->inTail->tot_len = pcrx->inTail->len;
+              if (pcrx->inTail != pcrx->inHead) {
+                pbuf_cat(pcrx->inHead, pcrx->inTail);
+                /* give up the inTail reference now */
+                pcrx->inTail = NULL;
+              }
+            }
+            /* If we haven't started a packet, we need a packet header. */
+            nextNBuf = pbuf_alloc(PBUF_RAW, 0, PBUF_POOL);
+            if (nextNBuf == NULL) {
+              /* No free buffers.  Drop the input packet and let the
+               * higher layers deal with it.  Continue processing
+               * the received pbuf chain in case a new packet starts. */
+              PPPDEBUG(LOG_ERR, ("pppInProc[%d]: NO FREE MBUFS!\n", pcrx->pd));
+              LINK_STATS_INC(link.memerr);
+              pppDrop(pcrx);
+              pcrx->inState = PDSTART;  /* Wait for flag sequence. */
+              break;
+            }
+            if (pcrx->inHead == NULL) {
+              struct pppInputHeader *pih = nextNBuf->payload;
+
+              pih->unit = pcrx->pd;
+              pih->proto = pcrx->inProtocol;
+
+              nextNBuf->len += sizeof(*pih);
+
+              pcrx->inHead = nextNBuf;
+            }
+            pcrx->inTail = nextNBuf;
+          }
+          /* Load character into buffer. */
+          ((u_char*)pcrx->inTail->payload)[pcrx->inTail->len++] = curChar;
+          break;
+      }
+
+      /* update the frame check sequence number. */
+      pcrx->inFCS = PPP_FCS(pcrx->inFCS, curChar);
+    }
+  } /* while (l-- > 0), all bytes processed */
+
+  avRandomize();
+}
+#endif /* PPPOS_SUPPORT */
+
+#if PPPOE_SUPPORT
+void
+pppInProcOverEthernet(int pd, struct pbuf *pb)
+{
+  struct pppInputHeader *pih;
+  u16_t inProtocol;
+
+  if(pb->len < sizeof(inProtocol)) {
+    PPPDEBUG(LOG_ERR, ("pppInProcOverEthernet: too small for protocol field\n"));
+    goto drop;
+  }
+
+  inProtocol = (((u8_t *)pb->payload)[0] << 8) | ((u8_t*)pb->payload)[1];
+
+  /* make room for pppInputHeader - should not fail */
+  if (pbuf_header(pb, sizeof(*pih) - sizeof(inProtocol)) != 0) {
+    PPPDEBUG(LOG_ERR, ("pppInProcOverEthernet: could not allocate room for header\n"));
+    goto drop;
+  }
+
+  pih = pb->payload;
+
+  pih->unit = pd;
+  pih->proto = inProtocol;
+
+  /* Dispatch the packet thereby consuming it. */
+  pppInput(pb);
+  return;
+
+drop:
+  LINK_STATS_INC(link.drop);
+  snmp_inc_ifindiscards(&pppControl[pd].netif);
+  pbuf_free(pb);
+  return;
+}
+#endif /* PPPOE_SUPPORT */
+
+#if LWIP_NETIF_STATUS_CALLBACK
+/** Set the status callback of a PPP's netif
+ *
+ * @param pd The PPP descriptor returned by pppOpen()
+ * @param status_callback pointer to the status callback function
+ *
+ * @see netif_set_status_callback
+ */
+void
+ppp_set_netif_statuscallback(int pd, netif_status_callback_fn status_callback)
+{
+  netif_set_status_callback(&pppControl[pd].netif, status_callback); 
+}
+#endif /* LWIP_NETIF_STATUS_CALLBACK */
+
+#if LWIP_NETIF_LINK_CALLBACK
+/** Set the link callback of a PPP's netif
+ *
+ * @param pd The PPP descriptor returned by pppOpen()
+ * @param link_callback pointer to the link callback function
+ *
+ * @see netif_set_link_callback
+ */
+void
+ppp_set_netif_linkcallback(int pd, netif_status_callback_fn link_callback)
+{
+  netif_set_link_callback(&pppControl[pd].netif, link_callback); 
+}
+#endif /* LWIP_NETIF_LINK_CALLBACK */
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/ppp.h b/external/badvpn_dns/lwip/src/netif/ppp/ppp.h
new file mode 100644
index 0000000..08d6e62
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/ppp.h
@@ -0,0 +1,201 @@
+/*****************************************************************************
+* ppp.h - Network Point to Point Protocol header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-11-05 Guy Lancaster <glanca@xxxxxxxx>, Global Election Systems Inc.
+*   Original derived from BSD codes.
+*****************************************************************************/
+
+#ifndef PPP_H
+#define PPP_H
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/sio.h"
+#include "lwip/stats.h"
+#include "lwip/mem.h"
+#include "lwip/netif.h"
+#include "lwip/sys.h"
+#include "lwip/timers.h"
+
+
+#ifndef __u_char_defined
+
+/* Type definitions for BSD code. */
+typedef unsigned long  u_long;
+typedef unsigned int   u_int;
+typedef unsigned short u_short;
+typedef unsigned char  u_char;
+
+#endif
+
+
+/*************************
+*** PUBLIC DEFINITIONS ***
+*************************/
+
+/* Error codes. */
+#define PPPERR_NONE      0 /* No error. */
+#define PPPERR_PARAM    -1 /* Invalid parameter. */
+#define PPPERR_OPEN     -2 /* Unable to open PPP session. */
+#define PPPERR_DEVICE   -3 /* Invalid I/O device for PPP. */
+#define PPPERR_ALLOC    -4 /* Unable to allocate resources. */
+#define PPPERR_USER     -5 /* User interrupt. */
+#define PPPERR_CONNECT  -6 /* Connection lost. */
+#define PPPERR_AUTHFAIL -7 /* Failed authentication challenge. */
+#define PPPERR_PROTOCOL -8 /* Failed to meet protocol. */
+
+/*
+ * PPP IOCTL commands.
+ */
+/*
+ * Get the up status - 0 for down, non-zero for up.  The argument must
+ * point to an int.
+ */
+#define PPPCTLG_UPSTATUS 100 /* Get the up status - 0 down else up */
+#define PPPCTLS_ERRCODE  101 /* Set the error code */
+#define PPPCTLG_ERRCODE  102 /* Get the error code */
+#define PPPCTLG_FD       103 /* Get the fd associated with the ppp */
+
+/************************
+*** PUBLIC DATA TYPES ***
+************************/
+
+struct ppp_addrs {
+  ip_addr_t our_ipaddr, his_ipaddr, netmask, dns1, dns2;
+};
+
+
+/***********************
+*** PUBLIC FUNCTIONS ***
+***********************/
+
+/* Initialize the PPP subsystem. */
+void pppInit(void);
+
+/* Warning: Using PPPAUTHTYPE_ANY might have security consequences.
+ * RFC 1994 says:
+ *
+ * In practice, within or associated with each PPP server, there is a
+ * database which associates "user" names with authentication
+ * information ("secrets").  It is not anticipated that a particular
+ * named user would be authenticated by multiple methods.  This would
+ * make the user vulnerable to attacks which negotiate the least secure
+ * method from among a set (such as PAP rather than CHAP).  If the same
+ * secret was used, PAP would reveal the secret to be used later with
+ * CHAP.
+ *
+ * Instead, for each user name there should be an indication of exactly
+ * one method used to authenticate that user name.  If a user needs to
+ * make use of different authentication methods under different
+ * circumstances, then distinct user names SHOULD be employed, each of
+ * which identifies exactly one authentication method.
+ *
+ */
+enum pppAuthType {
+    PPPAUTHTYPE_NONE,
+    PPPAUTHTYPE_ANY,
+    PPPAUTHTYPE_PAP,
+    PPPAUTHTYPE_CHAP
+};
+
+void pppSetAuth(enum pppAuthType authType, const char *user, const char *passwd);
+
+/* Link status callback function prototype */
+typedef void (*pppLinkStatusCB_fn)(void *ctx, int errCode, void *arg);
+
+#if PPPOS_SUPPORT
+/*
+ * Open a new PPP connection using the given serial I/O device.
+ * This initializes the PPP control block but does not
+ * attempt to negotiate the LCP session.
+ * Return a new PPP connection descriptor on success or
+ * an error code (negative) on failure. 
+ */
+int pppOverSerialOpen(sio_fd_t fd, pppLinkStatusCB_fn linkStatusCB, void *linkStatusCtx);
+#endif /* PPPOS_SUPPORT */
+
+#if PPPOE_SUPPORT
+/*
+ * Open a new PPP Over Ethernet (PPPOE) connection.
+ */
+int pppOverEthernetOpen(struct netif *ethif, const char *service_name, const char *concentrator_name,
+                        pppLinkStatusCB_fn linkStatusCB, void *linkStatusCtx);
+#endif /* PPPOE_SUPPORT */
+
+/* for source code compatibility */
+#define pppOpen(fd,cb,ls) pppOverSerialOpen(fd,cb,ls)
+
+/*
+ * Close a PPP connection and release the descriptor. 
+ * Any outstanding packets in the queues are dropped.
+ * Return 0 on success, an error code on failure. 
+ */
+int pppClose(int pd);
+
+/*
+ * Indicate to the PPP process that the line has disconnected.
+ */
+void pppSigHUP(int pd);
+
+/*
+ * Get and set parameters for the given connection.
+ * Return 0 on success, an error code on failure. 
+ */
+int  pppIOCtl(int pd, int cmd, void *arg);
+
+/*
+ * Return the Maximum Transmission Unit for the given PPP connection.
+ */
+u_short pppMTU(int pd);
+
+#if PPPOS_SUPPORT && !PPP_INPROC_OWNTHREAD
+/*
+ * PPP over Serial: this is the input function to be called for received data.
+ * If PPP_INPROC_OWNTHREAD==1, a seperate input thread using the blocking
+ * sio_read() is used, so this is deactivated.
+ */
+void pppos_input(int pd, u_char* data, int len);
+#endif /* PPPOS_SUPPORT && !PPP_INPROC_OWNTHREAD */
+
+
+#if LWIP_NETIF_STATUS_CALLBACK
+/* Set an lwIP-style status-callback for the selected PPP device */
+void ppp_set_netif_statuscallback(int pd, netif_status_callback_fn status_callback);
+#endif /* LWIP_NETIF_STATUS_CALLBACK */
+#if LWIP_NETIF_LINK_CALLBACK
+/* Set an lwIP-style link-callback for the selected PPP device */
+void ppp_set_netif_linkcallback(int pd, netif_status_callback_fn link_callback);
+#endif /* LWIP_NETIF_LINK_CALLBACK */
+
+#endif /* PPP_SUPPORT */
+
+#endif /* PPP_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/ppp_impl.h b/external/badvpn_dns/lwip/src/netif/ppp/ppp_impl.h
new file mode 100644
index 0000000..89aea60
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/ppp_impl.h
@@ -0,0 +1,363 @@
+/*****************************************************************************
+* ppp.h - Network Point to Point Protocol header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1997 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 97-11-05 Guy Lancaster <glanca@xxxxxxxx>, Global Election Systems Inc.
+*   Original derived from BSD codes.
+*****************************************************************************/
+
+#ifndef PPP_IMPL_H
+#define PPP_IMPL_H
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp.h"
+#include "lwip/def.h"
+#include "lwip/sio.h"
+#include "lwip/stats.h"
+#include "lwip/mem.h"
+#include "lwip/netif.h"
+#include "lwip/sys.h"
+#include "lwip/timers.h"
+
+/** Some defines for code we skip compared to the original pppd.
+ *  These are just here to minimise the use of the ugly "#if 0". */
+#define PPP_ADDITIONAL_CALLBACKS  0
+
+/** Some error checks to test for unsupported code */
+#if CBCP_SUPPORT
+#error "CBCP is not supported in lwIP PPP"
+#endif
+#if CCP_SUPPORT
+#error "CCP is not supported in lwIP PPP"
+#endif
+
+/*
+ * pppd.h - PPP daemon global declarations.
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+/*
+ * ppp_defs.h - PPP definitions.
+ *
+ * Copyright (c) 1994 The Australian National University.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation is hereby granted, provided that the above copyright
+ * notice appears in all copies.  This software is provided without any
+ * warranty, express or implied. The Australian National University
+ * makes no representations about the suitability of this software for
+ * any purpose.
+ *
+ * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY
+ * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
+ * THE AUSTRALIAN NATIONAL UNIVERSITY HAVE BEEN ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO
+ * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
+ * OR MODIFICATIONS.
+ */
+
+#define TIMEOUT(f, a, t)    do { sys_untimeout((f), (a)); sys_timeout((t)*1000, (f), (a)); } while(0)
+#define UNTIMEOUT(f, a)     sys_untimeout((f), (a))
+
+
+/*
+ * Constants and structures defined by the internet system,
+ * Per RFC 790, September 1981, and numerous additions.
+ */
+
+/*
+ * The basic PPP frame.
+ */
+#define PPP_HDRLEN      4       /* octets for standard ppp header */
+#define PPP_FCSLEN      2       /* octets for FCS */
+
+
+/*
+ * Significant octet values.
+ */
+#define PPP_ALLSTATIONS 0xff    /* All-Stations broadcast address */
+#define PPP_UI          0x03    /* Unnumbered Information */
+#define PPP_FLAG        0x7e    /* Flag Sequence */
+#define PPP_ESCAPE      0x7d    /* Asynchronous Control Escape */
+#define PPP_TRANS       0x20    /* Asynchronous transparency modifier */
+
+/*
+ * Protocol field values.
+ */
+#define PPP_IP          0x21    /* Internet Protocol */
+#define PPP_AT          0x29    /* AppleTalk Protocol */
+#define PPP_VJC_COMP    0x2d    /* VJ compressed TCP */
+#define PPP_VJC_UNCOMP  0x2f    /* VJ uncompressed TCP */
+#define PPP_COMP        0xfd    /* compressed packet */
+#define PPP_IPCP        0x8021  /* IP Control Protocol */
+#define PPP_ATCP        0x8029  /* AppleTalk Control Protocol */
+#define PPP_CCP         0x80fd  /* Compression Control Protocol */
+#define PPP_LCP         0xc021  /* Link Control Protocol */
+#define PPP_PAP         0xc023  /* Password Authentication Protocol */
+#define PPP_LQR         0xc025  /* Link Quality Report protocol */
+#define PPP_CHAP        0xc223  /* Cryptographic Handshake Auth. Protocol */
+#define PPP_CBCP        0xc029  /* Callback Control Protocol */
+
+/*
+ * Values for FCS calculations.
+ */
+#define PPP_INITFCS     0xffff  /* Initial FCS value */
+#define PPP_GOODFCS     0xf0b8  /* Good final FCS value */
+#define PPP_FCS(fcs, c) (((fcs) >> 8) ^ fcstab[((fcs) ^ (c)) & 0xff])
+
+/*
+ * Extended asyncmap - allows any character to be escaped.
+ */
+typedef u_char  ext_accm[32];
+
+/*
+ * What to do with network protocol (NP) packets.
+ */
+enum NPmode {
+  NPMODE_PASS,        /* pass the packet through */
+  NPMODE_DROP,        /* silently drop the packet */
+  NPMODE_ERROR,       /* return an error */
+  NPMODE_QUEUE        /* save it up for later. */
+};
+
+/*
+ * Inline versions of get/put char/short/long.
+ * Pointer is advanced; we assume that both arguments
+ * are lvalues and will already be in registers.
+ * cp MUST be u_char *.
+ */
+#define GETCHAR(c, cp) { \
+    (c) = *(cp)++; \
+}
+#define PUTCHAR(c, cp) { \
+    *(cp)++ = (u_char) (c); \
+}
+
+
+#define GETSHORT(s, cp) { \
+    (s) = *(cp); (cp)++; (s) <<= 8; \
+    (s) |= *(cp); (cp)++; \
+}
+#define PUTSHORT(s, cp) { \
+    *(cp)++ = (u_char) ((s) >> 8); \
+    *(cp)++ = (u_char) (s & 0xff); \
+}
+
+#define GETLONG(l, cp) { \
+    (l) = *(cp); (cp)++; (l) <<= 8; \
+    (l) |= *(cp); (cp)++; (l) <<= 8; \
+    (l) |= *(cp); (cp)++; (l) <<= 8; \
+    (l) |= *(cp); (cp)++; \
+}
+#define PUTLONG(l, cp) { \
+    *(cp)++ = (u_char) ((l) >> 24); \
+    *(cp)++ = (u_char) ((l) >> 16); \
+    *(cp)++ = (u_char) ((l) >> 8); \
+    *(cp)++ = (u_char) (l); \
+}
+
+
+#define INCPTR(n, cp)   ((cp) += (n))
+#define DECPTR(n, cp)   ((cp) -= (n))
+
+#define BCMP(s0, s1, l)     memcmp((u_char *)(s0), (u_char *)(s1), (l))
+#define BCOPY(s, d, l)      MEMCPY((d), (s), (l))
+#define BZERO(s, n)         memset(s, 0, n)
+
+#if PPP_DEBUG
+#define PRINTMSG(m, l)  { m[l] = '\0'; LWIP_DEBUGF(LOG_INFO, ("Remote message: %s\n", m)); }
+#else  /* PPP_DEBUG */
+#define PRINTMSG(m, l)
+#endif /* PPP_DEBUG */
+
+/*
+ * MAKEHEADER - Add PPP Header fields to a packet.
+ */
+#define MAKEHEADER(p, t) { \
+    PUTCHAR(PPP_ALLSTATIONS, p); \
+    PUTCHAR(PPP_UI, p); \
+    PUTSHORT(t, p); }
+
+/************************
+*** PUBLIC DATA TYPES ***
+************************/
+
+/*
+ * The following struct gives the addresses of procedures to call
+ * for a particular protocol.
+ */
+struct protent {
+    u_short protocol;       /* PPP protocol number */
+    /* Initialization procedure */
+    void (*init) (int unit);
+    /* Process a received packet */
+    void (*input) (int unit, u_char *pkt, int len);
+    /* Process a received protocol-reject */
+    void (*protrej) (int unit);
+    /* Lower layer has come up */
+    void (*lowerup) (int unit);
+    /* Lower layer has gone down */
+    void (*lowerdown) (int unit);
+    /* Open the protocol */
+    void (*open) (int unit);
+    /* Close the protocol */
+    void (*close) (int unit, char *reason);
+#if PPP_ADDITIONAL_CALLBACKS
+    /* Print a packet in readable form */
+    int  (*printpkt) (u_char *pkt, int len,
+              void (*printer) (void *, char *, ...),
+              void *arg);
+    /* Process a received data packet */
+    void (*datainput) (int unit, u_char *pkt, int len);
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+    int  enabled_flag;      /* 0 if protocol is disabled */
+    char *name;         /* Text name of protocol */
+#if PPP_ADDITIONAL_CALLBACKS
+    /* Check requested options, assign defaults */
+    void (*check_options) (u_long);
+    /* Configure interface for demand-dial */
+    int  (*demand_conf) (int unit);
+    /* Say whether to bring up link for this pkt */
+    int  (*active_pkt) (u_char *pkt, int len);
+#endif /* PPP_ADDITIONAL_CALLBACKS */
+};
+
+/*
+ * The following structure records the time in seconds since
+ * the last NP packet was sent or received.
+ */
+struct ppp_idle {
+  u_short xmit_idle;      /* seconds since last NP packet sent */
+  u_short recv_idle;      /* seconds since last NP packet received */
+};
+
+struct ppp_settings {
+
+  u_int  disable_defaultip : 1;       /* Don't use hostname for default IP addrs */
+  u_int  auth_required     : 1;       /* Peer is required to authenticate */
+  u_int  explicit_remote   : 1;       /* remote_name specified with remotename opt */
+  u_int  refuse_pap        : 1;       /* Don't wanna auth. ourselves with PAP */
+  u_int  refuse_chap       : 1;       /* Don't wanna auth. ourselves with CHAP */
+  u_int  usehostname       : 1;       /* Use hostname for our_name */
+  u_int  usepeerdns        : 1;       /* Ask peer for DNS adds */
+
+  u_short idle_time_limit;            /* Shut down link if idle for this long */
+  int  maxconnect;                    /* Maximum connect time (seconds) */
+
+  char user       [MAXNAMELEN   + 1]; /* Username for PAP */
+  char passwd     [MAXSECRETLEN + 1]; /* Password for PAP, secret for CHAP */
+  char our_name   [MAXNAMELEN   + 1]; /* Our name for authentication purposes */
+  char remote_name[MAXNAMELEN   + 1]; /* Peer's name for authentication */
+};
+
+/*****************************
+*** PUBLIC DATA STRUCTURES ***
+*****************************/
+
+/* Buffers for outgoing packets. */
+extern u_char outpacket_buf[NUM_PPP][PPP_MRU+PPP_HDRLEN];
+
+extern struct ppp_settings ppp_settings;
+
+extern struct protent *ppp_protocols[]; /* Table of pointers to supported protocols */
+
+
+/***********************
+*** PUBLIC FUNCTIONS ***
+***********************/
+
+/*
+ * Write n characters to a ppp link.
+ * RETURN: >= 0 Number of characters written, -1 Failed to write to device.
+ */
+int pppWrite(int pd, const u_char *s, int n);
+
+void pppInProcOverEthernet(int pd, struct pbuf *pb);
+
+struct pbuf *pppSingleBuf(struct pbuf *p);
+
+void pppLinkTerminated(int pd);
+
+void pppLinkDown(int pd);
+
+/* Configure i/f transmit parameters */
+void ppp_send_config (int, u16_t, u32_t, int, int);
+/* Set extended transmit ACCM */
+void ppp_set_xaccm (int, ext_accm *);
+/* Configure i/f receive parameters */
+void ppp_recv_config (int, int, u32_t, int, int);
+/* Find out how long link has been idle */
+int  get_idle_time (int, struct ppp_idle *);
+
+/* Configure VJ TCP header compression */
+int  sifvjcomp (int, int, u8_t, u8_t);
+/* Configure i/f down (for IP) */
+int  sifup (int);
+/* Set mode for handling packets for proto */
+int  sifnpmode (int u, int proto, enum NPmode mode);
+/* Configure i/f down (for IP) */
+int  sifdown (int);
+/* Configure IP addresses for i/f */
+int  sifaddr (int, u32_t, u32_t, u32_t, u32_t, u32_t);
+/* Reset i/f IP addresses */
+int  cifaddr (int, u32_t, u32_t);
+/* Create default route through i/f */
+int  sifdefaultroute (int, u32_t, u32_t);
+/* Delete default route through i/f */
+int  cifdefaultroute (int, u32_t, u32_t);
+
+/* Get appropriate netmask for address */
+u32_t GetMask (u32_t); 
+
+#endif /* PPP_SUPPORT */
+
+#endif /* PPP_IMPL_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/ppp_oe.c b/external/badvpn_dns/lwip/src/netif/ppp/ppp_oe.c
new file mode 100644
index 0000000..fdf52ae
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/ppp_oe.c
@@ -0,0 +1,1132 @@
+/*****************************************************************************
+* ppp_oe.c - PPP Over Ethernet implementation for lwIP.
+*
+* Copyright (c) 2006 by Marc Boucher, Services Informatiques (MBSI) inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 06-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+*****************************************************************************/
+
+
+
+/* based on NetBSD: if_pppoe.c,v 1.64 2006/01/31 23:50:15 martin Exp */
+
+/*-
+ * Copyright (c) 2002 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Martin Husemann <martin@xxxxxxxxxx>.
+ *
+ * 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. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *        This product includes software developed by the NetBSD
+ *        Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS
+ * 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 "lwip/opt.h"
+
+#if PPPOE_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "netif/ppp_oe.h"
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "lwip/timers.h"
+#include "lwip/memp.h"
+
+#include <string.h>
+#include <stdio.h>
+
+
+/* Add a 16 bit unsigned value to a buffer pointed to by PTR */
+#define PPPOE_ADD_16(PTR, VAL) \
+    *(PTR)++ = (u8_t)((VAL) / 256);    \
+    *(PTR)++ = (u8_t)((VAL) % 256)
+
+/* Add a complete PPPoE header to the buffer pointed to by PTR */
+#define PPPOE_ADD_HEADER(PTR, CODE, SESS, LEN)  \
+    *(PTR)++ = PPPOE_VERTYPE;  \
+    *(PTR)++ = (CODE);         \
+    PPPOE_ADD_16(PTR, SESS);   \
+    PPPOE_ADD_16(PTR, LEN)
+
+#define PPPOE_DISC_TIMEOUT (5*1000)  /* base for quick timeout calculation */
+#define PPPOE_SLOW_RETRY   (60*1000) /* persistent retry interval */
+#define PPPOE_DISC_MAXPADI  4        /* retry PADI four times (quickly) */
+#define PPPOE_DISC_MAXPADR  2        /* retry PADR twice */
+
+#ifdef PPPOE_SERVER
+#error "PPPOE_SERVER is not yet supported under lwIP!"
+/* from if_spppsubr.c */
+#define IFF_PASSIVE IFF_LINK0 /* wait passively for connection */
+#endif
+
+#ifndef PPPOE_ERRORSTRING_LEN
+#define PPPOE_ERRORSTRING_LEN     64
+#endif
+static char pppoe_error_tmp[PPPOE_ERRORSTRING_LEN];
+
+
+/* input routines */
+static void pppoe_dispatch_disc_pkt(struct netif *, struct pbuf *);
+
+/* management routines */
+static int pppoe_do_disconnect(struct pppoe_softc *);
+static void pppoe_abort_connect(struct pppoe_softc *);
+static void pppoe_clear_softc(struct pppoe_softc *, const char *);
+
+/* internal timeout handling */
+static void pppoe_timeout(void *);
+
+/* sending actual protocol controll packets */
+static err_t pppoe_send_padi(struct pppoe_softc *);
+static err_t pppoe_send_padr(struct pppoe_softc *);
+#ifdef PPPOE_SERVER
+static err_t pppoe_send_pado(struct pppoe_softc *);
+static err_t pppoe_send_pads(struct pppoe_softc *);
+#endif
+static err_t pppoe_send_padt(struct netif *, u_int, const u8_t *);
+
+/* internal helper functions */
+static struct pppoe_softc * pppoe_find_softc_by_session(u_int, struct netif *);
+static struct pppoe_softc * pppoe_find_softc_by_hunique(u8_t *, size_t, struct netif *);
+
+/** linked list of created pppoe interfaces */
+static struct pppoe_softc *pppoe_softc_list;
+
+err_t
+pppoe_create(struct netif *ethif, int pd, void (*linkStatusCB)(int pd, int up), struct pppoe_softc **scptr)
+{
+  struct pppoe_softc *sc;
+
+  sc = (struct pppoe_softc *)memp_malloc(MEMP_PPPOE_IF);
+  if (sc == NULL) {
+    *scptr = NULL;
+    return ERR_MEM;
+  }
+  memset(sc, 0, sizeof(struct pppoe_softc));
+
+  /* changed to real address later */
+  MEMCPY(&sc->sc_dest, ethbroadcast.addr, sizeof(sc->sc_dest));
+
+  sc->sc_pd = pd;
+  sc->sc_linkStatusCB = linkStatusCB;
+  sc->sc_ethif = ethif;
+
+  /* put the new interface at the head of the list */
+  sc->next = pppoe_softc_list;
+  pppoe_softc_list = sc;
+
+  *scptr = sc;
+
+  return ERR_OK;
+}
+
+err_t
+pppoe_destroy(struct netif *ifp)
+{
+  struct pppoe_softc *sc, *prev = NULL;
+
+  for (sc = pppoe_softc_list; sc != NULL; prev = sc, sc = sc->next) {
+    if (sc->sc_ethif == ifp) {
+      break;
+    }
+  }
+
+  if(!(sc && (sc->sc_ethif == ifp))) {
+    return ERR_IF;
+  }
+
+  sys_untimeout(pppoe_timeout, sc);
+  if (prev == NULL) {
+    /* remove sc from the head of the list */
+    pppoe_softc_list = sc->next;
+  } else {
+    /* remove sc from the list */
+    prev->next = sc->next;
+  }
+
+#ifdef PPPOE_TODO
+  if (sc->sc_concentrator_name) {
+    mem_free(sc->sc_concentrator_name);
+  }
+  if (sc->sc_service_name) {
+    mem_free(sc->sc_service_name);
+  }
+#endif /* PPPOE_TODO */
+  memp_free(MEMP_PPPOE_IF, sc);
+
+  return ERR_OK;
+}
+
+/*
+ * Find the interface handling the specified session.
+ * Note: O(number of sessions open), this is a client-side only, mean
+ * and lean implementation, so number of open sessions typically should
+ * be 1.
+ */
+static struct pppoe_softc *
+pppoe_find_softc_by_session(u_int session, struct netif *rcvif)
+{
+  struct pppoe_softc *sc;
+
+  if (session == 0) {
+    return NULL;
+  }
+
+  for (sc = pppoe_softc_list; sc != NULL; sc = sc->next) {
+    if (sc->sc_state == PPPOE_STATE_SESSION
+        && sc->sc_session == session) {
+      if (sc->sc_ethif == rcvif) {
+        return sc;
+      } else {
+        return NULL;
+      }
+    }
+  }
+  return NULL;
+}
+
+/* Check host unique token passed and return appropriate softc pointer,
+ * or NULL if token is bogus. */
+static struct pppoe_softc *
+pppoe_find_softc_by_hunique(u8_t *token, size_t len, struct netif *rcvif)
+{
+  struct pppoe_softc *sc, *t;
+
+  if (pppoe_softc_list == NULL) {
+    return NULL;
+  }
+
+  if (len != sizeof sc) {
+    return NULL;
+  }
+  MEMCPY(&t, token, len);
+
+  for (sc = pppoe_softc_list; sc != NULL; sc = sc->next) {
+    if (sc == t) {
+      break;
+    }
+  }
+
+  if (sc == NULL) {
+    PPPDEBUG(LOG_DEBUG, ("pppoe: alien host unique tag, no session found\n"));
+    return NULL;
+  }
+
+  /* should be safe to access *sc now */
+  if (sc->sc_state < PPPOE_STATE_PADI_SENT || sc->sc_state >= PPPOE_STATE_SESSION) {
+    printf("%c%c%"U16_F": host unique tag found, but it belongs to a connection in state %d\n",
+      sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num, sc->sc_state);
+    return NULL;
+  }
+  if (sc->sc_ethif != rcvif) {
+    printf("%c%c%"U16_F": wrong interface, not accepting host unique\n",
+      sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num);
+    return NULL;
+  }
+  return sc;
+}
+
+static void
+pppoe_linkstatus_up(struct pppoe_softc *sc)
+{
+  sc->sc_linkStatusCB(sc->sc_pd, 1);
+}
+
+/* analyze and handle a single received packet while not in session state */
+static void
+pppoe_dispatch_disc_pkt(struct netif *netif, struct pbuf *pb)
+{
+  u16_t tag, len;
+  u16_t session, plen;
+  struct pppoe_softc *sc;
+  const char *err_msg;
+  char devname[6];
+  u8_t *ac_cookie;
+  u16_t ac_cookie_len;
+#ifdef PPPOE_SERVER
+  u8_t *hunique;
+  size_t hunique_len;
+#endif
+  struct pppoehdr *ph;
+  struct pppoetag pt;
+  int off, err, errortag;
+  struct eth_hdr *ethhdr;
+
+  pb = pppSingleBuf(pb);
+
+  strcpy(devname, "pppoe");  /* as long as we don't know which instance */
+  err_msg = NULL;
+  errortag = 0;
+  if (pb->len < sizeof(*ethhdr)) {
+    goto done;
+  }
+  ethhdr = (struct eth_hdr *)pb->payload;
+  off = sizeof(*ethhdr);
+
+  ac_cookie = NULL;
+  ac_cookie_len = 0;
+#ifdef PPPOE_SERVER
+  hunique = NULL;
+  hunique_len = 0;
+#endif
+  session = 0;
+  if (pb->len - off < PPPOE_HEADERLEN) {
+    printf("pppoe: packet too short: %d\n", pb->len);
+    goto done;
+  }
+
+  ph = (struct pppoehdr *) (ethhdr + 1);
+  if (ph->vertype != PPPOE_VERTYPE) {
+    printf("pppoe: unknown version/type packet: 0x%x\n", ph->vertype);
+    goto done;
+  }
+  session = ntohs(ph->session);
+  plen = ntohs(ph->plen);
+  off += sizeof(*ph);
+
+  if (plen + off > pb->len) {
+    printf("pppoe: packet content does not fit: data available = %d, packet size = %u\n",
+        pb->len - off, plen);
+    goto done;
+  }
+  if(pb->tot_len == pb->len) {
+    pb->tot_len = pb->len = (u16_t)off + plen; /* ignore trailing garbage */
+  }
+  tag = 0;
+  len = 0;
+  sc = NULL;
+  while (off + sizeof(pt) <= pb->len) {
+    MEMCPY(&pt, (u8_t*)pb->payload + off, sizeof(pt));
+    tag = ntohs(pt.tag);
+    len = ntohs(pt.len);
+    if (off + sizeof(pt) + len > pb->len) {
+      printf("pppoe: tag 0x%x len 0x%x is too long\n", tag, len);
+      goto done;
+    }
+    switch (tag) {
+      case PPPOE_TAG_EOL:
+        goto breakbreak;
+      case PPPOE_TAG_SNAME:
+        break;  /* ignored */
+      case PPPOE_TAG_ACNAME:
+        break;  /* ignored */
+      case PPPOE_TAG_HUNIQUE:
+        if (sc != NULL) {
+          break;
+        }
+#ifdef PPPOE_SERVER
+        hunique = (u8_t*)pb->payload + off + sizeof(pt);
+        hunique_len = len;
+#endif
+        sc = pppoe_find_softc_by_hunique((u8_t*)pb->payload + off + sizeof(pt), len, netif);
+        if (sc != NULL) {
+          snprintf(devname, sizeof(devname), "%c%c%"U16_F, sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num);
+        }
+        break;
+      case PPPOE_TAG_ACCOOKIE:
+        if (ac_cookie == NULL) {
+          ac_cookie = (u8_t*)pb->payload + off + sizeof(pt);
+          ac_cookie_len = len;
+        }
+        break;
+      case PPPOE_TAG_SNAME_ERR:
+        err_msg = "SERVICE NAME ERROR";
+        errortag = 1;
+        break;
+      case PPPOE_TAG_ACSYS_ERR:
+        err_msg = "AC SYSTEM ERROR";
+        errortag = 1;
+        break;
+      case PPPOE_TAG_GENERIC_ERR:
+        err_msg = "GENERIC ERROR";
+        errortag = 1;
+        break;
+    }
+    if (err_msg) {
+      if (errortag && len) {
+        u16_t error_len = LWIP_MIN(len, sizeof(pppoe_error_tmp)-1);
+        strncpy(pppoe_error_tmp, (char*)pb->payload + off + sizeof(pt), error_len);
+        pppoe_error_tmp[error_len-1] = '\0';
+        printf("%s: %s: %s\n", devname, err_msg, pppoe_error_tmp);
+      } else {
+        printf("%s: %s\n", devname, err_msg);
+      }
+      if (errortag) {
+        goto done;
+      }
+    }
+    off += sizeof(pt) + len;
+  }
+
+breakbreak:;
+  switch (ph->code) {
+    case PPPOE_CODE_PADI:
+#ifdef PPPOE_SERVER
+      /*
+       * got service name, concentrator name, and/or host unique.
+       * ignore if we have no interfaces with IFF_PASSIVE|IFF_UP.
+       */
+      if (LIST_EMPTY(&pppoe_softc_list)) {
+        goto done;
+      }
+      LIST_FOREACH(sc, &pppoe_softc_list, sc_list) {
+        if (!(sc->sc_sppp.pp_if.if_flags & IFF_UP)) {
+          continue;
+        }
+        if (!(sc->sc_sppp.pp_if.if_flags & IFF_PASSIVE)) {
+          continue;
+        }
+        if (sc->sc_state == PPPOE_STATE_INITIAL) {
+          break;
+        }
+      }
+      if (sc == NULL) {
+        /* printf("pppoe: free passive interface is not found\n"); */
+        goto done;
+      }
+      if (hunique) {
+        if (sc->sc_hunique) {
+          mem_free(sc->sc_hunique);
+        }
+        sc->sc_hunique = mem_malloc(hunique_len);
+        if (sc->sc_hunique == NULL) {
+          goto done;
+        }
+        sc->sc_hunique_len = hunique_len;
+        MEMCPY(sc->sc_hunique, hunique, hunique_len);
+      }
+      MEMCPY(&sc->sc_dest, eh->ether_shost, sizeof sc->sc_dest);
+      sc->sc_state = PPPOE_STATE_PADO_SENT;
+      pppoe_send_pado(sc);
+      break;
+#endif /* PPPOE_SERVER */
+    case PPPOE_CODE_PADR:
+#ifdef PPPOE_SERVER
+      /*
+       * get sc from ac_cookie if IFF_PASSIVE
+       */
+      if (ac_cookie == NULL) {
+        /* be quiet if there is not a single pppoe instance */
+        printf("pppoe: received PADR but not includes ac_cookie\n");
+        goto done;
+      }
+      sc = pppoe_find_softc_by_hunique(ac_cookie, ac_cookie_len, netif);
+      if (sc == NULL) {
+        /* be quiet if there is not a single pppoe instance */
+        if (!LIST_EMPTY(&pppoe_softc_list)) {
+          printf("pppoe: received PADR but could not find request for it\n");
+        }
+        goto done;
+      }
+      if (sc->sc_state != PPPOE_STATE_PADO_SENT) {
+        printf("%c%c%"U16_F": received unexpected PADR\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num);
+        goto done;
+      }
+      if (hunique) {
+        if (sc->sc_hunique) {
+          mem_free(sc->sc_hunique);
+        }
+        sc->sc_hunique = mem_malloc(hunique_len);
+        if (sc->sc_hunique == NULL) {
+          goto done;
+        }
+        sc->sc_hunique_len = hunique_len;
+        MEMCPY(sc->sc_hunique, hunique, hunique_len);
+      }
+      pppoe_send_pads(sc);
+      sc->sc_state = PPPOE_STATE_SESSION;
+      pppoe_linkstatus_up(sc); /* notify upper layers */
+      break;
+#else
+      /* ignore, we are no access concentrator */
+      goto done;
+#endif /* PPPOE_SERVER */
+    case PPPOE_CODE_PADO:
+      if (sc == NULL) {
+        /* be quiet if there is not a single pppoe instance */
+        if (pppoe_softc_list != NULL) {
+          printf("pppoe: received PADO but could not find request for it\n");
+        }
+        goto done;
+      }
+      if (sc->sc_state != PPPOE_STATE_PADI_SENT) {
+        printf("%c%c%"U16_F": received unexpected PADO\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num);
+        goto done;
+      }
+      if (ac_cookie) {
+        sc->sc_ac_cookie_len = ac_cookie_len;
+        MEMCPY(sc->sc_ac_cookie, ac_cookie, ac_cookie_len);
+      }
+      MEMCPY(&sc->sc_dest, ethhdr->src.addr, sizeof(sc->sc_dest.addr));
+      sys_untimeout(pppoe_timeout, sc);
+      sc->sc_padr_retried = 0;
+      sc->sc_state = PPPOE_STATE_PADR_SENT;
+      if ((err = pppoe_send_padr(sc)) != 0) {
+        PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F": failed to send PADR, error=%d\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num, err));
+      }
+      sys_timeout(PPPOE_DISC_TIMEOUT * (1 + sc->sc_padr_retried), pppoe_timeout, sc);
+      break;
+    case PPPOE_CODE_PADS:
+      if (sc == NULL) {
+        goto done;
+      }
+      sc->sc_session = session;
+      sys_untimeout(pppoe_timeout, sc);
+      PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F": session 0x%x connected\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num, session));
+      sc->sc_state = PPPOE_STATE_SESSION;
+      pppoe_linkstatus_up(sc); /* notify upper layers */
+      break;
+    case PPPOE_CODE_PADT:
+      if (sc == NULL) {
+        goto done;
+      }
+      pppoe_clear_softc(sc, "received PADT");
+      break;
+    default:
+      if(sc) {
+        printf("%c%c%"U16_F": unknown code (0x%"X16_F") session = 0x%"X16_F"\n",
+            sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num,
+            (u16_t)ph->code, session);
+      } else {
+        printf("pppoe: unknown code (0x%"X16_F") session = 0x%"X16_F"\n", (u16_t)ph->code, session);
+      }
+      break;
+  }
+
+done:
+  pbuf_free(pb);
+  return;
+}
+
+void
+pppoe_disc_input(struct netif *netif, struct pbuf *p)
+{
+  /* avoid error messages if there is not a single pppoe instance */
+  if (pppoe_softc_list != NULL) {
+    pppoe_dispatch_disc_pkt(netif, p);
+  } else {
+    pbuf_free(p);
+  }
+}
+
+void
+pppoe_data_input(struct netif *netif, struct pbuf *pb)
+{
+  u16_t session, plen;
+  struct pppoe_softc *sc;
+  struct pppoehdr *ph;
+#ifdef PPPOE_TERM_UNKNOWN_SESSIONS
+  u8_t shost[ETHER_ADDR_LEN];
+#endif
+
+#ifdef PPPOE_TERM_UNKNOWN_SESSIONS
+  MEMCPY(shost, ((struct eth_hdr *)pb->payload)->src.addr, sizeof(shost));
+#endif
+  if (pbuf_header(pb, -(int)sizeof(struct eth_hdr)) != 0) {
+    /* bail out */
+    PPPDEBUG(LOG_ERR, ("pppoe_data_input: pbuf_header failed\n"));
+    LINK_STATS_INC(link.lenerr);
+    goto drop;
+  } 
+
+  pb = pppSingleBuf (pb);
+
+  if (pb->len <= PPPOE_HEADERLEN) {
+    printf("pppoe (data): dropping too short packet: %d bytes\n", pb->len);
+    goto drop;
+  }
+
+  if (pb->len < sizeof(*ph)) {
+    printf("pppoe_data_input: could not get PPPoE header\n");
+    goto drop;
+  }
+  ph = (struct pppoehdr *)pb->payload;
+
+  if (ph->vertype != PPPOE_VERTYPE) {
+    printf("pppoe (data): unknown version/type packet: 0x%x\n", ph->vertype);
+    goto drop;
+  }
+  if (ph->code != 0) {
+    goto drop;
+  }
+
+  session = ntohs(ph->session);
+  sc = pppoe_find_softc_by_session(session, netif);
+  if (sc == NULL) {
+#ifdef PPPOE_TERM_UNKNOWN_SESSIONS
+    printf("pppoe: input for unknown session 0x%x, sending PADT\n", session);
+    pppoe_send_padt(netif, session, shost);
+#endif
+    goto drop;
+  }
+
+  plen = ntohs(ph->plen);
+
+  if (pbuf_header(pb, -(int)(PPPOE_HEADERLEN)) != 0) {
+    /* bail out */
+    PPPDEBUG(LOG_ERR, ("pppoe_data_input: pbuf_header PPPOE_HEADERLEN failed\n"));
+    LINK_STATS_INC(link.lenerr);
+    goto drop;
+  } 
+
+  PPPDEBUG(LOG_DEBUG, ("pppoe_data_input: %c%c%"U16_F": pkthdr.len=%d, pppoe.len=%d\n",
+        sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num,
+        pb->len, plen));
+
+  if (pb->len < plen) {
+    goto drop;
+  }
+
+  pppInProcOverEthernet(sc->sc_pd, pb);
+
+  return;
+
+drop:
+  pbuf_free(pb);
+}
+
+static err_t
+pppoe_output(struct pppoe_softc *sc, struct pbuf *pb)
+{
+  struct eth_hdr *ethhdr;
+  u16_t etype;
+  err_t res;
+
+  if (!sc->sc_ethif) {
+    pbuf_free(pb);
+    return ERR_IF;
+  }
+
+  ethhdr = (struct eth_hdr *)pb->payload;
+  etype = sc->sc_state == PPPOE_STATE_SESSION ? ETHTYPE_PPPOE : ETHTYPE_PPPOEDISC;
+  ethhdr->type = htons(etype);
+  MEMCPY(ethhdr->dest.addr, sc->sc_dest.addr, sizeof(ethhdr->dest.addr));
+  MEMCPY(ethhdr->src.addr, ((struct eth_addr *)sc->sc_ethif->hwaddr)->addr, sizeof(ethhdr->src.addr));
+
+  PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F" (%x) state=%d, session=0x%x output -> %02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F":%02"X16_F", len=%d\n",
+      sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num, etype,
+      sc->sc_state, sc->sc_session,
+      sc->sc_dest.addr[0], sc->sc_dest.addr[1], sc->sc_dest.addr[2], sc->sc_dest.addr[3], sc->sc_dest.addr[4], sc->sc_dest.addr[5],
+      pb->tot_len));
+
+  res = sc->sc_ethif->linkoutput(sc->sc_ethif, pb);
+
+  pbuf_free(pb);
+
+  return res;
+}
+
+static err_t
+pppoe_send_padi(struct pppoe_softc *sc)
+{
+  struct pbuf *pb;
+  u8_t *p;
+  int len;
+#ifdef PPPOE_TODO
+  int l1 = 0, l2 = 0; /* XXX: gcc */
+#endif /* PPPOE_TODO */
+
+  if (sc->sc_state >PPPOE_STATE_PADI_SENT) {
+    PPPDEBUG(LOG_ERR, ("ERROR: pppoe_send_padi in state %d", sc->sc_state));
+  }
+
+  /* calculate length of frame (excluding ethernet header + pppoe header) */
+  len = 2 + 2 + 2 + 2 + sizeof sc;  /* service name tag is required, host unique is send too */
+#ifdef PPPOE_TODO
+  if (sc->sc_service_name != NULL) {
+    l1 = (int)strlen(sc->sc_service_name);
+    len += l1;
+  }
+  if (sc->sc_concentrator_name != NULL) {
+    l2 = (int)strlen(sc->sc_concentrator_name);
+    len += 2 + 2 + l2;
+  }
+#endif /* PPPOE_TODO */
+  LWIP_ASSERT("sizeof(struct eth_hdr) + PPPOE_HEADERLEN + len <= 0xffff",
+    sizeof(struct eth_hdr) + PPPOE_HEADERLEN + len <= 0xffff);
+
+  /* allocate a buffer */
+  pb = pbuf_alloc(PBUF_LINK, (u16_t)(sizeof(struct eth_hdr) + PPPOE_HEADERLEN + len), PBUF_RAM);
+  if (!pb) {
+    return ERR_MEM;
+  }
+  LWIP_ASSERT("pb->tot_len == pb->len", pb->tot_len == pb->len);
+
+  p = (u8_t*)pb->payload + sizeof (struct eth_hdr);
+  /* fill in pkt */
+  PPPOE_ADD_HEADER(p, PPPOE_CODE_PADI, 0, (u16_t)len);
+  PPPOE_ADD_16(p, PPPOE_TAG_SNAME);
+#ifdef PPPOE_TODO
+  if (sc->sc_service_name != NULL) {
+    PPPOE_ADD_16(p, l1);
+    MEMCPY(p, sc->sc_service_name, l1);
+    p += l1;
+  } else
+#endif /* PPPOE_TODO */
+  {
+    PPPOE_ADD_16(p, 0);
+  }
+#ifdef PPPOE_TODO
+  if (sc->sc_concentrator_name != NULL) {
+    PPPOE_ADD_16(p, PPPOE_TAG_ACNAME);
+    PPPOE_ADD_16(p, l2);
+    MEMCPY(p, sc->sc_concentrator_name, l2);
+    p += l2;
+  }
+#endif /* PPPOE_TODO */
+  PPPOE_ADD_16(p, PPPOE_TAG_HUNIQUE);
+  PPPOE_ADD_16(p, sizeof(sc));
+  MEMCPY(p, &sc, sizeof sc);
+
+  /* send pkt */
+  return pppoe_output(sc, pb);
+}
+
+static void
+pppoe_timeout(void *arg)
+{
+  int retry_wait, err;
+  struct pppoe_softc *sc = (struct pppoe_softc*)arg;
+
+  PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F": timeout\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num));
+
+  switch (sc->sc_state) {
+    case PPPOE_STATE_PADI_SENT:
+      /*
+       * We have two basic ways of retrying:
+       *  - Quick retry mode: try a few times in short sequence
+       *  - Slow retry mode: we already had a connection successfully
+       *    established and will try infinitely (without user
+       *    intervention)
+       * We only enter slow retry mode if IFF_LINK1 (aka autodial)
+       * is not set.
+       */
+
+      /* initialize for quick retry mode */
+      retry_wait = PPPOE_DISC_TIMEOUT * (1 + sc->sc_padi_retried);
+
+      sc->sc_padi_retried++;
+      if (sc->sc_padi_retried >= PPPOE_DISC_MAXPADI) {
+#if 0
+        if ((sc->sc_sppp.pp_if.if_flags & IFF_LINK1) == 0) {
+          /* slow retry mode */
+          retry_wait = PPPOE_SLOW_RETRY;
+        } else
+#endif
+        {
+          pppoe_abort_connect(sc);
+          return;
+        }
+      }
+      if ((err = pppoe_send_padi(sc)) != 0) {
+        sc->sc_padi_retried--;
+        PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F": failed to transmit PADI, error=%d\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num, err));
+      }
+      sys_timeout(retry_wait, pppoe_timeout, sc);
+      break;
+
+    case PPPOE_STATE_PADR_SENT:
+      sc->sc_padr_retried++;
+      if (sc->sc_padr_retried >= PPPOE_DISC_MAXPADR) {
+        MEMCPY(&sc->sc_dest, ethbroadcast.addr, sizeof(sc->sc_dest));
+        sc->sc_state = PPPOE_STATE_PADI_SENT;
+        sc->sc_padr_retried = 0;
+        if ((err = pppoe_send_padi(sc)) != 0) {
+          PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F": failed to send PADI, error=%d\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num, err));
+        }
+        sys_timeout(PPPOE_DISC_TIMEOUT * (1 + sc->sc_padi_retried), pppoe_timeout, sc);
+        return;
+      }
+      if ((err = pppoe_send_padr(sc)) != 0) {
+        sc->sc_padr_retried--;
+        PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F": failed to send PADR, error=%d\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num, err));
+      }
+      sys_timeout(PPPOE_DISC_TIMEOUT * (1 + sc->sc_padr_retried), pppoe_timeout, sc);
+      break;
+    case PPPOE_STATE_CLOSING:
+      pppoe_do_disconnect(sc);
+      break;
+    default:
+      return;  /* all done, work in peace */
+  }
+}
+
+/* Start a connection (i.e. initiate discovery phase) */
+int
+pppoe_connect(struct pppoe_softc *sc)
+{
+  int err;
+
+  if (sc->sc_state != PPPOE_STATE_INITIAL) {
+    return EBUSY;
+  }
+
+#ifdef PPPOE_SERVER
+  /* wait PADI if IFF_PASSIVE */
+  if ((sc->sc_sppp.pp_if.if_flags & IFF_PASSIVE)) {
+    return 0;
+  }
+#endif
+  /* save state, in case we fail to send PADI */
+  sc->sc_state = PPPOE_STATE_PADI_SENT;
+  sc->sc_padr_retried = 0;
+  err = pppoe_send_padi(sc);
+  PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F": failed to send PADI, error=%d\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num, err));
+  sys_timeout(PPPOE_DISC_TIMEOUT, pppoe_timeout, sc);
+  return err;
+}
+
+/* disconnect */
+void
+pppoe_disconnect(struct pppoe_softc *sc)
+{
+  if (sc->sc_state < PPPOE_STATE_SESSION) {
+    return;
+  }
+  /*
+   * Do not call pppoe_disconnect here, the upper layer state
+   * machine gets confused by this. We must return from this
+   * function and defer disconnecting to the timeout handler.
+   */
+  sc->sc_state = PPPOE_STATE_CLOSING;
+  sys_timeout(20, pppoe_timeout, sc);
+}
+
+static int
+pppoe_do_disconnect(struct pppoe_softc *sc)
+{
+  int err;
+
+  if (sc->sc_state < PPPOE_STATE_SESSION) {
+    err = EBUSY;
+  } else {
+    PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F": disconnecting\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num));
+    err = pppoe_send_padt(sc->sc_ethif, sc->sc_session, (const u8_t *)&sc->sc_dest);
+  }
+
+  /* cleanup softc */
+  sc->sc_state = PPPOE_STATE_INITIAL;
+  MEMCPY(&sc->sc_dest, ethbroadcast.addr, sizeof(sc->sc_dest));
+  sc->sc_ac_cookie_len = 0;
+#ifdef PPPOE_SERVER
+  if (sc->sc_hunique) {
+    mem_free(sc->sc_hunique);
+    sc->sc_hunique = NULL;
+  }
+  sc->sc_hunique_len = 0;
+#endif
+  sc->sc_session = 0;
+
+  sc->sc_linkStatusCB(sc->sc_pd, 0); /* notify upper layers */
+
+  return err;
+}
+
+/* Connection attempt aborted */
+static void
+pppoe_abort_connect(struct pppoe_softc *sc)
+{
+  printf("%c%c%"U16_F": could not establish connection\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num);
+  sc->sc_state = PPPOE_STATE_CLOSING;
+
+  sc->sc_linkStatusCB(sc->sc_pd, 0); /* notify upper layers */
+
+  /* clear connection state */
+  MEMCPY(&sc->sc_dest, ethbroadcast.addr, sizeof(sc->sc_dest));
+  sc->sc_state = PPPOE_STATE_INITIAL;
+}
+
+/* Send a PADR packet */
+static err_t
+pppoe_send_padr(struct pppoe_softc *sc)
+{
+  struct pbuf *pb;
+  u8_t *p;
+  size_t len;
+#ifdef PPPOE_TODO
+  size_t l1 = 0; /* XXX: gcc */
+#endif /* PPPOE_TODO */
+
+  if (sc->sc_state != PPPOE_STATE_PADR_SENT) {
+    return ERR_CONN;
+  }
+
+  len = 2 + 2 + 2 + 2 + sizeof(sc);    /* service name, host unique */
+#ifdef PPPOE_TODO
+  if (sc->sc_service_name != NULL) {    /* service name tag maybe empty */
+    l1 = strlen(sc->sc_service_name);
+    len += l1;
+  }
+#endif /* PPPOE_TODO */
+  if (sc->sc_ac_cookie_len > 0) {
+    len += 2 + 2 + sc->sc_ac_cookie_len;  /* AC cookie */
+  }
+  LWIP_ASSERT("sizeof(struct eth_hdr) + PPPOE_HEADERLEN + len <= 0xffff",
+    sizeof(struct eth_hdr) + PPPOE_HEADERLEN + len <= 0xffff);
+  pb = pbuf_alloc(PBUF_LINK, (u16_t)(sizeof(struct eth_hdr) + PPPOE_HEADERLEN + len), PBUF_RAM);
+  if (!pb) {
+    return ERR_MEM;
+  }
+  LWIP_ASSERT("pb->tot_len == pb->len", pb->tot_len == pb->len);
+  p = (u8_t*)pb->payload + sizeof (struct eth_hdr);
+  PPPOE_ADD_HEADER(p, PPPOE_CODE_PADR, 0, len);
+  PPPOE_ADD_16(p, PPPOE_TAG_SNAME);
+#ifdef PPPOE_TODO
+  if (sc->sc_service_name != NULL) {
+    PPPOE_ADD_16(p, l1);
+    MEMCPY(p, sc->sc_service_name, l1);
+    p += l1;
+  } else
+#endif /* PPPOE_TODO */
+  {
+    PPPOE_ADD_16(p, 0);
+  }
+  if (sc->sc_ac_cookie_len > 0) {
+    PPPOE_ADD_16(p, PPPOE_TAG_ACCOOKIE);
+    PPPOE_ADD_16(p, sc->sc_ac_cookie_len);
+    MEMCPY(p, sc->sc_ac_cookie, sc->sc_ac_cookie_len);
+    p += sc->sc_ac_cookie_len;
+  }
+  PPPOE_ADD_16(p, PPPOE_TAG_HUNIQUE);
+  PPPOE_ADD_16(p, sizeof(sc));
+  MEMCPY(p, &sc, sizeof sc);
+
+  return pppoe_output(sc, pb);
+}
+
+/* send a PADT packet */
+static err_t
+pppoe_send_padt(struct netif *outgoing_if, u_int session, const u8_t *dest)
+{
+  struct pbuf *pb;
+  struct eth_hdr *ethhdr;
+  err_t res;
+  u8_t *p;
+
+  pb = pbuf_alloc(PBUF_LINK, sizeof(struct eth_hdr) + PPPOE_HEADERLEN, PBUF_RAM);
+  if (!pb) {
+    return ERR_MEM;
+  }
+  LWIP_ASSERT("pb->tot_len == pb->len", pb->tot_len == pb->len);
+
+  ethhdr = (struct eth_hdr *)pb->payload;
+  ethhdr->type = PP_HTONS(ETHTYPE_PPPOEDISC);
+  MEMCPY(ethhdr->dest.addr, dest, sizeof(ethhdr->dest.addr));
+  MEMCPY(ethhdr->src.addr, ((struct eth_addr *)outgoing_if->hwaddr)->addr, sizeof(ethhdr->src.addr));
+
+  p = (u8_t*)(ethhdr + 1);
+  PPPOE_ADD_HEADER(p, PPPOE_CODE_PADT, session, 0);
+
+  res = outgoing_if->linkoutput(outgoing_if, pb);
+
+  pbuf_free(pb);
+
+  return res;
+}
+
+#ifdef PPPOE_SERVER
+static err_t
+pppoe_send_pado(struct pppoe_softc *sc)
+{
+  struct pbuf *pb;
+  u8_t *p;
+  size_t len;
+
+  if (sc->sc_state != PPPOE_STATE_PADO_SENT) {
+    return ERR_CONN;
+  }
+
+  /* calc length */
+  len = 0;
+  /* include ac_cookie */
+  len += 2 + 2 + sizeof(sc);
+  /* include hunique */
+  len += 2 + 2 + sc->sc_hunique_len;
+  pb = pbuf_alloc(PBUF_LINK, sizeof(struct eth_hdr) + PPPOE_HEADERLEN + len, PBUF_RAM);
+  if (!pb) {
+    return ERR_MEM;
+  }
+  LWIP_ASSERT("pb->tot_len == pb->len", pb->tot_len == pb->len);
+  p = (u8_t*)pb->payload + sizeof (struct eth_hdr);
+  PPPOE_ADD_HEADER(p, PPPOE_CODE_PADO, 0, len);
+  PPPOE_ADD_16(p, PPPOE_TAG_ACCOOKIE);
+  PPPOE_ADD_16(p, sizeof(sc));
+  MEMCPY(p, &sc, sizeof(sc));
+  p += sizeof(sc);
+  PPPOE_ADD_16(p, PPPOE_TAG_HUNIQUE);
+  PPPOE_ADD_16(p, sc->sc_hunique_len);
+  MEMCPY(p, sc->sc_hunique, sc->sc_hunique_len);
+  return pppoe_output(sc, pb);
+}
+
+static err_t
+pppoe_send_pads(struct pppoe_softc *sc)
+{
+  struct pbuf *pb;
+  u8_t *p;
+  size_t len, l1 = 0;  /* XXX: gcc */
+
+  if (sc->sc_state != PPPOE_STATE_PADO_SENT) {
+    return ERR_CONN;
+  }
+
+  sc->sc_session = mono_time.tv_sec % 0xff + 1;
+  /* calc length */
+  len = 0;
+  /* include hunique */
+  len += 2 + 2 + 2 + 2 + sc->sc_hunique_len;  /* service name, host unique*/
+  if (sc->sc_service_name != NULL) {    /* service name tag maybe empty */
+    l1 = strlen(sc->sc_service_name);
+    len += l1;
+  }
+  pb = pbuf_alloc(PBUF_LINK, sizeof(struct eth_hdr) + PPPOE_HEADERLEN + len, PBUF_RAM);
+  if (!pb) {
+    return ERR_MEM;
+  }
+  LWIP_ASSERT("pb->tot_len == pb->len", pb->tot_len == pb->len);
+  p = (u8_t*)pb->payload + sizeof (struct eth_hdr);
+  PPPOE_ADD_HEADER(p, PPPOE_CODE_PADS, sc->sc_session, len);
+  PPPOE_ADD_16(p, PPPOE_TAG_SNAME);
+  if (sc->sc_service_name != NULL) {
+    PPPOE_ADD_16(p, l1);
+    MEMCPY(p, sc->sc_service_name, l1);
+    p += l1;
+  } else {
+    PPPOE_ADD_16(p, 0);
+  }
+  PPPOE_ADD_16(p, PPPOE_TAG_HUNIQUE);
+  PPPOE_ADD_16(p, sc->sc_hunique_len);
+  MEMCPY(p, sc->sc_hunique, sc->sc_hunique_len);
+  return pppoe_output(sc, pb);
+}
+#endif
+
+err_t
+pppoe_xmit(struct pppoe_softc *sc, struct pbuf *pb)
+{
+  u8_t *p;
+  size_t len;
+
+  /* are we ready to process data yet? */
+  if (sc->sc_state < PPPOE_STATE_SESSION) {
+    /*sppp_flush(&sc->sc_sppp.pp_if);*/
+    pbuf_free(pb);
+    return ERR_CONN;
+  }
+
+  len = pb->tot_len;
+
+  /* make room for Ethernet header - should not fail */
+  if (pbuf_header(pb, sizeof(struct eth_hdr) + PPPOE_HEADERLEN) != 0) {
+    /* bail out */
+    PPPDEBUG(LOG_ERR, ("pppoe: %c%c%"U16_F": pppoe_xmit: could not allocate room for header\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num));
+    LINK_STATS_INC(link.lenerr);
+    pbuf_free(pb);
+    return ERR_BUF;
+  } 
+
+  p = (u8_t*)pb->payload + sizeof(struct eth_hdr);
+  PPPOE_ADD_HEADER(p, 0, sc->sc_session, len);
+
+  return pppoe_output(sc, pb);
+}
+
+#if 0 /*def PFIL_HOOKS*/
+static int
+pppoe_ifattach_hook(void *arg, struct pbuf **mp, struct netif *ifp, int dir)
+{
+  struct pppoe_softc *sc;
+  int s;
+
+  if (mp != (struct pbuf **)PFIL_IFNET_DETACH) {
+    return 0;
+  }
+
+  LIST_FOREACH(sc, &pppoe_softc_list, sc_list) {
+    if (sc->sc_ethif != ifp) {
+      continue;
+    }
+    if (sc->sc_sppp.pp_if.if_flags & IFF_UP) {
+      sc->sc_sppp.pp_if.if_flags &= ~(IFF_UP|IFF_RUNNING);
+      printf("%c%c%"U16_F": ethernet interface detached, going down\n",
+          sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num);
+    }
+    sc->sc_ethif = NULL;
+    pppoe_clear_softc(sc, "ethernet interface detached");
+  }
+
+  return 0;
+}
+#endif
+
+static void
+pppoe_clear_softc(struct pppoe_softc *sc, const char *message)
+{
+  LWIP_UNUSED_ARG(message);
+
+  /* stop timer */
+  sys_untimeout(pppoe_timeout, sc);
+  PPPDEBUG(LOG_DEBUG, ("pppoe: %c%c%"U16_F": session 0x%x terminated, %s\n", sc->sc_ethif->name[0], sc->sc_ethif->name[1], sc->sc_ethif->num, sc->sc_session, message));
+
+  /* fix our state */
+  sc->sc_state = PPPOE_STATE_INITIAL;
+
+  /* notify upper layers */
+  sc->sc_linkStatusCB(sc->sc_pd, 0);
+
+  /* clean up softc */
+  MEMCPY(&sc->sc_dest, ethbroadcast.addr, sizeof(sc->sc_dest));
+  sc->sc_ac_cookie_len = 0;
+  sc->sc_session = 0;
+}
+
+#endif /* PPPOE_SUPPORT */
+
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/pppdebug.h b/external/badvpn_dns/lwip/src/netif/ppp/pppdebug.h
new file mode 100644
index 0000000..8134997
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/pppdebug.h
@@ -0,0 +1,73 @@
+/*****************************************************************************
+* pppdebug.h - System debugging utilities.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* portions Copyright (c) 1998 Global Election Systems Inc.
+* portions Copyright (c) 2001 by Cognizant Pty Ltd.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY (please don't use tabs!)
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 98-07-29 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Original.
+*
+*****************************************************************************
+*/
+#ifndef PPPDEBUG_H
+#define PPPDEBUG_H
+
+/* Trace levels. */
+#define LOG_CRITICAL  (PPP_DEBUG | LWIP_DBG_LEVEL_SEVERE)
+#define LOG_ERR       (PPP_DEBUG | LWIP_DBG_LEVEL_SEVERE)
+#define LOG_NOTICE    (PPP_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define LOG_WARNING   (PPP_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define LOG_INFO      (PPP_DEBUG)
+#define LOG_DETAIL    (PPP_DEBUG)
+#define LOG_DEBUG     (PPP_DEBUG)
+
+
+#define TRACELCP PPP_DEBUG
+
+#if PPP_DEBUG
+
+#define AUTHDEBUG(a, b) LWIP_DEBUGF(a, b)
+#define IPCPDEBUG(a, b) LWIP_DEBUGF(a, b)
+#define UPAPDEBUG(a, b) LWIP_DEBUGF(a, b)
+#define LCPDEBUG(a, b)  LWIP_DEBUGF(a, b)
+#define FSMDEBUG(a, b)  LWIP_DEBUGF(a, b)
+#define CHAPDEBUG(a, b) LWIP_DEBUGF(a, b)
+#define PPPDEBUG(a, b)  LWIP_DEBUGF(a, b)
+
+#else /* PPP_DEBUG */
+
+#define AUTHDEBUG(a, b)
+#define IPCPDEBUG(a, b)
+#define UPAPDEBUG(a, b)
+#define LCPDEBUG(a, b)
+#define FSMDEBUG(a, b)
+#define CHAPDEBUG(a, b)
+#define PPPDEBUG(a, b)
+
+#endif /* PPP_DEBUG */
+
+#endif /* PPPDEBUG_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/randm.c b/external/badvpn_dns/lwip/src/netif/ppp/randm.c
new file mode 100644
index 0000000..b736091
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/randm.c
@@ -0,0 +1,249 @@
+/*****************************************************************************
+* randm.c - Random number generator program file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* Copyright (c) 1998 by Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 98-06-03 Guy Lancaster <lancasterg@xxxxxxx>, Global Election Systems Inc.
+*   Extracted from avos.
+*****************************************************************************/
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "md5.h"
+#include "randm.h"
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include <string.h>
+
+#if MD5_SUPPORT /* this module depends on MD5 */
+#define RANDPOOLSZ 16   /* Bytes stored in the pool of randomness. */
+
+/*****************************/
+/*** LOCAL DATA STRUCTURES ***/
+/*****************************/
+static char randPool[RANDPOOLSZ];   /* Pool of randomness. */
+static long randCount = 0;      /* Pseudo-random incrementer */
+
+
+/***********************************/
+/*** PUBLIC FUNCTION DEFINITIONS ***/
+/***********************************/
+/*
+ * Initialize the random number generator.
+ *
+ * Since this is to be called on power up, we don't have much
+ *  system randomess to work with.  Here all we use is the
+ *  real-time clock.  We'll accumulate more randomness as soon
+ *  as things start happening.
+ */
+void
+avRandomInit()
+{
+  avChurnRand(NULL, 0);
+}
+
+/*
+ * Churn the randomness pool on a random event.  Call this early and often
+ *  on random and semi-random system events to build randomness in time for
+ *  usage.  For randomly timed events, pass a null pointer and a zero length
+ *  and this will use the system timer and other sources to add randomness.
+ *  If new random data is available, pass a pointer to that and it will be
+ *  included.
+ *
+ * Ref: Applied Cryptography 2nd Ed. by Bruce Schneier p. 427
+ */
+void
+avChurnRand(char *randData, u32_t randLen)
+{
+  MD5_CTX md5;
+
+  /* LWIP_DEBUGF(LOG_INFO, ("churnRand: %u@%P\n", randLen, randData)); */
+  MD5Init(&md5);
+  MD5Update(&md5, (u_char *)randPool, sizeof(randPool));
+  if (randData) {
+    MD5Update(&md5, (u_char *)randData, randLen);
+  } else {
+    struct {
+      /* INCLUDE fields for any system sources of randomness */
+      char foobar;
+    } sysData;
+
+    /* Load sysData fields here. */
+    MD5Update(&md5, (u_char *)&sysData, sizeof(sysData));
+  }
+  MD5Final((u_char *)randPool, &md5);
+/*  LWIP_DEBUGF(LOG_INFO, ("churnRand: -> 0\n")); */
+}
+
+/*
+ * Use the random pool to generate random data.  This degrades to pseudo
+ *  random when used faster than randomness is supplied using churnRand().
+ * Note: It's important that there be sufficient randomness in randPool
+ *  before this is called for otherwise the range of the result may be
+ *  narrow enough to make a search feasible.
+ *
+ * Ref: Applied Cryptography 2nd Ed. by Bruce Schneier p. 427
+ *
+ * XXX Why does he not just call churnRand() for each block?  Probably
+ *  so that you don't ever publish the seed which could possibly help
+ *  predict future values.
+ * XXX Why don't we preserve md5 between blocks and just update it with
+ *  randCount each time?  Probably there is a weakness but I wish that
+ *  it was documented.
+ */
+void
+avGenRand(char *buf, u32_t bufLen)
+{
+  MD5_CTX md5;
+  u_char tmp[16];
+  u32_t n;
+
+  while (bufLen > 0) {
+    n = LWIP_MIN(bufLen, RANDPOOLSZ);
+    MD5Init(&md5);
+    MD5Update(&md5, (u_char *)randPool, sizeof(randPool));
+    MD5Update(&md5, (u_char *)&randCount, sizeof(randCount));
+    MD5Final(tmp, &md5);
+    randCount++;
+    MEMCPY(buf, tmp, n);
+    buf += n;
+    bufLen -= n;
+  }
+}
+
+/*
+ * Return a new random number.
+ */
+u32_t
+avRandom()
+{
+  u32_t newRand;
+
+  avGenRand((char *)&newRand, sizeof(newRand));
+
+  return newRand;
+}
+
+#else /* MD5_SUPPORT */
+
+/*****************************/
+/*** LOCAL DATA STRUCTURES ***/
+/*****************************/
+static int  avRandomized = 0;       /* Set when truely randomized. */
+static u32_t avRandomSeed = 0;      /* Seed used for random number generation. */
+
+
+/***********************************/
+/*** PUBLIC FUNCTION DEFINITIONS ***/
+/***********************************/
+/*
+ * Initialize the random number generator.
+ *
+ * Here we attempt to compute a random number seed but even if
+ * it isn't random, we'll randomize it later.
+ *
+ * The current method uses the fields from the real time clock,
+ * the idle process counter, the millisecond counter, and the
+ * hardware timer tick counter.  When this is invoked
+ * in startup(), then the idle counter and timer values may
+ * repeat after each boot and the real time clock may not be
+ * operational.  Thus we call it again on the first random
+ * event.
+ */
+void
+avRandomInit()
+{
+#if 0
+  /* Get a pointer into the last 4 bytes of clockBuf. */
+  u32_t *lptr1 = (u32_t *)((char *)&clockBuf[3]);
+
+  /*
+   * Initialize our seed using the real-time clock, the idle
+   * counter, the millisecond timer, and the hardware timer
+   * tick counter.  The real-time clock and the hardware
+   * tick counter are the best sources of randomness but
+   * since the tick counter is only 16 bit (and truncated
+   * at that), the idle counter and millisecond timer
+   * (which may be small values) are added to help
+   * randomize the lower 16 bits of the seed.
+   */
+  readClk();
+  avRandomSeed += *(u32_t *)clockBuf + *lptr1 + OSIdleCtr
+           + ppp_mtime() + ((u32_t)TM1 << 16) + TM1;
+#else
+  avRandomSeed += sys_jiffies(); /* XXX */
+#endif
+
+  /* Initialize the Borland random number generator. */
+  srand((unsigned)avRandomSeed);
+}
+
+/*
+ * Randomize our random seed value.  Here we use the fact that
+ * this function is called at *truely random* times by the polling
+ * and network functions.  Here we only get 16 bits of new random
+ * value but we use the previous value to randomize the other 16
+ * bits.
+ */
+void
+avRandomize(void)
+{
+  static u32_t last_jiffies;
+
+  if (!avRandomized) {
+    avRandomized = !0;
+    avRandomInit();
+    /* The initialization function also updates the seed. */
+  } else {
+    /* avRandomSeed += (avRandomSeed << 16) + TM1; */
+    avRandomSeed += (sys_jiffies() - last_jiffies); /* XXX */
+  }
+  last_jiffies = sys_jiffies();
+}
+
+/*
+ * Return a new random number.
+ * Here we use the Borland rand() function to supply a pseudo random
+ * number which we make truely random by combining it with our own
+ * seed which is randomized by truely random events. 
+ * Thus the numbers will be truely random unless there have been no
+ * operator or network events in which case it will be pseudo random
+ * seeded by the real time clock.
+ */
+u32_t
+avRandom()
+{
+  return ((((u32_t)rand() << 16) + rand()) + avRandomSeed);
+}
+
+#endif /* MD5_SUPPORT */
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/randm.h b/external/badvpn_dns/lwip/src/netif/ppp/randm.h
new file mode 100644
index 0000000..a0984b0
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/randm.h
@@ -0,0 +1,81 @@
+/*****************************************************************************
+* randm.h - Random number generator header file.
+*
+* Copyright (c) 2003 by Marc Boucher, Services Informatiques (MBSI) inc.
+* Copyright (c) 1998 Global Election Systems Inc.
+*
+* The authors hereby grant permission to use, copy, modify, distribute,
+* and license this software and its documentation for any purpose, provided
+* that existing copyright notices are retained in all copies and that this
+* notice and the following disclaimer are included verbatim in any 
+* distributions. No written agreement, license, or royalty fee is required
+* for any of the authorized uses.
+*
+* THIS SOFTWARE IS PROVIDED BY THE 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 CONTRIBUTORS 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.
+*
+******************************************************************************
+* REVISION HISTORY
+*
+* 03-01-01 Marc Boucher <marc@xxxxxxx>
+*   Ported to lwIP.
+* 98-05-29 Guy Lancaster <glanca@xxxxxxxx>, Global Election Systems Inc.
+*   Extracted from avos.
+*****************************************************************************/
+
+#ifndef RANDM_H
+#define RANDM_H
+
+/***********************
+*** PUBLIC FUNCTIONS ***
+***********************/
+/*
+ * Initialize the random number generator.
+ */
+void avRandomInit(void);
+
+/*
+ * Churn the randomness pool on a random event.  Call this early and often
+ * on random and semi-random system events to build randomness in time for
+ * usage.  For randomly timed events, pass a null pointer and a zero length
+ * and this will use the system timer and other sources to add randomness.
+ * If new random data is available, pass a pointer to that and it will be
+ * included.
+ */
+void avChurnRand(char *randData, u32_t randLen);
+
+/*
+ * Randomize our random seed value.  To be called for truely random events
+ * such as user operations and network traffic.
+ */
+#if MD5_SUPPORT
+#define avRandomize() avChurnRand(NULL, 0)
+#else  /* MD5_SUPPORT */
+void avRandomize(void);
+#endif /* MD5_SUPPORT */
+
+/*
+ * Use the random pool to generate random data.  This degrades to pseudo
+ * random when used faster than randomness is supplied using churnRand().
+ * Thus it's important to make sure that the results of this are not
+ * published directly because one could predict the next result to at
+ * least some degree.  Also, it's important to get a good seed before
+ * the first use.
+ */
+void avGenRand(char *buf, u32_t bufLen);
+
+/*
+ * Return a new random number.
+ */
+u32_t avRandom(void);
+
+
+#endif /* RANDM_H */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/readme.txt b/external/badvpn_dns/lwip/src/netif/ppp/readme.txt
new file mode 100644
index 0000000..5be41b9
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/readme.txt
@@ -0,0 +1,21 @@
+About the PPP code:
+
+The PPP code is not our "own" code - we just copied it from pppd (http://ppp.samba.org/) and adapted it to lwIP.
+Unfortunately, not many here know their way around it too well. Back in 2009, we took the effort to see which
+version of pppd our code relates to and we're pretty much on 2.3.11 with some bugs from 2.4.x backported.
+
+Aside from simple code adaptions, there are some files that are different, however:
+- chpms.c/.h are named chap_ms.c/.h in the original pppd 2.3.11 sources
+- pap.c/.h are named upap.c/.h in the original pppd 2.3.11 sources
+- randm.c is a random generator not included in the original pppd
+- magic.c does not use the C library's random functions, but uses randm.c instead
+- vj.c/.h is an implementation of the Van Jacobson header compression algorithm adapted to lwIP pbufs,
+  probably copied from one of the vjcompress.c files from pppd.
+- ppp.c, ppp.h and ppp_impl.h contain the adaption from pppd to lwIP. This is the "OS"-dependent part like there
+  is an implementation for linux, xBSD etc. in the pppd sources.
+- ppp_oe.c is Marc Boucher's implementation based on NetBSD's if_pppoe.c
+
+There is of course potential for bugs in it, but when analyzing of reporting bugs, it is strongly encouraged to
+compare the code in question to pppd 2.3.11 (our basis) and newer versions (perhaps it's already fixed?) and to
+share this knowledge with us when reporting a bug.
+
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/vj.c b/external/badvpn_dns/lwip/src/netif/ppp/vj.c
new file mode 100644
index 0000000..40fdad1
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/vj.c
@@ -0,0 +1,652 @@
+/*
+ * Routines to compress and uncompess tcp packets (for transmission
+ * over low speed serial lines.
+ *
+ * Copyright (c) 1989 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Van Jacobson (van@xxxxxxxxxxxxxxxxx), Dec 31, 1989:
+ *   Initial distribution.
+ *
+ * Modified June 1993 by Paul Mackerras, paulus@xxxxxxxxxxxxx,
+ * so that the entire packet being decompressed doesn't have
+ * to be in contiguous memory (just the compressed header).
+ *
+ * Modified March 1998 by Guy Lancaster, glanca@xxxxxxxx,
+ * for a 16 bit processor.
+ */
+
+#include "lwip/opt.h"
+
+#if PPP_SUPPORT /* don't build if not configured for use in lwipopts.h */
+
+#include "ppp_impl.h"
+#include "pppdebug.h"
+
+#include "vj.h"
+
+#include <string.h>
+
+#if VJ_SUPPORT
+
+#if LINK_STATS
+#define INCR(counter) ++comp->stats.counter
+#else
+#define INCR(counter)
+#endif
+
+void
+vj_compress_init(struct vjcompress *comp)
+{
+  register u_char i;
+  register struct cstate *tstate = comp->tstate;
+  
+#if MAX_SLOTS == 0
+  memset((char *)comp, 0, sizeof(*comp));
+#endif
+  comp->maxSlotIndex = MAX_SLOTS - 1;
+  comp->compressSlot = 0;    /* Disable slot ID compression by default. */
+  for (i = MAX_SLOTS - 1; i > 0; --i) {
+    tstate[i].cs_id = i;
+    tstate[i].cs_next = &tstate[i - 1];
+  }
+  tstate[0].cs_next = &tstate[MAX_SLOTS - 1];
+  tstate[0].cs_id = 0;
+  comp->last_cs = &tstate[0];
+  comp->last_recv = 255;
+  comp->last_xmit = 255;
+  comp->flags = VJF_TOSS;
+}
+
+
+/* ENCODE encodes a number that is known to be non-zero.  ENCODEZ
+ * checks for zero (since zero has to be encoded in the long, 3 byte
+ * form).
+ */
+#define ENCODE(n) { \
+  if ((u_short)(n) >= 256) { \
+    *cp++ = 0; \
+    cp[1] = (u_char)(n); \
+    cp[0] = (u_char)((n) >> 8); \
+    cp += 2; \
+  } else { \
+    *cp++ = (u_char)(n); \
+  } \
+}
+#define ENCODEZ(n) { \
+  if ((u_short)(n) >= 256 || (u_short)(n) == 0) { \
+    *cp++ = 0; \
+    cp[1] = (u_char)(n); \
+    cp[0] = (u_char)((n) >> 8); \
+    cp += 2; \
+  } else { \
+    *cp++ = (u_char)(n); \
+  } \
+}
+
+#define DECODEL(f) { \
+  if (*cp == 0) {\
+    u32_t tmp = ntohl(f) + ((cp[1] << 8) | cp[2]); \
+    (f) = htonl(tmp); \
+    cp += 3; \
+  } else { \
+    u32_t tmp = ntohl(f) + (u32_t)*cp++; \
+    (f) = htonl(tmp); \
+  } \
+}
+
+#define DECODES(f) { \
+  if (*cp == 0) {\
+    u_short tmp = ntohs(f) + (((u_short)cp[1] << 8) | cp[2]); \
+    (f) = htons(tmp); \
+    cp += 3; \
+  } else { \
+    u_short tmp = ntohs(f) + (u_short)*cp++; \
+    (f) = htons(tmp); \
+  } \
+}
+
+#define DECODEU(f) { \
+  if (*cp == 0) {\
+    (f) = htons(((u_short)cp[1] << 8) | cp[2]); \
+    cp += 3; \
+  } else { \
+    (f) = htons((u_short)*cp++); \
+  } \
+}
+
+/*
+ * vj_compress_tcp - Attempt to do Van Jacobson header compression on a
+ * packet.  This assumes that nb and comp are not null and that the first
+ * buffer of the chain contains a valid IP header.
+ * Return the VJ type code indicating whether or not the packet was
+ * compressed.
+ */
+u_int
+vj_compress_tcp(struct vjcompress *comp, struct pbuf *pb)
+{
+  register struct ip_hdr *ip = (struct ip_hdr *)pb->payload;
+  register struct cstate *cs = comp->last_cs->cs_next;
+  register u_short hlen = IPH_HL(ip);
+  register struct tcp_hdr *oth;
+  register struct tcp_hdr *th;
+  register u_short deltaS, deltaA;
+  register u_long deltaL;
+  register u_int changes = 0;
+  u_char new_seq[16];
+  register u_char *cp = new_seq;
+
+  /*  
+   * Check that the packet is IP proto TCP.
+   */
+  if (IPH_PROTO(ip) != IP_PROTO_TCP) {
+    return (TYPE_IP);
+  }
+
+  /*
+   * Bail if this is an IP fragment or if the TCP packet isn't
+   * `compressible' (i.e., ACK isn't set or some other control bit is
+   * set).  
+   */
+  if ((IPH_OFFSET(ip) & PP_HTONS(0x3fff)) || pb->tot_len < 40) {
+    return (TYPE_IP);
+  }
+  th = (struct tcp_hdr *)&((long *)ip)[hlen];
+  if ((TCPH_FLAGS(th) & (TCP_SYN|TCP_FIN|TCP_RST|TCP_ACK)) != TCP_ACK) {
+    return (TYPE_IP);
+  }
+  /*
+   * Packet is compressible -- we're going to send either a
+   * COMPRESSED_TCP or UNCOMPRESSED_TCP packet.  Either way we need
+   * to locate (or create) the connection state.  Special case the
+   * most recently used connection since it's most likely to be used
+   * again & we don't have to do any reordering if it's used.
+   */
+  INCR(vjs_packets);
+  if (!ip_addr_cmp(&ip->src, &cs->cs_ip.src)
+      || !ip_addr_cmp(&ip->dest, &cs->cs_ip.dest)
+      || *(long *)th != ((long *)&cs->cs_ip)[IPH_HL(&cs->cs_ip)]) {
+    /*
+     * Wasn't the first -- search for it.
+     *
+     * States are kept in a circularly linked list with
+     * last_cs pointing to the end of the list.  The
+     * list is kept in lru order by moving a state to the
+     * head of the list whenever it is referenced.  Since
+     * the list is short and, empirically, the connection
+     * we want is almost always near the front, we locate
+     * states via linear search.  If we don't find a state
+     * for the datagram, the oldest state is (re-)used.
+     */
+    register struct cstate *lcs;
+    register struct cstate *lastcs = comp->last_cs;
+    
+    do {
+      lcs = cs; cs = cs->cs_next;
+      INCR(vjs_searches);
+      if (ip_addr_cmp(&ip->src, &cs->cs_ip.src)
+          && ip_addr_cmp(&ip->dest, &cs->cs_ip.dest)
+          && *(long *)th == ((long *)&cs->cs_ip)[IPH_HL(&cs->cs_ip)]) {
+        goto found;
+      }
+    } while (cs != lastcs);
+
+    /*
+     * Didn't find it -- re-use oldest cstate.  Send an
+     * uncompressed packet that tells the other side what
+     * connection number we're using for this conversation.
+     * Note that since the state list is circular, the oldest
+     * state points to the newest and we only need to set
+     * last_cs to update the lru linkage.
+     */
+    INCR(vjs_misses);
+    comp->last_cs = lcs;
+    hlen += TCPH_HDRLEN(th);
+    hlen <<= 2;
+    /* Check that the IP/TCP headers are contained in the first buffer. */
+    if (hlen > pb->len) {
+      return (TYPE_IP);
+    }
+    goto uncompressed;
+
+    found:
+    /*
+     * Found it -- move to the front on the connection list.
+     */
+    if (cs == lastcs) {
+      comp->last_cs = lcs;
+    } else {
+      lcs->cs_next = cs->cs_next;
+      cs->cs_next = lastcs->cs_next;
+      lastcs->cs_next = cs;
+    }
+  }
+
+  oth = (struct tcp_hdr *)&((long *)&cs->cs_ip)[hlen];
+  deltaS = hlen;
+  hlen += TCPH_HDRLEN(th);
+  hlen <<= 2;
+  /* Check that the IP/TCP headers are contained in the first buffer. */
+  if (hlen > pb->len) {
+    PPPDEBUG(LOG_INFO, ("vj_compress_tcp: header len %d spans buffers\n", hlen));
+    return (TYPE_IP);
+  }
+
+  /*
+   * Make sure that only what we expect to change changed. The first
+   * line of the `if' checks the IP protocol version, header length &
+   * type of service.  The 2nd line checks the "Don't fragment" bit.
+   * The 3rd line checks the time-to-live and protocol (the protocol
+   * check is unnecessary but costless).  The 4th line checks the TCP
+   * header length.  The 5th line checks IP options, if any.  The 6th
+   * line checks TCP options, if any.  If any of these things are
+   * different between the previous & current datagram, we send the
+   * current datagram `uncompressed'.
+   */
+  if (((u_short *)ip)[0] != ((u_short *)&cs->cs_ip)[0] 
+      || ((u_short *)ip)[3] != ((u_short *)&cs->cs_ip)[3] 
+      || ((u_short *)ip)[4] != ((u_short *)&cs->cs_ip)[4] 
+      || TCPH_HDRLEN(th) != TCPH_HDRLEN(oth) 
+      || (deltaS > 5 && BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) 
+      || (TCPH_HDRLEN(th) > 5 && BCMP(th + 1, oth + 1, (TCPH_HDRLEN(th) - 5) << 2))) {
+    goto uncompressed;
+  }
+
+  /*
+   * Figure out which of the changing fields changed.  The
+   * receiver expects changes in the order: urgent, window,
+   * ack, seq (the order minimizes the number of temporaries
+   * needed in this section of code).
+   */
+  if (TCPH_FLAGS(th) & TCP_URG) {
+    deltaS = ntohs(th->urgp);
+    ENCODEZ(deltaS);
+    changes |= NEW_U;
+  } else if (th->urgp != oth->urgp) {
+    /* argh! URG not set but urp changed -- a sensible
+     * implementation should never do this but RFC793
+     * doesn't prohibit the change so we have to deal
+     * with it. */
+    goto uncompressed;
+  }
+
+  if ((deltaS = (u_short)(ntohs(th->wnd) - ntohs(oth->wnd))) != 0) {
+    ENCODE(deltaS);
+    changes |= NEW_W;
+  }
+
+  if ((deltaL = ntohl(th->ackno) - ntohl(oth->ackno)) != 0) {
+    if (deltaL > 0xffff) {
+      goto uncompressed;
+    }
+    deltaA = (u_short)deltaL;
+    ENCODE(deltaA);
+    changes |= NEW_A;
+  }
+
+  if ((deltaL = ntohl(th->seqno) - ntohl(oth->seqno)) != 0) {
+    if (deltaL > 0xffff) {
+      goto uncompressed;
+    }
+    deltaS = (u_short)deltaL;
+    ENCODE(deltaS);
+    changes |= NEW_S;
+  }
+
+  switch(changes) {
+  case 0:
+    /*
+     * Nothing changed. If this packet contains data and the
+     * last one didn't, this is probably a data packet following
+     * an ack (normal on an interactive connection) and we send
+     * it compressed.  Otherwise it's probably a retransmit,
+     * retransmitted ack or window probe.  Send it uncompressed
+     * in case the other side missed the compressed version.
+     */
+    if (IPH_LEN(ip) != IPH_LEN(&cs->cs_ip) &&
+      ntohs(IPH_LEN(&cs->cs_ip)) == hlen) {
+      break;
+    }
+
+  /* (fall through) */
+
+  case SPECIAL_I:
+  case SPECIAL_D:
+    /*
+     * actual changes match one of our special case encodings --
+     * send packet uncompressed.
+     */
+    goto uncompressed;
+
+  case NEW_S|NEW_A:
+    if (deltaS == deltaA && deltaS == ntohs(IPH_LEN(&cs->cs_ip)) - hlen) {
+      /* special case for echoed terminal traffic */
+      changes = SPECIAL_I;
+      cp = new_seq;
+    }
+    break;
+
+  case NEW_S:
+    if (deltaS == ntohs(IPH_LEN(&cs->cs_ip)) - hlen) {
+      /* special case for data xfer */
+      changes = SPECIAL_D;
+      cp = new_seq;
+    }
+    break;
+  }
+
+  deltaS = (u_short)(ntohs(IPH_ID(ip)) - ntohs(IPH_ID(&cs->cs_ip)));
+  if (deltaS != 1) {
+    ENCODEZ(deltaS);
+    changes |= NEW_I;
+  }
+  if (TCPH_FLAGS(th) & TCP_PSH) {
+    changes |= TCP_PUSH_BIT;
+  }
+  /*
+   * Grab the cksum before we overwrite it below.  Then update our
+   * state with this packet's header.
+   */
+  deltaA = ntohs(th->chksum);
+  BCOPY(ip, &cs->cs_ip, hlen);
+
+  /*
+   * We want to use the original packet as our compressed packet.
+   * (cp - new_seq) is the number of bytes we need for compressed
+   * sequence numbers.  In addition we need one byte for the change
+   * mask, one for the connection id and two for the tcp checksum.
+   * So, (cp - new_seq) + 4 bytes of header are needed.  hlen is how
+   * many bytes of the original packet to toss so subtract the two to
+   * get the new packet size.
+   */
+  deltaS = (u_short)(cp - new_seq);
+  if (!comp->compressSlot || comp->last_xmit != cs->cs_id) {
+    comp->last_xmit = cs->cs_id;
+    hlen -= deltaS + 4;
+    if(pbuf_header(pb, -hlen)){
+      /* Can we cope with this failing?  Just assert for now */
+      LWIP_ASSERT("pbuf_header failed\n", 0);
+    }
+    cp = (u_char *)pb->payload;
+    *cp++ = (u_char)(changes | NEW_C);
+    *cp++ = cs->cs_id;
+  } else {
+    hlen -= deltaS + 3;
+    if(pbuf_header(pb, -hlen)) {
+      /* Can we cope with this failing?  Just assert for now */
+      LWIP_ASSERT("pbuf_header failed\n", 0);
+    }
+    cp = (u_char *)pb->payload;
+    *cp++ = (u_char)changes;
+  }
+  *cp++ = (u_char)(deltaA >> 8);
+  *cp++ = (u_char)deltaA;
+  BCOPY(new_seq, cp, deltaS);
+  INCR(vjs_compressed);
+  return (TYPE_COMPRESSED_TCP);
+
+  /*
+   * Update connection state cs & send uncompressed packet (that is,
+   * a regular ip/tcp packet but with the 'conversation id' we hope
+   * to use on future compressed packets in the protocol field).
+   */
+uncompressed:
+  BCOPY(ip, &cs->cs_ip, hlen);
+  IPH_PROTO_SET(ip, cs->cs_id);
+  comp->last_xmit = cs->cs_id;
+  return (TYPE_UNCOMPRESSED_TCP);
+}
+
+/*
+ * Called when we may have missed a packet.
+ */
+void
+vj_uncompress_err(struct vjcompress *comp)
+{
+  comp->flags |= VJF_TOSS;
+  INCR(vjs_errorin);
+}
+
+/*
+ * "Uncompress" a packet of type TYPE_UNCOMPRESSED_TCP.
+ * Return 0 on success, -1 on failure.
+ */
+int
+vj_uncompress_uncomp(struct pbuf *nb, struct vjcompress *comp)
+{
+  register u_int hlen;
+  register struct cstate *cs;
+  register struct ip_hdr *ip;
+  
+  ip = (struct ip_hdr *)nb->payload;
+  hlen = IPH_HL(ip) << 2;
+  if (IPH_PROTO(ip) >= MAX_SLOTS
+      || hlen + sizeof(struct tcp_hdr) > nb->len
+      || (hlen += TCPH_HDRLEN(((struct tcp_hdr *)&((char *)ip)[hlen])) << 2)
+          > nb->len
+      || hlen > MAX_HDR) {
+    PPPDEBUG(LOG_INFO, ("vj_uncompress_uncomp: bad cid=%d, hlen=%d buflen=%d\n", 
+      IPH_PROTO(ip), hlen, nb->len));
+    comp->flags |= VJF_TOSS;
+    INCR(vjs_errorin);
+    return -1;
+  }
+  cs = &comp->rstate[comp->last_recv = IPH_PROTO(ip)];
+  comp->flags &=~ VJF_TOSS;
+  IPH_PROTO_SET(ip, IP_PROTO_TCP);
+  BCOPY(ip, &cs->cs_ip, hlen);
+  cs->cs_hlen = (u_short)hlen;
+  INCR(vjs_uncompressedin);
+  return 0;
+}
+
+/*
+ * Uncompress a packet of type TYPE_COMPRESSED_TCP.
+ * The packet is composed of a buffer chain and the first buffer
+ * must contain an accurate chain length.
+ * The first buffer must include the entire compressed TCP/IP header. 
+ * This procedure replaces the compressed header with the uncompressed
+ * header and returns the length of the VJ header.
+ */
+int
+vj_uncompress_tcp(struct pbuf **nb, struct vjcompress *comp)
+{
+  u_char *cp;
+  struct tcp_hdr *th;
+  struct cstate *cs;
+  u_short *bp;
+  struct pbuf *n0 = *nb;
+  u32_t tmp;
+  u_int vjlen, hlen, changes;
+
+  INCR(vjs_compressedin);
+  cp = (u_char *)n0->payload;
+  changes = *cp++;
+  if (changes & NEW_C) {
+    /* 
+     * Make sure the state index is in range, then grab the state.
+     * If we have a good state index, clear the 'discard' flag. 
+     */
+    if (*cp >= MAX_SLOTS) {
+      PPPDEBUG(LOG_INFO, ("vj_uncompress_tcp: bad cid=%d\n", *cp));
+      goto bad;
+    }
+
+    comp->flags &=~ VJF_TOSS;
+    comp->last_recv = *cp++;
+  } else {
+    /* 
+     * this packet has an implicit state index.  If we've
+     * had a line error since the last time we got an
+     * explicit state index, we have to toss the packet. 
+     */
+    if (comp->flags & VJF_TOSS) {
+      PPPDEBUG(LOG_INFO, ("vj_uncompress_tcp: tossing\n"));
+      INCR(vjs_tossed);
+      return (-1);
+    }
+  }
+  cs = &comp->rstate[comp->last_recv];
+  hlen = IPH_HL(&cs->cs_ip) << 2;
+  th = (struct tcp_hdr *)&((u_char *)&cs->cs_ip)[hlen];
+  th->chksum = htons((*cp << 8) | cp[1]);
+  cp += 2;
+  if (changes & TCP_PUSH_BIT) {
+    TCPH_SET_FLAG(th, TCP_PSH);
+  } else {
+    TCPH_UNSET_FLAG(th, TCP_PSH);
+  }
+
+  switch (changes & SPECIALS_MASK) {
+  case SPECIAL_I:
+    {
+      register u32_t i = ntohs(IPH_LEN(&cs->cs_ip)) - cs->cs_hlen;
+      /* some compilers can't nest inline assembler.. */
+      tmp = ntohl(th->ackno) + i;
+      th->ackno = htonl(tmp);
+      tmp = ntohl(th->seqno) + i;
+      th->seqno = htonl(tmp);
+    }
+    break;
+
+  case SPECIAL_D:
+    /* some compilers can't nest inline assembler.. */
+    tmp = ntohl(th->seqno) + ntohs(IPH_LEN(&cs->cs_ip)) - cs->cs_hlen;
+    th->seqno = htonl(tmp);
+    break;
+
+  default:
+    if (changes & NEW_U) {
+      TCPH_SET_FLAG(th, TCP_URG);
+      DECODEU(th->urgp);
+    } else {
+      TCPH_UNSET_FLAG(th, TCP_URG);
+    }
+    if (changes & NEW_W) {
+      DECODES(th->wnd);
+    }
+    if (changes & NEW_A) {
+      DECODEL(th->ackno);
+    }
+    if (changes & NEW_S) {
+      DECODEL(th->seqno);
+    }
+    break;
+  }
+  if (changes & NEW_I) {
+    DECODES(cs->cs_ip._id);
+  } else {
+    IPH_ID_SET(&cs->cs_ip, ntohs(IPH_ID(&cs->cs_ip)) + 1);
+    IPH_ID_SET(&cs->cs_ip, htons(IPH_ID(&cs->cs_ip)));
+  }
+
+  /*
+   * At this point, cp points to the first byte of data in the
+   * packet.  Fill in the IP total length and update the IP
+   * header checksum.
+   */
+  vjlen = (u_short)(cp - (u_char*)n0->payload);
+  if (n0->len < vjlen) {
+    /* 
+     * We must have dropped some characters (crc should detect
+     * this but the old slip framing won't) 
+     */
+    PPPDEBUG(LOG_INFO, ("vj_uncompress_tcp: head buffer %d too short %d\n", 
+          n0->len, vjlen));
+    goto bad;
+  }
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+  tmp = n0->tot_len - vjlen + cs->cs_hlen;
+  IPH_LEN_SET(&cs->cs_ip, htons((u_short)tmp));
+#else
+  IPH_LEN_SET(&cs->cs_ip, htons(n0->tot_len - vjlen + cs->cs_hlen));
+#endif
+
+  /* recompute the ip header checksum */
+  bp = (u_short *) &cs->cs_ip;
+  IPH_CHKSUM_SET(&cs->cs_ip, 0);
+  for (tmp = 0; hlen > 0; hlen -= 2) {
+    tmp += *bp++;
+  }
+  tmp = (tmp & 0xffff) + (tmp >> 16);
+  tmp = (tmp & 0xffff) + (tmp >> 16);
+  IPH_CHKSUM_SET(&cs->cs_ip,  (u_short)(~tmp));
+  
+  /* Remove the compressed header and prepend the uncompressed header. */
+  if(pbuf_header(n0, -((s16_t)(vjlen)))) {
+    /* Can we cope with this failing?  Just assert for now */
+    LWIP_ASSERT("pbuf_header failed\n", 0);
+    goto bad;
+  }
+
+  if(LWIP_MEM_ALIGN(n0->payload) != n0->payload) {
+    struct pbuf *np, *q;
+    u8_t *bufptr;
+
+    np = pbuf_alloc(PBUF_RAW, n0->len + cs->cs_hlen, PBUF_POOL);
+    if(!np) {
+      PPPDEBUG(LOG_WARNING, ("vj_uncompress_tcp: realign failed\n"));
+      goto bad;
+    }
+
+    if(pbuf_header(np, -cs->cs_hlen)) {
+      /* Can we cope with this failing?  Just assert for now */
+      LWIP_ASSERT("pbuf_header failed\n", 0);
+      goto bad;
+    }
+
+    bufptr = n0->payload;
+    for(q = np; q != NULL; q = q->next) {
+      MEMCPY(q->payload, bufptr, q->len);
+      bufptr += q->len;
+    }
+
+    if(n0->next) {
+      pbuf_chain(np, n0->next);
+      pbuf_dechain(n0);
+    }
+    pbuf_free(n0);
+    n0 = np;
+  }
+
+  if(pbuf_header(n0, cs->cs_hlen)) {
+    struct pbuf *np;
+
+    LWIP_ASSERT("vj_uncompress_tcp: cs->cs_hlen <= PBUF_POOL_BUFSIZE", cs->cs_hlen <= PBUF_POOL_BUFSIZE);
+    np = pbuf_alloc(PBUF_RAW, cs->cs_hlen, PBUF_POOL);
+    if(!np) {
+      PPPDEBUG(LOG_WARNING, ("vj_uncompress_tcp: prepend failed\n"));
+      goto bad;
+    }
+    pbuf_cat(np, n0);
+    n0 = np;
+  }
+  LWIP_ASSERT("n0->len >= cs->cs_hlen", n0->len >= cs->cs_hlen);
+  MEMCPY(n0->payload, &cs->cs_ip, cs->cs_hlen);
+
+  *nb = n0;
+
+  return vjlen;
+
+bad:
+  comp->flags |= VJF_TOSS;
+  INCR(vjs_errorin);
+  return (-1);
+}
+
+#endif /* VJ_SUPPORT */
+
+#endif /* PPP_SUPPORT */
diff --git a/external/badvpn_dns/lwip/src/netif/ppp/vj.h b/external/badvpn_dns/lwip/src/netif/ppp/vj.h
new file mode 100644
index 0000000..fad1213
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/ppp/vj.h
@@ -0,0 +1,156 @@
+/*
+ * Definitions for tcp compression routines.
+ *
+ * $Id: vj.h,v 1.7 2010/02/22 17:52:09 goldsimon Exp $
+ *
+ * Copyright (c) 1989 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Van Jacobson (van@xxxxxxxxxxxxxxxxx), Dec 31, 1989:
+ * - Initial distribution.
+ */
+
+#ifndef VJ_H
+#define VJ_H
+
+#include "lwip/ip.h"
+#include "lwip/tcp_impl.h"
+
+#define MAX_SLOTS 16 /* must be > 2 and < 256 */
+#define MAX_HDR   128
+
+/*
+ * Compressed packet format:
+ *
+ * The first octet contains the packet type (top 3 bits), TCP
+ * 'push' bit, and flags that indicate which of the 4 TCP sequence
+ * numbers have changed (bottom 5 bits).  The next octet is a
+ * conversation number that associates a saved IP/TCP header with
+ * the compressed packet.  The next two octets are the TCP checksum
+ * from the original datagram.  The next 0 to 15 octets are
+ * sequence number changes, one change per bit set in the header
+ * (there may be no changes and there are two special cases where
+ * the receiver implicitly knows what changed -- see below).
+ * 
+ * There are 5 numbers which can change (they are always inserted
+ * in the following order): TCP urgent pointer, window,
+ * acknowlegement, sequence number and IP ID.  (The urgent pointer
+ * is different from the others in that its value is sent, not the
+ * change in value.)  Since typical use of SLIP links is biased
+ * toward small packets (see comments on MTU/MSS below), changes
+ * use a variable length coding with one octet for numbers in the
+ * range 1 - 255 and 3 octets (0, MSB, LSB) for numbers in the
+ * range 256 - 65535 or 0.  (If the change in sequence number or
+ * ack is more than 65535, an uncompressed packet is sent.)
+ */
+
+/*
+ * Packet types (must not conflict with IP protocol version)
+ *
+ * The top nibble of the first octet is the packet type.  There are
+ * three possible types: IP (not proto TCP or tcp with one of the
+ * control flags set); uncompressed TCP (a normal IP/TCP packet but
+ * with the 8-bit protocol field replaced by an 8-bit connection id --
+ * this type of packet syncs the sender & receiver); and compressed
+ * TCP (described above).
+ *
+ * LSB of 4-bit field is TCP "PUSH" bit (a worthless anachronism) and
+ * is logically part of the 4-bit "changes" field that follows.  Top
+ * three bits are actual packet type.  For backward compatibility
+ * and in the interest of conserving bits, numbers are chosen so the
+ * IP protocol version number (4) which normally appears in this nibble
+ * means "IP packet".
+ */
+
+/* packet types */
+#define TYPE_IP               0x40
+#define TYPE_UNCOMPRESSED_TCP 0x70
+#define TYPE_COMPRESSED_TCP   0x80
+#define TYPE_ERROR            0x00
+
+/* Bits in first octet of compressed packet */
+#define NEW_C 0x40 /* flag bits for what changed in a packet */
+#define NEW_I 0x20
+#define NEW_S 0x08
+#define NEW_A 0x04
+#define NEW_W 0x02
+#define NEW_U 0x01
+
+/* reserved, special-case values of above */
+#define SPECIAL_I (NEW_S|NEW_W|NEW_U) /* echoed interactive traffic */
+#define SPECIAL_D (NEW_S|NEW_A|NEW_W|NEW_U) /* unidirectional data */
+#define SPECIALS_MASK (NEW_S|NEW_A|NEW_W|NEW_U)
+
+#define TCP_PUSH_BIT 0x10
+
+
+/*
+ * "state" data for each active tcp conversation on the wire.  This is
+ * basically a copy of the entire IP/TCP header from the last packet
+ * we saw from the conversation together with a small identifier
+ * the transmit & receive ends of the line use to locate saved header.
+ */
+struct cstate {
+  struct cstate *cs_next; /* next most recently used state (xmit only) */
+  u_short cs_hlen;        /* size of hdr (receive only) */
+  u_char cs_id;           /* connection # associated with this state */
+  u_char cs_filler;
+  union {
+    char csu_hdr[MAX_HDR];
+    struct ip_hdr csu_ip;     /* ip/tcp hdr from most recent packet */
+  } vjcs_u;
+};
+#define cs_ip vjcs_u.csu_ip
+#define cs_hdr vjcs_u.csu_hdr
+
+
+struct vjstat {
+  unsigned long vjs_packets;        /* outbound packets */
+  unsigned long vjs_compressed;     /* outbound compressed packets */
+  unsigned long vjs_searches;       /* searches for connection state */
+  unsigned long vjs_misses;         /* times couldn't find conn. state */
+  unsigned long vjs_uncompressedin; /* inbound uncompressed packets */
+  unsigned long vjs_compressedin;   /* inbound compressed packets */
+  unsigned long vjs_errorin;        /* inbound unknown type packets */
+  unsigned long vjs_tossed;         /* inbound packets tossed because of error */
+};
+
+/*
+ * all the state data for one serial line (we need one of these per line).
+ */
+struct vjcompress {
+  struct cstate *last_cs;          /* most recently used tstate */
+  u_char last_recv;                /* last rcvd conn. id */
+  u_char last_xmit;                /* last sent conn. id */
+  u_short flags;
+  u_char maxSlotIndex;
+  u_char compressSlot;             /* Flag indicating OK to compress slot ID. */
+#if LINK_STATS
+  struct vjstat stats;
+#endif
+  struct cstate tstate[MAX_SLOTS]; /* xmit connection states */
+  struct cstate rstate[MAX_SLOTS]; /* receive connection states */
+};
+
+/* flag values */
+#define VJF_TOSS 1U /* tossing rcvd frames because of input err */
+
+extern void  vj_compress_init    (struct vjcompress *comp);
+extern u_int vj_compress_tcp     (struct vjcompress *comp, struct pbuf *pb);
+extern void  vj_uncompress_err   (struct vjcompress *comp);
+extern int   vj_uncompress_uncomp(struct pbuf *nb, struct vjcompress *comp);
+extern int   vj_uncompress_tcp   (struct pbuf **nb, struct vjcompress *comp);
+
+#endif /* VJ_H */
diff --git a/external/badvpn_dns/lwip/src/netif/slipif.c b/external/badvpn_dns/lwip/src/netif/slipif.c
new file mode 100644
index 0000000..137ba89
--- /dev/null
+++ b/external/badvpn_dns/lwip/src/netif/slipif.c
@@ -0,0 +1,546 @@
+/**
+ * @file
+ * SLIP Interface
+ *
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ *
+ * 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 Institute 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 INSTITUTE 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 INSTITUTE OR CONTRIBUTORS 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. 
+ *
+ * This file is built upon the file: src/arch/rtxc/netif/sioslip.c
+ *
+ * Author: Magnus Ivarsson <magnus.ivarsson(at)volvo.com> 
+ *         Simon Goldschmidt
+ *
+ * Usage: This netif can be used in three ways:
+ *        1) For NO_SYS==0, an RX thread can be used which blocks on sio_read()
+ *           until data is received.
+ *        2) In your main loop, call slipif_poll() to check for new RX bytes,
+ *           completed packets are fed into netif->input().
+ *        3) Call slipif_received_byte[s]() from your serial RX ISR and
+ *           slipif_process_rxqueue() from your main loop. ISR level decodes
+ *           packets and puts completed packets on a queue which is fed into
+ *           the stack from the main loop (needs SYS_LIGHTWEIGHT_PROT for
+ *           pbuf_alloc to work on ISR level!).
+ *     
+ */
+
+/* 
+ * This is an arch independent SLIP netif. The specific serial hooks must be
+ * provided by another file. They are sio_open, sio_read/sio_tryread and sio_send
+ */
+
+#include "netif/slipif.h"
+#include "lwip/opt.h"
+
+#if LWIP_HAVE_SLIPIF
+
+#include "lwip/def.h"
+#include "lwip/pbuf.h"
+#include "lwip/stats.h"
+#include "lwip/snmp.h"
+#include "lwip/sio.h"
+#include "lwip/sys.h"
+
+#define SLIP_END     0xC0 /* 0300: start and end of every packet */
+#define SLIP_ESC     0xDB /* 0333: escape start (one byte escaped data follows) */
+#define SLIP_ESC_END 0xDC /* 0334: following escape: original byte is 0xC0 (END) */
+#define SLIP_ESC_ESC 0xDD /* 0335: following escape: original byte is 0xDB (ESC) */
+
+/** Maximum packet size that is received by this netif */
+#ifndef SLIP_MAX_SIZE
+#define SLIP_MAX_SIZE 1500
+#endif
+
+/** Define this to the interface speed for SNMP
+ * (sio_fd is the sio_fd_t returned by sio_open).
+ * The default value of zero means 'unknown'.
+ */
+#ifndef SLIP_SIO_SPEED
+#define SLIP_SIO_SPEED(sio_fd) 0
+#endif
+
+enum slipif_recv_state {
+    SLIP_RECV_NORMAL,
+    SLIP_RECV_ESCAPE,
+};
+
+struct slipif_priv {
+  sio_fd_t sd;
+  /* q is the whole pbuf chain for a packet, p is the current pbuf in the chain */
+  struct pbuf *p, *q;
+  u8_t state;
+  u16_t i, recved;
+#if SLIP_RX_FROM_ISR
+  struct pbuf *rxpackets;
+#endif
+};
+
+/**
+ * Send a pbuf doing the necessary SLIP encapsulation
+ *
+ * Uses the serial layer's sio_send()
+ *
+ * @param netif the lwip network interface structure for this slipif
+ * @param p the pbuf chaing packet to send
+ * @return always returns ERR_OK since the serial layer does not provide return values
+ */
+static err_t
+slipif_output(struct netif *netif, struct pbuf *p)
+{
+  struct slipif_priv *priv;
+  struct pbuf *q;
+  u16_t i;
+  u8_t c;
+
+  LWIP_ASSERT("netif != NULL", (netif != NULL));
+  LWIP_ASSERT("netif->state != NULL", (netif->state != NULL));
+  LWIP_ASSERT("p != NULL", (p != NULL));
+
+  LWIP_DEBUGF(SLIP_DEBUG, ("slipif_output(%"U16_F"): sending %"U16_F" bytes\n", (u16_t)netif->num, p->tot_len));
+  priv = netif->state;
+
+  /* Send pbuf out on the serial I/O device. */
+  /* Start with packet delimiter. */
+  sio_send(SLIP_END, priv->sd);
+
+  for (q = p; q != NULL; q = q->next) {
+    for (i = 0; i < q->len; i++) {
+      c = ((u8_t *)q->payload)[i];
+      switch (c) {
+      case SLIP_END:
+        /* need to escape this byte (0xC0 -> 0xDB, 0xDC) */
+        sio_send(SLIP_ESC, priv->sd);
+        sio_send(SLIP_ESC_END, priv->sd);
+        break;
+      case SLIP_ESC:
+        /* need to escape this byte (0xDB -> 0xDB, 0xDD) */
+        sio_send(SLIP_ESC, priv->sd);
+        sio_send(SLIP_ESC_ESC, priv->sd);
+        break;
+      default:
+        /* normal byte - no need for escaping */
+        sio_send(c, priv->sd);
+        break;
+      }
+    }
+  }
+  /* End with packet delimiter. */
+  sio_send(SLIP_END, priv->sd);
+  return ERR_OK;
+}
+
+/**
+ * Send a pbuf doing the necessary SLIP encapsulation
+ *
+ * Uses the serial layer's sio_send()
+ *
+ * @param netif the lwip network interface structure for this slipif
+ * @param p the pbuf chaing packet to send
+ * @param ipaddr the ip address to send the packet to (not used for slipif)
+ * @return always returns ERR_OK since the serial layer does not provide return values
+ */
+static err_t
+slipif_output_v4(struct netif *netif, struct pbuf *p, ip_addr_t *ipaddr)
+{
+  LWIP_UNUSED_ARG(ipaddr);
+  return slipif_output(netif, p);
+}
+
+#if LWIP_IPV6
+/**
+ * Send a pbuf doing the necessary SLIP encapsulation
+ *
+ * Uses the serial layer's sio_send()
+ *
+ * @param netif the lwip network interface structure for this slipif
+ * @param p the pbuf chaing packet to send
+ * @param ipaddr the ip address to send the packet to (not used for slipif)
+ * @return always returns ERR_OK since the serial layer does not provide return values
+ */
+static err_t
+slipif_output_v6(struct netif *netif, struct pbuf *p, ip6_addr_t *ipaddr)
+{
+  LWIP_UNUSED_ARG(ipaddr);
+  return slipif_output(netif, p);
+}
+#endif /* LWIP_IPV6 */
+
+/**
+ * Handle the incoming SLIP stream character by character
+ *
+ * @param netif the lwip network interface structure for this slipif
+ * @param c received character (multiple calls to this function will
+ *        return a complete packet, NULL is returned before - used for polling)
+ * @return The IP packet when SLIP_END is received
+ */
+static struct pbuf*
+slipif_rxbyte(struct netif *netif, u8_t c)
+{
+  struct slipif_priv *priv;
+  struct pbuf *t;
+
+  LWIP_ASSERT("netif != NULL", (netif != NULL));
+  LWIP_ASSERT("netif->state != NULL", (netif->state != NULL));
+
+  priv = netif->state;
+
+  switch (priv->state) {
+  case SLIP_RECV_NORMAL:
+    switch (c) {
+    case SLIP_END:
+      if (priv->recved > 0) {
+        /* Received whole packet. */
+        /* Trim the pbuf to the size of the received packet. */
+        pbuf_realloc(priv->q, priv->recved);
+
+        LINK_STATS_INC(link.recv);
+
+        LWIP_DEBUGF(SLIP_DEBUG, ("slipif: Got packet (%"U16_F" bytes)\n", priv->recved));
+        t = priv->q;
+        priv->p = priv->q = NULL;
+        priv->i = priv->recved = 0;
+        return t;
+      }
+      return NULL;
+    case SLIP_ESC:
+      priv->state = SLIP_RECV_ESCAPE;
+      return NULL;
+    } /* end switch (c) */
+    break;
+  case SLIP_RECV_ESCAPE:
+    /* un-escape END or ESC bytes, leave other bytes
+       (although that would be a protocol error) */
+    switch (c) {
+    case SLIP_ESC_END:
+      c = SLIP_END;
+      break;
+    case SLIP_ESC_ESC:
+      c = SLIP_ESC;
+      break;
+    }
+    priv->state = SLIP_RECV_NORMAL;
+    break;
+  } /* end switch (priv->state) */
+
+  /* byte received, packet not yet completely received */
+  if (priv->p == NULL) {
+    /* allocate a new pbuf */
+    LWIP_DEBUGF(SLIP_DEBUG, ("slipif_input: alloc\n"));
+    priv->p = pbuf_alloc(PBUF_LINK, (PBUF_POOL_BUFSIZE - PBUF_LINK_HLEN), PBUF_POOL);
+
+    if (priv->p == NULL) {
+      LINK_STATS_INC(link.drop);
+      LWIP_DEBUGF(SLIP_DEBUG, ("slipif_input: no new pbuf! (DROP)\n"));
+      /* don't process any further since we got no pbuf to receive to */
+      return NULL;
+    }
+
+    if (priv->q != NULL) {
+      /* 'chain' the pbuf to the existing chain */
+      pbuf_cat(priv->q, priv->p);
+    } else {
+      /* p is the first pbuf in the chain */
+      priv->q = priv->p;
+    }
+  }
+
+  /* this automatically drops bytes if > SLIP_MAX_SIZE */
+  if ((priv->p != NULL) && (priv->recved <= SLIP_MAX_SIZE)) {
+    ((u8_t *)priv->p->payload)[priv->i] = c;
+    priv->recved++;
+    priv->i++;
+    if (priv->i >= priv->p->len) {
+      /* on to the next pbuf */
+      priv->i = 0;
+      if (priv->p->next != NULL && priv->p->next->len > 0) {
+        /* p is a chain, on to the next in the chain */
+          priv->p = priv->p->next;
+      } else {
+        /* p is a single pbuf, set it to NULL so next time a new
+         * pbuf is allocated */
+          priv->p = NULL;
+      }
+    }
+  }
+  return NULL;
+}
+
+/** Like slipif_rxbyte, but passes completed packets to netif->input
+ *
+ * @param netif The lwip network interface structure for this slipif
+ * @param data received character
+ */
+static void
+slipif_rxbyte_input(struct netif *netif, u8_t c)
+{
+  struct pbuf *p;
+  p = slipif_rxbyte(netif, c);
+  if (p != NULL) {
+    if (netif->input(p, netif) != ERR_OK) {
+      pbuf_free(p);
+    }
+  }
+}
+
+#if SLIP_USE_RX_THREAD
+/**
+ * The SLIP input thread.
+ *
+ * Feed the IP layer with incoming packets
+ *
+ * @param nf the lwip network interface structure for this slipif
+ */
+static void
+slipif_loop_thread(void *nf)
+{
+  u8_t c;
+  struct netif *netif = (struct netif *)nf;
+  struct slipif_priv *priv = (struct slipif_priv *)netif->state;
+
+  while (1) {
+    if (sio_read(priv->sd, &c, 1) > 0) {
+      slipif_rxbyte_input(netif, c);
+    }
+  }
+}
+#endif /* SLIP_USE_RX_THREAD */
+
+/**
+ * SLIP netif initialization
+ *
+ * Call the arch specific sio_open and remember
+ * the opened device in the state field of the netif.
+ *
+ * @param netif the lwip network interface structure for this slipif
+ * @return ERR_OK if serial line could be opened,
+ *         ERR_MEM if no memory could be allocated,
+ *         ERR_IF is serial line couldn't be opened
+ *
+ * @note netif->num must contain the number of the serial port to open
+ *       (0 by default). If netif->state is != NULL, it is interpreted as an
+ *       u8_t pointer pointing to the serial port number instead of netif->num.
+ *
+ */
+err_t
+slipif_init(struct netif *netif)
+{
+  struct slipif_priv *priv;
+  u8_t sio_num;
+
+  LWIP_DEBUGF(SLIP_DEBUG, ("slipif_init: netif->num=%"U16_F"\n", (u16_t)netif->num));
+
+  /* Allocate private data */
+  priv = (struct slipif_priv *)mem_malloc(sizeof(struct slipif_priv));
+  if (!priv) {
+    return ERR_MEM;
+  }
+
+  netif->name[0] = 's';
+  netif->name[1] = 'l';
+  netif->output = slipif_output_v4;
+#if LWIP_IPV6
+  netif->output_ip6 = slipif_output_v6;
+#endif /* LWIP_IPV6 */
+  netif->mtu = SLIP_MAX_SIZE;
+  netif->flags |= NETIF_FLAG_POINTTOPOINT;
+
+  /* netif->state or netif->num contain the port number */
+  if (netif->state != NULL) {
+    sio_num = *(u8_t*)netif->state;
+  } else {
+    sio_num = netif->num;
+  }
+  /* Try to open the serial port. */
+  priv->sd = sio_open(sio_num);
+  if (!priv->sd) {
+    /* Opening the serial port failed. */
+    mem_free(priv);
+    return ERR_IF;
+  }
+
+  /* Initialize private data */
+  priv->p = NULL;
+  priv->q = NULL;
+  priv->state = SLIP_RECV_NORMAL;
+  priv->i = 0;
+  priv->recved = 0;
+#if SLIP_RX_FROM_ISR
+  priv->rxpackets = NULL;
+#endif
+
+  netif->state = priv;
+
+  /* initialize the snmp variables and counters inside the struct netif */
+  NETIF_INIT_SNMP(netif, snmp_ifType_slip, SLIP_SIO_SPEED(priv->sd));
+
+#if SLIP_USE_RX_THREAD
+  /* Create a thread to poll the serial line. */
+  sys_thread_new(SLIPIF_THREAD_NAME, slipif_loop_thread, netif,
+    SLIPIF_THREAD_STACKSIZE, SLIPIF_THREAD_PRIO);
+#endif /* SLIP_USE_RX_THREAD */
+  return ERR_OK;
+}
+
+/**
+ * Polls the serial device and feeds the IP layer with incoming packets.
+ *
+ * @param netif The lwip network interface structure for this slipif
+ */
+void
+slipif_poll(struct netif *netif)
+{
+  u8_t c;
+  struct slipif_priv *priv;
+
+  LWIP_ASSERT("netif != NULL", (netif != NULL));
+  LWIP_ASSERT("netif->state != NULL", (netif->state != NULL));
+
+  priv = (struct slipif_priv *)netif->state;
+
+  while (sio_tryread(priv->sd, &c, 1) > 0) {
+    slipif_rxbyte_input(netif, c);
+  }
+}
+
+#if SLIP_RX_FROM_ISR
+/**
+ * Feeds the IP layer with incoming packets that were receive
+ *
+ * @param netif The lwip network interface structure for this slipif
+ */
+void
+slipif_process_rxqueue(struct netif *netif)
+{
+  struct slipif_priv *priv;
+  SYS_ARCH_DECL_PROTECT(old_level);
+
+  LWIP_ASSERT("netif != NULL", (netif != NULL));
+  LWIP_ASSERT("netif->state != NULL", (netif->state != NULL));
+
+  priv = (struct slipif_priv *)netif->state;
+
+  SYS_ARCH_PROTECT(old_level);
+  while (priv->rxpackets != NULL) {
+    struct pbuf *p = priv->rxpackets;
+#if SLIP_RX_QUEUE
+    /* dequeue packet */
+    struct pbuf *q = p;
+    while ((q->len != q->tot_len) && (q->next != NULL)) {
+      q = q->next;
+    }
+    priv->rxpackets = q->next;
+    q->next = NULL;
+#else /* SLIP_RX_QUEUE */
+    priv->rxpackets = NULL;
+#endif /* SLIP_RX_QUEUE */
+    SYS_ARCH_UNPROTECT(old_level);
+    if (netif->input(p, netif) != ERR_OK) {
+      pbuf_free(p);
+    }
+    SYS_ARCH_PROTECT(old_level);
+  }
+}
+
+/** Like slipif_rxbyte, but queues completed packets.
+ *
+ * @param netif The lwip network interface structure for this slipif
+ * @param data Received serial byte
+ */
+static void
+slipif_rxbyte_enqueue(struct netif *netif, u8_t data)
+{
+  struct pbuf *p;
+  struct slipif_priv *priv = (struct slipif_priv *)netif->state;
+  SYS_ARCH_DECL_PROTECT(old_level);
+
+  p = slipif_rxbyte(netif, data);
+  if (p != NULL) {
+    SYS_ARCH_PROTECT(old_level);
+    if (priv->rxpackets != NULL) {
+#if SLIP_RX_QUEUE
+      /* queue multiple pbufs */
+      struct pbuf *q = p;
+      while(q->next != NULL) {
+        q = q->next;
+      }
+      q->next = p;
+    } else {
+#else /* SLIP_RX_QUEUE */
+      pbuf_free(priv->rxpackets);
+    }
+    {
+#endif /* SLIP_RX_QUEUE */
+      priv->rxpackets = p;
+    }
+    SYS_ARCH_UNPROTECT(old_level);
+  }
+}
+
+/**
+ * Process a received byte, completed packets are put on a queue that is
+ * fed into IP through slipif_process_rxqueue().
+ *
+ * This function can be called from ISR if SYS_LIGHTWEIGHT_PROT is enabled.
+ *
+ * @param netif The lwip network interface structure for this slipif
+ * @param data received character
+ */
+void
+slipif_received_byte(struct netif *netif, u8_t data)
+{
+  LWIP_ASSERT("netif != NULL", (netif != NULL));
+  LWIP_ASSERT("netif->state != NULL", (netif->state != NULL));
+  slipif_rxbyte_enqueue(netif, data);
+}
+
+/**
+ * Process multiple received byte, completed packets are put on a queue that is
+ * fed into IP through slipif_process_rxqueue().
+ *
+ * This function can be called from ISR if SYS_LIGHTWEIGHT_PROT is enabled.
+ *
+ * @param netif The lwip network interface structure for this slipif
+ * @param data received character
+ * @param len Number of received characters
+ */
+void
+slipif_received_bytes(struct netif *netif, u8_t *data, u8_t len)
+{
+  u8_t i;
+  u8_t *rxdata = data;
+  LWIP_ASSERT("netif != NULL", (netif != NULL));
+  LWIP_ASSERT("netif->state != NULL", (netif->state != NULL));
+
+  for (i = 0; i < len; i++, rxdata++) {
+    slipif_rxbyte_enqueue(netif, *rxdata);
+  }
+}
+#endif /* SLIP_RX_FROM_ISR */
+
+#endif /* LWIP_HAVE_SLIPIF */
diff --git a/external/badvpn_dns/lwip/test/unit/core/test_mem.c b/external/badvpn_dns/lwip/test/unit/core/test_mem.c
new file mode 100644
index 0000000..d3a5d54
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/core/test_mem.c
@@ -0,0 +1,73 @@
+#include "test_mem.h"
+
+#include "lwip/mem.h"
+#include "lwip/stats.h"
+
+#if !LWIP_STATS || !MEM_STATS
+#error "This tests needs MEM-statistics enabled"
+#endif
+#if LWIP_DNS
+#error "This test needs DNS turned off (as it mallocs on init)"
+#endif
+
+/* Setups/teardown functions */
+
+static void
+mem_setup(void)
+{
+}
+
+static void
+mem_teardown(void)
+{
+}
+
+
+/* Test functions */
+
+/** Call mem_malloc, mem_free and mem_trim and check stats */
+START_TEST(test_mem_one)
+{
+#define SIZE1   16
+#define SIZE1_2 12
+#define SIZE2   16
+  void *p1, *p2;
+  mem_size_t s1, s2;
+  LWIP_UNUSED_ARG(_i);
+
+#if LWIP_DNS
+  fail("This test needs DNS turned off (as it mallocs on init)");
+#endif
+
+  fail_unless(lwip_stats.mem.used == 0);
+
+  p1 = mem_malloc(SIZE1);
+  fail_unless(p1 != NULL);
+  fail_unless(lwip_stats.mem.used >= SIZE1);
+  s1 = lwip_stats.mem.used;
+
+  p2 = mem_malloc(SIZE2);
+  fail_unless(p2 != NULL);
+  fail_unless(lwip_stats.mem.used >= SIZE2 + s1);
+  s2 = lwip_stats.mem.used;
+
+  mem_trim(p1, SIZE1_2);
+
+  mem_free(p2);
+  fail_unless(lwip_stats.mem.used <= s2 - SIZE2);
+
+  mem_free(p1);
+  fail_unless(lwip_stats.mem.used == 0);
+}
+END_TEST
+
+
+/** Create the suite including all tests for this module */
+Suite *
+mem_suite(void)
+{
+  TFun tests[] = {
+    test_mem_one
+  };
+  return create_suite("MEM", tests, sizeof(tests)/sizeof(TFun), mem_setup, mem_teardown);
+}
diff --git a/external/badvpn_dns/lwip/test/unit/core/test_mem.h b/external/badvpn_dns/lwip/test/unit/core/test_mem.h
new file mode 100644
index 0000000..13803ed
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/core/test_mem.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_MEM_H__
+#define __TEST_MEM_H__
+
+#include "../lwip_check.h"
+
+Suite *mem_suite(void);
+
+#endif
diff --git a/external/badvpn_dns/lwip/test/unit/core/test_pbuf.c b/external/badvpn_dns/lwip/test/unit/core/test_pbuf.c
new file mode 100644
index 0000000..2911078
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/core/test_pbuf.c
@@ -0,0 +1,73 @@
+#include "test_pbuf.h"
+
+#include "lwip/pbuf.h"
+#include "lwip/stats.h"
+
+#if !LWIP_STATS || !MEM_STATS ||!MEMP_STATS
+#error "This tests needs MEM- and MEMP-statistics enabled"
+#endif
+#if LWIP_DNS
+#error "This test needs DNS turned off (as it mallocs on init)"
+#endif
+
+/* Setups/teardown functions */
+
+static void
+pbuf_setup(void)
+{
+}
+
+static void
+pbuf_teardown(void)
+{
+}
+
+
+/* Test functions */
+
+/** Call pbuf_copy on a pbuf with zero length */
+START_TEST(test_pbuf_copy_zero_pbuf)
+{
+  struct pbuf *p1, *p2, *p3;
+  err_t err;
+  LWIP_UNUSED_ARG(_i);
+
+  fail_unless(lwip_stats.mem.used == 0);
+  fail_unless(lwip_stats.memp[MEMP_PBUF_POOL].used == 0);
+
+  p1 = pbuf_alloc(PBUF_RAW, 1024, PBUF_RAM);
+  fail_unless(p1 != NULL);
+  fail_unless(p1->ref == 1);
+
+  p2 = pbuf_alloc(PBUF_RAW, 2, PBUF_POOL);
+  fail_unless(p2 != NULL);
+  fail_unless(p2->ref == 1);
+  p2->len = p2->tot_len = 0;
+
+  pbuf_cat(p1, p2);
+  fail_unless(p1->ref == 1);
+  fail_unless(p2->ref == 1);
+
+  p3 = pbuf_alloc(PBUF_RAW, p1->tot_len, PBUF_POOL);
+  err = pbuf_copy(p3, p1);
+  fail_unless(err == ERR_VAL);
+
+  pbuf_free(p1);
+  pbuf_free(p3);
+  fail_unless(lwip_stats.mem.used == 0);
+
+  fail_unless(lwip_stats.mem.used == 0);
+  fail_unless(lwip_stats.memp[MEMP_PBUF_POOL].used == 0);
+}
+END_TEST
+
+
+/** Create the suite including all tests for this module */
+Suite *
+pbuf_suite(void)
+{
+  TFun tests[] = {
+    test_pbuf_copy_zero_pbuf
+  };
+  return create_suite("PBUF", tests, sizeof(tests)/sizeof(TFun), pbuf_setup, pbuf_teardown);
+}
diff --git a/external/badvpn_dns/lwip/test/unit/core/test_pbuf.h b/external/badvpn_dns/lwip/test/unit/core/test_pbuf.h
new file mode 100644
index 0000000..b2715ad
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/core/test_pbuf.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_PBUF_H__
+#define __TEST_PBUF_H__
+
+#include "../lwip_check.h"
+
+Suite *pbuf_suite(void);
+
+#endif
diff --git a/external/badvpn_dns/lwip/test/unit/dhcp/test_dhcp.c b/external/badvpn_dns/lwip/test/unit/dhcp/test_dhcp.c
new file mode 100644
index 0000000..4b40de8
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/dhcp/test_dhcp.c
@@ -0,0 +1,916 @@
+#include "test_dhcp.h"
+
+#include "lwip/netif.h"
+#include "lwip/dhcp.h"
+#include "netif/etharp.h"
+
+struct netif net_test;
+
+static const u8_t broadcast[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+static const u8_t magic_cookie[] = { 0x63, 0x82, 0x53, 0x63 };
+
+static u8_t dhcp_offer[] = {
+    0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, /* To unit */
+    0x00, 0x0F, 0xEE, 0x30, 0xAB, 0x22, /* From Remote host */
+    0x08, 0x00, /* Protocol: IP */
+    0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x80, 0x11, 0x36, 0xcc, 0xc3, 0xaa, 0xbd, 0xab, 0xc3, 0xaa, 0xbd, 0xc8, /* IP header */
+    0x00, 0x43, 0x00, 0x44, 0x01, 0x34, 0x00, 0x00, /* UDP header */
+
+    0x02, /* Type == Boot reply */
+    0x01, 0x06, /* Hw Ethernet, 6 bytes addrlen */
+    0x00, /* 0 hops */
+    0xAA, 0xAA, 0xAA, 0xAA, /* Transaction id, will be overwritten */
+    0x00, 0x00, /* 0 seconds elapsed */
+    0x00, 0x00, /* Flags (unicast) */
+    0x00, 0x00, 0x00, 0x00, /* Client ip */
+    0xc3, 0xaa, 0xbd, 0xc8, /* Your IP */
+    0xc3, 0xaa, 0xbd, 0xab, /* DHCP server ip */
+    0x00, 0x00, 0x00, 0x00, /* relay agent */
+    0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* MAC addr + padding */
+
+    /* Empty server name and boot file name */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+
+    0x63, 0x82, 0x53, 0x63, /* Magic cookie */
+    0x35, 0x01, 0x02, /* Message type: Offer */
+    0x36, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* Server identifier (IP) */
+    0x33, 0x04, 0x00, 0x00, 0x00, 0x78, /* Lease time 2 minutes */
+    0x03, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* Router IP */
+    0x01, 0x04, 0xff, 0xff, 0xff, 0x00, /* Subnet mask */
+    0xff, /* End option */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Padding */
+};
+
+static u8_t dhcp_ack[] = {
+    0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, /* To unit */
+    0x00, 0x0f, 0xEE, 0x30, 0xAB, 0x22, /* From remote host */
+    0x08, 0x00, /* Proto IP */
+    0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x80, 0x11, 0x36, 0xcc, 0xc3, 0xaa, 0xbd, 0xab, 0xc3, 0xaa, 0xbd, 0xc8, /* IP header */
+    0x00, 0x43, 0x00, 0x44, 0x01, 0x34, 0x00, 0x00, /* UDP header */
+    0x02, /* Bootp reply */
+    0x01, 0x06, /* Hw type Eth, len 6 */
+    0x00, /* 0 hops */
+    0xAA, 0xAA, 0xAA, 0xAA,
+    0x00, 0x00, /* 0 seconds elapsed */
+    0x00, 0x00, /* Flags (unicast) */
+    0x00, 0x00, 0x00, 0x00, /* Client IP */
+    0xc3, 0xaa, 0xbd, 0xc8, /* Your IP */
+    0xc3, 0xaa, 0xbd, 0xab, /* DHCP server IP */
+    0x00, 0x00, 0x00, 0x00, /* Relay agent */
+    0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Macaddr + padding */
+
+    /* Empty server name and boot file name */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+
+    0x63, 0x82, 0x53, 0x63, /* Magic cookie */
+    0x35, 0x01, 0x05, /* Dhcp message type ack */
+    0x36, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* DHCP server identifier */
+    0x33, 0x04, 0x00, 0x00, 0x00, 0x78, /* Lease time 2 minutes */
+    0x03, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* Router IP */
+    0x01, 0x04, 0xff, 0xff, 0xff, 0x00, /* Netmask */
+    0xff, /* End marker */
+
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Padding */
+};
+
+static const u8_t arpreply[] = {
+    0x00, 0x23, 0xC1, 0xDE, 0xD0, 0x0D, /* dst mac */
+    0x00, 0x32, 0x44, 0x20, 0x01, 0x02, /* src mac */
+    0x08, 0x06, /* proto arp */
+    0x00, 0x01, /* hw eth */
+    0x08, 0x00, /* proto ip */
+    0x06, /* hw addr len 6 */
+    0x04, /* proto addr len 4 */
+    0x00, 0x02, /* arp reply */
+    0x00, 0x32, 0x44, 0x20, 0x01, 0x02, /* sender mac */
+    0xc3, 0xaa, 0xbd, 0xc8, /* sender ip */
+    0x00, 0x23, 0xC1, 0xDE, 0xD0, 0x0D, /* target mac */
+    0x00, 0x00, 0x00, 0x00, /* target ip */
+};
+
+static int txpacket;
+static enum tcase {
+  TEST_LWIP_DHCP,
+  TEST_LWIP_DHCP_NAK,
+  TEST_LWIP_DHCP_RELAY,
+  TEST_LWIP_DHCP_NAK_NO_ENDMARKER,
+} tcase;
+
+static int debug = 0;
+static void setdebug(int a) {debug = a;}
+
+static int tick = 0;
+static void tick_lwip(void)
+{
+  tick++;
+  if (tick % 5 == 0) {
+    dhcp_fine_tmr();
+  }
+  if (tick % 600 == 0) {
+    dhcp_coarse_tmr();
+  }
+}
+
+static void send_pkt(struct netif *netif, const u8_t *data, u32_t len)
+{
+  struct pbuf *p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
+  struct pbuf *q;
+
+  if (debug) {
+    /* Dump data */
+    u32_t i;
+    printf("RX data (len %d)", p->tot_len);
+    for (i = 0; i < len; i++) {
+      printf(" %02X", data[i]);
+    }
+    printf("\n");
+  }
+
+  fail_unless(p != NULL);
+  for(q = p; q != NULL; q = q->next) {
+    memcpy(q->payload, data, q->len);
+    data += q->len;
+  }
+  netif->input(p, netif);
+}
+
+static err_t lwip_tx_func(struct netif *netif, struct pbuf *p);
+
+static err_t testif_init(struct netif *netif)
+{
+  netif->name[0] = 'c';
+  netif->name[1] = 'h';
+  netif->output = etharp_output;
+  netif->linkoutput = lwip_tx_func;
+  netif->mtu = 1500;
+  netif->hwaddr_len = 6;
+  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
+
+  netif->hwaddr[0] = 0x00;
+  netif->hwaddr[1] = 0x23;
+  netif->hwaddr[2] = 0xC1;
+  netif->hwaddr[3] = 0xDE;
+  netif->hwaddr[4] = 0xD0;
+  netif->hwaddr[5] = 0x0D;
+
+  return ERR_OK;
+}
+
+static void dhcp_setup(void)
+{
+  txpacket = 0;
+}
+
+static void dhcp_teardown(void)
+{
+}
+
+static void check_pkt(struct pbuf *p, u32_t pos, const u8_t *mem, u32_t len)
+{
+  u8_t *data;
+
+  fail_if((pos + len) > p->tot_len);
+  while (pos > p->len && p->next) {
+    pos -= p->len;
+    p = p->next;
+  }
+  fail_if(p == NULL);
+  fail_unless(pos + len <= p->len); /* All data we seek within same pbuf */
+
+  data = p->payload;
+  fail_if(memcmp(&data[pos], mem, len), "data at pos %d, len %d in packet %d did not match", pos, len, txpacket);
+}
+
+static void check_pkt_fuzzy(struct pbuf *p, u32_t startpos, const u8_t *mem, u32_t len)
+{
+  int found;
+  u32_t i;
+  u8_t *data;
+
+  fail_if((startpos + len) > p->tot_len);
+  while (startpos > p->len && p->next) {
+    startpos -= p->len;
+    p = p->next;
+  }
+  fail_if(p == NULL);
+  fail_unless(startpos + len <= p->len); /* All data we seek within same pbuf */
+
+  found = 0;
+  data = p->payload;
+  for (i = startpos; i <= (p->len - len); i++) {
+    if (memcmp(&data[i], mem, len) == 0) {
+      found = 1;
+      break;
+    }
+  }
+  fail_unless(found);
+}
+
+static err_t lwip_tx_func(struct netif *netif, struct pbuf *p)
+{
+  fail_unless(netif == &net_test);
+  txpacket++;
+
+  if (debug) {
+    struct pbuf *pp = p;
+    /* Dump data */
+    printf("TX data (pkt %d, len %d, tick %d)", txpacket, p->tot_len, tick);
+    do {
+      int i;
+      for (i = 0; i < pp->len; i++) {
+        printf(" %02X", ((u8_t *) pp->payload)[i]);
+      }
+      if (pp->next) {
+        pp = pp->next;
+      }
+    } while (pp->next);
+    printf("\n");
+  }
+
+  switch (tcase) {
+  case TEST_LWIP_DHCP:
+    switch (txpacket) {
+    case 1:
+    case 2:
+      {
+        const u8_t ipproto[] = { 0x08, 0x00 };
+        const u8_t bootp_start[] = { 0x01, 0x01, 0x06, 0x00}; /* bootp request, eth, hwaddr len 6, 0 hops */
+        const u8_t ipaddrs[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+        check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+        check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+        check_pkt(p, 12, ipproto, sizeof(ipproto)); /* eth level proto: ip */
+
+        check_pkt(p, 42, bootp_start, sizeof(bootp_start));
+
+        check_pkt(p, 53, ipaddrs, sizeof(ipaddrs));
+
+        check_pkt(p, 70, netif->hwaddr, 6); /* mac addr inside bootp */
+
+        check_pkt(p, 278, magic_cookie, sizeof(magic_cookie));
+
+        /* Check dchp message type, can be at different positions */
+        if (txpacket == 1) {
+          u8_t dhcp_discover_opt[] = { 0x35, 0x01, 0x01 };
+          check_pkt_fuzzy(p, 282, dhcp_discover_opt, sizeof(dhcp_discover_opt));
+        } else if (txpacket == 2) {
+          u8_t dhcp_request_opt[] = { 0x35, 0x01, 0x03 };
+          u8_t requested_ipaddr[] = { 0x32, 0x04, 0xc3, 0xaa, 0xbd, 0xc8 }; /* Ask for offered IP */
+
+          check_pkt_fuzzy(p, 282, dhcp_request_opt, sizeof(dhcp_request_opt));
+          check_pkt_fuzzy(p, 282, requested_ipaddr, sizeof(requested_ipaddr));
+        }
+        break;
+      }
+    case 3:
+    case 4:
+    case 5:
+      {
+        const u8_t arpproto[] = { 0x08, 0x06 };
+
+        check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+        check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+        check_pkt(p, 12, arpproto, sizeof(arpproto)); /* eth level proto: ip */
+        break;
+      }
+    }
+    break;
+
+  case TEST_LWIP_DHCP_NAK:
+    {
+      const u8_t ipproto[] = { 0x08, 0x00 };
+      const u8_t bootp_start[] = { 0x01, 0x01, 0x06, 0x00}; /* bootp request, eth, hwaddr len 6, 0 hops */
+      const u8_t ipaddrs[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+      const u8_t dhcp_nak_opt[] = { 0x35, 0x01, 0x04 };
+      const u8_t requested_ipaddr[] = { 0x32, 0x04, 0xc3, 0xaa, 0xbd, 0xc8 }; /* offered IP */
+
+      fail_unless(txpacket == 4);
+      check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+      check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+      check_pkt(p, 12, ipproto, sizeof(ipproto)); /* eth level proto: ip */
+
+      check_pkt(p, 42, bootp_start, sizeof(bootp_start));
+
+      check_pkt(p, 53, ipaddrs, sizeof(ipaddrs));
+
+      check_pkt(p, 70, netif->hwaddr, 6); /* mac addr inside bootp */
+
+      check_pkt(p, 278, magic_cookie, sizeof(magic_cookie));
+
+      check_pkt_fuzzy(p, 282, dhcp_nak_opt, sizeof(dhcp_nak_opt)); /* NAK the ack */
+
+      check_pkt_fuzzy(p, 282, requested_ipaddr, sizeof(requested_ipaddr));
+      break;
+    }
+
+  case TEST_LWIP_DHCP_RELAY:
+    switch (txpacket) {
+    case 1:
+    case 2:
+      {
+        const u8_t ipproto[] = { 0x08, 0x00 };
+        const u8_t bootp_start[] = { 0x01, 0x01, 0x06, 0x00}; /* bootp request, eth, hwaddr len 6, 0 hops */
+        const u8_t ipaddrs[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+        check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+        check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+        check_pkt(p, 12, ipproto, sizeof(ipproto)); /* eth level proto: ip */
+
+        check_pkt(p, 42, bootp_start, sizeof(bootp_start));
+
+        check_pkt(p, 53, ipaddrs, sizeof(ipaddrs));
+
+        check_pkt(p, 70, netif->hwaddr, 6); /* mac addr inside bootp */
+
+        check_pkt(p, 278, magic_cookie, sizeof(magic_cookie));
+
+        /* Check dchp message type, can be at different positions */
+        if (txpacket == 1) {
+          u8_t dhcp_discover_opt[] = { 0x35, 0x01, 0x01 };
+          check_pkt_fuzzy(p, 282, dhcp_discover_opt, sizeof(dhcp_discover_opt));
+        } else if (txpacket == 2) {
+          u8_t dhcp_request_opt[] = { 0x35, 0x01, 0x03 };
+          u8_t requested_ipaddr[] = { 0x32, 0x04, 0x4f, 0x8a, 0x33, 0x05 }; /* Ask for offered IP */
+
+          check_pkt_fuzzy(p, 282, dhcp_request_opt, sizeof(dhcp_request_opt));
+          check_pkt_fuzzy(p, 282, requested_ipaddr, sizeof(requested_ipaddr));
+        }
+        break;
+      }
+    case 3:
+    case 4:
+    case 5:
+    case 6:
+      {
+        const u8_t arpproto[] = { 0x08, 0x06 };
+
+        check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+        check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+        check_pkt(p, 12, arpproto, sizeof(arpproto)); /* eth level proto: ip */
+        break;
+      }
+    case 7:
+      {
+        const u8_t fake_arp[6] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xab };
+        const u8_t ipproto[] = { 0x08, 0x00 };
+        const u8_t bootp_start[] = { 0x01, 0x01, 0x06, 0x00}; /* bootp request, eth, hwaddr len 6, 0 hops */
+        const u8_t ipaddrs[] = { 0x00, 0x4f, 0x8a, 0x33, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+        const u8_t dhcp_request_opt[] = { 0x35, 0x01, 0x03 };
+
+        check_pkt(p, 0, fake_arp, 6); /* eth level dest: broadcast */
+        check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+        check_pkt(p, 12, ipproto, sizeof(ipproto)); /* eth level proto: ip */
+
+        check_pkt(p, 42, bootp_start, sizeof(bootp_start));
+
+        check_pkt(p, 53, ipaddrs, sizeof(ipaddrs));
+
+        check_pkt(p, 70, netif->hwaddr, 6); /* mac addr inside bootp */
+
+        check_pkt(p, 278, magic_cookie, sizeof(magic_cookie));
+
+        /* Check dchp message type, can be at different positions */
+        check_pkt_fuzzy(p, 282, dhcp_request_opt, sizeof(dhcp_request_opt));
+        break;
+      }
+    }
+    break;
+
+  default:
+    break;
+  }
+
+  return ERR_OK;
+}
+
+/*
+ * Test basic happy flow DHCP session.
+ * Validate that xid is checked.
+ */
+START_TEST(test_dhcp)
+{
+  struct ip_addr addr;
+  struct ip_addr netmask;
+  struct ip_addr gw;
+  int i;
+  u32_t xid;
+  LWIP_UNUSED_ARG(_i);
+
+  tcase = TEST_LWIP_DHCP;
+  setdebug(0);
+
+  IP4_ADDR(&addr, 0, 0, 0, 0);
+  IP4_ADDR(&netmask, 0, 0, 0, 0);
+  IP4_ADDR(&gw, 0, 0, 0, 0);
+
+  netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+
+  dhcp_start(&net_test);
+
+  fail_unless(txpacket == 1); /* DHCP discover sent */
+  xid = net_test.dhcp->xid; /* Write bad xid, not using htonl! */
+  memcpy(&dhcp_offer[46], &xid, 4);
+  send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+  /* Interface down */
+  fail_if(netif_is_up(&net_test));
+
+  /* IP addresses should be zero */
+  fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(struct ip_addr)));
+  fail_if(memcmp(&netmask, &net_test.netmask, sizeof(struct ip_addr)));
+  fail_if(memcmp(&gw, &net_test.gw, sizeof(struct ip_addr)));
+
+  fail_unless(txpacket == 1, "TX %d packets, expected 1", txpacket); /* Nothing more sent */
+  xid = htonl(net_test.dhcp->xid);
+  memcpy(&dhcp_offer[46], &xid, 4); /* insert correct transaction id */
+  send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+  fail_unless(txpacket == 2, "TX %d packets, expected 2", txpacket); /* DHCP request sent */
+  xid = net_test.dhcp->xid; /* Write bad xid, not using htonl! */
+  memcpy(&dhcp_ack[46], &xid, 4);
+  send_pkt(&net_test, dhcp_ack, sizeof(dhcp_ack));
+
+  fail_unless(txpacket == 2, "TX %d packets, still expected 2", txpacket); /* No more sent */
+  xid = htonl(net_test.dhcp->xid); /* xid updated */
+  memcpy(&dhcp_ack[46], &xid, 4); /* insert transaction id */
+  send_pkt(&net_test, dhcp_ack, sizeof(dhcp_ack));
+
+  for (i = 0; i < 20; i++) {
+    tick_lwip();
+  }
+  fail_unless(txpacket == 4, "TX %d packets, expected 4", txpacket); /* ARP requests sent */
+
+  /* Interface up */
+  fail_unless(netif_is_up(&net_test));
+
+  /* Now it should have taken the IP */
+  IP4_ADDR(&addr, 195, 170, 189, 200);
+  IP4_ADDR(&netmask, 255, 255, 255, 0);
+  IP4_ADDR(&gw, 195, 170, 189, 171);
+  fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(struct ip_addr)));
+  fail_if(memcmp(&netmask, &net_test.netmask, sizeof(struct ip_addr)));
+  fail_if(memcmp(&gw, &net_test.gw, sizeof(struct ip_addr)));
+
+  netif_remove(&net_test);
+}
+END_TEST
+
+/*
+ * Test that IP address is not taken and NAK is sent if someone
+ * replies to ARP requests for the offered address.
+ */
+START_TEST(test_dhcp_nak)
+{
+  struct ip_addr addr;
+  struct ip_addr netmask;
+  struct ip_addr gw;
+  u32_t xid;
+  LWIP_UNUSED_ARG(_i);
+
+  tcase = TEST_LWIP_DHCP;
+  setdebug(0);
+
+  IP4_ADDR(&addr, 0, 0, 0, 0);
+  IP4_ADDR(&netmask, 0, 0, 0, 0);
+  IP4_ADDR(&gw, 0, 0, 0, 0);
+
+  netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+
+  dhcp_start(&net_test);
+
+  fail_unless(txpacket == 1); /* DHCP discover sent */
+  xid = net_test.dhcp->xid; /* Write bad xid, not using htonl! */
+  memcpy(&dhcp_offer[46], &xid, 4);
+  send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+  /* Interface down */
+  fail_if(netif_is_up(&net_test));
+
+  /* IP addresses should be zero */
+  fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(struct ip_addr)));
+  fail_if(memcmp(&netmask, &net_test.netmask, sizeof(struct ip_addr)));
+  fail_if(memcmp(&gw, &net_test.gw, sizeof(struct ip_addr)));
+
+  fail_unless(txpacket == 1); /* Nothing more sent */
+  xid = htonl(net_test.dhcp->xid);
+  memcpy(&dhcp_offer[46], &xid, 4); /* insert correct transaction id */
+  send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+  fail_unless(txpacket == 2); /* DHCP request sent */
+  xid = net_test.dhcp->xid; /* Write bad xid, not using htonl! */
+  memcpy(&dhcp_ack[46], &xid, 4);
+  send_pkt(&net_test, dhcp_ack, sizeof(dhcp_ack));
+
+  fail_unless(txpacket == 2); /* No more sent */
+  xid = htonl(net_test.dhcp->xid); /* xid updated */
+  memcpy(&dhcp_ack[46], &xid, 4); /* insert transaction id */
+  send_pkt(&net_test, dhcp_ack, sizeof(dhcp_ack));
+
+  fail_unless(txpacket == 3); /* ARP request sent */
+
+  tcase = TEST_LWIP_DHCP_NAK; /* Switch testcase */
+
+  /* Send arp reply, mark offered IP as taken */
+  send_pkt(&net_test, arpreply, sizeof(arpreply));
+
+  fail_unless(txpacket == 4); /* DHCP nak sent */
+
+  netif_remove(&net_test);
+}
+END_TEST
+
+/*
+ * Test case based on captured data where
+ * replies are sent from a different IP than the
+ * one the client unicasted to.
+ */
+START_TEST(test_dhcp_relayed)
+{
+  const u8_t relay_offer[] = {
+  0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d,
+  0x00, 0x22, 0x93, 0x5a, 0xf7, 0x60,
+  0x08, 0x00, 0x45, 0x00,
+  0x01, 0x38, 0xfd, 0x53, 0x00, 0x00, 0x40, 0x11,
+  0x78, 0x46, 0x4f, 0x8a, 0x32, 0x02, 0x4f, 0x8a,
+  0x33, 0x05, 0x00, 0x43, 0x00, 0x44, 0x01, 0x24,
+  0x00, 0x00, 0x02, 0x01, 0x06, 0x00, 0x51, 0x35,
+  0xb6, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x4f, 0x8a, 0x33, 0x05, 0x00, 0x00,
+  0x00, 0x00, 0x0a, 0xb5, 0x04, 0x01, 0x00, 0x23,
+  0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82,
+  0x53, 0x63, 0x01, 0x04, 0xff, 0xff, 0xfe, 0x00,
+  0x03, 0x04, 0x4f, 0x8a, 0x32, 0x01, 0x06, 0x08,
+  0x4f, 0x8a, 0x00, 0xb4, 0x55, 0x08, 0x1f, 0xd1,
+  0x1c, 0x04, 0x4f, 0x8a, 0x33, 0xff, 0x33, 0x04,
+  0x00, 0x00, 0x54, 0x49, 0x35, 0x01, 0x02, 0x36,
+  0x04, 0x0a, 0xb5, 0x04, 0x01, 0xff
+  };
+
+  const u8_t relay_ack1[] = {
+  0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x22,
+  0x93, 0x5a, 0xf7, 0x60, 0x08, 0x00, 0x45, 0x00,
+  0x01, 0x38, 0xfd, 0x55, 0x00, 0x00, 0x40, 0x11,
+  0x78, 0x44, 0x4f, 0x8a, 0x32, 0x02, 0x4f, 0x8a,
+  0x33, 0x05, 0x00, 0x43, 0x00, 0x44, 0x01, 0x24,
+  0x00, 0x00, 0x02, 0x01, 0x06, 0x00, 0x51, 0x35,
+  0xb6, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x4f, 0x8a, 0x33, 0x05, 0x00, 0x00,
+  0x00, 0x00, 0x0a, 0xb5, 0x04, 0x01, 0x00, 0x23,
+  0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82,
+  0x53, 0x63, 0x01, 0x04, 0xff, 0xff, 0xfe, 0x00,
+  0x03, 0x04, 0x4f, 0x8a, 0x32, 0x01, 0x06, 0x08,
+  0x4f, 0x8a, 0x00, 0xb4, 0x55, 0x08, 0x1f, 0xd1,
+  0x1c, 0x04, 0x4f, 0x8a, 0x33, 0xff, 0x33, 0x04,
+  0x00, 0x00, 0x54, 0x49, 0x35, 0x01, 0x05, 0x36,
+  0x04, 0x0a, 0xb5, 0x04, 0x01, 0xff
+  };
+
+  const u8_t relay_ack2[] = {
+  0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d,
+  0x00, 0x22, 0x93, 0x5a, 0xf7, 0x60,
+  0x08, 0x00, 0x45, 0x00,
+  0x01, 0x38, 0xfa, 0x18, 0x00, 0x00, 0x40, 0x11,
+  0x7b, 0x81, 0x4f, 0x8a, 0x32, 0x02, 0x4f, 0x8a,
+  0x33, 0x05, 0x00, 0x43, 0x00, 0x44, 0x01, 0x24,
+  0x00, 0x00, 0x02, 0x01, 0x06, 0x00, 0x49, 0x8b,
+  0x6e, 0xab, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x8a,
+  0x33, 0x05, 0x4f, 0x8a, 0x33, 0x05, 0x00, 0x00,
+  0x00, 0x00, 0x0a, 0xb5, 0x04, 0x01, 0x00, 0x23,
+  0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82,
+  0x53, 0x63, 0x01, 0x04, 0xff, 0xff, 0xfe, 0x00,
+  0x03, 0x04, 0x4f, 0x8a, 0x32, 0x01, 0x06, 0x08,
+  0x4f, 0x8a, 0x00, 0xb4, 0x55, 0x08, 0x1f, 0xd1,
+  0x1c, 0x04, 0x4f, 0x8a, 0x33, 0xff, 0x33, 0x04,
+  0x00, 0x00, 0x54, 0x60, 0x35, 0x01, 0x05, 0x36,
+  0x04, 0x0a, 0xb5, 0x04, 0x01, 0xff };
+
+  const u8_t arp_resp[] = {
+  0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, /* DEST */
+  0x00, 0x22, 0x93, 0x5a, 0xf7, 0x60, /* SRC */
+  0x08, 0x06, /* Type: ARP */
+  0x00, 0x01, /* HW: Ethernet */
+  0x08, 0x00, /* PROTO: IP */
+  0x06, /* HW size */
+  0x04, /* PROTO size */
+  0x00, 0x02, /* OPCODE: Reply */
+
+  0x12, 0x34, 0x56, 0x78, 0x9a, 0xab, /* Target MAC */
+  0x4f, 0x8a, 0x32, 0x01, /* Target IP */
+
+  0x00, 0x23, 0xc1, 0x00, 0x06, 0x50, /* src mac */
+  0x4f, 0x8a, 0x33, 0x05, /* src ip */
+
+  /* Padding follows.. */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00 };
+
+  struct ip_addr addr;
+  struct ip_addr netmask;
+  struct ip_addr gw;
+  int i;
+  u32_t xid;
+  LWIP_UNUSED_ARG(_i);
+
+  tcase = TEST_LWIP_DHCP_RELAY;
+  setdebug(0);
+
+  IP4_ADDR(&addr, 0, 0, 0, 0);
+  IP4_ADDR(&netmask, 0, 0, 0, 0);
+  IP4_ADDR(&gw, 0, 0, 0, 0);
+
+  netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+
+  dhcp_start(&net_test);
+
+  fail_unless(txpacket == 1); /* DHCP discover sent */
+
+  /* Interface down */
+  fail_if(netif_is_up(&net_test));
+
+  /* IP addresses should be zero */
+  fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(struct ip_addr)));
+  fail_if(memcmp(&netmask, &net_test.netmask, sizeof(struct ip_addr)));
+  fail_if(memcmp(&gw, &net_test.gw, sizeof(struct ip_addr)));
+
+  fail_unless(txpacket == 1); /* Nothing more sent */
+  xid = htonl(net_test.dhcp->xid);
+  memcpy(&relay_offer[46], &xid, 4); /* insert correct transaction id */
+  send_pkt(&net_test, relay_offer, sizeof(relay_offer));
+
+  /* request sent? */
+  fail_unless(txpacket == 2, "txpkt = %d, should be 2", txpacket);
+  xid = htonl(net_test.dhcp->xid); /* xid updated */
+  memcpy(&relay_ack1[46], &xid, 4); /* insert transaction id */
+  send_pkt(&net_test, relay_ack1, sizeof(relay_ack1));
+
+  for (i = 0; i < 25; i++) {
+    tick_lwip();
+  }
+  fail_unless(txpacket == 4, "txpkt should be 5, is %d", txpacket); /* ARP requests sent */
+
+  /* Interface up */
+  fail_unless(netif_is_up(&net_test));
+
+  /* Now it should have taken the IP */
+  IP4_ADDR(&addr, 79, 138, 51, 5);
+  IP4_ADDR(&netmask, 255, 255, 254, 0);
+  IP4_ADDR(&gw, 79, 138, 50, 1);
+  fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(struct ip_addr)));
+  fail_if(memcmp(&netmask, &net_test.netmask, sizeof(struct ip_addr)));
+  fail_if(memcmp(&gw, &net_test.gw, sizeof(struct ip_addr)));
+
+  fail_unless(txpacket == 4, "txpacket = %d", txpacket);
+
+  for (i = 0; i < 108000 - 25; i++) {
+    tick_lwip();
+  }
+
+  fail_unless(netif_is_up(&net_test));
+  fail_unless(txpacket == 6, "txpacket = %d", txpacket);
+
+  /* We need to send arp response here.. */
+
+  send_pkt(&net_test, arp_resp, sizeof(arp_resp));
+
+  fail_unless(txpacket == 7, "txpacket = %d", txpacket);
+  fail_unless(netif_is_up(&net_test));
+
+  xid = htonl(net_test.dhcp->xid); /* xid updated */
+  memcpy(&relay_ack2[46], &xid, 4); /* insert transaction id */
+  send_pkt(&net_test, relay_ack2, sizeof(relay_ack2));
+
+  for (i = 0; i < 100000; i++) {
+    tick_lwip();
+  }
+
+  fail_unless(txpacket == 7, "txpacket = %d", txpacket);
+
+  netif_remove(&net_test);
+
+}
+END_TEST
+
+START_TEST(test_dhcp_nak_no_endmarker)
+{
+  struct ip_addr addr;
+  struct ip_addr netmask;
+  struct ip_addr gw;
+
+  u8_t dhcp_nack_no_endmarker[] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x54, 0x75,
+    0xd0, 0x26, 0xd0, 0x0d, 0x08, 0x00, 0x45, 0x00,
+    0x01, 0x15, 0x38, 0x86, 0x00, 0x00, 0xff, 0x11,
+    0xc0, 0xa8, 0xc0, 0xa8, 0x01, 0x01, 0xff, 0xff,
+    0xff, 0xff, 0x00, 0x43, 0x00, 0x44, 0x01, 0x01,
+    0x00, 0x00, 0x02, 0x01, 0x06, 0x00, 0x7a, 0xcb,
+    0xba, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23,
+    0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82,
+    0x53, 0x63, 0x35, 0x01, 0x06, 0x36, 0x04, 0xc0,
+    0xa8, 0x01, 0x01, 0x31, 0xef, 0xad, 0x72, 0x31,
+    0x43, 0x4e, 0x44, 0x30, 0x32, 0x35, 0x30, 0x43,
+    0x52, 0x47, 0x44, 0x38, 0x35, 0x36, 0x3c, 0x08,
+    0x4d, 0x53, 0x46, 0x54, 0x20, 0x35, 0x2e, 0x30,
+    0x37, 0x0d, 0x01, 0x0f, 0x03, 0x06, 0x2c, 0x2e,
+    0x2f, 0x1f, 0x21, 0x79, 0xf9, 0x2b, 0xfc, 0xff,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2, 0x71,
+    0xf3, 0x5b, 0xe2, 0x71, 0x2e, 0x01, 0x08, 0x03,
+    0x04, 0xc0, 0xa8, 0x01, 0x01, 0xff, 0xeb, 0x1e,
+    0x44, 0xec, 0xeb, 0x1e, 0x30, 0x37, 0x0c, 0x01,
+    0x0f, 0x03, 0x06, 0x2c, 0x2e, 0x2f, 0x1f, 0x21,
+    0x79, 0xf9, 0x2b, 0xff, 0x25, 0xc0, 0x09, 0xd6,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+  };
+  u32_t xid;
+  LWIP_UNUSED_ARG(_i);
+
+  tcase = TEST_LWIP_DHCP_NAK_NO_ENDMARKER;
+  setdebug(0);
+
+  IP4_ADDR(&addr, 0, 0, 0, 0);
+  IP4_ADDR(&netmask, 0, 0, 0, 0);
+  IP4_ADDR(&gw, 0, 0, 0, 0);
+
+  netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+
+  dhcp_start(&net_test);
+
+  fail_unless(txpacket == 1); /* DHCP discover sent */
+  xid = net_test.dhcp->xid; /* Write bad xid, not using htonl! */
+  memcpy(&dhcp_offer[46], &xid, 4);
+  send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+  /* Interface down */
+  fail_if(netif_is_up(&net_test));
+
+  /* IP addresses should be zero */
+  fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(struct ip_addr)));
+  fail_if(memcmp(&netmask, &net_test.netmask, sizeof(struct ip_addr)));
+  fail_if(memcmp(&gw, &net_test.gw, sizeof(struct ip_addr)));
+
+  fail_unless(txpacket == 1); /* Nothing more sent */
+  xid = htonl(net_test.dhcp->xid);
+  memcpy(&dhcp_offer[46], &xid, 4); /* insert correct transaction id */
+  send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+  
+  fail_unless(net_test.dhcp->state == DHCP_REQUESTING);
+
+  fail_unless(txpacket == 2); /* No more sent */
+  xid = htonl(net_test.dhcp->xid); /* xid updated */
+  memcpy(&dhcp_nack_no_endmarker[46], &xid, 4); /* insert transaction id */
+  send_pkt(&net_test, dhcp_nack_no_endmarker, sizeof(dhcp_nack_no_endmarker));
+
+  /* NAK should put us in another state for a while, no other way detecting it */
+  fail_unless(net_test.dhcp->state != DHCP_REQUESTING);
+
+  netif_remove(&net_test);
+}
+END_TEST
+
+
+/** Create the suite including all tests for this module */
+Suite *
+dhcp_suite(void)
+{
+  TFun tests[] = {
+    test_dhcp,
+    test_dhcp_nak,
+    test_dhcp_relayed,
+    test_dhcp_nak_no_endmarker
+  };
+  return create_suite("DHCP", tests, sizeof(tests)/sizeof(TFun), dhcp_setup, dhcp_teardown);
+}
diff --git a/external/badvpn_dns/lwip/test/unit/dhcp/test_dhcp.h b/external/badvpn_dns/lwip/test/unit/dhcp/test_dhcp.h
new file mode 100644
index 0000000..aff44b7
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/dhcp/test_dhcp.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_DHCP_H__
+#define __TEST_DHCP_H__
+
+#include "../lwip_check.h"
+
+Suite* dhcp_suite(void);
+
+#endif
diff --git a/external/badvpn_dns/lwip/test/unit/etharp/test_etharp.c b/external/badvpn_dns/lwip/test/unit/etharp/test_etharp.c
new file mode 100644
index 0000000..cbbc950
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/etharp/test_etharp.c
@@ -0,0 +1,262 @@
+#include "test_etharp.h"
+
+#include "lwip/udp.h"
+#include "netif/etharp.h"
+#include "lwip/stats.h"
+
+#if !LWIP_STATS || !UDP_STATS || !MEMP_STATS || !ETHARP_STATS
+#error "This tests needs UDP-, MEMP- and ETHARP-statistics enabled"
+#endif
+#if !ETHARP_SUPPORT_STATIC_ENTRIES
+#error "This test needs ETHARP_SUPPORT_STATIC_ENTRIES enabled"
+#endif
+
+static struct netif test_netif;
+static ip_addr_t test_ipaddr, test_netmask, test_gw;
+struct eth_addr test_ethaddr = {1,1,1,1,1,1};
+struct eth_addr test_ethaddr2 = {1,1,1,1,1,2};
+struct eth_addr test_ethaddr3 = {1,1,1,1,1,3};
+struct eth_addr test_ethaddr4 = {1,1,1,1,1,4};
+static int linkoutput_ctr;
+
+/* Helper functions */
+static void
+etharp_remove_all(void)
+{
+  int i;
+  /* call etharp_tmr often enough to have all entries cleaned */
+  for(i = 0; i < 0xff; i++) {
+    etharp_tmr();
+  }
+}
+
+static err_t
+default_netif_linkoutput(struct netif *netif, struct pbuf *p)
+{
+  fail_unless(netif == &test_netif);
+  fail_unless(p != NULL);
+  linkoutput_ctr++;
+  return ERR_OK;
+}
+
+static err_t
+default_netif_init(struct netif *netif)
+{
+  fail_unless(netif != NULL);
+  netif->linkoutput = default_netif_linkoutput;
+  netif->output = etharp_output;
+  netif->mtu = 1500;
+  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
+  netif->hwaddr_len = ETHARP_HWADDR_LEN;
+  return ERR_OK;
+}
+
+static void
+default_netif_add(void)
+{
+  IP4_ADDR(&test_gw, 192,168,0,1);
+  IP4_ADDR(&test_ipaddr, 192,168,0,1);
+  IP4_ADDR(&test_netmask, 255,255,0,0);
+
+  fail_unless(netif_default == NULL);
+  netif_set_default(netif_add(&test_netif, &test_ipaddr, &test_netmask,
+                              &test_gw, NULL, default_netif_init, NULL));
+  netif_set_up(&test_netif);
+}
+
+static void
+default_netif_remove(void)
+{
+  fail_unless(netif_default == &test_netif);
+  netif_remove(&test_netif);
+}
+
+static void
+create_arp_response(ip_addr_t *adr)
+{
+  int k;
+  struct eth_hdr *ethhdr;
+  struct etharp_hdr *etharphdr;
+  struct pbuf *p = pbuf_alloc(PBUF_RAW, sizeof(struct eth_hdr) + sizeof(struct etharp_hdr), PBUF_RAM);
+  if(p == NULL) {
+    FAIL_RET();
+  }
+  ethhdr = (struct eth_hdr*)p->payload;
+  etharphdr = (struct etharp_hdr*)(ethhdr + 1);
+
+  ethhdr->dest = test_ethaddr;
+  ethhdr->src = test_ethaddr2;
+  ethhdr->type = htons(ETHTYPE_ARP);
+
+  etharphdr->hwtype = htons(/*HWTYPE_ETHERNET*/ 1);
+  etharphdr->proto = htons(ETHTYPE_IP);
+  etharphdr->hwlen = ETHARP_HWADDR_LEN;
+  etharphdr->protolen = sizeof(ip_addr_t);
+  etharphdr->opcode = htons(ARP_REPLY);
+
+  SMEMCPY(&etharphdr->sipaddr, adr, sizeof(ip_addr_t));
+  SMEMCPY(&etharphdr->dipaddr, &test_ipaddr, sizeof(ip_addr_t));
+
+  k = 6;
+  while(k > 0) {
+    k--;
+    /* Write the ARP MAC-Addresses */
+    etharphdr->shwaddr.addr[k] = test_ethaddr2.addr[k];
+    etharphdr->dhwaddr.addr[k] = test_ethaddr.addr[k];
+    /* Write the Ethernet MAC-Addresses */
+    ethhdr->dest.addr[k] = test_ethaddr.addr[k];
+    ethhdr->src.addr[k]  = test_ethaddr2.addr[k];
+  }
+
+  ethernet_input(p, &test_netif);
+}
+
+/* Setups/teardown functions */
+
+static void
+etharp_setup(void)
+{
+  etharp_remove_all();
+  default_netif_add();
+}
+
+static void
+etharp_teardown(void)
+{
+  etharp_remove_all();
+  default_netif_remove();
+}
+
+
+/* Test functions */
+
+START_TEST(test_etharp_table)
+{
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+  err_t err;
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+  s8_t idx;
+  ip_addr_t *unused_ipaddr;
+  struct eth_addr *unused_ethaddr;
+  struct udp_pcb* pcb;
+  LWIP_UNUSED_ARG(_i);
+
+  if (netif_default != &test_netif) {
+    fail("This test needs a default netif");
+  }
+
+  linkoutput_ctr = 0;
+
+  pcb = udp_new();
+  fail_unless(pcb != NULL);
+  if (pcb != NULL) {
+    ip_addr_t adrs[ARP_TABLE_SIZE + 2];
+    int i;
+    for(i = 0; i < ARP_TABLE_SIZE + 2; i++) {
+      IP4_ADDR(&adrs[i], 192,168,0,i+2);
+    }
+    /* fill ARP-table with dynamic entries */
+    for(i = 0; i < ARP_TABLE_SIZE; i++) {
+      struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, 10, PBUF_RAM);
+      fail_unless(p != NULL);
+      if (p != NULL) {
+        err_t err = udp_sendto(pcb, p, &adrs[i], 123);
+        fail_unless(err == ERR_OK);
+        /* etharp request sent? */
+        fail_unless(linkoutput_ctr == (2*i) + 1);
+        pbuf_free(p);
+
+        /* create an ARP response */
+        create_arp_response(&adrs[i]);
+        /* queued UDP packet sent? */
+        fail_unless(linkoutput_ctr == (2*i) + 2);
+
+        idx = etharp_find_addr(NULL, &adrs[i], &unused_ethaddr, &unused_ipaddr);
+        fail_unless(idx == i);
+        etharp_tmr();
+      }
+    }
+    linkoutput_ctr = 0;
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+    /* create one static entry */
+    err = etharp_add_static_entry(&adrs[ARP_TABLE_SIZE], &test_ethaddr3);
+    fail_unless(err == ERR_OK);
+    idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+    fail_unless(idx == 0);
+    fail_unless(linkoutput_ctr == 0);
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+    linkoutput_ctr = 0;
+    /* fill ARP-table with dynamic entries */
+    for(i = 0; i < ARP_TABLE_SIZE; i++) {
+      struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, 10, PBUF_RAM);
+      fail_unless(p != NULL);
+      if (p != NULL) {
+        err_t err = udp_sendto(pcb, p, &adrs[i], 123);
+        fail_unless(err == ERR_OK);
+        /* etharp request sent? */
+        fail_unless(linkoutput_ctr == (2*i) + 1);
+        pbuf_free(p);
+
+        /* create an ARP response */
+        create_arp_response(&adrs[i]);
+        /* queued UDP packet sent? */
+        fail_unless(linkoutput_ctr == (2*i) + 2);
+
+        idx = etharp_find_addr(NULL, &adrs[i], &unused_ethaddr, &unused_ipaddr);
+        if (i < ARP_TABLE_SIZE - 1) {
+          fail_unless(idx == i+1);
+        } else {
+          /* the last entry must not overwrite the static entry! */
+          fail_unless(idx == 1);
+        }
+        etharp_tmr();
+      }
+    }
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+    /* create a second static entry */
+    err = etharp_add_static_entry(&adrs[ARP_TABLE_SIZE+1], &test_ethaddr4);
+    fail_unless(err == ERR_OK);
+    idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+    fail_unless(idx == 0);
+    idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE+1], &unused_ethaddr, &unused_ipaddr);
+    fail_unless(idx == 2);
+    /* and remove it again */
+    err = etharp_remove_static_entry(&adrs[ARP_TABLE_SIZE+1]);
+    fail_unless(err == ERR_OK);
+    idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+    fail_unless(idx == 0);
+    idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE+1], &unused_ethaddr, &unused_ipaddr);
+    fail_unless(idx == -1);
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+    /* check that static entries don't time out */
+    etharp_remove_all();
+    idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+    fail_unless(idx == 0);
+
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+    /* remove the first static entry */
+    err = etharp_remove_static_entry(&adrs[ARP_TABLE_SIZE]);
+    fail_unless(err == ERR_OK);
+    idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+    fail_unless(idx == -1);
+    idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE+1], &unused_ethaddr, &unused_ipaddr);
+    fail_unless(idx == -1);
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+    udp_remove(pcb);
+  }
+}
+END_TEST
+
+
+/** Create the suite including all tests for this module */
+Suite *
+etharp_suite(void)
+{
+  TFun tests[] = {
+    test_etharp_table
+  };
+  return create_suite("ETHARP", tests, sizeof(tests)/sizeof(TFun), etharp_setup, etharp_teardown);
+}
diff --git a/external/badvpn_dns/lwip/test/unit/etharp/test_etharp.h b/external/badvpn_dns/lwip/test/unit/etharp/test_etharp.h
new file mode 100644
index 0000000..96e00c3
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/etharp/test_etharp.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_ETHARP_H__
+#define __TEST_ETHARP_H__
+
+#include "../lwip_check.h"
+
+Suite* etharp_suite(void);
+
+#endif
diff --git a/external/badvpn_dns/lwip/test/unit/lwip_check.h b/external/badvpn_dns/lwip/test/unit/lwip_check.h
new file mode 100644
index 0000000..e27f55a
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/lwip_check.h
@@ -0,0 +1,37 @@
+#ifndef __LWIP_CHECK_H__
+#define __LWIP_CHECK_H__
+
+/* Common header file for lwIP unit tests using the check framework */
+
+#include <config.h>
+#include <check.h>
+#include <stdlib.h>
+
+#define FAIL_RET() do { fail(); return; } while(0)
+#define EXPECT(x) fail_unless(x)
+#define EXPECT_RET(x) do { fail_unless(x); if(!(x)) { return; }} while(0)
+#define EXPECT_RETX(x, y) do { fail_unless(x); if(!(x)) { return y; }} while(0)
+#define EXPECT_RETNULL(x) EXPECT_RETX(x, NULL)
+
+/** typedef for a function returning a test suite */
+typedef Suite* (suite_getter_fn)(void);
+
+/** Create a test suite */
+static Suite* create_suite(const char* name, TFun *tests, size_t num_tests, SFun setup, SFun teardown)
+{
+  size_t i;
+  Suite *s = suite_create(name);
+
+  for(i = 0; i < num_tests; i++) {
+    /* Core test case */
+    TCase *tc_core = tcase_create("Core");
+    if ((setup != NULL) || (teardown != NULL)) {
+      tcase_add_checked_fixture(tc_core, setup, teardown);
+    }
+    tcase_add_test(tc_core, tests[i]);
+    suite_add_tcase(s, tc_core);
+  }
+  return s;
+}
+
+#endif /* __LWIP_CHECK_H__ */
diff --git a/external/badvpn_dns/lwip/test/unit/lwip_unittests.c b/external/badvpn_dns/lwip/test/unit/lwip_unittests.c
new file mode 100644
index 0000000..41d1f36
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/lwip_unittests.c
@@ -0,0 +1,49 @@
+#include "lwip_check.h"
+
+#include "udp/test_udp.h"
+#include "tcp/test_tcp.h"
+#include "tcp/test_tcp_oos.h"
+#include "core/test_mem.h"
+#include "core/test_pbuf.h"
+#include "etharp/test_etharp.h"
+#include "dhcp/test_dhcp.h"
+
+#include "lwip/init.h"
+
+
+int main()
+{
+  int number_failed;
+  SRunner *sr;
+  size_t i;
+  suite_getter_fn* suites[] = {
+    udp_suite,
+    tcp_suite,
+    tcp_oos_suite,
+    mem_suite,
+    pbuf_suite,
+    etharp_suite,
+    dhcp_suite
+  };
+  size_t num = sizeof(suites)/sizeof(void*);
+  LWIP_ASSERT("No suites defined", num > 0);
+
+  lwip_init();
+
+  sr = srunner_create((suites[0])());
+  for(i = 1; i < num; i++) {
+    srunner_add_suite(sr, ((suite_getter_fn*)suites[i])());
+  }
+
+#ifdef LWIP_UNITTESTS_NOFORK
+  srunner_set_fork_status(sr, CK_NOFORK);
+#endif
+#ifdef LWIP_UNITTESTS_FORK
+  srunner_set_fork_status(sr, CK_FORK);
+#endif
+
+  srunner_run_all(sr, CK_NORMAL);
+  number_failed = srunner_ntests_failed(sr);
+  srunner_free(sr);
+  return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/external/badvpn_dns/lwip/test/unit/lwipopts.h b/external/badvpn_dns/lwip/test/unit/lwipopts.h
new file mode 100644
index 0000000..0059515
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/lwipopts.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
+ * All rights reserved. 
+ * 
+ * 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. 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.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ * 
+ * Author: Simon Goldschmidt
+ *
+ */
+#ifndef __LWIPOPTS_H__
+#define __LWIPOPTS_H__
+
+/* Prevent having to link sys_arch.c (we don't test the API layers in unit tests) */
+#define NO_SYS                          1
+#define LWIP_NETCONN                    0
+#define LWIP_SOCKET                     0
+
+/* Enable DHCP to test it, disable UDP checksum to easier inject packets */
+#define LWIP_DHCP                       1
+
+/* Minimal changes to opt.h required for tcp unit tests: */
+#define MEM_SIZE                        16000
+#define TCP_SND_QUEUELEN                40
+#define MEMP_NUM_TCP_SEG                TCP_SND_QUEUELEN
+#define TCP_SND_BUF                     (12 * TCP_MSS)
+#define TCP_WND                         (10 * TCP_MSS)
+
+/* Minimal changes to opt.h required for etharp unit tests: */
+#define ETHARP_SUPPORT_STATIC_ENTRIES   1
+
+#endif /* __LWIPOPTS_H__ */
diff --git a/external/badvpn_dns/lwip/test/unit/tcp/tcp_helper.c b/external/badvpn_dns/lwip/test/unit/tcp/tcp_helper.c
new file mode 100644
index 0000000..ff503db
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/tcp/tcp_helper.c
@@ -0,0 +1,303 @@
+#include "tcp_helper.h"
+
+#include "lwip/tcp_impl.h"
+#include "lwip/stats.h"
+#include "lwip/pbuf.h"
+#include "lwip/inet_chksum.h"
+
+#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS
+#error "This tests needs TCP- and MEMP-statistics enabled"
+#endif
+
+/** Remove all pcbs on the given list. */
+static void
+tcp_remove(struct tcp_pcb* pcb_list)
+{
+  struct tcp_pcb *pcb = pcb_list;
+  struct tcp_pcb *pcb2;
+
+  while(pcb != NULL) {
+    pcb2 = pcb;
+    pcb = pcb->next;
+    tcp_abort(pcb2);
+  }
+}
+
+/** Remove all pcbs on listen-, active- and time-wait-list (bound- isn't exported). */
+void
+tcp_remove_all(void)
+{
+  tcp_remove(tcp_listen_pcbs.pcbs);
+  tcp_remove(tcp_active_pcbs);
+  tcp_remove(tcp_tw_pcbs);
+  fail_unless(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+  fail_unless(lwip_stats.memp[MEMP_TCP_PCB_LISTEN].used == 0);
+  fail_unless(lwip_stats.memp[MEMP_TCP_SEG].used == 0);
+  fail_unless(lwip_stats.memp[MEMP_PBUF_POOL].used == 0);
+}
+
+/** Create a TCP segment usable for passing to tcp_input */
+static struct pbuf*
+tcp_create_segment_wnd(ip_addr_t* src_ip, ip_addr_t* dst_ip,
+                   u16_t src_port, u16_t dst_port, void* data, size_t data_len,
+                   u32_t seqno, u32_t ackno, u8_t headerflags, u16_t wnd)
+{
+  struct pbuf *p, *q;
+  struct ip_hdr* iphdr;
+  struct tcp_hdr* tcphdr;
+  u16_t pbuf_len = (u16_t)(sizeof(struct ip_hdr) + sizeof(struct tcp_hdr) + data_len);
+
+  p = pbuf_alloc(PBUF_RAW, pbuf_len, PBUF_POOL);
+  EXPECT_RETNULL(p != NULL);
+  /* first pbuf must be big enough to hold the headers */
+  EXPECT_RETNULL(p->len >= (sizeof(struct ip_hdr) + sizeof(struct tcp_hdr)));
+  if (data_len > 0) {
+    /* first pbuf must be big enough to hold at least 1 data byte, too */
+    EXPECT_RETNULL(p->len > (sizeof(struct ip_hdr) + sizeof(struct tcp_hdr)));
+  }
+
+  for(q = p; q != NULL; q = q->next) {
+    memset(q->payload, 0, q->len);
+  }
+
+  iphdr = p->payload;
+  /* fill IP header */
+  iphdr->dest.addr = dst_ip->addr;
+  iphdr->src.addr = src_ip->addr;
+  IPH_VHL_SET(iphdr, 4, IP_HLEN / 4);
+  IPH_TOS_SET(iphdr, 0);
+  IPH_LEN_SET(iphdr, htons(p->tot_len));
+  IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));
+
+  /* let p point to TCP header */
+  pbuf_header(p, -(s16_t)sizeof(struct ip_hdr));
+
+  tcphdr = p->payload;
+  tcphdr->src   = htons(src_port);
+  tcphdr->dest  = htons(dst_port);
+  tcphdr->seqno = htonl(seqno);
+  tcphdr->ackno = htonl(ackno);
+  TCPH_HDRLEN_SET(tcphdr, sizeof(struct tcp_hdr)/4);
+  TCPH_FLAGS_SET(tcphdr, headerflags);
+  tcphdr->wnd   = htons(wnd);
+
+  if (data_len > 0) {
+    /* let p point to TCP data */
+    pbuf_header(p, -(s16_t)sizeof(struct tcp_hdr));
+    /* copy data */
+    pbuf_take(p, data, data_len);
+    /* let p point to TCP header again */
+    pbuf_header(p, sizeof(struct tcp_hdr));
+  }
+
+  /* calculate checksum */
+
+  tcphdr->chksum = inet_chksum_pseudo(p,
+          IP_PROTO_TCP, p->tot_len, src_ip, dst_ip);
+
+  pbuf_header(p, sizeof(struct ip_hdr));
+
+  return p;
+}
+
+/** Create a TCP segment usable for passing to tcp_input */
+struct pbuf*
+tcp_create_segment(ip_addr_t* src_ip, ip_addr_t* dst_ip,
+                   u16_t src_port, u16_t dst_port, void* data, size_t data_len,
+                   u32_t seqno, u32_t ackno, u8_t headerflags)
+{
+  return tcp_create_segment_wnd(src_ip, dst_ip, src_port, dst_port, data,
+    data_len, seqno, ackno, headerflags, TCP_WND);
+}
+
+/** Create a TCP segment usable for passing to tcp_input
+ * - IP-addresses, ports, seqno and ackno are taken from pcb
+ * - seqno and ackno can be altered with an offset
+ */
+struct pbuf*
+tcp_create_rx_segment(struct tcp_pcb* pcb, void* data, size_t data_len, u32_t seqno_offset,
+                      u32_t ackno_offset, u8_t headerflags)
+{
+  return tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port, pcb->local_port,
+    data, data_len, pcb->rcv_nxt + seqno_offset, pcb->lastack + ackno_offset, headerflags);
+}
+
+/** Create a TCP segment usable for passing to tcp_input
+ * - IP-addresses, ports, seqno and ackno are taken from pcb
+ * - seqno and ackno can be altered with an offset
+ * - TCP window can be adjusted
+ */
+struct pbuf* tcp_create_rx_segment_wnd(struct tcp_pcb* pcb, void* data, size_t data_len,
+                   u32_t seqno_offset, u32_t ackno_offset, u8_t headerflags, u16_t wnd)
+{
+  return tcp_create_segment_wnd(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port, pcb->local_port,
+    data, data_len, pcb->rcv_nxt + seqno_offset, pcb->lastack + ackno_offset, headerflags, wnd);
+}
+
+/** Safely bring a tcp_pcb into the requested state */
+void
+tcp_set_state(struct tcp_pcb* pcb, enum tcp_state state, ip_addr_t* local_ip,
+                   ip_addr_t* remote_ip, u16_t local_port, u16_t remote_port)
+{
+  /* @todo: are these all states? */
+  /* @todo: remove from previous list */
+  pcb->state = state;
+  if (state == ESTABLISHED) {
+    TCP_REG(&tcp_active_pcbs, pcb);
+    pcb->local_ip.addr = local_ip->addr;
+    pcb->local_port = local_port;
+    pcb->remote_ip.addr = remote_ip->addr;
+    pcb->remote_port = remote_port;
+  } else if(state == LISTEN) {
+    TCP_REG(&tcp_listen_pcbs.pcbs, pcb);
+    pcb->local_ip.addr = local_ip->addr;
+    pcb->local_port = local_port;
+  } else if(state == TIME_WAIT) {
+    TCP_REG(&tcp_tw_pcbs, pcb);
+    pcb->local_ip.addr = local_ip->addr;
+    pcb->local_port = local_port;
+    pcb->remote_ip.addr = remote_ip->addr;
+    pcb->remote_port = remote_port;
+  } else {
+    fail();
+  }
+}
+
+void
+test_tcp_counters_err(void* arg, err_t err)
+{
+  struct test_tcp_counters* counters = arg;
+  EXPECT_RET(arg != NULL);
+  counters->err_calls++;
+  counters->last_err = err;
+}
+
+static void
+test_tcp_counters_check_rxdata(struct test_tcp_counters* counters, struct pbuf* p)
+{
+  struct pbuf* q;
+  u32_t i, received;
+  if(counters->expected_data == NULL) {
+    /* no data to compare */
+    return;
+  }
+  EXPECT_RET(counters->recved_bytes + p->tot_len <= counters->expected_data_len);
+  received = counters->recved_bytes;
+  for(q = p; q != NULL; q = q->next) {
+    char *data = q->payload;
+    for(i = 0; i < q->len; i++) {
+      EXPECT_RET(data[i] == counters->expected_data[received]);
+      received++;
+    }
+  }
+  EXPECT(received == counters->recved_bytes + p->tot_len);
+}
+
+err_t
+test_tcp_counters_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err)
+{
+  struct test_tcp_counters* counters = arg;
+  EXPECT_RETX(arg != NULL, ERR_OK);
+  EXPECT_RETX(pcb != NULL, ERR_OK);
+  EXPECT_RETX(err == ERR_OK, ERR_OK);
+
+  if (p != NULL) {
+    if (counters->close_calls == 0) {
+      counters->recv_calls++;
+      test_tcp_counters_check_rxdata(counters, p);
+      counters->recved_bytes += p->tot_len;
+    } else {
+      counters->recv_calls_after_close++;
+      counters->recved_bytes_after_close += p->tot_len;
+    }
+    pbuf_free(p);
+  } else {
+    counters->close_calls++;
+  }
+  EXPECT(counters->recv_calls_after_close == 0 && counters->recved_bytes_after_close == 0);
+  return ERR_OK;
+}
+
+/** Allocate a pcb and set up the test_tcp_counters_* callbacks */
+struct tcp_pcb*
+test_tcp_new_counters_pcb(struct test_tcp_counters* counters)
+{
+  struct tcp_pcb* pcb = tcp_new();
+  if (pcb != NULL) {
+    /* set up args and callbacks */
+    tcp_arg(pcb, counters);
+    tcp_recv(pcb, test_tcp_counters_recv);
+    tcp_err(pcb, test_tcp_counters_err);
+    pcb->snd_wnd = TCP_WND;
+    pcb->snd_wnd_max = TCP_WND;
+  }
+  return pcb;
+}
+
+/** Calls tcp_input() after adjusting current_iphdr_dest */
+void test_tcp_input(struct pbuf *p, struct netif *inp)
+{
+  struct ip_hdr *iphdr = (struct ip_hdr*)p->payload;
+  /* these lines are a hack, don't use them as an example :-) */
+  ip_addr_copy(*ipX_current_dest_addr(), iphdr->dest);
+  ip_addr_copy(*ipX_current_src_addr(), iphdr->src);
+  ip_current_netif() = inp;
+  ip_current_header() = iphdr;
+
+  /* since adding IPv6, p->payload must point to tcp header, not ip header */
+  pbuf_header(p, -(s16_t)sizeof(struct ip_hdr));
+
+  tcp_input(p, inp);
+
+  ipX_current_dest_addr()->addr = 0;
+  ipX_current_src_addr()->addr = 0;
+  ip_current_netif() = NULL;
+  ip_current_header() = NULL;
+}
+
+static err_t test_tcp_netif_output(struct netif *netif, struct pbuf *p,
+       ip_addr_t *ipaddr)
+{
+  struct test_tcp_txcounters *txcounters = (struct test_tcp_txcounters*)netif->state;
+  LWIP_UNUSED_ARG(ipaddr);
+  if (txcounters != NULL)
+  {
+    txcounters->num_tx_calls++;
+    txcounters->num_tx_bytes += p->tot_len;
+    if (txcounters->copy_tx_packets) {
+      struct pbuf *p_copy = pbuf_alloc(PBUF_LINK, p->tot_len, PBUF_RAM);
+      err_t err;
+      EXPECT(p_copy != NULL);
+      err = pbuf_copy(p_copy, p);
+      EXPECT(err == ERR_OK);
+      if (txcounters->tx_packets == NULL) {
+        txcounters->tx_packets = p_copy;
+      } else {
+        pbuf_cat(txcounters->tx_packets, p_copy);
+      }
+    }
+  }
+  return ERR_OK;
+}
+
+void test_tcp_init_netif(struct netif *netif, struct test_tcp_txcounters *txcounters,
+                         ip_addr_t *ip_addr, ip_addr_t *netmask)
+{
+  struct netif *n;
+  memset(netif, 0, sizeof(struct netif));
+  if (txcounters != NULL) {
+    memset(txcounters, 0, sizeof(struct test_tcp_txcounters));
+    netif->state = txcounters;
+  }
+  netif->output = test_tcp_netif_output;
+  netif->flags |= NETIF_FLAG_UP;
+  ip_addr_copy(netif->netmask, *netmask);
+  ip_addr_copy(netif->ip_addr, *ip_addr);
+  for (n = netif_list; n != NULL; n = n->next) {
+    if (n == netif) {
+      return;
+    }
+  }
+  netif->next = NULL;
+  netif_list = netif;
+}
diff --git a/external/badvpn_dns/lwip/test/unit/tcp/tcp_helper.h b/external/badvpn_dns/lwip/test/unit/tcp/tcp_helper.h
new file mode 100644
index 0000000..4a72c93
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/tcp/tcp_helper.h
@@ -0,0 +1,52 @@
+#ifndef __TCP_HELPER_H__
+#define __TCP_HELPER_H__
+
+#include "../lwip_check.h"
+#include "lwip/arch.h"
+#include "lwip/tcp.h"
+#include "lwip/netif.h"
+
+/* counters used for test_tcp_counters_* callback functions */
+struct test_tcp_counters {
+  u32_t recv_calls;
+  u32_t recved_bytes;
+  u32_t recv_calls_after_close;
+  u32_t recved_bytes_after_close;
+  u32_t close_calls;
+  u32_t err_calls;
+  err_t last_err;
+  char* expected_data;
+  u32_t expected_data_len;
+};
+
+struct test_tcp_txcounters {
+  u32_t num_tx_calls;
+  u32_t num_tx_bytes;
+  u8_t  copy_tx_packets;
+  struct pbuf *tx_packets;
+};
+
+/* Helper functions */
+void tcp_remove_all(void);
+
+struct pbuf* tcp_create_segment(ip_addr_t* src_ip, ip_addr_t* dst_ip,
+                   u16_t src_port, u16_t dst_port, void* data, size_t data_len,
+                   u32_t seqno, u32_t ackno, u8_t headerflags);
+struct pbuf* tcp_create_rx_segment(struct tcp_pcb* pcb, void* data, size_t data_len,
+                   u32_t seqno_offset, u32_t ackno_offset, u8_t headerflags);
+struct pbuf* tcp_create_rx_segment_wnd(struct tcp_pcb* pcb, void* data, size_t data_len,
+                   u32_t seqno_offset, u32_t ackno_offset, u8_t headerflags, u16_t wnd);
+void tcp_set_state(struct tcp_pcb* pcb, enum tcp_state state, ip_addr_t* local_ip,
+                   ip_addr_t* remote_ip, u16_t local_port, u16_t remote_port);
+void test_tcp_counters_err(void* arg, err_t err);
+err_t test_tcp_counters_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err);
+
+struct tcp_pcb* test_tcp_new_counters_pcb(struct test_tcp_counters* counters);
+
+void test_tcp_input(struct pbuf *p, struct netif *inp);
+
+void test_tcp_init_netif(struct netif *netif, struct test_tcp_txcounters *txcounters,
+                         ip_addr_t *ip_addr, ip_addr_t *netmask);
+
+
+#endif
diff --git a/external/badvpn_dns/lwip/test/unit/tcp/test_tcp.c b/external/badvpn_dns/lwip/test/unit/tcp/test_tcp.c
new file mode 100644
index 0000000..8172098
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/tcp/test_tcp.c
@@ -0,0 +1,671 @@
+#include "test_tcp.h"
+
+#include "lwip/tcp_impl.h"
+#include "lwip/stats.h"
+#include "tcp_helper.h"
+
+#ifdef _MSC_VER
+#pragma warning(disable: 4307) /* we explicitly wrap around TCP seqnos */
+#endif
+
+#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS
+#error "This tests needs TCP- and MEMP-statistics enabled"
+#endif
+#if TCP_SND_BUF <= TCP_WND
+#error "This tests needs TCP_SND_BUF to be > TCP_WND"
+#endif
+
+static u8_t test_tcp_timer;
+
+/* our own version of tcp_tmr so we can reset fast/slow timer state */
+static void
+test_tcp_tmr(void)
+{
+  tcp_fasttmr();
+  if (++test_tcp_timer & 1) {
+    tcp_slowtmr();
+  }
+}
+
+/* Setups/teardown functions */
+
+static void
+tcp_setup(void)
+{
+  /* reset iss to default (6510) */
+  tcp_ticks = 0;
+  tcp_ticks = 0 - (tcp_next_iss() - 6510);
+  tcp_next_iss();
+  tcp_ticks = 0;
+
+  test_tcp_timer = 0;
+  tcp_remove_all();
+}
+
+static void
+tcp_teardown(void)
+{
+  tcp_remove_all();
+  netif_list = NULL;
+  netif_default = NULL;
+}
+
+
+/* Test functions */
+
+/** Call tcp_new() and tcp_abort() and test memp stats */
+START_TEST(test_tcp_new_abort)
+{
+  struct tcp_pcb* pcb;
+  LWIP_UNUSED_ARG(_i);
+
+  fail_unless(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+
+  pcb = tcp_new();
+  fail_unless(pcb != NULL);
+  if (pcb != NULL) {
+    fail_unless(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+    tcp_abort(pcb);
+    fail_unless(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+  }
+}
+END_TEST
+
+/** Create an ESTABLISHED pcb and check if receive callback is called */
+START_TEST(test_tcp_recv_inseq)
+{
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf* p;
+  char data[] = {1, 2, 3, 4};
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t data_len;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  struct netif netif;
+  struct test_tcp_txcounters txcounters;
+  LWIP_UNUSED_ARG(_i);
+
+  /* initialize local vars */
+  memset(&netif, 0, sizeof(netif));
+  IP4_ADDR(&local_ip, 192, 168, 1, 1);
+  IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, &txcounters, &local_ip, &netmask);
+  data_len = sizeof(data);
+  /* initialize counter struct */
+  memset(&counters, 0, sizeof(counters));
+  counters.expected_data_len = data_len;
+  counters.expected_data = data;
+
+  /* create and initialize the pcb */
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+
+  /* create a segment */
+  p = tcp_create_rx_segment(pcb, counters.expected_data, data_len, 0, 0, 0);
+  EXPECT(p != NULL);
+  if (p != NULL) {
+    /* pass the segment to tcp_input */
+    test_tcp_input(p, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 1);
+    EXPECT(counters.recved_bytes == data_len);
+    EXPECT(counters.err_calls == 0);
+  }
+
+  /* make sure the pcb is freed */
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+}
+END_TEST
+
+/** Provoke fast retransmission by duplicate ACKs and then recover by ACKing all sent data.
+ * At the end, send more data. */
+START_TEST(test_tcp_fast_retx_recover)
+{
+  struct netif netif;
+  struct test_tcp_txcounters txcounters;
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf* p;
+  char data1[] = { 1,  2,  3,  4};
+  char data2[] = { 5,  6,  7,  8};
+  char data3[] = { 9, 10, 11, 12};
+  char data4[] = {13, 14, 15, 16};
+  char data5[] = {17, 18, 19, 20};
+  char data6[] = {21, 22, 23, 24};
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  err_t err;
+  LWIP_UNUSED_ARG(_i);
+
+  /* initialize local vars */
+  IP4_ADDR(&local_ip,  192, 168,   1, 1);
+  IP4_ADDR(&remote_ip, 192, 168,   1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, &txcounters, &local_ip, &netmask);
+  memset(&counters, 0, sizeof(counters));
+
+  /* create and initialize the pcb */
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+  pcb->mss = TCP_MSS;
+  /* disable initial congestion window (we don't send a SYN here...) */
+  pcb->cwnd = pcb->snd_wnd;
+
+  /* send data1 */
+  err = tcp_write(pcb, data1, sizeof(data1), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  EXPECT_RET(txcounters.num_tx_calls == 1);
+  EXPECT_RET(txcounters.num_tx_bytes == sizeof(data1) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr));
+  memset(&txcounters, 0, sizeof(txcounters));
+ /* "recv" ACK for data1 */
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, 4, TCP_ACK);
+  EXPECT_RET(p != NULL);
+  test_tcp_input(p, &netif);
+  EXPECT_RET(txcounters.num_tx_calls == 0);
+  EXPECT_RET(pcb->unacked == NULL);
+  /* send data2 */
+  err = tcp_write(pcb, data2, sizeof(data2), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  EXPECT_RET(txcounters.num_tx_calls == 1);
+  EXPECT_RET(txcounters.num_tx_bytes == sizeof(data2) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr));
+  memset(&txcounters, 0, sizeof(txcounters));
+  /* duplicate ACK for data1 (data2 is lost) */
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+  EXPECT_RET(p != NULL);
+  test_tcp_input(p, &netif);
+  EXPECT_RET(txcounters.num_tx_calls == 0);
+  EXPECT_RET(pcb->dupacks == 1);
+  /* send data3 */
+  err = tcp_write(pcb, data3, sizeof(data3), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  /* nagle enabled, no tx calls */
+  EXPECT_RET(txcounters.num_tx_calls == 0);
+  EXPECT_RET(txcounters.num_tx_bytes == 0);
+  memset(&txcounters, 0, sizeof(txcounters));
+  /* 2nd duplicate ACK for data1 (data2 and data3 are lost) */
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+  EXPECT_RET(p != NULL);
+  test_tcp_input(p, &netif);
+  EXPECT_RET(txcounters.num_tx_calls == 0);
+  EXPECT_RET(pcb->dupacks == 2);
+  /* queue data4, don't send it (unsent-oversize is != 0) */
+  err = tcp_write(pcb, data4, sizeof(data4), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  /* 3nd duplicate ACK for data1 (data2 and data3 are lost) -> fast retransmission */
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+  EXPECT_RET(p != NULL);
+  test_tcp_input(p, &netif);
+  /*EXPECT_RET(txcounters.num_tx_calls == 1);*/
+  EXPECT_RET(pcb->dupacks == 3);
+  memset(&txcounters, 0, sizeof(txcounters));
+  /* TODO: check expected data?*/
+  
+  /* send data5, not output yet */
+  err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  /*err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);*/
+  EXPECT_RET(txcounters.num_tx_calls == 0);
+  EXPECT_RET(txcounters.num_tx_bytes == 0);
+  memset(&txcounters, 0, sizeof(txcounters));
+  {
+    int i = 0;
+    do
+    {
+      err = tcp_write(pcb, data6, TCP_MSS, TCP_WRITE_FLAG_COPY);
+      i++;
+    }while(err == ERR_OK);
+    EXPECT_RET(err != ERR_OK);
+  }
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  /*EXPECT_RET(txcounters.num_tx_calls == 0);
+  EXPECT_RET(txcounters.num_tx_bytes == 0);*/
+  memset(&txcounters, 0, sizeof(txcounters));
+
+  /* send even more data */
+  err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  /* ...and even more data */
+  err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  /* ...and even more data */
+  err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  /* ...and even more data */
+  err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+
+  /* send ACKs for data2 and data3 */
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, 12, TCP_ACK);
+  EXPECT_RET(p != NULL);
+  test_tcp_input(p, &netif);
+  /*EXPECT_RET(txcounters.num_tx_calls == 0);*/
+
+  /* ...and even more data */
+  err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  /* ...and even more data */
+  err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+
+#if 0
+  /* create expected segment */
+  p1 = tcp_create_rx_segment(pcb, counters.expected_data, data_len, 0, 0, 0);
+  EXPECT_RET(p != NULL);
+  if (p != NULL) {
+    /* pass the segment to tcp_input */
+    test_tcp_input(p, &netif);
+    /* check if counters are as expected */
+    EXPECT_RET(counters.close_calls == 0);
+    EXPECT_RET(counters.recv_calls == 1);
+    EXPECT_RET(counters.recved_bytes == data_len);
+    EXPECT_RET(counters.err_calls == 0);
+  }
+#endif
+  /* make sure the pcb is freed */
+  EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+}
+END_TEST
+
+static u8_t tx_data[TCP_WND*2];
+
+static void
+check_seqnos(struct tcp_seg *segs, int num_expected, u32_t *seqnos_expected)
+{
+  struct tcp_seg *s = segs;
+  int i;
+  for (i = 0; i < num_expected; i++, s = s->next) {
+    EXPECT_RET(s != NULL);
+    EXPECT(s->tcphdr->seqno == htonl(seqnos_expected[i]));
+  }
+  EXPECT(s == NULL);
+}
+
+/** Send data with sequence numbers that wrap around the u32_t range.
+ * Then, provoke fast retransmission by duplicate ACKs and check that all
+ * segment lists are still properly sorted. */
+START_TEST(test_tcp_fast_rexmit_wraparound)
+{
+  struct netif netif;
+  struct test_tcp_txcounters txcounters;
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf* p;
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  err_t err;
+#define SEQNO1 (0xFFFFFF00 - TCP_MSS)
+#define ISS    6510
+  u16_t i, sent_total = 0;
+  u32_t seqnos[] = {
+    SEQNO1,
+    SEQNO1 + (1 * TCP_MSS),
+    SEQNO1 + (2 * TCP_MSS),
+    SEQNO1 + (3 * TCP_MSS),
+    SEQNO1 + (4 * TCP_MSS),
+    SEQNO1 + (5 * TCP_MSS)};
+  LWIP_UNUSED_ARG(_i);
+
+  for (i = 0; i < sizeof(tx_data); i++) {
+    tx_data[i] = (u8_t)i;
+  }
+
+  /* initialize local vars */
+  IP4_ADDR(&local_ip,  192, 168,   1, 1);
+  IP4_ADDR(&remote_ip, 192, 168,   1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, &txcounters, &local_ip, &netmask);
+  memset(&counters, 0, sizeof(counters));
+
+  /* create and initialize the pcb */
+  tcp_ticks = SEQNO1 - ISS;
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  EXPECT(pcb->lastack == SEQNO1);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+  pcb->mss = TCP_MSS;
+  /* disable initial congestion window (we don't send a SYN here...) */
+  pcb->cwnd = 2*TCP_MSS;
+
+  /* send 6 mss-sized segments */
+  for (i = 0; i < 6; i++) {
+    err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+    EXPECT_RET(err == ERR_OK);
+    sent_total += TCP_MSS;
+  }
+  check_seqnos(pcb->unsent, 6, seqnos);
+  EXPECT(pcb->unacked == NULL);
+  err = tcp_output(pcb);
+  EXPECT(txcounters.num_tx_calls == 2);
+  EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U));
+  memset(&txcounters, 0, sizeof(txcounters));
+
+  check_seqnos(pcb->unacked, 2, seqnos);
+  check_seqnos(pcb->unsent, 4, &seqnos[2]);
+
+  /* ACK the first segment */
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK);
+  test_tcp_input(p, &netif);
+  /* ensure this didn't trigger a retransmission */
+  EXPECT(txcounters.num_tx_calls == 1);
+  EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U);
+  memset(&txcounters, 0, sizeof(txcounters));
+  check_seqnos(pcb->unacked, 2, &seqnos[1]);
+  check_seqnos(pcb->unsent, 3, &seqnos[3]);
+
+  /* 3 dupacks */
+  EXPECT(pcb->dupacks == 0);
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+  test_tcp_input(p, &netif);
+  EXPECT(txcounters.num_tx_calls == 0);
+  EXPECT(pcb->dupacks == 1);
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+  test_tcp_input(p, &netif);
+  EXPECT(txcounters.num_tx_calls == 0);
+  EXPECT(pcb->dupacks == 2);
+  /* 3rd dupack -> fast rexmit */
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+  test_tcp_input(p, &netif);
+  EXPECT(pcb->dupacks == 3);
+  EXPECT(txcounters.num_tx_calls == 4);
+  memset(&txcounters, 0, sizeof(txcounters));
+  EXPECT(pcb->unsent == NULL);
+  check_seqnos(pcb->unacked, 5, &seqnos[1]);
+
+  /* make sure the pcb is freed */
+  EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+}
+END_TEST
+
+/** Send data with sequence numbers that wrap around the u32_t range.
+ * Then, provoke RTO retransmission and check that all
+ * segment lists are still properly sorted. */
+START_TEST(test_tcp_rto_rexmit_wraparound)
+{
+  struct netif netif;
+  struct test_tcp_txcounters txcounters;
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  err_t err;
+#define SEQNO1 (0xFFFFFF00 - TCP_MSS)
+#define ISS    6510
+  u16_t i, sent_total = 0;
+  u32_t seqnos[] = {
+    SEQNO1,
+    SEQNO1 + (1 * TCP_MSS),
+    SEQNO1 + (2 * TCP_MSS),
+    SEQNO1 + (3 * TCP_MSS),
+    SEQNO1 + (4 * TCP_MSS),
+    SEQNO1 + (5 * TCP_MSS)};
+  LWIP_UNUSED_ARG(_i);
+
+  for (i = 0; i < sizeof(tx_data); i++) {
+    tx_data[i] = (u8_t)i;
+  }
+
+  /* initialize local vars */
+  IP4_ADDR(&local_ip,  192, 168,   1, 1);
+  IP4_ADDR(&remote_ip, 192, 168,   1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, &txcounters, &local_ip, &netmask);
+  memset(&counters, 0, sizeof(counters));
+
+  /* create and initialize the pcb */
+  tcp_ticks = 0;
+  tcp_ticks = 0 - tcp_next_iss();
+  tcp_ticks = SEQNO1 - tcp_next_iss();
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  EXPECT(pcb->lastack == SEQNO1);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+  pcb->mss = TCP_MSS;
+  /* disable initial congestion window (we don't send a SYN here...) */
+  pcb->cwnd = 2*TCP_MSS;
+
+  /* send 6 mss-sized segments */
+  for (i = 0; i < 6; i++) {
+    err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+    EXPECT_RET(err == ERR_OK);
+    sent_total += TCP_MSS;
+  }
+  check_seqnos(pcb->unsent, 6, seqnos);
+  EXPECT(pcb->unacked == NULL);
+  err = tcp_output(pcb);
+  EXPECT(txcounters.num_tx_calls == 2);
+  EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U));
+  memset(&txcounters, 0, sizeof(txcounters));
+
+  check_seqnos(pcb->unacked, 2, seqnos);
+  check_seqnos(pcb->unsent, 4, &seqnos[2]);
+
+  /* call the tcp timer some times */
+  for (i = 0; i < 10; i++) {
+    test_tcp_tmr();
+    EXPECT(txcounters.num_tx_calls == 0);
+  }
+  /* 11th call to tcp_tmr: RTO rexmit fires */
+  test_tcp_tmr();
+  EXPECT(txcounters.num_tx_calls == 1);
+  check_seqnos(pcb->unacked, 1, seqnos);
+  check_seqnos(pcb->unsent, 5, &seqnos[1]);
+
+  /* fake greater cwnd */
+  pcb->cwnd = pcb->snd_wnd;
+  /* send more data */
+  err = tcp_output(pcb);
+  EXPECT(err == ERR_OK);
+  /* check queues are sorted */
+  EXPECT(pcb->unsent == NULL);
+  check_seqnos(pcb->unacked, 6, seqnos);
+
+  /* make sure the pcb is freed */
+  EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+}
+END_TEST
+
+/** Provoke fast retransmission by duplicate ACKs and then recover by ACKing all sent data.
+ * At the end, send more data. */
+static void test_tcp_tx_full_window_lost(u8_t zero_window_probe_from_unsent)
+{
+  struct netif netif;
+  struct test_tcp_txcounters txcounters;
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf *p;
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  err_t err;
+  u16_t sent_total, i;
+  u8_t expected = 0xFE;
+
+  for (i = 0; i < sizeof(tx_data); i++) {
+    u8_t d = (u8_t)i;
+    if (d == 0xFE) {
+      d = 0xF0;
+    }
+    tx_data[i] = d;
+  }
+  if (zero_window_probe_from_unsent) {
+    tx_data[TCP_WND] = expected;
+  } else {
+    tx_data[0] = expected;
+  }
+
+  /* initialize local vars */
+  IP4_ADDR(&local_ip,  192, 168,   1, 1);
+  IP4_ADDR(&remote_ip, 192, 168,   1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, &txcounters, &local_ip, &netmask);
+  memset(&counters, 0, sizeof(counters));
+  memset(&txcounters, 0, sizeof(txcounters));
+
+  /* create and initialize the pcb */
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+  pcb->mss = TCP_MSS;
+  /* disable initial congestion window (we don't send a SYN here...) */
+  pcb->cwnd = pcb->snd_wnd;
+
+  /* send a full window (minus 1 packets) of TCP data in MSS-sized chunks */
+  sent_total = 0;
+  if ((TCP_WND - TCP_MSS) % TCP_MSS != 0) {
+    u16_t initial_data_len = (TCP_WND - TCP_MSS) % TCP_MSS;
+    err = tcp_write(pcb, &tx_data[sent_total], initial_data_len, TCP_WRITE_FLAG_COPY);
+    EXPECT_RET(err == ERR_OK);
+    err = tcp_output(pcb);
+    EXPECT_RET(err == ERR_OK);
+    EXPECT(txcounters.num_tx_calls == 1);
+    EXPECT(txcounters.num_tx_bytes == initial_data_len + 40U);
+    memset(&txcounters, 0, sizeof(txcounters));
+    sent_total += initial_data_len;
+  }
+  for (; sent_total < (TCP_WND - TCP_MSS); sent_total += TCP_MSS) {
+    err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+    EXPECT_RET(err == ERR_OK);
+    err = tcp_output(pcb);
+    EXPECT_RET(err == ERR_OK);
+    EXPECT(txcounters.num_tx_calls == 1);
+    EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U);
+    memset(&txcounters, 0, sizeof(txcounters));
+  }
+  EXPECT(sent_total == (TCP_WND - TCP_MSS));
+
+  /* now ACK the packet before the first */
+  p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+  test_tcp_input(p, &netif);
+  /* ensure this didn't trigger a retransmission */
+  EXPECT(txcounters.num_tx_calls == 0);
+  EXPECT(txcounters.num_tx_bytes == 0);
+
+  EXPECT(pcb->persist_backoff == 0);
+  /* send the last packet, now a complete window has been sent */
+  err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+  sent_total += TCP_MSS;
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  EXPECT(txcounters.num_tx_calls == 1);
+  EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U);
+  memset(&txcounters, 0, sizeof(txcounters));
+  EXPECT(pcb->persist_backoff == 0);
+
+  if (zero_window_probe_from_unsent) {
+    /* ACK all data but close the TX window */
+    p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, TCP_WND, TCP_ACK, 0);
+    test_tcp_input(p, &netif);
+    /* ensure this didn't trigger any transmission */
+    EXPECT(txcounters.num_tx_calls == 0);
+    EXPECT(txcounters.num_tx_bytes == 0);
+    EXPECT(pcb->persist_backoff == 1);
+  }
+
+  /* send one byte more (out of window) -> persist timer starts */
+  err = tcp_write(pcb, &tx_data[sent_total], 1, TCP_WRITE_FLAG_COPY);
+  EXPECT_RET(err == ERR_OK);
+  err = tcp_output(pcb);
+  EXPECT_RET(err == ERR_OK);
+  EXPECT(txcounters.num_tx_calls == 0);
+  EXPECT(txcounters.num_tx_bytes == 0);
+  memset(&txcounters, 0, sizeof(txcounters));
+  if (!zero_window_probe_from_unsent) {
+    /* no persist timer unless a zero window announcement has been received */
+    EXPECT(pcb->persist_backoff == 0);
+  } else {
+    EXPECT(pcb->persist_backoff == 1);
+
+    /* call tcp_timer some more times to let persist timer count up */
+    for (i = 0; i < 4; i++) {
+      test_tcp_tmr();
+      EXPECT(txcounters.num_tx_calls == 0);
+      EXPECT(txcounters.num_tx_bytes == 0);
+    }
+
+    /* this should trigger the zero-window-probe */
+    txcounters.copy_tx_packets = 1;
+    test_tcp_tmr();
+    txcounters.copy_tx_packets = 0;
+    EXPECT(txcounters.num_tx_calls == 1);
+    EXPECT(txcounters.num_tx_bytes == 1 + 40U);
+    EXPECT(txcounters.tx_packets != NULL);
+    if (txcounters.tx_packets != NULL) {
+      u8_t sent;
+      u16_t ret;
+      ret = pbuf_copy_partial(txcounters.tx_packets, &sent, 1, 40U);
+      EXPECT(ret == 1);
+      EXPECT(sent == expected);
+    }
+    if (txcounters.tx_packets != NULL) {
+      pbuf_free(txcounters.tx_packets);
+      txcounters.tx_packets = NULL;
+    }
+  }
+
+  /* make sure the pcb is freed */
+  EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+}
+
+START_TEST(test_tcp_tx_full_window_lost_from_unsent)
+{
+  LWIP_UNUSED_ARG(_i);
+  test_tcp_tx_full_window_lost(1);
+}
+END_TEST
+
+START_TEST(test_tcp_tx_full_window_lost_from_unacked)
+{
+  LWIP_UNUSED_ARG(_i);
+  test_tcp_tx_full_window_lost(0);
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+tcp_suite(void)
+{
+  TFun tests[] = {
+    test_tcp_new_abort,
+    test_tcp_recv_inseq,
+    test_tcp_fast_retx_recover,
+    test_tcp_fast_rexmit_wraparound,
+    test_tcp_rto_rexmit_wraparound,
+    test_tcp_tx_full_window_lost_from_unacked,
+    test_tcp_tx_full_window_lost_from_unsent
+  };
+  return create_suite("TCP", tests, sizeof(tests)/sizeof(TFun), tcp_setup, tcp_teardown);
+}
diff --git a/external/badvpn_dns/lwip/test/unit/tcp/test_tcp.h b/external/badvpn_dns/lwip/test/unit/tcp/test_tcp.h
new file mode 100644
index 0000000..f1c4a46
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/tcp/test_tcp.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_TCP_H__
+#define __TEST_TCP_H__
+
+#include "../lwip_check.h"
+
+Suite *tcp_suite(void);
+
+#endif
diff --git a/external/badvpn_dns/lwip/test/unit/tcp/test_tcp_oos.c b/external/badvpn_dns/lwip/test/unit/tcp/test_tcp_oos.c
new file mode 100644
index 0000000..612c468
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/tcp/test_tcp_oos.c
@@ -0,0 +1,958 @@
+#include "test_tcp_oos.h"
+
+#include "lwip/tcp_impl.h"
+#include "lwip/stats.h"
+#include "tcp_helper.h"
+
+#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS
+#error "This tests needs TCP- and MEMP-statistics enabled"
+#endif
+#if !TCP_QUEUE_OOSEQ
+#error "This tests needs TCP_QUEUE_OOSEQ enabled"
+#endif
+
+/** CHECK_SEGMENTS_ON_OOSEQ:
+ * 1: check count, seqno and len of segments on pcb->ooseq (strict)
+ * 0: only check that bytes are received in correct order (less strict) */
+#define CHECK_SEGMENTS_ON_OOSEQ 1
+
+#if CHECK_SEGMENTS_ON_OOSEQ
+#define EXPECT_OOSEQ(x) EXPECT(x)
+#else
+#define EXPECT_OOSEQ(x)
+#endif
+
+/* helper functions */
+
+/** Get the numbers of segments on the ooseq list */
+static int tcp_oos_count(struct tcp_pcb* pcb)
+{
+  int num = 0;
+  struct tcp_seg* seg = pcb->ooseq;
+  while(seg != NULL) {
+    num++;
+    seg = seg->next;
+  }
+  return num;
+}
+
+/** Get the numbers of pbufs on the ooseq list */
+static int tcp_oos_pbuf_count(struct tcp_pcb* pcb)
+{
+  int num = 0;
+  struct tcp_seg* seg = pcb->ooseq;
+  while(seg != NULL) {
+    num += pbuf_clen(seg->p);
+    seg = seg->next;
+  }
+  return num;
+}
+
+/** Get the seqno of a segment (by index) on the ooseq list
+ *
+ * @param pcb the pcb to check for ooseq segments
+ * @param seg_index index of the segment on the ooseq list
+ * @return seqno of the segment
+ */
+static u32_t
+tcp_oos_seg_seqno(struct tcp_pcb* pcb, int seg_index)
+{
+  int num = 0;
+  struct tcp_seg* seg = pcb->ooseq;
+
+  /* then check the actual segment */
+  while(seg != NULL) {
+    if(num == seg_index) {
+      return seg->tcphdr->seqno;
+    }
+    num++;
+    seg = seg->next;
+  }
+  fail();
+  return 0;
+}
+
+/** Get the tcplen (datalen + SYN/FIN) of a segment (by index) on the ooseq list
+ *
+ * @param pcb the pcb to check for ooseq segments
+ * @param seg_index index of the segment on the ooseq list
+ * @return tcplen of the segment
+ */
+static int
+tcp_oos_seg_tcplen(struct tcp_pcb* pcb, int seg_index)
+{
+  int num = 0;
+  struct tcp_seg* seg = pcb->ooseq;
+
+  /* then check the actual segment */
+  while(seg != NULL) {
+    if(num == seg_index) {
+      return TCP_TCPLEN(seg);
+    }
+    num++;
+    seg = seg->next;
+  }
+  fail();
+  return -1;
+}
+
+/** Get the tcplen (datalen + SYN/FIN) of all segments on the ooseq list
+ *
+ * @param pcb the pcb to check for ooseq segments
+ * @return tcplen of all segment
+ */
+static int
+tcp_oos_tcplen(struct tcp_pcb* pcb)
+{
+  int len = 0;
+  struct tcp_seg* seg = pcb->ooseq;
+
+  /* then check the actual segment */
+  while(seg != NULL) {
+    len += TCP_TCPLEN(seg);
+    seg = seg->next;
+  }
+  return len;
+}
+
+/* Setup/teardown functions */
+
+static void
+tcp_oos_setup(void)
+{
+  tcp_remove_all();
+}
+
+static void
+tcp_oos_teardown(void)
+{
+  tcp_remove_all();
+  netif_list = NULL;
+  netif_default = NULL;
+}
+
+
+
+/* Test functions */
+
+/** create multiple segments and pass them to tcp_input in a wrong
+ * order to see if ooseq-caching works correctly
+ * FIN is received in out-of-sequence segments only */
+START_TEST(test_tcp_recv_ooseq_FIN_OOSEQ)
+{
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf *p_8_9, *p_4_8, *p_4_10, *p_2_14, *p_fin, *pinseq;
+  char data[] = {
+     1,  2,  3,  4,
+     5,  6,  7,  8,
+     9, 10, 11, 12,
+    13, 14, 15, 16};
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t data_len;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  struct netif netif;
+  LWIP_UNUSED_ARG(_i);
+
+  /* initialize local vars */
+  memset(&netif, 0, sizeof(netif));
+  IP4_ADDR(&local_ip, 192, 168, 1, 1);
+  IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, NULL, &local_ip, &netmask);
+  data_len = sizeof(data);
+  /* initialize counter struct */
+  memset(&counters, 0, sizeof(counters));
+  counters.expected_data_len = data_len;
+  counters.expected_data = data;
+
+  /* create and initialize the pcb */
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+
+  /* create segments */
+  /* pinseq is sent as last segment! */
+  pinseq = tcp_create_rx_segment(pcb, &data[0],  4, 0, 0, TCP_ACK);
+  /* p1: 8 bytes before FIN */
+  /*     seqno: 8..16 */
+  p_8_9  = tcp_create_rx_segment(pcb, &data[8],  8, 8, 0, TCP_ACK|TCP_FIN);
+  /* p2: 4 bytes before p1, including the first 4 bytes of p1 (partly duplicate) */
+  /*     seqno: 4..11 */
+  p_4_8  = tcp_create_rx_segment(pcb, &data[4],  8, 4, 0, TCP_ACK);
+  /* p3: same as p2 but 2 bytes longer */
+  /*     seqno: 4..13 */
+  p_4_10 = tcp_create_rx_segment(pcb, &data[4], 10, 4, 0, TCP_ACK);
+  /* p4: 14 bytes before FIN, includes data from p1 and p2, plus partly from pinseq */
+  /*     seqno: 2..15 */
+  p_2_14 = tcp_create_rx_segment(pcb, &data[2], 14, 2, 0, TCP_ACK);
+  /* FIN, seqno 16 */
+  p_fin  = tcp_create_rx_segment(pcb,     NULL,  0,16, 0, TCP_ACK|TCP_FIN);
+  EXPECT(pinseq != NULL);
+  EXPECT(p_8_9 != NULL);
+  EXPECT(p_4_8 != NULL);
+  EXPECT(p_4_10 != NULL);
+  EXPECT(p_2_14 != NULL);
+  EXPECT(p_fin != NULL);
+  if ((pinseq != NULL) && (p_8_9 != NULL) && (p_4_8 != NULL) && (p_4_10 != NULL) && (p_2_14 != NULL) && (p_fin != NULL)) {
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_8_9, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 8);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 9); /* includes FIN */
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_4_8, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 4);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 4);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 8);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 9); /* includes FIN */
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_4_10, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* ooseq queue: unchanged */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 4);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 4);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 8);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 9); /* includes FIN */
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_2_14, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 2);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 15); /* includes FIN */
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_fin, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* ooseq queue: unchanged */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 2);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 15); /* includes FIN */
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(pinseq, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 1);
+    EXPECT(counters.recv_calls == 1);
+    EXPECT(counters.recved_bytes == data_len);
+    EXPECT(counters.err_calls == 0);
+    EXPECT(pcb->ooseq == NULL);
+  }
+
+  /* make sure the pcb is freed */
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+}
+END_TEST
+
+
+/** create multiple segments and pass them to tcp_input in a wrong
+ * order to see if ooseq-caching works correctly
+ * FIN is received IN-SEQUENCE at the end */
+START_TEST(test_tcp_recv_ooseq_FIN_INSEQ)
+{
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf *p_1_2, *p_4_8, *p_3_11, *p_2_12, *p_15_1, *p_15_1a, *pinseq, *pinseqFIN;
+  char data[] = {
+     1,  2,  3,  4,
+     5,  6,  7,  8,
+     9, 10, 11, 12,
+    13, 14, 15, 16};
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t data_len;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  struct netif netif;
+  LWIP_UNUSED_ARG(_i);
+
+  /* initialize local vars */
+  memset(&netif, 0, sizeof(netif));
+  IP4_ADDR(&local_ip, 192, 168, 1, 1);
+  IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, NULL, &local_ip, &netmask);
+  data_len = sizeof(data);
+  /* initialize counter struct */
+  memset(&counters, 0, sizeof(counters));
+  counters.expected_data_len = data_len;
+  counters.expected_data = data;
+
+  /* create and initialize the pcb */
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+
+  /* create segments */
+  /* p1: 7 bytes - 2 before FIN */
+  /*     seqno: 1..2 */
+  p_1_2  = tcp_create_rx_segment(pcb, &data[1],  2, 1, 0, TCP_ACK);
+  /* p2: 4 bytes before p1, including the first 4 bytes of p1 (partly duplicate) */
+  /*     seqno: 4..11 */
+  p_4_8  = tcp_create_rx_segment(pcb, &data[4],  8, 4, 0, TCP_ACK);
+  /* p3: same as p2 but 2 bytes longer and one byte more at the front */
+  /*     seqno: 3..13 */
+  p_3_11 = tcp_create_rx_segment(pcb, &data[3], 11, 3, 0, TCP_ACK);
+  /* p4: 13 bytes - 2 before FIN - should be ignored as contained in p1 and p3 */
+  /*     seqno: 2..13 */
+  p_2_12 = tcp_create_rx_segment(pcb, &data[2], 12, 2, 0, TCP_ACK);
+  /* pinseq is the first segment that is held back to create ooseq! */
+  /*     seqno: 0..3 */
+  pinseq = tcp_create_rx_segment(pcb, &data[0],  4, 0, 0, TCP_ACK);
+  /* p5: last byte before FIN */
+  /*     seqno: 15 */
+  p_15_1 = tcp_create_rx_segment(pcb, &data[15], 1, 15, 0, TCP_ACK);
+  /* p6: same as p5, should be ignored */
+  p_15_1a= tcp_create_rx_segment(pcb, &data[15], 1, 15, 0, TCP_ACK);
+  /* pinseqFIN: last 2 bytes plus FIN */
+  /*     only segment containing seqno 14 and FIN */
+  pinseqFIN = tcp_create_rx_segment(pcb,  &data[14], 2, 14, 0, TCP_ACK|TCP_FIN);
+  EXPECT(pinseq != NULL);
+  EXPECT(p_1_2 != NULL);
+  EXPECT(p_4_8 != NULL);
+  EXPECT(p_3_11 != NULL);
+  EXPECT(p_2_12 != NULL);
+  EXPECT(p_15_1 != NULL);
+  EXPECT(p_15_1a != NULL);
+  EXPECT(pinseqFIN != NULL);
+  if ((pinseq != NULL) && (p_1_2 != NULL) && (p_4_8 != NULL) && (p_3_11 != NULL) && (p_2_12 != NULL)
+    && (p_15_1 != NULL) && (p_15_1a != NULL) && (pinseqFIN != NULL)) {
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_1_2, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 2);
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_4_8, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 2);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 4);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 8);
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_3_11, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 2);
+    /* p_3_11 has removed p_4_8 from ooseq */
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 3);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 11);
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_2_12, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 2);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 12);
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(pinseq, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 1);
+    EXPECT(counters.recved_bytes == 14);
+    EXPECT(counters.err_calls == 0);
+    EXPECT(pcb->ooseq == NULL);
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_15_1, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 1);
+    EXPECT(counters.recved_bytes == 14);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 15);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 1);
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(p_15_1a, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 1);
+    EXPECT(counters.recved_bytes == 14);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue: unchanged */
+    EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+    EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 15);
+    EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 1);
+
+    /* pass the segment to tcp_input */
+    test_tcp_input(pinseqFIN, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 1);
+    EXPECT(counters.recv_calls == 2);
+    EXPECT(counters.recved_bytes == data_len);
+    EXPECT(counters.err_calls == 0);
+    EXPECT(pcb->ooseq == NULL);
+  }
+
+  /* make sure the pcb is freed */
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+}
+END_TEST
+
+static char data_full_wnd[TCP_WND];
+
+/** create multiple segments and pass them to tcp_input with the first segment missing
+ * to simulate overruning the rxwin with ooseq queueing enabled */
+START_TEST(test_tcp_recv_ooseq_overrun_rxwin)
+{
+#if !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS
+  int i, k;
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf *pinseq, *p_ovr;
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  struct netif netif;
+  int datalen = 0;
+  int datalen2;
+
+  for(i = 0; i < sizeof(data_full_wnd); i++) {
+    data_full_wnd[i] = (char)i;
+  }
+
+  /* initialize local vars */
+  memset(&netif, 0, sizeof(netif));
+  IP4_ADDR(&local_ip, 192, 168, 1, 1);
+  IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, NULL, &local_ip, &netmask);
+  /* initialize counter struct */
+  memset(&counters, 0, sizeof(counters));
+  counters.expected_data_len = TCP_WND;
+  counters.expected_data = data_full_wnd;
+
+  /* create and initialize the pcb */
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+  pcb->rcv_nxt = 0x8000;
+
+  /* create segments */
+  /* pinseq is sent as last segment! */
+  pinseq = tcp_create_rx_segment(pcb, &data_full_wnd[0],  TCP_MSS, 0, 0, TCP_ACK);
+
+  for(i = TCP_MSS, k = 0; i < TCP_WND; i += TCP_MSS, k++) {
+    int count, expected_datalen;
+    struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)],
+                                           TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK);
+    EXPECT_RET(p != NULL);
+    /* pass the segment to tcp_input */
+    test_tcp_input(p, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    count = tcp_oos_count(pcb);
+    EXPECT_OOSEQ(count == k+1);
+    datalen = tcp_oos_tcplen(pcb);
+    if (i + TCP_MSS < TCP_WND) {
+      expected_datalen = (k+1)*TCP_MSS;
+    } else {
+      expected_datalen = TCP_WND - TCP_MSS;
+    }
+    if (datalen != expected_datalen) {
+      EXPECT_OOSEQ(datalen == expected_datalen);
+    }
+  }
+
+  /* pass in one more segment, cleary overrunning the rxwin */
+  p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)], TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK);
+  EXPECT_RET(p_ovr != NULL);
+  /* pass the segment to tcp_input */
+  test_tcp_input(p_ovr, &netif);
+  /* check if counters are as expected */
+  EXPECT(counters.close_calls == 0);
+  EXPECT(counters.recv_calls == 0);
+  EXPECT(counters.recved_bytes == 0);
+  EXPECT(counters.err_calls == 0);
+  /* check ooseq queue */
+  EXPECT_OOSEQ(tcp_oos_count(pcb) == k);
+  datalen2 = tcp_oos_tcplen(pcb);
+  EXPECT_OOSEQ(datalen == datalen2);
+
+  /* now pass inseq */
+  test_tcp_input(pinseq, &netif);
+  EXPECT(pcb->ooseq == NULL);
+
+  /* make sure the pcb is freed */
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+#endif /* !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS */
+  LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+START_TEST(test_tcp_recv_ooseq_max_bytes)
+{
+#if TCP_OOSEQ_MAX_BYTES && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN))
+  int i, k;
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf *p_ovr;
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  struct netif netif;
+  int datalen = 0;
+  int datalen2;
+
+  for(i = 0; i < sizeof(data_full_wnd); i++) {
+    data_full_wnd[i] = (char)i;
+  }
+
+  /* initialize local vars */
+  memset(&netif, 0, sizeof(netif));
+  IP4_ADDR(&local_ip, 192, 168, 1, 1);
+  IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, NULL, &local_ip, &netmask);
+  /* initialize counter struct */
+  memset(&counters, 0, sizeof(counters));
+  counters.expected_data_len = TCP_WND;
+  counters.expected_data = data_full_wnd;
+
+  /* create and initialize the pcb */
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+  pcb->rcv_nxt = 0x8000;
+
+  /* don't 'recv' the first segment (1 byte) so that all other segments will be ooseq */
+
+  /* create segments and 'recv' them */
+  for(k = 1, i = 1; k < TCP_OOSEQ_MAX_BYTES; k += TCP_MSS, i++) {
+    int count;
+    struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[k],
+                                           TCP_MSS, k, 0, TCP_ACK);
+    EXPECT_RET(p != NULL);
+    EXPECT_RET(p->next == NULL);
+    /* pass the segment to tcp_input */
+    test_tcp_input(p, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    count = tcp_oos_pbuf_count(pcb);
+    EXPECT_OOSEQ(count == i);
+    datalen = tcp_oos_tcplen(pcb);
+    EXPECT_OOSEQ(datalen == (i * TCP_MSS));
+  }
+
+  /* pass in one more segment, overrunning the limit */
+  p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[k+1], 1, k+1, 0, TCP_ACK);
+  EXPECT_RET(p_ovr != NULL);
+  /* pass the segment to tcp_input */
+  test_tcp_input(p_ovr, &netif);
+  /* check if counters are as expected */
+  EXPECT(counters.close_calls == 0);
+  EXPECT(counters.recv_calls == 0);
+  EXPECT(counters.recved_bytes == 0);
+  EXPECT(counters.err_calls == 0);
+  /* check ooseq queue (ensure the new segment was not accepted) */
+  EXPECT_OOSEQ(tcp_oos_count(pcb) == (i-1));
+  datalen2 = tcp_oos_tcplen(pcb);
+  EXPECT_OOSEQ(datalen2 == ((i-1) * TCP_MSS));
+
+  /* make sure the pcb is freed */
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+#endif /* TCP_OOSEQ_MAX_BYTES && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) */
+  LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+START_TEST(test_tcp_recv_ooseq_max_pbufs)
+{
+#if TCP_OOSEQ_MAX_PBUFS && (TCP_OOSEQ_MAX_PBUFS < ((TCP_WND / TCP_MSS) + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN))
+  int i;
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf *p_ovr;
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  struct netif netif;
+  int datalen = 0;
+  int datalen2;
+
+  for(i = 0; i < sizeof(data_full_wnd); i++) {
+    data_full_wnd[i] = (char)i;
+  }
+
+  /* initialize local vars */
+  memset(&netif, 0, sizeof(netif));
+  IP4_ADDR(&local_ip, 192, 168, 1, 1);
+  IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, NULL, &local_ip, &netmask);
+  /* initialize counter struct */
+  memset(&counters, 0, sizeof(counters));
+  counters.expected_data_len = TCP_WND;
+  counters.expected_data = data_full_wnd;
+
+  /* create and initialize the pcb */
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+  pcb->rcv_nxt = 0x8000;
+
+  /* don't 'recv' the first segment (1 byte) so that all other segments will be ooseq */
+
+  /* create segments and 'recv' them */
+  for(i = 1; i <= TCP_OOSEQ_MAX_PBUFS; i++) {
+    int count;
+    struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[i],
+                                           1, i, 0, TCP_ACK);
+    EXPECT_RET(p != NULL);
+    EXPECT_RET(p->next == NULL);
+    /* pass the segment to tcp_input */
+    test_tcp_input(p, &netif);
+    /* check if counters are as expected */
+    EXPECT(counters.close_calls == 0);
+    EXPECT(counters.recv_calls == 0);
+    EXPECT(counters.recved_bytes == 0);
+    EXPECT(counters.err_calls == 0);
+    /* check ooseq queue */
+    count = tcp_oos_pbuf_count(pcb);
+    EXPECT_OOSEQ(count == i);
+    datalen = tcp_oos_tcplen(pcb);
+    EXPECT_OOSEQ(datalen == i);
+  }
+
+  /* pass in one more segment, overrunning the limit */
+  p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[i+1], 1, i+1, 0, TCP_ACK);
+  EXPECT_RET(p_ovr != NULL);
+  /* pass the segment to tcp_input */
+  test_tcp_input(p_ovr, &netif);
+  /* check if counters are as expected */
+  EXPECT(counters.close_calls == 0);
+  EXPECT(counters.recv_calls == 0);
+  EXPECT(counters.recved_bytes == 0);
+  EXPECT(counters.err_calls == 0);
+  /* check ooseq queue (ensure the new segment was not accepted) */
+  EXPECT_OOSEQ(tcp_oos_count(pcb) == (i-1));
+  datalen2 = tcp_oos_tcplen(pcb);
+  EXPECT_OOSEQ(datalen2 == (i-1));
+
+  /* make sure the pcb is freed */
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+#endif /* TCP_OOSEQ_MAX_PBUFS && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) */
+  LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+static void
+check_rx_counters(struct tcp_pcb *pcb, struct test_tcp_counters *counters, u32_t exp_close_calls, u32_t exp_rx_calls,
+                  u32_t exp_rx_bytes, u32_t exp_err_calls, int exp_oos_count, int exp_oos_len)
+{
+  int oos_len;
+  EXPECT(counters->close_calls == exp_close_calls);
+  EXPECT(counters->recv_calls == exp_rx_calls);
+  EXPECT(counters->recved_bytes == exp_rx_bytes);
+  EXPECT(counters->err_calls == exp_err_calls);
+  /* check that pbuf is queued in ooseq */
+  EXPECT_OOSEQ(tcp_oos_count(pcb) == exp_oos_count);
+  oos_len = tcp_oos_tcplen(pcb);
+  EXPECT_OOSEQ(exp_oos_len == oos_len);
+}
+
+/* this test uses 4 packets:
+ * - data (len=TCP_MSS)
+ * - FIN
+ * - data after FIN (len=1) (invalid)
+ * - 2nd FIN (invalid)
+ *
+ * the parameter 'delay_packet' is a bitmask that choses which on these packets is ooseq
+ */
+static void test_tcp_recv_ooseq_double_FINs(int delay_packet)
+{
+  int i, k;
+  struct test_tcp_counters counters;
+  struct tcp_pcb* pcb;
+  struct pbuf *p_normal_fin, *p_data_after_fin, *p, *p_2nd_fin_ooseq;
+  ip_addr_t remote_ip, local_ip, netmask;
+  u16_t remote_port = 0x100, local_port = 0x101;
+  struct netif netif;
+  u32_t exp_rx_calls = 0, exp_rx_bytes = 0, exp_close_calls = 0, exp_oos_pbufs = 0, exp_oos_tcplen = 0;
+  int first_dropped = 0xff;
+  int last_dropped = 0;
+
+  for(i = 0; i < sizeof(data_full_wnd); i++) {
+    data_full_wnd[i] = (char)i;
+  }
+
+  /* initialize local vars */
+  memset(&netif, 0, sizeof(netif));
+  IP4_ADDR(&local_ip, 192, 168, 1, 1);
+  IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+  IP4_ADDR(&netmask,   255, 255, 255, 0);
+  test_tcp_init_netif(&netif, NULL, &local_ip, &netmask);
+  /* initialize counter struct */
+  memset(&counters, 0, sizeof(counters));
+  counters.expected_data_len = TCP_WND;
+  counters.expected_data = data_full_wnd;
+
+  /* create and initialize the pcb */
+  pcb = test_tcp_new_counters_pcb(&counters);
+  EXPECT_RET(pcb != NULL);
+  tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, remote_port);
+  pcb->rcv_nxt = 0x8000;
+
+  /* create segments */
+  p = tcp_create_rx_segment(pcb, &data_full_wnd[0], TCP_MSS, 0, 0, TCP_ACK);
+  p_normal_fin = tcp_create_rx_segment(pcb, NULL, 0, TCP_MSS, 0, TCP_ACK|TCP_FIN);
+  k = 1;
+  p_data_after_fin = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS+1], k, TCP_MSS+1, 0, TCP_ACK);
+  p_2nd_fin_ooseq = tcp_create_rx_segment(pcb, NULL, 0, TCP_MSS+1+k, 0, TCP_ACK|TCP_FIN);
+
+  if(delay_packet & 1) {
+    /* drop normal data */
+    first_dropped = 1;
+    last_dropped = 1;
+  } else {
+    /* send normal data */
+    test_tcp_input(p, &netif);
+    exp_rx_calls++;
+    exp_rx_bytes += TCP_MSS;
+  }
+  /* check if counters are as expected */
+  check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+  if(delay_packet & 2) {
+    /* drop FIN */
+    if(first_dropped > 2) {
+      first_dropped = 2;
+    }
+    last_dropped = 2;
+  } else {
+    /* send FIN */
+    test_tcp_input(p_normal_fin, &netif);
+    if (first_dropped < 2) {
+      /* already dropped packets, this one is ooseq */
+      exp_oos_pbufs++;
+      exp_oos_tcplen++;
+    } else {
+      /* inseq */
+      exp_close_calls++;
+    }
+  }
+  /* check if counters are as expected */
+  check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+  if(delay_packet & 4) {
+    /* drop data-after-FIN */
+    if(first_dropped > 3) {
+      first_dropped = 3;
+    }
+    last_dropped = 3;
+  } else {
+    /* send data-after-FIN */
+    test_tcp_input(p_data_after_fin, &netif);
+    if (first_dropped < 3) {
+      /* already dropped packets, this one is ooseq */
+      if (delay_packet & 2) {
+        /* correct FIN was ooseq */
+        exp_oos_pbufs++;
+        exp_oos_tcplen += k;
+      }
+    } else {
+      /* inseq: no change */
+    }
+  }
+  /* check if counters are as expected */
+  check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+  if(delay_packet & 8) {
+    /* drop 2nd-FIN */
+    if(first_dropped > 4) {
+      first_dropped = 4;
+    }
+    last_dropped = 4;
+  } else {
+    /* send 2nd-FIN */
+    test_tcp_input(p_2nd_fin_ooseq, &netif);
+    if (first_dropped < 3) {
+      /* already dropped packets, this one is ooseq */
+      if (delay_packet & 2) {
+        /* correct FIN was ooseq */
+        exp_oos_pbufs++;
+        exp_oos_tcplen++;
+      }
+    } else {
+      /* inseq: no change */
+    }
+  }
+  /* check if counters are as expected */
+  check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+  if(delay_packet & 1) {
+    /* dropped normal data before */
+    test_tcp_input(p, &netif);
+    exp_rx_calls++;
+    exp_rx_bytes += TCP_MSS;
+    if((delay_packet & 2) == 0) {
+      /* normal FIN was NOT delayed */
+      exp_close_calls++;
+      exp_oos_pbufs = exp_oos_tcplen = 0;
+    }
+  }
+  /* check if counters are as expected */
+  check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+  if(delay_packet & 2) {
+    /* dropped normal FIN before */
+    test_tcp_input(p_normal_fin, &netif);
+    exp_close_calls++;
+    exp_oos_pbufs = exp_oos_tcplen = 0;
+  }
+  /* check if counters are as expected */
+  check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+  if(delay_packet & 4) {
+    /* dropped data-after-FIN before */
+    test_tcp_input(p_data_after_fin, &netif);
+  }
+  /* check if counters are as expected */
+  check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+  if(delay_packet & 8) {
+    /* dropped 2nd-FIN before */
+    test_tcp_input(p_2nd_fin_ooseq, &netif);
+  }
+  /* check if counters are as expected */
+  check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+  /* check that ooseq data has been dumped */
+  EXPECT(pcb->ooseq == NULL);
+
+  /* make sure the pcb is freed */
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+  tcp_abort(pcb);
+  EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+}
+
+/** create multiple segments and pass them to tcp_input with the first segment missing
+ * to simulate overruning the rxwin with ooseq queueing enabled */
+#define FIN_TEST(name, num) \
+  START_TEST(name) \
+  { \
+    LWIP_UNUSED_ARG(_i); \
+    test_tcp_recv_ooseq_double_FINs(num); \
+  } \
+  END_TEST
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_0, 0)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_1, 1)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_2, 2)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_3, 3)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_4, 4)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_5, 5)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_6, 6)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_7, 7)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_8, 8)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_9, 9)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_10, 10)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_11, 11)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_12, 12)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_13, 13)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_14, 14)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_15, 15)
+
+
+/** Create the suite including all tests for this module */
+Suite *
+tcp_oos_suite(void)
+{
+  TFun tests[] = {
+    test_tcp_recv_ooseq_FIN_OOSEQ,
+    test_tcp_recv_ooseq_FIN_INSEQ,
+    test_tcp_recv_ooseq_overrun_rxwin,
+    test_tcp_recv_ooseq_max_bytes,
+    test_tcp_recv_ooseq_max_pbufs,
+    test_tcp_recv_ooseq_double_FIN_0,
+    test_tcp_recv_ooseq_double_FIN_1,
+    test_tcp_recv_ooseq_double_FIN_2,
+    test_tcp_recv_ooseq_double_FIN_3,
+    test_tcp_recv_ooseq_double_FIN_4,
+    test_tcp_recv_ooseq_double_FIN_5,
+    test_tcp_recv_ooseq_double_FIN_6,
+    test_tcp_recv_ooseq_double_FIN_7,
+    test_tcp_recv_ooseq_double_FIN_8,
+    test_tcp_recv_ooseq_double_FIN_9,
+    test_tcp_recv_ooseq_double_FIN_10,
+    test_tcp_recv_ooseq_double_FIN_11,
+    test_tcp_recv_ooseq_double_FIN_12,
+    test_tcp_recv_ooseq_double_FIN_13,
+    test_tcp_recv_ooseq_double_FIN_14,
+    test_tcp_recv_ooseq_double_FIN_15
+  };
+  return create_suite("TCP_OOS", tests, sizeof(tests)/sizeof(TFun), tcp_oos_setup, tcp_oos_teardown);
+}
diff --git a/external/badvpn_dns/lwip/test/unit/tcp/test_tcp_oos.h b/external/badvpn_dns/lwip/test/unit/tcp/test_tcp_oos.h
new file mode 100644
index 0000000..5e411f0
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/tcp/test_tcp_oos.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_TCP_OOS_H__
+#define __TEST_TCP_OOS_H__
+
+#include "../lwip_check.h"
+
+Suite *tcp_oos_suite(void);
+
+#endif
diff --git a/external/badvpn_dns/lwip/test/unit/udp/test_udp.c b/external/badvpn_dns/lwip/test/unit/udp/test_udp.c
new file mode 100644
index 0000000..a2f02af
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/udp/test_udp.c
@@ -0,0 +1,68 @@
+#include "test_udp.h"
+
+#include "lwip/udp.h"
+#include "lwip/stats.h"
+
+#if !LWIP_STATS || !UDP_STATS || !MEMP_STATS
+#error "This tests needs UDP- and MEMP-statistics enabled"
+#endif
+
+/* Helper functions */
+static void
+udp_remove_all(void)
+{
+  struct udp_pcb *pcb = udp_pcbs;
+  struct udp_pcb *pcb2;
+
+  while(pcb != NULL) {
+    pcb2 = pcb;
+    pcb = pcb->next;
+    udp_remove(pcb2);
+  }
+  fail_unless(lwip_stats.memp[MEMP_UDP_PCB].used == 0);
+}
+
+/* Setups/teardown functions */
+
+static void
+udp_setup(void)
+{
+  udp_remove_all();
+}
+
+static void
+udp_teardown(void)
+{
+  udp_remove_all();
+}
+
+
+/* Test functions */
+
+START_TEST(test_udp_new_remove)
+{
+  struct udp_pcb* pcb;
+  LWIP_UNUSED_ARG(_i);
+
+  fail_unless(lwip_stats.memp[MEMP_UDP_PCB].used == 0);
+
+  pcb = udp_new();
+  fail_unless(pcb != NULL);
+  if (pcb != NULL) {
+    fail_unless(lwip_stats.memp[MEMP_UDP_PCB].used == 1);
+    udp_remove(pcb);
+    fail_unless(lwip_stats.memp[MEMP_UDP_PCB].used == 0);
+  }
+}
+END_TEST
+
+
+/** Create the suite including all tests for this module */
+Suite *
+udp_suite(void)
+{
+  TFun tests[] = {
+    test_udp_new_remove,
+  };
+  return create_suite("UDP", tests, sizeof(tests)/sizeof(TFun), udp_setup, udp_teardown);
+}
diff --git a/external/badvpn_dns/lwip/test/unit/udp/test_udp.h b/external/badvpn_dns/lwip/test/unit/udp/test_udp.h
new file mode 100644
index 0000000..9335368
--- /dev/null
+++ b/external/badvpn_dns/lwip/test/unit/udp/test_udp.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_UDP_H__
+#define __TEST_UDP_H__
+
+#include "../lwip_check.h"
+
+Suite* udp_suite(void);
+
+#endif
diff --git a/external/badvpn_dns/misc/BRefTarget.h b/external/badvpn_dns/misc/BRefTarget.h
new file mode 100644
index 0000000..4324605
--- /dev/null
+++ b/external/badvpn_dns/misc/BRefTarget.h
@@ -0,0 +1,114 @@
+/**
+ * @file BRefTarget.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_B_REF_TARGET_H
+#define BADVPN_B_REF_TARGET_H
+
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+
+/**
+ * Represents a reference-counted object.
+ */
+typedef struct BRefTarget_s BRefTarget;
+
+/**
+ * Callback function called after the reference count of a {@link BRefTarget}
+ * reaches has reached zero. At this point the BRefTarget object has already
+ * been invalidated, i.e. {@link BRefTarget_Ref} must not be called on this
+ * object after this handler is called.
+ */
+typedef void (*BRefTarget_func_release) (BRefTarget *o);
+
+struct BRefTarget_s {
+    BRefTarget_func_release func_release;
+    int refcnt;
+    DebugObject d_obj;
+};
+
+/**
+ * Initializes a reference target object. The initial reference count of the object
+ * is 1. The \a func_release argument specifies the function to be called from
+ * {@link BRefTarget_Deref} when the reference count reaches zero.
+ */
+static void BRefTarget_Init (BRefTarget *o, BRefTarget_func_release func_release);
+
+/**
+ * Decrements the reference count of a reference target object. If the reference
+ * count has reached zero, the object's {@link BRefTarget_func_release} function
+ * is called, and the object is considered destroyed.
+ */
+static void BRefTarget_Deref (BRefTarget *o);
+
+/**
+ * Increments the reference count of a reference target object.
+ * Returns 1 on success and 0 on failure.
+ */
+static int BRefTarget_Ref (BRefTarget *o) WARN_UNUSED;
+
+static void BRefTarget_Init (BRefTarget *o, BRefTarget_func_release func_release)
+{
+    ASSERT(func_release)
+    
+    o->func_release = func_release;
+    o->refcnt = 1;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+static void BRefTarget_Deref (BRefTarget *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->refcnt > 0)
+    
+    o->refcnt--;
+    
+    if (o->refcnt == 0) {
+        DebugObject_Free(&o->d_obj);
+        o->func_release(o);
+    }
+}
+
+static int BRefTarget_Ref (BRefTarget *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->refcnt > 0)
+    
+    if (o->refcnt == INT_MAX) {
+        return 0;
+    }
+    
+    o->refcnt++;
+    
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/Utf16Decoder.h b/external/badvpn_dns/misc/Utf16Decoder.h
new file mode 100644
index 0000000..819ac94
--- /dev/null
+++ b/external/badvpn_dns/misc/Utf16Decoder.h
@@ -0,0 +1,113 @@
+/**
+ * @file Utf16Decoder.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_UTF16DECODER_H
+#define BADVPN_UTF16DECODER_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+
+/**
+ * Decodes UTF-16 data into Unicode characters.
+ */
+typedef struct {
+    int cont;
+    uint32_t ch;
+} Utf16Decoder;
+
+/**
+ * Initializes the UTF-16 decoder.
+ * 
+ * @param o the object
+ */
+static void Utf16Decoder_Init (Utf16Decoder *o);
+
+/**
+ * Inputs a 16-bit value to the decoder.
+ * 
+ * @param o the object
+ * @param b 16-bit value to input
+ * @param out_ch will receive a Unicode character if this function returns 1.
+ *               If written, the character will be in the range 0 - 0x10FFFF,
+ *               excluding the surrogate range 0xD800 - 0xDFFF.
+ * @return 1 if a Unicode character has been written to *out_ch, 0 if not
+ */
+static int Utf16Decoder_Input (Utf16Decoder *o, uint16_t b, uint32_t *out_ch);
+
+void Utf16Decoder_Init (Utf16Decoder *o)
+{
+    o->cont = 0;
+}
+
+int Utf16Decoder_Input (Utf16Decoder *o, uint16_t b, uint32_t *out_ch)
+{
+    // high surrogate
+    if (b >= UINT16_C(0xD800) && b <= UINT16_C(0xDBFF)) {
+        // set continuation state
+        o->cont = 1;
+        
+        // add high bits
+        o->ch = (uint32_t)(b - UINT16_C(0xD800)) << 10;
+        
+        return 0;
+    }
+    
+    // low surrogate
+    if (b >= UINT16_C(0xDC00) && b <= UINT16_C(0xDFFF)) {
+        // check continuation
+        if (!o->cont) {
+            return 0;
+        }
+        
+        // add low bits
+        o->ch |= (b - UINT16_C(0xDC00));
+        
+        // reset state
+        o->cont = 0;
+        
+        // don't report surrogates
+        if (o->ch >= UINT32_C(0xD800) && o->ch <= UINT32_C(0xDFFF)) {
+            return 0;
+        }
+        
+        // return character
+        *out_ch = o->ch;
+        return 1;
+    }
+    
+    // reset state
+    o->cont = 0;
+    
+    // return character
+    *out_ch = b;
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/Utf16Encoder.h b/external/badvpn_dns/misc/Utf16Encoder.h
new file mode 100644
index 0000000..4900b42
--- /dev/null
+++ b/external/badvpn_dns/misc/Utf16Encoder.h
@@ -0,0 +1,67 @@
+/**
+ * @file Utf16Encoder.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_UTF16ENCODER_H
+#define BADVPN_UTF16ENCODER_H
+
+#include <stdint.h>
+
+/**
+ * Encodes a Unicode character into a sequence of 16-bit values according to UTF-16.
+ * 
+ * @param ch Unicode character to encode
+ * @param out will receive the encoded 16-bit values. Must have space for 2 values.
+ * @return number of 16-bit values written, 0-2, with 0 meaning the character cannot
+ *         be encoded
+ */
+static int Utf16Encoder_EncodeCharacter (uint32_t ch, uint16_t *out);
+
+int Utf16Encoder_EncodeCharacter (uint32_t ch, uint16_t *out)
+{
+    if (ch <= UINT32_C(0xFFFF)) {
+        // surrogates
+        if (ch >= UINT32_C(0xD800) && ch <= UINT32_C(0xDFFF)) {
+            return 0;
+        }
+        
+        out[0] = ch;
+        return 1;
+    }
+    
+    if (ch <= UINT32_C(0x10FFFF)) {
+        uint32_t x = ch - UINT32_C(0x10000);
+        out[0] = UINT32_C(0xD800) + (x >> 10);
+        out[1] = UINT32_C(0xDC00) + (x & UINT32_C(0x3FF));
+        return 2;
+    }
+    
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/Utf8Decoder.h b/external/badvpn_dns/misc/Utf8Decoder.h
new file mode 100644
index 0000000..c6412b1
--- /dev/null
+++ b/external/badvpn_dns/misc/Utf8Decoder.h
@@ -0,0 +1,143 @@
+/**
+ * @file Utf8Decoder.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_UTF8DECODER_H
+#define BADVPN_UTF8DECODER_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+
+/**
+ * Decodes UTF-8 data into Unicode characters.
+ */
+typedef struct {
+    int bytes;
+    int pos;
+    uint32_t ch;
+} Utf8Decoder;
+
+/**
+ * Initializes the UTF-8 decoder.
+ * 
+ * @param o the object
+ */
+static void Utf8Decoder_Init (Utf8Decoder *o);
+
+/**
+ * Inputs a byte to the decoder.
+ * 
+ * @param o the object
+ * @param b byte to input
+ * @param out_ch will receive a Unicode character if this function returns 1.
+ *               If written, the character will be in the range 0 - 0x10FFFF,
+ *               excluding the surrogate range 0xD800 - 0xDFFF.
+ * @return 1 if a Unicode character has been written to *out_ch, 0 if not
+ */
+static int Utf8Decoder_Input (Utf8Decoder *o, uint8_t b, uint32_t *out_ch);
+
+void Utf8Decoder_Init (Utf8Decoder *o)
+{
+    o->bytes = 0;
+}
+
+int Utf8Decoder_Input (Utf8Decoder *o, uint8_t b, uint32_t *out_ch)
+{
+    // one-byte character
+    if ((b & 128) == 0) {
+        o->bytes = 0;
+        *out_ch = b;
+        return 1;
+    }
+    
+    // start of two-byte character
+    if ((b & 224) == 192) {
+        o->bytes = 2;
+        o->pos = 1;
+        o->ch = (uint32_t)(b & 31) << 6;
+        return 0;
+    }
+    
+    // start of three-byte character
+    if ((b & 240) == 224) {
+        o->bytes = 3;
+        o->pos = 1;
+        o->ch = (uint32_t)(b & 15) << 12;
+        return 0;
+    }
+    
+    // start of four-byte character
+    if ((b & 248) == 240) {
+        o->bytes = 4;
+        o->pos = 1;
+        o->ch = (uint32_t)(b & 7) << 18;
+        return 0;
+    }
+    
+    // continuation of multi-byte character
+    if ((b & 192) == 128 && o->bytes > 0) {
+        ASSERT(o->bytes <= 4)
+        ASSERT(o->pos > 0)
+        ASSERT(o->pos < o->bytes)
+        
+        // add bits from this byte
+        o->ch |= (uint32_t)(b & 63) << (6 * (o->bytes - o->pos - 1));
+        
+        // end of multi-byte character?
+        if (o->pos == o->bytes - 1) {
+            // reset state
+            o->bytes = 0;
+            
+            // don't report out-of-range characters
+            if (o->ch > UINT32_C(0x10FFFF)) {
+                return 0;
+            }
+            
+            // don't report surrogates
+            if (o->ch >= UINT32_C(0xD800) && o->ch <= UINT32_C(0xDFFF)) {
+                return 0;
+            }
+            
+            *out_ch = o->ch;
+            return 1;
+        }
+        
+        // increment byte index
+        o->pos++;
+        
+        return 0;
+    }
+    
+    // error, reset state
+    o->bytes = 0;
+    
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/Utf8Encoder.h b/external/badvpn_dns/misc/Utf8Encoder.h
new file mode 100644
index 0000000..00eb5e6
--- /dev/null
+++ b/external/badvpn_dns/misc/Utf8Encoder.h
@@ -0,0 +1,81 @@
+/**
+ * @file Utf8Encoder.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_UTF8ENCODER_H
+#define BADVPN_UTF8ENCODER_H
+
+#include <stdint.h>
+
+/**
+ * Encodes a Unicode character into a sequence of bytes according to UTF-8.
+ * 
+ * @param ch Unicode character to encode
+ * @param out will receive the encoded bytes. Must have space for 4 bytes.
+ * @return number of bytes written, 0-4, with 0 meaning the character cannot
+ *         be encoded
+ */
+static int Utf8Encoder_EncodeCharacter (uint32_t ch, uint8_t *out);
+
+int Utf8Encoder_EncodeCharacter (uint32_t ch, uint8_t *out)
+{
+    if (ch <= UINT32_C(0x007F)) {
+        out[0] = ch;
+        return 1;
+    }
+    
+    if (ch <= UINT32_C(0x07FF)) {
+        out[0] = (0xC0 | (ch >> 6));
+        out[1] = (0x80 | ((ch >> 0) & 0x3F));
+        return 2;
+    }
+    
+    if (ch <= UINT32_C(0xFFFF)) {
+        // surrogates
+        if (ch >= UINT32_C(0xD800) && ch <= UINT32_C(0xDFFF)) {
+            return 0;
+        }
+        
+        out[0] = (0xE0 | (ch >> 12));
+        out[1] = (0x80 | ((ch >> 6) & 0x3F));
+        out[2] = (0x80 | ((ch >> 0) & 0x3F));
+        return 3;
+    }
+    
+    if (ch < UINT32_C(0x10FFFF)) {
+        out[0] = (0xF0 | (ch >> 18));
+        out[1] = (0x80 | ((ch >> 12) & 0x3F));
+        out[2] = (0x80 | ((ch >> 6) & 0x3F));
+        out[3] = (0x80 | ((ch >> 0) & 0x3F));
+        return 4;
+    }
+    
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/arp_proto.h b/external/badvpn_dns/misc/arp_proto.h
new file mode 100644
index 0000000..71a6c98
--- /dev/null
+++ b/external/badvpn_dns/misc/arp_proto.h
@@ -0,0 +1,60 @@
+/**
+ * @file arp_proto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 the ARP protocol.
+ */
+
+#ifndef BADVPN_ARP_PROTO_H
+#define BADVPN_ARP_PROTO_H
+
+#include <stdint.h>
+
+#include <misc/packed.h>
+
+#define ARP_HARDWARE_TYPE_ETHERNET 1
+
+#define ARP_OPCODE_REQUEST 1
+#define ARP_OPCODE_REPLY 2
+
+B_START_PACKED
+struct arp_packet {
+    uint16_t hardware_type;
+    uint16_t protocol_type;
+    uint8_t hardware_size;
+    uint8_t protocol_size;
+    uint16_t opcode;
+    uint8_t sender_mac[6];
+    uint32_t sender_ip;
+    uint8_t target_mac[6];
+    uint32_t target_ip;
+} B_PACKED;
+B_END_PACKED
+
+#endif
diff --git a/external/badvpn_dns/misc/array_length.h b/external/badvpn_dns/misc/array_length.h
new file mode 100644
index 0000000..15de964
--- /dev/null
+++ b/external/badvpn_dns/misc/array_length.h
@@ -0,0 +1,35 @@
+/**
+ * @file array_length.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_ARRAY_LENGTH
+#define BADVPN_ARRAY_LENGTH
+
+#define B_ARRAY_LENGTH(arr) (sizeof((arr)) / sizeof((arr)[0]))
+
+#endif
diff --git a/external/badvpn_dns/misc/balign.h b/external/badvpn_dns/misc/balign.h
new file mode 100644
index 0000000..57152af
--- /dev/null
+++ b/external/badvpn_dns/misc/balign.h
@@ -0,0 +1,76 @@
+/**
+ * @file balign.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Integer alignment macros.
+ */
+
+#ifndef BADVPN_MISC_BALIGN_H
+#define BADVPN_MISC_BALIGN_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * Checks if aligning x up to n would overflow.
+ */
+static int balign_up_overflows (size_t x, size_t n)
+{
+    size_t r = x % n;
+    
+    return (r && x > SIZE_MAX - (n - r));
+}
+
+/**
+ * Aligns x up to n.
+ */
+static size_t balign_up (size_t x, size_t n)
+{
+    size_t r = x % n;
+    return (r ? x + (n - r) : x);
+}
+
+/**
+ * Aligns x down to n.
+ */
+static size_t balign_down (size_t x, size_t n)
+{
+    return (x - (x % n));
+}
+
+/**
+ * Calculates the quotient of a and b, rounded up.
+ */
+static size_t bdivide_up (size_t a, size_t b)
+{
+    size_t r = a % b;
+    return (r > 0 ? a / b + 1 : a / b);
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/balloc.h b/external/badvpn_dns/misc/balloc.h
new file mode 100644
index 0000000..7d2d54f
--- /dev/null
+++ b/external/badvpn_dns/misc/balloc.h
@@ -0,0 +1,248 @@
+/**
+ * @file balloc.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Memory allocation functions.
+ */
+
+#ifndef BADVPN_MISC_BALLOC_H
+#define BADVPN_MISC_BALLOC_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/bsize.h>
+#include <misc/maxalign.h>
+
+/**
+ * Allocates memory.
+ * 
+ * @param bytes number of bytes to allocate.
+ * @return a non-NULL pointer to the memory, or NULL on failure.
+ *         The memory allocated can be freed using {@link BFree}.
+ */
+static void * BAlloc (size_t bytes);
+
+/**
+ * Frees memory.
+ * 
+ * @param m memory to free. Must have been obtained with {@link BAlloc},
+ *          {@link BAllocArray}, or {@link BAllocArray2}. May be NULL;
+ *          in this case, this function does nothing.
+ */
+static void BFree (void *m);
+
+/**
+ * Changes the size of a memory block. On success, the memory block
+ * may be moved to a different address.
+ * 
+ * @param m pointer to a memory block obtained from {@link BAlloc}
+ *          or other functions in this group. If this is NULL, the
+ *          call is equivalent to {@link BAlloc}(bytes).
+ * @param bytes new size of the memory block
+ * @return new pointer to the memory block, or NULL on failure
+ */
+static void * BRealloc (void *m, size_t bytes);
+
+/**
+ * Allocates memory, with size given as a {@link bsize_t}.
+ * 
+ * @param bytes number of bytes to allocate. If the size is overflow,
+ *              this function will return NULL.
+ * @return a non-NULL pointer to the memory, or NULL on failure.
+ *         The memory allocated can be freed using {@link BFree}.
+ */
+static void * BAllocSize (bsize_t bytes);
+
+/**
+ * Allocates memory for an array.
+ * A check is first done to make sure the multiplication doesn't overflow;
+ * otherwise, this is equivalent to {@link BAlloc}(count * bytes).
+ * This may be slightly faster if 'bytes' is constant, because a division
+ * with 'bytes' is performed.
+ * 
+ * @param count number of elements.
+ * @param bytes size of one array element.
+ * @return a non-NULL pointer to the memory, or NULL on failure.
+ *         The memory allocated can be freed using {@link BFree}.
+ */
+static void * BAllocArray (size_t count, size_t bytes);
+
+/**
+ * Reallocates memory that was allocated using one of the allocation
+ * functions in this file. On success, the memory may be moved to a
+ * different address, leaving the old address invalid.
+ * 
+ * @param mem pointer to an existing memory block. May be NULL, in which
+ *            case this is equivalent to {@link BAllocArray}.
+ * @param count number of elements for reallocation
+ * @param bytes size of one array element for reallocation
+ * @return a non-NULL pointer to the address of the reallocated memory
+ *         block, or NULL on failure. On failure, the original memory
+ *         block is left intact.
+ */
+static void * BReallocArray (void *mem, size_t count, size_t bytes);
+
+/**
+ * Allocates memory for a two-dimensional array.
+ * 
+ * Checks are first done to make sure the multiplications don't overflow;
+ * otherwise, this is equivalent to {@link BAlloc}((count2 * (count1 * bytes)).
+ * 
+ * @param count2 number of elements in one dimension.
+ * @param count1 number of elements in the other dimension.
+ * @param bytes size of one array element.
+ * @return a non-NULL pointer to the memory, or NULL on failure.
+ *         The memory allocated can be freed using {@link BFree}.
+ */
+static void * BAllocArray2 (size_t count2, size_t count1, size_t bytes);
+
+/**
+ * Adds to a size_t with overflow detection.
+ * 
+ * @param s pointer to a size_t to add to
+ * @param add number to add
+ * @return 1 on success, 0 on failure
+ */
+static int BSizeAdd (size_t *s, size_t add);
+
+/**
+ * Aligns a size_t upwards with overflow detection.
+ * 
+ * @param s pointer to a size_t to align
+ * @param align alignment value. Must be >0.
+ * @return 1 on success, 0 on failure
+ */
+static int BSizeAlign (size_t *s, size_t align);
+
+void * BAlloc (size_t bytes)
+{
+    if (bytes == 0) {
+        return malloc(1);
+    }
+    
+    return malloc(bytes);
+}
+
+void BFree (void *m)
+{
+    free(m);
+}
+
+void * BRealloc (void *m, size_t bytes)
+{
+    if (bytes == 0) {
+        return realloc(m, 1);
+    }
+    
+    return realloc(m, bytes);
+}
+
+void * BAllocSize (bsize_t bytes)
+{
+    if (bytes.is_overflow) {
+        return NULL;
+    }
+    
+    return BAlloc(bytes.value);
+}
+
+void * BAllocArray (size_t count, size_t bytes)
+{
+    if (count == 0 || bytes == 0) {
+        return malloc(1);
+    }
+    
+    if (count > SIZE_MAX / bytes) {
+        return NULL;
+    }
+    
+    return BAlloc(count * bytes);
+}
+
+void * BReallocArray (void *mem, size_t count, size_t bytes)
+{
+    if (count == 0 || bytes == 0) {
+        return realloc(mem, 1);
+    }
+    
+    if (count > SIZE_MAX / bytes) {
+        return NULL;
+    }
+    
+    return realloc(mem, count * bytes);
+}
+
+void * BAllocArray2 (size_t count2, size_t count1, size_t bytes)
+{
+    if (count2 == 0 || count1 == 0 || bytes == 0) {
+        return malloc(1);
+    }
+    
+    if (count1 > SIZE_MAX / bytes) {
+        return NULL;
+    }
+    
+    if (count2 > SIZE_MAX / (count1 * bytes)) {
+        return NULL;
+    }
+    
+    return BAlloc(count2 * (count1 * bytes));
+}
+
+int BSizeAdd (size_t *s, size_t add)
+{
+    ASSERT(s)
+    
+    if (add > SIZE_MAX - *s) {
+        return 0;
+    }
+    *s += add;
+    return 1;
+}
+
+int BSizeAlign (size_t *s, size_t align)
+{
+    ASSERT(s)
+    ASSERT(align > 0)
+    
+    size_t mod = *s % align;
+    if (mod > 0) {
+        if (align - mod > SIZE_MAX - *s) {
+            return 0;
+        }
+        *s += align - mod;
+    }
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/blimits.h b/external/badvpn_dns/misc/blimits.h
new file mode 100644
index 0000000..8bcdc2a
--- /dev/null
+++ b/external/badvpn_dns/misc/blimits.h
@@ -0,0 +1,60 @@
+/**
+ * @file blimits.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_BLIMITS_H
+#define BADVPN_BLIMITS_H
+
+#include <stdint.h>
+
+#define BTYPE_IS_SIGNED(type) ((type)-1 < 0)
+
+#define BSIGNED_TYPE_MIN(type) ( \
+    sizeof(type) == 1 ? INT8_MIN : ( \
+    sizeof(type) == 2 ? INT16_MIN : ( \
+    sizeof(type) == 4 ? INT32_MIN : ( \
+    sizeof(type) == 8 ? INT64_MIN : 0))))
+
+#define BSIGNED_TYPE_MAX(type) ( \
+    sizeof(type) == 1 ? INT8_MAX : ( \
+    sizeof(type) == 2 ? INT16_MAX : ( \
+    sizeof(type) == 4 ? INT32_MAX : ( \
+    sizeof(type) == 8 ? INT64_MAX : 0))))
+
+#define BUNSIGNED_TYPE_MIN(type) ((type)0)
+
+#define BUNSIGNED_TYPE_MAX(type) ( \
+    sizeof(type) == 1 ? UINT8_MAX : ( \
+    sizeof(type) == 2 ? UINT16_MAX : ( \
+    sizeof(type) == 4 ? UINT32_MAX : ( \
+    sizeof(type) == 8 ? UINT64_MAX : 0))))
+
+#define BTYPE_MIN(type) (BTYPE_IS_SIGNED(type) ? BSIGNED_TYPE_MIN(type) : BUNSIGNED_TYPE_MIN(type))
+#define BTYPE_MAX(type) (BTYPE_IS_SIGNED(type) ? BSIGNED_TYPE_MAX(type) : BUNSIGNED_TYPE_MAX(type))
+
+#endif
diff --git a/external/badvpn_dns/misc/bsize.h b/external/badvpn_dns/misc/bsize.h
new file mode 100644
index 0000000..2d724df
--- /dev/null
+++ b/external/badvpn_dns/misc/bsize.h
@@ -0,0 +1,117 @@
+/**
+ * @file bsize.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Arithmetic with overflow detection.
+ */
+
+#ifndef BADVPN_MISC_BSIZE_H
+#define BADVPN_MISC_BSIZE_H
+
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+
+typedef struct {
+    int is_overflow;
+    size_t value;
+} bsize_t;
+
+static bsize_t bsize_fromsize (size_t v);
+static bsize_t bsize_fromint (int v);
+static bsize_t bsize_overflow (void);
+static int bsize_tosize (bsize_t s, size_t *out);
+static int bsize_toint (bsize_t s, int *out);
+static bsize_t bsize_add (bsize_t s1, bsize_t s2);
+static bsize_t bsize_max (bsize_t s1, bsize_t s2);
+static bsize_t bsize_mul (bsize_t s1, bsize_t s2);
+
+bsize_t bsize_fromsize (size_t v)
+{
+    bsize_t s = {0, v};
+    return s;
+}
+
+bsize_t bsize_fromint (int v)
+{
+    bsize_t s = {(v < 0 || v > SIZE_MAX), v};
+    return s;
+}
+
+static bsize_t bsize_overflow (void)
+{
+    bsize_t s = {1, 0};
+    return s;
+}
+
+int bsize_tosize (bsize_t s, size_t *out)
+{
+    if (s.is_overflow) {
+        return 0;
+    }
+    
+    if (out) {
+        *out = s.value;
+    }
+    
+    return 1;
+}
+
+int bsize_toint (bsize_t s, int *out)
+{
+    if (s.is_overflow || s.value > INT_MAX) {
+        return 0;
+    }
+    
+    if (out) {
+        *out = s.value;
+    }
+    
+    return 1;
+}
+
+bsize_t bsize_add (bsize_t s1, bsize_t s2)
+{
+    bsize_t s = {(s1.is_overflow || s2.is_overflow || s2.value > SIZE_MAX - s1.value), (s1.value + s2.value)};
+    return s;
+}
+
+bsize_t bsize_max (bsize_t s1, bsize_t s2)
+{
+    bsize_t s = {(s1.is_overflow || s2.is_overflow), ((s1.value > s2.value) * s1.value + (s1.value <= s2.value) * s2.value)};
+    return s;
+}
+
+bsize_t bsize_mul (bsize_t s1, bsize_t s2)
+{
+    bsize_t s = {(s1.is_overflow || s2.is_overflow || (s1.value != 0 && s2.value > SIZE_MAX / s1.value)), (s1.value * s2.value)};
+    return s;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/bsort.h b/external/badvpn_dns/misc/bsort.h
new file mode 100644
index 0000000..24d7a66
--- /dev/null
+++ b/external/badvpn_dns/misc/bsort.h
@@ -0,0 +1,69 @@
+/**
+ * @file bsort.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Sorting functions.
+ */
+
+#ifndef BADVPN_MISC_BSORT_H
+#define BADVPN_MISC_BSORT_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+
+typedef int (*BSort_comparator) (const void *e1, const void *e2);
+
+static void BInsertionSort (void *arr, size_t count, size_t esize, BSort_comparator compatator, void *temp);
+
+void BInsertionSort (void *arr, size_t count, size_t esize, BSort_comparator compatator, void *temp)
+{
+    ASSERT(esize > 0)
+    
+    for (size_t i = 0; i < count; i++) {
+        size_t j = i;
+        while (j > 0) {
+            uint8_t *x = (uint8_t *)arr + (j - 1) * esize;
+            uint8_t *y = (uint8_t *)arr + j * esize;
+            int c = compatator(x, y);
+            if (c <= 0) {
+                break;
+            }
+            memcpy(temp, x, esize);
+            memcpy(x, y, esize);
+            memcpy(y, temp, esize);
+            j--;
+        }
+    }
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/bstring.h b/external/badvpn_dns/misc/bstring.h
new file mode 100644
index 0000000..caef106
--- /dev/null
+++ b/external/badvpn_dns/misc/bstring.h
@@ -0,0 +1,140 @@
+/**
+ * @file bstring.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_BSTRING_H
+#define BADVPN_BSTRING_H
+
+#include <misc/debug.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#define BSTRING_TYPE_STATIC 5
+#define BSTRING_TYPE_DYNAMIC 7
+#define BSTRING_TYPE_EXTERNAL 11
+
+#define BSTRING_STATIC_SIZE 23
+#define BSTRING_STATIC_MAX (BSTRING_STATIC_SIZE - 1)
+
+typedef struct {
+    union {
+        struct {
+            char type;
+            char static_string[BSTRING_STATIC_SIZE];
+        } us;
+        struct {
+            char type;
+            char *dynamic_string;
+        } ud;
+        struct {
+            char type;
+            const char *external_string;
+        } ue;
+    } u;
+} BString;
+
+static void BString__assert (BString *o)
+{
+    switch (o->u.us.type) {
+        case BSTRING_TYPE_STATIC:
+        case BSTRING_TYPE_DYNAMIC:
+        case BSTRING_TYPE_EXTERNAL:
+            return;
+    }
+    
+    ASSERT(0);
+}
+
+static int BString_Init (BString *o, const char *str)
+{
+    if (strlen(str) <= BSTRING_STATIC_MAX) {
+        strcpy(o->u.us.static_string, str);
+        o->u.us.type = BSTRING_TYPE_STATIC;
+    } else {
+        if (!(o->u.ud.dynamic_string = malloc(strlen(str) + 1))) {
+            return 0;
+        }
+        strcpy(o->u.ud.dynamic_string, str);
+        o->u.ud.type = BSTRING_TYPE_DYNAMIC;
+    }
+    
+    BString__assert(o);
+    return 1;
+}
+
+static void BString_InitStatic (BString *o, const char *str)
+{
+    ASSERT(strlen(str) <= BSTRING_STATIC_MAX)
+    
+    strcpy(o->u.us.static_string, str);
+    o->u.us.type = BSTRING_TYPE_STATIC;
+    
+    BString__assert(o);
+}
+
+static void BString_InitExternal (BString *o, const char *str)
+{
+    o->u.ue.external_string = str;
+    o->u.ue.type = BSTRING_TYPE_EXTERNAL;
+    
+    BString__assert(o);
+}
+
+static void BString_InitAllocated (BString *o, char *str)
+{
+    o->u.ud.dynamic_string = str;
+    o->u.ud.type = BSTRING_TYPE_DYNAMIC;
+    
+    BString__assert(o);
+}
+
+static void BString_Free (BString *o)
+{
+    BString__assert(o);
+    
+    if (o->u.ud.type == BSTRING_TYPE_DYNAMIC) {
+        free(o->u.ud.dynamic_string);
+    }
+}
+
+static const char * BString_Get (BString *o)
+{
+    BString__assert(o);
+    
+    switch (o->u.us.type) {
+        case BSTRING_TYPE_STATIC: return o->u.us.static_string;
+        case BSTRING_TYPE_DYNAMIC: return o->u.ud.dynamic_string;
+        case BSTRING_TYPE_EXTERNAL: return o->u.ue.external_string;
+    }
+    
+    ASSERT(0);
+    return NULL;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/byteorder.h b/external/badvpn_dns/misc/byteorder.h
new file mode 100644
index 0000000..055b0a5
--- /dev/null
+++ b/external/badvpn_dns/misc/byteorder.h
@@ -0,0 +1,196 @@
+/**
+ * @file byteorder.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Byte order conversion functions.
+ * 
+ * hton* functions convert from host to big-endian (network) byte order.
+ * htol* functions convert from host to little-endian byte order.
+ * ntoh* functions convert from big-endian (network) to host byte order.
+ * ltoh* functions convert from little-endian to host byte order.
+ */
+
+#ifndef BADVPN_MISC_BYTEORDER_H
+#define BADVPN_MISC_BYTEORDER_H
+
+#if (defined(BADVPN_LITTLE_ENDIAN) + defined(BADVPN_BIG_ENDIAN)) != 1
+#error Unknown byte order or too many byte orders
+#endif
+
+#include <stdint.h>
+
+static uint16_t badvpn_reverse16 (uint16_t x)
+{
+    uint16_t y;
+    *((uint8_t *)&y+0) = *((uint8_t *)&x+1);
+    *((uint8_t *)&y+1) = *((uint8_t *)&x+0);
+    return y;
+}
+
+static uint32_t badvpn_reverse32 (uint32_t x)
+{
+    uint32_t y;
+    *((uint8_t *)&y+0) = *((uint8_t *)&x+3);
+    *((uint8_t *)&y+1) = *((uint8_t *)&x+2);
+    *((uint8_t *)&y+2) = *((uint8_t *)&x+1);
+    *((uint8_t *)&y+3) = *((uint8_t *)&x+0);
+    return y;
+}
+
+static uint64_t badvpn_reverse64 (uint64_t x)
+{
+    uint64_t y;
+    *((uint8_t *)&y+0) = *((uint8_t *)&x+7);
+    *((uint8_t *)&y+1) = *((uint8_t *)&x+6);
+    *((uint8_t *)&y+2) = *((uint8_t *)&x+5);
+    *((uint8_t *)&y+3) = *((uint8_t *)&x+4);
+    *((uint8_t *)&y+4) = *((uint8_t *)&x+3);
+    *((uint8_t *)&y+5) = *((uint8_t *)&x+2);
+    *((uint8_t *)&y+6) = *((uint8_t *)&x+1);
+    *((uint8_t *)&y+7) = *((uint8_t *)&x+0);
+    return y;
+}
+
+static uint8_t hton8 (uint8_t x)
+{
+    return x;
+}
+
+static uint8_t htol8 (uint8_t x)
+{
+    return x;
+}
+
+#if defined(BADVPN_LITTLE_ENDIAN)
+
+static uint16_t hton16 (uint16_t x)
+{
+    return badvpn_reverse16(x);
+}
+
+static uint32_t hton32 (uint32_t x)
+{
+    return badvpn_reverse32(x);
+}
+
+static uint64_t hton64 (uint64_t x)
+{
+    return badvpn_reverse64(x);
+}
+
+static uint16_t htol16 (uint16_t x)
+{
+    return x;
+}
+
+static uint32_t htol32 (uint32_t x)
+{
+    return x;
+}
+
+static uint64_t htol64 (uint64_t x)
+{
+    return x;
+}
+
+#elif defined(BADVPN_BIG_ENDIAN)
+
+static uint16_t hton16 (uint16_t x)
+{
+    return x;
+}
+
+static uint32_t hton32 (uint32_t x)
+{
+    return x;
+}
+
+static uint64_t hton64 (uint64_t x)
+{
+    return x;
+}
+
+static uint16_t htol16 (uint16_t x)
+{
+    return badvpn_reverse16(x);
+}
+
+static uint32_t htol32 (uint32_t x)
+{
+    return badvpn_reverse32(x);
+}
+
+static uint64_t htol64 (uint64_t x)
+{
+    return badvpn_reverse64(x);
+}
+
+#endif
+
+static uint8_t ntoh8 (uint8_t x)
+{
+    return hton8(x);
+}
+
+static uint16_t ntoh16 (uint16_t x)
+{
+    return hton16(x);
+}
+
+static uint32_t ntoh32 (uint32_t x)
+{
+    return hton32(x);
+}
+
+static uint64_t ntoh64 (uint64_t x)
+{
+    return hton64(x);
+}
+
+static uint8_t ltoh8 (uint8_t x)
+{
+    return htol8(x);
+}
+
+static uint16_t ltoh16 (uint16_t x)
+{
+    return htol16(x);
+}
+
+static uint32_t ltoh32 (uint32_t x)
+{
+    return htol32(x);
+}
+
+static uint64_t ltoh64 (uint64_t x)
+{
+    return htol64(x);
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/cmdline.h b/external/badvpn_dns/misc/cmdline.h
new file mode 100644
index 0000000..fba899a
--- /dev/null
+++ b/external/badvpn_dns/misc/cmdline.h
@@ -0,0 +1,181 @@
+/**
+ * @file cmdline.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Command line construction functions.
+ */
+
+#ifndef BADVPN_MISC_CMDLINE_H
+#define BADVPN_MISC_CMDLINE_H
+
+#include <stddef.h>
+#include <stdarg.h>
+
+#include <misc/debug.h>
+#include <misc/exparray.h>
+#include <misc/strdup.h>
+#include <misc/cstring.h>
+
+typedef struct {
+    struct ExpArray arr;
+    size_t n;
+} CmdLine;
+
+static int CmdLine_Init (CmdLine *c);
+static void CmdLine_Free (CmdLine *c);
+static int CmdLine_Append (CmdLine *c, const char *str);
+static int CmdLine_AppendNoNull (CmdLine *c, const char *str, size_t str_len);
+static int CmdLine_AppendCstring (CmdLine *c, b_cstring cstr, size_t offset, size_t length);
+static int CmdLine_AppendMulti (CmdLine *c, int num, ...);
+static int CmdLine_Finish (CmdLine *c);
+static char ** CmdLine_Get (CmdLine *c);
+
+static int _CmdLine_finished (CmdLine *c)
+{
+    return (c->n > 0 && ((char **)c->arr.v)[c->n - 1] == NULL);
+}
+
+int CmdLine_Init (CmdLine *c)
+{
+    if (!ExpArray_init(&c->arr, sizeof(char *), 16)) {
+        return 0;
+    }
+    
+    c->n = 0;
+    
+    return 1;
+}
+
+void CmdLine_Free (CmdLine *c)
+{
+    for (size_t i = 0; i < c->n; i++) {
+        free(((char **)c->arr.v)[i]);
+    }
+    
+    free(c->arr.v);
+}
+
+int CmdLine_Append (CmdLine *c, const char *str)
+{
+    ASSERT(str)
+    ASSERT(!_CmdLine_finished(c))
+    
+    if (!ExpArray_resize(&c->arr, c->n + 1)) {
+        return 0;
+    }
+    
+    if (!(((char **)c->arr.v)[c->n] = strdup(str))) {
+        return 0;
+    }
+    
+    c->n++;
+    
+    return 1;
+}
+
+int CmdLine_AppendNoNull (CmdLine *c, const char *str, size_t str_len)
+{
+    ASSERT(str)
+    ASSERT(!memchr(str, '\0', str_len))
+    ASSERT(!_CmdLine_finished(c))
+    
+    if (!ExpArray_resize(&c->arr, c->n + 1)) {
+        return 0;
+    }
+    
+    if (!(((char **)c->arr.v)[c->n] = b_strdup_bin(str, str_len))) {
+        return 0;
+    }
+    
+    c->n++;
+    
+    return 1;
+}
+
+int CmdLine_AppendCstring (CmdLine *c, b_cstring cstr, size_t offset, size_t length)
+{
+    b_cstring_assert_range(cstr, offset, length);
+    ASSERT(!b_cstring_memchr(cstr, offset, length, '\0', NULL))
+    
+    if (!ExpArray_resize(&c->arr, c->n + 1)) {
+        return 0;
+    }
+    
+    if (!(((char **)c->arr.v)[c->n] = b_cstring_strdup(cstr, offset, length))) {
+        return 0;
+    }
+    
+    c->n++;
+    
+    return 1;
+}
+
+int CmdLine_AppendMulti (CmdLine *c, int num, ...)
+{
+    int res = 1;
+    
+    va_list vl;
+    va_start(vl, num);
+    
+    for (int i = 0; i < num; i++) {
+        const char *str = va_arg(vl, const char *);
+        if (!CmdLine_Append(c, str)) {
+            res = 0;
+            break;
+        }
+    }
+    
+    va_end(vl);
+    
+    return res;
+}
+
+int CmdLine_Finish (CmdLine *c)
+{
+    ASSERT(!_CmdLine_finished(c))
+    
+    if (!ExpArray_resize(&c->arr, c->n + 1)) {
+        return 0;
+    }
+    
+    ((char **)c->arr.v)[c->n] = NULL;
+    
+    c->n++;
+    
+    return 1;
+}
+
+char ** CmdLine_Get (CmdLine *c)
+{
+    ASSERT(_CmdLine_finished(c))
+    
+    return (char **)c->arr.v;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/compare.h b/external/badvpn_dns/misc/compare.h
new file mode 100644
index 0000000..8d1a1b9
--- /dev/null
+++ b/external/badvpn_dns/misc/compare.h
@@ -0,0 +1,37 @@
+/**
+ * @file compare.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_COMPARE_H
+#define BADVPN_COMPARE_H
+
+#define B_COMPARE(a, b) (((a) > (b)) - ((a) < (b)))
+#define B_COMPARE_COMBINE(cmp1, cmp2) ((cmp1) ? (cmp1) : (cmp2))
+#define B_COMPARE2(a, b, c, d) B_COMPARE_COMBINE(B_COMPARE((a), (b)), B_COMPARE((c), (d)))
+
+#endif
diff --git a/external/badvpn_dns/misc/concat_strings.h b/external/badvpn_dns/misc/concat_strings.h
new file mode 100644
index 0000000..30330bc
--- /dev/null
+++ b/external/badvpn_dns/misc/concat_strings.h
@@ -0,0 +1,85 @@
+/**
+ * @file concat_strings.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Function for concatenating strings.
+ */
+
+#ifndef BADVPN_MISC_CONCAT_STRINGS_H
+#define BADVPN_MISC_CONCAT_STRINGS_H
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <misc/debug.h>
+
+static char * concat_strings (int num, ...)
+{
+    ASSERT(num >= 0)
+    
+    // calculate sum of lengths
+    size_t sum = 0;
+    va_list ap;
+    va_start(ap, num);
+    for (int i = 0; i < num; i++) {
+        const char *str = va_arg(ap, const char *);
+        size_t str_len = strlen(str);
+        if (str_len > SIZE_MAX - 1 - sum) {
+            va_end(ap);
+            return NULL;
+        }
+        sum += str_len;
+    }
+    va_end(ap);
+    
+    // allocate memory
+    char *res_str = (char *)malloc(sum + 1);
+    if (!res_str) {
+        return NULL;
+    }
+    
+    // copy strings
+    sum = 0;
+    va_start(ap, num);
+    for (int i = 0; i < num; i++) {
+        const char *str = va_arg(ap, const char *);
+        size_t str_len = strlen(str);
+        memcpy(res_str + sum, str, str_len);
+        sum += str_len;
+    }
+    va_end(ap);
+    
+    // terminate
+    res_str[sum] = '\0';
+    
+    return res_str;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/cstring.h b/external/badvpn_dns/misc/cstring.h
new file mode 100644
index 0000000..bd8de75
--- /dev/null
+++ b/external/badvpn_dns/misc/cstring.h
@@ -0,0 +1,347 @@
+/**
+ * @file cstring.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_COMPOSED_STRING_H
+#define BADVPN_COMPOSED_STRING_H
+
+#include <stddef.h>
+#include <string.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+
+struct b_cstring_s;
+
+/**
+ * Callback function which is called by {@link b_cstring_get} to access the underlying resource.
+ * \a cstr points to the cstring being accessed, and the callback can use the userN members to
+ * retrieve any state information.
+ * \a offset is the offset from the beginning of the string; offset < cstr->length.
+ * This callback must set *\a out_length and return a pointer, representing a continuous
+ * region of the string that starts at the byte at index \a offset. Returning a region that
+ * spans past the end of the string is allowed.
+ */
+typedef const char * (*b_cstring_func) (const struct b_cstring_s *cstr, size_t offset, size_t *out_length);
+
+/**
+ * An abstract string which is not necessarily continuous. Given a cstring, its length
+ * can be determined by reading the 'length' member, and its data can be read using
+ * {@link b_cstring_get} (which internally invokes the {@link b_cstring_func} callback).
+ */
+typedef struct b_cstring_s {
+    size_t length;
+    b_cstring_func func;
+    union {
+        size_t size;
+        void *ptr;
+        void (*fptr) (void);
+    } user1;
+    union {
+        size_t size;
+        void *ptr;
+        void (*fptr) (void);
+    } user2;
+    union {
+        size_t size;
+        void *ptr;
+        void (*fptr) (void);
+    } user3;
+} b_cstring;
+
+/**
+ * Makes a cstring pointing to a buffer.
+ * \a data may be NULL if \a length is 0.
+ */
+static b_cstring b_cstring_make_buf (const char *data, size_t length);
+
+/**
+ * Makes a cstring which represents an empty string.
+ */
+static b_cstring b_cstring_make_empty (void);
+
+/**
+ * Retrieves a pointer to a continuous region of the string.
+ * \a offset specifies the starting offset of the region to retrieve, and must be < cstr.length.
+ * \a maxlen specifies the maximum length of the returned region, and must be > 0.
+ * The length of the region will be stored in *\a out_chunk_len, and it will always be > 0.
+ * It is possible that the returned region spans past the end of the string, unless limited
+ * by \a maxlen. The pointer to the region will be returned; it will point to the byte
+ * at offset exactly \a offset into the string.
+ */
+static const char * b_cstring_get (b_cstring cstr, size_t offset, size_t maxlen, size_t *out_chunk_len);
+
+/**
+ * Retrieves the byte in the string at position \a pos.
+ */
+static char b_cstring_at (b_cstring cstr, size_t pos);
+
+/**
+ * Asserts that the range given by \a offset and \a length is valid for the string.
+ */
+static void b_cstring_assert_range (b_cstring cstr, size_t offset, size_t length);
+
+/**
+ * Copies a range to an external buffer.
+ */
+static void b_cstring_copy_to_buf (b_cstring cstr, size_t offset, size_t length, char *dest);
+
+/**
+ * Performs a memcmp-like operation on the given ranges of two cstrings.
+ */
+static int b_cstring_memcmp (b_cstring cstr1, b_cstring cstr2, size_t offset1, size_t offset2, size_t length);
+
+/**
+ * Determines if a range within a string is equal to the bytes in an external buffer.
+ */
+static int b_cstring_equals_buffer (b_cstring cstr, size_t offset, size_t length, const char *data);
+
+/**
+ * Determines if a range within a string contains the byte \a ch.
+ * Returns 1 if it does, and 0 if it does not. If it does contain it, and \a out_pos is not
+ * NULL, *\a out_pos is set to the index of the first matching byte in the range, relative
+ * to the beginning of the range \a offset.
+ */
+static int b_cstring_memchr (b_cstring cstr, size_t offset, size_t length, char ch, size_t *out_pos);
+
+/**
+ * Allocates a buffer for a range and copies it. The buffer is allocated using {@link BAlloc}.
+ * An extra null byte will be appended. On failure, returns NULL.
+ */
+static char * b_cstring_strdup (b_cstring cstr, size_t offset, size_t length);
+
+/**
+ * Macro which iterates the continuous regions of a range within a cstring.
+ * For reach region, the statements in \a body are executed, in order.
+ * \a cstr is the string to be iterated.
+ * \a offset and \a length specify the range of the string to iterate; they must
+ * refer to a valid range for the string.
+ * \a rel_pos_var, \a chunk_data_var and \a chunk_length_var specify names of variables
+ * which will be available in \a body.
+ * \a rel_pos_var will hold the offset (size_t) of the current continuous region, relative
+ * to \a offset.
+ * \a chunk_data_var will hold a pointer (const char *) to the beginning of the region, and
+ * \a chunk_length_var will hold its length (size_t).
+ * 
+ * Note: \a cstr, \a offset and \a length may be evaluated multiple times, or not at all.
+ * Note: do not use 'continue' or 'break' from inside the body, their behavior depends
+ *       on the internal implementation of this macro.
+ * 
+ * See the implementation of {@link b_cstring_copy_to_buf} for a usage example.
+ */
+#define B_CSTRING_LOOP_RANGE(cstr, offset, length, rel_pos_var, chunk_data_var, chunk_length_var, body) \
+{ \
+    size_t rel_pos_var = 0; \
+    while (rel_pos_var < (length)) { \
+        size_t chunk_length_var; \
+        const char *chunk_data_var = b_cstring_get((cstr), (offset) + rel_pos_var, (length) - rel_pos_var, &chunk_length_var); \
+        { body } \
+        rel_pos_var += chunk_length_var; \
+    } \
+}
+
+/**
+ * Like {@link B_CSTRING_LOOP_RANGE}, but iterates the entire string,
+ * i.e. offset==0 and length==cstr.length.
+ */
+#define B_CSTRING_LOOP(cstr, rel_pos_var, chunk_data_var, chunk_length_var, body) B_CSTRING_LOOP_RANGE(cstr, 0, (cstr).length, rel_pos_var, chunk_data_var, chunk_length_var, body)
+
+/**
+ * Macro which iterates the characters of a range within a cstring.
+ * For each character, the statements in \a body are executed, in order.
+ * \a cstr is the string to be iterated.
+ * \a offset and \a length specify the range of the string to iterate; they must
+ * refer to a valid range for the string.
+ * \a char_rel_pos_var and \a char_var specify names of variables which will be
+ * available in \a body.
+ * \a char_rel_pos_var will hold the position (size_t) of the current character
+ * relative to \a offset.
+ * \a char_var will hold the current character (char).
+ * 
+ * Note: \a cstr, \a offset and \a length may be evaluated multiple times, or not at all.
+ * Note: do not use 'continue' or 'break' from inside the body, their behavior depends
+ *       on the internal implementation of this macro.
+ */
+#define B_CSTRING_LOOP_CHARS_RANGE(cstr, offset, length, char_rel_pos_var, char_var, body) \
+B_CSTRING_LOOP_RANGE(cstr, offset, length, b_cstring_loop_chars_pos, b_cstring_loop_chars_chunk_data, b_cstring_loop_chars_chunk_length, { \
+    for (size_t b_cstring_loop_chars_chunk_pos = 0; b_cstring_loop_chars_chunk_pos < b_cstring_loop_chars_chunk_length; b_cstring_loop_chars_chunk_pos++) { \
+        char char_rel_pos_var = b_cstring_loop_chars_pos + b_cstring_loop_chars_chunk_pos; \
+        B_USE(char_rel_pos_var) \
+        char char_var = b_cstring_loop_chars_chunk_data[b_cstring_loop_chars_chunk_pos]; \
+        { body } \
+    } \
+})
+
+/**
+ * Like {@link B_CSTRING_LOOP_CHARS_RANGE}, but iterates the entire string,
+ * i.e. offset==0 and length==cstr.length.
+ */
+#define B_CSTRING_LOOP_CHARS(cstr, char_rel_pos_var, char_var, body) B_CSTRING_LOOP_CHARS_RANGE(cstr, 0, (cstr).length, char_rel_pos_var, char_var, body)
+
+static const char * b_cstring__buf_func (const b_cstring *cstr, size_t offset, size_t *out_length)
+{
+    ASSERT(offset < cstr->length)
+    ASSERT(out_length)
+    ASSERT(cstr->func == b_cstring__buf_func)
+    ASSERT(cstr->user1.ptr)
+    
+    *out_length = cstr->length - offset;
+    return (const char *)cstr->user1.ptr + offset;
+}
+
+static b_cstring b_cstring_make_buf (const char *data, size_t length)
+{
+    ASSERT(length == 0 || data)
+    
+    b_cstring cstr;
+    cstr.length = length;
+    cstr.func = b_cstring__buf_func;
+    cstr.user1.ptr = (void *)data;
+    return cstr;
+}
+
+static b_cstring b_cstring_make_empty (void)
+{
+    b_cstring cstr;
+    cstr.length = 0;
+    cstr.func = NULL;
+    return cstr;
+}
+
+static const char * b_cstring_get (b_cstring cstr, size_t offset, size_t maxlen, size_t *out_chunk_len)
+{
+    ASSERT(offset < cstr.length)
+    ASSERT(maxlen > 0)
+    ASSERT(out_chunk_len)
+    ASSERT(cstr.func)
+    
+    const char *data = cstr.func(&cstr, offset, out_chunk_len);
+    ASSERT(data)
+    ASSERT(*out_chunk_len > 0)
+    
+    if (*out_chunk_len > maxlen) {
+        *out_chunk_len = maxlen;
+    }
+    
+    return data;
+}
+
+static char b_cstring_at (b_cstring cstr, size_t pos)
+{
+    ASSERT(pos < cstr.length)
+    ASSERT(cstr.func)
+    
+    size_t chunk_len;
+    const char *data = cstr.func(&cstr, pos, &chunk_len);
+    ASSERT(data)
+    ASSERT(chunk_len > 0)
+    
+    return *data;
+}
+
+static void b_cstring_assert_range (b_cstring cstr, size_t offset, size_t length)
+{
+    ASSERT(offset <= cstr.length)
+    ASSERT(length <= cstr.length - offset)
+}
+
+static void b_cstring_copy_to_buf (b_cstring cstr, size_t offset, size_t length, char *dest)
+{
+    b_cstring_assert_range(cstr, offset, length);
+    ASSERT(length == 0 || dest)
+    
+    B_CSTRING_LOOP_RANGE(cstr, offset, length, pos, chunk_data, chunk_length, {
+        memcpy(dest + pos, chunk_data, chunk_length);
+    })
+}
+
+static int b_cstring_memcmp (b_cstring cstr1, b_cstring cstr2, size_t offset1, size_t offset2, size_t length)
+{
+    b_cstring_assert_range(cstr1, offset1, length);
+    b_cstring_assert_range(cstr2, offset2, length);
+    
+    B_CSTRING_LOOP_RANGE(cstr1, offset1, length, pos1, chunk_data1, chunk_len1, {
+        B_CSTRING_LOOP_RANGE(cstr2, offset2 + pos1, chunk_len1, pos2, chunk_data2, chunk_len2, {
+            int cmp = memcmp(chunk_data1 + pos2, chunk_data2, chunk_len2);
+            if (cmp) {
+                return cmp;
+            }
+        })
+    })
+    
+    return 0;
+}
+
+static int b_cstring_equals_buffer (b_cstring cstr, size_t offset, size_t length, const char *data)
+{
+    b_cstring_assert_range(cstr, offset, length);
+    
+    B_CSTRING_LOOP_RANGE(cstr, offset, length, pos, chunk_data, chunk_len, {
+        if (memcmp(chunk_data, data + pos, chunk_len)) {
+            return 0;
+        }
+    })
+    
+    return 1;
+}
+
+static int b_cstring_memchr (b_cstring cstr, size_t offset, size_t length, char ch, size_t *out_pos)
+{
+    b_cstring_assert_range(cstr, offset, length);
+    
+    B_CSTRING_LOOP_CHARS_RANGE(cstr, offset, length, cur_ch_pos, cur_ch, {
+        if (cur_ch == ch) {
+            if (out_pos) {
+                *out_pos = cur_ch_pos;
+            }
+            return 1;
+        }
+    })
+    
+    return 0;
+}
+
+static char * b_cstring_strdup (b_cstring cstr, size_t offset, size_t length)
+{
+    b_cstring_assert_range(cstr, offset, length);
+    
+    if (length == SIZE_MAX) {
+        return NULL;
+    }
+    
+    char *buf = (char *)BAlloc(length + 1);
+    if (buf) {
+        b_cstring_copy_to_buf(cstr, offset, length, buf);
+        buf[length] = '\0';
+    }
+    
+    return buf;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/dead.h b/external/badvpn_dns/misc/dead.h
new file mode 100644
index 0000000..7f57421
--- /dev/null
+++ b/external/badvpn_dns/misc/dead.h
@@ -0,0 +1,134 @@
+/**
+ * @file dead.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Dead mechanism definitions.
+ * 
+ * The dead mechanism is a way for a piece of code to detect whether some
+ * specific event has occured during some operation (usually during calling
+ * a user-provided handler function), without requiring access to memory
+ * that might no longer be available because of the event.
+ * 
+ * It works somehow like that:
+ * 
+ * First a dead variable ({@link dead_t}) is allocated somewhere, and
+ * initialized with {@link DEAD_INIT}, e.g.:
+ *   DEAD_INIT(dead);
+ * 
+ * When the event that needs to be caught occurs, {@link DEAD_KILL} is
+ * called, e.g.:
+ *   DEAD_KILL(dead);
+ * The memory used by the dead variable is no longer needed after
+ * that.
+ * 
+ * If a piece of code needs to know whether the event occured during some
+ * operation (but it must not have occured before!), it puts {@link DEAD_ENTER}}
+ * in front of the operation, and does {@link DEAD_LEAVE} at the end. If
+ * {@link DEAD_LEAVE} returned nonzero, the event occured, otherwise it did
+ * not. Example:
+ *   DEAD_ENTER(dead)
+ *   HandlerFunction();
+ *   if (DEAD_LEAVE(dead)) {
+ *     (event occured)
+ *   }
+ * 
+ * If is is needed to check for the event more than once in a single block,
+ * {@link DEAD_DECLARE} should be put somewhere before, and {@link DEAD_ENTER2}
+ * should be used instead of {@link DEAD_ENTER}.
+ * 
+ * If it is needed to check for multiple events (dead variables) at the same
+ * time, DEAD_*_N macros should be used instead, specifying different
+ * identiers as the first argument for different dead variables.
+ */
+
+#ifndef BADVPN_MISC_DEAD_H
+#define BADVPN_MISC_DEAD_H
+
+#include <stdlib.h>
+
+/**
+ * Dead variable.
+ */
+typedef int *dead_t;
+
+/**
+ * Initializes a dead variable.
+ */
+#define DEAD_INIT(ptr) { ptr = NULL; }
+
+/**
+ * Kills the dead variable,
+ */
+#define DEAD_KILL(ptr) { if (ptr) *(ptr) = 1; }
+
+/**
+ * Kills the dead variable with the given value, or does nothing
+ * if the value is 0. The value will seen by {@link DEAD_KILLED}.
+ */
+#define DEAD_KILL_WITH(ptr, val) { if (ptr) *(ptr) = (val); }
+
+/**
+ * Declares dead catching variables.
+ */
+#define DEAD_DECLARE int badvpn__dead; dead_t badvpn__prev_ptr;
+
+/**
+ * Enters a dead catching using already declared dead catching variables.
+ * The dead variable must have been initialized with {@link DEAD_INIT},
+ * and {@link DEAD_KILL} must not have been called yet.
+ * {@link DEAD_LEAVE2} must be called before the current scope is left.
+ */
+#define DEAD_ENTER2(ptr) { badvpn__dead = 0; badvpn__prev_ptr = ptr; ptr = &badvpn__dead; }
+
+/**
+ * Declares dead catching variables and enters a dead catching.
+ * The dead variable must have been initialized with {@link DEAD_INIT},
+ * and {@link DEAD_KILL} must not have been called yet.
+ * {@link DEAD_LEAVE2} must be called before the current scope is left.
+ */
+#define DEAD_ENTER(ptr) DEAD_DECLARE DEAD_ENTER2(ptr)
+
+/**
+ * Leaves a dead catching.
+ */
+#define DEAD_LEAVE2(ptr) { if (!badvpn__dead) ptr = badvpn__prev_ptr; if (badvpn__prev_ptr) *badvpn__prev_ptr = badvpn__dead; }
+
+/**
+ * Returns 1 if {@link DEAD_KILL} was called for the dead variable, 0 otherwise.
+ * Must be called after entering a dead catching.
+ */
+#define DEAD_KILLED (badvpn__dead)
+
+#define DEAD_DECLARE_N(n) int badvpn__dead##n; dead_t badvpn__prev_ptr##n;
+#define DEAD_ENTER2_N(n, ptr) { badvpn__dead##n = 0; badvpn__prev_ptr##n = ptr; ptr = &badvpn__dead##n;}
+#define DEAD_ENTER_N(n, ptr) DEAD_DECLARE_N(n) DEAD_ENTER2_N(n, ptr)
+#define DEAD_LEAVE2_N(n, ptr) { if (!badvpn__dead##n) ptr = badvpn__prev_ptr##n; if (badvpn__prev_ptr##n) *badvpn__prev_ptr##n = badvpn__dead##n; }
+#define DEAD_KILLED_N(n) (badvpn__dead##n)
+
+#endif
diff --git a/external/badvpn_dns/misc/debug.h b/external/badvpn_dns/misc/debug.h
new file mode 100644
index 0000000..13bc866
--- /dev/null
+++ b/external/badvpn_dns/misc/debug.h
@@ -0,0 +1,142 @@
+/**
+ * @file debug.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Debugging macros.
+ */
+
+/**
+ * @def DEBUG
+ * 
+ * Macro for printing debugging text. Use the same way as printf,
+ * but without a newline.
+ * Prepends "function_name: " and appends a newline.
+ */
+
+/**
+ * @def ASSERT_FORCE
+ * 
+ * Macro for forced assertions.
+ * Evaluates the argument and terminates the program abnormally
+ * if the result is false.
+ */
+
+/**
+ * @def ASSERT
+ * 
+ * Macro for assertions.
+ * The argument may or may not be evaluated.
+ * If the argument is evaluated, it must not evaluate to false.
+ */
+
+/**
+ * @def ASSERT_EXECUTE
+ * 
+ * Macro for always-evaluated assertions.
+ * The argument is evaluated.
+ * The argument must not evaluate to false.
+ */
+
+/**
+ * @def DEBUG_ZERO_MEMORY
+ * 
+ * If debugging is enabled, zeroes the given memory region.
+ * First argument is pointer to the memory region, second is
+ * number of bytes.
+ */
+
+/**
+ * @def WARN_UNUSED
+ * 
+ * Tells the compiler that the result of a function should not be unused.
+ * Insert at the end of the declaration of a function before the semicolon.
+ */
+
+/**
+ * @def B_USE
+ * 
+ * This can be used to suppress warnings about unused variables. It can
+ * be applied to a variable or any expression. It does not evaluate the
+ * expression.
+ */
+
+#ifndef BADVPN_MISC_DEBUG_H
+#define BADVPN_MISC_DEBUG_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <assert.h>
+
+#define DEBUG(...) \
+    { \
+        fprintf(stderr, "%s: ", __FUNCTION__); \
+        fprintf(stderr, __VA_ARGS__); \
+        fprintf(stderr, "\n"); \
+    }
+
+#define ASSERT_FORCE(e) \
+    { \
+        if (!(e)) { \
+            fprintf(stderr, "%s:%d Assertion failed\n", __FILE__, __LINE__); \
+            abort(); \
+        } \
+    }
+
+#ifdef NDEBUG
+    #define DEBUG_ZERO_MEMORY(buf, len) {}
+    #define ASSERT(e) {}
+    #define ASSERT_EXECUTE(e) { (e); }
+#else
+    #define DEBUG_ZERO_MEMORY(buf, len) { memset((buf), 0, (len)); }
+    #ifdef BADVPN_USE_C_ASSERT
+        #define ASSERT(e) { assert(e); }
+        #define ASSERT_EXECUTE(e) \
+            { \
+                int _assert_res = !!(e); \
+                assert(_assert_res); \
+            }
+    #else
+        #define ASSERT(e) ASSERT_FORCE(e)
+        #define ASSERT_EXECUTE(e) ASSERT_FORCE(e)
+    #endif
+#endif
+
+#ifdef __GNUC__
+    #define WARN_UNUSED __attribute__((warn_unused_result))
+#else
+    #define WARN_UNUSED
+#endif
+
+#define B_USE(expr) (void)(sizeof((expr)));
+
+#define B_ASSERT_USE(expr) { ASSERT(expr) B_USE(expr) }
+
+#endif
diff --git a/external/badvpn_dns/misc/debugcounter.h b/external/badvpn_dns/misc/debugcounter.h
new file mode 100644
index 0000000..a8a54a1
--- /dev/null
+++ b/external/badvpn_dns/misc/debugcounter.h
@@ -0,0 +1,118 @@
+/**
+ * @file debugcounter.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Counter for detecting leaks.
+ */
+
+#ifndef BADVPN_MISC_DEBUGCOUNTER_H
+#define BADVPN_MISC_DEBUGCOUNTER_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+
+/**
+ * Counter for detecting leaks.
+ */
+typedef struct {
+#ifndef NDEBUG
+    int32_t c;
+#endif
+} DebugCounter;
+
+#ifndef NDEBUG
+#define DEBUGCOUNTER_STATIC { 0 }
+#else
+#define DEBUGCOUNTER_STATIC {}
+#endif
+
+/**
+ * Initializes the object.
+ * The object is initialized with counter value zero.
+ * 
+ * @param obj the object
+ */
+static void DebugCounter_Init (DebugCounter *obj)
+{
+#ifndef NDEBUG
+    obj->c = 0;
+#endif
+}
+
+/**
+ * Frees the object.
+ * This does not have to be called when the counter is no longer needed.
+ * The counter value must be zero.
+ * 
+ * @param obj the object
+ */
+static void DebugCounter_Free (DebugCounter *obj)
+{
+#ifndef NDEBUG
+    ASSERT(obj->c == 0 || obj->c == INT32_MAX)
+#endif
+}
+
+/**
+ * Increments the counter.
+ * Increments the counter value by one.
+ * 
+ * @param obj the object
+ */
+static void DebugCounter_Increment (DebugCounter *obj)
+{
+#ifndef NDEBUG
+    ASSERT(obj->c >= 0)
+    
+    if (obj->c != INT32_MAX) {
+        obj->c++;
+    }
+#endif
+}
+
+/**
+ * Decrements the counter.
+ * The counter value must be >0.
+ * Decrements the counter value by one.
+ * 
+ * @param obj the object
+ */
+static void DebugCounter_Decrement (DebugCounter *obj)
+{
+#ifndef NDEBUG
+    ASSERT(obj->c > 0)
+    
+    if (obj->c != INT32_MAX) {
+        obj->c--;
+    }
+#endif
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/debugerror.h b/external/badvpn_dns/misc/debugerror.h
new file mode 100644
index 0000000..182afd7
--- /dev/null
+++ b/external/badvpn_dns/misc/debugerror.h
@@ -0,0 +1,90 @@
+/**
+ * @file debugerror.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Mechanism for ensuring an object is destroyed from inside an error handler
+ * or its jobs.
+ */
+
+#ifndef BADVPN_MISC_DEBUGERROR_H
+#define BADVPN_MISC_DEBUGERROR_H
+
+#include <misc/debug.h>
+#include <base/BPending.h>
+
+#ifndef NDEBUG
+    #define DEBUGERROR(de, call) \
+        { \
+            ASSERT(!BPending_IsSet(&(de)->job)) \
+            BPending_Set(&(de)->job); \
+            (call); \
+        }
+#else
+    #define DEBUGERROR(de, call) { (call); }
+#endif
+
+typedef struct {
+    #ifndef NDEBUG
+    BPending job;
+    #endif
+} DebugError;
+
+static void DebugError_Init (DebugError *o, BPendingGroup *pg);
+static void DebugError_Free (DebugError *o);
+static void DebugError_AssertNoError (DebugError *o);
+
+#ifndef NDEBUG
+static void _DebugError_job_handler (DebugError *o)
+{
+    ASSERT(0);
+}
+#endif
+
+void DebugError_Init (DebugError *o, BPendingGroup *pg)
+{
+    #ifndef NDEBUG
+    BPending_Init(&o->job, pg, (BPending_handler)_DebugError_job_handler, o);
+    #endif
+}
+
+void DebugError_Free (DebugError *o)
+{
+    #ifndef NDEBUG
+    BPending_Free(&o->job);
+    #endif
+}
+
+void DebugError_AssertNoError (DebugError *o)
+{
+    #ifndef NDEBUG
+    ASSERT(!BPending_IsSet(&o->job))
+    #endif
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/dhcp_proto.h b/external/badvpn_dns/misc/dhcp_proto.h
new file mode 100644
index 0000000..ed83544
--- /dev/null
+++ b/external/badvpn_dns/misc/dhcp_proto.h
@@ -0,0 +1,131 @@
+/**
+ * @file dhcp_proto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 the DHCP protocol.
+ */
+
+#ifndef BADVPN_MISC_DHCP_PROTO_H
+#define BADVPN_MISC_DHCP_PROTO_H
+
+#include <stdint.h>
+
+#include <misc/packed.h>
+
+#define DHCP_OP_BOOTREQUEST 1
+#define DHCP_OP_BOOTREPLY 2
+
+#define DHCP_HARDWARE_ADDRESS_TYPE_ETHERNET 1
+
+#define DHCP_MAGIC 0x63825363
+
+#define DHCP_OPTION_PAD 0
+#define DHCP_OPTION_END 255
+
+#define DHCP_OPTION_SUBNET_MASK 1
+#define DHCP_OPTION_ROUTER 3
+#define DHCP_OPTION_DOMAIN_NAME_SERVER 6
+#define DHCP_OPTION_HOST_NAME 12
+#define DHCP_OPTION_REQUESTED_IP_ADDRESS 50
+#define DHCP_OPTION_IP_ADDRESS_LEASE_TIME 51
+#define DHCP_OPTION_DHCP_MESSAGE_TYPE 53
+#define DHCP_OPTION_DHCP_SERVER_IDENTIFIER 54
+#define DHCP_OPTION_PARAMETER_REQUEST_LIST 55
+#define DHCP_OPTION_MAXIMUM_MESSAGE_SIZE 57
+#define DHCP_OPTION_RENEWAL_TIME_VALUE 58
+#define DHCP_OPTION_REBINDING_TIME_VALUE 59
+#define DHCP_OPTION_VENDOR_CLASS_IDENTIFIER 60
+#define DHCP_OPTION_CLIENT_IDENTIFIER 61
+
+#define DHCP_MESSAGE_TYPE_DISCOVER 1
+#define DHCP_MESSAGE_TYPE_OFFER 2
+#define DHCP_MESSAGE_TYPE_REQUEST 3
+#define DHCP_MESSAGE_TYPE_DECLINE 4
+#define DHCP_MESSAGE_TYPE_ACK 5
+#define DHCP_MESSAGE_TYPE_NAK 6
+#define DHCP_MESSAGE_TYPE_RELEASE 7
+
+B_START_PACKED
+struct dhcp_header {
+    uint8_t op;
+    uint8_t htype;
+    uint8_t hlen;
+    uint8_t hops;
+    uint32_t xid;
+    uint16_t secs;
+    uint16_t flags;
+    uint32_t ciaddr;
+    uint32_t yiaddr;
+    uint32_t siaddr;
+    uint32_t giaddr;
+    uint8_t chaddr[16];
+    uint8_t sname[64];
+    uint8_t file[128];
+    uint32_t magic;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct dhcp_option_header {
+    uint8_t type;
+    uint8_t len;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct dhcp_option_dhcp_message_type {
+    uint8_t type;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct dhcp_option_maximum_message_size {
+    uint16_t size;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct dhcp_option_dhcp_server_identifier {
+    uint32_t id;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct dhcp_option_time {
+    uint32_t time;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct dhcp_option_addr {
+    uint32_t addr;
+} B_PACKED;
+B_END_PACKED
+
+#endif
diff --git a/external/badvpn_dns/misc/ethernet_proto.h b/external/badvpn_dns/misc/ethernet_proto.h
new file mode 100644
index 0000000..6e49e02
--- /dev/null
+++ b/external/badvpn_dns/misc/ethernet_proto.h
@@ -0,0 +1,52 @@
+/**
+ * @file ethernet_proto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 the Ethernet protocol.
+ */
+
+#ifndef BADVPN_MISC_ETHERNET_PROTO_H
+#define BADVPN_MISC_ETHERNET_PROTO_H
+
+#include <stdint.h>
+
+#include <misc/packed.h>
+
+#define ETHERTYPE_IPV4 0x0800
+#define ETHERTYPE_ARP 0x0806
+
+B_START_PACKED
+struct ethernet_header {
+    uint8_t dest[6];
+    uint8_t source[6];
+    uint16_t type;
+} B_PACKED;
+B_END_PACKED
+
+#endif
diff --git a/external/badvpn_dns/misc/exparray.h b/external/badvpn_dns/misc/exparray.h
new file mode 100644
index 0000000..b24149f
--- /dev/null
+++ b/external/badvpn_dns/misc/exparray.h
@@ -0,0 +1,101 @@
+/**
+ * @file exparray.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Dynamic array which grows exponentionally on demand.
+ */
+
+#ifndef BADVPN_MISC_EXPARRAY_H
+#define BADVPN_MISC_EXPARRAY_H
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+
+struct ExpArray {
+    size_t esize;
+    size_t size;
+    void *v;
+};
+
+static int ExpArray_init (struct ExpArray *o, size_t esize, size_t size)
+{
+    ASSERT(esize > 0)
+    ASSERT(size > 0)
+    
+    o->esize = esize;
+    o->size = size;
+    
+    if (o->size > SIZE_MAX / o->esize) {
+        return 0;
+    }
+    
+    if (!(o->v = malloc(o->size * o->esize))) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int ExpArray_resize (struct ExpArray *o, size_t size)
+{
+    ASSERT(size > 0)
+    
+    if (size <= o->size) {
+        return 1;
+    }
+    
+    size_t newsize = o->size;
+    
+    while (newsize < size) {
+        if (2 > SIZE_MAX / newsize) {
+            return 0;
+        }
+        
+        newsize = 2 * newsize;
+    }
+    
+    if (newsize > SIZE_MAX / o->esize) {
+        return 0;
+    }
+    
+    void *newarr = realloc(o->v, newsize * o->esize);
+    if (!newarr) {
+        return 0;
+    }
+    
+    o->size = newsize;
+    o->v = newarr;
+    
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/expstring.h b/external/badvpn_dns/misc/expstring.h
new file mode 100644
index 0000000..3247135
--- /dev/null
+++ b/external/badvpn_dns/misc/expstring.h
@@ -0,0 +1,161 @@
+/**
+ * @file expstring.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_MISC_EXPSTRING_H
+#define BADVPN_MISC_EXPSTRING_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <misc/exparray.h>
+#include <misc/bsize.h>
+
+typedef struct {
+    struct ExpArray arr;
+    size_t n;
+} ExpString;
+
+static int ExpString_Init (ExpString *c);
+static void ExpString_Free (ExpString *c);
+static int ExpString_Append (ExpString *c, const char *str);
+static int ExpString_AppendChar (ExpString *c, char ch);
+static int ExpString_AppendByte (ExpString *c, uint8_t x);
+static int ExpString_AppendBinary (ExpString *c, const uint8_t *data, size_t len);
+static int ExpString_AppendZeros (ExpString *c, size_t len);
+static char * ExpString_Get (ExpString *c);
+static size_t ExpString_Length (ExpString *c);
+
+int ExpString_Init (ExpString *c)
+{
+    if (!ExpArray_init(&c->arr, 1, 16)) {
+        return 0;
+    }
+    
+    c->n = 0;
+    ((char *)c->arr.v)[c->n] = '\0';
+    
+    return 1;
+}
+
+void ExpString_Free (ExpString *c)
+{
+    free(c->arr.v);
+}
+
+int ExpString_Append (ExpString *c, const char *str)
+{
+    ASSERT(str)
+    
+    size_t l = strlen(str);
+    bsize_t newsize = bsize_add(bsize_fromsize(c->n), bsize_add(bsize_fromsize(l), bsize_fromint(1)));
+    
+    if (newsize.is_overflow || !ExpArray_resize(&c->arr, newsize.value)) {
+        return 0;
+    }
+    
+    memcpy((char *)c->arr.v + c->n, str, l);
+    c->n += l;
+    ((char *)c->arr.v)[c->n] = '\0';
+    
+    return 1;
+}
+
+int ExpString_AppendChar (ExpString *c, char ch)
+{
+    ASSERT(ch != '\0')
+    
+    bsize_t newsize = bsize_add(bsize_fromsize(c->n), bsize_fromint(2));
+    
+    if (newsize.is_overflow || !ExpArray_resize(&c->arr, newsize.value)) {
+        return 0;
+    }
+    
+    ((char *)c->arr.v)[c->n] = ch;
+    c->n++;
+    ((char *)c->arr.v)[c->n] = '\0';
+    
+    return 1;
+}
+
+int ExpString_AppendByte (ExpString *c, uint8_t x)
+{
+    bsize_t newsize = bsize_add(bsize_fromsize(c->n), bsize_fromint(2));
+    
+    if (newsize.is_overflow || !ExpArray_resize(&c->arr, newsize.value)) {
+        return 0;
+    }
+    
+    ((uint8_t *)c->arr.v)[c->n] = x;
+    c->n++;
+    ((char *)c->arr.v)[c->n] = '\0';
+    
+    return 1;
+}
+
+int ExpString_AppendBinary (ExpString *c, const uint8_t *data, size_t len)
+{
+    bsize_t newsize = bsize_add(bsize_fromsize(c->n), bsize_add(bsize_fromsize(len), bsize_fromint(1)));
+    
+    if (newsize.is_overflow || !ExpArray_resize(&c->arr, newsize.value)) {
+        return 0;
+    }
+    
+    memcpy((char *)c->arr.v + c->n, data, len);
+    c->n += len;
+    ((char *)c->arr.v)[c->n] = '\0';
+    
+    return 1;
+}
+
+int ExpString_AppendZeros (ExpString *c, size_t len)
+{
+    bsize_t newsize = bsize_add(bsize_fromsize(c->n), bsize_add(bsize_fromsize(len), bsize_fromint(1)));
+    
+    if (newsize.is_overflow || !ExpArray_resize(&c->arr, newsize.value)) {
+        return 0;
+    }
+    
+    memset((char *)c->arr.v + c->n, 0, len);
+    c->n += len;
+    ((char *)c->arr.v)[c->n] = '\0';
+    
+    return 1;
+}
+
+char * ExpString_Get (ExpString *c)
+{
+    return (char *)c->arr.v;
+}
+
+size_t ExpString_Length (ExpString *c)
+{
+    return c->n;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/find_char.h b/external/badvpn_dns/misc/find_char.h
new file mode 100644
index 0000000..9277ac6
--- /dev/null
+++ b/external/badvpn_dns/misc/find_char.h
@@ -0,0 +1,58 @@
+/**
+ * @file find_char.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_FIND_CHAR_H
+#define BADVPN_FIND_CHAR_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+
+/**
+ * Finds the first character 'c' in the string represented by 'str' and 'len'.
+ * If found, returns 1 and writes the position to *out_pos (if out_pos!=NULL).
+ * If not found, returns 0 and does not modify *out_pos.
+ */
+static int b_find_char_bin (const char *str, size_t len, char c, size_t *out_pos)
+{
+    ASSERT(str)
+    
+    for (size_t i = 0; i < len; i++) {
+        if (str[i] == c) {
+            if (out_pos) {
+                *out_pos = i;
+            }
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/find_program.h b/external/badvpn_dns/misc/find_program.h
new file mode 100644
index 0000000..ecc87be
--- /dev/null
+++ b/external/badvpn_dns/misc/find_program.h
@@ -0,0 +1,103 @@
+/**
+ * @file find_program.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Function that finds the absolute path of a program by checking a predefined
+ * list of directories.
+ */
+
+#ifndef BADVPN_FIND_PROGRAM_H
+#define BADVPN_FIND_PROGRAM_H
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <misc/concat_strings.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+
+static char * badvpn_find_program (const char *name);
+
+static char * badvpn_find_program (const char *name)
+{
+    ASSERT(name)
+    
+    char *path = getenv("PATH");
+    if (path) {
+        while (1) {
+            size_t i = 0;
+            while (path[i] != ':' && path[i] != '\0') {
+                i++;
+            }
+            char const *src = path;
+            size_t src_len = i;
+            if (src_len == 0) {
+                src = ".";
+                src_len = 1;
+            }
+            size_t name_len = strlen(name);
+            char *entry = BAllocSize(bsize_add(bsize_fromsize(src_len), bsize_add(bsize_fromsize(name_len), bsize_fromsize(2))));
+            if (!entry) {
+                goto fail;
+            }
+            memcpy(entry, src, src_len);
+            entry[src_len] = '/';
+            strcpy(entry + (src_len + 1), name);
+            if (access(entry, X_OK) == 0) {
+                return entry;
+            }
+            free(entry);
+            if (path[i] == '\0') {
+                break;
+            }
+            path += i + 1;
+        }
+    }
+    
+    const char *dirs[] = {"/usr/sbin", "/usr/bin", "/sbin", "/bin", NULL};
+    
+    for (size_t i = 0; dirs[i]; i++) {
+        char *path = concat_strings(3, dirs[i], "/", name);
+        if (!path) {
+            goto fail;
+        }
+        
+        if (access(path, X_OK) == 0) {
+            return path;
+        }
+        
+        free(path);
+    }
+    
+fail:
+    return NULL;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/get_iface_info.h b/external/badvpn_dns/misc/get_iface_info.h
new file mode 100644
index 0000000..190741b
--- /dev/null
+++ b/external/badvpn_dns/misc/get_iface_info.h
@@ -0,0 +1,110 @@
+/**
+ * @file get_iface_info.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_GETIFACEINFO_H
+#define BADVPN_GETIFACEINFO_H
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <sys/ioctl.h>
+
+#include <misc/debug.h>
+
+/**
+ * Returns information about a network interface with the given name.
+ * 
+ * @param ifname name of interface to get information for
+ * @param out_mac the MAC address will be returned here, unless NULL
+ * @param out_mtu the MTU will be returned here, unless NULL
+ * @param out_ifindex the interface index will be returned here, unless NULL
+ * @return 1 on success, 0 on failure
+ */
+static int badvpn_get_iface_info (const char *ifname, uint8_t *out_mac, int *out_mtu, int *out_ifindex) WARN_UNUSED;
+
+
+static int badvpn_get_iface_info (const char *ifname, uint8_t *out_mac, int *out_mtu, int *out_ifindex)
+{
+    ASSERT(ifname)
+    
+    struct ifreq ifr;
+    
+    int s = socket(AF_INET, SOCK_DGRAM, 0);
+    if (s < 0) {
+        goto fail0;
+    }
+    
+    // get MAC
+    if (out_mac) {
+        memset(&ifr, 0, sizeof(ifr));
+        snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
+        if (ioctl(s, SIOCGIFHWADDR, &ifr)) {
+            goto fail1;
+        }
+        if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
+            goto fail1;
+        }
+        memcpy(out_mac, ifr.ifr_hwaddr.sa_data, 6);
+    }
+    
+    // get MTU
+    if (out_mtu) {
+        memset(&ifr, 0, sizeof(ifr));
+        snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
+        if (ioctl(s, SIOCGIFMTU, &ifr)) {
+            goto fail1;
+        }
+        *out_mtu = ifr.ifr_mtu;
+    }
+    
+    // get interface index
+    if (out_ifindex) {
+        memset(&ifr, 0, sizeof(ifr));
+        snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
+        if (ioctl(s, SIOCGIFINDEX, &ifr)) {
+            goto fail1;
+        }
+        *out_ifindex = ifr.ifr_ifindex;
+    }
+    
+    close(s);
+    
+    return 1;
+    
+fail1:
+    close(s);
+fail0:
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/grow_array.h b/external/badvpn_dns/misc/grow_array.h
new file mode 100644
index 0000000..9a42d04
--- /dev/null
+++ b/external/badvpn_dns/misc/grow_array.h
@@ -0,0 +1,139 @@
+/**
+ * @file grow_array.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+// Preprocessor inputs:
+// GROWARRAY_NAME - prefix of of functions to define
+// GROWARRAY_OBJECT_TYPE - type of structure where array and capacity sizes are
+// GROWARRAY_ARRAY_MEMBER - array member
+// GROWARRAY_CAPACITY_MEMBER - capacity member
+// GROWARRAY_MAX_CAPACITY - max value of capacity member
+
+#include <limits.h>
+#include <string.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <misc/merge.h>
+
+#define GrowArrayObject GROWARRAY_OBJECT_TYPE
+#define GrowArray_Init MERGE(GROWARRAY_NAME, _Init)
+#define GrowArray_InitEmpty MERGE(GROWARRAY_NAME, _InitEmpty)
+#define GrowArray_Free MERGE(GROWARRAY_NAME, _Free)
+#define GrowArray_DoubleUp MERGE(GROWARRAY_NAME, _DoubleUp)
+#define GrowArray_DoubleUpLimit MERGE(GROWARRAY_NAME, _DoubleUpLimit)
+
+static int GrowArray_Init (GrowArrayObject *o, size_t capacity) WARN_UNUSED;
+static void GrowArray_InitEmpty (GrowArrayObject *o);
+static void GrowArray_Free (GrowArrayObject *o);
+static int GrowArray_DoubleUp (GrowArrayObject *o) WARN_UNUSED;
+static int GrowArray_DoubleUpLimit (GrowArrayObject *o, size_t limit) WARN_UNUSED;
+
+static int GrowArray_Init (GrowArrayObject *o, size_t capacity)
+{
+    if (capacity > GROWARRAY_MAX_CAPACITY) {
+        return 0;
+    }
+    
+    if (capacity == 0) {
+        o->GROWARRAY_ARRAY_MEMBER = NULL;
+    } else {
+        if (!(o->GROWARRAY_ARRAY_MEMBER = BAllocArray(capacity, sizeof(o->GROWARRAY_ARRAY_MEMBER[0])))) {
+            return 0;
+        }
+    }
+    
+    o->GROWARRAY_CAPACITY_MEMBER = capacity;
+    
+    return 1;
+}
+
+static void GrowArray_InitEmpty (GrowArrayObject *o)
+{
+    o->GROWARRAY_ARRAY_MEMBER = NULL;
+    o->GROWARRAY_CAPACITY_MEMBER = 0;
+}
+
+static void GrowArray_Free (GrowArrayObject *o)
+{
+    if (o->GROWARRAY_ARRAY_MEMBER) {
+        BFree(o->GROWARRAY_ARRAY_MEMBER);
+    }
+}
+
+static int GrowArray_DoubleUp (GrowArrayObject *o)
+{
+    return GrowArray_DoubleUpLimit(o, SIZE_MAX);
+}
+
+static int GrowArray_DoubleUpLimit (GrowArrayObject *o, size_t limit)
+{
+    if (o->GROWARRAY_CAPACITY_MEMBER > SIZE_MAX / 2 || o->GROWARRAY_CAPACITY_MEMBER > GROWARRAY_MAX_CAPACITY / 2) {
+        return 0;
+    }
+    
+    size_t newcap = 2 * o->GROWARRAY_CAPACITY_MEMBER;
+    if (newcap == 0) {
+        newcap = 1;
+    }
+    
+    if (newcap > limit) {
+        newcap = limit;
+        if (newcap == o->GROWARRAY_CAPACITY_MEMBER) {
+            return 0;
+        }
+    }
+    
+    void *newarr = BAllocArray(newcap, sizeof(o->GROWARRAY_ARRAY_MEMBER[0]));
+    if (!newarr) {
+        return 0;
+    }
+    
+    memcpy(newarr, o->GROWARRAY_ARRAY_MEMBER, o->GROWARRAY_CAPACITY_MEMBER * sizeof(o->GROWARRAY_ARRAY_MEMBER[0]));
+    
+    BFree(o->GROWARRAY_ARRAY_MEMBER);
+    
+    o->GROWARRAY_ARRAY_MEMBER = newarr;
+    o->GROWARRAY_CAPACITY_MEMBER = newcap;
+    
+    return 1;
+}
+
+#undef GROWARRAY_NAME
+#undef GROWARRAY_OBJECT_TYPE
+#undef GROWARRAY_ARRAY_MEMBER
+#undef GROWARRAY_CAPACITY_MEMBER
+#undef GROWARRAY_MAX_CAPACITY
+
+#undef GrowArrayObject
+#undef GrowArray_Init
+#undef GrowArray_InitEmpty
+#undef GrowArray_Free
+#undef GrowArray_DoubleUp
+#undef GrowArray_DoubleUpLimit
diff --git a/external/badvpn_dns/misc/hashfun.h b/external/badvpn_dns/misc/hashfun.h
new file mode 100644
index 0000000..5e8956a
--- /dev/null
+++ b/external/badvpn_dns/misc/hashfun.h
@@ -0,0 +1,60 @@
+/**
+ * @file hashfun.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_HASHFUN_H
+#define BADVPN_HASHFUN_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+static size_t badvpn_djb2_hash (const uint8_t *str)
+{
+    size_t hash = 5381;
+    int c;
+    
+    while (c = *str++) {
+        hash = ((hash << 5) + hash) + c;
+    }
+    
+    return hash;
+}
+
+static size_t badvpn_djb2_hash_bin (const uint8_t *str, size_t str_len)
+{
+    size_t hash = 5381;
+    
+    while (str_len-- > 0) {
+        int c = *str++;
+        hash = ((hash << 5) + hash) + c;
+    }
+    
+    return hash;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/igmp_proto.h b/external/badvpn_dns/misc/igmp_proto.h
new file mode 100644
index 0000000..9188ea0
--- /dev/null
+++ b/external/badvpn_dns/misc/igmp_proto.h
@@ -0,0 +1,97 @@
+/**
+ * @file igmp_proto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 the IGMP protocol.
+ */
+
+#ifndef BADVPN_MISC_IGMP_PROTO_H
+#define BADVPN_MISC_IGMP_PROTO_H
+
+#include <stdint.h>
+
+#include <misc/packed.h>
+
+#define IGMP_TYPE_MEMBERSHIP_QUERY 0x11
+#define IGMP_TYPE_V1_MEMBERSHIP_REPORT 0x12
+#define IGMP_TYPE_V2_MEMBERSHIP_REPORT 0x16
+#define IGMP_TYPE_V3_MEMBERSHIP_REPORT 0x22
+#define IGMP_TYPE_V2_LEAVE_GROUP 0x17
+
+#define IGMP_RECORD_TYPE_MODE_IS_INCLUDE 1
+#define IGMP_RECORD_TYPE_MODE_IS_EXCLUDE 2
+#define IGMP_RECORD_TYPE_CHANGE_TO_INCLUDE_MODE 3
+#define IGMP_RECORD_TYPE_CHANGE_TO_EXCLUDE_MODE 4
+
+B_START_PACKED
+struct igmp_source {
+    uint32_t addr;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct igmp_base {
+    uint8_t type;
+    uint8_t max_resp_code;
+    uint16_t checksum;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct igmp_v3_query_extra {
+    uint32_t group;
+    uint8_t reserved4_suppress1_qrv3;
+    uint8_t qqic;
+    uint16_t number_of_sources;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct igmp_v3_report_extra {
+    uint16_t reserved;
+    uint16_t number_of_group_records;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct igmp_v3_report_record {
+    uint8_t type;
+    uint8_t aux_data_len;
+    uint16_t number_of_sources;
+    uint32_t group;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct igmp_v2_extra {
+    uint32_t group;
+} B_PACKED;
+B_END_PACKED
+
+#endif
diff --git a/external/badvpn_dns/misc/ipaddr.h b/external/badvpn_dns/misc/ipaddr.h
new file mode 100644
index 0000000..8c7cb05
--- /dev/null
+++ b/external/badvpn_dns/misc/ipaddr.h
@@ -0,0 +1,218 @@
+/**
+ * @file ipaddr.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * IP address parsing functions.
+ */
+
+#ifndef BADVPN_MISC_IPADDR_H
+#define BADVPN_MISC_IPADDR_H
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/parse_number.h>
+#include <misc/find_char.h>
+#include <misc/print_macros.h>
+
+struct ipv4_ifaddr {
+    uint32_t addr;
+    int prefix;
+};
+
+static int ipaddr_parse_ipv4_addr_bin (const char *name, size_t name_len, uint32_t *out_addr);
+static int ipaddr_parse_ipv4_addr (const char *name, uint32_t *out_addr);
+static int ipaddr_parse_ipv4_prefix_bin (const char *str, size_t str_len, int *num);
+static int ipaddr_parse_ipv4_prefix (const char *str, int *num);
+static int ipaddr_parse_ipv4_ifaddr_bin (const char *str, size_t str_len, struct ipv4_ifaddr *out);
+static int ipaddr_parse_ipv4_ifaddr (const char *str, struct ipv4_ifaddr *out);
+static int ipaddr_ipv4_ifaddr_from_addr_mask (uint32_t addr, uint32_t mask, struct ipv4_ifaddr *out);
+static uint32_t ipaddr_ipv4_mask_from_prefix (int prefix);
+static int ipaddr_ipv4_prefix_from_mask (uint32_t mask, int *out_prefix);
+static int ipaddr_ipv4_addrs_in_network (uint32_t addr1, uint32_t addr2, int netprefix);
+
+#define IPADDR_PRINT_MAX 19
+
+static void ipaddr_print_addr (uint32_t addr, char *out);
+static void ipaddr_print_ifaddr (struct ipv4_ifaddr ifaddr, char *out);
+
+int ipaddr_parse_ipv4_addr_bin (const char *name, size_t name_len, uint32_t *out_addr)
+{
+    for (size_t i = 0; ; i++) {
+        size_t j;
+        for (j = 0; j < name_len && name[j] != '.'; j++);
+        
+        if ((j == name_len && i < 3) || (j < name_len && i == 3)) {
+            return 0;
+        }
+        
+        if (j < 1 || j > 3) {
+            return 0;
+        }
+        
+        uintmax_t d;
+        if (!parse_unsigned_integer_bin(name, j, &d)) {
+            return 0;
+        }
+        
+        if (d > 255) {
+            return 0;
+        }
+        
+        ((uint8_t *)out_addr)[i] = d;
+        
+        if (i == 3) {
+            return 1;
+        }
+        
+        name += j + 1;
+        name_len -= j + 1;
+    }
+}
+
+int ipaddr_parse_ipv4_addr (const char *name, uint32_t *out_addr)
+{
+    return ipaddr_parse_ipv4_addr_bin(name, strlen(name), out_addr);
+}
+
+int ipaddr_parse_ipv4_prefix_bin (const char *str, size_t str_len, int *num)
+{
+    uintmax_t d;
+    if (!parse_unsigned_integer_bin(str, str_len, &d)) {
+        return 0;
+    }
+    if (d > 32) {
+        return 0;
+    }
+    
+    *num = d;
+    return 1;
+}
+
+int ipaddr_parse_ipv4_prefix (const char *str, int *num)
+{
+    return ipaddr_parse_ipv4_prefix_bin(str, strlen(str), num);
+}
+
+int ipaddr_parse_ipv4_ifaddr_bin (const char *str, size_t str_len, struct ipv4_ifaddr *out)
+{
+    size_t slash_pos;
+    if (!b_find_char_bin(str, str_len, '/', &slash_pos)) {
+        return 0;
+    }
+    
+    return (ipaddr_parse_ipv4_addr_bin(str, slash_pos, &out->addr) &&
+            ipaddr_parse_ipv4_prefix_bin(str + slash_pos + 1, str_len - slash_pos - 1, &out->prefix));
+}
+
+int ipaddr_parse_ipv4_ifaddr (const char *str, struct ipv4_ifaddr *out)
+{
+    return ipaddr_parse_ipv4_ifaddr_bin(str, strlen(str), out);
+}
+
+int ipaddr_ipv4_ifaddr_from_addr_mask (uint32_t addr, uint32_t mask, struct ipv4_ifaddr *out)
+{
+    int prefix;
+    if (!ipaddr_ipv4_prefix_from_mask(mask, &prefix)) {
+        return 0;
+    }
+    
+    out->addr = addr;
+    out->prefix = prefix;
+    return 1;
+}
+
+uint32_t ipaddr_ipv4_mask_from_prefix (int prefix)
+{
+    ASSERT(prefix >= 0)
+    ASSERT(prefix <= 32)
+    
+    uint32_t t = 0;
+    for (int i = 0; i < prefix; i++) {
+        t |= 1 << (32 - i - 1);
+    }
+    
+    return hton32(t);
+}
+
+int ipaddr_ipv4_prefix_from_mask (uint32_t mask, int *out_prefix)
+{
+    uint32_t t = 0;
+    int i;
+    for (i = 0; i <= 32; i++) {
+        if (ntoh32(mask) == t) {
+            break;
+        }
+        if (i < 32) {
+            t |= (1 << (32 - i - 1));
+        }
+    }
+    if (!(i <= 32)) {
+        return 0;
+    }
+    
+    *out_prefix = i;
+    return 1;
+}
+
+int ipaddr_ipv4_addrs_in_network (uint32_t addr1, uint32_t addr2, int netprefix)
+{
+    ASSERT(netprefix >= 0)
+    ASSERT(netprefix <= 32)
+    
+    uint32_t mask = ipaddr_ipv4_mask_from_prefix(netprefix);
+    
+    return !!((addr1 & mask) == (addr2 & mask));
+}
+
+void ipaddr_print_addr (uint32_t addr, char *out)
+{
+    ASSERT(out)
+    
+    uint8_t *b = (uint8_t *)&addr;
+    
+    sprintf(out, "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8,
+            b[0], b[1], b[2], b[3]);
+}
+
+void ipaddr_print_ifaddr (struct ipv4_ifaddr ifaddr, char *out)
+{
+    ASSERT(ifaddr.prefix >= 0)
+    ASSERT(ifaddr.prefix <= 32)
+    ASSERT(out)
+    
+    uint8_t *b = (uint8_t *)&ifaddr.addr;
+    
+    sprintf(out, "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"/%d",
+            b[0], b[1], b[2], b[3], ifaddr.prefix);
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/ipaddr6.h b/external/badvpn_dns/misc/ipaddr6.h
new file mode 100644
index 0000000..ebacd94
--- /dev/null
+++ b/external/badvpn_dns/misc/ipaddr6.h
@@ -0,0 +1,400 @@
+/**
+ * @file ipaddr6.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * IPv6 address parsing functions.
+ */
+
+#ifndef BADVPN_MISC_IPADDR6_H
+#define BADVPN_MISC_IPADDR6_H
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/parse_number.h>
+#include <misc/find_char.h>
+#include <misc/print_macros.h>
+
+struct ipv6_addr {
+    uint8_t bytes[16];
+};
+
+struct ipv6_ifaddr {
+    struct ipv6_addr addr;
+    int prefix;
+};
+
+static int ipaddr6_parse_ipv6_addr_bin (const char *name, size_t name_len, struct ipv6_addr *out_addr);
+static int ipaddr6_parse_ipv6_addr (const char *name, struct ipv6_addr *out_addr);
+static int ipaddr6_parse_ipv6_prefix_bin (const char *str, size_t str_len, int *out_num);
+static int ipaddr6_parse_ipv6_prefix (const char *str, int *out_num);
+static int ipaddr6_parse_ipv6_ifaddr_bin (const char *str, size_t str_len, struct ipv6_ifaddr *out);
+static int ipaddr6_parse_ipv6_ifaddr (const char *str, struct ipv6_ifaddr *out);
+static int ipaddr6_ipv6_ifaddr_from_addr_mask (struct ipv6_addr addr, struct ipv6_addr mask, struct ipv6_ifaddr *out);
+static void ipaddr6_ipv6_mask_from_prefix (int prefix, struct ipv6_addr *out_mask);
+static int ipaddr6_ipv6_prefix_from_mask (struct ipv6_addr mask, int *out_prefix);
+static int ipaddr6_ipv6_addrs_in_network (struct ipv6_addr addr1, struct ipv6_addr addr2, int netprefix);
+
+#define IPADDR6_PRINT_MAX 44
+
+static void ipaddr6_print_addr (struct ipv6_addr addr, char *out_buf);
+static void ipaddr6_print_ifaddr (struct ipv6_ifaddr addr, char *out_buf);
+
+int ipaddr6_parse_ipv6_addr_bin (const char *name, size_t name_len, struct ipv6_addr *out_addr)
+{
+    int num_blocks = 0;
+    int compress_pos = -1;
+    uint16_t block = 0;
+    int empty = 1;
+    
+    size_t i = 0;
+    
+    while (i < name_len) {
+        if (name[i] == '.') {
+            goto ipv4_ending;
+        } else if (name[i] == ':') {
+            int is_double = (i + 1 < name_len && name[i + 1] == ':');
+            
+            if (i > 0) {
+                if (empty || num_blocks == 7) {
+                    return 0;
+                }
+                out_addr->bytes[2 * num_blocks + 0] = block >> 8;
+                out_addr->bytes[2 * num_blocks + 1] = block & 0xFF;
+                num_blocks++;
+                block = 0;
+                empty = 1;
+            }
+            else if (!is_double) {
+                return 0;
+            }
+            
+            if (is_double) {
+                if (compress_pos != -1) {
+                    return 0;
+                }
+                compress_pos = num_blocks;
+            }
+            
+            i += 1 + is_double;
+        } else {
+            int digit = decode_hex_digit(name[i]);
+            if (digit < 0) {
+                return 0;
+            }
+            if (block > UINT16_MAX / 16) {
+                return 0;
+            }
+            block *= 16;
+            if (digit > UINT16_MAX - block) {
+                return 0;
+            }
+            block += digit;
+            empty = 0;
+            i += 1;
+        }
+    }
+    
+    if (!empty) {
+        out_addr->bytes[2 * num_blocks + 0] = block >> 8;
+        out_addr->bytes[2 * num_blocks + 1] = block & 0xFF;
+        num_blocks++;
+    }
+    else if (num_blocks != compress_pos) {
+        return 0;
+    }
+    
+ipv4_done:
+    if (compress_pos == -1) {
+        if (num_blocks != 8) {
+            return 0;
+        }
+        compress_pos = 0;
+    }
+    
+    int num_rear = num_blocks - compress_pos;
+    memmove(out_addr->bytes + 2 * (8 - num_rear), out_addr->bytes + 2 * compress_pos, 2 * num_rear);
+    memset(out_addr->bytes + 2 * compress_pos, 0, 2 * (8 - num_rear - compress_pos));
+    
+    return 1;
+    
+ipv4_ending:
+    if (empty || (num_blocks == 0 && compress_pos == -1)) {
+        return 0;
+    }
+    
+    while (name[i - 1] != ':') {
+        i--;
+    }
+    
+    uint8_t bytes[4];
+    int cur_byte = 0;
+    uint8_t byte = 0;
+    empty = 1;
+    
+    while (i < name_len) {
+        if (name[i] == '.') {
+            if (empty || cur_byte == 3) {
+                return 0;
+            }
+            bytes[cur_byte] = byte;
+            cur_byte++;
+            byte = 0;
+            empty = 1;
+        } else {
+            if (!empty && byte == 0) {
+                return 0;
+            }
+            int digit = decode_decimal_digit(name[i]);
+            if (digit < 0) {
+                return 0;
+            }
+            if (byte > UINT8_MAX / 10) {
+                return 0;
+            }
+            byte *= 10;
+            if (digit > UINT8_MAX - byte) {
+                return 0;
+            }
+            byte += digit;
+            empty = 0;
+        }
+        i++;
+    }
+    
+    if (cur_byte != 3 || empty) {
+        return 0;
+    }
+    bytes[cur_byte] = byte;
+    
+    if (8 - num_blocks < 2) {
+        return 0;
+    }
+    memcpy(out_addr->bytes + 2 * num_blocks, bytes, 4);
+    num_blocks += 2;
+    
+    goto ipv4_done;
+}
+
+int ipaddr6_parse_ipv6_addr (const char *name, struct ipv6_addr *out_addr)
+{
+    return ipaddr6_parse_ipv6_addr_bin(name, strlen(name), out_addr);
+}
+
+int ipaddr6_parse_ipv6_prefix_bin (const char *str, size_t str_len, int *out_num)
+{
+    uintmax_t d;
+    if (!parse_unsigned_integer_bin(str, str_len, &d)) {
+        return 0;
+    }
+    if (d > 128) {
+        return 0;
+    }
+    
+    *out_num = d;
+    return 1;
+}
+
+int ipaddr6_parse_ipv6_prefix (const char *str, int *out_num)
+{
+    return ipaddr6_parse_ipv6_prefix_bin(str, strlen(str), out_num);
+}
+
+int ipaddr6_parse_ipv6_ifaddr_bin (const char *str, size_t str_len, struct ipv6_ifaddr *out)
+{
+    size_t slash_pos;
+    if (!b_find_char_bin(str, str_len, '/', &slash_pos)) {
+        return 0;
+    }
+    
+    return (ipaddr6_parse_ipv6_addr_bin(str, slash_pos, &out->addr) &&
+            ipaddr6_parse_ipv6_prefix_bin(str + slash_pos + 1, str_len - slash_pos - 1, &out->prefix));
+}
+
+int ipaddr6_parse_ipv6_ifaddr (const char *str, struct ipv6_ifaddr *out)
+{
+    return ipaddr6_parse_ipv6_ifaddr_bin(str, strlen(str), out);
+}
+
+int ipaddr6_ipv6_ifaddr_from_addr_mask (struct ipv6_addr addr, struct ipv6_addr mask, struct ipv6_ifaddr *out)
+{
+    int prefix;
+    if (!ipaddr6_ipv6_prefix_from_mask(mask, &prefix)) {
+        return 0;
+    }
+    
+    out->addr = addr;
+    out->prefix = prefix;
+    return 1;
+}
+
+void ipaddr6_ipv6_mask_from_prefix (int prefix, struct ipv6_addr *out_mask)
+{
+    ASSERT(prefix >= 0)
+    ASSERT(prefix <= 128)
+    
+    int quot = prefix / 8;
+    int rem = prefix % 8;
+    
+    if (quot > 0) {
+        memset(out_mask->bytes, UINT8_MAX, quot);
+    }
+    if (16 - quot > 0) {
+        memset(out_mask->bytes + quot, 0, 16 - quot);
+    }
+    
+    for (int i = 0; i < rem; i++) {
+        out_mask->bytes[quot] |= (uint8_t)1 << (8 - i - 1);
+    }
+}
+
+int ipaddr6_ipv6_prefix_from_mask (struct ipv6_addr mask, int *out_prefix)
+{
+    int prefix = 0;
+    int i = 0;
+    
+    while (i < 16 && mask.bytes[i] == UINT8_MAX) {
+        prefix += 8;
+        i++;
+    }
+    
+    if (i < 16) {
+        uint8_t t = 0;
+        int j;
+        for (j = 0; j <= 8; j++) {
+            if (mask.bytes[i] == t) {
+                break;
+            }
+            if (j < 8) {
+                t |= ((uint8_t)1 << (8 - j - 1));
+            }
+        }
+        if (!(j <= 8)) {
+            return 0;
+        }
+        
+        prefix += j;
+        i++;
+        
+        while (i < 16) {
+            if (mask.bytes[i] != 0) {
+                return 0;
+            }
+            i++;
+        }
+    }
+    
+    *out_prefix = prefix;
+    return 1;
+}
+
+int ipaddr6_ipv6_addrs_in_network (struct ipv6_addr addr1, struct ipv6_addr addr2, int netprefix)
+{
+    ASSERT(netprefix >= 0)
+    ASSERT(netprefix <= 128)
+    
+    int quot = netprefix / 8;
+    int rem = netprefix % 8;
+    
+    if (memcmp(addr1.bytes, addr2.bytes, quot)) {
+        return 0;
+    }
+    
+    if (rem == 0) {
+        return 1;
+    }
+    
+    uint8_t t = 0;
+    for (int i = 0; i < rem; i++) {
+        t |= (uint8_t)1 << (8 - i - 1);
+    }
+    
+    return ((addr1.bytes[quot] & t) == (addr2.bytes[quot] & t));
+}
+
+void ipaddr6_print_addr (struct ipv6_addr addr, char *out_buf)
+{
+    int largest_start = 0;
+    int largest_len = 0;
+    int current_start = 0;
+    int current_len = 0;
+    
+    for (int i = 0; i < 8; i++) {
+        if (addr.bytes[2 * i] == 0 && addr.bytes[2 * i + 1] == 0) {
+            current_len++;
+            if (current_len > largest_len) {
+                largest_start = current_start;
+                largest_len = current_len;
+            }
+        } else {
+            current_start = i + 1;
+            current_len = 0;
+        }
+    }
+    
+    if (largest_len > 1) {
+        for (int i = 0; i < largest_start; i++) {
+            uint16_t block = ((uint16_t)addr.bytes[2 * i] << 8) | addr.bytes[2 * i + 1];
+            out_buf += sprintf(out_buf, "%"PRIx16":", block);
+        }
+        if (largest_start == 0) {
+            out_buf += sprintf(out_buf, ":");
+        }
+        
+        for (int i = largest_start + largest_len; i < 8; i++) {
+            uint16_t block = ((uint16_t)addr.bytes[2 * i] << 8) | addr.bytes[2 * i + 1];
+            out_buf += sprintf(out_buf, ":%"PRIx16, block);
+        }
+        if (largest_start + largest_len == 8) {
+            out_buf += sprintf(out_buf, ":");
+        }
+    } else {
+        const char *prefix = "";
+        for (int i = 0; i < 8; i++) {
+            uint16_t block = ((uint16_t)addr.bytes[2 * i] << 8) | addr.bytes[2 * i + 1];
+            out_buf += sprintf(out_buf, "%s%"PRIx16, prefix, block);
+            prefix = ":";
+        }
+    }
+}
+
+void ipaddr6_print_ifaddr (struct ipv6_ifaddr addr, char *out_buf)
+{
+    ASSERT(addr.prefix >= 0)
+    ASSERT(addr.prefix <= 128)
+    
+    ipaddr6_print_addr(addr.addr, out_buf);
+    sprintf(out_buf + strlen(out_buf), "/%d", addr.prefix);
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/ipv4_proto.h b/external/badvpn_dns/misc/ipv4_proto.h
new file mode 100644
index 0000000..fea7260
--- /dev/null
+++ b/external/badvpn_dns/misc/ipv4_proto.h
@@ -0,0 +1,145 @@
+/**
+ * @file ipv4_proto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 the IPv4 protocol.
+ */
+
+#ifndef BADVPN_MISC_IPV4_PROTO_H
+#define BADVPN_MISC_IPV4_PROTO_H
+
+#include <stdint.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/packed.h>
+#include <misc/read_write_int.h>
+
+#define IPV4_PROTOCOL_IGMP 2
+#define IPV4_PROTOCOL_UDP 17
+
+B_START_PACKED
+struct ipv4_header {
+    uint8_t version4_ihl4;
+    uint8_t ds;
+    uint16_t total_length;
+    //
+    uint16_t identification;
+    uint16_t flags3_fragmentoffset13;
+    //
+    uint8_t ttl;
+    uint8_t protocol;
+    uint16_t checksum;
+    //
+    uint32_t source_address;
+    //
+    uint32_t destination_address;
+} B_PACKED;
+B_END_PACKED
+
+#define IPV4_GET_VERSION(_header) (((_header).version4_ihl4&0xF0)>>4)
+#define IPV4_GET_IHL(_header) (((_header).version4_ihl4&0x0F)>>0)
+
+#define IPV4_MAKE_VERSION_IHL(size) (((size)/4) + (4 << 4))
+
+static uint16_t ipv4_checksum (const struct ipv4_header *header, const char *extra, uint16_t extra_len)
+{
+    ASSERT(extra_len % 2 == 0)
+    ASSERT(extra_len == 0 || extra)
+    
+    uint32_t t = 0;
+    
+    for (uint16_t i = 0; i < sizeof(*header) / 2; i++) {
+        t += badvpn_read_be16((const char *)header + 2 * i);
+    }
+    
+    for (uint16_t i = 0; i < extra_len / 2; i++) {
+        t += badvpn_read_be16((const char *)extra + 2 * i);
+    }
+    
+    while (t >> 16) {
+        t = (t & 0xFFFF) + (t >> 16);
+    }
+    
+    return hton16(~t);
+}
+
+static int ipv4_check (uint8_t *data, int data_len, struct ipv4_header *out_header, uint8_t **out_payload, int *out_payload_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(out_header)
+    ASSERT(out_payload)
+    ASSERT(out_payload_len)
+    
+    // check base header
+    if (data_len < sizeof(struct ipv4_header)) {
+        return 0;
+    }
+    memcpy(out_header, data, sizeof(*out_header));
+    
+    // check version
+    if (IPV4_GET_VERSION(*out_header) != 4) {
+        return 0;
+    }
+    
+    // check options
+    uint16_t header_len = IPV4_GET_IHL(*out_header) * 4;
+    if (header_len < sizeof(struct ipv4_header)) {
+        return 0;
+    }
+    if (header_len > data_len) {
+        return 0;
+    }
+    
+    // check total length
+    uint16_t total_length = ntoh16(out_header->total_length);
+    if (total_length < header_len) {
+        return 0;
+    }
+    if (total_length > data_len) {
+        return 0;
+    }
+    
+    // check checksum
+    uint16_t checksum_in_packet = out_header->checksum;
+    out_header->checksum = hton16(0);
+    uint16_t checksum_computed = ipv4_checksum(out_header, (char *)data + sizeof(*out_header), header_len - sizeof(*out_header));
+    out_header->checksum = checksum_in_packet;
+    if (checksum_in_packet != checksum_computed) {
+        return 0;
+    }
+    
+    *out_payload = data + header_len;
+    *out_payload_len = total_length - header_len;
+    
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/ipv6_proto.h b/external/badvpn_dns/misc/ipv6_proto.h
new file mode 100644
index 0000000..b255541
--- /dev/null
+++ b/external/badvpn_dns/misc/ipv6_proto.h
@@ -0,0 +1,86 @@
+/**
+ * @file ipv6_proto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_IPV6_PROTO_H
+#define BADVPN_IPV6_PROTO_H
+
+#include <stdint.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/packed.h>
+
+#define IPV6_NEXT_IGMP 2
+#define IPV6_NEXT_UDP 17
+
+B_START_PACKED
+struct ipv6_header {
+    uint8_t version4_tc4;
+    uint8_t tc4_fl4;
+    uint16_t fl;
+    uint16_t payload_length;
+    uint8_t next_header;
+    uint8_t hop_limit;
+    uint8_t source_address[16];
+    uint8_t destination_address[16];
+} B_PACKED;
+B_END_PACKED
+
+static int ipv6_check (uint8_t *data, int data_len, struct ipv6_header *out_header, uint8_t **out_payload, int *out_payload_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(out_header)
+    ASSERT(out_payload)
+    ASSERT(out_payload_len)
+    
+    // check base header
+    if (data_len < sizeof(struct ipv6_header)) {
+        return 0;
+    }
+    memcpy(out_header, data, sizeof(*out_header));
+    
+    // check version
+    if ((ntoh8(out_header->version4_tc4) >> 4) != 6) {
+        return 0;
+    }
+    
+    // check payload length
+    uint16_t payload_length = ntoh16(out_header->payload_length);
+    if (payload_length > data_len - sizeof(struct ipv6_header)) {
+        return 0;
+    }
+    
+    *out_payload = data + sizeof(struct ipv6_header);
+    *out_payload_len = payload_length;
+    
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/loggers_string.h b/external/badvpn_dns/misc/loggers_string.h
new file mode 100644
index 0000000..66986b8
--- /dev/null
+++ b/external/badvpn_dns/misc/loggers_string.h
@@ -0,0 +1,43 @@
+/**
+ * @file loggers_string.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * List of available loggers.
+ */
+
+#ifndef BADVPN_MISC_LOGGERSSTRING_H
+#define BADVPN_MISC_LOGGERSSTRING_H
+
+#ifdef BADVPN_USE_WINAPI
+#define LOGGERS_STRING "stdout"
+#else
+#define LOGGERS_STRING "stdout/syslog"
+#endif
+
+#endif
diff --git a/external/badvpn_dns/misc/loglevel.h b/external/badvpn_dns/misc/loglevel.h
new file mode 100644
index 0000000..6e9f911
--- /dev/null
+++ b/external/badvpn_dns/misc/loglevel.h
@@ -0,0 +1,80 @@
+/**
+ * @file loglevel.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Log level specification parsing function.
+ */
+
+#ifndef BADVPN_MISC_LOGLEVEL_H
+#define BADVPN_MISC_LOGLEVEL_H
+
+#include <string.h>
+
+#include <base/BLog.h>
+
+/**
+ * Parses the log level string.
+ * 
+ * @param str log level string. Recognizes none, error, warning, notice,
+ *            info, debug.
+ * @return 0 for none, one of BLOG_* for some log level, -1 for unrecognized
+ */
+static int parse_loglevel (char *str);
+
+int parse_loglevel (char *str)
+{
+    if (!strcmp(str, "none")) {
+        return 0;
+    }
+    if (!strcmp(str, "error")) {
+        return BLOG_ERROR;
+    }
+    if (!strcmp(str, "warning")) {
+        return BLOG_WARNING;
+    }
+    if (!strcmp(str, "notice")) {
+        return BLOG_NOTICE;
+    }
+    if (!strcmp(str, "info")) {
+        return BLOG_INFO;
+    }
+    if (!strcmp(str, "debug")) {
+        return BLOG_DEBUG;
+    }
+    
+    char *endptr;
+    long int res = strtol(str, &endptr, 10);
+    if (*str && !*endptr && res >= 0 && res <= BLOG_DEBUG) {
+        return res;
+    }
+    
+    return -1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/maxalign.h b/external/badvpn_dns/misc/maxalign.h
new file mode 100644
index 0000000..cb1f460
--- /dev/null
+++ b/external/badvpn_dns/misc/maxalign.h
@@ -0,0 +1,53 @@
+/**
+ * @file maxalign.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_MAXALIGN_H
+#define BADVPN_MAXALIGN_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef union {
+    short a;
+    long b;
+    long long c;
+    double d;
+    long double e;
+    void *f;
+    uint8_t g;
+    uint16_t h;
+    uint32_t i;
+    uint64_t j;
+    size_t k;
+    void (*l) (void);
+} bmax_align_t;
+
+#define BMAX_ALIGN (__alignof(bmax_align_t))
+
+#endif
diff --git a/external/badvpn_dns/misc/merge.h b/external/badvpn_dns/misc/merge.h
new file mode 100644
index 0000000..5322771
--- /dev/null
+++ b/external/badvpn_dns/misc/merge.h
@@ -0,0 +1,36 @@
+/**
+ * @file merge.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_MERGE_H
+#define BADVPN_MERGE_H
+
+#define MERGE_HELPER(x, y) x ## y
+#define MERGE(x, y) MERGE_HELPER(x, y)
+
+#endif
diff --git a/external/badvpn_dns/misc/minmax.h b/external/badvpn_dns/misc/minmax.h
new file mode 100644
index 0000000..ca82055
--- /dev/null
+++ b/external/badvpn_dns/misc/minmax.h
@@ -0,0 +1,56 @@
+/**
+ * @file minmax.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Minimum and maximum macros.
+ */
+
+#ifndef BADVPN_MISC_MINMAX_H
+#define BADVPN_MISC_MINMAX_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define DEFINE_BMINMAX(name, type) \
+static type bmin ## name (type a, type b) { return (a < b ? a : b); } \
+static type bmax ## name (type a, type b) { return (a > b ? a : b); }
+
+DEFINE_BMINMAX(_size, size_t)
+DEFINE_BMINMAX(_int, int)
+DEFINE_BMINMAX(_int8, int8_t)
+DEFINE_BMINMAX(_int16, int16_t)
+DEFINE_BMINMAX(_int32, int32_t)
+DEFINE_BMINMAX(_int64, int64_t)
+DEFINE_BMINMAX(_uint, unsigned int)
+DEFINE_BMINMAX(_uint8, uint8_t)
+DEFINE_BMINMAX(_uint16, uint16_t)
+DEFINE_BMINMAX(_uint32, uint32_t)
+DEFINE_BMINMAX(_uint64, uint64_t)
+
+#endif
diff --git a/external/badvpn_dns/misc/modadd.h b/external/badvpn_dns/misc/modadd.h
new file mode 100644
index 0000000..4e4f04a
--- /dev/null
+++ b/external/badvpn_dns/misc/modadd.h
@@ -0,0 +1,59 @@
+/**
+ * @file modadd.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Modular addition macro.
+ * 
+ * Calculates (x + y) mod m, assuming
+ * 0 <= x < m and 0 <= y < m.
+ */
+
+#ifndef BADVPN_MISC_MODADD_H
+#define BADVPN_MISC_MODADD_H
+
+#include <misc/debug.h>
+
+#define DECLARE_BMODADD(type, name) \
+static type bmodadd_##name (type x, type y, type m) \
+{ \
+    ASSERT(x >= 0) \
+    ASSERT(x < m) \
+    ASSERT(y >= 0) \
+    ASSERT(y < m) \
+     \
+    if (y >= m - x) { \
+        return (y - (m - x)); \
+    } else { \
+        return (x + y); \
+    } \
+} \
+
+DECLARE_BMODADD(int, int)
+
+#endif
diff --git a/external/badvpn_dns/misc/mswsock.h b/external/badvpn_dns/misc/mswsock.h
new file mode 100644
index 0000000..4f4c38d
--- /dev/null
+++ b/external/badvpn_dns/misc/mswsock.h
@@ -0,0 +1,229 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+
+#include <winsock2.h>
+
+#ifndef _MSWSOCK_
+#define _MSWSOCK_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SO_CONNDATA 0x7000
+#define SO_CONNOPT 0x7001
+#define SO_DISCDATA 0x7002
+#define SO_DISCOPT 0x7003
+#define SO_CONNDATALEN 0x7004
+#define SO_CONNOPTLEN 0x7005
+#define SO_DISCDATALEN 0x7006
+#define SO_DISCOPTLEN 0x7007
+
+#define SO_OPENTYPE 0x7008
+
+#define SO_SYNCHRONOUS_ALERT 0x10
+#define SO_SYNCHRONOUS_NONALERT 0x20
+
+#define SO_MAXDG 0x7009
+#define SO_MAXPATHDG 0x700A
+#define SO_UPDATE_ACCEPT_CONTEXT 0x700B
+#define SO_CONNECT_TIME 0x700C
+#define SO_UPDATE_CONNECT_CONTEXT 0x7010
+
+#define TCP_BSDURGENT 0x7000
+
+#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12)
+#if (_WIN32_WINNT < 0x0600) && (_WIN32_WINNT >= 0x0501)
+#define SIO_SOCKET_CLOSE_NOTIFY _WSAIOW(IOC_VENDOR,13)
+#endif /* >= XP && < VISTA */
+#if (_WIN32_WINNT >= 0x0600)
+#define SIO_BSP_HANDLE _WSAIOR(IOC_WS2,27)
+#define SIO_BSP_HANDLE_SELECT _WSAIOR(IOC_WS2,28)
+#define SIO_BSP_HANDLE_POLL _WSAIOR(IOC_WS2,29)
+
+#define SIO_EXT_SELECT _WSAIORW(IOC_WS2,30)
+#define SIO_EXT_POLL _WSAIORW(IOC_WS2,31)
+#define SIO_EXT_SENDMSG _WSAIORW(IOC_WS2,32)
+
+#define SIO_BASE_HANDLE _WSAIOR(IOC_WS2,34)
+#endif /* _WIN32_WINNT >= 0x0600 */
+
+#ifndef __MSWSOCK_WS1_SHARED
+  int WINAPI WSARecvEx(SOCKET s,char *buf,int len,int *flags);
+#endif /* __MSWSOCK_WS1_SHARED */
+
+#define TF_DISCONNECT 0x01
+#define TF_REUSE_SOCKET 0x02
+#define TF_WRITE_BEHIND 0x04
+#define TF_USE_DEFAULT_WORKER 0x00
+#define TF_USE_SYSTEM_THREAD 0x10
+#define TF_USE_KERNEL_APC 0x20
+
+#include <psdk_inc/_xmitfile.h>
+#ifndef __MSWSOCK_WS1_SHARED
+  WINBOOL WINAPI TransmitFile(SOCKET hSocket,HANDLE hFile,DWORD nNumberOfBytesToWrite,DWORD nNumberOfBytesPerSend,LPOVERLAPPED lpOverlapped,LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,DWORD dwReserved);
+  WINBOOL WINAPI AcceptEx(SOCKET sListenSocket,SOCKET sAcceptSocket,PVOID lpOutputBuffer,DWORD dwReceiveDataLength,DWORD dwLocalAddressLength,DWORD dwRemoteAddressLength,LPDWORD lpdwBytesReceived,LPOVERLAPPED lpOverlapped);
+  VOID WINAPI GetAcceptExSockaddrs(PVOID lpOutputBuffer,DWORD dwReceiveDataLength,DWORD dwLocalAddressLength,DWORD dwRemoteAddressLength,struct sockaddr **LocalSockaddr,LPINT LocalSockaddrLength,struct sockaddr **RemoteSockaddr,LPINT RemoteSockaddrLength);
+#endif /* __MSWSOCK_WS1_SHARED */
+
+  typedef WINBOOL (WINAPI *LPFN_TRANSMITFILE)(SOCKET hSocket,HANDLE hFile,DWORD nNumberOfBytesToWrite,DWORD nNumberOfBytesPerSend,LPOVERLAPPED lpOverlapped,LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,DWORD dwReserved);
+
+#define WSAID_TRANSMITFILE {0xb5367df0,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
+
+  typedef WINBOOL (WINAPI *LPFN_ACCEPTEX)(SOCKET sListenSocket,SOCKET sAcceptSocket,PVOID lpOutputBuffer,DWORD dwReceiveDataLength,DWORD dwLocalAddressLength,DWORD dwRemoteAddressLength,LPDWORD lpdwBytesReceived,LPOVERLAPPED lpOverlapped);
+
+#define WSAID_ACCEPTEX {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
+
+  typedef VOID (WINAPI *LPFN_GETACCEPTEXSOCKADDRS)(PVOID lpOutputBuffer,DWORD dwReceiveDataLength,DWORD dwLocalAddressLength,DWORD dwRemoteAddressLength,struct sockaddr **LocalSockaddr,LPINT LocalSockaddrLength,struct sockaddr **RemoteSockaddr,LPINT RemoteSockaddrLength);
+
+#define WSAID_GETACCEPTEXSOCKADDRS {0xb5367df2,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
+
+  typedef struct _TRANSMIT_PACKETS_ELEMENT {
+    ULONG dwElFlags;
+#define TP_ELEMENT_MEMORY 1
+#define TP_ELEMENT_FILE 2
+#define TP_ELEMENT_EOP 4
+    ULONG cLength;
+    __MINGW_EXTENSION union {
+      __MINGW_EXTENSION struct {
+	LARGE_INTEGER nFileOffset;
+	HANDLE hFile;
+      };
+      PVOID pBuffer;
+    };
+  } TRANSMIT_PACKETS_ELEMENT,*PTRANSMIT_PACKETS_ELEMENT,*LPTRANSMIT_PACKETS_ELEMENT;
+
+#define TP_DISCONNECT TF_DISCONNECT
+#define TP_REUSE_SOCKET TF_REUSE_SOCKET
+#define TP_USE_DEFAULT_WORKER TF_USE_DEFAULT_WORKER
+#define TP_USE_SYSTEM_THREAD TF_USE_SYSTEM_THREAD
+#define TP_USE_KERNEL_APC TF_USE_KERNEL_APC
+
+  typedef WINBOOL (WINAPI *LPFN_TRANSMITPACKETS) (SOCKET hSocket,LPTRANSMIT_PACKETS_ELEMENT lpPacketArray,DWORD nElementCount,DWORD nSendSize,LPOVERLAPPED lpOverlapped,DWORD dwFlags);
+
+#define WSAID_TRANSMITPACKETS {0xd9689da0,0x1f90,0x11d3,{0x99,0x71,0x00,0xc0,0x4f,0x68,0xc8,0x76}}
+
+  typedef WINBOOL (WINAPI *LPFN_CONNECTEX)(SOCKET s,const struct sockaddr *name,int namelen,PVOID lpSendBuffer,DWORD dwSendDataLength,LPDWORD lpdwBytesSent,LPOVERLAPPED lpOverlapped);
+
+#define WSAID_CONNECTEX {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}}
+
+  typedef WINBOOL (WINAPI *LPFN_DISCONNECTEX)(SOCKET s,LPOVERLAPPED lpOverlapped,DWORD dwFlags,DWORD dwReserved);
+
+#define WSAID_DISCONNECTEX {0x7fda2e11,0x8630,0x436f,{0xa0,0x31,0xf5,0x36,0xa6,0xee,0xc1,0x57}}
+
+#define DE_REUSE_SOCKET TF_REUSE_SOCKET
+
+#define NLA_NAMESPACE_GUID {0x6642243a,0x3ba8,0x4aa6,{0xba,0xa5,0x2e,0xb,0xd7,0x1f,0xdd,0x83}}
+
+#define NLA_SERVICE_CLASS_GUID {0x37e515,0xb5c9,0x4a43,{0xba,0xda,0x8b,0x48,0xa8,0x7a,0xd2,0x39}}
+
+#define NLA_ALLUSERS_NETWORK 0x00000001
+#define NLA_FRIENDLY_NAME 0x00000002
+
+  typedef enum _NLA_BLOB_DATA_TYPE {
+    NLA_RAW_DATA = 0,NLA_INTERFACE = 1,NLA_802_1X_LOCATION = 2,NLA_CONNECTIVITY = 3,NLA_ICS = 4
+  } NLA_BLOB_DATA_TYPE,*PNLA_BLOB_DATA_TYPE;
+
+  typedef enum _NLA_CONNECTIVITY_TYPE {
+    NLA_NETWORK_AD_HOC = 0,NLA_NETWORK_MANAGED = 1,NLA_NETWORK_UNMANAGED = 2,NLA_NETWORK_UNKNOWN = 3
+  } NLA_CONNECTIVITY_TYPE,*PNLA_CONNECTIVITY_TYPE;
+
+  typedef enum _NLA_INTERNET {
+    NLA_INTERNET_UNKNOWN = 0,NLA_INTERNET_NO = 1,NLA_INTERNET_YES = 2
+  } NLA_INTERNET,*PNLA_INTERNET;
+
+  typedef struct _NLA_BLOB {
+    struct {
+      NLA_BLOB_DATA_TYPE type;
+      DWORD dwSize;
+      DWORD nextOffset;
+    } header;
+    union {
+      CHAR rawData[1];
+      struct {
+	DWORD dwType;
+	DWORD dwSpeed;
+	CHAR adapterName[1];
+      } interfaceData;
+      struct {
+	CHAR information[1];
+      } locationData;
+      struct {
+	NLA_CONNECTIVITY_TYPE type;
+	NLA_INTERNET internet;
+      } connectivity;
+      struct {
+	struct {
+	  DWORD speed;
+	  DWORD type;
+	  DWORD state;
+	  WCHAR machineName[256];
+	  WCHAR sharedAdapterName[256];
+	} remote;
+      } ICS;
+    } data;
+  } NLA_BLOB,*PNLA_BLOB,*LPNLA_BLOB;
+
+#ifdef BADVPN_SHIPPED_MSWSOCK_DECLARE_WSAMSG
+  typedef struct _WSAMSG {
+    LPSOCKADDR name;
+    INT namelen;
+    LPWSABUF lpBuffers;
+    DWORD dwBufferCount;
+    WSABUF Control;
+    DWORD dwFlags;
+  } WSAMSG,*PWSAMSG,*LPWSAMSG;
+#endif
+  
+  typedef struct _WSACMSGHDR {
+    SIZE_T cmsg_len;
+    INT cmsg_level;
+    INT cmsg_type;
+  } WSACMSGHDR,*PWSACMSGHDR,*LPWSACMSGHDR;
+
+#define WSA_CMSGHDR_ALIGN(length) (((length) + TYPE_ALIGNMENT(WSACMSGHDR)-1) & (~(TYPE_ALIGNMENT(WSACMSGHDR)-1)))
+#define WSA_CMSGDATA_ALIGN(length) (((length) + MAX_NATURAL_ALIGNMENT-1) & (~(MAX_NATURAL_ALIGNMENT-1)))
+#define WSA_CMSG_FIRSTHDR(msg) (((msg)->Control.len >= sizeof(WSACMSGHDR)) ? (LPWSACMSGHDR)(msg)->Control.buf : (LPWSACMSGHDR)NULL)
+#define WSA_CMSG_NXTHDR(msg,cmsg) ((!(cmsg)) ? WSA_CMSG_FIRSTHDR(msg) : ((((u_char *)(cmsg) + WSA_CMSGHDR_ALIGN((cmsg)->cmsg_len) + sizeof(WSACMSGHDR)) > (u_char *)((msg)->Control.buf) + (msg)->Control.len) ? (LPWSACMSGHDR)NULL : (LPWSACMSGHDR)((u_char *)(cmsg) + WSA_CMSGHDR_ALIGN((cmsg)->cmsg_len))))
+#define WSA_CMSG_DATA(cmsg) ((u_char *)(cmsg) + WSA_CMSGDATA_ALIGN(sizeof(WSACMSGHDR)))
+#define WSA_CMSG_SPACE(length) (WSA_CMSGDATA_ALIGN(sizeof(WSACMSGHDR) + WSA_CMSGHDR_ALIGN(length)))
+#define WSA_CMSG_LEN(length) (WSA_CMSGDATA_ALIGN(sizeof(WSACMSGHDR)) + length)
+
+#define MSG_TRUNC 0x0100
+#define MSG_CTRUNC 0x0200
+#define MSG_BCAST 0x0400
+#define MSG_MCAST 0x0800
+
+  typedef INT (WINAPI *LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg,
+					LPDWORD lpdwNumberOfBytesRecvd,
+					LPWSAOVERLAPPED lpOverlapped,
+					LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+
+#define WSAID_WSARECVMSG {0xf689d7c8,0x6f1f,0x436b,{0x8a,0x53,0xe5,0x4f,0xe3,0x51,0xc3,0x22}}
+
+#if(_WIN32_WINNT >= 0x0600)
+  typedef struct {
+    LPWSAMSG lpMsg;
+    DWORD dwFlags;
+    LPDWORD lpNumberOfBytesSent;
+    LPWSAOVERLAPPED lpOverlapped;
+    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine;
+  } WSASENDMSG, *LPWSASENDMSG;
+
+  typedef INT (WSAAPI *LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags,
+					LPDWORD lpNumberOfBytesSent,
+					LPWSAOVERLAPPED lpOverlapped,
+					LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+
+#define WSAID_WSASENDMSG {0xa441e712,0x754f,0x43ca,{0x84,0xa7,0x0d,0xee,0x44,0xcf,0x60,0x6d}}
+
+#endif /* (_WIN32_WINNT >= 0x0600) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MSWSOCK_ */
diff --git a/external/badvpn_dns/misc/nonblocking.h b/external/badvpn_dns/misc/nonblocking.h
new file mode 100644
index 0000000..fe82584
--- /dev/null
+++ b/external/badvpn_dns/misc/nonblocking.h
@@ -0,0 +1,51 @@
+/**
+ * @file nonblocking.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Function for enabling non-blocking mode for a file descriptor.
+ */
+
+#ifndef BADVPN_MISC_NONBLOCKING_H
+#define BADVPN_MISC_NONBLOCKING_H
+
+#include <unistd.h>
+#include <fcntl.h>
+
+static int badvpn_set_nonblocking (int fd);
+
+int badvpn_set_nonblocking (int fd)
+{
+    if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/nsskey.h b/external/badvpn_dns/misc/nsskey.h
new file mode 100644
index 0000000..8268235
--- /dev/null
+++ b/external/badvpn_dns/misc/nsskey.h
@@ -0,0 +1,118 @@
+/**
+ * @file nsskey.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Function for opening a NSS certificate and its private key.
+ */
+
+#ifndef BADVPN_MISC_NSSKEY_H
+#define BADVPN_MISC_NSSKEY_H
+
+#include <stdlib.h>
+
+#include <prerror.h>
+#include <cert.h>
+#include <keyhi.h>
+#include <pk11func.h>
+
+#include <base/BLog.h>
+
+#include <generated/blog_channel_nsskey.h>
+
+/**
+ * Opens a NSS certificate and its private key.
+ * 
+ * @param name name of the certificate
+ * @param out_cert on success, the certificate will be returned here. Should be
+ *                 released with CERT_DestroyCertificate.
+ * @param out_key on success, the private key will be returned here. Should be
+ *                released with SECKEY_DestroyPrivateKey.
+ * @return 1 on success, 0 on failure
+ */
+static int open_nss_cert_and_key (char *name, CERTCertificate **out_cert, SECKEYPrivateKey **out_key) WARN_UNUSED;
+
+static SECKEYPrivateKey * find_nss_private_key (char *name)
+{
+    SECKEYPrivateKey *key = NULL;
+
+    PK11SlotList *slot_list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_FALSE, NULL);
+    if (!slot_list) {
+        return NULL;
+    }
+    
+    PK11SlotListElement *slot_entry;
+    for (slot_entry = slot_list->head; !key && slot_entry; slot_entry = slot_entry->next) {
+        SECKEYPrivateKeyList *key_list = PK11_ListPrivKeysInSlot(slot_entry->slot, name, NULL);
+        if (!key_list) {
+            BLog(BLOG_ERROR, "PK11_ListPrivKeysInSlot failed");
+            continue;
+        }
+        
+        SECKEYPrivateKeyListNode *key_node;
+        for (key_node = PRIVKEY_LIST_HEAD(key_list); !key && !PRIVKEY_LIST_END(key_node, key_list); key_node = PRIVKEY_LIST_NEXT(key_node)) {
+            char *key_name = PK11_GetPrivateKeyNickname(key_node->key);
+            if (!key_name || strcmp(key_name, name)) {
+                PORT_Free((void *)key_name);
+                continue;
+            }
+            PORT_Free((void *)key_name);
+            
+            key = SECKEY_CopyPrivateKey(key_node->key);
+        }
+        
+        SECKEY_DestroyPrivateKeyList(key_list);
+    }
+    
+    PK11_FreeSlotList(slot_list);
+    
+    return key;
+}
+
+int open_nss_cert_and_key (char *name, CERTCertificate **out_cert, SECKEYPrivateKey **out_key)
+{
+    CERTCertificate *cert;
+    cert = CERT_FindCertByNicknameOrEmailAddr(CERT_GetDefaultCertDB(), name);
+    if (!cert) {
+        BLog(BLOG_ERROR, "CERT_FindCertByName failed (%d)", (int)PR_GetError());
+        return 0;
+    }
+    
+    SECKEYPrivateKey *key = find_nss_private_key(name);
+    if (!key) {
+        BLog(BLOG_ERROR, "Failed to find private key");
+        CERT_DestroyCertificate(cert);
+        return 0;
+    }
+    
+    *out_cert = cert;
+    *out_key = key;
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/offset.h b/external/badvpn_dns/misc/offset.h
new file mode 100644
index 0000000..23b7683
--- /dev/null
+++ b/external/badvpn_dns/misc/offset.h
@@ -0,0 +1,51 @@
+/**
+ * @file offset.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Macros for determining offsets of members in structs.
+ */
+
+#ifndef BADVPN_MISC_OFFSET_H
+#define BADVPN_MISC_OFFSET_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * Returns a pointer to a struct, given a pointer to its member.
+ */
+#define UPPER_OBJECT(_ptr, _object_type, _field_name) ((_object_type *)((char *)(_ptr) - offsetof(_object_type, _field_name)))
+
+/**
+ * Returns the offset of one struct member from another.
+ * Expands to an int.
+ */
+#define OFFSET_DIFF(_object_type, _field1, _field2) ((int)offsetof(_object_type, _field1) - (int)offsetof(_object_type, _field2))
+
+#endif
diff --git a/external/badvpn_dns/misc/open_standard_streams.h b/external/badvpn_dns/misc/open_standard_streams.h
new file mode 100644
index 0000000..7fd6d41
--- /dev/null
+++ b/external/badvpn_dns/misc/open_standard_streams.h
@@ -0,0 +1,54 @@
+/**
+ * @file open_standard_streams.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_OPEN_STANDARD_STREAMS_H
+#define BADVPN_OPEN_STANDARD_STREAMS_H
+
+#ifndef BADVPN_USE_WINAPI
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
+static void open_standard_streams (void)
+{
+#ifndef BADVPN_USE_WINAPI
+    int fd;
+    
+    do {
+        fd = open("/dev/null", O_RDWR);
+        if (fd > 2) {
+            close(fd);
+        }
+    } while (fd >= 0 && fd <= 2);
+#endif
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/overflow.h b/external/badvpn_dns/misc/overflow.h
new file mode 100644
index 0000000..9fde52a
--- /dev/null
+++ b/external/badvpn_dns/misc/overflow.h
@@ -0,0 +1,66 @@
+/**
+ * @file overflow.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Functions for checking for overflow of integer addition.
+ */
+
+#ifndef BADVPN_MISC_OVERFLOW_H
+#define BADVPN_MISC_OVERFLOW_H
+
+#include <limits.h>
+#include <stdint.h>
+
+#define DEFINE_UNSIGNED_OVERFLOW(_name, _type, _max) \
+static int add_ ## _name ## _overflows (_type a, _type b) \
+{\
+    return (b > _max - a); \
+}
+
+#define DEFINE_SIGNED_OVERFLOW(_name, _type, _min, _max) \
+static int add_ ## _name ## _overflows (_type a, _type b) \
+{\
+    if ((a < 0) ^ (b < 0)) return 0; \
+    if (a < 0) return -(a < _min - b); \
+    return (a > _max - b); \
+}
+
+DEFINE_UNSIGNED_OVERFLOW(uint, unsigned int, UINT_MAX)
+DEFINE_UNSIGNED_OVERFLOW(uint8, uint8_t, UINT8_MAX)
+DEFINE_UNSIGNED_OVERFLOW(uint16, uint16_t, UINT16_MAX)
+DEFINE_UNSIGNED_OVERFLOW(uint32, uint32_t, UINT32_MAX)
+DEFINE_UNSIGNED_OVERFLOW(uint64, uint64_t, UINT64_MAX)
+
+DEFINE_SIGNED_OVERFLOW(int, int, INT_MIN, INT_MAX)
+DEFINE_SIGNED_OVERFLOW(int8, int8_t, INT8_MIN, INT8_MAX)
+DEFINE_SIGNED_OVERFLOW(int16, int16_t, INT16_MIN, INT16_MAX)
+DEFINE_SIGNED_OVERFLOW(int32, int32_t, INT32_MIN, INT32_MAX)
+DEFINE_SIGNED_OVERFLOW(int64, int64_t, INT64_MIN, INT64_MAX)
+
+#endif
diff --git a/external/badvpn_dns/misc/packed.h b/external/badvpn_dns/misc/packed.h
new file mode 100644
index 0000000..2175536
--- /dev/null
+++ b/external/badvpn_dns/misc/packed.h
@@ -0,0 +1,51 @@
+/**
+ * @file packed.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Structure packing macros.
+ */
+
+#ifndef BADVPN_PACKED_H
+#define BADVPN_PACKED_H
+
+#ifdef _MSC_VER
+
+#define B_START_PACKED __pragma(pack(push, 1))
+#define B_END_PACKED __pragma(pack(pop))
+#define B_PACKED
+
+#else
+
+#define B_START_PACKED
+#define B_END_PACKED
+#define B_PACKED __attribute__((packed))
+
+#endif
+
+#endif
diff --git a/external/badvpn_dns/misc/parse_number.h b/external/badvpn_dns/misc/parse_number.h
new file mode 100644
index 0000000..2601ebb
--- /dev/null
+++ b/external/badvpn_dns/misc/parse_number.h
@@ -0,0 +1,304 @@
+/**
+ * @file parse_number.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Numeric string parsing.
+ */
+
+#ifndef BADVPN_MISC_PARSE_NUMBER_H
+#define BADVPN_MISC_PARSE_NUMBER_H
+
+#include <stdint.h>
+#include <string.h>
+#include <stddef.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/cstring.h>
+
+// public parsing functions
+static int decode_decimal_digit (char c);
+static int decode_hex_digit (char c);
+static int parse_unsigned_integer_bin (const char *str, size_t str_len, uintmax_t *out) WARN_UNUSED;
+static int parse_unsigned_integer (const char *str, uintmax_t *out) WARN_UNUSED;
+static int parse_unsigned_integer_cstr (b_cstring cstr, size_t offset, size_t length, uintmax_t *out) WARN_UNUSED;
+static int parse_unsigned_hex_integer_bin (const char *str, size_t str_len, uintmax_t *out) WARN_UNUSED;
+static int parse_unsigned_hex_integer (const char *str, uintmax_t *out) WARN_UNUSED;
+static int parse_signmag_integer_bin (const char *str, size_t str_len, int *out_sign, uintmax_t *out_mag) WARN_UNUSED;
+static int parse_signmag_integer (const char *str, int *out_sign, uintmax_t *out_mag) WARN_UNUSED;
+static int parse_signmag_integer_cstr (b_cstring cstr, size_t offset, size_t length, int *out_sign, uintmax_t *out_mag) WARN_UNUSED;
+
+// public generation functions
+static int compute_decimal_repr_size (uintmax_t x);
+static void generate_decimal_repr (uintmax_t x, char *out, int repr_size);
+static int generate_decimal_repr_string (uintmax_t x, char *out);
+
+// implementation follows
+
+// decimal representation of UINTMAX_MAX
+static const char parse_number__uintmax_max_str[] = "18446744073709551615";
+
+// make sure UINTMAX_MAX is what we think it is
+static const char parse_number__uintmax_max_str_assert[(UINTMAX_MAX == UINTMAX_C(18446744073709551615)) ? 1 : -1];
+
+static int decode_decimal_digit (char c)
+{
+    switch (c) {
+        case '0': return 0;
+        case '1': return 1;
+        case '2': return 2;
+        case '3': return 3;
+        case '4': return 4;
+        case '5': return 5;
+        case '6': return 6;
+        case '7': return 7;
+        case '8': return 8;
+        case '9': return 9;
+    }
+    
+    return -1;
+}
+
+static int decode_hex_digit (char c)
+{
+    switch (c) {
+        case '0': return 0;
+        case '1': return 1;
+        case '2': return 2;
+        case '3': return 3;
+        case '4': return 4;
+        case '5': return 5;
+        case '6': return 6;
+        case '7': return 7;
+        case '8': return 8;
+        case '9': return 9;
+        case 'A': case 'a': return 10;
+        case 'B': case 'b': return 11;
+        case 'C': case 'c': return 12;
+        case 'D': case 'd': return 13;
+        case 'E': case 'e': return 14;
+        case 'F': case 'f': return 15;
+    }
+    
+    return -1;
+}
+
+static int parse__no_overflow (const char *str, size_t str_len, uintmax_t *out)
+{
+    uintmax_t n = 0;
+    
+    while (str_len > 0) {
+        if (*str < '0' || *str > '9') {
+            return 0;
+        }
+        
+        n = 10 * n + (*str - '0');
+        
+        str++;
+        str_len--;
+    }
+    
+    *out = n;
+    return 1;
+}
+
+int parse_unsigned_integer_bin (const char *str, size_t str_len, uintmax_t *out)
+{
+    // we do not allow empty strings
+    if (str_len == 0) {
+        return 0;
+    }
+    
+    // remove leading zeros
+    while (str_len > 0 && *str == '0') {
+        str++;
+        str_len--;
+    }
+    
+    // detect overflow
+    if (str_len > sizeof(parse_number__uintmax_max_str) - 1 ||
+        (str_len == sizeof(parse_number__uintmax_max_str) - 1 && memcmp(str, parse_number__uintmax_max_str, sizeof(parse_number__uintmax_max_str) - 1) > 0)) {
+        return 0;
+    }
+    
+    // will not overflow (but can still have invalid characters)
+    return parse__no_overflow(str, str_len, out);
+}
+
+int parse_unsigned_integer (const char *str, uintmax_t *out)
+{
+    return parse_unsigned_integer_bin(str, strlen(str), out);
+}
+
+int parse_unsigned_integer_cstr (b_cstring cstr, size_t offset, size_t length, uintmax_t *out)
+{
+    b_cstring_assert_range(cstr, offset, length);
+    
+    if (length == 0) {
+        return 0;
+    }
+    
+    uintmax_t n = 0;
+    
+    B_CSTRING_LOOP_RANGE(cstr, offset, length, pos, chunk_data, chunk_length, {
+        for (size_t i = 0; i < chunk_length; i++) {
+            int digit = decode_decimal_digit(chunk_data[i]);
+            if (digit < 0) {
+                return 0;
+            }
+            if (n > UINTMAX_MAX / 10) {
+                return 0;
+            }
+            n *= 10;
+            if (digit > UINTMAX_MAX - n) {
+                return 0;
+            }
+            n += digit;
+        }
+    })
+    
+    *out = n;
+    return 1;
+}
+
+int parse_unsigned_hex_integer_bin (const char *str, size_t str_len, uintmax_t *out)
+{
+    uintmax_t n = 0;
+    
+    if (str_len == 0) {
+        return 0;
+    }
+    
+    while (str_len > 0) {
+        int digit = decode_hex_digit(*str);
+        if (digit < 0) {
+            return 0;
+        }
+        
+        if (n > UINTMAX_MAX / 16) {
+            return 0;
+        }
+        n *= 16;
+        
+        if (digit > UINTMAX_MAX - n) {
+            return 0;
+        }
+        n += digit;
+        
+        str++;
+        str_len--;
+    }
+    
+    *out = n;
+    return 1;
+}
+
+int parse_unsigned_hex_integer (const char *str, uintmax_t *out)
+{
+    return parse_unsigned_hex_integer_bin(str, strlen(str), out);
+}
+
+int parse_signmag_integer_bin (const char *str, size_t str_len, int *out_sign, uintmax_t *out_mag)
+{
+    int sign = 1;
+    if (str_len > 0 && (str[0] == '+' || str[0] == '-')) {
+        sign = 1 - 2 * (str[0] == '-');
+        str++;
+        str_len--;
+    }
+    
+    if (!parse_unsigned_integer_bin(str, str_len, out_mag)) {
+        return 0;
+    }
+    
+    *out_sign = sign;
+    return 1;
+}
+
+int parse_signmag_integer (const char *str, int *out_sign, uintmax_t *out_mag)
+{
+    return parse_signmag_integer_bin(str, strlen(str), out_sign, out_mag);
+}
+
+int parse_signmag_integer_cstr (b_cstring cstr, size_t offset, size_t length, int *out_sign, uintmax_t *out_mag)
+{
+    b_cstring_assert_range(cstr, offset, length);
+    
+    int sign = 1;
+    if (length > 0 && (b_cstring_at(cstr, offset) == '+' || b_cstring_at(cstr, offset) == '-')) {
+        sign = 1 - 2 * (b_cstring_at(cstr, offset) == '-');
+        offset++;
+        length--;
+    }
+    
+    if (!parse_unsigned_integer_cstr(cstr, offset, length, out_mag)) {
+        return 0;
+    }
+    
+    *out_sign = sign;
+    return 1;
+}
+
+int compute_decimal_repr_size (uintmax_t x)
+{
+    int size = 0;
+    
+    do {
+        size++;
+        x /= 10;
+    } while (x > 0);
+    
+    return size;
+}
+
+void generate_decimal_repr (uintmax_t x, char *out, int repr_size)
+{
+    ASSERT(out)
+    ASSERT(repr_size == compute_decimal_repr_size(x))
+    
+    out += repr_size;
+    
+    do {
+        *(--out) = '0' + (x % 10);
+        x /= 10;
+    } while (x > 0);
+}
+
+int generate_decimal_repr_string (uintmax_t x, char *out)
+{
+    ASSERT(out)
+    
+    int repr_size = compute_decimal_repr_size(x);
+    generate_decimal_repr(x, out, repr_size);
+    out[repr_size] = '\0';
+    
+    return repr_size;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/print_macros.h b/external/badvpn_dns/misc/print_macros.h
new file mode 100644
index 0000000..a07fdbe
--- /dev/null
+++ b/external/badvpn_dns/misc/print_macros.h
@@ -0,0 +1,98 @@
+/**
+ * @file print_macros.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Format macros for printf() for non-standard compilers.
+ */
+
+#ifndef BADVPN_PRINT_MACROS
+#define BADVPN_PRINT_MACROS
+
+#ifdef _MSC_VER
+
+// size_t
+#define PRIsz "Iu"
+
+// signed exact width (intN_t)
+#define PRId8 "d"
+#define PRIi8 "i"
+#define PRId16 "d"
+#define PRIi16 "i"
+#define PRId32 "I32d"
+#define PRIi32 "I32i"
+#define PRId64 "I64d"
+#define PRIi64 "I64i"
+
+// unsigned exact width (uintN_t)
+#define PRIo8 "o"
+#define PRIu8 "u"
+#define PRIx8 "x"
+#define PRIX8 "X"
+#define PRIo16 "o"
+#define PRIu16 "u"
+#define PRIx16 "x"
+#define PRIX16 "X"
+#define PRIo32 "I32o"
+#define PRIu32 "I32u"
+#define PRIx32 "I32x"
+#define PRIX32 "I32X"
+#define PRIo64 "I64o"
+#define PRIu64 "I64u"
+#define PRIx64 "I64x"
+#define PRIX64 "I64X"
+
+// signed maximum width (intmax_t)
+#define PRIdMAX "I64d"
+#define PRIiMAX "I64i"
+
+// unsigned maximum width (uintmax_t)
+#define PRIoMAX "I64o"
+#define PRIuMAX "I64u"
+#define PRIxMAX "I64x"
+#define PRIXMAX "I64X"
+
+// signed pointer (intptr_t)
+#define PRIdPTR "Id"
+#define PRIiPTR "Ii"
+
+// unsigned pointer (uintptr_t)
+#define PRIoPTR "Io"
+#define PRIuPTR "Iu"
+#define PRIxPTR "Ix"
+#define PRIXPTR "IX"
+
+#else
+
+#include <inttypes.h>
+
+#define PRIsz "zu"
+
+#endif
+
+#endif
diff --git a/external/badvpn_dns/misc/read_file.h b/external/badvpn_dns/misc/read_file.h
new file mode 100644
index 0000000..e1862e5
--- /dev/null
+++ b/external/badvpn_dns/misc/read_file.h
@@ -0,0 +1,98 @@
+/**
+ * @file read_file.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Function for reading a file into memory using stdio.
+ */
+
+#ifndef BADVPN_MISC_READ_FILE_H
+#define BADVPN_MISC_READ_FILE_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static int read_file (const char *file, uint8_t **out_data, size_t *out_len)
+{
+    FILE *f = fopen(file, "r");
+    if (!f) {
+        goto fail0;
+    }
+    
+    size_t buf_len = 0;
+    size_t buf_size = 128;
+    
+    uint8_t *buf = (uint8_t *)malloc(buf_size);
+    if (!buf) {
+        goto fail1;
+    }
+    
+    while (1) {
+        if (buf_len == buf_size) {
+            if (2 > SIZE_MAX / buf_size) {
+                goto fail;
+            }
+            size_t newsize = 2 * buf_size;
+            
+            uint8_t *newbuf = (uint8_t *)realloc(buf, newsize);
+            if (!newbuf) {
+                goto fail;
+            }
+            
+            buf = newbuf;
+            buf_size = newsize;
+        }
+        
+        size_t bytes = fread(buf + buf_len, 1, buf_size - buf_len, f);
+        if (bytes == 0) {
+            if (feof(f)) {
+                break;
+            }
+            goto fail;
+        }
+        
+        buf_len += bytes;
+    }
+    
+    fclose(f);
+    
+    *out_data = buf;
+    *out_len = buf_len;
+    return 1;
+    
+fail:
+    free(buf);
+fail1:
+    fclose(f);
+fail0:
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/read_write_int.h b/external/badvpn_dns/misc/read_write_int.h
new file mode 100644
index 0000000..bc4ed2c
--- /dev/null
+++ b/external/badvpn_dns/misc/read_write_int.h
@@ -0,0 +1,181 @@
+/**
+ * @file read_write_int.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_READ_WRITE_INT_H
+#define BADVPN_READ_WRITE_INT_H
+
+#include <stdint.h>
+
+static uint8_t badvpn_read_le8 (const char *c_ptr);
+static uint16_t badvpn_read_le16 (const char *c_ptr);
+static uint32_t badvpn_read_le32 (const char *c_ptr);
+static uint64_t badvpn_read_le64 (const char *c_ptr);
+
+static uint8_t badvpn_read_be8 (const char *c_ptr);
+static uint16_t badvpn_read_be16 (const char *c_ptr);
+static uint32_t badvpn_read_be32 (const char *c_ptr);
+static uint64_t badvpn_read_be64 (const char *c_ptr);
+
+static void badvpn_write_le8 (uint8_t x, char *c_ptr);
+static void badvpn_write_le16 (uint16_t x, char *c_ptr);
+static void badvpn_write_le32 (uint32_t x, char *c_ptr);
+static void badvpn_write_le64 (uint64_t x, char *c_ptr);
+
+static void badvpn_write_be8 (uint8_t x, char *c_ptr);
+static void badvpn_write_be16 (uint16_t x, char *c_ptr);
+static void badvpn_write_be32 (uint32_t x, char *c_ptr);
+static void badvpn_write_be64 (uint64_t x, char *c_ptr);
+
+static uint8_t badvpn_read_le8 (const char *c_ptr)
+{
+    const uint8_t *ptr = (const uint8_t *)c_ptr;
+    return ((uint8_t)ptr[0] << 0);
+}
+
+static uint16_t badvpn_read_le16 (const char *c_ptr)
+{
+    const uint8_t *ptr = (const uint8_t *)c_ptr;
+    return ((uint16_t)ptr[1] << 8) | ((uint16_t)ptr[0] << 0);
+}
+
+static uint32_t badvpn_read_le32 (const char *c_ptr)
+{
+    const uint8_t *ptr = (const uint8_t *)c_ptr;
+    return ((uint32_t)ptr[3] << 24) | ((uint32_t)ptr[2] << 16) |
+           ((uint32_t)ptr[1] <<  8) | ((uint32_t)ptr[0] <<  0);
+}
+
+static uint64_t badvpn_read_le64 (const char *c_ptr)
+{
+    const uint8_t *ptr = (const uint8_t *)c_ptr;
+    return ((uint64_t)ptr[7] << 56) | ((uint64_t)ptr[6] << 48) |
+           ((uint64_t)ptr[5] << 40) | ((uint64_t)ptr[4] << 32) |
+           ((uint64_t)ptr[3] << 24) | ((uint64_t)ptr[2] << 16) |
+           ((uint64_t)ptr[1] <<  8) | ((uint64_t)ptr[0] <<  0);
+}
+
+static uint8_t badvpn_read_be8 (const char *c_ptr)
+{
+    const uint8_t *ptr = (const uint8_t *)c_ptr;
+    return ((uint8_t)ptr[0] << 0);
+}
+
+static uint16_t badvpn_read_be16 (const char *c_ptr)
+{
+    const uint8_t *ptr = (const uint8_t *)c_ptr;
+    return ((uint16_t)ptr[0] << 8) | ((uint16_t)ptr[1] << 0);
+}
+
+static uint32_t badvpn_read_be32 (const char *c_ptr)
+{
+    const uint8_t *ptr = (const uint8_t *)c_ptr;
+    return ((uint32_t)ptr[0] << 24) | ((uint32_t)ptr[1] << 16) |
+           ((uint32_t)ptr[2] <<  8) | ((uint32_t)ptr[3] <<  0);
+}
+
+static uint64_t badvpn_read_be64 (const char *c_ptr)
+{
+    const uint8_t *ptr = (const uint8_t *)c_ptr;
+    return ((uint64_t)ptr[0] << 56) | ((uint64_t)ptr[1] << 48) |
+           ((uint64_t)ptr[2] << 40) | ((uint64_t)ptr[3] << 32) |
+           ((uint64_t)ptr[4] << 24) | ((uint64_t)ptr[5] << 16) |
+           ((uint64_t)ptr[6] <<  8) | ((uint64_t)ptr[7] <<  0);
+}
+
+static void badvpn_write_le8 (uint8_t x, char *c_ptr)
+{
+    uint8_t *ptr = (uint8_t *)c_ptr;
+    ptr[0] = x >> 0;
+}
+
+static void badvpn_write_le16 (uint16_t x, char *c_ptr)
+{
+    uint8_t *ptr = (uint8_t *)c_ptr;
+    ptr[1] = x >> 8;
+    ptr[0] = x >> 0;
+}
+
+static void badvpn_write_le32 (uint32_t x, char *c_ptr)
+{
+    uint8_t *ptr = (uint8_t *)c_ptr;
+    ptr[3] = x >> 24;
+    ptr[2] = x >> 16;
+    ptr[1] = x >> 8;
+    ptr[0] = x >> 0;
+}
+
+static void badvpn_write_le64 (uint64_t x, char *c_ptr)
+{
+    uint8_t *ptr = (uint8_t *)c_ptr;
+    ptr[7] = x >> 56;
+    ptr[6] = x >> 48;
+    ptr[5] = x >> 40;
+    ptr[4] = x >> 32;
+    ptr[3] = x >> 24;
+    ptr[2] = x >> 16;
+    ptr[1] = x >> 8;
+    ptr[0] = x >> 0;
+}
+
+static void badvpn_write_be8 (uint8_t x, char *c_ptr)
+{
+    uint8_t *ptr = (uint8_t *)c_ptr;
+    ptr[0] = x >> 0;
+}
+
+static void badvpn_write_be16 (uint16_t x, char *c_ptr)
+{
+    uint8_t *ptr = (uint8_t *)c_ptr;
+    ptr[0] = x >> 8;
+    ptr[1] = x >> 0;
+}
+
+static void badvpn_write_be32 (uint32_t x, char *c_ptr)
+{
+    uint8_t *ptr = (uint8_t *)c_ptr;
+    ptr[0] = x >> 24;
+    ptr[1] = x >> 16;
+    ptr[2] = x >> 8;
+    ptr[3] = x >> 0;
+}
+
+static void badvpn_write_be64 (uint64_t x, char *c_ptr)
+{
+    uint8_t *ptr = (uint8_t *)c_ptr;
+    ptr[0] = x >> 56;
+    ptr[1] = x >> 48;
+    ptr[2] = x >> 40;
+    ptr[3] = x >> 32;
+    ptr[4] = x >> 24;
+    ptr[5] = x >> 16;
+    ptr[6] = x >> 8;
+    ptr[7] = x >> 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/socks_proto.h b/external/badvpn_dns/misc/socks_proto.h
new file mode 100644
index 0000000..41f5a1f
--- /dev/null
+++ b/external/badvpn_dns/misc/socks_proto.h
@@ -0,0 +1,118 @@
+/**
+ * @file socks_proto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 the SOCKS protocol.
+ */
+
+#ifndef BADVPN_MISC_SOCKS_PROTO_H
+#define BADVPN_MISC_SOCKS_PROTO_H
+
+#include <stdint.h>
+
+#include <misc/packed.h>
+
+#define SOCKS_VERSION 0x05
+
+#define SOCKS_METHOD_NO_AUTHENTICATION_REQUIRED 0x00
+#define SOCKS_METHOD_GSSAPI 0x01
+#define SOCKS_METHOD_USERNAME_PASSWORD 0x02
+#define SOCKS_METHOD_NO_ACCEPTABLE_METHODS 0xFF
+
+#define SOCKS_CMD_CONNECT 0x01
+#define SOCKS_CMD_BIND 0x02
+#define SOCKS_CMD_UDP_ASSOCIATE 0x03
+
+#define SOCKS_ATYP_IPV4 0x01
+#define SOCKS_ATYP_DOMAINNAME 0x03
+#define SOCKS_ATYP_IPV6 0x04
+
+#define SOCKS_REP_SUCCEEDED 0x00
+#define SOCKS_REP_GENERAL_FAILURE 0x01
+#define SOCKS_REP_CONNECTION_NOT_ALLOWED 0x02
+#define SOCKS_REP_NETWORK_UNREACHABLE 0x03
+#define SOCKS_REP_HOST_UNREACHABLE 0x04
+#define SOCKS_REP_CONNECTION_REFUSED 0x05
+#define SOCKS_REP_TTL_EXPIRED 0x06
+#define SOCKS_REP_COMMAND_NOT_SUPPORTED 0x07
+#define SOCKS_REP_ADDRESS_TYPE_NOT_SUPPORTED 0x08
+
+B_START_PACKED
+struct socks_client_hello_header {
+    uint8_t ver;
+    uint8_t nmethods;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct socks_client_hello_method {
+    uint8_t method;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct socks_server_hello {
+    uint8_t ver;
+    uint8_t method;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct socks_request_header {
+    uint8_t ver;
+    uint8_t cmd;
+    uint8_t rsv;
+    uint8_t atyp;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct socks_reply_header {
+    uint8_t ver;
+    uint8_t rep;
+    uint8_t rsv;
+    uint8_t atyp;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct socks_addr_ipv4 {
+    uint32_t addr;
+    uint16_t port;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct socks_addr_ipv6 {
+    uint8_t addr[16];
+    uint16_t port;
+} B_PACKED;    
+B_END_PACKED
+
+#endif
diff --git a/external/badvpn_dns/misc/sslsocket.h b/external/badvpn_dns/misc/sslsocket.h
new file mode 100644
index 0000000..71e43c2
--- /dev/null
+++ b/external/badvpn_dns/misc/sslsocket.h
@@ -0,0 +1,48 @@
+/**
+ * @file sslsocket.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Structure for moving around sockets, possibly together with SSL compoments.
+ */
+
+#ifndef BADVPN_MISC_SSLSOCKET_H
+#define BADVPN_MISC_SSLSOCKET_H
+
+#include <prio.h>
+
+#include <system/BConnection.h>
+#include <nspr_support/BSSLConnection.h>
+
+typedef struct {
+    BConnection con;
+    PRFileDesc bottom_prfd;
+    PRFileDesc *ssl_prfd;
+} sslsocket;
+
+#endif
diff --git a/external/badvpn_dns/misc/stdbuf_cmdline.h b/external/badvpn_dns/misc/stdbuf_cmdline.h
new file mode 100644
index 0000000..8459c64
--- /dev/null
+++ b/external/badvpn_dns/misc/stdbuf_cmdline.h
@@ -0,0 +1,92 @@
+/**
+ * @file stdbuf_cmdline.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Builds command line for running a program via stdbuf.
+ */
+
+#ifndef BADVPN_STDBUF_CMDLINE_H
+#define BADVPN_STDBUF_CMDLINE_H
+
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/cmdline.h>
+#include <misc/balloc.h>
+
+/**
+ * Builds the initial part of command line for calling a program via stdbuf
+ * with standard output buffering set to line-buffered.
+ * 
+ * @param out {@link CmdLine} to append the result to. Note than on failure, only
+ *            some part of the cmdline may have been appended.
+ * @param stdbuf_exec full path to stdbuf executable
+ * @param exec path to the executable. Must not contain nulls. 
+ * @param exec_len number of characters in exec
+ * @return 1 on success, 0 on failure
+ */
+static int build_stdbuf_cmdline (CmdLine *out, const char *stdbuf_exec, const char *exec, size_t exec_len) WARN_UNUSED;
+
+int build_stdbuf_cmdline (CmdLine *out, const char *stdbuf_exec, const char *exec, size_t exec_len)
+{
+    ASSERT(!memchr(exec, '\0', exec_len))
+    
+    if (!CmdLine_AppendMulti(out, 3, stdbuf_exec, "-o", "L")) {
+        goto fail1;
+    }
+    
+    if (exec[0] == '/') {
+        if (!CmdLine_AppendNoNull(out, exec, exec_len)) {
+            goto fail1;
+        }
+    } else {
+        bsize_t size = bsize_add(bsize_fromsize(exec_len), bsize_fromsize(3));
+        char *real_exec = BAllocSize(size);
+        if (!real_exec) {
+            goto fail1;
+        }
+        
+        memcpy(real_exec, "./", 2);
+        memcpy(real_exec + 2, exec, exec_len);
+        real_exec[2 + exec_len] = '\0';
+        
+        int res = CmdLine_Append(out, real_exec);
+        free(real_exec);
+        if (!res) {
+            goto fail1;
+        }
+    }
+    
+    return 1;
+    
+fail1:
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/strdup.h b/external/badvpn_dns/misc/strdup.h
new file mode 100644
index 0000000..2e475bb
--- /dev/null
+++ b/external/badvpn_dns/misc/strdup.h
@@ -0,0 +1,86 @@
+/**
+ * @file strdup.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Allocate memory for a string and copy it there.
+ */
+
+#ifndef BADVPN_STRDUP_H
+#define BADVPN_STRDUP_H
+
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+
+/**
+ * Allocate and copy a null-terminated string.
+ */
+static char * b_strdup (const char *str)
+{
+    ASSERT(str)
+    
+    size_t len = strlen(str);
+    
+    char *s = (char *)malloc(len + 1);
+    if (!s) {
+        return NULL;
+    }
+    
+    memcpy(s, str, len + 1);
+    
+    return s;
+}
+
+/**
+ * Allocate memory for a null-terminated string and use the
+ * given data as its contents. A null terminator is appended
+ * after the specified data.
+ */
+static char * b_strdup_bin (const char *str, size_t len)
+{
+    ASSERT(str)
+    
+    if (len == SIZE_MAX) {
+        return NULL;
+    }
+    
+    char *s = (char *)malloc(len + 1);
+    if (!s) {
+        return NULL;
+    }
+    
+    memcpy(s, str, len);
+    s[len] = '\0';
+    
+    return s;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/string_begins_with.h b/external/badvpn_dns/misc/string_begins_with.h
new file mode 100644
index 0000000..3c9c5e9
--- /dev/null
+++ b/external/badvpn_dns/misc/string_begins_with.h
@@ -0,0 +1,96 @@
+/**
+ * @file string_begins_with.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Function for checking if a string begins with a given string.
+ */
+
+#ifndef BADVPN_MISC_STRING_BEGINS_WITH
+#define BADVPN_MISC_STRING_BEGINS_WITH
+
+#include <stddef.h>
+#include <string.h>
+
+#include <misc/debug.h>
+
+static size_t data_begins_with (const char *str, size_t str_len, const char *needle)
+{
+    ASSERT(strlen(needle) > 0)
+    
+    size_t len = 0;
+    
+    while (str_len > 0 && *needle) {
+        if (*str != *needle) {
+            return 0;
+        }
+        str++;
+        str_len--;
+        needle++;
+        len++;
+    }
+    
+    if (*needle) {
+        return 0;
+    }
+    
+    return len;
+}
+
+static size_t string_begins_with (const char *str, const char *needle)
+{
+    ASSERT(strlen(needle) > 0)
+    
+    return data_begins_with(str, strlen(str), needle);
+}
+
+static size_t data_begins_with_bin (const char *str, size_t str_len, const char *needle, size_t needle_len)
+{
+    ASSERT(needle_len > 0)
+    
+    size_t len = 0;
+    
+    while (str_len > 0 && needle_len > 0) {
+        if (*str != *needle) {
+            return 0;
+        }
+        str++;
+        str_len--;
+        needle++;
+        needle_len--;
+        len++;
+    }
+    
+    if (needle_len > 0) {
+        return 0;
+    }
+    
+    return len;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/substring.h b/external/badvpn_dns/misc/substring.h
new file mode 100644
index 0000000..b3a8fff
--- /dev/null
+++ b/external/badvpn_dns/misc/substring.h
@@ -0,0 +1,81 @@
+#include <stddef.h>
+
+#include <misc/debug.h>
+
+static void build_substring_backtrack_table (const char *str, size_t len, size_t *out_table)
+{
+    ASSERT(len > 0)
+    
+    size_t x = 0;
+    
+    for (size_t i = 1; i < len; i++) {
+        out_table[i] = x;
+        while (x > 0 && str[i] != str[x]) {
+            x = out_table[x];
+        }
+        if (str[i] == str[x]) {
+            x++;
+        }
+    }
+}
+
+static int find_substring (const char *text, size_t text_len, const char *word, size_t word_len, const size_t *table, size_t *out_position)
+{
+    ASSERT(word_len > 0)
+    
+    size_t x = 0;
+    
+    for (size_t i = 0; i < text_len; i++) {
+        while (x > 0 && text[i] != word[x]) {
+            x = table[x];
+        }
+        if (text[i] == word[x]) {
+            if (x + 1 == word_len) {
+                *out_position = i - x;
+                return 1;
+            }
+            x++;
+        }
+    }
+    
+    return 0;
+}
+
+static void build_substring_backtrack_table_reverse (const char *str, size_t len, size_t *out_table)
+{
+    ASSERT(len > 0)
+    
+    size_t x = 0;
+    
+    for (size_t i = 1; i < len; i++) {
+        out_table[i] = x;
+        while (x > 0 && str[len - 1 - i] != str[len - 1 - x]) {
+            x = out_table[x];
+        }
+        if (str[len - 1 - i] == str[len - 1 - x]) {
+            x++;
+        }
+    }
+}
+
+static int find_substring_reverse (const char *text, size_t text_len, const char *word, size_t word_len, const size_t *table, size_t *out_position)
+{
+    ASSERT(word_len > 0)
+    
+    size_t x = 0;
+    
+    for (size_t i = 0; i < text_len; i++) {
+        while (x > 0 && text[text_len - 1 - i] != word[word_len - 1 - x]) {
+            x = table[x];
+        }
+        if (text[text_len - 1 - i] == word[word_len - 1 - x]) {
+            if (x + 1 == word_len) {
+                *out_position = (text_len - 1 - i);
+                return 1;
+            }
+            x++;
+        }
+    }
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/misc/udp_proto.h b/external/badvpn_dns/misc/udp_proto.h
new file mode 100644
index 0000000..23ec69a
--- /dev/null
+++ b/external/badvpn_dns/misc/udp_proto.h
@@ -0,0 +1,170 @@
+/**
+ * @file udp_proto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 the UDP protocol.
+ */
+
+#ifndef BADVPN_MISC_UDP_PROTO_H
+#define BADVPN_MISC_UDP_PROTO_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/ipv4_proto.h>
+#include <misc/ipv6_proto.h>
+#include <misc/read_write_int.h>
+
+B_START_PACKED
+struct udp_header {
+    uint16_t source_port;
+    uint16_t dest_port;
+    uint16_t length;
+    uint16_t checksum;
+} B_PACKED;
+B_END_PACKED
+
+static uint32_t udp_checksum_summer (const char *data, uint16_t len)
+{
+    ASSERT(len % 2 == 0)
+    
+    uint32_t t = 0;
+    
+    for (uint16_t i = 0; i < len / 2; i++) {
+        t += badvpn_read_be16(data + 2 * i);
+    }
+    
+    return t;
+}
+
+static uint16_t udp_checksum (const struct udp_header *header, const uint8_t *payload, uint16_t payload_len, uint32_t source_addr, uint32_t dest_addr)
+{
+    uint32_t t = 0;
+    
+    t += udp_checksum_summer((char *)&source_addr, sizeof(source_addr));
+    t += udp_checksum_summer((char *)&dest_addr, sizeof(dest_addr));
+    
+    uint16_t x;
+    x = hton16(IPV4_PROTOCOL_UDP);
+    t += udp_checksum_summer((char *)&x, sizeof(x));
+    x = hton16(sizeof(*header) + payload_len);
+    t += udp_checksum_summer((char *)&x, sizeof(x));
+    
+    t += udp_checksum_summer((const char *)header, sizeof(*header));
+    
+    if (payload_len % 2 == 0) {
+        t += udp_checksum_summer((const char *)payload, payload_len);
+    } else {
+        t += udp_checksum_summer((const char *)payload, payload_len - 1);
+        
+        x = hton16(((uint16_t)payload[payload_len - 1]) << 8);
+        t += udp_checksum_summer((char *)&x, sizeof(x));
+    }
+    
+    while (t >> 16) {
+        t = (t & 0xFFFF) + (t >> 16);
+    }
+    
+    if (t == 0) {
+        t = UINT16_MAX;
+    }
+    
+    return hton16(~t);
+}
+
+static uint16_t udp_ip6_checksum (const struct udp_header *header, const uint8_t *payload, uint16_t payload_len, const uint8_t *source_addr, const uint8_t *dest_addr)
+{
+    uint32_t t = 0;
+    
+    t += udp_checksum_summer((const char *)source_addr, 16);
+    t += udp_checksum_summer((const char *)dest_addr, 16);
+    
+    uint32_t x;
+    x = hton32(sizeof(*header) + payload_len);
+    t += udp_checksum_summer((char *)&x, sizeof(x));
+    x = hton32(IPV6_NEXT_UDP);
+    t += udp_checksum_summer((char *)&x, sizeof(x));
+    
+    t += udp_checksum_summer((const char *)header, sizeof(*header));
+    
+    if (payload_len % 2 == 0) {
+        t += udp_checksum_summer((const char *)payload, payload_len);
+    } else {
+        t += udp_checksum_summer((const char *)payload, payload_len - 1);
+        
+        uint16_t y;
+        y = hton16(((uint16_t)payload[payload_len - 1]) << 8);
+        t += udp_checksum_summer((char *)&y, sizeof(y));
+    }
+    
+    while (t >> 16) {
+        t = (t & 0xFFFF) + (t >> 16);
+    }
+    
+    if (t == 0) {
+        t = UINT16_MAX;
+    }
+    
+    return hton16(~t);
+}
+
+static int udp_check (const uint8_t *data, int data_len, struct udp_header *out_header, uint8_t **out_payload, int *out_payload_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(out_header)
+    ASSERT(out_payload)
+    ASSERT(out_payload_len)
+    
+    // parse UDP header
+    if (data_len < sizeof(struct udp_header)) {
+        return 0;
+    }
+    memcpy(out_header, data, sizeof(*out_header));
+    data += sizeof(*out_header);
+    data_len -= sizeof(*out_header);
+    
+    // verify UDP payload
+    int udp_length = ntoh16(out_header->length);
+    if (udp_length < sizeof(*out_header)) {
+        return 0;
+    }
+    if (udp_length > sizeof(*out_header) + data_len) {
+        return 0;
+    }
+    
+    // ignore stray data
+    data_len = udp_length - sizeof(*out_header);
+    
+    *out_payload = (uint8_t *)data;
+    *out_payload_len = data_len;
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/unicode_funcs.h b/external/badvpn_dns/misc/unicode_funcs.h
new file mode 100644
index 0000000..2442e7f
--- /dev/null
+++ b/external/badvpn_dns/misc/unicode_funcs.h
@@ -0,0 +1,232 @@
+/**
+ * @file unicode_funcs.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_UNICODE_FUNCS_H
+#define BADVPN_UNICODE_FUNCS_H
+
+#include <misc/expstring.h>
+#include <misc/bsize.h>
+#include <misc/Utf8Encoder.h>
+#include <misc/Utf8Decoder.h>
+#include <misc/Utf16Encoder.h>
+#include <misc/Utf16Decoder.h>
+
+/**
+ * Decodes UTF-16 data as bytes into an allocated null-terminated UTF-8 string.
+ * 
+ * @param data UTF-16 data, in big endian
+ * @param data_len size of data in bytes
+ * @param out_is_error if not NULL and the function returns a string,
+ *                     *out_is_error will be set to 0 or 1, indicating
+ *                     whether there have been errors decoding the input.
+ *                     A null decoded character is treated as an error.
+ * @return An UTF-8 null-terminated string which can be freed with free(),
+ *         or NULL if out of memory.
+ */
+static char * unicode_decode_utf16_to_utf8 (const uint8_t *data, size_t data_len, int *out_is_error);
+
+/**
+ * Decodes UTF-8 data into UTF-16 data as bytes.
+ * 
+ * @param data UTF-8 data
+ * @param data_len size of data in bytes
+ * @param out output buffer
+ * @param out_avail number of bytes available in output buffer
+ * @param out_len if not NULL, *out_len will contain the number of bytes
+ *                required to store the resulting data (or overflow)
+ * @param out_is_error if not NULL, *out_is_error will contain 0 or 1,
+ *                     indicating whether there have been errors decoding
+ *                     the input
+ */
+static void unicode_decode_utf8_to_utf16 (const uint8_t *data, size_t data_len, uint8_t *out, size_t out_avail, bsize_t *out_len, int *out_is_error);
+
+static char * unicode_decode_utf16_to_utf8 (const uint8_t *data, size_t data_len, int *out_is_error)
+{
+    // will build the resulting UTF-8 string by appending to ExpString
+    ExpString str;
+    if (!ExpString_Init(&str)) {
+        goto fail0;
+    }
+    
+    // init UTF-16 decoder
+    Utf16Decoder decoder;
+    Utf16Decoder_Init(&decoder);
+    
+    // set initial input and input matching positions
+    size_t i_in = 0;
+    size_t i_ch = 0;
+    
+    int error = 0;
+    
+    while (i_in < data_len) {
+        // read two input bytes from the input position
+        uint8_t x = data[i_in++];
+        if (i_in == data_len) {
+            break;
+        }
+        uint8_t y = data[i_in++];
+        
+        // combine them into a 16-bit value
+        uint16_t xy = (((uint16_t)x << 8) | (uint16_t)y);
+        
+        // give the 16-bit value to the UTF-16 decoder and maybe
+        // receive a Unicode character back
+        uint32_t ch;
+        if (!Utf16Decoder_Input(&decoder, xy, &ch)) {
+            continue;
+        }
+        
+        if (!error) {
+            // encode the Unicode character back into UTF-16
+            uint16_t chenc[2];
+            int chenc_n = Utf16Encoder_EncodeCharacter(ch, chenc);
+            ASSERT(chenc_n > 0)
+            
+            // match the result with input
+            for (int chenc_i = 0; chenc_i < chenc_n; chenc_i++) {
+                uint8_t cx = (chenc[chenc_i] >> 8);
+                uint8_t cy = (chenc[chenc_i] & 0xFF);
+                
+                if (i_ch >= data_len || data[i_ch] != cx) {
+                    error = 1;
+                    break;
+                }
+                i_ch++;
+                
+                if (i_ch >= data_len || data[i_ch] != cy) {
+                    error = 1;
+                    break;
+                }
+                i_ch++;
+            }
+        }
+        
+        // we don't like null Unicode characters because we're building a
+        // null-terminated UTF-8 string
+        if (ch == 0) {
+            error = 1;
+            continue;
+        }
+        
+        // encode the Unicode character into UTF-8
+        uint8_t enc[5];
+        int enc_n = Utf8Encoder_EncodeCharacter(ch, enc);
+        ASSERT(enc_n > 0)
+        
+        // append the resulting UTF-8 bytes to the result string
+        enc[enc_n] = 0;
+        if (!ExpString_Append(&str, enc)) {
+            goto fail1;
+        }
+    }
+    
+    // check if we matched the whole input string when encoding back
+    if (i_ch < data_len) {
+        error = 1;
+    }
+    
+    if (out_is_error) {
+        *out_is_error = error;
+    }
+    return ExpString_Get(&str);
+    
+fail1:
+    ExpString_Free(&str);
+fail0:
+    return NULL;
+}
+
+static void unicode_decode_utf8_to_utf16 (const uint8_t *data, size_t data_len, uint8_t *out, size_t out_avail, bsize_t *out_len, int *out_is_error)
+{
+    Utf8Decoder decoder;
+    Utf8Decoder_Init(&decoder);
+    
+    size_t i_in = 0;
+    size_t i_ch = 0;
+    
+    bsize_t len = bsize_fromsize(0);
+    
+    int error = 0;
+    
+    while (i_in < data_len) {
+        uint8_t x = data[i_in++];
+        
+        uint32_t ch;
+        if (!Utf8Decoder_Input(&decoder, x, &ch)) {
+            continue;
+        }
+        
+        if (!error) {
+            uint8_t chenc[4];
+            int chenc_n = Utf8Encoder_EncodeCharacter(ch, chenc);
+            ASSERT(chenc_n > 0)
+            
+            for (int chenc_i = 0; chenc_i < chenc_n; chenc_i++) {
+                if (i_ch >= data_len || data[i_ch] != chenc[chenc_i]) {
+                    error = 1;
+                    break;
+                }
+                i_ch++;
+            }
+        }
+        
+        uint16_t enc[2];
+        int enc_n = Utf16Encoder_EncodeCharacter(ch, enc);
+        ASSERT(enc_n > 0)
+        
+        len = bsize_add(len, bsize_fromsize(2 * enc_n));
+        
+        for (int enc_i = 0; enc_i < enc_n; enc_i++) {
+            if (out_avail == 0) {
+                break;
+            }
+            *(out++) = (enc[enc_i] >> 8);
+            out_avail--;
+            
+            if (out_avail == 0) {
+                break;
+            }
+            *(out++) = (enc[enc_i] & 0xFF);
+            out_avail--;
+        }
+    }
+    
+    if (i_ch < data_len) {
+        error = 1;
+    }
+    
+    if (out_len) {
+        *out_len = len;
+    }
+    if (out_is_error) {
+        *out_is_error = error;
+    }
+}
+
+#endif
diff --git a/external/badvpn_dns/misc/version.h b/external/badvpn_dns/misc/version.h
new file mode 100644
index 0000000..a90523f
--- /dev/null
+++ b/external/badvpn_dns/misc/version.h
@@ -0,0 +1,41 @@
+/**
+ * @file version.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Product information definitions.
+ */
+
+#ifndef BADVPN_MISC_VERSION_H
+#define BADVPN_MISC_VERSION_H
+
+#define GLOBAL_PRODUCT_NAME "BadVPN"
+#define GLOBAL_VERSION "1.999.129"
+#define GLOBAL_COPYRIGHT_NOTICE "Copyright (C) 2010 Ambroz Bizjak <ambrop7@xxxxxxxxx>"
+
+#endif
diff --git a/external/badvpn_dns/misc/write_file.h b/external/badvpn_dns/misc/write_file.h
new file mode 100644
index 0000000..97b1c19
--- /dev/null
+++ b/external/badvpn_dns/misc/write_file.h
@@ -0,0 +1,104 @@
+/**
+ * @file write_file.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_WRITE_FILE_H
+#define BADVPN_WRITE_FILE_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <misc/cstring.h>
+
+static int write_file (const char *file, const uint8_t *data, size_t len)
+{
+    FILE *f = fopen(file, "w");
+    if (!f) {
+        goto fail0;
+    }
+    
+    while (len > 0) {
+        size_t res = fwrite(data, 1, len, f);
+        if (res == 0) {
+            goto fail1;
+        }
+        
+        ASSERT(res <= len)
+        
+        data += res;
+        len -= res;
+    }
+    
+    if (fclose(f) != 0) {
+        return 0;
+    }
+    
+    return 1;
+    
+fail1:
+    fclose(f);
+fail0:
+    return 0;
+}
+
+static int write_file_cstring (const char *file, b_cstring cstr, size_t offset, size_t length)
+{
+    b_cstring_assert_range(cstr, offset, length);
+    
+    FILE *f = fopen(file, "w");
+    if (!f) {
+        goto fail0;
+    }
+    
+    B_CSTRING_LOOP_RANGE(cstr, offset, length, pos, chunk_data, chunk_length, {
+        size_t chunk_pos = 0;
+        while (chunk_pos < chunk_length) {
+            size_t res = fwrite(chunk_data + chunk_pos, 1, chunk_length - chunk_pos, f);
+            if (res == 0) {
+                goto fail1;
+            }
+            ASSERT(res <= chunk_length - chunk_pos)
+            chunk_pos += res;
+        }
+    })
+    
+    if (fclose(f) != 0) {
+        return 0;
+    }
+    
+    return 1;
+    
+fail1:
+    fclose(f);
+fail0:
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/ncd-request/CMakeLists.txt b/external/badvpn_dns/ncd-request/CMakeLists.txt
new file mode 100644
index 0000000..61447fd
--- /dev/null
+++ b/external/badvpn_dns/ncd-request/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_executable(badvpn-ncd-request
+    ncd-request.c
+)
+target_link_libraries(badvpn-ncd-request ncdrequest ncdvalgenerator ncdvalparser)
+
+install(
+    TARGETS badvpn-ncd-request
+    RUNTIME DESTINATION bin
+)
diff --git a/external/badvpn_dns/ncd-request/ncd-request.c b/external/badvpn_dns/ncd-request/ncd-request.c
new file mode 100644
index 0000000..5b44bdb
--- /dev/null
+++ b/external/badvpn_dns/ncd-request/ncd-request.c
@@ -0,0 +1,224 @@
+/**
+ * @file ncd-request.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stdlib.h>
+#include <unistd.h>
+
+#include <misc/string_begins_with.h>
+#include <base/BLog.h>
+#include <base/DebugObject.h>
+#include <system/BNetwork.h>
+#include <system/BReactor.h>
+#include <system/BAddr.h>
+#include <ncd/NCDValParser.h>
+#include <ncd/NCDValGenerator.h>
+#include <ncd/extra/NCDRequestClient.h>
+
+#include <generated/blog_channel_ncd_request.h>
+
+static void client_handler_error (void *user);
+static void client_handler_connected (void *user);
+static void request_handler_sent (void *user);
+static void request_handler_reply (void *user, NCDValMem reply_mem, NCDValRef reply_value);
+static void request_handler_finished (void *user, int is_error);
+static int write_all (int fd, const uint8_t *data, size_t len);
+static int make_connect_addr (const char *str, struct BConnection_addr *out_addr);
+
+NCDValMem request_mem;
+NCDValRef request_value;
+BReactor reactor;
+NCDRequestClient client;
+NCDRequestClientRequest request;
+int have_request;
+
+int main (int argc, char *argv[])
+{
+    int res = 1;
+    
+    if (argc != 3) {
+        fprintf(stderr, "Usage: %s < unix:<socket_path> / tcp:<address>:<port> > <request_payload>\n", (argc > 0 ? argv[0] : ""));
+        goto fail0;
+    }
+    
+    char *connect_address = argv[1];
+    char *request_payload_string = argv[2];
+    
+    BLog_InitStderr();
+    
+    BTime_Init();
+    
+    NCDValMem_Init(&request_mem);
+    
+    if (!NCDValParser_Parse(request_payload_string, strlen(request_payload_string), &request_mem, &request_value)) {
+        BLog(BLOG_ERROR, "BReactor_Init failed");
+        goto fail1;
+    }
+    
+    if (!BNetwork_GlobalInit()) {
+        BLog(BLOG_ERROR, "BNetwork_Init failed");
+        goto fail1;
+    }
+    
+    if (!BReactor_Init(&reactor)) {
+        BLog(BLOG_ERROR, "BReactor_Init failed");
+        goto fail1;
+    }
+    
+    struct BConnection_addr addr;
+    if (!make_connect_addr(connect_address, &addr)) {
+        goto fail2;
+    }
+    
+    if (!NCDRequestClient_Init(&client, addr, &reactor, NULL, client_handler_error, client_handler_connected)) {
+        BLog(BLOG_ERROR, "NCDRequestClient_Init failed");
+        goto fail2;
+    }
+    
+    have_request = 0;
+    
+    res = BReactor_Exec(&reactor);
+    
+    if (have_request) {
+        NCDRequestClientRequest_Free(&request);
+    }
+    NCDRequestClient_Free(&client);
+fail2:
+    BReactor_Free(&reactor);
+fail1:
+    NCDValMem_Free(&request_mem);
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    return res;
+}
+
+static int make_connect_addr (const char *str, struct BConnection_addr *out_addr)
+{
+    size_t i;
+    
+    if (i = string_begins_with(str, "unix:")) {
+        *out_addr = BConnection_addr_unix(str + i, strlen(str + i));
+    }
+    else if (i = string_begins_with(str, "tcp:")) {
+        BAddr baddr;
+        if (!BAddr_Parse2(&baddr, (char *)str + i, NULL, 0, 1)) {
+            BLog(BLOG_ERROR, "failed to parse tcp address");
+            return 0;
+        }
+        
+        *out_addr = BConnection_addr_baddr(baddr);
+    }
+    else {
+        BLog(BLOG_ERROR, "address must start with unix: or tcp:");
+        return 0;
+    }
+    
+    return 1;
+}
+
+static void client_handler_error (void *user)
+{
+    BLog(BLOG_ERROR, "client error");
+    
+    BReactor_Quit(&reactor, 1);
+}
+
+static void client_handler_connected (void *user)
+{
+    ASSERT(!have_request)
+    
+    if (!NCDRequestClientRequest_Init(&request, &client, request_value, NULL, request_handler_sent, request_handler_reply, request_handler_finished)) {
+        BLog(BLOG_ERROR, "NCDRequestClientRequest_Init failed");
+        BReactor_Quit(&reactor, 1);
+        return;
+    }
+    
+    have_request = 1;
+}
+
+static void request_handler_sent (void *user)
+{
+    ASSERT(have_request)
+}
+
+static void request_handler_reply (void *user, NCDValMem reply_mem, NCDValRef reply_value)
+{
+    ASSERT(have_request)
+    
+    char *str = NCDValGenerator_Generate(reply_value);
+    if (!str) {
+        BLog(BLOG_ERROR, "NCDValGenerator_Generate failed");
+        goto fail0;
+    }
+    
+    if (!write_all(1, (uint8_t *)str, strlen(str))) {
+        goto fail1;
+    }
+    if (!write_all(1, (const uint8_t *)"\n", 1)) {
+        goto fail1;
+    }
+    
+    free(str);
+    NCDValMem_Free(&reply_mem);
+    return;
+    
+fail1:
+    free(str);
+fail0:
+    NCDValMem_Free(&reply_mem);
+    BReactor_Quit(&reactor, 1);
+}
+
+static void request_handler_finished (void *user, int is_error)
+{
+    if (is_error) {
+        BLog(BLOG_ERROR, "request error");
+        BReactor_Quit(&reactor, 1);
+        return;
+    }
+    
+    BReactor_Quit(&reactor, 0);
+}
+
+static int write_all (int fd, const uint8_t *data, size_t len)
+{
+    while (len > 0) {
+        ssize_t res = write(fd, data, len);
+        if (res <= 0) {
+            BLog(BLOG_ERROR, "write failed");
+            return 0;
+        }
+        data += res;
+        len -= res;
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/ncd/CMakeLists.txt b/external/badvpn_dns/ncd/CMakeLists.txt
new file mode 100644
index 0000000..8c384ae
--- /dev/null
+++ b/external/badvpn_dns/ncd/CMakeLists.txt
@@ -0,0 +1,211 @@
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+set(NCD_ADDITIONAL_SOURCES)
+set(NCD_ADDITIONAL_LIBS)
+
+if (NOT EMSCRIPTEN)
+    if (BADVPN_USE_LINUX_RFKILL)
+        list(APPEND NCD_ADDITIONAL_SOURCES
+            extra/NCDRfkillMonitor.c
+            modules/net_backend_rfkill.c
+        )
+    endif ()
+
+    if (BADVPN_USE_LINUX_INPUT)
+        list(APPEND NCD_ADDITIONAL_SOURCES
+            modules/sys_evdev.c
+        )
+    endif ()
+
+    if (BADVPN_USE_INOTIFY)
+        list(APPEND NCD_ADDITIONAL_SOURCES
+            modules/sys_watch_directory.c
+        )
+    endif ()
+
+    badvpn_add_library(ncdinterfacemonitor "base;system" "" extra/NCDInterfaceMonitor.c)
+    
+    badvpn_add_library(ncdrequest "base;system;ncdvalgenerator;ncdvalparser" "" extra/NCDRequestClient.c)
+    
+    list(APPEND NCD_ADDITIONAL_SOURCES
+        extra/NCDIfConfig.c
+        extra/build_cmdline.c
+        extra/NCDBProcessOpts.c
+        modules/command_template.c
+        modules/event_template.c
+        modules/regex_match.c
+        modules/run.c
+        modules/runonce.c
+        modules/daemon.c
+        modules/net_backend_waitdevice.c
+        modules/net_backend_waitlink.c
+        modules/net_backend_badvpn.c
+        modules/net_backend_wpa_supplicant.c
+        modules/net_up.c
+        modules/net_dns.c
+        modules/net_iptables.c
+        modules/net_ipv4_addr.c
+        modules/net_ipv4_route.c
+        modules/net_ipv4_dhcp.c
+        modules/net_ipv4_arp_probe.c
+        modules/net_watch_interfaces.c
+        modules/sys_watch_input.c
+        modules/sys_watch_usb.c
+        modules/sys_request_server.c
+        modules/net_ipv6_wait_dynamic_addr.c
+        modules/sys_request_client.c
+        modules/reboot.c
+        modules/net_ipv6_addr.c
+        modules/net_ipv6_route.c
+        modules/socket.c
+        modules/sys_start_process.c
+        modules/load_module.c
+    )
+    
+    list(APPEND NCD_ADDITIONAL_LIBS
+        dhcpclient arpprobe ncdinterfacemonitor ncdrequest udevmonitor badvpn_random dl
+    )
+endif ()
+
+badvpn_add_library(ncdtokenizer "base" "" NCDConfigTokenizer.c)
+
+badvpn_add_library(ncdstringindex "base" "" NCDStringIndex.c)
+
+badvpn_add_library(ncdval "base;ncdstringindex" "" NCDVal.c)
+
+badvpn_add_library(ncdvalgenerator "base;ncdval" "" NCDValGenerator.c)
+
+badvpn_add_library(ncdvalparser "base;ncdval;ncdtokenizer;ncdvalcons" "" NCDValParser.c)
+
+badvpn_add_library(ncdast "" "" NCDAst.c)
+
+badvpn_add_library(ncdconfigparser "base;ncdtokenizer;ncdast" "" NCDConfigParser.c)
+
+badvpn_add_library(ncdsugar "ncdast" "" NCDSugar.c)
+
+badvpn_add_library(ncdvalcons "ncdval" "" NCDValCons.c)
+
+badvpn_add_library(ncdbuildprogram "base;ncdast;ncdconfigparser" "" NCDBuildProgram.c)
+
+badvpn_add_library(ncdobject "" "" NCDObject.c)
+
+badvpn_add_library(ncdmodule "base;ncdobject;ncdstringindex;ncdval" "" NCDModule.c)
+
+set(NCDINTERPRETER_SOURCES
+    NCDInterpreter.c
+    NCDModuleIndex.c
+    NCDInterpProcess.c
+    NCDInterpProg.c
+    NCDPlaceholderDb.c
+    NCDMethodIndex.c
+    extra/BEventLock.c
+    extra/NCDBuf.c
+    modules/var.c
+    modules/list.c
+    modules/depend.c
+    modules/multidepend.c
+    modules/dynamic_depend.c
+    modules/concat.c
+    modules/if.c
+    modules/strcmp.c
+    modules/logical.c
+    modules/sleep.c
+    modules/print.c
+    modules/blocker.c
+    modules/spawn.c
+    modules/imperative.c
+    modules/ref.c
+    modules/index.c
+    modules/alias.c
+    modules/process_manager.c
+    modules/ondemand.c
+    modules/foreach.c
+    modules/choose.c
+    modules/from_string.c
+    modules/to_string.c
+    modules/value.c
+    modules/try.c
+    modules/exit.c
+    modules/getargs.c
+    modules/arithmetic.c
+    modules/parse.c
+    modules/valuemetic.c
+    modules/file.c
+    modules/netmask.c
+    modules/implode.c
+    modules/call2.c
+    modules/assert.c
+    modules/explode.c
+    modules/net_ipv4_addr_in_network.c
+    modules/net_ipv6_addr_in_network.c
+    modules/timer.c
+    modules/file_open.c
+    modules/backtrack.c
+    modules/depend_scope.c
+    modules/substr.c
+    modules/log.c
+    modules/buffer.c
+    modules/getenv.c
+    ${NCD_ADDITIONAL_SOURCES}
+)
+set(NCDINTERPRETER_LIBS
+    base system flow flowextra ncdval ncdstringindex ncdvalgenerator ncdvalparser
+    ncdconfigparser ncdsugar ncdobject ncdmodule ${NCD_ADDITIONAL_LIBS})
+badvpn_add_library(ncdinterpreter "${NCDINTERPRETER_LIBS}" "" "${NCDINTERPRETER_SOURCES}")
+
+if (BADVPN_USE_LINUX_INPUT)
+    string(REPLACE " " ";" FLAGS_LIST "${CMAKE_C_FLAGS}")
+    execute_process(COMMAND ${CMAKE_C_COMPILER} ${FLAGS_LIST} -E ${CMAKE_CURRENT_SOURCE_DIR}/include_linux_input.c
+                    RESULT_VARIABLE LINUX_INPUT_PREPROCESS_RESULT
+                    OUTPUT_VARIABLE LINUX_INPUT_PREPROCESS_OUTPUT)
+    if (NOT LINUX_INPUT_PREPROCESS_RESULT EQUAL 0)
+        message(FATAL_ERROR "failed to preprocess linux/input.h include")
+    endif ()
+
+    string(REGEX MATCH "\"(/[^\"]+/linux/input.h)\"" LINUX_INPUT_MATCH ${LINUX_INPUT_PREPROCESS_OUTPUT})
+    if (NOT LINUX_INPUT_MATCH)
+        message(FATAL_ERROR "failed to match preprocessor output for path of linux/input.h")
+    endif ()
+    set(LINUX_INPUT_H_PATH ${CMAKE_MATCH_1})
+
+    message(STATUS "Generating linux_input_names.h from ${LINUX_INPUT_H_PATH}")
+
+    execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/parse_linux_input.sh 
+                            ${LINUX_INPUT_H_PATH}
+                            ${CMAKE_CURRENT_BINARY_DIR}/linux_input_names.h
+                    RESULT_VARIABLE LINUX_INPUT_PARSE_RESULT)
+    if (NOT LINUX_INPUT_PARSE_RESULT EQUAL 0)
+        message(FATAL_ERROR "failed to generate linux_input_names.h")
+    endif ()
+endif ()
+
+if (NOT EMSCRIPTEN)
+    add_executable(badvpn-ncd ncd.c)
+    target_link_libraries(badvpn-ncd ncdinterpreter ncdbuildprogram)
+    
+    install(
+        TARGETS badvpn-ncd
+        RUNTIME DESTINATION bin
+    )
+endif ()
+
+if (EMSCRIPTEN)
+    add_executable(emncd emncd.c)
+    target_link_libraries(emncd ncdinterpreter)
+    
+    add_custom_command(
+        OUTPUT emncd.bc
+        DEPENDS emncd
+        COMMAND cp emncd emncd.bc
+    )
+    
+    add_custom_command(
+        OUTPUT emncd.js
+        DEPENDS emncd.bc
+        COMMAND
+        ${CMAKE_C_COMPILER} emncd.bc -o emncd.js -O2
+        -s EXPORTED_FUNCTIONS=\"['_breactor_timer_cb','_main','_emncd_start','_emncd_stop']\"
+    )
+    
+    add_custom_target(emncd_js ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/emncd.js)
+endif ()
diff --git a/external/badvpn_dns/ncd/NCDAst.c b/external/badvpn_dns/ncd/NCDAst.c
new file mode 100644
index 0000000..2b229e3
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDAst.c
@@ -0,0 +1,1022 @@
+/**
+ * @file NCDAst.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <string.h>
+
+#include <misc/offset.h>
+#include <misc/strdup.h>
+
+#include "NCDAst.h"
+
+struct NCDValue__list_element {
+    LinkedList1Node list_node;
+    NCDValue v;
+};
+
+struct NCDValue__map_element {
+    LinkedList1Node list_node;
+    NCDValue key;
+    NCDValue val;
+};
+
+struct ProgramElem {
+    LinkedList1Node elems_list_node;
+    NCDProgramElem elem;
+};
+
+struct BlockStatement {
+    LinkedList1Node statements_list_node;
+    NCDStatement s;
+};
+
+struct IfBlockIf {
+    LinkedList1Node ifs_list_node;
+    NCDIf ifc;
+};
+
+static void value_assert (NCDValue *o)
+{
+    switch (o->type) {
+        case NCDVALUE_STRING:
+        case NCDVALUE_LIST:
+        case NCDVALUE_MAP:
+        case NCDVALUE_VAR:
+            return;
+        default:
+            ASSERT(0);
+    }
+}
+
+void NCDValue_Free (NCDValue *o)
+{
+    switch (o->type) {
+        case NCDVALUE_STRING: {
+            free(o->string);
+        } break;
+        
+        case NCDVALUE_LIST: {
+            LinkedList1Node *n;
+            while (n = LinkedList1_GetFirst(&o->list)) {
+                struct NCDValue__list_element *e = UPPER_OBJECT(n, struct NCDValue__list_element, list_node);
+                
+                NCDValue_Free(&e->v);
+                LinkedList1_Remove(&o->list, &e->list_node);
+                free(e);
+            }
+        } break;
+        
+        case NCDVALUE_MAP: {
+            LinkedList1Node *n;
+            while (n = LinkedList1_GetFirst(&o->map_list)) {
+                struct NCDValue__map_element *e = UPPER_OBJECT(n, struct NCDValue__map_element, list_node);
+                
+                LinkedList1_Remove(&o->map_list, &e->list_node);
+                NCDValue_Free(&e->key);
+                NCDValue_Free(&e->val);
+                free(e);
+            }
+        } break;
+        
+        case NCDVALUE_VAR: {
+            free(o->var_name);
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}
+
+int NCDValue_Type (NCDValue *o)
+{
+    value_assert(o);
+    
+    return o->type;
+}
+
+int NCDValue_InitString (NCDValue *o, const char *str)
+{
+    return NCDValue_InitStringBin(o, (const uint8_t *)str, strlen(str));
+}
+
+int NCDValue_InitStringBin (NCDValue *o, const uint8_t *str, size_t len)
+{
+    if (len == SIZE_MAX) {
+        return 0;
+    }
+    
+    if (!(o->string = malloc(len + 1))) {
+        return 0;
+    }
+    
+    memcpy(o->string, str, len);
+    o->string[len] = '\0';
+    o->string_len = len;
+    
+    o->type = NCDVALUE_STRING;
+    
+    return 1;
+}
+
+const char * NCDValue_StringValue (NCDValue *o)
+{
+    ASSERT(o->type == NCDVALUE_STRING)
+    
+    return (char *)o->string;
+}
+
+size_t NCDValue_StringLength (NCDValue *o)
+{
+    ASSERT(o->type == NCDVALUE_STRING)
+    
+    return o->string_len;
+}
+
+void NCDValue_InitList (NCDValue *o)
+{
+    o->type = NCDVALUE_LIST;
+    LinkedList1_Init(&o->list);
+    o->list_count = 0;
+}
+
+size_t NCDValue_ListCount (NCDValue *o)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_LIST)
+    
+    return o->list_count;
+}
+
+int NCDValue_ListAppend (NCDValue *o, NCDValue v)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_LIST)
+    value_assert(&v);
+    
+    if (o->list_count == SIZE_MAX) {
+        return 0;
+    }
+    
+    struct NCDValue__list_element *e = malloc(sizeof(*e));
+    if (!e) {
+        return 0;
+    }
+    
+    e->v = v;
+    LinkedList1_Append(&o->list, &e->list_node);
+    
+    o->list_count++;
+    
+    return 1;
+}
+
+int NCDValue_ListPrepend (NCDValue *o, NCDValue v)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_LIST)
+    value_assert(&v);
+    
+    if (o->list_count == SIZE_MAX) {
+        return 0;
+    }
+    
+    struct NCDValue__list_element *e = malloc(sizeof(*e));
+    if (!e) {
+        return 0;
+    }
+    
+    e->v = v;
+    LinkedList1_Prepend(&o->list, &e->list_node);
+    
+    o->list_count++;
+    
+    return 1;
+}
+
+NCDValue * NCDValue_ListFirst (NCDValue *o)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_LIST)
+    
+    LinkedList1Node *ln = LinkedList1_GetFirst(&o->list);
+    
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct NCDValue__list_element *e = UPPER_OBJECT(ln, struct NCDValue__list_element, list_node);
+    
+    return &e->v;
+}
+
+NCDValue * NCDValue_ListNext (NCDValue *o, NCDValue *ev)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_LIST)
+    
+    struct NCDValue__list_element *cur_e = UPPER_OBJECT(ev, struct NCDValue__list_element, v);
+    LinkedList1Node *ln = LinkedList1Node_Next(&cur_e->list_node);
+    
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct NCDValue__list_element *e = UPPER_OBJECT(ln, struct NCDValue__list_element, list_node);
+    
+    return &e->v;
+}
+
+void NCDValue_InitMap (NCDValue *o)
+{
+    o->type = NCDVALUE_MAP;
+    LinkedList1_Init(&o->map_list);
+    o->map_count = 0;
+}
+
+size_t NCDValue_MapCount (NCDValue *o)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    
+    return o->map_count;
+}
+
+int NCDValue_MapPrepend (NCDValue *o, NCDValue key, NCDValue val)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    value_assert(&key);
+    value_assert(&val);
+    
+    if (o->map_count == SIZE_MAX) {
+        return 0;
+    }
+    
+    struct NCDValue__map_element *e = malloc(sizeof(*e));
+    if (!e) {
+        return 0;
+    }
+    
+    e->key = key;
+    e->val = val;
+    LinkedList1_Prepend(&o->map_list, &e->list_node);
+    
+    o->map_count++;
+    
+    return 1;
+}
+
+NCDValue * NCDValue_MapFirstKey (NCDValue *o)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    
+    LinkedList1Node *ln = LinkedList1_GetFirst(&o->map_list);
+    
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct NCDValue__map_element *e = UPPER_OBJECT(ln, struct NCDValue__map_element, list_node);
+    
+    value_assert(&e->key);
+    value_assert(&e->val);
+    
+    return &e->key;
+}
+
+NCDValue * NCDValue_MapNextKey (NCDValue *o, NCDValue *ekey)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    
+    struct NCDValue__map_element *e0 = UPPER_OBJECT(ekey, struct NCDValue__map_element, key);
+    value_assert(&e0->key);
+    value_assert(&e0->val);
+    
+    LinkedList1Node *ln = LinkedList1Node_Next(&e0->list_node);
+    
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct NCDValue__map_element *e = UPPER_OBJECT(ln, struct NCDValue__map_element, list_node);
+    
+    value_assert(&e->key);
+    value_assert(&e->val);
+    
+    return &e->key;
+}
+
+NCDValue * NCDValue_MapKeyValue (NCDValue *o, NCDValue *ekey)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    
+    struct NCDValue__map_element *e = UPPER_OBJECT(ekey, struct NCDValue__map_element, key);
+    value_assert(&e->key);
+    value_assert(&e->val);
+    
+    return &e->val;
+}
+
+int NCDValue_InitVar (NCDValue *o, const char *var_name)
+{
+    ASSERT(var_name)
+    
+    if (!(o->var_name = strdup(var_name))) {
+        return 0;
+    }
+    
+    o->type = NCDVALUE_VAR;
+    
+    return 1;
+}
+
+const char * NCDValue_VarName (NCDValue *o)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_VAR)
+    
+    return o->var_name;
+}
+
+void NCDProgram_Init (NCDProgram *o)
+{
+    LinkedList1_Init(&o->elems_list);
+    o->num_elems = 0;
+}
+
+void NCDProgram_Free (NCDProgram *o)
+{
+    LinkedList1Node *ln;
+    while (ln = LinkedList1_GetFirst(&o->elems_list)) {
+        struct ProgramElem *e = UPPER_OBJECT(ln, struct ProgramElem, elems_list_node);
+        NCDProgramElem_Free(&e->elem);
+        LinkedList1_Remove(&o->elems_list, &e->elems_list_node);
+        free(e);
+    }
+}
+
+NCDProgramElem * NCDProgram_PrependElem (NCDProgram *o, NCDProgramElem elem)
+{
+    if (o->num_elems == SIZE_MAX) {
+        return NULL;
+    }
+    
+    struct ProgramElem *e = malloc(sizeof(*e));
+    if (!e) {
+        return NULL;
+    }
+    
+    LinkedList1_Prepend(&o->elems_list, &e->elems_list_node);
+    e->elem = elem;
+    
+    o->num_elems++;
+    
+    return &e->elem;
+}
+
+NCDProgramElem * NCDProgram_FirstElem (NCDProgram *o)
+{
+    LinkedList1Node *ln = LinkedList1_GetFirst(&o->elems_list);
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct ProgramElem *e = UPPER_OBJECT(ln, struct ProgramElem, elems_list_node);
+    
+    return &e->elem;
+}
+
+NCDProgramElem * NCDProgram_NextElem (NCDProgram *o, NCDProgramElem *ee)
+{
+    ASSERT(ee)
+    
+    struct ProgramElem *cur_e = UPPER_OBJECT(ee, struct ProgramElem, elem);
+    
+    LinkedList1Node *ln = LinkedList1Node_Next(&cur_e->elems_list_node);
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct ProgramElem *e = UPPER_OBJECT(ln, struct ProgramElem, elems_list_node);
+    
+    return &e->elem;
+}
+
+size_t NCDProgram_NumElems (NCDProgram *o)
+{
+    return o->num_elems;
+}
+
+int NCDProgram_ContainsElemType (NCDProgram *o, int elem_type)
+{
+    for (NCDProgramElem *elem = NCDProgram_FirstElem(o); elem; elem = NCDProgram_NextElem(o, elem)) {
+        if (NCDProgramElem_Type(elem) == elem_type) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+void NCDProgram_RemoveElem (NCDProgram *o, NCDProgramElem *ee)
+{
+    ASSERT(ee)
+    
+    struct ProgramElem *e = UPPER_OBJECT(ee, struct ProgramElem, elem);
+    NCDProgramElem_Free(&e->elem);
+    LinkedList1_Remove(&o->elems_list, &e->elems_list_node);
+    free(e);
+    
+    ASSERT(o->num_elems > 0)
+    o->num_elems--;
+}
+
+int NCDProgram_ReplaceElemWithProgram (NCDProgram *o, NCDProgramElem *ee, NCDProgram replace_prog)
+{
+    ASSERT(ee)
+    
+    if (replace_prog.num_elems > SIZE_MAX - o->num_elems) {
+        return 0;
+    }
+    
+    struct ProgramElem *e = UPPER_OBJECT(ee, struct ProgramElem, elem);
+    
+    LinkedList1_InsertListAfter(&o->elems_list, replace_prog.elems_list, &e->elems_list_node);
+    o->num_elems += replace_prog.num_elems;
+    
+    NCDProgram_RemoveElem(o, ee);
+    
+    return 1;
+}
+
+void NCDProgramElem_InitProcess (NCDProgramElem *o, NCDProcess process)
+{
+    o->type = NCDPROGRAMELEM_PROCESS;
+    o->process = process;
+}
+
+int NCDProgramElem_InitInclude (NCDProgramElem *o, const char *path_data, size_t path_length)
+{
+    if (!(o->include.path_data = b_strdup_bin(path_data, path_length))) {
+        return 0;
+    }
+    
+    o->type = NCDPROGRAMELEM_INCLUDE;
+    o->include.path_length = path_length;
+    
+    return 1;
+}
+
+int NCDProgramElem_InitIncludeGuard (NCDProgramElem *o, const char *id_data, size_t id_length)
+{
+    if (!(o->include_guard.id_data = b_strdup_bin(id_data, id_length))) {
+        return 0;
+    }
+    
+    o->type = NCDPROGRAMELEM_INCLUDE_GUARD;
+    o->include_guard.id_length = id_length;
+    
+    return 1;
+}
+
+
+void NCDProgramElem_Free (NCDProgramElem *o)
+{
+    switch (o->type) {
+        case NCDPROGRAMELEM_PROCESS: {
+            NCDProcess_Free(&o->process);
+        } break;
+        
+        case NCDPROGRAMELEM_INCLUDE: {
+            free(o->include.path_data);
+        } break;
+        
+        case NCDPROGRAMELEM_INCLUDE_GUARD: {
+            free(o->include_guard.id_data);
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+int NCDProgramElem_Type (NCDProgramElem *o)
+{
+    return o->type;
+}
+
+NCDProcess * NCDProgramElem_Process (NCDProgramElem *o)
+{
+    ASSERT(o->type == NCDPROGRAMELEM_PROCESS)
+    
+    return &o->process;
+}
+
+const char * NCDProgramElem_IncludePathData (NCDProgramElem *o)
+{
+    ASSERT(o->type == NCDPROGRAMELEM_INCLUDE)
+    
+    return o->include.path_data;
+}
+
+size_t NCDProgramElem_IncludePathLength (NCDProgramElem *o)
+{
+    ASSERT(o->type == NCDPROGRAMELEM_INCLUDE)
+    
+    return o->include.path_length;
+}
+
+const char * NCDProgramElem_IncludeGuardIdData (NCDProgramElem *o)
+{
+    ASSERT(o->type == NCDPROGRAMELEM_INCLUDE_GUARD)
+    
+    return o->include_guard.id_data;
+}
+
+size_t NCDProgramElem_IncludeGuardIdLength (NCDProgramElem *o)
+{
+    ASSERT(o->type == NCDPROGRAMELEM_INCLUDE_GUARD)
+    
+    return o->include_guard.id_length;
+}
+
+int NCDProcess_Init (NCDProcess *o, int is_template, const char *name, NCDBlock block)
+{
+    ASSERT(is_template == !!is_template)
+    ASSERT(name)
+    
+    if (!(o->name = strdup(name))) {
+        return 0;
+    }
+    
+    o->is_template = is_template;
+    o->block = block;
+    
+    return 1;
+}
+
+void NCDProcess_Free (NCDProcess *o)
+{
+    NCDBlock_Free(&o->block);
+    free(o->name);
+}
+
+int NCDProcess_IsTemplate (NCDProcess *o)
+{
+    return o->is_template;
+}
+
+const char * NCDProcess_Name (NCDProcess *o)
+{
+    return o->name;
+}
+
+NCDBlock * NCDProcess_Block (NCDProcess *o)
+{
+    return &o->block;
+}
+
+void NCDBlock_Init (NCDBlock *o)
+{
+    LinkedList1_Init(&o->statements_list);
+    o->count = 0;
+}
+
+void NCDBlock_Free (NCDBlock *o)
+{
+    LinkedList1Node *ln;
+    while (ln = LinkedList1_GetFirst(&o->statements_list)) {
+        struct BlockStatement *e = UPPER_OBJECT(ln, struct BlockStatement, statements_list_node);
+        NCDStatement_Free(&e->s);
+        LinkedList1_Remove(&o->statements_list, &e->statements_list_node);
+        free(e);
+    }
+}
+
+int NCDBlock_PrependStatement (NCDBlock *o, NCDStatement s)
+{
+    return NCDBlock_InsertStatementAfter(o, NULL, s);
+}
+
+int NCDBlock_InsertStatementAfter (NCDBlock *o, NCDStatement *after, NCDStatement s)
+{
+    struct BlockStatement *after_e = NULL;
+    if (after) {
+        after_e = UPPER_OBJECT(after, struct BlockStatement, s);
+    }
+    
+    if (o->count == SIZE_MAX) {
+        return 0;
+    }
+    
+    struct BlockStatement *e = malloc(sizeof(*e));
+    if (!e) {
+        return 0;
+    }
+    
+    if (after_e) {
+        LinkedList1_InsertAfter(&o->statements_list, &e->statements_list_node, &after_e->statements_list_node);
+    } else {
+        LinkedList1_Prepend(&o->statements_list, &e->statements_list_node);
+    }
+    e->s = s;
+    
+    o->count++;
+    
+    return 1;
+}
+
+NCDStatement * NCDBlock_ReplaceStatement (NCDBlock *o, NCDStatement *es, NCDStatement s)
+{
+    ASSERT(es)
+    
+    struct BlockStatement *e = UPPER_OBJECT(es, struct BlockStatement, s);
+    
+    NCDStatement_Free(&e->s);
+    e->s = s;
+    
+    return &e->s;
+}
+
+NCDStatement * NCDBlock_FirstStatement (NCDBlock *o)
+{
+    LinkedList1Node *ln = LinkedList1_GetFirst(&o->statements_list);
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct BlockStatement *e = UPPER_OBJECT(ln, struct BlockStatement, statements_list_node);
+    
+    return &e->s;
+}
+
+NCDStatement * NCDBlock_NextStatement (NCDBlock *o, NCDStatement *es)
+{
+    ASSERT(es)
+    
+    struct BlockStatement *cur_e = UPPER_OBJECT(es, struct BlockStatement, s);
+    
+    LinkedList1Node *ln = LinkedList1Node_Next(&cur_e->statements_list_node);
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct BlockStatement *e = UPPER_OBJECT(ln, struct BlockStatement, statements_list_node);
+    
+    return &e->s;
+}
+
+size_t NCDBlock_NumStatements (NCDBlock *o)
+{
+    return o->count;
+}
+
+int NCDStatement_InitReg (NCDStatement *o, const char *name, const char *objname, const char *cmdname, NCDValue args)
+{
+    ASSERT(cmdname)
+    ASSERT(NCDValue_Type(&args) == NCDVALUE_LIST)
+    
+    o->name = NULL;
+    o->reg.objname = NULL;
+    o->reg.cmdname = NULL;
+    
+    if (name && !(o->name = strdup(name))) {
+        goto fail;
+    }
+    
+    if (objname && !(o->reg.objname = strdup(objname))) {
+        goto fail;
+    }
+    
+    if (!(o->reg.cmdname = strdup(cmdname))) {
+        goto fail;
+    }
+    
+    o->type = NCDSTATEMENT_REG;
+    o->reg.args = args;
+    
+    return 1;
+    
+fail:
+    free(o->name);
+    free(o->reg.objname);
+    free(o->reg.cmdname);
+    return 0;
+}
+
+int NCDStatement_InitIf (NCDStatement *o, const char *name, NCDIfBlock ifblock)
+{
+    o->name = NULL;
+    
+    if (name && !(o->name = strdup(name))) {
+        return 0;
+    }
+    
+    o->type = NCDSTATEMENT_IF;
+    o->ifc.ifblock = ifblock;
+    o->ifc.have_else = 0;
+    
+    return 1;
+}
+
+int NCDStatement_InitForeach (NCDStatement *o, const char *name, NCDValue collection, const char *name1, const char *name2, NCDBlock block)
+{
+    ASSERT(name1)
+    
+    o->name = NULL;
+    o->foreach.name1 = NULL;
+    o->foreach.name2 = NULL;
+    
+    if (name && !(o->name = strdup(name))) {
+        goto fail;
+    }
+    
+    if (!(o->foreach.name1 = strdup(name1))) {
+        goto fail;
+    }
+    
+    if (name2 && !(o->foreach.name2 = strdup(name2))) {
+        goto fail;
+    }
+    
+    o->type = NCDSTATEMENT_FOREACH;
+    o->foreach.collection = collection;
+    o->foreach.block = block;
+    o->foreach.is_grabbed = 0;
+    
+    return 1;
+    
+fail:
+    free(o->name);
+    free(o->foreach.name1);
+    free(o->foreach.name2);
+    return 0;
+}
+
+void NCDStatement_Free (NCDStatement *o)
+{
+    switch (o->type) {
+        case NCDSTATEMENT_REG: {
+            NCDValue_Free(&o->reg.args);
+            free(o->reg.cmdname);
+            free(o->reg.objname);
+        } break;
+        
+        case NCDSTATEMENT_IF: {
+            if (o->ifc.have_else) {
+                NCDBlock_Free(&o->ifc.else_block);
+            }
+            
+            NCDIfBlock_Free(&o->ifc.ifblock);
+        } break;
+        
+        case NCDSTATEMENT_FOREACH: {
+            if (!o->foreach.is_grabbed) {
+                NCDBlock_Free(&o->foreach.block);
+                NCDValue_Free(&o->foreach.collection);
+            }
+            free(o->foreach.name2);
+            free(o->foreach.name1);
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    free(o->name);
+}
+
+int NCDStatement_Type (NCDStatement *o)
+{
+    return o->type;
+}
+
+const char * NCDStatement_Name (NCDStatement *o)
+{
+    return o->name;
+}
+
+const char * NCDStatement_RegObjName (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_REG)
+    
+    return o->reg.objname;
+}
+
+const char * NCDStatement_RegCmdName (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_REG)
+    
+    return o->reg.cmdname;
+}
+
+NCDValue * NCDStatement_RegArgs (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_REG)
+    
+    return &o->reg.args;
+}
+
+NCDIfBlock * NCDStatement_IfBlock (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_IF)
+    
+    return &o->ifc.ifblock;
+}
+
+void NCDStatement_IfAddElse (NCDStatement *o, NCDBlock else_block)
+{
+    ASSERT(o->type == NCDSTATEMENT_IF)
+    ASSERT(!o->ifc.have_else)
+    
+    o->ifc.have_else = 1;
+    o->ifc.else_block = else_block;
+}
+
+NCDBlock * NCDStatement_IfElse (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_IF)
+    
+    if (!o->ifc.have_else) {
+        return NULL;
+    }
+    
+    return &o->ifc.else_block;
+}
+
+NCDBlock NCDStatement_IfGrabElse (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_IF)
+    ASSERT(o->ifc.have_else)
+    
+    o->ifc.have_else = 0;
+    
+    return o->ifc.else_block;
+}
+
+NCDValue * NCDStatement_ForeachCollection (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_FOREACH)
+    ASSERT(!o->foreach.is_grabbed)
+    
+    return &o->foreach.collection;
+}
+
+const char * NCDStatement_ForeachName1 (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_FOREACH)
+    
+    return o->foreach.name1;
+}
+
+const char * NCDStatement_ForeachName2 (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_FOREACH)
+    
+    return o->foreach.name2;
+}
+
+NCDBlock * NCDStatement_ForeachBlock (NCDStatement *o)
+{
+    ASSERT(o->type == NCDSTATEMENT_FOREACH)
+    ASSERT(!o->foreach.is_grabbed)
+    
+    return &o->foreach.block;
+}
+
+void NCDStatement_ForeachGrab (NCDStatement *o, NCDValue *out_collection, NCDBlock *out_block)
+{
+    ASSERT(o->type == NCDSTATEMENT_FOREACH)
+    ASSERT(!o->foreach.is_grabbed)
+    
+    *out_collection = o->foreach.collection;
+    *out_block = o->foreach.block;
+    o->foreach.is_grabbed = 1;
+}
+
+void NCDIfBlock_Init (NCDIfBlock *o)
+{
+    LinkedList1_Init(&o->ifs_list);
+}
+
+void NCDIfBlock_Free (NCDIfBlock *o)
+{
+    LinkedList1Node *ln;
+    while (ln = LinkedList1_GetFirst(&o->ifs_list)) {
+        struct IfBlockIf *e = UPPER_OBJECT(ln, struct IfBlockIf, ifs_list_node);
+        NCDIf_Free(&e->ifc);
+        LinkedList1_Remove(&o->ifs_list, &e->ifs_list_node);
+        free(e);
+    }
+}
+
+int NCDIfBlock_PrependIf (NCDIfBlock *o, NCDIf ifc)
+{
+    struct IfBlockIf *e = malloc(sizeof(*e));
+    if (!e) {
+        return 0;
+    }
+    
+    LinkedList1_Prepend(&o->ifs_list, &e->ifs_list_node);
+    e->ifc = ifc;
+    
+    return 1;
+}
+
+NCDIf * NCDIfBlock_FirstIf (NCDIfBlock *o)
+{
+    LinkedList1Node *ln = LinkedList1_GetFirst(&o->ifs_list);
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct IfBlockIf *e = UPPER_OBJECT(ln, struct IfBlockIf, ifs_list_node);
+    
+    return &e->ifc;
+}
+
+NCDIf * NCDIfBlock_NextIf (NCDIfBlock *o, NCDIf *ei)
+{
+    ASSERT(ei)
+    
+    struct IfBlockIf *cur_e = UPPER_OBJECT(ei, struct IfBlockIf, ifc);
+    
+    LinkedList1Node *ln = LinkedList1Node_Next(&cur_e->ifs_list_node);
+    if (!ln) {
+        return NULL;
+    }
+    
+    struct IfBlockIf *e = UPPER_OBJECT(ln, struct IfBlockIf, ifs_list_node);
+    
+    return &e->ifc;
+}
+
+NCDIf NCDIfBlock_GrabIf (NCDIfBlock *o, NCDIf *ei)
+{
+    ASSERT(ei)
+    
+    struct IfBlockIf *e = UPPER_OBJECT(ei, struct IfBlockIf, ifc);
+    
+    NCDIf old_ifc = e->ifc;
+    
+    LinkedList1_Remove(&o->ifs_list, &e->ifs_list_node);
+    free(e);
+    
+    return old_ifc;
+}
+
+void NCDIf_Init (NCDIf *o, NCDValue cond, NCDBlock block)
+{
+    o->cond = cond;
+    o->block = block;
+}
+
+void NCDIf_Free (NCDIf *o)
+{
+    NCDValue_Free(&o->cond);
+    NCDBlock_Free(&o->block);
+}
+
+void NCDIf_FreeGrab (NCDIf *o, NCDValue *out_cond, NCDBlock *out_block)
+{
+    *out_cond = o->cond;
+    *out_block = o->block;
+}
+
+NCDValue * NCDIf_Cond (NCDIf *o)
+{
+    return &o->cond;
+}
+
+NCDBlock * NCDIf_Block (NCDIf *o)
+{
+    return &o->block;
+}
diff --git a/external/badvpn_dns/ncd/NCDAst.h b/external/badvpn_dns/ncd/NCDAst.h
new file mode 100644
index 0000000..95ca9e4
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDAst.h
@@ -0,0 +1,237 @@
+/**
+ * @file NCDAst.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDAST_H
+#define BADVPN_NCDAST_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <structure/LinkedList1.h>
+
+typedef struct NCDValue_s NCDValue;
+typedef struct NCDProgram_s NCDProgram;
+typedef struct NCDProgramElem_s NCDProgramElem;
+typedef struct NCDProcess_s NCDProcess;
+typedef struct NCDBlock_s NCDBlock;
+typedef struct NCDStatement_s NCDStatement;
+typedef struct NCDIfBlock_s NCDIfBlock;
+typedef struct NCDIf_s NCDIf;
+
+struct NCDValue_s {
+    int type;
+    union {
+        struct {
+            uint8_t *string;
+            size_t string_len;
+        };
+        struct {
+            LinkedList1 list;
+            size_t list_count;
+        };
+        struct {
+            LinkedList1 map_list;
+            size_t map_count;
+        };
+        struct {
+            char *var_name;
+        };
+    };
+};
+
+struct NCDProgram_s {
+    LinkedList1 elems_list;
+    size_t num_elems;
+};
+
+struct NCDBlock_s {
+    LinkedList1 statements_list;
+    size_t count;
+};
+
+struct NCDProcess_s {
+    int is_template;
+    char *name;
+    NCDBlock block;
+};
+
+struct NCDProgramElem_s {
+    int type;
+    union {
+        NCDProcess process;
+        struct {
+            char *path_data;
+            size_t path_length;
+        } include;
+        struct {
+            char *id_data;
+            size_t id_length;
+        } include_guard;
+    };
+};
+
+struct NCDIfBlock_s {
+    LinkedList1 ifs_list;
+};
+
+struct NCDStatement_s {
+    int type;
+    char *name;
+    union {
+        struct {
+            char *objname;
+            char *cmdname;
+            NCDValue args;
+        } reg;
+        struct {
+            NCDIfBlock ifblock;
+            int have_else;
+            NCDBlock else_block;
+        } ifc;
+        struct {
+            NCDValue collection;
+            char *name1;
+            char *name2;
+            NCDBlock block;
+            int is_grabbed;
+        } foreach;
+    };
+};
+
+struct NCDIf_s {
+    NCDValue cond;
+    NCDBlock block;
+};
+
+//
+
+#define NCDVALUE_STRING 1
+#define NCDVALUE_LIST 2
+#define NCDVALUE_MAP 3
+#define NCDVALUE_VAR 4
+
+#define NCDPROGRAMELEM_PROCESS 1
+#define NCDPROGRAMELEM_INCLUDE 2
+#define NCDPROGRAMELEM_INCLUDE_GUARD 3
+
+#define NCDSTATEMENT_REG 1
+#define NCDSTATEMENT_IF 2
+#define NCDSTATEMENT_FOREACH 3
+
+void NCDValue_Free (NCDValue *o);
+int NCDValue_Type (NCDValue *o);
+int NCDValue_InitString (NCDValue *o, const char *str) WARN_UNUSED;
+int NCDValue_InitStringBin (NCDValue *o, const uint8_t *str, size_t len) WARN_UNUSED;
+const char * NCDValue_StringValue (NCDValue *o);
+size_t NCDValue_StringLength (NCDValue *o);
+void NCDValue_InitList (NCDValue *o);
+size_t NCDValue_ListCount (NCDValue *o);
+int NCDValue_ListAppend (NCDValue *o, NCDValue v) WARN_UNUSED;
+int NCDValue_ListPrepend (NCDValue *o, NCDValue v) WARN_UNUSED;
+NCDValue * NCDValue_ListFirst (NCDValue *o);
+NCDValue * NCDValue_ListNext (NCDValue *o, NCDValue *ev);
+void NCDValue_InitMap (NCDValue *o);
+size_t NCDValue_MapCount (NCDValue *o);
+int NCDValue_MapPrepend (NCDValue *o, NCDValue key, NCDValue val) WARN_UNUSED;
+NCDValue * NCDValue_MapFirstKey (NCDValue *o);
+NCDValue * NCDValue_MapNextKey (NCDValue *o, NCDValue *ekey);
+NCDValue * NCDValue_MapKeyValue (NCDValue *o, NCDValue *ekey);
+int NCDValue_InitVar (NCDValue *o, const char *var_name) WARN_UNUSED;
+const char * NCDValue_VarName (NCDValue *o);
+
+void NCDProgram_Init (NCDProgram *o);
+void NCDProgram_Free (NCDProgram *o);
+NCDProgramElem * NCDProgram_PrependElem (NCDProgram *o, NCDProgramElem elem) WARN_UNUSED;
+NCDProgramElem * NCDProgram_FirstElem (NCDProgram *o);
+NCDProgramElem * NCDProgram_NextElem (NCDProgram *o, NCDProgramElem *ee);
+size_t NCDProgram_NumElems (NCDProgram *o);
+int NCDProgram_ContainsElemType (NCDProgram *o, int elem_type);
+void NCDProgram_RemoveElem (NCDProgram *o, NCDProgramElem *ee);
+int NCDProgram_ReplaceElemWithProgram (NCDProgram *o, NCDProgramElem *ee, NCDProgram replace_prog) WARN_UNUSED;
+
+void NCDProgramElem_InitProcess (NCDProgramElem *o, NCDProcess process);
+int NCDProgramElem_InitInclude (NCDProgramElem *o, const char *path_data, size_t path_length) WARN_UNUSED;
+int NCDProgramElem_InitIncludeGuard (NCDProgramElem *o, const char *id_data, size_t id_length) WARN_UNUSED;
+void NCDProgramElem_Free (NCDProgramElem *o);
+int NCDProgramElem_Type (NCDProgramElem *o);
+NCDProcess * NCDProgramElem_Process (NCDProgramElem *o);
+const char * NCDProgramElem_IncludePathData (NCDProgramElem *o);
+size_t NCDProgramElem_IncludePathLength (NCDProgramElem *o);
+const char * NCDProgramElem_IncludeGuardIdData (NCDProgramElem *o);
+size_t NCDProgramElem_IncludeGuardIdLength (NCDProgramElem *o);
+
+int NCDProcess_Init (NCDProcess *o, int is_template, const char *name, NCDBlock block) WARN_UNUSED;
+void NCDProcess_Free (NCDProcess *o);
+int NCDProcess_IsTemplate (NCDProcess *o);
+const char * NCDProcess_Name (NCDProcess *o);
+NCDBlock * NCDProcess_Block (NCDProcess *o);
+
+void NCDBlock_Init (NCDBlock *o);
+void NCDBlock_Free (NCDBlock *o);
+int NCDBlock_PrependStatement (NCDBlock *o, NCDStatement s) WARN_UNUSED;
+int NCDBlock_InsertStatementAfter (NCDBlock *o, NCDStatement *after, NCDStatement s) WARN_UNUSED;
+NCDStatement * NCDBlock_ReplaceStatement (NCDBlock *o, NCDStatement *es, NCDStatement s);
+NCDStatement * NCDBlock_FirstStatement (NCDBlock *o);
+NCDStatement * NCDBlock_NextStatement (NCDBlock *o, NCDStatement *es);
+size_t NCDBlock_NumStatements (NCDBlock *o);
+
+int NCDStatement_InitReg (NCDStatement *o, const char *name, const char *objname, const char *cmdname, NCDValue args) WARN_UNUSED;
+int NCDStatement_InitIf (NCDStatement *o, const char *name, NCDIfBlock ifblock) WARN_UNUSED;
+int NCDStatement_InitForeach (NCDStatement *o, const char *name, NCDValue collection, const char *name1, const char *name2, NCDBlock block) WARN_UNUSED;
+void NCDStatement_Free (NCDStatement *o);
+int NCDStatement_Type (NCDStatement *o);
+const char * NCDStatement_Name (NCDStatement *o);
+const char * NCDStatement_RegObjName (NCDStatement *o);
+const char * NCDStatement_RegCmdName (NCDStatement *o);
+NCDValue * NCDStatement_RegArgs (NCDStatement *o);
+NCDIfBlock * NCDStatement_IfBlock (NCDStatement *o);
+void NCDStatement_IfAddElse (NCDStatement *o, NCDBlock else_block);
+NCDBlock * NCDStatement_IfElse (NCDStatement *o);
+NCDBlock NCDStatement_IfGrabElse (NCDStatement *o);
+NCDValue * NCDStatement_ForeachCollection (NCDStatement *o);
+const char * NCDStatement_ForeachName1 (NCDStatement *o);
+const char * NCDStatement_ForeachName2 (NCDStatement *o);
+NCDBlock * NCDStatement_ForeachBlock (NCDStatement *o);
+void NCDStatement_ForeachGrab (NCDStatement *o, NCDValue *out_collection, NCDBlock *out_block);
+
+void NCDIfBlock_Init (NCDIfBlock *o);
+void NCDIfBlock_Free (NCDIfBlock *o);
+int NCDIfBlock_PrependIf (NCDIfBlock *o, NCDIf ifc) WARN_UNUSED;
+NCDIf * NCDIfBlock_FirstIf (NCDIfBlock *o);
+NCDIf * NCDIfBlock_NextIf (NCDIfBlock *o, NCDIf *ei);
+NCDIf NCDIfBlock_GrabIf (NCDIfBlock *o, NCDIf *ei);
+
+void NCDIf_Init (NCDIf *o, NCDValue cond, NCDBlock block);
+void NCDIf_Free (NCDIf *o);
+void NCDIf_FreeGrab (NCDIf *o, NCDValue *out_cond, NCDBlock *out_block);
+NCDValue * NCDIf_Cond (NCDIf *o);
+NCDBlock * NCDIf_Block (NCDIf *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDBuildProgram.c b/external/badvpn_dns/ncd/NCDBuildProgram.c
new file mode 100644
index 0000000..941a142
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDBuildProgram.c
@@ -0,0 +1,316 @@
+/**
+ * @file NCDBuildProgram.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stdlib.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/read_file.h>
+#include <misc/strdup.h>
+#include <misc/concat_strings.h>
+#include <base/BLog.h>
+#include <ncd/NCDConfigParser.h>
+
+#include "NCDBuildProgram.h"
+
+#include <generated/blog_channel_NCDBuildProgram.h>
+
+#define MAX_INCLUDE_DEPTH 32
+
+struct guard {
+    char *id_data;
+    size_t id_length;
+    struct guard *next;
+};
+
+struct build_state {
+    struct guard *top_guard;
+};
+
+static int add_guard (struct guard **first, const char *id_data, size_t id_length)
+{
+    struct guard *g = malloc(sizeof(*g));
+    if (!g) {
+        goto fail0;
+    }
+    
+    if (!(g->id_data = b_strdup_bin(id_data, id_length))) {
+        goto fail1;
+    }
+    
+    g->id_length = id_length;
+    
+    g->next = *first;
+    *first = g;
+    
+    return 1;
+    
+fail1:
+    free(g);
+fail0:
+    return 0;
+}
+
+static void prepend_guards (struct guard **first, struct guard *guards)
+{
+    if (!guards) {
+        return;
+    }
+    
+    struct guard *last = guards;
+    while (last->next) {
+        last = last->next;
+    }
+    
+    last->next = *first;
+    *first = guards;
+}
+
+static void free_guards (struct guard *g)
+{
+    while (g) {
+        struct guard *next_g = g->next;
+        free(g->id_data);
+        free(g);
+        g = next_g;
+    }
+}
+
+static int guard_exists (struct guard *top_guard, const char *id_data, size_t id_length)
+{
+    for (struct guard *g = top_guard; g; g = g->next) {
+        if (g->id_length == id_length && !memcmp(g->id_data, id_data, id_length)) {
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+static char * make_dir_path (const char *file_path)
+{
+    int found_slash = 0;
+    size_t last_slash = 0; // initialize to remove warning
+    
+    for (size_t i = 0; file_path[i]; i++) {
+        if (file_path[i] == '/') {
+            found_slash = 1;
+            last_slash = i;
+        }
+    }
+    
+    char *dir_path;
+    
+    if (!found_slash) {
+        if (!file_path[0]) {
+            BLog(BLOG_ERROR, "file '%s': file path must not be empty", file_path);
+            return NULL;
+        }
+        dir_path = b_strdup("");
+    } else {
+        if (!file_path[last_slash + 1]) {
+            BLog(BLOG_ERROR, "file '%s': file path must not end in a slash", file_path);
+            return NULL;
+        }
+        dir_path = b_strdup_bin(file_path, last_slash + 1);
+    }
+    
+    if (!dir_path) {
+        BLog(BLOG_ERROR, "file '%s': b_strdup/b_strdup_bin failed", file_path);
+        return NULL;
+    }
+    
+    return dir_path;
+}
+
+static char * make_include_path (const char *file_path, const char *dir_path, const char *target, size_t target_len)
+{
+    ASSERT(target_len == strlen(target))
+    
+    if (target_len == 0) {
+        BLog(BLOG_ERROR, "file '%s': include target must not be empty", file_path);
+        return NULL;
+    }
+    
+    if (target[target_len - 1] == '/') {
+        BLog(BLOG_ERROR, "file '%s': include target must not end in a slash", file_path);
+        return NULL;
+    }
+    
+    char *real_target;
+    
+    if (target[0] == '/') {
+        real_target = b_strdup(target);
+    } else {
+        real_target = concat_strings(2, dir_path, target);
+    }
+    
+    if (!real_target) {
+        BLog(BLOG_ERROR, "file '%s': b_strdup/concat_strings failed", file_path);
+        return NULL;
+    }
+    
+    return real_target;
+}
+
+static int process_file (struct build_state *st, int depth, const char *file_path, NCDProgram *out_program, int *out_guarded)
+{
+    int ret_val = 0;
+    
+    if (depth > MAX_INCLUDE_DEPTH) {
+        BLog(BLOG_ERROR, "file '%s': maximum include depth (%d) exceeded (include cycle?)", file_path, (int)MAX_INCLUDE_DEPTH);
+        goto fail0;
+    }
+    
+    char *dir_path = make_dir_path(file_path);
+    if (!dir_path) {
+        goto fail0;
+    }
+    
+    uint8_t *data;
+    size_t len;
+    if (!read_file(file_path, &data, &len)) {
+        BLog(BLOG_ERROR, "file '%s': failed to read contents", file_path);
+        goto fail1;
+    }
+    
+    NCDProgram program;
+    int res = NCDConfigParser_Parse((char *)data, len, &program);
+    free(data);
+    if (!res) {
+        BLog(BLOG_ERROR, "file '%s': failed to parse", file_path);
+        goto fail1;
+    }
+    
+    struct guard *our_guards = NULL;
+    
+    NCDProgramElem *elem = NCDProgram_FirstElem(&program);
+    while (elem) {
+        NCDProgramElem *next_elem = NCDProgram_NextElem(&program, elem);
+        if (NCDProgramElem_Type(elem) != NCDPROGRAMELEM_INCLUDE_GUARD) {
+            elem = next_elem;
+            continue;
+        }
+        
+        const char *id_data = NCDProgramElem_IncludeGuardIdData(elem);
+        size_t id_length = NCDProgramElem_IncludeGuardIdLength(elem);
+        
+        if (guard_exists(st->top_guard, id_data, id_length)) {
+            *out_guarded = 1;
+            ret_val = 1;
+            goto fail2;
+        }
+        
+        if (!add_guard(&our_guards, id_data, id_length)) {
+            BLog(BLOG_ERROR, "file '%s': add_guard failed", file_path);
+            goto fail2;
+        }
+        
+        NCDProgram_RemoveElem(&program, elem);
+        elem = next_elem;
+    }
+    
+    prepend_guards(&st->top_guard, our_guards);
+    our_guards = NULL;
+    
+    elem = NCDProgram_FirstElem(&program);
+    while (elem) {
+        NCDProgramElem *next_elem = NCDProgram_NextElem(&program, elem);
+        if (NCDProgramElem_Type(elem) != NCDPROGRAMELEM_INCLUDE) {
+            elem = next_elem;
+            continue;
+        }
+        
+        const char *target = NCDProgramElem_IncludePathData(elem);
+        size_t target_len = NCDProgramElem_IncludePathLength(elem);
+        
+        if (strlen(target) != target_len) {
+            BLog(BLOG_ERROR, "file '%s': include path must not contain null characters", file_path);
+            goto fail2;
+        }
+        
+        char *real_target = make_include_path(file_path, dir_path, target, target_len);
+        if (!real_target) {
+            goto fail2;
+        }
+        
+        NCDProgram included_program;
+        int included_guarded;
+        int res = process_file(st, depth + 1, real_target, &included_program, &included_guarded);
+        free(real_target);
+        if (!res) {
+            goto fail2;
+        }
+        
+        if (included_guarded) {
+            NCDProgram_RemoveElem(&program, elem);
+        } else {
+            if (!NCDProgram_ReplaceElemWithProgram(&program, elem, included_program)) {
+                BLog(BLOG_ERROR, "file '%s': NCDProgram_ReplaceElemWithProgram failed", file_path);
+                NCDProgram_Free(&included_program);
+                goto fail2;
+            }
+        }
+        
+        elem = next_elem;
+    }
+    
+    free(dir_path);
+    
+    *out_program = program;
+    *out_guarded = 0;
+    return 1;
+    
+fail2:
+    free_guards(our_guards);
+    NCDProgram_Free(&program);
+fail1:
+    free(dir_path);
+fail0:
+    return ret_val;
+}
+
+int NCDBuildProgram_Build (const char *file_path, NCDProgram *out_program)
+{
+    ASSERT(file_path)
+    ASSERT(out_program)
+    
+    struct build_state st;
+    st.top_guard = NULL;
+    
+    int guarded;
+    int res = process_file(&st, 0, file_path, out_program, &guarded);
+    
+    ASSERT(!res || !guarded)
+    
+    free_guards(st.top_guard);
+    
+    return res;
+}
diff --git a/external/badvpn_dns/ncd/NCDBuildProgram.h b/external/badvpn_dns/ncd/NCDBuildProgram.h
new file mode 100644
index 0000000..dff6685
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDBuildProgram.h
@@ -0,0 +1,49 @@
+/**
+ * @file NCDBuildProgram.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 NCD_BUILD_PROGRAM_H
+#define NCD_BUILD_PROGRAM_H
+
+#include <misc/debug.h>
+#include <ncd/NCDAst.h>
+
+/**
+ * Builds an NCD program in AST form suitable for passing to {@link NCDInterpreter},
+ * by opening and parsing it, as well as recursively processing any included files.
+ * The resulting program will not contain any 'include' or 'include_guard' elements;
+ * these will be resolved and removed.
+ * 
+ * @param file_path path to the main file of the program
+ * @param out_program on success, *out_program will contain the resulting program.
+ *                    On failure, *out_program will be unchanged.
+ * @return 1 on success, 0 on failure
+ */
+int NCDBuildProgram_Build (const char *file_path, NCDProgram *out_program) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDConfigParser.c b/external/badvpn_dns/ncd/NCDConfigParser.c
new file mode 100644
index 0000000..f883f72
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDConfigParser.c
@@ -0,0 +1,214 @@
+/**
+ * @file NCDConfigParser.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <misc/debug.h>
+#include <base/BLog.h>
+#include <ncd/NCDConfigTokenizer.h>
+
+#include "ncd/NCDConfigParser.h"
+
+#include <generated/blog_channel_NCDConfigParser.h>
+
+#include "../generated/NCDConfigParser_parse.c"
+#include "../generated/NCDConfigParser_parse.h"
+
+struct parser_state {
+    struct parser_out out;
+    int error;
+    void *parser;
+};
+
+static int tokenizer_output (void *user, int token, char *value, size_t value_len, size_t line, size_t line_char)
+{
+    struct parser_state *state = user;
+    ASSERT(!state->out.out_of_memory)
+    ASSERT(!state->out.syntax_error)
+    ASSERT(!state->error)
+    
+    if (token == NCD_ERROR) {
+        BLog(BLOG_ERROR, "line %zu, character %zu: tokenizer error", line, line_char);
+        state->error = 1;
+        return 0;
+    }
+    
+    struct token minor;
+    minor.str = value;
+    minor.len = value_len;
+    
+    switch (token) {
+        case NCD_EOF: {
+            Parse(state->parser, 0, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_CURLY_OPEN: {
+            Parse(state->parser, CURLY_OPEN, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_CURLY_CLOSE: {
+            Parse(state->parser, CURLY_CLOSE, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_ROUND_OPEN: {
+            Parse(state->parser, ROUND_OPEN, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_ROUND_CLOSE: {
+            Parse(state->parser, ROUND_CLOSE, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_SEMICOLON: {
+            Parse(state->parser, SEMICOLON, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_DOT: {
+            Parse(state->parser, DOT, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_COMMA: {
+            Parse(state->parser, COMMA, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_ARROW: {
+            Parse(state->parser, ARROW, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_PROCESS: {
+            Parse(state->parser, PROCESS, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_TEMPLATE: {
+            Parse(state->parser, TEMPLATE, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_NAME: {
+            Parse(state->parser, NAME, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_STRING: {
+            Parse(state->parser, STRING, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_COLON: {
+            Parse(state->parser, COLON, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_BRACKET_OPEN: {
+            Parse(state->parser, BRACKET_OPEN, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_BRACKET_CLOSE: {
+            Parse(state->parser, BRACKET_CLOSE, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_IF: {
+            Parse(state->parser, IF, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_ELIF: {
+            Parse(state->parser, ELIF, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_ELSE: {
+            Parse(state->parser, ELSE, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_FOREACH: {
+            Parse(state->parser, FOREACH, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_AS: {
+            Parse(state->parser, AS, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_INCLUDE: {
+            Parse(state->parser, INCLUDE, minor, &state->out);
+        } break;
+        
+        case NCD_TOKEN_INCLUDE_GUARD: {
+            Parse(state->parser, INCLUDE_GUARD, minor, &state->out);
+        } break;
+        
+        default:
+            BLog(BLOG_ERROR, "line %zu, character %zu: invalid token", line, line_char);
+            free(minor.str);
+            state->error = 1;
+            return 0;
+    }
+    
+    // if we got syntax error, stop parsing
+    if (state->out.syntax_error) {
+        BLog(BLOG_ERROR, "line %zu, character %zu: syntax error", line, line_char);
+        state->error = 1;
+        return 0;
+    }
+    
+    if (state->out.out_of_memory) {
+        BLog(BLOG_ERROR, "line %zu, character %zu: out of memory", line, line_char);
+        state->error = 1;
+        return 0;
+    }
+    
+    return 1;
+}
+
+int NCDConfigParser_Parse (char *config, size_t config_len, NCDProgram *out_ast)
+{
+    struct parser_state state;
+    
+    state.out.out_of_memory = 0;
+    state.out.syntax_error = 0;
+    state.out.have_ast = 0;
+    state.error = 0;
+    
+    if (!(state.parser = ParseAlloc(malloc))) {
+        BLog(BLOG_ERROR, "ParseAlloc failed");
+        return 0;
+    }
+    
+    // tokenize and parse
+    NCDConfigTokenizer_Tokenize(config, config_len, tokenizer_output, &state);
+    
+    ParseFree(state.parser, free);
+    
+    if (state.error) {
+        if (state.out.have_ast) {
+            NCDProgram_Free(&state.out.ast);
+        }
+        return 0;
+    }
+    
+    ASSERT(state.out.have_ast)
+    
+    *out_ast = state.out.ast;
+    return 1;
+}
diff --git a/external/badvpn_dns/ncd/NCDConfigParser.h b/external/badvpn_dns/ncd/NCDConfigParser.h
new file mode 100644
index 0000000..90cee54
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDConfigParser.h
@@ -0,0 +1,40 @@
+/**
+ * @file NCDConfigParser.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDCONFIG_NCDCONFIGPARSER_H
+#define BADVPN_NCDCONFIG_NCDCONFIGPARSER_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDAst.h>
+
+int NCDConfigParser_Parse (char *config, size_t config_len, NCDProgram *out_ast) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDConfigParser_parse.y b/external/badvpn_dns/ncd/NCDConfigParser_parse.y
new file mode 100644
index 0000000..fdf89f6
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDConfigParser_parse.y
@@ -0,0 +1,718 @@
+/**
+ * @file NCDConfigParser.y
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 {
+
+#include <string.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <misc/concat_strings.h>
+#include <ncd/NCDAst.h>
+
+struct parser_out {
+    int out_of_memory;
+    int syntax_error;
+    int have_ast;
+    NCDProgram ast;
+};
+
+struct token {
+    char *str;
+    size_t len;
+};
+
+struct program {
+    int have;
+    NCDProgram v;
+};
+
+struct block {
+    int have;
+    NCDBlock v;
+};
+
+struct statement {
+    int have;
+    NCDStatement v;
+};
+
+struct ifblock {
+    int have;
+    NCDIfBlock v;
+};
+
+struct value {
+    int have;
+    NCDValue v;
+};
+
+static void free_token (struct token o) { free(o.str); }
+static void free_program (struct program o) { if (o.have) NCDProgram_Free(&o.v); }
+static void free_block (struct block o) { if (o.have) NCDBlock_Free(&o.v); }
+static void free_statement (struct statement o) { if (o.have) NCDStatement_Free(&o.v); }
+static void free_ifblock (struct ifblock o) { if (o.have) NCDIfBlock_Free(&o.v); }
+static void free_value (struct value o) { if (o.have) NCDValue_Free(&o.v); }
+
+}
+
+%extra_argument { struct parser_out *parser_out }
+
+%token_type { struct token }
+
+%token_destructor { free_token($$); }
+
+%type processes { struct program }
+%type statement { struct statement }
+%type elif_maybe { struct ifblock }
+%type elif { struct ifblock }
+%type else_maybe { struct block }
+%type statements { struct block }
+%type dotted_name { char * }
+%type statement_args_maybe { struct value }
+%type list_contents { struct value }
+%type list { struct value }
+%type map_contents { struct value }
+%type map  { struct value }
+%type value  { struct value }
+%type name_maybe { char * }
+%type process_or_template { int }
+
+// mention parser_out in some destructor to a void unused variable warning
+%destructor processes { (void)parser_out; free_program($$); }
+%destructor statement { free_statement($$); }
+%destructor elif_maybe { free_ifblock($$); }
+%destructor elif { free_ifblock($$); }
+%destructor else_maybe { free_block($$); }
+%destructor statements { free_block($$); }
+%destructor dotted_name { free($$); }
+%destructor statement_args_maybe { free_value($$); }
+%destructor list_contents { free_value($$); }
+%destructor list { free_value($$); }
+%destructor map_contents { free_value($$); }
+%destructor map { free_value($$); }
+%destructor value { free_value($$); }
+%destructor name_maybe { free($$); }
+
+%stack_size 0
+
+%syntax_error {
+    parser_out->syntax_error = 1;
+}
+
+// workaroud Lemon bug: if the stack overflows, the token that caused the overflow will be leaked
+%stack_overflow {
+    if (yypMinor) {
+        free_token(yypMinor->yy0);
+    }
+}
+
+input ::= processes(A). {
+    ASSERT(!parser_out->have_ast)
+
+    if (A.have) {
+        parser_out->have_ast = 1;
+        parser_out->ast = A.v;
+    }
+}
+
+processes(R) ::= . {
+    NCDProgram prog;
+    NCDProgram_Init(&prog);
+    
+    R.have = 1;
+    R.v = prog;
+}
+
+processes(R) ::= INCLUDE STRING(A) processes(N). {
+    ASSERT(A.str)
+    if (!N.have) {
+        goto failA0;
+    }
+    
+    NCDProgramElem elem;
+    if (!NCDProgramElem_InitInclude(&elem, A.str, A.len)) {
+        goto failA0;
+    }
+    
+    if (!NCDProgram_PrependElem(&N.v, elem)) {
+        goto failA1;
+    }
+    
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneA;
+
+failA1:
+    NCDProgramElem_Free(&elem);
+failA0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneA:
+    free_token(A);
+    free_program(N);
+}
+
+processes(R) ::= INCLUDE_GUARD STRING(A) processes(N). {
+    ASSERT(A.str)
+    if (!N.have) {
+        goto failZ0;
+    }
+    
+    NCDProgramElem elem;
+    if (!NCDProgramElem_InitIncludeGuard(&elem, A.str, A.len)) {
+        goto failZ0;
+    }
+    
+    if (!NCDProgram_PrependElem(&N.v, elem)) {
+        goto failZ1;
+    }
+    
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneZ;
+
+failZ1:
+    NCDProgramElem_Free(&elem);
+failZ0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneZ:
+    free_token(A);
+    free_program(N);
+}
+
+processes(R) ::= process_or_template(T) NAME(A) CURLY_OPEN statements(B) CURLY_CLOSE processes(N). {
+    ASSERT(A.str)
+    if (!B.have || !N.have) {
+        goto failB0;
+    }
+
+    NCDProcess proc;
+    if (!NCDProcess_Init(&proc, T, A.str, B.v)) {
+        goto failB0;
+    }
+    B.have = 0;
+    
+    NCDProgramElem elem;
+    NCDProgramElem_InitProcess(&elem, proc);
+
+    if (!NCDProgram_PrependElem(&N.v, elem)) {
+        goto failB1;
+    }
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneB;
+
+failB1:
+    NCDProgramElem_Free(&elem);
+failB0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneB:
+    free_token(A);
+    free_block(B);
+    free_program(N);
+}
+
+statement(R) ::= dotted_name(A) ROUND_OPEN statement_args_maybe(B) ROUND_CLOSE name_maybe(C) SEMICOLON. {
+    if (!A || !B.have) {
+        goto failC0;
+    }
+
+    if (!NCDStatement_InitReg(&R.v, C, NULL, A, B.v)) {
+        goto failC0;
+    }
+    B.have = 0;
+
+    R.have = 1;
+    goto doneC;
+
+failC0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneC:
+    free(A);
+    free_value(B);
+    free(C);
+}
+
+statement(R) ::= dotted_name(M) ARROW dotted_name(A) ROUND_OPEN statement_args_maybe(B) ROUND_CLOSE name_maybe(C) SEMICOLON. {
+    if (!M || !A || !B.have) {
+        goto failD0;
+    }
+
+    if (!NCDStatement_InitReg(&R.v, C, M, A, B.v)) {
+        goto failD0;
+    }
+    B.have = 0;
+
+    R.have = 1;
+    goto doneD;
+
+failD0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneD:
+    free(M);
+    free(A);
+    free_value(B);
+    free(C);
+}
+
+statement(R) ::= IF ROUND_OPEN value(A) ROUND_CLOSE CURLY_OPEN statements(B) CURLY_CLOSE elif_maybe(I) else_maybe(E) name_maybe(C) SEMICOLON. {
+    if (!A.have || !B.have || !I.have) {
+        goto failE0;
+    }
+
+    NCDIf ifc;
+    NCDIf_Init(&ifc, A.v, B.v);
+    A.have = 0;
+    B.have = 0;
+
+    if (!NCDIfBlock_PrependIf(&I.v, ifc)) {
+        NCDIf_Free(&ifc);
+        goto failE0;
+    }
+
+    if (!NCDStatement_InitIf(&R.v, C, I.v)) {
+        goto failE0;
+    }
+    I.have = 0;
+
+    if (E.have) {
+        NCDStatement_IfAddElse(&R.v, E.v);
+        E.have = 0;
+    }
+
+    R.have = 1;
+    goto doneE;
+
+failE0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneE:
+    free_value(A);
+    free_block(B);
+    free_ifblock(I);
+    free_block(E);
+    free(C);
+}
+
+statement(R) ::= FOREACH ROUND_OPEN value(A) AS NAME(B) ROUND_CLOSE CURLY_OPEN statements(S) CURLY_CLOSE name_maybe(N) SEMICOLON. {
+    if (!A.have || !B.str || !S.have) {
+        goto failEA0;
+    }
+    
+    if (!NCDStatement_InitForeach(&R.v, N, A.v, B.str, NULL, S.v)) {
+        goto failEA0;
+    }
+    A.have = 0;
+    S.have = 0;
+    
+    R.have = 1;
+    goto doneEA0;
+    
+failEA0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneEA0:
+    free_value(A);
+    free_token(B);
+    free_block(S);
+    free(N);
+}
+
+statement(R) ::= FOREACH ROUND_OPEN value(A) AS NAME(B) COLON NAME(C) ROUND_CLOSE CURLY_OPEN statements(S) CURLY_CLOSE name_maybe(N) SEMICOLON. {
+    if (!A.have || !B.str || !C.str || !S.have) {
+        goto failEB0;
+    }
+    
+    if (!NCDStatement_InitForeach(&R.v, N, A.v, B.str, C.str, S.v)) {
+        goto failEB0;
+    }
+    A.have = 0;
+    S.have = 0;
+    
+    R.have = 1;
+    goto doneEB0;
+    
+failEB0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneEB0:
+    free_value(A);
+    free_token(B);
+    free_token(C);
+    free_block(S);
+    free(N);
+}
+
+elif_maybe(R) ::= . {
+    NCDIfBlock_Init(&R.v);
+    R.have = 1;
+}
+
+elif_maybe(R) ::= elif(A). {
+    R = A;
+}
+
+elif(R) ::= ELIF ROUND_OPEN value(A) ROUND_CLOSE CURLY_OPEN statements(B) CURLY_CLOSE. {
+    if (!A.have || !B.have) {
+        goto failF0;
+    }
+
+    NCDIfBlock_Init(&R.v);
+
+    NCDIf ifc;
+    NCDIf_Init(&ifc, A.v, B.v);
+    A.have = 0;
+    B.have = 0;
+
+    if (!NCDIfBlock_PrependIf(&R.v, ifc)) {
+        goto failF1;
+    }
+
+    R.have = 1;
+    goto doneF0;
+
+failF1:
+    NCDIf_Free(&ifc);
+    NCDIfBlock_Free(&R.v);
+failF0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneF0:
+    free_value(A);
+    free_block(B);
+}
+
+elif(R) ::= ELIF ROUND_OPEN value(A) ROUND_CLOSE CURLY_OPEN statements(B) CURLY_CLOSE elif(N). {
+    if (!A.have || !B.have || !N.have) {
+        goto failG0;
+    }
+
+    NCDIf ifc;
+    NCDIf_Init(&ifc, A.v, B.v);
+    A.have = 0;
+    B.have = 0;
+
+    if (!NCDIfBlock_PrependIf(&N.v, ifc)) {
+        goto failG1;
+    }
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneG0;
+
+failG1:
+    NCDIf_Free(&ifc);
+failG0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneG0:
+    free_value(A);
+    free_block(B);
+    free_ifblock(N);
+}
+
+else_maybe(R) ::= . {
+    R.have = 0;
+}
+
+else_maybe(R) ::= ELSE CURLY_OPEN statements(B) CURLY_CLOSE. {
+    R = B;
+}
+
+statements(R) ::= statement(A). {
+    if (!A.have) {
+        goto failH0;
+    }
+
+    NCDBlock_Init(&R.v);
+
+    if (!NCDBlock_PrependStatement(&R.v, A.v)) {
+        goto failH1;
+    }
+    A.have = 0;
+
+    R.have = 1;
+    goto doneH;
+
+failH1:
+    NCDBlock_Free(&R.v);
+failH0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneH:
+    free_statement(A);
+}
+
+statements(R) ::= statement(A) statements(N). {
+    if (!A.have || !N.have) {
+        goto failI0;
+    }
+
+    if (!NCDBlock_PrependStatement(&N.v, A.v)) {
+        goto failI1;
+    }
+    A.have = 0;
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneI;
+
+failI1:
+    NCDBlock_Free(&R.v);
+failI0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneI:
+    free_statement(A);
+    free_block(N);
+}
+
+dotted_name(R) ::= NAME(A). {
+    ASSERT(A.str)
+
+    R = A.str;
+}
+
+dotted_name(R) ::= NAME(A) DOT dotted_name(N). {
+    ASSERT(A.str)
+    if (!N) {
+        goto failJ0;
+    }
+
+    if (!(R = concat_strings(3, A.str, ".", N))) {
+        goto failJ0;
+    }
+
+    goto doneJ;
+
+failJ0:
+    R = NULL;
+    parser_out->out_of_memory = 1;
+doneJ:
+    free_token(A);
+    free(N);
+}
+
+statement_args_maybe(R) ::= . {
+    R.have = 1;
+    NCDValue_InitList(&R.v);
+}
+
+statement_args_maybe(R) ::= list_contents(A). {
+    R = A;
+}
+
+list_contents(R) ::= value(A). {
+    if (!A.have) {
+        goto failL0;
+    }
+
+    NCDValue_InitList(&R.v);
+
+    if (!NCDValue_ListPrepend(&R.v, A.v)) {
+        goto failL1;
+    }
+    A.have = 0;
+
+    R.have = 1;
+    goto doneL;
+
+failL1:
+    NCDValue_Free(&R.v);
+failL0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneL:
+    free_value(A);
+}
+
+list_contents(R) ::= value(A) COMMA list_contents(N). {
+    if (!A.have || !N.have) {
+        goto failM0;
+    }
+
+    if (!NCDValue_ListPrepend(&N.v, A.v)) {
+        goto failM0;
+    }
+    A.have = 0;
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneM;
+
+failM0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneM:
+    free_value(A);
+    free_value(N);
+}
+
+list(R) ::= CURLY_OPEN CURLY_CLOSE. {
+    R.have = 1;
+    NCDValue_InitList(&R.v);
+}
+
+list(R) ::= CURLY_OPEN list_contents(A) CURLY_CLOSE. {
+    R = A;
+}
+
+map_contents(R) ::= value(A) COLON value(B). {
+    if (!A.have || !B.have) {
+        goto failS0;
+    }
+
+    NCDValue_InitMap(&R.v);
+
+    if (!NCDValue_MapPrepend(&R.v, A.v, B.v)) {
+        goto failS1;
+    }
+    A.have = 0;
+    B.have = 0;
+
+    R.have = 1;
+    goto doneS;
+
+failS1:
+    NCDValue_Free(&R.v);
+failS0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneS:
+    free_value(A);
+    free_value(B);
+}
+
+map_contents(R) ::= value(A) COLON value(B) COMMA map_contents(N). {
+    if (!A.have || !B.have || !N.have) {
+        goto failT0;
+    }
+
+    if (!NCDValue_MapPrepend(&N.v, A.v, B.v)) {
+        goto failT0;
+    }
+    A.have = 0;
+    B.have = 0;
+
+    R.have = 1;
+    R.v = N.v;
+    N.have = 0;
+    goto doneT;
+
+failT0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneT:
+    free_value(A);
+    free_value(B);
+    free_value(N);
+}
+
+map(R) ::= BRACKET_OPEN BRACKET_CLOSE. {
+    R.have = 1;
+    NCDValue_InitMap(&R.v);
+}
+
+map(R) ::= BRACKET_OPEN map_contents(A) BRACKET_CLOSE. {
+    R = A;
+}
+
+value(R) ::= STRING(A). {
+    ASSERT(A.str)
+
+    if (!NCDValue_InitStringBin(&R.v, (uint8_t *)A.str, A.len)) {
+        goto failU0;
+    }
+
+    R.have = 1;
+    goto doneU;
+
+failU0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneU:
+    free_token(A);
+}
+
+value(R) ::= dotted_name(A). {
+    if (!A) {
+        goto failV0;
+    }
+
+    if (!NCDValue_InitVar(&R.v, A)) {
+        goto failV0;
+    }
+
+    R.have = 1;
+    goto doneV;
+
+failV0:
+    R.have = 0;
+    parser_out->out_of_memory = 1;
+doneV:
+    free(A);
+}
+
+value(R) ::= list(A). {
+    R = A;
+}
+
+value(R) ::= map(A). {
+    R = A;
+}
+
+name_maybe(R) ::= . {
+    R = NULL;
+}
+
+name_maybe(R) ::= NAME(A). {
+    ASSERT(A.str)
+
+    R = A.str;
+}
+
+process_or_template(R) ::= PROCESS. {
+    R = 0;
+}
+
+process_or_template(R) ::= TEMPLATE. {
+    R = 1;
+}
diff --git a/external/badvpn_dns/ncd/NCDConfigTokenizer.c b/external/badvpn_dns/ncd/NCDConfigTokenizer.c
new file mode 100644
index 0000000..5ba31b4
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDConfigTokenizer.c
@@ -0,0 +1,321 @@
+/**
+ * @file NCDConfigTokenizer.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stdlib.h>
+
+#include <misc/debug.h>
+#include <misc/string_begins_with.h>
+#include <misc/balloc.h>
+#include <misc/expstring.h>
+#include <misc/parse_number.h>
+#include <base/BLog.h>
+
+#include <ncd/NCDConfigTokenizer.h>
+
+#include <generated/blog_channel_NCDConfigTokenizer.h>
+
+static int is_name_char (char c)
+{
+    return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_');
+}
+
+static int is_name_first_char (char c)
+{
+    return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_');
+}
+
+static int is_space_char (char c)
+{
+    return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
+}
+
+static int string_equals (char *str, int str_len, char *needle)
+{
+    return (str_len == strlen(needle) && !memcmp(str, needle, str_len));
+}
+
+void NCDConfigTokenizer_Tokenize (char *str, size_t left, NCDConfigTokenizer_output output, void *user)
+{
+    size_t line = 1;
+    size_t line_char = 1;
+    
+    while (left > 0) {
+        size_t l;
+        int error = 0;
+        int token;
+        void *token_val = NULL;
+        size_t token_len = 0;
+        
+        if (*str == '#') {
+            l = 1;
+            while (l < left && str[l] != '\n') {
+                l++;
+            }
+            token = 0;
+        }
+        else if (l = data_begins_with(str, left, "{")) {
+            token = NCD_TOKEN_CURLY_OPEN;
+        }
+        else if (l = data_begins_with(str, left, "}")) {
+            token = NCD_TOKEN_CURLY_CLOSE;
+        }
+        else if (l = data_begins_with(str, left, "(")) {
+            token = NCD_TOKEN_ROUND_OPEN;
+        }
+        else if (l = data_begins_with(str, left, ")")) {
+            token = NCD_TOKEN_ROUND_CLOSE;
+        }
+        else if (l = data_begins_with(str, left, ";")) {
+            token = NCD_TOKEN_SEMICOLON;
+        }
+        else if (l = data_begins_with(str, left, ".")) {
+            token = NCD_TOKEN_DOT;
+        }
+        else if (l = data_begins_with(str, left, ",")) {
+            token = NCD_TOKEN_COMMA;
+        }
+        else if (l = data_begins_with(str, left, ":")) {
+            token = NCD_TOKEN_COLON;
+        }
+        else if (l = data_begins_with(str, left, "[")) {
+            token = NCD_TOKEN_BRACKET_OPEN;
+        }
+        else if (l = data_begins_with(str, left, "]")) {
+            token = NCD_TOKEN_BRACKET_CLOSE;
+        }
+        else if (l = data_begins_with(str, left, "->")) {
+            token = NCD_TOKEN_ARROW;
+        }
+        else if (l = data_begins_with(str, left, "If")) {
+            token = NCD_TOKEN_IF;
+        }
+        else if (l = data_begins_with(str, left, "Elif")) {
+            token = NCD_TOKEN_ELIF;
+        }
+        else if (l = data_begins_with(str, left, "elif")) {
+            token = NCD_TOKEN_ELIF;
+        }
+        else if (l = data_begins_with(str, left, "Else")) {
+            token = NCD_TOKEN_ELSE;
+        }
+        else if (l = data_begins_with(str, left, "else")) {
+            token = NCD_TOKEN_ELSE;
+        }
+        else if (l = data_begins_with(str, left, "Foreach")) {
+            token = NCD_TOKEN_FOREACH;
+        }
+        else if (l = data_begins_with(str, left, "As")) {
+            token = NCD_TOKEN_AS;
+        }
+        else if (l = data_begins_with(str, left, "include_guard")) {
+            token = NCD_TOKEN_INCLUDE_GUARD;
+        }
+        else if (l = data_begins_with(str, left, "include")) {
+            token = NCD_TOKEN_INCLUDE;
+        }
+        else if (is_name_first_char(*str)) {
+            l = 1;
+            while (l < left && is_name_char(str[l])) {
+                l++;
+            }
+            
+            // allocate buffer
+            bsize_t bufsize = bsize_add(bsize_fromsize(l), bsize_fromint(1));
+            char *buf;
+            if (bufsize.is_overflow || !(buf = malloc(bufsize.value))) {
+                BLog(BLOG_ERROR, "malloc failed");
+                error = 1;
+                goto out;
+            }
+            
+            // copy and terminate
+            memcpy(buf, str, l);
+            buf[l] = '\0';
+            
+            if (!strcmp(buf, "process")) {
+                token = NCD_TOKEN_PROCESS;
+                free(buf);
+            }
+            else if (!strcmp(buf, "template")) {
+                token = NCD_TOKEN_TEMPLATE;
+                free(buf);
+            }
+            else {
+                token = NCD_TOKEN_NAME;
+                token_val = buf;
+                token_len = l;
+            }
+        }
+        else if (*str == '"') do {
+            // init string
+            ExpString estr;
+            if (!ExpString_Init(&estr)) {
+                BLog(BLOG_ERROR, "ExpString_Init failed");
+                goto string_fail0;
+            }
+            
+            // skip start quote
+            l = 1;
+            
+            // decode string
+            while (l < left) {
+                uint8_t dec_ch;
+                
+                // get character
+                if (str[l] == '\\') {
+                    if (left - l < 2) {
+                        BLog(BLOG_ERROR, "escape character found in string but nothing follows");
+                        goto string_fail1;
+                    }
+                    
+                    size_t extra = 0;
+                    
+                    switch (str[l + 1]) {
+                        case '\'':
+                        case '\"':
+                        case '\\':
+                        case '\?':
+                            dec_ch = str[l + 1]; break;
+                        
+                        case 'a':
+                            dec_ch = '\a'; break;
+                        case 'b':
+                            dec_ch = '\b'; break;
+                        case 'f':
+                            dec_ch = '\f'; break;
+                        case 'n':
+                            dec_ch = '\n'; break;
+                        case 'r':
+                            dec_ch = '\r'; break;
+                        case 't':
+                            dec_ch = '\t'; break;
+                        case 'v':
+                            dec_ch = '\v'; break;
+                        
+                        case '0':
+                            dec_ch = 0; break;
+                        
+                        case 'x': {
+                            if (left - l < 4) {
+                                BLog(BLOG_ERROR, "hexadecimal escape found in string but too little characters follow");
+                                goto string_fail1;
+                            }
+                            
+                            uintmax_t hex_val;
+                            if (!parse_unsigned_hex_integer_bin(&str[l + 2], 2, &hex_val)) {
+                                BLog(BLOG_ERROR, "hexadecimal escape found in string but two hex characters don't follow");
+                                goto string_fail1;
+                            }
+                            
+                            dec_ch = hex_val;
+                            extra = 2;
+                        } break;
+                        
+                        default:
+                            BLog(BLOG_ERROR, "bad escape sequence in string");
+                            goto string_fail1;
+                    }
+                    
+                    l += 2 + extra;
+                }
+                else if (str[l] == '"') {
+                    break;
+                }
+                else {
+                    dec_ch = str[l];
+                    l++;
+                }
+                
+                // append character to string
+                if (!ExpString_AppendByte(&estr, dec_ch)) {
+                    BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                    goto string_fail1;
+                }
+            }
+            
+            // make sure ending quote was found
+            if (l == left) {
+                BLog(BLOG_ERROR, "missing ending quote for string");
+                goto string_fail1;
+            }
+            
+            // skip ending quote
+            l++;
+            
+            token = NCD_TOKEN_STRING;
+            token_val = ExpString_Get(&estr);
+            token_len = ExpString_Length(&estr);
+            break;
+            
+        string_fail1:
+            ExpString_Free(&estr);
+        string_fail0:
+            error = 1;
+        } while (0);
+        else if (is_space_char(*str)) {
+            token = 0;
+            l = 1;
+        }
+        else {
+            BLog(BLOG_ERROR, "unrecognized character");
+            error = 1;
+        }
+        
+    out:
+        // report error
+        if (error) {
+            output(user, NCD_ERROR, NULL, 0, line, line_char);
+            return;
+        }
+        
+        // output token
+        if (token) {
+            if (!output(user, token, token_val, token_len, line, line_char)) {
+                return;
+            }
+        }
+        
+        // update line/char counters
+        for (size_t i = 0; i < l; i++) {
+            if (str[i] == '\n') {
+                line++;
+                line_char = 1;
+            } else {
+                line_char++;
+            }
+        }
+        
+        str += l;
+        left -= l;
+    }
+    
+    output(user, NCD_EOF, NULL, 0, line, line_char);
+}
diff --git a/external/badvpn_dns/ncd/NCDConfigTokenizer.h b/external/badvpn_dns/ncd/NCDConfigTokenizer.h
new file mode 100644
index 0000000..38edfad
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDConfigTokenizer.h
@@ -0,0 +1,64 @@
+/**
+ * @file NCDConfigTokenizer.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDCONFIG_NCDCONFIGTOKENIZER_H
+#define BADVPN_NCDCONFIG_NCDCONFIGTOKENIZER_H
+
+#include <stdlib.h>
+
+#define NCD_ERROR -1
+#define NCD_EOF 0
+#define NCD_TOKEN_CURLY_OPEN 1
+#define NCD_TOKEN_CURLY_CLOSE 2
+#define NCD_TOKEN_ROUND_OPEN 3
+#define NCD_TOKEN_ROUND_CLOSE 4
+#define NCD_TOKEN_SEMICOLON 5
+#define NCD_TOKEN_DOT 6
+#define NCD_TOKEN_COMMA 7
+#define NCD_TOKEN_PROCESS 8
+#define NCD_TOKEN_NAME 9
+#define NCD_TOKEN_STRING 10
+#define NCD_TOKEN_ARROW 11
+#define NCD_TOKEN_TEMPLATE 12
+#define NCD_TOKEN_COLON 13
+#define NCD_TOKEN_BRACKET_OPEN 14
+#define NCD_TOKEN_BRACKET_CLOSE 15
+#define NCD_TOKEN_IF 16
+#define NCD_TOKEN_ELIF 17
+#define NCD_TOKEN_ELSE 18
+#define NCD_TOKEN_FOREACH 19
+#define NCD_TOKEN_AS 20
+#define NCD_TOKEN_INCLUDE 21
+#define NCD_TOKEN_INCLUDE_GUARD 22
+
+typedef int (*NCDConfigTokenizer_output) (void *user, int token, char *value, size_t value_len, size_t line, size_t line_char);
+
+void NCDConfigTokenizer_Tokenize (char *str, size_t str_len, NCDConfigTokenizer_output output, void *user);
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDInterpProcess.c b/external/badvpn_dns/ncd/NCDInterpProcess.c
new file mode 100644
index 0000000..4462ea6
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDInterpProcess.c
@@ -0,0 +1,497 @@
+/**
+ * @file NCDInterpProcess.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <limits.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <misc/balloc.h>
+#include <misc/maxalign.h>
+#include <misc/strdup.h>
+#include <base/BLog.h>
+#include <ncd/make_name_indices.h>
+
+#include "NCDInterpProcess.h"
+
+#include <generated/blog_channel_ncd.h>
+
+static int compute_prealloc (NCDInterpProcess *o)
+{
+    int size = 0;
+    
+    for (int i = 0; i < o->num_stmts; i++) {
+        int mod = size % BMAX_ALIGN;
+        int align_size = (mod == 0 ? 0 : BMAX_ALIGN - mod);
+        
+        if (align_size + o->stmts[i].alloc_size > INT_MAX - size) {
+            return 0;
+        }
+        
+        o->stmts[i].prealloc_offset = size + align_size;
+        size += align_size + o->stmts[i].alloc_size;
+    }
+    
+    ASSERT(size >= 0)
+    
+    o->prealloc_size = size;
+    
+    return 1;
+}
+
+static int convert_value_recurser (NCDPlaceholderDb *pdb, NCDStringIndex *string_index, NCDValue *value, NCDValMem *mem, NCDValRef *out)
+{
+    ASSERT(pdb)
+    ASSERT(string_index)
+    ASSERT((NCDValue_Type(value), 1))
+    ASSERT(mem)
+    ASSERT(out)
+    
+    switch (NCDValue_Type(value)) {
+        case NCDVALUE_STRING: {
+            const char *str = NCDValue_StringValue(value);
+            size_t len = NCDValue_StringLength(value);
+            
+            NCD_string_id_t string_id = NCDStringIndex_GetBin(string_index, str, len);
+            if (string_id < 0) {
+                BLog(BLOG_ERROR, "NCDStringIndex_GetBin failed");
+                goto fail;
+            }
+            
+            *out = NCDVal_NewIdString(mem, string_id, string_index);
+            if (NCDVal_IsInvalid(*out)) {
+                goto fail;
+            }
+        } break;
+        
+        case NCDVALUE_LIST: {
+            *out = NCDVal_NewList(mem, NCDValue_ListCount(value));
+            if (NCDVal_IsInvalid(*out)) {
+                goto fail;
+            }
+            
+            for (NCDValue *e = NCDValue_ListFirst(value); e; e = NCDValue_ListNext(value, e)) {
+                NCDValRef vval;
+                if (!convert_value_recurser(pdb, string_index, e, mem, &vval)) {
+                    goto fail;
+                }
+                
+                if (!NCDVal_ListAppend(*out, vval)) {
+                    BLog(BLOG_ERROR, "depth limit exceeded");
+                    goto fail;
+                }
+            }
+        } break;
+        
+        case NCDVALUE_MAP: {
+            *out = NCDVal_NewMap(mem, NCDValue_MapCount(value));
+            if (NCDVal_IsInvalid(*out)) {
+                goto fail;
+            }
+            
+            for (NCDValue *ekey = NCDValue_MapFirstKey(value); ekey; ekey = NCDValue_MapNextKey(value, ekey)) {
+                NCDValue *eval = NCDValue_MapKeyValue(value, ekey);
+                
+                NCDValRef vkey;
+                NCDValRef vval;
+                if (!convert_value_recurser(pdb, string_index, ekey, mem, &vkey) ||
+                    !convert_value_recurser(pdb, string_index, eval, mem, &vval)
+                ) {
+                    goto fail;
+                }
+                
+                int inserted;
+                if (!NCDVal_MapInsert(*out, vkey, vval, &inserted)) {
+                    BLog(BLOG_ERROR, "depth limit exceeded");
+                    goto fail;
+                }
+                if (!inserted) {
+                    BLog(BLOG_ERROR, "duplicate key in map");
+                    goto fail;
+                }
+            }
+        } break;
+        
+        case NCDVALUE_VAR: {
+            int plid;
+            if (!NCDPlaceholderDb_AddVariable(pdb, NCDValue_VarName(value), &plid)) {
+                goto fail;
+            }
+            
+            if (NCDVAL_MINIDX + plid >= -1) {
+                goto fail;
+            }
+            
+            *out = NCDVal_NewPlaceholder(mem, plid);
+        } break;
+        
+        default:
+            goto fail;
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+int NCDInterpProcess_Init (NCDInterpProcess *o, NCDProcess *process, NCDStringIndex *string_index, NCDPlaceholderDb *pdb, NCDModuleIndex *module_index)
+{
+    ASSERT(process)
+    ASSERT(string_index)
+    ASSERT(pdb)
+    ASSERT(module_index)
+    
+    NCDBlock *block = NCDProcess_Block(process);
+    
+    if (NCDBlock_NumStatements(block) > INT_MAX) {
+        BLog(BLOG_ERROR, "too many statements");
+        goto fail0;
+    }
+    int num_stmts = NCDBlock_NumStatements(block);
+    
+    if (!(o->stmts = BAllocArray(num_stmts, sizeof(o->stmts[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail0;
+    }
+    
+    o->num_hash_buckets = num_stmts;
+    
+    if (!(o->hash_buckets = BAllocArray(o->num_hash_buckets, sizeof(o->hash_buckets[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail1;
+    }
+    
+    for (size_t i = 0; i < o->num_hash_buckets; i++) {
+        o->hash_buckets[i] = -1;
+    }
+    
+    if (!(o->name = b_strdup(NCDProcess_Name(process)))) {
+        BLog(BLOG_ERROR, "b_strdup failed");
+        goto fail2;
+    }
+    
+    o->num_stmts = 0;
+    o->prealloc_size = -1;
+    o->is_template = NCDProcess_IsTemplate(process);
+    o->cache = NULL;
+    
+    for (NCDStatement *s = NCDBlock_FirstStatement(block); s; s = NCDBlock_NextStatement(block, s)) {
+        ASSERT(NCDStatement_Type(s) == NCDSTATEMENT_REG)
+        struct NCDInterpProcess__stmt *e = &o->stmts[o->num_stmts];
+        
+        e->name = -1;
+        e->objnames = NULL;
+        e->num_objnames = 0;
+        e->alloc_size = 0;
+        
+        if (NCDStatement_Name(s)) {
+            e->name = NCDStringIndex_Get(string_index, NCDStatement_Name(s));
+            if (e->name < 0) {
+                BLog(BLOG_ERROR, "NCDStringIndex_Get failed");
+                goto loop_fail0;
+            }
+        }
+        
+        e->cmdname = NCDStringIndex_Get(string_index, NCDStatement_RegCmdName(s));
+        if (e->cmdname < 0) {
+            BLog(BLOG_ERROR, "NCDStringIndex_Get failed");
+            goto loop_fail0;
+        }
+        
+        NCDValMem_Init(&e->arg_mem);
+        
+        NCDValRef val;
+        if (!convert_value_recurser(pdb, string_index, NCDStatement_RegArgs(s), &e->arg_mem, &val)) {
+            BLog(BLOG_ERROR, "convert_value_recurser failed");
+            goto loop_fail1;
+        }
+        
+        e->arg_ref = NCDVal_ToSafe(val);
+        
+        if (!NCDValReplaceProg_Init(&e->arg_prog, val)) {
+            BLog(BLOG_ERROR, "NCDValReplaceProg_Init failed");
+            goto loop_fail1;
+        }
+        
+        if (NCDStatement_RegObjName(s)) {
+            if (!ncd_make_name_indices(string_index, NCDStatement_RegObjName(s), &e->objnames, &e->num_objnames)) {
+                BLog(BLOG_ERROR, "ncd_make_name_indices failed");
+                goto loop_fail2;
+            }
+            
+            e->binding.method_name_id = NCDModuleIndex_GetMethodNameId(module_index, NCDStatement_RegCmdName(s));
+            if (e->binding.method_name_id == -1) {
+                BLog(BLOG_ERROR, "NCDModuleIndex_GetMethodNameId failed");
+                goto loop_fail3;
+            }
+        } else {
+            e->binding.simple_module = NCDModuleIndex_FindModule(module_index, NCDStatement_RegCmdName(s));
+        }
+        
+        if (e->name >= 0) {
+            size_t bucket_idx = e->name % o->num_hash_buckets;
+            e->hash_next = o->hash_buckets[bucket_idx];
+            o->hash_buckets[bucket_idx] = o->num_stmts;
+        }
+        
+        o->num_stmts++;
+        continue;
+        
+    loop_fail3:
+        BFree(e->objnames);
+    loop_fail2:
+        NCDValReplaceProg_Free(&e->arg_prog);
+    loop_fail1:
+        NCDValMem_Free(&e->arg_mem);
+    loop_fail0:
+        goto fail3;
+    }
+    
+    ASSERT(o->num_stmts == num_stmts)
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail3:
+    while (o->num_stmts-- > 0) {
+        struct NCDInterpProcess__stmt *e = &o->stmts[o->num_stmts];
+        BFree(e->objnames);
+        NCDValReplaceProg_Free(&e->arg_prog);
+        NCDValMem_Free(&e->arg_mem);
+    }
+    free(o->name);
+fail2:
+    BFree(o->hash_buckets);
+fail1:
+    BFree(o->stmts);
+fail0:
+    return 0;
+}
+
+void NCDInterpProcess_Free (NCDInterpProcess *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    while (o->num_stmts-- > 0) {
+        struct NCDInterpProcess__stmt *e = &o->stmts[o->num_stmts];
+        BFree(e->objnames);
+        NCDValReplaceProg_Free(&e->arg_prog);
+        NCDValMem_Free(&e->arg_mem);
+    }
+    
+    free(o->name);
+    BFree(o->hash_buckets);
+    BFree(o->stmts);
+}
+
+int NCDInterpProcess_FindStatement (NCDInterpProcess *o, int from_index, NCD_string_id_t name)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(from_index >= 0)
+    ASSERT(from_index <= o->num_stmts)
+    
+    size_t bucket_idx = name % o->num_hash_buckets;
+    int stmt_idx = o->hash_buckets[bucket_idx];
+    ASSERT(stmt_idx >= -1)
+    ASSERT(stmt_idx < o->num_stmts)
+    
+    while (stmt_idx >= 0) {
+        if (stmt_idx < from_index && o->stmts[stmt_idx].name == name) {
+            return stmt_idx;
+        }
+        
+        stmt_idx = o->stmts[stmt_idx].hash_next;
+        ASSERT(stmt_idx >= -1)
+        ASSERT(stmt_idx < o->num_stmts)
+    }
+    
+    return -1;
+}
+
+const char * NCDInterpProcess_StatementCmdName (NCDInterpProcess *o, int i, NCDStringIndex *string_index)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(i >= 0)
+    ASSERT(i < o->num_stmts)
+    ASSERT(string_index)
+    
+    return NCDStringIndex_Value(string_index, o->stmts[i].cmdname);
+}
+
+void NCDInterpProcess_StatementObjNames (NCDInterpProcess *o, int i, const NCD_string_id_t **out_objnames, size_t *out_num_objnames)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(i >= 0)
+    ASSERT(i < o->num_stmts)
+    ASSERT(out_objnames)
+    ASSERT(out_num_objnames)
+    
+    *out_objnames = o->stmts[i].objnames;
+    *out_num_objnames = o->stmts[i].num_objnames;
+}
+
+const struct NCDInterpModule * NCDInterpProcess_StatementGetSimpleModule (NCDInterpProcess *o, int i, NCDStringIndex *string_index, NCDModuleIndex *module_index)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(i >= 0)
+    ASSERT(i < o->num_stmts)
+    ASSERT(!o->stmts[i].objnames)
+    
+    struct NCDInterpProcess__stmt *e = &o->stmts[i];
+    
+    if (!e->binding.simple_module) {
+        const char *cmdname = NCDStringIndex_Value(string_index, e->cmdname);
+        e->binding.simple_module = NCDModuleIndex_FindModule(module_index, cmdname);
+    }
+    
+    return e->binding.simple_module;
+}
+
+const struct NCDInterpModule * NCDInterpProcess_StatementGetMethodModule (NCDInterpProcess *o, int i, NCD_string_id_t obj_type, NCDModuleIndex *module_index)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(i >= 0)
+    ASSERT(i < o->num_stmts)
+    ASSERT(o->stmts[i].objnames)
+    ASSERT(obj_type >= 0)
+    ASSERT(module_index)
+    
+    return NCDModuleIndex_GetMethodModule(module_index, obj_type, o->stmts[i].binding.method_name_id);
+}
+
+int NCDInterpProcess_CopyStatementArgs (NCDInterpProcess *o, int i, NCDValMem *out_valmem, NCDValRef *out_val, NCDValReplaceProg *out_prog)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(i >= 0)
+    ASSERT(i < o->num_stmts)
+    ASSERT(out_valmem)
+    ASSERT(out_val)
+    ASSERT(out_prog)
+    
+    struct NCDInterpProcess__stmt *e = &o->stmts[i];
+    
+    if (!NCDValMem_InitCopy(out_valmem, &e->arg_mem)) {
+        return 0;
+    }
+    
+    *out_val = NCDVal_FromSafe(out_valmem, e->arg_ref);
+    *out_prog = e->arg_prog;
+    return 1;
+}
+
+void NCDInterpProcess_StatementBumpAllocSize (NCDInterpProcess *o, int i, int alloc_size)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(i >= 0)
+    ASSERT(i < o->num_stmts)
+    ASSERT(alloc_size >= 0)
+    
+    if (alloc_size > o->stmts[i].alloc_size) {
+        o->stmts[i].alloc_size = alloc_size;
+        o->prealloc_size = -1;
+    }
+}
+
+int NCDInterpProcess_PreallocSize (NCDInterpProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->prealloc_size == -1 || o->prealloc_size >= 0)
+    
+    if (o->prealloc_size < 0 && !compute_prealloc(o)) {
+        return -1;
+    }
+    
+    return o->prealloc_size;
+}
+
+int NCDInterpProcess_StatementPreallocSize (NCDInterpProcess *o, int i)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(i >= 0)
+    ASSERT(i < o->num_stmts)
+    ASSERT(o->prealloc_size >= 0)
+    
+    return o->stmts[i].alloc_size;
+}
+
+int NCDInterpProcess_StatementPreallocOffset (NCDInterpProcess *o, int i)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(i >= 0)
+    ASSERT(i < o->num_stmts)
+    ASSERT(o->prealloc_size >= 0)
+    
+    return o->stmts[i].prealloc_offset;
+}
+
+const char * NCDInterpProcess_Name (NCDInterpProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return o->name;
+}
+
+int NCDInterpProcess_IsTemplate (NCDInterpProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return o->is_template;
+}
+
+int NCDInterpProcess_NumStatements (NCDInterpProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return o->num_stmts;
+}
+
+int NCDInterpProcess_CachePush (NCDInterpProcess *o, void *elem)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(elem)
+    
+    if (o->cache) {
+        return 0;
+    }
+    
+    o->cache = elem;
+    
+    return 1;
+}
+
+void * NCDInterpProcess_CachePull (NCDInterpProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    void *elem = o->cache;
+    o->cache = NULL;
+    
+    return elem;
+}
diff --git a/external/badvpn_dns/ncd/NCDInterpProcess.h b/external/badvpn_dns/ncd/NCDInterpProcess.h
new file mode 100644
index 0000000..49fd2c6
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDInterpProcess.h
@@ -0,0 +1,100 @@
+/**
+ * @file NCDInterpProcess.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDINTERPPROCESS_H
+#define BADVPN_NCDINTERPPROCESS_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <ncd/NCDAst.h>
+#include <ncd/NCDVal.h>
+#include <ncd/NCDPlaceholderDb.h>
+#include <ncd/NCDModule.h>
+#include <ncd/NCDModuleIndex.h>
+#include <ncd/NCDStringIndex.h>
+
+struct NCDInterpProcess__stmt {
+    NCD_string_id_t name;
+    NCD_string_id_t cmdname;
+    NCD_string_id_t *objnames;
+    size_t num_objnames;
+    union {
+        const struct NCDInterpModule *simple_module;
+        int method_name_id;
+    } binding;
+    NCDValMem arg_mem;
+    NCDValSafeRef arg_ref;
+    NCDValReplaceProg arg_prog;
+    int alloc_size;
+    int prealloc_offset;
+    int hash_next;
+};
+
+/**
+ * A data structure which contains information about a process or
+ * template, suitable for efficient interpretation. These structures
+ * are built at startup from the program AST for all processes and
+ * templates by \link NCDInterpProg. They are not modified after
+ * the program is loaded Inn case of template processes, the same
+ * NCDInterpProcess is shared by all processes created from the same
+ * template.
+ */
+typedef struct {
+    struct NCDInterpProcess__stmt *stmts;
+    char *name;
+    int num_stmts;
+    int prealloc_size;
+    int is_template;
+    int *hash_buckets;
+    size_t num_hash_buckets;
+    void *cache;
+    DebugObject d_obj;
+} NCDInterpProcess;
+
+int NCDInterpProcess_Init (NCDInterpProcess *o, NCDProcess *process, NCDStringIndex *string_index, NCDPlaceholderDb *pdb, NCDModuleIndex *module_index) WARN_UNUSED;
+void NCDInterpProcess_Free (NCDInterpProcess *o);
+int NCDInterpProcess_FindStatement (NCDInterpProcess *o, int from_index, NCD_string_id_t name);
+const char * NCDInterpProcess_StatementCmdName (NCDInterpProcess *o, int i, NCDStringIndex *string_index);
+void NCDInterpProcess_StatementObjNames (NCDInterpProcess *o, int i, const NCD_string_id_t **out_objnames, size_t *out_num_objnames);
+const struct NCDInterpModule * NCDInterpProcess_StatementGetSimpleModule (NCDInterpProcess *o, int i, NCDStringIndex *string_index, NCDModuleIndex *module_index);
+const struct NCDInterpModule * NCDInterpProcess_StatementGetMethodModule (NCDInterpProcess *o, int i, NCD_string_id_t obj_type, NCDModuleIndex *module_index);
+int NCDInterpProcess_CopyStatementArgs (NCDInterpProcess *o, int i, NCDValMem *out_valmem, NCDValRef *out_val, NCDValReplaceProg *out_prog) WARN_UNUSED;
+void NCDInterpProcess_StatementBumpAllocSize (NCDInterpProcess *o, int i, int alloc_size);
+int NCDInterpProcess_PreallocSize (NCDInterpProcess *o);
+int NCDInterpProcess_StatementPreallocSize (NCDInterpProcess *o, int i);
+int NCDInterpProcess_StatementPreallocOffset (NCDInterpProcess *o, int i);
+const char * NCDInterpProcess_Name (NCDInterpProcess *o);
+int NCDInterpProcess_IsTemplate (NCDInterpProcess *o);
+int NCDInterpProcess_NumStatements (NCDInterpProcess *o);
+int NCDInterpProcess_CachePush (NCDInterpProcess *o, void *elem) WARN_UNUSED;
+void * NCDInterpProcess_CachePull (NCDInterpProcess *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDInterpProg.c b/external/badvpn_dns/ncd/NCDInterpProg.c
new file mode 100644
index 0000000..7ee75de
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDInterpProg.c
@@ -0,0 +1,140 @@
+/**
+ * @file NCDInterpProg.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <limits.h>
+
+#include <misc/balloc.h>
+#include <misc/hashfun.h>
+#include <base/BLog.h>
+
+#include "NCDInterpProg.h"
+
+#include <generated/blog_channel_ncd.h>
+
+#include "NCDInterpProg_hash.h"
+#include <structure/CHash_impl.h>
+
+int NCDInterpProg_Init (NCDInterpProg *o, NCDProgram *prog, NCDStringIndex *string_index, NCDPlaceholderDb *pdb, NCDModuleIndex *module_index)
+{
+    ASSERT(prog)
+    ASSERT(!NCDProgram_ContainsElemType(prog, NCDPROGRAMELEM_INCLUDE))
+    ASSERT(!NCDProgram_ContainsElemType(prog, NCDPROGRAMELEM_INCLUDE_GUARD))
+    ASSERT(string_index)
+    ASSERT(pdb)
+    ASSERT(module_index)
+    
+    if (NCDProgram_NumElems(prog) > INT_MAX) {
+        BLog(BLOG_ERROR, "too many processes");
+        goto fail0;
+    }
+    int num_procs = NCDProgram_NumElems(prog);
+    
+    if (!(o->procs = BAllocArray(num_procs, sizeof(o->procs[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail0;
+    }
+    
+    if (!NCDInterpProg__Hash_Init(&o->hash, num_procs)) {
+        BLog(BLOG_ERROR, "NCDInterpProg__Hash_Init failed");
+        goto fail1;
+    }
+    
+    o->num_procs = 0;
+    
+    for (NCDProgramElem *elem = NCDProgram_FirstElem(prog); elem; elem = NCDProgram_NextElem(prog, elem)) {
+        ASSERT(NCDProgramElem_Type(elem) == NCDPROGRAMELEM_PROCESS)
+        NCDProcess *p = NCDProgramElem_Process(elem);
+        
+        struct NCDInterpProg__process *e = &o->procs[o->num_procs];
+        
+        e->name = NCDStringIndex_Get(string_index, NCDProcess_Name(p));
+        if (e->name < 0) {
+            BLog(BLOG_ERROR, "NCDStringIndex_Get failed");
+            goto fail2;
+        }
+        
+        if (!NCDInterpProcess_Init(&e->iprocess, p, string_index, pdb, module_index)) {
+            BLog(BLOG_ERROR, "NCDInterpProcess_Init failed");
+            goto fail2;
+        }
+        
+        NCDInterpProg__HashRef ref = {e, o->num_procs};
+        if (!NCDInterpProg__Hash_Insert(&o->hash, o->procs, ref, NULL)) {
+            BLog(BLOG_ERROR, "duplicate process or template name: %s", NCDProcess_Name(p));
+            NCDInterpProcess_Free(&e->iprocess);
+            goto fail2;
+        }
+        
+        o->num_procs++;
+    }
+    
+    ASSERT(o->num_procs == num_procs)
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail2:
+    while (o->num_procs-- > 0) {
+        NCDInterpProcess_Free(&o->procs[o->num_procs].iprocess);
+    }
+    NCDInterpProg__Hash_Free(&o->hash);
+fail1:
+    BFree(o->procs);
+fail0:
+    return 0;
+}
+
+void NCDInterpProg_Free (NCDInterpProg *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    while (o->num_procs-- > 0) {
+        NCDInterpProcess_Free(&o->procs[o->num_procs].iprocess);
+    }
+    
+    NCDInterpProg__Hash_Free(&o->hash);
+    
+    BFree(o->procs);
+}
+
+NCDInterpProcess * NCDInterpProg_FindProcess (NCDInterpProg *o, NCD_string_id_t name)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(name >= 0)
+    
+    NCDInterpProg__HashRef ref = NCDInterpProg__Hash_Lookup(&o->hash, o->procs, name);
+    if (ref.link == NCDInterpProg__HashNullLink()) {
+        return NULL;
+    }
+    
+    ASSERT(ref.ptr->name == name)
+    
+    return &ref.ptr->iprocess;
+}
diff --git a/external/badvpn_dns/ncd/NCDInterpProg.h b/external/badvpn_dns/ncd/NCDInterpProg.h
new file mode 100644
index 0000000..2c8aaff
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDInterpProg.h
@@ -0,0 +1,63 @@
+/**
+ * @file NCDInterpProg.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDINTERPPROG_H
+#define BADVPN_NCDINTERPPROG_H
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <ncd/NCDAst.h>
+#include <ncd/NCDInterpProcess.h>
+#include <ncd/NCDStringIndex.h>
+#include <structure/CHash.h>
+
+struct NCDInterpProg__process {
+    NCD_string_id_t name;
+    NCDInterpProcess iprocess;
+    int hash_next;
+};
+
+typedef struct NCDInterpProg__process NCDInterpProg__hashentry;
+typedef struct NCDInterpProg__process *NCDInterpProg__hasharg;
+
+#include "NCDInterpProg_hash.h"
+#include <structure/CHash_decl.h>
+
+typedef struct {
+    struct NCDInterpProg__process *procs;
+    int num_procs;
+    NCDInterpProg__Hash hash;
+    DebugObject d_obj;
+} NCDInterpProg;
+
+int NCDInterpProg_Init (NCDInterpProg *o, NCDProgram *prog, NCDStringIndex *string_index, NCDPlaceholderDb *pdb, NCDModuleIndex *module_index) WARN_UNUSED;
+void NCDInterpProg_Free (NCDInterpProg *o);
+NCDInterpProcess * NCDInterpProg_FindProcess (NCDInterpProg *o, NCD_string_id_t name);
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDInterpProg_hash.h b/external/badvpn_dns/ncd/NCDInterpProg_hash.h
new file mode 100644
index 0000000..fa8898e
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDInterpProg_hash.h
@@ -0,0 +1,12 @@
+#define CHASH_PARAM_NAME NCDInterpProg__Hash
+#define CHASH_PARAM_ENTRY NCDInterpProg__hashentry
+#define CHASH_PARAM_LINK int
+#define CHASH_PARAM_KEY NCD_string_id_t
+#define CHASH_PARAM_ARG NCDInterpProg__hasharg
+#define CHASH_PARAM_NULL ((int)-1)
+#define CHASH_PARAM_DEREF(arg, link) (&(arg)[(link)])
+#define CHASH_PARAM_ENTRYHASH(arg, entry) ((size_t)(entry).ptr->name)
+#define CHASH_PARAM_KEYHASH(arg, key) ((size_t)(key))
+#define CHASH_PARAM_COMPARE_ENTRIES(arg, entry1, entry2) ((entry1).ptr->name == (entry2).ptr->name)
+#define CHASH_PARAM_COMPARE_KEY_ENTRY(arg, key1, entry2) ((key1) == (entry2).ptr->name)
+#define CHASH_PARAM_ENTRY_NEXT hash_next
diff --git a/external/badvpn_dns/ncd/NCDInterpreter.c b/external/badvpn_dns/ncd/NCDInterpreter.c
new file mode 100644
index 0000000..dc05cc5
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDInterpreter.c
@@ -0,0 +1,1356 @@
+/**
+ * @file NCDInterpreter.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdarg.h>
+
+#include <misc/offset.h>
+#include <misc/balloc.h>
+#include <misc/expstring.h>
+#include <base/BLog.h>
+#include <ncd/NCDSugar.h>
+#include <ncd/modules/modules.h>
+
+#include "NCDInterpreter.h"
+
+#include <generated/blog_channel_ncd.h>
+
+#define SSTATE_CHILD 0
+#define SSTATE_ADULT 1
+#define SSTATE_DYING 2
+#define SSTATE_FORGOTTEN 3
+
+#define PSTATE_WORKING 0
+#define PSTATE_UP 1
+#define PSTATE_WAITING 2
+#define PSTATE_TERMINATING 3
+
+struct statement {
+    NCDModuleInst inst;
+    NCDValMem args_mem;
+    int mem_size;
+    int i;
+};
+
+struct process {
+    NCDInterpreter *interp;
+    BReactor *reactor;
+    NCDInterpProcess *iprocess;
+    NCDModuleProcess *module_process;
+    BSmallTimer wait_timer;
+    BSmallPending work_job;
+    LinkedList1Node list_node; // node in processes
+    int ap;
+    int fp;
+    int num_statements;
+    unsigned int error:1;
+    unsigned int have_alloc:1;
+#ifndef NDEBUG
+    int state;
+#endif
+    struct statement statements[];
+};
+
+static void start_terminate (NCDInterpreter *interp, int exit_code);
+static char * implode_id_strings (NCDInterpreter *interp, const NCD_string_id_t *names, size_t num_names, char del);
+static void clear_process_cache (NCDInterpreter *interp);
+static struct process * process_allocate (NCDInterpreter *interp, NCDInterpProcess *iprocess);
+static void process_release (struct process *p, int no_push);
+static void process_assert_statements_cleared (struct process *p);
+static int process_new (NCDInterpreter *interp, NCDInterpProcess *iprocess, NCDModuleProcess *module_process);
+static void process_free (struct process *p, NCDModuleProcess **out_mp);
+static void process_set_state (struct process *p, int state);
+static void process_start_terminating (struct process *p);
+static int process_have_child (struct process *p);
+static void process_assert_pointers (struct process *p);
+static void process_logfunc (struct process *p);
+static void process_log (struct process *p, int level, const char *fmt, ...);
+static void process_work_job_handler_working (struct process *p);
+static void process_work_job_handler_up (struct process *p);
+static void process_work_job_handler_waiting (struct process *p);
+static void process_work_job_handler_terminating (struct process *p);
+static int replace_placeholders_callback (void *arg, int plid, NCDValMem *mem, NCDValRef *out);
+static void process_advance (struct process *p);
+static void process_wait_timer_handler (BSmallTimer *timer);
+static int process_find_object (struct process *p, int pos, NCD_string_id_t name, NCDObject *out_object);
+static int process_resolve_object_expr (struct process *p, int pos, const NCD_string_id_t *names, size_t num_names, NCDObject *out_object);
+static int process_resolve_variable_expr (struct process *p, int pos, const NCD_string_id_t *names, size_t num_names, NCDValMem *mem, NCDValRef *out_value);
+static void statement_logfunc (struct statement *ps);
+static void statement_log (struct statement *ps, int level, const char *fmt, ...);
+static struct process * statement_process (struct statement *ps);
+static int statement_mem_is_allocated (struct statement *ps);
+static int statement_mem_size (struct statement *ps);
+static int statement_allocate_memory (struct statement *ps, int alloc_size);
+static void statement_instance_func_event (NCDModuleInst *inst, int event);
+static int statement_instance_func_getobj (NCDModuleInst *inst, NCD_string_id_t objname, NCDObject *out_object);
+static int statement_instance_func_initprocess (void *vinterp, NCDModuleProcess *mp, NCD_string_id_t template_name);
+static void statement_instance_logfunc (NCDModuleInst *inst);
+static void statement_instance_func_interp_exit (void *vinterp, int exit_code);
+static int statement_instance_func_interp_getargs (void *vinterp, NCDValMem *mem, NCDValRef *out_value);
+static btime_t statement_instance_func_interp_getretrytime (void *vinterp);
+static int statement_instance_func_interp_loadgroup (void *vinterp, const struct NCDModuleGroup *group);
+static void process_moduleprocess_func_event (struct process *p, int event);
+static int process_moduleprocess_func_getobj (struct process *p, NCD_string_id_t name, NCDObject *out_object);
+
+#define STATEMENT_LOG(ps, channel, ...) if (BLog_WouldLog(BLOG_CURRENT_CHANNEL, channel)) statement_log(ps, channel, __VA_ARGS__)
+
+int NCDInterpreter_Init (NCDInterpreter *o, NCDProgram program, struct NCDInterpreter_params params)
+{
+    ASSERT(!NCDProgram_ContainsElemType(&program, NCDPROGRAMELEM_INCLUDE));
+    ASSERT(!NCDProgram_ContainsElemType(&program, NCDPROGRAMELEM_INCLUDE_GUARD));
+    ASSERT(params.handler_finished);
+    ASSERT(params.num_extra_args >= 0);
+    ASSERT(params.reactor);
+#ifndef BADVPN_NO_PROCESS
+    ASSERT(params.manager);
+#endif
+#ifndef BADVPN_NO_UDEV
+    ASSERT(params.umanager);
+#endif
+#ifndef BADVPN_NO_RANDOM
+    ASSERT(params.random2);
+#endif
+    
+    // set params
+    o->params = params;
+    
+    // set not terminating
+    o->terminating = 0;
+    
+    // set program
+    o->program = program;
+    
+    // init string index
+    if (!NCDStringIndex_Init(&o->string_index)) {
+        BLog(BLOG_ERROR, "NCDStringIndex_Init failed");
+        goto fail0;
+    }
+    
+    // init module index
+    if (!NCDModuleIndex_Init(&o->mindex, &o->string_index)) {
+        BLog(BLOG_ERROR, "NCDModuleIndex_Init failed");
+        goto fail2;
+    }
+    
+    // init pointers to global resources in out struct NCDModuleInst_iparams.
+    // Don't initialize any callback at this point as these must not be called
+    // from globalinit functions of modules.
+    o->module_iparams.reactor = params.reactor;
+#ifndef BADVPN_NO_PROCESS
+    o->module_iparams.manager = params.manager;
+#endif
+#ifndef BADVPN_NO_UDEV
+    o->module_iparams.umanager = params.umanager;
+#endif
+#ifndef BADVPN_NO_RANDOM
+    o->module_iparams.random2 = params.random2;
+#endif
+    o->module_iparams.string_index = &o->string_index;
+    
+    // add module groups to index and allocate string id's for base_type's
+    for (const struct NCDModuleGroup **g = ncd_modules; *g; g++) {
+        if (!NCDModuleIndex_AddGroup(&o->mindex, *g, &o->module_iparams, &o->string_index)) {
+            BLog(BLOG_ERROR, "NCDModuleIndex_AddGroup failed");
+            goto fail3;
+        }
+    }
+    
+    // desugar
+    if (!NCDSugar_Desugar(&o->program)) {
+        BLog(BLOG_ERROR, "NCDSugar_Desugar failed");
+        goto fail3;
+    }
+    
+    // init placeholder database
+    if (!NCDPlaceholderDb_Init(&o->placeholder_db, &o->string_index)) {
+        BLog(BLOG_ERROR, "NCDPlaceholderDb_Init failed");
+        goto fail3;
+    }
+    
+    // init interp program
+    if (!NCDInterpProg_Init(&o->iprogram, &o->program, &o->string_index, &o->placeholder_db, &o->mindex)) {
+        BLog(BLOG_ERROR, "NCDInterpProg_Init failed");
+        goto fail5;
+    }
+    
+    // init the rest of the module parameters structures
+    o->module_params.func_event = statement_instance_func_event;
+    o->module_params.func_getobj = statement_instance_func_getobj;
+    o->module_params.logfunc = (BLog_logfunc)statement_instance_logfunc;
+    o->module_params.iparams = &o->module_iparams;
+    o->module_iparams.user = o;
+    o->module_iparams.func_initprocess = statement_instance_func_initprocess;
+    o->module_iparams.func_interp_exit = statement_instance_func_interp_exit;
+    o->module_iparams.func_interp_getargs = statement_instance_func_interp_getargs;
+    o->module_iparams.func_interp_getretrytime = statement_instance_func_interp_getretrytime;
+    o->module_iparams.func_loadgroup = statement_instance_func_interp_loadgroup;
+    
+    // init processes list
+    LinkedList1_Init(&o->processes);
+    
+    // init processes
+    for (NCDProgramElem *elem = NCDProgram_FirstElem(&o->program); elem; elem = NCDProgram_NextElem(&o->program, elem)) {
+        ASSERT(NCDProgramElem_Type(elem) == NCDPROGRAMELEM_PROCESS)
+        NCDProcess *p = NCDProgramElem_Process(elem);
+        
+        if (NCDProcess_IsTemplate(p)) {
+            continue;
+        }
+        
+        // get string id for process name
+        NCD_string_id_t name_id = NCDStringIndex_Lookup(&o->string_index, NCDProcess_Name(p));
+        ASSERT(name_id >= 0)
+        
+        // find iprocess
+        NCDInterpProcess *iprocess = NCDInterpProg_FindProcess(&o->iprogram, name_id);
+        ASSERT(iprocess)
+        
+        if (!process_new(o, iprocess, NULL)) {
+            BLog(BLOG_ERROR, "failed to initialize process, exiting");
+            goto fail7;
+        }
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail7:;
+    // free processes
+    LinkedList1Node *ln;
+    while (ln = LinkedList1_GetFirst(&o->processes)) {
+        struct process *p = UPPER_OBJECT(ln, struct process, list_node);
+        BSmallPending_Unset(&p->work_job, BReactor_PendingGroup(o->params.reactor));
+        NCDModuleProcess *mp;
+        process_free(p, &mp);
+        ASSERT(!mp)
+    }
+    // clear process cache (process_free() above may push to cache)
+    clear_process_cache(o);
+    // free interp program
+    NCDInterpProg_Free(&o->iprogram);
+fail5:
+    // free placeholder database
+    NCDPlaceholderDb_Free(&o->placeholder_db);
+fail3:
+    // free module index
+    NCDModuleIndex_Free(&o->mindex);
+fail2:
+    // free string index
+    NCDStringIndex_Free(&o->string_index);
+fail0:
+    // free program AST
+    NCDProgram_Free(&o->program);
+    return 0;
+}
+
+void NCDInterpreter_Free (NCDInterpreter *o)
+{
+    DebugObject_Free(&o->d_obj);
+    // any process that exists must be completely uninitialized
+    
+    // free processes
+    LinkedList1Node *ln;
+    while (ln = LinkedList1_GetFirst(&o->processes)) {
+        struct process *p = UPPER_OBJECT(ln, struct process, list_node);
+        BSmallPending_Unset(&p->work_job, BReactor_PendingGroup(o->params.reactor));
+        NCDModuleProcess *mp;
+        process_free(p, &mp);
+        ASSERT(!mp)
+    }
+    
+    // clear process cache
+    clear_process_cache(o);
+    
+    // free interp program
+    NCDInterpProg_Free(&o->iprogram);
+    
+    // free placeholder database
+    NCDPlaceholderDb_Free(&o->placeholder_db);
+    
+    // free module index
+    NCDModuleIndex_Free(&o->mindex);
+    
+    // free string index
+    NCDStringIndex_Free(&o->string_index);
+    
+    // free program AST
+    NCDProgram_Free(&o->program);
+}
+
+void NCDInterpreter_RequestShutdown (NCDInterpreter *o, int exit_code)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    start_terminate(o, exit_code);
+}
+
+void start_terminate (NCDInterpreter *interp, int exit_code)
+{
+    // remember exit code
+    interp->main_exit_code = exit_code;
+    
+    // if we're already terminating, there's nothing to do
+    if (interp->terminating) {
+        return;
+    }
+    
+    // set the terminating flag
+    interp->terminating = 1;
+    
+    // if there are no processes, we're done
+    if (LinkedList1_IsEmpty(&interp->processes)) {
+        interp->params.handler_finished(interp->params.user, interp->main_exit_code);
+        return;
+    }
+    
+    // start terminating non-template processes
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&interp->processes); ln; ln = LinkedList1Node_Next(ln)) {
+        struct process *p = UPPER_OBJECT(ln, struct process, list_node);
+        if (p->module_process) {
+            continue;
+        }
+        process_start_terminating(p);
+    }
+}
+
+char * implode_id_strings (NCDInterpreter *interp, const NCD_string_id_t *names, size_t num_names, char del)
+{
+    ExpString str;
+    if (!ExpString_Init(&str)) {
+        goto fail0;
+    }
+    
+    int is_first = 1;
+    
+    while (num_names > 0) {
+        if (!is_first && !ExpString_AppendChar(&str, del)) {
+            goto fail1;
+        }
+        const char *name_str = NCDStringIndex_Value(&interp->string_index, *names);
+        if (!ExpString_Append(&str, name_str)) {
+            goto fail1;
+        }
+        names++;
+        num_names--;
+        is_first = 0;
+    }
+    
+    return ExpString_Get(&str);
+    
+fail1:
+    ExpString_Free(&str);
+fail0:
+    return NULL;
+}
+
+void clear_process_cache (NCDInterpreter *interp)
+{
+    for (NCDProgramElem *elem = NCDProgram_FirstElem(&interp->program); elem; elem = NCDProgram_NextElem(&interp->program, elem)) {
+        ASSERT(NCDProgramElem_Type(elem) == NCDPROGRAMELEM_PROCESS)
+        NCDProcess *ast_p = NCDProgramElem_Process(elem);
+        
+        NCD_string_id_t name_id = NCDStringIndex_Lookup(&interp->string_index, NCDProcess_Name(ast_p));
+        NCDInterpProcess *iprocess = NCDInterpProg_FindProcess(&interp->iprogram, name_id);
+        
+        struct process *p;
+        while (p = NCDInterpProcess_CachePull(iprocess)) {
+            process_release(p, 1);
+        }
+    }
+}
+
+struct process * process_allocate (NCDInterpreter *interp, NCDInterpProcess *iprocess)
+{
+    ASSERT(iprocess)
+    
+    // try to pull from cache
+    struct process *p = NCDInterpProcess_CachePull(iprocess);
+    if (p) {
+        goto allocated;
+    }
+    
+    // get number of statements
+    int num_statements = NCDInterpProcess_NumStatements(iprocess);
+    
+    // get size of preallocated memory
+    int mem_size = NCDInterpProcess_PreallocSize(iprocess);
+    if (mem_size < 0) {
+        goto fail0;
+    }
+    
+    // start with size of process structure
+    size_t alloc_size = sizeof(struct process);
+    
+    // add size of statements array
+    if (num_statements > SIZE_MAX / sizeof(struct statement)) {
+        goto fail0;
+    }
+    if (!BSizeAdd(&alloc_size, num_statements * sizeof(struct statement))) {
+        goto fail0;
+    }
+    
+    // align for preallocated memory
+    if (!BSizeAlign(&alloc_size, BMAX_ALIGN)) {
+        goto fail0;
+    }
+    size_t mem_off = alloc_size;
+    
+    // add size of preallocated memory
+    if (mem_size > SIZE_MAX || !BSizeAdd(&alloc_size, mem_size)) {
+        goto fail0;
+    }
+    
+    // allocate memory
+    p = BAlloc(alloc_size);
+    if (!p) {
+        goto fail0;
+    }
+    
+    // set variables
+    p->interp = interp;
+    p->reactor = interp->params.reactor;
+    p->iprocess = iprocess;
+    p->ap = 0;
+    p->fp = 0;
+    p->num_statements = num_statements;
+    p->error = 0;
+    p->have_alloc = 0;
+    
+    // init statements
+    char *mem = (char *)p + mem_off;
+    for (int i = 0; i < num_statements; i++) {
+        struct statement *ps = &p->statements[i];
+        ps->i = i;
+        ps->inst.istate = SSTATE_FORGOTTEN;
+        ps->mem_size = NCDInterpProcess_StatementPreallocSize(iprocess, i);
+        ps->inst.mem = mem + NCDInterpProcess_StatementPreallocOffset(iprocess, i);
+    }
+    
+    // init timer
+    BSmallTimer_Init(&p->wait_timer, process_wait_timer_handler);
+    
+    // init work job
+    BSmallPending_Init(&p->work_job, BReactor_PendingGroup(p->reactor), NULL, NULL);
+    
+allocated:
+    ASSERT(p->interp == interp)
+    ASSERT(p->reactor == interp->params.reactor)
+    ASSERT(p->iprocess == iprocess)
+    ASSERT(p->ap == 0)
+    ASSERT(p->fp == 0)
+    ASSERT(p->num_statements == NCDInterpProcess_NumStatements(iprocess))
+    ASSERT(p->error == 0)
+    process_assert_statements_cleared(p);
+    ASSERT(!BSmallPending_IsSet(&p->work_job))
+    ASSERT(!BSmallTimer_IsRunning(&p->wait_timer))
+    
+    return p;
+    
+fail0:
+    BLog(BLOG_ERROR, "failed to allocate memory for process %s", NCDInterpProcess_Name(iprocess));
+    return NULL;
+}
+
+void process_release (struct process *p, int no_push)
+{
+    ASSERT(p->ap == 0)
+    ASSERT(p->fp == 0)
+    ASSERT(p->error == 0)
+    process_assert_statements_cleared(p);
+    ASSERT(!BSmallPending_IsSet(&p->work_job))
+    ASSERT(!BSmallTimer_IsRunning(&p->wait_timer))
+    
+    // try to push to cache
+    if (!no_push && !p->have_alloc) {
+        if (NCDInterpProcess_CachePush(p->iprocess, p)) {
+            return;
+        }
+    }
+    
+    // free work job
+    BSmallPending_Free(&p->work_job, BReactor_PendingGroup(p->reactor));
+    
+    // free statement memory
+    if (p->have_alloc) {
+        for (int i = 0; i < p->num_statements; i++) {
+            struct statement *ps = &p->statements[i];
+            if (statement_mem_is_allocated(ps)) {
+                free(ps->inst.mem);
+            }
+        }
+    }
+    
+    // free strucure
+    BFree(p);
+}
+
+void process_assert_statements_cleared (struct process *p)
+{
+#ifndef NDEBUG
+    for (int i = 0; i < p->num_statements; i++) {
+        ASSERT(p->statements[i].i == i)
+        ASSERT(p->statements[i].inst.istate == SSTATE_FORGOTTEN)
+    }
+#endif
+}
+
+int process_new (NCDInterpreter *interp, NCDInterpProcess *iprocess, NCDModuleProcess *module_process)
+{
+    ASSERT(iprocess)
+    
+    // allocate prepared process struct
+    struct process *p = process_allocate(interp, iprocess);
+    if (!p) {
+        return 0;
+    }
+    
+    // set module process pointer
+    p->module_process = module_process;
+    
+    // set module process handlers
+    if (p->module_process) {
+        NCDModuleProcess_Interp_SetHandlers(p->module_process, p,
+                                            (NCDModuleProcess_interp_func_event)process_moduleprocess_func_event,
+                                            (NCDModuleProcess_interp_func_getobj)process_moduleprocess_func_getobj);
+    }
+    
+    // set state
+    process_set_state(p, PSTATE_WORKING);
+    BSmallPending_SetHandler(&p->work_job, (BSmallPending_handler)process_work_job_handler_working, p);
+    
+    // insert to processes list
+    LinkedList1_Append(&interp->processes, &p->list_node);
+    
+    // schedule work
+    BSmallPending_Set(&p->work_job, BReactor_PendingGroup(p->reactor));
+    
+    return 1;
+}
+
+void process_set_state (struct process *p, int state)
+{
+#ifndef NDEBUG
+    p->state = state;
+#endif
+}
+
+void process_free (struct process *p, NCDModuleProcess **out_mp)
+{
+    ASSERT(p->ap == 0)
+    ASSERT(p->fp == 0)
+    ASSERT(out_mp)
+    ASSERT(!BSmallPending_IsSet(&p->work_job))
+    
+    // give module process to caller so it can inform the process creator that the process has terminated
+    *out_mp = p->module_process;
+    
+    // remove from processes list
+    LinkedList1_Remove(&p->interp->processes, &p->list_node);
+    
+    // free timer
+    BReactor_RemoveSmallTimer(p->reactor, &p->wait_timer);
+    
+    // clear error
+    p->error = 0;
+    
+    process_release(p, 0);
+}
+
+void process_start_terminating (struct process *p)
+{
+    // set state terminating
+    process_set_state(p, PSTATE_TERMINATING);
+    BSmallPending_SetHandler(&p->work_job, (BSmallPending_handler)process_work_job_handler_terminating, p);
+    
+    // schedule work
+    BSmallPending_Set(&p->work_job, BReactor_PendingGroup(p->reactor));
+}
+
+int process_have_child (struct process *p)
+{
+    return (p->ap > 0 && p->statements[p->ap - 1].inst.istate == SSTATE_CHILD);
+}
+
+void process_assert_pointers (struct process *p)
+{
+    ASSERT(p->ap <= p->num_statements)
+    ASSERT(p->fp >= p->ap)
+    ASSERT(p->fp <= p->num_statements)
+    
+#ifndef NDEBUG
+    // check AP
+    for (int i = 0; i < p->ap; i++) {
+        if (i == p->ap - 1) {
+            ASSERT(p->statements[i].inst.istate == SSTATE_ADULT || p->statements[i].inst.istate == SSTATE_CHILD)
+        } else {
+            ASSERT(p->statements[i].inst.istate == SSTATE_ADULT)
+        }
+    }
+    
+    // check FP
+    int fp = p->num_statements;
+    while (fp > 0 && p->statements[fp - 1].inst.istate == SSTATE_FORGOTTEN) {
+        fp--;
+    }
+    ASSERT(p->fp == fp)
+#endif
+}
+
+void process_logfunc (struct process *p)
+{
+    BLog_Append("process %s: ", NCDInterpProcess_Name(p->iprocess));
+}
+
+void process_log (struct process *p, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_LogViaFuncVarArg((BLog_logfunc)process_logfunc, p, BLOG_CURRENT_CHANNEL, level, fmt, vl);
+    va_end(vl);
+}
+
+void process_work_job_handler_working (struct process *p)
+{
+    process_assert_pointers(p);
+    ASSERT(p->state == PSTATE_WORKING)
+    
+    // cleaning up?
+    if (p->ap < p->fp) {
+        // order the last living statement to die, if needed
+        struct statement *ps = &p->statements[p->fp - 1];
+        if (ps->inst.istate == SSTATE_DYING) {
+            return;
+        }
+        
+        STATEMENT_LOG(ps, BLOG_INFO, "killing");
+        
+        // set statement state DYING
+        ps->inst.istate = SSTATE_DYING;
+        
+        // order it to die
+        NCDModuleInst_Die(&ps->inst);
+        return;
+    }
+    
+    // clean?
+    if (process_have_child(p)) {
+        ASSERT(p->ap > 0)
+        ASSERT(p->ap <= p->num_statements)
+        
+        struct statement *ps = &p->statements[p->ap - 1];
+        ASSERT(ps->inst.istate == SSTATE_CHILD)
+        
+        STATEMENT_LOG(ps, BLOG_INFO, "clean");
+        
+        // report clean
+        NCDModuleInst_Clean(&ps->inst);
+        return;
+    }
+    
+    // finished?
+    if (p->ap == p->num_statements) {
+        process_log(p, BLOG_INFO, "victory");
+        
+        // set state up
+        process_set_state(p, PSTATE_UP);
+        BSmallPending_SetHandler(&p->work_job, (BSmallPending_handler)process_work_job_handler_up, p);
+        
+        // set module process up
+        if (p->module_process) {
+            NCDModuleProcess_Interp_Up(p->module_process);
+        }
+        return;
+    }
+    
+    // advancing?
+    struct statement *ps = &p->statements[p->ap];
+    ASSERT(ps->inst.istate == SSTATE_FORGOTTEN)
+    
+    if (p->error) {
+        STATEMENT_LOG(ps, BLOG_INFO, "waiting after error");
+        
+        // clear error
+        p->error = 0;
+        
+        // set wait timer
+        BReactor_SetSmallTimer(p->reactor, &p->wait_timer, BTIMER_SET_RELATIVE, p->interp->params.retry_time);
+    } else {
+        // advance
+        process_advance(p);
+    }
+}
+
+void process_work_job_handler_up (struct process *p)
+{
+    process_assert_pointers(p);
+    ASSERT(p->state == PSTATE_UP)
+    ASSERT(p->ap < p->num_statements || process_have_child(p))
+    
+    // if we have module process, wait for its permission to continue
+    if (p->module_process) {
+        // set state waiting
+        process_set_state(p, PSTATE_WAITING);
+        BSmallPending_SetHandler(&p->work_job, (BSmallPending_handler)process_work_job_handler_waiting, p);
+        
+        // set module process down
+        NCDModuleProcess_Interp_Down(p->module_process);
+        return;
+    }
+    
+    // set state working
+    process_set_state(p, PSTATE_WORKING);
+    BSmallPending_SetHandler(&p->work_job, (BSmallPending_handler)process_work_job_handler_working, p);
+    
+    // delegate the rest to the working handler
+    process_work_job_handler_working(p);
+}
+
+void process_work_job_handler_waiting (struct process *p)
+{
+    process_assert_pointers(p);
+    ASSERT(p->state == PSTATE_WAITING)
+    
+    // do absolutely nothing. Having this no-op handler avoids a branch
+    // in statement_instance_func_event().
+}
+
+void process_work_job_handler_terminating (struct process *p)
+{
+    process_assert_pointers(p);
+    ASSERT(p->state == PSTATE_TERMINATING)
+    
+again:
+    if (p->fp == 0) {
+        NCDInterpreter *interp = p->interp;
+        
+        // free process
+        NCDModuleProcess *mp;
+        process_free(p, &mp);
+        
+        // if program is terminating amd there are no more processes, exit program
+        if (interp->terminating && LinkedList1_IsEmpty(&interp->processes)) {
+            ASSERT(!mp)
+            interp->params.handler_finished(interp->params.user, interp->main_exit_code);
+            return;
+        }
+        
+        // inform the process creator that the process has terminated
+        if (mp) {
+            NCDModuleProcess_Interp_Terminated(mp);
+            return;
+        }
+        
+        return;
+    }
+    
+    // order the last living statement to die, if needed
+    struct statement *ps = &p->statements[p->fp - 1];
+    ASSERT(ps->inst.istate != SSTATE_FORGOTTEN)
+    if (ps->inst.istate == SSTATE_DYING) {
+        return;
+    }
+    
+    STATEMENT_LOG(ps, BLOG_INFO, "killing");
+    
+    // update AP
+    if (p->ap > ps->i) {
+        p->ap = ps->i;
+    }
+    
+    // optimize for statements which can be destroyed immediately
+    if (NCDModuleInst_TryFree(&ps->inst)) {
+        STATEMENT_LOG(ps, BLOG_INFO, "died");
+        
+        // free arguments memory
+        NCDValMem_Free(&ps->args_mem);
+        
+        // set statement state FORGOTTEN
+        ps->inst.istate = SSTATE_FORGOTTEN;
+        
+        // update FP
+        while (p->fp > 0 && p->statements[p->fp - 1].inst.istate == SSTATE_FORGOTTEN) {
+            p->fp--;
+        }
+        
+        goto again;
+    }
+    
+    // set statement state DYING
+    ps->inst.istate = SSTATE_DYING;
+    
+    // order it to die
+    NCDModuleInst_Die(&ps->inst);
+    return;
+}
+
+int replace_placeholders_callback (void *arg, int plid, NCDValMem *mem, NCDValRef *out)
+{
+    struct process *p = arg;
+    ASSERT(plid >= 0)
+    ASSERT(mem)
+    ASSERT(out)
+    
+    const NCD_string_id_t *varnames;
+    size_t num_names;
+    NCDPlaceholderDb_GetVariable(&p->interp->placeholder_db, plid, &varnames, &num_names);
+    
+    return process_resolve_variable_expr(p, p->ap, varnames, num_names, mem, out);
+}
+
+void process_advance (struct process *p)
+{
+    process_assert_pointers(p);
+    ASSERT(p->ap == p->fp)
+    ASSERT(!process_have_child(p))
+    ASSERT(p->ap < p->num_statements)
+    ASSERT(!p->error)
+    ASSERT(!BSmallPending_IsSet(&p->work_job))
+    ASSERT(p->state == PSTATE_WORKING)
+    
+    struct statement *ps = &p->statements[p->ap];
+    ASSERT(ps->inst.istate == SSTATE_FORGOTTEN)
+    
+    STATEMENT_LOG(ps, BLOG_INFO, "initializing");
+    
+    // need to determine the module and object to use it on (if it's a method)
+    const struct NCDInterpModule *module;
+    void *method_context = NULL;
+    
+    // get object names, e.g. "my.cat" in "my.cat->meow();"
+    // (or NULL if this is not a method statement)
+    const NCD_string_id_t *objnames;
+    size_t num_objnames;
+    NCDInterpProcess_StatementObjNames(p->iprocess, p->ap, &objnames, &num_objnames);
+    
+    if (!objnames) {
+        // not a method; module is already known by NCDInterpProcess
+        module = NCDInterpProcess_StatementGetSimpleModule(p->iprocess, p->ap, &p->interp->string_index, &p->interp->mindex);
+        
+        if (!module) {
+            const char *cmdname_str = NCDInterpProcess_StatementCmdName(p->iprocess, p->ap, &p->interp->string_index);
+            STATEMENT_LOG(ps, BLOG_ERROR, "unknown simple statement: %s", cmdname_str);
+            goto fail0;
+        }
+    } else {
+        // get object
+        NCDObject object;
+        if (!process_resolve_object_expr(p, p->ap, objnames, num_objnames, &object)) {
+            goto fail0;
+        }
+        
+        // get object type
+        NCD_string_id_t object_type = NCDObject_Type(&object);
+        if (object_type < 0) {
+            STATEMENT_LOG(ps, BLOG_ERROR, "cannot call method on object with no type");
+            goto fail0;
+        }
+        
+        // get method context
+        method_context = NCDObject_MethodUser(&object);
+        
+        // find module based on type of object
+        module = NCDInterpProcess_StatementGetMethodModule(p->iprocess, p->ap, object_type, &p->interp->mindex);
+        
+        if (!module) {
+            const char *type_str = NCDStringIndex_Value(&p->interp->string_index, object_type);
+            const char *cmdname_str = NCDInterpProcess_StatementCmdName(p->iprocess, p->ap, &p->interp->string_index);
+            STATEMENT_LOG(ps, BLOG_ERROR, "unknown method statement: %s::%s", type_str, cmdname_str);
+            goto fail0;
+        }
+    }
+    
+    // copy arguments
+    NCDValRef args;
+    NCDValReplaceProg prog;
+    if (!NCDInterpProcess_CopyStatementArgs(p->iprocess, ps->i, &ps->args_mem, &args, &prog)) {
+        STATEMENT_LOG(ps, BLOG_ERROR, "NCDInterpProcess_CopyStatementArgs failed");
+        goto fail0;
+    }
+    
+    // replace placeholders with values of variables
+    if (!NCDValReplaceProg_Execute(prog, &ps->args_mem, replace_placeholders_callback, p)) {
+        STATEMENT_LOG(ps, BLOG_ERROR, "failed to replace variables in arguments with values");
+        goto fail1;
+    }
+    
+    // convert non-continuous strings unless the module can handle them
+    if (!(module->module.flags & NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS)) {
+        if (!NCDValMem_ConvertNonContinuousStrings(&ps->args_mem, &args)) {
+            STATEMENT_LOG(ps, BLOG_ERROR, "NCDValMem_ConvertNonContinuousStrings failed");
+            goto fail1;
+        }
+    }
+    
+    // allocate memory
+    if (!statement_allocate_memory(ps, module->module.alloc_size)) {
+        STATEMENT_LOG(ps, BLOG_ERROR, "failed to allocate memory");
+        goto fail1;
+    }
+    
+    // set statement state CHILD
+    ps->inst.istate = SSTATE_CHILD;
+    
+    // increment AP
+    p->ap++;
+    
+    // increment FP
+    p->fp++;
+    
+    process_assert_pointers(p);
+    
+    // initialize module instance
+    NCDModuleInst_Init(&ps->inst, module, method_context, args, &p->interp->module_params);
+    return;
+    
+fail1:
+    NCDValMem_Free(&ps->args_mem);
+fail0:
+    // set error
+    p->error = 1;
+    
+    // schedule work to start the timer
+    BSmallPending_Set(&p->work_job, BReactor_PendingGroup(p->reactor));
+}
+
+void process_wait_timer_handler (BSmallTimer *timer)
+{
+    struct process *p = UPPER_OBJECT(timer, struct process, wait_timer);
+    process_assert_pointers(p);
+    ASSERT(!BSmallPending_IsSet(&p->work_job))
+    
+    // check if something happened that means we no longer need to retry
+    if (p->ap != p->fp || process_have_child(p) || p->ap == p->num_statements) {
+        return;
+    }
+    
+    process_log(p, BLOG_INFO, "retrying");
+    
+    // advance. Note: the asserts for this are indeed satisfied, though this
+    // it not trivial to prove.
+    process_advance(p);
+}
+
+int process_find_object (struct process *p, int pos, NCD_string_id_t name, NCDObject *out_object)
+{
+    ASSERT(pos >= 0)
+    ASSERT(pos <= p->num_statements)
+    ASSERT(out_object)
+    
+    int i = NCDInterpProcess_FindStatement(p->iprocess, pos, name);
+    if (i >= 0) {
+        struct statement *ps = &p->statements[i];
+        ASSERT(i < p->num_statements)
+        
+        if (ps->inst.istate == SSTATE_FORGOTTEN) {
+            process_log(p, BLOG_ERROR, "statement (%d) is uninitialized", i);
+            return 0;
+        }
+        
+        *out_object = NCDModuleInst_Object(&ps->inst);
+        return 1;
+    }
+    
+    if (p->module_process && NCDModuleProcess_Interp_GetSpecialObj(p->module_process, name, out_object)) {
+        return 1;
+    }
+    
+    return 0;
+}
+
+int process_resolve_object_expr (struct process *p, int pos, const NCD_string_id_t *names, size_t num_names, NCDObject *out_object)
+{
+    ASSERT(pos >= 0)
+    ASSERT(pos <= p->num_statements)
+    ASSERT(names)
+    ASSERT(num_names > 0)
+    ASSERT(out_object)
+    
+    NCDObject object;
+    if (!process_find_object(p, pos, names[0], &object)) {
+        goto fail;
+    }
+    
+    if (!NCDObject_ResolveObjExprCompact(&object, names + 1, num_names - 1, out_object)) {
+        goto fail;
+    }
+    
+    return 1;
+    
+fail:;
+    char *name = implode_id_strings(p->interp, names, num_names, '.');
+    process_log(p, BLOG_ERROR, "failed to resolve object (%s) from position %zu", (name ? name : ""), pos);
+    free(name);
+    return 0;
+}
+
+int process_resolve_variable_expr (struct process *p, int pos, const NCD_string_id_t *names, size_t num_names, NCDValMem *mem, NCDValRef *out_value)
+{
+    ASSERT(pos >= 0)
+    ASSERT(pos <= p->num_statements)
+    ASSERT(names)
+    ASSERT(num_names > 0)
+    ASSERT(mem)
+    ASSERT(out_value)
+    
+    NCDObject object;
+    if (!process_find_object(p, pos, names[0], &object)) {
+        goto fail;
+    }
+    
+    if (!NCDObject_ResolveVarExprCompact(&object, names + 1, num_names - 1, mem, out_value)) {
+        goto fail;
+    }
+    
+    return 1;
+    
+fail:;
+    char *name = implode_id_strings(p->interp, names, num_names, '.');
+    process_log(p, BLOG_ERROR, "failed to resolve variable (%s) from position %zu", (name ? name : ""), pos);
+    free(name);
+    return 0;
+}
+
+void statement_logfunc (struct statement *ps)
+{
+    process_logfunc(statement_process(ps));
+    BLog_Append("statement %zu: ", ps->i);
+}
+
+void statement_log (struct statement *ps, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_LogViaFuncVarArg((BLog_logfunc)statement_logfunc, ps, BLOG_CURRENT_CHANNEL, level, fmt, vl);
+    va_end(vl);
+}
+
+struct process * statement_process (struct statement *ps)
+{
+    return UPPER_OBJECT(ps - ps->i, struct process, statements);
+}
+
+int statement_mem_is_allocated (struct statement *ps)
+{
+    return (ps->mem_size < 0);
+}
+
+int statement_mem_size (struct statement *ps)
+{
+    return (ps->mem_size >= 0 ? ps->mem_size : -ps->mem_size);
+}
+
+int statement_allocate_memory (struct statement *ps, int alloc_size)
+{
+    ASSERT(alloc_size >= 0)
+    
+    if (alloc_size > statement_mem_size(ps)) {
+        // allocate new memory
+        char *new_mem = malloc(alloc_size);
+        if (!new_mem) {
+            STATEMENT_LOG(ps, BLOG_ERROR, "malloc failed");
+            return 0;
+        }
+        
+        // release old memory unless it was preallocated
+        if (statement_mem_is_allocated(ps)) {
+            free(ps->inst.mem);
+        }
+        
+        struct process *p = statement_process(ps);
+        
+        // register memory in statement
+        ps->inst.mem = new_mem;
+        ps->mem_size = -alloc_size;
+        
+        // set the alloc flag in the process to make sure process_free()
+        // releases the allocated memory
+        p->have_alloc = 1;
+        
+        // register alloc size for future preallocations
+        NCDInterpProcess_StatementBumpAllocSize(p->iprocess, ps->i, alloc_size);
+    }
+    
+    return 1;
+}
+
+void statement_instance_func_event (NCDModuleInst *inst, int event)
+{
+    struct statement *ps = UPPER_OBJECT(inst, struct statement, inst);
+    ASSERT(ps->inst.istate == SSTATE_CHILD || ps->inst.istate == SSTATE_ADULT || ps->inst.istate == SSTATE_DYING)
+    
+    struct process *p = statement_process(ps);
+    process_assert_pointers(p);
+    
+    // schedule work
+    BSmallPending_Set(&p->work_job, BReactor_PendingGroup(p->reactor));
+    
+    switch (event) {
+        case NCDMODULE_EVENT_UP: {
+            ASSERT(ps->inst.istate == SSTATE_CHILD)
+            
+            STATEMENT_LOG(ps, BLOG_INFO, "up");
+            
+            // set state ADULT
+            ps->inst.istate = SSTATE_ADULT;
+        } break;
+        
+        case NCDMODULE_EVENT_DOWN: {
+            ASSERT(ps->inst.istate == SSTATE_ADULT)
+            
+            STATEMENT_LOG(ps, BLOG_INFO, "down");
+            
+            // set state CHILD
+            ps->inst.istate = SSTATE_CHILD;
+            
+            // clear error
+            if (ps->i < p->ap) {
+                p->error = 0;
+            }
+            
+            // update AP
+            if (p->ap > ps->i + 1) {
+                p->ap = ps->i + 1;
+            }
+        } break;
+        
+        case NCDMODULE_EVENT_DOWNUP: {
+            ASSERT(ps->inst.istate == SSTATE_ADULT)
+            
+            STATEMENT_LOG(ps, BLOG_INFO, "down");
+            STATEMENT_LOG(ps, BLOG_INFO, "up");
+            
+            // clear error
+            if (ps->i < p->ap) {
+                p->error = 0;
+            }
+            
+            // update AP
+            if (p->ap > ps->i + 1) {
+                p->ap = ps->i + 1;
+            }
+        } break;
+        
+        case NCDMODULE_EVENT_DEAD: {
+            STATEMENT_LOG(ps, BLOG_INFO, "died");
+            
+            // free instance
+            NCDModuleInst_Free(&ps->inst);
+            
+            // free arguments memory
+            NCDValMem_Free(&ps->args_mem);
+            
+            // set state FORGOTTEN
+            ps->inst.istate = SSTATE_FORGOTTEN;
+            
+            // update AP
+            if (p->ap > ps->i) {
+                p->ap = ps->i;
+            }
+            
+            // update FP
+            while (p->fp > 0 && p->statements[p->fp - 1].inst.istate == SSTATE_FORGOTTEN) {
+                p->fp--;
+            }
+        } break;
+        
+        case NCDMODULE_EVENT_DEADERROR: {
+            STATEMENT_LOG(ps, BLOG_ERROR, "died with error");
+            
+            // free instance
+            NCDModuleInst_Free(&ps->inst);
+            
+            // free arguments memory
+            NCDValMem_Free(&ps->args_mem);
+            
+            // set state FORGOTTEN
+            ps->inst.istate = SSTATE_FORGOTTEN;
+            
+            // set error
+            if (ps->i < p->ap) {
+                p->error = 1;
+            }
+            
+            // update AP
+            if (p->ap > ps->i) {
+                p->ap = ps->i;
+            }
+            
+            // update FP
+            while (p->fp > 0 && p->statements[p->fp - 1].inst.istate == SSTATE_FORGOTTEN) {
+                p->fp--;
+            }
+        } break;
+    }
+}
+
+int statement_instance_func_getobj (NCDModuleInst *inst, NCD_string_id_t objname, NCDObject *out_object)
+{
+    struct statement *ps = UPPER_OBJECT(inst, struct statement, inst);
+    ASSERT(ps->inst.istate != SSTATE_FORGOTTEN)
+    
+    return process_find_object(statement_process(ps), ps->i, objname, out_object);
+}
+
+int statement_instance_func_initprocess (void *vinterp, NCDModuleProcess* mp, NCD_string_id_t template_name)
+{
+    NCDInterpreter *interp = vinterp;
+    
+    // find process
+    NCDInterpProcess *iprocess = NCDInterpProg_FindProcess(&interp->iprogram, template_name);
+    if (!iprocess) {
+        const char *str = NCDStringIndex_Value(&interp->string_index, template_name);
+        BLog(BLOG_ERROR, "no template named %s", str);
+        return 0;
+    }
+    
+    // make sure it's a template
+    if (!NCDInterpProcess_IsTemplate(iprocess)) {
+        const char *str = NCDStringIndex_Value(&interp->string_index, template_name);
+        BLog(BLOG_ERROR, "need template to create a process, but %s is a process", str);
+        return 0;
+    }
+    
+    // create process
+    if (!process_new(interp, iprocess, mp)) {
+        const char *str = NCDStringIndex_Value(&interp->string_index, template_name);
+        BLog(BLOG_ERROR, "failed to create process from template %s", str);
+        return 0;
+    }
+    
+    if (BLog_WouldLog(BLOG_INFO, BLOG_CURRENT_CHANNEL)) {
+        const char *str = NCDStringIndex_Value(&interp->string_index, template_name);
+        BLog(BLOG_INFO, "created process from template %s", str);
+    }
+    
+    return 1;
+}
+
+void statement_instance_logfunc (NCDModuleInst *inst)
+{
+    struct statement *ps = UPPER_OBJECT(inst, struct statement, inst);
+    ASSERT(ps->inst.istate != SSTATE_FORGOTTEN)
+    
+    statement_logfunc(ps);
+    BLog_Append("module: ");
+}
+
+void statement_instance_func_interp_exit (void *vinterp, int exit_code)
+{
+    NCDInterpreter *interp = vinterp;
+    
+    start_terminate(interp, exit_code);
+}
+
+int statement_instance_func_interp_getargs (void *vinterp, NCDValMem *mem, NCDValRef *out_value)
+{
+    NCDInterpreter *interp = vinterp;
+    
+    *out_value = NCDVal_NewList(mem, interp->params.num_extra_args);
+    if (NCDVal_IsInvalid(*out_value)) {
+        BLog(BLOG_ERROR, "NCDVal_NewList failed");
+        goto fail;
+    }
+    
+    for (int i = 0; i < interp->params.num_extra_args; i++) {
+        NCDValRef arg = NCDVal_NewString(mem, interp->params.extra_args[i]);
+        if (NCDVal_IsInvalid(arg)) {
+            BLog(BLOG_ERROR, "NCDVal_NewString failed");
+            goto fail;
+        }
+        
+        if (!NCDVal_ListAppend(*out_value, arg)) {
+            BLog(BLOG_ERROR, "depth limit exceeded");
+            goto fail;
+        }
+    }
+    
+    return 1;
+    
+fail:
+    *out_value = NCDVal_NewInvalid();
+    return 1;
+}
+
+btime_t statement_instance_func_interp_getretrytime (void *vinterp)
+{
+    NCDInterpreter *interp = vinterp;
+    
+    return interp->params.retry_time;
+}
+
+int statement_instance_func_interp_loadgroup (void *vinterp, const struct NCDModuleGroup *group)
+{
+    NCDInterpreter *interp = vinterp;
+    
+    if (!NCDModuleIndex_AddGroup(&interp->mindex, group, &interp->module_iparams, &interp->string_index)) {
+        BLog(BLOG_ERROR, "NCDModuleIndex_AddGroup failed");
+        return 0;
+    }
+    
+    return 1;
+}
+
+void process_moduleprocess_func_event (struct process *p, int event)
+{
+    ASSERT(p->module_process)
+    
+    switch (event) {
+        case NCDMODULEPROCESS_INTERP_EVENT_CONTINUE: {
+            ASSERT(p->state == PSTATE_WAITING)
+            
+            // set state working
+            process_set_state(p, PSTATE_WORKING);
+            BSmallPending_SetHandler(&p->work_job, (BSmallPending_handler)process_work_job_handler_working, p);
+            
+            // schedule work
+            BSmallPending_Set(&p->work_job, BReactor_PendingGroup(p->reactor));
+        } break;
+        
+        case NCDMODULEPROCESS_INTERP_EVENT_TERMINATE: {
+            ASSERT(p->state != PSTATE_TERMINATING)
+            
+            process_log(p, BLOG_INFO, "process termination requested");
+        
+            // start terminating
+            process_start_terminating(p);
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+int process_moduleprocess_func_getobj (struct process *p, NCD_string_id_t name, NCDObject *out_object)
+{
+    ASSERT(p->module_process)
+    
+    return process_find_object(p, p->num_statements, name, out_object);
+}
diff --git a/external/badvpn_dns/ncd/NCDInterpreter.h b/external/badvpn_dns/ncd/NCDInterpreter.h
new file mode 100644
index 0000000..8d11c33
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDInterpreter.h
@@ -0,0 +1,156 @@
+/**
+ * @file NCDInterpreter.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCD_INTERPRETER_H
+#define BADVPN_NCD_INTERPRETER_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <system/BTime.h>
+#include <system/BReactor.h>
+#include <ncd/NCDStringIndex.h>
+#include <ncd/NCDModuleIndex.h>
+#include <ncd/NCDAst.h>
+#include <ncd/NCDPlaceholderDb.h>
+#include <ncd/NCDInterpProg.h>
+#include <ncd/NCDModule.h>
+#include <structure/LinkedList1.h>
+
+#ifndef BADVPN_NO_PROCESS
+#include <system/BProcess.h>
+#endif
+#ifndef BADVPN_NO_UDEV
+#include <udevmonitor/NCDUdevManager.h>
+#endif
+#ifndef BADVPN_NO_RANDOM
+#include <random/BRandom2.h>
+#endif
+
+/**
+ * Handler called when the interpreter has terminated, and {@link NCDInterpreter_Free}
+ * can be called.
+ * 
+ * @param user the user member of struct {@link NCDInterpreter_params}
+ * @param exit_code the exit code specified in the last interpreter termination request
+ */
+typedef void (*NCDInterpreter_handler_finished) (void *user, int exit_code);
+
+struct NCDInterpreter_params {
+    // callbacks
+    NCDInterpreter_handler_finished handler_finished;
+    void *user;
+    
+    // options
+    btime_t retry_time;
+    char **extra_args;
+    int num_extra_args;
+    
+    // possibly shared resources
+    BReactor *reactor;
+#ifndef BADVPN_NO_PROCESS
+    BProcessManager *manager;
+#endif
+#ifndef BADVPN_NO_UDEV
+    NCDUdevManager *umanager;
+#endif
+#ifndef BADVPN_NO_RANDOM
+    BRandom2 *random2;
+#endif
+};
+
+typedef struct {
+    // parameters
+    struct NCDInterpreter_params params;
+    
+    // are we terminating
+    int terminating;
+    int main_exit_code;
+
+    // string index
+    NCDStringIndex string_index;
+
+    // module index
+    NCDModuleIndex mindex;
+
+    // program AST
+    NCDProgram program;
+
+    // placeholder database
+    NCDPlaceholderDb placeholder_db;
+
+    // structure for efficient interpretation
+    NCDInterpProg iprogram;
+
+    // common module parameters
+    struct NCDModuleInst_params module_params;
+    struct NCDModuleInst_iparams module_iparams;
+    
+    // processes
+    LinkedList1 processes;
+    
+    DebugObject d_obj;
+} NCDInterpreter;
+
+/**
+ * Initializes and starts the interpreter.
+ * 
+ * @param o the interpreter
+ * @param program the program to execute in AST format. The program must
+ *                not contain any 'include' or 'include_guard' directives.
+ *                The interpreter takes ownership of the program, regardless
+ *                of the success of this function.
+ * @param params other parameters
+ * @return 1 on success, 0 on failure
+ */
+int NCDInterpreter_Init (NCDInterpreter *o, NCDProgram program, struct NCDInterpreter_params params) WARN_UNUSED;
+
+/**
+ * Frees the interpreter.
+ * This may only be called after the interpreter has terminated, i.e.
+ * the {@link NCDInterpreter_handler_finished} handler has been called.
+ * Additionally, it can be called right after {@link NCDInterpreter_Init}
+ * before any of the interpreter's {@link BPendingGroup} jobs have executed.
+ */
+void NCDInterpreter_Free (NCDInterpreter *o);
+
+/**
+ * Requests termination of the interpreter.
+ * NOTE: the program can request its own termination, possibly overriding the exit
+ * code specified here. Expect the program to terminate even if this function was
+ * not called.
+ * 
+ * @param o the interpreter
+ * @param exit_code the exit code to be passed to {@link NCDInterpreter_handler_finished}.
+ *                  This overrides any exit code set previously.
+ */
+void NCDInterpreter_RequestShutdown (NCDInterpreter *o, int exit_code);
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDMethodIndex.c b/external/badvpn_dns/ncd/NCDMethodIndex.c
new file mode 100644
index 0000000..5b3662a
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDMethodIndex.c
@@ -0,0 +1,272 @@
+/**
+ * @file NCDMethodIndex.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <limits.h>
+
+#include <misc/hashfun.h>
+#include <misc/balloc.h>
+#include <misc/strdup.h>
+
+#include "NCDMethodIndex.h"
+
+#include "NCDMethodIndex_hash.h"
+#include <structure/CHash_impl.h>
+
+#define GROWARRAY_NAME NamesArray
+#define GROWARRAY_OBJECT_TYPE NCDMethodIndex
+#define GROWARRAY_ARRAY_MEMBER names
+#define GROWARRAY_CAPACITY_MEMBER names_capacity
+#define GROWARRAY_MAX_CAPACITY INT_MAX
+#include <misc/grow_array.h>
+
+#define GROWARRAY_NAME EntriesArray
+#define GROWARRAY_OBJECT_TYPE NCDMethodIndex
+#define GROWARRAY_ARRAY_MEMBER entries
+#define GROWARRAY_CAPACITY_MEMBER entries_capacity
+#define GROWARRAY_MAX_CAPACITY INT_MAX
+#include <misc/grow_array.h>
+
+#include <generated/blog_channel_ncd.h>
+
+static int find_method_name (NCDMethodIndex *o, const char *method_name, int *out_entry_idx)
+{
+    ASSERT(method_name)
+    
+    NCDMethodIndex__HashRef ref = NCDMethodIndex__Hash_Lookup(&o->hash, o->names, method_name);
+    if (ref.link == -1) {
+        return 0;
+    }
+    
+    ASSERT(ref.link >= 0)
+    ASSERT(ref.link < o->num_names)
+    
+    struct NCDMethodIndex__method_name *name_entry = ref.ptr;
+    ASSERT(!strcmp(name_entry->method_name, method_name))
+    ASSERT(name_entry->first_entry >= 0)
+    ASSERT(name_entry->first_entry < o->num_entries)
+    
+    if (out_entry_idx) {
+        *out_entry_idx = name_entry->first_entry;
+    }
+    return 1;
+}
+
+static int add_method_name (NCDMethodIndex *o, const char *method_name, int *out_entry_idx)
+{
+    ASSERT(method_name)
+    ASSERT(!find_method_name(o, method_name, NULL))
+    
+    if (o->num_entries == o->entries_capacity && !EntriesArray_DoubleUp(o)) {
+        BLog(BLOG_ERROR, "EntriesArray_DoubleUp failed");
+        goto fail0;
+    }
+    
+    if (o->num_names == o->names_capacity && !NamesArray_DoubleUp(o)) {
+        BLog(BLOG_ERROR, "NamesArray_DoubleUp failed");
+        goto fail0;
+    }
+    
+    struct NCDMethodIndex__entry *entry = &o->entries[o->num_entries];
+    entry->obj_type = -1;
+    entry->next = -1;
+    
+    struct NCDMethodIndex__method_name *name_entry = &o->names[o->num_names];
+    
+    if (!(name_entry->method_name = b_strdup(method_name))) {
+        BLog(BLOG_ERROR, "b_strdup failed");
+        goto fail0;
+    }
+    
+    name_entry->first_entry = o->num_entries;
+    
+    NCDMethodIndex__HashRef ref = {name_entry, o->num_names};
+    int res = NCDMethodIndex__Hash_Insert(&o->hash, o->names, ref, NULL);
+    ASSERT_EXECUTE(res)
+    
+    o->num_entries++;
+    o->num_names++;
+    
+    if (out_entry_idx) {
+        *out_entry_idx = name_entry->first_entry;
+    }
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+int NCDMethodIndex_Init (NCDMethodIndex *o, NCDStringIndex *string_index)
+{
+    ASSERT(string_index)
+    
+    o->string_index = string_index;
+    
+    if (!NamesArray_Init(o, NCDMETHODINDEX_NUM_EXPECTED_METHOD_NAMES)) {
+        BLog(BLOG_ERROR, "NamesArray_Init failed");
+        goto fail0;
+    }
+    
+    if (!EntriesArray_Init(o, NCDMETHODINDEX_NUM_EXPECTED_ENTRIES)) {
+        BLog(BLOG_ERROR, "EntriesArray_Init failed");
+        goto fail1;
+    }
+    
+    o->num_names = 0;
+    o->num_entries = 0;
+    
+    if (!NCDMethodIndex__Hash_Init(&o->hash, NCDMETHODINDEX_NUM_EXPECTED_METHOD_NAMES)) {
+        BLog(BLOG_ERROR, "NCDMethodIndex__Hash_Init failed");
+        goto fail2;
+    }
+    
+    return 1;
+    
+fail2:
+    EntriesArray_Free(o);
+fail1:
+    NamesArray_Free(o);
+fail0:
+    return 0;
+}
+
+void NCDMethodIndex_Free (NCDMethodIndex *o)
+{
+    for (int i = 0; i < o->num_names; i++) {
+        free(o->names[i].method_name);
+    }
+    
+    NCDMethodIndex__Hash_Free(&o->hash);
+    EntriesArray_Free(o);
+    NamesArray_Free(o);
+}
+
+int NCDMethodIndex_AddMethod (NCDMethodIndex *o, const char *obj_type, size_t obj_type_len, const char *method_name, const struct NCDInterpModule *module)
+{
+    ASSERT(obj_type)
+    ASSERT(method_name)
+    ASSERT(module)
+    
+    NCD_string_id_t obj_type_id = NCDStringIndex_GetBin(o->string_index, obj_type, obj_type_len);
+    if (obj_type_id < 0) {
+        BLog(BLOG_ERROR, "NCDStringIndex_Get failed");
+        goto fail0;
+    }
+    
+    int entry_idx;
+    int first_entry_idx;
+    
+    if (!find_method_name(o, method_name, &first_entry_idx)) {
+        if (!add_method_name(o, method_name, &entry_idx)) {
+            goto fail0;
+        }
+        
+        ASSERT(entry_idx >= 0)
+        ASSERT(entry_idx < o->num_entries)
+        
+        struct NCDMethodIndex__entry *entry = &o->entries[entry_idx];
+        
+        entry->obj_type = obj_type_id;
+        entry->module = module;
+    } else {
+        ASSERT(first_entry_idx >= 0)
+        ASSERT(first_entry_idx < o->num_entries)
+        
+        if (o->num_entries == o->entries_capacity && !EntriesArray_DoubleUp(o)) {
+            BLog(BLOG_ERROR, "EntriesArray_DoubleUp failed");
+            goto fail0;
+        }
+        
+        entry_idx = o->num_entries;
+        struct NCDMethodIndex__entry *entry = &o->entries[o->num_entries];
+        
+        entry->obj_type = obj_type_id;
+        entry->module = module;
+        
+        entry->next = o->entries[first_entry_idx].next;
+        o->entries[first_entry_idx].next = o->num_entries;
+        
+        o->num_entries++;
+    }
+    
+    return entry_idx;
+    
+fail0:
+    return -1;
+}
+
+void NCDMethodIndex_RemoveMethod (NCDMethodIndex *o, int method_name_id)
+{
+    ASSERT(method_name_id >= 0)
+    ASSERT(method_name_id < o->num_entries)
+    ASSERT(o->entries[method_name_id].obj_type >= 0)
+    
+    o->entries[method_name_id].obj_type = -1;
+}
+
+int NCDMethodIndex_GetMethodNameId (NCDMethodIndex *o, const char *method_name)
+{
+    ASSERT(method_name)
+    
+    int first_entry_idx;
+    
+    if (!find_method_name(o, method_name, &first_entry_idx)) {
+        if (!add_method_name(o, method_name, &first_entry_idx)) {
+            return -1;
+        }
+    }
+    
+    ASSERT(first_entry_idx >= 0)
+    ASSERT(first_entry_idx < o->num_entries)
+    
+    return first_entry_idx;
+}
+
+const struct NCDInterpModule * NCDMethodIndex_GetMethodModule (NCDMethodIndex *o, NCD_string_id_t obj_type, int method_name_id)
+{
+    ASSERT(obj_type >= 0)
+    ASSERT(method_name_id >= 0)
+    ASSERT(method_name_id < o->num_entries)
+    
+    do {
+        struct NCDMethodIndex__entry *entry = &o->entries[method_name_id];
+        
+        if (entry->obj_type == obj_type) {
+            ASSERT(entry->module)
+            return entry->module;
+        }
+        
+        method_name_id = entry->next;
+        ASSERT(method_name_id >= -1)
+        ASSERT(method_name_id < o->num_entries)
+    } while (method_name_id >= 0);
+    
+    return NULL;
+}
diff --git a/external/badvpn_dns/ncd/NCDMethodIndex.h b/external/badvpn_dns/ncd/NCDMethodIndex.h
new file mode 100644
index 0000000..e4db0a5
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDMethodIndex.h
@@ -0,0 +1,135 @@
+/**
+ * @file NCDMethodIndex.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDMETHODINDEX_H
+#define BADVPN_NCDMETHODINDEX_H
+
+#include <misc/debug.h>
+#include <structure/CHash.h>
+#include <ncd/NCDModule.h>
+#include <ncd/NCDStringIndex.h>
+
+#define NCDMETHODINDEX_NUM_EXPECTED_METHOD_NAMES 64
+#define NCDMETHODINDEX_NUM_EXPECTED_ENTRIES 64
+
+struct NCDMethodIndex__method_name {
+    char *method_name;
+    int first_entry;
+    int hash_next;
+};
+
+struct NCDMethodIndex__entry {
+    NCD_string_id_t obj_type;
+    const struct NCDInterpModule *module;
+    int next;
+};
+
+typedef struct NCDMethodIndex__method_name NCDMethodIndex__hashentry;
+typedef const char *NCDMethodIndex__hashkey;
+typedef struct NCDMethodIndex__method_name *NCDMethodIndex__hasharg;
+
+#include "NCDMethodIndex_hash.h"
+#include <structure/CHash_decl.h>
+
+/**
+ * The method index associates (object_type, method_name) pairs to pointers
+ * to corresponding \link NCDInterpModule structures (whose type strings would
+ * be "object_type::method_name").
+ * More precisely, the method names are represented as indices into an
+ * internal array, which allows very efficient lookup when the method names
+ * are known in advance, but not the object types.
+ */
+typedef struct {
+    struct NCDMethodIndex__method_name *names;
+    struct NCDMethodIndex__entry *entries;
+    int names_capacity;
+    int entries_capacity;
+    int num_names;
+    int num_entries;
+    NCDMethodIndex__Hash hash;
+    NCDStringIndex *string_index;
+} NCDMethodIndex;
+
+/**
+ * Initializes the method index.
+ * 
+ * @return 1 on success, 0 on failure
+ */
+int NCDMethodIndex_Init (NCDMethodIndex *o, NCDStringIndex *string_index) WARN_UNUSED;
+
+/**
+ * Frees the method index.
+ */
+void NCDMethodIndex_Free (NCDMethodIndex *o);
+
+/**
+ * Adds a method to the index.
+ * Duplicate methods will not be detected here.
+ * 
+ * @param obj_type object type of method, e.g. "cat" in "cat::meow".
+ *                 Must not be NULL. Does not have to be null-terminated.
+ * @param obj_type_len number of characters in obj_type
+ * @param method_name name of method, e.g. "meow" in "cat::meow".
+ *                    Must not be NULL.
+ * @param module pointer to module structure. Must not be NULL.
+ * @return on success, a non-negative identifier; on failure, -1
+ */
+int NCDMethodIndex_AddMethod (NCDMethodIndex *o, const char *obj_type, size_t obj_type_len, const char *method_name, const struct NCDInterpModule *module);
+
+/**
+ * Removes a method from the index.
+ * 
+ * @param method_name_id method name identifier
+ */
+void NCDMethodIndex_RemoveMethod (NCDMethodIndex *o, int method_name_id);
+
+/**
+ * Obtains an internal integer identifier for a method name. The intention is that
+ * this is stored and later passed to \link NCDMethodIndex_GetMethodModule for
+ * efficient lookup of modules corresponding to methods.
+ * 
+ * @param method_name name of method, e.g. "meow" in "cat::meow".
+ *                    Must not be NULL.
+ * @return non-negative integer on success, -1 on failure
+ */
+int NCDMethodIndex_GetMethodNameId (NCDMethodIndex *o, const char *method_name);
+
+/**
+ * Looks up the module corresponding to a method. The method name is passed as an
+ * identifier obtained from \link NCDMethodIndex_GetMethodNameId.
+ * 
+ * @param obj_type object type of method, e.g. "cat" in "cat::meow", as a string
+ *                 identifier via {@link NCDStringIndex}
+ * @param method_name_id method name identifier. Must have been previously returned
+ *                       by a successfull call of \link NCDMethodIndex_GetMethodNameId.
+ * @return module pointer, or NULL if no such method exists
+ */
+const struct NCDInterpModule * NCDMethodIndex_GetMethodModule (NCDMethodIndex *o, NCD_string_id_t obj_type, int method_name_id);
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDMethodIndex_hash.h b/external/badvpn_dns/ncd/NCDMethodIndex_hash.h
new file mode 100644
index 0000000..f1108cb
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDMethodIndex_hash.h
@@ -0,0 +1,12 @@
+#define CHASH_PARAM_NAME NCDMethodIndex__Hash
+#define CHASH_PARAM_ENTRY NCDMethodIndex__hashentry
+#define CHASH_PARAM_LINK int
+#define CHASH_PARAM_KEY NCDMethodIndex__hashkey
+#define CHASH_PARAM_ARG NCDMethodIndex__hasharg
+#define CHASH_PARAM_NULL ((int)-1)
+#define CHASH_PARAM_DEREF(arg, link) (&(arg)[(link)])
+#define CHASH_PARAM_ENTRYHASH(arg, entry) (badvpn_djb2_hash((const uint8_t *)(entry).ptr->method_name))
+#define CHASH_PARAM_KEYHASH(arg, key) (badvpn_djb2_hash((const uint8_t *)(key)))
+#define CHASH_PARAM_COMPARE_ENTRIES(arg, entry1, entry2) (!strcmp((entry1).ptr->method_name, (entry2).ptr->method_name))
+#define CHASH_PARAM_COMPARE_KEY_ENTRY(arg, key1, entry2) (!strcmp((key1), (entry2).ptr->method_name))
+#define CHASH_PARAM_ENTRY_NEXT hash_next
diff --git a/external/badvpn_dns/ncd/NCDModule.c b/external/badvpn_dns/ncd/NCDModule.c
new file mode 100644
index 0000000..da6894a
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDModule.c
@@ -0,0 +1,625 @@
+/**
+ * @file NCDModule.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stdarg.h>
+#include <string.h>
+#include <stddef.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#define STATE_DEAD 0
+#define STATE_DOWN_CLEAN 1
+#define STATE_UP 2
+#define STATE_DOWN_UNCLEAN 3
+#define STATE_DYING 4
+
+#define PROCESS_STATE_INIT 0
+#define PROCESS_STATE_DOWN 1
+#define PROCESS_STATE_UP 2
+#define PROCESS_STATE_DOWN_WAITING 3
+#define PROCESS_STATE_TERMINATING 4
+#define PROCESS_STATE_TERMINATED 5
+
+static int object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value);
+static int object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static int process_args_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value);
+static int process_arg_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value);
+
+static void frontend_event (NCDModuleInst *n, int event)
+{
+    n->params->func_event(n, event);
+}
+
+static void inst_assert_backend (NCDModuleInst *n)
+{
+    ASSERT(n->state == STATE_DOWN_UNCLEAN || n->state == STATE_DOWN_CLEAN ||
+           n->state == STATE_UP ||
+           n->state == STATE_DYING)
+}
+
+static void set_process_state (NCDModuleProcess *p, int state)
+{
+#ifndef NDEBUG
+    p->state = state;
+#endif
+}
+
+void NCDModuleInst_Init (NCDModuleInst *n, const struct NCDInterpModule *m, void *method_context, NCDValRef args, const struct NCDModuleInst_params *params)
+{
+    ASSERT(m)
+    ASSERT(m->module.func_new2)
+    ASSERT(m->module.alloc_size >= 0)
+    ASSERT(m->base_type_id >= 0)
+    ASSERT(m->group)
+    ASSERT(n->mem)
+    ASSERT(NCDVal_IsList(args))
+    ASSERT(params)
+    ASSERT(params->func_event)
+    ASSERT(params->func_getobj)
+    ASSERT(params->logfunc)
+    ASSERT(params->iparams)
+    ASSERT(params->iparams->func_initprocess)
+    ASSERT(params->iparams->func_interp_exit)
+    ASSERT(params->iparams->func_interp_getargs)
+    ASSERT(params->iparams->func_interp_getretrytime)
+    
+    // init arguments
+    n->m = m;
+    n->params = params;
+    
+    // set initial state
+    n->state = STATE_DOWN_CLEAN;
+    
+    // give NCDModuleInst to methods, not mem
+    n->pass_mem_to_methods = 0;
+    
+    DebugObject_Init(&n->d_obj);
+    
+    struct NCDModuleInst_new_params new_params;
+    new_params.method_user = method_context;
+    new_params.args = args;
+    
+    n->m->module.func_new2(n->mem, n, &new_params);
+}
+
+void NCDModuleInst_Free (NCDModuleInst *n)
+{
+    DebugObject_Free(&n->d_obj);
+    ASSERT(n->state == STATE_DEAD)
+}
+
+void NCDModuleInst_Die (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_UP || n->state == STATE_DOWN_CLEAN || n->state == STATE_DOWN_UNCLEAN)
+    
+    n->state = STATE_DYING;
+    
+    if (!n->m->module.func_die) {
+        NCDModuleInst_Backend_Dead(n);
+        return;
+    }
+    
+    n->m->module.func_die(n->mem);
+    return;
+}
+
+int NCDModuleInst_TryFree (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_UP || n->state == STATE_DOWN_CLEAN || n->state == STATE_DOWN_UNCLEAN)
+    
+    if (n->m->module.func_die) {
+        return 0;
+    }
+    
+    DebugObject_Free(&n->d_obj);
+    
+    return 1;
+}
+
+void NCDModuleInst_Clean (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_DOWN_CLEAN || n->state == STATE_DOWN_UNCLEAN)
+    
+    if (n->state == STATE_DOWN_UNCLEAN) {
+        n->state = STATE_DOWN_CLEAN;
+            
+        if (n->m->module.func_clean) {
+            n->m->module.func_clean(n->mem);
+            return;
+        }
+    }
+}
+
+NCDObject NCDModuleInst_Object (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->m->base_type_id >= 0)
+    
+    void *method_user = (n->pass_mem_to_methods ? n->mem : n);
+    
+    return NCDObject_BuildFull(n->m->base_type_id, n, 0, method_user, object_func_getvar, object_func_getobj);
+}
+
+void NCDModuleInst_Backend_PassMemToMethods (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_DOWN_UNCLEAN || n->state == STATE_DOWN_CLEAN ||
+           n->state == STATE_UP ||
+           n->state == STATE_DYING)
+    
+    n->pass_mem_to_methods = 1;
+}
+
+static int can_resolve (NCDModuleInst *n)
+{
+    switch (n->state) {
+        case STATE_UP:
+            return 1;
+        case STATE_DOWN_CLEAN:
+        case STATE_DOWN_UNCLEAN:
+            return !!(n->m->module.flags & NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN);
+        default:
+            return 0;
+    }
+}
+
+static int object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value)
+{
+    NCDModuleInst *n = NCDObject_DataPtr(obj);
+    DebugObject_Access(&n->d_obj);
+    
+    if ((!n->m->module.func_getvar && !n->m->module.func_getvar2) || !can_resolve(n)) {
+        return 0;
+    }
+    
+    int res;
+    if (n->m->module.func_getvar2) {
+        res = n->m->module.func_getvar2(n->mem, name, mem, out_value);
+    } else {
+        if (NCDStringIndex_HasNulls(n->params->iparams->string_index, name)) {
+            return 0;
+        }
+        const char *name_str = NCDStringIndex_Value(n->params->iparams->string_index, name);
+        res = n->m->module.func_getvar(n->mem, name_str, mem, out_value);
+    }
+    ASSERT(res == 0 || res == 1)
+    ASSERT(res == 0 || (NCDVal_Assert(*out_value), 1))
+    
+    return res;
+}
+
+static int object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    NCDModuleInst *n = NCDObject_DataPtr(obj);
+    DebugObject_Access(&n->d_obj);
+    
+    if (!n->m->module.func_getobj || !can_resolve(n)) {
+        return 0;
+    }
+    
+    int res = n->m->module.func_getobj(n->mem, name, out_object);
+    ASSERT(res == 0 || res == 1)
+    
+    return res;
+}
+
+void * NCDModuleInst_Backend_GetUser (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_DOWN_UNCLEAN || n->state == STATE_DOWN_CLEAN ||
+           n->state == STATE_UP ||
+           n->state == STATE_DYING)
+    
+    return n->mem;
+}
+
+void NCDModuleInst_Backend_Up (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_DOWN_CLEAN || n->state == STATE_DOWN_UNCLEAN)
+    
+    n->state = STATE_UP;
+    frontend_event(n, NCDMODULE_EVENT_UP);
+}
+
+void NCDModuleInst_Backend_Down (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_UP)
+    
+    n->state = STATE_DOWN_UNCLEAN;
+    frontend_event(n, NCDMODULE_EVENT_DOWN);
+}
+
+void NCDModuleInst_Backend_DownUp (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_UP)
+    
+    frontend_event(n, NCDMODULE_EVENT_DOWNUP);
+}
+
+void NCDModuleInst_Backend_Dead (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_DOWN_CLEAN || n->state == STATE_DOWN_UNCLEAN ||
+           n->state == STATE_UP || n->state == STATE_DYING)
+    
+    n->state = STATE_DEAD;
+    
+    frontend_event(n, NCDMODULE_EVENT_DEAD);
+    return;
+}
+
+void NCDModuleInst_Backend_DeadError (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_DOWN_CLEAN || n->state == STATE_DOWN_UNCLEAN ||
+           n->state == STATE_UP || n->state == STATE_DYING)
+    
+    n->state = STATE_DEAD;
+    
+    frontend_event(n, NCDMODULE_EVENT_DEADERROR);
+    return;
+}
+
+int NCDModuleInst_Backend_GetObj (NCDModuleInst *n, NCD_string_id_t name, NCDObject *out_object)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_DOWN_UNCLEAN || n->state == STATE_DOWN_CLEAN ||
+           n->state == STATE_UP ||
+           n->state == STATE_DYING)
+    ASSERT(out_object)
+    
+    int res = n->params->func_getobj(n, name, out_object);
+    ASSERT(res == 0 || res == 1)
+    
+    return res;
+}
+
+void NCDModuleInst_Backend_Log (NCDModuleInst *n, int channel, int level, const char *fmt, ...)
+{
+    DebugObject_Access(&n->d_obj);
+    
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_LogViaFuncVarArg(n->params->logfunc, n, channel, level, fmt, vl);
+    va_end(vl);
+}
+
+void NCDModuleInst_Backend_LogVarArg (NCDModuleInst *n, int channel, int level, const char *fmt, va_list vl)
+{
+    DebugObject_Access(&n->d_obj);
+    
+    BLog_LogViaFuncVarArg(n->params->logfunc, n, channel, level, fmt, vl);
+}
+
+BLogContext NCDModuleInst_Backend_LogContext (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    
+    return BLog_MakeContext(n->params->logfunc, n);
+}
+
+void NCDModuleInst_Backend_InterpExit (NCDModuleInst *n, int exit_code)
+{
+    DebugObject_Access(&n->d_obj);
+    inst_assert_backend(n);
+    
+    n->params->iparams->func_interp_exit(n->params->iparams->user, exit_code);
+}
+
+int NCDModuleInst_Backend_InterpGetArgs (NCDModuleInst *n, NCDValMem *mem, NCDValRef *out_value)
+{
+    DebugObject_Access(&n->d_obj);
+    inst_assert_backend(n);
+    ASSERT(mem)
+    ASSERT(out_value)
+    
+    int res = n->params->iparams->func_interp_getargs(n->params->iparams->user, mem, out_value);
+    ASSERT(res == 0 || res == 1)
+    ASSERT(res == 0 || (NCDVal_Assert(*out_value), 1))
+    
+    return res;
+}
+
+btime_t NCDModuleInst_Backend_InterpGetRetryTime (NCDModuleInst *n)
+{
+    DebugObject_Access(&n->d_obj);
+    inst_assert_backend(n);
+    
+    return n->params->iparams->func_interp_getretrytime(n->params->iparams->user);
+}
+
+int NCDModuleInst_Backend_InterpLoadGroup (NCDModuleInst *n, const struct NCDModuleGroup *group)
+{
+    DebugObject_Access(&n->d_obj);
+    inst_assert_backend(n);
+    ASSERT(group)
+    
+    return n->params->iparams->func_loadgroup(n->params->iparams->user, group);
+}
+
+int NCDModuleProcess_InitId (NCDModuleProcess *o, NCDModuleInst *n, NCD_string_id_t template_name, NCDValRef args, NCDModuleProcess_handler_event handler_event)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_DOWN_UNCLEAN || n->state == STATE_DOWN_CLEAN ||
+           n->state == STATE_UP ||
+           n->state == STATE_DYING)
+    ASSERT(template_name >= 0)
+    ASSERT(NCDVal_IsInvalid(args) || NCDVal_IsList(args))
+    ASSERT(handler_event)
+    
+    // init arguments
+    o->args = args;
+    o->handler_event = handler_event;
+    
+    // set no special functions
+    o->func_getspecialobj = NULL;
+    
+    // set state
+    set_process_state(o, PROCESS_STATE_INIT);
+    
+#ifndef NDEBUG
+    // clear interp functions so we can assert they were set
+    o->interp_func_event = NULL;
+    o->interp_func_getobj = NULL;
+#endif
+    
+    // init interpreter part
+    if (!(n->params->iparams->func_initprocess(n->params->iparams->user, o, template_name))) {
+        goto fail1;
+    }
+    
+    ASSERT(o->interp_func_event)
+    ASSERT(o->interp_func_getobj)
+    
+    // set state
+    set_process_state(o, PROCESS_STATE_DOWN);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    return 0;
+}
+
+int NCDModuleProcess_InitValue (NCDModuleProcess *o, NCDModuleInst *n, NCDValRef template_name, NCDValRef args, NCDModuleProcess_handler_event handler_event)
+{
+    DebugObject_Access(&n->d_obj);
+    ASSERT(n->state == STATE_DOWN_UNCLEAN || n->state == STATE_DOWN_CLEAN ||
+           n->state == STATE_UP ||
+           n->state == STATE_DYING)
+    ASSERT(NCDVal_IsString(template_name))
+    ASSERT(NCDVal_IsInvalid(args) || NCDVal_IsList(args))
+    ASSERT(handler_event)
+    
+    NCD_string_id_t template_name_id;
+    
+    if (NCDVal_IsIdString(template_name)) {
+        template_name_id = NCDVal_IdStringId(template_name);
+    } else {
+        NCDValContString cts;
+        if (!NCDVal_StringContinuize(template_name, &cts)) {
+            BLog(BLOG_ERROR, "NCDVal_StringContinuize failed");
+            return 0;
+        }
+        
+        template_name_id = NCDStringIndex_GetBin(n->params->iparams->string_index, cts.data, NCDVal_StringLength(template_name));
+        NCDValContString_Free(&cts);
+        if (template_name_id < 0) {
+            BLog(BLOG_ERROR, "NCDStringIndex_GetBin failed");
+            return 0;
+        }
+    }
+    
+    return NCDModuleProcess_InitId(o, n, template_name_id, args, handler_event);
+}
+
+void NCDModuleProcess_Free (NCDModuleProcess *o)
+{
+    DebugObject_Free(&o->d_obj);
+    ASSERT(o->state == PROCESS_STATE_TERMINATED)
+}
+
+void NCDModuleProcess_AssertFree (NCDModuleProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state == PROCESS_STATE_TERMINATED)
+}
+
+void NCDModuleProcess_SetSpecialFuncs (NCDModuleProcess *o, NCDModuleProcess_func_getspecialobj func_getspecialobj)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    o->func_getspecialobj = func_getspecialobj;
+}
+
+void NCDModuleProcess_Continue (NCDModuleProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state == PROCESS_STATE_DOWN_WAITING)
+    
+    set_process_state(o, PROCESS_STATE_DOWN);
+    
+    o->interp_func_event(o->interp_user, NCDMODULEPROCESS_INTERP_EVENT_CONTINUE);
+}
+
+void NCDModuleProcess_Terminate (NCDModuleProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state == PROCESS_STATE_DOWN || o->state == PROCESS_STATE_UP ||
+           o->state == PROCESS_STATE_DOWN_WAITING)
+    
+    set_process_state(o, PROCESS_STATE_TERMINATING);
+    
+    o->interp_func_event(o->interp_user, NCDMODULEPROCESS_INTERP_EVENT_TERMINATE);
+}
+
+int NCDModuleProcess_GetObj (NCDModuleProcess *o, NCD_string_id_t name, NCDObject *out_object)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state != PROCESS_STATE_INIT)
+    ASSERT(o->state != PROCESS_STATE_TERMINATED)
+    ASSERT(out_object)
+    
+    int res = o->interp_func_getobj(o->interp_user, name, out_object);
+    ASSERT(res == 0 || res == 1)
+    
+    return res;
+}
+
+static void process_assert_interp (NCDModuleProcess *o)
+{
+    // assert that the interpreter knows about the object, and we're not in init
+    ASSERT(o->state == PROCESS_STATE_DOWN || o->state == PROCESS_STATE_UP ||
+           o->state == PROCESS_STATE_DOWN_WAITING || o->state == PROCESS_STATE_TERMINATING)
+}
+
+void NCDModuleProcess_Interp_SetHandlers (NCDModuleProcess *o, void *interp_user,
+                                          NCDModuleProcess_interp_func_event interp_func_event,
+                                          NCDModuleProcess_interp_func_getobj interp_func_getobj)
+{
+    ASSERT(o->state == PROCESS_STATE_INIT)
+    ASSERT(interp_func_event)
+    ASSERT(interp_func_getobj)
+    
+    o->interp_user = interp_user;
+    o->interp_func_event = interp_func_event;
+    o->interp_func_getobj = interp_func_getobj;
+}
+
+void NCDModuleProcess_Interp_Up (NCDModuleProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    process_assert_interp(o);
+    ASSERT(o->state == PROCESS_STATE_DOWN)
+    
+    set_process_state(o, PROCESS_STATE_UP);
+    
+    o->handler_event(o, NCDMODULEPROCESS_EVENT_UP);
+    return;
+}
+
+void NCDModuleProcess_Interp_Down (NCDModuleProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    process_assert_interp(o);
+    ASSERT(o->state == PROCESS_STATE_UP)
+    
+    set_process_state(o, PROCESS_STATE_DOWN_WAITING);
+    
+    o->handler_event(o, NCDMODULEPROCESS_EVENT_DOWN);
+    return;
+}
+
+void NCDModuleProcess_Interp_Terminated (NCDModuleProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    process_assert_interp(o);
+    ASSERT(o->state == PROCESS_STATE_TERMINATING)
+    
+    set_process_state(o, PROCESS_STATE_TERMINATED);
+    
+    o->handler_event(o, NCDMODULEPROCESS_EVENT_TERMINATED);
+    return;
+}
+
+int NCDModuleProcess_Interp_GetSpecialObj (NCDModuleProcess *o, NCD_string_id_t name, NCDObject *out_object)
+{
+    DebugObject_Access(&o->d_obj);
+    process_assert_interp(o);
+    ASSERT(out_object)
+    
+    if (!NCDVal_IsInvalid(o->args)) {
+        if (name == NCD_STRING_ARGS) {
+            *out_object = NCDObject_Build(-1, o, process_args_object_func_getvar, NCDObject_no_getobj);
+            return 1;
+        }
+        
+        if (name >= NCD_STRING_ARG0 && name <= NCD_STRING_ARG19) {
+            int num = name - NCD_STRING_ARG0;
+            if (num < NCDVal_ListCount(o->args)) {
+                *out_object = NCDObject_BuildFull(-1, o, num, NULL, process_arg_object_func_getvar, NCDObject_no_getobj);
+                return 1;
+            }
+        }
+    }
+    
+    if (!o->func_getspecialobj) {
+        return 0;
+    }
+    
+    int res = o->func_getspecialobj(o, name, out_object);
+    ASSERT(res == 0 || res == 1)
+    
+    return res;
+}
+
+static int process_args_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value)
+{
+    NCDModuleProcess *o = NCDObject_DataPtr(obj);
+    DebugObject_Access(&o->d_obj);
+    process_assert_interp(o);
+    ASSERT(!NCDVal_IsInvalid(o->args))
+    
+    if (name != NCD_STRING_EMPTY) {
+        return 0;
+    }
+    
+    *out_value = NCDVal_NewCopy(mem, o->args);
+    if (NCDVal_IsInvalid(*out_value)) {
+        BLog_LogToChannel(BLOG_CHANNEL_NCDModuleProcess, BLOG_ERROR, "NCDVal_NewCopy failed");
+    }
+    return 1;
+}
+
+static int process_arg_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value)
+{
+    NCDModuleProcess *o = NCDObject_DataPtr(obj);
+    DebugObject_Access(&o->d_obj);
+    process_assert_interp(o);
+    ASSERT(!NCDVal_IsInvalid(o->args))
+    
+    if (name != NCD_STRING_EMPTY) {
+        return 0;
+    }
+    
+    *out_value = NCDVal_NewCopy(mem, NCDVal_ListGet(o->args, NCDObject_DataInt(obj)));
+    if (NCDVal_IsInvalid(*out_value)) {
+        BLog_LogToChannel(BLOG_CHANNEL_NCDModuleProcess, BLOG_ERROR, "NCDVal_NewCopy failed");
+    }
+    return 1;
+}
diff --git a/external/badvpn_dns/ncd/NCDModule.h b/external/badvpn_dns/ncd/NCDModule.h
new file mode 100644
index 0000000..c00fe90
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDModule.h
@@ -0,0 +1,1011 @@
+/**
+ * @file NCDModule.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCD_NCDMODULE_H
+#define BADVPN_NCD_NCDMODULE_H
+
+#include <stdarg.h>
+
+#include <misc/debug.h>
+#include <system/BReactor.h>
+#include <base/BLog.h>
+#include <ncd/NCDObject.h>
+#include <ncd/NCDStringIndex.h>
+
+#ifndef BADVPN_NO_PROCESS
+#include <system/BProcess.h>
+#endif
+#ifndef BADVPN_NO_UDEV
+#include <udevmonitor/NCDUdevManager.h>
+#endif
+#ifndef BADVPN_NO_RANDOM
+#include <random/BRandom2.h>
+#endif
+
+#define NCDMODULE_EVENT_UP 1
+#define NCDMODULE_EVENT_DOWN 2
+#define NCDMODULE_EVENT_DOWNUP 3
+#define NCDMODULE_EVENT_DEAD 4
+#define NCDMODULE_EVENT_DEADERROR 5
+
+struct NCDModuleInst_s;
+struct NCDModuleProcess_s;
+struct NCDModuleGroup;
+struct NCDInterpModule;
+struct NCDInterpModuleGroup;
+
+/**
+ * Function called to inform the interpeter of state changes of the
+ * module instance.
+ * Possible events are:
+ * 
+ * - NCDMODULE_EVENT_UP: the instance came up.
+ *   The instance was in down state.
+ *   The instance enters up state.
+ * 
+ * - NCDMODULE_EVENT_DOWN: the instance went down.
+ *   The instance was in up state.
+ *   The instance enters down state.
+ * 
+ *   After the instance goes down, the interpreter should eventually call
+ *   {@link NCDModuleInst_Clean} or {@link NCDModuleInst_Die}, unless
+ *   the module goes up again.
+ * 
+ * - NCDMODULE_EVENT_DEAD: the module died. To determine if the module
+ *   died with error, read the is_error member of {@link NCDModuleInst}.
+ *   The instance enters dead state.
+ * 
+ * This function is not being called in event context. The interpreter should
+ * only update its internal state, and visibly react only via jobs that it pushes
+ * from within this function. The only exception is that it may free the
+ * instance from within the NCDMODULE_EVENT_DEAD event.
+ * 
+ * @param inst the module instance
+ * @param event event number
+ */
+typedef void (*NCDModuleInst_func_event) (struct NCDModuleInst_s *inst, int event);
+
+/**
+ * Function called when the module instance wants the interpreter to
+ * resolve an object from the point of view of its statement.
+ * The instance will not be in dead state.
+ * This function must not have any side effects.
+ * 
+ * @param inst the module instance
+ * @param name name of the object as an {@link NCDStringIndex} identifier
+ * @param out_object the object will be returned here
+ * @return 1 on success, 0 on failure
+ */
+typedef int (*NCDModuleInst_func_getobj) (struct NCDModuleInst_s *inst, NCD_string_id_t name, NCDObject *out_object);
+
+/**
+ * Function called when the module instance wants the interpreter to
+ * create a new process backend from a process template.
+ * The instance will not be in dead state.
+ * 
+ * On success, the interpreter must have called {@link NCDModuleProcess_Interp_SetHandlers}
+ * from within this function, to allow communication with the controller of the process.
+ * On success, the new process backend enters down state.
+ * 
+ * This function is not being called in event context. The interpreter should
+ * only update its internal state, and visibly react only via jobs that it pushes
+ * from within this function.
+ * 
+ * @param user value of 'user' member of {@link NCDModuleInst_iparams}
+ * @param p handle for the new process backend
+ * @param template_name name of the template to create the process from,
+ *                      as an {@link NCDStringIndex} identifier
+ * @return 1 on success, 0 on failure
+ */
+typedef int (*NCDModuleInst_func_initprocess) (void *user, struct NCDModuleProcess_s *p, NCD_string_id_t template_name);
+
+/**
+ * Function called when the module instance wants the interpreter to
+ * initiate termination, as if it received an external terminatio request (signal).
+ * 
+ * @param user value of 'user' member of {@link NCDModuleInst_iparams}
+ * @param exit_code exit code to return the the operating system. This overrides any previously
+ *                  set exit code, and will be overriden by a signal to the value 1.
+ *   
+ */
+typedef void (*NCDModuleInst_func_interp_exit) (void *user, int exit_code);
+
+/**
+ * Function called when the module instance wants the interpreter to
+ * provide its extra command line arguments.
+ * 
+ * @param user value of 'user' member of {@link NCDModuleInst_iparams}
+ * @param mem value memory to use
+ * @param out_value write value reference here on success
+ * @return 1 if available, 0 if not available. If available, but out of memory, return 1
+ *         and an invalid value.
+ */
+typedef int (*NCDModuleInst_func_interp_getargs) (void *user, NCDValMem *mem, NCDValRef *out_value);
+
+/**
+ * Function called when the module instance wants the interpreter to
+ * provide its retry time.
+ * 
+ * @param user value of 'user' member of {@link NCDModuleInst_iparams}
+ * @return retry time in milliseconds
+ */
+typedef btime_t (*NCDModuleInst_func_interp_getretrytime) (void *user);
+
+/**
+ * Function called when the module instance wants the interpreter to
+ * load a new module group.
+ * 
+ * @param user value of 'user' member of {@link NCDModuleInst_iparams}
+ * @param group module group to load
+ * @return 1 on success, 0 on failure
+ */
+typedef int (*NCDModuleInst_func_interp_loadgroup) (void *user, const struct NCDModuleGroup *group);
+
+#define NCDMODULEPROCESS_EVENT_UP 1
+#define NCDMODULEPROCESS_EVENT_DOWN 2
+#define NCDMODULEPROCESS_EVENT_TERMINATED 3
+
+/**
+ * Handler which reports process state changes from the interpreter.
+ * Possible events are:
+ * 
+ * - NCDMODULEPROCESS_EVENT_UP: the process went up.
+ *   The process was in down state.
+ *   The process enters up state.
+ * 
+ * - NCDMODULEPROCESS_EVENT_DOWN: the process went down.
+ *   The process was in up state.
+ *   The process enters waiting state.
+ * 
+ *   NOTE: the process enters waiting state, NOT down state, and is paused.
+ *   To allow the process to continue, call {@link NCDModuleProcess_Continue}.
+ * 
+ * - NCDMODULEPROCESS_EVENT_TERMINATED: the process terminated.
+ *   The process was in terminating state.
+ *   The process enters terminated state.
+ * 
+ * @param user pointer to the process. Use {@link UPPER_OBJECT} to retrieve the pointer
+ *             to the containing struct.
+ * @param event event number
+ */
+typedef void (*NCDModuleProcess_handler_event) (struct NCDModuleProcess_s *process, int event);
+
+/**
+ * Function called when the interpreter wants to resolve a special
+ * object in the process.
+ * This function must have no side effects.
+ * 
+ * @param user pointer to the process. Use {@link UPPER_OBJECT} to retrieve the pointer
+ *             to the containing struct.
+ * @param name name of the object as an {@link NCDStringIndex} identifier
+ * @param out_object the object will be returned here
+ * @return 1 on success, 0 on failure
+ */
+typedef int (*NCDModuleProcess_func_getspecialobj) (struct NCDModuleProcess_s *process, NCD_string_id_t name, NCDObject *out_object);
+
+#define NCDMODULEPROCESS_INTERP_EVENT_CONTINUE 1
+#define NCDMODULEPROCESS_INTERP_EVENT_TERMINATE 2
+
+/**
+ * Function called to report process backend requests to the interpreter.
+ * Possible events are:
+ * 
+ * - NCDMODULEPROCESS_INTERP_EVENT_CONTINUE: the process can continue.
+ *   The process backend was in waiting state.
+ *   The process backend enters down state.
+ * 
+ * - NCDMODULEPROCESS_INTERP_EVENT_TERMINATE: the process should terminate.
+ *   The process backend was in down, up or waiting state.
+ *   The process backend enters terminating state.
+ * 
+ *   The interpreter should call {@link NCDModuleProcess_Interp_Terminated}
+ *   when the process terminates.
+ * 
+ * This function is not being called in event context. The interpreter should
+ * only update its internal state, and visibly react only via jobs that it pushes
+ * from within this function.
+ * 
+ * @param user as in {@link NCDModuleProcess_Interp_SetHandlers}
+ * @param event event number
+ */
+typedef void (*NCDModuleProcess_interp_func_event) (void *user, int event);
+
+/**
+ * Function called to have the interpreter resolve an object within the process
+ * of a process backend.
+ * This function must not have any side effects.
+ * 
+ * @param user as in {@link NCDModuleProcess_Interp_SetHandlers}
+ * @param name name of the object as an {@link NCDStringIndex} identifier
+ * @param out_object the object will be returned here
+ * @return 1 on success, 0 in failure
+ */
+typedef int (*NCDModuleProcess_interp_func_getobj) (void *user, NCD_string_id_t name, NCDObject *out_object);
+
+struct NCDModule;
+
+/**
+ * Contains parameters to the module initialization function
+ * ({@link NCDModule_func_new2}) that are passed indirectly.
+ */
+struct NCDModuleInst_new_params {
+    /**
+     * A reference to the argument list for the module instance.
+     * The reference remains valid as long as the backend instance
+     * exists. Unless the module has the NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+     * flag set, it is guaranteed that any strings within the arguments will be
+     * some kind of ContinuousString.
+     */
+    NCDValRef args;
+    
+    /**
+     * If the module instance corresponds to a method-like statement,
+     * this pointer identifies the object it is being invoked with.
+     * If the object is a statement (i.e. a {@link NCDModuleInst}), then this
+     * will be the NCDModuleInst pointer, and {@link NCDModuleInst_Backend_GetUser}
+     * can be called on this to retrieve the pointer to preallocated memory for
+     * the backend instance; *unless* {@link NCDModuleInst_Backend_PassMemToMethods}
+     * was called for the object on which the method is being called, in which case
+     * this will directly point to the preallocated memory.
+     * On the other hand, if this is a method on an internal object built using
+     * only {@link NCDObject_Build} or {@link NCDObject_BuildFull},
+     * this pointer will be whatever was passed as the "data_ptr" argument, for the
+     * first function, and as "method_user", for the latter function.
+     */
+    void *method_user;
+};
+
+/**
+ * Contains parameters to {@link NCDModuleInst_Init} that are passed indirectly.
+ * This itself only contains parameters related to communication between the
+ * backend and the creator of the module instance; other parameters are passed
+ * via the iparams member;
+ */
+struct NCDModuleInst_params {
+    /**
+     * Callback to report state changes.
+     */
+    NCDModuleInst_func_event func_event;
+    /**
+     * Callback to resolve objects from the viewpoint of the instance.
+     */
+    NCDModuleInst_func_getobj func_getobj;
+    /**
+     * Log function which appends a log prefix with {@link BLog_Append}.
+     */
+    BLog_logfunc logfunc;
+    /**
+     * Pointer to an {@link NCDModuleInst_iparams} structure, which exposes
+     * services provided by the interpreter.
+     */
+    const struct NCDModuleInst_iparams *iparams;
+};
+
+/**
+ * Contains parameters to {@link NCDModuleInst_Init} that are passed indirectly.
+ * This only contains parameters related to services provided by the interpreter.
+ */
+struct NCDModuleInst_iparams {
+    /**
+     * Reactor we live in.
+     */
+    BReactor *reactor;
+#ifndef BADVPN_NO_PROCESS
+    /**
+     * Process manager.
+     */
+    BProcessManager *manager;
+#endif
+#ifndef BADVPN_NO_UDEV
+    /**
+     * Udev manager.
+     */
+    NCDUdevManager *umanager;
+#endif
+#ifndef BADVPN_NO_RANDOM
+    /**
+     * Random number generator.
+     */
+    BRandom2 *random2;
+#endif
+    /**
+     * String index which keeps a mapping between strings and string identifiers.
+     */
+    NCDStringIndex *string_index;
+    /**
+     * Pointer passed to the interpreter callbacks below, for state keeping.
+     */
+    void *user;
+    /**
+     * Callback to create a new template process.
+     */
+    NCDModuleInst_func_initprocess func_initprocess;
+    /**
+     * Callback to request interpreter termination.
+     */
+    NCDModuleInst_func_interp_exit func_interp_exit;
+    /**
+     * Callback to get extra command line arguments.
+     */
+    NCDModuleInst_func_interp_getargs func_interp_getargs;
+    /**
+     * Callback to get retry time.
+     */
+    NCDModuleInst_func_interp_getretrytime func_interp_getretrytime;
+    /**
+     * Callback to load a module group.
+     */
+    NCDModuleInst_func_interp_loadgroup func_loadgroup;
+};
+
+/**
+ * Module instance.
+ * The module instance is initialized by the interpreter by calling
+ * {@link NCDModuleInst_Init}. It is implemented by a module backend
+ * specified in a {@link NCDModule}.
+ */
+typedef struct NCDModuleInst_s {
+    const struct NCDInterpModule *m;
+    const struct NCDModuleInst_params *params;
+    void *mem; // not modified by NCDModuleInst (but passed to module)
+    unsigned int state:3;
+    unsigned int pass_mem_to_methods:1;
+    unsigned int istate:3; // untouched by NCDModuleInst
+    DebugObject d_obj;
+} NCDModuleInst;
+
+/**
+ * Process created from a process template on behalf of a module backend
+ * instance, implemented by the interpreter.
+ */
+typedef struct NCDModuleProcess_s {
+    NCDValRef args;
+    NCDModuleProcess_handler_event handler_event;
+    NCDModuleProcess_func_getspecialobj func_getspecialobj;
+    void *interp_user;
+    NCDModuleProcess_interp_func_event interp_func_event;
+    NCDModuleProcess_interp_func_getobj interp_func_getobj;
+#ifndef NDEBUG
+    int state;
+#endif
+    DebugObject d_obj;
+} NCDModuleProcess;
+
+/**
+ * Initializes an instance of an NCD module.
+ * The instance is initialized in down state.
+ * WARNING: this directly calls the module backend; expect to be called back
+ * 
+ * This and other non-Backend methods are the interpreter interface.
+ * The Backend methods are the module backend interface and are documented
+ * independently with their own logical states.
+ * 
+ * NOTE: the instance structure \a n should have the member 'mem' initialized
+ * to point to preallocated memory for the statement. This memory must be
+ * at least m->prealloc_size big and must be properly aligned for any object.
+ * The 'mem' pointer is never modified by NCDModuleInst, so that the interpreter
+ * can use it as outside the lifetime of NCDModuleInst.
+ * 
+ * @param n the instance
+ * @param m pointer to the {@link NCDInterpModule} structure representing the module
+ *          to be instantiated
+ * @param method_context a context pointer passed to the module backend, applicable to method-like
+ *                       statements only. This should be set to the 'user' member of the
+ *                       {@link NCDObject} which represents the base object for the method.
+ *                       The caller must ensure that the NCDObject that was used is of the type
+ *                       expected by the module being instanciated.
+ * @param args arguments to the module. Must be a list value. Must be available and unchanged
+ *             as long as the instance exists. Unless the module has the
+ *             NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS flag set, any strings within the
+ *             arguments must be some kind of ContinuousString. This can be ensured by calling
+ *             {@link NCDValMem_ConvertNonContinuousStrings}.
+ * @param user argument to callback functions
+ * @param params more parameters, see {@link NCDModuleInst_params}
+ */
+void NCDModuleInst_Init (NCDModuleInst *n, const struct NCDInterpModule *m, void *method_context, NCDValRef args, const struct NCDModuleInst_params *params);
+
+/**
+ * Frees the instance.
+ * The instance must be in dead state.
+ * 
+ * @param n the instance
+ */
+void NCDModuleInst_Free (NCDModuleInst *n);
+
+/**
+ * Requests the instance to die.
+ * The instance must be in down or up state.
+ * The instance enters dying state.
+ * WARNING: this directly calls the module backend; expect to be called back
+ * 
+ * @param n the instance
+ */
+void NCDModuleInst_Die (NCDModuleInst *n);
+
+/**
+ * Attempts to destroy the instance immediately.
+ * This function can be used to optimize destroying instances of modules which
+ * don't specify any {@link NCDModule_func_die} handler. If immediate destruction
+ * is not possible, this does nothing and returns 0; {@link NCDModuleInst_Die}
+ * should be used to destroy the instance instead. If however immediate destruction
+ * is possible, this destroys the module instance and returns 1; {@link NCDModuleInst_Free}
+ * must not be called after that.
+ * The instance must be in down or up state, as for {@link NCDModuleInst_Die}.
+ * 
+ * @param n the instance
+ * @return 1 if destruction was performed, 0 if not
+ */
+int NCDModuleInst_TryFree (NCDModuleInst *n);
+
+/**
+ * Informs the module that it is in a clean state to proceed.
+ * The instance must be in down state.
+ * WARNING: this directly calls the module backend; expect to be called back
+ * 
+ * @param n the instance
+ */
+void NCDModuleInst_Clean (NCDModuleInst *n);
+
+/**
+ * Returns an {@link NCDObject} which can be used to resolve variables and objects
+ * within this instance, as well as call its methods. The resulting object may only
+ * be used immediately, and becomes invalid when the instance is freed.
+ * 
+ * @param n the instance
+ * @return an NCDObject for this instance
+ */
+NCDObject NCDModuleInst_Object (NCDModuleInst *n);
+
+/**
+ * If this is called, any methods called on this object will receive the preallocated
+ * memory pointer as the object state pointer. This means that in the
+ * {@link NCDModule_func_getvar2} function which is called when a method is created,
+ * the preallocated memory should be accessed as params->method_user.
+ * By default, however, params->method_user points to the NCDModuleInst of the base
+ * object, and {@link NCDModuleInst_Backend_GetUser} is needed to retrieve the
+ * preallocated memory pointer.
+ */
+void NCDModuleInst_Backend_PassMemToMethods (NCDModuleInst *n);
+
+/**
+ * Retuns the state pointer passed to handlers of a module backend instance;
+ * see {@link NCDModule_func_new2}.
+ * 
+ * @param n backend instance handle
+ * @return argument passed to handlers
+ */
+void * NCDModuleInst_Backend_GetUser (NCDModuleInst *n);
+
+/**
+ * Puts the backend instance into up state.
+ * The instance must be in down state.
+ * The instance enters up state.
+ * 
+ * @param n backend instance handle
+ */
+void NCDModuleInst_Backend_Up (NCDModuleInst *n);
+
+/**
+ * Puts the backend instance into down state.
+ * The instance must be in up state.
+ * The instance enters down state.
+ * 
+ * @param n backend instance handle
+ */
+void NCDModuleInst_Backend_Down (NCDModuleInst *n);
+
+/**
+ * Puts the backend instance into down state, then immediatly back into the up state.
+ * This effectively causes the interpreter to start backtracking to this statement.
+ * The instance must be in up state, and remains in up state.
+ * 
+ * @param n backend instance handle
+ */
+void NCDModuleInst_Backend_DownUp (NCDModuleInst *n);
+
+/**
+ * Destroys the backend instance.
+ * The backend instance handle becomes invalid and must not be used from
+ * the backend any longer.
+ * 
+ * @param n backend instance handle
+ */
+void NCDModuleInst_Backend_Dead (NCDModuleInst *n);
+
+/**
+ * Like {@link NCDModuleInst_Backend_Dead}, but also reports an error condition
+ * to the interpreter.
+ */
+void NCDModuleInst_Backend_DeadError (NCDModuleInst *n);
+
+/**
+ * Resolves an object for a backend instance, from the point of the instance's
+ * statement in the containing process.
+ * 
+ * @param n backend instance handle
+ * @param name name of the object to resolve as an {@link NCDStringIndex} identifier
+ * @param out_object the object will be returned here
+ * @return 1 on success, 0 on failure
+ */
+int NCDModuleInst_Backend_GetObj (NCDModuleInst *n, NCD_string_id_t name, NCDObject *out_object) WARN_UNUSED;
+
+/**
+ * Logs a backend instance message.
+ * 
+ * @param n backend instance handle
+ * @param channel log channel
+ * @param level loglevel
+ * @param fmt format string as in printf, arguments follow
+ */
+void NCDModuleInst_Backend_Log (NCDModuleInst *n, int channel, int level, const char *fmt, ...);
+
+/**
+ * Like {@link NCDModuleInst_Backend_Log}, but the extra arguments are passed
+ * as a va_list. This allows creation of logging wrappers.
+ */
+void NCDModuleInst_Backend_LogVarArg (NCDModuleInst *n, int channel, int level, const char *fmt, va_list vl);
+
+/**
+ * Returns a logging context. The context is valid until the backend dies.
+ */
+BLogContext NCDModuleInst_Backend_LogContext (NCDModuleInst *n);
+
+/**
+ * Initiates interpreter termination.
+ * 
+ * @param n backend instance handle
+ * @param exit_code exit code to return to the operating system. This overrides
+ *                  any previously set exit code, and will be overriden by a
+ *                  termination signal to the value 1.
+ */
+void NCDModuleInst_Backend_InterpExit (NCDModuleInst *n, int exit_code);
+
+/**
+ * Retrieves extra command line arguments passed to the interpreter.
+ * 
+ * @param n backend instance handle
+ * @param mem value memory to use
+ * @param out_value the arguments will be written here on success as a list value
+ * @return 1 if available, 0 if not available. If available, but out of memory, returns 1
+ *         and an invalid value.
+ */
+int NCDModuleInst_Backend_InterpGetArgs (NCDModuleInst *n, NCDValMem *mem, NCDValRef *out_value);
+
+/**
+ * Returns the retry time of the intepreter.
+ * 
+ * @param n backend instance handle
+ * @return retry time in milliseconds
+ */
+btime_t NCDModuleInst_Backend_InterpGetRetryTime (NCDModuleInst *n);
+
+/**
+ * Loads a module group into the interpreter.
+ * 
+ * @param n backend instance handle
+ * @param group module group to load
+ * @return 1 on success, 0 on failure
+ */
+int NCDModuleInst_Backend_InterpLoadGroup (NCDModuleInst *n, const struct NCDModuleGroup *group);
+
+/**
+ * Initializes a process in the interpreter from a process template.
+ * This must be called on behalf of a module backend instance.
+ * The process is initializes in down state.
+ * 
+ * @param o the process
+ * @param n backend instance whose interpreter will be providing the process
+ * @param template_name name of the process template as an {@link NCDStringIndex} identifier
+ * @param args arguments to the process. Must be an invalid value or a list value.
+ *             The value must be available and unchanged while the process exists.
+ * @param handler_event handler which reports events about the process from the
+ *                      interpreter
+ * @return 1 on success, 0 on failure
+ */
+int NCDModuleProcess_InitId (NCDModuleProcess *o, NCDModuleInst *n, NCD_string_id_t template_name, NCDValRef args, NCDModuleProcess_handler_event handler_event) WARN_UNUSED;
+
+/**
+ * Wrapper around {@link NCDModuleProcess_InitId} which takes the template name as an
+ * {@link NCDValRef}, which must point to a string value.
+ */
+int NCDModuleProcess_InitValue (NCDModuleProcess *o, NCDModuleInst *n, NCDValRef template_name, NCDValRef args, NCDModuleProcess_handler_event handler_event) WARN_UNUSED;
+
+/**
+ * Frees the process.
+ * The process must be in terminated state.
+ * 
+ * @param o the process
+ */
+void NCDModuleProcess_Free (NCDModuleProcess *o);
+
+/**
+ * Does nothing.
+ * The process must be in terminated state.
+ * 
+ * @param o the process
+ */
+void NCDModuleProcess_AssertFree (NCDModuleProcess *o);
+
+/**
+ * Sets callback functions for providing special objects within the process.
+ * 
+ * @param o the process
+ * @param func_getspecialobj function for resolving special objects, or NULL
+ */
+void NCDModuleProcess_SetSpecialFuncs (NCDModuleProcess *o, NCDModuleProcess_func_getspecialobj func_getspecialobj);
+
+/**
+ * Continues the process after the process went down.
+ * The process must be in waiting state.
+ * The process enters down state.
+ * 
+ * @param o the process
+ */
+void NCDModuleProcess_Continue (NCDModuleProcess *o);
+
+/**
+ * Requests the process to terminate.
+ * The process must be in down, up or waiting state.
+ * The process enters terminating state.
+ * 
+ * @param o the process
+ */
+void NCDModuleProcess_Terminate (NCDModuleProcess *o);
+
+/**
+ * Resolves an object within the process from the point
+ * at the end of the process.
+ * This function has no side effects.
+ * 
+ * @param o the process
+ * @param name name of the object to resolve as an {@link NCDStringIndex} identifier
+ * @param out_object the object will be returned here
+ * @return 1 on success, 0 on failure
+ */
+int NCDModuleProcess_GetObj (NCDModuleProcess *o, NCD_string_id_t name, NCDObject *out_object) WARN_UNUSED;
+
+/**
+ * Sets callback functions for the interpreter to implement the
+ * process backend.
+ * Must be called from within {@link NCDModuleInst_func_initprocess}
+ * if success is to be reported there.
+ * 
+ * @param o process backend handle, as in {@link NCDModuleInst_func_initprocess}
+ * @param interp_user argument to callback functions
+ * @param interp_func_event function for reporting continue/terminate requests
+ * @param interp_func_getobj function for resolving objects within the process
+ */
+void NCDModuleProcess_Interp_SetHandlers (NCDModuleProcess *o, void *interp_user,
+                                          NCDModuleProcess_interp_func_event interp_func_event,
+                                          NCDModuleProcess_interp_func_getobj interp_func_getobj);
+
+/**
+ * Reports the process backend as up.
+ * The process backend must be in down state.
+ * The process backend enters up state.
+ * WARNING: this directly calls the process creator; expect to be called back
+ * 
+ * @param o process backend handle
+ */
+void NCDModuleProcess_Interp_Up (NCDModuleProcess *o);
+
+/**
+ * Reports the process backend as down.
+ * The process backend must be in up state.
+ * The process backend enters waiting state.
+ * WARNING: this directly calls the process creator; expect to be called back
+ * 
+ * NOTE: the backend enters waiting state, NOT down state. The interpreter should
+ * pause the process until {@link NCDModuleProcess_interp_func_event} reports
+ * NCDMODULEPROCESS_INTERP_EVENT_CONTINUE, unless termination is requested via
+ * NCDMODULEPROCESS_INTERP_EVENT_TERMINATE.
+ * 
+ * @param o process backend handle
+ */
+void NCDModuleProcess_Interp_Down (NCDModuleProcess *o);
+
+/**
+ * Reports termination of the process backend.
+ * The process backend must be in terminating state.
+ * The process backend handle becomes invalid and must not be used
+ * by the interpreter any longer.
+ * WARNING: this directly calls the process creator; expect to be called back
+ * 
+ * @param o process backend handle
+ */
+void NCDModuleProcess_Interp_Terminated (NCDModuleProcess *o);
+
+/**
+ * Resolves a special process object for the process backend.
+ * 
+ * @param o process backend handle
+ * @param name name of the object as an {@link NCDStringIndex} identifier
+ * @param out_object the object will be returned here
+ * @return 1 on success, 0 on failure
+ */
+int NCDModuleProcess_Interp_GetSpecialObj (NCDModuleProcess *o, NCD_string_id_t name, NCDObject *out_object) WARN_UNUSED;
+
+/**
+ * Function called before any instance of any backend in a module
+ * group is created;
+ * 
+ * @param params structure containing global resources, such as
+ *               {@link BReactor}, {@link BProcessManager} and {@link NCDUdevManager}.
+ * @return 1 on success, 0 on failure
+ */
+typedef int (*NCDModule_func_globalinit) (struct NCDInterpModuleGroup *group, const struct NCDModuleInst_iparams *params);
+
+/**
+ * Function called to clean up after {@link NCDModule_func_globalinit} and modules
+ * in a module group.
+ * There are no backend instances alive from this module group.
+ */ 
+typedef void (*NCDModule_func_globalfree) (struct NCDInterpModuleGroup *group);
+
+/**
+ * Handler called to create an new module backend instance.
+ * The backend is initialized in down state.
+ * 
+ * If the backend fails initialization, this function should report the backend
+ * instance to have died with error by calling {@link NCDModuleInst_Backend_DeadError}.
+ * 
+ * @param o if the module specifies a positive alloc_size value in the {@link NCDModule}
+ *          structure, this will point to the allocated memory that can be used by the
+ *          module instance while it exists. If the alloc_size is 0 (default), this may or
+ *          may not be NULL.
+ * @param i module backend instance handler. The backend may only use this handle via
+ *          the Backend functions of {@link NCDModuleInst}.
+ */
+typedef void (*NCDModule_func_new2) (void *o, NCDModuleInst *i, const struct NCDModuleInst_new_params *params);
+
+/**
+ * Handler called to request termination of a backend instance.
+ * The backend instance was in down or up state.
+ * The backend instance enters dying state.
+ * 
+ * @param o state pointer, as in {@link NCDModule_func_new2}
+ */
+typedef void (*NCDModule_func_die) (void *o);
+
+/**
+ * Function called to resolve a variable within a backend instance.
+ * The backend instance is in up state, or in up or down state if can_resolve_when_down=1.
+ * This function must not have any side effects.
+ * 
+ * @param o state pointer, as in {@link NCDModule_func_new2}
+ * @param name name of the variable to resolve
+ * @param mem value memory to use
+ * @param out on success, the backend should initialize the value here
+ * @return 1 if exists, 0 if not exists. If exists, but out of memory, return 1
+ *         and an invalid value.
+ */
+typedef int (*NCDModule_func_getvar) (void *o, const char *name, NCDValMem *mem, NCDValRef *out);
+
+/**
+ * Function called to resolve a variable within a backend instance.
+ * The backend instance is in up state, or in up or down state if can_resolve_when_down=1.
+ * This function must not have any side effects.
+ * 
+ * @param o state pointer, as in {@link NCDModule_func_new2}
+ * @param name name of the variable to resolve as an {@link NCDStringIndex} identifier
+ * @param mem value memory to use
+ * @param out on success, the backend should initialize the value here
+ * @return 1 if exists, 0 if not exists. If exists, but out of memory, return 1
+ *         and an invalid value.
+ */
+typedef int (*NCDModule_func_getvar2) (void *o, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out);
+
+/**
+ * Function called to resolve an object within a backend instance.
+ * The backend instance is in up state, or in up or down state if can_resolve_when_down=1.
+ * This function must not have any side effects.
+ * 
+ * @param o state pointer, as in {@link NCDModule_func_new2}
+ * @param name name of the object to resolve as an {@link NCDStringIndex} identifier
+ * @param out_object the object will be returned here
+ * @return 1 on success, 0 on failure
+ */
+typedef int (*NCDModule_func_getobj) (void *o, NCD_string_id_t name, NCDObject *out_object);
+
+/**
+ * Handler called when the module instance is in a clean state.
+ * This means that all statements preceding it in the process are
+ * up, this statement is down, and all following statements are
+ * uninitialized. When a backend instance goes down, it is guaranteed,
+ * as long as it stays down, that either this will be called or
+ * termination will be requested with {@link NCDModule_func_die}.
+ * The backend instance was in down state.
+ * 
+ * @param o state pointer, as in {@link NCDModule_func_new2}
+ */
+typedef void (*NCDModule_func_clean) (void *o);
+
+#define NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN (1 << 0)
+#define NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS (1 << 1)
+
+/**
+ * Structure encapsulating the implementation of a module backend.
+ */
+struct NCDModule {
+    /**
+     * If this implements a plain statement, the name of the statement.
+     * If this implements a method, then "base_type::method_name".
+     */
+    const char *type;
+    
+    /**
+     * The base type for methods operating on instances of this backend.
+     * Any module with type of form "base_type::method_name" is considered
+     * a method of instances of this backend.
+     * If this is NULL, the base type will default to type.
+     */
+    const char *base_type;
+    
+    /**
+     * Function called to create an new backend instance.
+     */
+    NCDModule_func_new2 func_new2;
+    
+    /**
+     * Function called to request termination of a backend instance.
+     * May be NULL, in which case the default is to call NCDModuleInst_Backend_Dead().
+     */
+    NCDModule_func_die func_die;
+    
+    /**
+     * Function called to resolve a variable within the backend instance.
+     * May be NULL.
+     */
+    NCDModule_func_getvar func_getvar;
+    
+    /**
+     * Function called to resolve a variable within the backend instance.
+     * May be NULL.
+     */
+    NCDModule_func_getvar2 func_getvar2;
+    
+    /**
+     * Function called to resolve an object within the backend instance.
+     * May be NULL.
+     */
+    NCDModule_func_getobj func_getobj;
+    
+    /**
+     * Function called when the backend instance is in a clean state.
+     * May be NULL.
+     */
+    NCDModule_func_clean func_clean;
+    
+    /**
+     * Various flags.
+     * 
+     * - NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN
+     *   Whether the interpreter is allowed to call func_getvar and func_getobj
+     *   even when the backend instance is in down state (as opposed to just
+     *   in up state.
+     * 
+     * - NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+     *   If not set, strings within arguments which are not some kind of ContinuousString
+     *   will be converted to some kind of ContinuousString before the module's init
+     *   function is called. If set, they will not be, and the module must work with any
+     *   kind of strings (i.e. {@link NCDVal_StringData} may not be allowed).
+     */
+    int flags;
+    
+    /**
+     * The amount of memory to preallocate for each module instance.
+     * Preallocation can be used to avoid having to allocate memory from
+     * module initialization. The memory can be accessed via the first
+     * argument to {@link NCDModule_func_new2} and other calls.
+     */
+    int alloc_size;
+};
+
+/**
+ * Structure encapsulating a group of module backend implementations,
+ * with global init and free functions.
+ */
+struct NCDModuleGroup {
+    /**
+     * Function called before any instance of any module backend in this
+     * group is crated. May be NULL.
+     */
+    NCDModule_func_globalinit func_globalinit;
+    
+    /**
+     * Function called to clean up after {@link NCDModule_func_globalinit}.
+     * May be NULL.
+     */
+    NCDModule_func_globalfree func_globalfree;
+    
+    /**
+     * Array of module backends. The array must be terminated with a
+     * structure that has a NULL type member.
+     */
+    const struct NCDModule *modules;
+    
+    /**
+     * A pointer to an array of strings which will be mapped to
+     * {@link NCDStringIndex} string identifiers for the module to use.
+     * The array must be terminated by NULL. The resulting string
+     * identifiers will be available in the 'strings' member in
+     * {@link NCDInterpModuleGroup}.
+     */
+    const char *const*strings;
+};
+
+/**
+ * Represents an {@link NCDModule} within an interpreter.
+ * This structure is initialized by the interpreter when it loads a module group.
+ */
+struct NCDInterpModule {
+    /**
+     * A copy of the original NCDModule structure,
+     */
+    struct NCDModule module;
+    
+    /**
+     * The string identifier of this module's base_type (or type if base_type is
+     * not specified) according to {@link NCDStringIndex}.
+     */
+    NCD_string_id_t base_type_id;
+    
+    /**
+     * A pointer to the {@link NCDInterpModuleGroup} representing the group
+     * this module belongs to.
+     */
+    struct NCDInterpModuleGroup *group;
+};
+
+/**
+ * Represents an {@link NCDModuleGroup} within an interpreter.
+ * This structure is initialized by the interpreter when it loads a module group.
+ */
+struct NCDInterpModuleGroup {
+    /**
+     * A copy of the original NCDModuleGroup structure.
+     */
+    struct NCDModuleGroup group;
+    
+    /**
+     * An array of string identifiers corresponding to the strings
+     * in the 'strings' member of NCDModuleGroup. May be NULL if there
+     * are no strings in the NCDModuleGroup.
+     */
+    NCD_string_id_t *strings;
+    
+    /**
+     * Pointer which allows the module to keep private interpreter-wide state.
+     * This can be freely modified by the module; the interpeter will not
+     * read or write it.
+     */
+    void *group_state;
+};
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDModuleIndex.c b/external/badvpn_dns/ncd/NCDModuleIndex.c
new file mode 100644
index 0000000..12ef48a
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDModuleIndex.c
@@ -0,0 +1,372 @@
+/**
+ * @file NCDModuleIndex.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/offset.h>
+#include <misc/balloc.h>
+#include <misc/bsize.h>
+#include <misc/hashfun.h>
+#include <misc/compare.h>
+#include <misc/substring.h>
+#include <base/BLog.h>
+
+#include "NCDModuleIndex.h"
+
+#include <generated/blog_channel_NCDModuleIndex.h>
+
+#include "NCDModuleIndex_mhash.h"
+#include <structure/CHash_impl.h>
+
+static int string_pointer_comparator (void *user, void *v1, void *v2)
+{
+    const char **s1 = v1;
+    const char **s2 = v2;
+    int cmp = strcmp(*s1, *s2);
+    return B_COMPARE(cmp, 0);
+}
+
+static struct NCDModuleIndex_module * find_module (NCDModuleIndex *o, const char *type)
+{
+    NCDModuleIndex__MHashRef ref = NCDModuleIndex__MHash_Lookup(&o->modules_hash, 0, type);
+    ASSERT(!ref.link || !strcmp(ref.link->imodule.module.type, type))
+    return ref.link;
+}
+
+#ifndef NDEBUG
+static struct NCDModuleIndex_base_type * find_base_type (NCDModuleIndex *o, const char *base_type)
+{
+    BAVLNode *node = BAVL_LookupExact(&o->base_types_tree, &base_type);
+    if (!node) {
+        return NULL;
+    }
+    
+    struct NCDModuleIndex_base_type *bt = UPPER_OBJECT(node, struct NCDModuleIndex_base_type, base_types_tree_node);
+    ASSERT(!strcmp(bt->base_type, base_type))
+    
+    return bt;
+}
+#endif
+
+static int add_method (const char *type, const struct NCDInterpModule *module, NCDMethodIndex *method_index, int *out_method_id)
+{
+    ASSERT(type)
+    ASSERT(module)
+    ASSERT(method_index)
+    ASSERT(out_method_id)
+    
+    const char search[] = "::";
+    size_t search_len = sizeof(search) - 1;
+    
+    size_t table[sizeof(search) - 1];
+    build_substring_backtrack_table_reverse(search, search_len, table);
+    
+    size_t pos;
+    if (!find_substring_reverse(type, strlen(type), search, search_len, table, &pos)) {
+        *out_method_id = -1;
+        return 1;
+    }
+    
+    ASSERT(pos >= 0)
+    ASSERT(pos <= strlen(type) - search_len)
+    ASSERT(!memcmp(type + pos, search, search_len))
+    
+    int method_id = NCDMethodIndex_AddMethod(method_index, type, pos, type + pos + search_len, module);
+    if (method_id < 0) {
+        BLog(BLOG_ERROR, "NCDMethodIndex_AddMethod failed");
+        return 0;
+    }
+    
+    *out_method_id = method_id;
+    return 1;
+}
+
+int NCDModuleIndex_Init (NCDModuleIndex *o, NCDStringIndex *string_index)
+{
+    ASSERT(string_index)
+    
+    // init modules hash
+    if (!NCDModuleIndex__MHash_Init(&o->modules_hash, NCDMODULEINDEX_MODULES_HASH_SIZE)) {
+        BLog(BLOG_ERROR, "NCDModuleIndex__MHash_Init failed");
+        goto fail0;
+    }
+    
+#ifndef NDEBUG
+    // init base types tree
+    BAVL_Init(&o->base_types_tree, OFFSET_DIFF(struct NCDModuleIndex_base_type, base_type, base_types_tree_node), string_pointer_comparator, NULL);
+#endif
+    
+    // init groups list
+    LinkedList0_Init(&o->groups_list);
+    
+    // init method index
+    if (!NCDMethodIndex_Init(&o->method_index, string_index)) {
+        BLog(BLOG_ERROR, "NCDMethodIndex_Init failed");
+        goto fail1;
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    NCDModuleIndex__MHash_Free(&o->modules_hash);
+fail0:
+    return 0;
+}
+
+void NCDModuleIndex_Free (NCDModuleIndex *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free groups
+    LinkedList0Node *ln;
+    while (ln = LinkedList0_GetFirst(&o->groups_list)) {
+        struct NCDModuleIndex_group *ig = UPPER_OBJECT(ln, struct NCDModuleIndex_group, groups_list_node);
+        if (ig->igroup.group.func_globalfree) {
+            ig->igroup.group.func_globalfree(&ig->igroup);
+        }
+        BFree(ig->igroup.strings);
+        LinkedList0_Remove(&o->groups_list, &ig->groups_list_node);
+        BFree(ig);
+    }
+    
+#ifndef NDEBUG
+    // free base types
+    BAVLNode *tn;
+    while (tn = BAVL_GetFirst(&o->base_types_tree)) {
+        struct NCDModuleIndex_base_type *bt = UPPER_OBJECT(tn, struct NCDModuleIndex_base_type, base_types_tree_node);
+        BAVL_Remove(&o->base_types_tree, &bt->base_types_tree_node);
+        BFree(bt);
+    }
+#endif
+    
+    // free method index
+    NCDMethodIndex_Free(&o->method_index);
+    
+    // free modules hash
+    NCDModuleIndex__MHash_Free(&o->modules_hash);
+}
+
+int NCDModuleIndex_AddGroup (NCDModuleIndex *o, const struct NCDModuleGroup *group, const struct NCDModuleInst_iparams *iparams, NCDStringIndex *string_index)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(group)
+    ASSERT(iparams)
+    ASSERT(string_index)
+    
+    // count modules in the group
+    size_t num_modules = 0;
+    while (group->modules[num_modules].type) {
+        num_modules++;
+    }
+    
+    // compute allocation size
+    bsize_t size = bsize_add(bsize_fromsize(sizeof(struct NCDModuleIndex_group)), bsize_mul(bsize_fromsize(num_modules), bsize_fromsize(sizeof(struct NCDModuleIndex_module))));
+    
+    // allocate group 
+    struct NCDModuleIndex_group *ig = BAllocSize(size);
+    if (!ig) {
+        BLog(BLOG_ERROR, "BAllocSize failed");
+        goto fail0;
+    }
+    
+    // insert to groups list
+    LinkedList0_Prepend(&o->groups_list, &ig->groups_list_node);
+    
+    // copy NCDModuleGroup
+    ig->igroup.group = *group;
+    
+    if (!group->strings) {
+        // not resolving strings
+        ig->igroup.strings = NULL;
+    } else {
+        // compute number of strings
+        size_t num_strings = 0;
+        while (group->strings[num_strings]) {
+            num_strings++;
+        }
+        
+        // allocate array for string IDs
+        ig->igroup.strings = BAllocArray(num_strings, sizeof(ig->igroup.strings[0]));
+        if (!ig->igroup.strings) {
+            BLog(BLOG_ERROR, "BAllocArray failed");
+            goto fail1;
+        }
+        
+        // map strings to IDs
+        for (size_t i = 0; i < num_strings; i++) {
+            ig->igroup.strings[i] = NCDStringIndex_Get(string_index, group->strings[i]);
+            if (ig->igroup.strings[i] < 0) {
+                BLog(BLOG_ERROR, "NCDStringIndex_Get failed");
+                goto fail2;
+            }
+        }
+    }
+    
+    // call group init function
+    if (group->func_globalinit) {
+        if (!group->func_globalinit(&ig->igroup, iparams)) {
+            BLog(BLOG_ERROR, "func_globalinit failed");
+            goto fail2;
+        }
+    }
+    
+    size_t num_inited_modules = 0;
+    
+    // initialize modules
+    for (size_t i = 0; i < num_modules; i++) {
+        const struct NCDModule *nm = &group->modules[i];
+        struct NCDModuleIndex_module *m = &ig->modules[i];
+        
+        // make sure a module with this name doesn't exist already
+        if (find_module(o, nm->type)) {
+            BLog(BLOG_ERROR, "module type '%s' already exists", nm->type);
+            goto loop_fail0;
+        }
+        
+        // copy NCDModule structure
+        m->imodule.module = *nm;
+        
+        // determine base type
+        const char *base_type = (nm->base_type ? nm->base_type : nm->type);
+        ASSERT(base_type)
+        
+        // map base type to ID
+        m->imodule.base_type_id = NCDStringIndex_Get(string_index, base_type);
+        if (m->imodule.base_type_id < 0) {
+            BLog(BLOG_ERROR, "NCDStringIndex_Get failed");
+            goto loop_fail0;
+        }
+        
+        // set group pointer
+        m->imodule.group = &ig->igroup;
+        
+        // register method
+        if (!add_method(nm->type, &m->imodule, &o->method_index, &m->method_id)) {
+            goto loop_fail0;
+        }
+        
+#ifndef NDEBUG
+        // ensure that this base_type does not appear in any other groups
+        struct NCDModuleIndex_base_type *bt = find_base_type(o, base_type);
+        if (bt) {
+            if (bt->group != ig) {
+                BLog(BLOG_ERROR, "module base type '%s' already exists in another module group", base_type);
+                goto loop_fail1;
+            }
+        } else {
+            if (!(bt = BAlloc(sizeof(*bt)))) {
+                BLog(BLOG_ERROR, "BAlloc failed");
+                goto loop_fail1;
+            }
+            bt->base_type = base_type;
+            bt->group = ig;
+            ASSERT_EXECUTE(BAVL_Insert(&o->base_types_tree, &bt->base_types_tree_node, NULL))
+        }
+#endif
+
+        // insert to modules hash
+        NCDModuleIndex__MHashRef ref = {m, m};
+        int res = NCDModuleIndex__MHash_Insert(&o->modules_hash, 0, ref, NULL);
+        ASSERT_EXECUTE(res)
+        
+        num_inited_modules++;
+        continue;
+        
+#ifndef NDEBUG
+    loop_fail1:
+        if (m->method_id >= 0) {
+            NCDMethodIndex_RemoveMethod(&o->method_index, m->method_id);
+        }
+#endif
+    loop_fail0:
+        goto fail3;
+    }
+    
+    return 1;
+    
+fail3:
+    while (num_inited_modules-- > 0) {
+        struct NCDModuleIndex_module *m = &ig->modules[num_inited_modules];
+        NCDModuleIndex__MHashRef ref = {m, m};
+        NCDModuleIndex__MHash_Remove(&o->modules_hash, 0, ref);
+#ifndef NDEBUG
+        const struct NCDModule *nm = &group->modules[num_inited_modules];
+        const char *base_type = (nm->base_type ? nm->base_type : nm->type);
+        struct NCDModuleIndex_base_type *bt = find_base_type(o, base_type);
+        if (bt) {
+            ASSERT(bt->group == ig)
+            BAVL_Remove(&o->base_types_tree, &bt->base_types_tree_node);
+            BFree(bt);
+        }
+#endif
+        if (m->method_id >= 0) {
+            NCDMethodIndex_RemoveMethod(&o->method_index, m->method_id);
+        }
+    }
+    if (group->func_globalfree) {
+        group->func_globalfree(&ig->igroup);
+    }
+fail2:
+    BFree(ig->igroup.strings);
+fail1:
+    LinkedList0_Remove(&o->groups_list, &ig->groups_list_node);
+    BFree(ig);
+fail0:
+    return 0;
+}
+
+const struct NCDInterpModule * NCDModuleIndex_FindModule (NCDModuleIndex *o, const char *type)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(type)
+    
+    struct NCDModuleIndex_module *m = find_module(o, type);
+    if (!m) {
+        return NULL;
+    }
+    
+    return &m->imodule;
+}
+
+int NCDModuleIndex_GetMethodNameId (NCDModuleIndex *o, const char *method_name)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(method_name)
+    
+    return NCDMethodIndex_GetMethodNameId(&o->method_index, method_name);
+}
+
+const struct NCDInterpModule * NCDModuleIndex_GetMethodModule (NCDModuleIndex *o, NCD_string_id_t obj_type, int method_name_id)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return NCDMethodIndex_GetMethodModule(&o->method_index, obj_type, method_name_id);
+}
diff --git a/external/badvpn_dns/ncd/NCDModuleIndex.h b/external/badvpn_dns/ncd/NCDModuleIndex.h
new file mode 100644
index 0000000..f7cc255
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDModuleIndex.h
@@ -0,0 +1,86 @@
+/**
+ * @file NCDModuleIndex.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDMODULEINDEX_H
+#define BADVPN_NCDMODULEINDEX_H
+
+#include <misc/debug.h>
+#include <structure/BAVL.h>
+#include <structure/CHash.h>
+#include <structure/LinkedList0.h>
+#include <base/DebugObject.h>
+#include <ncd/NCDModule.h>
+#include <ncd/NCDMethodIndex.h>
+
+#define NCDMODULEINDEX_MODULES_HASH_SIZE 512
+
+struct NCDModuleIndex_module {
+    struct NCDInterpModule imodule;
+    struct NCDModuleIndex_module *hash_next;
+    int method_id;
+};
+
+#ifndef NDEBUG
+struct NCDModuleIndex_base_type {
+    const char *base_type;
+    struct NCDModuleIndex_group *group;
+    BAVLNode base_types_tree_node;
+};
+#endif
+
+struct NCDModuleIndex_group {
+    LinkedList0Node groups_list_node;
+    struct NCDInterpModuleGroup igroup;
+    struct NCDModuleIndex_module modules[];
+};
+
+typedef struct NCDModuleIndex_module *NCDModuleIndex__mhash_link;
+typedef const char *NCDModuleIndex__mhash_key;
+
+#include "NCDModuleIndex_mhash.h"
+#include <structure/CHash_decl.h>
+
+typedef struct {
+    NCDModuleIndex__MHash modules_hash;
+#ifndef NDEBUG
+    BAVL base_types_tree;
+#endif
+    LinkedList0 groups_list;
+    NCDMethodIndex method_index;
+    DebugObject d_obj;
+} NCDModuleIndex;
+
+int NCDModuleIndex_Init (NCDModuleIndex *o, NCDStringIndex *string_index) WARN_UNUSED;
+void NCDModuleIndex_Free (NCDModuleIndex *o);
+int NCDModuleIndex_AddGroup (NCDModuleIndex *o, const struct NCDModuleGroup *group, const struct NCDModuleInst_iparams *iparams, NCDStringIndex *string_index) WARN_UNUSED;
+const struct NCDInterpModule * NCDModuleIndex_FindModule (NCDModuleIndex *o, const char *type);
+int NCDModuleIndex_GetMethodNameId (NCDModuleIndex *o, const char *method_name);
+const struct NCDInterpModule * NCDModuleIndex_GetMethodModule (NCDModuleIndex *o, NCD_string_id_t obj_type, int method_name_id);
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDModuleIndex_mhash.h b/external/badvpn_dns/ncd/NCDModuleIndex_mhash.h
new file mode 100644
index 0000000..8057b39
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDModuleIndex_mhash.h
@@ -0,0 +1,12 @@
+#define CHASH_PARAM_NAME NCDModuleIndex__MHash
+#define CHASH_PARAM_ENTRY struct NCDModuleIndex_module
+#define CHASH_PARAM_LINK NCDModuleIndex__mhash_link
+#define CHASH_PARAM_KEY NCDModuleIndex__mhash_key
+#define CHASH_PARAM_ARG int
+#define CHASH_PARAM_NULL ((NCDModuleIndex__mhash_link)NULL)
+#define CHASH_PARAM_DEREF(arg, link) (link)
+#define CHASH_PARAM_ENTRYHASH(arg, entry) (badvpn_djb2_hash((const uint8_t *)(entry).ptr->imodule.module.type))
+#define CHASH_PARAM_KEYHASH(arg, key) (badvpn_djb2_hash((const uint8_t *)(key)))
+#define CHASH_PARAM_COMPARE_ENTRIES(arg, entry1, entry2) (!strcmp((entry1).ptr->imodule.module.type, (entry2).ptr->imodule.module.type))
+#define CHASH_PARAM_COMPARE_KEY_ENTRY(arg, key1, entry2) (!strcmp((key1), (entry2).ptr->imodule.module.type))
+#define CHASH_PARAM_ENTRY_NEXT hash_next
diff --git a/external/badvpn_dns/ncd/NCDObject.c b/external/badvpn_dns/ncd/NCDObject.c
new file mode 100644
index 0000000..c2f4cad
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDObject.c
@@ -0,0 +1,40 @@
+/**
+ * @file NCDObject.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "NCDObject.h"
+
+int NCDObject_no_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value)
+{
+    return 0;
+}
+
+int NCDObject_no_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    return 0;
+}
diff --git a/external/badvpn_dns/ncd/NCDObject.h b/external/badvpn_dns/ncd/NCDObject.h
new file mode 100644
index 0000000..237ec72
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDObject.h
@@ -0,0 +1,356 @@
+/**
+ * @file NCDObject.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDOBJECT_H
+#define BADVPN_NCDOBJECT_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDVal.h>
+#include <ncd/NCDStringIndex.h>
+#include <ncd/static_strings.h>
+
+/**
+ * Represents an NCD object.
+ * Objects expose the following functionalities:
+ * - resolving variables by name,
+ * - resolving objects by name,
+ * - provide information for calling method-like statements.
+ * 
+ * The NCDObject structure must not be stored persistently; it is only
+ * valid at the time it was obtained, and any change of state in the
+ * execution of the NCD program may render the object invalid.
+ * However, the structure does not contain any resources, and can freely
+ * be passed around by value.
+ */
+typedef struct NCDObject_s NCDObject;
+
+/**
+ * Callback function for variable resolution requests.
+ * 
+ * @param obj const pointer to the NCDObject this is being called for.
+ *            {@link NCDObject_DataPtr} and {@link NCDObject_DataInt} can be
+ *            used to retrieve state information which was passed to
+ *            {@link NCDObject_Build} or {@link NCDObject_BuildFull}.
+ * @param name name of the variable being resolved, in form of an {@link NCDStringIndex}
+ *             string identifier
+ * @param mem pointer to the memory object where the resulting value should be
+ *            constructed
+ * @param out_value If the variable exists, *out_value should be set to the value
+ *                  reference to the result value. If the variable exists but there
+ *                  was an error constructing the value, should be set to an
+ *                  invalid value reference. Can be modified even if the variable
+ *                  does not exist.
+ * @return 1 if the variable exists, 0 if not
+ */
+typedef int (*NCDObject_func_getvar) (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value);
+
+/**
+ * Callback function for object resolution requests.
+ * 
+ * @param obj const pointer to the NCDObject this is being called for.
+ *            {@link NCDObject_DataPtr} and {@link NCDObject_DataInt} can be
+ *            used to retrieve state information which was passed to
+ *            {@link NCDObject_Build} or {@link NCDObject_BuildFull}.
+ * @param name name of the object being resolved, in form of an {@link NCDStringIndex}
+ *             string identifier
+ * @param out_object If the object exists, *out_object should be set to the result
+ *                   object. Can be modified even if the object does not exist.
+ * @return 1 if the object exists, 0 if not
+ */
+typedef int (*NCDObject_func_getobj) (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+
+struct NCDObject_s {
+    NCD_string_id_t type;
+    int data_int;
+    void *data_ptr;
+    void *method_user;
+    NCDObject_func_getvar func_getvar;
+    NCDObject_func_getobj func_getobj;
+};
+
+/**
+ * Basic object construction function.
+ * This is equivalent to calling {@link NCDObject_BuildFull} with data_int=0
+ * and method_user=data_ptr. See that function for detailed documentation.
+ */
+static NCDObject NCDObject_Build (NCD_string_id_t type, void *data_ptr, NCDObject_func_getvar func_getvar, NCDObject_func_getobj func_getobj);
+
+/**
+ * Constructs an {@link NCDObject} structure.
+ * This is the full version where all supported parameters have to be provided.
+ * In most cases, {@link NCDObject_Build} will suffice.
+ * 
+ * @param type type of the object for the purpose of calling method-like statements
+ *             on the object, in form of an {@link NCDStringIndex} string identifier.
+ *             May be set to -1 if the object has no methods.
+ * @param data_ptr state-keeping pointer which can be restored from callbacks using
+ *                 {@link NCDObject_DataPtr}
+ * @param data_int state-keeping integer which can be restored from callbacks using
+ *                 {@link NCDObject_DataInt}
+ * @param method_user state-keeping pointer to be passed to new method-like statements
+ *                    created using this object. The value of this argument will be
+ *                    available as params->method_user within the {@link NCDModule_func_new2}
+ *                    module backend callback.
+ * @param func_getvar callback for resolving variables within the object. This must not
+ *                    be NULL; if the object exposes no variables, pass {@link NCDObject_no_getvar}.
+ * @param func_getobj callback for resolving objects within the object. This must not
+ *                    be NULL; if the object exposes no objects, pass {@link NCDObject_no_getobj}.
+ * @return an NCDObject structure encapsulating the information given
+ */
+static NCDObject NCDObject_BuildFull (NCD_string_id_t type, void *data_ptr, int data_int, void *method_user, NCDObject_func_getvar func_getvar, NCDObject_func_getobj func_getobj);
+
+/**
+ * Returns the 'type' attribute; see {@link NCDObject_BuildFull}.
+ */
+static NCD_string_id_t NCDObject_Type (const NCDObject *o);
+
+/**
+ * Returns the 'data_ptr' attribute; see {@link NCDObject_BuildFull}.
+ */
+static void * NCDObject_DataPtr (const NCDObject *o);
+
+/**
+ * Returns the 'data_int' attribute; see {@link NCDObject_BuildFull}.
+ */
+static int NCDObject_DataInt (const NCDObject *o);
+
+/**
+ * Returns the 'method_user' attribute; see {@link NCDObject_BuildFull}.
+ */
+static void * NCDObject_MethodUser (const NCDObject *o);
+
+/**
+ * Attempts to resolve a variable within the object.
+ * This just calls {@link NCDObject_func_getvar}, but also has some assertions to detect
+ * incorrect behavior of the callback.
+ */
+static int NCDObject_GetVar (const NCDObject *o, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value) WARN_UNUSED;
+
+/**
+ * Attempts to resolve an object within the object.
+ * This just calls {@link NCDObject_func_getobj}, but also has some assertions to detect
+ * incorrect behavior of the callback.
+ */
+static int NCDObject_GetObj (const NCDObject *o, NCD_string_id_t name, NCDObject *out_object) WARN_UNUSED;
+
+/**
+ * Resolves a variable expression starting with this object.
+ * A variable expression is usually represented in dotted form,
+ * e.g. object1.object2.variable (for a named variable) or object1.object2.object3
+ * (for an empty string variable). This function however receives the expression
+ * as an array of string identifiers.
+ * 
+ * Consult the implementation for exact semantics of variable expression resolution.
+ * 
+ * @param o object to start the resolution with
+ * @param names pointer to an array of names for the resolution. May be NULL if num_names is 0.
+ * @param num_names number in names in the array
+ * @param mem pointer to the memory object where the resulting value
+ *            should be constructed
+ * @param out_value If the variable exists, *out_value will be set to the value
+ *                  reference to the result value. If the variable exists but there
+ *                  was an error constructing the value, will be set to an
+ *                  invalid value reference. May be modified even if the variable
+ *                  does not exist.
+ * @return 1 if the variable exists, 0 if not
+ */
+static int NCDObject_ResolveVarExprCompact (const NCDObject *o, const NCD_string_id_t *names, size_t num_names, NCDValMem *mem, NCDValRef *out_value) WARN_UNUSED;
+
+/**
+ * Resolves an object expression starting with this object.
+ * An object expression is usually represented in dotted form,
+ * e.g. object1.object2.object3. This function however receives the expression
+ * as an array of string identifiers.
+ * 
+ * Consult the implementation for exact semantics of object expression resolution.
+ * 
+ * @param o object to start the resolution with
+ * @param names pointer to an array of names for the resolution. May be NULL if num_names is 0.
+ * @param num_names number in names in the array
+ * @param out_object If the object exists, *out_object will be set to the result
+ *                   object. May be modified even if the object does not exist.
+ * @return 1 if the object exists, 0 if not
+ */
+static int NCDObject_ResolveObjExprCompact (const NCDObject *o, const NCD_string_id_t *names, size_t num_names, NCDObject *out_object) WARN_UNUSED;
+
+/**
+ * Returns 0. This can be used as a dummy variable resolution callback.
+ */
+int NCDObject_no_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value);
+
+/**
+ * Returns 0. This can be used as a dummy object resolution callback.
+ */
+int NCDObject_no_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+
+//
+
+NCDObject NCDObject_Build (NCD_string_id_t type, void *data_ptr, NCDObject_func_getvar func_getvar, NCDObject_func_getobj func_getobj)
+{
+    ASSERT(type >= -1)
+    ASSERT(func_getvar)
+    ASSERT(func_getobj)
+    
+    NCDObject obj;
+    obj.type = type;
+    obj.data_int = 0;
+    obj.data_ptr = data_ptr;
+    obj.method_user = data_ptr;
+    obj.func_getvar = func_getvar;
+    obj.func_getobj = func_getobj;
+    
+    return obj;
+}
+
+NCDObject NCDObject_BuildFull (NCD_string_id_t type, void *data_ptr, int data_int, void *method_user, NCDObject_func_getvar func_getvar, NCDObject_func_getobj func_getobj)
+{
+    ASSERT(type >= -1)
+    ASSERT(func_getvar)
+    ASSERT(func_getobj)
+    
+    NCDObject obj;
+    obj.type = type;
+    obj.data_int = data_int;
+    obj.data_ptr = data_ptr;
+    obj.method_user = method_user;
+    obj.func_getvar = func_getvar;
+    obj.func_getobj = func_getobj;
+    
+    return obj;
+}
+
+NCD_string_id_t NCDObject_Type (const NCDObject *o)
+{
+    return o->type;
+}
+
+void * NCDObject_DataPtr (const NCDObject *o)
+{
+    return o->data_ptr;
+}
+
+int NCDObject_DataInt (const NCDObject *o)
+{
+    return o->data_int;
+}
+
+void * NCDObject_MethodUser (const NCDObject *o)
+{
+    return o->method_user;
+}
+
+int NCDObject_GetVar (const NCDObject *o, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value)
+{
+    ASSERT(name >= 0)
+    ASSERT(mem)
+    ASSERT(out_value)
+    
+    int res = o->func_getvar(o, name, mem, out_value);
+    
+    ASSERT(res == 0 || res == 1)
+    ASSERT(res == 0 || (NCDVal_Assert(*out_value), 1))
+    
+    return res;
+}
+
+int NCDObject_GetObj (const NCDObject *o, NCD_string_id_t name, NCDObject *out_object)
+{
+    ASSERT(name >= 0)
+    ASSERT(out_object)
+    
+    int res = o->func_getobj(o, name, out_object);
+    
+    ASSERT(res == 0 || res == 1)
+    
+    return res;
+}
+
+static NCDObject NCDObject__dig_into_object (NCDObject object)
+{
+    NCDObject obj2;
+    while (NCDObject_GetObj(&object, NCD_STRING_EMPTY, &obj2)) {
+        object = obj2;
+    }
+    
+    return object;
+}
+
+int NCDObject_ResolveVarExprCompact (const NCDObject *o, const NCD_string_id_t *names, size_t num_names, NCDValMem *mem, NCDValRef *out_value)
+{
+    ASSERT(num_names == 0 || names)
+    ASSERT(mem)
+    ASSERT(out_value)
+    
+    NCDObject object = NCDObject__dig_into_object(*o);
+    
+    while (num_names > 0) {
+        NCDObject obj2;
+        if (!NCDObject_GetObj(&object, *names, &obj2)) {
+            if (num_names == 1 && NCDObject_GetVar(&object, *names, mem, out_value)) {
+                return 1;
+            }
+            
+            return 0;
+        }
+        
+        object = NCDObject__dig_into_object(obj2);
+        
+        names++;
+        num_names--;
+    }
+    
+    return NCDObject_GetVar(&object, NCD_STRING_EMPTY, mem, out_value);
+}
+
+int NCDObject_ResolveObjExprCompact (const NCDObject *o, const NCD_string_id_t *names, size_t num_names, NCDObject *out_object)
+{
+    ASSERT(num_names == 0 || names)
+    ASSERT(out_object)
+    
+    NCDObject object = NCDObject__dig_into_object(*o);
+    
+    while (num_names > 0) {
+        NCDObject obj2;
+        if (!NCDObject_GetObj(&object, *names, &obj2)) {
+            return 0;
+        }
+        
+        object = NCDObject__dig_into_object(obj2);
+        
+        names++;
+        num_names--;
+    }
+    
+    *out_object = object;
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDPlaceholderDb.c b/external/badvpn_dns/ncd/NCDPlaceholderDb.c
new file mode 100644
index 0000000..906509d
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDPlaceholderDb.c
@@ -0,0 +1,127 @@
+/**
+ * @file NCDPlaceholderDb.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <limits.h>
+#include <stdlib.h>
+
+#include <misc/balloc.h>
+#include <misc/strdup.h>
+#include <base/BLog.h>
+#include <ncd/make_name_indices.h>
+
+#include "NCDPlaceholderDb.h"
+
+#include <generated/blog_channel_NCDPlaceholderDb.h>
+
+int NCDPlaceholderDb_Init (NCDPlaceholderDb *o, NCDStringIndex *string_index)
+{
+    ASSERT(string_index)
+    
+    o->count = 0;
+    o->capacity = 1;
+    o->string_index = string_index;
+    
+    if (!(o->arr = BAllocArray(o->capacity, sizeof(o->arr[0])))) {
+        BLog(BLOG_ERROR, "NCDPlaceholderDb_Init failed");
+        return 0;
+    }
+    
+    return 1;
+}
+
+void NCDPlaceholderDb_Free (NCDPlaceholderDb *o)
+{
+    for (size_t i = 0; i < o->count; i++) {
+        BFree(o->arr[i].varnames);
+    }
+    
+    BFree(o->arr);
+}
+
+int NCDPlaceholderDb_AddVariable (NCDPlaceholderDb *o, const char *varname, int *out_plid)
+{
+    ASSERT(varname)
+    ASSERT(out_plid)
+    ASSERT(o->count <= o->capacity)
+    ASSERT(o->capacity > 0)
+    
+    if (o->count == o->capacity) {
+        if (o->capacity > SIZE_MAX / 2) {
+            BLog(BLOG_ERROR, "too many placeholder entries (cannot resize)");
+            return 0;
+        }
+        size_t newcap = 2 * o->capacity;
+        
+        struct NCDPlaceholderDb__entry *newarr = BAllocArray(newcap, sizeof(newarr[0]));
+        if (!newarr) {
+            BLog(BLOG_ERROR, "BAllocArray failed");
+            return 0;
+        }
+        
+        memcpy(newarr, o->arr, o->count * sizeof(newarr[0]));
+        BFree(o->arr);
+        
+        o->arr = newarr;
+        o->capacity = newcap;
+    }
+    
+    ASSERT(o->count < o->capacity)
+    
+    if (o->count > INT_MAX) {
+        BLog(BLOG_ERROR, "too many placeholder entries (cannot fit integer)");
+        return 0;
+    }
+    
+    NCD_string_id_t *varnames;
+    size_t num_names;
+    if (!ncd_make_name_indices(o->string_index, varname, &varnames, &num_names)) {
+        BLog(BLOG_ERROR, "ncd_make_name_indices failed");
+        return 0;
+    }
+    
+    *out_plid = o->count;
+    
+    o->arr[o->count].varnames = varnames;
+    o->arr[o->count].num_names = num_names;
+    o->count++;
+    
+    return 1;
+}
+
+void NCDPlaceholderDb_GetVariable (NCDPlaceholderDb *o, int plid, const NCD_string_id_t **out_varnames, size_t *out_num_names)
+{
+    ASSERT(plid >= 0)
+    ASSERT(plid < o->count)
+    ASSERT(out_varnames)
+    ASSERT(out_num_names)
+    
+    *out_varnames = o->arr[plid].varnames;
+    *out_num_names = o->arr[plid].num_names;
+}
diff --git a/external/badvpn_dns/ncd/NCDPlaceholderDb.h b/external/badvpn_dns/ncd/NCDPlaceholderDb.h
new file mode 100644
index 0000000..d6a1f40
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDPlaceholderDb.h
@@ -0,0 +1,95 @@
+/**
+ * @file NCDPlaceholderDb.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDPLACEHOLDERDB_H
+#define BADVPN_NCDPLACEHOLDERDB_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDStringIndex.h>
+
+struct NCDPlaceholderDb__entry {
+    NCD_string_id_t *varnames;
+    size_t num_names;
+};
+
+/**
+ * Associates variable placeholder numbers to variable names.
+ * This is populated by {@link NCDInterpProcess_Init} when converting the {@link NCDValue}
+ * objects in the AST to compact representations in {@link NCDValMem}. Variables are
+ * replaced with placeholder identifiers (integers), which this object associates
+ * with their names.
+ * During interpretation, when a statement is being initialized, the compact form held
+ * by {@link NCDInterpProcess} is byte-copied, and placeholders are replaced with the
+ * values of corresponding variables using {@link NCDVal_ReplacePlaceholders}.
+ */
+typedef struct {
+    struct NCDPlaceholderDb__entry *arr;
+    size_t count;
+    size_t capacity;
+    NCDStringIndex *string_index;
+} NCDPlaceholderDb;
+
+/**
+ * Initializes the placeholder database.
+ * Returns 1 on success, and 0 on failure.
+ */
+int NCDPlaceholderDb_Init (NCDPlaceholderDb *o, NCDStringIndex *string_index) WARN_UNUSED;
+
+/**
+ * Frees the placeholder database.
+ */
+void NCDPlaceholderDb_Free (NCDPlaceholderDb *o);
+
+/**
+ * Adds a variable to the database.
+ * 
+ * @param varname name of the variable (text, including dots). Must not be NULL.
+ * @param out_plid on success, the placeholder identifier will be returned here. Must
+ *                 not be NULL.
+ * @return 1 on success, 0 on failure
+ */
+int NCDPlaceholderDb_AddVariable (NCDPlaceholderDb *o, const char *varname, int *out_plid) WARN_UNUSED;
+
+/**
+ * Retrieves the name of the variable associated with a placeholder identifier.
+ * 
+ * @param plid placeholder identifier; must have been previously provided by
+ *             {@link NCDPlaceholderDb_AddVariable}.
+ * @return name of the variable, split by dots. The returned value points to
+ *         an array of pointers to strings, which is terminated by a NULL
+ *         pointer. These all point to internal data in the placeholder
+ *         database; they must not be modified, and remain valid until the
+ *         database is freed.
+ *         Note that there will always be at least one string in the result.
+ */
+void NCDPlaceholderDb_GetVariable (NCDPlaceholderDb *o, int plid, const NCD_string_id_t **out_varnames, size_t *out_num_names);
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDStringIndex.c b/external/badvpn_dns/ncd/NCDStringIndex.c
new file mode 100644
index 0000000..87a231d
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDStringIndex.c
@@ -0,0 +1,262 @@
+/**
+ * @file NCDStringIndex.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/hashfun.h>
+#include <misc/strdup.h>
+#include <misc/array_length.h>
+#include <base/BLog.h>
+
+#include "NCDStringIndex.h"
+
+#include "NCDStringIndex_hash.h"
+#include <structure/CHash_impl.h>
+
+#define GROWARRAY_NAME Array
+#define GROWARRAY_OBJECT_TYPE NCDStringIndex
+#define GROWARRAY_ARRAY_MEMBER entries
+#define GROWARRAY_CAPACITY_MEMBER entries_capacity
+#define GROWARRAY_MAX_CAPACITY NCD_STRING_ID_MAX
+#include <misc/grow_array.h>
+
+#include <generated/blog_channel_ncd.h>
+
+// NOTE: keep synchronized with static_strings.h
+static const char *static_strings[] = {
+    "",
+    "_args",
+    "_arg0",
+    "_arg1",
+    "_arg2",
+    "_arg3",
+    "_arg4",
+    "_arg5",
+    "_arg6",
+    "_arg7",
+    "_arg8",
+    "_arg9",
+    "_arg10",
+    "_arg11",
+    "_arg12",
+    "_arg13",
+    "_arg14",
+    "_arg15",
+    "_arg16",
+    "_arg17",
+    "_arg18",
+    "_arg19",
+    "true",
+    "false",
+    "<none>",
+    "_caller",
+    "succeeded",
+    "is_error",
+    "not_eof",
+    "length",
+    "type",
+    "exit_status",
+    "size"
+};
+
+static NCD_string_id_t do_get (NCDStringIndex *o, const char *str, size_t str_len)
+{
+    ASSERT(str)
+    
+    NCDStringIndex_hash_key key = {str, str_len};
+    NCDStringIndex__HashRef ref = NCDStringIndex__Hash_Lookup(&o->hash, o->entries, key);
+    ASSERT(ref.link == -1 || ref.link >= 0)
+    ASSERT(ref.link == -1 || ref.link < o->entries_size)
+    ASSERT(ref.link == -1 || (ref.ptr->str_len == str_len && !memcmp(ref.ptr->str, str, str_len)))
+    
+    if (ref.link != -1) {
+        return ref.link;
+    }
+    
+    if (o->entries_size == o->entries_capacity) {
+        if (!Array_DoubleUp(o)) {
+            BLog(BLOG_ERROR, "Array_DoubleUp failed");
+            return -1;
+        }
+        
+        if (!NCDStringIndex__Hash_MultiplyBuckets(&o->hash, o->entries, 1)) {
+            BLog(BLOG_ERROR, "NCDStringIndex__Hash_MultiplyBuckets failed");
+            return -1;
+        }
+    }
+    
+    ASSERT(o->entries_size < o->entries_capacity)
+    
+    struct NCDStringIndex__entry *entry = &o->entries[o->entries_size];
+    
+    if (!(entry->str = b_strdup_bin(str, str_len))) {
+        BLog(BLOG_ERROR, "b_strdup_bin failed");
+        return -1;
+    }
+    entry->str_len = str_len;
+    entry->has_nulls = !!memchr(str, '\0', str_len);
+    
+    NCDStringIndex__HashRef newref = {entry, o->entries_size};
+    int res = NCDStringIndex__Hash_Insert(&o->hash, o->entries, newref, NULL);
+    ASSERT_EXECUTE(res)
+    
+    return o->entries_size++;
+}
+
+int NCDStringIndex_Init (NCDStringIndex *o)
+{
+    o->entries_size = 0;
+    
+    if (!Array_Init(o, NCDSTRINGINDEX_INITIAL_CAPACITY)) {
+        BLog(BLOG_ERROR, "Array_Init failed");
+        goto fail0;
+    }
+    
+    if (!NCDStringIndex__Hash_Init(&o->hash, NCDSTRINGINDEX_INITIAL_HASH_BUCKETS)) {
+        BLog(BLOG_ERROR, "NCDStringIndex__Hash_Init failed");
+        goto fail1;
+    }
+    
+    for (size_t i = 0; i < B_ARRAY_LENGTH(static_strings); i++) {
+        if (do_get(o, static_strings[i], strlen(static_strings[i])) < 0) {
+            goto fail2;
+        }
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail2:
+    for (NCD_string_id_t i = 0; i < o->entries_size; i++) {
+        free(o->entries[i].str);
+    }
+    NCDStringIndex__Hash_Free(&o->hash);
+fail1:
+    Array_Free(o);
+fail0:
+    return 0;
+}
+
+void NCDStringIndex_Free (NCDStringIndex *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    for (NCD_string_id_t i = 0; i < o->entries_size; i++) {
+        free(o->entries[i].str);
+    }
+    
+    NCDStringIndex__Hash_Free(&o->hash);
+    Array_Free(o);
+}
+
+NCD_string_id_t NCDStringIndex_Lookup (NCDStringIndex *o, const char *str)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(str)
+    
+    return NCDStringIndex_LookupBin(o, str, strlen(str));
+}
+
+NCD_string_id_t NCDStringIndex_LookupBin (NCDStringIndex *o, const char *str, size_t str_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(str)
+    
+    NCDStringIndex_hash_key key = {str, str_len};
+    NCDStringIndex__HashRef ref = NCDStringIndex__Hash_Lookup(&o->hash, o->entries, key);
+    ASSERT(ref.link == -1 || ref.link >= 0)
+    ASSERT(ref.link == -1 || ref.link < o->entries_size)
+    ASSERT(ref.link == -1 || (ref.ptr->str_len == str_len && !memcmp(ref.ptr->str, str, str_len)))
+    
+    return ref.link;
+}
+
+NCD_string_id_t NCDStringIndex_Get (NCDStringIndex *o, const char *str)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(str)
+    
+    return NCDStringIndex_GetBin(o, str, strlen(str));
+}
+
+NCD_string_id_t NCDStringIndex_GetBin (NCDStringIndex *o, const char *str, size_t str_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(str)
+    
+    return do_get(o, str, str_len);
+}
+
+const char * NCDStringIndex_Value (NCDStringIndex *o, NCD_string_id_t id)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(id >= 0)
+    ASSERT(id < o->entries_size)
+    ASSERT(o->entries[id].str)
+    
+    return o->entries[id].str;
+}
+
+size_t NCDStringIndex_Length (NCDStringIndex *o, NCD_string_id_t id)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(id >= 0)
+    ASSERT(id < o->entries_size)
+    ASSERT(o->entries[id].str)
+    
+    return o->entries[id].str_len;
+}
+
+int NCDStringIndex_HasNulls (NCDStringIndex *o, NCD_string_id_t id)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(id >= 0)
+    ASSERT(id < o->entries_size)
+    ASSERT(o->entries[id].str)
+    
+    return o->entries[id].has_nulls;
+}
+
+int NCDStringIndex_GetRequests (NCDStringIndex *o, struct NCD_string_request *requests)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(requests)
+    
+    while (requests->str) {
+        NCD_string_id_t id = NCDStringIndex_Get(o, requests->str);
+        if (id < 0) {
+            return 0;
+        }
+        requests->id = id;
+        requests++;
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/ncd/NCDStringIndex.h b/external/badvpn_dns/ncd/NCDStringIndex.h
new file mode 100644
index 0000000..78e20ec
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDStringIndex.h
@@ -0,0 +1,83 @@
+/**
+ * @file NCDStringIndex.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCD_STRING_INDEX_H
+#define BADVPN_NCD_STRING_INDEX_H
+
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <structure/CHash.h>
+#include <base/DebugObject.h>
+
+#define NCDSTRINGINDEX_INITIAL_CAPACITY 256
+#define NCDSTRINGINDEX_INITIAL_HASH_BUCKETS 256
+
+typedef int NCD_string_id_t;
+
+#define NCD_STRING_ID_MAX INT_MAX
+
+struct NCDStringIndex__entry {
+    char *str;
+    size_t str_len;
+    int has_nulls;
+    NCD_string_id_t hash_next;
+};
+
+typedef struct { const char *str; size_t len; } NCDStringIndex_hash_key;
+typedef struct NCDStringIndex__entry *NCDStringIndex_hash_arg;
+
+#include "NCDStringIndex_hash.h"
+#include <structure/CHash_decl.h>
+
+typedef struct {
+    struct NCDStringIndex__entry *entries;
+    NCD_string_id_t entries_capacity;
+    NCD_string_id_t entries_size;
+    NCDStringIndex__Hash hash;
+    DebugObject d_obj;
+} NCDStringIndex;
+
+struct NCD_string_request {
+    const char *str;
+    NCD_string_id_t id;
+};
+
+int NCDStringIndex_Init (NCDStringIndex *o) WARN_UNUSED;
+void NCDStringIndex_Free (NCDStringIndex *o);
+NCD_string_id_t NCDStringIndex_Lookup (NCDStringIndex *o, const char *str);
+NCD_string_id_t NCDStringIndex_LookupBin (NCDStringIndex *o, const char *str, size_t str_len);
+NCD_string_id_t NCDStringIndex_Get (NCDStringIndex *o, const char *str);
+NCD_string_id_t NCDStringIndex_GetBin (NCDStringIndex *o, const char *str, size_t str_len);
+const char * NCDStringIndex_Value (NCDStringIndex *o, NCD_string_id_t id);
+size_t NCDStringIndex_Length (NCDStringIndex *o, NCD_string_id_t id);
+int NCDStringIndex_HasNulls (NCDStringIndex *o, NCD_string_id_t id);
+int NCDStringIndex_GetRequests (NCDStringIndex *o, struct NCD_string_request *requests) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDStringIndex_hash.h b/external/badvpn_dns/ncd/NCDStringIndex_hash.h
new file mode 100644
index 0000000..9a751fb
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDStringIndex_hash.h
@@ -0,0 +1,13 @@
+#define CHASH_PARAM_NAME NCDStringIndex__Hash
+#define CHASH_PARAM_ENTRY struct NCDStringIndex__entry
+#define CHASH_PARAM_LINK NCD_string_id_t
+#define CHASH_PARAM_KEY NCDStringIndex_hash_key
+#define CHASH_PARAM_ARG NCDStringIndex_hash_arg
+#define CHASH_PARAM_NULL ((NCD_string_id_t)-1)
+#define CHASH_PARAM_DEREF(arg, link) (&(arg)[(link)])
+#define CHASH_PARAM_ENTRYHASH(arg, entry) badvpn_djb2_hash_bin((const uint8_t *)(entry).ptr->str, (entry).ptr->str_len)
+#define CHASH_PARAM_KEYHASH(arg, key) badvpn_djb2_hash_bin((const uint8_t *)(key).str, (key).len)
+#define CHASH_PARAM_ENTRYHASH_IS_CHEAP 0
+#define CHASH_PARAM_COMPARE_ENTRIES(arg, entry1, entry2) ((entry1).ptr->str_len == (entry2).ptr->str_len && !memcmp((entry1).ptr->str, (entry2).ptr->str, (entry1).ptr->str_len))
+#define CHASH_PARAM_COMPARE_KEY_ENTRY(arg, key1, entry2) ((key1).len == (entry2).ptr->str_len && !memcmp((key1).str, (entry2).ptr->str, (key1).len))
+#define CHASH_PARAM_ENTRY_NEXT hash_next
diff --git a/external/badvpn_dns/ncd/NCDSugar.c b/external/badvpn_dns/ncd/NCDSugar.c
new file mode 100644
index 0000000..669bec8
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDSugar.c
@@ -0,0 +1,253 @@
+/**
+ * @file NCDSugar.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "NCDSugar.h"
+
+struct desugar_state {
+    NCDProgram *prog;
+    size_t template_name_ctr;
+};
+
+static int add_template (struct desugar_state *state, NCDBlock block, NCDValue *out_name_val);
+static int desugar_block (struct desugar_state *state, NCDBlock *block);
+static int desugar_if (struct desugar_state *state, NCDBlock *block, NCDStatement *stmt, NCDStatement **out_next);
+static int desugar_foreach (struct desugar_state *state, NCDBlock *block, NCDStatement *stmt, NCDStatement **out_next);
+
+static int add_template (struct desugar_state *state, NCDBlock block, NCDValue *out_name_val)
+{
+    char name[40];
+    snprintf(name, sizeof(name), "__tmpl%zu", state->template_name_ctr);
+    state->template_name_ctr++;
+    
+    if (!desugar_block(state, &block)) {
+        NCDBlock_Free(&block);
+        return 0;
+    }
+    
+    NCDProcess proc_tmp;
+    if (!NCDProcess_Init(&proc_tmp, 1, name, block)) {
+        NCDBlock_Free(&block);
+        return 0;
+    }
+    
+    NCDProgramElem elem;
+    NCDProgramElem_InitProcess(&elem, proc_tmp);
+    
+    if (!NCDProgram_PrependElem(state->prog, elem)) {
+        NCDProgramElem_Free(&elem);
+        return 0;
+    }
+    
+    if (!NCDValue_InitString(out_name_val, name)) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int desugar_block (struct desugar_state *state, NCDBlock *block)
+{
+    NCDStatement *stmt = NCDBlock_FirstStatement(block);
+    
+    while (stmt) {
+        switch (NCDStatement_Type(stmt)) {
+            case NCDSTATEMENT_REG: {
+                stmt = NCDBlock_NextStatement(block, stmt);
+            } break;
+            
+            case NCDSTATEMENT_IF: {
+                if (!desugar_if(state, block, stmt, &stmt)) {
+                    return 0;
+                }
+            } break;
+            
+            case NCDSTATEMENT_FOREACH: {
+                if (!desugar_foreach(state, block, stmt, &stmt)) {
+                    return 0;
+                }
+            } break;
+            
+            default: ASSERT(0);
+        }
+    }
+    
+    return 1;
+}
+
+static int desugar_if (struct desugar_state *state, NCDBlock *block, NCDStatement *stmt, NCDStatement **out_next)
+{
+    ASSERT(NCDStatement_Type(stmt) == NCDSTATEMENT_IF)
+    
+    NCDValue args;
+    NCDValue_InitList(&args);
+    
+    NCDIfBlock *ifblock = NCDStatement_IfBlock(stmt);
+    
+    while (NCDIfBlock_FirstIf(ifblock)) {
+        NCDIf ifc = NCDIfBlock_GrabIf(ifblock, NCDIfBlock_FirstIf(ifblock));
+        
+        NCDValue if_cond;
+        NCDBlock if_block;
+        NCDIf_FreeGrab(&ifc, &if_cond, &if_block);
+        
+        if (!NCDValue_ListAppend(&args, if_cond)) {
+            NCDValue_Free(&if_cond);
+            NCDBlock_Free(&if_block);
+            goto fail;
+        }
+        
+        NCDValue action_arg;
+        if (!add_template(state, if_block, &action_arg)) {
+            goto fail;
+        }
+        
+        if (!NCDValue_ListAppend(&args, action_arg)) {
+            NCDValue_Free(&action_arg);
+            goto fail;
+        }
+    }
+    
+    if (NCDStatement_IfElse(stmt)) {
+        NCDBlock else_block = NCDStatement_IfGrabElse(stmt);
+        
+        NCDValue action_arg;
+        if (!add_template(state, else_block, &action_arg)) {
+            goto fail;
+        }
+        
+        if (!NCDValue_ListAppend(&args, action_arg)) {
+            NCDValue_Free(&action_arg);
+            goto fail;
+        }
+    }
+    
+    NCDStatement new_stmt;
+    if (!NCDStatement_InitReg(&new_stmt, NCDStatement_Name(stmt), NULL, "embcall2_multif", args)) {
+        goto fail;
+    }
+    
+    stmt = NCDBlock_ReplaceStatement(block, stmt, new_stmt);
+    
+    *out_next = NCDBlock_NextStatement(block, stmt);
+    return 1;
+    
+fail:
+    NCDValue_Free(&args);
+    return 0;
+}
+
+static int desugar_foreach (struct desugar_state *state, NCDBlock *block, NCDStatement *stmt, NCDStatement **out_next)
+{
+    ASSERT(NCDStatement_Type(stmt) == NCDSTATEMENT_FOREACH)
+    
+    NCDValue args;
+    NCDValue_InitList(&args);
+    
+    NCDValue collection;
+    NCDBlock foreach_block;
+    NCDStatement_ForeachGrab(stmt, &collection, &foreach_block);
+    
+    NCDValue template_arg;
+    if (!add_template(state, foreach_block, &template_arg)) {
+        NCDValue_Free(&collection);
+        goto fail;
+    }
+    
+    if (!NCDValue_ListAppend(&args, collection)) {
+        NCDValue_Free(&template_arg);
+        NCDValue_Free(&collection);
+        goto fail;
+    }
+    
+    if (!NCDValue_ListAppend(&args, template_arg)) {
+        NCDValue_Free(&template_arg);
+        goto fail;
+    }
+    
+    NCDValue name1_arg;
+    if (!NCDValue_InitString(&name1_arg, NCDStatement_ForeachName1(stmt))) {
+        goto fail;
+    }
+    
+    if (!NCDValue_ListAppend(&args, name1_arg)) {
+        NCDValue_Free(&name1_arg);
+        goto fail;
+    }
+    
+    if (NCDStatement_ForeachName2(stmt)) {
+        NCDValue name2_arg;
+        if (!NCDValue_InitString(&name2_arg, NCDStatement_ForeachName2(stmt))) {
+            goto fail;
+        }
+        
+        if (!NCDValue_ListAppend(&args, name2_arg)) {
+            NCDValue_Free(&name2_arg);
+            goto fail;
+        }
+    }
+    
+    NCDStatement new_stmt;
+    if (!NCDStatement_InitReg(&new_stmt, NCDStatement_Name(stmt), NULL, "foreach_emb", args)) {
+        goto fail;
+    }
+    
+    stmt = NCDBlock_ReplaceStatement(block, stmt, new_stmt);
+    
+    *out_next = NCDBlock_NextStatement(block, stmt);
+    return 1;
+    
+fail:
+    NCDValue_Free(&args);
+    return 0;
+}
+
+int NCDSugar_Desugar (NCDProgram *prog)
+{
+    ASSERT(!NCDProgram_ContainsElemType(prog, NCDPROGRAMELEM_INCLUDE))
+    ASSERT(!NCDProgram_ContainsElemType(prog, NCDPROGRAMELEM_INCLUDE_GUARD))
+    
+    struct desugar_state state;
+    state.prog = prog;
+    state.template_name_ctr = 0;
+    
+    for (NCDProgramElem *elem = NCDProgram_FirstElem(prog); elem; elem = NCDProgram_NextElem(prog, elem)) {
+        ASSERT(NCDProgramElem_Type(elem) == NCDPROGRAMELEM_PROCESS)
+        NCDProcess *proc = NCDProgramElem_Process(elem);
+        
+        if (!desugar_block(&state, NCDProcess_Block(proc))) {
+            return 0;
+        }
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/ncd/NCDSugar.h b/external/badvpn_dns/ncd/NCDSugar.h
new file mode 100644
index 0000000..0aa0ad2
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDSugar.h
@@ -0,0 +1,38 @@
+/**
+ * @file NCDSugar.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDSUGAR_H
+#define BADVPN_NCDSUGAR_H
+
+#include <misc/debug.h>
+#include <ncd/NCDAst.h>
+
+int NCDSugar_Desugar (NCDProgram *prog) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDVal.c b/external/badvpn_dns/ncd/NCDVal.c
new file mode 100644
index 0000000..aecb82b
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDVal.c
@@ -0,0 +1,2065 @@
+/**
+ * @file NCDVal.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <limits.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+
+#include <misc/balloc.h>
+#include <misc/strdup.h>
+#include <misc/offset.h>
+#include <base/BLog.h>
+
+#include "NCDVal.h"
+
+#include <generated/blog_channel_NCDVal.h>
+
+#define TYPE_MASK_EXTERNAL_TYPE ((1 << 3) - 1)
+#define TYPE_MASK_INTERNAL_TYPE ((1 << 5) - 1)
+#define TYPE_SHIFT_DEPTH 5
+
+#define STOREDSTRING_TYPE (NCDVAL_STRING | (0 << 3))
+#define IDSTRING_TYPE (NCDVAL_STRING | (1 << 3))
+#define EXTERNALSTRING_TYPE (NCDVAL_STRING | (2 << 3))
+#define COMPOSEDSTRING_TYPE (NCDVAL_STRING | (3 << 3))
+
+static int make_type (int internal_type, int depth)
+{
+    ASSERT(internal_type == NCDVAL_LIST ||
+           internal_type == NCDVAL_MAP ||
+           internal_type == STOREDSTRING_TYPE ||
+           internal_type == IDSTRING_TYPE ||
+           internal_type == EXTERNALSTRING_TYPE ||
+           internal_type == COMPOSEDSTRING_TYPE)
+    ASSERT(depth >= 0)
+    ASSERT(depth <= NCDVAL_MAX_DEPTH)
+    
+    return (internal_type | (depth << TYPE_SHIFT_DEPTH));
+}
+
+static int get_external_type (int type)
+{
+    return (type & TYPE_MASK_EXTERNAL_TYPE);
+}
+
+static int get_internal_type (int type)
+{
+    return (type & TYPE_MASK_INTERNAL_TYPE);
+}
+
+static int get_depth (int type)
+{
+    return (type >> TYPE_SHIFT_DEPTH);
+}
+
+static int bump_depth (int *type_ptr, int elem_depth)
+{
+    if (get_depth(*type_ptr) < elem_depth + 1) {
+        if (elem_depth + 1 > NCDVAL_MAX_DEPTH) {
+            return 0;
+        }
+        *type_ptr = make_type(get_internal_type(*type_ptr), elem_depth + 1);
+    }
+    
+    return 1;
+}
+
+static void * NCDValMem__BufAt (NCDValMem *o, NCDVal__idx idx)
+{
+    ASSERT(idx >= 0)
+    ASSERT(idx < o->used)
+    
+    return (o->buf ? o->buf : o->fastbuf) + idx;
+}
+
+static NCDVal__idx NCDValMem__Alloc (NCDValMem *o, NCDVal__idx alloc_size, NCDVal__idx align)
+{
+    NCDVal__idx mod = o->used % align;
+    NCDVal__idx align_extra = mod ? (align - mod) : 0;
+    
+    if (alloc_size > NCDVAL_MAXIDX - align_extra) {
+        return -1;
+    }
+    NCDVal__idx aligned_alloc_size = align_extra + alloc_size;
+    
+    if (aligned_alloc_size > o->size - o->used) {
+        NCDVal__idx newsize = (o->buf ? o->size : NCDVAL_FIRST_SIZE);
+        while (aligned_alloc_size > newsize - o->used) {
+            if (newsize > NCDVAL_MAXIDX / 2) {
+                return -1;
+            }
+            newsize *= 2;
+        }
+        
+        char *newbuf;
+        
+        if (!o->buf) {
+            newbuf = malloc(newsize);
+            if (!newbuf) {
+                return -1;
+            }
+            memcpy(newbuf, o->fastbuf, o->used);
+        } else {
+            newbuf = realloc(o->buf, newsize);
+            if (!newbuf) {
+                return -1;
+            }
+        }
+        
+        o->buf = newbuf;
+        o->size = newsize;
+    }
+    
+    NCDVal__idx idx = o->used + align_extra;
+    o->used += aligned_alloc_size;
+    
+    return idx;
+}
+
+static NCDValRef NCDVal__Ref (NCDValMem *mem, NCDVal__idx idx)
+{
+    ASSERT(idx == -1 || mem)
+    
+    NCDValRef ref = {mem, idx};
+    return ref;
+}
+
+static void NCDVal__AssertMem (NCDValMem *mem)
+{
+    ASSERT(mem)
+    ASSERT(mem->size >= 0)
+    ASSERT(mem->used >= 0)
+    ASSERT(mem->used <= mem->size)
+    ASSERT(mem->buf || mem->size == NCDVAL_FASTBUF_SIZE)
+    ASSERT(!mem->buf || mem->size >= NCDVAL_FIRST_SIZE)
+}
+
+static void NCDVal_AssertExternal (NCDValMem *mem, const void *e_buf, size_t e_len)
+{
+#ifndef NDEBUG
+    const char *e_cbuf = e_buf;
+    char *buf = (mem->buf ? mem->buf : mem->fastbuf);
+    ASSERT(e_cbuf >= buf + mem->size || e_cbuf + e_len <= buf)
+#endif
+}
+
+static void NCDVal__AssertValOnly (NCDValMem *mem, NCDVal__idx idx)
+{
+    // placeholders
+    if (idx < -1) {
+        return;
+    }
+    
+    ASSERT(idx >= 0)
+    ASSERT(idx + sizeof(int) <= mem->used)
+    
+#ifndef NDEBUG
+    int *type_ptr = NCDValMem__BufAt(mem, idx);
+    
+    ASSERT(get_depth(*type_ptr) >= 0)
+    ASSERT(get_depth(*type_ptr) <= NCDVAL_MAX_DEPTH)
+    
+    switch (get_internal_type(*type_ptr)) {
+        case STOREDSTRING_TYPE: {
+            ASSERT(idx + sizeof(struct NCDVal__string) <= mem->used)
+            struct NCDVal__string *str_e = NCDValMem__BufAt(mem, idx);
+            ASSERT(str_e->length >= 0)
+            ASSERT(idx + sizeof(struct NCDVal__string) + str_e->length + 1 <= mem->used)
+        } break;
+        case NCDVAL_LIST: {
+            ASSERT(idx + sizeof(struct NCDVal__list) <= mem->used)
+            struct NCDVal__list *list_e = NCDValMem__BufAt(mem, idx);
+            ASSERT(list_e->maxcount >= 0)
+            ASSERT(list_e->count >= 0)
+            ASSERT(list_e->count <= list_e->maxcount)
+            ASSERT(idx + sizeof(struct NCDVal__list) + list_e->maxcount * sizeof(NCDVal__idx) <= mem->used)
+        } break;
+        case NCDVAL_MAP: {
+            ASSERT(idx + sizeof(struct NCDVal__map) <= mem->used)
+            struct NCDVal__map *map_e = NCDValMem__BufAt(mem, idx);
+            ASSERT(map_e->maxcount >= 0)
+            ASSERT(map_e->count >= 0)
+            ASSERT(map_e->count <= map_e->maxcount)
+            ASSERT(idx + sizeof(struct NCDVal__map) + map_e->maxcount * sizeof(struct NCDVal__mapelem) <= mem->used)
+        } break;
+        case IDSTRING_TYPE: {
+            ASSERT(idx + sizeof(struct NCDVal__idstring) <= mem->used)
+            struct NCDVal__idstring *ids_e = NCDValMem__BufAt(mem, idx);
+            ASSERT(ids_e->string_id >= 0)
+            ASSERT(ids_e->string_index)
+        } break;
+        case EXTERNALSTRING_TYPE: {
+            ASSERT(idx + sizeof(struct NCDVal__externalstring) <= mem->used)
+            struct NCDVal__externalstring *exs_e = NCDValMem__BufAt(mem, idx);
+            ASSERT(exs_e->data)
+            ASSERT(!exs_e->ref.target || exs_e->ref.next >= -1)
+            ASSERT(!exs_e->ref.target || exs_e->ref.next < mem->used)
+        } break;
+        case COMPOSEDSTRING_TYPE: {
+            ASSERT(idx + sizeof(struct NCDVal__composedstring) <= mem->used)
+            struct NCDVal__composedstring *cms_e = NCDValMem__BufAt(mem, idx);
+            ASSERT(cms_e->func_getptr)
+            ASSERT(!cms_e->ref.target || cms_e->ref.next >= -1)
+            ASSERT(!cms_e->ref.target || cms_e->ref.next < mem->used)
+        } break;
+        default: ASSERT(0);
+    }
+#endif
+}
+
+static void NCDVal__AssertVal (NCDValRef val)
+{
+    NCDVal__AssertMem(val.mem);
+    NCDVal__AssertValOnly(val.mem, val.idx);
+}
+
+static NCDValMapElem NCDVal__MapElem (NCDVal__idx elemidx)
+{
+    ASSERT(elemidx >= 0 || elemidx == -1)
+    
+    NCDValMapElem me = {elemidx};
+    return me;
+}
+
+static void NCDVal__MapAssertElemOnly (NCDValRef map, NCDVal__idx elemidx)
+{
+#ifndef NDEBUG
+    struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
+    ASSERT(elemidx >= map.idx + offsetof(struct NCDVal__map, elems))
+    ASSERT(elemidx < map.idx + offsetof(struct NCDVal__map, elems) + map_e->count * sizeof(struct NCDVal__mapelem))
+
+    struct NCDVal__mapelem *me_e = NCDValMem__BufAt(map.mem, elemidx);
+    NCDVal__AssertValOnly(map.mem, me_e->key_idx);
+    NCDVal__AssertValOnly(map.mem, me_e->val_idx);
+#endif
+}
+
+static void NCDVal__MapAssertElem (NCDValRef map, NCDValMapElem me)
+{
+    ASSERT(NCDVal_IsMap(map))
+    NCDVal__MapAssertElemOnly(map, me.elemidx);
+}
+
+static NCDVal__idx NCDVal__MapElemIdx (NCDVal__idx mapidx, NCDVal__idx pos)
+{
+    return mapidx + offsetof(struct NCDVal__map, elems) + pos * sizeof(struct NCDVal__mapelem);
+}
+
+static int NCDVal__Depth (NCDValRef val)
+{
+    ASSERT(val.idx != -1)
+    
+    // handle placeholders
+    if (val.idx < 0) {
+        return 0;
+    }
+    
+    int *elem_type_ptr = NCDValMem__BufAt(val.mem, val.idx);
+    int depth = get_depth(*elem_type_ptr);
+    ASSERT(depth >= 0)
+    ASSERT(depth <= NCDVAL_MAX_DEPTH)
+    
+    return depth;
+}
+
+static int NCDValMem__NeedRegisterLink (NCDValMem *mem, NCDVal__idx val_idx)
+{
+    NCDVal__AssertValOnly(mem, val_idx);
+    
+    return !(val_idx < -1) && get_internal_type(*(int *)NCDValMem__BufAt(mem, val_idx)) == COMPOSEDSTRING_TYPE;
+}
+
+static int NCDValMem__RegisterLink (NCDValMem *mem, NCDVal__idx val_idx, NCDVal__idx link_idx)
+{
+    NCDVal__AssertValOnly(mem, val_idx);
+    ASSERT(NCDValMem__NeedRegisterLink(mem, val_idx))
+    
+    NCDVal__idx cms_link_idx = NCDValMem__Alloc(mem, sizeof(struct NCDVal__cms_link), __alignof(struct NCDVal__cms_link));
+    if (cms_link_idx < 0) {
+        return 0;
+    }
+    
+    struct NCDVal__cms_link *cms_link = NCDValMem__BufAt(mem, cms_link_idx);
+    cms_link->link_idx = link_idx;
+    cms_link->next_cms_link = mem->first_cms_link;
+    mem->first_cms_link = cms_link_idx;
+    
+    return 1;
+}
+
+static void NCDValMem__PopLastRegisteredLink (NCDValMem *mem)
+{
+    ASSERT(mem->first_cms_link != -1)
+    
+    struct NCDVal__cms_link *cms_link = NCDValMem__BufAt(mem, mem->first_cms_link);
+    mem->first_cms_link = cms_link->next_cms_link;
+}
+
+static NCDValRef NCDVal__CopyComposedStringToStored (NCDValRef val)
+{
+    ASSERT(NCDVal_IsComposedString(val))
+    
+    struct NCDVal__composedstring cms_e = *(struct NCDVal__composedstring *)NCDValMem__BufAt(val.mem, val.idx);
+    
+    NCDValRef copy = NCDVal_NewStringUninitialized(val.mem, cms_e.length);
+    if (NCDVal_IsInvalid(copy)) {
+        return NCDVal_NewInvalid();
+    }
+    
+    char *copy_data = (char *)NCDVal_StringData(copy);
+    
+    size_t pos = 0;
+    while (pos < cms_e.length) {
+        const char *chunk_data;
+        size_t chunk_len;
+        cms_e.func_getptr(cms_e.user, cms_e.offset + pos, &chunk_data, &chunk_len);
+        ASSERT(chunk_data)
+        ASSERT(chunk_len > 0)
+        if (chunk_len > cms_e.length - pos) {
+            chunk_len = cms_e.length - pos;
+        }
+        memcpy(copy_data + pos, chunk_data, chunk_len);
+        pos += chunk_len;
+    }
+    
+    return copy;
+}
+
+static const char * NCDVal__composedstring_cstring_func (const b_cstring *cstr, size_t offset, size_t *out_length)
+{
+    ASSERT(offset < cstr->length)
+    ASSERT(out_length)
+    ASSERT(cstr->func == NCDVal__composedstring_cstring_func)
+    
+    size_t str_offset = cstr->user1.size;
+    NCDVal_ComposedString_func_getptr func_getptr = (NCDVal_ComposedString_func_getptr)cstr->user2.fptr;
+    void *user = cstr->user3.ptr;
+    
+    const char *data;
+    func_getptr(user, str_offset + offset, &data, out_length);
+    
+    ASSERT(data)
+    ASSERT(*out_length > 0)
+    
+    return data;
+}
+
+#include "NCDVal_maptree.h"
+#include <structure/CAvl_impl.h>
+
+void NCDValMem_Init (NCDValMem *o)
+{
+    o->buf = NULL;
+    o->size = NCDVAL_FASTBUF_SIZE;
+    o->used = 0;
+    o->first_ref = -1;
+    o->first_cms_link = -1;
+}
+
+void NCDValMem_Free (NCDValMem *o)
+{
+    NCDVal__AssertMem(o);
+    
+    NCDVal__idx refidx = o->first_ref;
+    while (refidx != -1) {
+        struct NCDVal__ref *ref = NCDValMem__BufAt(o, refidx);
+        ASSERT(ref->target)
+        BRefTarget_Deref(ref->target);
+        refidx = ref->next;
+    }
+    
+    if (o->buf) {
+        BFree(o->buf);
+    }
+}
+
+int NCDValMem_InitCopy (NCDValMem *o, NCDValMem *other)
+{
+    NCDVal__AssertMem(other);
+    
+    o->size = other->size;
+    o->used = other->used;
+    o->first_ref = other->first_ref;
+    o->first_cms_link = other->first_cms_link;
+    
+    if (!other->buf) {
+        o->buf = NULL;
+        memcpy(o->fastbuf, other->fastbuf, other->used);
+    } else {
+        o->buf = BAlloc(other->size);
+        if (!o->buf) {
+            goto fail0;
+        }
+        memcpy(o->buf, other->buf, other->used);
+    }
+    
+    NCDVal__idx refidx = o->first_ref;
+    while (refidx != -1) {
+        struct NCDVal__ref *ref = NCDValMem__BufAt(o, refidx);
+        ASSERT(ref->target)
+        if (!BRefTarget_Ref(ref->target)) {
+            goto fail1;
+        }
+        refidx = ref->next;
+    }
+    
+    return 1;
+    
+fail1:;
+    NCDVal__idx undo_refidx = o->first_ref;
+    while (undo_refidx != refidx) {
+        struct NCDVal__ref *ref = NCDValMem__BufAt(o, undo_refidx);
+        BRefTarget_Deref(ref->target);
+        undo_refidx = ref->next;
+    }
+    if (o->buf) {
+        BFree(o->buf);
+    }
+fail0:
+    return 0;
+}
+
+int NCDValMem_ConvertNonContinuousStrings (NCDValMem *o, NCDValRef *root_val)
+{
+    NCDVal__AssertMem(o);
+    ASSERT(root_val)
+    ASSERT(root_val->mem == o)
+    NCDVal__AssertValOnly(o, root_val->idx);
+    
+    while (o->first_cms_link != -1) {
+        struct NCDVal__cms_link cms_link = *(struct NCDVal__cms_link *)NCDValMem__BufAt(o, o->first_cms_link);
+        
+        NCDVal__idx val_idx = *(NCDVal__idx *)NCDValMem__BufAt(o, cms_link.link_idx);
+        NCDValRef val = NCDVal__Ref(o, val_idx);
+        ASSERT(NCDVal_IsComposedString(val))
+        
+        NCDValRef copy = NCDVal__CopyComposedStringToStored(val);
+        if (NCDVal_IsInvalid(copy)) {
+            return 0;
+        }
+        
+        *(int *)NCDValMem__BufAt(o, cms_link.link_idx) = copy.idx;
+        
+        o->first_cms_link = cms_link.next_cms_link;
+    }
+    
+    if (NCDVal_IsComposedString(*root_val)) {
+        NCDValRef copy = NCDVal__CopyComposedStringToStored(*root_val);
+        if (NCDVal_IsInvalid(copy)) {
+            return 0;
+        }
+        *root_val = copy;
+    }
+    
+    return 1;
+}
+
+void NCDVal_Assert (NCDValRef val)
+{
+    ASSERT(val.idx == -1 || (NCDVal__AssertVal(val), 1))
+}
+
+int NCDVal_IsInvalid (NCDValRef val)
+{
+    NCDVal_Assert(val);
+    
+    return (val.idx == -1);
+}
+
+int NCDVal_IsPlaceholder (NCDValRef val)
+{
+    NCDVal_Assert(val);
+    
+    return (val.idx < -1);
+}
+
+int NCDVal_Type (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    if (val.idx < -1) {
+        return NCDVAL_PLACEHOLDER;
+    }
+    
+    int *type_ptr = NCDValMem__BufAt(val.mem, val.idx);
+    
+    return get_external_type(*type_ptr);
+}
+
+NCDValRef NCDVal_NewInvalid (void)
+{
+    NCDValRef ref = {NULL, -1};
+    return ref;
+}
+
+NCDValRef NCDVal_NewPlaceholder (NCDValMem *mem, int plid)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(plid >= 0)
+    ASSERT(NCDVAL_MINIDX + plid < -1)
+    
+    NCDValRef ref = {mem, NCDVAL_MINIDX + plid};
+    return ref;
+}
+
+int NCDVal_PlaceholderId (NCDValRef val)
+{
+    ASSERT(NCDVal_IsPlaceholder(val))
+    
+    return (val.idx - NCDVAL_MINIDX);
+}
+
+NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val)
+{
+    NCDVal__AssertMem(mem);
+    NCDVal__AssertVal(val);
+    
+    if (val.idx < -1) {
+        return NCDVal_NewPlaceholder(mem, NCDVal_PlaceholderId(val));
+    }
+    
+    void *ptr = NCDValMem__BufAt(val.mem, val.idx);
+    
+    switch (get_internal_type(*(int *)ptr)) {
+        case STOREDSTRING_TYPE: {
+            struct NCDVal__string *str_e = ptr;
+            
+            NCDVal__idx size = sizeof(struct NCDVal__string) + str_e->length + 1;
+            NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__string));
+            if (idx < 0) {
+                goto fail;
+            }
+            
+            str_e = NCDValMem__BufAt(val.mem, val.idx);
+            struct NCDVal__string *new_str_e = NCDValMem__BufAt(mem, idx);
+            
+            memcpy(new_str_e, str_e, size);
+            
+            return NCDVal__Ref(mem, idx);
+        } break;
+        
+        case NCDVAL_LIST: {
+            struct NCDVal__list *list_e = ptr;
+            
+            NCDVal__idx size = sizeof(struct NCDVal__list) + list_e->maxcount * sizeof(NCDVal__idx);
+            NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__list));
+            if (idx < 0) {
+                goto fail;
+            }
+            
+            list_e = NCDValMem__BufAt(val.mem, val.idx);
+            struct NCDVal__list *new_list_e = NCDValMem__BufAt(mem, idx);
+            
+            *new_list_e = *list_e;
+            
+            NCDVal__idx count = list_e->count;
+            
+            for (NCDVal__idx i = 0; i < count; i++) {
+                NCDValRef elem_copy = NCDVal_NewCopy(mem, NCDVal__Ref(val.mem, list_e->elem_indices[i]));
+                if (NCDVal_IsInvalid(elem_copy)) {
+                    goto fail;
+                }
+                
+                if (NCDValMem__NeedRegisterLink(mem, elem_copy.idx)) {
+                    if (!NCDValMem__RegisterLink(mem, elem_copy.idx, idx + offsetof(struct NCDVal__list, elem_indices) + i * sizeof(NCDVal__idx))) {
+                        goto fail;
+                    }
+                }
+                
+                list_e = NCDValMem__BufAt(val.mem, val.idx);
+                new_list_e = NCDValMem__BufAt(mem, idx);
+                
+                new_list_e->elem_indices[i] = elem_copy.idx;
+            }
+            
+            return NCDVal__Ref(mem, idx);
+        } break;
+        
+        case NCDVAL_MAP: {
+            size_t count = NCDVal_MapCount(val);
+            
+            NCDValRef copy = NCDVal_NewMap(mem, count);
+            if (NCDVal_IsInvalid(copy)) {
+                goto fail;
+            }
+            
+            for (NCDValMapElem e = NCDVal_MapFirst(val); !NCDVal_MapElemInvalid(e); e = NCDVal_MapNext(val, e)) {
+                NCDValRef key_copy = NCDVal_NewCopy(mem, NCDVal_MapElemKey(val, e));
+                NCDValRef val_copy = NCDVal_NewCopy(mem, NCDVal_MapElemVal(val, e));
+                if (NCDVal_IsInvalid(key_copy) || NCDVal_IsInvalid(val_copy)) {
+                    goto fail;
+                }
+                
+                int inserted;
+                if (!NCDVal_MapInsert(copy, key_copy, val_copy, &inserted)) {
+                    goto fail;
+                }
+                ASSERT_EXECUTE(inserted)
+            }
+            
+            return copy;
+        } break;
+        
+        case IDSTRING_TYPE: {
+            NCDVal__idx size = sizeof(struct NCDVal__idstring);
+            NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__idstring));
+            if (idx < 0) {
+                goto fail;
+            }
+            
+            struct NCDVal__idstring *ids_e = NCDValMem__BufAt(val.mem, val.idx);
+            struct NCDVal__idstring *new_ids_e = NCDValMem__BufAt(mem, idx);
+            
+            *new_ids_e = *ids_e;
+            
+            return NCDVal__Ref(mem, idx);
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            struct NCDVal__externalstring *exs_e = ptr;
+            
+            return NCDVal_NewExternalString(mem, exs_e->data, exs_e->length, exs_e->ref.target);
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            struct NCDVal__composedstring *cms_e = ptr;
+            
+            NCDValComposedStringResource resource;
+            resource.func_getptr = cms_e->func_getptr;
+            resource.user = cms_e->user;
+            resource.ref_target = cms_e->ref.target;
+            
+            return NCDVal_NewComposedString(mem, resource, cms_e->offset, cms_e->length);
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    ASSERT(0);
+    
+fail:
+    return NCDVal_NewInvalid();
+}
+
+int NCDVal_Compare (NCDValRef val1, NCDValRef val2)
+{
+    NCDVal__AssertVal(val1);
+    NCDVal__AssertVal(val2);
+    
+    int type1 = NCDVal_Type(val1);
+    int type2 = NCDVal_Type(val2);
+    
+    if (type1 != type2) {
+        return (type1 > type2) - (type1 < type2);
+    }
+    
+    switch (type1) {
+        case NCDVAL_STRING: {
+            size_t len1 = NCDVal_StringLength(val1);
+            size_t len2 = NCDVal_StringLength(val2);
+            size_t min_len = len1 < len2 ? len1 : len2;
+            
+            int cmp = NCDVal_StringMemCmp(val1, val2, 0, 0, min_len);
+            if (cmp) {
+                return (cmp > 0) - (cmp < 0);
+            }
+            
+            return (len1 > len2) - (len1 < len2);
+        } break;
+        
+        case NCDVAL_LIST: {
+            size_t count1 = NCDVal_ListCount(val1);
+            size_t count2 = NCDVal_ListCount(val2);
+            size_t min_count = count1 < count2 ? count1 : count2;
+            
+            for (size_t i = 0; i < min_count; i++) {
+                NCDValRef ev1 = NCDVal_ListGet(val1, i);
+                NCDValRef ev2 = NCDVal_ListGet(val2, i);
+                
+                int cmp = NCDVal_Compare(ev1, ev2);
+                if (cmp) {
+                    return cmp;
+                }
+            }
+            
+            return (count1 > count2) - (count1 < count2);
+        } break;
+        
+        case NCDVAL_MAP: {
+            NCDValMapElem e1 = NCDVal_MapOrderedFirst(val1);
+            NCDValMapElem e2 = NCDVal_MapOrderedFirst(val2);
+            
+            while (1) {
+                int inv1 = NCDVal_MapElemInvalid(e1);
+                int inv2 = NCDVal_MapElemInvalid(e2);
+                if (inv1 || inv2) {
+                    return inv2 - inv1;
+                }
+                
+                NCDValRef key1 = NCDVal_MapElemKey(val1, e1);
+                NCDValRef key2 = NCDVal_MapElemKey(val2, e2);
+                
+                int cmp = NCDVal_Compare(key1, key2);
+                if (cmp) {
+                    return cmp;
+                }
+                
+                NCDValRef value1 = NCDVal_MapElemVal(val1, e1);
+                NCDValRef value2 = NCDVal_MapElemVal(val2, e2);
+                
+                cmp = NCDVal_Compare(value1, value2);
+                if (cmp) {
+                    return cmp;
+                }
+                
+                e1 = NCDVal_MapOrderedNext(val1, e1);
+                e2 = NCDVal_MapOrderedNext(val2, e2);
+            }
+        } break;
+        
+        case NCDVAL_PLACEHOLDER: {
+            int plid1 = NCDVal_PlaceholderId(val1);
+            int plid2 = NCDVal_PlaceholderId(val2);
+            
+            return (plid1 > plid2) - (plid1 < plid2);
+        } break;
+        
+        default:
+            ASSERT(0);
+            return 0;
+    }
+}
+
+NCDValSafeRef NCDVal_ToSafe (NCDValRef val)
+{
+    NCDVal_Assert(val);
+    
+    NCDValSafeRef sval = {val.idx};
+    return sval;
+}
+
+NCDValRef NCDVal_FromSafe (NCDValMem *mem, NCDValSafeRef sval)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(sval.idx == -1 || (NCDVal__AssertValOnly(mem, sval.idx), 1))
+    
+    NCDValRef val = {mem, sval.idx};
+    return val;
+}
+
+NCDValRef NCDVal_Moved (NCDValMem *mem, NCDValRef val)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(val.idx == -1 || (NCDVal__AssertValOnly(mem, val.idx), 1))
+    
+    NCDValRef val2 = {mem, val.idx};
+    return val2;
+}
+
+int NCDVal_HasOnlyContinuousStrings (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    switch (NCDVal_Type(val)) {
+        case NCDVAL_STRING: {
+            if (!NCDVal_IsContinuousString(val)) {
+                return 0;
+            }
+        } break;
+        
+        case NCDVAL_LIST: {
+            size_t count = NCDVal_ListCount(val);
+            for (size_t i = 0; i < count; i++) {
+                NCDValRef elem = NCDVal_ListGet(val, i);
+                if (!NCDVal_HasOnlyContinuousStrings(elem)) {
+                    return 0;
+                }
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            for (NCDValMapElem me = NCDVal_MapFirst(val); !NCDVal_MapElemInvalid(me); me = NCDVal_MapNext(val, me)) {
+                NCDValRef e_key = NCDVal_MapElemKey(val, me);
+                NCDValRef e_val = NCDVal_MapElemVal(val, me);
+                if (!NCDVal_HasOnlyContinuousStrings(e_key) || !NCDVal_HasOnlyContinuousStrings(e_val)) {
+                    return 0;
+                }
+            }
+        } break;
+        
+        case NCDVAL_PLACEHOLDER: {
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+    
+    return 1;
+}
+
+int NCDVal_IsString (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    return NCDVal_Type(val) == NCDVAL_STRING;
+}
+
+int NCDVal_IsContinuousString (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    if (val.idx < -1) {
+        return 0;
+    }
+    
+    switch (get_internal_type(*(int *)NCDValMem__BufAt(val.mem, val.idx))) {
+        case STOREDSTRING_TYPE:
+        case IDSTRING_TYPE:
+        case EXTERNALSTRING_TYPE:
+            return 1;
+        default:
+            return 0;
+    }
+}
+
+int NCDVal_IsStoredString (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    return !(val.idx < -1) && get_internal_type(*(int *)NCDValMem__BufAt(val.mem, val.idx)) == STOREDSTRING_TYPE;
+}
+
+int NCDVal_IsIdString (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    return !(val.idx < -1) && get_internal_type(*(int *)NCDValMem__BufAt(val.mem, val.idx)) == IDSTRING_TYPE;
+}
+
+int NCDVal_IsExternalString (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    return !(val.idx < -1) && get_internal_type(*(int *)NCDValMem__BufAt(val.mem, val.idx)) == EXTERNALSTRING_TYPE;
+}
+
+int NCDVal_IsComposedString (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    return !(val.idx < -1) && get_internal_type(*(int *)NCDValMem__BufAt(val.mem, val.idx)) == COMPOSEDSTRING_TYPE;
+}
+
+int NCDVal_IsStringNoNulls (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    return NCDVal_Type(val) == NCDVAL_STRING && !NCDVal_StringHasNulls(val);
+}
+
+NCDValRef NCDVal_NewString (NCDValMem *mem, const char *data)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(data)
+    NCDVal_AssertExternal(mem, data, strlen(data));
+    
+    return NCDVal_NewStringBin(mem, (const uint8_t *)data, strlen(data));
+}
+
+NCDValRef NCDVal_NewStringBin (NCDValMem *mem, const uint8_t *data, size_t len)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(len == 0 || data)
+    NCDVal_AssertExternal(mem, data, len);
+    
+    if (len > NCDVAL_MAXIDX - sizeof(struct NCDVal__string) - 1) {
+        goto fail;
+    }
+    
+    NCDVal__idx size = sizeof(struct NCDVal__string) + len + 1;
+    NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__string));
+    if (idx < 0) {
+        goto fail;
+    }
+    
+    struct NCDVal__string *str_e = NCDValMem__BufAt(mem, idx);
+    str_e->type = make_type(STOREDSTRING_TYPE, 0);
+    str_e->length = len;
+    if (len > 0) {
+        memcpy(str_e->data, data, len);
+    }
+    str_e->data[len] = '\0';
+    
+    return NCDVal__Ref(mem, idx);
+    
+fail:
+    return NCDVal_NewInvalid();
+}
+
+NCDValRef NCDVal_NewStringUninitialized (NCDValMem *mem, size_t len)
+{
+    NCDVal__AssertMem(mem);
+    
+    if (len > NCDVAL_MAXIDX - sizeof(struct NCDVal__string) - 1) {
+        goto fail;
+    }
+    
+    NCDVal__idx size = sizeof(struct NCDVal__string) + len + 1;
+    NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__string));
+    if (idx < 0) {
+        goto fail;
+    }
+    
+    struct NCDVal__string *str_e = NCDValMem__BufAt(mem, idx);
+    str_e->type = make_type(STOREDSTRING_TYPE, 0);
+    str_e->length = len;
+    str_e->data[len] = '\0';
+    
+    return NCDVal__Ref(mem, idx);
+    
+fail:
+    return NCDVal_NewInvalid();
+}
+
+NCDValRef NCDVal_NewIdString (NCDValMem *mem, NCD_string_id_t string_id, NCDStringIndex *string_index)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(string_id >= 0)
+    ASSERT(string_index)
+    
+    NCDVal__idx size = sizeof(struct NCDVal__idstring);
+    NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__idstring));
+    if (idx < 0) {
+        goto fail;
+    }
+    
+    struct NCDVal__idstring *ids_e = NCDValMem__BufAt(mem, idx);
+    ids_e->type = make_type(IDSTRING_TYPE, 0);
+    ids_e->string_id = string_id;
+    ids_e->string_index = string_index;
+    
+    return NCDVal__Ref(mem, idx);
+    
+fail:
+    return NCDVal_NewInvalid();
+}
+
+NCDValRef NCDVal_NewExternalString (NCDValMem *mem, const char *data, size_t len,
+                                    BRefTarget *ref_target)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(data)
+    NCDVal_AssertExternal(mem, data, len);
+    
+    NCDVal__idx size = sizeof(struct NCDVal__externalstring);
+    NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__externalstring));
+    if (idx < 0) {
+        goto fail;
+    }
+    
+    if (ref_target) {
+        if (!BRefTarget_Ref(ref_target)) {
+            goto fail;
+        }
+    }
+    
+    struct NCDVal__externalstring *exs_e = NCDValMem__BufAt(mem, idx);
+    exs_e->type = make_type(EXTERNALSTRING_TYPE, 0);
+    exs_e->data = data;
+    exs_e->length = len;
+    exs_e->ref.target = ref_target;
+    
+    if (ref_target) {
+        exs_e->ref.next = mem->first_ref;
+        mem->first_ref = idx + offsetof(struct NCDVal__externalstring, ref);
+    }
+    
+    return NCDVal__Ref(mem, idx);
+    
+fail:
+    return NCDVal_NewInvalid();
+}
+
+NCDValRef NCDVal_NewComposedString (NCDValMem *mem, NCDValComposedStringResource resource, size_t offset, size_t length)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(resource.func_getptr)
+    
+    NCDVal__idx size = sizeof(struct NCDVal__composedstring);
+    NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__composedstring));
+    if (idx < 0) {
+        goto fail;
+    }
+    
+    if (resource.ref_target) {
+        if (!BRefTarget_Ref(resource.ref_target)) {
+            goto fail;
+        }
+    }
+    
+    struct NCDVal__composedstring *cms_e = NCDValMem__BufAt(mem, idx);
+    cms_e->type = make_type(COMPOSEDSTRING_TYPE, 0);
+    cms_e->offset = offset;
+    cms_e->length = length;
+    cms_e->func_getptr = resource.func_getptr;
+    cms_e->user = resource.user;
+    cms_e->ref.target = resource.ref_target;
+    
+    if (resource.ref_target) {
+        cms_e->ref.next = mem->first_ref;
+        mem->first_ref = idx + offsetof(struct NCDVal__composedstring, ref);
+    }
+    
+    return NCDVal__Ref(mem, idx);
+    
+fail:
+    return NCDVal_NewInvalid();
+}
+
+const char * NCDVal_StringData (NCDValRef contstring)
+{
+    ASSERT(NCDVal_IsContinuousString(contstring))
+    
+    void *ptr = NCDValMem__BufAt(contstring.mem, contstring.idx);
+    
+    switch (get_internal_type(*(int *)ptr)) {
+        case STOREDSTRING_TYPE: {
+            struct NCDVal__string *str_e = ptr;
+            return str_e->data;
+        } break;
+        
+        case IDSTRING_TYPE: {
+            struct NCDVal__idstring *ids_e = ptr;
+            const char *value = NCDStringIndex_Value(ids_e->string_index, ids_e->string_id);
+            return value;
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            struct NCDVal__externalstring *exs_e = ptr;
+            return exs_e->data;
+        } break;
+        
+        default:
+            ASSERT(0);
+            return NULL;
+    }
+}
+
+size_t NCDVal_StringLength (NCDValRef string)
+{
+    ASSERT(NCDVal_IsString(string))
+    
+    void *ptr = NCDValMem__BufAt(string.mem, string.idx);
+    
+    switch (get_internal_type(*(int *)ptr)) {
+        case STOREDSTRING_TYPE: {
+            struct NCDVal__string *str_e = ptr;
+            return str_e->length;
+        } break;
+        
+        case IDSTRING_TYPE: {
+            struct NCDVal__idstring *ids_e = ptr;
+            return NCDStringIndex_Length(ids_e->string_index, ids_e->string_id);
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            struct NCDVal__externalstring *exs_e = ptr;
+            return exs_e->length;
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            struct NCDVal__composedstring *cms_e = ptr;
+            return cms_e->length;
+        } break;
+        
+        default:
+            ASSERT(0);
+            return 0;
+    }
+}
+
+b_cstring NCDValComposedStringResource_Cstring (NCDValComposedStringResource resource, size_t offset, size_t length)
+{
+    b_cstring cstr;
+    cstr.length = length;
+    cstr.func = NCDVal__composedstring_cstring_func;
+    cstr.user1.size = offset;
+    cstr.user2.fptr = (void (*) (void))resource.func_getptr;
+    cstr.user3.ptr = resource.user;
+    return cstr;
+}
+
+b_cstring NCDVal_StringCstring (NCDValRef string)
+{
+    ASSERT(NCDVal_IsString(string))
+    
+    void *ptr = NCDValMem__BufAt(string.mem, string.idx);
+    
+    switch (get_internal_type(*(int *)ptr)) {
+        case STOREDSTRING_TYPE: {
+            struct NCDVal__string *str_e = ptr;
+            return b_cstring_make_buf(str_e->data, str_e->length);
+        } break;
+        
+        case IDSTRING_TYPE: {
+            struct NCDVal__idstring *ids_e = ptr;
+            return b_cstring_make_buf(NCDStringIndex_Value(ids_e->string_index, ids_e->string_id), NCDStringIndex_Length(ids_e->string_index, ids_e->string_id));
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            struct NCDVal__externalstring *exs_e = ptr;
+            return b_cstring_make_buf(exs_e->data, exs_e->length);
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            struct NCDVal__composedstring *cms_e = ptr;
+            b_cstring cstr;
+            cstr.length = cms_e->length;
+            cstr.func = NCDVal__composedstring_cstring_func;
+            cstr.user1.size = cms_e->offset;
+            cstr.user2.fptr = (void (*) (void))cms_e->func_getptr;
+            cstr.user3.ptr = cms_e->user;
+            return cstr;
+        } break;
+        
+        default: {
+            ASSERT(0);
+            return b_cstring_make_empty();
+        } break;
+    }
+}
+
+int NCDVal_StringNullTerminate (NCDValRef string, NCDValNullTermString *out)
+{
+    ASSERT(NCDVal_IsString(string))
+    ASSERT(out)
+    
+    void *ptr = NCDValMem__BufAt(string.mem, string.idx);
+    
+    switch (get_internal_type(*(int *)ptr)) {
+        case STOREDSTRING_TYPE: {
+            struct NCDVal__string *str_e = ptr;
+            out->data = str_e->data;
+            out->is_allocated = 0;
+            return 1;
+        } break;
+        
+        case IDSTRING_TYPE: {
+            struct NCDVal__idstring *ids_e = ptr;
+            out->data = (char *)NCDStringIndex_Value(ids_e->string_index, ids_e->string_id);
+            out->is_allocated = 0;
+            return 1;
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            struct NCDVal__externalstring *exs_e = ptr;
+            
+            char *copy = b_strdup_bin(exs_e->data, exs_e->length);
+            if (!copy) {
+                return 0;
+            }
+            
+            out->data = copy;
+            out->is_allocated = 1;
+            return 1;
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            struct NCDVal__composedstring *cms_e = ptr;
+            size_t length = cms_e->length;
+            
+            if (length == SIZE_MAX) {
+                return 0;
+            }
+            
+            char *copy = BAlloc(length + 1);
+            if (!copy) {
+                return 0;
+            }
+            
+            NCDVal_StringCopyOut(string, 0, length, copy);
+            copy[length] = '\0';
+            
+            out->data = copy;
+            out->is_allocated = 1;
+            return 1;
+        } break;
+        
+        default:
+            ASSERT(0);
+            return 0;
+    }
+}
+
+NCDValNullTermString NCDValNullTermString_NewDummy (void)
+{
+    NCDValNullTermString nts;
+    nts.data = NULL;
+    nts.is_allocated = 0;
+    return nts;
+}
+
+void NCDValNullTermString_Free (NCDValNullTermString *o)
+{
+    if (o->is_allocated) {
+        BFree(o->data);
+    }
+}
+
+int NCDVal_StringContinuize (NCDValRef string, NCDValContString *out)
+{
+    ASSERT(NCDVal_IsString(string))
+    ASSERT(out)
+    
+    if (NCDVal_IsContinuousString(string)) {
+        out->data = (char *)NCDVal_StringData(string);
+        out->is_allocated = 0;
+        return 1;
+    }
+    
+    size_t length = NCDVal_StringLength(string);
+    
+    char *data = BAlloc(length);
+    if (!data) {
+        return 0;
+    }
+    
+    NCDVal_StringCopyOut(string, 0, length, data);
+    
+    out->data = data;
+    out->is_allocated = 1;
+    return 1;
+}
+
+NCDValContString NCDValContString_NewDummy (void)
+{
+    NCDValContString cts;
+    cts.data = NULL;
+    cts.is_allocated = 0;
+    return cts;
+}
+
+void NCDValContString_Free (NCDValContString *o)
+{
+    if (o->is_allocated) {
+        BFree(o->data);
+    }
+}
+
+void NCDVal_IdStringGet (NCDValRef idstring, NCD_string_id_t *out_string_id,
+                         NCDStringIndex **out_string_index)
+{
+    ASSERT(NCDVal_IsIdString(idstring))
+    ASSERT(out_string_id)
+    ASSERT(out_string_index)
+    
+    struct NCDVal__idstring *ids_e = NCDValMem__BufAt(idstring.mem, idstring.idx);
+    *out_string_id = ids_e->string_id;
+    *out_string_index = ids_e->string_index;
+}
+
+NCD_string_id_t NCDVal_IdStringId (NCDValRef idstring)
+{
+    ASSERT(NCDVal_IsIdString(idstring))
+    
+    struct NCDVal__idstring *ids_e = NCDValMem__BufAt(idstring.mem, idstring.idx);
+    return ids_e->string_id;
+}
+
+NCDStringIndex * NCDVal_IdStringStringIndex (NCDValRef idstring)
+{
+    ASSERT(NCDVal_IsIdString(idstring))
+    
+    struct NCDVal__idstring *ids_e = NCDValMem__BufAt(idstring.mem, idstring.idx);
+    return ids_e->string_index;
+}
+
+BRefTarget * NCDVal_ExternalStringTarget (NCDValRef externalstring)
+{
+    ASSERT(NCDVal_IsExternalString(externalstring))
+    
+    struct NCDVal__externalstring *exs_e = NCDValMem__BufAt(externalstring.mem, externalstring.idx);
+    return exs_e->ref.target;
+}
+
+NCDValComposedStringResource NCDVal_ComposedStringResource (NCDValRef composedstring)
+{
+    ASSERT(NCDVal_IsComposedString(composedstring))
+    
+    struct NCDVal__composedstring *cms_e = NCDValMem__BufAt(composedstring.mem, composedstring.idx);
+    
+    NCDValComposedStringResource res;
+    res.func_getptr = cms_e->func_getptr;
+    res.user = cms_e->user;
+    res.ref_target = cms_e->ref.target;
+    
+    return res;
+}
+
+size_t NCDVal_ComposedStringOffset (NCDValRef composedstring)
+{
+    ASSERT(NCDVal_IsComposedString(composedstring))
+    
+    struct NCDVal__composedstring *cms_e = NCDValMem__BufAt(composedstring.mem, composedstring.idx);
+    
+    return cms_e->offset;
+}
+
+int NCDVal_StringHasNulls (NCDValRef string)
+{
+    ASSERT(NCDVal_IsString(string))
+    
+    void *ptr = NCDValMem__BufAt(string.mem, string.idx);
+    
+    switch (get_internal_type(*(int *)ptr)) {
+        case IDSTRING_TYPE: {
+            struct NCDVal__idstring *ids_e = ptr;
+            return NCDStringIndex_HasNulls(ids_e->string_index, ids_e->string_id);
+        } break;
+        
+        case STOREDSTRING_TYPE:
+        case EXTERNALSTRING_TYPE: {
+            const char *data = NCDVal_StringData(string);
+            size_t length = NCDVal_StringLength(string);
+            return !!memchr(data, '\0', length);
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            b_cstring cstr = NCDVal_StringCstring(string);
+            return b_cstring_memchr(cstr, 0, cstr.length, '\0', NULL);
+        } break;
+        
+        default:
+            ASSERT(0);
+            return 0;
+    }
+}
+
+int NCDVal_StringEquals (NCDValRef string, const char *data)
+{
+    ASSERT(NCDVal_IsString(string))
+    ASSERT(data)
+    
+    size_t data_len = strlen(data);
+    
+    return NCDVal_StringLength(string) == data_len && NCDVal_StringRegionEquals(string, 0, data_len, data);
+}
+
+int NCDVal_StringEqualsId (NCDValRef string, NCD_string_id_t string_id,
+                           NCDStringIndex *string_index)
+{
+    ASSERT(NCDVal_IsString(string))
+    ASSERT(string_id >= 0)
+    ASSERT(string_index)
+    
+    void *ptr = NCDValMem__BufAt(string.mem, string.idx);
+    
+    switch (get_internal_type(*(int *)ptr)) {
+        case STOREDSTRING_TYPE: {
+            struct NCDVal__string *str_e = ptr;
+            const char *string_data = NCDStringIndex_Value(string_index, string_id);
+            size_t string_length = NCDStringIndex_Length(string_index, string_id);
+            return (string_length == str_e->length) && !memcmp(string_data, str_e->data, string_length);
+        } break;
+        
+        case IDSTRING_TYPE: {
+            struct NCDVal__idstring *ids_e = ptr;
+            ASSERT(ids_e->string_index == string_index)
+            return ids_e->string_id == string_id;
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            struct NCDVal__externalstring *exs_e = ptr;
+            const char *string_data = NCDStringIndex_Value(string_index, string_id);
+            size_t string_length = NCDStringIndex_Length(string_index, string_id);
+            return (string_length == exs_e->length) && !memcmp(string_data, exs_e->data, string_length);
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            struct NCDVal__composedstring *cms_e = ptr;
+            const char *string_data = NCDStringIndex_Value(string_index, string_id);
+            size_t string_length = NCDStringIndex_Length(string_index, string_id);
+            return (string_length == cms_e->length) && NCDVal_StringRegionEquals(string, 0, string_length, string_data);
+        } break;
+        
+        default:
+            ASSERT(0);
+            return 0;
+    }
+}
+
+int NCDVal_StringMemCmp (NCDValRef string1, NCDValRef string2, size_t start1, size_t start2, size_t length)
+{
+    ASSERT(NCDVal_IsString(string1))
+    ASSERT(NCDVal_IsString(string2))
+    ASSERT(start1 <= NCDVal_StringLength(string1))
+    ASSERT(start2 <= NCDVal_StringLength(string2))
+    ASSERT(length <= NCDVal_StringLength(string1) - start1)
+    ASSERT(length <= NCDVal_StringLength(string2) - start2)
+    
+    if (NCDVal_IsContinuousString(string1) && NCDVal_IsContinuousString(string2)) {
+        return memcmp(NCDVal_StringData(string1) + start1, NCDVal_StringData(string2) + start2, length);
+    }
+    
+    b_cstring cstr1 = NCDVal_StringCstring(string1);
+    b_cstring cstr2 = NCDVal_StringCstring(string2);
+    return b_cstring_memcmp(cstr1, cstr2, start1, start2, length);
+}
+
+void NCDVal_StringCopyOut (NCDValRef string, size_t start, size_t length, char *dst)
+{
+    ASSERT(NCDVal_IsString(string))
+    ASSERT(start <= NCDVal_StringLength(string))
+    ASSERT(length <= NCDVal_StringLength(string) - start)
+    
+    if (NCDVal_IsContinuousString(string)) {
+        memcpy(dst, NCDVal_StringData(string) + start, length);
+        return;
+    }
+    
+    b_cstring cstr = NCDVal_StringCstring(string);
+    b_cstring_copy_to_buf(cstr, start, length, dst);
+}
+
+int NCDVal_StringRegionEquals (NCDValRef string, size_t start, size_t length, const char *data)
+{
+    ASSERT(NCDVal_IsString(string))
+    ASSERT(start <= NCDVal_StringLength(string))
+    ASSERT(length <= NCDVal_StringLength(string) - start)
+    
+    if (NCDVal_IsContinuousString(string)) {
+        return !memcmp(NCDVal_StringData(string) + start, data, length);
+    }
+    
+    b_cstring cstr = NCDVal_StringCstring(string);
+    return b_cstring_equals_buffer(cstr, start, length, data);
+}
+
+int NCDVal_IsList (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    return NCDVal_Type(val) == NCDVAL_LIST;
+}
+
+NCDValRef NCDVal_NewList (NCDValMem *mem, size_t maxcount)
+{
+    NCDVal__AssertMem(mem);
+    
+    if (maxcount > (NCDVAL_MAXIDX - sizeof(struct NCDVal__list)) / sizeof(NCDVal__idx)) {
+        goto fail;
+    }
+    
+    NCDVal__idx size = sizeof(struct NCDVal__list) + maxcount * sizeof(NCDVal__idx);
+    NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__list));
+    if (idx < 0) {
+        goto fail;
+    }
+    
+    struct NCDVal__list *list_e = NCDValMem__BufAt(mem, idx);
+    list_e->type = make_type(NCDVAL_LIST, 0);
+    list_e->maxcount = maxcount;
+    list_e->count = 0;
+    
+    return NCDVal__Ref(mem, idx);
+    
+fail:
+    return NCDVal_NewInvalid();
+}
+
+int NCDVal_ListAppend (NCDValRef list, NCDValRef elem)
+{
+    ASSERT(NCDVal_IsList(list))
+    ASSERT(NCDVal_ListCount(list) < NCDVal_ListMaxCount(list))
+    ASSERT(elem.mem == list.mem)
+    NCDVal__AssertValOnly(list.mem, elem.idx);
+    
+    struct NCDVal__list *list_e = NCDValMem__BufAt(list.mem, list.idx);
+    
+    int new_type = list_e->type;
+    if (!bump_depth(&new_type, NCDVal__Depth(elem))) {
+        return 0;
+    }
+    
+    if (NCDValMem__NeedRegisterLink(list.mem, elem.idx)) {
+        if (!NCDValMem__RegisterLink(list.mem, elem.idx, list.idx + offsetof(struct NCDVal__list, elem_indices) + list_e->count * sizeof(NCDVal__idx))) {
+            return 0;
+        }
+        list_e = NCDValMem__BufAt(list.mem, list.idx);
+    }
+    
+    list_e->type = new_type;
+    list_e->elem_indices[list_e->count++] = elem.idx;
+    
+    return 1;
+}
+
+size_t NCDVal_ListCount (NCDValRef list)
+{
+    ASSERT(NCDVal_IsList(list))
+    
+    struct NCDVal__list *list_e = NCDValMem__BufAt(list.mem, list.idx);
+    
+    return list_e->count;
+}
+
+size_t NCDVal_ListMaxCount (NCDValRef list)
+{
+    ASSERT(NCDVal_IsList(list))
+    
+    struct NCDVal__list *list_e = NCDValMem__BufAt(list.mem, list.idx);
+    
+    return list_e->maxcount;
+}
+
+NCDValRef NCDVal_ListGet (NCDValRef list, size_t pos)
+{
+    ASSERT(NCDVal_IsList(list))
+    ASSERT(pos < NCDVal_ListCount(list))
+    
+    struct NCDVal__list *list_e = NCDValMem__BufAt(list.mem, list.idx);
+    
+    ASSERT(pos < list_e->count)
+    NCDVal__AssertValOnly(list.mem, list_e->elem_indices[pos]);
+    
+    return NCDVal__Ref(list.mem, list_e->elem_indices[pos]);
+}
+
+int NCDVal_ListRead (NCDValRef list, int num, ...)
+{
+    ASSERT(NCDVal_IsList(list))
+    ASSERT(num >= 0)
+    
+    struct NCDVal__list *list_e = NCDValMem__BufAt(list.mem, list.idx);
+    
+    if (num != list_e->count) {
+        return 0;
+    }
+    
+    va_list ap;
+    va_start(ap, num);
+    
+    for (int i = 0; i < num; i++) {
+        NCDValRef *dest = va_arg(ap, NCDValRef *);
+        *dest = NCDVal__Ref(list.mem, list_e->elem_indices[i]);
+    }
+    
+    va_end(ap);
+    
+    return 1;
+}
+
+int NCDVal_ListReadHead (NCDValRef list, int num, ...)
+{
+    ASSERT(NCDVal_IsList(list))
+    ASSERT(num >= 0)
+    
+    struct NCDVal__list *list_e = NCDValMem__BufAt(list.mem, list.idx);
+    
+    if (num > list_e->count) {
+        return 0;
+    }
+    
+    va_list ap;
+    va_start(ap, num);
+    
+    for (int i = 0; i < num; i++) {
+        NCDValRef *dest = va_arg(ap, NCDValRef *);
+        *dest = NCDVal__Ref(list.mem, list_e->elem_indices[i]);
+    }
+    
+    va_end(ap);
+    
+    return 1;
+}
+
+int NCDVal_IsMap (NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    
+    return NCDVal_Type(val) == NCDVAL_MAP;
+}
+
+NCDValRef NCDVal_NewMap (NCDValMem *mem, size_t maxcount)
+{
+    NCDVal__AssertMem(mem);
+    
+    if (maxcount > (NCDVAL_MAXIDX - sizeof(struct NCDVal__map)) / sizeof(struct NCDVal__mapelem)) {
+        goto fail;
+    }
+    
+    NCDVal__idx size = sizeof(struct NCDVal__map) + maxcount * sizeof(struct NCDVal__mapelem);
+    NCDVal__idx idx = NCDValMem__Alloc(mem, size, __alignof(struct NCDVal__map));
+    if (idx < 0) {
+        goto fail;
+    }
+    
+    struct NCDVal__map *map_e = NCDValMem__BufAt(mem, idx);
+    map_e->type = make_type(NCDVAL_MAP, 0);
+    map_e->maxcount = maxcount;
+    map_e->count = 0;
+    NCDVal__MapTree_Init(&map_e->tree);
+    
+    return NCDVal__Ref(mem, idx);
+    
+fail:
+    return NCDVal_NewInvalid();
+}
+
+int NCDVal_MapInsert (NCDValRef map, NCDValRef key, NCDValRef val, int *out_inserted)
+{
+    ASSERT(NCDVal_IsMap(map))
+    ASSERT(NCDVal_MapCount(map) < NCDVal_MapMaxCount(map))
+    ASSERT(key.mem == map.mem)
+    ASSERT(val.mem == map.mem)
+    NCDVal__AssertValOnly(map.mem, key.idx);
+    NCDVal__AssertValOnly(map.mem, val.idx);
+    
+    struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
+    
+    int new_type = map_e->type;
+    if (!bump_depth(&new_type, NCDVal__Depth(key)) || !bump_depth(&new_type, NCDVal__Depth(val))) {
+        goto fail0;
+    }
+    
+    NCDVal__idx elemidx = NCDVal__MapElemIdx(map.idx, map_e->count);
+    
+    if (NCDValMem__NeedRegisterLink(map.mem, key.idx)) {
+        if (!NCDValMem__RegisterLink(map.mem, key.idx, elemidx + offsetof(struct NCDVal__mapelem, key_idx))) {
+            goto fail0;
+        }
+        map_e = NCDValMem__BufAt(map.mem, map.idx);
+    }
+    
+    if (NCDValMem__NeedRegisterLink(map.mem, val.idx)) {
+        if (!NCDValMem__RegisterLink(map.mem, val.idx, elemidx + offsetof(struct NCDVal__mapelem, val_idx))) {
+            goto fail1;
+        }
+        map_e = NCDValMem__BufAt(map.mem, map.idx);
+    }
+    
+    struct NCDVal__mapelem *me_e = NCDValMem__BufAt(map.mem, elemidx);
+    ASSERT(me_e == &map_e->elems[map_e->count])
+    me_e->key_idx = key.idx;
+    me_e->val_idx = val.idx;
+    
+    int res = NCDVal__MapTree_Insert(&map_e->tree, map.mem, NCDVal__MapTreeDeref(map.mem, elemidx), NULL);
+    if (!res) {
+        if (out_inserted) {
+            *out_inserted = 0;
+        }
+        return 1;
+    }
+    
+    map_e->type = new_type;
+    map_e->count++;
+    
+    if (out_inserted) {
+        *out_inserted = 1;
+    }
+    return 1;
+    
+fail1:
+    if (NCDValMem__NeedRegisterLink(map.mem, key.idx)) {
+        NCDValMem__PopLastRegisteredLink(map.mem);
+    }
+fail0:
+    return 0;
+}
+
+size_t NCDVal_MapCount (NCDValRef map)
+{
+    ASSERT(NCDVal_IsMap(map))
+    
+    struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
+    
+    return map_e->count;
+}
+
+size_t NCDVal_MapMaxCount (NCDValRef map)
+{
+    ASSERT(NCDVal_IsMap(map))
+    
+    struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
+    
+    return map_e->maxcount;
+}
+
+int NCDVal_MapElemInvalid (NCDValMapElem me)
+{
+    ASSERT(me.elemidx >= 0 || me.elemidx == -1)
+    
+    return me.elemidx < 0;
+}
+
+NCDValMapElem NCDVal_MapFirst (NCDValRef map)
+{
+    ASSERT(NCDVal_IsMap(map))
+    
+    struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
+    
+    if (map_e->count == 0) {
+        return NCDVal__MapElem(-1);
+    }
+    
+    NCDVal__idx elemidx = NCDVal__MapElemIdx(map.idx, 0);
+    NCDVal__MapAssertElemOnly(map, elemidx);
+    
+    return NCDVal__MapElem(elemidx);
+}
+
+NCDValMapElem NCDVal_MapNext (NCDValRef map, NCDValMapElem me)
+{
+    NCDVal__MapAssertElem(map, me);
+    
+    struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
+    ASSERT(map_e->count > 0)
+    
+    NCDVal__idx last_elemidx = NCDVal__MapElemIdx(map.idx, map_e->count - 1);
+    ASSERT(me.elemidx <= last_elemidx)
+    
+    if (me.elemidx == last_elemidx) {
+        return NCDVal__MapElem(-1);
+    }
+    
+    NCDVal__idx elemidx = me.elemidx + sizeof(struct NCDVal__mapelem);
+    NCDVal__MapAssertElemOnly(map, elemidx);
+    
+    return NCDVal__MapElem(elemidx);
+}
+
+NCDValMapElem NCDVal_MapOrderedFirst (NCDValRef map)
+{
+    ASSERT(NCDVal_IsMap(map))
+    
+    struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
+    
+    NCDVal__MapTreeRef ref = NCDVal__MapTree_GetFirst(&map_e->tree, map.mem);
+    ASSERT(ref.link == -1 || (NCDVal__MapAssertElemOnly(map, ref.link), 1))
+    
+    return NCDVal__MapElem(ref.link);
+}
+
+NCDValMapElem NCDVal_MapOrderedNext (NCDValRef map, NCDValMapElem me)
+{
+    NCDVal__MapAssertElem(map, me);
+    
+    struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
+    
+    NCDVal__MapTreeRef ref = NCDVal__MapTree_GetNext(&map_e->tree, map.mem, NCDVal__MapTreeDeref(map.mem, me.elemidx));
+    ASSERT(ref.link == -1 || (NCDVal__MapAssertElemOnly(map, ref.link), 1))
+    
+    return NCDVal__MapElem(ref.link);
+}
+
+NCDValRef NCDVal_MapElemKey (NCDValRef map, NCDValMapElem me)
+{
+    NCDVal__MapAssertElem(map, me);
+    
+    struct NCDVal__mapelem *me_e = NCDValMem__BufAt(map.mem, me.elemidx);
+    
+    return NCDVal__Ref(map.mem, me_e->key_idx);
+}
+
+NCDValRef NCDVal_MapElemVal (NCDValRef map, NCDValMapElem me)
+{
+    NCDVal__MapAssertElem(map, me);
+    
+    struct NCDVal__mapelem *me_e = NCDValMem__BufAt(map.mem, me.elemidx);
+    
+    return NCDVal__Ref(map.mem, me_e->val_idx);
+}
+
+NCDValMapElem NCDVal_MapFindKey (NCDValRef map, NCDValRef key)
+{
+    ASSERT(NCDVal_IsMap(map))
+    NCDVal__AssertVal(key);
+    
+    struct NCDVal__map *map_e = NCDValMem__BufAt(map.mem, map.idx);
+    
+    NCDVal__MapTreeRef ref = NCDVal__MapTree_LookupExact(&map_e->tree, map.mem, key);
+    ASSERT(ref.link == -1 || (NCDVal__MapAssertElemOnly(map, ref.link), 1))
+    
+    return NCDVal__MapElem(ref.link);
+}
+
+NCDValRef NCDVal_MapGetValue (NCDValRef map, const char *key_str)
+{
+    ASSERT(NCDVal_IsMap(map))
+    ASSERT(key_str)
+    
+    NCDValMem mem;
+    mem.buf = NULL;
+    mem.size = NCDVAL_FASTBUF_SIZE;
+    mem.used = sizeof(struct NCDVal__externalstring);
+    mem.first_ref = -1;
+    
+    struct NCDVal__externalstring *exs_e = (void *)mem.fastbuf;
+    exs_e->type = make_type(EXTERNALSTRING_TYPE, 0);
+    exs_e->data = key_str;
+    exs_e->length = strlen(key_str);
+    exs_e->ref.target = NULL;
+    
+    NCDValRef key = NCDVal__Ref(&mem, 0);
+    
+    NCDValMapElem elem = NCDVal_MapFindKey(map, key);
+    if (NCDVal_MapElemInvalid(elem)) {
+        return NCDVal_NewInvalid();
+    }
+    
+    return NCDVal_MapElemVal(map, elem);
+}
+
+static void replaceprog_build_recurser (NCDValMem *mem, NCDVal__idx idx, size_t *out_num_instr, NCDValReplaceProg *prog)
+{
+    ASSERT(idx >= 0)
+    NCDVal__AssertValOnly(mem, idx);
+    ASSERT(out_num_instr)
+    
+    *out_num_instr = 0;
+    
+    void *ptr = NCDValMem__BufAt(mem, idx);
+    
+    struct NCDVal__instr instr;
+    
+    switch (get_internal_type(*((int *)(ptr)))) {
+        case STOREDSTRING_TYPE:
+        case IDSTRING_TYPE:
+        case EXTERNALSTRING_TYPE:
+        case COMPOSEDSTRING_TYPE: {
+        } break;
+        
+        case NCDVAL_LIST: {
+            struct NCDVal__list *list_e = ptr;
+            
+            for (NCDVal__idx i = 0; i < list_e->count; i++) {
+                int elem_changed = 0;
+                
+                if (list_e->elem_indices[i] < -1) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_PLACEHOLDER;
+                        instr.placeholder.plid = list_e->elem_indices[i] - NCDVAL_MINIDX;
+                        instr.placeholder.plidx = idx + offsetof(struct NCDVal__list, elem_indices) + i * sizeof(NCDVal__idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                    elem_changed = 1;
+                } else {
+                    size_t elem_num_instr;
+                    replaceprog_build_recurser(mem, list_e->elem_indices[i], &elem_num_instr, prog);
+                    (*out_num_instr) += elem_num_instr;
+                    if (elem_num_instr > 0) {
+                        elem_changed = 1;
+                    }
+                }
+                
+                if (elem_changed) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_BUMPDEPTH;
+                        instr.bumpdepth.parent_idx = idx;
+                        instr.bumpdepth.child_idx_idx = idx + offsetof(struct NCDVal__list, elem_indices) + i * sizeof(NCDVal__idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                }
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            struct NCDVal__map *map_e = ptr;
+            
+            for (NCDVal__idx i = 0; i < map_e->count; i++) {
+                int key_changed = 0;
+                int val_changed = 0;
+                
+                if (map_e->elems[i].key_idx < -1) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_PLACEHOLDER;
+                        instr.placeholder.plid = map_e->elems[i].key_idx - NCDVAL_MINIDX;
+                        instr.placeholder.plidx = idx + offsetof(struct NCDVal__map, elems) + i * sizeof(struct NCDVal__mapelem) + offsetof(struct NCDVal__mapelem, key_idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                    key_changed = 1;
+                } else {
+                    size_t key_num_instr;
+                    replaceprog_build_recurser(mem, map_e->elems[i].key_idx, &key_num_instr, prog);
+                    (*out_num_instr) += key_num_instr;
+                    if (key_num_instr > 0) {
+                        key_changed = 1;
+                    }
+                }
+                
+                if (map_e->elems[i].val_idx < -1) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_PLACEHOLDER;
+                        instr.placeholder.plid = map_e->elems[i].val_idx - NCDVAL_MINIDX;
+                        instr.placeholder.plidx = idx + offsetof(struct NCDVal__map, elems) + i * sizeof(struct NCDVal__mapelem) + offsetof(struct NCDVal__mapelem, val_idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                    val_changed = 1;
+                } else {
+                    size_t val_num_instr;
+                    replaceprog_build_recurser(mem, map_e->elems[i].val_idx, &val_num_instr, prog);
+                    (*out_num_instr) += val_num_instr;
+                    if (val_num_instr > 0) {
+                        val_changed = 1;
+                    }
+                }
+                
+                if (key_changed) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_REINSERT;
+                        instr.reinsert.mapidx = idx;
+                        instr.reinsert.elempos = i;
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                    
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_BUMPDEPTH;
+                        instr.bumpdepth.parent_idx = idx;
+                        instr.bumpdepth.child_idx_idx = idx + offsetof(struct NCDVal__map, elems) + i * sizeof(struct NCDVal__mapelem) + offsetof(struct NCDVal__mapelem, key_idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                }
+                
+                if (val_changed) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_BUMPDEPTH;
+                        instr.bumpdepth.parent_idx = idx;
+                        instr.bumpdepth.child_idx_idx = idx + offsetof(struct NCDVal__map, elems) + i * sizeof(struct NCDVal__mapelem) + offsetof(struct NCDVal__mapelem, val_idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                }
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+int NCDValReplaceProg_Init (NCDValReplaceProg *o, NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    ASSERT(!NCDVal_IsPlaceholder(val))
+    
+    size_t num_instrs;
+    replaceprog_build_recurser(val.mem, val.idx, &num_instrs, NULL);
+    
+    if (!(o->instrs = BAllocArray(num_instrs, sizeof(o->instrs[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        return 0;
+    }
+    
+    o->num_instrs = 0;
+    
+    size_t num_instrs2;
+    replaceprog_build_recurser(val.mem, val.idx, &num_instrs2, o);
+    
+    ASSERT(num_instrs2 == num_instrs)
+    ASSERT(o->num_instrs == num_instrs)
+    
+    return 1;
+}
+
+void NCDValReplaceProg_Free (NCDValReplaceProg *o)
+{
+    BFree(o->instrs);
+}
+
+int NCDValReplaceProg_Execute (NCDValReplaceProg prog, NCDValMem *mem, NCDVal_replace_func replace, void *arg)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(replace)
+    
+    for (size_t i = 0; i < prog.num_instrs; i++) {
+        struct NCDVal__instr instr = prog.instrs[i];
+        
+        switch (instr.type) {
+            case NCDVAL_INSTR_PLACEHOLDER: {
+#ifndef NDEBUG
+                NCDVal__idx *check_plptr = NCDValMem__BufAt(mem, instr.placeholder.plidx);
+                ASSERT(*check_plptr < -1)
+                ASSERT(*check_plptr - NCDVAL_MINIDX == instr.placeholder.plid)
+#endif
+                NCDValRef repval;
+                if (!replace(arg, instr.placeholder.plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
+                    return 0;
+                }
+                ASSERT(repval.mem == mem)
+                
+                if (NCDValMem__NeedRegisterLink(mem, repval.idx)) {
+                    NCDValMem__RegisterLink(mem, repval.idx, instr.placeholder.plidx);
+                }
+                
+                NCDVal__idx *plptr = NCDValMem__BufAt(mem, instr.placeholder.plidx);
+                *plptr = repval.idx;
+            } break;
+            
+            case NCDVAL_INSTR_REINSERT: {
+                NCDVal__AssertValOnly(mem, instr.reinsert.mapidx);
+                struct NCDVal__map *map_e = NCDValMem__BufAt(mem, instr.reinsert.mapidx);
+                ASSERT(get_internal_type(map_e->type) == NCDVAL_MAP)
+                ASSERT(instr.reinsert.elempos >= 0)
+                ASSERT(instr.reinsert.elempos < map_e->count)
+                
+                NCDVal__MapTreeRef ref = {&map_e->elems[instr.reinsert.elempos], NCDVal__MapElemIdx(instr.reinsert.mapidx, instr.reinsert.elempos)};
+                NCDVal__MapTree_Remove(&map_e->tree, mem, ref);
+                if (!NCDVal__MapTree_Insert(&map_e->tree, mem, ref, NULL)) {
+                    BLog(BLOG_ERROR, "duplicate key in map");
+                    return 0;
+                }
+            } break;
+            
+            case NCDVAL_INSTR_BUMPDEPTH: {
+                NCDVal__AssertValOnly(mem, instr.bumpdepth.parent_idx);
+                int *parent_type_ptr = NCDValMem__BufAt(mem, instr.bumpdepth.parent_idx);
+                
+                NCDVal__idx *child_type_idx_ptr = NCDValMem__BufAt(mem, instr.bumpdepth.child_idx_idx);
+                NCDVal__AssertValOnly(mem, *child_type_idx_ptr);
+                int *child_type_ptr = NCDValMem__BufAt(mem, *child_type_idx_ptr);
+                
+                if (!bump_depth(parent_type_ptr, get_depth(*child_type_ptr))) {
+                    BLog(BLOG_ERROR, "depth limit exceeded");
+                    return 0;
+                }
+            } break;
+            
+            default: ASSERT(0);
+        }
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/ncd/NCDVal.h b/external/badvpn_dns/ncd/NCDVal.h
new file mode 100644
index 0000000..26154da
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDVal.h
@@ -0,0 +1,857 @@
+/**
+ * @file NCDVal.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDVAL_H
+#define BADVPN_NCDVAL_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/cstring.h>
+#include <misc/BRefTarget.h>
+#include <structure/CAvl.h>
+#include <ncd/NCDStringIndex.h>
+
+// these are implementation details. The interface is defined below.
+
+#define NCDVAL_FASTBUF_SIZE 64
+#define NCDVAL_FIRST_SIZE 256
+#define NCDVAL_MAX_DEPTH 32
+
+#define NCDVAL_MAXIDX INT_MAX
+#define NCDVAL_MINIDX INT_MIN
+
+typedef int NCDVal__idx;
+
+struct NCDVal__ref {
+    NCDVal__idx next;
+    BRefTarget *target;
+};
+
+struct NCDVal__string {
+    int type;
+    NCDVal__idx length;
+    char data[];
+};
+
+struct NCDVal__list {
+    int type;
+    NCDVal__idx maxcount;
+    NCDVal__idx count;
+    NCDVal__idx elem_indices[];
+};
+
+struct NCDVal__mapelem {
+    NCDVal__idx key_idx;
+    NCDVal__idx val_idx;
+    NCDVal__idx tree_child[2];
+    NCDVal__idx tree_parent;
+    int8_t tree_balance;
+};
+
+struct NCDVal__idstring {
+    int type;
+    NCD_string_id_t string_id;
+    NCDStringIndex *string_index;
+};
+
+struct NCDVal__externalstring {
+    int type;
+    const char *data;
+    size_t length;
+    struct NCDVal__ref ref;
+};
+
+struct NCDVal__composedstring {
+    int type;
+    size_t offset;
+    size_t length;
+    void (*func_getptr) (void *, size_t, const char **, size_t *);
+    void *user;
+    struct NCDVal__ref ref;
+};
+
+struct NCDVal__cms_link {
+    NCDVal__idx link_idx;
+    NCDVal__idx next_cms_link;
+};
+
+typedef struct {
+    char *buf;
+    NCDVal__idx size;
+    NCDVal__idx used;
+    NCDVal__idx first_ref;
+    NCDVal__idx first_cms_link;
+    union {
+        char fastbuf[NCDVAL_FASTBUF_SIZE];
+        struct NCDVal__ref align_ref;
+        struct NCDVal__string align_string;
+        struct NCDVal__list align_list;
+        struct NCDVal__mapelem align_mapelem;
+        struct NCDVal__idstring align_idstring;
+        struct NCDVal__externalstring align_externalstring;
+        struct NCDVal__composedstring align_composedstring;
+        struct NCDVal__cms_link align_cms_link;
+    };
+} NCDValMem;
+
+typedef struct {
+    NCDValMem *mem;
+    NCDVal__idx idx;
+} NCDValRef;
+
+typedef struct {
+    NCDVal__idx idx;
+} NCDValSafeRef;
+
+typedef struct NCDVal__mapelem NCDVal__maptree_entry;
+typedef NCDValMem *NCDVal__maptree_arg;
+
+#include "NCDVal_maptree.h"
+#include <structure/CAvl_decl.h>
+
+struct NCDVal__map {
+    int type;
+    NCDVal__idx maxcount;
+    NCDVal__idx count;
+    NCDVal__MapTree tree;
+    struct NCDVal__mapelem elems[];
+};
+
+typedef struct {
+    NCDVal__idx elemidx;
+} NCDValMapElem;
+
+#define NCDVAL_INSTR_PLACEHOLDER 0
+#define NCDVAL_INSTR_REINSERT 1
+#define NCDVAL_INSTR_BUMPDEPTH 2
+
+struct NCDVal__instr {
+    int type;
+    union {
+        struct {
+            NCDVal__idx plid;
+            NCDVal__idx plidx;
+        } placeholder;
+        struct {
+            NCDVal__idx mapidx;
+            NCDVal__idx elempos;
+        } reinsert;
+        struct {
+            NCDVal__idx parent_idx;
+            NCDVal__idx child_idx_idx;
+        } bumpdepth;
+    };
+};
+
+typedef struct {
+    struct NCDVal__instr *instrs;
+    size_t num_instrs;
+} NCDValReplaceProg;
+
+typedef struct {
+    char *data;
+    int is_allocated;
+} NCDValNullTermString;
+
+typedef struct {
+    char *data;
+    int is_allocated;
+} NCDValContString;
+
+//
+
+#define NCDVAL_STRING 1
+#define NCDVAL_LIST 2
+#define NCDVAL_MAP 3
+#define NCDVAL_PLACEHOLDER 4
+
+/**
+ * Initializes a value memory object.
+ * A value memory object holds memory for value structures. Values within
+ * the memory are referenced using {@link NCDValRef} objects, which point
+ * to values within memory objects.
+ * 
+ * Values may be added to a memory object using functions such as
+ * {@link NCDVal_NewString}, {@link NCDVal_NewList} and {@link NCDVal_NewMap},
+ * and {@link NCDVal_NewCopy}, which return references to the new values within
+ * the memory object.
+ * 
+ * It is not possible to remove values from the memory object, or modify existing
+ * values other than adding elements to pre-allocated slots in lists and maps.
+ * Once a value is added, it will consume memory as long as its memory object
+ * exists. This is by design - this code is intended and optimized for constructing
+ * and passing around values, not for operating on them in place. In fact, al
+ * values within a memory object are stored in a single memory buffer, as an
+ * embedded data structure with relativepointers. For example, map values use an
+ * embedded AVL tree.
+ */
+void NCDValMem_Init (NCDValMem *o);
+
+/**
+ * Frees a value memory object.
+ * All values within the memory object cease to exist, and any {@link NCDValRef}
+ * object pointing to them must no longer be used.
+ */
+void NCDValMem_Free (NCDValMem *o);
+
+/**
+ * Initializes the memory object to be a copy of an existing memory object.
+ * Value references from the original may be used if they are first turned
+ * to {@link NCDValSafeRef} using {@link NCDVal_ToSafe} and back to
+ * {@link NCDValRef} using {@link NCDVal_FromSafe} with the new memory object
+ * specified. Alternatively, {@link NCDVal_Moved} can be used.
+ * Returns 1 on success and 0 on failure.
+ */
+int NCDValMem_InitCopy (NCDValMem *o, NCDValMem *other) WARN_UNUSED;
+
+/**
+ * For each internal link (e.g. list element) to a ComposedString in the memory
+ * object, copies the ComposedString to some kind ContinuousString, and updates
+ * the link to point to the new ContinuousString.
+ * Additionally, if *\a root_val points to a ComposedString, copies it to a new
+ * ContinuousString and updates *\a root_val to point to it.
+ * \a root_val must be non-NULL and *\a root_val must not be an invalid value
+ * reference.
+ * Returns 1 on success and 0 on failure. On failure, some strings may have
+ * been converted, but the memory object is left in a consistent state.
+ */
+int NCDValMem_ConvertNonContinuousStrings (NCDValMem *o, NCDValRef *root_val) WARN_UNUSED;
+
+/**
+ * Does nothing.
+ * The value reference object must either point to a valid value within a valid
+ * memory object, or must be an invalid reference (most functions operating on
+ * {@link NCDValRef} implicitly require that).
+ */
+void NCDVal_Assert (NCDValRef val);
+
+/**
+ * Determines if a value reference is invalid.
+ */
+int NCDVal_IsInvalid (NCDValRef val);
+
+/**
+ * Determines if a value is a placeholder value.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsPlaceholder (NCDValRef val);
+
+/**
+ * Returns the type of the value reference, which must not be an invalid reference.
+ * Possible values are NCDVAL_STRING, NCDVAL_LIST, NCDVAL_MAP and NCDVAL_PLACEHOLDER.
+ * The placeholder type is only used internally in the interpreter for argument
+ * resolution, and is never seen by modules; see {@link NCDVal_NewPlaceholder}.
+ */
+int NCDVal_Type (NCDValRef val);
+
+/**
+ * Returns an invalid reference.
+ * An invalid reference must not be passed to any function here, except:
+ *   {@link NCDVal_Assert}, {@link NCDVal_IsInvalid}, {@link NCDVal_ToSafe},
+ *   {@link NCDVal_FromSafe}, {@link NCDVal_Moved}.
+ */
+NCDValRef NCDVal_NewInvalid (void);
+
+/**
+ * Returns a new placeholder value reference. A placeholder value is a valid value
+ * containing an integer placeholder identifier.
+ * This always succeeds; however, the caller must ensure the identifier is
+ * non-negative and satisfies (NCDVAL_MINIDX + plid < -1).
+ * 
+ * The placeholder type is only used internally in the interpreter for argument
+ * resolution, and is never seen by modules. Also see {@link NCDPlaceholderDb}.
+ */
+NCDValRef NCDVal_NewPlaceholder (NCDValMem *mem, int plid);
+
+/**
+ * Returns the indentifier of a placeholder value.
+ * The value reference must point to a placeholder value.
+ */
+int NCDVal_PlaceholderId (NCDValRef val);
+
+/**
+ * Copies a value into the specified memory object. The source
+ * must not be an invalid reference, however it may reside in any memory
+ * object (including 'mem').
+ * Returns a reference to the copied value. On out of memory, returns
+ * an invalid reference.
+ */
+NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val);
+
+/**
+ * Compares two values, both of which must not be invalid references.
+ * Returns -1, 0 or 1.
+ */
+int NCDVal_Compare (NCDValRef val1, NCDValRef val2);
+
+/**
+ * Converts a value reference to a safe referece format, which remains valid
+ * if the memory object is moved (safe references do not contain a pointer
+ * to the memory object, unlike {@link NCDValRef} references).
+ */
+NCDValSafeRef NCDVal_ToSafe (NCDValRef val);
+
+/**
+ * Converts a safe value reference to a normal value reference.
+ * This should be used to recover references from safe references
+ * after the memory object is moved.
+ */
+NCDValRef NCDVal_FromSafe (NCDValMem *mem, NCDValSafeRef sval);
+
+/**
+ * Fixes a value reference after its memory object was moved.
+ */
+NCDValRef NCDVal_Moved (NCDValMem *mem, NCDValRef val);
+
+/**
+ * Determines if all strings within this value are ContinuousString's,
+ * by recusively walking the entire value.
+ * If all strings are ContinuousString's, returns 1; if there is at least
+ * one string which is not a ContinuousString, returns 0.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_HasOnlyContinuousStrings (NCDValRef val);
+
+/**
+ * Determines if the value implements the String interface.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsString (NCDValRef val);
+
+/**
+ * Determines if the value implements the ContinuousString interface.
+ * A ContinuousString also implements the String interface.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsContinuousString (NCDValRef val);
+
+/**
+ * Determines if the value is a StoredString.
+ * A StoredString implements the ContinuousString interface.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsStoredString (NCDValRef val);
+
+/**
+ * Determines if the value is an IdString. See {@link NCDVal_NewIdString}
+ * for details.
+ * An IdString implements the ContinuousString interface.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsIdString (NCDValRef val);
+
+/**
+ * Determines if a value is an ExternalString.
+ * See {@link NCDVal_NewExternalString} for details.
+ * An ExternalString implements the ContinuousString interface.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsExternalString (NCDValRef val);
+
+/**
+ * Determines if a value is a ComposedString.
+ * A ComposedString implements the String interface.
+ */
+int NCDVal_IsComposedString (NCDValRef val);
+
+/**
+ * Determines if a value is a String which contains no null bytes.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsStringNoNulls (NCDValRef val);
+
+/**
+ * Equivalent to NCDVal_NewStringBin(mem, data, strlen(data)).
+ */
+NCDValRef NCDVal_NewString (NCDValMem *mem, const char *data);
+
+/**
+ * Builds a new StoredString.
+ * Returns a reference to the new value, or an invalid reference
+ * on out of memory.
+ * WARNING: The buffer passed must NOT be part of any value in the
+ * memory object specified. In particular, you may NOT use this
+ * function to copy a string that resides in the same memory object.
+ * 
+ * A StoredString is a kind of ContinuousString which is represented directly in the
+ * value memory object.
+ */
+NCDValRef NCDVal_NewStringBin (NCDValMem *mem, const uint8_t *data, size_t len);
+
+/**
+ * Builds a new StoredString of the given length with undefined contents.
+ * You can define the contents of the string later by copying to the address
+ * returned by {@link NCDVal_StringData}.
+ */
+NCDValRef NCDVal_NewStringUninitialized (NCDValMem *mem, size_t len);
+
+/**
+ * Builds a new IdString.
+ * Returns a reference to the new value, or an invalid reference
+ * on out of memory.
+ * 
+ * An IdString is a kind of ContinuousString which is represented efficiently as a string
+ * identifier via {@link NCDStringIndex}.
+ */
+NCDValRef NCDVal_NewIdString (NCDValMem *mem, NCD_string_id_t string_id,
+                              NCDStringIndex *string_index);
+
+/**
+ * Builds a new ExternalString, pointing to the given external data. A reference to
+ * the external data is taken using {@link BRefTarget}, unless 'ref_target' is
+ * NULL. The data must not change while this value exists.
+ * Returns a reference to the new value, or an invalid reference
+ * on out of memory.
+ * 
+ * An ExternalString is a kind of ContinuousString where the actual string contents are
+ * stored outside of the value memory object.
+ */
+NCDValRef NCDVal_NewExternalString (NCDValMem *mem, const char *data, size_t len,
+                                    BRefTarget *ref_target);
+
+/**
+ * Callback function which is called for ComposedString's to access the underlying string resource.
+ * \a user is whatever was passed to 'resource.user' in {@link NCDVal_NewComposedString}.
+ * \a offset is the offset from the beginning of the string exposed by the resource; it will be
+ * >= 'offset' and < 'offset' + 'length' as given to NCDVal_NewComposedString.
+ * This callback must set *\a out_data and *\a out_length to represent a continuous (sub-)region
+ * of the string that starts at the byte at index \a offset. The pointed-to data must remain
+ * valid and unchanged until all references to the string resource are released.
+ * \a *out_data must be set to non-NULL and *\a out_length must be set to greater than zero,
+ * since the conditions above imply that there is at least one byte available from \a offset.
+ */
+typedef void (*NCDVal_ComposedString_func_getptr) (void *user, size_t offset, const char **out_data, size_t *out_length);
+
+/**
+ * Structure representing a string resource used by ComposedString's,
+ * to simplify {@link NCDVal_NewComposedString} and {@link NCDVal_ComposedStringResource}.
+ */
+typedef struct {
+    NCDVal_ComposedString_func_getptr func_getptr;
+    void *user;
+    BRefTarget *ref_target;
+} NCDValComposedStringResource;
+
+/**
+ * Returns a cstring referencing a range within a {@link NCDValComposedStringResource}.
+ * \a offset and \a length specify the range within the resource which the returned
+ * cstring will reference. To reference the contents of a ComposedString, use:
+ *   - resource = NCDVal_ComposedStringResource(composedstring),
+ *   - offset = NCDVal_ComposedStringOffset(composedstring),
+ *   - length = NCDVal_StringLength(composedstring).
+ * 
+ * The returned cstring is valid as long as the resource is not released. Note that
+ * a reference to resource.ref_target may need to be taken to ensure the resource
+ * is not released while it is being referenced by the returned cstring (unless
+ * resource.ref_target is NULL).
+ */
+b_cstring NCDValComposedStringResource_Cstring (NCDValComposedStringResource resource, size_t offset, size_t length);
+
+/**
+ * Builds a new ComposedString from a string resource.
+ * A reference to the underlying string resource via the {@link BRefTarget} object
+ * specified in 'resource.ref_target'.
+ * 
+ * A ComposedString is a kind of String with an abstract representation exposed via the
+ * {@link NCDVal_ComposedString_func_getptr} callback.
+ */
+NCDValRef NCDVal_NewComposedString (NCDValMem *mem, NCDValComposedStringResource resource, size_t offset, size_t length);
+
+/**
+ * Returns a pointer to the data of a ContinuousString.
+ * WARNING: the string data may not be null-terminated. To get a null-terminated
+ * version, use {@link NCDVal_StringNullTerminate}.
+ * The value reference must point to a ContinuousString.
+ */
+const char * NCDVal_StringData (NCDValRef contstring);
+
+/**
+ * Returns the length of a String.
+ * The value reference must point to a String.
+ */
+size_t NCDVal_StringLength (NCDValRef string);
+
+/**
+ * Returns a {@link b_cstring} interface to the given string value.
+ * The returned cstring is valid as long as the memory object exists.
+ * However, if the memory object is moved or copied, the cstring is
+ * invalid in the new or moved (respectively) memory object.
+ */
+b_cstring NCDVal_StringCstring (NCDValRef string);
+
+/**
+ * Produces a null-terminated continuous version of a String. On success, the result is
+ * stored into an {@link NCDValNullTermString} structure, and the null-terminated
+ * string is available via its 'data' member. This function may either simply pass
+ * through the data pointer (if the string is known to be continuous and null-terminated) or
+ * produce a null-terminated dynamically allocated copy.
+ * On success, {@link NCDValNullTermString_Free} should be called to release any allocated
+ * memory when the null-terminated string is no longer needed. This must be called before
+ * the memory object is freed, because it may point to data inside the memory object.
+ * It is guaranteed that *out is not modified on failure.
+ * Returns 1 on success and 0 on failure.
+ */
+int NCDVal_StringNullTerminate (NCDValRef string, NCDValNullTermString *out) WARN_UNUSED;
+
+/**
+ * Returns a dummy {@link NCDValNullTermString} which can be freed using
+ * {@link NCDValNullTermString_Free}, but need not be.
+ */
+NCDValNullTermString NCDValNullTermString_NewDummy (void);
+
+/**
+ * Releases any memory which was dynamically allocated by {@link NCDVal_StringNullTerminate}
+ * to null-terminate a string.
+ */
+void NCDValNullTermString_Free (NCDValNullTermString *o);
+
+/**
+ * Produces a continuous version of a String. On success, the result is stored into an
+ * {@link NCDValContString} structure, and the continuous string is available via its
+ * 'data' member. This function may either simply pass through the data pointer (if the
+ * string is known to be continuous) or produce a continuous dynamically allocated copy.
+ * On success, {@link NCDValContString_Free} should be called to release any allocated
+ * memory when the continuous string is no longer needed. This must be called before
+ * the memory object is freed, because it may point to data inside the memory object.
+ * It is guaranteed that *out is not modified on failure.
+ * Returns 1 on success and 0 on failure.
+ */
+int NCDVal_StringContinuize (NCDValRef string, NCDValContString *out) WARN_UNUSED;
+
+/**
+ * Returns a dummy {@link NCDValContString} which can be freed using
+ * {@link NCDValContString_Free}, but need not be.
+ */
+NCDValContString NCDValContString_NewDummy (void);
+
+/**
+ * Releases any memory which was dynamically allocated by {@link NCDVal_StringContinuize}
+ * to continuize a string.
+ */
+void NCDValContString_Free (NCDValContString *o);
+
+/**
+ * Returns the string ID and the string index of an IdString.
+ * Both the \a out_string_id and \a out_string_index pointers must be non-NULL.
+ */
+void NCDVal_IdStringGet (NCDValRef idstring, NCD_string_id_t *out_string_id,
+                         NCDStringIndex **out_string_index);
+
+/**
+ * Returns the string ID of an IdString.
+ */
+NCD_string_id_t NCDVal_IdStringId (NCDValRef idstring);
+
+/**
+ * Returns the string index of an IdString.
+ */
+NCDStringIndex * NCDVal_IdStringStringIndex (NCDValRef idstring);
+
+/**
+ * Returns the reference target of an ExternalString. This may be NULL
+ * if the external string is not associated with a reference target.
+ */
+BRefTarget * NCDVal_ExternalStringTarget (NCDValRef externalstring);
+
+/**
+ * Returns the underlying string resource of a ComposedString.
+ */
+NCDValComposedStringResource NCDVal_ComposedStringResource (NCDValRef composedstring);
+
+/**
+ * Returns the resource offset of a ComposedString.
+ */
+size_t NCDVal_ComposedStringOffset (NCDValRef composedstring);
+
+/**
+ * Determines if the String has any null bytes in its contents.
+ */
+int NCDVal_StringHasNulls (NCDValRef string);
+
+/**
+ * Determines if the String value is equal to the given null-terminated
+ * string.
+ * The value reference must point to a String value.
+ */
+int NCDVal_StringEquals (NCDValRef string, const char *data);
+
+/**
+ * Determines if the String is equal to the given string represented
+ * by an {@link NCDStringIndex} identifier.
+ * NOTE: \a string_index must be equal to the string_index of every ID-string
+ * that exist within this memory object.
+ */
+int NCDVal_StringEqualsId (NCDValRef string, NCD_string_id_t string_id,
+                           NCDStringIndex *string_index);
+
+/**
+ * Compares two String's in a manner similar to memcmp().
+ * The startN and length arguments must refer to a valid region within
+ * stringN, i.e. startN + length <= length_of_stringN must hold.
+ */
+int NCDVal_StringMemCmp (NCDValRef string1, NCDValRef string2, size_t start1, size_t start2, size_t length);
+
+/**
+ * Copies a part of a String to a buffer.
+ * \a start and \a length must refer to a valid region within the string,
+ * i.e. start + length <= length_of_string must hold.
+ */
+void NCDVal_StringCopyOut (NCDValRef string, size_t start, size_t length, char *dst);
+
+/**
+ * Determines if a part of a String is equal to the \a length bytes in \a data.
+ * \a start and \a length must refer to a valid region within the string,
+ * i.e. start + length <= length_of_string must hold.
+ */
+int NCDVal_StringRegionEquals (NCDValRef string, size_t start, size_t length, const char *data);
+
+/**
+ * Determines if a value is a list value.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsList (NCDValRef val);
+
+/**
+ * Builds a new list value. The 'maxcount' argument specifies how
+ * many element slots to preallocate. Not more than that many
+ * elements may be appended to the list using {@link NCDVal_ListAppend}.
+ * Returns a reference to the new value, or an invalid reference
+ * on out of memory.
+ */
+NCDValRef NCDVal_NewList (NCDValMem *mem, size_t maxcount);
+
+/**
+ * Appends a value to to the list value.
+ * The 'list' reference must point to a list value, and the
+ * 'elem' reference must be non-invalid and point to a value within
+ * the same memory object as the list.
+ * Inserting a value into a list does not in any way change it;
+ * internally, the list only points to it.
+ * You must not modify the element after it has been inserted into the
+ * list.
+ * Returns 1 on success and 0 on failure (depth limit exceeded).
+ */
+int NCDVal_ListAppend (NCDValRef list, NCDValRef elem) WARN_UNUSED;
+
+/**
+ * Returns the number of elements in a list value, i.e. the number
+ * of times {@link NCDVal_ListAppend} was called.
+ * The 'list' reference must point to a list value.
+ */
+size_t NCDVal_ListCount (NCDValRef list);
+
+/**
+ * Returns the maximum number of elements a list value may contain,
+ * i.e. the 'maxcount' argument to {@link NCDVal_NewList}.
+ * The 'list' reference must point to a list value.
+ */
+size_t NCDVal_ListMaxCount (NCDValRef list);
+
+/**
+ * Returns a reference to the value at the given position 'pos' in a list,
+ * starting with zero.
+ * The 'list' reference must point to a list value.
+ * The position 'pos' must refer to an existing element, i.e.
+ * pos < NCDVal_ListCount().
+ */
+NCDValRef NCDVal_ListGet (NCDValRef list, size_t pos);
+
+/**
+ * Returns references to elements within a list by writing them
+ * via (NCDValRef *) variable arguments.
+ * If 'num' == NCDVal_ListCount(), succeeds, returing 1 and writing 'num'
+ * references, as mentioned.
+ * If 'num' != NCDVal_ListCount(), fails, returning 0, without writing any
+ * references
+ */
+int NCDVal_ListRead (NCDValRef list, int num, ...);
+
+/**
+ * Like {@link NCDVal_ListRead}, but the list can contain more than 'num'
+ * elements.
+ */
+int NCDVal_ListReadHead (NCDValRef list, int num, ...);
+
+/**
+ * Determines if a value is a map value.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsMap (NCDValRef val);
+
+/**
+ * Builds a new map value. The 'maxcount' argument specifies how
+ * many entry slots to preallocate. Not more than that many
+ * entries may be inserted to the map using {@link NCDVal_MapInsert}.
+ * Returns a reference to the new value, or an invalid reference
+ * on out of memory.
+ */
+NCDValRef NCDVal_NewMap (NCDValMem *mem, size_t maxcount);
+
+/**
+ * Inserts an entry to the map value.
+ * The 'map' reference must point to a map value, and the
+ * 'key' and 'val' references must be non-invalid and point to values within
+ * the same memory object as the map.
+ * Inserting an entry does not in any way change the 'key'and 'val';
+ * internally, the map only points to it.
+ * You must not modify the key after inserting it into a map. This is because
+ * the map builds an embedded AVL tree of entries indexed by keys.
+ * If insertion fails due to a maximum depth limit, returns 0.
+ * Otherwise returns 1, and *out_inserted is set to 1 if the key did not
+ * yet exist and the entry was inserted, and to 0 if it did exist and the
+ * entry was not inserted. The 'out_inserted' pointer may be NULL, in which
+ * case *out_inserted is never set.
+ */
+int NCDVal_MapInsert (NCDValRef map, NCDValRef key, NCDValRef val, int *out_inserted) WARN_UNUSED;
+
+/**
+ * Returns the number of entries in a map value, i.e. the number
+ * of times {@link NCDVal_MapInsert} was called successfully.
+ * The 'map' reference must point to a map value.
+ */
+size_t NCDVal_MapCount (NCDValRef map);
+
+/**
+ * Returns the maximum number of entries a map value may contain,
+ * i.e. the 'maxcount' argument to {@link NCDVal_NewMap}.
+ * The 'map' reference must point to a map value.
+ */
+size_t NCDVal_MapMaxCount (NCDValRef map);
+
+/**
+ * Determines if a map entry reference is invalid. This is used in combination
+ * with the map iteration functions to detect the end of iteration.
+ */
+int NCDVal_MapElemInvalid (NCDValMapElem me);
+
+/**
+ * Returns a reference to the first entry in a map, with respect to some
+ * arbitrary order.
+ * If the map is empty, returns an invalid map entry reference.
+ */
+NCDValMapElem NCDVal_MapFirst (NCDValRef map);
+
+/**
+ * Returns a reference to the entry in a map that follows the entry referenced
+ * by 'me', with respect to some arbitrary order.
+ * The 'me' argument must be a non-invalid reference to an entry in the map.
+ * If 'me' is the last entry, returns an invalid map entry reference.
+ */
+NCDValMapElem NCDVal_MapNext (NCDValRef map, NCDValMapElem me);
+
+/**
+ * Like {@link NCDVal_MapFirst}, but with respect to the order defined by
+ * {@link NCDVal_Compare}.
+ * Ordered iteration is slower and should only be used when needed.
+ */
+NCDValMapElem NCDVal_MapOrderedFirst (NCDValRef map);
+
+/**
+ * Like {@link NCDVal_MapNext}, but with respect to the order defined by
+ * {@link NCDVal_Compare}.
+ * Ordered iteration is slower and should only be used when needed.
+ */
+NCDValMapElem NCDVal_MapOrderedNext (NCDValRef map, NCDValMapElem me);
+
+/**
+ * Returns a reference to the key of the map entry referenced by 'me'.
+ * The 'me' argument must be a non-invalid reference to an entry in the map.
+ */
+NCDValRef NCDVal_MapElemKey (NCDValRef map, NCDValMapElem me);
+
+/**
+ * Returns a reference to the value of the map entry referenced by 'me'.
+ * The 'me' argument must be a non-invalid reference to an entry in the map.
+ */
+NCDValRef NCDVal_MapElemVal (NCDValRef map, NCDValMapElem me);
+
+/**
+ * Looks for a key in the map. The 'key' reference must be a non-invalid
+ * value reference, and may point to a value in a different memory object
+ * than the map.
+ * If the key exists in the map, returns a reference to the corresponding
+ * map entry.
+ * If the key does not exist, returns an invalid map entry reference.
+ */
+NCDValMapElem NCDVal_MapFindKey (NCDValRef map, NCDValRef key);
+
+/**
+ * Retrieves the value reference to the value of the map entry whose key is a
+ * string value equal to the given null-terminated string. If there is no such
+ * entry, returns an invalid value reference.
+ */
+NCDValRef NCDVal_MapGetValue (NCDValRef map, const char *key_str);
+
+/**
+ * Builds a placeholder replacement program, which is a list of instructions for
+ * efficiently replacing placeholders in identical values in identical memory
+ * objects.
+ * To actually perform replacements, make copies of the memory object of this value
+ * using {@link NCDValMem_InitCopy}, then call {@link NCDValReplaceProg_Execute}
+ * on the copies.
+ * The value passed must be a valid value, and not a placeholder.
+ * Returns 1 on success, 0 on failure.
+ */
+int NCDValReplaceProg_Init (NCDValReplaceProg *o, NCDValRef val);
+
+/**
+ * Frees the placeholder replacement program.
+ */
+void NCDValReplaceProg_Free (NCDValReplaceProg *o);
+
+/**
+ * Callback used by {@link NCDValReplaceProg_Execute} to allow the caller to produce
+ * values of placeholders.
+ * This function should build a new value within the memory object 'mem' (which is
+ * the same as of the memory object where placeholders are being replaced).
+ * On success, it should return 1, writing a valid value reference to *out.
+ * On failure, it can either return 0, or return 1 but write an invalid value reference.
+ * This callback must not access the memory object in any other way than building
+ * new values in it; it must not modify any values that were already present at the
+ * point it was called.
+ */
+typedef int (*NCDVal_replace_func) (void *arg, int plid, NCDValMem *mem, NCDValRef *out);
+
+/**
+ * Executes the replacement program, replacing placeholders in a value.
+ * The memory object must given be identical to the memory object which was used in
+ * {@link NCDValReplaceProg_Init}; see {@link NCDValMem_InitCopy}.
+ * This will call the callback 'replace', which should build the values to replace
+ * the placeholders.
+ * Returns 1 on success and 0 on failure. On failure, the entire memory object enters
+ * and inconsistent state and must be freed using {@link NCDValMem_Free} before
+ * performing any other operation on it.
+ * The program is passed by value instead of pointer because this appears to be faster.
+ * Is is not modified in any way.
+ */
+int NCDValReplaceProg_Execute (NCDValReplaceProg prog, NCDValMem *mem, NCDVal_replace_func replace, void *arg);
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDValCons.c b/external/badvpn_dns/ncd/NCDValCons.c
new file mode 100644
index 0000000..9872a47
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDValCons.c
@@ -0,0 +1,283 @@
+/**
+ * @file NCDValCons.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "NCDValCons.h"
+
+#define GROWARRAY_NAME NCDValCons__Array
+#define GROWARRAY_OBJECT_TYPE NCDValCons
+#define GROWARRAY_ARRAY_MEMBER elems
+#define GROWARRAY_CAPACITY_MEMBER elems_capacity
+#define GROWARRAY_MAX_CAPACITY INT_MAX
+#include <misc/grow_array.h>
+
+static int alloc_elem (NCDValCons *o)
+{
+    ASSERT(o->elems_size >= 0)
+    ASSERT(o->elems_size <= o->elems_capacity)
+    
+    if (o->elems_size == o->elems_capacity && !NCDValCons__Array_DoubleUp(o)) {
+        return -1;
+    }
+    
+    return o->elems_size++;
+}
+
+static void assert_cons_val (NCDValCons *o, NCDValConsVal val)
+{
+#ifndef NDEBUG
+    switch (val.cons_type) {
+        case NCDVALCONS_TYPE_COMPLETE: {
+            ASSERT(!NCDVal_IsInvalid(NCDVal_FromSafe(o->mem, val.u.complete_ref)))
+        } break;
+        case NCDVALCONS_TYPE_INCOMPLETE_LIST:
+        case NCDVALCONS_TYPE_INCOMPLETE_MAP: {
+            ASSERT(val.u.incomplete.elems_idx >= -1)
+            ASSERT(val.u.incomplete.elems_idx < o->elems_size)
+            ASSERT(val.u.incomplete.count >= 0)
+        } break;
+        default:
+            ASSERT(0);
+    }
+#endif
+}
+
+static int complete_value (NCDValCons *o, NCDValConsVal val, NCDValSafeRef *out, int *out_error)
+{
+    assert_cons_val(o, val);
+    ASSERT(out)
+    ASSERT(out_error)
+    
+    switch (val.cons_type) {
+        case NCDVALCONS_TYPE_COMPLETE: {
+            *out = val.u.complete_ref;
+        } break;
+        
+        case NCDVALCONS_TYPE_INCOMPLETE_LIST: {
+            NCDValRef list = NCDVal_NewList(o->mem, val.u.incomplete.count);
+            if (NCDVal_IsInvalid(list)) {
+                goto fail_memory;
+            }
+            
+            int elemidx = val.u.incomplete.elems_idx;
+            
+            while (elemidx != -1) {
+                ASSERT(elemidx >= 0)
+                ASSERT(elemidx < o->elems_size)
+                
+                NCDValRef elem = NCDVal_FromSafe(o->mem, o->elems[elemidx].ref);
+                
+                if (!NCDVal_ListAppend(list, elem)) {
+                    *out_error = NCDVALCONS_ERROR_DEPTH;
+                    return 0;
+                }
+                
+                elemidx = o->elems[elemidx].next;
+            }
+            
+            *out = NCDVal_ToSafe(list);
+        } break;
+        
+        case NCDVALCONS_TYPE_INCOMPLETE_MAP: {
+            NCDValRef map = NCDVal_NewMap(o->mem, val.u.incomplete.count);
+            if (NCDVal_IsInvalid(map)) {
+                goto fail_memory;
+            }
+            
+            int keyidx = val.u.incomplete.elems_idx;
+            
+            while (keyidx != -1) {
+                ASSERT(keyidx >= 0)
+                ASSERT(keyidx < o->elems_size)
+                
+                int validx = o->elems[keyidx].next;
+                ASSERT(validx >= 0)
+                ASSERT(validx < o->elems_size)
+                
+                NCDValRef key = NCDVal_FromSafe(o->mem, o->elems[keyidx].ref);
+                NCDValRef value = NCDVal_FromSafe(o->mem, o->elems[validx].ref);
+                
+                int inserted;
+                if (!NCDVal_MapInsert(map, key, value, &inserted)) {
+                    *out_error = NCDVALCONS_ERROR_DEPTH;
+                    return 0;
+                }
+                if (!inserted) {
+                    *out_error = NCDVALCONS_ERROR_DUPLICATE_KEY;
+                    return 0;
+                }
+                
+                keyidx = o->elems[validx].next;
+            }
+            
+            *out = NCDVal_ToSafe(map);
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+    
+    return 1;
+    
+fail_memory:
+    *out_error = NCDVALCONS_ERROR_MEMORY;
+    return 0;
+}
+
+int NCDValCons_Init (NCDValCons *o, NCDValMem *mem)
+{
+    ASSERT(mem)
+    
+    o->mem = mem;
+    o->elems_size = 0;
+    
+    if (!NCDValCons__Array_Init(o, 1)) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+void NCDValCons_Free (NCDValCons *o)
+{
+    NCDValCons__Array_Free(o);
+}
+
+int NCDValCons_NewString (NCDValCons *o, const uint8_t *data, size_t len, NCDValConsVal *out, int *out_error)
+{
+    ASSERT(out)
+    ASSERT(out_error)
+    
+    NCDValRef ref = NCDVal_NewStringBin(o->mem, data, len);
+    if (NCDVal_IsInvalid(ref)) {
+        *out_error = NCDVALCONS_ERROR_MEMORY;
+        return 0;
+    }
+    
+    out->cons_type = NCDVALCONS_TYPE_COMPLETE;
+    out->u.complete_ref = NCDVal_ToSafe(ref);
+    
+    return 1;
+}
+
+void NCDValCons_NewList (NCDValCons *o, NCDValConsVal *out)
+{
+    ASSERT(out)
+    
+    out->cons_type = NCDVALCONS_TYPE_INCOMPLETE_LIST;
+    out->u.incomplete.elems_idx = -1;
+    out->u.incomplete.count = 0;
+}
+
+void NCDValCons_NewMap (NCDValCons *o, NCDValConsVal *out)
+{
+    ASSERT(out)
+    
+    out->cons_type = NCDVALCONS_TYPE_INCOMPLETE_MAP;
+    out->u.incomplete.elems_idx = -1;
+    out->u.incomplete.count = 0;
+}
+
+int NCDValCons_ListPrepend (NCDValCons *o, NCDValConsVal *list, NCDValConsVal elem, int *out_error)
+{
+    assert_cons_val(o, *list);
+    ASSERT(list->cons_type == NCDVALCONS_TYPE_INCOMPLETE_LIST)
+    assert_cons_val(o, elem);
+    ASSERT(out_error)
+    
+    int elemidx = alloc_elem(o);
+    if (elemidx < 0) {
+        *out_error = NCDVALCONS_ERROR_MEMORY;
+        return 0;
+    }
+    
+    o->elems[elemidx].next = list->u.incomplete.elems_idx;
+    
+    if (!complete_value(o, elem, &o->elems[elemidx].ref, out_error)) {
+        return 0;
+    }
+    
+    list->u.incomplete.elems_idx = elemidx;
+    list->u.incomplete.count++;
+    
+    return 1;
+}
+
+int NCDValCons_MapInsert (NCDValCons *o, NCDValConsVal *map, NCDValConsVal key, NCDValConsVal value, int *out_error)
+{
+    assert_cons_val(o, *map);
+    ASSERT(map->cons_type == NCDVALCONS_TYPE_INCOMPLETE_MAP)
+    assert_cons_val(o, key);
+    assert_cons_val(o, value);
+    ASSERT(out_error)
+    
+    int validx = alloc_elem(o);
+    if (validx < 0) {
+        *out_error = NCDVALCONS_ERROR_MEMORY;
+        return 0;
+    }
+    
+    int keyidx = alloc_elem(o);
+    if (keyidx < 0) {
+        *out_error = NCDVALCONS_ERROR_MEMORY;
+        return 0;
+    }
+    
+    o->elems[validx].next = map->u.incomplete.elems_idx;
+    o->elems[keyidx].next = validx;
+    
+    if (!complete_value(o, value, &o->elems[validx].ref, out_error)) {
+        return 0;
+    }
+    
+    if (!complete_value(o, key, &o->elems[keyidx].ref, out_error)) {
+        return 0;
+    }
+    
+    map->u.incomplete.elems_idx = keyidx;
+    map->u.incomplete.count++;
+    
+    return 1;
+}
+
+int NCDValCons_Complete (NCDValCons *o, NCDValConsVal val, NCDValRef *out, int *out_error)
+{
+    assert_cons_val(o, val);
+    ASSERT(out)
+    ASSERT(out_error)
+    
+    NCDValSafeRef sref;
+    if (!complete_value(o, val, &sref, out_error)) {
+        return 0;
+    }
+    
+    *out = NCDVal_FromSafe(o->mem, sref);
+    return 1;
+}
diff --git a/external/badvpn_dns/ncd/NCDValCons.h b/external/badvpn_dns/ncd/NCDValCons.h
new file mode 100644
index 0000000..3b25d66
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDValCons.h
@@ -0,0 +1,176 @@
+/**
+ * @file NCDValCons.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDVALCONS_H
+#define BADVPN_NCDVALCONS_H
+
+#include <limits.h>
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDVal.h>
+
+struct NCDValCons__temp_elem {
+    NCDValSafeRef ref;
+    int next;
+};
+
+/**
+ * Value constructor; implements a mechanism for efficiently constructing
+ * NCD values into {@link NCDVal} compact representation, but without
+ * having to know the number of list or map elements in advance.
+ * For the purpose of value construction, values are representing using
+ * {@link NCDValConsVal} objects.
+ */
+typedef struct {
+    NCDValMem *mem;
+    struct NCDValCons__temp_elem *elems;
+    int elems_size;
+    int elems_capacity;
+} NCDValCons;
+
+#define NCDVALCONS_TYPE_COMPLETE 1
+#define NCDVALCONS_TYPE_INCOMPLETE_LIST 2
+#define NCDVALCONS_TYPE_INCOMPLETE_MAP 3
+
+/**
+ * Abstract handle which represents a value during constuction via
+ * {@link NCDValCons}. 
+ */
+typedef struct {
+    int cons_type;
+    union {
+        NCDValSafeRef complete_ref;
+        struct {
+            int elems_idx;
+            int count;
+        } incomplete;
+    } u;
+} NCDValConsVal;
+
+#define NCDVALCONS_ERROR_MEMORY 1
+#define NCDVALCONS_ERROR_DUPLICATE_KEY 2
+#define NCDVALCONS_ERROR_DEPTH 3
+
+/**
+ * Initializes a value constructor.
+ * 
+ * @param o value constructor to initialize
+ * @param mem memory object where values will be stored into
+ * @return 1 on success, 0 on failure
+ */
+int NCDValCons_Init (NCDValCons *o, NCDValMem *mem) WARN_UNUSED;
+
+/**
+ * Frees the value constructor. This only means the constuctor does
+ * not exist any more; any values constructed and completed using
+ * {@link NCDValCons_Complete} remain in the memory object.
+ * 
+ * @param o value constructor to free
+ */
+void NCDValCons_Free (NCDValCons *o);
+
+/**
+ * Creates a new string value with the given data.
+ * 
+ * @param o value constructor
+ * @param data pointer to string data. This must not point into the
+ *             memory object the value constructor is using. The data
+ *             is copied.
+ * @param len length of the string
+ * @param out on success, *out will be set to a handle representing
+ *            the new string
+ * @param out_error on failure, *out_error will be set to an error code
+ * @return 1 on success, 0 on failure
+ */
+int NCDValCons_NewString (NCDValCons *o, const uint8_t *data, size_t len, NCDValConsVal *out, int *out_error) WARN_UNUSED;
+
+/**
+ * Creates an empty list value.
+ * 
+ * @param o value constructor
+ * @param out *out will be set to a handle representing the new list
+ */
+void NCDValCons_NewList (NCDValCons *o, NCDValConsVal *out);
+
+/**
+ * Creates an empty map value.
+ * 
+ * @param o value constructor
+ * @param out *out will be set to a handle representing the new map
+ */
+void NCDValCons_NewMap (NCDValCons *o, NCDValConsVal *out);
+
+/**
+ * Prepends an element to a list value.
+ * 
+ * @param o value constructor
+ * @param list pointer to the handle representing the list. On success,
+ *             the handle will be modified, and the old handle must not
+ *             be used any more.
+ * @param elem handle representing the value to be prepended. This handle
+ *             must not be used any more after being prepended to the list.
+ * @param out_error on failure, *out_error will be set to an error code
+ * @return 1 on success, 0 on failure
+ */
+int NCDValCons_ListPrepend (NCDValCons *o, NCDValConsVal *list, NCDValConsVal elem, int *out_error) WARN_UNUSED;
+
+/**
+ * Inserts an entry into a map value.
+ * 
+ * @param o value constructor
+ * @param map pointer to the handle representing the map. On success,
+ *             the handle will be modified, and the old handle must not
+ *             be used any more.
+ * @param key handle representing the key of the entry. This handle
+ *            must not be used any more after being inserted into the map.
+ * @param value handle representing the value of the entry. This handle
+ *              must not be used any more after being inserted into the
+ *              map.
+ * @param out_error on failure, *out_error will be set to an error code
+ * @return 1 on success, 0 on failure
+ */
+int NCDValCons_MapInsert (NCDValCons *o, NCDValConsVal *map, NCDValConsVal key, NCDValConsVal value, int *out_error) WARN_UNUSED;
+
+/**
+ * Completes a value represented by a {@link NCDValConsVal} handle,
+ * producing a {@link NCDValRef} object which refers to this value within
+ * the memory object.
+ * 
+ * @param o value constructor
+ * @param val handle representing the value to be completed. After a value
+ *            is completed, the handle must not be used any more.
+ * @param out on success, *out will be set to a {@link NCDValRef} object
+ *            referencing the completed value
+ * @param out_error on failure, *out_error will be set to an error code
+ * @return 1 on success, 0 on failure
+ */
+int NCDValCons_Complete (NCDValCons *o, NCDValConsVal val, NCDValRef *out, int *out_error) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDValGenerator.c b/external/badvpn_dns/ncd/NCDValGenerator.c
new file mode 100644
index 0000000..3355f8b
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDValGenerator.c
@@ -0,0 +1,193 @@
+/**
+ * @file NCDValGenerator.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <inttypes.h>
+
+#include <misc/debug.h>
+#include <misc/expstring.h>
+#include <base/BLog.h>
+
+#include "NCDValGenerator.h"
+
+#include <generated/blog_channel_NCDValGenerator.h>
+
+static int generate_val (NCDValRef value, ExpString *out_str)
+{
+    ASSERT(!NCDVal_IsInvalid(value))
+    
+    switch (NCDVal_Type(value)) {
+        case NCDVAL_STRING: {
+            b_cstring cstr = NCDVal_StringCstring(value);
+            
+            if (!ExpString_AppendChar(out_str, '"')) {
+                BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                goto fail;
+            }
+            
+            B_CSTRING_LOOP_CHARS(cstr, char_pos, ch, {
+                if (ch == '\0') {
+                    char buf[5];
+                    snprintf(buf, sizeof(buf), "\\x%02"PRIx8, (uint8_t)ch);
+                    
+                    if (!ExpString_Append(out_str, buf)) {
+                        BLog(BLOG_ERROR, "ExpString_Append failed");
+                        goto fail;
+                    }
+                    
+                    goto next_char;
+                }
+                
+                if (ch == '"' || ch == '\\') {
+                    if (!ExpString_AppendChar(out_str, '\\')) {
+                        BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                        goto fail;
+                    }
+                }
+                
+                if (!ExpString_AppendChar(out_str, ch)) {
+                    BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                    goto fail;
+                }
+            next_char:;
+            })
+            
+            if (!ExpString_AppendChar(out_str, '"')) {
+                BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                goto fail;
+            }
+        } break;
+        
+        case NCDVAL_LIST: {
+            size_t count = NCDVal_ListCount(value);
+            
+            if (!ExpString_AppendChar(out_str, '{')) {
+                BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                goto fail;
+            }
+            
+            for (size_t i = 0; i < count; i++) {
+                if (i > 0) {
+                    if (!ExpString_Append(out_str, ", ")) {
+                        BLog(BLOG_ERROR, "ExpString_Append failed");
+                        goto fail;
+                    }
+                }
+                
+                if (!generate_val(NCDVal_ListGet(value, i), out_str)) {
+                    goto fail;
+                }
+            }
+            
+            if (!ExpString_AppendChar(out_str, '}')) {
+                BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                goto fail;
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            if (!ExpString_AppendChar(out_str, '[')) {
+                BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                goto fail;
+            }
+            
+            int is_first = 1;
+            
+            for (NCDValMapElem e = NCDVal_MapOrderedFirst(value); !NCDVal_MapElemInvalid(e); e = NCDVal_MapOrderedNext(value, e)) {
+                NCDValRef ekey = NCDVal_MapElemKey(value, e);
+                NCDValRef eval = NCDVal_MapElemVal(value, e);
+                
+                if (!is_first) {
+                    if (!ExpString_Append(out_str, ", ")) {
+                        BLog(BLOG_ERROR, "ExpString_Append failed");
+                        goto fail;
+                    }
+                }
+                
+                if (!generate_val(ekey, out_str)) {
+                    goto fail;
+                }
+                
+                if (!ExpString_AppendChar(out_str, ':')) {
+                    BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                    goto fail;
+                }
+                
+                if (!generate_val(eval, out_str)) {
+                    goto fail;
+                }
+                
+                is_first = 0;
+            }
+            
+            if (!ExpString_AppendChar(out_str, ']')) {
+                BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                goto fail;
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+char * NCDValGenerator_Generate (NCDValRef value)
+{
+    ASSERT(!NCDVal_IsInvalid(value))
+    
+    ExpString str;
+    if (!ExpString_Init(&str)) {
+        BLog(BLOG_ERROR, "ExpString_Init failed");
+        goto fail0;
+    }
+    
+    if (!generate_val(value, &str)) {
+        goto fail1;
+    }
+    
+    return ExpString_Get(&str);
+    
+fail1:
+    ExpString_Free(&str);
+fail0:
+    return NULL;
+}
+
+int NCDValGenerator_AppendGenerate (NCDValRef value, ExpString *str)
+{
+    ASSERT(!NCDVal_IsInvalid(value))
+    ASSERT(str)
+    
+    return generate_val(value, str);
+}
diff --git a/external/badvpn_dns/ncd/NCDValGenerator.h b/external/badvpn_dns/ncd/NCDValGenerator.h
new file mode 100644
index 0000000..35fd991
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDValGenerator.h
@@ -0,0 +1,40 @@
+/**
+ * @file NCDValGenerator.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDVALUEGENERATOR_H
+#define BADVPN_NCDVALUEGENERATOR_H
+
+#include <misc/debug.h>
+#include <misc/expstring.h>
+#include <ncd/NCDVal.h>
+
+char * NCDValGenerator_Generate (NCDValRef value);
+int NCDValGenerator_AppendGenerate (NCDValRef value, ExpString *str) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDValParser.c b/external/badvpn_dns/ncd/NCDValParser.c
new file mode 100644
index 0000000..7cdb4da
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDValParser.c
@@ -0,0 +1,225 @@
+/**
+ * @file NCDValParser.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <base/BLog.h>
+#include <ncd/NCDConfigTokenizer.h>
+#include <ncd/NCDValCons.h>
+
+#include "NCDValParser.h"
+
+#include <generated/blog_channel_NCDValParser.h>
+
+struct token {
+    char *str;
+    size_t len;
+};
+
+struct value {
+    int have;
+    NCDValConsVal v;
+};
+
+#define ERROR_FLAG_MEMORY        (1 << 0)
+#define ERROR_FLAG_TOKENIZATION  (1 << 1)
+#define ERROR_FLAG_SYNTAX        (1 << 2)
+#define ERROR_FLAG_DUPLICATE_KEY (1 << 3)
+#define ERROR_FLAG_DEPTH         (1 << 4)
+
+struct parser_state {
+    NCDValCons cons;
+    NCDValRef value;
+    int cons_error;
+    int error_flags;
+    void *parser;
+};
+
+static void free_token (struct token o)
+{
+    if (o.str) {
+        free(o.str);
+    }
+};
+
+static void handle_cons_error (struct parser_state *state)
+{
+    switch (state->cons_error) {
+        case NCDVALCONS_ERROR_MEMORY:
+            state->error_flags |= ERROR_FLAG_MEMORY;
+            break;
+        case NCDVALCONS_ERROR_DUPLICATE_KEY:
+            state->error_flags |= ERROR_FLAG_DUPLICATE_KEY;
+            break;
+        case NCDVALCONS_ERROR_DEPTH:
+            state->error_flags |= ERROR_FLAG_DEPTH;
+            break;
+        default:
+            ASSERT(0);
+    }
+}
+
+// rename non-static functions defined by our Lemon parser
+// to avoid clashes with other Lemon parsers
+#define ParseTrace ParseTrace_NCDValParser
+#define ParseAlloc ParseAlloc_NCDValParser
+#define ParseFree ParseFree_NCDValParser
+#define Parse Parse_NCDValParser
+
+// include the generated Lemon parser
+#include "../generated/NCDValParser_parse.c"
+#include "../generated/NCDValParser_parse.h"
+
+static int tokenizer_output (void *user, int token, char *value, size_t value_len, size_t line, size_t line_char)
+{
+    struct parser_state *state = user;
+    ASSERT(!state->error_flags)
+    
+    if (token == NCD_ERROR) {
+        state->error_flags |= ERROR_FLAG_TOKENIZATION;
+        goto fail;
+    }
+    
+    struct token minor;
+    minor.str = value;
+    minor.len = value_len;
+    
+    switch (token) {
+        case NCD_EOF: {
+            Parse(state->parser, 0, minor, state);
+        } break;
+        
+        case NCD_TOKEN_CURLY_OPEN: {
+            Parse(state->parser, CURLY_OPEN, minor, state);
+        } break;
+        
+        case NCD_TOKEN_CURLY_CLOSE: {
+            Parse(state->parser, CURLY_CLOSE, minor, state);
+        } break;
+        
+        case NCD_TOKEN_COMMA: {
+            Parse(state->parser, COMMA, minor, state);
+        } break;
+        
+        case NCD_TOKEN_STRING: {
+            Parse(state->parser, STRING, minor, state);
+        } break;
+        
+        case NCD_TOKEN_COLON: {
+            Parse(state->parser, COLON, minor, state);
+        } break;
+        
+        case NCD_TOKEN_BRACKET_OPEN: {
+            Parse(state->parser, BRACKET_OPEN, minor, state);
+        } break;
+        
+        case NCD_TOKEN_BRACKET_CLOSE: {
+            Parse(state->parser, BRACKET_CLOSE, minor, state);
+        } break;
+        
+        default:
+            state->error_flags |= ERROR_FLAG_TOKENIZATION;
+            free_token(minor);
+            goto fail;
+    }
+    
+    if (state->error_flags) {
+        goto fail;
+    }
+    
+    return 1;
+    
+fail:
+    ASSERT(state->error_flags)
+    
+    if ((state->error_flags & ERROR_FLAG_MEMORY)) {
+        BLog(BLOG_ERROR, "line %zu, character %zu: memory allocation error", line, line_char);
+    }
+    
+    if ((state->error_flags & ERROR_FLAG_TOKENIZATION)) {
+        BLog(BLOG_ERROR, "line %zu, character %zu: tokenization error", line, line_char);
+    }
+    
+    if ((state->error_flags & ERROR_FLAG_SYNTAX)) {
+        BLog(BLOG_ERROR, "line %zu, character %zu: syntax error", line, line_char);
+    }
+    
+    if ((state->error_flags & ERROR_FLAG_DUPLICATE_KEY)) {
+        BLog(BLOG_ERROR, "line %zu, character %zu: duplicate key in map error", line, line_char);
+    }
+    
+    if ((state->error_flags & ERROR_FLAG_DEPTH)) {
+        BLog(BLOG_ERROR, "line %zu, character %zu: depth limit exceeded", line, line_char);
+    }
+    
+    return 0;
+}
+
+int NCDValParser_Parse (const char *str, size_t str_len, NCDValMem *mem, NCDValRef *out_value)
+{
+    ASSERT(str_len == 0 || str)
+    ASSERT(mem)
+    ASSERT(out_value)
+    
+    int ret = 0;
+    
+    struct parser_state state;
+    state.value = NCDVal_NewInvalid();
+    state.error_flags = 0;
+    
+    if (!NCDValCons_Init(&state.cons, mem)) {
+        BLog(BLOG_ERROR, "NCDValCons_Init failed");
+        goto fail0;
+    }
+    
+    if (!(state.parser = ParseAlloc(malloc))) {
+        BLog(BLOG_ERROR, "ParseAlloc failed");
+        goto fail1;
+    }
+    
+    NCDConfigTokenizer_Tokenize((char *)str, str_len, tokenizer_output, &state);
+    
+    ParseFree(state.parser, free);
+    
+    if (state.error_flags) {
+        goto fail1;
+    }
+    
+    ASSERT(!NCDVal_IsInvalid(state.value))
+    
+    *out_value = state.value;
+    ret = 1;
+    
+fail1:
+    NCDValCons_Free(&state.cons);
+fail0:
+    return ret;
+}
diff --git a/external/badvpn_dns/ncd/NCDValParser.h b/external/badvpn_dns/ncd/NCDValParser.h
new file mode 100644
index 0000000..57d37b0
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDValParser.h
@@ -0,0 +1,50 @@
+/**
+ * @file NCDValParser.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDVALUEPARSER_H
+#define BADVPN_NCDVALUEPARSER_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDVal.h>
+
+/**
+ * Parses an NCD value string into {@link NCDVal} compact representation.
+ * 
+ * @param str pointer to the string to be parsed
+ * @param str_len length of the string in bytes
+ * @param mem value memory object which the result will be stored in
+ * @param out_value on success, the value reference of the result will be
+ *                  written here
+ * @return 1 on success, 0 on failure
+ */
+int NCDValParser_Parse (const char *str, size_t str_len, NCDValMem *mem, NCDValRef *out_value) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/ncd/NCDValParser_parse.y b/external/badvpn_dns/ncd/NCDValParser_parse.y
new file mode 100644
index 0000000..ced123d
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDValParser_parse.y
@@ -0,0 +1,202 @@
+/**
+ * @file NCDConfigParser_parse.y
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+// argument for passing state to parser hooks
+%extra_argument { struct parser_state *parser_out }
+
+// type of structure representing tokens
+%token_type { struct token }
+
+// token destructor frees extra memory allocated for tokens
+%token_destructor { free_token($$); }
+
+// types of nonterminals
+%type list_contents { struct value }
+%type list { struct value }
+%type map_contents { struct value }
+%type map  { struct value }
+%type value  { struct value }
+
+// mention parser_out in some destructor to and avoid unused variable warning
+%destructor list_contents { (void)parser_out; }
+
+// try to dynamically grow the parse stack
+%stack_size 0
+
+// on syntax error, set the corresponding error flag
+%syntax_error {
+    parser_out->error_flags |= ERROR_FLAG_SYNTAX;
+}
+
+// workaroud Lemon bug: if the stack overflows, the token that caused the overflow will be leaked
+%stack_overflow {
+    if (yypMinor) {
+        free_token(yypMinor->yy0);
+    }
+}
+
+input ::= value(A). {
+    if (!A.have) {
+        goto failZ0;
+    }
+    
+    if (!NCDVal_IsInvalid(parser_out->value)) {
+        // should never happen
+        parser_out->error_flags |= ERROR_FLAG_SYNTAX;
+        goto failZ0;
+    }
+    
+    if (!NCDValCons_Complete(&parser_out->cons, A.v, &parser_out->value, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failZ0;
+    }
+    
+failZ0:;
+}
+
+list_contents(R) ::= value(A). {
+    if (!A.have) {
+        goto failL0;
+    }
+
+    NCDValCons_NewList(&parser_out->cons, &R.v);
+
+    if (!NCDValCons_ListPrepend(&parser_out->cons, &R.v, A.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failL0;
+    }
+
+    R.have = 1;
+    goto doneL;
+
+failL0:
+    R.have = 0;
+doneL:;
+}
+
+list_contents(R) ::= value(A) COMMA list_contents(N). {
+    if (!A.have || !N.have) {
+        goto failM0;
+    }
+
+    if (!NCDValCons_ListPrepend(&parser_out->cons, &N.v, A.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failM0;
+    }
+
+    R.have = 1;
+    R.v = N.v;
+    goto doneM;
+
+failM0:
+    R.have = 0;
+doneM:;
+}
+
+list(R) ::= CURLY_OPEN CURLY_CLOSE. {
+    NCDValCons_NewList(&parser_out->cons, &R.v);
+    R.have = 1;
+}
+
+list(R) ::= CURLY_OPEN list_contents(A) CURLY_CLOSE. {
+    R = A;
+}
+
+map_contents(R) ::= value(A) COLON value(B). {
+    if (!A.have || !B.have) {
+        goto failS0;
+    }
+
+    NCDValCons_NewMap(&parser_out->cons, &R.v);
+
+    if (!NCDValCons_MapInsert(&parser_out->cons, &R.v, A.v, B.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failS0;
+    }
+
+    R.have = 1;
+    goto doneS;
+
+failS0:
+    R.have = 0;
+doneS:;
+}
+
+map_contents(R) ::= value(A) COLON value(B) COMMA map_contents(N). {
+    if (!A.have || !B.have || !N.have) {
+        goto failT0;
+    }
+
+    if (!NCDValCons_MapInsert(&parser_out->cons, &N.v, A.v, B.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failT0;
+    }
+
+    R.have = 1;
+    R.v = N.v;
+    goto doneT;
+
+failT0:
+    R.have = 0;
+doneT:;
+}
+
+map(R) ::= BRACKET_OPEN BRACKET_CLOSE. {
+    NCDValCons_NewMap(&parser_out->cons, &R.v);
+    R.have = 1;
+}
+
+map(R) ::= BRACKET_OPEN map_contents(A) BRACKET_CLOSE. {
+    R = A;
+}
+
+value(R) ::= STRING(A). {
+    ASSERT(A.str)
+
+    if (!NCDValCons_NewString(&parser_out->cons, (const uint8_t *)A.str, A.len, &R.v, &parser_out->cons_error)) {
+        handle_cons_error(parser_out);
+        goto failU0;
+    }
+
+    R.have = 1;
+    goto doneU;
+
+failU0:
+    R.have = 0;
+doneU:;
+    free_token(A);
+}
+
+value(R) ::= list(A). {
+    R = A;
+}
+
+value(R) ::= map(A). {
+    R = A;
+}
diff --git a/external/badvpn_dns/ncd/NCDVal_maptree.h b/external/badvpn_dns/ncd/NCDVal_maptree.h
new file mode 100644
index 0000000..d5b9c0c
--- /dev/null
+++ b/external/badvpn_dns/ncd/NCDVal_maptree.h
@@ -0,0 +1,15 @@
+#define CAVL_PARAM_NAME NCDVal__MapTree
+#define CAVL_PARAM_FEATURE_COUNTS 0
+#define CAVL_PARAM_FEATURE_KEYS_ARE_INDICES 0
+#define CAVL_PARAM_FEATURE_NOKEYS 0
+#define CAVL_PARAM_TYPE_ENTRY NCDVal__maptree_entry
+#define CAVL_PARAM_TYPE_LINK NCDVal__idx
+#define CAVL_PARAM_TYPE_KEY NCDValRef
+#define CAVL_PARAM_TYPE_ARG NCDVal__maptree_arg
+#define CAVL_PARAM_VALUE_NULL ((NCDVal__idx)-1)
+#define CAVL_PARAM_FUN_DEREF(arg, link) ((struct NCDVal__mapelem *)NCDValMem__BufAt((arg), (link)))
+#define CAVL_PARAM_FUN_COMPARE_ENTRIES(arg, node1, node2) NCDVal_Compare(NCDVal__Ref((arg), (node1).ptr->key_idx), NCDVal__Ref((arg), (node2).ptr->key_idx))
+#define CAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, node2) NCDVal_Compare((key1), NCDVal__Ref((arg), (node2).ptr->key_idx))
+#define CAVL_PARAM_MEMBER_CHILD tree_child
+#define CAVL_PARAM_MEMBER_BALANCE tree_balance
+#define CAVL_PARAM_MEMBER_PARENT tree_parent
diff --git a/external/badvpn_dns/ncd/README b/external/badvpn_dns/ncd/README
new file mode 100644
index 0000000..05d638f
--- /dev/null
+++ b/external/badvpn_dns/ncd/README
@@ -0,0 +1,386 @@
+# This file contains some examples of using NCD, the Network Configuration Daemon.
+#
+# A short introduction to NCD follows.
+#
+# NCD is a general-purpose system configuration system, operated with a unique programming language.
+# The configuration consists of one or more so-called processes that can be considered executing in
+# parallel. Further, each process consists of one or more statements, representing the individual
+# actions. Statements are implemented as modules built into NCD.
+#
+# Inside a process, statements can be considered "executed" one after another. That is, when NCD
+# starts up, it initializes the first statement, putting it in the DOWN state. When the statement
+# reports having transitioned into the UP state, it initializes the next statement in the DOWN state,
+# and so on.
+#
+# However, execution can go in the other direction too. A statement in the UP state can, at any time,
+# report having transitioned into the DOWN state. At this point, any statements after that one will
+# automatically be de-initialized. The de-initiazation is done from the bottom up. First the last
+# initialized statement after the problematic statement is requested to terminate and enters the
+# DYING state. After it terminates, its preceding statement enters the DYING state, and so on, until
+# all statements following the problematic statement have been de-initiazed.
+#
+# The backward-execution is the key feature of NCD, and is particularly well suited for programming
+# system configurations. Read on to see why.
+#
+# Statements in NCD can be divided into two categories:
+#   - Statements that configure something. These statements transition into the UP state "immediately".
+#     On de-initialization, such statements perform the reverse operation of what they did when initialized.
+#     Imaginary example: a statement that turn a light on intialization, and turns if off on de-initialization.
+#   - Statements that wait for something. These statements may remain in the DOWN state indefinitely.
+#     They enter the UP state when the waited-for condition is satisfied, and also go back into the DOWN
+#     state when it is no longer satisfied.
+#     Imaginary example: a statement that is UP when a switch is turned on, and DOWN when it is turned off.
+#
+# Using the two example statements, we can constuct a process that controls the light based on the switch:
+# (these are not really implemented in NCD :)
+#
+#  process light {
+#      wait_switch();
+#      turn_light();
+#  }
+#
+# When the switch is turned on, wait_switch() will transition to UP, initializing turn_light(), turning the
+# light on. When the switch is turned off, wait_switch() will transition to DOWN, causing the de-initialization
+# of turn_light(), turning the light off.
+# We can add another turn_light() at the end to make the switch control two lights.
+#
+# A more complex example: We have a christmas three with lights on it. There are multiple "regular" lights,
+# controlled with switches, and a special "top" light. The regular lights take a long time to turn on, and
+# each takes a different, unpredictable time. We want the top light to be turned on if and only if all the regular
+# lights are completely on.
+#
+# This problem can easily be solved using dependencies. NCD has built-in support for dependencies, provided
+# in the form of provide() and depend() statements. A depend() statement is DOWN when its corresponding
+# provide() statement is not initialized, and UP when it is. When a provide() is requested to de-initialize, it
+# transitions the depend() statements back into the DOWN state, and, before actually dying, waits for any
+# statements following them to de-initialize.
+#
+# The christmas three problem can then be programmed as follows:
+#
+# process light1 {
+#     wait_switch1();
+#     turn_light1();
+#     provide("L1");
+# }
+#
+# process light2 {
+#     wait_switch2();
+#     turn_light2();
+#     provide("L2");
+# }
+#
+# process top_light {
+#     depend("L1");
+#     depend("L2");
+#     turn_top_light();
+# }
+#
+# Follow some real examples of network configuration using NCD.
+# For a list of implemented statements and their descriptions, take a look at the BadVPN source code, in
+# the ncd/modules/ folder.
+#
+
+#
+# Network card using DHCP.
+#
+
+process lan {
+    # Make the interface name a variable so we can refer to it.
+    # The NCD language has no notion of assigning a variable. Instead variables are
+    # provided by statements preceding the statement where they are used.
+    # The built-in var() statement can be used to make an alias.
+    var("eth0") dev;
+
+    # Wait for the network card to appear, set it up and wait for the cable to be
+    # plugged it.
+    net.backend.waitdevice(dev);
+    net.up(dev);
+    net.backend.waitlink(dev);
+
+    # Start DHCP.
+    net.ipv4.dhcp(dev) dhcp;
+    
+    # DHCP has obtained an address.
+    # Because net.ipv4.dhcp does no checks of the IP address, as a safety measure, do not proceed
+    # if the address is local.
+    ip_in_network(dhcp.addr, "127.0.0.0", "8") test_local;
+    ifnot(test_local);
+
+    # Assign the obtained address to the interface.
+    net.ipv4.addr(dev, dhcp.addr, dhcp.prefix);
+
+    # Add a default route.
+    # <dest> <dest_prefix> <gateway/"none"> <metric> <device>
+    net.ipv4.route("0.0.0.0", "0", dhcp.gateway, "20", dev);
+
+    # Add DNS servers, as provided by DHCP.
+    # "20" is the priority of the servers. When applying DNS servers, NCD collects the servers
+    # from all active net.dns() statements, sorts them by priority ascending (stable), and writes
+    # them to /etc/resolv.conf, overwriting anything that was previously there.
+    net.dns(dhcp.dns_servers, "20");
+}
+
+#
+# Network card with static configuration.
+#
+
+process lan2 {
+    # Make the interface name a variable so we can refer to it.
+    var("eth1") dev;
+
+    # Wait for the network card to appear, set it up and wait for the cable to be
+    # plugged it.
+    net.backend.waitdevice(dev);
+    net.up(dev);
+    net.backend.waitlink(dev);
+
+    # Assign an IP address.
+    # "24" is prefix length, i.e. subnet mask 255.255.255.0
+    net.ipv4.addr(dev, "192.168.62.3", "24");
+
+    # Add a default route.
+    net.ipv4.route("0.0.0.0", "0", "192.168.62.3", "20", dev);
+
+    # Build a list of DNS servers.
+    # The NCD language does not support "expressions" - statement arguments must be
+    # constant strings or variables referring to preceding statements.
+    # A list can be constructed using the built-in list() statement.
+    list("192.168.62.5", "192.168.62.6") dns_servers;
+
+    # Add the DNS servers.
+    net.dns(dns_servers, "20");
+}
+
+#
+# Wireless network interface using wpa_supplicant.
+#
+
+process WLAN {
+    # Set device.
+    var("wlan0") dev;
+
+    # Wait for device and rfkill switch.
+    net.backend.waitdevice(dev);
+    net.backend.rfkill("wlan", dev);
+
+    # Start wpa_supplicant on this interface, using configuration in /etc/wpa_supplicant/all.conf .
+    list() args;
+    net.backend.wpa_supplicant(dev, "/etc/wpa_supplicant/all.conf", "/usr/sbin/wpa_supplicant", args) sup;
+
+    # wpa_supplicant tells us what network we connected to. Look below for how this can be used to
+    # have different configurations, "BadVPN, but configured differently based on what network we're in".
+    println("connected to wireless network: bssid=", sup.bssid, " ssid=", sup.ssid);
+
+    # Wireless connection successful, here comes network config (DHCP/static/whatever) ...
+}
+
+#
+# A BadVPN VPN interface for access to the virtual network (only).
+#
+
+process lan {
+    ... (something like above) ...
+
+    # Alias our IP address for easy access from the "vpn" process (or, for a static address, alias
+    # it before assigning it, and assign it using the alias).
+    var(dhcp.addr) ipaddr;
+
+    # Allow VPN to start at this point.
+    # (and require it to stop before deconfiguring the interface if e.g. the cable is plugged out)
+    provide("LAN");
+}
+
+process vpn {
+    # Need the local interface to be working in order start VPN.
+    depend("LAN") landep;
+
+    # Choose the name of the network interface.
+    var("tap3") dev;
+
+    # Construct command line arguments for badvpn-client. Adapt according to your setup.
+    # "--tapdev" will be provided automatically.
+
+    # Alias the port number that the VPN process will bind to.
+    var("6000") port;
+
+    # Construct dynamic parts of command line options.
+    # The VPN client program needs to know some IP addresses in order to tell other peers where to connect to.
+    # Obtain this informations from variables in the "lan" process through the depend() statement.
+
+    # Construct the local address (addr + port).
+    concat(landep.ipaddr, ":", port) local_addr_arg;
+
+    # Construct the Internet address (assuming we are behind a NAT).
+    # Need to know the NAT's external address here. But we could queried it somehow.
+    # That is if we have preconfigured the NAT router to forward ports. But we could implement a statement
+    # that obtains the mappings dynamically with UPnP!
+    concat("1.2.3.4", ":", port) internet_addr_arg;
+
+    # Finally construct the complete arguments, using the above address arguments.
+    list(
+        "--logger", "syslog", "--syslog-ident", "badvpn",
+        "--server-addr", "badvpn.example.com:7000",
+        "--ssl", "--nssdb", "sql:/home/badvpn/nssdb", "--client-cert-name", "peer-someone",
+        "--transport-mode", "udp", "--encryption-mode", "blowfish", "--hash-mode", "md5", "--otp", "blowfish", "3000", "2000",
+        "--scope", "mylan", "--scope", "internet",
+        "--bind-addr", "0.0.0.0:6000", "--num-ports", "20",
+                "--ext-addr", local_addr_arg, "mylan",
+                "--ext-addr", internet_addr_arg, "internet"
+    ) args;
+
+    # Start the BadVPN backend.
+    # "badvpn" is the user account which the VPN client will run as.
+    # If you use SSL, the NSS database must be accessible to this user.
+    net.backend.badvpn(dev, "badvpn", "/usr/bin/badvpn-client-26", args);
+
+    # Assign an IP address to the VPN interface.
+    # (we could easily use DHCP here!)
+    net.ipv4.addr(dev, "10.0.0.1", "24");
+}
+
+#
+# BadVPN, but configured differently based on what network we're in.
+# The network is identified based on the IP address we were assigned by DHCP.
+# The different configuration provide specific arguents to badvpn-client.
+#
+
+process lan {
+    ... (interface config stuff using DHCP, see above) ...
+    ... (the 'ipaddr' variable holds the local IP address) ...
+
+    # Match the address to various known networks.
+    ip_in_network(ipaddr, "192.168.4.0", "24") is_lan1;
+    ip_in_network(ipaddr, "192.168.7.0", "24") is_lan2;
+
+    # Allow VPN to start at this point.
+    provide("LAN");
+}
+
+process vpn {
+    ...
+
+    # Construct common arguments here ...
+    list( ... ) common_args;
+
+    # Choose appropriate configuration by waking up the configuration processes
+    # and waiting for one to complete.
+    provide("VPN_CONF_START");
+    depend("VPN_CONF_END") config;
+
+    # Concatenate common and configuration-specific arguments.
+    concatlist(common_args, config.args) args;
+
+    ...
+}
+
+process vpn_config_lan1 {
+    depend("VPN_CONF_START") dep;
+
+    # Proceed only if we're in lan1.
+    if(dep.landep.is_lan1);
+
+    list(
+        ...
+    ) args;
+
+    provide("VPN_CONF_END");
+}
+
+process vpn_config_lan2 {
+    depend("VPN_CONF_START") dep;
+
+    # Proceed only if we're in lan2.
+    if(dep.landep.is_lan2);
+
+    list(
+        ...
+    ) args;
+
+    provide("VPN_CONF_END");
+}
+
+process vpn_config_inet {
+    depend("VPN_CONF_START") dep;
+
+    # Proceed only if we're not in any known network.
+    ifnot(dep.landep.is_lan1);
+    ifnot(dep.landep.is_lan2);
+
+    list(
+        ...
+    ) args;
+
+    provide("VPN_CONF_END");
+}
+
+#
+# Two wired network interfaces (eth0, eth1), both of which may be used for Internet access.
+# When both are working, give priority to eth1 (e.g. if eth0 is up, but later eth1 also comes
+# up, the configuration will be changed to use eth1 for Internet access).
+#
+
+process eth0 {
+    # Set device.
+    var("eth0") dev;
+
+    # Wait for device.
+    net.backend.waitdevice(dev);
+    net.up(dev);
+    net.backend.waitlink(dev);
+
+    # DHCP configuration.
+    net.ipv4.dhcp(dev) dhcp;
+    ip_in_network(dhcp.addr, "127.0.0.0", "8") test_local;
+    ifnot(test_local);
+    var(dhcp.addr) addr;
+    var(dhcp.prefix) addr_prefix;
+    var(dhcp.gateway) gateway;
+    var(dhcp.dns_servers) dns_servers;
+
+    # Assign IP address.
+    net.ipv4.addr(dev, addr, addr_prefix);
+
+    # Go on configuring the network.
+    multiprovide("NET-eth0");
+}
+
+process eth1 {
+    # Set device.
+    var("eth1") dev;
+
+    # Wait for device.
+    net.backend.waitdevice(dev);
+    net.up(dev);
+    net.backend.waitlink(dev);
+
+    # Static configuration.
+    var("192.168.111.116") addr;
+    var("24") addr_prefix;
+    var("192.168.111.1") gateway;
+    list("192.168.111.14", "193.2.1.66") dns_servers;
+
+    # Assign IP address.
+    net.ipv4.addr(dev, addr, addr_prefix);
+
+    # Go on configuring the network.
+    multiprovide("NET-eth1");
+}
+
+process NETCONF {
+    # Wait for some network connection. Prefer eth1 by putting it in front of eth0.
+    list("NET-eth1", "NET-eth0") pnames;
+    multidepend(pnames) ifdep;
+
+    # Alias device values.
+    var(ifdep.dev) dev;
+    var(ifdep.addr) addr;
+    var(ifdep.addr_prefix) addr_prefix;
+    var(ifdep.gateway) gateway;
+    var(ifdep.dns_servers) dns_servers;
+
+    # Add default route.
+    net.ipv4.route("0.0.0.0", "0", gateway, "20", dev);
+
+    # Configure DNS servers.
+    net.dns(dns_servers, "20");
+}
diff --git a/external/badvpn_dns/ncd/emncd.c b/external/badvpn_dns/ncd/emncd.c
new file mode 100644
index 0000000..db41968
--- /dev/null
+++ b/external/badvpn_dns/ncd/emncd.c
@@ -0,0 +1,137 @@
+/**
+ * @file emncd.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/version.h>
+#include <misc/debug.h>
+#include <base/BLog.h>
+#include <system/BTime.h>
+#include <system/BReactor.h>
+#include <ncd/NCDInterpreter.h>
+#include <ncd/NCDConfigParser.h>
+
+#include <generated/blog_channel_ncd.h>
+
+static BReactor reactor;
+static int running;
+static NCDInterpreter interpreter;
+
+static void interpreter_handler_finished (void *user, int exit_code)
+{
+    ASSERT(running)
+    
+    fprintf(stderr, "--- interpreter terminated ---\n");
+    
+    NCDInterpreter_Free(&interpreter);
+    
+    running = 0;
+}
+
+__attribute__((used))
+int main ()
+{
+    BLog_InitStderr();
+    
+    fprintf(stderr, "--- initializing emncd version "GLOBAL_VERSION" ---\n");
+    
+    BTime_Init();
+    
+    BReactor_EmscriptenInit(&reactor);
+    
+    running = 0;
+    
+    return 0;
+}
+
+__attribute__((used))
+void emncd_start (const char *program_text, int loglevel)
+{
+    ASSERT(program_text);
+    ASSERT(loglevel >= 0);
+    ASSERT(loglevel <= BLOG_DEBUG);
+    
+    if (running) {
+        fprintf(stderr, "--- cannot start, interpreter is already running! ---\n");
+        return;
+    }
+    
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        BLog_SetChannelLoglevel(i, loglevel);
+    }
+    
+    // parse program
+    NCDProgram program;
+    if (!NCDConfigParser_Parse((char *)program_text ,strlen(program_text), &program)) {
+        fprintf(stderr, "--- error: failed to parse the program ---\n");
+        return;
+    }
+    
+    // include commands are not implemented currently
+    if (NCDProgram_ContainsElemType(&program, NCDPROGRAMELEM_INCLUDE) || NCDProgram_ContainsElemType(&program, NCDPROGRAMELEM_INCLUDE_GUARD)) {
+        fprintf(stderr, "--- error: include mechanism is not supported in emncd ---\n");
+        NCDProgram_Free(&program);
+        return;
+    }
+    
+    fprintf(stderr, "--- starting interpreter ---\n");
+    
+    struct NCDInterpreter_params params;
+    params.handler_finished = interpreter_handler_finished;
+    params.user = NULL;
+    params.retry_time = 5000;
+    params.extra_args = NULL;
+    params.num_extra_args = 0;
+    params.reactor = &reactor;
+    
+    if (!NCDInterpreter_Init(&interpreter, program, params)) {
+        fprintf(stderr, "--- failed to initialize the interpreter ---\n");
+        return;
+    }
+    
+    running = 1;
+    
+    BReactor_EmscriptenSync(&reactor);
+}
+
+__attribute__((used))
+void emncd_stop (void)
+{
+    if (!running) {
+        fprintf(stderr, "--- cannot request termination, interpreter is not running! ---\n");
+        return;
+    }
+    
+    fprintf(stderr, "--- requesting interpreter termination ---\n");
+    
+    NCDInterpreter_RequestShutdown(&interpreter, 0);
+    
+    BReactor_EmscriptenSync(&reactor);
+}
diff --git a/external/badvpn_dns/ncd/emncd.html b/external/badvpn_dns/ncd/emncd.html
new file mode 100644
index 0000000..befc070
--- /dev/null
+++ b/external/badvpn_dns/ncd/emncd.html
@@ -0,0 +1,320 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>NCD in Javascript</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+</head>
+<body>
+
+<script src="emncd.js"></script>
+
+<script>
+
+var cfunc_emncd_start = Module.cwrap('emncd_start', 'null', ['string']);
+var cfunc_emncd_stop = Module.cwrap('emncd_stop', 'null', []);
+
+function on_start ()
+{
+    var code = document.getElementById('codetext').value;
+    
+    var loglevel = 0;
+    for (i = 0; i <= 5; i++) {
+        if (document.getElementById('loglevel' + i).checked) {
+            loglevel = i;
+        }
+    }
+
+    cfunc_emncd_start(code, loglevel);
+}
+
+function on_stop ()
+{
+    cfunc_emncd_stop();
+}
+
+function load_example (num)
+{
+    document.getElementById('codetext').value = document.getElementById('example' + num).value;
+}
+
+</script>
+
+This is a quick port of my <a href="http://code.google.com/p/badvpn/wiki/NCD";>NCD programming language</a>
+to Javascript using the <a href="https://github.com/kripken/emscripten";>Emscripten</a> compiler.
+<br />
+
+Please open the Javascript console so you can see the output (Chrome: CTRL+Shift+J. Firefox: CTRL+Shift+K).
+<br />
+
+<textarea id="example1" style="display:none;">
+process hello {
+    println("hello, world");
+    exit("0");
+}
+</textarea>
+
+<textarea id="example2" style="display:none;">
+process foo {
+    println("Starting up, please wait...");
+    rprintln("Goodbye World!");
+
+    sleep("500", "300"); # sleeps 500ms on init and 300ms on deinit
+
+    println("Hello World!");
+    rprintln("Shutting down, please wait...");
+}
+</textarea>
+
+<textarea id="example3" style="display:none;">
+process hello {
+    var("0") ctr;
+
+    blocker() blk;
+    blk->up();
+    blk->use();
+
+    num_modulo(ctr, "3") m_three;
+    num_modulo(ctr, "5") m_five;
+    num_equal(m_three, "0") d_three;
+    num_equal(m_five, "0") d_five;
+    and(d_three, d_five) d_three_five;
+
+    If (d_three_five) {
+        var("fizzbuzz") text;
+    } Elif (d_three) {
+        var("fizz") text;
+    } Elif (d_five) {
+        var("buzz") text;
+    } else {
+        var(ctr) text;
+    } branch;
+
+    println(branch.text);
+
+    num_add(ctr, "1") i;
+    num_modulo(i, "20") i;
+    ctr->set(i);
+
+    sleep("100", "0");
+    blk->downup();
+
+}
+</textarea>
+
+<textarea id="example4" style="display:none;">
+process main {
+    var({"0", "1", "3", "2", "2", "3", "1", "1", "6", "end"}) list;
+    value(["1":"one", "2":"two", "3":"three"]) map;
+
+    call("replace", {"_caller.list", "_caller.map"}) replace;
+
+    to_string(replace.result) str;
+    println(str);
+    exit("0");
+}
+
+template replace {
+    alias(_arg0) list;
+    alias(_arg1) map;
+
+    value({}) new_list;
+    Foreach (list As elem) {
+        map->try_get(elem) y;
+        If (y.exists) {
+            new_list->insert(new_list.length, y);
+        } Else {
+            new_list->insert(new_list.length, elem);
+        };
+    };
+
+    alias("new_list") result;
+}
+</textarea>
+
+<textarea id="example5" style="display:none;">
+process hello {
+    println("Hello, NCD in Javascript!");
+    println("Will now wait 2 seconds.");
+    rprintln("Goodbye.");
+
+    sleep("2000", "3000");
+    rprintln("Waiting 3 seconds before dying.");
+
+    call("func", {"FirstArg", "SecondArg"});
+    
+    var({"one", "two", "three"}) list;
+    to_string(list) str;
+    println(str);
+    
+    Foreach (list As elem) {
+        println("start ", elem);
+        rprintln("stop ", elem);
+    };
+
+    println("We're finished. Press \"Request termination\" to unwind us.");
+    rprintln("Terminating...");
+}
+
+template func {
+    println("func here, my args are: ", _arg0, " ", _arg1);
+    rprintln("func going away");
+}
+</textarea>
+
+<textarea id="example6" style="display:none;">
+process main {
+    # Turing machine specification.
+    var("B") blank;
+    var([
+        {"0", "0"}:{"0", "0", "right"},
+        {"0", "1"}:{"1", "x", "right"},
+        {"1", "1"}:{"1", "1", "right"},
+        {"1", "0"}:{"2", "0", "right"},
+        {"2", "0"}:{"2", "0", "right"},
+        {"2", "1"}:{"3", "1", "right"},
+        {"3", "1"}:{"3", "1", "right"},
+        {"3", "0"}:{"4", "1", "left"},
+        {"3", "B"}:{"4", "1", "left"},
+        {"4", "1"}:{"4", "1", "left"},
+        {"4", "0"}:{"5", "0", "left"},
+        {"5", "0"}:{"5", "0", "left"},
+        {"5", "1"}:{"6", "1", "left"},
+        {"5", "x"}:{"h", "x", "stay"},
+        {"6", "1"}:{"6", "1", "left"},
+        {"6", "x"}:{"0", "x", "right"},
+        {"6", "0"}:{"0", "0", "right"}
+    ]) rules;
+    var("0") initial_state;
+    var({}) initial_tape_left;
+    var({
+        "1", "1", "1", "1", "0", "0", "1", "1", "1", "1"
+    }) initial_tape_right;
+
+    # Perform the computation, stopping when no rule matches.
+    call("turing", {blank, rules, initial_state, initial_tape_left, initial_tape_right}) results;
+
+    # Print results.
+    to_string(results.tape_left) tape_left;
+    to_string(results.tape_right) tape_right;
+    to_string({results.side, results.pos}) head_pos;
+    to_string(results.state) head_state;
+    println("Tape L: ", tape_left);
+    println("Tape R: ", tape_right);
+    println("Head position: ", head_pos);
+    println("Head state: ", head_state);
+
+    exit("0");
+}
+
+template turing {
+    alias("_arg0") blank;
+    value(_arg1) rules;
+    alias("_arg2") initial_state;
+    alias("_arg3") initial_tape_left;
+    alias("_arg4") initial_tape_right;
+
+    # Head state.
+    var(initial_state) state;
+
+    # Tape. Positions go like this: ... L2 L1 L0 R0 R1 R2 ... 
+    value(initial_tape_left) tape_left;
+    value(initial_tape_right) tape_right;
+
+    # Make sure each side of the tape has at least one symbol so we can flip easily.
+    tape_left->insert(tape_left.length, blank);
+    tape_right->insert(tape_right.length, blank);
+
+    # Head position.
+    var("right") side;
+    var("0") pos;
+
+    # Enter loop.
+    blocker() loop_blk;
+    loop_blk->up();
+    loop_blk->use();
+
+    # Get symbol under head.
+    concat("tape_", side) tape_name;
+    alias(tape_name) cur_tape;
+    cur_tape->get(pos) symbol;
+
+    # Look for a matching rule.
+    rules->try_get({state, symbol}) rule;
+
+    If (rule.exists) {
+        # Extract directions from rule.
+        rule->get("0") new_state;
+        rule->get("1") new_symbol;
+        rule->get("2") move;
+
+        # Change head state.
+        state->set(new_state);
+
+        # Replace symbol under head.
+        cur_tape->remove(pos);
+        cur_tape->insert(pos, new_symbol);
+
+        # Branch based on how we move.
+        strcmp(move, side) is_outside;
+        strcmp(move, "stay") is_stay;
+        strcmp(pos, "0") is_zero;
+        If (is_outside) {
+            # Increment position.
+            num_add(pos, "1") new_pos;
+            pos->set(new_pos);
+
+            # If the new position is out of range, extend tape.
+            strcmp(pos, cur_tape.length) need_extend;
+            If (need_extend) {
+                cur_tape->insert(pos, blank);
+            };
+        } elif (is_stay) {
+            # Nop.
+            getargs();
+        } elif (is_zero) {
+            # Flip side, leave pos at zero.
+            side->set(move);
+        } else {
+            # Decrement position.
+            num_subtract(pos, "1") new_pos;
+            pos->set(new_pos);
+        };
+
+        # Continue loop.
+        loop_blk->downup();
+    };
+}
+</textarea>
+
+Examples:
+<button onclick="load_example(1)">Hello World</button>
+<button onclick="load_example(2)">Sleep</button>
+<button onclick="load_example(3)">FizzBuzz</button>
+<button onclick="load_example(4)">Replace</button>
+<button onclick="load_example(5)">Foreach, call</button>
+<button onclick="load_example(6)">Turing Machine</button>
+<br />
+
+<textarea rows=30 cols=80 id="codetext">
+process hello {
+    println("hello, world");
+    exit("0");
+}
+</textarea>
+<br />
+
+Loglevel: 
+<input type="radio" name="loglevel" id="loglevel0"> None
+<input type="radio" name="loglevel" id="loglevel1"> Error
+<input type="radio" name="loglevel" id="loglevel2" checked> Warning
+<input type="radio" name="loglevel" id="loglevel3"> Notice
+<input type="radio" name="loglevel" id="loglevel4"> Info
+<input type="radio" name="loglevel" id="loglevel5"> Debug <br />
+
+<button onclick="on_start()">Start NCD</button>
+<button onclick="on_stop()">Request termination</button>
+<br />
+Note: if you get the interpreter stuck and unable to terminate, just refresh the page
+
+</body>
+</html>
diff --git a/external/badvpn_dns/ncd/examples/dbus_start.ncd b/external/badvpn_dns/ncd/examples/dbus_start.ncd
new file mode 100644
index 0000000..832b359
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/dbus_start.ncd
@@ -0,0 +1,82 @@
+process main {
+    call("dbus_start", {{"--session", "--nopidfile"}}) dbus_start;
+
+    println("Got dbus: ", dbus_start.dbus_address);
+    rprintln("Lost dbus");
+}
+
+template dbus_start {
+    alias("_arg0") dbus_args;
+
+    var("false") retrying;
+
+    backtrack_point() retry_point;
+    If (retrying) {
+        println("dbus_start: retrying in one second");
+        sleep("1000", "0");
+    };
+
+    retrying->set("true");
+
+    listfrom({"/usr/bin/dbus-daemon"}, dbus_args, {"--print-address"}) dbus_command;
+
+    sys.start_process(dbus_command, "r") proc;
+    If (proc.is_error) {
+        println("dbus_start: error starting process");
+        retry_point->go();
+    };
+
+    var("") dbus_address;
+    blocker() finished_blocker;
+
+    process_manager() mgr;
+    mgr->start("dbus_start__waiter", {});
+    mgr->start("dbus_start__reader", {});
+
+    finished_blocker->use();
+}
+
+template dbus_start__waiter {
+    alias("_caller.proc") proc;
+    alias("_caller.retry_point") retry_point;
+
+    proc->wait();
+    println("dbus_start: process terminated");
+    retry_point->go();
+}
+
+template dbus_start__reader {
+    alias("_caller.proc") proc;
+    alias("_caller.retry_point") retry_point;
+    alias("_caller.dbus_address") dbus_address;
+    alias("_caller.finished_blocker") finished_blocker;
+
+    proc->read_pipe() rpipe;
+    If (rpipe.is_error) {
+        println("dbus_start: read pipe error");
+        retry_point->go();
+    };
+
+    value("") buffer;
+
+    backtrack_point() read_point;
+    rpipe->read() data;
+
+    If (data.not_eof) {
+        buffer->append(data);
+
+        explode("\n", buffer, "2") exp;
+        value(exp) exp;
+
+        num_greater(exp.length, "1") found;
+        If (found) {
+            exp->get("0") address;
+            dbus_address->set(address);
+            finished_blocker->up();
+        };
+
+        read_point->go();
+    };
+
+    println("dbus_start: read pipe eof");
+}
diff --git a/external/badvpn_dns/ncd/examples/dhcpd.conf.template b/external/badvpn_dns/ncd/examples/dhcpd.conf.template
new file mode 100644
index 0000000..9ce5ad2
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/dhcpd.conf.template
@@ -0,0 +1,11 @@
+default-lease-time 600;
+max-lease-time 7200;
+log-facility local7;
+ddns-update-style none;
+local-address <LOCAL_ADDRESS>;
+
+subnet <NETWORK> netmask <NETMASK> {
+    range <RANGE_START> <RANGE_END>;
+    option routers <ROUTERS>;
+    option domain-name-servers <DNS_SERVERS>;
+}
diff --git a/external/badvpn_dns/ncd/examples/directory_updater.ncd b/external/badvpn_dns/ncd/examples/directory_updater.ncd
new file mode 100644
index 0000000..cb546ff
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/directory_updater.ncd
@@ -0,0 +1,72 @@
+#
+# Watches a directory and runs an update program whenever something
+# changes. This is race-free, and if the directory changes while the
+# update is running, it will be done again.
+#
+# NOTE: the update should not modify the directory. If it does, the
+#       the result will be endless and constant updating.
+#
+
+process watcher {
+    # Blocker object used to trigger an update.
+    blocker() blk;
+
+    # State: updating - if updater script is running
+    #        updating_dirty - if something changed while
+    #                         script was running
+    var("false") updating;
+    var("false") updating_dirty;
+
+    # Start update process.
+    spawn("updater", {});
+
+    # Wait for directory event.
+    # CHANGE THIS
+    sys.watch_directory("/home/ambro") watcher;
+
+    # Print event details (e.g. "added somefile").
+    println(watcher.filename, " ", watcher.event_type);
+
+    # If updating is in progress, mark dirty.
+    If (updating) {
+        updating_dirty->set("true");
+    };
+
+    # Request update. This makes use() proceed forward.
+    blk->up();
+
+    # Wait for next event (execution moves up).
+    watcher->nextevent();
+}
+
+template updater {
+    # Wait for update request.
+    _caller.blk->use();
+
+    # Wait some time.
+    sleep("1000", "0");
+
+    # We're about to start update script - set updating
+    # variable and mark as non dirty.
+    _caller.updating_dirty->set("false");
+    _caller.updating->set("true");
+
+    println("Update started");
+
+    # CHANGE THIS
+    sleep("3000", "0");
+    #runonce({"/bin/echo", "Updater speaking"}, {"keep_stdout", "keep_stderr"});
+
+    println("Update finished");
+
+    # No longer running update script.
+    _caller.updating->set("false");
+
+    # If something changed while script was running, restart
+    # update; else wait for next update request.
+    If (_caller.updating_dirty) {
+        _caller.blk->downup();
+    } else {
+        _caller.blk->down();
+    };
+}
diff --git a/external/badvpn_dns/ncd/examples/events.ncd b/external/badvpn_dns/ncd/examples/events.ncd
new file mode 100644
index 0000000..9fe80a8
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/events.ncd
@@ -0,0 +1,101 @@
+#
+# NCD input event handling example program.
+#
+# This program responds to volume key presses by synchronously calling an external
+# script for muting and adjusting volume, and responds to power button presses by
+# suspending using pm-suspend.
+#
+# It uses process_manager() and sys.watch_input() to dynamically create and remove
+# processes that deal with specific input devices. The individual input device processes
+# then use sys.evdev() to handle input events from their input device.
+#
+
+process events_main {
+    # Volume control script, called with argument "up", "down" or "mute".
+    var("/usr/local/bin/volumekey") volume_script;
+
+    # Suspend command.
+    list("/usr/sbin/pm-suspend") suspend_cmd;
+
+    provide("GLOBAL");
+}
+
+process events_watcher {
+    depend("GLOBAL");
+  
+    # Create process manager.
+    process_manager() manager;
+
+    # Wait for input device events.
+    sys.watch_input("event") watcher;
+
+    # Dispatch.
+    concat("events_watcher_", watcher.event_type) func;
+    call(func, {});
+
+    # Next event.
+    watcher->nextevent();
+}
+
+template events_watcher_added {
+    # Start event handling process for this device.
+    _caller.manager->start(_caller.watcher.devname, "events_input_device", {_caller.watcher.devname});
+}
+
+template events_watcher_removed {
+    # Stop event handling process for this device.
+    _caller.manager->stop(_caller.watcher.devname);
+}
+
+template events_input_device {
+    # Alias arguments.
+    var(_arg0) dev;
+
+    # Get global.
+    depend("GLOBAL") gdep;
+
+    # Wait for input events.
+    sys.evdev(dev) evdev;
+
+    # Query event details.
+    strcmp(evdev.code, "KEY_MUTE") is_mute;
+    strcmp(evdev.code, "KEY_VOLUMEUP") is_vup;
+    strcmp(evdev.code, "KEY_VOLUMEDOWN") is_vdown;
+    strcmp(evdev.code, "KEY_POWER") is_power;
+    strcmp(evdev.value, "1") is_pressed;
+
+    # Compute where to dispatch the event.
+    and(is_mute, is_pressed) dispatch_mute;
+    and(is_vup, is_pressed) dispatch_vup;
+    and(is_vdown, is_pressed) dispatch_vdown;
+    and(is_power, is_pressed) dispatch_power;
+
+    # Dispatch event.
+    choose({
+        {dispatch_mute, "events_input_event_mute"},
+        {dispatch_vup, "events_input_event_vup"},
+        {dispatch_vdown, "events_input_event_vdown"},
+        {dispatch_power, "events_input_event_power"}},
+        "<none>"
+    ) func;
+    call(func, {});
+
+    # Next event.
+    evdev->nextevent();
+}
+
+template events_input_event_mute {
+    runonce({_caller.gdep.volume_script, "mute"});
+}
+
+template events_input_event_vup {
+    runonce({_caller.gdep.volume_script, "up"});
+}
+
+template events_input_event_vdown {
+    runonce({_caller.gdep.volume_script, "down"});
+}
+
+template events_input_event_power {
+    runonce(_caller.gdep.suspend_cmd);
+}
diff --git a/external/badvpn_dns/ncd/examples/igmpproxy.conf.template b/external/badvpn_dns/ncd/examples/igmpproxy.conf.template
new file mode 100644
index 0000000..9e5a83c
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/igmpproxy.conf.template
@@ -0,0 +1,10 @@
+quickleave
+
+phyint <UPSTREAM_IFACE> upstream  ratelimit 0  threshold 1
+    altnet 89.143.8.0/24
+    altnet 95.176.0.0/16
+
+phyint <DOWNSTREAM_IFACE> downstream  ratelimit 0  threshold 1
+
+# A lot of these will be appended by make_igmpproxy_config:
+# phyint eth0 disabled
diff --git a/external/badvpn_dns/ncd/examples/make_dhcp_config.ncd b/external/badvpn_dns/ncd/examples/make_dhcp_config.ncd
new file mode 100644
index 0000000..201a052
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/make_dhcp_config.ncd
@@ -0,0 +1,27 @@
+process main {
+    call("make_dhcp_config", {"192.168.1.1", "24", "192.168.1.100", "192.168.1.199", {"192.168.1.1"}, {"192.168.1.1", "4.4.4.4"}, "dhcpd.conf.template", "dhcpd.conf"}) config;
+    exit("0");
+}
+
+template make_dhcp_config {
+    alias("_arg0") addr;
+    alias("_arg1") prefix;
+    alias("_arg2") range_start;
+    alias("_arg3") range_end;
+    alias("_arg4") routers;
+    alias("_arg5") dns_servers;
+    alias("_arg6") template_file;
+    alias("_arg7") output_file;
+
+    ipv4_net_from_addr_and_prefix(addr, prefix) network;
+    ipv4_prefix_to_mask(prefix) netmask;
+    implode(", ", routers) routers_str;
+    implode(", ", dns_servers) dns_servers_str;
+
+    var({"<LOCAL_ADDRESS>", "<NETWORK>", "<NETMASK>", "<RANGE_START>", "<RANGE_END>", "<ROUTERS>", "<DNS_SERVERS>"}) regex;
+    var({addr, network, netmask, range_start, range_end, routers_str, dns_servers_str}) replace;
+
+    file_read(template_file) template_data;
+    regex_replace(template_data, regex, replace) replaced_data;
+    file_write(output_file, replaced_data);
+}
diff --git a/external/badvpn_dns/ncd/examples/make_igmpproxy_config.ncd b/external/badvpn_dns/ncd/examples/make_igmpproxy_config.ncd
new file mode 100644
index 0000000..77ca0a9
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/make_igmpproxy_config.ncd
@@ -0,0 +1,53 @@
+process main {
+    call("make_igmpproxy_config", {"br0", "eth1", "./igmpproxy.conf.template", "./igmpproxy.conf"});
+
+    println("Done.");
+    exit("0");
+}
+
+template make_igmpproxy_config {
+    alias("_arg0") upstream_iface;
+    alias("_arg1") downstream_iface;
+    alias("_arg2") template_file;
+    alias("_arg3") output_file;
+
+    # Make a list of interfaces to add as disabled (upstream and downstream will not be disabled).
+    var({
+        "eth0", "eth1", "eth2", "eth3", "eth4", "eth5", "eth6", "eth7", "eth8", "eth9",
+        "wlan0", "wlan1", "wlan2", "wlan3", "wlan4", "wlan5", "wlan6", "wlan7", "wlan8", "wlan9",
+        "tap0", "tap1", "tap2", "tap3", "tap4", "tap5", "tap6", "tap7", "tap8", "tap9",
+        "br0", "br1", "br2", "br3", "br4", "br5", "br6", "br7", "br8", "br9"
+    }) disabled_ifaces;
+
+    # Build replacements for template config.
+    var({"<UPSTREAM_IFACE>", "<DOWNSTREAM_IFACE>"}) regex;
+    var({upstream_iface, downstream_iface}) replace;
+
+    # Read template config.
+    file_read(template_file) template_data;
+
+    # Perform replacements.
+    regex_replace(template_data, regex, replace) replaced_data;
+    
+    # Build string value from replaced_data, to which we will append
+    # configuration for disabled interfaces.
+    value(replaced_data) output_data;
+
+    # Build disabled strings.
+    Foreach (disabled_ifaces As iface) {
+        # Determine if the interface is disabled.
+        val_equal(iface, upstream_iface) is_up;
+        val_equal(iface, downstream_iface) is_down;
+        or(is_up, is_down) is_not_disabled;
+        not(is_not_disabled) is_disabled;
+        
+        # If it's disabled, append to the configuration file.
+        If (is_disabled) {
+            concat("phyint ", iface, " disabled\n") str;
+            output_data->append(str);
+        };
+    };
+    
+    # Write config.
+    file_write(output_file, output_data);
+}
diff --git a/external/badvpn_dns/ncd/examples/network.ncd b/external/badvpn_dns/ncd/examples/network.ncd
new file mode 100644
index 0000000..2eaeb8f
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/network.ncd
@@ -0,0 +1,123 @@
+# An example NCD script for network configuration.
+#
+# The first three processes demonstrate different kinds of interfaces
+# and configurations. They are all disabled by default.
+#
+# The last process waits for one of the interfaces to come up
+# and sets up routes and DNS entries to use that interface for
+# Internet access.
+#
+# Be sure to change the dependency list in the last process to name
+# the interfaces you use.
+
+# Example wired interface with static configuration.
+process wired_example_static {
+    if("false"); # remove/comment to enable
+
+    # Set device.
+    var("eth0") dev;
+
+    # Wait for device.
+    net.backend.waitdevice(dev);
+    net.up(dev);
+    net.backend.waitlink(dev);
+
+    # Static configuration.
+    var("192.168.111.116") addr;
+    var("24") addr_prefix;
+    var("192.168.111.1") gateway;
+    var({"192.168.111.14", "193.2.1.66"}) dns_servers;
+
+    # Assign IP address.
+    net.ipv4.addr(dev, addr, addr_prefix);
+
+    # Go on configuring the network.
+    concat("NET-", dev) provide_name;
+    multiprovide(provide_name);
+}
+
+# Example wired interface with DHCP configuration.
+process wired_example_dhcp {
+    if("false"); # remove/comment to enable
+
+    # Set device.
+    var("eth1") dev;
+
+    # Wait for device.
+    net.backend.waitdevice(dev);
+    net.up(dev);
+    net.backend.waitlink(dev);
+
+    # DHCP configuration.
+    net.ipv4.dhcp(dev) dhcp;
+    ip_in_network(dhcp.addr, "127.0.0.0", "8") test_local;
+    ifnot(test_local);
+    var(dhcp.addr) addr;
+    var(dhcp.prefix) addr_prefix;
+    var(dhcp.gateway) gateway;
+    var(dhcp.dns_servers) dns_servers;
+
+    # Assign IP address.
+    net.ipv4.addr(dev, addr, addr_prefix);
+
+    # Go on configuring the network.
+    concat("NET-", dev) provide_name;
+    multiprovide(provide_name);
+}
+
+# Example wireless interface with DHCP configuration.
+# This will use the wpa_supplicant configuration file /etc/wpa_supplicant/all.conf
+# which should specify the wireless networks and other options.
+process wireless_example_dhcp {
+    if("false"); # remove/comment to enable
+
+    # Set device.
+    var("wlan0") dev;
+
+    # Wait for device and rfkill.
+    net.backend.waitdevice(dev);
+    net.backend.rfkill("wlan", dev);
+
+    # Connect to wireless network.
+    net.backend.wpa_supplicant(dev, "/etc/wpa_supplicant/all.conf", "/usr/sbin/wpa_supplicant", {});
+
+    # DHCP configuration.
+    net.ipv4.dhcp(dev) dhcp;
+    ip_in_network(dhcp.addr, "127.0.0.0", "8") test_local;
+    ifnot(test_local);
+    var(dhcp.addr) addr;
+    var(dhcp.prefix) addr_prefix;
+    var(dhcp.gateway) gateway;
+    var(dhcp.dns_servers) dns_servers;
+
+    # Assign IP address.
+    net.ipv4.addr(dev, addr, addr_prefix);
+
+    # Go on configuring the network.
+    concat("NET-", dev) provide_name;
+    multiprovide(provide_name);
+}
+
+# This process sets up routes and DNS servers for at most one of
+# the working interfaces. It will change the configuration if a
+# more important interface comes up while one is already up.
+process NETCONF {
+    # Choose devices and priorities; put preferred devices to the front.
+    var({"NET-eth0", "NET-eth1", "NET-wlan0"}) provide_names;
+
+    # Wait for one of the interfaces (and deinit/switch appropriately).
+    multidepend(provide_names) ifdep;
+
+    # Alias device values.
+    var(ifdep.dev) dev;
+    var(ifdep.addr) addr;
+    var(ifdep.addr_prefix) addr_prefix;
+    var(ifdep.gateway) gateway;
+    var(ifdep.dns_servers) dns_servers;
+
+    # Add default route.
+    net.ipv4.route("0.0.0.0", "0", gateway, "20", dev);
+
+    # Configure DNS servers.
+    net.dns(dns_servers, "20");
+}
diff --git a/external/badvpn_dns/ncd/examples/onoff_server.ncdi b/external/badvpn_dns/ncd/examples/onoff_server.ncdi
new file mode 100644
index 0000000..a380cde
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/onoff_server.ncdi
@@ -0,0 +1,82 @@
+include_guard "onoff_server"
+
+template onoff_server_main {
+    alias("_arg0") socket_path;
+
+    depend_scope() depsc;
+    process_manager() mgr;
+
+    sys.request_server({"unix", socket_path}, "onoff_server__request_handler", {});
+}
+
+template onoff_server_onoff {
+    alias(_arg0) main;
+    alias("_arg1") onoff_id;
+    alias("_arg2") default_state;
+
+    main.mgr->start(onoff_id, "onoff_server__id_proc", {onoff_id, default_state});
+    main.depsc->depend({onoff_id}) id_proc;
+
+    id_proc.blk->use();
+}
+
+template onoff_server__id_proc {
+    alias("_caller") main;
+    alias("_arg0") onoff_id;
+    alias("_arg1") default_state;
+
+    blocker() blk;
+    If (default_state) {
+        blk->up();
+    };
+    main.depsc->provide(onoff_id);
+}
+
+template onoff_server__request_handler {
+    alias("_caller") main;
+
+    try("onoff_server__try_request", {});
+
+    _request->reply({"error", "Bad request."});
+    _request->finish();
+}
+
+template onoff_server__try_request {
+    alias("_caller.main") main;
+    alias("_caller._request") request;
+
+    value(request.data) data;
+
+    val_equal(data.type, "list") a;
+    _try->assert(a);
+
+    num_greater_equal(data.length, "1") a;
+    _try->assert(a);
+
+    data->get("0") request_cmd;
+    val_equal(request_cmd, "set_onoff") is_set_onoff;
+
+    If (is_set_onoff) {
+        num_equal(data.length, "3") a;
+        _try->assert(a);
+
+        data->get("1") onoff_id;
+        data->get("2") new_state;
+
+        main.mgr->start(onoff_id, "onoff_server__id_proc", {onoff_id, new_state});
+        main.depsc->depend({onoff_id}) id_proc;
+
+        val_equal(new_state, "true") is_on;
+        If (is_on) {
+            id_proc.blk->up();
+        } Else {
+            id_proc.blk->down();
+        };
+    }
+    Else {
+        _try->assert("false");
+    };
+
+    request->reply({"OK", "state has been set"});
+    request->finish();
+}
diff --git a/external/badvpn_dns/ncd/examples/onoff_server_test.ncd b/external/badvpn_dns/ncd/examples/onoff_server_test.ncd
new file mode 100644
index 0000000..7ff0b16
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/onoff_server_test.ncd
@@ -0,0 +1,20 @@
+include "onoff_server.ncdi"
+
+process main {
+    call("onoff_server_main", {"/home/ambro/onoff.socket"}) onoff_server;
+
+    process_manager() mgr;
+    mgr->start("service1", {});
+    #mgr->start("service2", {});
+}
+
+template service1 {
+    alias("_caller") main;
+
+    call("onoff_server_onoff", {"_caller.main.onoff_server", "ServiceId1", "true"});
+
+    println("service1 up");
+    rprintln("service1 down");
+
+    # Do your stuff.
+}
diff --git a/external/badvpn_dns/ncd/examples/router/README b/external/badvpn_dns/ncd/examples/router/README
new file mode 100644
index 0000000..7be3743
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/README
@@ -0,0 +1,36 @@
+NCD Router Example
+
+-- Operation ---
+
+These are the NCD scripts I run on my home router.
+Three network interfaces are being configured:
+
+1. The LAN interface.
+The DHCP server is started for this interface, and also a DNS server (unbound).
+2. The Internet interface.
+This is a PPPoE interface with NAT.
+3. The ServerIf interface.
+This one behaves similarly to the LAN interface, except that there is no DHCP server.
+The intention is to put servers here so you can restrict communication not only between Internet and the servers,
+but also between LAN and the servers (though this configuration doesn't actually do the latter).
+
+Hosts on the LAN and ServerIf interfaces can access the Internet, and source NAT is used here.
+Additionally, it is possible to add port forwardings (DNAT) from the Internet interface to either
+of those two interfaces. These can be managed with the scripts {list,add,remove}-port-forwarding.
+The list of port forwarding is stored in the file /var/lib/ncd-port-forwardings.ncdvalue.
+However, you should NOT modify this file while NCD is running. You should not modify it at all, because
+NCD may accidentally overwrite your changes. Just use the scripts.
+
+Iptables is used to filter incoming connections from the Internet interface.
+Exceptions can be added; for example, there's a commented line in template network_internet_pppoe_preup which allows access to the local SSH server.
+To allow access to servers running on other hosts (LAN or ServerIf interface), a port forwarding should be added dynamically.
+
+-- Installation --
+
+The following pppd patch is required for PPPoE to work:
+https://code.google.com/p/ambro-gentoo-overlay/source/browse/trunk/net-dialup/ppp/files/pppd-configurable-paths.patch
+
+Copy ncd.conf to /etc/, and copy all other files here into a new directory /etc/ncd-network.
+Explanation: ncd.conf just loads network.ncdi, which is where the bulk of the configuration is defined.
+Make the {list,add,remove}-port-forwarding scripts executable. Additionally, if your NCD interpreter is not located at /usr/bin/badvpn-ncd,
+adjust the interpreter paths inside them.
diff --git a/external/badvpn_dns/ncd/examples/router/add-port-forwarding b/external/badvpn_dns/ncd/examples/router/add-port-forwarding
new file mode 100644
index 0000000..5d8a508
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/add-port-forwarding
@@ -0,0 +1,43 @@
+#!/usr/bin/badvpn-ncd
+
+process main {
+    getargs() args;
+    value(args) args;
+
+    num_different(args.length, "4") bad_args;
+    If (bad_args) {
+        println("Usage: add-port-forwarding <protocol> <port_start> <port_end> <dest_addr>");
+        exit("1");
+    };
+
+    args->get("0") protocol;
+    args->get("1") port_start;
+    args->get("2") port_end;
+    args->get("3") dest_addr;
+
+    var("0") exit_status;
+
+    sys.request_client({"unix", "/run/ncd-control.socket"}) client;
+
+    var({"add-port-forwarding", protocol, port_start, port_end, dest_addr}) request_data;
+
+    client->request(request_data, "reply_handler", "finished_handler", {});
+}
+
+template reply_handler {
+    value(_reply.data) reply_data;
+    reply_data->get("0") status;
+    reply_data->get("1") text;
+
+    val_equal(status, "ok") is_ok;
+    If (is_ok) {
+        println(text);
+    } Else {
+        _caller.exit_status->set("1");
+        println("Error: ", text);
+    };
+}
+
+template finished_handler {
+    exit(_caller.exit_status);
+}
diff --git a/external/badvpn_dns/ncd/examples/router/dhcp_server.ncdi b/external/badvpn_dns/ncd/examples/router/dhcp_server.ncdi
new file mode 100644
index 0000000..98c2543
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/dhcp_server.ncdi
@@ -0,0 +1,60 @@
+include_guard "dhcp_server"
+
+template dhcp_server {
+    alias("_arg0") addr;
+    alias("_arg1") prefix;
+    alias("_arg2") range_start;
+    alias("_arg3") range_end;
+    alias("_arg4") routers;
+    alias("_arg5") dns_servers;
+
+    # Choose lease file.
+    concat("/var/lib/dhcp/dhcpd-", addr, ".leases") leases_file;
+
+    # Create leases file if it doesn't exist.
+    file_stat(leases_file) stat;
+    If (stat.succeeded) { print(); } Else {
+        file_write(leases_file, "");
+    };
+
+    # Create a temporary directory.
+    concat("/run/ncd-dhcp-server-", addr) run_dir;
+    run({"/bin/rm", "-rf", run_dir}, {});
+    run({"/bin/mkdir", run_dir}, {"/bin/rm", "-rf", run_dir});
+
+    # Compute path for dhcp.conf.
+    concat(run_dir, "/dhcp.conf") dhcp_conf_path;
+
+    # This is a template for dhcp.conf.
+    var("
+default-lease-time 43200;
+max-lease-time 43200;
+log-facility local7;
+ddns-update-style none;
+local-address <LOCAL_ADDRESS>;
+
+subnet <NETWORK> netmask <NETMASK> {
+        authoritative;
+        range <RANGE_START> <RANGE_END>;
+        option routers <ROUTERS>;
+        option domain-name-servers <DNS_SERVERS>;
+}
+"   ) config_template;
+
+    # Compute some of the variables.
+    ipv4_net_from_addr_and_prefix(addr, prefix) network;
+    ipv4_prefix_to_mask(prefix) netmask;
+    implode(", ", routers) routers_str;
+    implode(", ", dns_servers) dns_servers_str;
+
+    # Perform substitutions.
+    var({"<LOCAL_ADDRESS>", "<NETWORK>", "<NETMASK>", "<RANGE_START>", "<RANGE_END>", "<ROUTERS>", "<DNS_SERVERS>"}) regex;
+    var({addr, network, netmask, range_start, range_end, routers_str, dns_servers_str}) replace;
+    regex_replace(config_template, regex, replace) config_data;
+
+    # Write dhcp.conf.
+    file_write(dhcp_conf_path, config_data);
+
+    # Start dhcpd.
+    daemon({"/usr/sbin/dhcpd", "-f", "-cf", dhcp_conf_path, "-user", "dhcp", "-group", "dhcp", "--no-pid", "-lf", leases_file});
+}
diff --git a/external/badvpn_dns/ncd/examples/router/list-port-forwardings b/external/badvpn_dns/ncd/examples/router/list-port-forwardings
new file mode 100644
index 0000000..3244c56
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/list-port-forwardings
@@ -0,0 +1,61 @@
+#!/usr/bin/badvpn-ncd
+
+process main {
+    getargs() args;
+    value(args) args;
+
+    num_different(args.length, "0") bad_args;
+    If (bad_args) {
+        println("Usage: list-port-forwardings");
+        exit("1");
+    };
+
+    var("0") exit_status;
+
+    sys.request_client({"unix", "/run/ncd-control.socket"}) client;
+
+    var({"list-port-forwardings"}) request_data;
+
+    client->request(request_data, "reply_handler", "finished_handler", {});
+}
+
+template reply_handler {
+    value(_reply.data) reply_data;
+    reply_data->get("0") status;
+    reply_data->get("1") arg;
+
+    val_equal(status, "ok") is_ok;
+    If (is_ok) {
+        println("Protocol  Start  End    Destination");
+        Foreach (arg As entry) {
+            value(entry) entry;
+            entry->get("0") protocol;
+            entry->get("1") port_start;
+            entry->get("2") port_end;
+            entry->get("3") dest_addr;
+            call("append_spaces", {port_start, "5"}) fixed_start;
+            call("append_spaces", {port_end, "5"}) fixed_end;
+            println(protocol, "       ", fixed_start.result, "  ", fixed_end.result, "  ", dest_addr);
+        };
+    } Else {
+        _caller.exit_status->set("1");
+        println("Error: ", arg);
+    };
+}
+
+template finished_handler {
+    exit(_caller.exit_status);
+}
+
+template append_spaces {
+    alias("_arg0") input;
+    alias("_arg1") min_length;
+
+    value(input) result;
+    backtrack_point() point;
+    num_lesser(result.length, min_length) more;
+    If (more) {
+        result->append(" ");
+        point->go();
+    };
+}
diff --git a/external/badvpn_dns/ncd/examples/router/ncd.conf b/external/badvpn_dns/ncd/examples/router/ncd.conf
new file mode 100644
index 0000000..56f6a73
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/ncd.conf
@@ -0,0 +1,6 @@
+include "/etc/ncd-network/network.ncdi"
+
+process main {
+    process_manager() mgr;
+    mgr->start("network_main", {});
+}
diff --git a/external/badvpn_dns/ncd/examples/router/network.ncdi b/external/badvpn_dns/ncd/examples/router/network.ncdi
new file mode 100644
index 0000000..f2a02cd
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/network.ncdi
@@ -0,0 +1,356 @@
+include_guard "network"
+
+include "pppoe.ncdi"
+include "dhcp_server.ncdi"
+include "unbound.ncdi"
+include "network_control_server.ncdi"
+include "port_forwarding.ncdi"
+
+template network_main {
+    log("notice", "NCD starting");
+    log_r("notice", "NCD stopped");
+
+    # Load ipv6 module so we can disable ipv6.
+    runonce({"/sbin/modprobe", "ipv6"});
+
+    # Set some sysctl's.
+    runonce({"/sbin/sysctl", "net.ipv4.ip_forward=1"});
+    runonce({"/sbin/sysctl", "net.ipv6.conf.all.disable_ipv6=1"});
+
+    # Setup iptables INPUT chain.
+    net.iptables.policy("filter", "INPUT", "ACCEPT", "ACCEPT");
+    net.iptables.append("filter", "INPUT", "-i", "lo", "-j", "ACCEPT");
+    net.iptables.append("filter", "INPUT", "-m", "conntrack", "--ctstate", "ESTABLISHED", "-j", "ACCEPT");
+
+    # Setup iptables OUTPUT chain.
+    net.iptables.policy("filter", "OUTPUT", "ACCEPT", "ACCEPT");
+
+    # Setup iptables FORWARD chain.
+    net.iptables.policy("filter", "FORWARD", "DROP", "ACCEPT");
+    net.iptables.append("filter", "FORWARD", "-m", "conntrack", "--ctstate", "ESTABLISHED", "-j", "ACCEPT");
+    net.iptables.append("filter", "FORWARD", "-m", "connmark", "--mark", "0x1/0x1", "-j", "ACCEPT");
+
+    # Create dependency scope.
+    depend_scope() depsc;
+
+    # Start processes.
+    process_manager() mgr;
+    mgr->start("network_lan", {});
+    mgr->start("network_serverif", {});
+    mgr->start("network_internet", {});
+    mgr->start("network_lan_internet_rules", {});
+    mgr->start("network_serverif_internet_rules", {});
+    mgr->start("network_lan_serverif_rules", {});
+    mgr->start("network_lan_dhcp_server", {});
+    mgr->start("network_unbound", {});
+    mgr->start("network_port_forwarding", {});
+    mgr->start("network_start_control_server", {});
+}
+
+template network_weak_hostmodel_rules {
+    alias("_arg0") dev;
+    alias("_arg1") addr;
+
+    concat("INPUT_hostmodel_drop_", dev) drop_chain;
+
+    net.iptables.newchain("filter", drop_chain);
+    net.iptables.append("filter", drop_chain, "-j", "DROP");
+    net.iptables.append("filter", "INPUT", "-d", addr, "!", "-i", dev, "-j", drop_chain);
+}
+
+template network_weak_hostmodel_exception {
+    alias("_arg0") dev;
+    alias("_arg1") match;
+
+    concat("INPUT_hostmodel_drop_", dev) drop_chain;
+
+    listfrom({"filter", drop_chain}, match, {"-j", "RETURN"}) args;
+    net.iptables.insert(args);
+}
+
+template network_lan {
+    alias("_caller") main;
+
+    # Some configuration.
+    var("enp1s0") dev;
+    var("192.168.111.1") addr;
+    var("24") prefix;
+    var("192.168.111.100") dhcp_start;
+    var("192.168.111.149") dhcp_end;
+
+    main.depsc->provide("lan_config");
+
+    # Wait for device, set up, wait for link.
+    net.backend.waitdevice(dev);
+    net.up(dev);
+    net.backend.waitlink(dev);
+
+    # Weak host model.
+    call("network_weak_hostmodel_rules", {dev, addr});
+
+    # Assign IP address.
+    net.ipv4.addr(dev, addr, prefix);
+
+    # Do SNAT for port forwardings when connections originate from the inside.
+    net.iptables.append("nat", "POSTROUTING", "-m", "connmark", "--mark", "0x2/0x2", "-j", "SNAT", "--to-source", addr, "--random");
+
+    main.depsc->provide("lan");
+}
+
+template network_serverif {
+    alias("_caller") main;
+
+    # Some configuration.
+    var("enp3s0") dev;
+    var("192.168.113.1") addr;
+    var("24") prefix;
+
+    main.depsc->provide("serverif_config");
+
+    # Wait for device, set up, wait for link.
+    net.backend.waitdevice(dev);
+    net.up(dev);
+    net.backend.waitlink(dev);
+
+    # Weak host model.
+    call("network_weak_hostmodel_rules", {dev, addr});
+
+    # Assign IP address.
+    net.ipv4.addr(dev, addr, prefix);
+
+    # Do SNAT for port forwardings when connections originate from the inside.
+    net.iptables.append("nat", "POSTROUTING", "-m", "connmark", "--mark", "0x4/0x4", "-j", "SNAT", "--to-source", addr, "--random");
+
+    main.depsc->provide("serverif");
+}
+
+template network_internet {
+    alias("_caller") main;
+
+    # Some configuration.
+    var("enp2s0") pppoe_dev;
+    var("MISSING") pppoe_username;
+    var("MISSING") pppoe_password;
+
+    # Wait for device, set up, wait for link.
+    net.backend.waitdevice(pppoe_dev);
+    net.up(pppoe_dev);
+    net.backend.waitlink(pppoe_dev);
+
+    log("notice", "PPPoE started");
+    log_r("notice", "PPPoE stopped");
+
+    # Start PPPoE.
+    call("pppoe", {pppoe_dev, pppoe_username, pppoe_password, "network_internet_pppoe_preup"}) pppoe;
+
+    # Grab configuration.
+    var(pppoe.ifname) dev;
+    var(pppoe.local_ip) addr;
+    var(pppoe.remote_ip) remote_addr;
+    var(pppoe.dns_servers) dns_servers;
+
+    to_string(dns_servers) dns_str;
+    log("notice", "PPPoE up dev=", dev, " local=", addr, " remote=", remote_addr, " dns=", dns_str);
+    log_r("notice", "PPPoE down");
+
+    # Add default route.
+    net.ipv4.route("0.0.0.0/0", remote_addr, "20", dev);
+
+    main.depsc->provide("internet");
+}
+
+template network_internet_pppoe_preup {
+    alias("_arg0") dev;
+    alias("_arg1") addr;
+    alias("_arg2") remote_ip;
+    alias("_arg3") dns_servers;
+
+    # Weak host model.
+    call("network_weak_hostmodel_rules", {dev, addr});
+
+    # Drop packets to this system, except some things.
+    net.iptables.newchain("filter", "INPUT_internet_drop");
+    #net.iptables.append("filter", "INPUT_internet_drop", "-p", "tcp", "--dport", "22", "-j", "RETURN");
+    net.iptables.append("filter", "INPUT_internet_drop", "-j", "DROP");
+    net.iptables.append("filter", "INPUT", "-i", dev, "-j", "INPUT_internet_drop");
+
+    # Do SNAT for packets going out.
+    net.iptables.append("nat", "POSTROUTING", "-o", dev, "-j", "SNAT", "--to-source", addr, "--random");
+
+    # Do MMS clamping.
+    net.iptables.append("mangle", "OUTPUT", "-o", dev, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu");
+    net.iptables.append("mangle", "FORWARD", "-o", dev, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu");
+}
+
+template network_lan_internet_rules {
+    alias("_caller") main;
+    main.depsc->depend({"lan"}) lan;
+    main.depsc->depend({"internet"}) internet;
+
+    # Add exception to weak host model of internet interface.
+    call("network_weak_hostmodel_exception", {internet.dev, {"-i", lan.dev}});
+    net.iptables.append("filter", "FORWARD", "-m", "conntrack", "--ctstate", "NEW", "-i", lan.dev, "-o", internet.dev, "-j", "ACCEPT");
+}
+
+template network_serverif_internet_rules {
+    alias("_caller") main;
+    main.depsc->depend({"serverif"}) serverif;
+    main.depsc->depend({"internet"}) internet;
+
+    # Allow traffic from LAN to Internet.
+    call("network_weak_hostmodel_exception", {internet.dev, {"-i", serverif.dev}});
+    net.iptables.append("filter", "FORWARD", "-m", "conntrack", "--ctstate", "NEW", "-i", serverif.dev, "-o", internet.dev, "-j", "ACCEPT");
+}
+
+template network_lan_serverif_rules {
+    alias("_caller") main;
+    main.depsc->depend({"lan"}) lan;
+    main.depsc->depend({"serverif"}) serverif;
+
+    # Allow traffic from serverif to LAN.
+    call("network_weak_hostmodel_exception", {serverif.dev, {"-i", lan.dev}});
+    net.iptables.append("filter", "FORWARD", "-m", "conntrack", "--ctstate", "NEW", "-i", lan.dev, "-o", serverif.dev, "-j", "ACCEPT");
+
+    # Allow traffic from LAN to serverif.
+    call("network_weak_hostmodel_exception", {lan.dev, {"-i", serverif.dev}});
+    net.iptables.append("filter", "FORWARD", "-m", "conntrack", "--ctstate", "NEW", "-i", serverif.dev, "-o", lan.dev, "-j", "ACCEPT");
+}
+
+template network_lan_dhcp_server {
+    alias("_caller") main;
+    main.depsc->depend({"lan"}) lan;
+
+    # Start DHCP server.
+    call("dhcp_server", {lan.addr, lan.prefix, lan.dhcp_start, lan.dhcp_end, {lan.addr}, {lan.addr}});
+}
+
+template network_unbound {
+    alias("_caller") main;
+    main.depsc->depend({"lan_config"}) lan_config;
+    main.depsc->depend({"serverif_config"}) serverif_config;
+
+    # Add DNS servers.
+    net.dns({"127.0.0.1"}, "20");
+
+    # Build configuration.
+    ipv4_net_from_addr_and_prefix(lan_config.addr, lan_config.prefix) lan_network;
+    ipv4_net_from_addr_and_prefix(serverif_config.addr, serverif_config.prefix) serverif_network;
+    var({
+        {lan_network, lan_config.prefix, "allow"},
+        {serverif_network, serverif_config.prefix, "allow"}
+    }) access_control_rules;
+
+    # Start Unbound.
+    call("unbound", {"lan", access_control_rules});
+}
+
+template network_port_forwarding {
+    alias("_caller") main;
+
+    # Start forwarding.
+    call("port_forwarding", {"/var/lib/ncd-port-forwardings.ncdvalue", "network_port_forwarding_rules"}) pf;
+
+    main.depsc->provide("port_forwarding");
+}
+
+template network_port_forwarding_rules {
+    alias("_caller.main") main;
+    alias("_arg0") protocol;
+    alias("_arg1") port_start;
+    alias("_arg2") port_end;
+    alias("_arg3") dest_addr;
+
+    # Get access to lan and serverif configuration.
+    main.depsc->depend({"lan_config"}) lan;
+    main.depsc->depend({"serverif_config"}) serverif;
+
+    # Wait for Internet interface.
+    main.depsc->depend({"internet"}) internet;
+
+    # Build port range string.
+    concat(port_start, ":", port_end) port_range;
+
+    # Add rules.
+    net.iptables.append("nat", "PREROUTING", "-d", internet.addr, "-p", protocol, "--dport", port_range, "-i", lan.dev, "-j", "CONNMARK", "--set-xmark", "0x3/0x3");
+    net.iptables.append("nat", "PREROUTING", "-d", internet.addr, "-p", protocol, "--dport", port_range, "-i", serverif.dev, "-j", "CONNMARK", "--set-xmark", "0x5/0x5");
+    net.iptables.append("nat", "PREROUTING", "-d", internet.addr, "-p", protocol, "--dport", port_range, "-i", internet.dev, "-j", "CONNMARK", "--set-xmark", "0x1/0x1");
+    net.iptables.append("nat", "PREROUTING", "-d", internet.addr, "-p", protocol, "--dport", port_range, "-j", "DNAT", "--to-destination", dest_addr);
+}
+
+template network_start_control_server {
+    alias("_caller") main;
+    main.depsc->depend({"lan_config"}) lan_config;
+
+    # Start control server.
+    call("network_control_server", {"/run/ncd-control.socket",
+        "network_control_list_port_forwardings",
+        "network_control_add_port_forwarding",
+        "network_control_remove_port_forwarding"});
+}
+
+template network_control_list_port_forwardings {
+    alias("_caller.main") main;
+
+    main.depsc->depend({"port_forwarding"}) port_forwarding;
+    var(port_forwarding.pf.map.keys) port_forwardings;
+}
+
+template network_control_add_port_forwarding {
+    alias("_caller.main") main;
+    alias("_arg0") protocol;
+    alias("_arg1") port_start;
+    alias("_arg2") port_end;
+    alias("_arg3") dest_addr;
+
+    var("") try_error_text;
+    try("network_verify_port_forwarding_try", {}) verify_try;
+
+    If (verify_try.succeeded) {
+        main.depsc->depend({"port_forwarding"}) port_forwarding;
+
+        call("port_forwarding_add", {"_caller.port_forwarding.pf", protocol, port_start, port_end, dest_addr}) call;
+        alias("call.succeeded") succeeded;
+        alias("call.error_text") error_text;
+    } Else {
+        var("false") succeeded;
+        alias("try_error_text") error_text;
+    } branch;
+
+    alias("branch.succeeded") succeeded;
+    alias("branch.error_text") error_text;
+}
+
+template network_control_remove_port_forwarding {
+    alias("_caller.main") main;
+    alias("_arg0") protocol;
+    alias("_arg1") port_start;
+    alias("_arg2") port_end;
+    alias("_arg3") dest_addr;
+
+    main.depsc->depend({"port_forwarding"}) port_forwarding;
+
+    call("port_forwarding_remove", {"_caller.port_forwarding.pf", protocol, port_start, port_end, dest_addr}) call;
+    alias("call.succeeded") succeeded;
+    alias("call.error_text") error_text;
+}
+
+template network_verify_port_forwarding_try {
+    alias("_caller") c;
+
+    c.main.depsc->depend({"lan_config"}) lan;
+    c.main.depsc->depend({"serverif_config"}) serverif;
+
+    net.ipv4.addr_in_network(c.dest_addr, lan.addr, lan.prefix) in_lan;
+    net.ipv4.addr_in_network(c.dest_addr, serverif.addr, serverif.prefix) in_serverif;
+
+    If (in_lan) {
+        print();
+    }
+    Elif (in_serverif) {
+        print();
+    }
+    Else {
+        c.try_error_text->set("Destination address does not belong to any permitted network.");
+        _try->assert("false");
+    };
+}
diff --git a/external/badvpn_dns/ncd/examples/router/network_control_server.ncdi b/external/badvpn_dns/ncd/examples/router/network_control_server.ncdi
new file mode 100644
index 0000000..e94f05e
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/network_control_server.ncdi
@@ -0,0 +1,96 @@
+include_guard "network_control_server"
+
+template network_control_server {
+    alias("_arg0") socket_path;
+    alias("_arg1") template_list_port_forwardings;
+    alias("_arg2") template_add_port_forwarding;
+    alias("_arg3") template_remove_port_forwarding;
+
+    # Start request server.
+    sys.request_server({"unix", socket_path}, "network_control_server__request_handler", {});
+}
+
+template network_control_server__request_handler {
+    alias("_caller") server;
+
+    value(_request.data) data;
+
+    try("network_control_server__try", {});
+
+    _request->reply({"error", "Bad request."});
+    _request->finish();
+}
+
+template network_control_server__try {
+    alias("_caller._request") request;
+    alias("_caller.server") server;
+    alias("_caller.data") data;
+
+    val_equal(data.type, "list") a1;
+    _try->assert(a1);
+
+    num_greater_equal(data.length, "1") a2;
+    _try->assert(a2);
+
+    data->get("0") request_cmd;
+
+    val_equal(request_cmd, "list-port-forwardings") is_list_port_forwardings;
+    val_equal(request_cmd, "add-port-forwarding") is_add_port_forwarding;
+    val_equal(request_cmd, "remove-port-forwarding") is_remove_port_forwarding;
+    or(is_add_port_forwarding, is_remove_port_forwarding) is_add_remove_port_forwarding;
+
+    If (is_list_port_forwardings) {
+        num_equal(data.length, "1") a3;
+        _try->assert(a3);
+
+        call_with_caller_target(server.template_list_port_forwardings, {}, "server._caller") call;
+        request->reply({"ok", call.port_forwardings});
+    }
+    Elif (is_add_remove_port_forwarding) {
+        num_equal(data.length, "5") a4;
+        _try->assert(a4);
+
+        data->get("1") req_protocol;
+        data->get("2") req_port_start;
+        data->get("3") req_port_end;
+        data->get("4") req_dest_addr;
+
+        val_equal(req_protocol, "tcp") is_tcp;
+        val_equal(req_protocol, "udp") is_udp;
+        or(is_tcp, is_udp) a5;
+        _try->assert(a5);
+
+        parse_number(req_port_start) port_start;
+        _try->assert(port_start.succeeded);
+
+        parse_number(req_port_end) port_end;
+        _try->assert(port_end.succeeded);
+
+        num_lesser_equal(port_start, port_end) a6;
+        _try->assert(a6);
+
+        parse_ipv4_addr(req_dest_addr) dest_addr;
+        _try->assert(dest_addr.succeeded);
+
+        If (is_add_port_forwarding) {
+            call_with_caller_target(server.template_add_port_forwarding, {req_protocol, port_start, port_end, dest_addr}, "server._caller") call;
+            If (call.succeeded) {
+                request->reply({"ok", "Port forwarding added."});
+            } Else {
+                request->reply({"error", call.error_text});
+            };
+        } Else {
+            call_with_caller_target(server.template_remove_port_forwarding, {req_protocol, port_start, port_end, dest_addr}, "server._caller") call;
+            If (call.succeeded) {
+                request->reply({"ok", "Port forwarding removed."});
+            } Else {
+                request->reply({"error", call.error_text});
+            };
+        };
+    }
+    Else {
+        _try->assert("false");
+    };
+
+    request->finish();
+}
diff --git a/external/badvpn_dns/ncd/examples/router/port_forwarding.ncdi b/external/badvpn_dns/ncd/examples/router/port_forwarding.ncdi
new file mode 100644
index 0000000..1ac727b
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/port_forwarding.ncdi
@@ -0,0 +1,170 @@
+include_guard "port_forwarding"
+
+template port_forwarding {
+    alias("_arg0") forwardings_file;
+    alias("_arg1") template_forward;
+
+    # Map which holds the set of current port forwardings.
+    # Enties are: {protocol, port_start, port_end, dest_addr}:""
+    value([]) map;
+
+    # Blocker which is initially down and is toggled down-up
+    # whenever the forwarding change.
+    blocker() update_blocker;
+
+    # Process manager, each forwarding has a port_forwarding__instance process.
+    # The process identifiers are the same as the keys in the map.
+    process_manager() mgr;
+
+    # Spawn a process for dealing with storage of port forwardings on disk.
+    spawn("port_forwarding__stored", {});
+}
+
+template port_forwarding__instance {
+    alias("_caller") pf;
+    alias("_arg0") protocol;
+    alias("_arg1") port_start;
+    alias("_arg2") port_end;
+    alias("_arg3") dest_addr;
+
+    log("notice", "adding port forwarding ", protocol, ":", port_start, ":", port_end, " to ", dest_addr);
+    log_r("notice", "removed port forwarding ", protocol, ":", port_start, ":", port_end, " to ", dest_addr);
+
+    # Do the forwarding.
+    call_with_caller_target(pf.template_forward, {protocol, port_start, port_end, dest_addr}, "pf._caller");
+}
+
+template port_forwarding_add {
+    alias(_arg0) pf;
+    alias("_arg1") protocol;
+    alias("_arg2") port_start;
+    alias("_arg3") port_end;
+    alias("_arg4") dest_addr;
+
+    var("false") succeeded;
+    var("") error_text;
+    var("true") not_finished;
+    backtrack_point() finished_point;
+
+    If (not_finished) {
+        # Check for conflicts with existing forwardings.
+        Foreach (pf.map.keys As entry) {
+            value(entry) entry;
+            entry->get("0") e_protocol;
+            entry->get("1") e_port_start;
+            entry->get("2") e_port_end;
+
+            val_different(protocol, e_protocol) different_protocol;
+            num_lesser(port_end, e_port_start) before;
+            num_greater(port_start, e_port_end) after;
+            or(different_protocol, before, after) no_conflict;
+            not(no_conflict) conflict;
+
+            If (conflict) {
+                error_text->set("Port forwarding conflicts with an existing forwarding.");
+                not_finished->set("false");
+                finished_point->go();
+            };
+        };
+
+        # Build entry key.
+        var({protocol, port_start, port_end, dest_addr}) key;
+
+        # Insert to map and toggle blocker.
+        pf.map->insert(key, "");
+        pf.update_blocker->downup();
+
+        # Start process.
+        pf.mgr->start(key, "port_forwarding__instance", {protocol, port_start, port_end, dest_addr});
+
+        succeeded->set("true");
+        not_finished->set("false");
+        finished_point->go();
+    };
+}
+
+template port_forwarding_remove {
+    alias(_arg0) pf;
+    alias("_arg1") protocol;
+    alias("_arg2") port_start;
+    alias("_arg3") port_end;
+    alias("_arg4") dest_addr;
+
+    var("false") succeeded;
+    var("") error_text;
+    var("true") not_finished;
+    backtrack_point() finished_point;
+
+    If (not_finished) {
+        # Build entry key.
+        var({protocol, port_start, port_end, dest_addr}) key;
+
+        # Check if the forwarding exists.
+        pf.map->try_get(key) entry;
+        not(entry.exists) does_not_exist;
+        If (does_not_exist) {
+            error_text->set("Port forwarding does not exist.");
+            not_finished->set("false");
+            finished_point->go();
+        };
+
+        # Stop process.
+        pf.mgr->stop(key);
+
+        # Remove from map and toggle blocker.
+        pf.map->remove(key);
+        pf.update_blocker->downup();
+
+        succeeded->set("true");
+        not_finished->set("false");
+        finished_point->go();
+    };
+}
+
+template port_forwarding__stored {
+    alias("_caller") pf;
+
+    # Create file if it doesn't exist.
+    file_stat(pf.forwardings_file) stat;
+    If (stat.succeeded) { print(); } Else {
+        file_write(pf.forwardings_file, "{}\n");
+    };
+
+    # Read port forwardings from file.
+    file_read(pf.forwardings_file) data;
+    from_string(data) forwardings;
+
+    # Add them.
+    Foreach (forwardings As fwd) {
+        value(fwd) fwd;
+        fwd->get("0") protocol;
+        fwd->get("1") port_start;
+        fwd->get("2") port_end;
+        fwd->get("3") dest_addr;
+        call("port_forwarding_add", {"_caller.pf", protocol, port_start, port_end, dest_addr});
+    };
+
+    # Write forwardings to file on exit.
+    imperative("<none>", {}, "port_forwarding__write", {}, "6000");
+
+    # Also write forwardings whenever they are changed.
+    pf.update_blocker->use();
+    call("port_forwarding__write", {});
+}
+
+template port_forwarding__write {
+    alias("_caller.pf") pf;
+
+    # Convert forwardings to string.
+    to_string(pf.map.keys) data;
+    concat(data, "\n") data;
+
+    # Build name of temporary file.
+    concat(pf.forwardings_file, ".new") temp_file;
+
+    # Write temporary file.
+    file_write(temp_file, data);
+
+    # Move to live file.
+    runonce({"/bin/mv", temp_file, pf.forwardings_file});
+}
diff --git a/external/badvpn_dns/ncd/examples/router/pppoe.ncdi b/external/badvpn_dns/ncd/examples/router/pppoe.ncdi
new file mode 100644
index 0000000..676a8fe
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/pppoe.ncdi
@@ -0,0 +1,296 @@
+include_guard "pppoe"
+
+template pppoe {
+    alias("_arg0") dev;
+    alias("_arg1") username;
+    alias("_arg2") password;
+    alias("_arg3") pre_up_template;
+
+    # Choose which NCD interpreter will be used for the pppd event scripts.
+    var("/usr/local/badvpn/bin/badvpn-ncd") ncd_interpreter_path;
+
+    # Retry point here.
+    var("false") retrying;
+    backtrack_point() retry_point;
+    If (retrying) {
+        sleep("5000");
+    };
+    retrying->set("true");
+
+    # Create a temporary directory.
+    concat("/run/ncd-pppoe-", dev) run_dir;
+    run({"/bin/rm", "-rf", run_dir}, {});
+    run({"/bin/mkdir", run_dir}, {"/bin/rm", "-rf", run_dir});
+
+    # Build paths for pppd scripts and other files.
+    concat(run_dir, "/ncd-request.socket") socket_path;
+    concat(run_dir, "/pppoe.pid") pppoe_pid_path;
+    concat(run_dir, "/pap-secrets") pap_secrets_path;
+    concat(run_dir, "/chap-secrets") chap_secrets_path;
+    concat(run_dir, "/pppd2.tdb") pppdb_path;
+    concat(run_dir, "/resolv.conf") resolv_conf_path;
+    concat(run_dir, "/script-auth-up") path_auth_up;
+    concat(run_dir, "/script-auth-down") path_auth_down;
+    concat(run_dir, "/script-auth-fail") path_auth_fail;
+    concat(run_dir, "/script-ip-pre-up") path_ip_pre_up;
+    concat(run_dir, "/script-ip-up") path_ip_up;
+    concat(run_dir, "/script-ip-down") path_ip_down;
+    concat(run_dir, "/script-ipv6-up") path_ipv6_up;
+    concat(run_dir, "/script-ipv6-down") path_ipv6_down;
+    concat(run_dir, "/script-ipx-up") path_ipx_up;
+    concat(run_dir, "/script-ipx-down") path_ipx_down;
+
+    # Write secrets files.
+    call("pppoe__write_secrets", {pap_secrets_path, username, password});
+    call("pppoe__write_secrets", {chap_secrets_path, username, password});
+
+    # Write pppd scripts. These will contact us via the request socket.
+    call("pppoe__write_script", {"ip-pre-up", path_ip_pre_up});
+    call("pppoe__write_script", {"ip-up", path_ip_up});
+    call("pppoe__write_script", {"ip-down", path_ip_down});
+
+    # Build path arguments for pppd;
+    concat("pid-dir=", run_dir) arg_pid_dir;
+    concat("pap-secrets=", pap_secrets_path) arg_pap_secrets;
+    concat("chap-secrets=", chap_secrets_path) arg_chap_secrets;
+    concat("pppdb=", pppdb_path) arg_pppdb;
+    concat("resolv.conf=", resolv_conf_path) arg_resolv_conf;
+    concat("auth-up=", path_auth_up) arg_auth_up;
+    concat("auth-down=", path_auth_down) arg_auth_down;
+    concat("auth-fail=", path_auth_fail) arg_auth_fail;
+    concat("ip-pre-up=", path_ip_pre_up) arg_ip_pre_up;
+    concat("ip-up=", path_ip_up) arg_ip_up;
+    concat("ip-down=", path_ip_down) arg_ip_down;
+    concat("ipv6-up=", path_ipv6_up) arg_ipv6_up;
+    concat("ipv6-down=", path_ipv6_down) arg_ipv6_down;
+    concat("ipx-up=", path_ipx_up) arg_ipx_up;
+    concat("ipx-down=", path_ipx_down) arg_ipx_down;
+
+    # Create state variables and blockers. When the request server
+    # receives requests it will update those variables and blockers.
+    var("down") state;
+    var("") current_ifname;
+    var("") current_local_ip;
+    var("") current_remote_ip;
+    value({}) current_dns_servers;
+    blocker() ip_pre_up_blocker;
+    blocker() ip_pre_up_done_blocker;
+    blocker() ip_up_blocker;
+
+    # Start request server.
+    sys.request_server({"unix", socket_path}, "pppoe__request_handler", {});
+
+    # Start pppd.
+    sys.start_process({
+        "/usr/sbin/pppd", "nodetach", "plugin", "rp-pppoe.so", dev, "noipdefault", "hide-password",
+        "usepeerdns", "user", username,
+        "path", arg_pid_dir, "path", arg_pap_secrets, "path", arg_chap_secrets, "path", arg_pppdb,
+        "path", arg_resolv_conf,
+        "path", arg_auth_up, "path", arg_auth_down, "path", arg_auth_fail, "path", arg_ip_pre_up,
+        "path", arg_ip_up, "path", arg_ip_down, "path", arg_ipv6_up, "path", arg_ipv6_down,
+        "path", arg_ipx_up, "path", arg_ipx_down
+    }, "", ["deinit_kill_time":"2000"]) pppd;
+
+    # Start a process which will cause retrying when pppd dies.
+    spawn("pppoe__pppd_wait", {});
+
+    # Wait for ip-pre-up.
+    ip_pre_up_blocker->use();
+
+    # Grab the current state variables, so the user doesn't
+    # see any unexpected changes.
+    var(current_ifname) ifname;
+    var(current_local_ip) local_ip;
+    var(current_remote_ip) remote_ip;
+    var(current_dns_servers) dns_servers;
+
+    # Call pre-up callback template.
+    call_with_caller_target(pre_up_template, {ifname, local_ip, remote_ip, dns_servers}, "_caller");
+
+    # Allow pre-up script to terminate.
+    ip_pre_up_done_blocker->up();
+
+    # Wait for connection to go up.
+    ip_up_blocker->use();
+}
+
+template pppoe__pppd_wait {
+    # Wait for pppd to die.
+    _caller.pppd->wait();
+
+    # Retry.
+    _caller.retry_point->go();
+}
+
+template pppoe__write_secrets {
+    alias("_arg0") file_path;
+    alias("_arg1") username;
+    alias("_arg2") password;
+
+    # Escape username and password.
+    regex_replace(username, {"\""}, {"\\\""}) username_esc;
+    regex_replace(password, {"\""}, {"\\\""}) password_esc;
+
+    # Write empty file and chmod it.
+    file_write(file_path, "");
+    run({"/bin/chmod", "600", file_path}, {});
+
+    # Build contents.
+    concat("\"", username_esc, "\" * \"", password_esc, "\"\n") contents;
+
+    # Write file.
+    file_write(file_path, contents);
+}
+
+template pppoe__write_script {
+    alias("_arg0") event;
+    alias("_arg1") script_path;
+
+    # This string is an NCD script which will be run by pppd.
+    # When run, it will contact us via the request server,
+    # and the requests will be processed in pppoe__request_handler.
+    var("#!<NCD_INTERPRETER_PATH>
+
+process main {
+    # Hardcoded strings.
+    var(\"<EVENT>\") hardcoded_event;
+    var(\"<SOCKET>\") hardcoded_socket;
+
+    # Start timeout to kill us after some time if we don't manage
+    # to contact the server.
+    spawn(\"timeout_process\", {});
+
+    # Build event data map.
+    getargs() args;
+    value([\"EVENT\":hardcoded_event, \"ARGS\":args]) msg_map;
+    var({\"DEVICE\", \"IFNAME\", \"IPLOCAL\", \"IPREMOTE\", \"PEERNAME\", \"LOCALNAME\",
+         \"SPEED\", \"ORIG_UID\", \"PPPLOGNAME\", \"CONNECT_TIME\", \"BYTES_SENT\",
+         \"BYTES_RCVD\", \"LINKNAME\", \"DNS1\", \"DNS2\", \"WINS1\", \"WINS2\"}) var_names;
+    Foreach (var_names As var_name) {
+        getenv(var_name) env;
+        If (env.exists) {
+            msg_map->insert(var_name, env);
+        };
+    };
+
+    # Connect to socket.
+    sys.request_client({\"unix\", hardcoded_socket}) client;
+
+    # Send request.
+    client->request(msg_map, \"reply_handler\", \"finished_handler\", {});
+}
+
+template reply_handler {
+    print();
+}
+
+template finished_handler {
+    # Request was received by server, exit now.
+    exit(\"0\");
+}
+
+template timeout_process {
+    # Sleep some time.
+    sleep(\"5000\");
+    # Timed out, exit now.
+    exit(\"1\");
+}
+
+"   ) script_contents_template;
+
+    # Replace some constants in the script with the right values.
+    regex_replace(script_contents_template,
+                  {"<NCD_INTERPRETER_PATH>", "<EVENT>", "<SOCKET>"},
+                  {_caller.ncd_interpreter_path, event, _caller.socket_path}
+    ) script_contents;
+
+    # Write the script.
+    file_write(script_path, script_contents);
+
+    # Make it executable.
+    run({"/bin/chmod", "+x", script_path}, {});
+}
+
+template pppoe__request_handler {
+    alias("_caller") pppoe;
+
+    # Get event type from request.
+    value(_request.data) request;
+    request->get("EVENT") event;
+
+    # Match to known types.
+    val_equal(event, "ip-down") is_ip_down;
+    val_equal(event, "ip-pre-up") is_ip_pre_up;
+    val_equal(event, "ip-up") is_ip_up;
+
+    If (is_ip_down) {
+        # Set state.
+        pppoe.state->set("down");
+
+        # Set blockers down.
+        pppoe.ip_up_blocker->down();
+        pppoe.ip_pre_up_done_blocker->down();
+        pppoe.ip_pre_up_blocker->down();
+    }
+    Elif (is_ip_pre_up) {
+        # Expecting to be in "down" state here.
+        val_different(pppoe.state, "down") state_is_wrong;
+        If (state_is_wrong) {
+            pppoe.retry_point->go();
+            _request->finish();
+        };
+
+        # Get variables from request.
+        request->get("IFNAME") ifname;
+        request->get("IPLOCAL") local_ip;
+        request->get("IPREMOTE") remote_ip;
+        request->try_get("DNS1") dns1;
+        request->try_get("DNS2") dns2;
+
+        # Write variables.
+        pppoe.current_ifname->set(ifname);
+        pppoe.current_local_ip->set(local_ip);
+        pppoe.current_remote_ip->set(remote_ip);
+        pppoe.current_dns_servers->reset({});
+        If (dns1.exists) {
+            pppoe.current_dns_servers->insert(pppoe.current_dns_servers.length, dns1);
+        };
+        If (dns2.exists) {
+            pppoe.current_dns_servers->insert(pppoe.current_dns_servers.length, dns2);
+        };
+
+        # Set state.
+        pppoe.state->set("pre-up");
+
+        # Set ip-pre-up blocker up.
+        pppoe.ip_pre_up_blocker->up();
+
+        # Wait for pre-up to be finished. This causes the script contacting
+        # us to not return until then, and effectively delays pppd in setting
+        # the device up and calling the ip-up script.
+        pppoe.ip_pre_up_done_blocker->use();
+    }
+    Elif(is_ip_up) {
+        # Expecting to be in "pre-up" state here.
+        val_different(pppoe.state, "pre-up") state_is_wrong;
+        If (state_is_wrong) {
+            pppoe.retry_point->go();
+            _request->finish();
+        };
+
+        # Set state.
+        pppoe.state->set("up");
+
+        # Set ip-up blocker up.
+        pppoe.ip_up_blocker->up();
+    };
+
+    # Finish request.
+    _request->finish();
+}
+
+template pppoe__escapeshellarg {
+    alias("_arg0") input;
+    regex_replace(input, {"'"}, {"\\'"}) replaced;
+    concat("'", replaced, "'") result;
+}
diff --git a/external/badvpn_dns/ncd/examples/router/remove-port-forwarding b/external/badvpn_dns/ncd/examples/router/remove-port-forwarding
new file mode 100644
index 0000000..6019404
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/remove-port-forwarding
@@ -0,0 +1,43 @@
+#!/usr/bin/badvpn-ncd
+
+process main {
+    getargs() args;
+    value(args) args;
+
+    num_different(args.length, "4") bad_args;
+    If (bad_args) {
+        println("Usage: remove-port-forwarding <protocol> <port_start> <port_end> <dest_addr>");
+        exit("1");
+    };
+
+    args->get("0") protocol;
+    args->get("1") port_start;
+    args->get("2") port_end;
+    args->get("3") dest_addr;
+
+    var("0") exit_status;
+
+    sys.request_client({"unix", "/run/ncd-control.socket"}) client;
+
+    var({"remove-port-forwarding", protocol, port_start, port_end, dest_addr}) request_data;
+
+    client->request(request_data, "reply_handler", "finished_handler", {});
+}
+
+template reply_handler {
+    value(_reply.data) reply_data;
+    reply_data->get("0") status;
+    reply_data->get("1") text;
+
+    val_equal(status, "ok") is_ok;
+    If (is_ok) {
+        println(text);
+    } Else {
+        _caller.exit_status->set("1");
+        println("Error: ", text);
+    };
+}
+
+template finished_handler {
+    exit(_caller.exit_status);
+}
diff --git a/external/badvpn_dns/ncd/examples/router/unbound.ncdi b/external/badvpn_dns/ncd/examples/router/unbound.ncdi
new file mode 100644
index 0000000..9ea0d41
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/router/unbound.ncdi
@@ -0,0 +1,42 @@
+include_guard "unbound"
+
+template unbound {
+    alias("_arg0") unique_id;
+    alias("_arg1") access_control_rules;
+
+    # Create a temporary directory.
+    concat("/run/ncd-unbound-", unique_id) run_dir;
+    run({"/bin/rm", "-rf", run_dir}, {});
+    run({"/bin/mkdir", run_dir}, {"/bin/rm", "-rf", run_dir});
+
+    # Compute path for unbound.conf.
+    concat(run_dir, "/unbound.conf") unbound_conf_path;
+
+    # This is a template for unbound.conf.
+    value("
+server:
+    verbosity: 1
+    do-ip4: yes
+    do-ip6: no
+    do-udp: yes
+    do-tcp: no
+    interface: 0.0.0.0
+    access-control: 127.0.0.0/8 allow
+"   ) config;
+
+    # Append access control rules.
+    Foreach (access_control_rules As rule) {
+        value(rule) rule;
+        rule->get("0") network;
+        rule->get("1") prefix;
+        rule->get("2") action;
+        concat("    access-control: ", network, "/", prefix, " ", action, "\n") line;
+        config->append(line);
+    };
+
+    # Write unbound.conf.
+    file_write(unbound_conf_path, config);
+
+    # Start unbound.
+    daemon({"/usr/sbin/unbound", "-d", "-c", unbound_conf_path});
+}
diff --git a/external/badvpn_dns/ncd/examples/tcp_echo_client.ncd b/external/badvpn_dns/ncd/examples/tcp_echo_client.ncd
new file mode 100644
index 0000000..47dd999
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/tcp_echo_client.ncd
@@ -0,0 +1,35 @@
+process main {
+    getargs() args;
+    value(args) args;
+
+    num_different(args.length, "2") bad_args;
+    If (bad_args) {
+        println("bad arguments");
+        exit("1");
+    };
+
+    args->get("0") addr_ip;
+    args->get("1") addr_port;
+
+    sys.connect({"tcp", {"ipv4", addr_ip, addr_port}}) socket;
+    If (socket.is_error) {
+        println("connection error!");
+        exit("1");
+    };
+
+    println("connected");
+
+    socket->write("This echo client is implemented in NCD!\n\n");
+
+    backtrack_point() recv_point;
+
+    socket->read() data;
+    If (data.not_eof) {
+        socket->write(data);
+        recv_point->go();
+    };
+
+    println("server disconnected");
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/examples/tcp_echo_server.ncd b/external/badvpn_dns/ncd/examples/tcp_echo_server.ncd
new file mode 100644
index 0000000..a3c6267
--- /dev/null
+++ b/external/badvpn_dns/ncd/examples/tcp_echo_server.ncd
@@ -0,0 +1,40 @@
+process main {
+    getargs() args;
+    value(args) args;
+
+    num_different(args.length, "2") bad_args;
+    If (bad_args) {
+        println("bad arguments");
+        exit("1");
+    };
+
+    args->get("0") addr_ip;
+    args->get("1") addr_port;
+
+    sys.listen({"tcp", {"ipv4", addr_ip, addr_port}}, "client_handler", {}) listener;
+    If (listener.is_error) {
+        println("failed to listen");
+        exit("1");
+    };
+
+    println("listening");
+}
+
+template client_handler {
+    to_string(_socket.client_addr) addr_str;
+
+    println("client ", addr_str, ": connected");
+    rprintln("client ", addr_str, ": disconnected");
+
+    _socket->write("This echo server is implemented in NCD!\n\n");
+
+    backtrack_point() recv_point;
+
+    _socket->read() data;
+    If (data.not_eof) {
+        _socket->write(data);
+        recv_point->go();
+    };
+
+    _socket->close();
+}
diff --git a/external/badvpn_dns/ncd/extra/BEventLock.c b/external/badvpn_dns/ncd/extra/BEventLock.c
new file mode 100644
index 0000000..e65bd20
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/BEventLock.c
@@ -0,0 +1,146 @@
+/**
+ * @file BEventLock.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/offset.h>
+
+#include "BEventLock.h"
+
+static void exec_job_handler (BEventLock *o)
+{
+    ASSERT(!LinkedList1_IsEmpty(&o->jobs))
+    DebugObject_Access(&o->d_obj);
+    
+    // get job
+    BEventLockJob *j = UPPER_OBJECT(LinkedList1_GetFirst(&o->jobs), BEventLockJob, pending_node);
+    ASSERT(j->pending)
+    
+    // call handler
+    j->handler(j->user);
+    return;
+}
+
+void BEventLock_Init (BEventLock *o, BPendingGroup *pg)
+{
+    // init jobs list
+    LinkedList1_Init(&o->jobs);
+    
+    // init exec job
+    BPending_Init(&o->exec_job, pg, (BPending_handler)exec_job_handler, o);
+    
+    DebugObject_Init(&o->d_obj);
+    DebugCounter_Init(&o->pending_ctr);
+}
+
+void BEventLock_Free (BEventLock *o)
+{
+    ASSERT(LinkedList1_IsEmpty(&o->jobs))
+    DebugCounter_Free(&o->pending_ctr);
+    DebugObject_Free(&o->d_obj);
+    
+    // free exec jobs
+    BPending_Free(&o->exec_job);
+}
+
+void BEventLockJob_Init (BEventLockJob *o, BEventLock *l, BEventLock_handler handler, void *user)
+{
+    // init arguments
+    o->l = l;
+    o->handler = handler;
+    o->user = user;
+    
+    // set not pending
+    o->pending = 0;
+    
+    DebugObject_Init(&o->d_obj);
+    DebugCounter_Increment(&l->pending_ctr);
+}
+
+void BEventLockJob_Free (BEventLockJob *o)
+{
+    BEventLock *l = o->l;
+    
+    DebugCounter_Decrement(&l->pending_ctr);
+    DebugObject_Free(&o->d_obj);
+    
+    if (o->pending) {
+        int was_first = (&o->pending_node == LinkedList1_GetFirst(&l->jobs));
+        
+        // remove from jobs list
+        LinkedList1_Remove(&l->jobs, &o->pending_node);
+        
+        // schedule/unschedule job
+        if (was_first) {
+            if (LinkedList1_IsEmpty(&l->jobs)) {
+                BPending_Unset(&l->exec_job);
+            } else {
+                BPending_Set(&l->exec_job);
+            }
+        }
+    }
+}
+
+void BEventLockJob_Wait (BEventLockJob *o)
+{
+    BEventLock *l = o->l;
+    ASSERT(!o->pending)
+    
+    // append to jobs
+    LinkedList1_Append(&l->jobs, &o->pending_node);
+    
+    // set pending
+    o->pending = 1;
+    
+    // schedule next job if needed
+    if (&o->pending_node == LinkedList1_GetFirst(&l->jobs)) {
+        BPending_Set(&l->exec_job);
+    }
+}
+
+void BEventLockJob_Release (BEventLockJob *o)
+{
+    BEventLock *l = o->l;
+    ASSERT(o->pending)
+    
+    int was_first = (&o->pending_node == LinkedList1_GetFirst(&l->jobs));
+    
+    // remove from jobs list
+    LinkedList1_Remove(&l->jobs, &o->pending_node);
+    
+    // set not pending
+    o->pending = 0;
+    
+    // schedule/unschedule job
+    if (was_first) {
+        if (LinkedList1_IsEmpty(&l->jobs)) {
+            BPending_Unset(&l->exec_job);
+        } else {
+            BPending_Set(&l->exec_job);
+        }
+    }
+}
diff --git a/external/badvpn_dns/ncd/extra/BEventLock.h b/external/badvpn_dns/ncd/extra/BEventLock.h
new file mode 100644
index 0000000..2664318
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/BEventLock.h
@@ -0,0 +1,127 @@
+/**
+ * @file BEventLock.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 FIFO lock for events using the job queue ({@link BPending}).
+ */
+
+#ifndef BADVPN_BEVENTLOCK_H
+#define BADVPN_BEVENTLOCK_H
+
+#include <misc/debugcounter.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+
+/**
+ * Event context handler called when the lock job has acquired the lock
+ * after requesting the lock with {@link BEventLockJob_Wait}.
+ * The object was in waiting state.
+ * The object enters locked state before the handler is called.
+ * 
+ * @param user as in {@link BEventLockJob_Init}
+ */
+typedef void (*BEventLock_handler) (void *user);
+
+/**
+ * A FIFO lock for events using the job queue ({@link BPending}).
+ */
+typedef struct {
+    LinkedList1 jobs;
+    BPending exec_job;
+    DebugObject d_obj;
+    DebugCounter pending_ctr;
+} BEventLock;
+
+/**
+ * An object that can request a {@link BEventLock} lock.
+ */
+typedef struct {
+    BEventLock *l;
+    BEventLock_handler handler;
+    void *user;
+    int pending;
+    LinkedList1Node pending_node;
+    DebugObject d_obj;
+} BEventLockJob;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param pg pending group
+ */
+void BEventLock_Init (BEventLock *o, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ * There must be no {@link BEventLockJob} objects using this lock
+ * (regardless of their state).
+ * 
+ * @param o the object
+ */
+void BEventLock_Free (BEventLock *o);
+
+/**
+ * Initializes the object.
+ * The object is initialized in idle state.
+ * 
+ * @param o the object
+ * @param l the lock
+ * @param handler handler to call when the lock is aquired
+ * @param user value to pass to handler
+ */
+void BEventLockJob_Init (BEventLockJob *o, BEventLock *l, BEventLock_handler handler, void *user);
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void BEventLockJob_Free (BEventLockJob *o);
+
+/**
+ * Requests the lock.
+ * The object must be in idle state.
+ * The object enters waiting state.
+ * 
+ * @param o the object
+ */
+void BEventLockJob_Wait (BEventLockJob *o);
+
+/**
+ * Aborts the lock request or releases the lock.
+ * The object must be in waiting or locked state.
+ * The object enters idle state.
+ * 
+ * @param o the object
+ */
+void BEventLockJob_Release (BEventLockJob *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/extra/NCDBProcessOpts.c b/external/badvpn_dns/ncd/extra/NCDBProcessOpts.c
new file mode 100644
index 0000000..2af4beb
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDBProcessOpts.c
@@ -0,0 +1,154 @@
+/**
+ * @file NCDBProcessOpts.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/balloc.h>
+
+#include <ncd/extra/value_utils.h>
+
+#include "NCDBProcessOpts.h"
+
+int NCDBProcessOpts_Init (NCDBProcessOpts *o, NCDValRef opts_arg, NCDBProcessOpts_func_unknown func_unknown, void *func_unknown_user, NCDModuleInst *i, int blog_channel)
+{
+    return NCDBProcessOpts_Init2(o, opts_arg, func_unknown, func_unknown_user, i, blog_channel, NULL, NULL);
+}
+
+int NCDBProcessOpts_Init2 (NCDBProcessOpts *o, NCDValRef opts_arg, NCDBProcessOpts_func_unknown func_unknown, void *func_unknown_user, NCDModuleInst *i, int blog_channel,
+                           int *out_keep_stdout, int *out_keep_stderr)
+{
+    if (!NCDVal_IsInvalid(opts_arg) && !NCDVal_IsMap(opts_arg)) {
+        NCDModuleInst_Backend_Log(i, blog_channel, BLOG_ERROR, "options must be a map");
+        goto fail0;
+    }
+    
+    o->username = NULL;
+    o->do_setsid = 0;
+    
+    int keep_stdout = 0;
+    int keep_stderr = 0;
+    
+    if (!NCDVal_IsInvalid(opts_arg)) {
+        for (NCDValMapElem me = NCDVal_MapFirst(opts_arg); !NCDVal_MapElemInvalid(me); me = NCDVal_MapNext(opts_arg, me)) {
+            NCDValRef key = NCDVal_MapElemKey(opts_arg, me);
+            NCDValRef val = NCDVal_MapElemVal(opts_arg, me);
+            
+            if (NCDVal_IsString(key) && NCDVal_StringEquals(key, "keep_stdout")) {
+                keep_stdout = ncd_read_boolean(val);
+            }
+            else if (NCDVal_IsString(key) && NCDVal_StringEquals(key, "keep_stderr")) {
+                keep_stderr = ncd_read_boolean(val);
+            }
+            else if (NCDVal_IsString(key) && NCDVal_StringEquals(key, "do_setsid")) {
+                o->do_setsid = ncd_read_boolean(val);
+            }
+            else if (NCDVal_IsString(key) && NCDVal_StringEquals(key, "username")) {
+                if (!NCDVal_IsStringNoNulls(val)) {
+                    NCDModuleInst_Backend_Log(i, blog_channel, BLOG_ERROR, "username must be a string without nulls");
+                    goto fail1;
+                }
+                b_cstring cstr = NCDVal_StringCstring(val);
+                o->username = b_cstring_strdup(cstr, 0, cstr.length);
+                if (!o->username) {
+                    NCDModuleInst_Backend_Log(i, blog_channel, BLOG_ERROR, "b_cstring_strdup failed");
+                    goto fail1;
+                }
+            }
+            else {
+                if (!func_unknown || !func_unknown(func_unknown_user, key, val)) {
+                    NCDModuleInst_Backend_Log(i, blog_channel, BLOG_ERROR, "unknown option");
+                    goto fail1;
+                }
+            }
+        }
+    }
+    
+    o->nfds = 0;
+    if (keep_stdout) {
+        o->fds[o->nfds] = 1;
+        o->fds_map[o->nfds++] = 1;
+    }
+    if (keep_stderr) {
+        o->fds[o->nfds] = 2;
+        o->fds_map[o->nfds++] = 2;
+    }
+    o->fds[o->nfds] = -1;
+    
+    if (out_keep_stdout) {
+        *out_keep_stdout = keep_stdout;
+    }
+    if (out_keep_stderr) {
+        *out_keep_stderr = keep_stderr;
+    }
+    
+    return 1;
+    
+fail1:
+    if (o->username) {
+        BFree(o->username);
+    }
+fail0:
+    return 0;
+}
+
+void NCDBProcessOpts_InitOld (NCDBProcessOpts *o, int keep_stdout, int keep_stderr, int do_setsid)
+{
+    o->username = NULL;
+    o->do_setsid = do_setsid;
+    
+    o->nfds = 0;
+    if (keep_stdout) {
+        o->fds[o->nfds] = 1;
+        o->fds_map[o->nfds++] = 1;
+    }
+    if (keep_stderr) {
+        o->fds[o->nfds] = 2;
+        o->fds_map[o->nfds++] = 2;
+    }
+    o->fds[o->nfds] = -1;
+}
+
+void NCDBProcessOpts_Free (NCDBProcessOpts *o)
+{
+    if (o->username) {
+        BFree(o->username);
+    }
+}
+
+struct BProcess_params NCDBProcessOpts_GetParams (NCDBProcessOpts *o)
+{
+    struct BProcess_params params;
+    
+    params.username = o->username;
+    params.fds = o->fds;
+    params.fds_map = o->fds_map;
+    params.do_setsid = o->do_setsid;
+    
+    return params;
+}
diff --git a/external/badvpn_dns/ncd/extra/NCDBProcessOpts.h b/external/badvpn_dns/ncd/extra/NCDBProcessOpts.h
new file mode 100644
index 0000000..9a9a79c
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDBProcessOpts.h
@@ -0,0 +1,54 @@
+/**
+ * @file NCDBProcessOpts.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 NCD_BPROCESS_OPTS_H
+#define NCD_BPROCESS_OPTS_H
+
+#include <misc/debug.h>
+#include <ncd/NCDModule.h>
+#include <system/BProcess.h>
+
+typedef struct {
+    char *username;
+    int do_setsid;
+    int fds[3];
+    int fds_map[2];
+    int nfds;
+} NCDBProcessOpts;
+
+typedef int (*NCDBProcessOpts_func_unknown) (void *user, NCDValRef key, NCDValRef val);
+
+int NCDBProcessOpts_Init (NCDBProcessOpts *o, NCDValRef opts_arg, NCDBProcessOpts_func_unknown func_unknown, void *func_unknown_user, NCDModuleInst *i, int blog_channel) WARN_UNUSED;
+int NCDBProcessOpts_Init2 (NCDBProcessOpts *o, NCDValRef opts_arg, NCDBProcessOpts_func_unknown func_unknown, void *func_unknown_user, NCDModuleInst *i, int blog_channel,
+                           int *out_keep_stdout, int *out_keep_stderr) WARN_UNUSED;
+void NCDBProcessOpts_InitOld (NCDBProcessOpts *o, int keep_stdout, int keep_stderr, int do_setsid);
+void NCDBProcessOpts_Free (NCDBProcessOpts *o);
+struct BProcess_params NCDBProcessOpts_GetParams (NCDBProcessOpts *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/extra/NCDBuf.c b/external/badvpn_dns/ncd/extra/NCDBuf.c
new file mode 100644
index 0000000..6262652
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDBuf.c
@@ -0,0 +1,123 @@
+/**
+ * @file NCDBuf.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "NCDBuf.h"
+
+#include <misc/balloc.h>
+#include <misc/offset.h>
+#include <misc/debug.h>
+
+static void ref_target_func_release (BRefTarget *ref_target)
+{
+    NCDBuf *o = UPPER_OBJECT(ref_target, NCDBuf, ref_target);
+    NCDBufStore *store = o->store;
+    
+    if (store) {
+        LinkedList0_Remove(&store->used_bufs_list, &o->list_node);
+        LinkedList0_Prepend(&store->free_bufs_list, &o->list_node);
+    } else {
+        BFree(o);
+    }
+}
+
+void NCDBufStore_Init (NCDBufStore *o, size_t buf_size)
+{
+    o->buf_size = buf_size;
+    LinkedList0_Init(&o->used_bufs_list);
+    LinkedList0_Init(&o->free_bufs_list);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void NCDBufStore_Free (NCDBufStore *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    LinkedList0Node *ln;
+    
+    ln = LinkedList0_GetFirst(&o->used_bufs_list);
+    while (ln) {
+        NCDBuf *buf = UPPER_OBJECT(ln, NCDBuf, list_node);
+        ASSERT(buf->store == o)
+        buf->store = NULL;
+        ln = LinkedList0Node_Next(ln);
+    }
+    
+    ln = LinkedList0_GetFirst(&o->free_bufs_list);
+    while (ln) {
+        LinkedList0Node *next_ln = LinkedList0Node_Next(ln);
+        NCDBuf *buf = UPPER_OBJECT(ln, NCDBuf, list_node);
+        ASSERT(buf->store == o)
+        BFree(buf);
+        ln = next_ln;
+    }
+}
+
+size_t NCDBufStore_BufSize (NCDBufStore *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return o->buf_size;
+}
+
+NCDBuf * NCDBufStore_GetBuf (NCDBufStore *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    NCDBuf *buf;
+    
+    LinkedList0Node *ln = LinkedList0_GetFirst(&o->free_bufs_list);
+    if (ln) {
+        buf = UPPER_OBJECT(ln, NCDBuf, list_node);
+        ASSERT(buf->store == o)
+        LinkedList0_Remove(&o->free_bufs_list, &buf->list_node);
+    } else {
+        bsize_t size = bsize_add(bsize_fromsize(sizeof(NCDBuf)), bsize_fromsize(o->buf_size));
+        buf = BAllocSize(size);
+        if (!buf) {
+            return NULL;
+        }
+        buf->store = o;
+    }
+    
+    LinkedList0_Prepend(&o->used_bufs_list, &buf->list_node);
+    BRefTarget_Init(&buf->ref_target, ref_target_func_release);
+    
+    return buf;
+}
+
+BRefTarget * NCDBuf_RefTarget (NCDBuf *o)
+{
+    return &o->ref_target;
+}
+
+char * NCDBuf_Data (NCDBuf *o)
+{
+    return o->data;
+}
diff --git a/external/badvpn_dns/ncd/extra/NCDBuf.h b/external/badvpn_dns/ncd/extra/NCDBuf.h
new file mode 100644
index 0000000..8542dcd
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDBuf.h
@@ -0,0 +1,61 @@
+/**
+ * @file NCDBuf.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 NCD_NCDBUF_H
+#define NCD_NCDBUF_H
+
+#include <stddef.h>
+
+#include <misc/BRefTarget.h>
+#include <structure/LinkedList0.h>
+#include <base/DebugObject.h>
+
+typedef struct {
+    size_t buf_size;
+    LinkedList0 used_bufs_list;
+    LinkedList0 free_bufs_list;
+    DebugObject d_obj;
+} NCDBufStore;
+
+typedef struct {
+    NCDBufStore *store;
+    LinkedList0Node list_node;
+    BRefTarget ref_target;
+    char data[];
+} NCDBuf;
+
+void NCDBufStore_Init (NCDBufStore *o, size_t buf_size);
+void NCDBufStore_Free (NCDBufStore *o);
+size_t NCDBufStore_BufSize (NCDBufStore *o);
+NCDBuf * NCDBufStore_GetBuf (NCDBufStore *o);
+
+BRefTarget * NCDBuf_RefTarget (NCDBuf *o);
+char * NCDBuf_Data (NCDBuf *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/extra/NCDIfConfig.c b/external/badvpn_dns/ncd/extra/NCDIfConfig.c
new file mode 100644
index 0000000..2d89d5d
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDIfConfig.c
@@ -0,0 +1,483 @@
+/**
+ * @file NCDIfConfig.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/if_tun.h>
+#include <pwd.h>
+  
+#include <misc/debug.h>
+#include <base/BLog.h>
+
+#include "NCDIfConfig.h"
+
+#include <generated/blog_channel_NCDIfConfig.h>
+
+#define IP_CMD "ip"
+#define MODPROBE_CMD "modprobe"
+#define RESOLVCONF_FILE "/etc/resolv.conf"
+#define RESOLVCONF_TEMP_FILE "/etc/resolv.conf-ncd-temp"
+#define TUN_DEVNODE "/dev/net/tun"
+
+static int run_command (const char *cmd)
+{
+    BLog(BLOG_INFO, "run: %s", cmd);
+    
+    return system(cmd);
+}
+
+static int write_to_file (uint8_t *data, size_t data_len, FILE *f)
+{
+    while (data_len > 0) {
+        size_t bytes = fwrite(data, 1, data_len, f);
+        if (bytes == 0) {
+            return 0;
+        }
+        data += bytes;
+        data_len -= bytes;
+    }
+    
+    return 1;
+}
+
+int NCDIfConfig_query (const char *ifname)
+{
+    struct ifreq ifr;
+    
+    int flags = 0;
+    
+    int s = socket(AF_INET, SOCK_DGRAM, 0);
+    if (!s) {
+        BLog(BLOG_ERROR, "socket failed");
+        goto fail0;
+    }
+    
+    memset(&ifr, 0, sizeof(ifr));
+    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
+    if (ioctl(s, SIOCGIFFLAGS, &ifr)) {
+        BLog(BLOG_ERROR, "SIOCGIFFLAGS failed");
+        goto fail1;
+    }
+    
+    flags |= NCDIFCONFIG_FLAG_EXISTS;
+    
+    if ((ifr.ifr_flags&IFF_UP)) {
+        flags |= NCDIFCONFIG_FLAG_UP;
+        
+        if ((ifr.ifr_flags&IFF_RUNNING)) {
+            flags |= NCDIFCONFIG_FLAG_RUNNING;
+        }
+    }
+    
+fail1:
+    close(s);
+fail0:
+    return flags;
+}
+
+int NCDIfConfig_set_up (const char *ifname)
+{
+    if (strlen(ifname) >= IFNAMSIZ) {
+        BLog(BLOG_ERROR, "ifname too long");
+        return 0;
+    }
+    
+    char cmd[50 + IFNAMSIZ];
+    sprintf(cmd, IP_CMD" link set %s up", ifname);
+    
+    return !run_command(cmd);
+}
+
+int NCDIfConfig_set_down (const char *ifname)
+{
+    if (strlen(ifname) >= IFNAMSIZ) {
+        BLog(BLOG_ERROR, "ifname too long");
+        return 0;
+    }
+    
+    char cmd[50 + IFNAMSIZ];
+    sprintf(cmd, IP_CMD" link set %s down", ifname);
+    
+    return !run_command(cmd);
+}
+
+int NCDIfConfig_add_ipv4_addr (const char *ifname, struct ipv4_ifaddr ifaddr)
+{
+    ASSERT(ifaddr.prefix >= 0)
+    ASSERT(ifaddr.prefix <= 32)
+    
+    if (strlen(ifname) >= IFNAMSIZ) {
+        BLog(BLOG_ERROR, "ifname too long");
+        return 0;
+    }
+    
+    uint8_t *addr = (uint8_t *)&ifaddr.addr;
+    
+    char cmd[50 + IFNAMSIZ];
+    sprintf(cmd, IP_CMD" addr add %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"/%d dev %s", addr[0], addr[1], addr[2], addr[3], ifaddr.prefix, ifname);
+    
+    return !run_command(cmd);
+}
+
+int NCDIfConfig_remove_ipv4_addr (const char *ifname, struct ipv4_ifaddr ifaddr)
+{
+    ASSERT(ifaddr.prefix >= 0)
+    ASSERT(ifaddr.prefix <= 32)
+    
+    if (strlen(ifname) >= IFNAMSIZ) {
+        BLog(BLOG_ERROR, "ifname too long");
+        return 0;
+    }
+    
+    uint8_t *addr = (uint8_t *)&ifaddr.addr;
+    
+    char cmd[50 + IFNAMSIZ];
+    sprintf(cmd, IP_CMD" addr del %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"/%d dev %s", addr[0], addr[1], addr[2], addr[3], ifaddr.prefix, ifname);
+    
+    return !run_command(cmd);
+}
+
+int NCDIfConfig_add_ipv6_addr (const char *ifname, struct ipv6_ifaddr ifaddr)
+{
+    ASSERT(ifaddr.prefix >= 0)
+    ASSERT(ifaddr.prefix <= 128)
+    
+    if (strlen(ifname) >= IFNAMSIZ) {
+        BLog(BLOG_ERROR, "ifname too long");
+        return 0;
+    }
+    
+    char addr_str[IPADDR6_PRINT_MAX];
+    ipaddr6_print_addr(ifaddr.addr, addr_str);
+    
+    char cmd[40 + IPADDR6_PRINT_MAX + IFNAMSIZ];
+    sprintf(cmd, IP_CMD" addr add %s/%d dev %s", addr_str, ifaddr.prefix, ifname);
+    
+    return !run_command(cmd);
+}
+
+int NCDIfConfig_remove_ipv6_addr (const char *ifname, struct ipv6_ifaddr ifaddr)
+{
+    ASSERT(ifaddr.prefix >= 0)
+    ASSERT(ifaddr.prefix <= 128)
+    
+    if (strlen(ifname) >= IFNAMSIZ) {
+        BLog(BLOG_ERROR, "ifname too long");
+        return 0;
+    }
+    
+    char addr_str[IPADDR6_PRINT_MAX];
+    ipaddr6_print_addr(ifaddr.addr, addr_str);
+    
+    char cmd[40 + IPADDR6_PRINT_MAX + IFNAMSIZ];
+    sprintf(cmd, IP_CMD" addr del %s/%d dev %s", addr_str, ifaddr.prefix, ifname);
+    
+    return !run_command(cmd);
+}
+
+static int route_cmd (const char *cmdtype, struct ipv4_ifaddr dest, const uint32_t *gateway, int metric, const char *ifname)
+{
+    ASSERT(!strcmp(cmdtype, "add") || !strcmp(cmdtype, "del"))
+    ASSERT(dest.prefix >= 0)
+    ASSERT(dest.prefix <= 32)
+    
+    if (strlen(ifname) >= IFNAMSIZ) {
+        BLog(BLOG_ERROR, "ifname too long");
+        return 0;
+    }
+    
+    uint8_t *d_addr = (uint8_t *)&dest.addr;
+    
+    char gwstr[30];
+    if (gateway) {
+        const uint8_t *g_addr = (uint8_t *)gateway;
+        sprintf(gwstr, " via %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, g_addr[0], g_addr[1], g_addr[2], g_addr[3]);
+    } else {
+        gwstr[0] = '\0';
+    }
+    
+    char cmd[120 + IFNAMSIZ];
+    sprintf(cmd, IP_CMD" route %s %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"/%d%s metric %d dev %s",
+            cmdtype, d_addr[0], d_addr[1], d_addr[2], d_addr[3], dest.prefix, gwstr, metric, ifname);
+    
+    return !run_command(cmd);
+}
+
+int NCDIfConfig_add_ipv4_route (struct ipv4_ifaddr dest, const uint32_t *gateway, int metric, const char *device)
+{
+    return route_cmd("add", dest, gateway, metric, device);
+}
+
+int NCDIfConfig_remove_ipv4_route (struct ipv4_ifaddr dest, const uint32_t *gateway, int metric, const char *device)
+{
+    return route_cmd("del", dest, gateway, metric, device);
+}
+
+static int route_cmd6 (const char *cmdtype, struct ipv6_ifaddr dest, const struct ipv6_addr *gateway, int metric, const char *ifname)
+{
+    ASSERT(!strcmp(cmdtype, "add") || !strcmp(cmdtype, "del"))
+    ASSERT(dest.prefix >= 0)
+    ASSERT(dest.prefix <= 128)
+    
+    if (strlen(ifname) >= IFNAMSIZ) {
+        BLog(BLOG_ERROR, "ifname too long");
+        return 0;
+    }
+    
+    char dest_str[IPADDR6_PRINT_MAX];
+    ipaddr6_print_addr(dest.addr, dest_str);
+    
+    char gwstr[10 + IPADDR6_PRINT_MAX];
+    if (gateway) {
+        strcpy(gwstr, " via ");
+        ipaddr6_print_addr(*gateway, gwstr + strlen(gwstr));
+    } else {
+        gwstr[0] = '\0';
+    }
+    
+    char cmd[70 + IPADDR6_PRINT_MAX + IPADDR6_PRINT_MAX + IFNAMSIZ];
+    sprintf(cmd, IP_CMD" route %s %s/%d%s metric %d dev %s",
+            cmdtype, dest_str, dest.prefix, gwstr, metric, ifname);
+    
+    return !run_command(cmd);
+}
+
+int NCDIfConfig_add_ipv6_route (struct ipv6_ifaddr dest, const struct ipv6_addr *gateway, int metric, const char *device)
+{
+    return route_cmd6("add", dest, gateway, metric, device);
+}
+
+int NCDIfConfig_remove_ipv6_route (struct ipv6_ifaddr dest, const struct ipv6_addr *gateway, int metric, const char *device)
+{
+    return route_cmd6("del", dest, gateway, metric, device);
+}
+
+static int blackhole_route_cmd (const char *cmdtype, struct ipv4_ifaddr dest, int metric)
+{
+    ASSERT(!strcmp(cmdtype, "add") || !strcmp(cmdtype, "del"))
+    ASSERT(dest.prefix >= 0)
+    ASSERT(dest.prefix <= 32)
+    
+    uint8_t *d_addr = (uint8_t *)&dest.addr;
+    
+    char cmd[120];
+    sprintf(cmd, IP_CMD" route %s blackhole %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"/%d metric %d",
+            cmdtype, d_addr[0], d_addr[1], d_addr[2], d_addr[3], dest.prefix, metric);
+    
+    return !run_command(cmd);
+}
+
+int NCDIfConfig_add_ipv4_blackhole_route (struct ipv4_ifaddr dest, int metric)
+{
+    return blackhole_route_cmd("add", dest, metric);
+}
+
+int NCDIfConfig_remove_ipv4_blackhole_route (struct ipv4_ifaddr dest, int metric)
+{
+    return blackhole_route_cmd("del", dest, metric);
+}
+
+static int blackhole_route_cmd6 (const char *cmdtype, struct ipv6_ifaddr dest, int metric)
+{
+    ASSERT(!strcmp(cmdtype, "add") || !strcmp(cmdtype, "del"))
+    ASSERT(dest.prefix >= 0)
+    ASSERT(dest.prefix <= 128)
+    
+    char dest_str[IPADDR6_PRINT_MAX];
+    ipaddr6_print_addr(dest.addr, dest_str);
+    
+    char cmd[70 + IPADDR6_PRINT_MAX];
+    sprintf(cmd, IP_CMD" route %s blackhole %s/%d metric %d",
+            cmdtype, dest_str, dest.prefix, metric);
+    
+    return !run_command(cmd);
+}
+
+int NCDIfConfig_add_ipv6_blackhole_route (struct ipv6_ifaddr dest, int metric)
+{
+    return blackhole_route_cmd6("add", dest, metric);
+}
+
+int NCDIfConfig_remove_ipv6_blackhole_route (struct ipv6_ifaddr dest, int metric)
+{
+    return blackhole_route_cmd6("del", dest, metric);
+}
+
+int NCDIfConfig_set_resolv_conf (const char *data, size_t data_len)
+{
+    FILE *temp_file = fopen(RESOLVCONF_TEMP_FILE, "w");
+    if (!temp_file) {
+        BLog(BLOG_ERROR, "failed to open resolvconf temp file");
+        goto fail0;
+    }
+    
+    char line[] = "# generated by badvpn-ncd\n";
+    if (!write_to_file((uint8_t *)line, strlen(line), temp_file) ||
+        !write_to_file((uint8_t *)data, data_len, temp_file)
+    ) {
+        BLog(BLOG_ERROR, "failed to write to resolvconf temp file");
+        goto fail1;
+    }
+    
+    if (fclose(temp_file) != 0) {
+        BLog(BLOG_ERROR, "failed to close resolvconf temp file");
+        return 0;
+    }
+    
+    if (rename(RESOLVCONF_TEMP_FILE, RESOLVCONF_FILE) < 0) {
+        BLog(BLOG_ERROR, "failed to rename resolvconf temp file to resolvconf file");
+        return 0;
+    }
+    
+    return 1;
+    
+fail1:
+    fclose(temp_file);
+fail0:
+    return 0;
+}
+
+static int open_tuntap (const char *ifname, int flags)
+{
+    if (strlen(ifname) >= IFNAMSIZ) {
+        BLog(BLOG_ERROR, "ifname too long");
+        return -1;
+    }
+    
+    int fd = open(TUN_DEVNODE, O_RDWR);
+    if (fd < 0) {
+         BLog(BLOG_ERROR, "open tun failed");
+         return -1;
+    }
+    
+    struct ifreq ifr;
+    memset(&ifr, 0, sizeof(ifr));
+    ifr.ifr_flags = flags;
+    snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
+    
+    if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) {
+        BLog(BLOG_ERROR, "TUNSETIFF failed");
+        close(fd);
+        return -1;
+    }
+    
+    return fd;
+}
+
+int NCDIfConfig_make_tuntap (const char *ifname, const char *owner, int tun)
+{
+    // load tun module if needed
+    if (access(TUN_DEVNODE, F_OK) < 0) {
+        if (run_command(MODPROBE_CMD" tun") != 0) {
+            BLog(BLOG_ERROR, "modprobe tun failed");
+        }
+    }
+    
+    int fd;
+    if ((fd = open_tuntap(ifname, (tun ? IFF_TUN : IFF_TAP))) < 0) {
+        goto fail0;
+    }
+    
+    if (owner) {
+        long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+        if (bufsize < 0) {
+            bufsize = 16384;
+        }
+        
+        char *buf = malloc(bufsize);
+        if (!buf) {
+            BLog(BLOG_ERROR, "malloc failed");
+            goto fail1;
+        }
+        
+        struct passwd pwd;
+        struct passwd *res;
+        getpwnam_r(owner, &pwd, buf, bufsize, &res);
+        if (!res) {
+            BLog(BLOG_ERROR, "getpwnam_r failed");
+            free(buf);
+            goto fail1;
+        }
+        
+        int uid = pwd.pw_uid;
+        
+        free(buf);
+        
+        if (ioctl(fd, TUNSETOWNER, uid) < 0) {
+            BLog(BLOG_ERROR, "TUNSETOWNER failed");
+            goto fail1;
+        }
+    }
+    
+    if (ioctl(fd, TUNSETPERSIST, (void *)1) < 0) {
+        BLog(BLOG_ERROR, "TUNSETPERSIST failed");
+        goto fail1;
+    }
+    
+    close(fd);
+    
+    return 1;
+    
+fail1:
+    close(fd);
+fail0:
+    return 0;
+}
+
+int NCDIfConfig_remove_tuntap (const char *ifname, int tun)
+{
+    int fd;
+    if ((fd = open_tuntap(ifname, (tun ? IFF_TUN : IFF_TAP))) < 0) {
+        goto fail0;
+    }
+    
+    if (ioctl(fd, TUNSETPERSIST, (void *)0) < 0) {
+        BLog(BLOG_ERROR, "TUNSETPERSIST failed");
+        goto fail1;
+    }
+    
+    close(fd);
+    
+    return 1;
+    
+fail1:
+    close(fd);
+fail0:
+    return 0;
+}
diff --git a/external/badvpn_dns/ncd/extra/NCDIfConfig.h b/external/badvpn_dns/ncd/extra/NCDIfConfig.h
new file mode 100644
index 0000000..711979f
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDIfConfig.h
@@ -0,0 +1,70 @@
+/**
+ * @file NCDIfConfig.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCD_NCDIFCONFIG_H
+#define BADVPN_NCD_NCDIFCONFIG_H
+
+#include <stddef.h>
+
+#include <misc/ipaddr.h>
+#include <misc/ipaddr6.h>
+
+#define NCDIFCONFIG_FLAG_EXISTS (1 << 0)
+#define NCDIFCONFIG_FLAG_UP (1 << 1)
+#define NCDIFCONFIG_FLAG_RUNNING (1 << 2)
+
+int NCDIfConfig_query (const char *ifname);
+
+int NCDIfConfig_set_up (const char *ifname);
+int NCDIfConfig_set_down (const char *ifname);
+
+int NCDIfConfig_add_ipv4_addr (const char *ifname, struct ipv4_ifaddr ifaddr);
+int NCDIfConfig_remove_ipv4_addr (const char *ifname, struct ipv4_ifaddr ifaddr);
+
+int NCDIfConfig_add_ipv6_addr (const char *ifname, struct ipv6_ifaddr ifaddr);
+int NCDIfConfig_remove_ipv6_addr (const char *ifname, struct ipv6_ifaddr ifaddr);
+
+int NCDIfConfig_add_ipv4_route (struct ipv4_ifaddr dest, const uint32_t *gateway, int metric, const char *device);
+int NCDIfConfig_remove_ipv4_route (struct ipv4_ifaddr dest, const uint32_t *gateway, int metric, const char *device);
+
+int NCDIfConfig_add_ipv6_route (struct ipv6_ifaddr dest, const struct ipv6_addr *gateway, int metric, const char *device);
+int NCDIfConfig_remove_ipv6_route (struct ipv6_ifaddr dest, const struct ipv6_addr *gateway, int metric, const char *device);
+
+int NCDIfConfig_add_ipv4_blackhole_route (struct ipv4_ifaddr dest, int metric);
+int NCDIfConfig_remove_ipv4_blackhole_route (struct ipv4_ifaddr dest, int metric);
+
+int NCDIfConfig_add_ipv6_blackhole_route (struct ipv6_ifaddr dest, int metric);
+int NCDIfConfig_remove_ipv6_blackhole_route (struct ipv6_ifaddr dest, int metric);
+
+int NCDIfConfig_set_resolv_conf (const char *data, size_t data_len);
+
+int NCDIfConfig_make_tuntap (const char *ifname, const char *owner, int tun);
+int NCDIfConfig_remove_tuntap (const char *ifname, int tun);
+
+#endif
diff --git a/external/badvpn_dns/ncd/extra/NCDInterfaceMonitor.c b/external/badvpn_dns/ncd/extra/NCDInterfaceMonitor.c
new file mode 100644
index 0000000..8b457ef
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDInterfaceMonitor.c
@@ -0,0 +1,446 @@
+/**
+ * @file NCDInterfaceMonitor.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <unistd.h>
+#include <stdint.h>
+
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <asm/types.h>
+#include <asm/types.h>
+
+#include <misc/debug.h>
+#include <misc/nonblocking.h>
+#include <base/BLog.h>
+
+#include <ncd/extra/NCDInterfaceMonitor.h>
+
+#include <generated/blog_channel_NCDInterfaceMonitor.h>
+
+#define IFA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
+
+static int send_wilddump_request (NCDInterfaceMonitor *o, int fd, uint32_t seq, int family, int type);
+static int get_attr (int type, struct rtattr *rta, int rta_len, void **out_attr, int *out_attr_len);
+static void report_error (NCDInterfaceMonitor *o);
+static int send_next_dump_request (NCDInterfaceMonitor *o);
+static void netlink_fd_handler (NCDInterfaceMonitor *o, int events);
+static void process_buffer (NCDInterfaceMonitor *o);
+static void more_job_handler (NCDInterfaceMonitor *o);
+
+static int send_wilddump_request (NCDInterfaceMonitor *o, int fd, uint32_t seq, int family, int type)
+{
+    struct {
+        struct nlmsghdr nlh;
+        struct rtgenmsg g;
+    } req;
+    
+    memset(&req, 0, sizeof(req));
+    req.nlh.nlmsg_len = sizeof(req);
+    req.nlh.nlmsg_type = type;
+    req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
+    req.nlh.nlmsg_pid = 0;
+    req.nlh.nlmsg_seq = seq;
+    req.g.rtgen_family = family;
+    
+    int res = write(fd, &req, sizeof(req));
+    if (res < 0) {
+        BLog(BLOG_ERROR, "write failed");
+        return 0;
+    }
+    if (res != sizeof(req)) {
+        BLog(BLOG_ERROR, "write short");
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int get_attr (int type, struct rtattr *rta, int rta_len, void **out_attr, int *out_attr_len)
+{
+    for (; RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) {
+        uint8_t *attr = RTA_DATA(rta);
+        int attr_len = RTA_PAYLOAD(rta);
+        
+        if (rta->rta_type == type) {
+            *out_attr = attr;
+            *out_attr_len = attr_len;
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+static void report_error (NCDInterfaceMonitor *o)
+{
+    DEBUGERROR(&o->d_err, o->handler_error(o->user))
+}
+
+static int send_next_dump_request (NCDInterfaceMonitor *o)
+{
+    ASSERT(o->dump_queue)
+    
+    if (o->dump_queue & NCDIFMONITOR_WATCH_LINK) {
+        o->dump_queue &= ~NCDIFMONITOR_WATCH_LINK;
+        return send_wilddump_request(o, o->netlink_fd, o->dump_seq, 0, RTM_GETLINK);
+    }
+    else if (o->dump_queue & NCDIFMONITOR_WATCH_IPV4_ADDR) {
+        o->dump_queue &= ~NCDIFMONITOR_WATCH_IPV4_ADDR;
+        return send_wilddump_request(o, o->netlink_fd, o->dump_seq, AF_INET, RTM_GETADDR);
+    }
+    else if (o->dump_queue & NCDIFMONITOR_WATCH_IPV6_ADDR) {
+        o->dump_queue &= ~NCDIFMONITOR_WATCH_IPV6_ADDR;
+        return send_wilddump_request(o, o->netlink_fd, o->dump_seq, AF_INET6, RTM_GETADDR);
+    }
+    
+    ASSERT(0)
+    return 0;
+}
+
+void netlink_fd_handler (NCDInterfaceMonitor *o, int events)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_bfd)
+    
+    // handler fd error
+    if (o->buf_left >= 0) {
+        BLog(BLOG_ERROR, "file descriptor error");
+        goto fail;
+    }
+    
+    // read from netlink fd
+    int len = read(o->netlink_fd, o->buf.buf, sizeof(o->buf));
+    if (len < 0) {
+        BLog(BLOG_ERROR, "read failed");
+        goto fail;
+    }
+    
+    // stop receiving fd events
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, 0);
+    
+    // set buffer
+    o->buf_nh = &o->buf.nlh;
+    o->buf_left = len;
+    
+    // process buffer
+    process_buffer(o);
+    return;
+    
+fail:
+    report_error(o);
+}
+
+void process_buffer (NCDInterfaceMonitor *o)
+{
+    ASSERT(o->buf_left >= 0)
+    ASSERT(o->have_bfd)
+    
+    int done = 0;
+    
+    for (; NLMSG_OK(o->buf_nh, o->buf_left); o->buf_nh = NLMSG_NEXT(o->buf_nh, o->buf_left)) {
+        if (o->buf_nh->nlmsg_type == NLMSG_DONE) {
+            done = 1;
+            break;
+        }
+        
+        struct nlmsghdr *buf = o->buf_nh;
+        void *pl = NLMSG_DATA(buf);
+        int pl_len = NLMSG_PAYLOAD(buf, 0);
+        
+        struct NCDInterfaceMonitor_event ev;
+        
+        switch (buf->nlmsg_type) {
+            case RTM_NEWLINK: { // not RTM_DELLINK! who knows what these mean...
+                if (pl_len < sizeof(struct ifinfomsg)) {
+                    BLog(BLOG_ERROR, "ifinfomsg too short");
+                    goto fail;
+                }
+                struct ifinfomsg *msg = pl;
+                
+                if (msg->ifi_index == o->ifindex && (o->watch_events & NCDIFMONITOR_WATCH_LINK)) {
+                    ev.event = (buf->nlmsg_type == RTM_NEWLINK && (msg->ifi_flags & IFF_RUNNING)) ? NCDIFMONITOR_EVENT_LINK_UP : NCDIFMONITOR_EVENT_LINK_DOWN;
+                    goto dispatch;
+                }
+            } break;
+            
+            case RTM_NEWADDR:
+            case RTM_DELADDR: {
+                if (pl_len < sizeof(struct ifaddrmsg)) {
+                    BLog(BLOG_ERROR, "ifaddrmsg too short");
+                    goto fail;
+                }
+                struct ifaddrmsg *msg = pl;
+                
+                void *addr;
+                int addr_len;
+                if (!get_attr(IFA_ADDRESS, IFA_RTA(msg), buf->nlmsg_len - NLMSG_LENGTH(sizeof(*msg)), &addr, &addr_len)) {
+                    break;
+                }
+                
+                if (msg->ifa_index == o->ifindex && msg->ifa_family == AF_INET && (o->watch_events & NCDIFMONITOR_WATCH_IPV4_ADDR)) {
+                    if (addr_len != 4 || msg->ifa_prefixlen > 32) {
+                        BLog(BLOG_ERROR, "bad ipv4 ifaddrmsg");
+                        goto fail;
+                    }
+                    
+                    ev.event = (buf->nlmsg_type == RTM_NEWADDR) ? NCDIFMONITOR_EVENT_IPV4_ADDR_ADDED : NCDIFMONITOR_EVENT_IPV4_ADDR_REMOVED;
+                    ev.u.ipv4_addr.addr.addr = ((struct in_addr *)addr)->s_addr;
+                    ev.u.ipv4_addr.addr.prefix = msg->ifa_prefixlen;
+                    goto dispatch;
+                }
+                
+                if (msg->ifa_index == o->ifindex && msg->ifa_family == AF_INET6 && (o->watch_events & NCDIFMONITOR_WATCH_IPV6_ADDR)) {
+                    if (addr_len != 16 || msg->ifa_prefixlen > 128) {
+                        BLog(BLOG_ERROR, "bad ipv6 ifaddrmsg");
+                        goto fail;
+                    }
+                    
+                    ev.event = (buf->nlmsg_type == RTM_NEWADDR) ? NCDIFMONITOR_EVENT_IPV6_ADDR_ADDED : NCDIFMONITOR_EVENT_IPV6_ADDR_REMOVED;
+                    memcpy(ev.u.ipv6_addr.addr.addr.bytes, ((struct in6_addr *)addr)->s6_addr, 16);
+                    ev.u.ipv6_addr.addr.prefix = msg->ifa_prefixlen;
+                    ev.u.ipv6_addr.addr_flags = 0;
+                    ev.u.ipv6_addr.scope = msg->ifa_scope;
+                    if (!(msg->ifa_flags & IFA_F_PERMANENT)) {
+                        ev.u.ipv6_addr.addr_flags |= NCDIFMONITOR_ADDR_FLAG_DYNAMIC;
+                    }
+                    goto dispatch;
+                }
+            } break;
+        }
+        
+        continue;
+        
+    dispatch:
+        // move to next message
+        o->buf_nh = NLMSG_NEXT(o->buf_nh, o->buf_left);
+        
+        // schedule more job
+        BPending_Set(&o->more_job);
+        
+        // dispatch event
+        o->handler(o->user, ev);
+        return;
+    }
+    
+    if (done) {
+        if (o->dump_queue) {
+            // increment dump request sequence number
+            o->dump_seq++;
+            
+            // send next dump request
+            if (!send_next_dump_request(o)) {
+                goto fail;
+            }
+        }
+        else if (o->event_netlink_fd >= 0) {
+            // stop watching dump fd
+            BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+            o->have_bfd = 0;
+            
+            // close dump fd, make event fd current
+            close(o->netlink_fd);
+            o->netlink_fd = o->event_netlink_fd;
+            o->event_netlink_fd = -1;
+            
+            // start watching event fd
+            BFileDescriptor_Init(&o->bfd, o->netlink_fd, (BFileDescriptor_handler)netlink_fd_handler, o);
+            if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+                BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+                goto fail;
+            }
+            o->have_bfd = 1;
+        }
+    }
+    
+    // set no buffer
+    o->buf_left = -1;
+    
+    // continue receiving fd events
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, BREACTOR_READ);
+    return;
+    
+fail:
+    report_error(o);
+}
+
+void more_job_handler (NCDInterfaceMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->buf_left >= 0)
+    
+    // process buffer
+    process_buffer(o);
+    return;
+}
+
+int NCDInterfaceMonitor_Init (NCDInterfaceMonitor *o, int ifindex, int watch_events, BReactor *reactor, void *user,
+                              NCDInterfaceMonitor_handler handler,
+                              NCDInterfaceMonitor_handler_error handler_error)
+{
+    ASSERT(watch_events)
+    ASSERT((watch_events&~(NCDIFMONITOR_WATCH_LINK|NCDIFMONITOR_WATCH_IPV4_ADDR|NCDIFMONITOR_WATCH_IPV6_ADDR)) == 0)
+    ASSERT(handler)
+    ASSERT(handler_error)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->ifindex = ifindex;
+    o->watch_events = watch_events;
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    o->handler_error = handler_error;
+    
+    // init dump netlink fd
+    if ((o->netlink_fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) {
+        BLog(BLOG_ERROR, "socket failed");
+        goto fail0;
+    }
+    if (!badvpn_set_nonblocking(o->netlink_fd)) {
+        BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail1;
+    }
+    
+    // init event netlink fd
+    if ((o->event_netlink_fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) {
+        BLog(BLOG_ERROR, "socket failed");
+        goto fail1;
+    }
+    if (!badvpn_set_nonblocking(o->event_netlink_fd)) {
+        BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail2;
+    }
+    
+    // build bind address
+    struct sockaddr_nl sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.nl_family = AF_NETLINK;
+    sa.nl_groups = 0;
+    if (watch_events & NCDIFMONITOR_WATCH_LINK) sa.nl_groups |= RTMGRP_LINK;
+    if (watch_events & NCDIFMONITOR_WATCH_IPV4_ADDR) sa.nl_groups |= RTMGRP_IPV4_IFADDR;
+    if (watch_events & NCDIFMONITOR_WATCH_IPV6_ADDR) sa.nl_groups |= RTMGRP_IPV6_IFADDR;
+    
+    // bind event netlink fd
+    if (bind(o->event_netlink_fd, (void *)&sa, sizeof(sa)) < 0) {
+        BLog(BLOG_ERROR, "bind failed");
+        goto fail2;
+    }
+    
+    // set dump state
+    o->dump_queue = watch_events;
+    o->dump_seq = 0;
+    
+    // init BFileDescriptor
+    BFileDescriptor_Init(&o->bfd, o->netlink_fd, (BFileDescriptor_handler)netlink_fd_handler, o);
+    if (!BReactor_AddFileDescriptor(reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail2;
+    }
+    o->have_bfd = 1;
+    
+    // set nothing in buffer
+    o->buf_left = -1;
+    
+    // init more job
+    BPending_Init(&o->more_job, BReactor_PendingGroup(reactor), (BPending_handler)more_job_handler, o);
+    
+    // send first dump request
+    if (!send_next_dump_request(o)) {
+        goto fail3;
+    }
+    
+    // wait for reading fd
+    BReactor_SetFileDescriptorEvents(reactor, &o->bfd, BREACTOR_READ);
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail3:
+    BPending_Free(&o->more_job);
+    BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+fail2:
+    close(o->event_netlink_fd);
+fail1:
+    close(o->netlink_fd);
+fail0:
+    return 0;
+}
+
+void NCDInterfaceMonitor_Free (NCDInterfaceMonitor *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    
+    // free more job
+    BPending_Free(&o->more_job);
+    
+    // free BFileDescriptor
+    if (o->have_bfd) {
+        BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+    }
+    
+    // close event fd, in case we're still dumping
+    if (o->event_netlink_fd >= 0) {
+        close(o->event_netlink_fd);
+    }
+    
+    // close fd
+    close(o->netlink_fd);
+}
+
+void NCDInterfaceMonitor_Pause (NCDInterfaceMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->have_bfd)
+    
+    if (o->buf_left >= 0) {
+        BPending_Unset(&o->more_job);
+    } else {
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, 0);
+    }
+}
+
+void NCDInterfaceMonitor_Continue (NCDInterfaceMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->have_bfd)
+    
+    if (o->buf_left >= 0) {
+        BPending_Set(&o->more_job);
+    } else {
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, BREACTOR_READ);
+    }
+}
diff --git a/external/badvpn_dns/ncd/extra/NCDInterfaceMonitor.h b/external/badvpn_dns/ncd/extra/NCDInterfaceMonitor.h
new file mode 100644
index 0000000..805b8de
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDInterfaceMonitor.h
@@ -0,0 +1,160 @@
+/**
+ * @file NCDInterfaceMonitor.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCD_NCDINTERFACEMONITOR_H
+#define BADVPN_NCD_NCDINTERFACEMONITOR_H
+
+#include <stdint.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+
+#include <misc/debug.h>
+#include <misc/ipaddr.h>
+#include <misc/ipaddr6.h>
+#include <misc/debugerror.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+#include <system/BNetwork.h>
+
+#define NCDIFMONITOR_WATCH_LINK (1 << 0)
+#define NCDIFMONITOR_WATCH_IPV4_ADDR (1 << 1)
+#define NCDIFMONITOR_WATCH_IPV6_ADDR (1 << 2)
+
+#define NCDIFMONITOR_EVENT_LINK_UP 1
+#define NCDIFMONITOR_EVENT_LINK_DOWN 2
+#define NCDIFMONITOR_EVENT_IPV4_ADDR_ADDED 3
+#define NCDIFMONITOR_EVENT_IPV4_ADDR_REMOVED 4
+#define NCDIFMONITOR_EVENT_IPV6_ADDR_ADDED 5
+#define NCDIFMONITOR_EVENT_IPV6_ADDR_REMOVED 6
+
+#define NCDIFMONITOR_ADDR_FLAG_DYNAMIC (1 << 0)
+
+struct NCDInterfaceMonitor_event {
+    int event;
+    union {
+        struct {
+            struct ipv4_ifaddr addr;
+        } ipv4_addr;
+        struct {
+            struct ipv6_ifaddr addr;
+            int addr_flags;
+            uint8_t scope;
+        } ipv6_addr;
+    } u;
+};
+
+/**
+ * Handler called to report an interface event.
+ * Note that the event reporter does not keep any interface state, and as such may
+ * report redundant events. You should therefore handle events in an idempotent
+ * fashion.
+ * 
+ * @param event.event event type. One of:
+ *        - NCDIFMONITOR_EVENT_LINK_UP, NCDIFMONITOR_EVENT_LINK_DOWN,
+ *        - NCDIFMONITOR_EVENT_IPV4_ADDR_ADDED, NCDIFMONITOR_EVENT_IPV4_ADDR_REMOVED,
+ *        - NCDIFMONITOR_EVENT_IPV6_ADDR_ADDED, NCDIFMONITOR_EVENT_IPV6_ADDR_REMOVED.
+ *        Only events that correspont to enabled watch flags are reported.
+ * @param event.ipv4_addr.addr the IPv4 address and prefix length
+ * @param event.ipv6_addr.addr the IPv6 address, prefix length and scope
+ * @param event.ipv6_addr.addr_flags IPv6 address flags. Valid flags:
+ *        - NCDIFMONITOR_ADDR_FLAG_DYNAMIC - this address was assigned dynamically (NDP)
+ */
+typedef void (*NCDInterfaceMonitor_handler) (void *user, struct NCDInterfaceMonitor_event event);
+
+/**
+ * Handler called when an error occurs.
+ * The event reporter must be freed from within the job context of this
+ * handler, and no other operations must be performed.
+ */
+typedef void (*NCDInterfaceMonitor_handler_error) (void *user);
+
+/**
+ * Watches for network interface events using a Linux rtnetlink socket.
+ */
+typedef struct {
+    int ifindex;
+    int watch_events;
+    BReactor *reactor;
+    void *user;
+    NCDInterfaceMonitor_handler handler;
+    NCDInterfaceMonitor_handler_error handler_error;
+    int netlink_fd;
+    int event_netlink_fd;
+    int dump_queue;
+    uint32_t dump_seq;
+    BFileDescriptor bfd;
+    int have_bfd;
+    union {
+        uint8_t buf[4096];
+        struct nlmsghdr nlh;
+    } buf;
+    struct nlmsghdr *buf_nh;
+    int buf_left;
+    BPending more_job;
+    DebugError d_err;
+    DebugObject d_obj;
+} NCDInterfaceMonitor;
+
+/**
+ * Initializes the event reporter.
+ * The reporter is not paused initially.
+ * {@link BNetwork_GlobalInit} must have been done.
+ * 
+ * @param ifindex index of network interface to report events for
+ * @param watch_events mask specifying what kind of events to report. Valid flags are
+ *        NCDIFMONITOR_WATCH_LINK, NCDIFMONITOR_WATCH_IPV4_ADDR, NCDIFMONITOR_WATCH_IPV6_ADDR.
+ *        At least one flag must be provided.
+ * @param reactor reactor we live in
+ * @param user argument to handlers
+ * @param handler handler to report interface events to
+ * @param handler_error error handler
+ * @return 1 on success, 0 on failure
+ */
+int NCDInterfaceMonitor_Init (NCDInterfaceMonitor *o, int ifindex, int watch_events, BReactor *reactor, void *user,
+                              NCDInterfaceMonitor_handler handler,
+                              NCDInterfaceMonitor_handler_error handler_error) WARN_UNUSED;
+
+/**
+ * Frees the event reporter.
+ */
+void NCDInterfaceMonitor_Free (NCDInterfaceMonitor *o);
+
+/**
+ * Pauses event reporting.
+ * This operation is idempotent.
+ */
+void NCDInterfaceMonitor_Pause (NCDInterfaceMonitor *o);
+
+/**
+ * Resumes event reporting.
+ * This operation is idempotent.
+ */
+void NCDInterfaceMonitor_Continue (NCDInterfaceMonitor *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/extra/NCDRequestClient.c b/external/badvpn_dns/ncd/extra/NCDRequestClient.c
new file mode 100644
index 0000000..edf6378
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDRequestClient.c
@@ -0,0 +1,647 @@
+/**
+ * @file NCDRequestClient.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stddef.h>
+#include <stdint.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <misc/byteorder.h>
+#include <misc/expstring.h>
+#include <misc/offset.h>
+#include <misc/compare.h>
+#include <protocol/packetproto.h>
+#include <protocol/requestproto.h>
+#include <base/BLog.h>
+
+#include "NCDRequestClient.h"
+
+#include <generated/blog_channel_NCDRequestClient.h>
+
+#define SEND_PAYLOAD_MTU 32768
+#define RECV_PAYLOAD_MTU 32768
+
+#define SEND_MTU (SEND_PAYLOAD_MTU + sizeof(struct requestproto_header))
+#define RECV_MTU (RECV_PAYLOAD_MTU + sizeof(struct requestproto_header))
+
+#define CSTATE_CONNECTING 1
+#define CSTATE_CONNECTED 2
+
+#define RSTATE_SENDING_REQUEST 1
+#define RSTATE_READY 2
+#define RSTATE_SENDING_REQUEST_ABORT 3
+#define RSTATE_SENDING_ABORT 4
+#define RSTATE_WAITING_END 5
+#define RSTATE_DEAD_SENDING 6
+
+static int uint32_comparator (void *unused, void *vv1, void *vv2);
+static void report_error (NCDRequestClient *o);
+static void request_report_finished (NCDRequestClientRequest *o, int is_error);
+static void connector_handler (NCDRequestClient *o, int is_error);
+static void connection_handler (NCDRequestClient *o, int event);
+static void decoder_handler_error (NCDRequestClient *o);
+static void recv_if_handler_send (NCDRequestClient *o, uint8_t *data, int data_len);
+static struct NCDRequestClient_req * find_req (NCDRequestClient *o, uint32_t request_id);
+static int get_free_request_id (NCDRequestClient *o, uint32_t *out);
+static int build_requestproto_packet (uint32_t request_id, uint32_t type, NCDValRef payload_value, uint8_t **out_data, int *out_len);
+static void build_nodata_packet (uint32_t request_id, uint32_t type, uint8_t *data, int *out_len);
+static int req_is_aborted (struct NCDRequestClient_req *req);
+static void req_abort (struct NCDRequestClient_req *req);
+static void req_free (struct NCDRequestClient_req *req);
+static void req_send_abort (struct NCDRequestClient_req *req);
+static void req_qflow_send_iface_handler_done (struct NCDRequestClient_req *req);
+
+static int uint32_comparator (void *unused, void *vv1, void *vv2)
+{
+    uint32_t *v1 = vv1;
+    uint32_t *v2 = vv2;
+    return B_COMPARE(*v1, *v2);
+}
+
+static void report_error (NCDRequestClient *o)
+{
+    ASSERT(!o->is_error)
+    
+    o->is_error = 1;
+    DEBUGERROR(&o->d_err, o->handler_error(o->user))
+}
+
+static void request_report_finished (NCDRequestClientRequest *o, int is_error)
+{
+    o->req = NULL;
+    
+    DEBUGERROR(&o->d_err, o->handler_finished(o->user, is_error))
+}
+
+static void connector_handler (NCDRequestClient *o, int is_error)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->state == CSTATE_CONNECTING)
+    
+    // check error
+    if (is_error) {
+        BLog(BLOG_ERROR, "failed to connect to socket");
+        goto fail0;
+    }
+    
+    BPendingGroup *pg = BReactor_PendingGroup(o->reactor);
+    
+    // init connection
+    if (!BConnection_Init(&o->con, BConnection_source_connector(&o->connector), o->reactor, o, (BConnection_handler)connection_handler)) {
+        BLog(BLOG_ERROR, "BConnection_Init failed");
+        goto fail0;
+    }
+    
+    // init connection interfaces
+    BConnection_SendAsync_Init(&o->con);
+    BConnection_RecvAsync_Init(&o->con);
+    StreamPassInterface *con_send_if = BConnection_SendAsync_GetIf(&o->con);
+    StreamRecvInterface *con_recv_if = BConnection_RecvAsync_GetIf(&o->con);
+    
+    // init receive interface
+    PacketPassInterface_Init(&o->recv_if, RECV_MTU, (PacketPassInterface_handler_send)recv_if_handler_send, o, pg);
+    
+    // init receive decoder
+    if (!PacketProtoDecoder_Init(&o->recv_decoder, con_recv_if, &o->recv_if, pg, o, (PacketProtoDecoder_handler_error)decoder_handler_error)) {
+        BLog(BLOG_ERROR, "PacketProtoDecoder_Init failed");
+        goto fail1;
+    }
+    
+    // init send sender
+    PacketStreamSender_Init(&o->send_sender, con_send_if, PACKETPROTO_ENCLEN(SEND_MTU), pg);
+    
+    // init send queue
+    PacketPassFifoQueue_Init(&o->send_queue, PacketStreamSender_GetInput(&o->send_sender), pg);
+    
+    // set state connected
+    o->state = CSTATE_CONNECTED;
+    
+    // call connected handler
+    o->handler_connected(o->user);
+    return;
+    
+fail1:
+    PacketPassInterface_Free(&o->recv_if);
+    BConnection_RecvAsync_Free(&o->con);
+    BConnection_SendAsync_Free(&o->con);
+    BConnection_Free(&o->con);
+fail0:
+    report_error(o);
+}
+
+static void connection_handler (NCDRequestClient *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->state == CSTATE_CONNECTED)
+    
+    BLog(BLOG_ERROR, "connection error");
+    
+    report_error(o);
+}
+
+static void decoder_handler_error (NCDRequestClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->state == CSTATE_CONNECTED)
+    
+    BLog(BLOG_ERROR, "decoder error");
+    
+    report_error(o);
+}
+
+static void recv_if_handler_send (NCDRequestClient *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->state == CSTATE_CONNECTED)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= RECV_MTU)
+    
+    // accept packet
+    PacketPassInterface_Done(&o->recv_if);
+    
+    if (data_len < sizeof(struct requestproto_header)) {
+        BLog(BLOG_ERROR, "missing requestproto header");
+        goto fail;
+    }
+    
+    struct requestproto_header header;
+    memcpy(&header, data, sizeof(header));
+    uint32_t request_id = ltoh32(header.request_id);
+    uint32_t type = ltoh32(header.type);
+    
+    uint8_t *payload = data + sizeof(header);
+    int payload_len = data_len - sizeof(header);
+    
+    // find request
+    struct NCDRequestClient_req *req = find_req(o, request_id);
+    if (!req) {
+        BLog(BLOG_ERROR, "received packet with unknown request ID");
+        goto fail;
+    }
+    
+    switch (type) {
+        case REQUESTPROTO_TYPE_SERVER_REPLY: {
+            switch (o->state) {
+                case RSTATE_READY: {
+                    // init memory
+                    NCDValMem mem;
+                    NCDValMem_Init(&mem);
+                    
+                    // parse payload
+                    NCDValRef payload_value;
+                    if (!NCDValParser_Parse((char *)payload, payload_len, &mem, &payload_value)) {
+                        BLog(BLOG_ERROR, "failed to parse reply payload");
+                        NCDValMem_Free(&mem);
+                        goto fail;
+                    }
+                    
+                    // call reply handler
+                    req->creq->handler_reply(req->creq->user, mem, payload_value);
+                    return;
+                } break;
+                
+                case RSTATE_SENDING_ABORT:
+                case RSTATE_WAITING_END:
+                    return;
+                
+                default:
+                    BLog(BLOG_ERROR, "received unexpected reply");
+                    goto fail;
+            }
+        } break;
+        
+        case REQUESTPROTO_TYPE_SERVER_FINISHED:
+        case REQUESTPROTO_TYPE_SERVER_ERROR: {
+            if (payload_len != 0) {
+                BLog(BLOG_ERROR, "finshed/aborted message has non-empty payload");
+                goto fail;
+            }
+            
+            NCDRequestClientRequest *creq = req->creq;
+            req->creq = NULL;
+            
+            switch (req->state) {
+                case RSTATE_SENDING_ABORT: {
+                    // set state dying send
+                    req->state = RSTATE_DEAD_SENDING;
+                } break;
+                
+                case RSTATE_WAITING_END:
+                case RSTATE_READY: {
+                    // free req
+                    req_free(req);
+                } break;
+                
+                default:
+                    BLog(BLOG_ERROR, "received unexpected finished/aborted");
+                    goto fail;
+            }
+            
+            // report finished
+            if (creq) {
+                request_report_finished(creq, type == REQUESTPROTO_TYPE_SERVER_ERROR);
+            }
+            return;
+        } break;
+        
+        default:
+            BLog(BLOG_ERROR, "received invalid message type");
+            goto fail;
+    }
+    
+    ASSERT(0)
+    
+fail:
+    report_error(o);
+}
+
+static struct NCDRequestClient_req * find_req (NCDRequestClient *o, uint32_t request_id)
+{
+    BAVLNode *tn = BAVL_LookupExact(&o->reqs_tree, &request_id);
+    if (!tn) {
+        return NULL;
+    }
+    
+    struct NCDRequestClient_req *req = UPPER_OBJECT(tn, struct NCDRequestClient_req, reqs_tree_node);
+    ASSERT(req->request_id == request_id)
+    
+    return req;
+}
+
+static int get_free_request_id (NCDRequestClient *o, uint32_t *out)
+{
+    uint32_t first = o->next_request_id;
+    
+    do {
+        if (!find_req(o, o->next_request_id)) {
+            *out = o->next_request_id;
+            return 1;
+        }
+        o->next_request_id++;
+    } while (o->next_request_id != first);
+    
+    return 0;
+}
+
+static int build_requestproto_packet (uint32_t request_id, uint32_t type, NCDValRef payload_value, uint8_t **out_data, int *out_len)
+{
+    ExpString str;
+    if (!ExpString_Init(&str)) {
+        BLog(BLOG_ERROR, "ExpString_Init failed");
+        goto fail0;
+    }
+    
+    if (!ExpString_AppendZeros(&str, sizeof(struct packetproto_header) + sizeof(struct requestproto_header))) {
+        BLog(BLOG_ERROR, "ExpString_AppendBinary failed");
+        goto fail1;
+    }
+    
+    if (!NCDVal_IsInvalid(payload_value) && !NCDValGenerator_AppendGenerate(payload_value, &str)) {
+        BLog(BLOG_ERROR, "NCDValGenerator_AppendGenerate failed");
+        goto fail1;
+    }
+    
+    size_t len = ExpString_Length(&str);
+    if (len > INT_MAX || len > PACKETPROTO_ENCLEN(SEND_MTU) || len - sizeof(struct packetproto_header) > UINT16_MAX) {
+        BLog(BLOG_ERROR, "reply is too long");
+        goto fail1;
+    }
+    
+    uint8_t *packet = (uint8_t *)ExpString_Get(&str);
+    
+    struct packetproto_header pp;
+    pp.len = htol16(len - sizeof(struct packetproto_header));
+    
+    struct requestproto_header rp;
+    rp.request_id = htol32(request_id);
+    rp.type = htol32(type);
+    
+    memcpy(packet, &pp, sizeof(pp));
+    memcpy(packet + sizeof(pp), &rp, sizeof(rp));
+    
+    *out_data = packet;
+    *out_len = len;
+    return 1;
+    
+fail1:
+    ExpString_Free(&str);
+fail0:
+    return 0;
+}
+
+static void build_nodata_packet (uint32_t request_id, uint32_t type, uint8_t *data, int *out_len)
+{
+    struct packetproto_header pp;
+    pp.len = htol16(sizeof(struct requestproto_header));
+    
+    struct requestproto_header rp;
+    rp.request_id = htol32(request_id);
+    rp.type = htol32(type);
+    
+    memcpy(data, &pp, sizeof(pp));
+    memcpy(data + sizeof(pp), &rp, sizeof(rp));
+    
+    *out_len = sizeof(pp) + sizeof(rp);
+}
+
+static int req_is_aborted (struct NCDRequestClient_req *req)
+{
+    switch (req->state) {
+        case RSTATE_SENDING_REQUEST:
+        case RSTATE_READY:
+            return 0;
+        default:
+            return 1;
+    }
+}
+
+static void req_abort (struct NCDRequestClient_req *req)
+{
+    ASSERT(!req_is_aborted(req))
+    ASSERT(!req->client->is_error)
+    
+    switch (req->state) {
+        case RSTATE_SENDING_REQUEST: {
+            req->state = RSTATE_SENDING_REQUEST_ABORT;
+        } break;
+        
+        case RSTATE_READY: {
+            req_send_abort(req);
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void req_free (struct NCDRequestClient_req *req)
+{
+    NCDRequestClient *client = req->client;
+    PacketPassFifoQueueFlow_AssertFree(&req->send_qflow);
+    ASSERT(!req->creq)
+    
+    // free queue flow
+    PacketPassFifoQueueFlow_Free(&req->send_qflow);
+    
+    // free request data
+    free(req->request_data);
+    
+    // remove from reqs tree
+    BAVL_Remove(&client->reqs_tree, &req->reqs_tree_node);
+    
+    // free structure
+    free(req);
+}
+
+static void req_send_abort (struct NCDRequestClient_req *req)
+{
+    // build packet
+    build_nodata_packet(req->request_id, REQUESTPROTO_TYPE_CLIENT_ABORT, req->request_data, &req->request_len);
+    
+    // start sending
+    PacketPassInterface_Sender_Send(req->send_qflow_iface, req->request_data, req->request_len);
+    
+    // set state sending abort
+    req->state = RSTATE_SENDING_ABORT;
+}
+
+static void req_qflow_send_iface_handler_done (struct NCDRequestClient_req *req)
+{
+    switch (req->state) {
+        case RSTATE_SENDING_REQUEST: {
+            // set state ready
+            req->state = RSTATE_READY;
+            
+            // call sent handler
+            req->creq->handler_sent(req->creq->user);
+            return;
+        } break;
+        
+        case RSTATE_SENDING_REQUEST_ABORT: {
+            // send abort
+            req_send_abort(req);
+        } break;
+        
+        case RSTATE_SENDING_ABORT: {
+            // set state waiting end
+            req->state = RSTATE_WAITING_END;
+        } break;
+        
+        case RSTATE_DEAD_SENDING: {
+            // free req
+            req_free(req);
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+int NCDRequestClient_Init (NCDRequestClient *o, struct BConnection_addr addr, BReactor *reactor, void *user,
+                           NCDRequestClient_handler_error handler_error,
+                           NCDRequestClient_handler_connected handler_connected)
+{
+    ASSERT(handler_error)
+    ASSERT(handler_connected)
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler_error = handler_error;
+    o->handler_connected = handler_connected;
+    
+    // init connector
+    if (!BConnector_InitGeneric(&o->connector, addr, reactor, o, (BConnector_handler)connector_handler)) {
+        BLog(BLOG_ERROR, "BConnector_InitGeneric failed");
+        goto fail0;
+    }
+    
+    // init reqs tree
+    BAVL_Init(&o->reqs_tree, OFFSET_DIFF(struct NCDRequestClient_req, request_id, reqs_tree_node), uint32_comparator, NULL);
+    
+    // set next request ID
+    o->next_request_id = 0;
+    
+    // set state connecting
+    o->state = CSTATE_CONNECTING;
+    
+    // set is not error
+    o->is_error = 0;
+    
+    DebugCounter_Init(&o->d_reqests_ctr);
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void NCDRequestClient_Free (NCDRequestClient *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    DebugCounter_Free(&o->d_reqests_ctr);
+    
+    if (o->state == CSTATE_CONNECTED) {
+        // allow freeing queue flow
+        PacketPassFifoQueue_PrepareFree(&o->send_queue);
+        
+        // free remaining reqs
+        BAVLNode *tn;
+        while (tn = BAVL_GetFirst(&o->reqs_tree)) {
+            struct NCDRequestClient_req *req = UPPER_OBJECT(tn, struct NCDRequestClient_req, reqs_tree_node);
+            ASSERT(!req->creq)
+            req_free(req);
+        }
+        
+        // free connection stuff
+        PacketPassFifoQueue_Free(&o->send_queue);
+        PacketStreamSender_Free(&o->send_sender);
+        PacketProtoDecoder_Free(&o->recv_decoder);
+        PacketPassInterface_Free(&o->recv_if);
+        BConnection_RecvAsync_Free(&o->con);
+        BConnection_SendAsync_Free(&o->con);
+        BConnection_Free(&o->con);
+    }
+    
+    // free connector
+    BConnector_Free(&o->connector);
+}
+
+int NCDRequestClientRequest_Init (NCDRequestClientRequest *o, NCDRequestClient *client, NCDValRef payload_value, void *user,
+                                  NCDRequestClientRequest_handler_sent handler_sent,
+                                  NCDRequestClientRequest_handler_reply handler_reply,
+                                  NCDRequestClientRequest_handler_finished handler_finished)
+{
+    ASSERT(client->state == CSTATE_CONNECTED)
+    DebugError_AssertNoError(&client->d_err);
+    ASSERT(!NCDVal_IsInvalid(payload_value))
+    ASSERT(handler_sent)
+    ASSERT(handler_reply)
+    ASSERT(handler_finished)
+    
+    // init arguments
+    o->client = client;
+    o->user = user;
+    o->handler_sent = handler_sent;
+    o->handler_reply = handler_reply;
+    o->handler_finished = handler_finished;
+    
+    // allocate req structure
+    struct NCDRequestClient_req *req = malloc(sizeof(*req));
+    if (!req) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // allocate request ID
+    if (!get_free_request_id(client, &req->request_id)) {
+        BLog(BLOG_ERROR, "failed to allocate request ID");
+        goto fail1;
+    }
+    
+    // insert to reqs tree
+    int res = BAVL_Insert(&client->reqs_tree, &req->reqs_tree_node, NULL);
+    ASSERT_EXECUTE(res)
+    
+    // set pointers
+    o->req = req;
+    req->creq = o;
+    req->client = client;
+    
+    // build request
+    if (!build_requestproto_packet(req->request_id, REQUESTPROTO_TYPE_CLIENT_REQUEST, payload_value, &req->request_data, &req->request_len)) {
+        BLog(BLOG_ERROR, "failed to build request");
+        goto fail2;
+    }
+    
+    // init queue flow
+    PacketPassFifoQueueFlow_Init(&req->send_qflow, &client->send_queue);
+    
+    // init send interface
+    req->send_qflow_iface = PacketPassFifoQueueFlow_GetInput(&req->send_qflow);
+    PacketPassInterface_Sender_Init(req->send_qflow_iface, (PacketPassInterface_handler_done)req_qflow_send_iface_handler_done, req);
+    
+    // start sending request
+    PacketPassInterface_Sender_Send(req->send_qflow_iface, req->request_data, req->request_len);
+    
+    // set state sending request
+    req->state = RSTATE_SENDING_REQUEST;
+    
+    DebugCounter_Increment(&client->d_reqests_ctr);
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(client->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail2:
+    BAVL_Remove(&client->reqs_tree, &req->reqs_tree_node);
+fail1:
+    free(req);
+fail0:
+    return 0;
+}
+
+void NCDRequestClientRequest_Free (NCDRequestClientRequest *o)
+{
+    struct NCDRequestClient_req *req = o->req;
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    DebugCounter_Decrement(&o->client->d_reqests_ctr);
+    
+    if (req) {
+        ASSERT(req->creq == o)
+        
+        // remove reference to us
+        req->creq = NULL;
+        
+        // abort req if not already
+        if (!req->client->is_error && !req_is_aborted(req)) {
+            req_abort(req);
+        }
+    }
+}
+
+void NCDRequestClientRequest_Abort (NCDRequestClientRequest *o)
+{
+    struct NCDRequestClient_req *req = o->req;
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    DebugError_AssertNoError(&o->client->d_err);
+    ASSERT(req)
+    ASSERT(req->creq == o)
+    ASSERT(!req_is_aborted(req))
+    
+    // abort req
+    req_abort(req);
+}
diff --git a/external/badvpn_dns/ncd/extra/NCDRequestClient.h b/external/badvpn_dns/ncd/extra/NCDRequestClient.h
new file mode 100644
index 0000000..7e0a6d5
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDRequestClient.h
@@ -0,0 +1,111 @@
+/**
+ * @file NCDRequestClient.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCDREQUESTCLIENT_H
+#define BADVPN_NCDREQUESTCLIENT_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <misc/debugcounter.h>
+#include <structure/BAVL.h>
+#include <base/DebugObject.h>
+#include <system/BConnection.h>
+#include <system/BConnectionGeneric.h>
+#include <flow/PacketProtoDecoder.h>
+#include <flow/PacketStreamSender.h>
+#include <flow/PacketPassFifoQueue.h>
+#include <ncd/NCDValGenerator.h>
+#include <ncd/NCDValParser.h>
+
+struct NCDRequestClient_req;
+
+typedef void (*NCDRequestClient_handler_error) (void *user);
+typedef void (*NCDRequestClient_handler_connected) (void *user);
+typedef void (*NCDRequestClientRequest_handler_sent) (void *user);
+typedef void (*NCDRequestClientRequest_handler_reply) (void *user, NCDValMem reply_mem, NCDValRef reply_value);
+typedef void (*NCDRequestClientRequest_handler_finished) (void *user, int is_error);
+
+typedef struct {
+    BReactor *reactor;
+    void *user;
+    NCDRequestClient_handler_error handler_error;
+    NCDRequestClient_handler_connected handler_connected;
+    BConnector connector;
+    BConnection con;
+    PacketPassFifoQueue send_queue;
+    PacketStreamSender send_sender;
+    PacketProtoDecoder recv_decoder;
+    PacketPassInterface recv_if;
+    BAVL reqs_tree;
+    uint32_t next_request_id;
+    int state;
+    int is_error;
+    DebugCounter d_reqests_ctr;
+    DebugError d_err;
+    DebugObject d_obj;
+} NCDRequestClient;
+
+typedef struct {
+    NCDRequestClient *client;
+    void *user;
+    NCDRequestClientRequest_handler_sent handler_sent;
+    NCDRequestClientRequest_handler_reply handler_reply;
+    NCDRequestClientRequest_handler_finished handler_finished;
+    struct NCDRequestClient_req *req;
+    DebugError d_err;
+    DebugObject d_obj;
+} NCDRequestClientRequest;
+
+struct NCDRequestClient_req {
+    NCDRequestClientRequest *creq;
+    NCDRequestClient *client;
+    BAVLNode reqs_tree_node;
+    uint32_t request_id;
+    uint8_t *request_data;
+    int request_len;
+    PacketPassInterface *send_qflow_iface;
+    PacketPassFifoQueueFlow send_qflow;
+    int state;
+};
+
+int NCDRequestClient_Init (NCDRequestClient *o, struct BConnection_addr addr, BReactor *reactor, void *user,
+                           NCDRequestClient_handler_error handler_error,
+                           NCDRequestClient_handler_connected handler_connected) WARN_UNUSED;
+void NCDRequestClient_Free (NCDRequestClient *o);
+
+int NCDRequestClientRequest_Init (NCDRequestClientRequest *o, NCDRequestClient *client, NCDValRef payload_value, void *user,
+                                  NCDRequestClientRequest_handler_sent handler_sent,
+                                  NCDRequestClientRequest_handler_reply handler_reply,
+                                  NCDRequestClientRequest_handler_finished handler_finished) WARN_UNUSED;
+void NCDRequestClientRequest_Free (NCDRequestClientRequest *o);
+void NCDRequestClientRequest_Abort (NCDRequestClientRequest *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/extra/NCDRfkillMonitor.c b/external/badvpn_dns/ncd/extra/NCDRfkillMonitor.c
new file mode 100644
index 0000000..cec2a3d
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDRfkillMonitor.c
@@ -0,0 +1,117 @@
+/**
+ * @file NCDRfkillMonitor.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <misc/debug.h>
+#include <misc/nonblocking.h>
+#include <base/BLog.h>
+
+#include "NCDRfkillMonitor.h"
+
+#include <generated/blog_channel_NCDRfkillMonitor.h>
+
+#define RFKILL_DEVICE_NODE "/dev/rfkill"
+
+static void rfkill_fd_handler (NCDRfkillMonitor *o, int events);
+
+void rfkill_fd_handler (NCDRfkillMonitor *o, int events)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // read from netlink fd
+    struct rfkill_event event;
+    int len = read(o->rfkill_fd, &event, sizeof(event));
+    if (len < 0) {
+        BLog(BLOG_ERROR, "read failed");
+        return;
+    }
+    if (len != sizeof(event)) {
+        BLog(BLOG_ERROR, "read returned wrong length");
+        return;
+    }
+    
+    // call handler
+    o->handler(o->user, event);
+    return;
+}
+
+int NCDRfkillMonitor_Init (NCDRfkillMonitor *o, BReactor *reactor, NCDRfkillMonitor_handler handler, void *user)
+{
+    // init arguments
+    o->reactor = reactor;
+    o->handler = handler;
+    o->user = user;
+    
+    // open rfkill
+    if ((o->rfkill_fd = open(RFKILL_DEVICE_NODE, O_RDONLY)) < 0) {
+        BLog(BLOG_ERROR, "open failed");
+        goto fail0;
+    }
+    
+    // set fd non-blocking
+    if (!badvpn_set_nonblocking(o->rfkill_fd)) {
+        BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail1;
+    }
+    
+    // init BFileDescriptor
+    BFileDescriptor_Init(&o->bfd, o->rfkill_fd, (BFileDescriptor_handler)rfkill_fd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, BREACTOR_READ);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    if (close(o->rfkill_fd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+fail0:
+    return 0;
+}
+
+void NCDRfkillMonitor_Free (NCDRfkillMonitor *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free BFileDescriptor
+    BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+    
+    // close rfkill
+    if (close(o->rfkill_fd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+}
diff --git a/external/badvpn_dns/ncd/extra/NCDRfkillMonitor.h b/external/badvpn_dns/ncd/extra/NCDRfkillMonitor.h
new file mode 100644
index 0000000..00aa265
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/NCDRfkillMonitor.h
@@ -0,0 +1,53 @@
+/**
+ * @file NCDRfkillMonitor.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCD_NCDRFKILLMONITOR_H
+#define BADVPN_NCD_NCDRFKILLMONITOR_H
+
+#include <linux/rfkill.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+
+typedef void (*NCDRfkillMonitor_handler) (void *user, struct rfkill_event event);
+
+typedef struct {
+    BReactor *reactor;
+    NCDRfkillMonitor_handler handler;
+    void *user;
+    int rfkill_fd;
+    BFileDescriptor bfd;
+    DebugObject d_obj;
+} NCDRfkillMonitor;
+
+int NCDRfkillMonitor_Init (NCDRfkillMonitor *o, BReactor *reactor, NCDRfkillMonitor_handler handler, void *user) WARN_UNUSED;
+void NCDRfkillMonitor_Free (NCDRfkillMonitor *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/extra/address_utils.h b/external/badvpn_dns/ncd/extra/address_utils.h
new file mode 100644
index 0000000..24ff79a
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/address_utils.h
@@ -0,0 +1,280 @@
+/**
+ * @file address_utils.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 NCD_ADDRESS_UTILS_H
+#define NCD_ADDRESS_UTILS_H
+
+#include <string.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/ipaddr.h>
+#include <misc/ipaddr6.h>
+#include <misc/byteorder.h>
+#include <system/BAddr.h>
+#include <system/BConnectionGeneric.h>
+#include <ncd/NCDVal.h>
+#include <ncd/extra/value_utils.h>
+
+static int ncd_read_baddr (NCDValRef val, BAddr *out) WARN_UNUSED;
+static NCDValRef ncd_make_baddr (BAddr addr, NCDValMem *mem);
+static int ncd_read_bconnection_addr (NCDValRef val, struct BConnection_addr *out_addr) WARN_UNUSED;
+
+static int ncd_read_baddr (NCDValRef val, BAddr *out)
+{
+    ASSERT(!NCDVal_IsInvalid(val))
+    ASSERT(NCDVal_HasOnlyContinuousStrings(val))
+    ASSERT(out)
+    
+    if (!NCDVal_IsList(val)) {
+        goto fail;
+    }
+    
+    NCDValRef type_val;
+    if (!NCDVal_ListReadHead(val, 1, &type_val)) {
+        goto fail;
+    }
+    if (!NCDVal_IsString(type_val)) {
+        goto fail;
+    }
+    
+    BAddr addr;
+    
+    if (NCDVal_StringEquals(type_val, "none")) {
+        if (!NCDVal_ListRead(val, 1, &type_val)) {
+            goto fail;
+        }
+        
+        addr.type = BADDR_TYPE_NONE;
+    }
+    else if (NCDVal_StringEquals(type_val, "ipv4")) {
+        NCDValRef ipaddr_val;
+        NCDValRef port_val;
+        if (!NCDVal_ListRead(val, 3, &type_val, &ipaddr_val, &port_val)) {
+            goto fail;
+        }
+        if (!NCDVal_IsString(ipaddr_val) || !NCDVal_IsString(port_val)) {
+            goto fail;
+        }
+        
+        addr.type = BADDR_TYPE_IPV4;
+        
+        if (!ipaddr_parse_ipv4_addr_bin(NCDVal_StringData(ipaddr_val), NCDVal_StringLength(ipaddr_val), &addr.ipv4.ip)) {
+            goto fail;
+        }
+        
+        uintmax_t port;
+        if (!ncd_read_uintmax(port_val, &port) || port > UINT16_MAX) {
+            goto fail;
+        }
+        addr.ipv4.port = hton16(port);
+    }
+    else if (NCDVal_StringEquals(type_val, "ipv6")) {
+        NCDValRef ipaddr_val;
+        NCDValRef port_val;
+        if (!NCDVal_ListRead(val, 3, &type_val, &ipaddr_val, &port_val)) {
+            goto fail;
+        }
+        if (!NCDVal_IsString(ipaddr_val) || !NCDVal_IsString(port_val)) {
+            goto fail;
+        }
+        
+        addr.type = BADDR_TYPE_IPV6;
+        
+        struct ipv6_addr i6addr;
+        if (!ipaddr6_parse_ipv6_addr_bin(NCDVal_StringData(ipaddr_val), NCDVal_StringLength(ipaddr_val), &i6addr)) {
+            goto fail;
+        }
+        memcpy(addr.ipv6.ip, i6addr.bytes, 16);
+        
+        uintmax_t port;
+        if (!ncd_read_uintmax(port_val, &port) || port > UINT16_MAX) {
+            goto fail;
+        }
+        addr.ipv6.port = hton16(port);
+    }
+    else {
+        goto fail;
+    }
+    
+    *out = addr;
+    return 1;
+    
+fail:
+    return 0;
+}
+
+static NCDValRef ncd_make_baddr (BAddr addr, NCDValMem *mem)
+{
+    BAddr_Assert(&addr);
+    ASSERT(mem)
+    
+    NCDValRef val;
+    
+    switch (addr.type) {
+        default:
+        case BADDR_TYPE_NONE: {
+            val = NCDVal_NewList(mem, 1);
+            if (NCDVal_IsInvalid(val)) {
+                goto fail;
+            }
+            
+            const char *str = (addr.type == BADDR_TYPE_NONE ? "none" : "unknown");
+            NCDValRef type_val = NCDVal_NewString(mem, str);
+            if (NCDVal_IsInvalid(type_val)) {
+                goto fail;
+            }
+            
+            if (!NCDVal_ListAppend(val, type_val)) {
+                goto fail;
+            }
+        } break;
+        
+        case BADDR_TYPE_IPV4: {
+            val = NCDVal_NewList(mem, 3);
+            if (NCDVal_IsInvalid(val)) {
+                goto fail;
+            }
+            
+            NCDValRef type_val = NCDVal_NewString(mem, "ipv4");
+            if (NCDVal_IsInvalid(type_val)) {
+                goto fail;
+            }
+            
+            char ipaddr_buf[IPADDR_PRINT_MAX];
+            ipaddr_print_addr(addr.ipv4.ip, ipaddr_buf);
+            NCDValRef ipaddr_val = NCDVal_NewString(mem, ipaddr_buf);
+            if (NCDVal_IsInvalid(ipaddr_val)) {
+                goto fail;
+            }
+            
+            NCDValRef port_val = ncd_make_uintmax(mem, ntoh16(addr.ipv4.port));
+            if (NCDVal_IsInvalid(port_val)) {
+                goto fail;
+            }
+            
+            if (!NCDVal_ListAppend(val, type_val)) {
+                goto fail;
+            }
+            if (!NCDVal_ListAppend(val, ipaddr_val)) {
+                goto fail;
+            }
+            if (!NCDVal_ListAppend(val, port_val)) {
+                goto fail;
+            }
+        } break;
+        
+        case BADDR_TYPE_IPV6: {
+            val = NCDVal_NewList(mem, 3);
+            if (NCDVal_IsInvalid(val)) {
+                goto fail;
+            }
+            
+            NCDValRef type_val = NCDVal_NewString(mem, "ipv6");
+            if (NCDVal_IsInvalid(type_val)) {
+                goto fail;
+            }
+            
+            char ipaddr_buf[IPADDR6_PRINT_MAX];
+            struct ipv6_addr i6addr;
+            memcpy(i6addr.bytes, addr.ipv6.ip, 16);
+            ipaddr6_print_addr(i6addr, ipaddr_buf);
+            NCDValRef ipaddr_val = NCDVal_NewString(mem, ipaddr_buf);
+            if (NCDVal_IsInvalid(ipaddr_val)) {
+                goto fail;
+            }
+            
+            NCDValRef port_val = ncd_make_uintmax(mem, ntoh16(addr.ipv6.port));
+            if (NCDVal_IsInvalid(port_val)) {
+                goto fail;
+            }
+            
+            if (!NCDVal_ListAppend(val, type_val)) {
+                goto fail;
+            }
+            if (!NCDVal_ListAppend(val, ipaddr_val)) {
+                goto fail;
+            }
+            if (!NCDVal_ListAppend(val, port_val)) {
+                goto fail;
+            }
+        } break;
+    }
+    
+    return val;
+    
+fail:
+    return NCDVal_NewInvalid();
+}
+
+static int ncd_read_bconnection_addr (NCDValRef val, struct BConnection_addr *out_addr)
+{
+    ASSERT(!NCDVal_IsInvalid(val))
+    ASSERT(NCDVal_HasOnlyContinuousStrings(val))
+    
+    if (!NCDVal_IsList(val)) {
+        goto fail;
+    }
+    
+    NCDValRef protocol_arg;
+    NCDValRef data_arg;
+    if (!NCDVal_ListRead(val, 2, &protocol_arg, &data_arg)) {
+        goto fail;
+    }
+    
+    if (!NCDVal_IsString(protocol_arg)) {
+        goto fail;
+    }
+    
+    if (NCDVal_StringEquals(protocol_arg, "unix")) {
+        if (!NCDVal_IsStringNoNulls(data_arg)) {
+            goto fail;
+        }
+        
+        *out_addr = BConnection_addr_unix(NCDVal_StringData(data_arg), NCDVal_StringLength(data_arg));
+    }
+    else if (NCDVal_StringEquals(protocol_arg, "tcp")) {
+        BAddr baddr;
+        if (!ncd_read_baddr(data_arg, &baddr)) {
+            goto fail;
+        }
+        
+        *out_addr = BConnection_addr_baddr(baddr);
+    }
+    else {
+        goto fail;
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/ncd/extra/build_cmdline.c b/external/badvpn_dns/ncd/extra/build_cmdline.c
new file mode 100644
index 0000000..ea96b0f
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/build_cmdline.c
@@ -0,0 +1,111 @@
+/**
+ * @file build_cmdline.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <ncd/extra/value_utils.h>
+
+#include "build_cmdline.h"
+
+int ncd_build_cmdline (NCDModuleInst *i, int log_channel, NCDValRef cmd_arg, char **out_exec, CmdLine *out_cl)
+{
+    ASSERT(!NCDVal_IsInvalid(cmd_arg))
+    ASSERT(out_exec)
+    ASSERT(out_cl)
+    
+    if (!NCDVal_IsList(cmd_arg)) {
+        NCDModuleInst_Backend_Log(i, log_channel, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    size_t count = NCDVal_ListCount(cmd_arg);
+    
+    // read exec
+    if (count == 0) {
+        NCDModuleInst_Backend_Log(i, log_channel, BLOG_ERROR, "missing executable name");
+        goto fail0;
+    }
+    NCDValRef exec_arg = NCDVal_ListGet(cmd_arg, 0);
+    if (!NCDVal_IsStringNoNulls(exec_arg)) {
+        NCDModuleInst_Backend_Log(i, log_channel, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    char *exec = ncd_strdup(exec_arg);
+    if (!exec) {
+        NCDModuleInst_Backend_Log(i, log_channel, BLOG_ERROR, "ncd_strdup failed");
+        goto fail0;
+    }
+    
+    // start cmdline
+    CmdLine cl;
+    if (!CmdLine_Init(&cl)) {
+        NCDModuleInst_Backend_Log(i, log_channel, BLOG_ERROR, "CmdLine_Init failed");
+        goto fail1;
+    }
+    
+    // add header
+    if (!CmdLine_Append(&cl, exec)) {
+        NCDModuleInst_Backend_Log(i, log_channel, BLOG_ERROR, "CmdLine_Append failed");
+        goto fail2;
+    }
+    
+    // add additional arguments
+    for (size_t j = 1; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(cmd_arg, j);
+        
+        if (!NCDVal_IsStringNoNulls(arg)) {
+            NCDModuleInst_Backend_Log(i, log_channel, BLOG_ERROR, "wrong type");
+            goto fail2;
+        }
+        
+        b_cstring cstr = NCDVal_StringCstring(arg);
+        if (!CmdLine_AppendCstring(&cl, cstr, 0, cstr.length)) {
+            NCDModuleInst_Backend_Log(i, log_channel, BLOG_ERROR, "CmdLine_AppendCstring failed");
+            goto fail2;
+        }
+    }
+    
+    // finish
+    if (!CmdLine_Finish(&cl)) {
+        NCDModuleInst_Backend_Log(i, log_channel, BLOG_ERROR, "CmdLine_Finish failed");
+        goto fail2;
+    }
+    
+    *out_exec = exec;
+    *out_cl = cl;
+    return 1;
+    
+fail2:
+    CmdLine_Free(&cl);
+fail1:
+    free(exec);
+fail0:
+    return 0;
+}
diff --git a/external/badvpn_dns/ncd/extra/build_cmdline.h b/external/badvpn_dns/ncd/extra/build_cmdline.h
new file mode 100644
index 0000000..27abf18
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/build_cmdline.h
@@ -0,0 +1,38 @@
+/**
+ * @file build_cmdline.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 NCD_BUILD_CMDLINE_H
+#define NCD_BUILD_CMDLINE_H
+
+#include <ncd/NCDModule.h>
+#include <misc/cmdline.h>
+
+int ncd_build_cmdline (NCDModuleInst *i, int log_channel, NCDValRef cmd_arg, char **exec, CmdLine *cl);
+
+#endif
diff --git a/external/badvpn_dns/ncd/extra/make_fast_names.h b/external/badvpn_dns/ncd/extra/make_fast_names.h
new file mode 100644
index 0000000..3b7d72a
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/make_fast_names.h
@@ -0,0 +1,154 @@
+/**
+ * @file make_fast_names.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/merge.h>
+#include <misc/balloc.h>
+#include <ncd/NCDStringIndex.h>
+
+// Input parameters:
+// #define NAMES_PARAM_NAME
+// #define NAMES_PARAM_TYPE struct instance
+// #define NAMES_PARAM_MEMBER_DYNAMIC_NAMES dynamic_names
+// #define NAMES_PARAM_MEMBER_STATIC_NAMES static_names
+// #define NAMES_PARAM_MEMBER_NUM_NAMES num_names
+// #define NAMES_PARAM_NUM_STATIC_NAMES 10
+
+#define MakeFastNames_count_names MERGE(NAMES_PARAM_NAME, _count_names)
+#define MakeFastNames_add_name MERGE(NAMES_PARAM_NAME, _add_name)
+#define MakeFastNames_InitNames MERGE(NAMES_PARAM_NAME, _InitNames)
+#define MakeFastNames_FreeNames MERGE(NAMES_PARAM_NAME, _FreeNames)
+#define MakeFastNames_GetNames MERGE(NAMES_PARAM_NAME, _GetNames)
+
+static size_t MakeFastNames_count_names (const char *str, size_t str_len)
+{
+    size_t count = 1;
+    
+    while (str_len > 0) {
+        if (*str == '.') {
+            count++;
+        }
+        str++;
+        str_len--;
+    }
+    
+    return count;
+}
+
+static int MakeFastNames_add_name (NAMES_PARAM_TYPE *o, NCDStringIndex *string_index, const char *str, size_t str_len, const char *remain, size_t remain_len)
+{
+    ASSERT(str)
+    ASSERT(!!o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES == (o->NAMES_PARAM_MEMBER_NUM_NAMES > NAMES_PARAM_NUM_STATIC_NAMES))
+    
+    NCD_string_id_t id = NCDStringIndex_GetBin(string_index, str, str_len);
+    if (id < 0) {
+        return 0;
+    }
+    
+    if (o->NAMES_PARAM_MEMBER_NUM_NAMES < NAMES_PARAM_NUM_STATIC_NAMES) {
+        o->NAMES_PARAM_MEMBER_STATIC_NAMES[o->NAMES_PARAM_MEMBER_NUM_NAMES++] = id;
+        return 1;
+    }
+    
+    if (o->NAMES_PARAM_MEMBER_NUM_NAMES == NAMES_PARAM_NUM_STATIC_NAMES) {
+        size_t num_more = (!remain ? 0 : MakeFastNames_count_names(remain, remain_len));
+        size_t num_all = o->NAMES_PARAM_MEMBER_NUM_NAMES + 1 + num_more;
+        
+        if (!(o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES = BAllocArray(num_all, sizeof(o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES[0])))) {
+            return 0;
+        }
+        
+        memcpy(o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES, o->NAMES_PARAM_MEMBER_STATIC_NAMES, NAMES_PARAM_NUM_STATIC_NAMES * sizeof(o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES[0]));
+    }
+    
+    o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES[o->NAMES_PARAM_MEMBER_NUM_NAMES++] = id;
+    
+    return 1;
+}
+
+static int MakeFastNames_InitNames (NAMES_PARAM_TYPE *o, NCDStringIndex *string_index, const char *str, size_t str_len)
+{
+    ASSERT(str)
+    
+    o->NAMES_PARAM_MEMBER_NUM_NAMES = 0;
+    o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES = NULL;
+    
+    size_t i = 0;
+    while (i < str_len) {
+        if (str[i] == '.') {
+            if (!MakeFastNames_add_name(o, string_index, str, i, str + (i + 1), str_len - (i + 1))) {
+                goto fail;
+            }
+            str += i + 1;
+            str_len -= i + 1;
+            i = 0;
+            continue;
+        }
+        i++;
+    }
+    
+    if (!MakeFastNames_add_name(o, string_index, str, i, NULL, 0)) {
+        goto fail;
+    }
+    
+    return 1;
+    
+fail:
+    BFree(o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES);
+    return 0;
+}
+
+static void MakeFastNames_FreeNames (NAMES_PARAM_TYPE *o)
+{
+    if (o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES) {
+        BFree(o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES);
+    }
+}
+
+static NCD_string_id_t * MakeFastNames_GetNames (NAMES_PARAM_TYPE *o)
+{
+    ASSERT(o->NAMES_PARAM_MEMBER_NUM_NAMES > 0)
+    
+    return (o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES ? o->NAMES_PARAM_MEMBER_DYNAMIC_NAMES : o->NAMES_PARAM_MEMBER_STATIC_NAMES);
+}
+
+#undef MakeFastNames_count_names
+#undef MakeFastNames_add_name
+#undef MakeFastNames_InitNames
+#undef MakeFastNames_FreeNames
+#undef MakeFastNames_GetNames
+
+#undef NAMES_PARAM_NAME
+#undef NAMES_PARAM_TYPE
+#undef NAMES_PARAM_MEMBER_DYNAMIC_NAMES
+#undef NAMES_PARAM_MEMBER_STATIC_NAMES
+#undef NAMES_PARAM_MEMBER_NUM_NAMES
+#undef NAMES_PARAM_NUM_STATIC_NAMES
diff --git a/external/badvpn_dns/ncd/extra/value_utils.h b/external/badvpn_dns/ncd/extra/value_utils.h
new file mode 100644
index 0000000..2d01148
--- /dev/null
+++ b/external/badvpn_dns/ncd/extra/value_utils.h
@@ -0,0 +1,174 @@
+/**
+ * @file value_utils.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 NCD_VALUE_UTILS_H
+#define NCD_VALUE_UTILS_H
+
+#include <stdint.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/parse_number.h>
+#include <misc/strdup.h>
+#include <misc/balloc.h>
+#include <system/BTime.h>
+#include <ncd/NCDVal.h>
+#include <ncd/NCDStringIndex.h>
+#include <ncd/static_strings.h>
+
+static int ncd_is_none (NCDValRef val);
+static NCDValRef ncd_make_boolean (NCDValMem *mem, int value, NCDStringIndex *string_index);
+static int ncd_read_boolean (NCDValRef val);
+static int ncd_read_uintmax (NCDValRef string, uintmax_t *out) WARN_UNUSED;
+static int ncd_read_time (NCDValRef string, btime_t *out) WARN_UNUSED;
+static NCD_string_id_t ncd_get_string_id (NCDValRef string, NCDStringIndex *string_index);
+static NCDValRef ncd_make_uintmax (NCDValMem *mem, uintmax_t value);
+static char * ncd_strdup (NCDValRef stringnonulls);
+
+static int ncd_is_none (NCDValRef string)
+{
+    ASSERT(NCDVal_IsString(string))
+    
+    if (NCDVal_IsIdString(string)) {
+        return NCDVal_IdStringId(string) == NCD_STRING_NONE;
+    } else {
+        return NCDVal_StringEquals(string, "<none>");
+    }
+}
+
+static NCDValRef ncd_make_boolean (NCDValMem *mem, int value, NCDStringIndex *string_index)
+{
+    ASSERT(mem)
+    ASSERT(string_index)
+    
+    NCD_string_id_t str_id = (value ? NCD_STRING_TRUE : NCD_STRING_FALSE);
+    return NCDVal_NewIdString(mem, str_id, string_index);
+}
+
+static int ncd_read_boolean (NCDValRef string)
+{
+    ASSERT(NCDVal_IsString(string))
+    
+    if (NCDVal_IsIdString(string)) {
+        return NCDVal_IdStringId(string) == NCD_STRING_TRUE;
+    } else {
+        return NCDVal_StringEquals(string, "true");
+    }
+}
+
+static int ncd_read_uintmax (NCDValRef string, uintmax_t *out)
+{
+    ASSERT(NCDVal_IsString(string))
+    ASSERT(out)
+    
+    size_t length = NCDVal_StringLength(string);
+    
+    if (NCDVal_IsContinuousString(string)) {
+        return parse_unsigned_integer_bin(NCDVal_StringData(string), length, out);
+    }
+    
+    b_cstring cstr = NCDVal_StringCstring(string);
+    
+    return parse_unsigned_integer_cstr(cstr, 0, cstr.length, out);
+}
+
+static int ncd_read_time (NCDValRef string, btime_t *out)
+{
+    ASSERT(NCDVal_IsString(string))
+    ASSERT(out)
+    
+    uintmax_t n;
+    if (!ncd_read_uintmax(string, &n)) {
+        return 0;
+    }
+    
+    if (n > INT64_MAX) {
+        return 0;
+    }
+    
+    *out = n;
+    return 1;
+}
+
+static NCD_string_id_t ncd_get_string_id (NCDValRef string, NCDStringIndex *string_index)
+{
+    ASSERT(NCDVal_IsString(string))
+    ASSERT(string_index)
+    
+    if (NCDVal_IsIdString(string)) {
+        return NCDVal_IdStringId(string);
+    } else if (NCDVal_IsContinuousString(string)) {
+        return NCDStringIndex_GetBin(string_index, NCDVal_StringData(string), NCDVal_StringLength(string));
+    }
+    
+    b_cstring cstr = NCDVal_StringCstring(string);
+    
+    char *temp = b_cstring_strdup(cstr, 0, cstr.length);
+    if (!temp) {
+        return -1;
+    }
+    
+    NCD_string_id_t res = NCDStringIndex_GetBin(string_index, temp, cstr.length);
+    BFree(temp);
+    
+    return res;
+}
+
+static NCDValRef ncd_make_uintmax (NCDValMem *mem, uintmax_t value)
+{
+    ASSERT(mem)
+    
+    int size = compute_decimal_repr_size(value);
+    
+    NCDValRef val = NCDVal_NewStringUninitialized(mem, size);
+    
+    if (!NCDVal_IsInvalid(val)) {
+        char *data = (char *)NCDVal_StringData(val);
+        generate_decimal_repr(value, data, size);
+    }
+    
+    return val;
+}
+
+static char * ncd_strdup (NCDValRef stringnonulls)
+{
+    ASSERT(NCDVal_IsStringNoNulls(stringnonulls))
+    
+    size_t length = NCDVal_StringLength(stringnonulls);
+    
+    if (NCDVal_IsContinuousString(stringnonulls)) {
+        return b_strdup_bin(NCDVal_StringData(stringnonulls), length);
+    }
+    
+    b_cstring cstr = NCDVal_StringCstring(stringnonulls);
+    
+    return b_cstring_strdup(cstr, 0, cstr.length);
+}
+
+#endif
diff --git a/external/badvpn_dns/ncd/include_linux_input.c b/external/badvpn_dns/ncd/include_linux_input.c
new file mode 100644
index 0000000..8eb6c02
--- /dev/null
+++ b/external/badvpn_dns/ncd/include_linux_input.c
@@ -0,0 +1 @@
+#include <linux/input.h>
diff --git a/external/badvpn_dns/ncd/make_name_indices.h b/external/badvpn_dns/ncd/make_name_indices.h
new file mode 100644
index 0000000..b5c7c8e
--- /dev/null
+++ b/external/badvpn_dns/ncd/make_name_indices.h
@@ -0,0 +1,104 @@
+/**
+ * @file make_name_indices.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_MAKE_NAME_INDICES_H
+#define BADVPN_MAKE_NAME_INDICES_H
+
+#include <stdlib.h>
+#include <stddef.h>
+
+#include <misc/strdup.h>
+#include <misc/balloc.h>
+#include <misc/debug.h>
+#include <ncd/NCDStringIndex.h>
+
+static int ncd_make_name_indices (NCDStringIndex *string_index, const char *name, NCD_string_id_t **out_varnames, size_t *out_num_names) WARN_UNUSED;
+
+static size_t split_string_inplace2__indices (char *str, char del)
+{
+    ASSERT(str)
+    
+    size_t num_extra_parts = 0;
+    
+    while (*str) {
+        if (*str == del) {
+            *str = '\0';
+            num_extra_parts++;
+        }
+        str++;
+    }
+    
+    return num_extra_parts;
+}
+
+static int ncd_make_name_indices (NCDStringIndex *string_index, const char *name, NCD_string_id_t **out_varnames, size_t *out_num_names)
+{
+    ASSERT(string_index)
+    ASSERT(name)
+    ASSERT(out_varnames)
+    ASSERT(out_num_names)
+    
+    char *data = b_strdup(name);
+    if (!data) {
+        goto fail0;
+    }
+    
+    size_t num_names = split_string_inplace2__indices(data, '.') + 1;
+    
+    NCD_string_id_t *varnames = BAllocArray(num_names, sizeof(varnames[0]));
+    if (!varnames) {
+        goto fail1;
+    }
+    
+    char *cur = data;
+    for (size_t i = 0; i < num_names; i++) {
+        NCD_string_id_t id = NCDStringIndex_Get(string_index, cur);
+        if (id < 0) {
+            goto fail2;
+        }
+        
+        varnames[i] = id;
+        cur += strlen(cur) + 1;
+    }
+    
+    free(data);
+    
+    *out_varnames = varnames;
+    *out_num_names = num_names;
+    return 1;
+    
+fail2:
+    BFree(varnames);
+fail1:
+    free(data);
+fail0:
+    return 0;
+}
+
+#endif
diff --git a/external/badvpn_dns/ncd/modules/alias.c b/external/badvpn_dns/ncd/modules/alias.c
new file mode 100644
index 0000000..f6bb7ec
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/alias.c
@@ -0,0 +1,148 @@
+/**
+ * @file alias.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   alias(string target)
+ * 
+ * Variables and objects:
+ *   - empty name - resolves target
+ *   - nonempty name N - resolves target.N
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_alias.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define NUM_STATIC_NAMES 4
+
+struct instance {
+    NCDModuleInst *i;
+    NCD_string_id_t *dynamic_names;
+    size_t num_names;
+    NCD_string_id_t static_names[NUM_STATIC_NAMES];
+};
+
+#define NAMES_PARAM_NAME AliasNames
+#define NAMES_PARAM_TYPE struct instance
+#define NAMES_PARAM_MEMBER_DYNAMIC_NAMES dynamic_names
+#define NAMES_PARAM_MEMBER_STATIC_NAMES static_names
+#define NAMES_PARAM_MEMBER_NUM_NAMES num_names
+#define NAMES_PARAM_NUM_STATIC_NAMES NUM_STATIC_NAMES
+#include <ncd/extra/make_fast_names.h>
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef target_arg;
+    if (!NCDVal_ListRead(params->args, 1, &target_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(target_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse name string
+    if (!AliasNames_InitNames(o, i->params->iparams->string_index, NCDVal_StringData(target_arg), NCDVal_StringLength(target_arg))) {
+        ModuleLog(i, BLOG_ERROR, "make_names failed");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    AliasNames_FreeNames(o);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getobj (void *vo, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = vo;
+    ASSERT(o->num_names > 0)
+    
+    NCD_string_id_t *names = AliasNames_GetNames(o);
+    
+    NCDObject object;
+    if (!NCDModuleInst_Backend_GetObj(o->i, names[0], &object)) {
+        return 0;
+    }
+    
+    NCDObject obj2;
+    if (!NCDObject_ResolveObjExprCompact(&object, names + 1, o->num_names - 1, &obj2)) {
+        return 0;
+    }
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out_object = obj2;
+        return 1;
+    }
+    
+    return NCDObject_GetObj(&obj2, name, out_object);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "alias",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getobj = func_getobj,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_alias = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/arithmetic.c b/external/badvpn_dns/ncd/modules/arithmetic.c
new file mode 100644
index 0000000..288a637
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/arithmetic.c
@@ -0,0 +1,404 @@
+/**
+ * @file arithmetic.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Arithmetic functions for unsigned integers.
+ * 
+ * Synopsis:
+ *   num_lesser(string n1, string n2)
+ *   num_greater(string n1, string n2)
+ *   num_lesser_equal(string n1, string n2)
+ *   num_greater_equal(string n1, string n2)
+ *   num_equal(string n1, string n2)
+ *   num_different(string n1, string n2)
+ * 
+ * Variables:
+ *   (empty) - "true" or "false", reflecting the value of the relation in question
+ * 
+ * Description:
+ *   These statements perform arithmetic comparisons. The operands passed must be
+ *   non-negative decimal integers representable in a uintmax_t. Otherwise, an error
+ *   is triggered.
+ * 
+ * Synopsis:
+ *   num_add(string n1, string n2)
+ *   num_subtract(string n1, string n2)
+ *   num_multiply(string n1, string n2)
+ *   num_divide(string n1, string n2)
+ *   num_modulo(string n1, string n2)
+ * 
+ * Description:
+ *   These statements perform arithmetic operations. The operands passed must be
+ *   non-negative decimal integers representable in a uintmax_t, and the result must
+ *   also be representable and non-negative. For divide and modulo, n2 must be non-zero.
+ *   If any of these restrictions is violated, an error is triggered.
+ * 
+ * Variables:
+ *   (empty) - the result of the operation as a string representing a decimal number
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#include <misc/parse_number.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_arithmetic.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct boolean_instance {
+    NCDModuleInst *i;
+    int value;
+};
+
+typedef int (*boolean_compute_func) (uintmax_t n1, uintmax_t n2);
+
+struct number_instance {
+    NCDModuleInst *i;
+    uintmax_t value;
+};
+
+typedef int (*number_compute_func) (NCDModuleInst *i, uintmax_t n1, uintmax_t n2, uintmax_t *out);
+
+static int compute_lesser (uintmax_t n1, uintmax_t n2)
+{
+    return n1 < n2;
+}
+
+static int compute_greater (uintmax_t n1, uintmax_t n2)
+{
+    return n1 > n2;
+}
+
+static int compute_lesser_equal (uintmax_t n1, uintmax_t n2)
+{
+    return n1 <= n2;
+}
+
+static int compute_greater_equal (uintmax_t n1, uintmax_t n2)
+{
+    return n1 >= n2;
+}
+
+static int compute_equal (uintmax_t n1, uintmax_t n2)
+{
+    return n1 == n2;
+}
+
+static int compute_different (uintmax_t n1, uintmax_t n2)
+{
+    return n1 != n2;
+}
+
+static int compute_add (NCDModuleInst *i, uintmax_t n1, uintmax_t n2, uintmax_t *out)
+{
+    if (n1 > UINTMAX_MAX - n2) {
+        ModuleLog(i, BLOG_ERROR, "addition overflow");
+        return 0;
+    }
+    *out = n1 + n2;
+    return 1;
+}
+
+static int compute_subtract (NCDModuleInst *i, uintmax_t n1, uintmax_t n2, uintmax_t *out)
+{
+    if (n1 < n2) {
+        ModuleLog(i, BLOG_ERROR, "subtraction underflow");
+        return 0;
+    }
+    *out = n1 - n2;
+    return 1;
+}
+
+static int compute_multiply (NCDModuleInst *i, uintmax_t n1, uintmax_t n2, uintmax_t *out)
+{
+    if (n1 > UINTMAX_MAX / n2) {
+        ModuleLog(i, BLOG_ERROR, "multiplication overflow");
+        return 0;
+    }
+    *out = n1 * n2;
+    return 1;
+}
+
+static int compute_divide (NCDModuleInst *i, uintmax_t n1, uintmax_t n2, uintmax_t *out)
+{
+    if (n2 == 0) {
+        ModuleLog(i, BLOG_ERROR, "division quotient is zero");
+        return 0;
+    }
+    *out = n1 / n2;
+    return 1;
+}
+
+static int compute_modulo (NCDModuleInst *i, uintmax_t n1, uintmax_t n2, uintmax_t *out)
+{
+    if (n2 == 0) {
+        ModuleLog(i, BLOG_ERROR, "modulo modulus is zero");
+        return 0;
+    }
+    *out = n1 % n2;
+    return 1;
+}
+
+static void new_boolean_templ (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, boolean_compute_func cfunc)
+{
+    struct boolean_instance *o = vo;
+    o->i = i;
+    
+    NCDValRef n1_arg;
+    NCDValRef n2_arg;
+    if (!NCDVal_ListRead(params->args, 2, &n1_arg, &n2_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(n1_arg) || !NCDVal_IsString(n2_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    uintmax_t n1;
+    if (!ncd_read_uintmax(n1_arg, &n1)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong first argument");
+        goto fail0;
+    }
+    
+    uintmax_t n2;
+    if (!ncd_read_uintmax(n2_arg, &n2)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong second argument");
+        goto fail0;
+    }
+    
+    o->value = cfunc(n1, n2);
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static int boolean_func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct boolean_instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = ncd_make_boolean(mem, o->value, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void new_number_templ (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, number_compute_func cfunc)
+{
+    struct number_instance *o = vo;
+    o->i = i;
+    
+    NCDValRef n1_arg;
+    NCDValRef n2_arg;
+    if (!NCDVal_ListRead(params->args, 2, &n1_arg, &n2_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(n1_arg) || !NCDVal_IsString(n2_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    uintmax_t n1;
+    if (!ncd_read_uintmax(n1_arg, &n1)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong first argument");
+        goto fail0;
+    }
+    
+    uintmax_t n2;
+    if (!ncd_read_uintmax(n2_arg, &n2)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong second argument");
+        goto fail0;
+    }
+    
+    if (!cfunc(i, n1, n2, &o->value)) {
+        goto fail0;
+    }
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static int number_func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct number_instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = ncd_make_uintmax(mem, o->value);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void func_new_lesser (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_boolean_templ(vo, i, params, compute_lesser);
+}
+
+static void func_new_greater (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_boolean_templ(vo, i, params, compute_greater);
+}
+
+static void func_new_lesser_equal (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_boolean_templ(vo, i, params, compute_lesser_equal);
+}
+
+static void func_new_greater_equal (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_boolean_templ(vo, i, params, compute_greater_equal);
+}
+
+static void func_new_equal (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_boolean_templ(vo, i, params, compute_equal);
+}
+
+static void func_new_different (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_boolean_templ(vo, i, params, compute_different);
+}
+
+static void func_new_add (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_number_templ(vo, i, params, compute_add);
+}
+
+static void func_new_subtract (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_number_templ(vo, i, params, compute_subtract);
+}
+
+static void func_new_multiply (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_number_templ(vo, i, params, compute_multiply);
+}
+
+static void func_new_divide (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_number_templ(vo, i, params, compute_divide);
+}
+
+static void func_new_modulo (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_number_templ(vo, i, params, compute_modulo);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "num_lesser",
+        .func_new2 = func_new_lesser,
+        .func_getvar2 = boolean_func_getvar2,
+        .alloc_size = sizeof(struct boolean_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_greater",
+        .func_new2 = func_new_greater,
+        .func_getvar2 = boolean_func_getvar2,
+        .alloc_size = sizeof(struct boolean_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_lesser_equal",
+        .func_new2 = func_new_lesser_equal,
+        .func_getvar2 = boolean_func_getvar2,
+        .alloc_size = sizeof(struct boolean_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_greater_equal",
+        .func_new2 = func_new_greater_equal,
+        .func_getvar2 = boolean_func_getvar2,
+        .alloc_size = sizeof(struct boolean_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_equal",
+        .func_new2 = func_new_equal,
+        .func_getvar2 = boolean_func_getvar2,
+        .alloc_size = sizeof(struct boolean_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_different",
+        .func_new2 = func_new_different,
+        .func_getvar2 = boolean_func_getvar2,
+        .alloc_size = sizeof(struct boolean_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_add",
+        .func_new2 = func_new_add,
+        .func_getvar2 = number_func_getvar2,
+        .alloc_size = sizeof(struct number_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_subtract",
+        .func_new2 = func_new_subtract,
+        .func_getvar2 = number_func_getvar2,
+        .alloc_size = sizeof(struct number_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_multiply",
+        .func_new2 = func_new_multiply,
+        .func_getvar2 = number_func_getvar2,
+        .alloc_size = sizeof(struct number_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_divide",
+        .func_new2 = func_new_divide,
+        .func_getvar2 = number_func_getvar2,
+        .alloc_size = sizeof(struct number_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "num_modulo",
+        .func_new2 = func_new_modulo,
+        .func_getvar2 = number_func_getvar2,
+        .alloc_size = sizeof(struct number_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_arithmetic = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/assert.c b/external/badvpn_dns/ncd/modules/assert.c
new file mode 100644
index 0000000..75205c5
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/assert.c
@@ -0,0 +1,105 @@
+/**
+ * @file assert.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   assert(string cond)
+ *   assert_false(string cond)
+ * 
+ * Description:
+ *   If 'cond' is equal to the string "true" (assert) or "false" (assert_false),
+ *   does nothing. Otherwise, logs an error and initiates interpreter termination
+ *   with exit code 1, i.e. it is equivalent to calling exit("1").
+ *   Note that "assert_false(cond);" is not completely equivalent to
+ *   "not(cond) a; assert(a);", in case 'cond'  is something other than "true"
+ *   or "false".
+ */
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_assert.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void func_new_common (NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_false)
+{
+    // check arguments
+    NCDValRef cond_arg;
+    if (!NCDVal_ListRead(params->args, 1, &cond_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(cond_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    
+    // if failed, initiate exit (before up!)
+    if ((!is_false && !NCDVal_StringEqualsId(cond_arg, NCD_STRING_TRUE, i->params->iparams->string_index)) ||
+        (is_false && !NCDVal_StringEqualsId(cond_arg, NCD_STRING_FALSE, i->params->iparams->string_index))
+    ) {
+        ModuleLog(i, BLOG_ERROR, "assertion failed");
+        NCDModuleInst_Backend_InterpExit(i, 1);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_common(i, params, 0);
+}
+
+static void func_new_false (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_common(i, params, 1);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "assert",
+        .func_new2 = func_new
+    }, {
+        .type = "assert_false",
+        .func_new2 = func_new_false
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_assert = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/backtrack.c b/external/badvpn_dns/ncd/modules/backtrack.c
new file mode 100644
index 0000000..ec9c5d7
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/backtrack.c
@@ -0,0 +1,103 @@
+/**
+ * @file backtrack.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   backtrack_point()
+ *   backtrack_point::go()
+ * 
+ * Description:
+ *   The backtrack_point() statement creates a backtrack point, going up immedietely.
+ *   The go() method triggers backtracking to the backtrack point, i.e. makes the
+ *   backtrack_point() statement go down and back up at atomically. The go() method
+ *   itself goes up immedietely, but side effects of triggering backtracking have
+ *   priority.
+ */
+
+#include <stddef.h>
+
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_backtrack.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void go_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get backtrack point
+    NCDModuleInst *backtrack_point_inst = params->method_user;
+    
+    // go up (after toggling)
+    NCDModuleInst_Backend_Up(i);
+    
+    // toggle backtrack point
+    NCDModuleInst_Backend_DownUp(backtrack_point_inst);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "backtrack_point",
+        .func_new2 = func_new
+    }, {
+        .type = "backtrack_point::go",
+        .func_new2 = go_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_backtrack = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/blocker.c b/external/badvpn_dns/ncd/modules/blocker.c
new file mode 100644
index 0000000..b742d72
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/blocker.c
@@ -0,0 +1,353 @@
+/**
+ * @file blocker.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Blocker module. Provides a statement that blocks when initialized, and which can be blocked
+ * and unblocked from outside.
+ * 
+ * Synopsis: blocker()
+ * Description: provides blocking operations. Initially the blocking state is down (but this statement
+ *              does not block). On deinitialization, waits for all corresponding use() statements
+ *              to die before dying itself.
+ * 
+ * Synopsis: blocker::up()
+ * Description: sets the blocking state to up.
+ *              The immediate effects of corresponding use() statements going up are processed before
+ *              this statement goes up; but this statement statement still goes up immediately,
+ *              assuming the effects mentioned haven't resulted in the intepreter scheduling this
+ *              very statement for destruction.
+ * 
+ * Synopsis: blocker::down()
+ * Description: sets the blocking state to down.
+ *              The immediate effects of corresponding use() statements going up are processed before
+ *              this statement goes up; but this statement statement still goes up immediately,
+ *              assuming the effects mentioned haven't resulted in the intepreter scheduling this
+ *              very statement for destruction.
+ * 
+ * Synopsis: blocker::downup()
+ * Description: atomically sets the blocker to down state (if it was up), then (back) to up state.
+ *              Note that this is not equivalent to calling down() and immediately up(); in that case,
+ *              the interpreter will first handle the immediate effects of any use() statements
+ *              going down as a result of having called down() and will only later execute the up()
+ *              statement. In fact, it is possible that the effects of down() will prevent up() from
+ *              executing, which may leave the program in an undesirable state.
+ * 
+ * Synopsis: blocker::rdownup()
+ * Description: on deinitialization, atomically sets the blocker to down state (if it was up), then
+ *              (back) to up state.
+ *              The immediate effects of corresponding use() statements changing state are processed
+ *              *after* the immediate effects of this statement dying (in contrast to downup()).
+ * 
+ * Synopsis: blocker::use()
+ * Description: blocks on the blocker. This module is in up state if and only if the blocking state of
+ * the blocker is up. Multiple use statements may be used with the same blocker.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <structure/LinkedList1.h>
+#include <structure/LinkedList0.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_blocker.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    LinkedList1 users;
+    LinkedList0 rdownups_list;
+    int up;
+    int dying;
+};
+
+struct rdownup_instance {
+    NCDModuleInst *i;
+    struct instance *blocker;
+    LinkedList0Node rdownups_list_node;
+};
+
+struct use_instance {
+    NCDModuleInst *i;
+    struct instance *blocker;
+    LinkedList1Node blocker_node;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // init users list
+    LinkedList1_Init(&o->users);
+    
+    // init rdownups list
+    LinkedList0_Init(&o->rdownups_list);
+    
+    // set not up
+    o->up = 0;
+    
+    // set not dying
+    o->dying = 0;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o)
+{
+    ASSERT(LinkedList1_IsEmpty(&o->users))
+    
+    // break any rdownups
+    LinkedList0Node *ln;
+    while (ln = LinkedList0_GetFirst(&o->rdownups_list)) {
+        struct rdownup_instance *rdu = UPPER_OBJECT(ln, struct rdownup_instance, rdownups_list_node);
+        ASSERT(rdu->blocker == o)
+        LinkedList0_Remove(&o->rdownups_list, &rdu->rdownups_list_node);
+        rdu->blocker = NULL;
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(!o->dying)
+    
+    // if we have no users, die right away, else wait for users
+    if (LinkedList1_IsEmpty(&o->users)) {
+        instance_free(o);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+}
+
+static void updown_func_new_templ (NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int up, int first_down)
+{
+    ASSERT(!first_down || up)
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    if (first_down || mo->up != up) {
+        // signal users
+        for (LinkedList1Node *node = LinkedList1_GetFirst(&mo->users); node; node = LinkedList1Node_Next(node)) {
+            struct use_instance *user = UPPER_OBJECT(node, struct use_instance, blocker_node);
+            ASSERT(user->blocker == mo)
+            if (first_down && mo->up) {
+                NCDModuleInst_Backend_Down(user->i);
+            }
+            if (up) {
+                NCDModuleInst_Backend_Up(user->i);
+            } else {
+                NCDModuleInst_Backend_Down(user->i);
+            }
+        }
+        
+        // change up state
+        mo->up = up;
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void up_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    updown_func_new_templ(i, params, 1, 0);
+}
+
+static void down_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    updown_func_new_templ(i, params, 0, 0);
+}
+
+static void downup_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    updown_func_new_templ(i, params, 1, 1);
+}
+
+static void rdownup_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct rdownup_instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get blocker
+    struct instance *blk = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // set blocker
+    o->blocker = blk;
+    
+    // insert to rdownups list
+    LinkedList0_Prepend(&blk->rdownups_list, &o->rdownups_list_node);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void rdownup_func_die (void *vo)
+{
+    struct rdownup_instance *o = vo;
+    
+    struct instance *blk = o->blocker;
+    
+    if (blk) {
+        // remove from rdownups list
+        LinkedList0_Remove(&blk->rdownups_list, &o->rdownups_list_node);
+        
+        // downup users
+        for (LinkedList1Node *ln = LinkedList1_GetFirst(&blk->users); ln; ln = LinkedList1Node_Next(ln)) {
+            struct use_instance *user = UPPER_OBJECT(ln, struct use_instance, blocker_node);
+            ASSERT(user->blocker == blk)
+            if (blk->up) {
+                NCDModuleInst_Backend_Down(user->i);
+            }
+            NCDModuleInst_Backend_Up(user->i);
+        }
+        
+        // set up
+        blk->up = 1;
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void use_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct use_instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // set blocker
+    o->blocker = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // add to blocker's list
+    LinkedList1_Append(&o->blocker->users, &o->blocker_node);
+    
+    // signal up if needed
+    if (o->blocker->up) {
+        NCDModuleInst_Backend_Up(o->i);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void use_func_die (void *vo)
+{
+    struct use_instance *o = vo;
+    
+    // remove from blocker's list
+    LinkedList1_Remove(&o->blocker->users, &o->blocker_node);
+    
+    // make the blocker die if needed
+    if (o->blocker->dying && LinkedList1_IsEmpty(&o->blocker->users)) {
+        instance_free(o->blocker);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "blocker",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "blocker::up",
+        .func_new2 = up_func_new
+    }, {
+        .type = "blocker::down",
+        .func_new2 = down_func_new
+    }, {
+        .type = "blocker::downup",
+        .func_new2 = downup_func_new
+    }, {
+        .type = "blocker::rdownup",
+        .func_new2 = rdownup_func_new,
+        .func_die = rdownup_func_die,
+        .alloc_size = sizeof(struct rdownup_instance)
+    }, {
+        .type = "blocker::use",
+        .func_new2 = use_func_new,
+        .func_die = use_func_die,
+        .alloc_size = sizeof(struct use_instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_blocker = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/buffer.c b/external/badvpn_dns/ncd/modules/buffer.c
new file mode 100644
index 0000000..eeb3715
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/buffer.c
@@ -0,0 +1,619 @@
+/**
+ * @file buffer.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   buffer([string data])
+ * 
+ * Variables:
+ *   string (empty) - data in the buffer
+ *   string length - number of bytes in the buffer
+ * 
+ * Description:
+ *   Implements an array of bytes which supports appending bytes and removing
+ *   bytes from the beginning. The buffer is implemented using chunks;
+ *   the time complexity of operations depends on the number of chunks affected,
+ *   and not on the actual number of bytes. Each append operation produces a single
+ *   chunk. In particular:
+ * 
+ *   Complexity of append and construction:
+ *     log(total number of chunks) + (time for copying data).
+ *   Complexity of consume:
+ *     log(total number of chunks) * (1 + (number of chunks in consumed range))
+ *   Complexity of referencing and unreferencing a range:
+ *     log(total number of chunks) * (1 + (number of chunks in referenced range))
+ * 
+ * Synopsis:
+ *   buffer::append(string data)
+ * 
+ * Description:
+ *   Appends the given data to the end of the buffer.
+ * 
+ * Synopsis:
+ *   buffer::consume(string amount)
+ * 
+ * Description:
+ *   Removes the specified number of bytes from the beginning of the buffer.
+ *   'amount' must not be larger than the current length of the buffer.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <misc/compare.h>
+#include <misc/offset.h>
+#include <structure/SAvl.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_buffer.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct chunk;
+
+#include "buffer_chunks_tree.h"
+#include <structure/SAvl_decl.h>
+
+struct buffer {
+    struct instance *inst;
+    ChunksTree chunks_tree;
+    int refcnt;
+};
+
+struct chunk {
+    struct buffer *buf;
+    size_t offset;
+    size_t length;
+    ChunksTreeNode chunks_tree_node;
+    int refcnt;
+    char data[];
+};
+
+struct reference {
+    struct chunk *first_chunk;
+    size_t first_offset;
+    size_t length;
+    BRefTarget ref_target;
+};
+
+struct instance {
+    NCDModuleInst *i;
+    size_t offset;
+    size_t total_length;
+    struct buffer *buf;
+};
+
+#include "buffer_chunks_tree.h"
+#include <structure/SAvl_impl.h>
+
+static void instance_assert (struct instance *inst);
+static int instance_append (struct instance *inst, NCDValRef string);
+static void instance_consume (struct instance *inst, size_t amount);
+static struct buffer * buffer_init (struct instance *inst, NCDModuleInst *i);
+static void buffer_free (struct buffer *buf);
+static void buffer_detach (struct buffer *buf);
+static struct chunk * buffer_get_existing_chunk (struct buffer *buf, size_t offset);
+static struct chunk * chunk_init (struct instance *inst, size_t length);
+static void chunk_unref (struct chunk *c);
+static void chunk_assert (struct chunk *c);
+static struct reference * reference_init (struct instance *inst, size_t offset, size_t length, NCDValComposedStringResource *out_resource);
+static void reference_ref_target_func_release (BRefTarget *ref_target);
+static void reference_assert (struct reference *ref);
+static void reference_resource_func_getptr (void *user, size_t offset, const char **out_data, size_t *out_length);
+
+static void instance_assert (struct instance *inst)
+{
+    ASSERT(inst->buf->inst == inst)
+}
+
+static int instance_append (struct instance *inst, NCDValRef string)
+{
+    instance_assert(inst);
+    ASSERT(NCDVal_IsString(string))
+    
+    size_t length = NCDVal_StringLength(string);
+    
+    // if string is empty do nothing, we can't make an empty chunk
+    if (length == 0) {
+        return 1;
+    }
+    
+    // init chunk
+    struct chunk *c = chunk_init(inst, length);
+    if (!c) {
+        return 0;
+    }
+    
+    // copy data to chunk
+    NCDVal_StringCopyOut(string, 0, length, c->data);
+    
+    return 1;
+}
+
+static void instance_consume (struct instance *inst, size_t amount)
+{
+    instance_assert(inst);
+    ASSERT(amount <= inst->total_length - inst->offset)
+    
+    // nothing do to if amount is zero
+    if (amount == 0) {
+        return;
+    }
+    
+    // find chunk where the byte in the buffer resides
+    struct chunk *c = buffer_get_existing_chunk(inst->buf, inst->offset);
+    
+    // increment buffer offset
+    inst->offset += amount;
+    
+    // unreference chunks which no longer contain buffer contents
+    while (c && c->offset + c->length <= inst->offset) {
+        struct chunk *next_c = ChunksTree_GetNext(&inst->buf->chunks_tree, 0, c);
+        chunk_unref(c);
+        c = next_c;
+    }
+}
+
+static struct buffer * buffer_init (struct instance *inst, NCDModuleInst *i)
+{
+    ASSERT(inst)
+    
+    // allocate structure
+    struct buffer *buf = BAlloc(sizeof(*buf));
+    if (!buf) {
+        ModuleLog(i, BLOG_ERROR, "BAlloc failed");
+        return NULL;
+    }
+    
+    // set instance pointer
+    buf->inst = inst;
+    
+    // init chunks tree
+    ChunksTree_Init(&buf->chunks_tree);
+    
+    // set refcnt to 0 (number of reference objects)
+    buf->refcnt = 0;
+    
+    return buf;
+}
+
+static void buffer_free (struct buffer *buf)
+{
+    ASSERT(!buf->inst)
+    ASSERT(ChunksTree_IsEmpty(&buf->chunks_tree))
+    ASSERT(buf->refcnt == 0)
+    
+    // free structure
+    BFree(buf);
+}
+
+static void buffer_detach (struct buffer *buf)
+{
+    ASSERT(buf->inst)
+    struct instance *inst = buf->inst;
+    
+    // consume entire buffer to free any chunks that aren't referenced
+    instance_consume(inst, inst->total_length - inst->offset);
+    
+    // clear instance pointer
+    buf->inst = NULL;
+    
+    // free buffer if there are no more chunks
+    if (ChunksTree_IsEmpty(&buf->chunks_tree)) {
+        buffer_free(buf);
+    }
+}
+
+static struct chunk * buffer_get_existing_chunk (struct buffer *buf, size_t offset)
+{
+    struct chunk *c = ChunksTree_GetLastLesserEqual(&buf->chunks_tree, 0, offset);
+    
+    ASSERT(c)
+    chunk_assert(c);
+    ASSERT(offset >= c->offset)
+    ASSERT(offset < c->offset + c->length)
+    
+    return c;
+}
+
+static struct chunk * chunk_init (struct instance *inst, size_t length)
+{
+    instance_assert(inst);
+    ASSERT(length > 0)
+    struct buffer *buf = inst->buf;
+    
+    // make sure length is not too large
+    if (length >= SIZE_MAX - inst->total_length) {
+        ModuleLog(inst->i, BLOG_ERROR, "length overflow");
+        return NULL;
+    }
+    
+    // allocate structure
+    bsize_t size = bsize_add(bsize_fromsize(sizeof(struct chunk)), bsize_fromsize(length));
+    struct chunk *c = BAllocSize(size);
+    if (!c) {
+        ModuleLog(inst->i, BLOG_ERROR, "BAllocSize failed");
+        return NULL;
+    }
+    
+    // set some members
+    c->buf = buf;
+    c->offset = inst->total_length;
+    c->length = length;
+    
+    // insert into chunks tree
+    int res = ChunksTree_Insert(&buf->chunks_tree, 0, c, NULL);
+    B_ASSERT_USE(res)
+    
+    // set reference count to 1 (referenced by buffer contents)
+    c->refcnt = 1;
+    
+    // increment buffer length
+    inst->total_length += length;
+    
+    chunk_assert(c);
+    return c;
+}
+
+static void chunk_unref (struct chunk *c)
+{
+    chunk_assert(c);
+    
+    // decrement reference count
+    c->refcnt--;
+    
+    // if reference count is not yet zero, do nothing else
+    if (c->refcnt > 0) {
+        return;
+    }
+    
+    // remove from chunks tree
+    ChunksTree_Remove(&c->buf->chunks_tree, 0, c);
+    
+    // free structure
+    BFree(c);
+}
+
+static void chunk_assert (struct chunk *c)
+{
+    ASSERT(c->buf)
+    ASSERT(c->length > 0)
+    ASSERT(!c->buf->inst || c->offset <= c->buf->inst->total_length)
+    ASSERT(!c->buf->inst || c->length <= c->buf->inst->total_length - c->offset)
+    ASSERT(c->refcnt > 0)
+}
+
+static struct reference * reference_init (struct instance *inst, size_t offset, size_t length, NCDValComposedStringResource *out_resource)
+{
+    instance_assert(inst);
+    struct buffer *buf = inst->buf;
+    ASSERT(offset >= inst->offset)
+    ASSERT(offset <= inst->total_length)
+    ASSERT(length <= inst->total_length - offset)
+    ASSERT(length > 0)
+    ASSERT(out_resource)
+    
+    // check buffer reference count. This ensures we can always increment the
+    // chunk reference counts, below. We use (INT_MAX - 1) here because the buffer
+    // itself can also own references to chunks.
+    if (buf->refcnt == INT_MAX - 1) {
+        ModuleLog(inst->i, BLOG_ERROR, "too many references");
+        return NULL;
+    }
+    
+    // allocate structure
+    struct reference *ref = BAlloc(sizeof(*ref));
+    if (!ref) {
+        ModuleLog(inst->i, BLOG_ERROR, "BAlloc failed");
+        return NULL;
+    }
+    
+    // find chunk where the first byte of the interval resides
+    struct chunk *c = buffer_get_existing_chunk(buf, offset);
+    
+    // set some members
+    ref->first_chunk = c;
+    ref->first_offset = offset - c->offset;
+    ref->length = length;
+    
+    // increment buffer reference count
+    buf->refcnt++;
+    
+    // reference chunks
+    do {
+        struct chunk *next_c = ChunksTree_GetNext(&buf->chunks_tree, 0, c);
+        ASSERT(c->refcnt < INT_MAX)
+        c->refcnt++;
+        c = next_c;
+    } while (c && c->offset < offset + length);
+    
+    // init reference target
+    BRefTarget_Init(&ref->ref_target, reference_ref_target_func_release);
+    
+    // write resource
+    out_resource->func_getptr = reference_resource_func_getptr;
+    out_resource->user = ref;
+    out_resource->ref_target = &ref->ref_target;
+    
+    reference_assert(ref);
+    return ref;
+}
+
+static void reference_ref_target_func_release (BRefTarget *ref_target)
+{
+    struct reference *ref = UPPER_OBJECT(ref_target, struct reference, ref_target);
+    reference_assert(ref);
+    struct buffer *buf = ref->first_chunk->buf;
+    
+    // compute offset
+    size_t offset = ref->first_chunk->offset + ref->first_offset;
+    
+    // unreference chunks
+    struct chunk *c = ref->first_chunk;
+    do {
+        struct chunk *next_c = ChunksTree_GetNext(&buf->chunks_tree, 0, c);
+        chunk_unref(c);
+        c = next_c;
+    } while (c && c->offset < offset + ref->length);
+    
+    // decrement buffer reference count
+    ASSERT(buf->refcnt > 0)
+    buf->refcnt--;
+    
+    // free structure
+    BFree(ref);
+    
+    // if the instance has died and there are no more chunks, free buffer
+    if (!buf->inst && ChunksTree_IsEmpty(&buf->chunks_tree)) {
+        buffer_free(buf);
+    }
+}
+
+static void reference_assert (struct reference *ref)
+{
+    ASSERT(ref->first_chunk)
+    ASSERT(ref->first_offset < ref->first_chunk->length)
+    ASSERT(ref->length > 0)
+    chunk_assert(ref->first_chunk);
+}
+
+static void reference_resource_func_getptr (void *user, size_t offset, const char **out_data, size_t *out_length)
+{
+    struct reference *ref = user;
+    reference_assert(ref);
+    ASSERT(offset < ref->length)
+    ASSERT(out_data)
+    ASSERT(out_length)
+    
+    // compute absolute offset of request
+    size_t abs_offset = ref->first_chunk->offset + ref->first_offset + offset;
+    
+    // find chunk where the byte at the requested offset resides
+    struct chunk *c = buffer_get_existing_chunk(ref->first_chunk->buf, abs_offset);
+    
+    // compute offset of this byte within the chunk
+    size_t chunk_offset = abs_offset - c->offset;
+    
+    // return the data from this byte to the end of the chunk
+    *out_data = c->data + chunk_offset;
+    *out_length = c->length - chunk_offset;
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // pass instance pointer to methods
+    NCDModuleInst_Backend_PassMemToMethods(i);
+    
+    // read arguments
+    NCDValRef data_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 0) &&
+        !NCDVal_ListRead(params->args, 1, &data_arg)
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsInvalid(data_arg) && !NCDVal_IsString(data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // set offset and total length
+    o->offset = 0;
+    o->total_length = 0;
+    
+    // allocate buffer
+    o->buf = buffer_init(o, i);
+    if (!o->buf) {
+        goto fail0;
+    }
+    
+    // append initial data
+    if (!NCDVal_IsInvalid(data_arg)) {
+        if (!instance_append(o, data_arg)) {
+            goto fail1;
+        }
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail1:
+    o->buf->inst = NULL;
+    buffer_free(o->buf);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    instance_assert(o);
+    
+    // detach buffer from instance
+    buffer_detach(o->buf);
+    
+    // die
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    instance_assert(o);
+    
+    if (name == NCD_STRING_EMPTY) {
+        if (o->total_length - o->offset == 0) {
+            *out = NCDVal_NewStringUninitialized(mem, 0);
+        } else {
+            NCDValComposedStringResource resource;
+            struct reference *ref = reference_init(o, o->offset, o->total_length - o->offset, &resource);
+            if (!ref) {
+                goto fail;
+            }
+            *out = NCDVal_NewComposedString(mem, resource, 0, ref->length);
+            BRefTarget_Deref(resource.ref_target);
+        }
+        return 1;
+    }
+    
+    if (name == NCD_STRING_LENGTH) {
+        *out = ncd_make_uintmax(mem, o->total_length - o->offset);
+        return 1;
+    }
+    
+    return 0;
+    
+fail:
+    *out = NCDVal_NewInvalid();
+    return 1;
+}
+
+static void append_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef data_arg;
+    if (!NCDVal_ListRead(params->args, 1, &data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // get instance
+    struct instance *inst = params->method_user;
+    
+    // append
+    if (!instance_append(inst, data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "instance_append failed");
+        goto fail0;
+    }
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void consume_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef amount_arg;
+    if (!NCDVal_ListRead(params->args, 1, &amount_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(amount_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse amount
+    uintmax_t amount;
+    if (!ncd_read_uintmax(amount_arg, &amount)) {
+        ModuleLog(i, BLOG_ERROR, "wrong amount");
+        goto fail0;
+    }
+    
+    // get instance
+    struct instance *inst = params->method_user;
+    
+    // check amount
+    if (amount > inst->total_length - inst->offset) {
+        ModuleLog(i, BLOG_ERROR, "amount is more than buffer length");
+        goto fail0;
+    }
+    
+    // consume
+    instance_consume(inst, amount);
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "buffer",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "buffer::append",
+        .func_new2 = append_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "buffer::consume",
+        .func_new2 = consume_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_buffer = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/buffer_chunks_tree.h b/external/badvpn_dns/ncd/modules/buffer_chunks_tree.h
new file mode 100644
index 0000000..c299d7c
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/buffer_chunks_tree.h
@@ -0,0 +1,9 @@
+#define SAVL_PARAM_NAME ChunksTree
+#define SAVL_PARAM_FEATURE_COUNTS 0
+#define SAVL_PARAM_FEATURE_NOKEYS 0
+#define SAVL_PARAM_TYPE_ENTRY struct chunk
+#define SAVL_PARAM_TYPE_KEY size_t
+#define SAVL_PARAM_TYPE_ARG int
+#define SAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) B_COMPARE((entry1)->offset, (entry2)->offset)
+#define SAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) B_COMPARE((key1), (entry2)->offset)
+#define SAVL_PARAM_MEMBER_NODE chunks_tree_node
diff --git a/external/badvpn_dns/ncd/modules/call2.c b/external/badvpn_dns/ncd/modules/call2.c
new file mode 100644
index 0000000..f3e65ba
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/call2.c
@@ -0,0 +1,498 @@
+/**
+ * @file call2.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   call(string template, list args)
+ * 
+ * Description:
+ *   Calls a process template. The 'template' argument is the name of the process
+ *   template to call, and the 'list' argument is a list of arguments for the
+ *   process template. Calling a process template is roughly equivalent to placing
+ *   the statements within that template into the place of call(), except for the
+ *   points presented next. The 'template' argument can be a special value "<none>",
+ *   which makes call() a no-op.
+ * 
+ *   The process created from the called template will be able to access the arguments
+ *   that were given in the 'args' argument to call() via the '_argN' predefined\
+ *   objects (e.g. _arg0 for the first argumens), and also via '_args' for the entire
+ *   argument list.
+ *
+ *   The called process also will be able to access objects within the calling
+ *   process as seen by the call() statement. However such any access needs to happen
+ *   via a special '_caller' predefined object. For example, if there is a statement
+ *   'var("a") x;' somewhere above the call() statement, the called process can access
+ *   it as '_caller.x'.
+ * 
+ *   Note that call() preserves backtracking semantics, i.e. when a statement within
+ *   the called process goes down after having gone up, the behaviour really is as
+ *   if the call() statement was replaced with the statements in the called template,
+ *   (disregarding variable resolution).
+ * 
+ *   Because the template name is an argument, call() can be used for branching.
+ *   For example, if we have an object 'x' with the value "true" or "false", a
+ *   branch can be performed by defining two process templates, 'branch_true'
+ *   and 'branch_false', and branching with the following code:
+ * 
+ *     concat("branch_", x) name;
+ *     call(name, {});
+ * 
+ * Synopsis:
+ *   call_with_caller_target(string template, list args, string caller_target)
+ * 
+ * Description:
+ *   Like call(), except that the target of the '_caller' predefined object is
+ *   specified by the 'caller_target' argument. This is indented to be used from
+ *   generic code for user-specified callbacks, allowing the user to easily refer to
+ *   his own objects from inside the callback.
+ * 
+ *   The 'caller_target' must be a non-empty string referring to an actual object;
+ *   there is no choice of 'caller_target' that would make call_with_caller_target()
+ *   equivalent to call().
+ * 
+ * Synopsis:
+ *   embcall2_multif(string cond1, string template1, ..., [string else_template])
+ * 
+ * Description:
+ *   This is an internal command used to implement the 'If' clause. The arguments
+ *   are pairs of (cond, template), where 'cond' is a condition in form of a string,
+ *   and 'template' is the name of the process template for this condition. The
+ *   template corresponding to the first condition equal to "true" is called; if
+ *   there is no true condition, either the template 'else_template' is called,
+ *   if it is provided, or nothing is performed, if 'else_template' is not provided.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_call2.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define STATE_WORKING 1
+#define STATE_UP 2
+#define STATE_WAITING 3
+#define STATE_TERMINATING 4
+#define STATE_NONE 5
+
+#define NUM_STATIC_NAMES 4
+
+struct instance {
+    NCDModuleInst *i;
+    NCDModuleProcess process;
+    int state;
+};
+
+struct instance_with_caller_target {
+    struct instance base;
+    NCD_string_id_t *dynamic_names;
+    size_t num_names;
+    NCD_string_id_t static_names[NUM_STATIC_NAMES];
+};
+
+#define NAMES_PARAM_NAME CallNames
+#define NAMES_PARAM_TYPE struct instance_with_caller_target
+#define NAMES_PARAM_MEMBER_DYNAMIC_NAMES dynamic_names
+#define NAMES_PARAM_MEMBER_STATIC_NAMES static_names
+#define NAMES_PARAM_MEMBER_NUM_NAMES num_names
+#define NAMES_PARAM_NUM_STATIC_NAMES NUM_STATIC_NAMES
+#include <ncd/extra/make_fast_names.h>
+
+static void process_handler_event (NCDModuleProcess *process, int event);
+static int process_func_getspecialobj_embed (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
+static int process_func_getspecialobj_noembed (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
+static int process_func_getspecialobj_with_caller_target (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
+static int caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static int caller_obj_func_getobj_with_caller_target (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static void func_new_templ (void *vo, NCDModuleInst *i, NCDValRef template_name, NCDValRef args, int embed);
+static void instance_free (struct instance *o);
+
+static void process_handler_event (NCDModuleProcess *process, int event)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(o->state == STATE_WORKING)
+            
+            // signal up
+            NCDModuleInst_Backend_Up(o->i);
+            
+            // set state up
+            o->state = STATE_UP;
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(o->state == STATE_UP)
+            
+            // signal down
+            NCDModuleInst_Backend_Down(o->i);
+            
+            // set state waiting
+            o->state = STATE_WAITING;
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(o->state == STATE_TERMINATING)
+            
+            // die finally
+            instance_free(o);
+            return;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static int process_func_getspecialobj_embed (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    
+    return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
+}
+
+static int process_func_getspecialobj_noembed (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    
+    if (name == NCD_STRING_CALLER) {
+        *out_object = NCDObject_Build(-1, o, NCDObject_no_getvar, caller_obj_func_getobj);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int process_func_getspecialobj_with_caller_target (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    
+    if (name == NCD_STRING_CALLER) {
+        *out_object = NCDObject_Build(-1, o, NCDObject_no_getvar, caller_obj_func_getobj_with_caller_target);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = NCDObject_DataPtr(obj);
+    
+    return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
+}
+
+static int caller_obj_func_getobj_with_caller_target (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance_with_caller_target *o_ch = NCDObject_DataPtr(obj);
+    ASSERT(o_ch->num_names > 0)
+    
+    NCD_string_id_t *names = CallNames_GetNames(o_ch);
+    
+    NCDObject object;
+    if (!NCDModuleInst_Backend_GetObj(o_ch->base.i, names[0], &object)) {
+        return 0;
+    }
+    
+    NCDObject obj2;
+    if (!NCDObject_ResolveObjExprCompact(&object, names + 1, o_ch->num_names - 1, &obj2)) {
+        return 0;
+    }
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out_object = obj2;
+        return 1;
+    }
+    
+    return NCDObject_GetObj(&obj2, name, out_object);
+}
+
+static void func_new_templ (void *vo, NCDModuleInst *i, NCDValRef template_name, NCDValRef args, int embed)
+{
+    ASSERT(NCDVal_IsInvalid(template_name) || NCDVal_IsString(template_name))
+    ASSERT(NCDVal_IsInvalid(args) || NCDVal_IsList(args))
+    ASSERT(embed == !!embed)
+    
+    struct instance *o = vo;
+    o->i = i;
+    
+    if (NCDVal_IsInvalid(template_name) || ncd_is_none(template_name)) {
+        // signal up
+        NCDModuleInst_Backend_Up(o->i);
+        
+        // set state none
+        o->state = STATE_NONE;
+    } else {
+        // create process
+        if (!NCDModuleProcess_InitValue(&o->process, o->i, template_name, args, process_handler_event)) {
+            ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+            goto fail0;
+        }
+        
+        // set special functions
+        if (embed) {
+            NCDModuleProcess_SetSpecialFuncs(&o->process, process_func_getspecialobj_embed);
+        } else {
+            NCDModuleProcess_SetSpecialFuncs(&o->process, process_func_getspecialobj_noembed);
+        }
+        
+        // set state working
+        o->state = STATE_WORKING;
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o)
+{
+    // free process
+    if (o->state != STATE_NONE) {
+        NCDModuleProcess_Free(&o->process);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_new_call (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef template_arg;
+    NCDValRef args_arg;
+    if (!NCDVal_ListRead(params->args, 2, &template_arg, &args_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(template_arg) || !NCDVal_IsList(args_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    func_new_templ(vo, i, template_arg, args_arg, 0);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_call_with_caller_target (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    struct instance_with_caller_target *o_ct = vo;
+    o->i = i;
+    
+    NCDValRef template_arg;
+    NCDValRef args_arg;
+    NCDValRef caller_target_arg;
+    if (!NCDVal_ListRead(params->args, 3, &template_arg, &args_arg, &caller_target_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(template_arg) || !NCDVal_IsList(args_arg) || !NCDVal_IsString(caller_target_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    NCDValContString cts;
+    if (!NCDVal_StringContinuize(caller_target_arg, &cts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringContinuize failed");
+        goto fail0;
+    }
+    
+    int res = CallNames_InitNames(o_ct, i->params->iparams->string_index, cts.data, NCDVal_StringLength(caller_target_arg));
+    NCDValContString_Free(&cts);
+    if (!res) {
+        ModuleLog(i, BLOG_ERROR, "CallerNames_InitNames failed");
+        goto fail0;
+    }
+    
+    if (ncd_is_none(template_arg)) {
+        // signal up
+        NCDModuleInst_Backend_Up(i);
+        
+        // set state none
+        o->state = STATE_NONE;
+    } else {
+        // create process
+        if (!NCDModuleProcess_InitValue(&o->process, i, template_arg, args_arg, process_handler_event)) {
+            ModuleLog(i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+            goto fail1;
+        }
+        
+        // set special functions
+        NCDModuleProcess_SetSpecialFuncs(&o->process, process_func_getspecialobj_with_caller_target);
+        
+        // set state working
+        o->state = STATE_WORKING;
+    }
+    
+    return;
+    
+fail1:
+    CallNames_FreeNames(o_ct);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_embcall_multif (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef args = params->args;
+    
+    NCDValRef template_value = NCDVal_NewInvalid();
+    
+    size_t count = NCDVal_ListCount(args);
+    size_t j = 0;
+    
+    while (j < count) {
+        NCDValRef arg = NCDVal_ListGet(args, j);
+        
+        if (j == count - 1) {
+            if (!NCDVal_IsString(arg)) {
+                ModuleLog(i, BLOG_ERROR, "bad arguments");
+                goto fail0;
+            }
+            
+            template_value = arg;
+            break;
+        }
+        
+        NCDValRef arg2 = NCDVal_ListGet(args, j + 1);
+        
+        if (!NCDVal_IsString(arg) || !NCDVal_IsString(arg2)) {
+            ModuleLog(i, BLOG_ERROR, "bad arguments");
+            goto fail0;
+        }
+        
+        if (ncd_read_boolean(arg)) {
+            template_value = arg2;
+            break;
+        }
+        
+        j += 2;
+    }
+    
+    func_new_templ(vo, i, template_value, NCDVal_NewInvalid(), 1);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(o->state != STATE_TERMINATING)
+    
+    // if none, die now
+    if (o->state == STATE_NONE) {
+        instance_free(o);
+        return;
+    }
+    
+    // request process to terminate
+    NCDModuleProcess_Terminate(&o->process);
+    
+    // set state terminating
+    o->state = STATE_TERMINATING;
+}
+
+static void func_die_with_caller_target (void *vo)
+{
+    struct instance_with_caller_target *o_ct = vo;
+    
+    CallNames_FreeNames(o_ct);
+    
+    func_die(vo);
+}
+
+static void func_clean (void *vo)
+{
+    struct instance *o = vo;
+    if (o->state != STATE_WAITING) {
+        return;
+    }
+    
+    // allow process to continue
+    NCDModuleProcess_Continue(&o->process);
+    
+    // set state working
+    o->state = STATE_WORKING;
+}
+
+static int func_getobj (void *vo, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = vo;
+    
+    if (o->state == STATE_NONE) {
+        return 0;
+    }
+    
+    return NCDModuleProcess_GetObj(&o->process, name, out_object);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "call",
+        .func_new2 = func_new_call,
+        .func_die = func_die,
+        .func_clean = func_clean,
+        .func_getobj = func_getobj,
+        .flags = NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN|NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "call_with_caller_target",
+        .func_new2 = func_new_call_with_caller_target,
+        .func_die = func_die_with_caller_target,
+        .func_clean = func_clean,
+        .func_getobj = func_getobj,
+        .flags = NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN|NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS,
+        .alloc_size = sizeof(struct instance_with_caller_target)
+    }, {
+        .type = "embcall2_multif",
+        .func_new2 = func_new_embcall_multif,
+        .func_die = func_die,
+        .func_clean = func_clean,
+        .func_getobj = func_getobj,
+        .flags = NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN|NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_call2 = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/choose.c b/external/badvpn_dns/ncd/modules/choose.c
new file mode 100644
index 0000000..23f8bbe
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/choose.c
@@ -0,0 +1,145 @@
+/**
+ * @file choose.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Multiple value selection based on boolean conditions.
+ * 
+ * Synopsis:
+ *   choose({{string cond1, result1}, ..., {string condN, resultN}}, default_result)
+ * 
+ * Variables:
+ *   (empty) - If cond1="true" then result1,
+ *             else if cond2="true" then result2,
+ *             ...,
+ *             else default_result.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_choose.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValRef result;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef arg_choices;
+    NCDValRef arg_default_result;
+    if (!NCDVal_ListRead(params->args, 2, &arg_choices, &arg_default_result)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsList(arg_choices)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // iterate choices
+    int have_result = 0;
+    size_t count = NCDVal_ListCount(arg_choices);
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef c = NCDVal_ListGet(arg_choices, j);
+        
+        // check choice type
+        if (!NCDVal_IsList(c)) {
+            ModuleLog(i, BLOG_ERROR, "wrong choice type");
+            goto fail0;
+        }
+        
+        // read choice
+        NCDValRef c_cond;
+        NCDValRef c_result;
+        if (!NCDVal_ListRead(c, 2, &c_cond, &c_result)) {
+            ModuleLog(i, BLOG_ERROR, "wrong choice contents arity");
+            goto fail0;
+        }
+        if (!NCDVal_IsString(c_cond)) {
+            ModuleLog(i, BLOG_ERROR, "wrong choice condition type");
+            goto fail0;
+        }
+        
+        // update result
+        if (!have_result && ncd_read_boolean(c_cond)) {
+            o->result = c_result;
+            have_result = 1;
+        }
+    }
+    
+    // default?
+    if (!have_result) {
+        o->result = arg_default_result;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewCopy(mem, o->result);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "choose",
+        .func_new2 = func_new,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_choose = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/command_template.c b/external/badvpn_dns/ncd/modules/command_template.c
new file mode 100644
index 0000000..6eb92f6
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/command_template.c
@@ -0,0 +1,218 @@
+/**
+ * @file command_template.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <ncd/modules/command_template.h>
+
+#define STATE_ADDING_LOCK 1
+#define STATE_ADDING 2
+#define STATE_ADDING_NEED_DELETE 3
+#define STATE_DONE 4
+#define STATE_DELETING_LOCK 5
+#define STATE_DELETING 6
+
+static void lock_handler (command_template_instance *o);
+static void process_handler (command_template_instance *o, int normally, uint8_t normally_exit_status);
+static void free_template (command_template_instance *o, int is_error);
+
+static void lock_handler (command_template_instance *o)
+{
+    ASSERT(o->state == STATE_ADDING_LOCK || o->state == STATE_DELETING_LOCK)
+    ASSERT(!(o->state == STATE_ADDING_LOCK) || o->do_exec)
+    ASSERT(!(o->state == STATE_DELETING_LOCK) || o->undo_exec)
+    
+    if (o->state == STATE_ADDING_LOCK) {
+        // start process
+        if (!BProcess_Init(&o->process, o->i->params->iparams->manager, (BProcess_handler)process_handler, o, o->do_exec, CmdLine_Get(&o->do_cmdline), NULL)) {
+            NCDModuleInst_Backend_Log(o->i, o->blog_channel, BLOG_ERROR, "BProcess_Init failed");
+            free_template(o, 1);
+            return;
+        }
+        
+        // set state
+        o->state = STATE_ADDING;
+    } else {
+        // start process
+        if (!BProcess_Init(&o->process, o->i->params->iparams->manager, (BProcess_handler)process_handler, o, o->undo_exec, CmdLine_Get(&o->undo_cmdline), NULL)) {
+            NCDModuleInst_Backend_Log(o->i, o->blog_channel, BLOG_ERROR, "BProcess_Init failed");
+            free_template(o, 1);
+            return;
+        }
+        
+        // set state
+        o->state = STATE_DELETING;
+    }
+}
+
+static void process_handler (command_template_instance *o, int normally, uint8_t normally_exit_status)
+{
+    ASSERT(o->state == STATE_ADDING || o->state == STATE_ADDING_NEED_DELETE || o->state == STATE_DELETING)
+    
+    // release lock
+    BEventLockJob_Release(&o->elock_job);
+    
+    // free process
+    BProcess_Free(&o->process);
+    
+    if (!normally || normally_exit_status != 0) {
+        NCDModuleInst_Backend_Log(o->i, o->blog_channel, BLOG_ERROR, "command failed");
+        
+        free_template(o, 1);
+        return;
+    }
+    
+    switch (o->state) {
+        case STATE_ADDING: {
+            // set state
+            o->state = STATE_DONE;
+            
+            // signal up
+            NCDModuleInst_Backend_Up(o->i);
+        } break;
+        
+        case STATE_ADDING_NEED_DELETE: {
+            if (o->undo_exec) {
+                // wait for lock
+                BEventLockJob_Wait(&o->elock_job);
+                
+                // set state
+                o->state = STATE_DELETING_LOCK;
+            } else {
+                free_template(o, 0);
+                return;
+            }
+        } break;
+        
+        case STATE_DELETING: {
+            // finish
+            free_template(o, 0);
+            return;
+        } break;
+    }
+}
+
+void command_template_new (command_template_instance *o, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, command_template_build_cmdline build_cmdline, command_template_free_func free_func, void *user, int blog_channel, BEventLock *elock)
+{
+    // init arguments
+    o->i = i;
+    o->free_func = free_func;
+    o->user = user;
+    o->blog_channel = blog_channel;
+    
+    // build do command
+    if (!build_cmdline(o->i, params->args, 0, &o->do_exec, &o->do_cmdline)) {
+        NCDModuleInst_Backend_Log(o->i, o->blog_channel, BLOG_ERROR, "build_cmdline do callback failed");
+        goto fail0;
+    }
+    
+    // build undo command
+    if (!build_cmdline(o->i, params->args, 1, &o->undo_exec, &o->undo_cmdline)) {
+        NCDModuleInst_Backend_Log(o->i, o->blog_channel, BLOG_ERROR, "build_cmdline undo callback failed");
+        goto fail1;
+    }
+    
+    // init lock job
+    BEventLockJob_Init(&o->elock_job, elock, (BEventLock_handler)lock_handler, o);
+    
+    if (o->do_exec) {
+        // wait for lock
+        BEventLockJob_Wait(&o->elock_job);
+        
+        // set state
+        o->state = STATE_ADDING_LOCK;
+    } else {
+        // set state
+        o->state = STATE_DONE;
+        
+        // signal up
+        NCDModuleInst_Backend_Up(o->i);
+    }
+    
+    return;
+    
+fail1:
+    if (o->do_exec) {
+        free(o->do_exec);
+        CmdLine_Free(&o->do_cmdline);
+    }
+fail0:
+    o->free_func(o->user, 1);
+}
+
+static void free_template (command_template_instance *o, int is_error)
+{
+    // free lock job
+    BEventLockJob_Free(&o->elock_job);
+    
+    // free undo command
+    if (o->undo_exec) {
+        free(o->undo_exec);
+        CmdLine_Free(&o->undo_cmdline);
+    }
+    
+    // free do command
+    if (o->do_exec) {
+        free(o->do_exec);
+        CmdLine_Free(&o->do_cmdline);
+    }
+    
+    // call free function
+    o->free_func(o->user, is_error);
+}
+
+void command_template_die (command_template_instance *o)
+{
+    ASSERT(o->state == STATE_ADDING_LOCK || o->state == STATE_ADDING || o->state == STATE_DONE)
+    
+    switch (o->state) {
+        case STATE_ADDING_LOCK: {
+            free_template(o, 0);
+            return;
+        } break;
+        
+        case STATE_ADDING: {
+            // set state
+            o->state = STATE_ADDING_NEED_DELETE;
+        } break;
+        
+        case STATE_DONE: {
+            if (o->undo_exec) {
+                // wait for lock
+                BEventLockJob_Wait(&o->elock_job);
+                
+                // set state
+                o->state = STATE_DELETING_LOCK;
+            } else {
+                free_template(o, 0);
+                return;
+            }
+        } break;
+    }
+}
diff --git a/external/badvpn_dns/ncd/modules/command_template.h b/external/badvpn_dns/ncd/modules/command_template.h
new file mode 100644
index 0000000..e1228ae
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/command_template.h
@@ -0,0 +1,62 @@
+/**
+ * @file command_template.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Template for a module which executes a command to start and stop.
+ * The command is executed asynchronously.
+ */
+
+#ifndef BADVPN_NCD_MODULES_COMMAND_TEMPLATE_H
+#define BADVPN_NCD_MODULES_COMMAND_TEMPLATE_H
+
+#include <misc/cmdline.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/BEventLock.h>
+
+typedef int (*command_template_build_cmdline) (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl);
+typedef void (*command_template_free_func) (void *user, int is_error);
+
+typedef struct {
+    NCDModuleInst *i;
+    command_template_free_func free_func;
+    void *user;
+    int blog_channel;
+    char *do_exec;
+    CmdLine do_cmdline;
+    char *undo_exec;
+    CmdLine undo_cmdline;
+    BEventLockJob elock_job;
+    int state;
+    BProcess process;
+} command_template_instance;
+
+void command_template_new (command_template_instance *o, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, command_template_build_cmdline build_cmdline, command_template_free_func free_func, void *user, int blog_channel, BEventLock *elock);
+void command_template_die (command_template_instance *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/modules/concat.c b/external/badvpn_dns/ncd/modules/concat.c
new file mode 100644
index 0000000..e71e69e
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/concat.c
@@ -0,0 +1,189 @@
+/**
+ * @file concat.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   concat([string elem ...])
+ *   concatv(list strings)
+ * 
+ * Description:
+ *   Concatenates zero or more strings. The result is available as the empty
+ *   string variable. For concatv(), the strings are provided as a single
+ *   list argument. For concat(), the strings are provided as arguments
+ *   themselves.
+ */
+
+#include <stddef.h>
+#include <string.h>
+
+#include <misc/balloc.h>
+#include <misc/offset.h>
+#include <misc/BRefTarget.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_concat.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct result {
+    BRefTarget ref_target;
+    size_t length;
+    char data[];
+};
+
+struct instance {
+    NCDModuleInst *i;
+    struct result *result;
+};
+
+static void result_ref_target_func_release (BRefTarget *ref_target)
+{
+    struct result *result = UPPER_OBJECT(ref_target, struct result, ref_target);
+    
+    BFree(result);
+}
+
+static void new_concat_common (void *vo, NCDModuleInst *i, NCDValRef list)
+{
+    ASSERT(NCDVal_IsList(list))
+    struct instance *o = vo;
+    o->i = i;
+    
+    size_t count = NCDVal_ListCount(list);
+    bsize_t result_size = bsize_fromsize(sizeof(struct result));
+    
+    // check arguments and compute result size
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(list, j);
+        
+        if (!NCDVal_IsString(arg)) {
+            ModuleLog(i, BLOG_ERROR, "wrong type");
+            goto fail0;
+        }
+        
+        result_size = bsize_add(result_size, bsize_fromsize(NCDVal_StringLength(arg)));
+    }
+    
+    // allocate result
+    o->result = BAllocSize(result_size);
+    if (!o->result) {
+        ModuleLog(i, BLOG_ERROR, "BAllocSize failed");
+        goto fail0;
+    }
+    
+    // init ref target
+    BRefTarget_Init(&o->result->ref_target, result_ref_target_func_release);
+    
+    // copy data to result
+    o->result->length = 0;
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(list, j);
+        b_cstring cstr = NCDVal_StringCstring(arg);
+        b_cstring_copy_to_buf(cstr, 0, cstr.length, o->result->data + o->result->length);
+        o->result->length += cstr.length;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_concat (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_concat_common(vo, i, params->args);
+}
+
+static void func_new_concatv (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef list_arg;
+    if (!NCDVal_ListRead(params->args, 1, &list_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsList(list_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    new_concat_common(vo, i, list_arg);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // release result reference
+    BRefTarget_Deref(&o->result->ref_target);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewExternalString(mem, o->result->data, o->result->length, &o->result->ref_target);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "concat",
+        .func_new2 = func_new_concat,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "concatv",
+        .func_new2 = func_new_concatv,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_concat = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/daemon.c b/external/badvpn_dns/ncd/modules/daemon.c
new file mode 100644
index 0000000..3dac4c5
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/daemon.c
@@ -0,0 +1,296 @@
+/**
+ * @file daemon.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Runs a program in the background, restarting it if it crashes.
+ * On deinitialization, sends SIGTERM to the daemon and waits for it to terminate
+ * (unless it's crashed at the time).
+ * 
+ * Synopsis:
+ *   daemon(list(string) cmd [, map options])
+ * 
+ * Arguments:
+ *   cmd - Command for the daemon. The first element is the full path
+ *     to the executable, other elements are command line arguments (excluding
+ *     the zeroth argument).
+ *   options - Map of options:
+ *     "keep_stdout":"true" - Start the program with the same stdout as the NCD process.
+ *     "keep_stderr":true" - Start the program with the same stderr as the NCD process.
+ *     "do_setsid":"true" - Call setsid() in the child before exec. This is needed to
+ *       start the 'agetty' program.
+ *     "username":username_string - Start the process under the permissions of the
+ *       specified user. 
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <misc/cmdline.h>
+#include <misc/strdup.h>
+#include <system/BProcess.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+#include <ncd/extra/NCDBProcessOpts.h>
+
+#include <generated/blog_channel_ncd_daemon.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define RETRY_TIME 10000
+
+#define STATE_RETRYING 1
+#define STATE_RUNNING 2
+#define STATE_RUNNING_DIE 3
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValRef cmd_arg;
+    NCDBProcessOpts opts;
+    BTimer timer;
+    BProcess process;
+    int state;
+};
+
+static int build_cmdline (NCDModuleInst *i, NCDValRef cmd_arg, char **exec, CmdLine *cl);
+static void start_process (struct instance *o);
+static void timer_handler (struct instance *o);
+static void process_handler (struct instance *o, int normally, uint8_t normally_exit_status);
+static void instance_free (struct instance *o);
+
+static int build_cmdline (NCDModuleInst *i, NCDValRef cmd_arg, char **exec, CmdLine *cl)
+{
+    ASSERT(!NCDVal_IsInvalid(cmd_arg))
+    
+    if (!NCDVal_IsList(cmd_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    size_t count = NCDVal_ListCount(cmd_arg);
+    
+    // read exec
+    if (count == 0) {
+        ModuleLog(i, BLOG_ERROR, "missing executable name");
+        goto fail0;
+    }
+    NCDValRef exec_arg = NCDVal_ListGet(cmd_arg, 0);
+    if (!NCDVal_IsStringNoNulls(exec_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    if (!(*exec = ncd_strdup(exec_arg))) {
+        ModuleLog(i, BLOG_ERROR, "ncd_strdup failed");
+        goto fail0;
+    }
+    
+    // start cmdline
+    if (!CmdLine_Init(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
+        goto fail1;
+    }
+    
+    // add header
+    if (!CmdLine_Append(cl, *exec)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
+        goto fail2;
+    }
+    
+    // add additional arguments
+    for (size_t j = 1; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(cmd_arg, j);
+        
+        if (!NCDVal_IsStringNoNulls(arg)) {
+            ModuleLog(i, BLOG_ERROR, "wrong type");
+            goto fail2;
+        }
+        
+        b_cstring cstr = NCDVal_StringCstring(arg);
+        if (!CmdLine_AppendCstring(cl, cstr, 0, cstr.length)) {
+            ModuleLog(i, BLOG_ERROR, "CmdLine_AppendCstring failed");
+            goto fail2;
+        }
+    }
+    
+    // finish
+    if (!CmdLine_Finish(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
+        goto fail2;
+    }
+    
+    return 1;
+    
+fail2:
+    CmdLine_Free(cl);
+fail1:
+    free(*exec);
+fail0:
+    return 0;
+}
+
+static void start_process (struct instance *o)
+{
+    // build cmdline
+    char *exec;
+    CmdLine cl;
+    if (!build_cmdline(o->i, o->cmd_arg, &exec, &cl)) {
+        goto fail;
+    }
+    
+    // start process
+    struct BProcess_params p_params = NCDBProcessOpts_GetParams(&o->opts);
+    int res = BProcess_Init2(&o->process, o->i->params->iparams->manager, (BProcess_handler)process_handler, o, exec, CmdLine_Get(&cl), p_params);
+    CmdLine_Free(&cl);
+    free(exec);
+    
+    if (!res) {
+        ModuleLog(o->i, BLOG_ERROR, "BProcess_Init2 failed");
+        goto fail;
+    }
+    
+    // set state running
+    o->state = STATE_RUNNING;
+    return;
+    
+fail:
+    // start timer
+    BReactor_SetTimer(o->i->params->iparams->reactor, &o->timer);
+    
+    // set state retrying
+    o->state = STATE_RETRYING;
+}
+
+static void timer_handler (struct instance *o)
+{
+    ASSERT(o->state == STATE_RETRYING)
+    
+    ModuleLog(o->i, BLOG_INFO, "restarting after crash");
+    
+    start_process(o);
+}
+
+static void process_handler (struct instance *o, int normally, uint8_t normally_exit_status)
+{
+    ASSERT(o->state == STATE_RUNNING || o->state == STATE_RUNNING_DIE)
+    
+    // free process
+    BProcess_Free(&o->process);
+    
+    // if we were requested to die, die now
+    if (o->state == STATE_RUNNING_DIE) {
+        instance_free(o);
+        return;
+    }
+    
+    BLog(BLOG_ERROR, "daemon crashed");
+    
+    // start timer
+    BReactor_SetTimer(o->i->params->iparams->reactor, &o->timer);
+    
+    // set state retrying
+    o->state = STATE_RETRYING;
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef opts_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 1, &o->cmd_arg) &&
+        !NCDVal_ListRead(params->args, 2, &o->cmd_arg, &opts_arg)
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // init options
+    if (!NCDBProcessOpts_Init(&o->opts, opts_arg, NULL, NULL, i, BLOG_CURRENT_CHANNEL)) {
+        goto fail0;
+    }
+    
+    // init timer
+    BTimer_Init(&o->timer, RETRY_TIME, (BTimer_handler)timer_handler, o);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    
+    // try starting process
+    start_process(o);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o)
+{
+    // free timer
+    BReactor_RemoveTimer(o->i->params->iparams->reactor, &o->timer);
+    
+    // free options
+    NCDBProcessOpts_Free(&o->opts);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(o->state != STATE_RUNNING_DIE)
+    
+    // if not running, die immediately
+    if (o->state == STATE_RETRYING) {
+        instance_free(o);
+        return;
+    }
+    
+    // request termination
+    BProcess_Terminate(&o->process);
+    
+    // set state running die
+    o->state = STATE_RUNNING_DIE;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "daemon",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_daemon = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/depend.c b/external/badvpn_dns/ncd/modules/depend.c
new file mode 100644
index 0000000..5019cf4
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/depend.c
@@ -0,0 +1,452 @@
+/**
+ * @file depend.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Dependencies module.
+ * 
+ * Synopsis: provide(string name)
+ * Description: Provides a resource. On initialization, transitions any depend()-s
+ *   waiting for this resource to UP state. On deinitialization, transitions
+ *   depend()-s using this resource to DOWN state, and waits for all of them to
+ *   receive the clean signal (i.e. wait for all of the statements following them in
+ *   their processes to terminate). Initialization fails if a provide() already
+ *   exists for this resource (including if it is being deinitialized).
+ * 
+ * Synopsis: provide_event(string name)
+ * Description: Like provide(), but if another provide() already exists for this
+ *   resource, initialization does not fail, and the request is queued to the active
+ *   provide() for this resource. When an active provide() disappears that has
+ *   queued provide()-s, one of them is promoted to be the active provide() for this
+ *   resource, and the remaining queue is transferred to it.
+ *   (mentions of provide() in this text also apply to provide_event())
+ * 
+ * Synopsis: depend(string name)
+ * Description: Depends on a resource. Is in UP state when a provide()
+ *   for this resource is available, and in DOWN state when it is not (either
+ *   it does not exist or is being terminated).
+ * Variables: Provides variables available from the corresponding provide,
+ *     ("modname.varname" or "modname").
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <structure/LinkedList1.h>
+#include <structure/LinkedList3.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_depend.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleGlobal(i) ((i)->m->group->group_state)
+
+struct provide {
+    NCDModuleInst *i;
+    const char *name;
+    size_t name_len;
+    int is_queued;
+    union {
+        struct {
+            LinkedList3Node queued_node; // node in list which begins with provide.queued_provides_firstnode
+        };
+        struct {
+            LinkedList1Node provides_node; // node in provides
+            LinkedList1 depends;
+            LinkedList3Node queued_provides_firstnode;
+            int dying;
+        };
+    };
+};
+
+struct depend {
+    NCDModuleInst *i;
+    const char *name;
+    size_t name_len;
+    struct provide *p;
+    LinkedList1Node node;
+};
+
+struct global {
+    LinkedList1 provides;
+    LinkedList1 free_depends;
+};
+
+static struct provide * find_provide (struct global *g, const char *name, size_t name_len)
+{
+    for (LinkedList1Node *n = LinkedList1_GetFirst(&g->provides); n; n = LinkedList1Node_Next(n)) {
+        struct provide *p = UPPER_OBJECT(n, struct provide, provides_node);
+        ASSERT(!p->is_queued)
+        
+        if (p->name_len == name_len && !memcmp(p->name, name, name_len)) {
+            return p;
+        }
+    }
+    
+    return NULL;
+}
+
+static void provide_promote (struct provide *o)
+{
+    struct global *g = ModuleGlobal(o->i);
+    ASSERT(!find_provide(g, o->name, o->name_len))
+    
+    // set not queued
+    o->is_queued = 0;
+    
+    // insert to provides list
+    LinkedList1_Append(&g->provides, &o->provides_node);
+    
+    // init depends list
+    LinkedList1_Init(&o->depends);
+    
+    // set not dying
+    o->dying = 0;
+    
+    // attach free depends with this name
+    LinkedList1Node *n = LinkedList1_GetFirst(&g->free_depends);
+    while (n) {
+        LinkedList1Node *next = LinkedList1Node_Next(n);
+        struct depend *d = UPPER_OBJECT(n, struct depend, node);
+        ASSERT(!d->p)
+        
+        if (d->name_len != o->name_len || memcmp(d->name, o->name, d->name_len)) {
+            n = next;
+            continue;
+        }
+        
+        // remove from free depends list
+        LinkedList1_Remove(&g->free_depends, &d->node);
+        
+        // insert to provide's list
+        LinkedList1_Append(&o->depends, &d->node);
+        
+        // set provide
+        d->p = o;
+        
+        // signal up
+        NCDModuleInst_Backend_Up(d->i);
+        
+        n = next;
+    }
+}
+
+static int func_globalinit (struct NCDInterpModuleGroup *group, const struct NCDModuleInst_iparams *params)
+{
+    // allocate global state structure
+    struct global *g = BAlloc(sizeof(*g));
+    if (!g) {
+        BLog(BLOG_ERROR, "BAlloc failed");
+        return 0;
+    }
+    
+    // set group state pointer
+    group->group_state = g;
+    
+    // init provides list
+    LinkedList1_Init(&g->provides);
+    
+    // init free depends list
+    LinkedList1_Init(&g->free_depends);
+    
+    return 1;
+}
+
+static void func_globalfree (struct NCDInterpModuleGroup *group)
+{
+    struct global *g = group->group_state;
+    ASSERT(LinkedList1_IsEmpty(&g->free_depends))
+    ASSERT(LinkedList1_IsEmpty(&g->provides))
+    
+    // free global state structure
+    BFree(g);
+}
+
+static void provide_func_new_templ (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int event)
+{
+    struct global *g = ModuleGlobal(i);
+    struct provide *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(name_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    o->name = NCDVal_StringData(name_arg);
+    o->name_len = NCDVal_StringLength(name_arg);
+    
+    // signal up.
+    // This comes above provide_promote(), so that effects on related depend statements are
+    // computed before this process advances, avoiding problems like failed variable resolutions.
+    NCDModuleInst_Backend_Up(o->i);
+    
+    // check for existing provide with this name
+    struct provide *ep = find_provide(g, o->name, o->name_len);
+    if (ep) {
+        ASSERT(!ep->is_queued)
+        
+        if (!event) {
+            ModuleLog(o->i, BLOG_ERROR, "a provide with this name already exists");
+            goto fail0;
+        }
+        
+        // set queued
+        o->is_queued = 1;
+        
+        // insert to existing provide's queued provides list
+        LinkedList3Node_InitAfter(&o->queued_node, &ep->queued_provides_firstnode);
+    } else {
+        // init first node for queued provides list
+        LinkedList3Node_InitLonely(&o->queued_provides_firstnode);
+        
+        // promote provide
+        provide_promote(o);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void provide_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    provide_func_new_templ(vo, i, params, 0);
+}
+
+static void provide_event_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    provide_func_new_templ(vo, i, params, 1);
+}
+
+static void provide_free (struct provide *o)
+{
+    struct global *g = ModuleGlobal(o->i);
+    ASSERT(o->is_queued || LinkedList1_IsEmpty(&o->depends))
+    
+    if (o->is_queued) {
+        // remove from existing provide's queued provides list
+        LinkedList3Node_Free(&o->queued_node);
+    } else {
+        // remove from provides list
+        LinkedList1_Remove(&g->provides, &o->provides_node);
+        
+        // if we have provides queued, promote the first one
+        if (LinkedList3Node_Next(&o->queued_provides_firstnode)) {
+            // get first queued provide
+            struct provide *qp = UPPER_OBJECT(LinkedList3Node_Next(&o->queued_provides_firstnode), struct provide, queued_node);
+            ASSERT(qp->is_queued)
+            
+            // make it the head of the queued provides list
+            LinkedList3Node_Free(&qp->queued_node);
+            LinkedList3Node_InitAfter(&qp->queued_provides_firstnode, &o->queued_provides_firstnode);
+            LinkedList3Node_Free(&o->queued_provides_firstnode);
+            
+            // promote provide
+            provide_promote(qp);
+        }
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void provide_func_die (void *vo)
+{
+    struct provide *o = vo;
+    ASSERT(o->is_queued || !o->dying)
+    
+    // if we are queued or have no depends, die immediately
+    if (o->is_queued || LinkedList1_IsEmpty(&o->depends)) {
+        provide_free(o);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+    
+    // signal our depends down
+    for (LinkedList1Node *n = LinkedList1_GetFirst(&o->depends); n; n = LinkedList1Node_Next(n)) {
+        struct depend *d = UPPER_OBJECT(n, struct depend, node);
+        ASSERT(d->p == o)
+        
+        // signal down
+        NCDModuleInst_Backend_Down(d->i);
+    }
+}
+
+static void depend_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct global *g = ModuleGlobal(i);
+    struct depend *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(name_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    o->name = NCDVal_StringData(name_arg);
+    o->name_len = NCDVal_StringLength(name_arg);
+    
+    // find a provide with our name
+    struct provide *p = find_provide(g, o->name, o->name_len);
+    ASSERT(!p || !p->is_queued)
+    
+    if (p && !p->dying) {
+        // insert to provide's list
+        LinkedList1_Append(&p->depends, &o->node);
+        
+        // set provide
+        o->p = p;
+        
+        // signal up
+        NCDModuleInst_Backend_Up(o->i);
+    } else {
+        // insert to free depends list
+        LinkedList1_Append(&g->free_depends, &o->node);
+        
+        // set no provide
+        o->p = NULL;
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void depend_free (struct depend *o)
+{
+    struct global *g = ModuleGlobal(o->i);
+    ASSERT(!o->p || !o->p->is_queued)
+    
+    if (o->p) {
+        // remove from provide's list
+        LinkedList1_Remove(&o->p->depends, &o->node);
+        
+        // if provide is dying and is empty, let it die
+        if (o->p->dying && LinkedList1_IsEmpty(&o->p->depends)) {
+            provide_free(o->p);
+        }
+    } else {
+        // remove free depends list
+        LinkedList1_Remove(&g->free_depends, &o->node);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void depend_func_die (void *vo)
+{
+    struct depend *o = vo;
+    
+    depend_free(o);
+}
+
+static void depend_func_clean (void *vo)
+{
+    struct depend *o = vo;
+    struct global *g = ModuleGlobal(o->i);
+    ASSERT(!o->p || !o->p->is_queued)
+    
+    if (!(o->p && o->p->dying)) {
+        return;
+    }
+    
+    struct provide *p = o->p;
+    
+    // remove from provide's list
+    LinkedList1_Remove(&p->depends, &o->node);
+    
+    // insert to free depends list
+    LinkedList1_Append(&g->free_depends, &o->node);
+    
+    // set no provide
+    o->p = NULL;
+    
+    // if provide is empty, let it die
+    if (LinkedList1_IsEmpty(&p->depends)) {
+        provide_free(p);
+    }
+}
+
+static int depend_func_getobj (void *vo, NCD_string_id_t objname, NCDObject *out_object)
+{
+    struct depend *o = vo;
+    ASSERT(!o->p || !o->p->is_queued)
+    
+    if (!o->p) {
+        return 0;
+    }
+    
+    return NCDModuleInst_Backend_GetObj(o->p->i, objname, out_object);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "provide",
+        .func_new2 = provide_func_new,
+        .func_die = provide_func_die,
+        .alloc_size = sizeof(struct provide)
+    }, {
+        .type = "provide_event",
+        .func_new2 = provide_event_func_new,
+        .func_die = provide_func_die,
+        .alloc_size = sizeof(struct provide)
+    }, {
+        .type = "depend",
+        .func_new2 = depend_func_new,
+        .func_die = depend_func_die,
+        .func_clean = depend_func_clean,
+        .func_getobj = depend_func_getobj,
+        .flags = NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN,
+        .alloc_size = sizeof(struct depend)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_depend = {
+    .func_globalinit = func_globalinit,
+    .func_globalfree = func_globalfree,
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/depend_scope.c b/external/badvpn_dns/ncd/modules/depend_scope.c
new file mode 100644
index 0000000..1a814a0
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/depend_scope.c
@@ -0,0 +1,466 @@
+/**
+ * @file depend_scope.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Multiple-option dependencies module.
+ * 
+ * Synopsis:
+ *   depend_scope()
+ * 
+ * Description:
+ *   A scope for dependency names. Any dependency names used in provide() and depend()
+ *   methods on this object are local to this object. Contrast to the multidepend module,
+ *   which provides the same functionality as this module, but with a single global
+ *   dependency name scope.
+ * 
+ * Synopsis:
+ *   depend_scope::provide(name)
+ * 
+ * Arguments:
+ *   name - provider identifier
+ * 
+ * Description:
+ *   Satisfies a dependency.
+ *   If any depend()'s get immediately bound to this provide(),
+ *   the side effects of those first happen, and only then can the process of this
+ *   provide() continue.
+ *   When provide() is requested to deinitialize, if there are any depend()'s bound,
+ *   provide() will not finish deinitializing until all the processes containing the
+ *   bound depend()'s have backtracked to the point of the corresponding depend().
+ *   More specifically, when backtracking has finished for the last bound depend(),
+ *   first the immediate effects of the provide() finshing deinitialization will happen,
+ *   and only then will the depend() attempt to rebind. (If the converse was true, the
+ *   depend() could rebind, but when deinitialization of the provide()'s process
+ *   continues, lose this binding. See ncd/tests/depend_scope.ncd .)
+ * 
+ * Synopsis:
+ *   depend_scope::depend(list names)
+ * 
+ * Arguments:
+ *   names - list of provider identifiers. Names more to the beginning are considered
+ *     more desirable.
+ * 
+ * Description:
+ *   Binds to the provide() providing one of the specified dependency names which is most
+ *   desirable. If there is no provide() providing any of the given dependency names,
+ *   waits and binds when one becomes available. 
+ *   If the depend() is bound to a provide(), and the bound provide() is requested to
+ *   deinitize, or a more desirable provide() becomes available, the depend() statement
+ *   will go down (triggering backtracking), wait for backtracking to finish, and then
+ *   try to bind to a provide() again, as if it was just initialized.
+ *   When depend() is requested to deinitialize, it deinitializes immediately.
+ * 
+ * Attributes:
+ *   Exposes objects as seen from the corresponding provide.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <misc/BRefTarget.h>
+#include <structure/LinkedList1.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_depend_scope.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct scope {
+    BRefTarget ref_target;
+    LinkedList1 provides_list;
+    LinkedList1 depends_list;
+};
+
+struct scope_instance {
+    NCDModuleInst *i;
+    struct scope *scope;
+};
+
+struct provide {
+    NCDModuleInst *i;
+    struct scope *scope;
+    NCDValRef name;
+    LinkedList1Node provides_list_node;
+    LinkedList1 depends_list;
+    int dying;
+};
+
+struct depend {
+    NCDModuleInst *i;
+    struct scope *scope;
+    NCDValRef names;
+    LinkedList1Node depends_list_node;
+    struct provide *provide;
+    LinkedList1Node provide_depends_list_node;
+    int provide_collapsing;
+};
+
+static struct provide * find_provide (struct scope *o, NCDValRef name)
+{
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->provides_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct provide *provide = UPPER_OBJECT(ln, struct provide, provides_list_node);
+        ASSERT(provide->scope == o)
+        if (NCDVal_Compare(provide->name, name) == 0) {
+            return provide;
+        }
+    }
+    
+    return NULL;
+}
+
+static struct provide * depend_find_best_provide (struct depend *o)
+{
+    size_t count = NCDVal_ListCount(o->names);
+    
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef name = NCDVal_ListGet(o->names, j);
+        struct provide *provide = find_provide(o->scope, name);
+        if (provide && !provide->dying) {
+            return provide;
+        }
+    }
+    
+    return NULL;
+}
+
+static void depend_update (struct depend *o)
+{
+    // if we're collapsing, do nothing
+    if (o->provide && o->provide_collapsing) {
+        return;
+    }
+    
+    // find best provide
+    struct provide *best_provide = depend_find_best_provide(o);
+    ASSERT(!best_provide || !best_provide->dying)
+    
+    // has anything changed?
+    if (best_provide == o->provide) {
+        return;
+    }
+    
+    if (o->provide) {
+        // set collapsing
+        o->provide_collapsing = 1;
+        
+        // signal down
+        NCDModuleInst_Backend_Down(o->i);
+    } else {
+        // insert to provide's list
+        LinkedList1_Append(&best_provide->depends_list, &o->provide_depends_list_node);
+        
+        // set not collapsing
+        o->provide_collapsing = 0;
+        
+        // set provide
+        o->provide = best_provide;
+        
+        // signal up
+        NCDModuleInst_Backend_Up(o->i);
+    }
+}
+
+static void scope_ref_target_func_release (BRefTarget *ref_target)
+{
+    struct scope *o = UPPER_OBJECT(ref_target, struct scope, ref_target);
+    ASSERT(LinkedList1_IsEmpty(&o->provides_list))
+    ASSERT(LinkedList1_IsEmpty(&o->depends_list))
+    
+    BFree(o);
+}
+
+static void scope_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct scope_instance *o = vo;
+    o->i = i;
+    
+    // pass scope instance pointer to methods not NCDModuleInst pointer
+    NCDModuleInst_Backend_PassMemToMethods(i);
+    
+    // read arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // allocate scope
+    o->scope = BAlloc(sizeof(*o->scope));
+    if (!o->scope) {
+        ModuleLog(i, BLOG_ERROR, "BAlloc failed");
+        goto fail0;
+    }
+    
+    // init reference target
+    BRefTarget_Init(&o->scope->ref_target, scope_ref_target_func_release);
+    
+    // init provide and depend lists
+    LinkedList1_Init(&o->scope->provides_list);
+    LinkedList1_Init(&o->scope->depends_list);
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void scope_func_die (void *vo)
+{
+    struct scope_instance *o = vo;
+    
+    // release scope reference
+    BRefTarget_Deref(&o->scope->ref_target);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void provide_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct provide *o = vo;
+    o->i = i;
+    o->scope = ((struct scope_instance *)params->method_user)->scope;
+    
+    // read arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // remember name
+    o->name = name_arg;
+    
+    // check for existing provide with this name
+    if (find_provide(o->scope, o->name)) {
+        ModuleLog(o->i, BLOG_ERROR, "a provide with this name already exists");
+        goto fail0;
+    }
+    
+    // grab scope reference
+    if (!BRefTarget_Ref(&o->scope->ref_target)) {
+        ModuleLog(o->i, BLOG_ERROR, "BRefTarget_Ref failed");
+        goto fail0;
+    }
+    
+    // insert to provides list
+    LinkedList1_Append(&o->scope->provides_list, &o->provides_list_node);
+    
+    // init depends list
+    LinkedList1_Init(&o->depends_list);
+    
+    // set not dying
+    o->dying = 0;
+    
+    // signal up.
+    // This comes above the loop which follows, so that effects on related depend statements are
+    // computed before this process advances, avoiding problems like failed variable resolutions.
+    NCDModuleInst_Backend_Up(o->i);
+    
+    // update depends
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->scope->depends_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct depend *depend = UPPER_OBJECT(ln, struct depend, depends_list_node);
+        depend_update(depend);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void provide_free (struct provide *o)
+{
+    ASSERT(LinkedList1_IsEmpty(&o->depends_list))
+    
+    // remove from provides list
+    LinkedList1_Remove(&o->scope->provides_list, &o->provides_list_node);
+    
+    // release scope reference
+    BRefTarget_Deref(&o->scope->ref_target);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void provide_func_die (void *vo)
+{
+    struct provide *o = vo;
+    ASSERT(!o->dying)
+    
+    // if we have no depends, die immediately
+    if (LinkedList1_IsEmpty(&o->depends_list)) {
+        provide_free(o);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+    
+    // start collapsing our depends
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->depends_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct depend *depend = UPPER_OBJECT(ln, struct depend, provide_depends_list_node);
+        ASSERT(depend->provide == o)
+        
+        // update depend to make sure it is collapsing
+        depend_update(depend);
+    }
+}
+
+static void depend_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct depend *o = vo;
+    o->i = i;
+    o->scope = ((struct scope_instance *)params->method_user)->scope;
+    
+    // read arguments
+    NCDValRef names_arg;
+    if (!NCDVal_ListRead(params->args, 1, &names_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsList(names_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // remember names
+    o->names = names_arg;
+    
+    // grab scope reference
+    if (!BRefTarget_Ref(&o->scope->ref_target)) {
+        ModuleLog(o->i, BLOG_ERROR, "BRefTarget_Ref failed");
+        goto fail0;
+    }
+    
+    // insert to depends list
+    LinkedList1_Append(&o->scope->depends_list, &o->depends_list_node);
+    
+    // set no provide
+    o->provide = NULL;
+    
+    // update
+    depend_update(o);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void depend_func_die (void *vo)
+{
+    struct depend *o = vo;
+    
+    if (o->provide) {
+        // remove from provide's list
+        LinkedList1_Remove(&o->provide->depends_list, &o->provide_depends_list_node);
+        
+        // if provide is dying and is empty, let it die
+        if (o->provide->dying && LinkedList1_IsEmpty(&o->provide->depends_list)) {
+            provide_free(o->provide);
+        }
+    }
+    
+    // remove from depends list
+    LinkedList1_Remove(&o->scope->depends_list, &o->depends_list_node);
+    
+    // release scope reference
+    BRefTarget_Deref(&o->scope->ref_target);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void depend_func_clean (void *vo)
+{
+    struct depend *o = vo;
+    
+    if (!(o->provide && o->provide_collapsing)) {
+        return;
+    }
+    
+    // save provide
+    struct provide *provide = o->provide;
+    
+    // remove from provide's list
+    LinkedList1_Remove(&provide->depends_list, &o->provide_depends_list_node);
+    
+    // set no provide
+    o->provide = NULL;
+    
+    // update
+    depend_update(o);
+    
+    // if provide is dying and is empty, let it die.
+    // This comes after depend_update so that the side effects of the
+    // provide dying have priority over rebinding the depend.
+    if (provide->dying && LinkedList1_IsEmpty(&provide->depends_list)) {
+        provide_free(provide);
+    }
+}
+
+static int depend_func_getobj (void *vo, NCD_string_id_t objname, NCDObject *out_object)
+{
+    struct depend *o = vo;
+    
+    if (!o->provide) {
+        return 0;
+    }
+    
+    return NCDModuleInst_Backend_GetObj(o->provide->i, objname, out_object);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "depend_scope",
+        .func_new2 = scope_func_new,
+        .func_die = scope_func_die,
+        .alloc_size = sizeof(struct scope_instance)
+    }, {
+        .type = "depend_scope::provide",
+        .func_new2 = provide_func_new,
+        .func_die = provide_func_die,
+        .alloc_size = sizeof(struct provide)
+    }, {
+        .type = "depend_scope::depend",
+        .func_new2 = depend_func_new,
+        .func_die = depend_func_die,
+        .func_clean = depend_func_clean,
+        .func_getobj = depend_func_getobj,
+        .flags = NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN,
+        .alloc_size = sizeof(struct depend)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_depend_scope = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/dynamic_depend.c b/external/badvpn_dns/ncd/modules/dynamic_depend.c
new file mode 100644
index 0000000..1fc747a
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/dynamic_depend.c
@@ -0,0 +1,548 @@
+/**
+ * @file dynamic_depend.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Dynamic dependencies module.
+ * 
+ * Synopsis: dynamic_provide(string name, order_value)
+ * Synopsis: dynamic_depend(string name)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/compare.h>
+#include <misc/strdup.h>
+#include <misc/balloc.h>
+#include <structure/LinkedList0.h>
+#include <structure/BAVL.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_dynamic_depend.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleGlobal(i) ((i)->m->group->group_state)
+
+struct provide;
+
+struct name_string {
+    char *data;
+    size_t len;
+};
+
+struct name {
+    struct global *g;
+    struct name_string name;
+    BAVLNode names_tree_node;
+    BAVL provides_tree;
+    LinkedList0 waiting_depends_list;
+    struct provide *cur_p;
+    LinkedList0 cur_bound_depends_list;
+    int cur_resetting;
+};
+
+struct provide {
+    NCDModuleInst *i;
+    struct name *n;
+    NCDValRef order_value;
+    BAVLNode provides_tree_node;
+    int dying;
+};
+
+struct depend {
+    NCDModuleInst *i;
+    struct name *n;
+    int is_bound;
+    LinkedList0Node depends_list_node;
+};
+
+struct global {
+    BAVL names_tree;
+};
+
+static void provide_free (struct provide *o);
+static void depend_free (struct depend *o);
+
+static int name_string_comparator (void *user, void *vv1, void *vv2)
+{
+    struct name_string *v1 = vv1;
+    struct name_string *v2 = vv2;
+    
+    size_t min_len = (v1->len < v2->len ? v1->len : v2->len);
+    
+    int cmp = memcmp(v1->data, v2->data, min_len);
+    if (cmp) {
+        return B_COMPARE(cmp, 0);
+    }
+    
+    return B_COMPARE(v1->len, v2->len);
+}
+
+static int val_comparator (void *user, NCDValRef *v1, NCDValRef *v2)
+{
+    return NCDVal_Compare(*v1, *v2);
+}
+
+static struct name * find_name (struct global *g, const char *name, size_t name_len)
+{
+    struct name_string ns = {(char *)name, name_len};
+    BAVLNode *tn = BAVL_LookupExact(&g->names_tree, &ns);
+    if (!tn) {
+        return NULL;
+    }
+    
+    struct name *n = UPPER_OBJECT(tn, struct name, names_tree_node);
+    ASSERT(n->name.len == name_len)
+    ASSERT(!memcmp(n->name.data, name, name_len))
+    
+    return n;
+}
+
+static struct name * name_init (NCDModuleInst *i, struct global *g, const char *name, size_t name_len)
+{
+    ASSERT(!find_name(g, name, name_len))
+    
+    // allocate structure
+    struct name *o = malloc(sizeof(*o));
+    if (!o) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // set global state
+    o->g = g;
+    
+    // copy name
+    if (!(o->name.data = b_strdup_bin(name, name_len))) {
+        ModuleLog(i, BLOG_ERROR, "strdup failed");
+        goto fail1;
+    }
+    o->name.len = name_len;
+    
+    // insert to names tree
+    ASSERT_EXECUTE(BAVL_Insert(&g->names_tree, &o->names_tree_node, NULL))
+    
+    // init provides tree
+    BAVL_Init(&o->provides_tree, OFFSET_DIFF(struct provide, order_value, provides_tree_node), (BAVL_comparator)val_comparator, NULL);
+    
+    // init waiting depends list
+    LinkedList0_Init(&o->waiting_depends_list);
+    
+    // set no current provide
+    o->cur_p = NULL;
+    
+    return o;
+    
+fail1:
+    free(o);
+fail0:
+    return NULL;
+}
+
+static void name_free (struct name *o)
+{
+    ASSERT(BAVL_IsEmpty(&o->provides_tree))
+    ASSERT(LinkedList0_IsEmpty(&o->waiting_depends_list))
+    ASSERT(!o->cur_p)
+    
+    // remove from names tree
+    BAVL_Remove(&o->g->names_tree, &o->names_tree_node);
+    
+    // free name
+    free(o->name.data);
+    
+    // free structure
+    free(o);
+}
+
+static struct provide * name_get_first_provide (struct name *o)
+{
+    BAVLNode *tn = BAVL_GetFirst(&o->provides_tree);
+    if (!tn) {
+        return NULL;
+    }
+    
+    struct provide *p = UPPER_OBJECT(tn, struct provide, provides_tree_node);
+    ASSERT(p->n == o)
+    
+    return p;
+}
+
+static void name_new_current (struct name *o)
+{
+    ASSERT(!o->cur_p)
+    ASSERT(!BAVL_IsEmpty(&o->provides_tree))
+    
+    // set current provide
+    o->cur_p = name_get_first_provide(o);
+    
+    // init bound depends list
+    LinkedList0_Init(&o->cur_bound_depends_list);
+    
+    // set not resetting
+    o->cur_resetting = 0;
+    
+    // bind waiting depends
+    while (!LinkedList0_IsEmpty(&o->waiting_depends_list)) {
+        struct depend *d = UPPER_OBJECT(LinkedList0_GetFirst(&o->waiting_depends_list), struct depend, depends_list_node);
+        ASSERT(d->n == o)
+        ASSERT(!d->is_bound)
+        
+        // remove from waiting depends list
+        LinkedList0_Remove(&o->waiting_depends_list, &d->depends_list_node);
+        
+        // set bound
+        d->is_bound = 1;
+        
+        // add to bound depends list
+        LinkedList0_Prepend(&o->cur_bound_depends_list, &d->depends_list_node);
+        
+        // signal up
+        NCDModuleInst_Backend_Up(d->i);
+    }
+}
+
+static void name_free_if_unused (struct name *o)
+{
+    if (BAVL_IsEmpty(&o->provides_tree) && LinkedList0_IsEmpty(&o->waiting_depends_list)) {
+        name_free(o);
+    }
+}
+
+static void name_continue_resetting (struct name *o)
+{
+    ASSERT(o->cur_p)
+    ASSERT(o->cur_resetting)
+    
+    // still have bound depends?
+    if (!LinkedList0_IsEmpty(&o->cur_bound_depends_list)) {
+        return;
+    }
+    
+    struct provide *old_p = o->cur_p;
+    
+    // set no current provide
+    o->cur_p = NULL;
+    
+    // free old current provide if it's dying
+    if (old_p->dying) {
+        provide_free(old_p);
+    }
+    
+    if (!BAVL_IsEmpty(&o->provides_tree)) {
+        // get new current provide
+        name_new_current(o);
+    } else {
+        // free name if unused
+        name_free_if_unused(o);
+    }
+}
+
+static void name_start_resetting (struct name *o)
+{
+    ASSERT(o->cur_p)
+    ASSERT(!o->cur_resetting)
+    
+    // set resetting
+    o->cur_resetting = 1;
+    
+    // signal bound depends down
+    for (LinkedList0Node *ln = LinkedList0_GetFirst(&o->cur_bound_depends_list); ln; ln = LinkedList0Node_Next(ln)) {
+        struct depend *d = UPPER_OBJECT(ln, struct depend, depends_list_node);
+        ASSERT(d->n == o)
+        ASSERT(d->is_bound)
+        NCDModuleInst_Backend_Down(d->i);
+    }
+    
+    // if there were no bound depends, continue right away
+    name_continue_resetting(o);
+}
+
+static int func_globalinit (struct NCDInterpModuleGroup *group, const struct NCDModuleInst_iparams *params)
+{
+    // allocate global state structure
+    struct global *g = BAlloc(sizeof(*g));
+    if (!g) {
+        BLog(BLOG_ERROR, "BAlloc failed");
+        return 0;
+    }
+    
+    // set group state pointer
+    group->group_state = g;
+    
+    // init names tree
+    BAVL_Init(&g->names_tree, OFFSET_DIFF(struct name, name, names_tree_node), name_string_comparator, NULL);
+    
+    return 1;
+}
+
+static void func_globalfree (struct NCDInterpModuleGroup *group)
+{
+    struct global *g = group->group_state;
+    ASSERT(BAVL_IsEmpty(&g->names_tree))
+    
+    // free global state structure
+    BFree(g);
+}
+
+static void provide_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct global *g = ModuleGlobal(i);
+    struct provide *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 2, &name_arg, &o->order_value)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    const char *name_str = NCDVal_StringData(name_arg);
+    size_t name_len = NCDVal_StringLength(name_arg);
+    
+    // find name, create new if needed
+    struct name *n = find_name(g, name_str, name_len);
+    if (!n && !(n = name_init(i, g, name_str, name_len))) {
+        goto fail0;
+    }
+    
+    // set name
+    o->n = n;
+    
+    // check for order value conflict
+    if (BAVL_LookupExact(&n->provides_tree, &o->order_value)) {
+        ModuleLog(i, BLOG_ERROR, "order value already exists");
+        goto fail0;
+    }
+    
+    // add to name's provides tree
+    ASSERT_EXECUTE(BAVL_Insert(&n->provides_tree, &o->provides_tree_node, NULL))
+    
+    // set not dying
+    o->dying = 0;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    
+    // should this be the current provide?
+    if (o == name_get_first_provide(n)) {
+        if (!n->cur_p) {
+            name_new_current(n);
+        }
+        else if (!n->cur_resetting) {
+            name_start_resetting(n);
+        }
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void provide_free (struct provide *o)
+{
+    struct name *n = o->n;
+    ASSERT(o->dying)
+    ASSERT(o != n->cur_p)
+    
+    // remove from name's provides tree
+    BAVL_Remove(&n->provides_tree, &o->provides_tree_node);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void provide_func_die (void *vo)
+{
+    struct provide *o = vo;
+    struct name *n = o->n;
+    ASSERT(!o->dying)
+    
+    // set dying
+    o->dying = 1;
+    
+    // if this is not the current provide, die right away
+    if (o != n->cur_p) {
+        // free provide
+        provide_free(o);
+        
+        // free name if unused
+        name_free_if_unused(n);
+        return;
+    }
+    
+    ASSERT(!n->cur_resetting)
+    
+    // start resetting
+    name_start_resetting(n);
+}
+
+static void depend_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct global *g = ModuleGlobal(i);
+    struct depend *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    const char *name_str = NCDVal_StringData(name_arg);
+    size_t name_len = NCDVal_StringLength(name_arg);
+    
+    // find name, create new if needed
+    struct name *n = find_name(g, name_str, name_len);
+    if (!n && !(n = name_init(i, g, name_str, name_len))) {
+        goto fail0;
+    }
+    
+    // set name
+    o->n = n;
+    
+    if (n->cur_p && !n->cur_resetting) {
+        // set bound
+        o->is_bound = 1;
+        
+        // add to bound depends list
+        LinkedList0_Prepend(&n->cur_bound_depends_list, &o->depends_list_node);
+        
+        // signal up
+        NCDModuleInst_Backend_Up(i);
+    } else {
+        // set not bound
+        o->is_bound = 0;
+        
+        // add to waiting depends list
+        LinkedList0_Prepend(&n->waiting_depends_list, &o->depends_list_node);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void depend_func_die (void *vo)
+{
+    struct depend *o = vo;
+    struct name *n = o->n;
+    
+    if (o->is_bound) {
+        ASSERT(n->cur_p)
+        
+        // remove from bound depends list
+        LinkedList0_Remove(&n->cur_bound_depends_list, &o->depends_list_node);
+        
+        // continue resetting
+        if (n->cur_resetting) {
+            name_continue_resetting(n);
+        }
+    } else {
+        // remove from waiting depends list
+        LinkedList0_Remove(&n->waiting_depends_list, &o->depends_list_node);
+        
+        // free name if unused
+        name_free_if_unused(n);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void depend_func_clean (void *vo)
+{
+    struct depend *o = vo;
+    struct name *n = o->n;
+    ASSERT(!o->is_bound || n->cur_p)
+    
+    if (!(o->is_bound && n->cur_resetting)) {
+        return;
+    }
+    
+    // remove from bound depends list
+    LinkedList0_Remove(&n->cur_bound_depends_list, &o->depends_list_node);
+    
+    // set not bound
+    o->is_bound = 0;
+    
+    // add to waiting depends list
+    LinkedList0_Prepend(&n->waiting_depends_list, &o->depends_list_node);
+    
+    // continue resetting
+    name_continue_resetting(n);
+}
+
+static int depend_func_getobj (void *vo, NCD_string_id_t objname, NCDObject *out_object)
+{
+    struct depend *o = vo;
+    struct name *n = o->n;
+    ASSERT(!o->is_bound || n->cur_p)
+    
+    if (!o->is_bound) {
+        return 0;
+    }
+    
+    return NCDModuleInst_Backend_GetObj(n->cur_p->i, objname, out_object);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "dynamic_provide",
+        .func_new2 = provide_func_new,
+        .func_die = provide_func_die,
+        .alloc_size = sizeof(struct provide)
+    }, {
+        .type = "dynamic_depend",
+        .func_new2 = depend_func_new,
+        .func_die = depend_func_die,
+        .func_clean = depend_func_clean,
+        .func_getobj = depend_func_getobj,
+        .flags = NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN,
+        .alloc_size = sizeof(struct depend)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_dynamic_depend = {
+    .func_globalinit = func_globalinit,
+    .func_globalfree = func_globalfree,
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/event_template.c b/external/badvpn_dns/ncd/modules/event_template.c
new file mode 100644
index 0000000..2fcab9e
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/event_template.c
@@ -0,0 +1,184 @@
+/**
+ * @file event_template.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <ncd/modules/event_template.h>
+
+#define TemplateLog(o, ...) NCDModuleInst_Backend_Log((o)->i, (o)->blog_channel, __VA_ARGS__)
+
+static void enable_event (event_template *o)
+{
+    ASSERT(!LinkedList1_IsEmpty(&o->events_list))
+    ASSERT(!o->enabled)
+    
+    // get event
+    struct event_template_event *e = UPPER_OBJECT(LinkedList1_GetFirst(&o->events_list), struct event_template_event, events_list_node);
+    
+    // remove from events list
+    LinkedList1_Remove(&o->events_list, &e->events_list_node);
+    
+    // grab enabled map
+    o->enabled_map = e->map;
+    
+    // append to free list
+    LinkedList1_Append(&o->free_list, &e->events_list_node);
+    
+    // set enabled
+    o->enabled = 1;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+}
+
+void event_template_new (event_template *o, NCDModuleInst *i, int blog_channel, int maxevents, void *user,
+                         event_template_func_free func_free)
+{
+    ASSERT(maxevents > 0)
+    
+    // init arguments
+    o->i = i;
+    o->blog_channel = blog_channel;
+    o->user = user;
+    o->func_free = func_free;
+    
+    // allocate events array
+    if (!(o->events = BAllocArray(maxevents, sizeof(o->events[0])))) {
+        TemplateLog(o, BLOG_ERROR, "BAllocArray failed");
+        goto fail0;
+    }
+    
+    // init events lists
+    LinkedList1_Init(&o->events_list);
+    LinkedList1_Init(&o->free_list);
+    for (int i = 0; i < maxevents; i++) {
+        LinkedList1_Append(&o->free_list, &o->events[i].events_list_node);
+    }
+    
+    // set not enabled
+    o->enabled = 0;
+    
+    return;
+    
+fail0:
+    o->func_free(o->user, 1);
+    return;
+}
+
+void event_template_die (event_template *o)
+{
+    // free enabled map
+    if (o->enabled) {
+        BStringMap_Free(&o->enabled_map);
+    }
+    
+    // free event maps
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->events_list);
+    while (list_node) {
+        struct event_template_event *e = UPPER_OBJECT(list_node, struct event_template_event, events_list_node);
+        BStringMap_Free(&e->map);
+        list_node = LinkedList1Node_Next(list_node);
+    }
+    
+    // free events array
+    BFree(o->events);
+    
+    o->func_free(o->user, 0);
+    return;
+}
+
+int event_template_getvar (event_template *o, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    ASSERT(o->enabled)
+    ASSERT(name)
+    
+    const char *val = BStringMap_Get(&o->enabled_map, name);
+    if (!val) {
+        return 0;
+    }
+    
+    *out = NCDVal_NewString(mem, val);
+    return 1;
+}
+
+void event_template_queue (event_template *o, BStringMap map, int *out_was_empty)
+{
+    ASSERT(!LinkedList1_IsEmpty(&o->free_list))
+    
+    // get event
+    struct event_template_event *e = UPPER_OBJECT(LinkedList1_GetFirst(&o->free_list), struct event_template_event, events_list_node);
+    
+    // remove from free list
+    LinkedList1_Remove(&o->free_list, &e->events_list_node);
+    
+    // set map
+    e->map = map;
+    
+    // insert to events list
+    LinkedList1_Append(&o->events_list, &e->events_list_node);
+    
+    // enable if not already
+    if (!o->enabled) {
+        enable_event(o);
+        *out_was_empty = 1;
+    } else {
+        *out_was_empty = 0;
+    }
+}
+
+void event_template_dequeue (event_template *o, int *out_is_empty)
+{
+    ASSERT(o->enabled)
+    
+    // free enabled map
+    BStringMap_Free(&o->enabled_map);
+    
+    // set not enabled
+    o->enabled = 0;
+    
+    // signal down
+    NCDModuleInst_Backend_Down(o->i);
+    
+    // enable if there are more events
+    if (!LinkedList1_IsEmpty(&o->events_list)) {
+        enable_event(o);
+        *out_is_empty = 0;
+    } else {
+        *out_is_empty = 1;
+    }
+}
+
+int event_template_is_enabled (event_template *o)
+{
+    return o->enabled;
+}
diff --git a/external/badvpn_dns/ncd/modules/event_template.h b/external/badvpn_dns/ncd/modules/event_template.h
new file mode 100644
index 0000000..a3a5ea8
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/event_template.h
@@ -0,0 +1,64 @@
+/**
+ * @file event_template.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCD_MODULES_EVENT_TEMPLATE_H
+#define BADVPN_NCD_MODULES_EVENT_TEMPLATE_H
+
+#include <structure/LinkedList1.h>
+#include <stringmap/BStringMap.h>
+#include <ncd/NCDModule.h>
+
+typedef void (*event_template_func_free) (void *user, int is_error);
+
+typedef struct {
+    NCDModuleInst *i;
+    int blog_channel;
+    void *user;
+    event_template_func_free func_free;
+    struct event_template_event *events;
+    LinkedList1 events_list;
+    LinkedList1 free_list;
+    int enabled;
+    BStringMap enabled_map;
+} event_template;
+
+struct event_template_event {
+    BStringMap map;
+    LinkedList1Node events_list_node;
+};
+
+void event_template_new (event_template *o, NCDModuleInst *i, int blog_channel, int maxevents, void *user,
+                         event_template_func_free func_free);
+void event_template_die (event_template *o);
+int event_template_getvar (event_template *o, const char *name, NCDValMem *mem, NCDValRef *out);
+void event_template_queue (event_template *o, BStringMap map, int *out_was_empty);
+void event_template_dequeue (event_template *o, int *out_is_empty);
+int event_template_is_enabled (event_template *o);
+
+#endif
diff --git a/external/badvpn_dns/ncd/modules/exit.c b/external/badvpn_dns/ncd/modules/exit.c
new file mode 100644
index 0000000..d611e2e
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/exit.c
@@ -0,0 +1,91 @@
+/**
+ * @file exit.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   exit(string exit_code)
+ * 
+ * Description:
+ *   Initiates termination of the interpreter. Calling exit() multiple times will override
+ *   the previously set exit code. When the interpreter receives a signal, it reacts
+ *   equivalently to calling exit(1) (possibly overriding your error code).
+ */
+
+#include <limits.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_exit.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    NCDValRef exit_code_arg;
+    if (!NCDVal_ListRead(params->args, 1, &exit_code_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(exit_code_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse exit code
+    uintmax_t exit_code;
+    if (!ncd_read_uintmax(exit_code_arg, &exit_code) || exit_code >= INT_MAX) {
+        ModuleLog(i, BLOG_ERROR, "wrong exit code value");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    
+    // initiate exit (before up!)
+    NCDModuleInst_Backend_InterpExit(i, exit_code);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "exit",
+        .func_new2 = func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_exit = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/explode.c b/external/badvpn_dns/ncd/modules/explode.c
new file mode 100644
index 0000000..08425d5
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/explode.c
@@ -0,0 +1,232 @@
+/**
+ * @file explode.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   explode(string delimiter, string input [, string limit])
+ * 
+ * Description:
+ *   Splits the string 'input' into a list of components. The first component
+ *   is the part of 'input' until the first occurence of 'delimiter', if any.
+ *   If 'delimiter' was found, the remaining components are defined recursively
+ *   via the same procedure, starting with the part of 'input' after the first
+ *   substring.
+ *   'delimiter' must be nonempty.
+ * 
+ * Variables:
+ *   list (empty) - the components of 'input', determined based on 'delimiter'
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <misc/exparray.h>
+#include <misc/string_begins_with.h>
+#include <misc/substring.h>
+#include <misc/balloc.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_explode.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    struct ExpArray arr;
+    size_t num;
+};
+
+struct substring {
+    char *data;
+    size_t len;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef delimiter_arg;
+    NCDValRef input_arg;
+    NCDValRef limit_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 2, &delimiter_arg, &input_arg) && !NCDVal_ListRead(params->args, 3, &delimiter_arg, &input_arg, &limit_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(delimiter_arg) || !NCDVal_IsString(input_arg) || (!NCDVal_IsInvalid(limit_arg) && !NCDVal_IsString(limit_arg))) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    size_t limit = SIZE_MAX;
+    if (!NCDVal_IsInvalid(limit_arg)) {
+        uintmax_t n;
+        if (!ncd_read_uintmax(limit_arg, &n) || n == 0) {
+            ModuleLog(i, BLOG_ERROR, "bad limit argument");
+            goto fail0;
+        }
+        n--;
+        limit = (n <= SIZE_MAX ? n : SIZE_MAX);
+    }
+    
+    const char *del_data = NCDVal_StringData(delimiter_arg);
+    size_t del_len = NCDVal_StringLength(delimiter_arg);
+    
+    if (del_len == 0) {
+        ModuleLog(i, BLOG_ERROR, "delimiter must be nonempty");
+        goto fail0;
+    }
+    
+    size_t *table = BAllocArray(del_len, sizeof(table[0]));
+    if (!table) {
+        ModuleLog(i, BLOG_ERROR, "ExpArray_init failed");
+        goto fail0;
+    }
+    
+    build_substring_backtrack_table(del_data, del_len, table);
+    
+    if (!ExpArray_init(&o->arr, sizeof(struct substring), 8)) {
+        ModuleLog(i, BLOG_ERROR, "ExpArray_init failed");
+        goto fail1;
+    }
+    o->num = 0;
+    
+    const char *data = NCDVal_StringData(input_arg);
+    size_t len = NCDVal_StringLength(input_arg);
+    
+    while (1) {
+        size_t start;
+        int is_end = 0;
+        if (limit == 0 || !find_substring(data, len, del_data, del_len, table, &start)) {
+            start = len;
+            is_end = 1;
+        }
+        
+        if (!ExpArray_resize(&o->arr, o->num + 1)) {
+            ModuleLog(i, BLOG_ERROR, "ExpArray_init failed");
+            goto fail2;
+        }
+        
+        struct substring *elem = &((struct substring *)o->arr.v)[o->num];
+        
+        if (!(elem->data = BAlloc(start))) {
+            ModuleLog(i, BLOG_ERROR, "BAlloc failed");
+            goto fail2;
+        }
+        
+        memcpy(elem->data, data, start);
+        elem->len = start;
+        o->num++;
+        
+        if (is_end) {
+            break;
+        }
+        
+        data += start + del_len;
+        len -= start + del_len;
+        limit--;
+    }
+    
+    BFree(table);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+
+fail2:
+    while (o->num-- > 0) {
+        BFree(((struct substring *)o->arr.v)[o->num].data);
+    }
+    free(o->arr.v);
+fail1:
+    BFree(table);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    while (o->num-- > 0) {
+        BFree(((struct substring *)o->arr.v)[o->num].data);
+    }
+    free(o->arr.v);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewList(mem, o->num);
+        if (NCDVal_IsInvalid(*out)) {
+            goto fail;
+        }
+        for (size_t j = 0; j < o->num; j++) {
+            struct substring *elem = &((struct substring *)o->arr.v)[j];
+            NCDValRef str = NCDVal_NewStringBin(mem, (uint8_t *)elem->data, elem->len);
+            if (NCDVal_IsInvalid(str)) {
+                goto fail;
+            }
+            if (!NCDVal_ListAppend(*out, str)) {
+                goto fail;
+            }
+        }
+        return 1;
+    }
+    
+    return 0;
+    
+fail:
+    *out = NCDVal_NewInvalid();
+    return 1;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "explode",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_explode = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/file.c b/external/badvpn_dns/ncd/modules/file.c
new file mode 100644
index 0000000..bda93ea
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/file.c
@@ -0,0 +1,350 @@
+/**
+ * @file file.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * File I/O module.
+ * 
+ * Synopsis:
+ *   file_read(string filename)
+ * 
+ * Variables:
+ *   string (empty) - file contents
+ * 
+ * Description:
+ *   Reads the contents of a file. Reports an error if something goes wrong.
+ *   WARNING: this uses fopen/fread/fclose, blocking the entire interpreter while
+ *            the file is being read. For this reason, you should only use this
+ *            to read small local files which will be read quickly, and especially
+ *            not files on network mounts.
+ * 
+ * Synopsis:
+ *   file_write(string filename, string contents)
+ * 
+ * Description:
+ *   Writes a file, possibly overwriting an existing one. Reports an error if something
+ *   goes wrong.
+ *   WARNING: this is not an atomic operation; other programs may see the file in an
+ *            inconsistent state while it is being written. Similarly, if writing
+ *            fails, the file may remain in an inconsistent state indefinitely.
+ *            If this is a problem, you should write the new contents to a temporary
+ *            file and rename this temporary file to the live file.
+ *   WARNING: this uses fopen/fwrite/fclose, blocking the entire interpreter while
+ *            the file is being written. For this reason, you should only use this
+ *            to write small local files which will be written quickly, and especially
+ *            not files on network mounts.
+ * 
+ * Synopsis:
+ *   file_stat(string filename)
+ *   file_lstat(string filename)
+ * 
+ * Description:
+ *   Retrieves information about a file.
+ *   file_stat() follows symlinks; file_lstat() does not and allows retrieving information
+ *   about a symlink.
+ *   WARNING: this blocks the interpreter
+ * 
+ * Variables:
+ *   succeeded - whether the stat operation succeeded (true/false). If false, all other
+ *               variables obtain the value "failed".
+ *   type - file, dir, chr, blk, fifo, link, socket, other, failed
+ *   size - size of the file, or failed
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <misc/read_file.h>
+#include <misc/write_file.h>
+#include <misc/parse_number.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_file.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct read_instance {
+    NCDModuleInst *i;
+    uint8_t *file_data;
+    size_t file_len;
+};
+
+struct stat_instance {
+    NCDModuleInst *i;
+    int succeeded;
+    struct stat result;
+};
+
+static void read_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct read_instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef filename_arg;
+    if (!NCDVal_ListRead(params->args, 1, &filename_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(filename_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // get null terminated name
+    NCDValNullTermString filename_nts;
+    if (!NCDVal_StringNullTerminate(filename_arg, &filename_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // read file
+    int res = read_file(filename_nts.data, &o->file_data, &o->file_len);
+    NCDValNullTermString_Free(&filename_nts);
+    if (!res) {
+        ModuleLog(i, BLOG_ERROR, "failed to read file");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void read_func_die (void *vo)
+{
+    struct read_instance *o = vo;
+    
+    // free data
+    free(o->file_data);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int read_func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct read_instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewStringBin(mem, o->file_data, o->file_len);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void write_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef filename_arg;
+    NCDValRef contents_arg;
+    if (!NCDVal_ListRead(params->args, 2, &filename_arg, &contents_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(filename_arg) || !NCDVal_IsString(contents_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // get null terminated name
+    NCDValNullTermString filename_nts;
+    if (!NCDVal_StringNullTerminate(filename_arg, &filename_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // write file
+    b_cstring contents_cstr = NCDVal_StringCstring(contents_arg);
+    int res = write_file_cstring(filename_nts.data, contents_cstr, 0, contents_cstr.length);
+    NCDValNullTermString_Free(&filename_nts);
+    if (!res) {
+        ModuleLog(i, BLOG_ERROR, "failed to write file");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void stat_func_new_common (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_lstat)
+{
+    struct stat_instance *o = vo;
+    o->i = i;
+    
+    NCDValRef filename_arg;
+    if (!NCDVal_ListRead(params->args, 1, &filename_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(filename_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    o->succeeded = 0;
+    
+    if (!NCDVal_IsStringNoNulls(filename_arg)) {
+        goto out;
+    }
+    
+    // null terminate filename
+    NCDValNullTermString filename_nts;
+    if (!NCDVal_StringNullTerminate(filename_arg, &filename_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    int res;
+    if (is_lstat) {
+        res = lstat(filename_nts.data, &o->result);
+    } else {
+        res = stat(filename_nts.data, &o->result);
+    }
+    NCDValNullTermString_Free(&filename_nts);
+    
+    if (res < 0) {
+        goto out;
+    }
+    
+    o->succeeded = 1;
+    
+out:
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void stat_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    stat_func_new_common(vo, i, params, 0);
+}
+
+static void lstat_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    stat_func_new_common(vo, i, params, 1);
+}
+
+static int stat_func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct stat_instance *o = vo;
+    
+    if (name == NCD_STRING_SUCCEEDED) {
+        *out = ncd_make_boolean(mem, o->succeeded, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    if (name == NCD_STRING_TYPE) {
+        const char *str;
+        
+        if (!o->succeeded) {
+            str = "failed";
+        } else if (S_ISREG(o->result.st_mode)) {
+            str = "file";
+        } else if (S_ISDIR(o->result.st_mode)) {
+            str = "dir";
+        } else if (S_ISCHR(o->result.st_mode)) {
+            str = "chr";
+        } else if (S_ISBLK(o->result.st_mode)) {
+            str = "blk";
+        } else if (S_ISFIFO(o->result.st_mode)) {
+            str = "fifo";
+        } else if (S_ISLNK(o->result.st_mode)) {
+            str = "link";
+        } else if (S_ISSOCK(o->result.st_mode)) {
+            str = "socket";
+        } else {
+            str = "other";
+        }
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    if (name == NCD_STRING_SIZE) {
+        char str[50];
+        if (!o->succeeded) {
+            strcpy(str, "failed");
+        } else {
+            generate_decimal_repr_string((uintmax_t)o->result.st_size, str);
+        }
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "file_read",
+        .func_new2 = read_func_new,
+        .func_die = read_func_die,
+        .func_getvar2 = read_func_getvar2,
+        .alloc_size = sizeof(struct read_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "file_write",
+        .func_new2 = write_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "file_stat",
+        .func_new2 = stat_func_new,
+        .func_getvar2 = stat_func_getvar2,
+        .alloc_size = sizeof(struct stat_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "file_lstat",
+        .func_new2 = lstat_func_new,
+        .func_getvar2 = stat_func_getvar2,
+        .alloc_size = sizeof(struct stat_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_file = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/file_open.c b/external/badvpn_dns/ncd/modules/file_open.c
new file mode 100644
index 0000000..d0a14c3
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/file_open.c
@@ -0,0 +1,585 @@
+/**
+ * @file file_open.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   file_open(string filename, string mode [, map options])
+ * 
+ * Variables:
+ *   string is_error - "true" if the file_open object is in error state, "false"
+ *     otherwise
+ * 
+ * Options:
+ *   "read_size" - the maximum number of bytes that can be read by a single
+ *     read() call. Must be greater than zero. Greater values may improve
+ *     performance, but will increase memory usage. Default: 8192.
+ * 
+ * Description:
+ *   Opens a file for subsequent reading or writing. The 'mode' argument must
+ *   be one of: "r", "w", "a", "r+", "w+", "a+"; it corresponds to the mode string
+ *   that will be passed to the fopen() function.
+ *   When the file_open() statement goes up, the error state is set depending on
+ *   whether opening succeeded or failed. The 'is_error' variable should be used
+ *   to check the error state.
+ *   If an error occurs afterward within read(), write() or seek(), the error state
+ *   is set, and the file_open() statement is toggled down and back up. This way,
+ *   the same piece of user code can handle all file errors.
+ * 
+ * Synopsis:
+ *   file_open::read()
+ * 
+ * Variables:
+ *   string (empty) - the data which was read, or an empty string if EOF was reached
+ *   string not_eof - "false" if EOF was reached, "true" if not
+ * 
+ * Description:
+ *   Reads data from an opened file. The file must not be in error state.
+ *   If reading fails, this statement will never go up, the error state of the
+ *   file_open() statement will be set, and the file_open() statement will trigger
+ *   backtracking (go down and up).
+ * 
+ * Synopsis:
+ *   file_open::write(string data)
+ * 
+ * Description:
+ *   Writes data to an opened file. The file must not be in error state.
+ *   If writing fails, this statement will never go up, the error state of the
+ *   file_open() statement will be set, and the file_open() statement will trigger
+ *   backtracking (go down and up).
+ * 
+ * Synopsis:
+ *   file_open::seek(string position, string whence)
+ * 
+ * Description:
+ *   Sets the file position indicator. The 'position' argument must be a possibly
+ *   negative decimal number, and is interpreted relative to 'whence'. Here, 'whence'
+ *   may be one of:
+ *   - "set", meaning beginning of file,
+ *   - "cur", meaning the current position, and
+ *   - "end", meaning the end of file.
+ *   Errors are handled as in read() and write(). Note that if the position argument
+ *   is too small or too large to convert to off_t, this is not a seek error, and only
+ *   the seek command will fail.
+ * 
+ * Synopsis:
+ *   file_open::close()
+ * 
+ * Description:
+ *   Closes the file. The file must not be in error state.
+ *   Errors are handled as handled as in read() and write(), i.e. the process is
+ *   backtracked to file_open() with the error state set.
+ *   On success, the error state of the file is set (but without backtracking), and
+ *   the close() statement goes up .
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <misc/parse_number.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+#include <ncd/extra/NCDBuf.h>
+
+#include <generated/blog_channel_ncd_file_open.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define READ_BUF_SIZE 8192
+
+struct open_instance {
+    NCDModuleInst *i;
+    FILE *fh;
+    NCDBufStore store;
+};
+
+struct read_instance {
+    NCDModuleInst *i;
+    NCDBuf *buf;
+    size_t length;
+};
+
+static int parse_mode (b_cstring cstr, char *out)
+{
+    size_t pos = 0;
+    size_t left = cstr.length;
+    
+    if (left == 0) {
+        return 0;
+    }
+    switch (b_cstring_at(cstr, pos)) {
+        case 'r':
+        case 'w':
+        case 'a':
+            *out++ = b_cstring_at(cstr, pos);
+            pos++;
+            left--;
+            break;
+        default:
+            return 0;
+    }
+    
+    if (left == 0) {
+        goto finish;
+    }
+    switch (b_cstring_at(cstr, pos)) {
+        case '+':
+            *out++ = b_cstring_at(cstr, pos);
+            pos++;
+            left--;
+            break;
+        default:
+            return 0;
+    }
+    
+    if (left == 0) {
+        goto finish;
+    }
+    
+    return 0;
+    
+finish:
+    *out = '\0';
+    return 1;
+}
+
+static void trigger_error (struct open_instance *o)
+{
+    if (o->fh) {
+        // close file
+        if (fclose(o->fh) != 0) {
+            ModuleLog(o->i, BLOG_ERROR, "fclose failed");
+        }
+        
+        // set no file, indicating error
+        o->fh = NULL;
+    }
+    
+    // go down and up
+    NCDModuleInst_Backend_Down(o->i);
+    NCDModuleInst_Backend_Up(o->i);
+}
+
+static void open_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct open_instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef filename_arg;
+    NCDValRef mode_arg;
+    NCDValRef options_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 2, &filename_arg, &mode_arg) &&
+        !NCDVal_ListRead(params->args, 3, &filename_arg, &mode_arg, &options_arg)
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(filename_arg) || !NCDVal_IsString(mode_arg) ||
+        (!NCDVal_IsInvalid(options_arg) && !NCDVal_IsMap(options_arg))
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // check mode
+    char mode[5];
+    if (!parse_mode(NCDVal_StringCstring(mode_arg), mode)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong mode");
+        goto fail0;
+    }
+    
+    size_t read_size_opt = READ_BUF_SIZE;
+    
+    // parse options
+    if (!NCDVal_IsInvalid(options_arg)) {
+        int num_recognized = 0;
+        NCDValRef value;
+        
+        if (!NCDVal_IsInvalid(value = NCDVal_MapGetValue(options_arg, "read_size"))) {
+            uintmax_t read_size;
+            if (!NCDVal_IsString(value) || !ncd_read_uintmax(value, &read_size) || read_size > SIZE_MAX || read_size == 0) {
+                ModuleLog(o->i, BLOG_ERROR, "wrong read_size");
+                goto fail0;
+            }
+            num_recognized++;
+            read_size_opt = read_size;
+        }
+        
+        if (NCDVal_MapCount(options_arg) > num_recognized) {
+            ModuleLog(o->i, BLOG_ERROR, "unrecognized options present");
+            goto fail0;
+        }
+    }
+    
+    // init store
+    NCDBufStore_Init(&o->store, read_size_opt);
+    
+    // null terminate filename
+    NCDValNullTermString filename_nts;
+    if (!NCDVal_StringNullTerminate(filename_arg, &filename_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail1;
+    }
+    
+    // open file
+    o->fh = fopen(filename_nts.data, mode);
+    NCDValNullTermString_Free(&filename_nts);
+    if (!o->fh) {
+        ModuleLog(o->i, BLOG_ERROR, "fopen failed");
+    }
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail1:
+    NCDBufStore_Free(&o->store);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void open_func_die (void *vo)
+{
+    struct open_instance *o = vo;
+    
+    // close file
+    if (o->fh) {
+        if (fclose(o->fh) != 0) {
+            ModuleLog(o->i, BLOG_ERROR, "fclose failed");
+        }
+    }
+    
+    // free store
+    NCDBufStore_Free(&o->store);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int open_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct open_instance *o = vo;
+    
+    if (name == NCD_STRING_IS_ERROR) {
+        *out = ncd_make_boolean(mem, !o->fh, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void read_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct read_instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get open instance
+    struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure it's not in error
+    if (!open_inst->fh) {
+        ModuleLog(o->i, BLOG_ERROR, "open instance is in error");
+        goto fail0;
+    }
+    
+    // get buffer
+    o->buf = NCDBufStore_GetBuf(&open_inst->store);
+    if (!o->buf) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDBufStore_GetBuf failed");
+        goto fail0;
+    }
+    
+    // starting with empty buffer
+    char *data = NCDBuf_Data(o->buf);
+    size_t buf_size = NCDBufStore_BufSize(&open_inst->store);
+    o->length = 0;
+    
+    while (o->length < buf_size) {
+        // read
+        size_t readed = fread(data + o->length, 1, buf_size - o->length, open_inst->fh);
+        if (readed == 0) {
+            break;
+        }
+        ASSERT(readed <= buf_size - o->length)
+        
+        // increment length
+        o->length += readed;
+    }
+    
+    // if we couldn't read anything due to an error, trigger
+    // error in the open instance, and don't go up
+    if (o->length == 0 && !feof(open_inst->fh)) {
+        ModuleLog(o->i, BLOG_ERROR, "fread failed");
+        trigger_error(open_inst);
+        return;
+    }
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void read_func_die (void *vo)
+{
+    struct read_instance *o = vo;
+    
+    // release buffer
+    BRefTarget_Deref(NCDBuf_RefTarget(o->buf));
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int read_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct read_instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewExternalString(mem, NCDBuf_Data(o->buf), o->length, NCDBuf_RefTarget(o->buf));
+        return 1;
+    }
+    
+    if (name == NCD_STRING_NOT_EOF) {
+        *out = ncd_make_boolean(mem, (o->length != 0), o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void write_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    NCDValRef data_arg;
+    if (!NCDVal_ListRead(params->args, 1, &data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // get open instance
+    struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure it's not in error
+    if (!open_inst->fh) {
+        ModuleLog(i, BLOG_ERROR, "open instance is in error");
+        goto fail0;
+    }
+    
+    // write all the data
+    b_cstring data_cstr = NCDVal_StringCstring(data_arg);
+    B_CSTRING_LOOP(data_cstr, pos, chunk_data, chunk_length, {
+        size_t chunk_pos = 0;
+        while (chunk_pos < chunk_length) {
+            size_t written = fwrite(chunk_data + chunk_pos, 1, chunk_length - chunk_pos, open_inst->fh);
+            if (written == 0) {
+                ModuleLog(i, BLOG_ERROR, "fwrite failed");
+                trigger_error(open_inst);
+                return;
+            }
+            ASSERT(written <= chunk_length - chunk_pos)
+            chunk_pos += written;
+        }
+    })
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void seek_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    NCDValRef position_arg;
+    NCDValRef whence_arg;
+    if (!NCDVal_ListRead(params->args, 2, &position_arg, &whence_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(position_arg) || !NCDVal_IsString(whence_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse position
+    int position_sign;
+    uintmax_t position_mag;
+    b_cstring position_cstr = NCDVal_StringCstring(position_arg);
+    if (!parse_signmag_integer_cstr(position_cstr, 0, position_cstr.length, &position_sign, &position_mag)) {
+        ModuleLog(i, BLOG_ERROR, "wrong position");
+        goto fail0;
+    }
+    
+    // parse whence
+    int whence;
+    if (NCDVal_StringEquals(whence_arg, "set")) {
+        whence = SEEK_SET;
+    }
+    else if (NCDVal_StringEquals(whence_arg, "cur")) {
+        whence = SEEK_CUR;
+    }
+    else if (NCDVal_StringEquals(whence_arg, "end")) {
+        whence = SEEK_END;
+    }
+    else {
+        ModuleLog(i, BLOG_ERROR, "wrong whence");
+        goto fail0;
+    }
+    
+    // determine min/max values of off_t (non-portable hack)
+    off_t off_t_min = (sizeof(off_t) == 8 ? INT64_MIN : INT32_MIN);
+    off_t off_t_max = (sizeof(off_t) == 8 ? INT64_MAX : INT32_MAX);
+    
+    // compute position as off_t
+    off_t position;
+    if (position_sign < 0 && position_mag > 0) {
+        if (position_mag - 1 > -(off_t_min + 1)) {
+            ModuleLog(i, BLOG_ERROR, "position underflow");
+            goto fail0;
+        }
+        position = -(off_t)(position_mag - 1) - 1;
+    } else {
+        if (position_mag > off_t_max) {
+            ModuleLog(i, BLOG_ERROR, "position overflow");
+            goto fail0;
+        }
+        position = position_mag;
+    }
+    
+    // get open instance
+    struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure it's not in error
+    if (!open_inst->fh) {
+        ModuleLog(i, BLOG_ERROR, "open instance is in error");
+        goto fail0;
+    }
+    
+    // seek
+    if (fseeko(open_inst->fh, position, whence) < 0) {
+        ModuleLog(i, BLOG_ERROR, "fseeko failed");
+        trigger_error(open_inst);
+        return;
+    }
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void close_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get open instance
+    struct open_instance *open_inst = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure it's not in error
+    if (!open_inst->fh) {
+        ModuleLog(i, BLOG_ERROR, "open instance is in error");
+        goto fail0;
+    }
+    
+    // close
+    int res = fclose(open_inst->fh);
+    open_inst->fh = NULL;
+    if (res != 0) {
+        ModuleLog(i, BLOG_ERROR, "fclose failed");
+        trigger_error(open_inst);
+        return;
+    }
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "file_open",
+        .func_new2 = open_func_new,
+        .func_die = open_func_die,
+        .func_getvar2 = open_func_getvar,
+        .alloc_size = sizeof(struct open_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "file_open::read",
+        .func_new2 = read_func_new,
+        .func_die = read_func_die,
+        .func_getvar2 = read_func_getvar,
+        .alloc_size = sizeof(struct read_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "file_open::write",
+        .func_new2 = write_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "file_open::seek",
+        .func_new2 = seek_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "file_open::close",
+        .func_new2 = close_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_file_open = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/foreach.c b/external/badvpn_dns/ncd/modules/foreach.c
new file mode 100644
index 0000000..f0b3eff
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/foreach.c
@@ -0,0 +1,715 @@
+/**
+ * @file foreach.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   foreach(list/map collection, string template, list args)
+ * 
+ * Description:
+ *   Initializes a template process for each element of list, sequentially,
+ *   obeying to the usual execution model of NCD.
+ *   It's equivalent to (except for special variables):
+ * 
+ *   call(template, args);
+ *   ...
+ *   call(template, args); # one call() for every element of list
+ * 
+ * Template process specials:
+ * 
+ *   _index - (lists only) index of the list element corresponding to the template,
+ *            process, as a decimal string, starting from zero
+ *   _elem - (lists only) element of list corresponding to the template process
+ *   _key - (maps only) key of the current map entry
+ *   _val - (maps only) value of the current map entry
+ *   _caller.X - X as seen from the foreach() statement
+ * 
+ * Synopsis:
+ *   foreach_emb(list/map collection, string template, string name1 [, string name2])
+ * 
+ * Description:
+ *   Foreach for embedded templates; the desugaring process converts Foreach
+ *   clauses into this statement. The called templates have direct access to
+ *   objects as seen from this statement, and also some kind of access to the
+ *   current element of the iteration, depending on the type of collection
+ *   being iterated, and whether 'name2' is provided:
+ *   List and one name: current element is named 'name1'.
+ *   List and both names: current index is named 'name1', current element 'name2'.
+ *   Map and one name: current key is named 'name1'.
+ *   Map and both names: current key is named 'name1', current value 'name2'.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <misc/balloc.h>
+#include <misc/string_begins_with.h>
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <system/BReactor.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_foreach.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+#define ISTATE_WORKING 1
+#define ISTATE_UP 2
+#define ISTATE_WAITING 3
+#define ISTATE_TERMINATING 4
+
+#define ESTATE_FORGOTTEN 1
+#define ESTATE_DOWN 2
+#define ESTATE_UP 3
+#define ESTATE_WAITING 4
+#define ESTATE_TERMINATING 5
+
+struct element;
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValRef template_name;
+    NCDValRef args;
+    NCD_string_id_t name1;
+    NCD_string_id_t name2;
+    BTimer timer;
+    struct element *elems;
+    int type;
+    int num_elems;
+    int gp; // good pointer
+    int ip; // initialized pointer
+    int state;
+};
+
+struct element {
+    struct instance *inst;
+    union {
+        struct {
+            NCDValRef list_elem;
+        };
+        struct {
+            NCDValRef map_key;
+            NCDValRef map_val;
+        };
+    };
+    NCDModuleProcess process;
+    int i;
+    int state;
+};
+
+static void assert_state (struct instance *o);
+static void work (struct instance *o);
+static void advance (struct instance *o);
+static void timer_handler (struct instance *o);
+static void element_process_handler_event (NCDModuleProcess *process, int event);
+static int element_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
+static int element_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static int element_list_index_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out);
+static int element_list_elem_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out);
+static int element_map_key_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out);
+static int element_map_val_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out);
+static void instance_free (struct instance *o);
+
+enum {STRING_INDEX, STRING_ELEM, STRING_KEY, STRING_VAL};
+
+static const char *strings[] = {
+    "_index", "_elem", "_key", "_val", NULL
+};
+
+static void assert_state (struct instance *o)
+{
+    ASSERT(o->num_elems >= 0)
+    ASSERT(o->gp >= 0)
+    ASSERT(o->ip >= 0)
+    ASSERT(o->gp <= o->num_elems)
+    ASSERT(o->ip <= o->num_elems)
+    ASSERT(o->gp <= o->ip)
+    
+#ifndef NDEBUG
+    // check GP
+    for (int i = 0; i < o->gp; i++) {
+        if (i == o->gp - 1) {
+            ASSERT(o->elems[i].state == ESTATE_UP || o->elems[i].state == ESTATE_DOWN ||
+                   o->elems[i].state == ESTATE_WAITING)
+        } else {
+            ASSERT(o->elems[i].state == ESTATE_UP)
+        }
+    }
+    
+    // check IP
+    int ip = o->num_elems;
+    while (ip > 0 && o->elems[ip - 1].state == ESTATE_FORGOTTEN) {
+        ip--;
+    }
+    ASSERT(o->ip == ip)
+    
+    // check gap
+    for (int i = o->gp; i < o->ip; i++) {
+        if (i == o->ip - 1) {
+            ASSERT(o->elems[i].state == ESTATE_UP || o->elems[i].state == ESTATE_DOWN ||
+                   o->elems[i].state == ESTATE_WAITING || o->elems[i].state == ESTATE_TERMINATING)
+        } else {
+            ASSERT(o->elems[i].state == ESTATE_UP || o->elems[i].state == ESTATE_DOWN ||
+                   o->elems[i].state == ESTATE_WAITING)
+        }
+    }
+#endif
+}
+
+static void work (struct instance *o)
+{
+    assert_state(o);
+    
+    // stop timer
+    BReactor_RemoveTimer(o->i->params->iparams->reactor, &o->timer);
+    
+    if (o->state == ISTATE_WAITING) {
+        return;
+    }
+    
+    if (o->state == ISTATE_UP && !(o->gp == o->ip && o->gp == o->num_elems && (o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP))) {
+        // signal down
+        NCDModuleInst_Backend_Down(o->i);
+        
+        // set state waiting
+        o->state = ISTATE_WAITING;
+        return;
+    }
+    
+    if (o->gp < o->ip) {
+        // get last element
+        struct element *le = &o->elems[o->ip - 1];
+        ASSERT(le->state != ESTATE_FORGOTTEN)
+        
+        // start terminating if not already
+        if (le->state != ESTATE_TERMINATING) {
+            // request termination
+            NCDModuleProcess_Terminate(&le->process);
+            
+            // set element state terminating
+            le->state = ESTATE_TERMINATING;
+        }
+        
+        return;
+    }
+    
+    if (o->state == ISTATE_TERMINATING) {
+        // finally die
+        instance_free(o);
+        return;
+    }
+    
+    if (o->gp == o->num_elems && (o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP)) {
+        if (o->state == ISTATE_WORKING) {
+            // signal up
+            NCDModuleInst_Backend_Up(o->i);
+            
+            // set state up
+            o->state = ISTATE_UP;
+        }
+        
+        return;
+    }
+    
+    if (o->gp > 0 && o->elems[o->gp - 1].state == ESTATE_WAITING) {
+        // get last element
+        struct element *le = &o->elems[o->gp - 1];
+        
+        // continue process
+        NCDModuleProcess_Continue(&le->process);
+        
+        // set state down
+        le->state = ESTATE_DOWN;
+        return;
+    }
+    
+    if (o->gp > 0 && o->elems[o->gp - 1].state == ESTATE_DOWN) {
+        return;
+    }
+    
+    ASSERT(o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP)
+    
+    advance(o);
+    return;
+}
+
+static void advance (struct instance *o)
+{
+    assert_state(o);
+    ASSERT(o->gp == o->ip)
+    ASSERT(o->gp < o->num_elems)
+    ASSERT(o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP)
+    ASSERT(o->elems[o->gp].state == ESTATE_FORGOTTEN)
+    
+    // get next element
+    struct element *e = &o->elems[o->gp];
+    
+    // init process
+    if (!NCDModuleProcess_InitValue(&e->process, o->i, o->template_name, o->args, element_process_handler_event)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        goto fail;
+    }
+    
+    // set special functions
+    NCDModuleProcess_SetSpecialFuncs(&e->process, element_process_func_getspecialobj);
+    
+    // set element state down
+    e->state = ESTATE_DOWN;
+    
+    // increment GP and IP
+    o->gp++;
+    o->ip++;
+    return;
+    
+fail:
+    // set timer
+    BReactor_SetTimer(o->i->params->iparams->reactor, &o->timer);
+}
+
+static void timer_handler (struct instance *o)
+{
+    assert_state(o);
+    ASSERT(o->gp == o->ip)
+    ASSERT(o->gp < o->num_elems)
+    ASSERT(o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP)
+    ASSERT(o->elems[o->gp].state == ESTATE_FORGOTTEN)
+    
+    advance(o);
+    return;
+}
+
+static void element_process_handler_event (NCDModuleProcess *process, int event)
+{
+    struct element *e = UPPER_OBJECT(process, struct element, process);
+    struct instance *o = e->inst;
+    assert_state(o);
+    ASSERT(e->i < o->ip)
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(e->state == ESTATE_DOWN)
+            ASSERT(o->gp == o->ip)
+            ASSERT(o->gp == e->i + 1)
+            
+            // set element state up
+            e->state = ESTATE_UP;
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(e->state == ESTATE_UP)
+            
+            // set element state waiting
+            e->state = ESTATE_WAITING;
+            
+            // bump down GP
+            if (o->gp > e->i + 1) {
+                o->gp = e->i + 1;
+            }
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(e->state == ESTATE_TERMINATING)
+            ASSERT(o->gp < o->ip)
+            ASSERT(o->ip == e->i + 1)
+            
+            // free process
+            NCDModuleProcess_Free(&e->process);
+            
+            // set element state forgotten
+            e->state = ESTATE_FORGOTTEN;
+            
+            // decrement IP
+            o->ip--;
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    work(o);
+    return;
+}
+
+static int element_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct element *e = UPPER_OBJECT(process, struct element, process);
+    struct instance *o = e->inst;
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    
+    switch (o->type) {
+        case NCDVAL_LIST: {
+            NCD_string_id_t index_name = (o->name2 >= 0 ? o->name1 : -1);
+            NCD_string_id_t elem_name = (o->name2 >= 0 ? o->name2 : o->name1);
+            
+            if (index_name >= 0 && name == index_name) {
+                *out_object = NCDObject_Build(-1, e, element_list_index_object_func_getvar, NCDObject_no_getobj);
+                return 1;
+            }
+            
+            if (name == elem_name) {
+                *out_object = NCDObject_Build(-1, e, element_list_elem_object_func_getvar, NCDObject_no_getobj);
+                return 1;
+            }
+        } break;
+        case NCDVAL_MAP: {
+            NCD_string_id_t key_name = o->name1;
+            NCD_string_id_t val_name = o->name2;
+            
+            if (name == key_name) {
+                *out_object = NCDObject_Build(-1, e, element_map_key_object_func_getvar, NCDObject_no_getobj);
+                return 1;
+            }
+            
+            if (val_name >= 0 && name == val_name) {
+                *out_object = NCDObject_Build(-1, e, element_map_val_object_func_getvar, NCDObject_no_getobj);
+                return 1;
+            }
+        } break;
+    }
+    
+    if (NCDVal_IsInvalid(o->args)) {
+        return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
+    }
+    
+    if (name == NCD_STRING_CALLER) {
+        *out_object = NCDObject_Build(-1, e, NCDObject_no_getvar, element_caller_object_func_getobj);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int element_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct element *e = NCDObject_DataPtr(obj);
+    struct instance *o = e->inst;
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    ASSERT(!NCDVal_IsInvalid(o->args))
+    
+    return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
+}
+
+static int element_list_index_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct element *e = NCDObject_DataPtr(obj);
+    struct instance *o = e->inst;
+    B_USE(o)
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    ASSERT(o->type == NCDVAL_LIST)
+    
+    if (name != NCD_STRING_EMPTY) {
+        return 0;
+    }
+    
+    *out = ncd_make_uintmax(mem, e->i);
+    return 1;
+}
+
+static int element_list_elem_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct element *e = NCDObject_DataPtr(obj);
+    struct instance *o = e->inst;
+    B_USE(o)
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    ASSERT(o->type == NCDVAL_LIST)
+    
+    if (name != NCD_STRING_EMPTY) {
+        return 0;
+    }
+    
+    *out = NCDVal_NewCopy(mem, e->list_elem);
+    return 1;
+}
+
+static int element_map_key_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct element *e = NCDObject_DataPtr(obj);
+    struct instance *o = e->inst;
+    B_USE(o)
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    ASSERT(o->type == NCDVAL_MAP)
+    
+    if (name != NCD_STRING_EMPTY) {
+        return 0;
+    }
+    
+    *out = NCDVal_NewCopy(mem, e->map_key);
+    return 1;
+}
+
+static int element_map_val_object_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct element *e = NCDObject_DataPtr(obj);
+    struct instance *o = e->inst;
+    B_USE(o)
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    ASSERT(o->type == NCDVAL_MAP)
+    
+    if (name != NCD_STRING_EMPTY) {
+        return 0;
+    }
+    
+    *out = NCDVal_NewCopy(mem, e->map_val);
+    return 1;
+}
+
+static void func_new_common (void *vo, NCDModuleInst *i, NCDValRef collection, NCDValRef template_name, NCDValRef args, NCD_string_id_t name1, NCD_string_id_t name2)
+{
+    ASSERT(!NCDVal_IsInvalid(collection))
+    ASSERT(NCDVal_IsString(template_name))
+    ASSERT(NCDVal_IsInvalid(args) || NCDVal_IsList(args))
+    ASSERT(name1 >= 0)
+    
+    struct instance *o = vo;
+    o->i = i;
+    
+    o->type = NCDVal_Type(collection);
+    o->template_name = template_name;
+    o->args = args;
+    o->name1 = name1;
+    o->name2 = name2;
+    
+    // init timer
+    btime_t retry_time = NCDModuleInst_Backend_InterpGetRetryTime(i);
+    BTimer_Init(&o->timer, retry_time, (BTimer_handler)timer_handler, o);
+    
+    size_t num_elems;
+    NCDValMapElem cur_map_elem;
+    
+    switch (o->type) {
+        case NCDVAL_LIST: {
+            num_elems = NCDVal_ListCount(collection);
+        } break;
+        case NCDVAL_MAP: {
+            num_elems = NCDVal_MapCount(collection);
+            cur_map_elem = NCDVal_MapOrderedFirst(collection); 
+        } break;
+        default:
+            ModuleLog(i, BLOG_ERROR, "invalid collection type");
+            goto fail0;
+    }
+    
+    if (num_elems > INT_MAX) {
+        ModuleLog(i, BLOG_ERROR, "too many elements");
+        goto fail0;
+    }
+    o->num_elems = num_elems;
+    
+    // allocate elements
+    if (!(o->elems = BAllocArray(o->num_elems, sizeof(o->elems[0])))) {
+        ModuleLog(i, BLOG_ERROR, "BAllocArray failed");
+        goto fail0;
+    }
+    
+    for (int j = 0; j < o->num_elems; j++) {
+        struct element *e = &o->elems[j];
+        
+        // set instance
+        e->inst = o;
+        
+        // set index
+        e->i = j;
+        
+        // set state forgotten
+        e->state = ESTATE_FORGOTTEN;
+        
+        // set values
+        switch (o->type) {
+            case NCDVAL_LIST: {
+                e->list_elem = NCDVal_ListGet(collection, j);
+            } break;
+            case NCDVAL_MAP: {
+                e->map_key = NCDVal_MapElemKey(collection, cur_map_elem);
+                e->map_val = NCDVal_MapElemVal(collection, cur_map_elem);
+                cur_map_elem = NCDVal_MapOrderedNext(collection, cur_map_elem);
+            } break;
+        }
+    }
+    
+    // set GP and IP zero
+    o->gp = 0;
+    o->ip = 0;
+    
+    // set state working
+    o->state = ISTATE_WORKING;
+    
+    work(o);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_foreach (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef arg_collection;
+    NCDValRef arg_template;
+    NCDValRef arg_args;
+    if (!NCDVal_ListRead(params->args, 3, &arg_collection, &arg_template, &arg_args)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(arg_template) || !NCDVal_IsList(arg_args)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    NCD_string_id_t name1;
+    NCD_string_id_t name2;
+    
+    switch (NCDVal_Type(arg_collection)) {
+        case NCDVAL_LIST: {
+            name1 = ModuleString(i, STRING_INDEX);
+            name2 = ModuleString(i, STRING_ELEM);
+        } break;
+        case NCDVAL_MAP: {
+            name1 = ModuleString(i, STRING_KEY);
+            name2 = ModuleString(i, STRING_VAL);
+        } break;
+        default:
+            ModuleLog(i, BLOG_ERROR, "invalid collection type");
+            goto fail0;
+    }
+    
+    func_new_common(vo, i, arg_collection, arg_template, arg_args, name1, name2);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_foreach_emb (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef arg_collection;
+    NCDValRef arg_template;
+    NCDValRef arg_name1;
+    NCDValRef arg_name2 = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 3, &arg_collection, &arg_template, &arg_name1) && !NCDVal_ListRead(params->args, 4, &arg_collection, &arg_template, &arg_name1, &arg_name2)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(arg_template) || !NCDVal_IsString(arg_name1) || (!NCDVal_IsInvalid(arg_name2) && !NCDVal_IsString(arg_name2))) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    NCD_string_id_t name1 = ncd_get_string_id(arg_name1, i->params->iparams->string_index);
+    if (name1 < 0) {
+        ModuleLog(i, BLOG_ERROR, "ncd_get_string_id failed");
+        goto fail0;
+    }
+    
+    NCD_string_id_t name2 = -1;
+    if (!NCDVal_IsInvalid(arg_name2)) {
+        name2 = ncd_get_string_id(arg_name2, i->params->iparams->string_index);
+        if (name2 < 0) {
+            ModuleLog(i, BLOG_ERROR, "ncd_get_string_id failed");
+            goto fail0;
+        }
+    }
+    
+    func_new_common(vo, i, arg_collection, arg_template, NCDVal_NewInvalid(), name1, name2);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o)
+{
+    ASSERT(o->gp == 0)
+    ASSERT(o->ip == 0)
+    
+    // free elements
+    BFree(o->elems);
+    
+    // free timer
+    BReactor_RemoveTimer(o->i->params->iparams->reactor, &o->timer);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    assert_state(o);
+    ASSERT(o->state != ISTATE_TERMINATING)
+    
+    // set GP zero
+    o->gp = 0;
+    
+    // set state terminating
+    o->state = ISTATE_TERMINATING;
+    
+    work(o);
+    return;
+}
+
+static void func_clean (void *vo)
+{
+    struct instance *o = vo;
+    
+    if (o->state != ISTATE_WAITING) {
+        return;
+    }
+    
+    // set state working
+    o->state = ISTATE_WORKING;
+    
+    work(o);
+    return;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "foreach",
+        .func_new2 = func_new_foreach,
+        .func_die = func_die,
+        .func_clean = func_clean,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "foreach_emb",
+        .func_new2 = func_new_foreach_emb,
+        .func_die = func_die,
+        .func_clean = func_clean,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_foreach = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/from_string.c b/external/badvpn_dns/ncd/modules/from_string.c
new file mode 100644
index 0000000..3bc446b
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/from_string.c
@@ -0,0 +1,125 @@
+/**
+ * @file from_string.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   from_string(string str)
+ * Variables:
+ *   (empty) - str, parsed as a value
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/NCDValParser.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_from_string.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValMem mem;
+    NCDValRef val;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef str_arg;
+    if (!NCDVal_ListRead(params->args, 1, &str_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(str_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // init mem
+    NCDValMem_Init(&o->mem);
+    
+    // parse value string
+    if (!NCDValParser_Parse(NCDVal_StringData(str_arg), NCDVal_StringLength(str_arg), &o->mem, &o->val)) {
+        ModuleLog(i, BLOG_ERROR, "failed to parse");
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail1:
+    NCDValMem_Free(&o->mem);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free mem
+    NCDValMem_Free(&o->mem);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewCopy(mem, o->val);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "from_string",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_from_string = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/getargs.c b/external/badvpn_dns/ncd/modules/getargs.c
new file mode 100644
index 0000000..f620a65
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/getargs.c
@@ -0,0 +1,98 @@
+/**
+ * @file getargs.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   getargs()
+ * 
+ * Variables:
+ *   (empty) - list of extra command line arguments that were passed to the intrepreter
+ */
+
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_getargs.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        if (!NCDModuleInst_Backend_InterpGetArgs(o->i, mem, out)) {
+            ModuleLog(o->i, BLOG_ERROR, "NCDModuleInst_Backend_InterpGetArgs failed");
+            return 0;
+        }
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "getargs",
+        .func_new2 = func_new,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_getargs = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/getenv.c b/external/badvpn_dns/ncd/modules/getenv.c
new file mode 100644
index 0000000..2cb3e91
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/getenv.c
@@ -0,0 +1,153 @@
+/**
+ * @file getenv.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   getenv(string name)
+ * 
+ * Variables:
+ *   string (empty) - if the environment value exists, its value
+ *   string exists - "true" if the variable exists, "false" if not
+ */
+
+#include <stdlib.h>
+
+#include <misc/strdup.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_getenv.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+struct instance {
+    NCDModuleInst *i;
+    char *value;
+};
+
+enum {STRING_EXISTS};
+
+static const char *strings[] = {"exists", NULL};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    o->value = NULL;
+    
+    if (NCDVal_StringHasNulls(name_arg)) {
+        goto out;
+    }
+    
+    NCDValNullTermString nts;
+    if (!NCDVal_StringNullTerminate(name_arg, &nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    char *result = getenv(nts.data);
+    NCDValNullTermString_Free(&nts);
+    if (!result) {
+        goto out;
+    }
+    
+    o->value = b_strdup(result);
+    if (!o->value) {
+        ModuleLog(i, BLOG_ERROR, "b_strdup failed");
+        goto fail0;
+    }
+    
+out:    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free value
+    if (o->value) {
+        BFree(o->value);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY && o->value) {
+        *out = NCDVal_NewString(mem, o->value);
+        return 1;
+    }
+    
+    if (name == ModuleString(o->i, STRING_EXISTS)) {
+        *out = ncd_make_boolean(mem, !!o->value, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "getenv",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_getenv = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/if.c b/external/badvpn_dns/ncd/modules/if.c
new file mode 100644
index 0000000..3bbd362
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/if.c
@@ -0,0 +1,103 @@
+/**
+ * @file if.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Conditional module.
+ * 
+ * Synopsis: if(string cond)
+ * Description: on initialization, transitions to UP state if cond equals "true", else
+ *      remains in the DOWN state indefinitely.
+ * 
+ * Synopsis: ifnot(string cond)
+ * Description: on initialization, transitions to UP state if cond does not equal "true", else
+ *      remains in the DOWN state indefinitely.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_if.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void new_templ (NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_not)
+{
+    // check arguments
+    NCDValRef arg;
+    if (!NCDVal_ListRead(params->args, 1, &arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // compute logical value of argument
+    int c = ncd_read_boolean(arg);
+    
+    // signal up if needed
+    if ((is_not && !c) || (!is_not && c)) {
+        NCDModuleInst_Backend_Up(i);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(i, params, 0);
+}
+
+static void func_new_not (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(i, params, 1);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "if",
+        .func_new2 = func_new
+    }, {
+        .type = "ifnot",
+        .func_new2 = func_new_not
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_if = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/imperative.c b/external/badvpn_dns/ncd/modules/imperative.c
new file mode 100644
index 0000000..f964888
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/imperative.c
@@ -0,0 +1,324 @@
+/**
+ * @file imperative.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Imperative statement.
+ * 
+ * Synopsis:
+ *   imperative(string init_template, list init_args, string deinit_template, list deinit_args, string deinit_timeout)
+ * 
+ * Description:
+ *   Does the following, in order:
+ *     1. Starts a template process from (init_template, init_args) and waits for it to
+ *        initialize completely.
+ *     2. Initiates termination of the process and wait for it to terminate.
+ *     3. Puts the statement UP, then waits for a statement termination request (which may
+ *        already have been received).
+ *     4. Starts a template process from (deinit_template, deinit_args) and waits for it
+ *        to initialize completely, or for the timeout to elapse.
+ *     5. Initiates termination of the process and wait for it to terminate.
+ *     6. Terminates the statement.
+ * 
+ *   If init_template="<none>", steps (1-2) are skipped.
+ *   If deinit_template="<none>", steps (4-5) are skipped.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/string_begins_with.h>
+#include <misc/offset.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_imperative.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define STATE_INIT_WORKING 1
+#define STATE_INIT_CLEANING 2
+#define STATE_UP 3
+#define STATE_DEINIT_WORKING 4
+#define STATE_DEINIT_CLEANING 5
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValRef deinit_template;
+    NCDValRef deinit_args;
+    BTimer deinit_timer;
+    NCDModuleProcess process;
+    int state;
+    int dying;
+};
+
+static int start_process (struct instance *o, NCDValRef template_name, NCDValRef args, NCDModuleProcess_handler_event handler);
+static void go_deinit (struct instance *o);
+static void init_process_handler_event (NCDModuleProcess *process, int event);
+static void deinit_process_handler_event (NCDModuleProcess *process, int event);
+static int process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
+static int process_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static void deinit_timer_handler (struct instance *o);
+static void instance_free (struct instance *o);
+
+static int start_process (struct instance *o, NCDValRef template_name, NCDValRef args, NCDModuleProcess_handler_event handler)
+{
+    ASSERT(NCDVal_IsString(template_name))
+    ASSERT(NCDVal_IsList(args))
+    
+    // create process
+    if (!NCDModuleProcess_InitValue(&o->process, o->i, template_name, args, handler)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        return 0;
+    }
+    
+    // set special functions
+    NCDModuleProcess_SetSpecialFuncs(&o->process, process_func_getspecialobj);
+    return 1;
+}
+
+static void go_deinit (struct instance *o)
+{
+    ASSERT(o->dying)
+    
+    // deinit is no-op?
+    if (ncd_is_none(o->deinit_template)) {
+        instance_free(o);
+        return;
+    }
+    
+    // start deinit process
+    if (!start_process(o, o->deinit_template, o->deinit_args, deinit_process_handler_event)) {
+        instance_free(o);
+        return;
+    }
+    
+    // start timer
+    BReactor_SetTimer(o->i->params->iparams->reactor, &o->deinit_timer);
+    
+    // set state deinit working
+    o->state = STATE_DEINIT_WORKING;
+}
+
+static void init_process_handler_event (NCDModuleProcess *process, int event)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(o->state == STATE_INIT_WORKING)
+            
+            // start terminating
+            NCDModuleProcess_Terminate(&o->process);
+            
+            // set state init cleaning
+            o->state = STATE_INIT_CLEANING;
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(o->state == STATE_INIT_CLEANING)
+            
+            // free process
+            NCDModuleProcess_Free(&o->process);
+            
+            // were we requested to die aleady?
+            if (o->dying) {
+                go_deinit(o);
+                return;
+            }
+            
+            // signal up
+            NCDModuleInst_Backend_Up(o->i);
+            
+            // set state up
+            o->state = STATE_UP;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void deinit_process_handler_event (NCDModuleProcess *process, int event)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    ASSERT(o->dying)
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(o->state == STATE_DEINIT_WORKING)
+            
+            // stop timer
+            BReactor_RemoveTimer(o->i->params->iparams->reactor, &o->deinit_timer);
+            
+            // start terminating
+            NCDModuleProcess_Terminate(&o->process);
+            
+            // set state deinit cleaning
+            o->state = STATE_DEINIT_CLEANING;
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(o->state == STATE_DEINIT_CLEANING)
+            
+            // free process
+            NCDModuleProcess_Free(&o->process);
+            
+            // die
+            instance_free(o);
+            return;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static int process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    ASSERT(o->state != STATE_UP)
+    
+    if (name == NCD_STRING_CALLER) {
+        *out_object = NCDObject_Build(-1, o, NCDObject_no_getvar, process_caller_object_func_getobj);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int process_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = NCDObject_DataPtr(obj);
+    ASSERT(o->state != STATE_UP)
+    
+    return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
+}
+
+static void deinit_timer_handler (struct instance *o)
+{
+    ASSERT(o->state == STATE_DEINIT_WORKING)
+    
+    ModuleLog(o->i, BLOG_ERROR, "imperative deinit timeout elapsed");
+    
+    // start terminating
+    NCDModuleProcess_Terminate(&o->process);
+    
+    // set state deinit cleaning
+    o->state = STATE_DEINIT_CLEANING;
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef init_template_arg;
+    NCDValRef init_args;
+    NCDValRef deinit_template_arg;
+    NCDValRef deinit_timeout_arg;
+    if (!NCDVal_ListRead(params->args, 5, &init_template_arg, &init_args, &deinit_template_arg, &o->deinit_args, &deinit_timeout_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(init_template_arg) || !NCDVal_IsList(init_args)  ||
+        !NCDVal_IsString(deinit_template_arg) || !NCDVal_IsList(o->deinit_args) ||
+        !NCDVal_IsString(deinit_timeout_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    o->deinit_template = deinit_template_arg;
+    
+    // read timeout
+    uintmax_t timeout;
+    if (!ncd_read_uintmax(deinit_timeout_arg, &timeout) || timeout > UINT64_MAX){
+        ModuleLog(i, BLOG_ERROR, "wrong timeout");
+        goto fail0;
+    }
+    
+    // init timer
+    BTimer_Init(&o->deinit_timer, timeout, (BTimer_handler)deinit_timer_handler, o);
+    
+    if (ncd_is_none(init_template_arg)) {
+        // signal up
+        NCDModuleInst_Backend_Up(i);
+        
+        // set state up
+        o->state = STATE_UP;
+    } else {
+        // start init process
+        if (!start_process(o, init_template_arg, init_args, init_process_handler_event)) {
+            goto fail0;
+        }
+        
+        // set state init working
+        o->state = STATE_INIT_WORKING;
+    }
+    
+    // set not dying
+    o->dying = 0;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o)
+{
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(!o->dying)
+    
+    // set dying
+    o->dying = 1;
+    
+    if (o->state == STATE_UP) {
+        go_deinit(o);
+        return;
+    }
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "imperative",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_imperative = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/implode.c b/external/badvpn_dns/ncd/modules/implode.c
new file mode 100644
index 0000000..0520eca
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/implode.c
@@ -0,0 +1,155 @@
+/**
+ * @file implode.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   implode(string glue, list(string) pieces)
+ * 
+ * Variables:
+ *   string (empty) - concatenation of strings in 'pieces', with 'glue' in between
+ *                    every two elements.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/expstring.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_implode.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    char *result;
+    size_t result_len;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef glue_arg;
+    NCDValRef pieces_arg;
+    if (!NCDVal_ListRead(params->args, 2, &glue_arg, &pieces_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(glue_arg) || !NCDVal_IsList(pieces_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // init result string
+    ExpString str;
+    if (!ExpString_Init(&str)) {
+        ModuleLog(i, BLOG_ERROR, "ExpString_Init failed");
+        goto fail0;
+    }
+    
+    size_t count = NCDVal_ListCount(pieces_arg);
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef piece = NCDVal_ListGet(pieces_arg, j);
+        
+        // check piece type
+        if (!NCDVal_IsString(piece)) {
+            ModuleLog(i, BLOG_ERROR, "wrong piece type");
+            goto fail1;
+        }
+        
+        // append glue
+        if (j > 0) {
+            if (!ExpString_AppendBinary(&str, (const uint8_t *)NCDVal_StringData(glue_arg), NCDVal_StringLength(glue_arg))) {
+                ModuleLog(i, BLOG_ERROR, "ExpString_AppendBinary failed");
+                goto fail1;
+            }
+        }
+        
+        // append piece
+        if (!ExpString_AppendBinary(&str, (const uint8_t *)NCDVal_StringData(piece), NCDVal_StringLength(piece))) {
+            ModuleLog(i, BLOG_ERROR, "ExpString_AppendBinary failed");
+            goto fail1;
+        }
+    }
+    
+    // store result
+    o->result = ExpString_Get(&str);
+    o->result_len = ExpString_Length(&str);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail1:
+    ExpString_Free(&str);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free result
+    free(o->result);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewStringBin(mem, (uint8_t *)o->result, o->result_len);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "implode",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_implode = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/index.c b/external/badvpn_dns/ncd/modules/index.c
new file mode 100644
index 0000000..fe68bdd
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/index.c
@@ -0,0 +1,164 @@
+/**
+ * @file index.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   index index(string value)
+ *   index index::next()
+ * 
+ * Description:
+ *   Non-negative integer with range of a size_t.
+ *   The first form creates an index from the given decimal string.
+ *   The second form cretes an index with value one more than an existing
+ *   index.
+ * 
+ * Variables:
+ *   string (empty) - the index value. Note this may be different from
+ *     than the value given to index() if it was not in normal form.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_index.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    size_t value;
+};
+
+static void func_new_templ (void *vo, NCDModuleInst *i, size_t value)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // set value
+    o->value = value;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+}
+
+static void func_new_from_value (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef arg_value;
+    if (!NCDVal_ListRead(params->args, 1, &arg_value)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(arg_value)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse value
+    uintmax_t value;
+    if (!ncd_read_uintmax(arg_value, &value)) {
+        ModuleLog(i, BLOG_ERROR, "wrong value");
+        goto fail0;
+    }
+    
+    // check overflow
+    if (value > SIZE_MAX) {
+        ModuleLog(i, BLOG_ERROR, "value too large");
+        goto fail0;
+    }
+    
+    func_new_templ(vo, i, value);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_from_index (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *index = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // check overflow
+    if (index->value == SIZE_MAX) {
+        ModuleLog(i, BLOG_ERROR, "overflow");
+        goto fail0;
+    }
+    
+    func_new_templ(vo, i, index->value + 1);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        *out = ncd_make_uintmax(mem, o->value);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "index",
+        .func_new2 = func_new_from_value,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "index::next",
+        .base_type = "index",
+        .func_new2 = func_new_from_index,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_index = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/list.c b/external/badvpn_dns/ncd/modules/list.c
new file mode 100644
index 0000000..e1df1f1
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/list.c
@@ -0,0 +1,871 @@
+/**
+ * @file list.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * List construction module.
+ * 
+ * Synopsis:
+ *   list(elem1, ..., elemN)
+ *   list listfrom(list l1, ..., list lN)
+ * 
+ * Description:
+ *   The first form creates a list with the given elements.
+ *   The second form creates a list by concatenating the given
+ *   lists.
+ * 
+ * Variables:
+ *   (empty) - list containing elem1, ..., elemN
+ *   length - number of elements in list
+ * 
+ * Synopsis: list::append(arg)
+ * 
+ * Synopsis: list::appendv(list arg)
+ * Description: Appends the elements of arg to the list.
+ * 
+ * Synopsis: list::length()
+ * Variables:
+ *   (empty) - number of elements in list at the time of initialization
+ *             of this method
+ * 
+ * Synopsis: list::get(string index)
+ * Variables:
+ *   (empty) - element of list at position index (starting from zero) at the time of initialization
+ * 
+ * Synopsis: list::shift()
+ * 
+ * Synopsis: list::contains(value)
+ * Variables:
+ *   (empty) - "true" if list contains value, "false" if not
+ * 
+ * Synopsis:
+ *   list::find(start_pos, value)
+ * Description:
+ *   finds the first occurrence of 'value' in the list at position >='start_pos'.
+ * Variables:
+ *   pos - position of element, or "none" if not found
+ *   found - "true" if found, "false" if not
+ * 
+ * Sysnopsis:
+ *   list::remove_at(remove_pos)
+ * Description:
+ *   Removes the element at position 'remove_pos', which must refer to an existing element.
+ * 
+ * Synopsis:
+ *   list::remove(value)
+ * Description:
+ *   Removes the first occurrence of value in the list, which must be in the list.
+ * 
+ * Synopsis:
+ *   list::set(list l1, ..., list lN)
+ * Description:
+ *   Replaces the list with the concatenation of given lists.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <misc/offset.h>
+#include <misc/parse_number.h>
+#include <structure/IndexedList.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_list.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct elem {
+    IndexedListNode il_node;
+    NCDValMem mem;
+    NCDValRef val;
+};
+
+struct instance {
+    NCDModuleInst *i;
+    IndexedList il;
+};
+
+struct length_instance {
+    NCDModuleInst *i;
+    uint64_t length;
+};
+
+struct get_instance {
+    NCDModuleInst *i;
+    NCDValMem mem;
+    NCDValRef val;
+};
+
+struct contains_instance {
+    NCDModuleInst *i;
+    int contains;
+};
+
+struct find_instance {
+    NCDModuleInst *i;
+    int is_found;
+    uint64_t found_pos;
+};
+
+static uint64_t list_count (struct instance *o)
+{
+    return IndexedList_Count(&o->il);
+}
+
+static struct elem * insert_value (NCDModuleInst *i, struct instance *o, NCDValRef val, uint64_t idx)
+{
+    ASSERT(idx <= list_count(o))
+    ASSERT(!NCDVal_IsInvalid(val))
+    
+    struct elem *e = malloc(sizeof(*e));
+    if (!e) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    NCDValMem_Init(&e->mem);
+    
+    e->val = NCDVal_NewCopy(&e->mem, val);
+    if (NCDVal_IsInvalid(e->val)) {
+        goto fail1;
+    }
+    
+    IndexedList_InsertAt(&o->il, &e->il_node, idx);
+    
+    return e;
+    
+fail1:
+    NCDValMem_Free(&e->mem);
+    free(e);
+fail0:
+    return NULL;
+}
+
+static void remove_elem (struct instance *o, struct elem *e)
+{
+    IndexedList_Remove(&o->il, &e->il_node);
+    NCDValMem_Free(&e->mem);
+    free(e);
+}
+
+static struct elem * get_elem_at (struct instance *o, uint64_t idx)
+{
+    ASSERT(idx < list_count(o))
+    
+    IndexedListNode *iln = IndexedList_GetAt(&o->il, idx);
+    struct elem *e = UPPER_OBJECT(iln, struct elem, il_node);
+    
+    return e;
+}
+
+static struct elem * get_first_elem (struct instance *o)
+{
+    ASSERT(list_count(o) > 0)
+    
+    IndexedListNode *iln = IndexedList_GetFirst(&o->il);
+    struct elem *e = UPPER_OBJECT(iln, struct elem, il_node);
+    
+    return e;
+}
+
+static struct elem * get_last_elem (struct instance *o)
+{
+    ASSERT(list_count(o) > 0)
+    
+    IndexedListNode *iln = IndexedList_GetLast(&o->il);
+    struct elem *e = UPPER_OBJECT(iln, struct elem, il_node);
+    
+    return e;
+}
+
+static void cut_list_front (struct instance *o, uint64_t count)
+{
+    while (list_count(o) > count) {
+        remove_elem(o, get_first_elem(o));
+    }
+}
+
+static void cut_list_back (struct instance *o, uint64_t count)
+{
+    while (list_count(o) > count) {
+        remove_elem(o, get_last_elem(o));
+    }
+}
+
+static int append_list_contents (NCDModuleInst *i, struct instance *o, NCDValRef args)
+{
+    ASSERT(NCDVal_IsList(args))
+    
+    uint64_t orig_count = list_count(o);
+    
+    size_t append_count = NCDVal_ListCount(args);
+    
+    for (size_t j = 0; j < append_count; j++) {
+        NCDValRef elem = NCDVal_ListGet(args, j);
+        if (!insert_value(i, o, elem, list_count(o))) {
+            goto fail;
+        }
+    }
+    
+    return 1;
+    
+fail:
+    cut_list_back(o, orig_count);
+    return 0;
+}
+
+static int append_list_contents_contents (NCDModuleInst *i, struct instance *o, NCDValRef args)
+{
+    ASSERT(NCDVal_IsList(args))
+    
+    uint64_t orig_count = list_count(o);
+    
+    size_t append_count = NCDVal_ListCount(args);
+    
+    for (size_t j = 0; j < append_count; j++) {
+        NCDValRef elem = NCDVal_ListGet(args, j);
+        
+        if (!NCDVal_IsList(elem)) {
+            ModuleLog(i, BLOG_ERROR, "wrong type");
+            goto fail;
+        }
+        
+        if (!append_list_contents(i, o, elem)) {
+            goto fail;
+        }
+    }
+    
+    return 1;
+    
+fail:
+    cut_list_back(o, orig_count);
+    return 0;
+}
+
+static struct elem * find_elem (struct instance *o, NCDValRef val, uint64_t start_idx, uint64_t *out_idx)
+{
+    if (start_idx >= list_count(o)) {
+        return NULL;
+    }
+    
+    for (IndexedListNode *iln = IndexedList_GetAt(&o->il, start_idx); iln; iln = IndexedList_GetNext(&o->il, iln)) {
+        struct elem *e = UPPER_OBJECT(iln, struct elem, il_node);
+        if (NCDVal_Compare(e->val, val) == 0) {
+            if (out_idx) {
+                *out_idx = start_idx;
+            }
+            return e;
+        }
+        start_idx++;
+    }
+    
+    return NULL;
+}
+
+static int list_to_value (NCDModuleInst *i, struct instance *o, NCDValMem *mem, NCDValRef *out_val)
+{
+    *out_val = NCDVal_NewList(mem, IndexedList_Count(&o->il));
+    if (NCDVal_IsInvalid(*out_val)) {
+        goto fail;
+    }
+    
+    for (IndexedListNode *iln = IndexedList_GetFirst(&o->il); iln; iln = IndexedList_GetNext(&o->il, iln)) {
+        struct elem *e = UPPER_OBJECT(iln, struct elem, il_node);
+        
+        NCDValRef copy = NCDVal_NewCopy(mem, e->val);
+        if (NCDVal_IsInvalid(copy)) {
+            goto fail;
+        }
+        
+        if (!NCDVal_ListAppend(*out_val, copy)) {
+            goto fail;
+        }
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+static void func_new_list (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // init list
+    IndexedList_Init(&o->il);
+    
+    // append contents
+    if (!append_list_contents(i, o, params->args)) {
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail1:
+    cut_list_front(o, 0);
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_listfrom (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // init list
+    IndexedList_Init(&o->il);
+    
+    // append contents contents
+    if (!append_list_contents_contents(i, o, params->args)) {
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail1:
+    cut_list_front(o, 0);
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free list elements
+    cut_list_front(o, 0);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        if (!list_to_value(o->i, o, mem, out)) {
+            return 0;
+        }
+        
+        return 1;
+    }
+    
+    if (!strcmp(name, "length")) {
+        *out = ncd_make_uintmax(mem, list_count(o));
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void append_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    NCDValRef arg;
+    if (!NCDVal_ListRead(params->args, 1, &arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // append
+    if (!insert_value(i, mo, arg, list_count(mo))) {
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void appendv_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    NCDValRef arg;
+    if (!NCDVal_ListRead(params->args, 1, &arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsList(arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // append
+    if (!append_list_contents(i, mo, arg)) {
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void length_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct length_instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // remember length
+    o->length = list_count(mo);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void length_func_die (void *vo)
+{
+    struct length_instance *o = vo;
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int length_func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct length_instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        *out = ncd_make_uintmax(mem, o->length);
+        return 1;
+    }
+    
+    return 0;
+}
+    
+static void get_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct get_instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef index_arg;
+    if (!NCDVal_ListRead(params->args, 1, &index_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(index_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    uintmax_t index;
+    if (!ncd_read_uintmax(index_arg, &index)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong value");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // check index
+    if (index >= list_count(mo)) {
+        ModuleLog(o->i, BLOG_ERROR, "no element at index %"PRIuMAX, index);
+        goto fail0;
+    }
+    
+    // get element
+    struct elem *e = get_elem_at(mo, index);
+    
+    // init mem
+    NCDValMem_Init(&o->mem);
+    
+    // copy value
+    o->val = NCDVal_NewCopy(&o->mem, e->val);
+    if (NCDVal_IsInvalid(o->val)) {
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail1:
+    NCDValMem_Free(&o->mem);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void get_func_die (void *vo)
+{
+    struct get_instance *o = vo;
+    
+    // free mem
+    NCDValMem_Free(&o->mem);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int get_func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct get_instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        *out = NCDVal_NewCopy(mem, o->val);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void shift_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // check first
+    if (list_count(mo) == 0) {
+        ModuleLog(i, BLOG_ERROR, "list has no elements");
+        goto fail0;
+    }
+    
+    // remove first
+    remove_elem(mo, get_first_elem(mo));
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void contains_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct contains_instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef value_arg;
+    if (!NCDVal_ListRead(params->args, 1, &value_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // search
+    o->contains = !!find_elem(mo, value_arg, 0, NULL);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void contains_func_die (void *vo)
+{
+    struct contains_instance *o = vo;
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int contains_func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct contains_instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        *out = ncd_make_boolean(mem, o->contains, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void find_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct find_instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef start_pos_arg;
+    NCDValRef value_arg;
+    if (!NCDVal_ListRead(params->args, 2, &start_pos_arg, &value_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(start_pos_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // read start position
+    uintmax_t start_pos;
+    if (!ncd_read_uintmax(start_pos_arg, &start_pos) || start_pos > UINT64_MAX) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong start pos");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // find
+    o->is_found = !!find_elem(mo, value_arg, start_pos, &o->found_pos);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void find_func_die (void *vo)
+{
+    struct find_instance *o = vo;
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int find_func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct find_instance *o = vo;
+    
+    if (!strcmp(name, "pos")) {
+        char value[64] = "none";
+        
+        if (o->is_found) {
+            generate_decimal_repr_string(o->found_pos, value);
+        }
+        
+        *out = NCDVal_NewString(mem, value);
+        return 1;
+    }
+    
+    if (!strcmp(name, "found")) {
+        *out = ncd_make_boolean(mem, o->is_found, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void removeat_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef remove_pos_arg;
+    if (!NCDVal_ListRead(params->args, 1, &remove_pos_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(remove_pos_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // read position
+    uintmax_t remove_pos;
+    if (!ncd_read_uintmax(remove_pos_arg, &remove_pos)) {
+        ModuleLog(i, BLOG_ERROR, "wrong pos");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // check position
+    if (remove_pos >= list_count(mo)) {
+        ModuleLog(i, BLOG_ERROR, "pos out of range");
+        goto fail0;
+    }
+    
+    // remove
+    remove_elem(mo, get_elem_at(mo, remove_pos));
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void remove_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef value_arg;
+    if (!NCDVal_ListRead(params->args, 1, &value_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // find element
+    struct elem *e = find_elem(mo, value_arg, 0, NULL);
+    if (!e) {
+        ModuleLog(i, BLOG_ERROR, "value does not exist");
+        goto fail0;
+    }
+    
+    // remove element
+    remove_elem(mo, e);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void set_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // remember old count
+    uint64_t old_count = list_count(mo);
+    
+    // append contents of our lists
+    if (!append_list_contents_contents(i, mo, params->args)) {
+        goto fail0;
+    }
+    
+    // remove old elements
+    cut_list_front(mo, list_count(mo) - old_count);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "list",
+        .func_new2 = func_new_list,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "listfrom",
+        .base_type = "list",
+        .func_new2 = func_new_listfrom,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "concatlist", // alias for listfrom
+        .base_type = "list",
+        .func_new2 = func_new_listfrom,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "list::append",
+        .func_new2 = append_func_new
+    }, {
+        .type = "list::appendv",
+        .func_new2 = appendv_func_new
+    }, {
+        .type = "list::length",
+        .func_new2 = length_func_new,
+        .func_die = length_func_die,
+        .func_getvar = length_func_getvar,
+        .alloc_size = sizeof(struct length_instance)
+    }, {
+        .type = "list::get",
+        .func_new2 = get_func_new,
+        .func_die = get_func_die,
+        .func_getvar = get_func_getvar,
+        .alloc_size = sizeof(struct get_instance)
+    }, {
+        .type = "list::shift",
+        .func_new2 = shift_func_new
+    }, {
+        .type = "list::contains",
+        .func_new2 = contains_func_new,
+        .func_die = contains_func_die,
+        .func_getvar = contains_func_getvar,
+        .alloc_size = sizeof(struct contains_instance)
+    }, {
+        .type = "list::find",
+        .func_new2 = find_func_new,
+        .func_die = find_func_die,
+        .func_getvar = find_func_getvar,
+        .alloc_size = sizeof(struct find_instance)
+    }, {
+        .type = "list::remove_at",
+        .func_new2 = removeat_func_new
+    }, {
+        .type = "list::remove",
+        .func_new2 = remove_func_new
+    }, {
+        .type = "list::set",
+        .func_new2 = set_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_list = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/load_module.c b/external/badvpn_dns/ncd/modules/load_module.c
new file mode 100644
index 0000000..f9bf832
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/load_module.c
@@ -0,0 +1,313 @@
+/**
+ * @file load_module.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   load_module(string name)
+ */
+
+#include <stddef.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+#include <misc/balloc.h>
+#include <misc/concat_strings.h>
+#include <misc/strdup.h>
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <structure/LinkedList0.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_load_module.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleGlobal(i) ((i)->m->group->group_state)
+
+struct global {
+    LinkedList0 modules_list;
+};
+
+struct module {
+    char *name;
+    void *lib_handle;
+    int ncdmodule_loaded;
+    LinkedList0Node modules_list_node;
+};
+
+static struct module * find_module (const char *name, struct global *g)
+{
+    for (LinkedList0Node *ln = LinkedList0_GetFirst(&g->modules_list); ln; ln = LinkedList0Node_Next(ln)) {
+        struct module *mod = UPPER_OBJECT(ln, struct module, modules_list_node);
+        if (!strcmp(mod->name, name)) {
+            return mod;
+        }
+    }
+    return NULL;
+}
+
+static struct module * module_init (const char *name, NCDModuleInst *i)
+{
+    struct global *g = ModuleGlobal(i);
+    ASSERT(!find_module(name, g))
+    
+    struct module *mod = BAlloc(sizeof(*mod));
+    if (!mod) {
+        ModuleLog(i, BLOG_ERROR, "BAlloc failed");
+        goto fail0;
+    }
+    
+    mod->name = b_strdup(name);
+    if (!mod->name) {
+        ModuleLog(i, BLOG_ERROR, "b_strdup failed");
+        goto fail1;
+    }
+    
+    mod->lib_handle = NULL;
+    mod->ncdmodule_loaded = 0;
+    LinkedList0_Prepend(&g->modules_list, &mod->modules_list_node);
+    
+    return mod;
+    
+fail1:
+    BFree(mod);
+fail0:
+    return NULL;
+}
+
+static void module_free (struct module *mod, struct global *g)
+{
+    LinkedList0_Remove(&g->modules_list, &mod->modules_list_node);
+    if (mod->lib_handle) {
+        if (dlclose(mod->lib_handle) != 0) {
+            BLog(BLOG_ERROR, "dlclose failed");
+        }
+    }
+    BFree(mod->name);
+    BFree(mod);
+}
+
+static char * x_read_link (const char *path)
+{
+    size_t size = 32;
+    char *buf = BAlloc(size + 1);
+    if (!buf) {
+        goto fail0;
+    }
+    
+    ssize_t link_size;
+    while (1) {
+        link_size = readlink(path, buf, size);
+        if (link_size < 0) {
+            goto fail1;
+        }
+        if (link_size >= 0 && link_size < size) {
+            break;
+        }
+        if (size > SIZE_MAX / 2 || 2 * size > SIZE_MAX - 1) {
+            goto fail1;
+        }
+        size *= 2;
+        char *new_buf = BRealloc(buf, size + 1);
+        if (!new_buf) {
+            goto fail1;
+        }
+        buf = new_buf;
+    }
+    
+    buf[link_size] = '\0';
+    return buf;
+    
+fail1:
+    BFree(buf);
+fail0:
+    return NULL;
+}
+
+static char * find_module_library (NCDModuleInst *i, const char *module_name)
+{
+    char *ret = NULL;
+    
+    char *self = x_read_link("/proc/self/exe");
+    if (!self) {
+        ModuleLog(i, BLOG_ERROR, "failed to read /proc/self/exe");
+        goto fail0;
+    }
+    
+    char *slash = strrchr(self, '/');
+    if (!slash) {
+        ModuleLog(i, BLOG_ERROR, "contents of /proc/self/exe do not have a slash");
+        goto fail1;
+    }
+    *slash = '\0';
+    
+    const char *paths[] = {"../lib/badvpn-ncd", "../mcvpn", NULL};
+    
+    size_t j;
+    for (j = 0; paths[j]; j++) {
+        char *module_path = concat_strings(6, self, "/", paths[j], "/libncdmodule_", module_name, ".so");
+        if (!module_path) {
+            ModuleLog(i, BLOG_ERROR, "concat_strings failed");
+            goto fail1;
+        }
+        
+        if (access(module_path, F_OK) == 0) {
+            ret = module_path;
+            break;
+        }
+        
+        BFree(module_path);
+    }
+    
+    if (!paths[j]) {
+        ModuleLog(i, BLOG_ERROR, "failed to find module");
+    }
+    
+fail1:
+    BFree(self);
+fail0:
+    return ret;
+}
+
+static int func_globalinit (struct NCDInterpModuleGroup *group, const struct NCDModuleInst_iparams *params)
+{
+    struct global *g = BAlloc(sizeof(*g));
+    if (!g) {
+        BLog(BLOG_ERROR, "BAlloc failed");
+        return 0;
+    }
+    
+    group->group_state = g;
+    LinkedList0_Init(&g->modules_list);
+    
+    return 1;
+}
+
+static void func_globalfree (struct NCDInterpModuleGroup *group)
+{
+    struct global *g = group->group_state;
+    
+    LinkedList0Node *ln;
+    while ((ln = LinkedList0_GetFirst(&g->modules_list))) {
+        struct module *mod = UPPER_OBJECT(ln, struct module, modules_list_node);
+        module_free(mod, g);
+    }
+    
+    BFree(g);
+}
+
+static void func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    struct module *mod = find_module(NCDVal_StringData(name_arg), ModuleGlobal(i));
+    ASSERT(!mod || mod->lib_handle)
+    
+    if (!mod) {
+        mod = module_init(NCDVal_StringData(name_arg), i);
+        if (!mod) {
+            ModuleLog(i, BLOG_ERROR, "module_init failed");
+            goto fail0;
+        }
+        
+        // find module library
+        char *module_path = find_module_library(i, NCDVal_StringData(name_arg));
+        if (!module_path) {
+            module_free(mod, ModuleGlobal(i));
+            goto fail0;
+        }
+        
+        // load it as a dynamic library
+        mod->lib_handle = dlopen(module_path, RTLD_NOW);
+        BFree(module_path);
+        if (!mod->lib_handle) {
+            ModuleLog(i, BLOG_ERROR, "dlopen failed");
+            module_free(mod, ModuleGlobal(i));
+            goto fail0;
+        }
+    }
+    
+    if (!mod->ncdmodule_loaded) {
+        // build name of NCDModuleGroup structure symbol
+        char *group_symbol = concat_strings(2, "ncdmodule_", NCDVal_StringData(name_arg));
+        if (!group_symbol) {
+            ModuleLog(i, BLOG_ERROR, "concat_strings failed");
+            goto fail0;
+        }
+        
+        // resolve NCDModuleGroup structure symbol
+        void *group = dlsym(mod->lib_handle, group_symbol);
+        BFree(group_symbol);
+        if (!group) {
+            ModuleLog(i, BLOG_ERROR, "dlsym failed");
+            goto fail0;
+        }
+        
+        // load module group
+        if (!NCDModuleInst_Backend_InterpLoadGroup(i, (struct NCDModuleGroup *)group)) {
+            ModuleLog(i, BLOG_ERROR, "NCDModuleInst_Backend_InterpLoadGroup failed");
+            goto fail0;
+        }
+        
+        mod->ncdmodule_loaded = 1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "load_module",
+        .func_new2 = func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_load_module = {
+    .func_globalinit = func_globalinit,
+    .func_globalfree = func_globalfree,
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/log.c b/external/badvpn_dns/ncd/modules/log.c
new file mode 100644
index 0000000..5b4251d
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/log.c
@@ -0,0 +1,285 @@
+/**
+ * @file log.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 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
+ * 
+ * Message logging using the BLog system provided by the BadVPN framework.
+ * Each message has an associated loglevel, which must be one of: "error, "warning",
+ * "notice", "info", "debug", or a numeric identifier (1=error to 5=debug).
+ * 
+ * Synopsis:
+ *   log(string level [, string ...])
+ * 
+ * Description:
+ *   On init, logs the concatenation of the given strings.
+ * 
+ * Synopsis:
+ *   log_r(string level [, string ...])
+ * 
+ * Description:
+ *   On deinit, logs the concatenation of the given strings.
+ * 
+ * Synopsis:
+ *   log_fr(string level, list(string) strings_init, list(string) strings_deinit)
+ * 
+ * Description:
+ *   On init, logs the concatenation of the strings in 'strings_init',
+ *   and on deinit, logs the concatenation of the strings in 'strings_deinit'.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_log.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+struct rlog_instance {
+    NCDModuleInst *i;
+    int level;
+    NCDValRef list;
+    size_t start;
+};
+
+enum {STRING_ERROR, STRING_WARNING, STRING_NOTICE, STRING_INFO, STRING_DEBUG};
+
+static const char *strings[] = {
+    "error", "warning", "notice", "info", "debug", NULL
+};
+
+static int check_strings (NCDValRef list, size_t start)
+{
+    ASSERT(NCDVal_IsList(list))
+    
+    size_t count = NCDVal_ListCount(list);
+    
+    for (size_t j = start; j < count; j++) {
+        NCDValRef string = NCDVal_ListGet(list, j);
+        if (!NCDVal_IsString(string)) {
+            return 0;
+        }
+    }
+    
+    return 1;
+}
+
+static void do_log (int level, NCDValRef list, size_t start)
+{
+    ASSERT(level >= BLOG_ERROR)
+    ASSERT(level <= BLOG_DEBUG)
+    ASSERT(check_strings(list, start))
+    
+    if (!BLog_WouldLog(BLOG_CHANNEL_ncd_log_msg, level)) {
+        return;
+    }
+    
+    size_t count = NCDVal_ListCount(list);
+    
+    BLog_Begin();
+    
+    for (size_t j = start; j < count; j++) {
+        NCDValRef string = NCDVal_ListGet(list, j);
+        ASSERT(NCDVal_IsString(string))
+        BLog_AppendBytes(NCDVal_StringData(string), NCDVal_StringLength(string));
+    }
+    
+    BLog_Finish(BLOG_CHANNEL_ncd_log_msg, level);
+}
+
+static int parse_level (NCDModuleInst *i, NCDValRef level_arg, int *out_level)
+{
+    if (!NCDVal_IsString(level_arg)) {
+        return 0;
+    }
+    
+    NCDStringIndex *string_index = i->params->iparams->string_index;
+    
+    uintmax_t level_numeric;
+    if (ncd_read_uintmax(level_arg, &level_numeric) && level_numeric >= BLOG_ERROR && level_numeric <= BLOG_DEBUG) {
+        *out_level = level_numeric;
+    }
+    else if (NCDVal_StringEqualsId(level_arg, ModuleString(i, STRING_ERROR), string_index)) {
+        *out_level = BLOG_ERROR;
+    }
+    else if (NCDVal_StringEqualsId(level_arg, ModuleString(i, STRING_WARNING), string_index)) {
+        *out_level = BLOG_WARNING;
+    }
+    else if (NCDVal_StringEqualsId(level_arg, ModuleString(i, STRING_NOTICE), string_index)) {
+        *out_level = BLOG_NOTICE;
+    }
+    else if (NCDVal_StringEqualsId(level_arg, ModuleString(i, STRING_INFO), string_index)) {
+        *out_level = BLOG_INFO;
+    }
+    else if (NCDVal_StringEqualsId(level_arg, ModuleString(i, STRING_DEBUG), string_index)) {
+        *out_level = BLOG_DEBUG;
+    }
+    else {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static void rlog_func_new_common (void *vo, NCDModuleInst *i, int level, NCDValRef list, size_t start)
+{
+    ASSERT(level >= BLOG_ERROR)
+    ASSERT(level <= BLOG_DEBUG)
+    ASSERT(check_strings(list, start))
+    
+    struct rlog_instance *o = vo;
+    o->i = i;
+    o->level = level;
+    o->list = list;
+    o->start = start;
+    
+    NCDModuleInst_Backend_Up(i);
+}
+
+static void rlog_func_die (void *vo)
+{
+    struct rlog_instance *o = vo;
+    
+    do_log(o->level, o->list, o->start);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void log_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    if (NCDVal_ListCount(params->args) < 1) {
+        ModuleLog(i, BLOG_ERROR, "missing level argument");
+        goto fail0;
+    }
+    
+    int level;
+    if (!parse_level(i, NCDVal_ListGet(params->args, 0), &level)) {
+        ModuleLog(i, BLOG_ERROR, "wrong level argument");
+        goto fail0;
+    }
+    
+    if (!check_strings(params->args, 1)) {
+        ModuleLog(i, BLOG_ERROR, "wrong string arguments");
+        goto fail0;
+    }
+    
+    do_log(level, params->args, 1);
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void log_r_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    if (NCDVal_ListCount(params->args) < 1) {
+        ModuleLog(i, BLOG_ERROR, "missing level argument");
+        goto fail0;
+    }
+    
+    int level;
+    if (!parse_level(i, NCDVal_ListGet(params->args, 0), &level)) {
+        ModuleLog(i, BLOG_ERROR, "wrong level argument");
+        goto fail0;
+    }
+    
+    if (!check_strings(params->args, 1)) {
+        ModuleLog(i, BLOG_ERROR, "wrong string arguments");
+        goto fail0;
+    }
+    
+    rlog_func_new_common(vo, i, level, params->args, 1);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void log_fr_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef level_arg;
+    NCDValRef strings_init_arg;
+    NCDValRef strings_deinit_arg;
+    if (!NCDVal_ListRead(params->args, 3, &level_arg, &strings_init_arg, &strings_deinit_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    int level;
+    if (!parse_level(i, level_arg, &level)) {
+        ModuleLog(i, BLOG_ERROR, "wrong level argument");
+        goto fail0;
+    }
+    
+    if (!NCDVal_IsList(strings_init_arg) || !check_strings(strings_init_arg, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong string_init argument");
+        goto fail0;
+    }
+    
+    if (!NCDVal_IsList(strings_deinit_arg) || !check_strings(strings_deinit_arg, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong strings_deinit argument");
+        goto fail0;
+    }
+    
+    do_log(level, strings_init_arg, 0);
+    
+    rlog_func_new_common(vo, i, level, strings_deinit_arg, 0);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "log",
+        .func_new2 = log_func_new
+    }, {
+        .type = "log_r",
+        .func_new2 = log_r_func_new,
+        .func_die = rlog_func_die,
+        .alloc_size = sizeof(struct rlog_instance)
+    }, {
+        .type = "log_fr",
+        .func_new2 = log_fr_func_new,
+        .func_die = rlog_func_die,
+        .alloc_size = sizeof(struct rlog_instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_log = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/logical.c b/external/badvpn_dns/ncd/modules/logical.c
new file mode 100644
index 0000000..8ac6660
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/logical.c
@@ -0,0 +1,160 @@
+/**
+ * @file logical.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 logical operators.
+ * 
+ * Synopsis: not(string val)
+ * Variables:
+ *   string (empty) - "true" if val does not equal "true", "false" otherwise
+ * 
+ * Synopsis: or([string val1, ...])
+ * Variables:
+ *   string (empty) - "true" if at least one of the values equals "true", "false" otherwise
+ * 
+ * Synopsis: and([string val1, ...])
+ * Variables:
+ *   string (empty) - "true" if all of the values equal "true", "false" otherwise
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_logical.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    int value;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_not, int is_or)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // compute value from arguments
+    if (is_not) {
+        NCDValRef arg;
+        if (!NCDVal_ListRead(params->args, 1, &arg)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+            goto fail0;
+        }
+        if (!NCDVal_IsString(arg)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong type");
+            goto fail0;
+        }
+        
+        o->value = !ncd_read_boolean(arg);
+    } else {
+        o->value = (is_or ? 0 : 1);
+        
+        size_t count = NCDVal_ListCount(params->args);
+        
+        for (size_t j = 0; j < count; j++) {
+            NCDValRef arg = NCDVal_ListGet(params->args, j);
+            
+            if (!NCDVal_IsString(arg)) {
+                ModuleLog(o->i, BLOG_ERROR, "wrong type");
+                goto fail0;
+            }
+            
+            int this_value = ncd_read_boolean(arg);
+            if (is_or) {
+                o->value = o->value || this_value;
+            } else {
+                o->value = o->value && this_value;
+            }
+        }
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_not (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, 1, 0);
+}
+
+static void func_new_or (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, 0, 1);
+}
+
+static void func_new_and (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, 0, 0);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = ncd_make_boolean(mem, o->value, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "not",
+        .func_new2 = func_new_not,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "or",
+        .func_new2 = func_new_or,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "and",
+        .func_new2 = func_new_and,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_logical = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/modules.h b/external/badvpn_dns/ncd/modules/modules.h
new file mode 100644
index 0000000..ea39027
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/modules.h
@@ -0,0 +1,210 @@
+/**
+ * @file modules.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_NCD_MODULES_MODULES_H
+#define BADVPN_NCD_MODULES_MODULES_H
+
+#include <stddef.h>
+
+#include <ncd/NCDModule.h>
+
+extern const struct NCDModuleGroup ncdmodule_var;
+extern const struct NCDModuleGroup ncdmodule_list;
+extern const struct NCDModuleGroup ncdmodule_depend;
+extern const struct NCDModuleGroup ncdmodule_multidepend;
+extern const struct NCDModuleGroup ncdmodule_dynamic_depend;
+extern const struct NCDModuleGroup ncdmodule_concat;
+extern const struct NCDModuleGroup ncdmodule_if;
+extern const struct NCDModuleGroup ncdmodule_strcmp;
+extern const struct NCDModuleGroup ncdmodule_logical;
+extern const struct NCDModuleGroup ncdmodule_sleep;
+extern const struct NCDModuleGroup ncdmodule_print;
+extern const struct NCDModuleGroup ncdmodule_blocker;
+extern const struct NCDModuleGroup ncdmodule_spawn;
+extern const struct NCDModuleGroup ncdmodule_imperative;
+extern const struct NCDModuleGroup ncdmodule_ref;
+extern const struct NCDModuleGroup ncdmodule_index;
+extern const struct NCDModuleGroup ncdmodule_alias;
+extern const struct NCDModuleGroup ncdmodule_process_manager;
+extern const struct NCDModuleGroup ncdmodule_ondemand;
+extern const struct NCDModuleGroup ncdmodule_foreach;
+extern const struct NCDModuleGroup ncdmodule_choose;
+extern const struct NCDModuleGroup ncdmodule_from_string;
+extern const struct NCDModuleGroup ncdmodule_to_string;
+extern const struct NCDModuleGroup ncdmodule_value;
+extern const struct NCDModuleGroup ncdmodule_try;
+extern const struct NCDModuleGroup ncdmodule_exit;
+extern const struct NCDModuleGroup ncdmodule_getargs;
+extern const struct NCDModuleGroup ncdmodule_arithmetic;
+extern const struct NCDModuleGroup ncdmodule_parse;
+extern const struct NCDModuleGroup ncdmodule_valuemetic;
+extern const struct NCDModuleGroup ncdmodule_file;
+extern const struct NCDModuleGroup ncdmodule_netmask;
+extern const struct NCDModuleGroup ncdmodule_implode;
+extern const struct NCDModuleGroup ncdmodule_call2;
+extern const struct NCDModuleGroup ncdmodule_assert;
+extern const struct NCDModuleGroup ncdmodule_explode;
+extern const struct NCDModuleGroup ncdmodule_net_ipv4_addr_in_network;
+extern const struct NCDModuleGroup ncdmodule_net_ipv6_addr_in_network;
+extern const struct NCDModuleGroup ncdmodule_timer;
+extern const struct NCDModuleGroup ncdmodule_file_open;
+extern const struct NCDModuleGroup ncdmodule_backtrack;
+extern const struct NCDModuleGroup ncdmodule_depend_scope;
+extern const struct NCDModuleGroup ncdmodule_substr;
+extern const struct NCDModuleGroup ncdmodule_log;
+extern const struct NCDModuleGroup ncdmodule_buffer;
+extern const struct NCDModuleGroup ncdmodule_getenv;
+#ifndef BADVPN_EMSCRIPTEN
+extern const struct NCDModuleGroup ncdmodule_regex_match;
+extern const struct NCDModuleGroup ncdmodule_run;
+extern const struct NCDModuleGroup ncdmodule_runonce;
+extern const struct NCDModuleGroup ncdmodule_daemon;
+extern const struct NCDModuleGroup ncdmodule_net_backend_waitdevice;
+extern const struct NCDModuleGroup ncdmodule_net_backend_waitlink;
+extern const struct NCDModuleGroup ncdmodule_net_backend_badvpn;
+extern const struct NCDModuleGroup ncdmodule_net_backend_wpa_supplicant;
+#ifdef BADVPN_USE_LINUX_RFKILL
+extern const struct NCDModuleGroup ncdmodule_net_backend_rfkill;
+#endif
+extern const struct NCDModuleGroup ncdmodule_net_up;
+extern const struct NCDModuleGroup ncdmodule_net_dns;
+extern const struct NCDModuleGroup ncdmodule_net_iptables;
+extern const struct NCDModuleGroup ncdmodule_net_ipv4_addr;
+extern const struct NCDModuleGroup ncdmodule_net_ipv4_route;
+extern const struct NCDModuleGroup ncdmodule_net_ipv4_dhcp;
+extern const struct NCDModuleGroup ncdmodule_net_ipv4_arp_probe;
+extern const struct NCDModuleGroup ncdmodule_net_watch_interfaces;
+extern const struct NCDModuleGroup ncdmodule_sys_watch_input;
+extern const struct NCDModuleGroup ncdmodule_sys_watch_usb;
+#ifdef BADVPN_USE_LINUX_INPUT
+extern const struct NCDModuleGroup ncdmodule_sys_evdev;
+#endif
+#ifdef BADVPN_USE_INOTIFY
+extern const struct NCDModuleGroup ncdmodule_sys_watch_directory;
+#endif
+extern const struct NCDModuleGroup ncdmodule_sys_request_server;
+extern const struct NCDModuleGroup ncdmodule_net_ipv6_wait_dynamic_addr;
+extern const struct NCDModuleGroup ncdmodule_sys_request_client;
+extern const struct NCDModuleGroup ncdmodule_reboot;
+extern const struct NCDModuleGroup ncdmodule_net_ipv6_addr;
+extern const struct NCDModuleGroup ncdmodule_net_ipv6_route;
+extern const struct NCDModuleGroup ncdmodule_socket;
+extern const struct NCDModuleGroup ncdmodule_sys_start_process;
+extern const struct NCDModuleGroup ncdmodule_load_module;
+#endif
+
+static const struct NCDModuleGroup *ncd_modules[] = {
+    &ncdmodule_var,
+    &ncdmodule_list,
+    &ncdmodule_depend,
+    &ncdmodule_multidepend,
+    &ncdmodule_dynamic_depend,
+    &ncdmodule_concat,
+    &ncdmodule_if,
+    &ncdmodule_strcmp,
+    &ncdmodule_logical,
+    &ncdmodule_sleep,
+    &ncdmodule_print,
+    &ncdmodule_blocker,
+    &ncdmodule_spawn,
+    &ncdmodule_imperative,
+    &ncdmodule_ref,
+    &ncdmodule_index,
+    &ncdmodule_alias,
+    &ncdmodule_process_manager,
+    &ncdmodule_ondemand,
+    &ncdmodule_foreach,
+    &ncdmodule_choose,
+    &ncdmodule_from_string,
+    &ncdmodule_to_string,
+    &ncdmodule_value,
+    &ncdmodule_try,
+    &ncdmodule_exit,
+    &ncdmodule_getargs,
+    &ncdmodule_arithmetic,
+    &ncdmodule_parse,
+    &ncdmodule_valuemetic,
+    &ncdmodule_file,
+    &ncdmodule_netmask,
+    &ncdmodule_implode,
+    &ncdmodule_call2,
+    &ncdmodule_assert,
+    &ncdmodule_explode,
+    &ncdmodule_net_ipv4_addr_in_network,
+    &ncdmodule_net_ipv6_addr_in_network,
+    &ncdmodule_timer,
+    &ncdmodule_file_open,
+    &ncdmodule_backtrack,
+    &ncdmodule_depend_scope,
+    &ncdmodule_substr,
+    &ncdmodule_log,
+    &ncdmodule_buffer,
+    &ncdmodule_getenv,
+#ifndef BADVPN_EMSCRIPTEN
+    &ncdmodule_regex_match,
+    &ncdmodule_run,
+    &ncdmodule_runonce,
+    &ncdmodule_daemon,
+    &ncdmodule_net_backend_waitdevice,
+    &ncdmodule_net_backend_waitlink,
+    &ncdmodule_net_backend_badvpn,
+    &ncdmodule_net_backend_wpa_supplicant,
+#ifdef BADVPN_USE_LINUX_RFKILL
+    &ncdmodule_net_backend_rfkill,
+#endif
+    &ncdmodule_net_up,
+    &ncdmodule_net_dns,
+    &ncdmodule_net_iptables,
+    &ncdmodule_net_ipv4_addr,
+    &ncdmodule_net_ipv4_route,
+    &ncdmodule_net_ipv4_dhcp,
+    &ncdmodule_net_ipv4_arp_probe,
+    &ncdmodule_net_watch_interfaces,
+    &ncdmodule_sys_watch_input,
+    &ncdmodule_sys_watch_usb,
+#ifdef BADVPN_USE_LINUX_INPUT
+    &ncdmodule_sys_evdev,
+#endif
+#ifdef BADVPN_USE_INOTIFY
+    &ncdmodule_sys_watch_directory,
+#endif
+    &ncdmodule_sys_request_server,
+    &ncdmodule_net_ipv6_wait_dynamic_addr,
+    &ncdmodule_sys_request_client,
+    &ncdmodule_reboot,
+    &ncdmodule_net_ipv6_addr,
+    &ncdmodule_net_ipv6_route,
+    &ncdmodule_socket,
+    &ncdmodule_sys_start_process,
+    &ncdmodule_load_module,
+#endif
+    NULL
+};
+
+#endif
diff --git a/external/badvpn_dns/ncd/modules/multidepend.c b/external/badvpn_dns/ncd/modules/multidepend.c
new file mode 100644
index 0000000..9b201ae
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/multidepend.c
@@ -0,0 +1,401 @@
+/**
+ * @file multidepend.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * This is a compatibility module. It behaves exactly like the depend_scope module,
+ * except that there is a single global scope for dependency names.
+ * 
+ * Use depend_scope instead. If you are using multidepend between non-template
+ * processes, make those processes templates instead and start them via
+ * process_manager(). For example, instead of this:
+ * 
+ *   process foo {
+ *       multiprovide("FOO");
+ *   }
+ *   
+ *   process bar {
+ *       multidepend({"FOO"});
+ *   }
+ * 
+ * Use this:
+ * 
+ *   process main {
+ *       depend_scope() scope;
+ *       process_manager() mgr;
+ *       mgr->start("foo", "foo", {});
+ *       mgr->start("bar", "bar", {});
+ *   }
+ *   
+ *   template foo {
+ *       _caller.scope->provide("FOO");
+ *   }
+ *   
+ *   template bar {
+ *       _caller.scope->depend({"FOO"});
+ *   }
+ * 
+ * Synopsis:
+ *   multiprovide(name)
+ * 
+ * Synopsis:
+ *   multidepend(list names)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <structure/LinkedList1.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_multidepend.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleGlobal(i) ((i)->m->group->group_state)
+
+struct provide {
+    NCDModuleInst *i;
+    NCDValRef name;
+    LinkedList1Node provides_list_node;
+    LinkedList1 depends_list;
+    int dying;
+};
+
+struct depend {
+    NCDModuleInst *i;
+    NCDValRef names;
+    LinkedList1Node depends_list_node;
+    struct provide *provide;
+    LinkedList1Node provide_depends_list_node;
+    int provide_collapsing;
+};
+
+struct global {
+    LinkedList1 provides_list;
+    LinkedList1 depends_list;
+};
+
+static struct provide * find_provide (struct global *g, NCDValRef name)
+{
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&g->provides_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct provide *provide = UPPER_OBJECT(ln, struct provide, provides_list_node);
+        if (NCDVal_Compare(provide->name, name) == 0) {
+            return provide;
+        }
+    }
+    
+    return NULL;
+}
+
+static struct provide * depend_find_best_provide (struct depend *o)
+{
+    struct global *g = ModuleGlobal(o->i);
+    
+    size_t count = NCDVal_ListCount(o->names);
+    
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef name = NCDVal_ListGet(o->names, j);
+        struct provide *provide = find_provide(g, name);
+        if (provide && !provide->dying) {
+            return provide;
+        }
+    }
+    
+    return NULL;
+}
+
+static void depend_update (struct depend *o)
+{
+    // if we're collapsing, do nothing
+    if (o->provide && o->provide_collapsing) {
+        return;
+    }
+    
+    // find best provide
+    struct provide *best_provide = depend_find_best_provide(o);
+    ASSERT(!best_provide || !best_provide->dying)
+    
+    // has anything changed?
+    if (best_provide == o->provide) {
+        return;
+    }
+    
+    if (o->provide) {
+        // set collapsing
+        o->provide_collapsing = 1;
+        
+        // signal down
+        NCDModuleInst_Backend_Down(o->i);
+    } else {
+        // insert to provide's list
+        LinkedList1_Append(&best_provide->depends_list, &o->provide_depends_list_node);
+        
+        // set not collapsing
+        o->provide_collapsing = 0;
+        
+        // set provide
+        o->provide = best_provide;
+        
+        // signal up
+        NCDModuleInst_Backend_Up(o->i);
+    }
+}
+
+static int func_globalinit (struct NCDInterpModuleGroup *group, const struct NCDModuleInst_iparams *params)
+{
+    // allocate global state structure
+    struct global *g = BAlloc(sizeof(*g));
+    if (!g) {
+        BLog(BLOG_ERROR, "BAlloc failed");
+        return 0;
+    }
+    
+    // set group state pointer
+    group->group_state = g;
+    
+    // init provides list
+    LinkedList1_Init(&g->provides_list);
+    
+    // init depends list
+    LinkedList1_Init(&g->depends_list);
+    
+    return 1;
+}
+
+static void func_globalfree (struct NCDInterpModuleGroup *group)
+{
+    struct global *g = group->group_state;
+    ASSERT(LinkedList1_IsEmpty(&g->depends_list))
+    ASSERT(LinkedList1_IsEmpty(&g->provides_list))
+    
+    // free global state structure
+    BFree(g);
+}
+
+static void provide_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct global *g = ModuleGlobal(i);
+    struct provide *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // remember name
+    o->name = name_arg;
+    
+    // check for existing provide with this name
+    if (find_provide(g, o->name)) {
+        ModuleLog(o->i, BLOG_ERROR, "a provide with this name already exists");
+        goto fail0;
+    }
+    
+    // insert to provides list
+    LinkedList1_Append(&g->provides_list, &o->provides_list_node);
+    
+    // init depends list
+    LinkedList1_Init(&o->depends_list);
+    
+    // set not dying
+    o->dying = 0;
+    
+    // signal up.
+    // This comes above the loop which follows, so that effects on related depend statements are
+    // computed before this process advances, avoiding problems like failed variable resolutions.
+    NCDModuleInst_Backend_Up(o->i);
+    
+    // update depends
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&g->depends_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct depend *depend = UPPER_OBJECT(ln, struct depend, depends_list_node);
+        depend_update(depend);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void provide_free (struct provide *o)
+{
+    struct global *g = ModuleGlobal(o->i);
+    ASSERT(LinkedList1_IsEmpty(&o->depends_list))
+    
+    // remove from provides list
+    LinkedList1_Remove(&g->provides_list, &o->provides_list_node);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void provide_func_die (void *vo)
+{
+    struct provide *o = vo;
+    ASSERT(!o->dying)
+    
+    // if we have no depends, die immediately
+    if (LinkedList1_IsEmpty(&o->depends_list)) {
+        provide_free(o);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+    
+    // start collapsing our depends
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&o->depends_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct depend *depend = UPPER_OBJECT(ln, struct depend, provide_depends_list_node);
+        ASSERT(depend->provide == o)
+        
+        // update depend to make sure it is collapsing
+        depend_update(depend);
+    }
+}
+
+static void depend_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct global *g = ModuleGlobal(i);
+    struct depend *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef names_arg;
+    if (!NCDVal_ListRead(params->args, 1, &names_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsList(names_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // remember names
+    o->names = names_arg;
+    
+    // insert to depends list
+    LinkedList1_Append(&g->depends_list, &o->depends_list_node);
+    
+    // set no provide
+    o->provide = NULL;
+    
+    // update
+    depend_update(o);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void depend_func_die (void *vo)
+{
+    struct depend *o = vo;
+    struct global *g = ModuleGlobal(o->i);
+    
+    if (o->provide) {
+        // remove from provide's list
+        LinkedList1_Remove(&o->provide->depends_list, &o->provide_depends_list_node);
+        
+        // if provide is dying and is empty, let it die
+        if (o->provide->dying && LinkedList1_IsEmpty(&o->provide->depends_list)) {
+            provide_free(o->provide);
+        }
+    }
+    
+    // remove from depends list
+    LinkedList1_Remove(&g->depends_list, &o->depends_list_node);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void depend_func_clean (void *vo)
+{
+    struct depend *o = vo;
+    
+    if (!(o->provide && o->provide_collapsing)) {
+        return;
+    }
+    
+    // save provide
+    struct provide *provide = o->provide;
+    
+    // remove from provide's list
+    LinkedList1_Remove(&provide->depends_list, &o->provide_depends_list_node);
+    
+    // set no provide
+    o->provide = NULL;
+    
+    // update
+    depend_update(o);
+    
+    // if provide is dying and is empty, let it die
+    if (provide->dying && LinkedList1_IsEmpty(&provide->depends_list)) {
+        provide_free(provide);
+    }
+}
+
+static int depend_func_getobj (void *vo, NCD_string_id_t objname, NCDObject *out_object)
+{
+    struct depend *o = vo;
+    
+    if (!o->provide) {
+        return 0;
+    }
+    
+    return NCDModuleInst_Backend_GetObj(o->provide->i, objname, out_object);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "multiprovide",
+        .func_new2 = provide_func_new,
+        .func_die = provide_func_die,
+        .alloc_size = sizeof(struct provide)
+    }, {
+        .type = "multidepend",
+        .func_new2 = depend_func_new,
+        .func_die = depend_func_die,
+        .func_clean = depend_func_clean,
+        .func_getobj = depend_func_getobj,
+        .flags = NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN,
+        .alloc_size = sizeof(struct depend)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_multidepend = {
+    .func_globalinit = func_globalinit,
+    .func_globalfree = func_globalfree,
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_backend_badvpn.c b/external/badvpn_dns/ncd/modules/net_backend_badvpn.c
new file mode 100644
index 0000000..572ae71
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_backend_badvpn.c
@@ -0,0 +1,281 @@
+/**
+ * @file net_backend_badvpn.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * BadVPN interface module.
+ * 
+ * Synopsis: net.backend.badvpn(string ifname, string user, string exec, list(string) args)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/cmdline.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDIfConfig.h>
+
+#include <generated/blog_channel_ncd_net_backend_badvpn.h>
+
+#define RETRY_TIME 5000
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValNullTermString ifname_nts;
+    const char *user;
+    size_t user_len;
+    const char *exec;
+    size_t exec_len;
+    NCDValRef args;
+    int dying;
+    int started;
+    BTimer timer;
+    BProcess process;
+};
+
+static void try_process (struct instance *o);
+static void process_handler (struct instance *o, int normally, uint8_t normally_exit_status);
+static void timer_handler (struct instance *o);
+static void instance_free (struct instance *o);
+
+void try_process (struct instance *o)
+{
+    CmdLine c;
+    if (!CmdLine_Init(&c)) {
+        goto fail0;
+    }
+    
+    // append exec
+    if (!CmdLine_AppendNoNull(&c, o->exec, o->exec_len)) {
+        goto fail1;
+    }
+    
+    // append tapdev
+    if (!CmdLine_Append(&c, "--tapdev") || !CmdLine_Append(&c, o->ifname_nts.data)) {
+        goto fail1;
+    }
+    
+    // append arguments
+    size_t count = NCDVal_ListCount(o->args);
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(o->args, j);
+        if (!CmdLine_AppendNoNull(&c, NCDVal_StringData(arg), NCDVal_StringLength(arg))) {
+            goto fail1;
+        }
+    }
+    
+    // terminate cmdline
+    if (!CmdLine_Finish(&c)) {
+        goto fail1;
+    }
+    
+    // start process
+    if (!BProcess_Init(&o->process, o->i->params->iparams->manager, (BProcess_handler)process_handler, o, ((char **)c.arr.v)[0], (char **)c.arr.v, o->user)) {
+        ModuleLog(o->i, BLOG_ERROR, "BProcess_Init failed");
+        goto fail1;
+    }
+    
+    CmdLine_Free(&c);
+    
+    // set started
+    o->started = 1;
+    
+    return;
+    
+fail1:
+    CmdLine_Free(&c);
+fail0:
+    // retry
+    o->started = 0;
+    BReactor_SetTimer(o->i->params->iparams->reactor, &o->timer);
+}
+
+void process_handler (struct instance *o, int normally, uint8_t normally_exit_status)
+{
+    ASSERT(o->started)
+    
+    ModuleLog(o->i, BLOG_INFO, "process terminated");
+    
+    // free process
+    BProcess_Free(&o->process);
+    
+    // set not started
+    o->started = 0;
+    
+    if (o->dying) {
+        instance_free(o);
+        return;
+    }
+    
+    // set timer
+    BReactor_SetTimer(o->i->params->iparams->reactor, &o->timer);
+}
+
+void timer_handler (struct instance *o)
+{
+    ASSERT(!o->started)
+    
+    ModuleLog(o->i, BLOG_INFO, "retrying");
+    
+    // try starting process again
+    try_process(o);
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef ifname_arg;
+    NCDValRef user_arg;
+    NCDValRef exec_arg;
+    NCDValRef args_arg;
+    if (!NCDVal_ListRead(params->args, 4, &ifname_arg, &user_arg, &exec_arg, &args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(ifname_arg) || !NCDVal_IsStringNoNulls(user_arg) ||
+        !NCDVal_IsStringNoNulls(exec_arg) || !NCDVal_IsList(args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    o->user = NCDVal_StringData(user_arg);
+    o->user_len = NCDVal_StringLength(user_arg);
+    o->exec = NCDVal_StringData(exec_arg);
+    o->exec_len = NCDVal_StringLength(exec_arg);
+    o->args = args_arg;
+    
+    // check arguments
+    size_t count = NCDVal_ListCount(o->args);
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(o->args, j);
+        if (!NCDVal_IsStringNoNulls(arg)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong type");
+            goto fail0;
+        }
+    }
+    
+    // null terminate ifname
+    if (!NCDVal_StringNullTerminate(ifname_arg, &o->ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // create TAP device
+    if (!NCDIfConfig_make_tuntap(o->ifname_nts.data, o->user, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to create TAP device");
+        goto fail1;
+    }
+    
+    // set device up
+    if (!NCDIfConfig_set_up(o->ifname_nts.data)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to set device up");
+        goto fail2;
+    }
+    
+    // set not dying
+    o->dying = 0;
+    
+    // init timer
+    BTimer_Init(&o->timer, RETRY_TIME, (BTimer_handler)timer_handler, o);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    
+    // try starting process
+    try_process(o);
+    return;
+    
+fail2:
+    if (!NCDIfConfig_remove_tuntap(o->ifname_nts.data, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove TAP device");
+    }
+fail1:
+    NCDValNullTermString_Free(&o->ifname_nts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+void instance_free (struct instance *o)
+{
+    ASSERT(!o->started)
+    
+    // free timer
+    BReactor_RemoveTimer(o->i->params->iparams->reactor, &o->timer);
+    
+    // set device down
+    if (!NCDIfConfig_set_down(o->ifname_nts.data)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to set device down");
+    }
+    
+    // free TAP device
+    if (!NCDIfConfig_remove_tuntap(o->ifname_nts.data, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove TAP device");
+    }
+    
+    // free ifname nts
+    NCDValNullTermString_Free(&o->ifname_nts);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(!o->dying)
+    
+    if (!o->started) {
+        instance_free(o);
+        return;
+    }
+    
+    // request termination
+    BProcess_Terminate(&o->process);
+    
+    // remember dying
+    o->dying = 1;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.backend.badvpn",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_backend_badvpn = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_backend_rfkill.c b/external/badvpn_dns/ncd/modules/net_backend_rfkill.c
new file mode 100644
index 0000000..311d973
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_backend_rfkill.c
@@ -0,0 +1,216 @@
+/**
+ * @file net_backend_rfkill.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Rfkill monitoring module.
+ * 
+ * Synopsis: net.backend.rfkill(string type, string name)
+ * Arguments:
+ *   type - method of determining the index of the rfkill device. "index" for
+ *     rfkill device index, "wlan" for wireless device. Be aware that, for
+ *     the wireless device method, the index is resloved at initialization,
+ *     and no attempt is made to refresh it if the device goes away. In other
+ *     words, you should probably put a "net.backend.waitdevice" statement
+ *     in front of the rfkill statement.
+ *   name - rfkill index or wireless device name
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include <misc/string_begins_with.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDRfkillMonitor.h>
+
+#include <generated/blog_channel_ncd_net_backend_rfkill.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    uint32_t index;
+    NCDRfkillMonitor monitor;
+    int up;
+};
+
+static int find_wlan_rfill (const char *ifname, uint32_t *out_index)
+{
+    char ieee_path[100];
+    snprintf(ieee_path, sizeof(ieee_path), "/sys/class/net/%s/../../ieee80211", ifname);
+    
+    int res = 0;
+    
+    DIR *d = opendir(ieee_path);
+    if (!d) {
+        goto fail0;
+    }
+    
+    struct dirent *e;
+    while (e = readdir(d)) {
+        if (!string_begins_with(e->d_name, "phy")) {
+            continue;
+        }
+        
+        char phy_path[150];
+        snprintf(phy_path, sizeof(phy_path), "%s/%s", ieee_path, e->d_name);
+        
+        DIR *d2 = opendir(phy_path);
+        if (!d2) {
+            continue;
+        }
+        
+        struct dirent *e2;
+        while (e2 = readdir(d2)) {
+            int index_pos;
+            if (!(index_pos = string_begins_with(e2->d_name, "rfkill"))) {
+                continue;
+            }
+            
+            uint32_t index;
+            if (sscanf(e2->d_name + index_pos, "%"SCNu32, &index) != 1) {
+                continue;
+            }
+            
+            res = 1;
+            *out_index = index;
+        }
+        
+        closedir(d2);
+    }
+    
+    closedir(d);
+fail0:
+    return res;
+}
+
+static void monitor_handler (struct instance *o, struct rfkill_event event)
+{
+    if (event.idx != o->index) {
+        return;
+    }
+    
+    int was_up = o->up;
+    o->up = (event.op != RFKILL_OP_DEL && !event.soft && !event.hard);
+    
+    if (o->up && !was_up) {
+        NCDModuleInst_Backend_Up(o->i);
+    }
+    else if (!o->up && was_up) {
+        NCDModuleInst_Backend_Down(o->i);
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef type_arg;
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 2, &type_arg, &name_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(type_arg) || !NCDVal_IsStringNoNulls(name_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // null terminate name
+    NCDValNullTermString name_nts;
+    if (!NCDVal_StringNullTerminate(name_arg, &name_nts)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    if (NCDVal_StringEquals(type_arg, "index")) {
+        if (sscanf(name_nts.data, "%"SCNu32, &o->index) != 1) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong index argument");
+            goto fail1;
+        }
+    }
+    else if (NCDVal_StringEquals(type_arg, "wlan")) {
+        if (!find_wlan_rfill(name_nts.data, &o->index)) {
+            ModuleLog(o->i, BLOG_ERROR, "failed to find rfkill for wlan interface");
+            goto fail1;
+        }
+    }
+    else {
+        ModuleLog(o->i, BLOG_ERROR, "unknown type argument");
+        goto fail1;
+    }
+    
+    // init monitor
+    if (!NCDRfkillMonitor_Init(&o->monitor, o->i->params->iparams->reactor, (NCDRfkillMonitor_handler)monitor_handler, o)) {
+        ModuleLog(o->i, BLOG_ERROR, "monitor failed");
+        goto fail1;
+    }
+    
+    // set not up
+    o->up = 0;
+    
+    // free name nts
+    NCDValNullTermString_Free(&name_nts);
+    return;
+    
+fail1:
+    NCDValNullTermString_Free(&name_nts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free monitor
+    NCDRfkillMonitor_Free(&o->monitor);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.backend.rfkill",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_backend_rfkill = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_backend_waitdevice.c b/external/badvpn_dns/ncd/modules/net_backend_waitdevice.c
new file mode 100644
index 0000000..6ed99f6
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_backend_waitdevice.c
@@ -0,0 +1,187 @@
+/**
+ * @file net_backend_waitdevice.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 which waits for the presence of a network interface.
+ * 
+ * Synopsis: net.backend.waitdevice(string ifname)
+ * Description: statement is UP when a network interface named ifname
+ *   exists, and DOWN when it does not.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <regex.h>
+
+#include <misc/parse_number.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDIfConfig.h>
+
+#include <generated/blog_channel_ncd_net_backend_waitdevice.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define DEVPATH_REGEX "/net/[^/]+$"
+
+struct instance {
+    NCDModuleInst *i;
+    const char *ifname;
+    size_t ifname_len;
+    NCDUdevClient client;
+    regex_t reg;
+    char *devpath;
+    uintmax_t ifindex;
+};
+
+static void client_handler (struct instance *o, char *devpath, int have_map, BStringMap map)
+{
+    if (o->devpath && !strcmp(devpath, o->devpath) && !NCDUdevManager_Query(o->i->params->iparams->umanager, o->devpath)) {
+        // free devpath
+        free(o->devpath);
+        
+        // set no devpath
+        o->devpath = NULL;
+        
+        // signal down
+        NCDModuleInst_Backend_Down(o->i);
+    } else {
+        const BStringMap *cache_map = NCDUdevManager_Query(o->i->params->iparams->umanager, devpath);
+        if (!cache_map) {
+            goto out;
+        }
+        
+        int match_res = regexec(&o->reg, devpath, 0, NULL, 0);
+        const char *interface = BStringMap_Get(cache_map, "INTERFACE");
+        const char *ifindex_str = BStringMap_Get(cache_map, "IFINDEX");
+        
+        uintmax_t ifindex;
+        if (!(!match_res && interface && strlen(interface) == o->ifname_len && !memcmp(interface, o->ifname, o->ifname_len) && ifindex_str && parse_unsigned_integer(ifindex_str, &ifindex))) {
+            goto out;
+        }
+        
+        if (o->devpath && (strcmp(o->devpath, devpath) || o->ifindex != ifindex)) {
+            // free devpath
+            free(o->devpath);
+            
+            // set no devpath
+            o->devpath = NULL;
+            
+            // signal down
+            NCDModuleInst_Backend_Down(o->i);
+        }
+        
+        if (!o->devpath) {
+            // grab devpath
+            o->devpath = devpath;
+            devpath = NULL;
+            
+            // remember ifindex
+            o->ifindex = ifindex;
+            
+            // signal up
+            NCDModuleInst_Backend_Up(o->i);
+        }
+    }
+    
+out:
+    free(devpath);
+    if (have_map) {
+        BStringMap_Free(&map);
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef arg;
+    if (!NCDVal_ListRead(params->args, 1, &arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    o->ifname = NCDVal_StringData(arg);
+    o->ifname_len = NCDVal_StringLength(arg);
+    
+    // init client
+    NCDUdevClient_Init(&o->client, o->i->params->iparams->umanager, o, (NCDUdevClient_handler)client_handler);
+    
+    // compile regex
+    if (regcomp(&o->reg, DEVPATH_REGEX, REG_EXTENDED)) {
+        ModuleLog(o->i, BLOG_ERROR, "regcomp failed");
+        goto fail1;
+    }
+    
+    // set no devpath
+    o->devpath = NULL;
+    return;
+    
+fail1:
+    NCDUdevClient_Free(&o->client);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free devpath
+    if (o->devpath) {
+        free(o->devpath);
+    }
+    
+    // free regex
+    regfree(&o->reg);
+    
+    // free client
+    NCDUdevClient_Free(&o->client);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.backend.waitdevice",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_backend_waitdevice = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_backend_waitlink.c b/external/badvpn_dns/ncd/modules/net_backend_waitlink.c
new file mode 100644
index 0000000..4ea54e8
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_backend_waitlink.c
@@ -0,0 +1,155 @@
+/**
+ * @file net_backend_waitlink.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 which waits for the link on a network interface.
+ * 
+ * Synopsis: net.backend.waitlink(string ifname)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/get_iface_info.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDIfConfig.h>
+#include <ncd/extra/NCDInterfaceMonitor.h>
+
+#include <generated/blog_channel_ncd_net_backend_waitlink.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDInterfaceMonitor monitor;
+    int up;
+};
+
+static void instance_free (struct instance *o, int is_error);
+
+static void monitor_handler (struct instance *o, struct NCDInterfaceMonitor_event event)
+{
+    ASSERT(event.event == NCDIFMONITOR_EVENT_LINK_UP || event.event == NCDIFMONITOR_EVENT_LINK_DOWN)
+    
+    int was_up = o->up;
+    o->up = (event.event == NCDIFMONITOR_EVENT_LINK_UP);
+    
+    if (o->up && !was_up) {
+        NCDModuleInst_Backend_Up(o->i);
+    }
+    else if (!o->up && was_up) {
+        NCDModuleInst_Backend_Down(o->i);
+    }
+}
+
+static void monitor_handler_error (struct instance *o)
+{
+    ModuleLog(o->i, BLOG_ERROR, "monitor error");
+    
+    instance_free(o, 1);
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef ifname_arg;
+    if (!NCDVal_ListRead(params->args, 1, &ifname_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(ifname_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // null terminate ifname
+    NCDValNullTermString ifname_nts;
+    if (!NCDVal_StringNullTerminate(ifname_arg, &ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // get interface index
+    int ifindex;
+    int res = badvpn_get_iface_info(ifname_nts.data, NULL, NULL, &ifindex);
+    NCDValNullTermString_Free(&ifname_nts);
+    if (!res) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to get interface index");
+        goto fail0;
+    }
+    
+    // init monitor
+    if (!NCDInterfaceMonitor_Init(&o->monitor, ifindex, NCDIFMONITOR_WATCH_LINK, i->params->iparams->reactor, o, (NCDInterfaceMonitor_handler)monitor_handler, (NCDInterfaceMonitor_handler_error)monitor_handler_error)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDInterfaceMonitor_Init failed");
+        goto fail0;
+    }
+    
+    // set not up
+    o->up = 0;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o, int is_error)
+{
+    // free monitor
+    NCDInterfaceMonitor_Free(&o->monitor);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    instance_free(o, 0);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.backend.waitlink",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_backend_waitlink = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_backend_wpa_supplicant.c b/external/badvpn_dns/ncd/modules/net_backend_wpa_supplicant.c
new file mode 100644
index 0000000..ce72198
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_backend_wpa_supplicant.c
@@ -0,0 +1,573 @@
+/**
+ * @file net_backend_wpa_supplicant.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Wireless interface module which runs wpa_supplicant to connect to a wireless network.
+ * 
+ * Note: wpa_supplicant does not monitor the state of rfkill switches and will fail to
+ * start if the switch is of when it is started, and will stop working indefinitely if the
+ * switch is turned off while it is running. Therefore, you should put a "net.backend.rfkill"
+ * statement in front of the wpa_supplicant statement.
+ * 
+ * Synopsis: net.backend.wpa_supplicant(string ifname, string conf, string exec, list(string) args)
+ * Variables:
+ *   bssid - BSSID of the wireless network we connected to, or "none".
+ *           Consists of 6 capital, two-character hexadecimal numbers, separated with colons.
+ *           Example: "01:B2:C3:04:E5:F6"
+ *   ssid - SSID of the wireless network we connected to. Note that this is after what
+ *          wpa_supplicant does to it before it prints it. In particular, it replaces all bytes
+ *          outside [32, 126] with underscores.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <misc/cmdline.h>
+#include <misc/string_begins_with.h>
+#include <misc/stdbuf_cmdline.h>
+#include <misc/balloc.h>
+#include <misc/find_program.h>
+#include <flow/LineBuffer.h>
+#include <system/BInputProcess.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_net_backend_wpa_supplicant.h>
+
+#define MAX_LINE_LEN 512
+#define EVENT_STRING_CONNECTED "CTRL-EVENT-CONNECTED"
+#define EVENT_STRING_DISCONNECTED "CTRL-EVENT-DISCONNECTED"
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    const char *ifname;
+    size_t ifname_len;
+    const char *conf;
+    size_t conf_len;
+    const char *exec;
+    size_t exec_len;
+    NCDValRef args;
+    int dying;
+    int up;
+    BInputProcess process;
+    int have_pipe;
+    LineBuffer pipe_buffer;
+    PacketPassInterface pipe_input;
+    int have_info;
+    int info_have_bssid;
+    uint8_t info_bssid[6];
+    char *info_ssid;
+};
+
+static int parse_hex_digit (uint8_t d, uint8_t *out);
+static int parse_trying (uint8_t *data, int data_len, uint8_t *out_bssid, uint8_t **out_ssid, int *out_ssid_len);
+static int parse_trying_nobssid (uint8_t *data, int data_len, uint8_t **out_ssid, int *out_ssid_len);
+static int build_cmdline (struct instance *o, CmdLine *c);
+static int init_info (struct instance *o, int have_bssid, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len);
+static void free_info (struct instance *o);
+static void process_error (struct instance *o);
+static void process_handler_terminated (struct instance *o, int normally, uint8_t normally_exit_status);
+static void process_handler_closed (struct instance *o, int is_error);
+static void process_pipe_handler_send (struct instance *o, uint8_t *data, int data_len);
+static void instance_free (struct instance *o, int is_error);
+
+int parse_hex_digit (uint8_t d, uint8_t *out)
+{
+    switch (d) {
+        case '0': *out = 0; return 1;
+        case '1': *out = 1; return 1;
+        case '2': *out = 2; return 1;
+        case '3': *out = 3; return 1;
+        case '4': *out = 4; return 1;
+        case '5': *out = 5; return 1;
+        case '6': *out = 6; return 1;
+        case '7': *out = 7; return 1;
+        case '8': *out = 8; return 1;
+        case '9': *out = 9; return 1;
+        case 'A': case 'a': *out = 10; return 1;
+        case 'B': case 'b': *out = 11; return 1;
+        case 'C': case 'c': *out = 12; return 1;
+        case 'D': case 'd': *out = 13; return 1;
+        case 'E': case 'e': *out = 14; return 1;
+        case 'F': case 'f': *out = 15; return 1;
+    }
+    
+    return 0;
+}
+
+int parse_trying (uint8_t *data, int data_len, uint8_t *out_bssid, uint8_t **out_ssid, int *out_ssid_len)
+{
+    // Trying to associate with AB:CD:EF:01:23:45 (SSID='Some SSID' freq=2462 MHz)
+    
+    int p;
+    if (!(p = data_begins_with((char *)data, data_len, "Trying to associate with "))) {
+        return 0;
+    }
+    data += p;
+    data_len -= p;
+    
+    for (int i = 0; i < 6; i++) {
+        uint8_t d1;
+        uint8_t d2;
+        if (data_len < 2 || !parse_hex_digit(data[0], &d1) || !parse_hex_digit(data[1], &d2)) {
+            return 0;
+        }
+        data += 2;
+        data_len -= 2;
+        out_bssid[i] = ((d1 << 4) | d2);
+        
+        if (i != 5) {
+            if (data_len < 1 || data[0] != ':') {
+                return 0;
+            }
+            data += 1;
+            data_len -= 1;
+        }
+    }
+    
+    if (!(p = data_begins_with((char *)data, data_len, " (SSID='"))) {
+        return 0;
+    }
+    data += p;
+    data_len -= p;
+    
+    // find last '
+    uint8_t *q = NULL;
+    for (int i = data_len; i > 0; i--) {
+        if (data[i - 1] == '\'') {
+            q = &data[i - 1];
+            break;
+        }
+    }
+    if (!q) {
+        return 0;
+    }
+    
+    *out_ssid = data;
+    *out_ssid_len = q - data;
+    
+    return 1;
+}
+
+int parse_trying_nobssid (uint8_t *data, int data_len, uint8_t **out_ssid, int *out_ssid_len)
+{
+    // Trying to associate with SSID 'Some SSID'
+    
+    int p;
+    if (!(p = data_begins_with((char *)data, data_len, "Trying to associate with SSID '"))) {
+        return 0;
+    }
+    data += p;
+    data_len -= p;
+    
+    // find last '
+    uint8_t *q = NULL;
+    for (int i = data_len; i > 0; i--) {
+        if (data[i - 1] == '\'') {
+            q = &data[i - 1];
+            break;
+        }
+    }
+    if (!q) {
+        return 0;
+    }
+    
+    *out_ssid = data;
+    *out_ssid_len = q - data;
+    
+    return 1;
+}
+
+int build_cmdline (struct instance *o, CmdLine *c)
+{
+    // init cmdline
+    if (!CmdLine_Init(c)) {
+        goto fail0;
+    }
+    
+    // find stdbuf executable
+    char *stdbuf_exec = badvpn_find_program("stdbuf");
+    if (!stdbuf_exec) {
+        ModuleLog(o->i, BLOG_ERROR, "cannot find stdbuf executable");
+        goto fail1;
+    }
+    
+    // append stdbuf part
+    int res = build_stdbuf_cmdline(c, stdbuf_exec, o->exec, o->exec_len);
+    free(stdbuf_exec);
+    if (!res) {
+        goto fail1;
+    }
+    
+    // append user arguments
+    size_t count = NCDVal_ListCount(o->args);
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(o->args, j);
+        
+        if (!NCDVal_IsStringNoNulls(arg)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong type");
+            goto fail1;
+        }
+        
+        // append argument
+        if (!CmdLine_AppendNoNull(c, NCDVal_StringData(arg), NCDVal_StringLength(arg))) {
+            goto fail1;
+        }
+    }
+    
+    // append interface name
+    if (!CmdLine_Append(c, "-i") || !CmdLine_AppendNoNull(c, o->ifname, o->ifname_len)) {
+        goto fail1;
+    }
+    
+    // append config file
+    if (!CmdLine_Append(c, "-c") || !CmdLine_AppendNoNull(c, o->conf, o->conf_len)) {
+        goto fail1;
+    }
+    
+    // terminate cmdline
+    if (!CmdLine_Finish(c)) {
+        goto fail1;
+    }
+    
+    return 1;
+    
+fail1:
+    CmdLine_Free(c);
+fail0:
+    return 0;
+}
+
+int init_info (struct instance *o, int have_bssid, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len)
+{
+    ASSERT(!o->have_info)
+    
+    // set bssid
+    o->info_have_bssid = have_bssid;
+    if (have_bssid) {
+        memcpy(o->info_bssid, bssid, 6);
+    }
+    
+    // set ssid
+    if (!(o->info_ssid = BAllocSize(bsize_add(bsize_fromsize(ssid_len), bsize_fromsize(1))))) {
+        ModuleLog(o->i, BLOG_ERROR, "BAllocSize failed");
+        return 0;
+    }
+    memcpy(o->info_ssid, ssid, ssid_len);
+    o->info_ssid[ssid_len] = '\0';
+    
+    // set have info
+    o->have_info = 1;
+    
+    return 1;
+}
+
+void free_info (struct instance *o)
+{
+    ASSERT(o->have_info)
+    
+    // free ssid
+    BFree(o->info_ssid);
+    
+    // set not have info
+    o->have_info = 0;
+}
+
+void process_error (struct instance *o)
+{
+    BInputProcess_Terminate(&o->process);
+}
+
+void process_handler_terminated (struct instance *o, int normally, uint8_t normally_exit_status)
+{
+    ModuleLog(o->i, (o->dying ? BLOG_INFO : BLOG_ERROR), "process terminated");
+    
+    // die
+    instance_free(o, !o->dying);
+    return;
+}
+
+void process_handler_closed (struct instance *o, int is_error)
+{
+    ASSERT(o->have_pipe)
+    
+    if (is_error) {
+        ModuleLog(o->i, BLOG_ERROR, "pipe error");
+    } else {
+        ModuleLog(o->i, BLOG_INFO, "pipe closed");
+    }
+    
+    // free buffer
+    LineBuffer_Free(&o->pipe_buffer);
+    
+    // free input interface
+    PacketPassInterface_Free(&o->pipe_input);
+    
+    // set have no pipe
+    o->have_pipe = 0;
+}
+
+void process_pipe_handler_send (struct instance *o, uint8_t *data, int data_len)
+{
+    ASSERT(o->have_pipe)
+    ASSERT(data_len > 0)
+    
+    // accept packet
+    PacketPassInterface_Done(&o->pipe_input);
+    
+    if (o->dying) {
+        return;
+    }
+    
+    // strip "interface: " from beginning of line. Older wpa_supplicant versions (<1.0) don't add this
+    // prefix, so don't fail if there isn't one.
+    size_t l1;
+    size_t l2;
+    if (o->ifname_len > 0 && (l1 = data_begins_with_bin((char *)data, data_len, o->ifname, o->ifname_len)) && (l2 = data_begins_with((char *)data + l1, data_len - l1, ": "))) {
+        data += l1 + l2;
+        data_len -= l1 + l2;
+    }
+    
+    int have_bssid = 1;
+    uint8_t bssid[6];
+    uint8_t *ssid;
+    int ssid_len;
+    if (parse_trying(data, data_len, bssid, &ssid, &ssid_len) || (have_bssid = 0, parse_trying_nobssid(data, data_len, &ssid, &ssid_len))) {
+        ModuleLog(o->i, BLOG_INFO, "trying event");
+        
+        if (o->up) {
+            ModuleLog(o->i, BLOG_ERROR, "trying unexpected!");
+            process_error(o);
+            return;
+        }
+        
+        if (o->have_info) {
+            free_info(o);
+        }
+        
+        if (!init_info(o, have_bssid, bssid, ssid, ssid_len)) {
+            ModuleLog(o->i, BLOG_ERROR, "init_info failed");
+            process_error(o);
+            return;
+        }
+    }
+    else if (data_begins_with((char *)data, data_len, EVENT_STRING_CONNECTED)) {
+        ModuleLog(o->i, BLOG_INFO, "connected event");
+        
+        if (o->up || !o->have_info) {
+            ModuleLog(o->i, BLOG_ERROR, "connected unexpected!");
+            process_error(o);
+            return;
+        }
+        
+        o->up = 1;
+        NCDModuleInst_Backend_Up(o->i);
+    }
+    else if (data_begins_with((char *)data, data_len, EVENT_STRING_DISCONNECTED)) {
+        ModuleLog(o->i, BLOG_INFO, "disconnected event");
+        
+        if (o->have_info) {
+            free_info(o);
+        }
+        
+        if (o->up) {
+            o->up = 0;
+            NCDModuleInst_Backend_Down(o->i);
+        }
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef ifname_arg;
+    NCDValRef conf_arg;
+    NCDValRef exec_arg;
+    NCDValRef args_arg;
+    if (!NCDVal_ListRead(params->args, 4, &ifname_arg, &conf_arg, &exec_arg, &args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(ifname_arg) || !NCDVal_IsStringNoNulls(conf_arg)  ||
+        !NCDVal_IsStringNoNulls(exec_arg) || !NCDVal_IsList(args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    o->ifname = NCDVal_StringData(ifname_arg);
+    o->ifname_len = NCDVal_StringLength(ifname_arg);
+    o->conf = NCDVal_StringData(conf_arg);
+    o->conf_len = NCDVal_StringLength(conf_arg);
+    o->exec = NCDVal_StringData(exec_arg);
+    o->exec_len = NCDVal_StringLength(exec_arg);
+    o->args = args_arg;
+    
+    // set not dying
+    o->dying = 0;
+    
+    // set not up
+    o->up = 0;
+    
+    // build process cmdline
+    CmdLine c;
+    if (!build_cmdline(o, &c)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to build cmdline");
+        goto fail0;
+    }
+    
+    // init process
+    if (!BInputProcess_Init(&o->process, o->i->params->iparams->reactor, o->i->params->iparams->manager, o,
+                            (BInputProcess_handler_terminated)process_handler_terminated,
+                            (BInputProcess_handler_closed)process_handler_closed
+    )) {
+        ModuleLog(o->i, BLOG_ERROR, "BInputProcess_Init failed");
+        goto fail1;
+    }
+    
+    // init input interface
+    PacketPassInterface_Init(&o->pipe_input, MAX_LINE_LEN, (PacketPassInterface_handler_send)process_pipe_handler_send, o, BReactor_PendingGroup(o->i->params->iparams->reactor));
+    
+    // init buffer
+    if (!LineBuffer_Init(&o->pipe_buffer, BInputProcess_GetInput(&o->process), &o->pipe_input, MAX_LINE_LEN, '\n')) {
+        ModuleLog(o->i, BLOG_ERROR, "LineBuffer_Init failed");
+        goto fail2;
+    }
+    
+    // set have pipe
+    o->have_pipe = 1;
+    
+    // start process
+    if (!BInputProcess_Start(&o->process, ((char **)c.arr.v)[0], (char **)c.arr.v, NULL)) {
+        ModuleLog(o->i, BLOG_ERROR, "BInputProcess_Start failed");
+        goto fail3;
+    }
+    
+    // set not have info
+    o->have_info = 0;
+    
+    CmdLine_Free(&c);
+    return;
+    
+fail3:
+    LineBuffer_Free(&o->pipe_buffer);
+fail2:
+    PacketPassInterface_Free(&o->pipe_input);
+    BInputProcess_Free(&o->process);
+fail1:
+    CmdLine_Free(&c);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+void instance_free (struct instance *o, int is_error)
+{
+    // free info
+    if (o->have_info) {
+        free_info(o);
+    }
+    
+    if (o->have_pipe) {
+        // free buffer
+        LineBuffer_Free(&o->pipe_buffer);
+        
+        // free input interface
+        PacketPassInterface_Free(&o->pipe_input);
+    }
+    
+    // free process
+    BInputProcess_Free(&o->process);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(!o->dying)
+    
+    // request termination
+    BInputProcess_Terminate(&o->process);
+    
+    // remember dying
+    o->dying = 1;
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    ASSERT(o->up)
+    ASSERT(o->have_info)
+    
+    if (!strcmp(name, "bssid")) {
+        char str[18];
+        
+        if (!o->info_have_bssid) {
+            sprintf(str, "none");
+        } else {
+            uint8_t *id = o->info_bssid;
+            sprintf(str, "%02"PRIX8":%02"PRIX8":%02"PRIX8":%02"PRIX8":%02"PRIX8":%02"PRIX8, id[0], id[1], id[2], id[3], id[4], id[5]);
+        }
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    if (!strcmp(name, "ssid")) {
+        *out = NCDVal_NewString(mem, o->info_ssid);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.backend.wpa_supplicant",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_backend_wpa_supplicant = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_dns.c b/external/badvpn_dns/ncd/modules/net_dns.c
new file mode 100644
index 0000000..9ecdf1c
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_dns.c
@@ -0,0 +1,434 @@
+/**
+ * @file net_dns.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * DNS servers module.
+ * 
+ * Synopsis: net.dns(list(string) servers, string priority)
+ * Synopsis: net.dns.resolvconf(list({string type, string value}) lines, string priority)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <misc/offset.h>
+#include <misc/bsort.h>
+#include <misc/balloc.h>
+#include <misc/compare.h>
+#include <misc/concat_strings.h>
+#include <misc/expstring.h>
+#include <misc/ipaddr.h>
+#include <structure/LinkedList1.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDIfConfig.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_net_dns.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleGlobal(i) ((i)->m->group->group_state)
+
+struct instance {
+    NCDModuleInst *i;
+    LinkedList1 entries;
+    LinkedList1Node instances_node; // node in instances
+};
+
+struct dns_entry {
+    LinkedList1Node list_node; // node in instance.entries
+    char *line;
+    int priority;
+};
+
+struct global {
+    LinkedList1 instances;
+};
+
+static struct dns_entry * add_dns_entry (struct instance *o, const char *type, const char *value, int priority)
+{
+    // allocate entry
+    struct dns_entry *entry = malloc(sizeof(*entry));
+    if (!entry) {
+        goto fail0;
+    }
+    
+    // generate line
+    entry->line = concat_strings(4, type, " ", value, "\n");
+    if (!entry->line) {
+        goto fail1;
+    }
+    
+    // set info
+    entry->priority = priority;
+    
+    // add to list
+    LinkedList1_Append(&o->entries, &entry->list_node);
+    
+    return entry;
+    
+fail1:
+    free(entry);
+fail0:
+    return NULL;
+}
+
+static void remove_dns_entry (struct instance *o, struct dns_entry *entry)
+{
+    // remove from list
+    LinkedList1_Remove(&o->entries, &entry->list_node);
+    
+    // free line
+    free(entry->line);
+    
+    // free entry
+    free(entry);
+}
+
+static void remove_entries (struct instance *o)
+{
+    LinkedList1Node *n;
+    while (n = LinkedList1_GetFirst(&o->entries)) {
+        struct dns_entry *e = UPPER_OBJECT(n, struct dns_entry, list_node);
+        remove_dns_entry(o, e);
+    }
+}
+
+static size_t count_entries (struct global *g)
+{
+    size_t c = 0;
+    
+    for (LinkedList1Node *n = LinkedList1_GetFirst(&g->instances); n; n = LinkedList1Node_Next(n)) {
+        struct instance *o = UPPER_OBJECT(n, struct instance, instances_node);
+        for (LinkedList1Node *en = LinkedList1_GetFirst(&o->entries); en; en = LinkedList1Node_Next(en)) {
+            c++;
+        }
+    }
+    
+    return c;
+}
+
+struct dns_sort_entry {
+    char *line;
+    int priority;
+};
+
+static int dns_sort_comparator (const void *v1, const void *v2)
+{
+    const struct dns_sort_entry *e1 = v1;
+    const struct dns_sort_entry *e2 = v2;
+    return B_COMPARE(e1->priority, e2->priority);
+}
+
+static int set_servers (struct global *g)
+{
+    int ret = 0;
+    
+    // count servers
+    size_t num_entries = count_entries(g);
+    
+    // allocate sort array
+    struct dns_sort_entry *sort_entries = BAllocArray(num_entries, sizeof(sort_entries[0]));
+    if (!sort_entries) {
+        goto fail0;
+    }
+    
+    // fill sort array
+    num_entries = 0;
+    for (LinkedList1Node *n = LinkedList1_GetFirst(&g->instances); n; n = LinkedList1Node_Next(n)) {
+        struct instance *o = UPPER_OBJECT(n, struct instance, instances_node);
+        for (LinkedList1Node *en = LinkedList1_GetFirst(&o->entries); en; en = LinkedList1Node_Next(en)) {
+            struct dns_entry *e = UPPER_OBJECT(en, struct dns_entry, list_node);
+            sort_entries[num_entries].line = e->line;
+            sort_entries[num_entries].priority= e->priority;
+            num_entries++;
+        }
+    }
+    
+    // sort by priority
+    // use a custom insertion sort instead of qsort() because we want a stable sort
+    struct dns_sort_entry temp;
+    BInsertionSort(sort_entries, num_entries, sizeof(sort_entries[0]), dns_sort_comparator, &temp);
+    
+    ExpString estr;
+    if (!ExpString_Init(&estr)) {
+        goto fail1;
+    }
+    
+    for (size_t i = 0; i < num_entries; i++) {
+        if (!ExpString_Append(&estr, sort_entries[i].line)) {
+            goto fail2;
+        }
+    }
+    
+    // set servers
+    if (!NCDIfConfig_set_resolv_conf(ExpString_Get(&estr), ExpString_Length(&estr))) {
+        goto fail2;
+    }
+    
+    ret = 1;
+    
+fail2:
+    ExpString_Free(&estr);
+fail1:
+    BFree(sort_entries);
+fail0:
+    return ret;
+}
+
+static int func_globalinit (struct NCDInterpModuleGroup *group, const struct NCDModuleInst_iparams *params)
+{
+    // allocate global state structure
+    struct global *g = BAlloc(sizeof(*g));
+    if (!g) {
+        BLog(BLOG_ERROR, "BAlloc failed");
+        return 0;
+    }
+    
+    // set group state pointer
+    group->group_state = g;
+    
+    // init instances list
+    LinkedList1_Init(&g->instances);
+    
+    return 1;
+}
+
+static void func_globalfree (struct NCDInterpModuleGroup *group)
+{
+    struct global *g = group->group_state;
+    ASSERT(LinkedList1_IsEmpty(&g->instances))
+    
+    // free global state structure
+    BFree(g);
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct global *g = ModuleGlobal(i);
+    struct instance *o = vo;
+    o->i = i;
+    
+    // init servers list
+    LinkedList1_Init(&o->entries);
+    
+    // get arguments
+    NCDValRef servers_arg;
+    NCDValRef priority_arg;
+    if (!NCDVal_ListRead(params->args, 2, &servers_arg, &priority_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (!NCDVal_IsList(servers_arg) || !NCDVal_IsString(priority_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    
+    uintmax_t priority;
+    if (!ncd_read_uintmax(priority_arg, &priority) || priority > INT_MAX) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong priority");
+        goto fail1;
+    }
+    
+    // read servers
+    size_t count = NCDVal_ListCount(servers_arg);
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef server_arg = NCDVal_ListGet(servers_arg, j);
+        
+        if (!NCDVal_IsString(server_arg)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong type");
+            goto fail1;
+        }
+        
+        uint32_t addr;
+        if (!ipaddr_parse_ipv4_addr_bin((char *)NCDVal_StringData(server_arg), NCDVal_StringLength(server_arg), &addr)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong addr");
+            goto fail1;
+        }
+        
+        char addr_str[IPADDR_PRINT_MAX];
+        ipaddr_print_addr(addr, addr_str);
+        
+        if (!add_dns_entry(o, "nameserver", addr_str, priority)) {
+            ModuleLog(o->i, BLOG_ERROR, "failed to add dns entry");
+            goto fail1;
+        }
+    }
+    
+    // add to instances
+    LinkedList1_Append(&g->instances, &o->instances_node);
+    
+    // set servers
+    if (!set_servers(g)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to set DNS servers");
+        goto fail2;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail2:
+    LinkedList1_Remove(&g->instances, &o->instances_node);
+fail1:
+    remove_entries(o);
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_resolvconf (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct global *g = ModuleGlobal(i);
+    struct instance *o = vo;
+    o->i = i;
+    
+    // init servers list
+    LinkedList1_Init(&o->entries);
+    
+    // get arguments
+    NCDValRef lines_arg;
+    NCDValRef priority_arg;
+    if (!NCDVal_ListRead(params->args, 2, &lines_arg, &priority_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (!NCDVal_IsList(lines_arg) || !NCDVal_IsString(priority_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    
+    uintmax_t priority;
+    if (!ncd_read_uintmax(priority_arg, &priority) || priority > INT_MAX) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong priority");
+        goto fail1;
+    }
+    
+    // read lines
+    size_t count = NCDVal_ListCount(lines_arg);
+    for (size_t j = 0; j < count; j++) {
+        int loop_failed = 1;
+        
+        NCDValRef line = NCDVal_ListGet(lines_arg, j);
+        if (!NCDVal_IsList(line) || NCDVal_ListCount(line) != 2) {
+            ModuleLog(o->i, BLOG_ERROR, "lines element is not a list with two elements");
+            goto loop_fail0;
+        }
+        
+        NCDValRef type = NCDVal_ListGet(line, 0);
+        NCDValRef value = NCDVal_ListGet(line, 1);
+        if (!NCDVal_IsStringNoNulls(type) || !NCDVal_IsStringNoNulls(value)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong type of type or value");
+            goto loop_fail0;
+        }
+        
+        NCDValNullTermString type_nts;
+        if (!NCDVal_StringNullTerminate(type, &type_nts)) {
+            ModuleLog(o->i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+            goto loop_fail0;
+        }
+        
+        NCDValNullTermString value_nts;
+        if (!NCDVal_StringNullTerminate(value, &value_nts)) {
+            ModuleLog(o->i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+            goto loop_fail1;
+        }
+        
+        if (!add_dns_entry(o, type_nts.data, value_nts.data, priority)) {
+            ModuleLog(o->i, BLOG_ERROR, "failed to add dns entry");
+            goto loop_fail2;
+        }
+        
+        loop_failed = 0;
+    loop_fail2:
+        NCDValNullTermString_Free(&value_nts);
+    loop_fail1:
+        NCDValNullTermString_Free(&type_nts);
+    loop_fail0:
+        if (loop_failed) {
+            goto fail1;
+        }
+    }
+    
+    // add to instances
+    LinkedList1_Append(&g->instances, &o->instances_node);
+    
+    // set servers
+    if (!set_servers(g)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to set DNS servers");
+        goto fail2;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail2:
+    LinkedList1_Remove(&g->instances, &o->instances_node);
+fail1:
+    remove_entries(o);
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    struct global *g = ModuleGlobal(o->i);
+    
+    // remove from instances
+    LinkedList1_Remove(&g->instances, &o->instances_node);
+    
+    // set servers
+    set_servers(g);
+    
+    // free servers
+    remove_entries(o);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.dns",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.dns.resolvconf",
+        .func_new2 = func_new_resolvconf,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_dns = {
+    .func_globalinit = func_globalinit,
+    .func_globalfree = func_globalfree,
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_iptables.c b/external/badvpn_dns/ncd/modules/net_iptables.c
new file mode 100644
index 0000000..a5af2ee
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_iptables.c
@@ -0,0 +1,749 @@
+/**
+ * @file net_iptables.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * iptables and ebtables module.
+ * 
+ * Note that all iptables/ebtables commands (in general) must be issued synchronously, or
+ * the kernel may randomly report errors if there is another iptables/ebtables command in
+ * progress. To solve this, the NCD process contains a single "iptables lock". All
+ * iptables/ebtables commands exposed here go through that lock.
+ * In case you wish to call iptables/ebtables directly, the lock is exposed via
+ * net.iptables.lock().
+ * 
+ * The append and insert commands, instead of using the variable-argument form below
+ * as documented below, may alternatively be called with a single list argument.
+ * 
+ * Synopsis:
+ *   net.iptables.append(string table, string chain, string arg1  ...)
+ * Description:
+ *   init:   iptables -t table -A chain arg1 ...
+ *   deinit: iptables -t table -D chain arg1 ...
+ * 
+ * Synopsis:
+ *   net.iptables.insert(string table, string chain, string arg1  ...)
+ * Description:
+ *   init:   iptables -t table -I chain arg1 ...
+ *   deinit: iptables -t table -D chain arg1 ...
+ * 
+ * Synopsis:
+ *   net.iptables.policy(string table, string chain, string target, string revert_target)
+ * Description:
+ *   init:   iptables -t table -P chain target
+ *   deinit: iptables -t table -P chain revert_target
+ * 
+ * Synopsis:
+ *   net.iptables.newchain(string table, string chain)
+ *   net.iptables.newchain(string chain) // DEPRECATED, defaults to table="filter"
+ * Description:
+ *   init:   iptables -t table -N chain
+ *   deinit: iptables -t table -X chain
+ * 
+ * Synopsis:
+ *   net.ebtables.append(string table, string chain, string arg1 ...)
+ * Description:
+ *   init:   ebtables -t table -A chain arg1 ...
+ *   deinit: ebtables -t table -D chain arg1 ...
+ * 
+ * Synopsis:
+ *   net.ebtables.insert(string table, string chain, string arg1 ...)
+ * Description:
+ *   init:   ebtables -t table -I chain arg1 ...
+ *   deinit: ebtables -t table -D chain arg1 ...
+ * 
+ * Synopsis:
+ *   net.ebtables.policy(string table, string chain, string target, string revert_target)
+ * Description:
+ *   init:   ebtables -t table -P chain target
+ *   deinit: ebtables -t table -P chain revert_target
+ * 
+ * Synopsis:
+ *   net.ebtables.newchain(string table, string chain)
+ * Description:
+ *   init:   ebtables -t table -N chain
+ *   deinit: ebtables -t table -X chain
+ * 
+ * Synopsis:
+ *   net.iptables.lock()
+ * Description:
+ *   Use at the beginning of a block of custom iptables/ebtables commands to make sure
+ *   they do not interfere with other iptables/ebtables commands.
+ *   WARNING: improper usage of the lock can lead to deadlock. In particular:
+ *   - Do not call any of the iptables/ebtables wrappers above from a lock section;
+ *     those will attempt to aquire the lock themselves.
+ *   - Do not enter another lock section from a lock section.
+ *   - Do not perform any potentially long wait from a lock section.
+ * 
+ * Synopsis:
+ *   net.iptables.lock::unlock()
+ * Description:
+ *   Use at the end of a block of custom iptables/ebtables commands to make sure
+ *   they do not interfere with other iptables/ebtables commands.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <misc/debug.h>
+#include <misc/find_program.h>
+#include <misc/balloc.h>
+#include <ncd/extra/BEventLock.h>
+
+#include <ncd/modules/command_template.h>
+
+#include <generated/blog_channel_ncd_net_iptables.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleGlobal(i) ((i)->m->group->group_state)
+
+static void template_free_func (void *vo, int is_error);
+
+struct global {
+    BEventLock iptables_lock;
+};
+
+struct instance {
+    NCDModuleInst *i;
+    command_template_instance cti;
+};
+
+struct unlock_instance;
+
+#define LOCK_STATE_LOCKING 1
+#define LOCK_STATE_LOCKED 2
+#define LOCK_STATE_UNLOCKED 3
+#define LOCK_STATE_RELOCKING 4
+
+struct lock_instance {
+    NCDModuleInst *i;
+    BEventLockJob lock_job;
+    struct unlock_instance *unlock;
+    int state;
+};
+
+struct unlock_instance {
+    NCDModuleInst *i;
+    struct lock_instance *lock;
+};
+
+static void unlock_free (struct unlock_instance *o);
+
+static int build_append_or_insert_cmdline (NCDModuleInst *i, NCDValRef args, const char *prog, int remove, char **exec, CmdLine *cl, const char *type)
+{
+    if (NCDVal_ListRead(args, 1, &args) && !NCDVal_IsList(args)) {
+        ModuleLog(i, BLOG_ERROR, "in one-argument form a list is expected");
+        goto fail0;
+    }
+    
+    // read arguments
+    NCDValRef table_arg;
+    NCDValRef chain_arg;
+    if (!NCDVal_ListReadHead(args, 2, &table_arg, &chain_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(table_arg) || !NCDVal_IsStringNoNulls(chain_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    const char *table = NCDVal_StringData(table_arg);
+    size_t table_len = NCDVal_StringLength(table_arg);
+    const char *chain = NCDVal_StringData(chain_arg);
+    size_t chain_len = NCDVal_StringLength(chain_arg);
+    
+    // find program
+    if (!(*exec = badvpn_find_program(prog))) {
+        ModuleLog(i, BLOG_ERROR, "failed to find program: %s", prog);
+        goto fail0;
+    }
+    
+    // start cmdline
+    if (!CmdLine_Init(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
+        goto fail1;
+    }
+    
+    // add header
+    if (!CmdLine_Append(cl, *exec) || !CmdLine_Append(cl, "-t") || !CmdLine_AppendNoNull(cl, table, table_len) || !CmdLine_Append(cl, (remove ? "-D" : type)) || !CmdLine_AppendNoNull(cl, chain, chain_len)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
+        goto fail2;
+    }
+    
+    // add additional arguments
+    size_t count = NCDVal_ListCount(args);
+    for (size_t j = 2; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(args, j);
+        
+        if (!NCDVal_IsStringNoNulls(arg)) {
+            ModuleLog(i, BLOG_ERROR, "wrong type");
+            goto fail2;
+        }
+        
+        if (!CmdLine_AppendNoNull(cl, NCDVal_StringData(arg), NCDVal_StringLength(arg))) {
+            ModuleLog(i, BLOG_ERROR, "CmdLine_AppendNoNull failed");
+            goto fail2;
+        }
+    }
+    
+    // finish
+    if (!CmdLine_Finish(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
+        goto fail2;
+    }
+    
+    return 1;
+    
+fail2:
+    CmdLine_Free(cl);
+fail1:
+    free(*exec);
+fail0:
+    return 0;
+}
+
+static int build_append_cmdline (NCDModuleInst *i, NCDValRef args, const char *prog, int remove, char **exec, CmdLine *cl)
+{
+    return build_append_or_insert_cmdline(i, args, prog, remove, exec, cl, "-A");
+}
+
+static int build_insert_cmdline (NCDModuleInst *i, NCDValRef args, const char *prog, int remove, char **exec, CmdLine *cl)
+{
+    return build_append_or_insert_cmdline(i, args, prog, remove, exec, cl, "-I");
+}
+
+static int build_policy_cmdline (NCDModuleInst *i, NCDValRef args, const char *prog, int remove, char **exec, CmdLine *cl)
+{
+    // read arguments
+    NCDValRef table_arg;
+    NCDValRef chain_arg;
+    NCDValRef target_arg;
+    NCDValRef revert_target_arg;
+    if (!NCDVal_ListRead(args, 4, &table_arg, &chain_arg, &target_arg, &revert_target_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(table_arg) || !NCDVal_IsStringNoNulls(chain_arg) ||
+        !NCDVal_IsStringNoNulls(target_arg) || !NCDVal_IsStringNoNulls(revert_target_arg)
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    const char *table = NCDVal_StringData(table_arg);
+    size_t table_len = NCDVal_StringLength(table_arg);
+    const char *chain = NCDVal_StringData(chain_arg);
+    size_t chain_len = NCDVal_StringLength(chain_arg);
+    const char *target = NCDVal_StringData(target_arg);
+    size_t target_len = NCDVal_StringLength(target_arg);
+    const char *revert_target = NCDVal_StringData(revert_target_arg);
+    size_t revert_target_len = NCDVal_StringLength(revert_target_arg);
+    
+    // find program
+    if (!(*exec = badvpn_find_program(prog))) {
+        ModuleLog(i, BLOG_ERROR, "failed to find program: %s", prog);
+        goto fail0;
+    }
+    
+    // start cmdline
+    if (!CmdLine_Init(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
+        goto fail1;
+    }
+    
+    // add arguments
+    if (!CmdLine_Append(cl, *exec) || !CmdLine_Append(cl, "-t") || !CmdLine_AppendNoNull(cl, table, table_len) ||
+        !CmdLine_Append(cl, "-P") || !CmdLine_AppendNoNull(cl, chain, chain_len) ||
+        !CmdLine_AppendNoNull(cl, (remove ? revert_target : target), (remove ? revert_target_len : target_len))
+    ) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
+        goto fail2;
+    }
+    
+    // finish
+    if (!CmdLine_Finish(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
+        goto fail2;
+    }
+    
+    return 1;
+    
+fail2:
+    CmdLine_Free(cl);
+fail1:
+    free(*exec);
+fail0:
+    return 0;
+}
+
+static int build_newchain_cmdline (NCDModuleInst *i, NCDValRef args, const char *prog, int remove, char **exec, CmdLine *cl)
+{
+    // read arguments
+    NCDValRef table_arg = NCDVal_NewInvalid();
+    NCDValRef chain_arg;
+    if (!NCDVal_ListRead(args, 1, &chain_arg) && !NCDVal_ListRead(args, 2, &table_arg, &chain_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if ((!NCDVal_IsInvalid(table_arg) && !NCDVal_IsStringNoNulls(table_arg)) || !NCDVal_IsStringNoNulls(chain_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    const char *table = (NCDVal_IsInvalid(table_arg) ? "filter" : NCDVal_StringData(table_arg));
+    size_t table_len = (NCDVal_IsInvalid(table_arg) ? 6 : NCDVal_StringLength(table_arg));
+    const char *chain = NCDVal_StringData(chain_arg);
+    size_t chain_len = NCDVal_StringLength(chain_arg);
+    
+    // find program
+    if (!(*exec = badvpn_find_program(prog))) {
+        ModuleLog(i, BLOG_ERROR, "failed to find program: %s", prog);
+        goto fail0;
+    }
+    
+    // start cmdline
+    if (!CmdLine_Init(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
+        goto fail1;
+    }
+    
+    // add arguments
+    if (!CmdLine_AppendMulti(cl, 2, *exec, "-t") ||
+        !CmdLine_AppendNoNull(cl, table, table_len) ||
+        !CmdLine_Append(cl, (remove ? "-X" : "-N")) ||
+        !CmdLine_AppendNoNull(cl, chain, chain_len)
+    ) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
+        goto fail2;
+    }
+    
+    // finish
+    if (!CmdLine_Finish(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
+        goto fail2;
+    }
+    
+    return 1;
+    
+fail2:
+    CmdLine_Free(cl);
+fail1:
+    free(*exec);
+fail0:
+    return 0;
+}
+
+static int build_iptables_append_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_append_cmdline(i, args, "iptables", remove, exec, cl);
+}
+
+static int build_iptables_insert_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_insert_cmdline(i, args, "iptables", remove, exec, cl);
+}
+
+static int build_iptables_policy_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_policy_cmdline(i, args, "iptables", remove, exec, cl);
+}
+
+static int build_iptables_newchain_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_newchain_cmdline(i, args, "iptables", remove, exec, cl);
+}
+
+static int build_ip6tables_append_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_append_cmdline(i, args, "ip6tables", remove, exec, cl);
+}
+
+static int build_ip6tables_insert_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_insert_cmdline(i, args, "ip6tables", remove, exec, cl);
+}
+
+static int build_ip6tables_policy_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_policy_cmdline(i, args, "ip6tables", remove, exec, cl);
+}
+
+static int build_ip6tables_newchain_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_newchain_cmdline(i, args, "ip6tables", remove, exec, cl);
+}
+
+static int build_ebtables_append_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_append_cmdline(i, args, "ebtables", remove, exec, cl);
+}
+
+static int build_ebtables_insert_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_insert_cmdline(i, args, "ebtables", remove, exec, cl);
+}
+
+static int build_ebtables_policy_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_policy_cmdline(i, args, "ebtables", remove, exec, cl);
+}
+
+static int build_ebtables_newchain_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    return build_newchain_cmdline(i, args, "ebtables", remove, exec, cl);
+}
+
+static void lock_job_handler (struct lock_instance *o)
+{
+    ASSERT(o->state == LOCK_STATE_LOCKING || o->state == LOCK_STATE_RELOCKING)
+    
+    if (o->state == LOCK_STATE_LOCKING) {
+        ASSERT(!o->unlock)
+        
+        // up
+        NCDModuleInst_Backend_Up(o->i);
+        
+        // set state locked
+        o->state = LOCK_STATE_LOCKED;
+    }
+    else if (o->state == LOCK_STATE_RELOCKING) {
+        ASSERT(o->unlock)
+        ASSERT(o->unlock->lock == o)
+        
+        // die unlock
+        unlock_free(o->unlock);
+        o->unlock = NULL;
+        
+        // set state locked
+        o->state = LOCK_STATE_LOCKED;
+    }
+}
+
+static int func_globalinit (struct NCDInterpModuleGroup *group, const struct NCDModuleInst_iparams *params)
+{
+    // allocate global state structure
+    struct global *g = BAlloc(sizeof(*g));
+    if (!g) {
+        BLog(BLOG_ERROR, "BAlloc failed");
+        return 0;
+    }
+    
+    // set group state pointer
+    group->group_state = g;
+    
+    // init iptables lock
+    BEventLock_Init(&g->iptables_lock, BReactor_PendingGroup(params->reactor));
+    
+    return 1;
+}
+
+static void func_globalfree (struct NCDInterpModuleGroup *group)
+{
+    struct global *g = group->group_state;
+    
+    // free iptables lock
+    BEventLock_Free(&g->iptables_lock);
+    
+    // free global state structure
+    BFree(g);
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, command_template_build_cmdline build_cmdline)
+{
+    struct global *g = ModuleGlobal(i);
+    struct instance *o = vo;
+    o->i = i;
+    
+    command_template_new(&o->cti, i, params, build_cmdline, template_free_func, o, BLOG_CURRENT_CHANNEL, &g->iptables_lock);
+}
+
+void template_free_func (void *vo, int is_error)
+{
+    struct instance *o = vo;
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void append_iptables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_iptables_append_cmdline);
+}
+
+static void insert_iptables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_iptables_insert_cmdline);
+}
+
+static void policy_iptables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_iptables_policy_cmdline);
+}
+
+static void newchain_iptables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_iptables_newchain_cmdline);
+}
+
+static void append_ip6tables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_ip6tables_append_cmdline);
+}
+
+static void insert_ip6tables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_ip6tables_insert_cmdline);
+}
+
+static void policy_ip6tables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_ip6tables_policy_cmdline);
+}
+
+static void newchain_ip6tables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_ip6tables_newchain_cmdline);
+}
+
+static void append_ebtables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_ebtables_append_cmdline);
+}
+
+static void insert_ebtables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_ebtables_insert_cmdline);
+}
+
+static void policy_ebtables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_ebtables_policy_cmdline);
+}
+
+static void newchain_ebtables_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new(vo, i, params, build_ebtables_newchain_cmdline);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    command_template_die(&o->cti);
+}
+
+static void lock_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct global *g = ModuleGlobal(i);
+    struct lock_instance *o = vo;
+    o->i = i;
+    
+    // init lock job
+    BEventLockJob_Init(&o->lock_job, &g->iptables_lock, (BEventLock_handler)lock_job_handler, o);
+    BEventLockJob_Wait(&o->lock_job);
+    
+    // set no unlock
+    o->unlock = NULL;
+    
+    // set state locking
+    o->state = LOCK_STATE_LOCKING;
+}
+
+static void lock_func_die (void *vo)
+{
+    struct lock_instance *o = vo;
+    
+    if (o->state == LOCK_STATE_UNLOCKED) {
+        ASSERT(o->unlock)
+        ASSERT(o->unlock->lock == o)
+        o->unlock->lock = NULL;
+    }
+    else if (o->state == LOCK_STATE_RELOCKING) {
+        ASSERT(o->unlock)
+        ASSERT(o->unlock->lock == o)
+        unlock_free(o->unlock);
+    }
+    else {
+        ASSERT(!o->unlock)
+    }
+    
+    // free lock job
+    BEventLockJob_Free(&o->lock_job);
+    
+    // dead
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void unlock_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct unlock_instance *o = vo;
+    o->i = i;
+    
+    // get lock lock
+    struct lock_instance *lock = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure lock doesn't already have an unlock
+    if (lock->unlock) {
+        BLog(BLOG_ERROR, "lock already has an unlock");
+        goto fail0;
+    }
+    
+    // make sure lock is locked
+    if (lock->state != LOCK_STATE_LOCKED) {
+        BLog(BLOG_ERROR, "lock is not locked");
+        goto fail0;
+    }
+    
+    // set lock
+    o->lock = lock;
+    
+    // set unlock in lock
+    lock->unlock = o;
+    
+    // up
+    NCDModuleInst_Backend_Up(o->i);
+    
+    // release lock
+    BEventLockJob_Release(&lock->lock_job);
+    
+    // set lock state unlocked
+    lock->state = LOCK_STATE_UNLOCKED;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void unlock_func_die (void *vo)
+{
+    struct unlock_instance *o = vo;
+    
+    // if lock is gone, die right away
+    if (!o->lock) {
+        unlock_free(o);
+        return;
+    }
+    
+    ASSERT(o->lock->unlock == o)
+    ASSERT(o->lock->state == LOCK_STATE_UNLOCKED)
+    
+    // wait lock
+    BEventLockJob_Wait(&o->lock->lock_job);
+    
+    // set lock state relocking
+    o->lock->state = LOCK_STATE_RELOCKING;
+}
+
+static void unlock_free (struct unlock_instance *o)
+{
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.iptables.append",
+        .func_new2 = append_iptables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.iptables.insert",
+        .func_new2 = insert_iptables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.iptables.policy",
+        .func_new2 = policy_iptables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.iptables.newchain",
+        .func_new2 = newchain_iptables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ip6tables.append",
+        .func_new2 = append_ip6tables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ip6tables.insert",
+        .func_new2 = insert_ip6tables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ip6tables.policy",
+        .func_new2 = policy_ip6tables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ip6tables.newchain",
+        .func_new2 = newchain_ip6tables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ebtables.append",
+        .func_new2 = append_ebtables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ebtables.insert",
+        .func_new2 = insert_ebtables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ebtables.policy",
+        .func_new2 = policy_ebtables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ebtables.newchain",
+        .func_new2 = newchain_ebtables_func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.iptables.lock",
+        .func_new2 = lock_func_new,
+        .func_die = lock_func_die,
+        .alloc_size = sizeof(struct lock_instance)
+    }, {
+        .type = "net.iptables.lock::unlock",
+        .func_new2 = unlock_func_new,
+        .func_die = unlock_func_die,
+        .alloc_size = sizeof(struct unlock_instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_iptables = {
+    .modules = modules,
+    .func_globalinit = func_globalinit,
+    .func_globalfree = func_globalfree
+};
diff --git a/external/badvpn_dns/ncd/modules/net_ipv4_addr.c b/external/badvpn_dns/ncd/modules/net_ipv4_addr.c
new file mode 100644
index 0000000..14eaea4
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_ipv4_addr.c
@@ -0,0 +1,148 @@
+/**
+ * @file net_ipv4_addr.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * IPv4 address module.
+ * 
+ * Synopsis:
+ *     net.ipv4.addr(string ifname, string addr, string prefix)
+ *     net.ipv4.addr(string ifname, string cidr_addr)
+ * 
+ * Description:
+ *     Adds the given address to the given network interface on initialization,
+ *     and removes it on deinitialization. The second form takes the address and
+ *     prefix in CIDR notation (a.b.c.d/n).
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDIfConfig.h>
+
+#include <generated/blog_channel_ncd_net_ipv4_addr.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValNullTermString ifname_nts;
+    struct ipv4_ifaddr ifaddr;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef ifname_arg;
+    NCDValRef addr_arg;
+    NCDValRef prefix_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 2, &ifname_arg, &addr_arg) &&
+        !NCDVal_ListRead(params->args, 3, &ifname_arg, &addr_arg, &prefix_arg)
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(ifname_arg) || !NCDVal_IsString(addr_arg) ||
+        (!NCDVal_IsInvalid(prefix_arg) && !NCDVal_IsString(prefix_arg))
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // null terminate ifname
+    if (!NCDVal_StringNullTerminate(ifname_arg, &o->ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    if (NCDVal_IsInvalid(prefix_arg)) {
+        if (!ipaddr_parse_ipv4_ifaddr_bin(NCDVal_StringData(addr_arg), NCDVal_StringLength(addr_arg), &o->ifaddr)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong CIDR notation address");
+            goto fail1;
+        }
+    } else {
+        if (!ipaddr_parse_ipv4_addr_bin(NCDVal_StringData(addr_arg), NCDVal_StringLength(addr_arg), &o->ifaddr.addr)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong address");
+            goto fail1;
+        }
+        
+        if (!ipaddr_parse_ipv4_prefix_bin(NCDVal_StringData(prefix_arg), NCDVal_StringLength(prefix_arg), &o->ifaddr.prefix)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong prefix");
+            goto fail1;
+        }
+    }
+    
+    // add address
+    if (!NCDIfConfig_add_ipv4_addr(o->ifname_nts.data, o->ifaddr)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to add IP address");
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail1:
+    NCDValNullTermString_Free(&o->ifname_nts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // remove address
+    if (!NCDIfConfig_remove_ipv4_addr(o->ifname_nts.data, o->ifaddr)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove IP address");
+    }
+    
+    // free ifname nts
+    NCDValNullTermString_Free(&o->ifname_nts);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.ipv4.addr",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_ipv4_addr = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_ipv4_addr_in_network.c b/external/badvpn_dns/ncd/modules/net_ipv4_addr_in_network.c
new file mode 100644
index 0000000..fa63811
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_ipv4_addr_in_network.c
@@ -0,0 +1,173 @@
+/**
+ * @file net_ipv4_addr_in_network.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   net.ipv4.addr_in_network(string addr, string net_addr, string net_prefix)
+ *   net.ipv4.addr_in_network(string addr, string cidr_net_addr)
+ *   net.ipv4.ifnot_addr_in_network(string addr, string net_addr, string net_prefix)
+ *   net.ipv4.ifnot_addr_in_network(string addr, string cidr_net_addr)
+ * 
+ * Description:
+ *   Checks if two IPv4 addresses belong to the same subnet.
+ *   The prefix length is given either in the a separate argument or along with
+ *   the second address in CIDR notation (address/prefix).
+ *   This can be used to check whether an address belongs to a certain
+ *   subnet, hence the name.
+ * 
+ * Variables:
+ *   (empty) - "true" if addresses belong to the same subnet, "false" if not
+ */
+
+#include <string.h>
+
+#include <misc/ipaddr.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_net_ipv4_addr_in_network.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    int value;
+};
+
+static void func_new_common (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_ifnot)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef arg_addr;
+    NCDValRef arg_net_addr;
+    NCDValRef arg_net_prefix = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 2, &arg_addr, &arg_net_addr) &&
+        !NCDVal_ListRead(params->args, 3, &arg_addr, &arg_net_addr, &arg_net_prefix) 
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(arg_addr) || !NCDVal_IsString(arg_net_addr) ||
+        (!NCDVal_IsInvalid(arg_net_prefix) && !NCDVal_IsString(arg_net_prefix))
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse addr
+    uint32_t addr;
+    if (!ipaddr_parse_ipv4_addr_bin(NCDVal_StringData(arg_addr), NCDVal_StringLength(arg_addr), &addr)) {
+        ModuleLog(o->i, BLOG_ERROR, "bad address");
+        goto fail0;
+    }
+    
+    // parse network
+    struct ipv4_ifaddr network;
+    if (NCDVal_IsInvalid(arg_net_prefix)) {
+        if (!ipaddr_parse_ipv4_ifaddr_bin(NCDVal_StringData(arg_net_addr), NCDVal_StringLength(arg_net_addr), &network)) {
+            ModuleLog(o->i, BLOG_ERROR, "bad network in CIDR notation");
+            goto fail0;
+        }
+    } else {
+        if (!ipaddr_parse_ipv4_addr_bin(NCDVal_StringData(arg_net_addr), NCDVal_StringLength(arg_net_addr), &network.addr)) {
+            ModuleLog(o->i, BLOG_ERROR, "bad network address");
+            goto fail0;
+        }
+        if (!ipaddr_parse_ipv4_prefix_bin(NCDVal_StringData(arg_net_prefix), NCDVal_StringLength(arg_net_prefix), &network.prefix)) {
+            ModuleLog(o->i, BLOG_ERROR, "bad network prefix");
+            goto fail0;
+        }
+    }
+    
+    // test
+    o->value = ipaddr_ipv4_addrs_in_network(addr, network.addr, network.prefix);
+    
+    if (is_ifnot && o->value) {
+        ModuleLog(o->i, BLOG_ERROR, "addresses belong to same subnet, not proceeding");
+    }
+    
+    // signal up
+    if (!is_ifnot || !o->value) {
+        NCDModuleInst_Backend_Up(o->i);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_normal (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_common(vo, i, params, 0);
+}
+
+static void func_new_ifnot (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_common(vo, i, params, 1);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        *out = ncd_make_boolean(mem, o->value, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.ipv4.addr_in_network",
+        .func_new2 = func_new_normal,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "ip_in_network", // compatibility name
+        .func_new2 = func_new_normal,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ipv4.ifnot_addr_in_network",
+        .func_new2 = func_new_ifnot,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_ipv4_addr_in_network = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_ipv4_arp_probe.c b/external/badvpn_dns/ncd/modules/net_ipv4_arp_probe.c
new file mode 100644
index 0000000..6f8e45e
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_ipv4_arp_probe.c
@@ -0,0 +1,212 @@
+/**
+ * @file net_ipv4_arp_probe.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * ARP probing module.
+ * 
+ * Synopsis:
+ *   net.ipv4.arp_probe(string ifname, string addr)
+ * 
+ * Description:
+ *   Monitors local presence of an IPv4 host on a network interface.
+ *   On initialization, may take some time to determine whether
+ *   the host is present or not, then goes to UP state. When it
+ *   determines that presence has changed, toggles itself DOWN then
+ *   UP to expose the new determination.
+ * 
+ * Variables:
+ *   exists - "true" if the host exists, "false" if not
+ */
+
+#include <stdlib.h>
+
+#include <misc/ipaddr.h>
+#include <arpprobe/BArpProbe.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_net_ipv4_arp_probe.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define STATE_UNKNOWN 1
+#define STATE_EXIST 2
+#define STATE_NOEXIST 3
+
+struct instance {
+    NCDModuleInst *i;
+    BArpProbe arpprobe;
+    int state;
+};
+
+static void instance_free (struct instance *o, int is_error);
+
+static void arpprobe_handler (struct instance *o, int event)
+{
+    switch (event) {
+        case BARPPROBE_EVENT_EXIST: {
+            ASSERT(o->state == STATE_UNKNOWN || o->state == STATE_NOEXIST)
+            
+            ModuleLog(o->i, BLOG_INFO, "exist");
+            
+            if (o->state == STATE_NOEXIST) {
+                // signal down
+                NCDModuleInst_Backend_Down(o->i);
+            }
+            
+            // signal up
+            NCDModuleInst_Backend_Up(o->i);
+            
+            // set state exist
+            o->state = STATE_EXIST;
+        } break;
+        
+        case BARPPROBE_EVENT_NOEXIST: {
+            ASSERT(o->state == STATE_UNKNOWN || o->state == STATE_EXIST)
+            
+            ModuleLog(o->i, BLOG_INFO, "noexist");
+            
+            if (o->state == STATE_EXIST) {
+                // signal down
+                NCDModuleInst_Backend_Down(o->i);
+            }
+            
+            // signal up
+            NCDModuleInst_Backend_Up(o->i);
+            
+            // set state noexist
+            o->state = STATE_NOEXIST;
+        } break;
+        
+        case BARPPROBE_EVENT_ERROR: {
+            ModuleLog(o->i, BLOG_ERROR, "error");
+            
+            // die
+            instance_free(o, 1);
+            return;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef arg_ifname;
+    NCDValRef arg_addr;
+    if (!NCDVal_ListRead(params->args, 2, &arg_ifname, &arg_addr)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(arg_ifname) || !NCDVal_IsString(arg_addr)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse address
+    uint32_t addr;
+    if (!ipaddr_parse_ipv4_addr_bin(NCDVal_StringData(arg_addr), NCDVal_StringLength(arg_addr), &addr)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong address");
+        goto fail0;
+    }
+    
+    // null terminate ifname
+    NCDValNullTermString ifname_nts;
+    if (!NCDVal_StringNullTerminate(arg_ifname, &ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // init arpprobe
+    int res = BArpProbe_Init(&o->arpprobe, ifname_nts.data, addr, i->params->iparams->reactor, o, (BArpProbe_handler)arpprobe_handler);
+    NCDValNullTermString_Free(&ifname_nts);
+    if (!res) {
+        ModuleLog(o->i, BLOG_ERROR, "BArpProbe_Init failed");
+        goto fail0;
+    }
+    
+    // set state unknown
+    o->state = STATE_UNKNOWN;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o, int is_error)
+{
+    // free arpprobe
+    BArpProbe_Free(&o->arpprobe);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    instance_free(o, 0);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    ASSERT(o->state == STATE_EXIST || o->state == STATE_NOEXIST)
+    
+    if (!strcmp(name, "exists")) {
+        *out = ncd_make_boolean(mem, o->state == STATE_EXIST, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.ipv4.arp_probe",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_ipv4_arp_probe = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_ipv4_dhcp.c b/external/badvpn_dns/ncd/modules/net_ipv4_dhcp.c
new file mode 100644
index 0000000..d9e8b74
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_ipv4_dhcp.c
@@ -0,0 +1,351 @@
+/**
+ * @file net_ipv4_dhcp.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 module.
+ * 
+ * Synopsis:
+ *   net.ipv4.dhcp(string ifname [, list opts])
+ * 
+ * Description:
+ *   Runs a DHCP client on a network interface. When an address is obtained,
+ *   transitions up (but does not assign anything). If the lease times out,
+ *   transitions down.
+ *   The interface must already be up.
+ *   Supported options (in the opts argument):
+ *   - "hostname", (string value): send this hostname to the DHCP server
+ *   - "vendorclassid", (string value): send this vendor class identifier
+ *   - "auto_clientid": send a client identifier generated from the MAC address
+ * 
+ * Variables:
+ *   string addr - assigned IP address ("A.B.C.D")
+ *   string prefix - address prefix length ("N")
+ *   string cidr_addr - address and prefix in CIDR notation ("A.B.C.D/N")
+ *   string gateway - router address ("A.B.C.D"), or "none" if not provided
+ *   list(string) dns_servers - DNS server addresses ("A.B.C.D" ...)
+ *   string server_mac - MAC address of the DHCP server (6 two-digit caps hexadecimal values
+ *     separated with colons, e.g."AB:CD:EF:01:02:03")
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <misc/ipaddr.h>
+#include <dhcpclient/BDHCPClient.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_net_ipv4_dhcp.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    BDHCPClient dhcp;
+    int up;
+};
+
+static void instance_free (struct instance *o, int is_error);
+
+static void dhcp_handler (struct instance *o, int event)
+{
+    switch (event) {
+        case BDHCPCLIENT_EVENT_UP: {
+            ASSERT(!o->up)
+            o->up = 1;
+            NCDModuleInst_Backend_Up(o->i);
+        } break;
+        
+        case BDHCPCLIENT_EVENT_DOWN: {
+            ASSERT(o->up)
+            o->up = 0;
+            NCDModuleInst_Backend_Down(o->i);
+        } break;
+        
+        case BDHCPCLIENT_EVENT_ERROR: {
+            instance_free(o, 1);
+            return;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef ifname_arg;
+    NCDValRef opts_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 1, &ifname_arg) && !NCDVal_ListRead(params->args, 2, &ifname_arg, &opts_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(ifname_arg) || (!NCDVal_IsInvalid(opts_arg) && !NCDVal_IsList(opts_arg))) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    NCDValNullTermString hostname_nts = NCDValNullTermString_NewDummy();
+    NCDValNullTermString vendorclassid_nts = NCDValNullTermString_NewDummy();
+    
+    struct BDHCPClient_opts opts = {};
+    
+    // read options
+    size_t count = NCDVal_IsInvalid(opts_arg) ? 0 : NCDVal_ListCount(opts_arg);
+    for (size_t j = 0; j < count; j++) {
+        NCDValRef opt = NCDVal_ListGet(opts_arg, j);
+        
+        // read name
+        if (!NCDVal_IsString(opt)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong option name type");
+            goto fail1;
+        }
+        
+        if (NCDVal_StringEquals(opt, "hostname") || NCDVal_StringEquals(opt, "vendorclassid")) {
+            int is_hostname = NCDVal_StringEquals(opt, "hostname");
+            
+            // read value
+            if (j == count) {
+                ModuleLog(o->i, BLOG_ERROR, "option value missing");
+                goto fail1;
+            }
+            NCDValRef val = NCDVal_ListGet(opts_arg, j + 1);
+            if (!NCDVal_IsStringNoNulls(val)) {
+                ModuleLog(o->i, BLOG_ERROR, "wrong option value type");
+                goto fail1;
+            }
+            
+            // null terminate
+            NCDValNullTermString nts;
+            if (!NCDVal_StringNullTerminate(val, &nts)) {
+                ModuleLog(o->i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+                goto fail1;
+            }
+            NCDValNullTermString *nts_ptr = (is_hostname ? &hostname_nts : &vendorclassid_nts);
+            NCDValNullTermString_Free(nts_ptr);
+            *nts_ptr = nts;
+            
+            if (is_hostname) {
+                opts.hostname = nts.data;
+            } else {
+                opts.vendorclassid = nts.data;
+            }
+            
+            j++;
+        }
+        else if (NCDVal_StringEquals(opt, "auto_clientid")) {
+            opts.auto_clientid = 1;
+        }
+        else {
+            ModuleLog(o->i, BLOG_ERROR, "unknown option name");
+            goto fail1;
+        }
+    }
+    
+    // null terminate ifname
+    NCDValNullTermString ifname_nts;
+    if (!NCDVal_StringNullTerminate(ifname_arg, &ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail1;
+    }
+    
+    // init DHCP
+    int res = BDHCPClient_Init(&o->dhcp, ifname_nts.data, opts, o->i->params->iparams->reactor, o->i->params->iparams->random2, (BDHCPClient_handler)dhcp_handler, o);
+    NCDValNullTermString_Free(&ifname_nts);
+    if (!res) {
+        ModuleLog(o->i, BLOG_ERROR, "BDHCPClient_Init failed");
+        goto fail1;
+    }
+    
+    // set not up
+    o->up = 0;
+    
+    // free options nts's
+    NCDValNullTermString_Free(&hostname_nts);
+    NCDValNullTermString_Free(&vendorclassid_nts);
+    return;
+    
+fail1:
+    NCDValNullTermString_Free(&hostname_nts);
+    NCDValNullTermString_Free(&vendorclassid_nts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o, int is_error)
+{
+    // free DHCP
+    BDHCPClient_Free(&o->dhcp);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    instance_free(o, 0);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    ASSERT(o->up)
+    
+    if (!strcmp(name, "addr")) {
+        uint32_t addr;
+        BDHCPClient_GetClientIP(&o->dhcp, &addr);
+        
+        char str[IPADDR_PRINT_MAX];
+        ipaddr_print_addr(addr, str);
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    if (!strcmp(name, "prefix")) {
+        uint32_t addr;
+        BDHCPClient_GetClientIP(&o->dhcp, &addr);
+        uint32_t mask;
+        BDHCPClient_GetClientMask(&o->dhcp, &mask);
+        
+        struct ipv4_ifaddr ifaddr;
+        if (!ipaddr_ipv4_ifaddr_from_addr_mask(addr, mask, &ifaddr)) {
+            ModuleLog(o->i, BLOG_ERROR, "bad netmask");
+            return 0;
+        }
+        
+        char str[10];
+        sprintf(str, "%d", ifaddr.prefix);
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    if (!strcmp(name, "cidr_addr")) {
+        uint32_t addr;
+        BDHCPClient_GetClientIP(&o->dhcp, &addr);
+        uint32_t mask;
+        BDHCPClient_GetClientMask(&o->dhcp, &mask);
+        
+        struct ipv4_ifaddr ifaddr;
+        if (!ipaddr_ipv4_ifaddr_from_addr_mask(addr, mask, &ifaddr)) {
+            ModuleLog(o->i, BLOG_ERROR, "bad netmask");
+            return 0;
+        }
+        
+        char str[IPADDR_PRINT_MAX];
+        ipaddr_print_ifaddr(ifaddr, str);
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    if (!strcmp(name, "gateway")) {
+        char str[IPADDR_PRINT_MAX];
+        
+        uint32_t addr;
+        if (!BDHCPClient_GetRouter(&o->dhcp, &addr)) {
+            strcpy(str, "none");
+        } else {
+            ipaddr_print_addr(addr, str);
+        }
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    if (!strcmp(name, "dns_servers")) {
+        uint32_t servers[BDHCPCLIENT_MAX_DOMAIN_NAME_SERVERS];
+        int num_servers = BDHCPClient_GetDNS(&o->dhcp, servers, BDHCPCLIENT_MAX_DOMAIN_NAME_SERVERS);
+        
+        *out = NCDVal_NewList(mem, num_servers);
+        if (NCDVal_IsInvalid(*out)) {
+            goto fail;
+        }
+        
+        for (int i = 0; i < num_servers; i++) {
+            char str[IPADDR_PRINT_MAX];
+            ipaddr_print_addr(servers[i], str);
+            
+            NCDValRef server = NCDVal_NewString(mem, str);
+            if (NCDVal_IsInvalid(server)) {
+                goto fail;
+            }
+            
+            if (!NCDVal_ListAppend(*out, server)) {
+                goto fail;
+            }
+        }
+        
+        return 1;
+    }
+    
+    if (!strcmp(name, "server_mac")) {
+        uint8_t mac[6];
+        BDHCPClient_GetServerMAC(&o->dhcp, mac);
+        
+        char str[18];
+        sprintf(str, "%02"PRIX8":%02"PRIX8":%02"PRIX8":%02"PRIX8":%02"PRIX8":%02"PRIX8,
+                mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    return 0;
+    
+fail:
+    *out = NCDVal_NewInvalid();
+    return 1;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.ipv4.dhcp",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_ipv4_dhcp = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_ipv4_route.c b/external/badvpn_dns/ncd/modules/net_ipv4_route.c
new file mode 100644
index 0000000..0f34388
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_ipv4_route.c
@@ -0,0 +1,211 @@
+/**
+ * @file net_ipv4_route.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * IPv4 route module.
+ * 
+ * Synopsis:
+ *     net.ipv4.route(string dest, string dest_prefix, string gateway, string metric, string ifname)
+ *     net.ipv4.route(string cidr_dest, string gateway, string metric, string ifname)
+ * 
+ * Description:
+ *     Adds an IPv4 route to the system's routing table on initiailzation, and
+ *     removes it on deinitialization. The second form takes the destination in
+ *     CIDR notation (a.b.c.d/n).
+ *     If 'gateway' is "none", the route will only be associated with an interface.
+ *     If 'gateway' is "blackhole", the route will be a blackhole route (and 'ifname' is unused).
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDIfConfig.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_net_ipv4_route.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define TYPE_NORMAL 1
+#define TYPE_IFONLY 2
+#define TYPE_BLACKHOLE 3
+
+struct instance {
+    NCDModuleInst *i;
+    struct ipv4_ifaddr dest;
+    int type;
+    uint32_t gateway;
+    int metric;
+    NCDValNullTermString ifname_nts;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef dest_arg;
+    NCDValRef dest_prefix_arg = NCDVal_NewInvalid();
+    NCDValRef gateway_arg;
+    NCDValRef metric_arg;
+    NCDValRef ifname_arg;
+    if (!NCDVal_ListRead(params->args, 4, &dest_arg, &gateway_arg, &metric_arg, &ifname_arg) &&
+        !NCDVal_ListRead(params->args, 5, &dest_arg, &dest_prefix_arg, &gateway_arg, &metric_arg, &ifname_arg)
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(dest_arg) || !NCDVal_IsString(gateway_arg) ||
+        !NCDVal_IsString(metric_arg) || !NCDVal_IsStringNoNulls(ifname_arg) ||
+        (!NCDVal_IsInvalid(dest_prefix_arg) && !NCDVal_IsString(dest_prefix_arg))
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // read dest
+    if (NCDVal_IsInvalid(dest_prefix_arg)) {
+        if (!ipaddr_parse_ipv4_ifaddr_bin(NCDVal_StringData(dest_arg), NCDVal_StringLength(dest_arg), &o->dest)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong CIDR notation dest");
+            goto fail0;
+        }
+    } else {
+        if (!ipaddr_parse_ipv4_addr_bin(NCDVal_StringData(dest_arg), NCDVal_StringLength(dest_arg), &o->dest.addr)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong dest addr");
+            goto fail0;
+        }
+        if (!ipaddr_parse_ipv4_prefix_bin(NCDVal_StringData(dest_prefix_arg), NCDVal_StringLength(dest_prefix_arg), &o->dest.prefix)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong dest prefix");
+            goto fail0;
+        }
+    }
+    
+    // read gateway and choose type
+    if (NCDVal_StringEquals(gateway_arg, "none")) {
+        o->type = TYPE_IFONLY;
+    }
+    else if (NCDVal_StringEquals(gateway_arg, "blackhole")) {
+        o->type = TYPE_BLACKHOLE;
+    } else {
+        if (!ipaddr_parse_ipv4_addr_bin(NCDVal_StringData(gateway_arg), NCDVal_StringLength(gateway_arg), &o->gateway)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong gateway");
+            goto fail0;
+        }
+        o->type = TYPE_NORMAL;
+    }
+    
+    // read metric
+    uintmax_t metric;
+    if (!ncd_read_uintmax(metric_arg, &metric) || metric > INT_MAX) {
+        ModuleLog(i, BLOG_ERROR, "bad metric");
+        goto fail0;
+    }
+    o->metric = metric;
+    
+    // null terminate ifname
+    if (!NCDVal_StringNullTerminate(ifname_arg, &o->ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // add route
+    int res = 0; // to remove warning
+    switch (o->type) {
+        case TYPE_NORMAL:
+            res = NCDIfConfig_add_ipv4_route(o->dest, &o->gateway, o->metric, o->ifname_nts.data);
+            break;
+        case TYPE_IFONLY:
+            res = NCDIfConfig_add_ipv4_route(o->dest, NULL, o->metric, o->ifname_nts.data);
+            break;
+        case TYPE_BLACKHOLE:
+            res = NCDIfConfig_add_ipv4_blackhole_route(o->dest, o->metric);
+            break;
+        default: ASSERT(0);
+    }
+    if (!res) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to add route");
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail1:
+    NCDValNullTermString_Free(&o->ifname_nts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // remove route
+    int res = 0; // to remove warning
+    switch (o->type) {
+        case TYPE_NORMAL:
+            res = NCDIfConfig_remove_ipv4_route(o->dest, &o->gateway, o->metric, o->ifname_nts.data);
+            break;
+        case TYPE_IFONLY:
+            res = NCDIfConfig_remove_ipv4_route(o->dest, NULL, o->metric, o->ifname_nts.data);
+            break;
+        case TYPE_BLACKHOLE:
+            res = NCDIfConfig_remove_ipv4_blackhole_route(o->dest, o->metric);
+            break;
+        default: ASSERT(0);
+    }
+    if (!res) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove route");
+    }
+    
+    // free ifname nts
+    NCDValNullTermString_Free(&o->ifname_nts);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.ipv4.route",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_ipv4_route = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_ipv6_addr.c b/external/badvpn_dns/ncd/modules/net_ipv6_addr.c
new file mode 100644
index 0000000..debee66
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_ipv6_addr.c
@@ -0,0 +1,148 @@
+/**
+ * @file net_ipv6_addr.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * IPv6 address module.
+ * 
+ * Synopsis:
+ *     net.ipv6.addr(string ifname, string addr, string prefix)
+ *     net.ipv6.addr(string ifname, string cidr_addr)
+ * 
+ * Description:
+ *     Adds the given address to the given network interface on initialization,
+ *     and removes it on deinitialization. The second form takes the address and
+ *     prefix in CIDR notation (a.b.c.d/n).
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDIfConfig.h>
+
+#include <generated/blog_channel_ncd_net_ipv6_addr.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValNullTermString ifname_nts;
+    struct ipv6_ifaddr ifaddr;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef ifname_arg;
+    NCDValRef addr_arg;
+    NCDValRef prefix_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 2, &ifname_arg, &addr_arg) &&
+        !NCDVal_ListRead(params->args, 3, &ifname_arg, &addr_arg, &prefix_arg)
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(ifname_arg) || !NCDVal_IsString(addr_arg) ||
+        (!NCDVal_IsInvalid(prefix_arg) && !NCDVal_IsString(prefix_arg))
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // null terminate ifname
+    if (!NCDVal_StringNullTerminate(ifname_arg, &o->ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    if (NCDVal_IsInvalid(prefix_arg)) {
+        if (!ipaddr6_parse_ipv6_ifaddr_bin(NCDVal_StringData(addr_arg), NCDVal_StringLength(addr_arg), &o->ifaddr)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong CIDR notation address");
+            goto fail1;
+        }
+    } else {
+        if (!ipaddr6_parse_ipv6_addr_bin(NCDVal_StringData(addr_arg), NCDVal_StringLength(addr_arg), &o->ifaddr.addr)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong address");
+            goto fail1;
+        }
+        
+        if (!ipaddr6_parse_ipv6_prefix_bin(NCDVal_StringData(prefix_arg), NCDVal_StringLength(prefix_arg), &o->ifaddr.prefix)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong prefix");
+            goto fail1;
+        }
+    }
+    
+    // add address
+    if (!NCDIfConfig_add_ipv6_addr(o->ifname_nts.data, o->ifaddr)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to add IP address");
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail1:
+    NCDValNullTermString_Free(&o->ifname_nts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // remove address
+    if (!NCDIfConfig_remove_ipv6_addr(o->ifname_nts.data, o->ifaddr)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove IP address");
+    }
+    
+    // free ifname nts
+    NCDValNullTermString_Free(&o->ifname_nts);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.ipv6.addr",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_ipv6_addr = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_ipv6_addr_in_network.c b/external/badvpn_dns/ncd/modules/net_ipv6_addr_in_network.c
new file mode 100644
index 0000000..1ae9677
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_ipv6_addr_in_network.c
@@ -0,0 +1,168 @@
+/**
+ * @file net_ipv6_addr_in_network.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   net.ipv6.addr_in_network(string addr, string net_addr, string net_prefix)
+ *   net.ipv6.addr_in_network(string addr, string cidr_net_addr)
+ *   net.ipv6.ifnot_addr_in_network(string addr, string net_addr, string net_prefix)
+ *   net.ipv6.ifnot_addr_in_network(string addr, string cidr_net_addr)
+ * 
+ * Description:
+ *   Checks if two IPv6 addresses belong to the same subnet.
+ *   The prefix length is given either in the a separate argument or along with
+ *   the second address in CIDR notation (address/prefix).
+ *   This can be used to check whether an address belongs to a certain
+ *   subnet, hence the name.
+ * 
+ * Variables:
+ *   (empty) - "true" if addresses belong to the same subnet, "false" if not
+ */
+
+#include <string.h>
+
+#include <misc/ipaddr6.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_net_ipv6_addr_in_network.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    int value;
+};
+
+static void func_new_common (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_ifnot)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef arg_addr;
+    NCDValRef arg_net_addr;
+    NCDValRef arg_net_prefix = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 2, &arg_addr, &arg_net_addr) &&
+        !NCDVal_ListRead(params->args, 3, &arg_addr, &arg_net_addr, &arg_net_prefix) 
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(arg_addr) || !NCDVal_IsString(arg_net_addr) ||
+        (!NCDVal_IsInvalid(arg_net_prefix) && !NCDVal_IsString(arg_net_prefix))
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse addr
+    struct ipv6_addr addr;
+    if (!ipaddr6_parse_ipv6_addr_bin(NCDVal_StringData(arg_addr), NCDVal_StringLength(arg_addr), &addr)) {
+        ModuleLog(o->i, BLOG_ERROR, "bad address");
+        goto fail0;
+    }
+    
+    // parse network
+    struct ipv6_ifaddr network;
+    if (NCDVal_IsInvalid(arg_net_prefix)) {
+        if (!ipaddr6_parse_ipv6_ifaddr_bin(NCDVal_StringData(arg_net_addr), NCDVal_StringLength(arg_net_addr), &network)) {
+            ModuleLog(o->i, BLOG_ERROR, "bad network in CIDR notation");
+            goto fail0;
+        }
+    } else {
+        if (!ipaddr6_parse_ipv6_addr_bin(NCDVal_StringData(arg_net_addr), NCDVal_StringLength(arg_net_addr), &network.addr)) {
+            ModuleLog(o->i, BLOG_ERROR, "bad network address");
+            goto fail0;
+        }
+        if (!ipaddr6_parse_ipv6_prefix_bin(NCDVal_StringData(arg_net_prefix), NCDVal_StringLength(arg_net_prefix), &network.prefix)) {
+            ModuleLog(o->i, BLOG_ERROR, "bad network prefix");
+            goto fail0;
+        }
+    }
+    
+    // test
+    o->value = ipaddr6_ipv6_addrs_in_network(addr, network.addr, network.prefix);
+    
+    if (is_ifnot && o->value) {
+        ModuleLog(o->i, BLOG_ERROR, "addresses belong to same subnet, not proceeding");
+    }
+    
+    // signal up
+    if (!is_ifnot || !o->value) {
+        NCDModuleInst_Backend_Up(o->i);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_normal (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_common(vo, i, params, 0);
+}
+
+static void func_new_ifnot (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_common(vo, i, params, 1);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        *out = ncd_make_boolean(mem, o->value, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.ipv6.addr_in_network",
+        .func_new2 = func_new_normal,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.ipv6.ifnot_addr_in_network",
+        .func_new2 = func_new_ifnot,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_ipv6_addr_in_network = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_ipv6_route.c b/external/badvpn_dns/ncd/modules/net_ipv6_route.c
new file mode 100644
index 0000000..2f4ed0b
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_ipv6_route.c
@@ -0,0 +1,213 @@
+/**
+ * @file net_ipv6_route.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * IPv6 route module.
+ * 
+ * Synopsis:
+ *     net.ipv6.route(string dest, string dest_prefix, string gateway, string metric, string ifname)
+ *     net.ipv6.route(string cidr_dest, string gateway, string metric, string ifname)
+ * 
+ * Description:
+ *     Adds an IPv6 route to the system's routing table on initiailzation, and
+ *     removes it on deinitialization. The second form takes the destination in
+ *     CIDR notation (address/prefix).
+ *     If 'gateway' is "none", the route will only be associated with an interface.
+ *     If 'gateway' is "blackhole", the route will be a blackhole route (and 'ifname' is unused).
+ *     NOTE: blackhole routes for IPv6 are not yet implemented in Linux;
+ *           adding them via this interface will only work once they
+ *           have been.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDIfConfig.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_net_ipv6_route.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define TYPE_NORMAL 1
+#define TYPE_IFONLY 2
+#define TYPE_BLACKHOLE 3
+
+struct instance {
+    NCDModuleInst *i;
+    struct ipv6_ifaddr dest;
+    int type;
+    struct ipv6_addr gateway;
+    int metric;
+    NCDValNullTermString ifname_nts;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef dest_arg;
+    NCDValRef dest_prefix_arg = NCDVal_NewInvalid();
+    NCDValRef gateway_arg;
+    NCDValRef metric_arg;
+    NCDValRef ifname_arg;
+    if (!NCDVal_ListRead(params->args, 4, &dest_arg, &gateway_arg, &metric_arg, &ifname_arg) &&
+        !NCDVal_ListRead(params->args, 5, &dest_arg, &dest_prefix_arg, &gateway_arg, &metric_arg, &ifname_arg)
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(dest_arg) || !NCDVal_IsString(gateway_arg) ||
+        !NCDVal_IsString(metric_arg) || !NCDVal_IsStringNoNulls(ifname_arg) ||
+        (!NCDVal_IsInvalid(dest_prefix_arg) && !NCDVal_IsString(dest_prefix_arg))
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // read dest
+    if (NCDVal_IsInvalid(dest_prefix_arg)) {
+        if (!ipaddr6_parse_ipv6_ifaddr_bin(NCDVal_StringData(dest_arg), NCDVal_StringLength(dest_arg), &o->dest)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong CIDR notation dest");
+            goto fail0;
+        }
+    } else {
+        if (!ipaddr6_parse_ipv6_addr_bin(NCDVal_StringData(dest_arg), NCDVal_StringLength(dest_arg), &o->dest.addr)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong dest addr");
+            goto fail0;
+        }
+        if (!ipaddr6_parse_ipv6_prefix_bin(NCDVal_StringData(dest_prefix_arg), NCDVal_StringLength(dest_prefix_arg), &o->dest.prefix)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong dest prefix");
+            goto fail0;
+        }
+    }
+    
+    // read gateway and choose type
+    if (NCDVal_StringEquals(gateway_arg, "none")) {
+        o->type = TYPE_IFONLY;
+    }
+    else if (NCDVal_StringEquals(gateway_arg, "blackhole")) {
+        o->type = TYPE_BLACKHOLE;
+    } else {
+        if (!ipaddr6_parse_ipv6_addr_bin(NCDVal_StringData(gateway_arg), NCDVal_StringLength(gateway_arg), &o->gateway)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong gateway");
+            goto fail0;
+        }
+        o->type = TYPE_NORMAL;
+    }
+    
+    // read metric
+    uintmax_t metric;
+    if (!ncd_read_uintmax(metric_arg, &metric) || metric > INT_MAX) {
+        ModuleLog(i, BLOG_ERROR, "bad metric");
+        goto fail0;
+    }
+    o->metric = metric;
+    
+    // null terminate ifname
+    if (!NCDVal_StringNullTerminate(ifname_arg, &o->ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // add route
+    int res = 0; // to remove warning
+    switch (o->type) {
+        case TYPE_NORMAL:
+            res = NCDIfConfig_add_ipv6_route(o->dest, &o->gateway, o->metric, o->ifname_nts.data);
+            break;
+        case TYPE_IFONLY:
+            res = NCDIfConfig_add_ipv6_route(o->dest, NULL, o->metric, o->ifname_nts.data);
+            break;
+        case TYPE_BLACKHOLE:
+            res = NCDIfConfig_add_ipv6_blackhole_route(o->dest, o->metric);
+            break;
+        default: ASSERT(0);
+    }
+    if (!res) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to add route");
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail1:
+    NCDValNullTermString_Free(&o->ifname_nts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // remove route
+    int res = 0; // to remove warning
+    switch (o->type) {
+        case TYPE_NORMAL:
+            res = NCDIfConfig_remove_ipv6_route(o->dest, &o->gateway, o->metric, o->ifname_nts.data);
+            break;
+        case TYPE_IFONLY:
+            res = NCDIfConfig_remove_ipv6_route(o->dest, NULL, o->metric, o->ifname_nts.data);
+            break;
+        case TYPE_BLACKHOLE:
+            res = NCDIfConfig_remove_ipv6_blackhole_route(o->dest, o->metric);
+            break;
+        default: ASSERT(0);
+    }
+    if (!res) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to remove route");
+    }
+    
+    // free ifname nts
+    NCDValNullTermString_Free(&o->ifname_nts);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.ipv6.route",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_ipv6_route = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_ipv6_wait_dynamic_addr.c b/external/badvpn_dns/ncd/modules/net_ipv6_wait_dynamic_addr.c
new file mode 100644
index 0000000..f0404d5
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_ipv6_wait_dynamic_addr.c
@@ -0,0 +1,201 @@
+/**
+ * @file net_ipv6_wait_dynamic_addr.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   net.ipv6.wait_dynamic_addr(string ifname)
+ * 
+ * Description:
+ *   Waits for a dynamic IPv6 address to be obtained on the interface,
+ *   and goes up when it is obtained.
+ *   If the address is subsequently lost, goes back down and again waits
+ *   for an address.
+ * 
+ * Variables:
+ *   string addr - dynamic address obtained on the interface
+ *   string prefix - prefix length
+ *   string cidr_addr - address and prefix (addr/prefix)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <misc/get_iface_info.h>
+#include <misc/ipaddr6.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDInterfaceMonitor.h>
+
+#include <generated/blog_channel_ncd_net_ipv6_wait_dynamic_addr.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDInterfaceMonitor monitor;
+    struct ipv6_ifaddr ifaddr;
+    int up;
+};
+
+static void instance_free (struct instance *o, int is_error);
+
+static void monitor_handler (struct instance *o, struct NCDInterfaceMonitor_event event)
+{
+    if (!o->up && event.event == NCDIFMONITOR_EVENT_IPV6_ADDR_ADDED && (event.u.ipv6_addr.addr_flags & NCDIFMONITOR_ADDR_FLAG_DYNAMIC)) {
+        // rememeber address, set up
+        o->ifaddr = event.u.ipv6_addr.addr;
+        o->up = 1;
+        
+        // signal up
+        NCDModuleInst_Backend_Up(o->i);
+    }
+    else if (o->up && event.event == NCDIFMONITOR_EVENT_IPV6_ADDR_REMOVED && !memcmp(event.u.ipv6_addr.addr.addr.bytes, o->ifaddr.addr.bytes, 16) && event.u.ipv6_addr.addr.prefix == o->ifaddr.prefix) {
+        // set not up
+        o->up = 0;
+        
+        // signal down
+        NCDModuleInst_Backend_Down(o->i);
+    }
+}
+
+static void monitor_handler_error (struct instance *o)
+{
+    ModuleLog(o->i, BLOG_ERROR, "monitor error");
+    
+    instance_free(o, 1);
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef ifname_arg;
+    if (!NCDVal_ListRead(params->args, 1, &ifname_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(ifname_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // null terminate ifname
+    NCDValNullTermString ifname_nts;
+    if (!NCDVal_StringNullTerminate(ifname_arg, &ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // get interface index
+    int ifindex;
+    int res = badvpn_get_iface_info(ifname_nts.data, NULL, NULL, &ifindex);
+    NCDValNullTermString_Free(&ifname_nts);
+    if (!res) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to get interface index");
+        goto fail0;
+    }
+    
+    // init monitor
+    if (!NCDInterfaceMonitor_Init(&o->monitor, ifindex, NCDIFMONITOR_WATCH_IPV6_ADDR, i->params->iparams->reactor, o, (NCDInterfaceMonitor_handler)monitor_handler, (NCDInterfaceMonitor_handler_error)monitor_handler_error)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDInterfaceMonitor_Init failed");
+        goto fail0;
+    }
+    
+    // set not up
+    o->up = 0;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o, int is_error)
+{
+    // free monitor
+    NCDInterfaceMonitor_Free(&o->monitor);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    instance_free(o, 0);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    ASSERT(o->up)
+    
+    if (!strcmp(name, "addr")) {
+        char str[IPADDR6_PRINT_MAX];
+        ipaddr6_print_addr(o->ifaddr.addr, str);
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    if (!strcmp(name, "prefix")) {
+        char str[10];
+        sprintf(str, "%d", o->ifaddr.prefix);
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    if (!strcmp(name, "cidr_addr")) {
+        char str[IPADDR6_PRINT_MAX];
+        ipaddr6_print_ifaddr(o->ifaddr, str);
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.ipv6.wait_dynamic_addr",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_ipv6_wait_dynamic_addr = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_up.c b/external/badvpn_dns/ncd/modules/net_up.c
new file mode 100644
index 0000000..3aa04ce
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_up.c
@@ -0,0 +1,119 @@
+/**
+ * @file net_up.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Network interface up and down module.
+ * 
+ * Synopsis: net.up(string ifname)
+ * Description: Sets a network interface up on initialization and down on
+ *   deinitialization.
+ */
+
+#include <stdlib.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/extra/NCDIfConfig.h>
+
+#include <generated/blog_channel_ncd_net_up.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValNullTermString ifname_nts;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef ifname_arg;
+    if (!NCDVal_ListRead(params->args, 1, &ifname_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(ifname_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // null terminate ifname
+    if (!NCDVal_StringNullTerminate(ifname_arg, &o->ifname_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // set interface up
+    if (!NCDIfConfig_set_up(o->ifname_nts.data)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to set interface up");
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    
+    return;
+    
+fail1:
+    NCDValNullTermString_Free(&o->ifname_nts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // set interface down
+    if (!NCDIfConfig_set_down(o->ifname_nts.data)) {
+        ModuleLog(o->i, BLOG_ERROR, "failed to set interface down");
+    }
+    
+    // free ifname nts
+    NCDValNullTermString_Free(&o->ifname_nts);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.up",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_up = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/net_watch_interfaces.c b/external/badvpn_dns/ncd/modules/net_watch_interfaces.c
new file mode 100644
index 0000000..7bd7b70
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/net_watch_interfaces.c
@@ -0,0 +1,474 @@
+/**
+ * @file net_watch_interfaces.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Network interface watcher.
+ * 
+ * Synopsis: net.watch_interfaces()
+ * Description: reports network interface events. Transitions up when an event is detected, and
+ *   goes down waiting for the next event when net.watch_interfaces::nextevent() is called.
+ *   On startup, "added" events are reported for existing interfaces.
+ * Variables:
+ *   string event_type - what happened with the interface: "added" or "removed". This may not be
+ *     consistent across events.
+ *   string devname - interface name
+ *   string bus - bus location, for example "pci:0000:06:00.0", "usb:2-1.3:1.0", or "unknown"
+ * 
+ * Synopsis: net.watch_interfaces::nextevent()
+ * Description: makes the watch_interfaces module transition down in order to report the next event.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <regex.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/parse_number.h>
+#include <misc/bsize.h>
+#include <structure/LinkedList1.h>
+#include <udevmonitor/NCDUdevManager.h>
+#include <ncd/NCDModule.h>
+#include <ncd/modules/event_template.h>
+
+#include <generated/blog_channel_ncd_net_watch_interfaces.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define DEVPATH_REGEX "/net/[^/]+$"
+#define DEVPATH_USB_REGEX "/usb[^/]*(/[^/]+)+/([^/]+)/net/[^/]+$"
+#define DEVPATH_PCI_REGEX "/pci[^/]*/[^/]+/([^/]+)/net/[^/]+$"
+
+struct device {
+    char *ifname;
+    char *devpath;
+    uintmax_t ifindex;
+    BStringMap removed_map;
+    LinkedList1Node devices_list_node;
+};
+
+struct instance {
+    NCDModuleInst *i;
+    NCDUdevClient client;
+    LinkedList1 devices_list;
+    regex_t reg;
+    regex_t usb_reg;
+    regex_t pci_reg;
+    event_template templ;
+};
+
+static void templ_func_free (struct instance *o, int is_error);
+
+static struct device * find_device_by_ifname (struct instance *o, const char *ifname)
+{
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->devices_list);
+    while (list_node) {
+        struct device *device = UPPER_OBJECT(list_node, struct device, devices_list_node);
+        if (!strcmp(device->ifname, ifname)) {
+            return device;
+        }
+        list_node = LinkedList1Node_Next(list_node);
+    }
+    
+    return NULL;
+}
+
+static struct device * find_device_by_devpath (struct instance *o, const char *devpath)
+{
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->devices_list);
+    while (list_node) {
+        struct device *device = UPPER_OBJECT(list_node, struct device, devices_list_node);
+        if (!strcmp(device->devpath, devpath)) {
+            return device;
+        }
+        list_node = LinkedList1Node_Next(list_node);
+    }
+    
+    return NULL;
+}
+
+static void free_device (struct instance *o, struct device *device, int have_removed_map)
+{
+    // remove from devices list
+    LinkedList1_Remove(&o->devices_list, &device->devices_list_node);
+    
+    // free removed map
+    if (have_removed_map) {
+        BStringMap_Free(&device->removed_map);
+    }
+    
+    // free devpath
+    free(device->devpath);
+    
+    // free ifname
+    free(device->ifname);
+    
+    // free structure
+    free(device);
+}
+
+static int make_event_map (struct instance *o, int added, const char *ifname, const char *bus, BStringMap *out_map)
+{
+    // init map
+    BStringMap map;
+    BStringMap_Init(&map);
+    
+    // set type
+    if (!BStringMap_Set(&map, "event_type", (added ? "added" : "removed"))) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    // set ifname
+    if (!BStringMap_Set(&map, "devname", ifname)) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    // set bus
+    if (!BStringMap_Set(&map, "bus", bus)) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    *out_map = map;
+    return 1;
+    
+fail1:
+    BStringMap_Free(&map);
+    return 0;
+}
+
+static void queue_event (struct instance *o, BStringMap map)
+{
+    // pass event to template
+    int was_empty;
+    event_template_queue(&o->templ, map, &was_empty);
+    
+    // if event queue was empty, stop receiving udev events
+    if (was_empty) {
+        NCDUdevClient_Pause(&o->client);
+    }
+}
+
+static void add_device (struct instance *o, const char *ifname, const char *devpath, uintmax_t ifindex, const char *bus)
+{
+    ASSERT(!find_device_by_ifname(o, ifname))
+    ASSERT(!find_device_by_devpath(o, devpath))
+    
+    // allocate structure
+    struct device *device = malloc(sizeof(*device));
+    if (!device) {
+        ModuleLog(o->i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // init ifname
+    if (!(device->ifname = strdup(ifname))) {
+        ModuleLog(o->i, BLOG_ERROR, "strdup failed");
+        goto fail1;
+    }
+    
+    // init devpath
+    if (!(device->devpath = strdup(devpath))) {
+        ModuleLog(o->i, BLOG_ERROR, "strdup failed");
+        goto fail2;
+    }
+    
+    // set ifindex
+    device->ifindex = ifindex;
+    
+    // init removed map
+    if (!make_event_map(o, 0, ifname, bus, &device->removed_map)) {
+        ModuleLog(o->i, BLOG_ERROR, "make_event_map failed");
+        goto fail3;
+    }
+    
+    // init added map
+    BStringMap added_map;
+    if (!make_event_map(o, 1, ifname, bus, &added_map)) {
+        ModuleLog(o->i, BLOG_ERROR, "make_event_map failed");
+        goto fail4;
+    }
+    
+    // insert to devices list
+    LinkedList1_Append(&o->devices_list, &device->devices_list_node);
+    
+    // queue event
+    queue_event(o, added_map);
+    
+    return;
+    
+fail4:
+    BStringMap_Free(&device->removed_map);
+fail3:
+    free(device->devpath);
+fail2:
+    free(device->ifname);
+fail1:
+    free(device);
+fail0:
+    ModuleLog(o->i, BLOG_ERROR, "failed to add device %s", ifname);
+}
+
+static void remove_device (struct instance *o, struct device *device)
+{
+    queue_event(o, device->removed_map);
+    free_device(o, device, 0);
+}
+
+static void next_event (struct instance *o)
+{
+    ASSERT(event_template_is_enabled(&o->templ))
+    
+    // order template to finish the current event
+    int is_empty;
+    event_template_dequeue(&o->templ, &is_empty);
+    
+    // if template has no events, continue udev events
+    if (is_empty) {
+        NCDUdevClient_Continue(&o->client);
+    }
+}
+
+static void make_bus (struct instance *o, const char *devpath, const BStringMap *map, char *out_bus, size_t bus_avail)
+{
+    regmatch_t pmatch[3];
+    
+    const char *type;
+    const char *id;
+    size_t id_len;
+    
+    if (!regexec(&o->usb_reg, devpath, 3, pmatch, 0)) {
+        type = "usb";
+        id = devpath + pmatch[2].rm_so;
+        id_len = pmatch[2].rm_eo - pmatch[2].rm_so;
+    }
+    else if (!regexec(&o->pci_reg, devpath, 3, pmatch, 0)) {
+        type = "pci";
+        id = devpath + pmatch[1].rm_so;
+        id_len = pmatch[1].rm_eo - pmatch[1].rm_so;
+    } else {
+        goto fail;
+    }
+    
+    size_t type_len = strlen(type);
+    bsize_t bus_len = bsize_add(bsize_fromsize(type_len), bsize_add(bsize_fromint(1), bsize_add(bsize_fromsize(id_len), bsize_fromint(1))));
+    if (bus_len.is_overflow || bus_len.value > bus_avail) {
+        goto fail;
+    }
+    
+    memcpy(out_bus, type, type_len);
+    out_bus[type_len] = ':';
+    memcpy(out_bus + type_len + 1, id, id_len);
+    out_bus[type_len + 1 + id_len] = '\0';
+    return;
+    
+fail:
+    snprintf(out_bus, bus_avail, "%s", "unknown");
+}
+
+static void client_handler (struct instance *o, char *devpath, int have_map, BStringMap map)
+{
+    // lookup existing device with this devpath
+    struct device *ex_device = find_device_by_devpath(o, devpath);
+    // lookup cache entry
+    const BStringMap *cache_map = NCDUdevManager_Query(o->i->params->iparams->umanager, devpath);
+    
+    if (!cache_map) {
+        if (ex_device) {
+            remove_device(o, ex_device);
+        }
+        goto out;
+    }
+    
+    int match_res = regexec(&o->reg, devpath, 0, NULL, 0);
+    const char *interface = BStringMap_Get(cache_map, "INTERFACE");
+    const char *ifindex_str = BStringMap_Get(cache_map, "IFINDEX");
+    
+    uintmax_t ifindex;
+    if (!(!match_res && interface && ifindex_str && parse_unsigned_integer(ifindex_str, &ifindex))) {
+        if (ex_device) {
+            remove_device(o, ex_device);
+        }
+        goto out;
+    }
+    
+    if (ex_device && (strcmp(ex_device->ifname, interface) || ex_device->ifindex != ifindex)) {
+        remove_device(o, ex_device);
+        ex_device = NULL;
+    }
+    
+    if (!ex_device) {
+        struct device *ex_ifname_device = find_device_by_ifname(o, interface);
+        if (ex_ifname_device) {
+            remove_device(o, ex_ifname_device);
+        }
+        
+        char bus[128];
+        make_bus(o, devpath, cache_map, bus, sizeof(bus));
+        
+        add_device(o, interface, devpath, ifindex, bus);
+    }
+    
+out:
+    free(devpath);
+    if (have_map) {
+        BStringMap_Free(&map);
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // init client
+    NCDUdevClient_Init(&o->client, o->i->params->iparams->umanager, o, (NCDUdevClient_handler)client_handler);
+    
+    // init devices list
+    LinkedList1_Init(&o->devices_list);
+    
+    // compile regex's
+    if (regcomp(&o->reg, DEVPATH_REGEX, REG_EXTENDED)) {
+        ModuleLog(o->i, BLOG_ERROR, "regcomp failed");
+        goto fail1;
+    }
+    if (regcomp(&o->usb_reg, DEVPATH_USB_REGEX, REG_EXTENDED)) {
+        ModuleLog(o->i, BLOG_ERROR, "regcomp failed");
+        goto fail2;
+    }
+    if (regcomp(&o->pci_reg, DEVPATH_PCI_REGEX, REG_EXTENDED)) {
+        ModuleLog(o->i, BLOG_ERROR, "regcomp failed");
+        goto fail3;
+    }
+    
+    event_template_new(&o->templ, o->i, BLOG_CURRENT_CHANNEL, 3, o, (event_template_func_free)templ_func_free);
+    return;
+    
+fail3:
+    regfree(&o->usb_reg);
+fail2:
+    regfree(&o->reg);
+fail1:
+    NCDUdevClient_Free(&o->client);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void templ_func_free (struct instance *o, int is_error)
+{
+    // free devices
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&o->devices_list)) {
+        struct device *device = UPPER_OBJECT(list_node, struct device, devices_list_node);
+        free_device(o, device, 1);
+    }
+    
+    // free regex's
+    regfree(&o->pci_reg);
+    regfree(&o->usb_reg);
+    regfree(&o->reg);
+    
+    // free client
+    NCDUdevClient_Free(&o->client);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    event_template_die(&o->templ);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    return event_template_getvar(&o->templ, name, mem, out);
+}
+
+static void nextevent_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure we are currently reporting an event
+    if (!event_template_is_enabled(&mo->templ)) {
+        ModuleLog(i, BLOG_ERROR, "not reporting an event");
+        goto fail0;
+    }
+    
+    // signal up.
+    // Do it before finishing the event so our process does not advance any further if
+    // we would be killed the event provider going down.
+    NCDModuleInst_Backend_Up(i);
+    
+    // wait for next event
+    next_event(mo);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "net.watch_interfaces",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "net.watch_interfaces::nextevent",
+        .func_new2 = nextevent_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_net_watch_interfaces = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/netmask.c b/external/badvpn_dns/ncd/modules/netmask.c
new file mode 100644
index 0000000..53d9398
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/netmask.c
@@ -0,0 +1,263 @@
+/**
+ * @file netmask.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   ipv4_prefix_to_mask(string prefix)
+ * 
+ * Variables:
+ *   string (empty) - prefix, converted to dotted decimal format without leading
+ *                    zeros
+ * 
+ * Synopsis:
+ *   ipv4_mask_to_prefix(string mask)
+ * 
+ * Variables:
+ *   string (empty) - mask, converted to prefix length
+ * 
+ * Synopsis:
+ *   ipv4_net_from_addr_and_prefix(string addr, string prefix)
+ * 
+ * Variables:
+ *   string (empty) - network part of the address, according to given prefix
+ *                    length, in dotted decimal format without leading zeros
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <misc/ipaddr.h>
+#include <misc/parse_number.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_netmask.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct addr_instance {
+    NCDModuleInst *i;
+    uint32_t addr;
+};
+
+struct prefix_instance {
+    NCDModuleInst *i;
+    int prefix;
+};
+
+static void addr_func_init_templ (void *vo, NCDModuleInst *i, uint32_t addr)
+{
+    struct addr_instance *o = vo;
+    o->i = i;
+    
+    // remember address
+    o->addr = addr;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+}
+
+static void prefix_to_mask_func_init (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef prefix_arg;
+    if (!NCDVal_ListRead(params->args, 1, &prefix_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(prefix_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse prefix
+    int prefix;
+    if (!ipaddr_parse_ipv4_prefix_bin((char *)NCDVal_StringData(prefix_arg), NCDVal_StringLength(prefix_arg), &prefix)) {
+        ModuleLog(i, BLOG_ERROR, "bad prefix");
+        goto fail0;
+    }
+    
+    // make mask
+    uint32_t mask = ipaddr_ipv4_mask_from_prefix(prefix);
+    
+    addr_func_init_templ(vo, i, mask);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void ipv4_net_from_addr_and_prefix_func_init (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef addr_arg;
+    NCDValRef prefix_arg;
+    if (!NCDVal_ListRead(params->args, 2, &addr_arg, &prefix_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(addr_arg) || !NCDVal_IsString(prefix_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse addr
+    uint32_t addr;
+    if (!ipaddr_parse_ipv4_addr_bin((char *)NCDVal_StringData(addr_arg), NCDVal_StringLength(addr_arg), &addr)) {
+        ModuleLog(i, BLOG_ERROR, "bad addr");
+        goto fail0;
+    }
+    
+    // parse prefix
+    int prefix;
+    if (!ipaddr_parse_ipv4_prefix_bin((char *)NCDVal_StringData(prefix_arg), NCDVal_StringLength(prefix_arg), &prefix)) {
+        ModuleLog(i, BLOG_ERROR, "bad prefix");
+        goto fail0;
+    }
+    
+    // make network
+    uint32_t network = (addr & ipaddr_ipv4_mask_from_prefix(prefix));
+    
+    addr_func_init_templ(vo, i, network);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void addr_func_die (void *vo)
+{
+    struct addr_instance *o = vo;
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int addr_func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct addr_instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        char buf[IPADDR_PRINT_MAX];
+        ipaddr_print_addr(o->addr, buf);
+        
+        *out = NCDVal_NewString(mem, buf);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void mask_to_prefix_func_init (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct prefix_instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef mask_arg;
+    if (!NCDVal_ListRead(params->args, 1, &mask_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(mask_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse mask
+    uint32_t mask;
+    if (!ipaddr_parse_ipv4_addr_bin((char *)NCDVal_StringData(mask_arg), NCDVal_StringLength(mask_arg), &mask)) {
+        ModuleLog(i, BLOG_ERROR, "bad mask");
+        goto fail0;
+    }
+    
+    // build prefix
+    if (!ipaddr_ipv4_prefix_from_mask(mask, &o->prefix)) {
+        ModuleLog(i, BLOG_ERROR, "bad mask");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void prefix_func_die (void *vo)
+{
+    struct prefix_instance *o = vo;
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int prefix_func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct prefix_instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        char buf[6];
+        sprintf(buf, "%d", o->prefix);
+        
+        *out = NCDVal_NewString(mem, buf);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "ipv4_prefix_to_mask",
+        .func_new2 = prefix_to_mask_func_init,
+        .func_die = addr_func_die,
+        .func_getvar2 = addr_func_getvar2,
+        .alloc_size = sizeof(struct addr_instance)
+    }, {
+        .type = "ipv4_mask_to_prefix",
+        .func_new2 = mask_to_prefix_func_init,
+        .func_die = prefix_func_die,
+        .func_getvar2 = prefix_func_getvar2,
+        .alloc_size = sizeof(struct prefix_instance)
+    }, {
+        .type = "ipv4_net_from_addr_and_prefix",
+        .func_new2 = ipv4_net_from_addr_and_prefix_func_init,
+        .func_die = addr_func_die,
+        .func_getvar2 = addr_func_getvar2,
+        .alloc_size = sizeof(struct addr_instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_netmask = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/ondemand.c b/external/badvpn_dns/ncd/modules/ondemand.c
new file mode 100644
index 0000000..15c2531
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/ondemand.c
@@ -0,0 +1,372 @@
+/**
+ * @file ondemand.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * On-demand process manager.
+ * 
+ * Synopsis:
+ *   ondemand(string template_name, list args)
+ * 
+ * Description:
+ *   Manages an on-demand template process using a process template named
+ *   template_name.
+ *   On deinitialization, if the process is running, reqests its termination
+ *   and waits for it to terminate.
+ * 
+ * Synopsis:
+ *   ondemand::demand()
+ * 
+ * Description:
+ *   Demands the availability of an on-demand template process.
+ *   This statement is in UP state if and only if the template process of the
+ *   corresponding ondemand object is completely up.
+ * 
+ * Variables:
+ *   Exposes variables and objects from the template process corresponding to
+ *   the ondemand object.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <structure/LinkedList1.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_ondemand.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct ondemand {
+    NCDModuleInst *i;
+    NCDValRef template_name;
+    NCDValRef args;
+    LinkedList1 demands_list;
+    int dying;
+    int have_process;
+    NCDModuleProcess process;
+    int process_terminating;
+    int process_up;
+};
+
+struct demand {
+    NCDModuleInst *i;
+    struct ondemand *od;
+    LinkedList1Node demands_list_node;
+};
+
+static int ondemand_start_process (struct ondemand *o);
+static void ondemand_terminate_process (struct ondemand *o);
+static void ondemand_process_handler (NCDModuleProcess *process, int event);
+static void ondemand_free (struct ondemand *o);
+static void demand_free (struct demand *o, int is_error);
+
+static int ondemand_start_process (struct ondemand *o)
+{
+    ASSERT(!o->dying)
+    ASSERT(!o->have_process)
+    
+    // start process
+    if (!NCDModuleProcess_InitValue(&o->process, o->i, o->template_name, o->args, ondemand_process_handler)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        goto fail0;
+    }
+    
+    // set have process
+    o->have_process = 1;
+    
+    // set process not terminating
+    o->process_terminating = 0;
+    
+    // set process not up
+    o->process_up = 0;
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+static void ondemand_terminate_process (struct ondemand *o)
+{
+    ASSERT(o->have_process)
+    ASSERT(!o->process_terminating)
+    
+    // request termination
+    NCDModuleProcess_Terminate(&o->process);
+    
+    // set process terminating
+    o->process_terminating = 1;
+    
+    if (o->process_up) {
+        // set process down
+        o->process_up = 0;
+        
+        // signal demands down
+        for (LinkedList1Node *n = LinkedList1_GetFirst(&o->demands_list); n; n = LinkedList1Node_Next(n)) {
+            struct demand *demand = UPPER_OBJECT(n, struct demand, demands_list_node);
+            ASSERT(demand->od == o)
+            NCDModuleInst_Backend_Down(demand->i);
+        }
+    }
+}
+
+static void ondemand_process_handler (NCDModuleProcess *process, int event)
+{
+    struct ondemand *o = UPPER_OBJECT(process, struct ondemand, process);
+    ASSERT(o->have_process)
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(!o->process_terminating)
+            ASSERT(!o->process_up)
+            
+            // set process up
+            o->process_up = 1;
+            
+            // signal demands up
+            for (LinkedList1Node *n = LinkedList1_GetFirst(&o->demands_list); n; n = LinkedList1Node_Next(n)) {
+                struct demand *demand = UPPER_OBJECT(n, struct demand, demands_list_node);
+                ASSERT(demand->od == o)
+                NCDModuleInst_Backend_Up(demand->i);
+            }
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(!o->process_terminating)
+            ASSERT(o->process_up)
+            
+            // continue process
+            NCDModuleProcess_Continue(&o->process);
+            
+            // set process down
+            o->process_up = 0;
+            
+            // signal demands down
+            for (LinkedList1Node *n = LinkedList1_GetFirst(&o->demands_list); n; n = LinkedList1Node_Next(n)) {
+                struct demand *demand = UPPER_OBJECT(n, struct demand, demands_list_node);
+                ASSERT(demand->od == o)
+                NCDModuleInst_Backend_Down(demand->i);
+            }
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(o->process_terminating)
+            ASSERT(!o->process_up)
+            
+            // free process
+            NCDModuleProcess_Free(&o->process);
+            
+            // set have no process
+            o->have_process = 0;
+            
+            // if dying, die finally
+            if (o->dying) {
+                ondemand_free(o);
+                return;
+            }
+            
+            // if demands arrivied, restart process
+            if (!LinkedList1_IsEmpty(&o->demands_list)) {
+                if (!ondemand_start_process(o)) {
+                    // error demands
+                    while (!LinkedList1_IsEmpty(&o->demands_list)) {
+                        struct demand *demand = UPPER_OBJECT(LinkedList1_GetFirst(&o->demands_list), struct demand, demands_list_node);
+                        ASSERT(demand->od == o)
+                        demand_free(demand, 1);
+                    }
+                }
+            }
+        } break;
+    }
+}
+
+static void ondemand_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct ondemand *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef arg_template_name;
+    NCDValRef arg_args;
+    if (!NCDVal_ListRead(params->args, 2, &arg_template_name, &arg_args)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(arg_template_name) || !NCDVal_IsList(arg_args)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    o->template_name = arg_template_name;
+    o->args = arg_args;
+    
+    // init demands list
+    LinkedList1_Init(&o->demands_list);
+    
+    // set not dying
+    o->dying = 0;
+    
+    // set have no process
+    o->have_process = 0;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void ondemand_free (struct ondemand *o)
+{
+    ASSERT(!o->have_process)
+    
+    // die demands
+    while (!LinkedList1_IsEmpty(&o->demands_list)) {
+        struct demand *demand = UPPER_OBJECT(LinkedList1_GetFirst(&o->demands_list), struct demand, demands_list_node);
+        ASSERT(demand->od == o)
+        demand_free(demand, 0);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void ondemand_func_die (void *vo)
+{
+    struct ondemand *o = vo;
+    ASSERT(!o->dying)
+    
+    // if not have process, die right away
+    if (!o->have_process) {
+        ondemand_free(o);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+    
+    // request process termination if not already
+    if (!o->process_terminating) {
+        ondemand_terminate_process(o);
+    }
+}
+
+static void demand_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct demand *o = vo;
+    o->i = i;
+    
+    // read arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // set ondemand
+    o->od = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // add to ondemand's demands list
+    LinkedList1_Append(&o->od->demands_list, &o->demands_list_node);
+    
+    // start process if needed
+    if (!o->od->have_process) {
+        ASSERT(!o->od->dying)
+        
+        if (!ondemand_start_process(o->od)) {
+            goto fail1;
+        }
+    }
+    
+    // if process is up, signal up
+    if (o->od->process_up) {
+        NCDModuleInst_Backend_Up(i);
+    }
+    
+    return;
+    
+fail1:
+    LinkedList1_Remove(&o->od->demands_list, &o->demands_list_node);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void demand_free (struct demand *o, int is_error)
+{
+    // remove from ondemand's demands list
+    LinkedList1_Remove(&o->od->demands_list, &o->demands_list_node);
+    
+    // request process termination if no longer needed
+    if (o->od->have_process && !o->od->process_terminating && LinkedList1_IsEmpty(&o->od->demands_list)) {
+        ondemand_terminate_process(o->od);
+    }
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void demand_func_die (void *vo)
+{
+    struct demand *o = vo;
+    
+    demand_free(o, 0);
+}
+
+static int demand_func_getobj (void *vo, NCD_string_id_t objname, NCDObject *out_object)
+{
+    struct demand *o = vo;
+    ASSERT(o->od->have_process)
+    ASSERT(o->od->process_up)
+    
+    return NCDModuleProcess_GetObj(&o->od->process, objname, out_object);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "ondemand",
+        .func_new2 = ondemand_func_new,
+        .func_die = ondemand_func_die,
+        .alloc_size = sizeof(struct ondemand)
+    }, {
+        .type = "ondemand::demand",
+        .func_new2 = demand_func_new,
+        .func_die = demand_func_die,
+        .func_getobj = demand_func_getobj,
+        .alloc_size = sizeof(struct demand)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_ondemand = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/parse.c b/external/badvpn_dns/ncd/modules/parse.c
new file mode 100644
index 0000000..f4f4065
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/parse.c
@@ -0,0 +1,392 @@
+/**
+ * @file parse.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   parse_number(string str)
+ *   parse_value(string str)
+ *   parse_ipv4_addr(string str)
+ *   parse_ipv6_addr(string str)
+ *   
+ * Variables:
+ *   succeeded - "true" or "false", reflecting success of the parsing
+ *   (empty) - normalized parsed value (only if succeeded)
+ * 
+ * Synopsis:
+ *   parse_ipv4_cidr_addr(string str)
+ *   parse_ipv6_cidr_addr(string str)
+ * 
+ * Variables:
+ *   succeeded - "true" or "false", reflecting success of the parsing
+ *   (empty) - normalized CIDR notation address (only if succeeded)
+ *   addr - normalized address without prefix (only if succeeded)
+ *   prefix - normalized prefix without address (only if succeeded)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <misc/parse_number.h>
+#include <misc/ipaddr.h>
+#include <misc/ipaddr6.h>
+#include <ncd/NCDValParser.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_parse.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValMem mem;
+    NCDValRef value;
+    int succeeded;
+};
+
+struct ipv4_cidr_instance {
+    NCDModuleInst *i;
+    int succeeded;
+    struct ipv4_ifaddr ifaddr;
+};
+
+struct ipv6_cidr_instance {
+    NCDModuleInst *i;
+    int succeeded;
+    struct ipv6_ifaddr ifaddr;
+};
+
+enum {STRING_ADDR, STRING_PREFIX};
+
+static const char *strings[] = {
+    "addr", "prefix", NULL
+};
+
+typedef int (*parse_func) (NCDModuleInst *i, const char *str, size_t str_len, NCDValMem *mem, NCDValRef *out);
+
+static int parse_number (NCDModuleInst *i, const char *str, size_t str_len, NCDValMem *mem, NCDValRef *out)
+{
+    uintmax_t n;
+    if (!parse_unsigned_integer_bin(str, str_len, &n)) {
+        ModuleLog(i, BLOG_ERROR, "failed to parse number");
+        return 0;
+    }
+    
+    *out = ncd_make_uintmax(mem, n);
+    if (NCDVal_IsInvalid(*out)) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int parse_value (NCDModuleInst *i, const char *str, size_t str_len, NCDValMem *mem, NCDValRef *out)
+{
+    if (!NCDValParser_Parse(str, str_len, mem, out)) {
+        ModuleLog(i, BLOG_ERROR, "failed to parse value");
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int parse_ipv4_addr (NCDModuleInst *i, const char *str, size_t str_len, NCDValMem *mem, NCDValRef *out)
+{
+    uint32_t addr;
+    if (!ipaddr_parse_ipv4_addr_bin(str, str_len, &addr)) {
+        ModuleLog(i, BLOG_ERROR, "failed to parse ipv4 addresss");
+        return 0;
+    }
+    
+    char buf[IPADDR_PRINT_MAX];
+    ipaddr_print_addr(addr, buf);
+    
+    *out = NCDVal_NewString(mem, buf);
+    if (NCDVal_IsInvalid(*out)) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int parse_ipv6_addr (NCDModuleInst *i, const char *str, size_t str_len, NCDValMem *mem, NCDValRef *out)
+{
+    struct ipv6_addr addr;
+    if (!ipaddr6_parse_ipv6_addr_bin(str, str_len, &addr)) {
+        ModuleLog(i, BLOG_ERROR, "failed to parse ipv6 addresss");
+        return 0;
+    }
+    
+    char buf[IPADDR6_PRINT_MAX];
+    ipaddr6_print_addr(addr, buf);
+    
+    *out = NCDVal_NewString(mem, buf);
+    if (NCDVal_IsInvalid(*out)) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static void new_templ (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, parse_func pfunc)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef str_arg;
+    if (!NCDVal_ListRead(params->args, 1, &str_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(str_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // init mem
+    NCDValMem_Init(&o->mem);
+    
+    // parse
+    o->succeeded = pfunc(i, NCDVal_StringData(str_arg), NCDVal_StringLength(str_arg), &o->mem, &o->value);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free mem
+    NCDValMem_Free(&o->mem);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_SUCCEEDED) {
+        *out = ncd_make_boolean(mem, o->succeeded, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    if (o->succeeded && name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewCopy(mem, o->value);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void func_new_parse_number (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, parse_number);
+}
+
+static void func_new_parse_value (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, parse_value);
+}
+
+static void func_new_parse_ipv4_addr (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, parse_ipv4_addr);
+}
+
+static void func_new_parse_ipv6_addr (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, parse_ipv6_addr);
+}
+
+static void ipv4_cidr_addr_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct ipv4_cidr_instance *o = vo;
+    o->i = i;
+    
+    NCDValRef str_arg;
+    if (!NCDVal_ListRead(params->args, 1, &str_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(str_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    o->succeeded = ipaddr_parse_ipv4_ifaddr_bin(NCDVal_StringData(str_arg), NCDVal_StringLength(str_arg), &o->ifaddr);
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static int ipv4_cidr_addr_func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct ipv4_cidr_instance *o = vo;
+    
+    if (name == NCD_STRING_SUCCEEDED) {
+        *out = ncd_make_boolean(mem, o->succeeded, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    if (!o->succeeded) {
+        return 0;
+    }
+    
+    char str[IPADDR_PRINT_MAX];
+    
+    if (name == NCD_STRING_EMPTY) {
+        ipaddr_print_ifaddr(o->ifaddr, str);
+    }
+    else if (name == ModuleString(o->i, STRING_ADDR)) {
+        ipaddr_print_addr(o->ifaddr.addr, str);
+    }
+    else if (name == ModuleString(o->i, STRING_PREFIX)) {
+        sprintf(str, "%d", o->ifaddr.prefix);
+    }
+    else {
+        return 0;
+    }
+    
+    *out = NCDVal_NewString(mem, str);
+    return 1;
+}
+
+static void ipv6_cidr_addr_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct ipv6_cidr_instance *o = vo;
+    o->i = i;
+    
+    NCDValRef str_arg;
+    if (!NCDVal_ListRead(params->args, 1, &str_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(str_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    o->succeeded = ipaddr6_parse_ipv6_ifaddr_bin(NCDVal_StringData(str_arg), NCDVal_StringLength(str_arg), &o->ifaddr);
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static int ipv6_cidr_addr_func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct ipv6_cidr_instance *o = vo;
+    
+    if (name == NCD_STRING_SUCCEEDED) {
+        *out = ncd_make_boolean(mem, o->succeeded, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    if (!o->succeeded) {
+        return 0;
+    }
+    
+    char str[IPADDR6_PRINT_MAX];
+    
+    if (name == NCD_STRING_EMPTY) {
+        ipaddr6_print_ifaddr(o->ifaddr, str);
+    }
+    else if (name == ModuleString(o->i, STRING_ADDR)) {
+        ipaddr6_print_addr(o->ifaddr.addr, str);
+    }
+    else if (name == ModuleString(o->i, STRING_PREFIX)) {
+        sprintf(str, "%d", o->ifaddr.prefix);
+    }
+    else {
+        return 0;
+    }
+    
+    *out = NCDVal_NewString(mem, str);
+    return 1;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "parse_number",
+        .func_new2 = func_new_parse_number,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "parse_value",
+        .func_new2 = func_new_parse_value,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "parse_ipv4_addr",
+        .func_new2 = func_new_parse_ipv4_addr,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "parse_ipv6_addr",
+        .func_new2 = func_new_parse_ipv6_addr,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "parse_ipv4_cidr_addr",
+        .func_new2 = ipv4_cidr_addr_func_new,
+        .func_getvar2 = ipv4_cidr_addr_func_getvar2,
+        .alloc_size = sizeof(struct ipv4_cidr_instance)
+    }, {
+        .type = "parse_ipv6_cidr_addr",
+        .func_new2 = ipv6_cidr_addr_func_new,
+        .func_getvar2 = ipv6_cidr_addr_func_getvar2,
+        .alloc_size = sizeof(struct ipv6_cidr_instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_parse = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/print.c b/external/badvpn_dns/ncd/modules/print.c
new file mode 100644
index 0000000..6fa0b56
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/print.c
@@ -0,0 +1,207 @@
+/**
+ * @file print.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Modules for printing to standard output.
+ * 
+ * Synopsis:
+ *   print([string str ...])
+ * Description:
+ *   On initialization, prints strings to standard output.
+ * 
+ * Synopsis:
+ *   println([string str ...])
+ * Description:
+ *   On initialization, prints strings to standard output, and a newline.
+ * 
+ * Synopsis:
+ *   rprint([string str ...])
+ * Description:
+ *   On deinitialization, prints strings to standard output.
+ * 
+ * Synopsis:
+ *   rprintln([string str ...])
+ * Description:
+ *   On deinitialization, prints strings to standard output, and a newline.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_print.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct rprint_instance {
+    NCDModuleInst *i;
+    NCDValRef args;
+    int ln;
+};
+
+static int check_args (NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    size_t num_args = NCDVal_ListCount(params->args);
+    
+    for (size_t j = 0; j < num_args; j++) {
+        NCDValRef arg = NCDVal_ListGet(params->args, j);
+        if (!NCDVal_IsString(arg)) {
+            ModuleLog(i, BLOG_ERROR, "wrong type");
+            return 0;
+        }
+    }
+    
+    return 1;
+}
+
+static void do_print (NCDModuleInst *i, NCDValRef args, int ln)
+{
+    size_t num_args = NCDVal_ListCount(args);
+    
+    for (size_t j = 0; j < num_args; j++) {
+        NCDValRef arg = NCDVal_ListGet(args, j);
+        ASSERT(NCDVal_IsString(arg))
+        
+        b_cstring arg_cstr = NCDVal_StringCstring(arg);
+        
+        B_CSTRING_LOOP_RANGE(arg_cstr, 0, arg_cstr.length, pos, chunk_data, chunk_length, {
+            size_t chunk_pos = 0;
+            while (chunk_pos < chunk_length) {
+                ssize_t res = fwrite(chunk_data + chunk_pos, 1, chunk_length - chunk_pos, stdout);
+                if (res <= 0) {
+                    goto out;
+                }
+                chunk_pos += res;
+            }
+        })
+    }
+    
+out:
+    if (ln) {
+        printf("\n");
+    }
+}
+
+static void rprint_func_new_common (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int ln)
+{
+    struct rprint_instance *o = vo;
+    o->i = i;
+    o->args = params->args;
+    o->ln = ln;
+    
+    if (!check_args(i, params)) {
+        goto fail0;
+    }
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void rprint_func_die (void *vo)
+{
+    struct rprint_instance *o = vo;
+    
+    do_print(o->i, o->args, o->ln);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void print_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    if (!check_args(i, params)) {
+        goto fail0;
+    }
+    
+    do_print(i, params->args, 0);
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void println_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    if (!check_args(i, params)) {
+        goto fail0;
+    }
+    
+    do_print(i, params->args, 1);
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void rprint_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    return rprint_func_new_common(vo, i, params, 0);
+}
+
+static void rprintln_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    return rprint_func_new_common(vo, i, params, 1);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "print",
+        .func_new2 = print_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "println",
+        .func_new2 = println_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "rprint",
+        .func_new2 = rprint_func_new,
+        .func_die = rprint_func_die,
+        .alloc_size = sizeof(struct rprint_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+     }, {
+        .type = "rprintln",
+        .func_new2 = rprintln_func_new,
+        .func_die = rprint_func_die,
+        .alloc_size = sizeof(struct rprint_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_print = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/process_manager.c b/external/badvpn_dns/ncd/modules/process_manager.c
new file mode 100644
index 0000000..390c8d5
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/process_manager.c
@@ -0,0 +1,538 @@
+/**
+ * @file process_manager.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 which allows starting and controlling template processes using an imperative
+ * interface.
+ * 
+ * Synopsis:
+ *   process_manager()
+ * 
+ * Description:
+ *   Represents a set of managed processes. Each process has a "name", which is a value
+ *   that uniquely identifies it within its process manager.
+ *   When deinitialization is requested, requests termination of all managed processes
+ *   and waits for all of them to terminate before deinitializing.
+ *   Managed processes can access objects as seen from the process_manager() statement
+ *   via the special _caller object.
+ * 
+ * Synopsis:
+ *   process_manager::start(name, string template_name, list args)
+ *   process_manager::start(string template_name, list args)
+ * 
+ * Description:
+ *   Creates a new process from the template named 'template_name', with arguments 'args',
+ *   identified by 'name' within the process manager. If the two-argument form of start() is
+ *   used, the process does not have a name, and cannot be imperatively stopped using
+ *   stop().
+ *   If a process with this name already exists and is not being terminated, does nothing.
+ *   If it exists and is being terminated, it will be restarted using the given parameters
+ *   after it terminates. If the process does not exist, it is created immediately, and the
+ *   immediate effects of the process being created happnen before the immediate effects of
+ *   the start() statement going up.
+ * 
+ * Synopsis:
+ *   process_manager::stop(name)
+ * 
+ * Description:
+ *   Initiates termination of the process identified by 'name' within the process manager.
+ *   If there is no such process, or the process is already being terminated, does nothing.
+ *   If the process does exist and is not already being terminated, termination of the
+ *   process is requested, and the immediate effects of the termination request happen
+ *   before the immediate effects of the stop() statement going up.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/strdup.h>
+#include <misc/balloc.h>
+#include <structure/LinkedList1.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_process_manager.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define RETRY_TIME 10000
+
+#define PROCESS_STATE_RUNNING 1
+#define PROCESS_STATE_STOPPING 2
+#define PROCESS_STATE_RESTARTING 3
+#define PROCESS_STATE_RETRYING 4
+
+struct instance {
+    NCDModuleInst *i;
+    LinkedList1 processes_list;
+    int dying;
+};
+
+struct process {
+    struct instance *manager;
+    LinkedList1Node processes_list_node;
+    BSmallTimer retry_timer; // running if state=retrying
+    int state;
+    NCD_string_id_t template_name;
+    NCDValMem current_mem;
+    NCDValSafeRef current_name;
+    NCDValSafeRef current_args;
+    NCDValMem next_mem; // next_* if state=restarting
+    NCDValSafeRef next_name;
+    NCDValSafeRef next_args;
+    NCDModuleProcess module_process; // if state!=retrying
+};
+
+static struct process * find_process (struct instance *o, NCDValRef name);
+static int process_new (struct instance *o, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args);
+static void process_free (struct process *p);
+static void process_try (struct process *p);
+static void process_retry_timer_handler (BSmallTimer *retry_timer);
+static void process_module_process_handler_event (NCDModuleProcess *module_process, int event);
+static int process_module_process_func_getspecialobj (NCDModuleProcess *module_process, NCD_string_id_t name, NCDObject *out_object);
+static int process_module_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static void process_stop (struct process *p);
+static int process_restart (struct process *p, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args);
+static void instance_free (struct instance *o);
+
+static struct process * find_process (struct instance *o, NCDValRef name)
+{
+    ASSERT(!NCDVal_IsInvalid(name))
+    
+    LinkedList1Node *n = LinkedList1_GetFirst(&o->processes_list);
+    while (n) {
+        struct process *p = UPPER_OBJECT(n, struct process, processes_list_node);
+        ASSERT(p->manager == o)
+        if (!NCDVal_IsInvalid(NCDVal_FromSafe(&p->current_mem, p->current_name)) && NCDVal_Compare(NCDVal_FromSafe(&p->current_mem, p->current_name), name) == 0) {
+            return p;
+        }
+        n = LinkedList1Node_Next(n);
+    }
+    
+    return NULL;
+}
+
+static int process_new (struct instance *o, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args)
+{
+    ASSERT(!o->dying)
+    ASSERT(NCDVal_IsInvalid(NCDVal_FromSafe(mem, name)) || !find_process(o, NCDVal_FromSafe(mem, name)))
+    ASSERT(NCDVal_IsString(NCDVal_FromSafe(mem, template_name)))
+    ASSERT(NCDVal_IsList(NCDVal_FromSafe(mem, args)))
+    
+    // allocate structure
+    struct process *p = BAlloc(sizeof(*p));
+    if (!p) {
+        ModuleLog(o->i, BLOG_ERROR, "BAlloc failed");
+        goto fail0;
+    }
+    
+    // set manager
+    p->manager = o;
+
+    // insert to processes list
+    LinkedList1_Append(&o->processes_list, &p->processes_list_node);
+
+    // init retry timer
+    BSmallTimer_Init(&p->retry_timer, process_retry_timer_handler);
+    
+    // init template name
+    p->template_name = ncd_get_string_id(NCDVal_FromSafe(mem, template_name), o->i->params->iparams->string_index);
+    if (p->template_name < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "ncd_get_string_id failed");
+        goto fail1;
+    }
+    
+    // init current mem as a copy of mem
+    if (!NCDValMem_InitCopy(&p->current_mem, mem)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDValMem_InitCopy failed");
+        goto fail1;
+    }
+    
+    // remember name and args
+    p->current_name = name;
+    p->current_args = args;
+    
+    // try starting it
+    process_try(p);
+    return 1;
+    
+fail1:
+    LinkedList1_Remove(&o->processes_list, &p->processes_list_node);
+    BFree(p);
+fail0:
+    return 0;
+}
+
+static void process_free (struct process *p)
+{
+    struct instance *o = p->manager;
+    
+    // free current mem
+    NCDValMem_Free(&p->current_mem);
+    
+    // free timer
+    BReactor_RemoveSmallTimer(o->i->params->iparams->reactor, &p->retry_timer);
+    
+    // remove from processes list
+    LinkedList1_Remove(&o->processes_list, &p->processes_list_node);
+    
+    // free structure
+    BFree(p);
+}
+
+static void process_try (struct process *p)
+{
+    struct instance *o = p->manager;
+    ASSERT(!o->dying)
+    ASSERT(!BSmallTimer_IsRunning(&p->retry_timer))
+    
+    // init module process
+    if (!NCDModuleProcess_InitId(&p->module_process, o->i, p->template_name, NCDVal_FromSafe(&p->current_mem, p->current_args), process_module_process_handler_event)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        goto fail;
+    }
+        
+    // set special objects function
+    NCDModuleProcess_SetSpecialFuncs(&p->module_process, process_module_process_func_getspecialobj);
+    
+    // set state
+    p->state = PROCESS_STATE_RUNNING;
+    return;
+    
+fail:
+    // set timer
+    BReactor_SetSmallTimer(o->i->params->iparams->reactor, &p->retry_timer, BTIMER_SET_RELATIVE, RETRY_TIME);
+    
+    // set state
+    p->state = PROCESS_STATE_RETRYING;
+}
+
+static void process_retry_timer_handler (BSmallTimer *retry_timer)
+{
+    struct process *p = UPPER_OBJECT(retry_timer, struct process, retry_timer);
+    struct instance *o = p->manager;
+    B_USE(o)
+    ASSERT(p->state == PROCESS_STATE_RETRYING)
+    ASSERT(!o->dying)
+    
+    // retry
+    process_try(p);
+}
+
+void process_module_process_handler_event (NCDModuleProcess *module_process, int event)
+{
+    struct process *p = UPPER_OBJECT(module_process, struct process, module_process);
+    struct instance *o = p->manager;
+    ASSERT(p->state != PROCESS_STATE_RETRYING)
+    ASSERT(p->state != PROCESS_STATE_RESTARTING || !o->dying)
+    ASSERT(!BSmallTimer_IsRunning(&p->retry_timer))
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(p->state == PROCESS_STATE_RUNNING)
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(p->state == PROCESS_STATE_RUNNING)
+            
+            // allow process to continue
+            NCDModuleProcess_Continue(&p->module_process);
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(p->state == PROCESS_STATE_RESTARTING || p->state == PROCESS_STATE_STOPPING)
+            
+            // free module process
+            NCDModuleProcess_Free(&p->module_process);
+            
+            if (p->state == PROCESS_STATE_RESTARTING) {
+                // free current mem
+                NCDValMem_Free(&p->current_mem);
+                
+                // move next mem/values over current mem/values
+                p->current_mem = p->next_mem;
+                p->current_name = p->next_name;
+                p->current_args = p->next_args;
+                
+                // try starting it again
+                process_try(p);
+                return;
+            }
+            
+            // free process
+            process_free(p);
+            
+            // if manager is dying and there are no more processes, let it die
+            if (o->dying && LinkedList1_IsEmpty(&o->processes_list)) {
+                instance_free(o);
+            }
+        } break;
+    }
+}
+
+static int process_module_process_func_getspecialobj (NCDModuleProcess *module_process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct process *p = UPPER_OBJECT(module_process, struct process, module_process);
+    ASSERT(p->state != PROCESS_STATE_RETRYING)
+    
+    if (name == NCD_STRING_CALLER) {
+        *out_object = NCDObject_Build(-1, p, NCDObject_no_getvar, process_module_process_caller_obj_func_getobj);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int process_module_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct process *p = NCDObject_DataPtr(obj);
+    struct instance *o = p->manager;
+    ASSERT(p->state != PROCESS_STATE_RETRYING)
+    
+    return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
+}
+
+static void process_stop (struct process *p)
+{
+    switch (p->state) {
+        case PROCESS_STATE_RETRYING: {
+            // free process
+            process_free(p);
+        } break;
+        
+        case PROCESS_STATE_RUNNING: {
+            // request process to terminate
+            NCDModuleProcess_Terminate(&p->module_process);
+            
+            // set state
+            p->state = PROCESS_STATE_STOPPING;
+        } break;
+        
+        case PROCESS_STATE_RESTARTING: {
+            // free next mem
+            NCDValMem_Free(&p->next_mem);
+            
+            // set state
+            p->state = PROCESS_STATE_STOPPING;
+        } break;
+        
+        case PROCESS_STATE_STOPPING: {
+            // nothing to do
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static int process_restart (struct process *p, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args)
+{
+    struct instance *o = p->manager;
+    ASSERT(!o->dying)
+    ASSERT(p->state == PROCESS_STATE_STOPPING)
+    ASSERT(!NCDVal_IsInvalid(NCDVal_FromSafe(&p->current_mem, p->current_name)) || NCDVal_IsInvalid(NCDVal_FromSafe(mem, name)))
+    ASSERT(NCDVal_IsInvalid(NCDVal_FromSafe(&p->current_mem, p->current_name)) || NCDVal_Compare(NCDVal_FromSafe(mem, name), NCDVal_FromSafe(&p->current_mem, p->current_name)) == 0)
+    ASSERT(NCDVal_IsString(NCDVal_FromSafe(mem, template_name)))
+    ASSERT(NCDVal_IsList(NCDVal_FromSafe(mem, args)))
+    
+    // copy mem to next mem
+    if (!NCDValMem_InitCopy(&p->next_mem, mem)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDValMem_InitCopy failed");
+        goto fail0;
+    }
+    
+    // remember name and args to next
+    p->next_name = name;
+    p->next_args = args;
+    
+    // set state
+    p->state = PROCESS_STATE_RESTARTING;
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // init processes list
+    LinkedList1_Init(&o->processes_list);
+    
+    // set not dying
+    o->dying = 0;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+void instance_free (struct instance *o)
+{
+    ASSERT(LinkedList1_IsEmpty(&o->processes_list))
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(!o->dying)
+    
+    // request all processes to die
+    LinkedList1Node *n = LinkedList1_GetFirst(&o->processes_list);
+    while (n) {
+        LinkedList1Node *next = LinkedList1Node_Next(n);
+        struct process *p = UPPER_OBJECT(n, struct process, processes_list_node);
+        process_stop(p);
+        n = next;
+    }
+    
+    // if there are no processes, die immediately
+    if (LinkedList1_IsEmpty(&o->processes_list)) {
+        instance_free(o);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+}
+
+static void start_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    NCDValRef name_arg = NCDVal_NewInvalid();
+    NCDValRef template_name_arg;
+    NCDValRef args_arg;
+    if (!NCDVal_ListRead(params->args, 2, &template_name_arg, &args_arg) &&
+        !NCDVal_ListRead(params->args, 3, &name_arg, &template_name_arg, &args_arg)
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(template_name_arg) || !NCDVal_IsList(args_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // signal up.
+    // Do it before creating the process so that the process starts initializing before our own process continues.
+    NCDModuleInst_Backend_Up(i);
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    if (!mo->dying) {
+        struct process *p = (NCDVal_IsInvalid(name_arg) ? NULL : find_process(mo, name_arg));
+        if (!p || p->state == PROCESS_STATE_STOPPING) {
+            if (p) {
+                if (!process_restart(p, args_arg.mem, NCDVal_ToSafe(name_arg), NCDVal_ToSafe(template_name_arg), NCDVal_ToSafe(args_arg))) {
+                    ModuleLog(i, BLOG_ERROR, "failed to restart process");
+                    goto fail0;
+                }
+            } else {
+                if (!process_new(mo, args_arg.mem, NCDVal_ToSafe(name_arg), NCDVal_ToSafe(template_name_arg), NCDVal_ToSafe(args_arg))) {
+                    ModuleLog(i, BLOG_ERROR, "failed to create process");
+                    goto fail0;
+                }
+            }
+        }
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void stop_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    NCDValRef name_arg;
+    if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // signal up.
+    // Do it before stopping the process so that the process starts terminating before our own process continues.
+    NCDModuleInst_Backend_Up(i);
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    if (!mo->dying) {
+        struct process *p = find_process(mo, name_arg);
+        if (p && p->state != PROCESS_STATE_STOPPING) {
+            process_stop(p);
+        }
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "process_manager",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "process_manager::start",
+        .func_new2 = start_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "process_manager::stop",
+        .func_new2 = stop_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_process_manager = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/reboot.c b/external/badvpn_dns/ncd/modules/reboot.c
new file mode 100644
index 0000000..3522431
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/reboot.c
@@ -0,0 +1,103 @@
+/**
+ * @file reboot.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   hard_reboot()
+ *   hard_poweroff()
+ */
+
+#include <unistd.h>
+#include <sys/reboot.h>
+
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_reboot.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void func_new_hard_reboot (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // reboot
+    if (reboot(RB_AUTOBOOT) < 0) {
+        ModuleLog(i, BLOG_ERROR, "reboot(RB_AUTOBOOT) failed");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_hard_poweroff (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // power off
+    if (reboot(RB_POWER_OFF) < 0) {
+        ModuleLog(i, BLOG_ERROR, "reboot(RB_POWER_OFF) failed");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "hard_reboot",
+        .func_new2 = func_new_hard_reboot
+    }, {
+        .type = "hard_poweroff",
+        .func_new2 = func_new_hard_poweroff
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_reboot = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/ref.c b/external/badvpn_dns/ncd/modules/ref.c
new file mode 100644
index 0000000..a0e9cf8
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/ref.c
@@ -0,0 +1,215 @@
+/**
+ * @file ref.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * References module.
+ * 
+ * Synopsis:
+ *   refhere()
+ * Variables:
+ *   Exposes variables and objects as seen from this refhere() statement.
+ * 
+ * Synopsis:
+ *   ref refhere::ref()
+ *   ref ref::ref()
+ * Variables:
+ *   Exposes variables and objects as seen from the corresponding refhere()
+ *   statement.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <structure/LinkedList0.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_ref.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct refhere_instance {
+    NCDModuleInst *i;
+    LinkedList0 refs_list;
+};
+
+struct ref_instance {
+    NCDModuleInst *i;
+    struct refhere_instance *rh;
+    LinkedList0Node refs_list_node;
+};
+
+static void ref_instance_free (struct ref_instance *o);
+
+static void refhere_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct refhere_instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // init refs list
+    LinkedList0_Init(&o->refs_list);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void refhere_func_die (void *vo)
+{
+    struct refhere_instance *o = vo;
+    
+    // die refs
+    while (!LinkedList0_IsEmpty(&o->refs_list)) {
+        struct ref_instance *ref = UPPER_OBJECT(LinkedList0_GetFirst(&o->refs_list), struct ref_instance, refs_list_node);
+        ASSERT(ref->rh == o)
+        ref_instance_free(ref);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int refhere_func_getobj (void *vo, NCD_string_id_t objname, NCDObject *out_object)
+{
+    struct refhere_instance *o = vo;
+    
+    // We don't redirect methods, and there will never be an object
+    // with empty name. Fail here so we don't report non-errors.
+    if (objname == NCD_STRING_EMPTY) {
+        return 0;
+    }
+    
+    return NCDModuleInst_Backend_GetObj(o->i, objname, out_object);
+}
+
+static void ref_func_new_templ (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, struct refhere_instance *rh)
+{
+    struct ref_instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // set refhere
+    o->rh = rh;
+    
+    // add to refhere's refs list
+    LinkedList0_Prepend(&o->rh->refs_list, &o->refs_list_node);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void ref_func_new_from_refhere (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct refhere_instance *rh = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    return ref_func_new_templ(vo, i, params, rh);
+}
+
+static void ref_func_new_from_ref (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct ref_instance *ref = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    return ref_func_new_templ(vo, i, params, ref->rh);
+}
+
+static void ref_instance_free (struct ref_instance *o)
+{
+    // remove from refhere's reft list
+    LinkedList0_Remove(&o->rh->refs_list, &o->refs_list_node);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void ref_func_die (void *vo)
+{
+    struct ref_instance *o = vo;
+    
+    ref_instance_free(o);
+}
+
+static int ref_func_getobj (void *vo, NCD_string_id_t objname, NCDObject *out_object)
+{
+    struct ref_instance *o = vo;
+    
+    // We don't redirect methods, and there will never be an object
+    // with empty name. Fail here so we don't report non-errors.
+    if (objname == NCD_STRING_EMPTY) {
+        return 0;
+    }
+    
+    return NCDModuleInst_Backend_GetObj(o->rh->i, objname, out_object);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "refhere",
+        .func_new2 = refhere_func_new,
+        .func_die = refhere_func_die,
+        .func_getobj = refhere_func_getobj,
+        .alloc_size = sizeof(struct refhere_instance)
+    }, {
+        .type = "refhere::ref",
+        .base_type = "ref",
+        .func_new2 = ref_func_new_from_refhere,
+        .func_die = ref_func_die,
+        .func_getobj = ref_func_getobj,
+        .alloc_size = sizeof(struct ref_instance)
+    }, {
+        .type = "ref::ref",
+        .base_type = "ref",
+        .func_new2 = ref_func_new_from_ref,
+        .func_die = ref_func_die,
+        .func_getobj = ref_func_getobj,
+        .alloc_size = sizeof(struct ref_instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_ref = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/regex_match.c b/external/badvpn_dns/ncd/modules/regex_match.c
new file mode 100644
index 0000000..d541b88
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/regex_match.c
@@ -0,0 +1,369 @@
+/**
+ * @file regex_match.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Regular expression matching module.
+ * 
+ * Synopsis:
+ *   regex_match(string input, string regex)
+ * 
+ * Variables:
+ *   succeeded - "true" or "false", indicating whether input matched regex
+ *   matchN - for N=0,1,2,..., the matching data for the N-th subexpression
+ *     (match0 = whole match)
+ * 
+ * Description:
+ *   Matches 'input' with the POSIX extended regular expression 'regex'.
+ *   'regex' must be a string without null bytes, but 'input' can contain null bytes.
+ *   However, it's difficult, if not impossible, to actually match nulls with the regular
+ *   expression.
+ *   The input and regex strings are interpreted according to the POSIX regex functions
+ *   (regcomp(), regexec()); in particular, the current locale setting affects the
+ *   interpretation.
+ * 
+ * Synopsis:
+ *   regex_replace(string input, list(string) regex, list(string) replace)
+ * 
+ * Variables:
+ *   string (empty) - transformed input
+ * 
+ * Description:
+ *   Replaces matching parts of a string. Replacement is performed by repetedly matching
+ *   the remaining part of the string with all regular expressions. On each step, out of
+ *   all regular expressions that match the remainder of the string, the one whose match
+ *   starts at the least position wins, and the matching part is replaced with the
+ *   replacement string corresponding to this regular expression. The process continues
+ *   from the end of the just-replaced portion until no more regular expressions match.
+ *   If multiple regular expressions match at the least position, the one that appears
+ *   first in the 'regex' argument wins.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <regex.h>
+
+#include <misc/string_begins_with.h>
+#include <misc/parse_number.h>
+#include <misc/expstring.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_regex_match.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define MAX_MATCHES 64
+
+struct instance {
+    NCDModuleInst *i;
+    const char *input;
+    size_t input_len;
+    int succeeded;
+    int num_matches;
+    regmatch_t matches[MAX_MATCHES];
+};
+
+struct replace_instance {
+    NCDModuleInst *i;
+    char *output;
+    size_t output_len;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef input_arg;
+    NCDValRef regex_arg;
+    if (!NCDVal_ListRead(params->args, 2, &input_arg, &regex_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(input_arg) || !NCDVal_IsStringNoNulls(regex_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    o->input = NCDVal_StringData(input_arg);
+    o->input_len = NCDVal_StringLength(input_arg);
+    
+    // make sure we don't overflow regoff_t
+    if (o->input_len > INT_MAX) {
+        ModuleLog(o->i, BLOG_ERROR, "input string too long");
+        goto fail0;
+    }
+    
+    // null terminate regex
+    NCDValNullTermString regex_nts;
+    if (!NCDVal_StringNullTerminate(regex_arg, &regex_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // compile regex
+    regex_t preg;
+    int ret = regcomp(&preg, regex_nts.data, REG_EXTENDED);
+    NCDValNullTermString_Free(&regex_nts);
+    if (ret != 0) {
+        ModuleLog(o->i, BLOG_ERROR, "regcomp failed (error=%d)", ret);
+        goto fail0;
+    }
+    
+    // execute match
+    o->matches[0].rm_so = 0;
+    o->matches[0].rm_eo = o->input_len;
+    o->succeeded = (regexec(&preg, o->input, MAX_MATCHES, o->matches, REG_STARTEND) == 0);
+    
+    // free regex
+    regfree(&preg);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (!strcmp(name, "succeeded")) {
+        *out = ncd_make_boolean(mem, o->succeeded, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    size_t pos;
+    uintmax_t n;
+    if ((pos = string_begins_with(name, "match")) && parse_unsigned_integer(name + pos, &n)) {
+        if (o->succeeded && n < MAX_MATCHES && o->matches[n].rm_so >= 0) {
+            regmatch_t *m = &o->matches[n];
+            
+            ASSERT(m->rm_so <= o->input_len)
+            ASSERT(m->rm_eo >= m->rm_so)
+            ASSERT(m->rm_eo <= o->input_len)
+            
+            size_t len = m->rm_eo - m->rm_so;
+            
+            *out = NCDVal_NewStringBin(mem, (uint8_t *)o->input + m->rm_so, len);
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+static void replace_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct replace_instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef input_arg;
+    NCDValRef regex_arg;
+    NCDValRef replace_arg;
+    if (!NCDVal_ListRead(params->args, 3, &input_arg, &regex_arg, &replace_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (!NCDVal_IsString(input_arg) || !NCDVal_IsList(regex_arg) || !NCDVal_IsList(replace_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    
+    // check number of regex/replace
+    if (NCDVal_ListCount(regex_arg) != NCDVal_ListCount(replace_arg)) {
+        ModuleLog(i, BLOG_ERROR, "number of regex's is not the same as number of replacements");
+        goto fail1;
+    }
+    size_t num_regex = NCDVal_ListCount(regex_arg);
+    
+    // allocate array for compiled regex's
+    regex_t *regs = BAllocArray(num_regex, sizeof(regs[0]));
+    if (!regs) {
+        ModuleLog(i, BLOG_ERROR, "BAllocArray failed");
+        goto fail1;
+    }
+    size_t num_done_regex = 0;
+    
+    // compile regex's, check arguments
+    while (num_done_regex < num_regex) {
+        NCDValRef regex = NCDVal_ListGet(regex_arg, num_done_regex);
+        NCDValRef replace = NCDVal_ListGet(replace_arg, num_done_regex);
+        
+        if (!NCDVal_IsStringNoNulls(regex) || !NCDVal_IsString(replace)) {
+            ModuleLog(i, BLOG_ERROR, "wrong regex/replace type for pair %zu", num_done_regex);
+            goto fail2;
+        }
+        
+        // null terminate regex
+        NCDValNullTermString regex_nts;
+        if (!NCDVal_StringNullTerminate(regex, &regex_nts)) {
+            ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+            goto fail2;
+        }
+        
+        int res = regcomp(&regs[num_done_regex], regex_nts.data, REG_EXTENDED);
+        NCDValNullTermString_Free(&regex_nts);
+        if (res != 0) {
+            ModuleLog(i, BLOG_ERROR, "regcomp failed for pair %zu (error=%d)", num_done_regex, res);
+            goto fail2;
+        }
+        
+        num_done_regex++;
+    }
+    
+    // init output string
+    ExpString out;
+    if (!ExpString_Init(&out)) {
+        ModuleLog(i, BLOG_ERROR, "ExpString_Init failed");
+        goto fail2;
+    }
+    
+    // input state
+    const char *in = NCDVal_StringData(input_arg);
+    size_t in_pos = 0;
+    size_t in_len = NCDVal_StringLength(input_arg);
+    
+    // process input
+    while (in_pos < in_len) {
+        // find first match
+        int have_match = 0;
+        size_t match_regex = 0; // to remove warning
+        regmatch_t match = {0, 0}; // to remove warning
+        for (size_t j = 0; j < num_regex; j++) {
+            regmatch_t this_match;
+            this_match.rm_so = 0;
+            this_match.rm_eo = in_len - in_pos;
+            if (regexec(&regs[j], in + in_pos, 1, &this_match, REG_STARTEND) == 0 && (!have_match || this_match.rm_so < match.rm_so)) {
+                have_match = 1;
+                match_regex = j;
+                match = this_match;
+            }
+        }
+        
+        // if no match, append remaining data and finish
+        if (!have_match) {
+            if (!ExpString_AppendBinary(&out, (const uint8_t *)in + in_pos, in_len - in_pos)) {
+                ModuleLog(i, BLOG_ERROR, "ExpString_AppendBinary failed");
+                goto fail3;
+            }
+            break;
+        }
+        
+        // append data before match
+        if (!ExpString_AppendBinary(&out, (const uint8_t *)in + in_pos, match.rm_so)) {
+            ModuleLog(i, BLOG_ERROR, "ExpString_AppendBinary failed");
+            goto fail3;
+        }
+        
+        // append replacement data
+        NCDValRef replace = NCDVal_ListGet(replace_arg, match_regex);
+        if (!ExpString_AppendBinary(&out, (const uint8_t *)NCDVal_StringData(replace), NCDVal_StringLength(replace))) {
+            ModuleLog(i, BLOG_ERROR, "ExpString_AppendBinary failed");
+            goto fail3;
+        }
+        
+        in_pos += match.rm_eo;
+    }
+    
+    // set output
+    o->output = ExpString_Get(&out);
+    o->output_len = ExpString_Length(&out);
+    
+    // free compiled regex's
+    while (num_done_regex-- > 0) {
+        regfree(&regs[num_done_regex]);
+    }
+    
+    // free array
+    BFree(regs);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail3:
+    ExpString_Free(&out);
+fail2:
+    while (num_done_regex-- > 0) {
+        regfree(&regs[num_done_regex]);
+    }
+    BFree(regs);
+fail1:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void replace_func_die (void *vo)
+{
+    struct replace_instance *o = vo;
+    
+    // free output
+    BFree(o->output);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int replace_func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct replace_instance *o = vo;
+    
+    if (!strcmp(name, "")) {
+        *out = NCDVal_NewStringBin(mem, (uint8_t *)o->output, o->output_len);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "regex_match",
+        .func_new2 = func_new,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "regex_replace",
+        .func_new2 = replace_func_new,
+        .func_die = replace_func_die,
+        .func_getvar = replace_func_getvar,
+        .alloc_size = sizeof(struct replace_instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_regex_match = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/run.c b/external/badvpn_dns/ncd/modules/run.c
new file mode 100644
index 0000000..147914c
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/run.c
@@ -0,0 +1,187 @@
+/**
+ * @file run.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 running arbitrary programs.
+ * NOTE: There is no locking - the program may run in parallel with other
+ * NCD processes and their programs.
+ * 
+ * Synopsis: run(list do_cmd, list undo_cmd)
+ * Arguments:
+ *   list do_cmd - Command run on startup. The first element is the full path
+ *     to the executable, other elements are command line arguments (excluding
+ *     the zeroth argument). An empty list is interpreted as no operation.
+ *   list undo_cmd - Command run on shutdown, like do_cmd.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/strdup.h>
+#include <ncd/extra/value_utils.h>
+#include <ncd/modules/command_template.h>
+
+#include <generated/blog_channel_ncd_run.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void template_free_func (void *vo, int is_error);
+
+struct instance {
+    NCDModuleInst *i;
+    BEventLock lock;
+    command_template_instance cti;
+};
+
+static int build_cmdline (NCDModuleInst *i, NCDValRef args, int remove, char **exec, CmdLine *cl)
+{
+    // read arguments
+    NCDValRef do_cmd_arg;
+    NCDValRef undo_cmd_arg;
+    if (!NCDVal_ListRead(args, 2, &do_cmd_arg, &undo_cmd_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsList(do_cmd_arg) || !NCDVal_IsList(undo_cmd_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    NCDValRef list = (remove ? undo_cmd_arg : do_cmd_arg);
+    size_t count = NCDVal_ListCount(list);
+    
+    // check if there is no command
+    if (count == 0) {
+        *exec = NULL;
+        return 1;
+    }
+    
+    // read exec
+    NCDValRef exec_arg = NCDVal_ListGet(list, 0);
+    if (!NCDVal_IsStringNoNulls(exec_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    if (!(*exec = ncd_strdup(exec_arg))) {
+        ModuleLog(i, BLOG_ERROR, "ncd_strdup failed");
+        goto fail0;
+    }
+    
+    // start cmdline
+    if (!CmdLine_Init(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
+        goto fail1;
+    }
+    
+    // add header
+    if (!CmdLine_Append(cl, *exec)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
+        goto fail2;
+    }
+    
+    // add additional arguments
+    for (size_t j = 1; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(list, j);
+        
+        if (!NCDVal_IsStringNoNulls(arg)) {
+            ModuleLog(i, BLOG_ERROR, "wrong type");
+            goto fail2;
+        }
+        
+        b_cstring arg_cstr = NCDVal_StringCstring(arg);
+        if (!CmdLine_AppendCstring(cl, arg_cstr, 0, arg_cstr.length)) {
+            ModuleLog(i, BLOG_ERROR, "CmdLine_AppendCstring failed");
+            goto fail2;
+        }
+    }
+    
+    // finish
+    if (!CmdLine_Finish(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
+        goto fail2;
+    }
+    
+    return 1;
+    
+fail2:
+    CmdLine_Free(cl);
+fail1:
+    free(*exec);
+fail0:
+    return 0;
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // init dummy event lock
+    BEventLock_Init(&o->lock, BReactor_PendingGroup(i->params->iparams->reactor));
+    
+    command_template_new(&o->cti, i, params, build_cmdline, template_free_func, o, BLOG_CURRENT_CHANNEL, &o->lock);
+    return;
+}
+
+void template_free_func (void *vo, int is_error)
+{
+    struct instance *o = vo;
+    
+    // free dummy event lock
+    BEventLock_Free(&o->lock);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    command_template_die(&o->cti);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "run",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_run= {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/runonce.c b/external/badvpn_dns/ncd/modules/runonce.c
new file mode 100644
index 0000000..bd7cea4
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/runonce.c
@@ -0,0 +1,331 @@
+/**
+ * @file runonce.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Imperative program execution module. On initialization, starts the process.
+ * Goes to UP state when the process terminates. When requested to die, waits for
+ * the process to terminate if it's running, optionally sending SIGTERM.
+ * 
+ * Synopsis: runonce(list(string) cmd, [list opts])
+ * Arguments:
+ *   cmd - Command to run on startup. The first element is the full path
+ *     to the executable, other elements are command line arguments (excluding
+ *     the zeroth argument).
+ *   opts - Map of options:
+ *     "term_on_deinit":"true" - If we get a deinit request while the process is
+ *       running, send it SIGTERM.
+ *     "keep_stdout":"true" - Start the program with the same stdout as the NCD process.
+ *     "keep_stderr":true" - Start the program with the same stderr as the NCD process.
+ *     "do_setsid":"true" - Call setsid() in the child before exec. This is needed to
+ *       start the 'agetty' program.
+ *     "username":username_string - Start the process under the permissions of the
+ *       specified user. 
+ * Variables:
+ *   string exit_status - if the program exited normally, the non-negative exit code, otherwise -1
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <misc/cmdline.h>
+#include <misc/strdup.h>
+#include <system/BProcess.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+#include <ncd/extra/NCDBProcessOpts.h>
+
+#include <generated/blog_channel_ncd_runonce.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define STATE_RUNNING 1
+#define STATE_RUNNING_DIE 2
+#define STATE_FINISHED 3
+
+struct instance {
+    NCDModuleInst *i;
+    int term_on_deinit;
+    int state;
+    BProcess process;
+    int exit_status;
+};
+
+static void instance_free (struct instance *o);
+
+static int build_cmdline (NCDModuleInst *i, NCDValRef cmd_arg, char **exec, CmdLine *cl)
+{
+    ASSERT(!NCDVal_IsInvalid(cmd_arg))
+    
+    if (!NCDVal_IsList(cmd_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    size_t count = NCDVal_ListCount(cmd_arg);
+    
+    // read exec
+    if (count == 0) {
+        ModuleLog(i, BLOG_ERROR, "missing executable name");
+        goto fail0;
+    }
+    NCDValRef exec_arg = NCDVal_ListGet(cmd_arg, 0);
+    if (!NCDVal_IsStringNoNulls(exec_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    if (!(*exec = ncd_strdup(exec_arg))) {
+        ModuleLog(i, BLOG_ERROR, "strdup failed");
+        goto fail0;
+    }
+    
+    // start cmdline
+    if (!CmdLine_Init(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
+        goto fail1;
+    }
+    
+    // add header
+    if (!CmdLine_Append(cl, *exec)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
+        goto fail2;
+    }
+    
+    // add additional arguments
+    for (size_t j = 1; j < count; j++) {
+        NCDValRef arg = NCDVal_ListGet(cmd_arg, j);
+        
+        if (!NCDVal_IsStringNoNulls(arg)) {
+            ModuleLog(i, BLOG_ERROR, "wrong type");
+            goto fail2;
+        }
+        
+        b_cstring arg_cstr = NCDVal_StringCstring(arg);
+        if (!CmdLine_AppendCstring(cl, arg_cstr, 0, arg_cstr.length)) {
+            ModuleLog(i, BLOG_ERROR, "CmdLine_AppendCstring failed");
+            goto fail2;
+        }
+    }
+    
+    // finish
+    if (!CmdLine_Finish(cl)) {
+        ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
+        goto fail2;
+    }
+    
+    return 1;
+    
+fail2:
+    CmdLine_Free(cl);
+fail1:
+    free(*exec);
+fail0:
+    return 0;
+}
+
+static void process_handler (struct instance *o, int normally, uint8_t normally_exit_status)
+{
+    ASSERT(o->state == STATE_RUNNING || o->state == STATE_RUNNING_DIE)
+    
+    // free process
+    BProcess_Free(&o->process);
+    
+    // if we were requested to die, die now
+    if (o->state == STATE_RUNNING_DIE) {
+        instance_free(o);
+        return;
+    }
+    
+    // remember exit status
+    o->exit_status = (normally ? normally_exit_status : -1);
+    
+    // set state
+    o->state = STATE_FINISHED;
+    
+    // set up
+    NCDModuleInst_Backend_Up(o->i);
+}
+
+static int opts_func_unknown (void *user, NCDValRef key, NCDValRef val)
+{
+    struct instance *o = user;
+    
+    if (NCDVal_IsString(key) && NCDVal_StringEquals(key, "term_on_deinit")) {
+        o->term_on_deinit = ncd_read_boolean(val);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // init arguments
+    o->term_on_deinit = 0;
+    
+    // read arguments
+    NCDValRef cmd_arg;
+    NCDValRef opts_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 1, &cmd_arg) && !NCDVal_ListRead(params->args, 2, &cmd_arg, &opts_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    NCDBProcessOpts opts;
+    
+    // deprecated options format
+    if (!NCDVal_IsInvalid(opts_arg) && NCDVal_IsList(opts_arg)) {
+        int keep_stdout = 0;
+        int keep_stderr = 0;
+        int do_setsid = 0;
+        
+        // read options
+        size_t count = NCDVal_IsInvalid(opts_arg) ? 0 : NCDVal_ListCount(opts_arg);
+        for (size_t j = 0; j < count; j++) {
+            NCDValRef opt = NCDVal_ListGet(opts_arg, j);
+            
+            // read name
+            if (!NCDVal_IsString(opt)) {
+                ModuleLog(o->i, BLOG_ERROR, "wrong option name type");
+                goto fail0;
+            }
+            
+            if (NCDVal_StringEquals(opt, "term_on_deinit")) {
+                o->term_on_deinit = 1;
+            }
+            else if (NCDVal_StringEquals(opt, "keep_stdout")) {
+                keep_stdout = 1;
+            }
+            else if (NCDVal_StringEquals(opt, "keep_stderr")) {
+                keep_stderr = 1;
+            }
+            else if (NCDVal_StringEquals(opt, "do_setsid")) {
+                do_setsid = 1;
+            }
+            else {
+                ModuleLog(o->i, BLOG_ERROR, "unknown option name");
+                goto fail0;
+            }
+        }
+        
+        NCDBProcessOpts_InitOld(&opts, keep_stdout, keep_stderr, do_setsid);
+    } else {
+        if (!NCDBProcessOpts_Init(&opts, opts_arg, opts_func_unknown, o, i, BLOG_CURRENT_CHANNEL)) {
+            goto fail0;
+        }
+    }
+    
+    // build cmdline
+    char *exec;
+    CmdLine cl;
+    if (!build_cmdline(o->i, cmd_arg, &exec, &cl)) {
+        NCDBProcessOpts_Free(&opts);
+        goto fail0;
+    }
+    
+    // start process
+    struct BProcess_params p_params = NCDBProcessOpts_GetParams(&opts);
+    if (!BProcess_Init2(&o->process, o->i->params->iparams->manager, (BProcess_handler)process_handler, o, exec, CmdLine_Get(&cl), p_params)) {
+        ModuleLog(i, BLOG_ERROR, "BProcess_Init failed");
+        CmdLine_Free(&cl);
+        free(exec);
+        NCDBProcessOpts_Free(&opts);
+        goto fail0;
+    }
+    
+    CmdLine_Free(&cl);
+    free(exec);
+    NCDBProcessOpts_Free(&opts);
+    
+    // set state
+    o->state = STATE_RUNNING;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o)
+{
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(o->state != STATE_RUNNING_DIE)
+    
+    if (o->state == STATE_FINISHED) {
+        instance_free(o);
+        return;
+    }
+    
+    // send SIGTERM if requested
+    if (o->term_on_deinit) {
+        BProcess_Terminate(&o->process);
+    }
+    
+    o->state = STATE_RUNNING_DIE;
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    ASSERT(o->state == STATE_FINISHED)
+    
+    if (!strcmp(name, "exit_status")) {
+        char str[30];
+        snprintf(str, sizeof(str), "%d", o->exit_status);
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "runonce",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_runonce = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/sleep.c b/external/badvpn_dns/ncd/modules/sleep.c
new file mode 100644
index 0000000..a139aa9
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/sleep.c
@@ -0,0 +1,178 @@
+/**
+ * @file sleep.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   sleep(string ms_start [, string ms_stop])
+ * 
+ * Description:
+ *   On init, sleeps 'ms_start' milliseconds then goes up, or goes up immediately
+ *   if 'ms_start' is an empty string.
+ *   On deinit, sleeps 'ms_stop' milliseconds then dies, or dies immediately if
+ *   'ms_stop' is an empty string or is not provided. If a deinit is requested while
+ *   the init sleep is still in progress, the init sleep is aborted and the deinit
+ *   sleep is started immediately (if any).
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_sleep.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    btime_t ms_stop;
+    BTimer timer;
+    int dying;
+};
+
+static void instance_free (struct instance *o);
+
+static void timer_handler (void *vo)
+{
+    struct instance *o = vo;
+    
+    if (!o->dying) {
+        // signal up
+        NCDModuleInst_Backend_Up(o->i);
+    } else {
+        // die
+        instance_free(o);
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef ms_start_arg;
+    NCDValRef ms_stop_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 1, &ms_start_arg) &&
+        !NCDVal_ListRead(params->args, 2, &ms_start_arg, &ms_stop_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(ms_start_arg) || (!NCDVal_IsInvalid(ms_stop_arg) && !NCDVal_IsString(ms_stop_arg))) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    uintmax_t ms;
+    btime_t ms_start;
+    
+    if (NCDVal_StringEqualsId(ms_start_arg, NCD_STRING_EMPTY, i->params->iparams->string_index)) {
+        ms_start = -1;
+    } else {
+        if (!ncd_read_uintmax(ms_start_arg, &ms) || ms > INT64_MAX) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong start time");
+            goto fail0;
+        }
+        ms_start = ms;
+    }
+    
+    if (NCDVal_IsInvalid(ms_stop_arg) || NCDVal_StringEqualsId(ms_stop_arg, NCD_STRING_EMPTY, i->params->iparams->string_index)) {
+        o->ms_stop = -1;
+    } else {
+        if (!ncd_read_uintmax(ms_stop_arg, &ms) || ms > INT64_MAX) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong stop time");
+            goto fail0;
+        }
+        o->ms_stop = ms;
+    }
+    
+    // init timer
+    BTimer_Init(&o->timer, 0, timer_handler, o);
+    
+    // set not dying
+    o->dying = 0;
+    
+    if (ms_start < 0) {
+        // go up
+        NCDModuleInst_Backend_Up(i);
+    } else {
+        // set timer
+        BReactor_SetTimerAfter(o->i->params->iparams->reactor, &o->timer, ms_start);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+void instance_free (struct instance *o)
+{
+    // free timer
+    BReactor_RemoveTimer(o->i->params->iparams->reactor, &o->timer);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    if (o->ms_stop < 0) {
+        // die immediately
+        instance_free(o);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+    
+    // set timer
+    BReactor_SetTimerAfter(o->i->params->iparams->reactor, &o->timer, o->ms_stop);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "sleep",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_sleep = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/socket.c b/external/badvpn_dns/ncd/modules/socket.c
new file mode 100644
index 0000000..59ca8d7
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/socket.c
@@ -0,0 +1,1057 @@
+/**
+ * @file socket.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   sys.socket sys.connect(string addr [, map options])
+ * 
+ * Options:
+ *   "read_size" - the maximum number of bytes that can be read by a single
+ *     read() call. Must be greater than zero. Greater values may improve
+ *     performance, but will increase memory usage. Default: 8192.
+ * 
+ * Variables:
+ *   string is_error - "true" if there was an error with the connection,
+ *     "false" if not
+ * 
+ * Description:
+ *   Attempts to establish a connection to a server. The address should be
+ *   in one of the following forms:
+ *   - {"tcp", {"ipv4", ipv4_address, port_number}},
+ *   - {"tcp", {"ipv6", ipv6_address, port_number}},
+ *   - {"unix", socket_path}.
+ *   When the connection attempt is finished, the sys.connect() statement goes
+ *   up, and the 'is_error' variable should be used to check for connection
+ *   failure. If there was no error, the read(), write() and close() methods
+ *   can be used to work with the connection.
+ *   If an error occurs after the connection has been established, the
+ *   sys.connect() statement will automatically trigger backtracking, and the
+ *   'is_error' variable will be changed to "true". This means that all
+ *   errors with the connection can be handled at the place of sys.connect(),
+ *   and no special care is normally needed to handle error in read() and
+ *   write().
+ *   WARNING: when you're not trying to either send or receive data, the
+ *   connection may be unable to detect any events with the connection.
+ *   You should never be neither sending nor receiving for an indefinite time.
+ * 
+ * Synopsis:
+ *   sys.socket::read()
+ * 
+ * Variables:
+ *   string (empty) - some data received from the socket, or empty on EOF
+ *   string not_eof - "true" if EOF was not encountered, "false" if it was
+ * 
+ * Description:
+ *   Receives data from the connection. If EOF was encountered (remote host
+ *   has closed the connection), this returns no data. Otherwise it returns
+ *   at least one byte.
+ *   WARNING: after you receive EOF from a sys.listen() type socket, is is
+ *   your responsibility to call close() eventually, else the cline process
+ *   may remain alive indefinitely.
+ *   WARNING: this may return an arbitrarily small chunk of data. There is
+ *   no significance to the size of the chunks. Correct code will behave
+ *   the same no matter how the incoming data stream is split up.
+ *   WARNING: if a read() is terminated while it is still in progress, i.e.
+ *   has not gone up yet, then the connection is automatically closed, as
+ *   if close() was called.
+ * 
+ * Synopsis:
+ *   sys.socket::write(string data)
+ * 
+ * Description:
+ *   Sends data to the connection.
+ *   WARNING: this may block if the operating system's internal send buffer
+ *   is full. Be careful not to enter a deadlock where both ends of the
+ *   connection are trying to send data to the other, but neither is trying
+ *   to receive any data.
+ *   WARNING: if a write() is terminated while it is still in progress, i.e.
+ *   has not gone up yet, then the connection is automatically closed, as
+ *   if close() was called.
+ * 
+ * Synopsis:
+ *   sys.socket::close()
+ * 
+ * Description:
+ *   Closes the connection. After this, any further read(), write() or close()
+ *   will trigger an error with the interpreter. For client sockets created
+ *   via sys.listen(), this will immediately trigger termination of the client
+ *   process.
+ * 
+ * Synopsis:
+ *   sys.listen(string address, string client_template, list args [, map options])
+ * 
+ * Options:
+ *   "read_size" - the maximum number of bytes that can be read by a single
+ *     read() call. Must be greater than zero. Greater values may improve
+ *     performance, but will increase memory usage. Default: 8192.
+ * 
+ * Variables:
+ *   string is_error - "true" if listening failed to inittialize, "false" if
+ *     not
+ * 
+ * Special objects and variables in client_template:
+ *   sys.socket _socket - the socket object for the client
+ *   string _socket.client_addr - the address of the client. The form is
+ *     like the second part of the sys.connect() address format, e.g.
+ *     {"ipv4", "1.2.3.4", "4000"}.
+ * 
+ * Description:
+ *   Starts listening on the specified address. The 'is_error' variable
+ *   reflects the success of listening initiation. If listening succeeds,
+ *   then for every client that connects, a process is automatically created
+ *   from the template specified by 'client_template', and the 'args' list
+ *   is used as template arguments. Inside such processes, a special object
+ *   '_socket' is available, which represents the connection, and supports
+ *   the same methods as sys.connect(), i.e. read(), write() and close().
+ *   When an error occurs with the connection, the socket is automatically
+ *   closed, triggering process termination.
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+#include <stdarg.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <structure/LinkedList0.h>
+#include <system/BConnection.h>
+#include <system/BConnectionGeneric.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+#include <ncd/extra/address_utils.h>
+#include <ncd/extra/NCDBuf.h>
+
+#include <generated/blog_channel_ncd_socket.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+#define CONNECTION_TYPE_CONNECT 1
+#define CONNECTION_TYPE_LISTEN 2
+
+#define CONNECTION_STATE_CONNECTING 1
+#define CONNECTION_STATE_ESTABLISHED 2
+#define CONNECTION_STATE_ERROR 3
+#define CONNECTION_STATE_ABORTED 4
+
+#define DEFAULT_READ_BUF_SIZE 8192
+
+struct connection {
+    union {
+        struct {
+            NCDModuleInst *i;
+            BConnector connector;
+            size_t read_buf_size;
+        } connect;
+        struct {
+            struct listen_instance *listen_inst;
+            LinkedList0Node clients_list_node;
+            BAddr addr;
+            NCDModuleProcess process;
+        } listen;
+    };
+    
+    unsigned int type:2;
+    unsigned int state:3;
+    unsigned int recv_closed:1;
+    BConnection connection;
+    NCDBufStore store;
+    struct read_instance *read_inst;
+    struct write_instance *write_inst;
+};
+
+struct read_instance {
+    NCDModuleInst *i;
+    struct connection *con_inst;
+    NCDBuf *buf;
+    size_t read_size;
+};
+
+struct write_instance {
+    NCDModuleInst *i;
+    struct connection *con_inst;
+    b_cstring cstr;
+    size_t pos;
+};
+
+struct listen_instance {
+    NCDModuleInst *i;
+    unsigned int have_error:1;
+    unsigned int dying:1;
+    size_t read_buf_size;
+    NCDValRef client_template;
+    NCDValRef client_template_args;
+    BListener listener;
+    LinkedList0 clients_list;
+};
+
+enum {STRING_SOCKET, STRING_SYS_SOCKET, STRING_CLIENT_ADDR};
+
+static const char *strings[] = {
+    "_socket", "sys.socket", "client_addr", NULL
+};
+
+static int parse_options (NCDModuleInst *i, NCDValRef options, size_t *out_read_size);
+static void connection_log (struct connection *o, int level, const char *fmt, ...);
+static void connection_free_connection (struct connection *o);
+static void connection_error (struct connection *o);
+static void connection_abort (struct connection *o);
+static void connection_connector_handler (void *user, int is_error);
+static void connection_connection_handler (void *user, int event);
+static void connection_send_handler_done (void *user, int data_len);
+static void connection_recv_handler_done (void *user, int data_len);
+static void connection_process_handler (struct NCDModuleProcess_s *process, int event);
+static int connection_process_func_getspecialobj (struct NCDModuleProcess_s *process, NCD_string_id_t name, NCDObject *out_object);
+static int connection_process_socket_obj_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value);
+static void listen_listener_handler (void *user);
+
+static int parse_options (NCDModuleInst *i, NCDValRef options, size_t *out_read_size)
+{
+    ASSERT(out_read_size)
+    
+    *out_read_size = DEFAULT_READ_BUF_SIZE;
+    
+    if (!NCDVal_IsInvalid(options)) {
+        if (!NCDVal_IsMap(options)) {
+            ModuleLog(i, BLOG_ERROR, "options argument is not a map");
+            return 0;
+        }
+        
+        int num_recognized = 0;
+        NCDValRef value;
+        
+        if (!NCDVal_IsInvalid(value = NCDVal_MapGetValue(options, "read_size"))) {
+            uintmax_t read_size;
+            if (!NCDVal_IsString(value) || !ncd_read_uintmax(value, &read_size) || read_size > SIZE_MAX || read_size == 0) {
+                ModuleLog(i, BLOG_ERROR, "wrong read_size");
+                return 0;
+            }
+            num_recognized++;
+            *out_read_size = read_size;
+        }
+        
+        if (NCDVal_MapCount(options) > num_recognized) {
+            ModuleLog(i, BLOG_ERROR, "unrecognized options present");
+            return 0;
+        }
+    }
+    
+    return 1;
+}
+
+static void connection_log (struct connection *o, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    
+    switch (o->type) {
+        case CONNECTION_TYPE_CONNECT: {
+            NCDModuleInst_Backend_LogVarArg(o->connect.i, BLOG_CURRENT_CHANNEL, level, fmt, vl);
+        } break;
+        
+        case CONNECTION_TYPE_LISTEN: {
+            if (BLog_WouldLog(BLOG_CURRENT_CHANNEL, level)) {
+                BLog_Begin();
+                o->listen.listen_inst->i->params->logfunc(o->listen.listen_inst->i);
+                char addr_str[BADDR_MAX_PRINT_LEN];
+                BAddr_Print(&o->listen.addr, addr_str);
+                BLog_Append("client %s: ", addr_str);
+                BLog_AppendVarArg(fmt, vl);
+                BLog_Finish(BLOG_CURRENT_CHANNEL, level);
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    va_end(vl);
+}
+
+static void connection_free_connection (struct connection *o)
+{
+    // disconnect read instance
+    if (o->read_inst) {
+        ASSERT(o->read_inst->con_inst == o)
+        o->read_inst->con_inst = NULL;
+    }
+    
+    // disconnect write instance
+    if (o->write_inst) {
+        ASSERT(o->write_inst->con_inst == o)
+        o->write_inst->con_inst = NULL;
+    }
+    
+    // free connection interfaces
+    BConnection_RecvAsync_Free(&o->connection);
+    BConnection_SendAsync_Free(&o->connection);
+    
+    // free connection
+    BConnection_Free(&o->connection);
+    
+    // free store
+    NCDBufStore_Free(&o->store);
+}
+
+static void connection_error (struct connection *o)
+{
+    ASSERT(o->state == CONNECTION_STATE_CONNECTING ||
+           o->state == CONNECTION_STATE_ESTABLISHED)
+    
+    // for listen clients, we don't report errors directly,
+    // we just terminate the client process
+    if (o->type == CONNECTION_TYPE_LISTEN) {
+        ASSERT(o->state != CONNECTION_STATE_CONNECTING)
+        connection_abort(o);
+        return;
+    }
+    
+    // free connector
+    if (o->state == CONNECTION_STATE_CONNECTING) {
+        BConnector_Free(&o->connect.connector);
+    }
+    
+    // free connection resources
+    if (o->state == CONNECTION_STATE_ESTABLISHED) {
+        connection_free_connection(o);
+    }
+    
+    // trigger reporting of failure
+    if (o->state == CONNECTION_STATE_ESTABLISHED) {
+        NCDModuleInst_Backend_Down(o->connect.i);
+    }
+    NCDModuleInst_Backend_Up(o->connect.i);
+    
+    // set state
+    o->state = CONNECTION_STATE_ERROR;
+}
+
+static void connection_abort (struct connection *o)
+{
+    ASSERT(o->state == CONNECTION_STATE_ESTABLISHED)
+    
+    // free connection resources
+    connection_free_connection(o);
+    
+    // if this is a listen connection, terminate client process
+    if (o->type == CONNECTION_TYPE_LISTEN) {
+        NCDModuleProcess_Terminate(&o->listen.process);
+    }
+    
+    // set state
+    o->state = CONNECTION_STATE_ABORTED;
+}
+
+static void connection_connector_handler (void *user, int is_error)
+{
+    struct connection *o = user;
+    ASSERT(o->type == CONNECTION_TYPE_CONNECT)
+    ASSERT(o->state == CONNECTION_STATE_CONNECTING)
+    
+    // check error
+    if (is_error) {
+        connection_log(o, BLOG_ERROR, "connection failed");
+        goto fail;
+    }
+    
+    // init connection
+    if (!BConnection_Init(&o->connection, BConnection_source_connector(&o->connect.connector), o->connect.i->params->iparams->reactor, o, connection_connection_handler)) {
+        connection_log(o, BLOG_ERROR, "BConnection_Init failed");
+        goto fail;
+    }
+    
+    // init connection interfaces
+    BConnection_SendAsync_Init(&o->connection);
+    BConnection_RecvAsync_Init(&o->connection);
+    
+    // setup send/recv done callbacks
+    StreamPassInterface_Sender_Init(BConnection_SendAsync_GetIf(&o->connection), connection_send_handler_done, o);
+    StreamRecvInterface_Receiver_Init(BConnection_RecvAsync_GetIf(&o->connection), connection_recv_handler_done, o);
+    
+    // init store
+    NCDBufStore_Init(&o->store, o->connect.read_buf_size);
+    
+    // set not reading, not writing, recv not closed
+    o->read_inst = NULL;
+    o->write_inst = NULL;
+    o->recv_closed = 0;
+    
+    // free connector
+    BConnector_Free(&o->connect.connector);
+    
+    // set state
+    o->state = CONNECTION_STATE_ESTABLISHED;
+    
+    // go up
+    NCDModuleInst_Backend_Up(o->connect.i);
+    return;
+    
+fail:
+    connection_error(o);
+}
+
+static void connection_connection_handler (void *user, int event)
+{
+    struct connection *o = user;
+    ASSERT(o->state == CONNECTION_STATE_ESTABLISHED)
+    ASSERT(event == BCONNECTION_EVENT_RECVCLOSED || event == BCONNECTION_EVENT_ERROR)
+    ASSERT(event != BCONNECTION_EVENT_RECVCLOSED || !o->recv_closed)
+    
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        // if we have read operation, make it finish with eof
+        if (o->read_inst) {
+            ASSERT(o->read_inst->con_inst == o)
+            o->read_inst->con_inst = NULL;
+            o->read_inst->read_size = 0;
+            NCDModuleInst_Backend_Up(o->read_inst->i);
+            o->read_inst = NULL;
+        }
+        
+        // set recv closed
+        o->recv_closed = 1;
+        return;
+    }
+    
+    connection_log(o, BLOG_ERROR, "connection error");
+    
+    // handle error
+    connection_error(o);
+}
+
+static void connection_send_handler_done (void *user, int data_len)
+{
+    struct connection *o = user;
+    ASSERT(o->state == CONNECTION_STATE_ESTABLISHED)
+    ASSERT(o->write_inst)
+    ASSERT(o->write_inst->con_inst == o)
+    ASSERT(o->write_inst->pos < o->write_inst->cstr.length)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->write_inst->cstr.length - o->write_inst->pos)
+    
+    struct write_instance *wr = o->write_inst;
+    
+    // update send state
+    wr->pos += data_len;
+    
+    // if there's more to send, send again
+    if (wr->pos < wr->cstr.length) {
+        size_t chunk_len;
+        const char *chunk_data = b_cstring_get(wr->cstr, wr->pos, wr->cstr.length - wr->pos, &chunk_len);
+        size_t to_send = (chunk_len > INT_MAX ? INT_MAX : chunk_len);
+        StreamPassInterface_Sender_Send(BConnection_SendAsync_GetIf(&o->connection), (uint8_t *)chunk_data, to_send);
+        return;
+    }
+    
+    // finish write operation
+    wr->con_inst = NULL;
+    NCDModuleInst_Backend_Up(wr->i);
+    o->write_inst = NULL;
+}
+
+static void connection_recv_handler_done (void *user, int data_len)
+{
+    struct connection *o = user;
+    ASSERT(o->state == CONNECTION_STATE_ESTABLISHED)
+    ASSERT(o->read_inst)
+    ASSERT(o->read_inst->con_inst == o)
+    ASSERT(!o->recv_closed)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= NCDBufStore_BufSize(&o->store))
+    
+    struct read_instance *re = o->read_inst;
+    
+    // finish read operation
+    re->con_inst = NULL;
+    re->read_size = data_len;
+    NCDModuleInst_Backend_Up(re->i);
+    o->read_inst = NULL;
+}
+
+static void connection_process_handler (struct NCDModuleProcess_s *process, int event)
+{
+    struct connection *o = UPPER_OBJECT(process, struct connection, listen.process);
+    ASSERT(o->type == CONNECTION_TYPE_LISTEN)
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(o->state == CONNECTION_STATE_ESTABLISHED)
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(o->state == CONNECTION_STATE_ESTABLISHED)
+            NCDModuleProcess_Continue(&o->listen.process);
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(o->state == CONNECTION_STATE_ABORTED)
+            
+            struct listen_instance *li = o->listen.listen_inst;
+            ASSERT(!li->have_error)
+            
+            // remove from clients list
+            LinkedList0_Remove(&li->clients_list, &o->listen.clients_list_node);
+            
+            // free process
+            NCDModuleProcess_Free(&o->listen.process);
+            
+            // free connection structure
+            free(o);
+            
+            // if listener is dying and this was the last process, have it die
+            if (li->dying && LinkedList0_IsEmpty(&li->clients_list)) {
+                NCDModuleInst_Backend_Dead(li->i);
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static int connection_process_func_getspecialobj (struct NCDModuleProcess_s *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct connection *o = UPPER_OBJECT(process, struct connection, listen.process);
+    ASSERT(o->type == CONNECTION_TYPE_LISTEN)
+    
+    if (name == ModuleString(o->listen.listen_inst->i, STRING_SOCKET)) {
+        *out_object = NCDObject_Build(ModuleString(o->listen.listen_inst->i, STRING_SYS_SOCKET), o, connection_process_socket_obj_func_getvar, NCDObject_no_getobj);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int connection_process_socket_obj_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value)
+{
+    struct connection *o = NCDObject_DataPtr(obj);
+    ASSERT(o->type == CONNECTION_TYPE_LISTEN)
+    
+    if (name == ModuleString(o->listen.listen_inst->i, STRING_CLIENT_ADDR)) {
+        *out_value = ncd_make_baddr(o->listen.addr, mem);
+        if (NCDVal_IsInvalid(*out_value)) {
+            connection_log(o, BLOG_ERROR, "ncd_make_baddr failed");
+        }
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void listen_listener_handler (void *user)
+{
+    struct listen_instance *o = user;
+    ASSERT(!o->have_error)
+    ASSERT(!o->dying)
+    
+    // allocate connection structure
+    struct connection *con = malloc(sizeof(*con));
+    if (!con) {
+        ModuleLog(o->i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // set connection type and listen instance
+    con->type = CONNECTION_TYPE_LISTEN;
+    con->listen.listen_inst = o;
+    
+    // init connection
+    if (!BConnection_Init(&con->connection, BConnection_source_listener(&o->listener, &con->listen.addr), o->i->params->iparams->reactor, con, connection_connection_handler)) {
+        ModuleLog(o->i, BLOG_ERROR, "BConnection_Init failed");
+        goto fail1;
+    }
+    
+    // init connection interfaces
+    BConnection_SendAsync_Init(&con->connection);
+    BConnection_RecvAsync_Init(&con->connection);
+    
+    // setup send/recv done callbacks
+    StreamPassInterface_Sender_Init(BConnection_SendAsync_GetIf(&con->connection), connection_send_handler_done, con);
+    StreamRecvInterface_Receiver_Init(BConnection_RecvAsync_GetIf(&con->connection), connection_recv_handler_done, con);
+    
+    // init process
+    if (!NCDModuleProcess_InitValue(&con->listen.process, o->i, o->client_template, o->client_template_args, connection_process_handler)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_InitValue failed");
+        goto fail2;
+    }
+    
+    // set special objects callback
+    NCDModuleProcess_SetSpecialFuncs(&con->listen.process, connection_process_func_getspecialobj);
+    
+    // insert to clients list
+    LinkedList0_Prepend(&o->clients_list, &con->listen.clients_list_node);
+    
+    // init store
+    NCDBufStore_Init(&con->store, o->read_buf_size);
+    
+    // set not reading, not writing, recv not closed
+    con->read_inst = NULL;
+    con->write_inst = NULL;
+    con->recv_closed = 0;
+    
+    // set state
+    con->state = CONNECTION_STATE_ESTABLISHED;
+    return;
+    
+fail2:
+    BConnection_RecvAsync_Free(&con->connection);
+    BConnection_SendAsync_Free(&con->connection);
+    BConnection_Free(&con->connection);
+fail1:
+    free(con);
+fail0:
+    return;
+}
+
+static void connect_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct connection *o = vo;
+    o->type = CONNECTION_TYPE_CONNECT;
+    o->connect.i = i;
+    
+    // pass connection pointer to methods so the same methods can work for
+    // listen type connections
+    NCDModuleInst_Backend_PassMemToMethods(i);
+    
+    // read arguments
+    NCDValRef address_arg;
+    NCDValRef options_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 1, &address_arg) &&
+        !NCDVal_ListRead(params->args, 2, &address_arg, &options_arg)
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // parse options
+    if (!parse_options(i, options_arg, &o->connect.read_buf_size)) {
+        goto fail0;
+    }
+    
+    // read address
+    struct BConnection_addr address;
+    if (!ncd_read_bconnection_addr(address_arg, &address)) {
+        ModuleLog(i, BLOG_ERROR, "wrong address");
+        goto error;
+    }
+    
+    // init connector
+    if (!BConnector_InitGeneric(&o->connect.connector, address, i->params->iparams->reactor, o, connection_connector_handler)) {
+        ModuleLog(i, BLOG_ERROR, "BConnector_InitGeneric failed");
+        goto error;
+    }
+    
+    // set state
+    o->state = CONNECTION_STATE_CONNECTING;
+    return;
+    
+error:
+    // go up in error state
+    o->state = CONNECTION_STATE_ERROR;
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void connect_func_die (void *vo)
+{
+    struct connection *o = vo;
+    ASSERT(o->type == CONNECTION_TYPE_CONNECT)
+    
+    // free connector
+    if (o->state == CONNECTION_STATE_CONNECTING) {
+        BConnector_Free(&o->connect.connector);
+    }
+    
+    // free connection resources
+    if (o->state == CONNECTION_STATE_ESTABLISHED) {
+        connection_free_connection(o);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->connect.i);
+}
+
+static int connect_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct connection *o = vo;
+    ASSERT(o->type == CONNECTION_TYPE_CONNECT)
+    ASSERT(o->state != CONNECTION_STATE_CONNECTING)
+    
+    if (name == NCD_STRING_IS_ERROR) {
+        int is_error = (o->state == CONNECTION_STATE_ERROR);
+        *out = ncd_make_boolean(mem, is_error, o->connect.i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void read_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct read_instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get connection
+    struct connection *con_inst = params->method_user;
+    
+    // check connection state
+    if (con_inst->state != CONNECTION_STATE_ESTABLISHED) {
+        ModuleLog(i, BLOG_ERROR, "connection is not established");
+        goto fail0;
+    }
+    
+    // check if there's already a read in progress
+    if (con_inst->read_inst) {
+        ModuleLog(i, BLOG_ERROR, "read is already in progress");
+        goto fail0;
+    }
+    
+    // get buffer
+    o->buf = NCDBufStore_GetBuf(&con_inst->store);
+    if (!o->buf) {
+        ModuleLog(i, BLOG_ERROR, "NCDBufStore_GetBuf failed");
+        goto fail0;
+    }
+    
+    // if eof was reached, go up immediately
+    if (con_inst->recv_closed) {
+        o->con_inst = NULL;
+        o->read_size = 0;
+        NCDModuleInst_Backend_Up(i);
+        return;
+    }
+    
+    // set connection
+    o->con_inst = con_inst;
+    
+    // register read operation in connection
+    con_inst->read_inst = o;
+    
+    // receive
+    size_t buf_size = NCDBufStore_BufSize(&con_inst->store);
+    int to_read = (buf_size > INT_MAX ? INT_MAX : buf_size);
+    StreamRecvInterface_Receiver_Recv(BConnection_RecvAsync_GetIf(&con_inst->connection), (uint8_t *)NCDBuf_Data(o->buf), to_read);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void read_func_die (void *vo)
+{
+    struct read_instance *o = vo;
+    
+    // if we're receiving, abort connection
+    if (o->con_inst) {
+        ASSERT(o->con_inst->state == CONNECTION_STATE_ESTABLISHED)
+        ASSERT(o->con_inst->read_inst == o)
+        connection_abort(o->con_inst);
+    }
+    
+    // release buffer
+    BRefTarget_Deref(NCDBuf_RefTarget(o->buf));
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int read_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct read_instance *o = vo;
+    ASSERT(!o->con_inst)
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewExternalString(mem, NCDBuf_Data(o->buf), o->read_size, NCDBuf_RefTarget(o->buf));
+        return 1;
+    }
+    
+    if (name == NCD_STRING_NOT_EOF) {
+        *out = ncd_make_boolean(mem, (o->read_size != 0), o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void write_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct write_instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef data_arg;
+    if (!NCDVal_ListRead(params->args, 1, &data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // get connection
+    struct connection *con_inst = params->method_user;
+    
+    // check connection state
+    if (con_inst->state != CONNECTION_STATE_ESTABLISHED) {
+        ModuleLog(i, BLOG_ERROR, "connection is not established");
+        goto fail0;
+    }
+    
+    // check if there's already a write in progress
+    if (con_inst->write_inst) {
+        ModuleLog(i, BLOG_ERROR, "write is already in progress");
+        goto fail0;
+    }
+    
+    // set send state
+    o->cstr = NCDVal_StringCstring(data_arg);
+    o->pos = 0;
+    
+    // if there's nothing to send, go up immediately
+    if (o->cstr.length == 0) {
+        o->con_inst = NULL;
+        NCDModuleInst_Backend_Up(i);
+        return;
+    }
+    
+    // set connection
+    o->con_inst = con_inst;
+    
+    // register write operation in connection
+    con_inst->write_inst = o;
+    
+    // send
+    size_t chunk_len;
+    const char *chunk_data = b_cstring_get(o->cstr, o->pos, o->cstr.length - o->pos, &chunk_len);
+    size_t to_send = (chunk_len > INT_MAX ? INT_MAX : chunk_len);
+    StreamPassInterface_Sender_Send(BConnection_SendAsync_GetIf(&con_inst->connection), (uint8_t *)chunk_data, to_send);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void write_func_die (void *vo)
+{
+    struct write_instance *o = vo;
+    
+    // if we're sending, abort connection
+    if (o->con_inst) {
+        ASSERT(o->con_inst->state == CONNECTION_STATE_ESTABLISHED)
+        ASSERT(o->con_inst->write_inst == o)
+        connection_abort(o->con_inst);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void close_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get connection
+    struct connection *con_inst = params->method_user;
+    
+    // check connection state
+    if (con_inst->state != CONNECTION_STATE_ESTABLISHED) {
+        ModuleLog(i, BLOG_ERROR, "connection is not established");
+        goto fail0;
+    }
+    
+    // abort
+    connection_abort(con_inst);
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void listen_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct listen_instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef address_arg;
+    NCDValRef client_template_arg;
+    NCDValRef args_arg;
+    NCDValRef options_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 3, &address_arg, &client_template_arg, &args_arg) &&
+        !NCDVal_ListRead(params->args, 4, &address_arg, &client_template_arg, &args_arg, &options_arg)
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(client_template_arg) || !NCDVal_IsList(args_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // parse options
+    if (!parse_options(i, options_arg, &o->read_buf_size)) {
+        goto fail0;
+    }
+    
+    // remember client template and arguments
+    o->client_template = client_template_arg;
+    o->client_template_args = args_arg;
+    
+    // set no error, not dying
+    o->have_error = 0;
+    o->dying = 0;
+    
+    // read address
+    struct BConnection_addr address;
+    if (!ncd_read_bconnection_addr(address_arg, &address)) {
+        ModuleLog(i, BLOG_ERROR, "wrong address");
+        goto error;
+    }
+    
+    // init listener
+    if (!BListener_InitGeneric(&o->listener, address, i->params->iparams->reactor, o, listen_listener_handler)) {
+        ModuleLog(i, BLOG_ERROR, "BListener_InitGeneric failed");
+        goto error;
+    }
+    
+    // init clients list
+    LinkedList0_Init(&o->clients_list);
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+error:
+    // go up with error
+    o->have_error = 1;
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void listen_func_die (void *vo)
+{
+    struct listen_instance *o = vo;
+    ASSERT(!o->dying)
+    
+    // free listener
+    if (!o->have_error) {
+        BListener_Free(&o->listener);
+    }
+    
+    // if we have no clients, die right away
+    if (o->have_error || LinkedList0_IsEmpty(&o->clients_list)) {
+        NCDModuleInst_Backend_Dead(o->i);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+    
+    // abort all clients and wait for them
+    for (LinkedList0Node *ln = LinkedList0_GetFirst(&o->clients_list); ln; ln = LinkedList0Node_Next(ln)) {
+        struct connection *con = UPPER_OBJECT(ln, struct connection, listen.clients_list_node);
+        ASSERT(con->type == CONNECTION_TYPE_LISTEN)
+        ASSERT(con->listen.listen_inst == o)
+        ASSERT(con->state == CONNECTION_STATE_ESTABLISHED || con->state == CONNECTION_STATE_ABORTED)
+        
+        if (con->state != CONNECTION_STATE_ABORTED) {
+            connection_abort(con);
+        }
+    }
+}
+
+static int listen_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct listen_instance *o = vo;
+    
+    if (name == NCD_STRING_IS_ERROR) {
+        *out = ncd_make_boolean(mem, o->have_error, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "sys.connect",
+        .base_type = "sys.socket",
+        .func_new2 = connect_func_new,
+        .func_die = connect_func_die,
+        .func_getvar2 = connect_func_getvar,
+        .alloc_size = sizeof(struct connection),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.socket::read",
+        .func_new2 = read_func_new,
+        .func_die = read_func_die,
+        .func_getvar2 = read_func_getvar,
+        .alloc_size = sizeof(struct read_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.socket::write",
+        .func_new2 = write_func_new,
+        .func_die = write_func_die,
+        .alloc_size = sizeof(struct write_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.socket::close",
+        .func_new2 = close_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.listen",
+        .func_new2 = listen_func_new,
+        .func_die = listen_func_die,
+        .func_getvar2 = listen_func_getvar,
+        .alloc_size = sizeof(struct listen_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_socket = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/spawn.c b/external/badvpn_dns/ncd/modules/spawn.c
new file mode 100644
index 0000000..4f68670
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/spawn.c
@@ -0,0 +1,410 @@
+/**
+ * @file spawn.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 which starts a process from a process template on initialization, and
+ * stops it on deinitialization.
+ * 
+ * Synopsis:
+ *   spawn(string template_name, list args)
+ * 
+ * Description:
+ *   On initialization, creates a new process from the template named
+ *   'template_name', with arguments 'args'. On deinitialization, initiates termination
+ *   of the process and waits for it to terminate. The process can access objects
+ *   as seen from 'spawn' via the _caller special object.
+ * 
+ * Synopsis:
+ *   spawn::join()
+ * 
+ * Description:
+ *   A join() on a spawn() is like a depend() on a provide() which is located at the
+ *   end of the spawned process.
+ * 
+ * Variables:
+ *   Exposes objects from the spawned process.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <structure/LinkedList0.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_spawn.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define STATE_WORKING 1
+#define STATE_UP 2
+#define STATE_WAITING 3
+#define STATE_WAITING_TERMINATING 4
+#define STATE_TERMINATING 5
+
+struct instance {
+    NCDModuleInst *i;
+    NCDModuleProcess process;
+    LinkedList0 clean_list;
+    LinkedList0 dirty_list;
+    int state;
+};
+
+struct join_instance {
+    NCDModuleInst *i;
+    struct instance *spawn;
+    LinkedList0Node list_node;
+    int is_dirty;
+};
+
+static void assert_dirty_state (struct instance *o);
+static void process_handler_event (NCDModuleProcess *process, int event);
+static int process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
+static int caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static void bring_joins_down (struct instance *o);
+static void continue_working (struct instance *o);
+static void continue_terminating (struct instance *o);
+static void instance_free (struct instance *o);
+
+static void assert_dirty_state (struct instance *o)
+{
+    ASSERT(!LinkedList0_IsEmpty(&o->dirty_list) == (o->state == STATE_WAITING || o->state == STATE_WAITING_TERMINATING))
+}
+
+static void process_handler_event (NCDModuleProcess *process, int event)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    assert_dirty_state(o);
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(o->state == STATE_WORKING)
+            ASSERT(LinkedList0_IsEmpty(&o->dirty_list))
+            
+            // set state up
+            o->state = STATE_UP;
+            
+            // bring joins up
+            for (LinkedList0Node *ln = LinkedList0_GetFirst(&o->clean_list); ln; ln = LinkedList0Node_Next(ln)) {
+                struct join_instance *join = UPPER_OBJECT(ln, struct join_instance, list_node);
+                ASSERT(join->spawn == o)
+                ASSERT(!join->is_dirty)
+                NCDModuleInst_Backend_Up(join->i);
+            }
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(o->state == STATE_UP)
+            ASSERT(LinkedList0_IsEmpty(&o->dirty_list))
+            
+            // bring joins down, moving them to the dirty list
+            bring_joins_down(o);
+            
+            // set state waiting
+            o->state = STATE_WAITING;
+            
+            // if we have no joins, continue immediately
+            if (LinkedList0_IsEmpty(&o->dirty_list)) {
+                continue_working(o);
+                return;
+            }
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(o->state == STATE_TERMINATING)
+            ASSERT(LinkedList0_IsEmpty(&o->dirty_list))
+            
+            // die finally
+            instance_free(o);
+            return;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static int process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    
+    if (name == NCD_STRING_CALLER) {
+        *out_object = NCDObject_Build(-1, o, NCDObject_no_getvar, caller_obj_func_getobj);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = NCDObject_DataPtr(obj);
+    
+    return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
+}
+
+static void bring_joins_down (struct instance *o)
+{
+    LinkedList0Node *ln;
+    while (ln = LinkedList0_GetFirst(&o->clean_list)) {
+        struct join_instance *join = UPPER_OBJECT(ln, struct join_instance, list_node);
+        ASSERT(join->spawn == o)
+        ASSERT(!join->is_dirty)
+        NCDModuleInst_Backend_Down(join->i);
+        LinkedList0_Remove(&o->clean_list, &join->list_node);
+        LinkedList0_Prepend(&o->dirty_list, &join->list_node);
+        join->is_dirty = 1;
+    }
+}
+
+static void continue_working (struct instance *o)
+{
+    ASSERT(o->state == STATE_WAITING)
+    ASSERT(LinkedList0_IsEmpty(&o->dirty_list))
+    
+    // continue process
+    NCDModuleProcess_Continue(&o->process);
+    
+    // set state working
+    o->state = STATE_WORKING;
+}
+
+static void continue_terminating (struct instance *o)
+{
+    ASSERT(o->state == STATE_WAITING_TERMINATING)
+    ASSERT(LinkedList0_IsEmpty(&o->dirty_list))
+    
+    // request process to terminate
+    NCDModuleProcess_Terminate(&o->process);
+    
+    // set state terminating
+    o->state = STATE_TERMINATING;
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef template_name_arg;
+    NCDValRef args_arg;
+    if (!NCDVal_ListRead(params->args, 2, &template_name_arg, &args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(template_name_arg) || !NCDVal_IsList(args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // signal up.
+    // Do it before creating the process so that the process starts initializing before our own process continues.
+    NCDModuleInst_Backend_Up(o->i);
+    
+    // create process
+    if (!NCDModuleProcess_InitValue(&o->process, o->i, template_name_arg, args_arg, process_handler_event)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        goto fail0;
+    }
+    
+    // set object resolution function
+    NCDModuleProcess_SetSpecialFuncs(&o->process, process_func_getspecialobj);
+    
+    // init lists
+    LinkedList0_Init(&o->clean_list);
+    LinkedList0_Init(&o->dirty_list);
+    
+    // set state working
+    o->state = STATE_WORKING;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+void instance_free (struct instance *o)
+{
+    ASSERT(LinkedList0_IsEmpty(&o->dirty_list))
+    
+    // unlink joins
+    LinkedList0Node *ln;
+    while (ln = LinkedList0_GetFirst(&o->clean_list)) {
+        struct join_instance *join = UPPER_OBJECT(ln, struct join_instance, list_node);
+        ASSERT(join->spawn == o)
+        ASSERT(!join->is_dirty)
+        LinkedList0_Remove(&o->clean_list, &join->list_node);
+        join->spawn = NULL;
+    }
+    
+    // free process
+    NCDModuleProcess_Free(&o->process);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(o->state != STATE_WAITING_TERMINATING)
+    ASSERT(o->state != STATE_TERMINATING)
+    assert_dirty_state(o);
+    
+    // bring joins down
+    if (o->state == STATE_UP) {
+        bring_joins_down(o);
+    }
+    
+    // set state waiting terminating
+    o->state = STATE_WAITING_TERMINATING;
+    
+    // start terminating now if possible
+    if (LinkedList0_IsEmpty(&o->dirty_list)) {
+        continue_terminating(o);
+        return;
+    }
+}
+
+static void join_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct join_instance *o = vo;
+    o->i = i;
+    
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    o->spawn = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    assert_dirty_state(o->spawn);
+    
+    LinkedList0_Prepend(&o->spawn->clean_list, &o->list_node);
+    o->is_dirty = 0;
+    
+    if (o->spawn->state == STATE_UP) {
+        NCDModuleInst_Backend_Up(i);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void join_func_die (void *vo)
+{
+    struct join_instance *o = vo;
+    
+    if (o->spawn) {
+        assert_dirty_state(o->spawn);
+        
+        // remove from list
+        if (o->is_dirty) {
+            LinkedList0_Remove(&o->spawn->dirty_list, &o->list_node);
+        } else {
+            LinkedList0_Remove(&o->spawn->clean_list, &o->list_node);
+        }
+        
+        if (o->is_dirty && LinkedList0_IsEmpty(&o->spawn->dirty_list)) {
+            ASSERT(o->spawn->state == STATE_WAITING || o->spawn->state == STATE_WAITING_TERMINATING)
+            
+            if (o->spawn->state == STATE_WAITING) {
+                continue_working(o->spawn);
+            } else {
+                continue_terminating(o->spawn);
+            }
+        }
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int join_func_getobj (void *vo, NCD_string_id_t name, NCDObject *out)
+{
+    struct join_instance *o = vo;
+    
+    if (!o->spawn) {
+        return 0;
+    }
+    
+    return NCDModuleProcess_GetObj(&o->spawn->process, name, out);
+}
+
+static void join_func_clean (void *vo)
+{
+    struct join_instance *o = vo;
+    
+    if (!(o->spawn && o->is_dirty)) {
+        return;
+    }
+    
+    assert_dirty_state(o->spawn);
+    ASSERT(o->spawn->state == STATE_WAITING || o->spawn->state == STATE_WAITING_TERMINATING)
+    
+    LinkedList0_Remove(&o->spawn->dirty_list, &o->list_node);
+    LinkedList0_Prepend(&o->spawn->clean_list, &o->list_node);
+    o->is_dirty = 0;
+    
+    if (LinkedList0_IsEmpty(&o->spawn->dirty_list)) {
+        if (o->spawn->state == STATE_WAITING) {
+            continue_working(o->spawn);
+        } else {
+            continue_terminating(o->spawn);
+        }
+    }
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "spawn",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "synchronous_process", // deprecated name
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "spawn::join",
+        .func_new2 = join_func_new,
+        .func_die = join_func_die,
+        .func_getobj = join_func_getobj,
+        .func_clean = join_func_clean,
+        .alloc_size = sizeof(struct join_instance),
+        .flags = NCDMODULE_FLAG_CAN_RESOLVE_WHEN_DOWN
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_spawn = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/strcmp.c b/external/badvpn_dns/ncd/modules/strcmp.c
new file mode 100644
index 0000000..e648719
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/strcmp.c
@@ -0,0 +1,107 @@
+/**
+ * @file strcmp.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * String comparison module.
+ * 
+ * Synopsis: strcmp(string str1, string str2)
+ * Variables:
+ *   string (empty) - "true" if str1 and str2 are equal, "false" if not
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_strcmp.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    int result;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef str1_arg;
+    NCDValRef str2_arg;
+    if (!NCDVal_ListRead(params->args, 2, &str1_arg, &str2_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(str1_arg) || !NCDVal_IsString(str2_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // compare
+    o->result = (NCDVal_Compare(str1_arg, str2_arg) == 0);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = ncd_make_boolean(mem, o->result, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "strcmp",
+        .func_new2 = func_new,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_strcmp = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/substr.c b/external/badvpn_dns/ncd/modules/substr.c
new file mode 100644
index 0000000..6a50b0c
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/substr.c
@@ -0,0 +1,167 @@
+/**
+ * @file substr.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   substr(string str, string start [, string max])
+ * 
+ * Description:
+ *   Extracts a substring from a string. The result is the longest substring which
+ *   starts at the offset 'start' bytes into 'str', and is no longer than 'max' bytes.
+ *   If 'max' is not provided, the result is the substring from the offset 'start' to
+ *   the end. In any case, 'start' must not be greater than the length of 'str'.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <limits.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_substr.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct substr_instance {
+    NCDModuleInst *i;
+    const char *data;
+    size_t length;
+    int is_external;
+    BRefTarget *external_ref_target;
+};
+
+static void substr_func_new_common (void *vo, NCDModuleInst *i, const char *data, size_t length, int is_external, BRefTarget *external_ref_target)
+{
+    struct substr_instance *o = vo;
+    o->i = i;
+    
+    o->data = data;
+    o->length = length;
+    o->is_external = is_external;
+    o->external_ref_target = external_ref_target;
+    
+    NCDModuleInst_Backend_Up(i);
+}
+
+static int substr_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct substr_instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        if (o->is_external) {
+            *out = NCDVal_NewExternalString(mem, o->data, o->length, o->external_ref_target);
+        } else {
+            *out = NCDVal_NewStringBin(mem, (const uint8_t *)o->data, o->length);
+        }
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void func_new_substr (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef str_arg;
+    NCDValRef start_arg;
+    NCDValRef max_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 2, &str_arg, &start_arg) &&
+        !NCDVal_ListRead(params->args, 3, &str_arg, &start_arg, &max_arg)
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(str_arg) || !NCDVal_IsString(start_arg) ||
+        (!NCDVal_IsInvalid(max_arg) && !NCDVal_IsString(max_arg))
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    uintmax_t start;
+    if (!ncd_read_uintmax(start_arg, &start) || start > SIZE_MAX) {
+        ModuleLog(i, BLOG_ERROR, "wrong size");
+        goto fail0;
+    }
+    
+    uintmax_t max = SIZE_MAX;
+    if (!NCDVal_IsInvalid(max_arg)) {
+        if (!ncd_read_uintmax(max_arg, &max) || max > SIZE_MAX) {
+            ModuleLog(i, BLOG_ERROR, "wrong max");
+            goto fail0;
+        }
+    }
+    
+    const char *str_data = NCDVal_StringData(str_arg);
+    size_t str_length = NCDVal_StringLength(str_arg);
+    
+    if (start > str_length) {
+        ModuleLog(i, BLOG_ERROR, "start is beyond the end of the string");
+        goto fail0;
+    }
+    
+    const char *sub_data = str_data + start;
+    size_t sub_length = str_length - start;
+    if (sub_length > max) {
+        sub_length = max;
+    }
+    
+    int is_external = 0;
+    BRefTarget *external_ref_target = NULL;
+    
+    if (NCDVal_IsExternalString(str_arg)) {
+        is_external = 1;
+        external_ref_target = NCDVal_ExternalStringTarget(str_arg);
+    }
+    else if (NCDVal_IsIdString(str_arg)) {
+        is_external = 1;
+    }
+    
+    substr_func_new_common(vo, i, sub_data, sub_length, is_external, external_ref_target);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "substr",
+        .func_new2 = func_new_substr,
+        .func_getvar2 = substr_func_getvar,
+        .alloc_size = sizeof(struct substr_instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_substr = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/sys_evdev.c b/external/badvpn_dns/ncd/modules/sys_evdev.c
new file mode 100644
index 0000000..d848a89
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/sys_evdev.c
@@ -0,0 +1,348 @@
+/**
+ * @file sys_evdev.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Linux event device module.
+ * 
+ * Synopsis: sys.evdev(string device)
+ * Description: reports input events from a Linux event device. Transitions up when an event is
+ *   detected, and goes down waiting for the next event when sys.evdev::nextevent() is called.
+ * Variables:
+ *   string type - symbolic event type (e.g. EV_KEY, EV_REL, EV_ABS), corresponding to
+ *     (struct input_event).type, or "unknown"
+ *   string value - event value (signed integer), equal to (struct input_event).value
+ *   string code_numeric - numeric event code (unsigned integer), equal to
+ *     (struct input_event).code
+ *   string code - symbolic event code (e.g. KEY_ESC. KEY_1, KEY_2, BTN_LEFT), corrresponding
+ *     to (struct input_event).code, or "unknown"
+ * 
+ * Synopsis: sys.evdev::nextevent()
+ * Description: makes the evdev module transition down in order to report the next event.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <linux/input.h>
+
+#include <misc/nonblocking.h>
+#include <misc/debug.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_sys_evdev.h>
+
+#include "linux_input_names.h"
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+struct instance {
+    NCDModuleInst *i;
+    int evdev_fd;
+    BFileDescriptor bfd;
+    int processing;
+    struct input_event event;
+};
+
+static void instance_free (struct instance *o, int is_error);
+
+enum {STRING_VALUE, STRING_CODE_NUMERIC, STRING_CODE};
+
+static const char *strings[] = {
+    "value", "code_numeric", "code", NULL
+};
+
+#define MAKE_LOOKUP_FUNC(_name_) \
+static const char * evdev_##_name_##_to_str (uint16_t type) \
+{ \
+    if (type >= (sizeof(_name_##_names) / sizeof(_name_##_names[0])) || !_name_##_names[type]) { \
+        return "unknown"; \
+    } \
+    return _name_##_names[type]; \
+}
+
+MAKE_LOOKUP_FUNC(type)
+MAKE_LOOKUP_FUNC(key)
+MAKE_LOOKUP_FUNC(rel)
+MAKE_LOOKUP_FUNC(abs)
+MAKE_LOOKUP_FUNC(sw)
+MAKE_LOOKUP_FUNC(msc)
+MAKE_LOOKUP_FUNC(led)
+MAKE_LOOKUP_FUNC(rep)
+MAKE_LOOKUP_FUNC(snd)
+MAKE_LOOKUP_FUNC(ffstatus)
+
+static void device_handler (struct instance *o, int events)
+{
+    if (o->processing) {
+        ModuleLog(o->i, BLOG_ERROR, "device error");
+        instance_free(o, 1);
+        return;
+    }
+    
+    int res = read(o->evdev_fd, &o->event, sizeof(o->event));
+    if (res < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "read failed");
+        instance_free(o, 1);
+        return;
+    }
+    if (res != sizeof(o->event)) {
+        ModuleLog(o->i, BLOG_ERROR, "read wrong");
+        instance_free(o, 1);
+        return;
+    }
+    
+    // stop reading
+    BReactor_SetFileDescriptorEvents(o->i->params->iparams->reactor, &o->bfd, 0);
+    
+    // set processing
+    o->processing = 1;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+}
+
+static void device_nextevent (struct instance *o)
+{
+    ASSERT(o->processing)
+    
+    // start reading
+    BReactor_SetFileDescriptorEvents(o->i->params->iparams->reactor, &o->bfd, BREACTOR_READ);
+    
+    // set not processing
+    o->processing = 0;
+    
+    // signal down
+    NCDModuleInst_Backend_Down(o->i);
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef device_arg;
+    if (!NCDVal_ListRead(params->args, 1, &device_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(device_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // null terminate device
+    NCDValNullTermString device_nts;
+    if (!NCDVal_StringNullTerminate(device_arg, &device_nts)) {
+        ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // open device
+    o->evdev_fd = open(device_nts.data, O_RDONLY);
+    NCDValNullTermString_Free(&device_nts);
+    if (o->evdev_fd < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "open failed");
+        goto fail0;
+    }
+    
+    // set non-blocking
+    if (!badvpn_set_nonblocking(o->evdev_fd)) {
+        ModuleLog(o->i, BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail1;
+    }
+    
+    // init BFileDescriptor
+    BFileDescriptor_Init(&o->bfd, o->evdev_fd, (BFileDescriptor_handler)device_handler, o);
+    if (!BReactor_AddFileDescriptor(o->i->params->iparams->reactor, &o->bfd)) {
+        ModuleLog(o->i, BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    BReactor_SetFileDescriptorEvents(o->i->params->iparams->reactor, &o->bfd, BREACTOR_READ);
+    
+    // set not processing
+    o->processing = 0;
+    return;
+    
+fail1:
+    if (close(o->evdev_fd) < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "close failed");
+    }
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+void instance_free (struct instance *o, int is_error)
+{
+    // free BFileDescriptor
+    BReactor_RemoveFileDescriptor(o->i->params->iparams->reactor, &o->bfd);
+    
+    // close device.
+    // Ignore close error which happens if the device is removed.
+    if (close(o->evdev_fd) < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "close failed");
+    }
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    instance_free(o, 0);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    ASSERT(o->processing)
+    
+    if (name == NCD_STRING_TYPE) {
+        *out = NCDVal_NewString(mem, evdev_type_to_str(o->event.type));
+        return 1;
+    }
+    
+    if (name == ModuleString(o->i, STRING_VALUE)) {
+        char str[50];
+        snprintf(str, sizeof(str), "%"PRIi32, o->event.value);
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    if (name == ModuleString(o->i, STRING_CODE_NUMERIC)) {
+        *out = ncd_make_uintmax(mem, o->event.code);
+        return 1;
+    }
+    
+    if (name == ModuleString(o->i, STRING_CODE)) {
+        const char *str = "unknown";
+        
+        #define MAKE_CASE(_evname_, _name_) \
+            case _evname_: \
+                str = evdev_##_name_##_to_str(o->event.code); \
+                break;
+        
+        switch (o->event.type) {
+            #ifdef EV_KEY
+            MAKE_CASE(EV_KEY, key)
+            #endif
+            #ifdef EV_REL
+            MAKE_CASE(EV_REL, rel)
+            #endif
+            #ifdef EV_ABS
+            MAKE_CASE(EV_ABS, abs)
+            #endif
+            #ifdef EV_SW
+            MAKE_CASE(EV_SW, sw)
+            #endif
+            #ifdef EV_MSC
+            MAKE_CASE(EV_MSC, msc)
+            #endif
+            #ifdef EV_LED
+            MAKE_CASE(EV_LED, led)
+            #endif
+            #ifdef EV_REP
+            MAKE_CASE(EV_REP, rep)
+            #endif
+            #ifdef EV_SND
+            MAKE_CASE(EV_SND, snd)
+            #endif
+            #ifdef EV_FF_STATUS
+            MAKE_CASE(EV_FF_STATUS, ffstatus)
+            #endif
+        }
+        
+        *out = NCDVal_NewString(mem, str);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void nextevent_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure we are currently reporting an event
+    if (!mo->processing) {
+        ModuleLog(i, BLOG_ERROR, "not reporting an event");
+        goto fail0;
+    }
+    
+    // signal up.
+    // Do it before finishing the event so our process does not advance any further if
+    // we would be killed the event provider going down.
+    NCDModuleInst_Backend_Up(i);
+    
+    // wait for next event
+    device_nextevent(mo);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "sys.evdev",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "sys.evdev::nextevent",
+        .func_new2 = nextevent_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_sys_evdev = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/sys_request_client.c b/external/badvpn_dns/ncd/modules/sys_request_client.c
new file mode 100644
index 0000000..fe4a54c
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/sys_request_client.c
@@ -0,0 +1,646 @@
+/**
+ * @file sys_request_client.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   sys.request_client(string connect_addr)
+ * 
+ * Description:
+ *   Connects to a request server (sys.request_server()).
+ *   Goes up when the connection, and dies with error when it is broken.
+ *   When requested to die, dies immediately, breaking the connection.
+ * 
+ *   The connect address should be in the same format as for the socket module.
+ *   In particular, it must be in one of the following forms:
+ *   - {"tcp", {"ipv4", ipv4_address, port_number}},
+ *   - {"tcp", {"ipv6", ipv6_address, port_number}},
+ *   - {"unix", socket_path}.
+ * 
+ * Synopsis:
+ *   sys.request_client::request(request_data, string reply_handler, string finished_handler, list args)
+ * 
+ * Description:
+ *   Sends a request to the server and dispatches replies to the provided handlers.
+ * 
+ *   The 'request_data' argument is sent as part of the request and is used by the server
+ *   to determine what to do with the request.
+ * 
+ *   When a reply is received, a new template process is created from 'reply_handler' to process the
+ *   reply. This process can access the reply data sent by the server using '_reply.data'.
+ *   Similarly, if the server finishes the request, a process is created from 'finished_handler'.
+ *   In both cases, the process can access objects as seen from the request statement via "_caller".
+ *   Termination of these processes is initiated immediately after they completes. They are created
+ *   synchronously - if a reply or a finished message arrives before a previous process is has
+ *   finished, it is queued. Once the finished message has been processed by 'finished_handler', no
+ *   more processes will be created.
+ * 
+ *   When the request statement is requested to terminate, it initiates termination of the current
+ *   handler process and waits for it to terminate (if any is running), and then dies.
+ *   If the corresponding client statement dies after being requested to die, or as a result of
+ *   an error, the request statement will not react to this. It will dispatch any pending messages
+ *   and then proceed to do nothing. In this case, if a finished message was not received, it will
+ *   not be dispatched.
+ * 
+ *   The request statement may however die at any time due to errors. In this case, it will
+ *   initiate termination of the current process and wait for it to terminate (if any) before dying.
+ * 
+ *   The request protocol and the server allow the client the abort requests at any time, and to
+ *   have the client notified only after the request has been completely aborted (i.e. the handler
+ *   process of sys.request_server() has deinitialized completely). This client implementation will
+ *   automatically request abortion of active requests when the request statement is requested
+ *   to die. However, the request statement will not wait for the abortion to finish before dying.
+ *   This means, for instance, that if you initialize a request statement right after having
+ *   deinitiazed it, the requests may overlap on the server side.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#include <misc/offset.h>
+#include <structure/LinkedList0.h>
+#include <structure/LinkedList1.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/NCDRequestClient.h>
+#include <ncd/extra/value_utils.h>
+#include <ncd/extra/address_utils.h>
+
+#include <generated/blog_channel_ncd_sys_request_client.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+#define CSTATE_CONNECTING 1
+#define CSTATE_CONNECTED 2
+
+#define RRSTATE_SENDING_REQUEST 1
+#define RRSTATE_READY 2
+#define RRSTATE_GONE_BAD 3
+#define RRSTATE_GONE_GOOD 4
+
+#define RPSTATE_NONE 1
+#define RPSTATE_WORKING 2
+#define RPSTATE_TERMINATING 3
+
+#define RDSTATE_NONE 1
+#define RDSTATE_DYING 2
+#define RDSTATE_DYING_ERROR 3
+
+struct instance {
+    NCDModuleInst *i;
+    NCDRequestClient client;
+    LinkedList0 requests_list;
+    int state;
+};
+
+struct request_instance {
+    NCDModuleInst *i;
+    NCDValRef reply_handler;
+    NCDValRef finished_handler;
+    NCDValRef args;
+    struct instance *client;
+    NCDRequestClientRequest request;
+    LinkedList0Node requests_list_node;
+    LinkedList1 replies_list;
+    NCDModuleProcess process;
+    int process_is_finished;
+    NCDValMem process_reply_mem;
+    NCDValRef process_reply_data;
+    int rstate;
+    int pstate;
+    int dstate;
+};
+
+struct reply {
+    LinkedList1Node replies_list_node;
+    NCDValMem mem;
+    NCDValRef val;
+};
+
+static void client_handler_error (struct instance *o);
+static void client_handler_connected (struct instance *o);
+static void request_handler_sent (struct request_instance *o);
+static void request_handler_reply (struct request_instance *o, NCDValMem reply_mem, NCDValRef reply_value);
+static void request_handler_finished (struct request_instance *o, int is_error);
+static void request_process_handler_event (NCDModuleProcess *process, int event);
+static int request_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
+static int request_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static int request_process_reply_obj_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out);
+static void request_gone (struct request_instance *o, int is_bad);
+static void request_terminate_process (struct request_instance *o);
+static void request_die (struct request_instance *o, int is_error);
+static void request_free_reply (struct request_instance *o, struct reply *r, int have_value);
+static int request_init_reply_process (struct request_instance *o, NCDValMem reply_mem, NCDValSafeRef reply_data);
+static int request_init_finished_process (struct request_instance *o);
+static void instance_free (struct instance *o, int with_error);
+static void request_instance_free (struct request_instance *o, int with_error);
+
+enum {STRING_REPLY, STRING_DATA};
+
+static const char *strings[] = {
+    "_reply", "data", NULL
+};
+
+static void client_handler_error (struct instance *o)
+{
+    ModuleLog(o->i, BLOG_ERROR, "client error");
+    
+    // free instance
+    instance_free(o, 1);
+}
+
+static void client_handler_connected (struct instance *o)
+{
+    ASSERT(o->state == CSTATE_CONNECTING)
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    
+    // set state connected
+    o->state = CSTATE_CONNECTED;
+}
+
+static void request_handler_sent (struct request_instance *o)
+{
+    ASSERT(o->rstate == RRSTATE_SENDING_REQUEST)
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    
+    // set rstate ready
+    o->rstate = RRSTATE_READY;
+}
+
+static void request_handler_reply (struct request_instance *o, NCDValMem reply_mem, NCDValRef reply_value)
+{
+    ASSERT(o->rstate == RRSTATE_READY)
+    
+    // queue reply if process is running
+    if (o->pstate != RPSTATE_NONE) {
+        struct reply *r = malloc(sizeof(*r));
+        if (!r) {
+            ModuleLog(o->i, BLOG_ERROR, "malloc failed");
+            goto fail1;
+        }
+        r->mem = reply_mem;
+        r->val = NCDVal_Moved(&r->mem, reply_value);
+        LinkedList1_Append(&o->replies_list, &r->replies_list_node);
+        return;
+    }
+    
+    // start reply process
+    if (!request_init_reply_process(o, reply_mem, NCDVal_ToSafe(reply_value))) {
+        goto fail1;
+    }
+    
+    return;
+    
+fail1:
+    NCDValMem_Free(&reply_mem);
+    request_die(o, 1);
+}
+
+static void request_handler_finished (struct request_instance *o, int is_error)
+{
+    ASSERT(o->rstate == RRSTATE_SENDING_REQUEST || o->rstate == RRSTATE_READY)
+    ASSERT(is_error || o->rstate == RRSTATE_READY)
+    
+    if (is_error) {
+        ModuleLog(o->i, BLOG_ERROR, "received error reply");
+        goto fail;
+    }
+    
+    // request gone good
+    request_gone(o, 0);
+    
+    // start process for reporting finished, if possible
+    if (o->pstate == RPSTATE_NONE) {
+        if (!request_init_finished_process(o)) {
+            goto fail;
+        }
+    }
+    
+    return;
+    
+fail:
+    request_die(o, 1);
+}
+
+static void request_process_handler_event (NCDModuleProcess *process, int event)
+{
+    struct request_instance *o = UPPER_OBJECT(process, struct request_instance, process);
+    ASSERT(o->pstate != RPSTATE_NONE)
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(o->pstate == RPSTATE_WORKING)
+            
+            // request process termination
+            request_terminate_process(o);
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(0)
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(o->pstate == RPSTATE_TERMINATING)
+            ASSERT(o->rstate != RRSTATE_SENDING_REQUEST)
+            
+            // free process
+            NCDModuleProcess_Free(&o->process);
+            
+            // free reply data
+            if (!o->process_is_finished) {
+                NCDValMem_Free(&o->process_reply_mem);
+            }
+            
+            // set process state none
+            o->pstate = RPSTATE_NONE;
+            
+            // die finally if requested
+            if (o->dstate == RDSTATE_DYING || o->dstate == RDSTATE_DYING_ERROR) {
+                request_instance_free(o, o->dstate == RDSTATE_DYING_ERROR);
+                return;
+            }
+            
+            if (!LinkedList1_IsEmpty(&o->replies_list)) {
+                // get first reply
+                struct reply *r = UPPER_OBJECT(LinkedList1_GetFirst(&o->replies_list), struct reply, replies_list_node);
+                
+                // start reply process
+                if (!request_init_reply_process(o, r->mem, NCDVal_ToSafe(r->val))) {
+                    goto fail;
+                }
+                
+                // free reply
+                request_free_reply(o, r, 0);
+            }
+            else if (o->rstate == RRSTATE_GONE_GOOD && !o->process_is_finished) {
+                // start process for reporting finished
+                if (!request_init_finished_process(o)) {
+                    goto fail;
+                }
+            }
+            
+            return;
+            
+        fail:
+            request_die(o, 1);
+        } break;
+    }
+}
+
+static int request_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct request_instance *o = UPPER_OBJECT(process, struct request_instance, process);
+    ASSERT(o->pstate != RPSTATE_NONE)
+    
+    if (name == NCD_STRING_CALLER) {
+        *out_object = NCDObject_Build(-1, o, NCDObject_no_getvar, request_process_caller_obj_func_getobj);
+        return 1;
+    }
+    
+    if (!o->process_is_finished && name == ModuleString(o->i, STRING_REPLY)) {
+        *out_object = NCDObject_Build(-1, o, request_process_reply_obj_func_getvar, NCDObject_no_getobj);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int request_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct request_instance *o = NCDObject_DataPtr(obj);
+    ASSERT(o->pstate != RPSTATE_NONE)
+    
+    return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
+}
+
+static int request_process_reply_obj_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct request_instance *o = NCDObject_DataPtr(obj);
+    ASSERT(o->pstate != RPSTATE_NONE)
+    ASSERT(!o->process_is_finished)
+    
+    if (name == ModuleString(o->i, STRING_DATA)) {
+        *out = NCDVal_NewCopy(mem, o->process_reply_data);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void request_gone (struct request_instance *o, int is_bad)
+{
+    ASSERT(o->rstate != RRSTATE_GONE_BAD)
+    ASSERT(o->rstate != RRSTATE_GONE_GOOD)
+    
+    // remove from requests list
+    LinkedList0_Remove(&o->client->requests_list, &o->requests_list_node);
+    
+    // free request
+    NCDRequestClientRequest_Free(&o->request);
+    
+    // set state over
+    o->rstate = (is_bad ? RRSTATE_GONE_BAD : RRSTATE_GONE_GOOD);
+}
+
+static void request_terminate_process (struct request_instance *o)
+{
+    ASSERT(o->pstate == RPSTATE_WORKING)
+    
+    // request process termination
+    NCDModuleProcess_Terminate(&o->process);
+    
+    // set process state terminating
+    o->pstate = RPSTATE_TERMINATING;
+}
+
+static void request_die (struct request_instance *o, int is_error)
+{
+    // if we have no process, die right away, else we have to wait for process to terminate
+    if (o->pstate == RPSTATE_NONE) {
+        request_instance_free(o, is_error);
+        return;
+    }
+    
+    // release request
+    if (o->rstate != RRSTATE_GONE_BAD && o->rstate != RRSTATE_GONE_GOOD) {
+        request_gone(o, 1);
+    }
+    
+    // initiate process termination, if needed
+    if (o->pstate != RPSTATE_TERMINATING) {
+        request_terminate_process(o);
+    }
+    
+    // set dstate
+    o->dstate = (is_error ? RDSTATE_DYING_ERROR : RDSTATE_DYING);
+}
+
+static void request_free_reply (struct request_instance *o, struct reply *r, int have_value)
+{
+    // remove from replies list
+    LinkedList1_Remove(&o->replies_list, &r->replies_list_node);
+    
+    // free value
+    if (have_value) {
+        NCDValMem_Free(&r->mem);
+    }
+    
+    // free structure
+    free(r);
+}
+
+static int request_init_reply_process (struct request_instance *o, NCDValMem reply_mem, NCDValSafeRef reply_data)
+{
+    ASSERT(o->pstate == RPSTATE_NONE)
+    
+    // set parameters
+    o->process_is_finished = 0;
+    o->process_reply_mem = reply_mem;
+    o->process_reply_data = NCDVal_FromSafe(&o->process_reply_mem, reply_data);
+    
+    // init process
+    if (!NCDModuleProcess_InitValue(&o->process, o->i, o->reply_handler, o->args, request_process_handler_event)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        goto fail0;
+    }
+    
+    // set special objects function
+    NCDModuleProcess_SetSpecialFuncs(&o->process, request_process_func_getspecialobj);
+    
+    // set process state working
+    o->pstate = RPSTATE_WORKING;
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+static int request_init_finished_process (struct request_instance *o)
+{
+    ASSERT(o->pstate == RPSTATE_NONE)
+    
+    // set parameters
+    o->process_is_finished = 1;
+    
+    // init process
+    if (!NCDModuleProcess_InitValue(&o->process, o->i, o->finished_handler, o->args, request_process_handler_event)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        goto fail0;
+    }
+    
+    // set special objects function
+    NCDModuleProcess_SetSpecialFuncs(&o->process, request_process_func_getspecialobj);
+    
+    // set process state working
+    o->pstate = RPSTATE_WORKING;
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef connect_addr_arg;
+    if (!NCDVal_ListRead(params->args, 1, &connect_addr_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // read connect address
+    struct BConnection_addr addr;
+    if (!ncd_read_bconnection_addr(connect_addr_arg, &addr)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong connect address");
+        goto fail0;
+    }
+    
+    // init client
+    if (!NCDRequestClient_Init(&o->client, addr, i->params->iparams->reactor, o,
+        (NCDRequestClient_handler_error)client_handler_error,
+        (NCDRequestClient_handler_connected)client_handler_connected)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDRequestClient_Init failed");
+        goto fail0;
+    }
+    
+    // init requests list
+    LinkedList0_Init(&o->requests_list);
+    
+    // set state connecting
+    o->state = CSTATE_CONNECTING;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o, int with_error)
+{
+    // deal with requests
+    LinkedList0Node *ln;
+    while (ln = LinkedList0_GetFirst(&o->requests_list)) {
+        struct request_instance *req = UPPER_OBJECT(ln, struct request_instance, requests_list_node);
+        request_gone(req, 1);
+    }
+    
+    // free client
+    NCDRequestClient_Free(&o->client);
+    
+    if (with_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    instance_free(o, 0);
+}
+
+static void request_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct request_instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef request_data_arg;
+    NCDValRef reply_handler_arg;
+    NCDValRef finished_handler_arg;
+    NCDValRef args_arg;
+    if (!NCDVal_ListRead(params->args, 4, &request_data_arg, &reply_handler_arg, &finished_handler_arg, &args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(reply_handler_arg) || !NCDVal_IsString(finished_handler_arg) ||
+        !NCDVal_IsList(args_arg)
+    ) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    o->reply_handler = reply_handler_arg;
+    o->finished_handler = finished_handler_arg;
+    o->args = args_arg;
+    
+    // get client
+    struct instance *client = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    o->client = client;
+    
+    // check client state
+    if (client->state != CSTATE_CONNECTED) {
+        ModuleLog(o->i, BLOG_ERROR, "client is not connected");
+        goto fail0;
+    }
+    
+    // init request
+    if (!NCDRequestClientRequest_Init(&o->request, &client->client, request_data_arg, o,
+        (NCDRequestClientRequest_handler_sent)request_handler_sent,
+        (NCDRequestClientRequest_handler_reply)request_handler_reply,
+        (NCDRequestClientRequest_handler_finished)request_handler_finished)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDRequestClientRequest_Init failed");
+        goto fail0;
+    }
+    
+    // add to requests list
+    LinkedList0_Prepend(&client->requests_list, &o->requests_list_node);
+    
+    // init replies list
+    LinkedList1_Init(&o->replies_list);
+    
+    // set state
+    o->rstate = RRSTATE_SENDING_REQUEST;
+    o->pstate = RPSTATE_NONE;
+    o->dstate = RDSTATE_NONE;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void request_instance_free (struct request_instance *o, int with_error)
+{
+    ASSERT(o->pstate == RPSTATE_NONE)
+    
+    // free replies
+    LinkedList1Node *ln;
+    while (ln = LinkedList1_GetFirst(&o->replies_list)) {
+        struct reply *r = UPPER_OBJECT(ln, struct reply, replies_list_node);
+        request_free_reply(o, r, 1);
+    }
+    
+    // release request
+    if (o->rstate != RRSTATE_GONE_BAD && o->rstate != RRSTATE_GONE_GOOD) {
+        request_gone(o, 1);
+    }
+    
+    if (with_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void request_func_die (void *vo)
+{
+    struct request_instance *o = vo;
+    
+    request_die(o, 0);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "sys.request_client",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "sys.request_client::request",
+        .func_new2 = request_func_new,
+        .func_die = request_func_die,
+        .alloc_size = sizeof(struct request_instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_sys_request_client = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/sys_request_server.c b/external/badvpn_dns/ncd/modules/sys_request_server.c
new file mode 100644
index 0000000..31bc431
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/sys_request_server.c
@@ -0,0 +1,792 @@
+/**
+ * @file sys_request_server.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 simple a IPC interface for NCD to talk to other processes over a Unix socket.
+ * 
+ * Synopsis:
+ *   sys.request_server(listen_address, string request_handler_template, list args)
+ * 
+ * Description:
+ *   Initializes a request server on the given socket path. Requests are served by
+ *   starting a template process for every request. Multiple such processes may
+ *   exist simultaneously. Termination of these processess may be initiated at
+ *   any time if the request server no longer needs the request in question served.
+ *   The payload of a request is a value, and can be accessed as _request.data
+ *   from within the handler process. Replies to the request can be sent using
+ *   _request->reply(data); replies are values too. Finally, _request->finish()
+ *   should be called to indicate that no further replies will be sent. Calling
+ *   finish() will immediately initiate termination of the handler process.
+ *   Requests can be sent to NCD using the badvpn-ncd-request program.
+ * 
+ *   The listen address should be in the same format as for the socket module.
+ *   In particular, it must be in one of the following forms:
+ *   - {"tcp", {"ipv4", ipv4_address, port_number}},
+ *   - {"tcp", {"ipv6", ipv6_address, port_number}},
+ *   - {"unix", socket_path}.
+ * 
+ * Predefined objects and variables in request_handler_template:
+ *   _caller - provides access to objects as seen from the sys.request_server()
+ *     command
+ *   _request.data - the request payload as sent by the client
+ *   string _request.client_addr - the address of the client. The form is
+ *     like the second part of the sys.request_server() address format, e.g.
+ *     {"ipv4", "1.2.3.4", "4000"}.
+ * 
+ * Synopsis:
+ *   sys.request_server.request::reply(reply_data)
+ * 
+ * Synopsis:
+ *   sys.request_server.request::finish()
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <protocol/packetproto.h>
+#include <protocol/requestproto.h>
+#include <structure/LinkedList0.h>
+#include <system/BConnection.h>
+#include <system/BConnectionGeneric.h>
+#include <system/BAddr.h>
+#include <flow/PacketProtoDecoder.h>
+#include <flow/PacketStreamSender.h>
+#include <flow/PacketPassFifoQueue.h>
+#include <ncd/NCDValParser.h>
+#include <ncd/NCDValGenerator.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+#include <ncd/extra/address_utils.h>
+
+#include <generated/blog_channel_ncd_sys_request_server.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+#define SEND_PAYLOAD_MTU 32768
+#define RECV_PAYLOAD_MTU 32768
+
+#define SEND_MTU (SEND_PAYLOAD_MTU + sizeof(struct requestproto_header))
+#define RECV_MTU (RECV_PAYLOAD_MTU + sizeof(struct requestproto_header))
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValRef request_handler_template;
+    NCDValRef args;
+    BListener listener;
+    LinkedList0 connections_list;
+    int dying;
+};
+
+#define CONNECTION_STATE_RUNNING 1
+#define CONNECTION_STATE_TERMINATING 2
+
+struct reply;
+
+struct connection {
+    struct instance *inst;
+    LinkedList0Node connections_list_node;
+    BConnection con;
+    BAddr addr;
+    PacketProtoDecoder recv_decoder;
+    PacketPassInterface recv_if;
+    PacketPassFifoQueue send_queue;
+    PacketStreamSender send_pss;
+    LinkedList0 requests_list;
+    LinkedList0 replies_list;
+    int state;
+};
+
+struct request {
+    struct connection *con;
+    uint32_t request_id;
+    LinkedList0Node requests_list_node;
+    NCDValMem request_data_mem;
+    NCDValRef request_data;
+    struct reply *end_reply;
+    NCDModuleProcess process;
+    int terminating;
+    int got_finished;
+};
+
+struct reply {
+    struct connection *con;
+    LinkedList0Node replies_list_node;
+    PacketPassFifoQueueFlow send_qflow;
+    PacketPassInterface *send_qflow_if;
+    uint8_t *send_buf;
+};
+
+static void listener_handler (struct instance *o);
+static void connection_free (struct connection *c);
+static void connection_free_link (struct connection *c);
+static void connection_terminate (struct connection *c);
+static void connection_con_handler (struct connection *c, int event);
+static void connection_recv_decoder_handler_error (struct connection *c);
+static void connection_recv_if_handler_send (struct connection *c, uint8_t *data, int data_len);
+static int request_init (struct connection *c, uint32_t request_id, const uint8_t *data, int data_len);
+static void request_free (struct request *r);
+static struct request * find_request (struct connection *c, uint32_t request_id);
+static void request_process_handler_event (NCDModuleProcess *process, int event);
+static int request_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
+static int request_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static int request_process_request_obj_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out_value);
+static void request_terminate (struct request *r);
+static struct reply * reply_init (struct connection *c, uint32_t request_id, NCDValRef reply_data);
+static void reply_start (struct reply *r, uint32_t type);
+static void reply_free (struct reply *r);
+static void reply_send_qflow_if_handler_done (struct reply *r);
+static void instance_free (struct instance *o);
+
+enum {STRING_REQUEST, STRING_DATA, STRING_CLIENT_ADDR,
+      STRING_SYS_REQUEST_SERVER_REQUEST};
+
+static const char *strings[] = {
+    "_request", "data", "client_addr",
+    "sys.request_server.request", NULL
+};
+
+static void listener_handler (struct instance *o)
+{
+    ASSERT(!o->dying)
+    
+    BReactor *reactor = o->i->params->iparams->reactor;
+    BPendingGroup *pg = BReactor_PendingGroup(reactor);
+    
+    struct connection *c = malloc(sizeof(*c));
+    if (!c) {
+        ModuleLog(o->i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    c->inst = o;
+    
+    LinkedList0_Prepend(&o->connections_list, &c->connections_list_node);
+    
+    if (!BConnection_Init(&c->con, BConnection_source_listener(&o->listener, &c->addr), reactor, c, (BConnection_handler)connection_con_handler)) {
+        ModuleLog(o->i, BLOG_ERROR, "BConnection_Init failed");
+        goto fail1;
+    }
+    
+    BConnection_SendAsync_Init(&c->con);
+    BConnection_RecvAsync_Init(&c->con);
+    StreamPassInterface *con_send_if = BConnection_SendAsync_GetIf(&c->con);
+    StreamRecvInterface *con_recv_if = BConnection_RecvAsync_GetIf(&c->con);
+    
+    PacketPassInterface_Init(&c->recv_if, RECV_MTU, (PacketPassInterface_handler_send)connection_recv_if_handler_send, c, pg);
+    
+    if (!PacketProtoDecoder_Init(&c->recv_decoder, con_recv_if, &c->recv_if, pg, c, (PacketProtoDecoder_handler_error)connection_recv_decoder_handler_error)) {
+        ModuleLog(o->i, BLOG_ERROR, "PacketProtoDecoder_Init failed");
+        goto fail2;
+    }
+    
+    PacketStreamSender_Init(&c->send_pss, con_send_if, PACKETPROTO_ENCLEN(SEND_MTU), pg);
+    
+    PacketPassFifoQueue_Init(&c->send_queue, PacketStreamSender_GetInput(&c->send_pss), pg);
+    
+    LinkedList0_Init(&c->requests_list);
+    
+    LinkedList0_Init(&c->replies_list);
+    
+    c->state = CONNECTION_STATE_RUNNING;
+    
+    ModuleLog(o->i, BLOG_INFO, "connection initialized");
+    return;
+    
+fail2:
+    PacketPassInterface_Free(&c->recv_if);
+    BConnection_RecvAsync_Free(&c->con);
+    BConnection_SendAsync_Free(&c->con);
+    BConnection_Free(&c->con);
+fail1:
+    LinkedList0_Remove(&o->connections_list, &c->connections_list_node);
+    free(c);
+fail0:
+    return;
+}
+
+static void connection_free (struct connection *c)
+{
+    struct instance *o = c->inst;
+    ASSERT(c->state == CONNECTION_STATE_TERMINATING)
+    ASSERT(LinkedList0_IsEmpty(&c->requests_list))
+    ASSERT(LinkedList0_IsEmpty(&c->replies_list))
+    
+    LinkedList0_Remove(&o->connections_list, &c->connections_list_node);
+    free(c);
+}
+
+static void connection_free_link (struct connection *c)
+{
+    PacketPassFifoQueue_PrepareFree(&c->send_queue);
+    
+    LinkedList0Node *ln;
+    while (ln = LinkedList0_GetFirst(&c->replies_list)) {
+        struct reply *r = UPPER_OBJECT(ln, struct reply, replies_list_node);
+        ASSERT(r->con == c)
+        reply_free(r);
+    }
+    
+    PacketPassFifoQueue_Free(&c->send_queue);
+    PacketStreamSender_Free(&c->send_pss);
+    PacketProtoDecoder_Free(&c->recv_decoder);
+    PacketPassInterface_Free(&c->recv_if);
+    BConnection_RecvAsync_Free(&c->con);
+    BConnection_SendAsync_Free(&c->con);
+    BConnection_Free(&c->con);
+}
+
+static void connection_terminate (struct connection *c)
+{
+    ASSERT(c->state == CONNECTION_STATE_RUNNING)
+    
+    for (LinkedList0Node *ln = LinkedList0_GetFirst(&c->requests_list); ln; ln = LinkedList0Node_Next(ln)) {
+        struct request *r = UPPER_OBJECT(ln, struct request, requests_list_node);
+        
+        if (!r->terminating) {
+            request_terminate(r);
+        }
+    }
+    
+    connection_free_link(c);
+    
+    c->state = CONNECTION_STATE_TERMINATING;
+    
+    if (LinkedList0_IsEmpty(&c->requests_list)) {
+        connection_free(c);
+        return;
+    }
+}
+
+static void connection_con_handler (struct connection *c, int event)
+{
+    struct instance *o = c->inst;
+    ASSERT(c->state == CONNECTION_STATE_RUNNING)
+    
+    ModuleLog(o->i, BLOG_INFO, "connection closed");
+    
+    connection_terminate(c);
+}
+
+static void connection_recv_decoder_handler_error (struct connection *c)
+{
+    struct instance *o = c->inst;
+    ASSERT(c->state == CONNECTION_STATE_RUNNING)
+    
+    ModuleLog(o->i, BLOG_ERROR, "decoder error");
+    
+    connection_terminate(c);
+}
+
+static void connection_recv_if_handler_send (struct connection *c, uint8_t *data, int data_len)
+{
+    struct instance *o = c->inst;
+    ASSERT(c->state == CONNECTION_STATE_RUNNING)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= RECV_MTU)
+    
+    PacketPassInterface_Done(&c->recv_if);
+    
+    if (data_len < sizeof(struct requestproto_header)) {
+        ModuleLog(o->i, BLOG_ERROR, "missing requestproto header");
+        goto fail;
+    }
+    
+    struct requestproto_header header;
+    memcpy(&header, data, sizeof(header));
+    uint32_t request_id = ltoh32(header.request_id);
+    uint32_t type = ltoh32(header.type);
+    
+    switch (type) {
+        case REQUESTPROTO_TYPE_CLIENT_REQUEST: {
+            if (find_request(c, request_id)) {
+                ModuleLog(o->i, BLOG_ERROR, "request with the same ID already exists");
+                goto fail;
+            }
+            
+            if (!request_init(c, request_id, data + sizeof(header), data_len - sizeof(header))) {
+                goto fail;
+            }
+        } break;
+        
+        case REQUESTPROTO_TYPE_CLIENT_ABORT: {
+            struct request *r = find_request(c, request_id);
+            if (!r) {
+                // this is expected if we finish before we get the abort
+                return;
+            }
+            
+            if (!r->terminating) {
+                request_terminate(r);
+            }
+        } break;
+        
+        default:
+            ModuleLog(o->i, BLOG_ERROR, "invalid requestproto type");
+            goto fail;
+    }
+    
+    return;
+    
+fail:
+    connection_terminate(c);
+}
+
+static int request_init (struct connection *c, uint32_t request_id, const uint8_t *data, int data_len)
+{
+    struct instance *o = c->inst;
+    ASSERT(c->state == CONNECTION_STATE_RUNNING)
+    ASSERT(!find_request(c, request_id))
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= RECV_PAYLOAD_MTU)
+    
+    struct request *r = malloc(sizeof(*r));
+    if (!r) {
+        ModuleLog(o->i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    r->con = c;
+    r->request_id = request_id;
+    
+    LinkedList0_Prepend(&c->requests_list, &r->requests_list_node);
+    
+    NCDValMem_Init(&r->request_data_mem);
+    
+    if (!NCDValParser_Parse((const char *)data, data_len, &r->request_data_mem, &r->request_data)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDValParser_Parse failed");
+        goto fail1;
+    }
+    
+    if (!(r->end_reply = reply_init(c, request_id, NCDVal_NewInvalid()))) {
+        goto fail1;
+    }
+    
+    if (!NCDModuleProcess_InitValue(&r->process, o->i, o->request_handler_template, o->args, request_process_handler_event)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        goto fail3;
+    }
+    
+    NCDModuleProcess_SetSpecialFuncs(&r->process, request_process_func_getspecialobj);
+    
+    r->terminating = 0;
+    r->got_finished = 0;
+    
+    ModuleLog(o->i, BLOG_INFO, "request initialized");
+    return 1;
+    
+fail3:
+    reply_free(r->end_reply);
+fail1:
+    NCDValMem_Free(&r->request_data_mem);
+    LinkedList0_Remove(&c->requests_list, &r->requests_list_node);
+    free(r);
+fail0:
+    return 0;
+}
+
+static void request_free (struct request *r)
+{
+    struct connection *c = r->con;
+    NCDModuleProcess_AssertFree(&r->process);
+    
+    if (c->state != CONNECTION_STATE_TERMINATING) {
+        uint32_t type = r->got_finished ? REQUESTPROTO_TYPE_SERVER_FINISHED : REQUESTPROTO_TYPE_SERVER_ERROR;
+        reply_start(r->end_reply, type);
+    }
+    
+    NCDModuleProcess_Free(&r->process);
+    NCDValMem_Free(&r->request_data_mem);
+    LinkedList0_Remove(&c->requests_list, &r->requests_list_node);
+    free(r);
+}
+
+static struct request * find_request (struct connection *c, uint32_t request_id)
+{
+    for (LinkedList0Node *ln = LinkedList0_GetFirst(&c->requests_list); ln; ln = LinkedList0Node_Next(ln)) {
+        struct request *r = UPPER_OBJECT(ln, struct request, requests_list_node);
+        if (!r->terminating && r->request_id == request_id) {
+            return r;
+        }
+    }
+    
+    return NULL;
+}
+
+static void request_process_handler_event (NCDModuleProcess *process, int event)
+{
+    struct request *r = UPPER_OBJECT(process, struct request, process);
+    struct connection *c = r->con;
+    struct instance *o = c->inst;
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(!r->terminating)
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(!r->terminating)
+            
+            NCDModuleProcess_Continue(&r->process);
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(r->terminating)
+            
+            request_free(r);
+            
+            if (c->state == CONNECTION_STATE_TERMINATING && LinkedList0_IsEmpty(&c->requests_list)) {
+                connection_free(c);
+                
+                if (o->dying && LinkedList0_IsEmpty(&o->connections_list)) {
+                    instance_free(o);
+                    return;
+                }
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static int request_process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct request *r = UPPER_OBJECT(process, struct request, process);
+    
+    if (name == NCD_STRING_CALLER) {
+        *out_object = NCDObject_Build(-1, r, NCDObject_no_getvar, request_process_caller_obj_func_getobj);
+        return 1;
+    }
+    
+    if (name == ModuleString(r->con->inst->i, STRING_REQUEST)) {
+        *out_object = NCDObject_Build(ModuleString(r->con->inst->i, STRING_SYS_REQUEST_SERVER_REQUEST), r, request_process_request_obj_func_getvar, NCDObject_no_getobj);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int request_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct request *r = NCDObject_DataPtr(obj);
+    
+    return NCDModuleInst_Backend_GetObj(r->con->inst->i, name, out_object);
+}
+
+static int request_process_request_obj_func_getvar (const NCDObject *obj, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct request *r = NCDObject_DataPtr(obj);
+    
+    if (name == ModuleString(r->con->inst->i, STRING_DATA)) {
+        *out = NCDVal_NewCopy(mem, r->request_data);
+        return 1;
+    }
+    
+    if (name == ModuleString(r->con->inst->i, STRING_CLIENT_ADDR)) {
+        *out = ncd_make_baddr(r->con->addr, mem);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void request_terminate (struct request *r)
+{
+    ASSERT(!r->terminating)
+    
+    NCDModuleProcess_Terminate(&r->process);
+    
+    r->terminating = 1;
+}
+
+static struct reply * reply_init (struct connection *c, uint32_t request_id, NCDValRef reply_data)
+{
+    struct instance *o = c->inst;
+    ASSERT(c->state == CONNECTION_STATE_RUNNING)
+    NCDVal_Assert(reply_data);
+    
+    struct reply *r = malloc(sizeof(*r));
+    if (!r) {
+        ModuleLog(o->i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    r->con = c;
+    
+    LinkedList0_Prepend(&c->replies_list, &r->replies_list_node);
+    
+    PacketPassFifoQueueFlow_Init(&r->send_qflow, &c->send_queue);
+    
+    r->send_qflow_if = PacketPassFifoQueueFlow_GetInput(&r->send_qflow);
+    PacketPassInterface_Sender_Init(r->send_qflow_if, (PacketPassInterface_handler_done)reply_send_qflow_if_handler_done, r);
+    
+    ExpString str;
+    if (!ExpString_Init(&str)) {
+        ModuleLog(o->i, BLOG_ERROR, "ExpString_Init failed");
+        goto fail1;
+    }
+    
+    if (!ExpString_AppendZeros(&str, sizeof(struct packetproto_header) + sizeof(struct requestproto_header))) {
+        ModuleLog(o->i, BLOG_ERROR, "ExpString_AppendZeros failed");
+        goto fail2;
+    }
+    
+    if (!NCDVal_IsInvalid(reply_data) && !NCDValGenerator_AppendGenerate(reply_data, &str)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDValGenerator_AppendGenerate failed");
+        goto fail2;
+    }
+    
+    size_t len = ExpString_Length(&str);
+    if (len > INT_MAX || len > PACKETPROTO_ENCLEN(SEND_MTU) || len - sizeof(struct packetproto_header) > UINT16_MAX) {
+        ModuleLog(o->i, BLOG_ERROR, "reply is too long");
+        goto fail2;
+    }
+    
+    r->send_buf = (uint8_t *)ExpString_Get(&str);
+    
+    struct packetproto_header pp;
+    pp.len = htol16(len - sizeof(pp));
+    
+    struct requestproto_header rp;
+    rp.request_id = htol32(request_id);
+    
+    memcpy(r->send_buf, &pp, sizeof(pp));
+    memcpy(r->send_buf + sizeof(pp), &rp, sizeof(rp));
+    
+    return r;
+    
+fail2:
+    ExpString_Free(&str);
+fail1:
+    PacketPassFifoQueueFlow_Free(&r->send_qflow);
+    LinkedList0_Remove(&c->replies_list, &r->replies_list_node);
+    free(r);
+fail0:
+    return NULL;
+}
+
+static void reply_start (struct reply *r, uint32_t type)
+{
+    struct requestproto_header rp;
+    memcpy(&rp, r->send_buf + sizeof(struct packetproto_header), sizeof(rp));
+    rp.type = htol32(type);
+    memcpy(r->send_buf + sizeof(struct packetproto_header), &rp, sizeof(rp));
+    
+    struct packetproto_header pp;
+    memcpy(&pp, r->send_buf, sizeof(pp));
+    
+    int len = ltoh16(pp.len) + sizeof(struct packetproto_header);
+    
+    PacketPassInterface_Sender_Send(r->send_qflow_if, r->send_buf, len);
+}
+
+static void reply_free (struct reply *r)
+{
+    struct connection *c = r->con;
+    PacketPassFifoQueueFlow_AssertFree(&r->send_qflow);
+    
+    free(r->send_buf);
+    PacketPassFifoQueueFlow_Free(&r->send_qflow);
+    LinkedList0_Remove(&c->replies_list, &r->replies_list_node);
+    free(r);
+}
+
+static void reply_send_qflow_if_handler_done (struct reply *r)
+{
+    reply_free(r);
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef listen_addr_arg;
+    NCDValRef request_handler_template_arg;
+    NCDValRef args_arg;
+    if (!NCDVal_ListRead(params->args, 3, &listen_addr_arg, &request_handler_template_arg, &args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(request_handler_template_arg) || !NCDVal_IsList(args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    o->request_handler_template = request_handler_template_arg;
+    o->args = args_arg;
+    
+    // read listen address
+    struct BConnection_addr addr;
+    if (!ncd_read_bconnection_addr(listen_addr_arg, &addr)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong listen address");
+        goto fail0;
+    }
+    
+    // init listener
+    if (!BListener_InitGeneric(&o->listener, addr, i->params->iparams->reactor, o, (BListener_handler)listener_handler)) {
+        ModuleLog(o->i, BLOG_ERROR, "BListener_InitGeneric failed");
+        goto fail0;
+    }
+    
+    // init connections list
+    LinkedList0_Init(&o->connections_list);
+    
+    // set not dying
+    o->dying = 0;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o)
+{
+    ASSERT(o->dying)
+    ASSERT(LinkedList0_IsEmpty(&o->connections_list))
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(!o->dying)
+    
+    // free listener
+    BListener_Free(&o->listener);
+    
+    // terminate connections
+    LinkedList0Node *next_ln;
+    for (LinkedList0Node *ln = LinkedList0_GetFirst(&o->connections_list); ln && (next_ln = LinkedList0Node_Next(ln)), ln; ln = next_ln) { 
+        struct connection *c = UPPER_OBJECT(ln, struct connection, connections_list_node);
+        ASSERT(c->inst == o)
+        
+        if (c->state != CONNECTION_STATE_TERMINATING) {
+            connection_terminate(c);
+        }
+    }
+    
+    // set dying
+    o->dying = 1;
+    
+    // if no connections, die right away
+    if (LinkedList0_IsEmpty(&o->connections_list)) {
+        instance_free(o);
+        return;
+    }
+}
+
+static void reply_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef reply_data;
+    if (!NCDVal_ListRead(params->args, 1, &reply_data)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail;
+    }
+    
+    NCDModuleInst_Backend_Up(i);
+    
+    struct request *r = params->method_user;
+    struct connection *c = r->con;
+    
+    if (r->terminating) {
+        ModuleLog(i, BLOG_ERROR, "request is dying, cannot submit reply");
+        goto fail;
+    }
+    
+    struct reply *rpl = reply_init(c, r->request_id, reply_data);
+    if (!rpl) {
+        ModuleLog(i, BLOG_ERROR, "failed to submit reply");
+        goto fail;
+    }
+    
+    reply_start(rpl, REQUESTPROTO_TYPE_SERVER_REPLY);
+    return;
+    
+fail:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void finish_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail;
+    }
+    
+    NCDModuleInst_Backend_Up(i);
+    
+    struct request *r = params->method_user;
+    
+    if (r->terminating) {
+        ModuleLog(i, BLOG_ERROR, "request is dying, cannot submit finished");
+        goto fail;
+    }
+    
+    r->got_finished = 1;
+    
+    request_terminate(r);
+    return;
+    
+fail:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "sys.request_server",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "sys.request_server.request::reply",
+        .func_new2 = reply_func_new
+    }, {
+        .type = "sys.request_server.request::finish",
+        .func_new2 = finish_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_sys_request_server = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/sys_start_process.c b/external/badvpn_dns/ncd/modules/sys_start_process.c
new file mode 100644
index 0000000..f34e7b3
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/sys_start_process.c
@@ -0,0 +1,1266 @@
+/**
+ * @file sys_start_process.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   sys.start_process(list command, string mode [, map options])
+ * 
+ * Options:
+ *   "keep_stdout":"true" - Start the program with the same stdout as the NCD process.
+ *     Must not be present if the process is being opened for reading.
+ *   "keep_stderr":true" - Start the program with the same stderr as the NCD process.
+ *   "do_setsid":"true" - Call setsid() in the child before exec. This is needed to
+ *     start the 'agetty' program.
+ *   "username":username_string - Start the process under the permissions of the
+ *     specified user. 
+ *   "term_on_deinit":"false" - do not send SIGTERM to the process when this statement
+ *     is requested to terminate
+ *   "deinit_kill_time":milliseconds - how long to wait for the process to terminate
+ *     after this statement is requested to terminate until we send SIGKILL. If this option
+ *     is not present or is "never", SIGKILL will not be sent. If this option is empty, the
+ *     process will be sent SIGKILL immediately when the statement is requested to terminate.
+ * 
+ * Variables:
+ *   is_error - "true" if there was an error starting the process, "false" if the process
+ *              has been started successfully
+ * 
+ * Synopsis:
+ *   sys.start_process::wait()
+ * 
+ * Variables:
+ *   exit_status - the exit code if the process terminated normally, -1 if it terminated
+ *                 with a signal
+ * 
+ * Synopsis:
+ *   sys.start_process::terminate()
+ *   sys.start_process::kill()
+ * 
+ * Synopsis:
+ *   sys.start_process::read_pipe()
+ * 
+ * Description:
+ *   Creates a read interface to the process's standard output. Data is read using the
+ *   read() method on this object. Read errors are reported implicitly by this statement
+ *   going down and the 'is_error' variable changing to "true".
+ *   When read_pipe() is initialized for a process, it takes ownership of the read pipe
+ *   to the process. When read_pipe() is requested to terminate, it will close the pipe.
+ *   Attempting to initialize read_pipe() on a process which was not started with 'r'
+ *   in the mode argument, or where another read_pipe() object has already taken ownership
+ *   of the read pipe, will result in throwing an error to the interpreter.
+ * 
+ * Variables:
+ *   string is_error - "true" if there was a read error, "false" if not
+ * 
+ * Synopsis:
+ *   sys.start_process::read_pipe::read()
+ * 
+ * Description:
+ *   Reads some data. If a read error occurs, it is reported implicitly via the
+ *   read_pipe() object going down. If end of file is reached, this and any future read()
+ *   operations will indicate that via the 'not_eof' variable. It is guaranteed that after
+ *   EOF is reached, the read_pipe() object will not go down to report any errors.
+ *   WARNING: if a read() is requested to terminate before it has completed, the
+ *            read_pipe() will become unusable and any read() invocation after that will
+ *            throw an error to the interpreter.
+ * 
+ * Variables:
+ *   string (empty) - data that was read, or an empty string on EOF
+ *   string not_eof - "true" is EOF was not reached, "false" if it was
+ * 
+ * Synopsis:
+ *   sys.start_process::write_pipe()
+ * 
+ * Description:
+ *   Creates a write interface to the process's standard input. Data is written using the
+ *   write() method on this object. Write errors are reported implicitly by this statement
+ *   going down and the ''is_error variable changing to "true".
+ *   When write_pipe() is initialized for a process, it takes ownership of the write pipe
+ *   to the process. When write_pipe() is requested to terminate, it will close the pipe
+ *   (unless the close() has been used).
+ *   Attempting to initialize write_pipe() on a process which was not started with 'w'
+ *   in the mode argument, or where another write_pipe() object has already taken ownership
+ *   of the write pope, will result in throwing an error to the interpreter.
+ * 
+ * Variables:
+ *   string is_error - "true" if there was a write error, "false" if not
+ * 
+ * Synopsis:
+ *   sys.start_process::write_pipe::write(string data)
+ * 
+ * Description:
+ *   Writes the given data. If a write error occurs, it is reported implicitly via the
+ *   write_pipe() object going down.
+ *   WARNING: if a write() is requested to terminate before it has completed, the
+ *            write_pipe() will become unusable and any write() or close() invocation after
+ *            that will throw an error to the interpreter.
+ * 
+ * Synopsis:
+ *   sys.start_process::write_pipe::close(string data)
+ * 
+ * Description:
+ *   Closes the write pipe. This will make whatever is reading the other end of the pipe
+ *   encounter EOF after it has read any pending data. It is guaranteed that after the
+ *   pipe is closed, the write_pipe() object will not go down to report any errors.
+ *   After close() is performed, any further write() or close() calls are disallowed and
+ *   will throw errors to the interpreter.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <misc/offset.h>
+#include <structure/LinkedList0.h>
+#include <system/BProcess.h>
+#include <system/BConnection.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/NCDBuf.h>
+#include <ncd/extra/value_utils.h>
+#include <ncd/extra/build_cmdline.h>
+#include <ncd/extra/NCDBProcessOpts.h>
+
+#include <generated/blog_channel_ncd_sys_start_process.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define READ_BUF_SIZE 8192
+
+#define PROCESS_STATE_ERROR 1
+#define PROCESS_STATE_RUNNING 2
+#define PROCESS_STATE_TERMINATED 3
+#define PROCESS_STATE_DYING 4
+
+#define READER_STATE_RUNNING 1
+#define READER_STATE_EOF 2
+#define READER_STATE_ERROR 3
+#define READER_STATE_ABORTED 4
+
+#define WRITER_STATE_RUNNING 1
+#define WRITER_STATE_CLOSED 2
+#define WRITER_STATE_ERROR 3
+#define WRITER_STATE_ABORTED 4
+
+struct process_instance {
+    NCDModuleInst *i;
+    BProcess process;
+    BSmallTimer kill_timer;
+    LinkedList0 waits_list;
+    btime_t deinit_kill_time;
+    int term_on_deinit;
+    int read_fd;
+    int write_fd;
+    int exit_status;
+    int state;
+};
+
+struct wait_instance {
+    NCDModuleInst *i;
+    struct process_instance *pinst;
+    LinkedList0Node waits_list_node;
+    int exit_status;
+};
+
+struct read_pipe_instance {
+    NCDModuleInst *i;
+    int state;
+    int read_fd;
+    BConnection connection;
+    NCDBufStore store;
+    struct read_instance *read_inst;
+};
+
+struct read_instance {
+    NCDModuleInst *i;
+    struct read_pipe_instance *read_pipe_inst;
+    NCDBuf *buf;
+    size_t read_size;
+};
+
+struct write_pipe_instance {
+    NCDModuleInst *i;
+    int state;
+    int write_fd;
+    BConnection connection;
+    struct write_instance *write_inst;
+};
+
+struct write_instance {
+    NCDModuleInst *i;
+    struct write_pipe_instance *write_pipe_inst;
+    b_cstring cstr;
+    size_t pos;
+};
+
+static int parse_mode (NCDModuleInst *i, NCDValRef mode_arg, int *out_read, int *out_write)
+{
+    if (!NCDVal_IsString(mode_arg)) {
+        ModuleLog(i, BLOG_ERROR, "mode argument must be a string");
+        return 0;
+    }
+    
+    *out_read = 0;
+    *out_write = 0;
+    
+    b_cstring cstr = NCDVal_StringCstring(mode_arg);
+    
+    B_CSTRING_LOOP_CHARS(cstr, char_pos, ch, {
+        if (ch == 'r') {
+            *out_read = 1;
+        }
+        else if (ch == 'w') {
+            *out_write = 1;
+        }
+        else {
+            ModuleLog(i, BLOG_ERROR, "invalid character in mode argument");
+            return 0;
+        }
+    })
+    
+    return 1;
+}
+
+static void process_free (struct process_instance *o)
+{
+    // close write fd
+    if (o->write_fd != -1) {
+        if (close(o->write_fd) < 0) {
+            ModuleLog(o->i, BLOG_ERROR, "close failed");
+        }
+    }
+    
+    // close read fd
+    if (o->read_fd != -1) {
+        if (close(o->read_fd) < 0) {
+            ModuleLog(o->i, BLOG_ERROR, "close failed");
+        }
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void process_handler (void *vo, int normally, uint8_t normally_exit_status)
+{
+    struct process_instance *o = vo;
+    ASSERT(o->state == PROCESS_STATE_RUNNING || o->state == PROCESS_STATE_DYING)
+    
+    ModuleLog(o->i, BLOG_INFO, "process terminated");
+    
+    // free kill timer
+    BReactor_RemoveSmallTimer(o->i->params->iparams->reactor, &o->kill_timer);
+    
+    // free process
+    BProcess_Free(&o->process);
+    
+    // remember exit code
+    o->exit_status = (!normally ? -1 : normally_exit_status);
+    
+    // finish waits
+    LinkedList0Node *ln;
+    while ((ln = LinkedList0_GetFirst(&o->waits_list))) {
+        struct wait_instance *winst = UPPER_OBJECT(ln, struct wait_instance, waits_list_node);
+        ASSERT(winst->pinst == o)
+        LinkedList0_Remove(&o->waits_list, &winst->waits_list_node);
+        winst->pinst = NULL;
+        winst->exit_status = o->exit_status;
+        NCDModuleInst_Backend_Up(winst->i);
+    }
+    
+    // if we have been requested to die, then die now
+    if (o->state == PROCESS_STATE_DYING) {
+        process_free(o);
+        return;
+    }
+    
+    // set state
+    o->state = PROCESS_STATE_TERMINATED;
+}
+
+static void process_kill_timer_handler (BSmallTimer *kill_timer)
+{
+    struct process_instance *o = UPPER_OBJECT(kill_timer, struct process_instance, kill_timer);
+    ASSERT(o->state == PROCESS_STATE_DYING)
+    
+    ModuleLog(o->i, BLOG_INFO, "killing process after timeout");
+    BProcess_Kill(&o->process);
+}
+
+static int opts_func_unknown (void *user, NCDValRef key, NCDValRef val)
+{
+    struct process_instance *o = user;
+    
+    if (NCDVal_IsString(key) && NCDVal_StringEquals(key, "term_on_deinit")) {
+        o->term_on_deinit = ncd_read_boolean(val);
+        return 1;
+    }
+    
+    if (NCDVal_IsString(key) && NCDVal_StringEquals(key, "deinit_kill_time")) {
+        if (NCDVal_StringEquals(val, "never")) {
+            o->deinit_kill_time = -2;
+        }
+        else if (NCDVal_StringEqualsId(val, NCD_STRING_EMPTY, o->i->params->iparams->string_index)) {
+            o->deinit_kill_time = -1;
+        }
+        else if (!ncd_read_time(val, &o->deinit_kill_time)) {
+            ModuleLog(o->i, BLOG_ERROR, "wrong value for deinit_kill_time option");
+            return 0;
+        }
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void process_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct process_instance *o = vo;
+    o->i = i;
+    NCDModuleInst_Backend_PassMemToMethods(i);
+    
+    // check arguments
+    NCDValRef command_arg;
+    NCDValRef mode_arg;
+    NCDValRef options_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 2, &command_arg, &mode_arg) &&
+        !NCDVal_ListRead(params->args, 3, &command_arg, &mode_arg, &options_arg)
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // parse mode
+    int is_read;
+    int is_write;
+    if (!parse_mode(i, mode_arg, &is_read, &is_write)) {
+        goto fail0;
+    }
+    
+    // parse options
+    NCDBProcessOpts opts;
+    int keep_stdout;
+    int keep_stderr;
+    o->deinit_kill_time = -2;
+    o->term_on_deinit = 1;
+    if (!NCDBProcessOpts_Init2(&opts, options_arg, opts_func_unknown, o, i, BLOG_CURRENT_CHANNEL, &keep_stdout, &keep_stderr)) {
+        goto fail0;
+    }
+    
+    // keep-stdout option and read mode are not compatible
+    if (keep_stdout && is_read) {
+        ModuleLog(i, BLOG_ERROR, "keep-stdout and read mode are not compatible");
+        goto fail1;
+    }
+    
+    // prepare for creating pipes
+    int fds[4];
+    int fds_map[3];
+    int start_num_fds = opts.nfds;
+    int num_fds = start_num_fds;
+    memcpy(fds, opts.fds, num_fds * sizeof(int));
+    memcpy(fds_map, opts.fds_map, num_fds * sizeof(int));
+    int read_fd = -1;
+    int write_fd = -1;
+    
+    // create read pipe
+    if (is_read) {
+        int pipefd[2];
+        if (pipe(pipefd) < 0) {
+            ModuleLog(i, BLOG_ERROR, "pipe failed");
+            goto error1;
+        }
+        read_fd = pipefd[0];
+        fds[num_fds] = pipefd[1];
+        fds_map[num_fds++] = STDOUT_FILENO;
+    }
+    
+    // create write pipe
+    if (is_write) {
+        int pipefd[2];
+        if (pipe(pipefd) < 0) {
+            ModuleLog(i, BLOG_ERROR, "pipe failed");
+            goto error1;
+        }
+        write_fd = pipefd[1];
+        fds[num_fds] = pipefd[0];
+        fds_map[num_fds++] = STDIN_FILENO;
+    }
+    
+    // terminate fds array
+    fds[num_fds] = -1;
+    
+    // build process parameters struct
+    struct BProcess_params p_params = {};
+    p_params.fds = fds;
+    p_params.fds_map = fds_map;
+    p_params.do_setsid = opts.do_setsid;
+    p_params.username = opts.username;
+    
+    // build command line
+    char *exec;
+    CmdLine cl;
+    if (!ncd_build_cmdline(i, BLOG_CURRENT_CHANNEL, command_arg, &exec, &cl)) {
+        goto error1;
+    }
+    
+    // start process
+    int res = BProcess_Init2(&o->process, i->params->iparams->manager, process_handler, o, exec, CmdLine_Get(&cl), p_params);
+    CmdLine_Free(&cl);
+    free(exec);
+    if (!res) {
+        ModuleLog(i, BLOG_ERROR, "BProcess_Init failed");
+        goto error1;
+    }
+    
+    // init kill timer
+    BSmallTimer_Init(&o->kill_timer, process_kill_timer_handler);
+    
+    // close child fds
+    while (num_fds-- > start_num_fds) {
+        if (close(fds[num_fds]) < 0) {
+            ModuleLog(i, BLOG_ERROR, "close failed");
+        }
+    }
+    
+    // free opts
+    NCDBProcessOpts_Free(&opts);
+    
+    // init waits list
+    LinkedList0_Init(&o->waits_list);
+    
+    // remember our fds
+    o->read_fd = read_fd;
+    o->write_fd = write_fd;
+    
+    // set state
+    o->state = PROCESS_STATE_RUNNING;
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail1:
+    NCDBProcessOpts_Free(&opts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+    return;
+    
+error1:
+    if (write_fd != -1) {
+        if (close(write_fd) < 0) {
+            ModuleLog(i, BLOG_ERROR, "close failed");
+        }
+    }
+    if (read_fd != -1) {
+        if (close(read_fd) < 0) {
+            ModuleLog(i, BLOG_ERROR, "close failed");
+        }
+    }
+    while (num_fds-- > start_num_fds) {
+        if (close(fds[num_fds]) < 0) {
+            ModuleLog(i, BLOG_ERROR, "close failed");
+        }
+    }
+    NCDBProcessOpts_Free(&opts);
+    
+    o->read_fd = -1;
+    o->write_fd = -1;
+    o->state = PROCESS_STATE_ERROR;
+    NCDModuleInst_Backend_Up(i);
+}
+
+static void process_func_die (void *vo)
+{
+    struct process_instance *o = vo;
+    ASSERT(o->state != PROCESS_STATE_DYING)
+    
+    // if process is not running, die immediately
+    if (o->state != PROCESS_STATE_RUNNING) {
+        process_free(o);
+        return;
+    }
+    
+    if (o->term_on_deinit) {
+        ModuleLog(o->i, BLOG_INFO, "terminating process");
+        
+        // send termination signal
+        BProcess_Terminate(&o->process);
+    } else {
+        ModuleLog(o->i, BLOG_INFO, "not terminating process as requested");
+    }
+    
+    if (o->deinit_kill_time == -1) {
+        // user wants SIGKILL immediately
+        ModuleLog(o->i, BLOG_INFO, "killing process immediately");
+        BProcess_Kill(&o->process);
+    } else if (o->deinit_kill_time >= 0) {
+        // user wants SIGKILL after some time
+        BReactor_SetSmallTimer(o->i->params->iparams->reactor, &o->kill_timer, BTIMER_SET_RELATIVE, o->deinit_kill_time);
+    }
+    
+    // set state
+    o->state = PROCESS_STATE_DYING;
+}
+
+static int process_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct process_instance *o = vo;
+    
+    if (name == NCD_STRING_IS_ERROR) {
+        int is_error = (o->state == PROCESS_STATE_ERROR);
+        *out = ncd_make_boolean(mem, is_error, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void wait_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct wait_instance *o = vo;
+    o->i = i;
+    
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct process_instance *pinst = params->method_user;
+    
+    if (pinst->state == PROCESS_STATE_ERROR) {
+        ModuleLog(i, BLOG_ERROR, "wait() is disallowed after the process has failed to start");
+        goto fail0;
+    }
+    
+    if (pinst->state == PROCESS_STATE_TERMINATED) {
+        // not waiting, set no pinst
+        o->pinst = NULL;
+        
+        // remember exit code
+        o->exit_status = pinst->exit_status;
+        
+        // go up
+        NCDModuleInst_Backend_Up(i);
+    } else {
+        // waitint, set pinst
+        o->pinst = pinst;
+        
+        // insert to waits list
+        LinkedList0_Prepend(&pinst->waits_list, &o->waits_list_node);
+    }
+    
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void wait_func_die (void *vo)
+{
+    struct wait_instance *o = vo;
+    
+    // remove from waits list
+    if (o->pinst) {
+        LinkedList0_Remove(&o->pinst->waits_list, &o->waits_list_node);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int wait_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct wait_instance *o = vo;
+    ASSERT(!o->pinst)
+    
+    if (name == NCD_STRING_EXIT_STATUS) {
+        if (o->exit_status == -1) {
+            *out = NCDVal_NewString(mem, "-1");
+        } else {
+            *out = ncd_make_uintmax(mem, o->exit_status);
+        }
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void terminate_kill_new_common (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_kill)
+{
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct process_instance *pinst = params->method_user;
+    
+    if (pinst->state == PROCESS_STATE_ERROR) {
+        ModuleLog(i, BLOG_ERROR, "terminate()/kill() is disallowed after the process has failed to start");
+        goto fail0;
+    }
+    
+    if (pinst->state != PROCESS_STATE_TERMINATED) {
+        if (is_kill) {
+            BProcess_Kill(&pinst->process);
+        } else {
+            BProcess_Terminate(&pinst->process);
+        }
+    }
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void terminate_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    terminate_kill_new_common(vo, i, params, 0);
+}
+
+static void kill_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    terminate_kill_new_common(vo, i, params, 1);
+}
+
+static void read_pipe_free_connection (struct read_pipe_instance *o)
+{
+    // disconnect read instance
+    if (o->read_inst) {
+        ASSERT(o->read_inst->read_pipe_inst == o)
+        o->read_inst->read_pipe_inst = NULL;
+    }
+    
+    // free store
+    NCDBufStore_Free(&o->store);
+    
+    // free connection read interface
+    BConnection_RecvAsync_Free(&o->connection);
+    
+    // free connection
+    BConnection_Free(&o->connection);
+    
+    // close fd
+    if (close(o->read_fd) < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "close failed");
+    }
+}
+
+static void read_pipe_abort (struct read_pipe_instance *o)
+{
+    ASSERT(o->state == READER_STATE_RUNNING)
+    
+    // release connection resources
+    read_pipe_free_connection(o);
+    
+    // set state
+    o->state = READER_STATE_ABORTED;
+}
+
+static void read_pipe_connection_handler (void *vo, int event)
+{
+    struct read_pipe_instance *o = vo;
+    ASSERT(o->state == READER_STATE_RUNNING)
+    
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        // if we have read operation, make it finish with eof
+        if (o->read_inst) {
+            ASSERT(o->read_inst->read_pipe_inst == o)
+            ASSERT(o->read_inst->buf)
+            o->read_inst->read_pipe_inst = NULL;
+            o->read_inst->read_size = 0;
+            NCDModuleInst_Backend_Up(o->read_inst->i);
+            o->read_inst = NULL;
+        }
+        
+        // free connection resources
+        read_pipe_free_connection(o);
+        
+        // set state closed
+        o->state = READER_STATE_EOF;
+        return;
+    }
+    
+    ModuleLog(o->i, BLOG_ERROR, "read pipe error");
+    
+    // free connection resources
+    read_pipe_free_connection(o);
+    
+    // set state error
+    o->state = READER_STATE_ERROR;
+    
+    // backtrack
+    NCDModuleInst_Backend_DownUp(o->i);
+}
+
+static void read_pipe_recv_handler_done (void *vo, int data_len)
+{
+    struct read_pipe_instance *o = vo;
+    ASSERT(o->state == READER_STATE_RUNNING)
+    ASSERT(o->read_inst)
+    ASSERT(o->read_inst->read_pipe_inst == o)
+    ASSERT(o->read_inst->buf)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= NCDBufStore_BufSize(&o->store))
+    
+    // finish read operation
+    o->read_inst->read_pipe_inst = NULL;
+    o->read_inst->read_size = data_len;
+    NCDModuleInst_Backend_Up(o->read_inst->i);
+    o->read_inst = NULL;
+}
+
+static void read_pipe_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct read_pipe_instance *o = vo;
+    o->i = i;
+    NCDModuleInst_Backend_PassMemToMethods(i);
+    
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct process_instance *pinst = params->method_user;
+    
+    if (pinst->read_fd == -1) {
+        ModuleLog(i, BLOG_ERROR, "process did not start successfully, was not opened for reading or a read_pipe was already created");
+        goto fail0;
+    }
+    
+    // init connection
+    if (!BConnection_Init(&o->connection, BConnection_source_pipe(pinst->read_fd), i->params->iparams->reactor, o, read_pipe_connection_handler)) {
+        ModuleLog(i, BLOG_ERROR, "BConnection_Init failed");
+        goto fail0;
+    }
+    
+    // init connection read interface
+    BConnection_RecvAsync_Init(&o->connection);
+    
+    // set recv done callback
+    StreamRecvInterface_Receiver_Init(BConnection_RecvAsync_GetIf(&o->connection), read_pipe_recv_handler_done, o);
+    
+    // init store
+    NCDBufStore_Init(&o->store, READ_BUF_SIZE);
+    
+    // set variables
+    o->state = READER_STATE_RUNNING;
+    o->read_fd = pinst->read_fd;
+    o->read_inst = NULL;
+    
+    // steal read fd from process instance
+    pinst->read_fd = -1;
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void read_pipe_func_die (void *vo)
+{
+    struct read_pipe_instance *o = vo;
+    
+    // free connection resources
+    if (o->state == READER_STATE_RUNNING) {
+        read_pipe_free_connection(o);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int read_pipe_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct read_pipe_instance *o = vo;
+    
+    if (name == NCD_STRING_IS_ERROR) {
+        int is_error = (o->state == READER_STATE_ERROR);
+        *out = ncd_make_boolean(mem, is_error, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void read_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct read_instance *o = vo;
+    o->i = i;
+    
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct read_pipe_instance *read_pipe_inst = params->method_user;
+    
+    // check if a read error has already occured
+    if (read_pipe_inst->state == READER_STATE_ERROR) {
+        ModuleLog(i, BLOG_ERROR, "read() is disallowed after a read error has occured");
+        goto fail0;
+    }
+    
+    // check if the read_pipe has been aborted
+    if (read_pipe_inst->state == READER_STATE_ABORTED) {
+        ModuleLog(i, BLOG_ERROR, "read() is disallowed after a read() has been aborted");
+        goto fail0;
+    }
+    
+    // if EOF has already been encountered, complete the read immediately
+    if (read_pipe_inst->state == READER_STATE_EOF) {
+        o->buf = NULL;
+        o->read_pipe_inst = NULL;
+        o->read_size = 0;
+        NCDModuleInst_Backend_Up(i);
+        return;
+    }
+    
+    ASSERT(read_pipe_inst->state == READER_STATE_RUNNING)
+    
+    // check if there's already a read in progress
+    if (read_pipe_inst->read_inst) {
+        ModuleLog(i, BLOG_ERROR, "read() is disallowed while another read() is in progress");
+        goto fail0;
+    }
+    
+    // get buffer
+    o->buf = NCDBufStore_GetBuf(&read_pipe_inst->store);
+    if (!o->buf) {
+        ModuleLog(i, BLOG_ERROR, "NCDBufStore_GetBuf failed");
+        goto fail0;
+    }
+    
+    // set read_pipe
+    o->read_pipe_inst = read_pipe_inst;
+    
+    // register read in read_pipe
+    read_pipe_inst->read_inst = o;
+    
+    // receive
+    size_t buf_size = NCDBufStore_BufSize(&read_pipe_inst->store);
+    int to_read = (buf_size > INT_MAX ? INT_MAX : buf_size);
+    StreamRecvInterface_Receiver_Recv(BConnection_RecvAsync_GetIf(&read_pipe_inst->connection), (uint8_t *)NCDBuf_Data(o->buf), to_read);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void read_func_die (void *vo)
+{
+    struct read_instance *o = vo;
+    
+    // if we're receiving, abort read_pipe
+    if (o->read_pipe_inst) {
+        ASSERT(o->read_pipe_inst->state == READER_STATE_RUNNING)
+        ASSERT(o->read_pipe_inst->read_inst == o)
+        ASSERT(o->buf)
+        read_pipe_abort(o->read_pipe_inst);
+    }
+    
+    // release buffer
+    if (o->buf) {
+        BRefTarget_Deref(NCDBuf_RefTarget(o->buf));
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int read_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct read_instance *o = vo;
+    ASSERT(!o->read_pipe_inst)
+    ASSERT(!(o->read_size > 0) || o->buf)
+    
+    if (name == NCD_STRING_EMPTY) {
+        if (o->read_size > 0) {
+            *out = NCDVal_NewExternalString(mem, NCDBuf_Data(o->buf), o->read_size, NCDBuf_RefTarget(o->buf));
+        } else {
+            *out = NCDVal_NewIdString(mem, NCD_STRING_EMPTY, o->i->params->iparams->string_index);
+        }
+        return 1;
+    }
+    
+    if (name == NCD_STRING_NOT_EOF) {
+        int not_eof = (o->read_size > 0);
+        *out = ncd_make_boolean(mem, not_eof, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void write_pipe_free_connection (struct write_pipe_instance *o)
+{
+    // disconnect write instance
+    if (o->write_inst) {
+        ASSERT(o->write_inst->write_pipe_inst == o)
+        o->write_inst->write_pipe_inst = NULL;
+    }
+    
+    // free connection send interface
+    BConnection_SendAsync_Free(&o->connection);
+    
+    // free connection
+    BConnection_Free(&o->connection);
+    
+    // close fd
+    if (close(o->write_fd) < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "close failed");
+    }
+}
+
+static void write_pipe_abort (struct write_pipe_instance *o)
+{
+    ASSERT(o->state == WRITER_STATE_RUNNING)
+    
+    // release connection resources
+    write_pipe_free_connection(o);
+    
+    // set state
+    o->state = WRITER_STATE_ABORTED;
+}
+
+static void write_pipe_close (struct write_pipe_instance *o)
+{
+    ASSERT(o->state == WRITER_STATE_RUNNING)
+    
+    // release connection resources
+    write_pipe_free_connection(o);
+    
+    // set state
+    o->state = WRITER_STATE_CLOSED;
+}
+
+static void write_pipe_connection_handler (void *vo, int event)
+{
+    struct write_pipe_instance *o = vo;
+    ASSERT(o->state == WRITER_STATE_RUNNING)
+    
+    ModuleLog(o->i, BLOG_ERROR, "write pipe error");
+    
+    // free connection resources
+    write_pipe_free_connection(o);
+    
+    // set state error
+    o->state = WRITER_STATE_ERROR;
+    
+    // backtrack
+    NCDModuleInst_Backend_DownUp(o->i);
+}
+
+static void write_pipe_send_handler_done (void *vo, int data_len)
+{
+    struct write_pipe_instance *o = vo;
+    ASSERT(o->state == WRITER_STATE_RUNNING)
+    ASSERT(o->write_inst)
+    ASSERT(o->write_inst->write_pipe_inst == o)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->write_inst->cstr.length - o->write_inst->pos)
+    
+    struct write_instance *wr = o->write_inst;
+    
+    // update write progress
+    wr->pos += data_len;
+    
+    // if there is more data, start another write operation
+    if (wr->pos < wr->cstr.length) {
+        size_t chunk_length;
+        const char *chunk_data = b_cstring_get(wr->cstr, wr->pos, wr->cstr.length - wr->pos, &chunk_length);
+        size_t to_send = (chunk_length > INT_MAX ? INT_MAX : chunk_length);
+        StreamPassInterface_Sender_Send(BConnection_SendAsync_GetIf(&o->connection), (uint8_t *)chunk_data, to_send);
+        return;
+    }
+    
+    // finish write operation
+    wr->write_pipe_inst = NULL;
+    NCDModuleInst_Backend_Up(wr->i);
+    o->write_inst = NULL;
+}
+
+static void write_pipe_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct write_pipe_instance *o = vo;
+    o->i = i;
+    NCDModuleInst_Backend_PassMemToMethods(i);
+    
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct process_instance *pinst = params->method_user;
+    
+    if (pinst->write_fd == -1) {
+        ModuleLog(i, BLOG_ERROR, "process did not start successfully, was not opened for writing or a write_pipe was already created");
+        goto fail0;
+    }
+    
+    // init connection
+    if (!BConnection_Init(&o->connection, BConnection_source_pipe(pinst->write_fd), i->params->iparams->reactor, o, write_pipe_connection_handler)) {
+        ModuleLog(i, BLOG_ERROR, "BConnection_Init failed");
+        goto fail0;
+    }
+    
+    // init connection send interface
+    BConnection_SendAsync_Init(&o->connection);
+    
+    // set send done callback
+    StreamPassInterface_Sender_Init(BConnection_SendAsync_GetIf(&o->connection), write_pipe_send_handler_done, o);
+    
+    // set variables
+    o->state = WRITER_STATE_RUNNING;
+    o->write_fd = pinst->write_fd;
+    o->write_inst = NULL;
+    
+    // steal write fd from process instance
+    pinst->write_fd = -1;
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void write_pipe_func_die (void *vo)
+{
+    struct write_pipe_instance *o = vo;
+    
+    // free connection resources
+    if (o->state == WRITER_STATE_RUNNING) {
+        write_pipe_free_connection(o);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int write_pipe_func_getvar (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct write_pipe_instance *o = vo;
+    
+    if (name == NCD_STRING_IS_ERROR) {
+        int is_error = (o->state == WRITER_STATE_ERROR);
+        *out = ncd_make_boolean(mem, is_error, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void write_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct write_instance *o = vo;
+    o->i = i;
+    
+    NCDValRef data_arg;
+    if (!NCDVal_ListRead(params->args, 1, &data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    struct write_pipe_instance *write_pipe_inst = params->method_user;
+    
+    // check if a write error has already occured
+    if (write_pipe_inst->state == WRITER_STATE_ERROR) {
+        ModuleLog(i, BLOG_ERROR, "write() is disallowed after a write error has occured");
+        goto fail0;
+    }
+    
+    // check if the write_pipe has been aborted
+    if (write_pipe_inst->state == WRITER_STATE_ABORTED) {
+        ModuleLog(i, BLOG_ERROR, "write() is disallowed after a write() has been aborted");
+        goto fail0;
+    }
+    
+    // check if the write_pipe has been aborted
+    if (write_pipe_inst->state == WRITER_STATE_CLOSED) {
+        ModuleLog(i, BLOG_ERROR, "write() is disallowed after close() has been called");
+        goto fail0;
+    }
+    
+    ASSERT(write_pipe_inst->state == WRITER_STATE_RUNNING)
+    
+    // check if there's already a write in progress
+    if (write_pipe_inst->write_inst) {
+        ModuleLog(i, BLOG_ERROR, "write() is disallowed while another write() is in progress");
+        goto fail0;
+    }
+    
+    // initialize write progress state
+    o->cstr = NCDVal_StringCstring(data_arg);
+    o->pos = 0;
+    
+    // if there's nothing to send, go up immediately
+    if (o->cstr.length == 0) {
+        o->write_pipe_inst = NULL;
+        NCDModuleInst_Backend_Up(i);
+        return;
+    }
+    
+    // set write_pipe
+    o->write_pipe_inst = write_pipe_inst;
+    
+    // register write in write_pipe
+    write_pipe_inst->write_inst = o;
+    
+    // start send operation
+    size_t chunk_length;
+    const char *chunk_data = b_cstring_get(o->cstr, o->pos, o->cstr.length - o->pos, &chunk_length);
+    size_t to_send = (chunk_length > INT_MAX ? INT_MAX : chunk_length);
+    StreamPassInterface_Sender_Send(BConnection_SendAsync_GetIf(&write_pipe_inst->connection), (uint8_t *)chunk_data, to_send);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void write_func_die (void *vo)
+{
+    struct write_instance *o = vo;
+    
+    // if we're sending, abort write_pipe
+    if (o->write_pipe_inst) {
+        ASSERT(o->write_pipe_inst->state == WRITER_STATE_RUNNING)
+        ASSERT(o->write_pipe_inst->write_inst == o)
+        write_pipe_abort(o->write_pipe_inst);
+    }
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void close_func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct write_pipe_instance *write_pipe_inst = params->method_user;
+    
+    // check if a write error has already occured
+    if (write_pipe_inst->state == WRITER_STATE_ERROR) {
+        ModuleLog(i, BLOG_ERROR, "close() is disallowed after a write error has occured");
+        goto fail0;
+    }
+    
+    // check if the write_pipe has been aborted
+    if (write_pipe_inst->state == WRITER_STATE_ABORTED) {
+        ModuleLog(i, BLOG_ERROR, "close() is disallowed after a write() has been aborted");
+        goto fail0;
+    }
+    
+    // check if the write_pipe has been closed
+    if (write_pipe_inst->state == WRITER_STATE_CLOSED) {
+        ModuleLog(i, BLOG_ERROR, "close() is disallowed after close() has been called");
+        goto fail0;
+    }
+    
+    // close
+    write_pipe_close(write_pipe_inst);
+    
+    // go up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "sys.start_process",
+        .func_new2 = process_func_new,
+        .func_die = process_func_die,
+        .func_getvar2 = process_func_getvar,
+        .alloc_size = sizeof(struct process_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.start_process::wait",
+        .func_new2 = wait_func_new,
+        .func_die = wait_func_die,
+        .func_getvar2 = wait_func_getvar,
+        .alloc_size = sizeof(struct wait_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.start_process::terminate",
+        .func_new2 = terminate_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.start_process::kill",
+        .func_new2 = kill_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.start_process::read_pipe",
+        .func_new2 = read_pipe_func_new,
+        .func_die = read_pipe_func_die,
+        .func_getvar2 = read_pipe_func_getvar,
+        .alloc_size = sizeof(struct read_pipe_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.start_process::read_pipe::read",
+        .func_new2 = read_func_new,
+        .func_die = read_func_die,
+        .func_getvar2 = read_func_getvar,
+        .alloc_size = sizeof(struct read_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.start_process::write_pipe",
+        .func_new2 = write_pipe_func_new,
+        .func_die = write_pipe_func_die,
+        .func_getvar2 = write_pipe_func_getvar,
+        .alloc_size = sizeof(struct write_pipe_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.start_process::write_pipe::write",
+        .func_new2 = write_func_new,
+        .func_die = write_func_die,
+        .alloc_size = sizeof(struct write_instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "sys.start_process::write_pipe::close",
+        .func_new2 = close_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_sys_start_process = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/sys_watch_directory.c b/external/badvpn_dns/ncd/modules/sys_watch_directory.c
new file mode 100644
index 0000000..41f7d42
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/sys_watch_directory.c
@@ -0,0 +1,425 @@
+/**
+ * @file sys_watch_directory.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Directory watcher.
+ * 
+ * Synopsis: sys.watch_directory(string dir)
+ * Description: reports directory entry events. Transitions up when an event is detected, and
+ *   goes down waiting for the next event when sys.watch_directory::nextevent() is called.
+ *   The directory is first scanned and "added" events are reported for all files.
+ * Variables:
+ *   string event_type - what happened with the file: "added", "removed" or "changed"
+ *   string filename - name of the file in the directory the event refers to
+ *   string filepath - "dir/filename"
+ * 
+ * Synopsis: sys.watch_directory::nextevent()
+ * Description: makes the watch_directory module transition down in order to report the next event.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/inotify.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include <misc/nonblocking.h>
+#include <misc/concat_strings.h>
+
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_sys_watch_directory.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define MAX_INOTIFY_EVENTS 128
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValNullTermString dir_nts;
+    DIR *dir_handle;
+    int inotify_fd;
+    BFileDescriptor bfd;
+    struct inotify_event events[MAX_INOTIFY_EVENTS];
+    int events_count;
+    int events_index;
+    int processing;
+    const char *processing_file;
+    const char *processing_type;
+};
+
+static void instance_free (struct instance *o, int is_error);
+
+static void next_dir_event (struct instance *o)
+{
+    ASSERT(!o->processing)
+    ASSERT(o->dir_handle)
+    
+    struct dirent *entry;
+    
+    do {
+        // get next entry
+        errno = 0;
+        if (!(entry = readdir(o->dir_handle))) {
+            if (errno != 0) {
+                ModuleLog(o->i, BLOG_ERROR, "readdir failed");
+                instance_free(o, 1);
+                return;
+            }
+            
+            // close directory
+            if (closedir(o->dir_handle) < 0) {
+                ModuleLog(o->i, BLOG_ERROR, "closedir failed");
+                o->dir_handle = NULL;
+                instance_free(o, 1);
+                return;
+            }
+            
+            // set no dir handle
+            o->dir_handle = NULL;
+            
+            // start receiving inotify events
+            BReactor_SetFileDescriptorEvents(o->i->params->iparams->reactor, &o->bfd, BREACTOR_READ);
+            return;
+        }
+    } while (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."));
+    
+    // set event
+    o->processing_file = entry->d_name;
+    o->processing_type = "added";
+    o->processing = 1;
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+}
+
+static void assert_inotify_event (struct instance *o)
+{
+    ASSERT(o->events_index < o->events_count)
+    ASSERT(o->events[o->events_index].len % sizeof(o->events[0]) == 0)
+    ASSERT(o->events[o->events_index].len / sizeof(o->events[0]) <= o->events_count - (o->events_index + 1))
+}
+
+static const char * translate_inotify_event (struct instance *o)
+{
+    assert_inotify_event(o);
+    
+    struct inotify_event *event = &o->events[o->events_index];
+    
+    if (strlen(event->name) > 0) {
+        if ((event->mask & (IN_CREATE | IN_MOVED_TO))) {
+            return "added";
+        }
+        if ((event->mask & (IN_DELETE | IN_MOVED_FROM))) {
+            return "removed";
+        }
+        if ((event->mask & IN_MODIFY)) {
+            return "changed";
+        }
+    }
+    
+    return NULL;
+}
+
+static void skip_inotify_event (struct instance *o)
+{
+    assert_inotify_event(o);
+    
+    o->events_index += 1 + o->events[o->events_index].len / sizeof(o->events[0]);
+}
+
+static void next_inotify_event (struct instance *o)
+{
+    ASSERT(!o->processing)
+    ASSERT(!o->dir_handle)
+    
+    // skip any bad events
+    while (o->events_index < o->events_count && !translate_inotify_event(o)) {
+        ModuleLog(o->i, BLOG_ERROR, "unknown inotify event");
+        skip_inotify_event(o);
+    }
+    
+    if (o->events_index == o->events_count) {
+        // wait for more events
+        BReactor_SetFileDescriptorEvents(o->i->params->iparams->reactor, &o->bfd, BREACTOR_READ);
+        return;
+    }
+    
+    // set event
+    o->processing_file = o->events[o->events_index].name;
+    o->processing_type = translate_inotify_event(o);
+    o->processing = 1;
+    
+    // consume this event
+    skip_inotify_event(o);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+}
+
+static void inotify_fd_handler (struct instance *o, int events)
+{
+    if (o->processing) {
+        ModuleLog(o->i, BLOG_ERROR, "file descriptor error");
+        instance_free(o, 1);
+        return;
+    }
+    
+    ASSERT(!o->dir_handle)
+    
+    int res = read(o->inotify_fd, o->events, sizeof(o->events));
+    if (res < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "read failed");
+        instance_free(o, 1);
+        return;
+    }
+    
+    // stop waiting for inotify events
+    BReactor_SetFileDescriptorEvents(o->i->params->iparams->reactor, &o->bfd, 0);
+    
+    ASSERT(res <= sizeof(o->events))
+    ASSERT(res % sizeof(o->events[0]) == 0)
+    
+    // setup buffer state
+    o->events_count = res / sizeof(o->events[0]);
+    o->events_index = 0;
+    
+    // process inotify events
+    next_inotify_event(o);
+}
+
+static void next_event (struct instance *o)
+{
+    ASSERT(o->processing)
+    
+    // set not processing
+    o->processing = 0;
+    
+    // signal down
+    NCDModuleInst_Backend_Down(o->i);
+    
+    if (o->dir_handle) {
+        next_dir_event(o);
+        return;
+    } else {
+        next_inotify_event(o);
+        return;
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef dir_arg;
+    if (!NCDVal_ListRead(params->args, 1, &dir_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsStringNoNulls(dir_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // null terminate dir
+    if (!NCDVal_StringNullTerminate(dir_arg, &o->dir_nts)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
+        goto fail0;
+    }
+    
+    // open inotify
+    if ((o->inotify_fd = inotify_init()) < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "inotify_init failed");
+        goto fail1;
+    }
+    
+    // add watch
+    if (inotify_add_watch(o->inotify_fd, o->dir_nts.data, IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO) < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "inotify_add_watch failed");
+        goto fail2;
+    }
+    
+    // set non-blocking
+    if (!badvpn_set_nonblocking(o->inotify_fd)) {
+        ModuleLog(o->i, BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail2;
+    }
+    
+    // init BFileDescriptor
+    BFileDescriptor_Init(&o->bfd, o->inotify_fd, (BFileDescriptor_handler)inotify_fd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->i->params->iparams->reactor, &o->bfd)) {
+        ModuleLog(o->i, BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail2;
+    }
+    
+    // open directory
+    if (!(o->dir_handle = opendir(o->dir_nts.data))) {
+        ModuleLog(o->i, BLOG_ERROR, "opendir failed");
+        goto fail3;
+    }
+    
+    // set not processing
+    o->processing = 0;
+    
+    // read first directory entry
+    next_dir_event(o);
+    return;
+    
+fail3:
+    BReactor_RemoveFileDescriptor(o->i->params->iparams->reactor, &o->bfd);
+fail2:
+    if (close(o->inotify_fd) < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "close failed");
+    }
+fail1:
+    NCDValNullTermString_Free(&o->dir_nts);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+void instance_free (struct instance *o, int is_error)
+{
+    // close directory
+    if (o->dir_handle) {
+        if (closedir(o->dir_handle) < 0) {
+            ModuleLog(o->i, BLOG_ERROR, "closedir failed");
+        }
+    }
+    
+    // free BFileDescriptor
+    BReactor_RemoveFileDescriptor(o->i->params->iparams->reactor, &o->bfd);
+    
+    // close inotify
+    if (close(o->inotify_fd) < 0) {
+        ModuleLog(o->i, BLOG_ERROR, "close failed");
+    }
+    
+    // free dir nts
+    NCDValNullTermString_Free(&o->dir_nts);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    instance_free(o, 0);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    ASSERT(o->processing)
+    
+    if (!strcmp(name, "event_type")) {
+        *out = NCDVal_NewString(mem, o->processing_type);
+        return 1;
+    }
+    
+    if (!strcmp(name, "filename")) {
+        *out = NCDVal_NewString(mem, o->processing_file);
+        return 1;
+    }
+    
+    if (!strcmp(name, "filepath")) {
+        char *str = concat_strings(3, o->dir_nts.data, "/", o->processing_file);
+        if (!str) {
+            ModuleLog(o->i, BLOG_ERROR, "concat_strings failed");
+            goto fail;
+        }
+        
+        *out = NCDVal_NewString(mem, str);
+        
+        free(str);
+        return 1;
+    }
+    
+    return 0;
+    
+fail:
+    *out = NCDVal_NewInvalid();
+    return 1;
+}
+
+static void nextevent_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure we are currently reporting an event
+    if (!mo->processing) {
+        ModuleLog(i, BLOG_ERROR, "not reporting an event");
+        goto fail0;
+    }
+    
+    // signal up.
+    // Do it before finishing the event so our process does not advance any further if
+    // we would be killed the event provider going down.
+    NCDModuleInst_Backend_Up(i);
+    
+    // wait for next event
+    next_event(mo);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "sys.watch_directory",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "sys.watch_directory::nextevent",
+        .func_new2 = nextevent_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_sys_watch_directory = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/sys_watch_input.c b/external/badvpn_dns/ncd/modules/sys_watch_input.c
new file mode 100644
index 0000000..6039bee
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/sys_watch_input.c
@@ -0,0 +1,455 @@
+/**
+ * @file sys_watch_input.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Input device watcher.
+ * 
+ * Synopsis: sys.watch_input(string devnode_type)
+ * Description: reports input device events. Transitions up when an event is detected, and
+ *   goes down waiting for the next event when sys.watch_input::nextevent() is called.
+ *   On startup, "added" events are reported for existing input devices.
+ * Arguments:
+ *   string devnode_type - device node type, for example "event", "mouse" or "js".
+ * Variables:
+ *   string event_type - what happened with the input device: "added" or "removed"
+ *   string devname - device node path
+ *   string device_type - input device type, "tablet", "joystick", "touchscreen", "mouse",
+ *     "touchpad", "key", "keyboard" or "unknown"
+ * 
+ * Synopsis: sys.watch_input::nextevent()
+ * Description: makes the watch_input module transition down in order to report the next event.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <structure/LinkedList1.h>
+#include <udevmonitor/NCDUdevManager.h>
+#include <ncd/NCDModule.h>
+#include <ncd/modules/event_template.h>
+
+#include <generated/blog_channel_ncd_sys_watch_input.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct device {
+    char *devname;
+    char *devpath;
+    BStringMap removed_map;
+    LinkedList1Node devices_list_node;
+};
+
+struct instance {
+    NCDModuleInst *i;
+    const char *devnode_type;
+    size_t devnode_type_len;
+    NCDUdevClient client;
+    LinkedList1 devices_list;
+    event_template templ;
+};
+
+static void templ_func_free (struct instance *o, int is_error);
+
+static struct device * find_device_by_devname (struct instance *o, const char *devname)
+{
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->devices_list);
+    while (list_node) {
+        struct device *device = UPPER_OBJECT(list_node, struct device, devices_list_node);
+        if (!strcmp(device->devname, devname)) {
+            return device;
+        }
+        list_node = LinkedList1Node_Next(list_node);
+    }
+    
+    return NULL;
+}
+
+static struct device * find_device_by_devpath (struct instance *o, const char *devpath)
+{
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->devices_list);
+    while (list_node) {
+        struct device *device = UPPER_OBJECT(list_node, struct device, devices_list_node);
+        if (!strcmp(device->devpath, devpath)) {
+            return device;
+        }
+        list_node = LinkedList1Node_Next(list_node);
+    }
+    
+    return NULL;
+}
+
+static void free_device (struct instance *o, struct device *device, int have_removed_map)
+{
+    // remove from devices list
+    LinkedList1_Remove(&o->devices_list, &device->devices_list_node);
+    
+    // free removed map
+    if (have_removed_map) {
+        BStringMap_Free(&device->removed_map);
+    }
+    
+    // free devpath
+    free(device->devpath);
+    
+    // free devname
+    free(device->devname);
+    
+    // free structure
+    free(device);
+}
+
+static int make_event_map (struct instance *o, int added, const char *devname, const char *device_type, BStringMap *out_map)
+{
+    // init map
+    BStringMap map;
+    BStringMap_Init(&map);
+    
+    // set type
+    if (!BStringMap_Set(&map, "event_type", (added ? "added" : "removed"))) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    // set devname
+    if (!BStringMap_Set(&map, "devname", devname)) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    // set device type
+    if (!BStringMap_Set(&map, "device_type", device_type)) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    *out_map = map;
+    return 1;
+    
+fail1:
+    BStringMap_Free(&map);
+    return 0;
+}
+
+static int devname_is_type (const char *devname, const char *devname_type, size_t devname_type_len)
+{
+    // skip digits at the end
+    size_t i;
+    for (i = strlen(devname); i > 0; i--) {
+        if (!isdigit(devname[i - 1])) {
+            break;
+        }
+    }
+    
+    // check if devname_type precedes skipped digits
+    for (size_t j = devname_type_len; j > 0; j--) {
+        if (!(i > 0 && devname[i - 1] == devname_type[j - 1])) {
+            return 0;
+        }
+        i--;
+    }
+    
+    // check if slash precedes devname_type
+    if (!(i > 0 && devname[i - 1] == '/')) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static void queue_event (struct instance *o, BStringMap map)
+{
+    // pass event to template
+    int was_empty;
+    event_template_queue(&o->templ, map, &was_empty);
+    
+    // if event queue was empty, stop receiving udev events
+    if (was_empty) {
+        NCDUdevClient_Pause(&o->client);
+    }
+}
+
+static void add_device (struct instance *o, const char *devname, const char *devpath, const char *device_type)
+{
+    ASSERT(!find_device_by_devname(o, devname))
+    ASSERT(!find_device_by_devpath(o, devpath))
+    
+    // allocate structure
+    struct device *device = malloc(sizeof(*device));
+    if (!device) {
+        ModuleLog(o->i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // init devname
+    if (!(device->devname = strdup(devname))) {
+        ModuleLog(o->i, BLOG_ERROR, "strdup failed");
+        goto fail1;
+    }
+    
+    // init devpath
+    if (!(device->devpath = strdup(devpath))) {
+        ModuleLog(o->i, BLOG_ERROR, "strdup failed");
+        goto fail2;
+    }
+    
+    // init removed map
+    if (!make_event_map(o, 0, devname, device_type, &device->removed_map)) {
+        ModuleLog(o->i, BLOG_ERROR, "make_event_map failed");
+        goto fail3;
+    }
+    
+    // init added map
+    BStringMap added_map;
+    if (!make_event_map(o, 1, devname, device_type, &added_map)) {
+        ModuleLog(o->i, BLOG_ERROR, "make_event_map failed");
+        goto fail4;
+    }
+    
+    // insert to devices list
+    LinkedList1_Append(&o->devices_list, &device->devices_list_node);
+    
+    // queue event
+    queue_event(o, added_map);
+    
+    return;
+    
+fail4:
+    BStringMap_Free(&device->removed_map);
+fail3:
+    free(device->devpath);
+fail2:
+    free(device->devname);
+fail1:
+    free(device);
+fail0:
+    ModuleLog(o->i, BLOG_ERROR, "failed to add device %s", devname);
+}
+
+static void remove_device (struct instance *o, struct device *device)
+{
+    queue_event(o, device->removed_map);
+    free_device(o, device, 0);
+}
+
+static void next_event (struct instance *o)
+{
+    ASSERT(event_template_is_enabled(&o->templ))
+    
+    // order template to finish the current event
+    int is_empty;
+    event_template_dequeue(&o->templ, &is_empty);
+    
+    // if template has no events, continue udev events
+    if (is_empty) {
+        NCDUdevClient_Continue(&o->client);
+    }
+}
+
+static void client_handler (struct instance *o, char *devpath, int have_map, BStringMap map)
+{
+    // lookup existing device with this devpath
+    struct device *ex_device = find_device_by_devpath(o, devpath);
+    // lookup cache entry
+    const BStringMap *cache_map = NCDUdevManager_Query(o->i->params->iparams->umanager, devpath);
+    
+    if (!cache_map) {
+        if (ex_device) {
+            remove_device(o, ex_device);
+        }
+        goto out;
+    }
+    
+    const char *subsystem = BStringMap_Get(cache_map, "SUBSYSTEM");
+    const char *devname = BStringMap_Get(cache_map, "DEVNAME");
+    
+    if (!(subsystem && !strcmp(subsystem, "input") && devname && devname_is_type(devname, o->devnode_type, o->devnode_type_len))) {
+        if (ex_device) {
+            remove_device(o, ex_device);
+        }
+        goto out;
+    }
+    
+    if (ex_device && strcmp(ex_device->devname, devname)) {
+        remove_device(o, ex_device);
+        ex_device = NULL;
+    }
+    
+    if (!ex_device) {
+        struct device *ex_devname_device = find_device_by_devname(o, devname);
+        if (ex_devname_device) {
+            remove_device(o, ex_devname_device);
+        }
+        
+        // determine type
+        const char *device_type = "unknown";
+        if (BStringMap_Get(cache_map, "ID_INPUT_TABLET")) {
+            device_type = "tablet";
+        }
+        else if (BStringMap_Get(cache_map, "ID_INPUT_JOYSTICK")) {
+            device_type = "joystick";
+        }
+        else if (BStringMap_Get(cache_map, "ID_INPUT_TOUCHSCREEN")) {
+            device_type = "touchscreen";
+        }
+        else if (BStringMap_Get(cache_map, "ID_INPUT_MOUSE")) {
+            device_type = "mouse";
+        }
+        else if (BStringMap_Get(cache_map, "ID_INPUT_TOUCHPAD")) {
+            device_type = "touchpad";
+        }
+        else if (BStringMap_Get(cache_map, "ID_INPUT_KEY")) {
+            device_type = "key";
+        }
+        else if (BStringMap_Get(cache_map, "ID_INPUT_KEYBOARD")) {
+            device_type = "keyboard";
+        }
+        
+        add_device(o, devname, devpath, device_type);
+    }
+    
+out:
+    free(devpath);
+    if (have_map) {
+        BStringMap_Free(&map);
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef devnode_type_arg;
+    if (!NCDVal_ListRead(params->args, 1, &devnode_type_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(devnode_type_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    o->devnode_type = NCDVal_StringData(devnode_type_arg);
+    o->devnode_type_len = NCDVal_StringLength(devnode_type_arg);
+    
+    // init client
+    NCDUdevClient_Init(&o->client, o->i->params->iparams->umanager, o, (NCDUdevClient_handler)client_handler);
+    
+    // init devices list
+    LinkedList1_Init(&o->devices_list);
+    
+    event_template_new(&o->templ, o->i, BLOG_CURRENT_CHANNEL, 3, o, (event_template_func_free)templ_func_free);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void templ_func_free (struct instance *o, int is_error)
+{
+    // free devices
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&o->devices_list)) {
+        struct device *device = UPPER_OBJECT(list_node, struct device, devices_list_node);
+        free_device(o, device, 1);
+    }
+    
+    // free client
+    NCDUdevClient_Free(&o->client);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    event_template_die(&o->templ);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    return event_template_getvar(&o->templ, name, mem, out);
+}
+
+static void nextevent_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure we are currently reporting an event
+    if (!event_template_is_enabled(&mo->templ)) {
+        ModuleLog(i, BLOG_ERROR, "not reporting an event");
+        goto fail0;
+    }
+    
+    // signal up.
+    // Do it before finishing the event so our process does not advance any further if
+    // we would be killed the event provider going down.
+    NCDModuleInst_Backend_Up(i);
+    
+    // wait for next event
+    next_event(mo);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "sys.watch_input",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "sys.watch_input::nextevent",
+        .func_new2 = nextevent_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_sys_watch_input = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/sys_watch_usb.c b/external/badvpn_dns/ncd/modules/sys_watch_usb.c
new file mode 100644
index 0000000..d2e6b1c
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/sys_watch_usb.c
@@ -0,0 +1,421 @@
+/**
+ * @file sys_watch_usb.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * USB device watcher.
+ * 
+ * Synopsis: sys.watch_usb()
+ * Description: reports USB device events. Transitions up when an event is detected, and
+ *   goes down waiting for the next event when ->nextevent() is called.
+ *   On startup, "added" events are reported for existing USB devices.
+ * 
+ * Variables:
+ *   string event_type - what happened with the USB device: "added" or "removed"
+ *   string devname - device node path, e.g. /dev/bus/usb/XXX/YYY
+ *   string vendor_id - vendor ID, e.g. 046d
+ *   string model_id - model ID, e.g. c03e
+ *   
+ * Synopsis: sys.watch_usb::nextevent()
+ * Description: makes the watch_usb module transition down in order to report the next event.
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/parse_number.h>
+#include <structure/LinkedList1.h>
+#include <udevmonitor/NCDUdevManager.h>
+#include <ncd/NCDModule.h>
+#include <ncd/modules/event_template.h>
+
+#include <generated/blog_channel_ncd_sys_watch_usb.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct device {
+    char *devname;
+    char *devpath;
+    uint16_t vendor_id;
+    uint16_t model_id;
+    BStringMap removed_map;
+    LinkedList1Node devices_list_node;
+};
+
+struct instance {
+    NCDModuleInst *i;
+    NCDUdevClient client;
+    LinkedList1 devices_list;
+    event_template templ;
+};
+
+static void templ_func_free (struct instance *o, int is_error);
+
+static struct device * find_device_by_devname (struct instance *o, const char *devname)
+{
+    for (LinkedList1Node *list_node = LinkedList1_GetFirst(&o->devices_list); list_node; list_node = LinkedList1Node_Next(list_node)) {
+        struct device *device = UPPER_OBJECT(list_node, struct device, devices_list_node);
+        if (!strcmp(device->devname, devname)) {
+            return device;
+        }
+    }
+    
+    return NULL;
+}
+
+static struct device * find_device_by_devpath (struct instance *o, const char *devpath)
+{
+    for (LinkedList1Node *list_node = LinkedList1_GetFirst(&o->devices_list); list_node; list_node = LinkedList1Node_Next(list_node)) {
+        struct device *device = UPPER_OBJECT(list_node, struct device, devices_list_node);
+        if (!strcmp(device->devpath, devpath)) {
+            return device;
+        }
+    }
+    
+    return NULL;
+}
+
+static void free_device (struct instance *o, struct device *device, int have_removed_map)
+{
+    // remove from devices list
+    LinkedList1_Remove(&o->devices_list, &device->devices_list_node);
+    
+    // free removed map
+    if (have_removed_map) {
+        BStringMap_Free(&device->removed_map);
+    }
+    
+    // free devpath
+    free(device->devpath);
+    
+    // free devname
+    free(device->devname);
+    
+    // free structure
+    free(device);
+}
+
+static int make_event_map (struct instance *o, int added, const char *devname, uint16_t vendor_id, uint16_t model_id, BStringMap *out_map)
+{
+    // init map
+    BStringMap map;
+    BStringMap_Init(&map);
+    
+    // set type
+    if (!BStringMap_Set(&map, "event_type", (added ? "added" : "removed"))) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    // set devname
+    if (!BStringMap_Set(&map, "devname", devname)) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    // set vendor ID
+    char vendor_id_str[5];
+    sprintf(vendor_id_str, "%04"PRIx16, vendor_id);
+    if (!BStringMap_Set(&map, "vendor_id", vendor_id_str)) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    // set model ID
+    char model_id_str[5];
+    sprintf(model_id_str, "%04"PRIx16, model_id);
+    if (!BStringMap_Set(&map, "model_id", model_id_str)) {
+        ModuleLog(o->i, BLOG_ERROR, "BStringMap_Set failed");
+        goto fail1;
+    }
+    
+    *out_map = map;
+    return 1;
+    
+fail1:
+    BStringMap_Free(&map);
+    return 0;
+}
+
+static void queue_event (struct instance *o, BStringMap map)
+{
+    // pass event to template
+    int was_empty;
+    event_template_queue(&o->templ, map, &was_empty);
+    
+    // if event queue was empty, stop receiving udev events
+    if (was_empty) {
+        NCDUdevClient_Pause(&o->client);
+    }
+}
+
+static void add_device (struct instance *o, const char *devname, const char *devpath, uint16_t vendor_id, uint16_t model_id)
+{
+    ASSERT(!find_device_by_devname(o, devname))
+    ASSERT(!find_device_by_devpath(o, devpath))
+    
+    // allocate structure
+    struct device *device = malloc(sizeof(*device));
+    if (!device) {
+        ModuleLog(o->i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // init devname
+    if (!(device->devname = strdup(devname))) {
+        ModuleLog(o->i, BLOG_ERROR, "strdup failed");
+        goto fail1;
+    }
+    
+    // init devpath
+    if (!(device->devpath = strdup(devpath))) {
+        ModuleLog(o->i, BLOG_ERROR, "strdup failed");
+        goto fail2;
+    }
+    
+    // set vendor and model ID
+    device->vendor_id = vendor_id;
+    device->model_id = model_id;
+    
+    // init removed map
+    if (!make_event_map(o, 0, devname, vendor_id, model_id, &device->removed_map)) {
+        ModuleLog(o->i, BLOG_ERROR, "make_event_map failed");
+        goto fail3;
+    }
+    
+    // init added map
+    BStringMap added_map;
+    if (!make_event_map(o, 1, devname, vendor_id, model_id, &added_map)) {
+        ModuleLog(o->i, BLOG_ERROR, "make_event_map failed");
+        goto fail4;
+    }
+    
+    // insert to devices list
+    LinkedList1_Append(&o->devices_list, &device->devices_list_node);
+    
+    // queue event
+    queue_event(o, added_map);
+    return;
+    
+fail4:
+    BStringMap_Free(&device->removed_map);
+fail3:
+    free(device->devpath);
+fail2:
+    free(device->devname);
+fail1:
+    free(device);
+fail0:
+    ModuleLog(o->i, BLOG_ERROR, "failed to add device %s", devname);
+}
+
+static void remove_device (struct instance *o, struct device *device)
+{
+    queue_event(o, device->removed_map);
+    free_device(o, device, 0);
+}
+
+static void next_event (struct instance *o)
+{
+    ASSERT(event_template_is_enabled(&o->templ))
+    
+    // order template to finish the current event
+    int is_empty;
+    event_template_dequeue(&o->templ, &is_empty);
+    
+    // if template has no events, continue udev events
+    if (is_empty) {
+        NCDUdevClient_Continue(&o->client);
+    }
+}
+
+static void client_handler (struct instance *o, char *devpath, int have_map, BStringMap map)
+{
+    // lookup existing device with this devpath
+    struct device *ex_device = find_device_by_devpath(o, devpath);
+    // lookup cache entry
+    const BStringMap *cache_map = NCDUdevManager_Query(o->i->params->iparams->umanager, devpath);
+    
+    if (!cache_map) {
+        if (ex_device) {
+            remove_device(o, ex_device);
+        }
+        goto out;
+    }
+    
+    const char *subsystem = BStringMap_Get(cache_map, "SUBSYSTEM");
+    const char *devname = BStringMap_Get(cache_map, "DEVNAME");
+    const char *devtype = BStringMap_Get(cache_map, "DEVTYPE");
+    const char *vendor_id_str = BStringMap_Get(cache_map, "ID_VENDOR_ID");
+    const char *model_id_str = BStringMap_Get(cache_map, "ID_MODEL_ID");
+    
+    uintmax_t vendor_id;
+    uintmax_t model_id;
+    
+    if (!(subsystem && !strcmp(subsystem, "usb") &&
+          devname &&
+          devtype && !strcmp(devtype, "usb_device") &&
+          vendor_id_str && parse_unsigned_hex_integer(vendor_id_str, &vendor_id) &&
+          model_id_str && parse_unsigned_hex_integer(model_id_str, &model_id)
+    )) {
+        if (ex_device) {
+            remove_device(o, ex_device);
+        }
+        goto out;
+    }
+    
+    if (ex_device && (
+        strcmp(ex_device->devname, devname) ||
+        ex_device->vendor_id != vendor_id || ex_device->model_id != model_id
+    )) {
+        remove_device(o, ex_device);
+        ex_device = NULL;
+    }
+    
+    if (!ex_device) {
+        struct device *ex_devname_device = find_device_by_devname(o, devname);
+        if (ex_devname_device) {
+            remove_device(o, ex_devname_device);
+        }
+        
+        add_device(o, devname, devpath, vendor_id, model_id);
+    }
+    
+out:
+    free(devpath);
+    if (have_map) {
+        BStringMap_Free(&map);
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // init client
+    NCDUdevClient_Init(&o->client, o->i->params->iparams->umanager, o, (NCDUdevClient_handler)client_handler);
+    
+    // init devices list
+    LinkedList1_Init(&o->devices_list);
+    
+    event_template_new(&o->templ, o->i, BLOG_CURRENT_CHANNEL, 3, o, (event_template_func_free)templ_func_free);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void templ_func_free (struct instance *o, int is_error)
+{
+    // free devices
+    while (!LinkedList1_IsEmpty(&o->devices_list)) {
+        struct device *device = UPPER_OBJECT(LinkedList1_GetFirst(&o->devices_list), struct device, devices_list_node);
+        free_device(o, device, 1);
+    }
+    
+    // free client
+    NCDUdevClient_Free(&o->client);
+    
+    if (is_error) {
+        NCDModuleInst_Backend_DeadError(o->i);
+    } else {
+        NCDModuleInst_Backend_Dead(o->i);
+    }
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    event_template_die(&o->templ);
+}
+
+static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    return event_template_getvar(&o->templ, name, mem, out);
+}
+
+static void nextevent_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // make sure we are currently reporting an event
+    if (!event_template_is_enabled(&mo->templ)) {
+        ModuleLog(i, BLOG_ERROR, "not reporting an event");
+        goto fail0;
+    }
+    
+    // signal up.
+    // Do it before finishing the event so our process does not advance any further if
+    // we would be killed the event provider going down.
+    NCDModuleInst_Backend_Up(i);
+    
+    // wait for next event
+    next_event(mo);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "sys.watch_usb",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar = func_getvar,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "sys.watch_usb::nextevent",
+        .func_new2 = nextevent_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_sys_watch_usb = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/timer.c b/external/badvpn_dns/ncd/modules/timer.c
new file mode 100644
index 0000000..6a748f9
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/timer.c
@@ -0,0 +1,146 @@
+/**
+ * @file timer.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   periodic_timer(string down_time, string up_time)
+ * 
+ * Description:
+ *   Periodically goes up and down. Starting in the down state, waits 'down_time'
+ *   milliseconds. Then it goes up, and after 'up_time' milliseconds, goes back down,
+ *   and the process repeats. When requested to terminate, terminates immedietely.
+ */
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDModule.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_timer.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define STATE_DOWN 1
+#define STATE_UP 2
+
+struct instance {
+    NCDModuleInst *i;
+    btime_t down_time;
+    btime_t up_time;
+    BTimer timer;
+    int state;
+};
+
+static void timer_handler (void *vo)
+{
+    struct instance *o = vo;
+    
+    switch (o->state) {
+        case STATE_DOWN: {
+            o->state = STATE_UP;
+            BReactor_SetTimerAfter(o->i->params->iparams->reactor, &o->timer, o->up_time);
+            NCDModuleInst_Backend_Up(o->i);
+        } break;
+        
+        case STATE_UP: {
+            o->state = STATE_DOWN;
+            BReactor_SetTimerAfter(o->i->params->iparams->reactor, &o->timer, o->down_time);
+            NCDModuleInst_Backend_Down(o->i);
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef down_time_arg;
+    NCDValRef up_time_arg;
+    if (!NCDVal_ListRead(params->args, 2, &down_time_arg, &up_time_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(down_time_arg) || !NCDVal_IsString(up_time_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // read times
+    if (!ncd_read_time(down_time_arg, &o->down_time)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong down time");
+        goto fail0;
+    }
+    if (!ncd_read_time(up_time_arg, &o->up_time)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong up time");
+        goto fail0;
+    }
+    
+    // init timer
+    BTimer_Init(&o->timer, 0, timer_handler, o);
+    
+    // set state down
+    o->state = STATE_DOWN;
+    
+    // set timer
+    BReactor_SetTimerAfter(o->i->params->iparams->reactor, &o->timer, o->down_time);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free timer
+    BReactor_RemoveTimer(o->i->params->iparams->reactor, &o->timer);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "periodic_timer",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_timer = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/to_string.c b/external/badvpn_dns/ncd/modules/to_string.c
new file mode 100644
index 0000000..2380c34
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/to_string.c
@@ -0,0 +1,116 @@
+/**
+ * @file to_string.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   to_string(value)
+ * Variables:
+ *   string (empty) - value, converted to string
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/NCDValGenerator.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_to_string.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    char *str;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read arguments
+    NCDValRef value_arg;
+    if (!NCDVal_ListRead(params->args, 1, &value_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // convert to string
+    if (!(o->str = NCDValGenerator_Generate(value_arg))) {
+        ModuleLog(i, BLOG_ERROR, "NCDValGenerator_Generate failed");
+        goto fail0;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free string
+    free(o->str);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewString(mem, o->str);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "to_string",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_to_string = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/try.c b/external/badvpn_dns/ncd/modules/try.c
new file mode 100644
index 0000000..4c4613c
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/try.c
@@ -0,0 +1,302 @@
+/**
+ * @file try.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   try(string template_name, list args)
+ * 
+ * Description:
+ *   Does the following:
+ *   1. Starts a template process from the specified template and arguments.
+ *   2. Waits for the process to initialize completely, or for a _try->assert()
+ *      assertion to fail.
+ *   3. Initiates termination of the process and waits for it to terminate.
+ *   4. Goes to up state. The "succeeded" variable reflects whether the process
+ *      managed to initialize, or an assertion failed.
+ *   If at any point during these steps termination of the try statement is
+ *   requested, requests the process to terminate (if not already), and dies
+ *   when it terminates.
+ * 
+ * Variables:
+ *   string succeeded - "true" if the template process finished, "false" if assert
+ *     was called.
+ * 
+ * Synopsis:
+ *   try.try::assert(string cond)
+ * 
+ * Description:
+ *   Call as _try->assert() from the template process. If cond is "true",
+ *   does nothing. Else, initiates termination of the process (if not already),
+ *   and marks the try operation as not succeeded.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_try.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+struct instance {
+    NCDModuleInst *i;
+    NCDModuleProcess process;
+    int state;
+    int dying;
+    int succeeded;
+};
+
+#define STATE_INIT 1
+#define STATE_DEINIT 2
+#define STATE_FINISHED 3
+
+static void process_handler_event (NCDModuleProcess *process, int event);
+static int process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object);
+static int process_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
+static void start_terminating (struct instance *o);
+static void instance_free (struct instance *o);
+
+enum {STRING_TRY, STRING_TRY_TRY};
+
+static const char *strings[] = {
+    "_try", "try.try", NULL
+};
+
+static void process_handler_event (NCDModuleProcess *process, int event)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(o->state == STATE_INIT)
+            
+            // start terminating
+            start_terminating(o);
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(o->state == STATE_INIT)
+            
+            // continue
+            NCDModuleProcess_Continue(&o->process);
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(o->state == STATE_DEINIT)
+            
+            // free process
+            NCDModuleProcess_Free(&o->process);
+            
+            // die finally if requested
+            if (o->dying) {
+                instance_free(o);
+                return;
+            }
+            
+            // signal up
+            NCDModuleInst_Backend_Up(o->i);
+            
+            // set state finished
+            o->state = STATE_FINISHED;
+        } break;
+    }
+}
+
+static int process_func_getspecialobj (NCDModuleProcess *process, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = UPPER_OBJECT(process, struct instance, process);
+    ASSERT(o->state == STATE_INIT || o->state == STATE_DEINIT)
+    
+    if (name == NCD_STRING_CALLER) {
+        *out_object = NCDObject_Build(-1, o, NCDObject_no_getvar, process_caller_object_func_getobj);
+        return 1;
+    }
+    
+    if (name == ModuleString(o->i, STRING_TRY)) {
+        *out_object = NCDObject_Build(ModuleString(o->i, STRING_TRY_TRY), o, NCDObject_no_getvar, NCDObject_no_getobj);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int process_caller_object_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
+{
+    struct instance *o = NCDObject_DataPtr(obj);
+    ASSERT(o->state == STATE_INIT || o->state == STATE_DEINIT)
+    
+    return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
+}
+
+static void start_terminating (struct instance *o)
+{
+    ASSERT(o->state == STATE_INIT)
+    
+    // request process termination
+    NCDModuleProcess_Terminate(&o->process);
+    
+    // set state deinit
+    o->state = STATE_DEINIT;
+}
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // check arguments
+    NCDValRef template_name_arg;
+    NCDValRef args_arg;
+    if (!NCDVal_ListRead(params->args, 2, &template_name_arg, &args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(template_name_arg) || !NCDVal_IsList(args_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    // start process
+    if (!NCDModuleProcess_InitValue(&o->process, i, template_name_arg, args_arg, process_handler_event)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        goto fail0;
+    }
+    
+    // set special object function
+    NCDModuleProcess_SetSpecialFuncs(&o->process, process_func_getspecialobj);
+    
+    // set state init, not dying, assume succeeded
+    o->state = STATE_INIT;
+    o->dying = 0;
+    o->succeeded = 1;
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void instance_free (struct instance *o)
+{   
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    ASSERT(!o->dying)
+    
+    // if we're finished, die immediately
+    if (o->state == STATE_FINISHED) {
+        instance_free(o);
+        return;
+    }
+    
+    // set dying
+    o->dying = 1;
+    
+    // start terminating if not already
+    if (o->state == STATE_INIT) {
+        start_terminating(o);
+    }
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    ASSERT(o->state == STATE_FINISHED)
+    ASSERT(!o->dying)
+    
+    if (name == NCD_STRING_SUCCEEDED) {
+        *out = ncd_make_boolean(mem, o->succeeded, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void assert_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // check arguments
+    NCDValRef cond_arg;
+    if (!NCDVal_ListRead(params->args, 1, &cond_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (!NCDVal_IsString(cond_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    
+    // get instance
+    struct instance *mo = params->method_user;
+    ASSERT(mo->state == STATE_INIT || mo->state == STATE_DEINIT)
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    
+    if (!NCDVal_StringEquals(cond_arg, "true")) {
+        // mark not succeeded
+        mo->succeeded = 0;
+        
+        // start terminating if not already
+        if (mo->state == STATE_INIT) {
+            start_terminating(mo);
+        }
+    }
+    
+    return;
+    
+fail1:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "try",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "try.try::assert",
+        .func_new2 = assert_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_try = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/value.c b/external/badvpn_dns/ncd/modules/value.c
new file mode 100644
index 0000000..41ccf35
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/value.c
@@ -0,0 +1,2102 @@
+/**
+ * @file value.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Synopsis:
+ *   value(value)
+ *   value value::get(where)
+ *   value value::try_get(where)
+ *   value value::getpath(list path)
+ *   value value::insert(where, what)
+ *   value value::insert(what)
+ *   value value::replace(where, what)
+ *   value value::replace_this(value)
+ *   value value::insert_undo(where, what)
+ *   value value::insert_undo(what)
+ *   value value::replace_undo(where, what)
+ *   value value::replace_this_undo(value)
+ * 
+ * Description:
+ *   Value objects allow examining and manipulating values.
+ *   These value objects are actually references to internal value structures, which
+ *   may be shared between value objects.
+ * 
+ *   value(value) constructs a new value object from the given value.
+ * 
+ *   value::get(where) constructs a value object for the element at position 'where'
+ *   (for a list), or the value corresponding to key 'where' (for a map). It is an
+ *   error if the base value is not a list or a map, the index is out of bounds of
+ *   the list, or the key does not exist in the map.
+ *   The resulting value object is NOT a copy, and shares (part of) the same
+ *   underlying value structure as the base value object. Deleting it will remove
+ *   it from the list or map it is part of.
+ * 
+ *   value::try_get(where) is like get(), except that if any restriction on 'where'
+ *   is violated, no error is triggered; instead, the value object is constructed
+ *   as being deleted; this state is exposed via the 'exists' variable.
+ *   This can be used to check for the presence of a key in a map, and in case it
+ *   exists, allow access to the corresponding value without another get() statement.
+ * 
+ *   value::getpath(path) is like get(), except that it performs multiple
+ *   consecutive resolutions. Also, if the path is an empty list, it performs
+ *   no resulution at all.
+ * 
+ *   value::insert(where, what) constructs a value object by inserting into an
+ *   existing value object.
+ *   For lists, 'where' is the index of the element to insert before, or the length
+ *   of the list to append to it.
+ *   For maps, 'where' is the key to insert under. If the key already exists in the
+ *   map, its value is replaced; any references to the old value however remain valid.
+ * 
+ *   value::insert(what) constructs a value object by appending to a list. An error
+ *   is triggered if the base value is not a list. Assuming 'list' is a list value,
+ *   list->insert(X) is equivalent to list->insert(list.length, X).
+ * 
+ *   value::replace(where, what) is like insert(), exept that, when inserting into a
+ *   list, the value at the specified index is replaced with the new value (unless
+ *   the index is equal to the length of the list).
+ * 
+ *   insert_undo() and replace_undo() are versions of insert() and replace() which
+ *   attempt to revert the modifications when they deinitialize.
+ *   Specifically, they work like that:
+ *   - On initiialization, they take an internal reference to the value being replaced
+ *     (if any; note that insert_undo() into a list never replaces a value).
+ *   - On deinitialization, they remove the the inserted value from its parent (if there
+ *     is one), and insert the old replaced value (to which a reference was kept) in that
+ *     place (if any, and assuming it has not been deleted).
+ *     Note that if the inserted value changes parents in between init and deinit, the
+ *     result of undoing may be unexpected.
+ * 
+ * Variables:
+ *   (empty) - the value stored in the value object
+ *   type - type of the value; "string", "list" or "map"
+ *   length - number of elements in the list or map, or the number of bytes in a
+ *            string
+ *   keys - a list of keys in the map (only if the value is a map)
+ *   exists - "true" or "false", reflecting whether the value object holds a value
+ *            (is not in deleted state)
+ * 
+ * Synopsis:
+ *   value::remove(where)
+ *   value::delete()
+ * 
+ * Description:
+ *   value::remove(where) removes from an existing value object.
+ *   For lists, 'where' is the index of the element to remove, and must be in range.
+ *   For maps, 'where' is the key to remove, and must be an existing key.
+ *   In any case, any references to the removed value remain valid.
+ * 
+ *   value::delete() deletes the underlying value data of this value object.
+ *   After delection, the value object enters a deleted state, which will cause any
+ *   operation on it to fail. Any other value objects which referred to the same value
+ *   or parts of it will too enter deleted state. If the value was an element
+ *   in a list or map, is is removed from it.
+ * 
+ * Synopsis:
+ *   value value::substr(string start [, string length])
+ * 
+ * Description:
+ *   Constructs a string value by extracting a part of a string.
+ *   'start' specifies the index of the character (from zero) where the substring to
+ *   extract starts, and must be <= the length of the string.
+ *   'length' specifies the maximum number of characters to extract, if given.
+ *   The newly constructed value is a copy of the extracted substring.
+ *   The value must be a string value.
+ * 
+ * Synopsis:
+ *   value::reset(what)
+ * 
+ * Description:
+ *   Effectively deconstructs and reconstructs the value object. More precisely,
+ *   it builds a new value structure from 'what', possibly invokes a scheduled undo
+ *   operation (as scheduled by insert_undo() and replace_undo()), sets up this
+ *   value object to reference the newly built value structure, without any scheduled
+ *   undo operation.
+ * 
+ * Synopsis:
+ *   value::append(append_val)
+ * 
+ * Description:
+ *   Only defined when the existing value object is a string or list. If it is a string,
+ *   appends the string 'append_val' to this string value; 'append_val' must be a string.
+ *   If is is a list, inserts 'append_val' to the end of this list value. Unlike insert(),
+ *   the resulting append() object is not itself a value object (which in case of insert()
+ *   serves as a reference to the new value).
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <limits.h>
+#include <inttypes.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <structure/LinkedList0.h>
+#include <structure/IndexedList.h>
+#include <structure/SAvl.h>
+#include <ncd/NCDModule.h>
+#include <ncd/NCDStringIndex.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_value.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define ModuleString(i, id) ((i)->m->group->strings[(id)])
+
+#define STOREDSTRING_TYPE (NCDVAL_STRING | (0 << 3))
+#define IDSTRING_TYPE (NCDVAL_STRING | (1 << 3))
+#define EXTERNALSTRING_TYPE (NCDVAL_STRING | (2 << 3))
+#define COMPOSEDSTRING_TYPE (NCDVAL_STRING | (3 << 3))
+
+struct value;
+
+#include "value_maptree.h"
+#include <structure/SAvl_decl.h>
+
+struct valref {
+    struct value *v;
+    LinkedList0Node refs_list_node;
+};
+
+typedef void (*value_deinit_func) (void *deinit_data, NCDModuleInst *i);
+
+struct instance {
+    NCDModuleInst *i;
+    struct valref ref;
+    value_deinit_func deinit_func;
+    void *deinit_data;
+};
+
+struct value {
+    LinkedList0 refs_list;
+    
+    struct value *parent;
+    union {
+        struct {
+            IndexedListNode list_contents_il_node;
+        } list_parent;
+        struct {
+            NCDValMem key_mem;
+            NCDValRef key;
+            MapTreeNode maptree_node;
+        } map_parent;
+    };
+    
+    int type;
+    union {
+        struct {
+            char *data;
+            size_t length;
+        } storedstring;
+        struct {
+            NCD_string_id_t id;
+            NCDStringIndex *string_index;
+        } idstring;
+        struct {
+            const char *data;
+            size_t length;
+            BRefTarget *ref_target;
+        } externalstring;
+        struct {
+            NCDValComposedStringResource resource;
+            size_t offset;
+            size_t length;
+        } composedstring;
+        struct {
+            IndexedList list_contents_il;
+        } list;
+        struct {
+            MapTree map_tree;
+        } map;
+    };
+};
+
+static const char * get_type_str (int type);
+static void value_cleanup (struct value *v);
+static void value_delete (struct value *v);
+static struct value * value_init_storedstring (NCDModuleInst *i, const char *str, size_t len);
+static struct value * value_init_idstring (NCDModuleInst *i, NCD_string_id_t id, NCDStringIndex *string_index);
+static struct value * value_init_externalstring (NCDModuleInst *i, const char *data, size_t length, BRefTarget *ref_target);
+static struct value * value_init_composedstring (NCDModuleInst *i, NCDValComposedStringResource resource, size_t offset, size_t length);
+static int value_is_string (struct value *v);
+static size_t value_string_length (struct value *v);
+static void value_string_copy_out (struct value *v, char *dest);
+static void value_string_set_allocd (struct value *v, char *data, size_t length);
+static struct value * value_init_list (NCDModuleInst *i);
+static size_t value_list_len (struct value *v);
+static struct value * value_list_at (struct value *v, size_t index);
+static size_t value_list_indexof (struct value *v, struct value *ev);
+static int value_list_insert (NCDModuleInst *i, struct value *list, struct value *v, size_t index);
+static void value_list_remove (struct value *list, struct value *v);
+static struct value * value_init_map (NCDModuleInst *i);
+static size_t value_map_len (struct value *map);
+static struct value * value_map_at (struct value *map, size_t index);
+static struct value * value_map_find (struct value *map, NCDValRef key);
+static int value_map_insert (struct value *map, struct value *v, NCDValMem mem, NCDValSafeRef key, NCDModuleInst *i);
+static void value_map_remove (struct value *map, struct value *v);
+static void value_map_remove2 (struct value *map, struct value *v, NCDValMem *out_mem, NCDValSafeRef *out_key);
+static struct value * value_init_fromvalue (NCDModuleInst *i, NCDValRef value);
+static int value_to_value (NCDModuleInst *i, struct value *v, NCDValMem *mem, NCDValRef *out_value);
+static struct value * value_get (NCDModuleInst *i, struct value *v, NCDValRef where, int no_error);
+static struct value * value_get_path (NCDModuleInst *i, struct value *v, NCDValRef path);
+static struct value * value_insert (NCDModuleInst *i, struct value *v, NCDValRef where, NCDValRef what, int is_replace, struct value **out_oldv);
+static struct value * value_insert_simple (NCDModuleInst *i, struct value *v, NCDValRef what);
+static int value_remove (NCDModuleInst *i, struct value *v, NCDValRef where);
+static int value_append (NCDModuleInst *i, struct value *v, NCDValRef data);
+static void valref_init (struct valref *r, struct value *v);
+static void valref_free (struct valref *r);
+static struct value * valref_val (struct valref *r);
+static void valref_break (struct valref *r);
+
+enum {STRING_EXISTS, STRING_KEYS};
+
+static const char *strings[] = {
+    "exists", "keys", NULL
+};
+
+#include "value_maptree.h"
+#include <structure/SAvl_impl.h>
+
+static const char * get_type_str (int type)
+{
+    switch (type) {
+        case STOREDSTRING_TYPE:
+        case IDSTRING_TYPE:
+        case EXTERNALSTRING_TYPE:
+        case COMPOSEDSTRING_TYPE:
+            return "string";
+        case NCDVAL_LIST:
+            return "list";
+        case NCDVAL_MAP:
+            return "map";
+    }
+    ASSERT(0)
+    return NULL;
+}
+
+static void value_cleanup (struct value *v)
+{
+    if (v->parent || !LinkedList0_IsEmpty(&v->refs_list)) {
+        return;
+    }
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE: {
+            BFree(v->storedstring.data);
+        } break;
+        
+        case IDSTRING_TYPE: {
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            if (v->externalstring.ref_target) {
+                BRefTarget_Deref(v->externalstring.ref_target);
+            }
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            if (v->composedstring.resource.ref_target) {
+                BRefTarget_Deref(v->composedstring.resource.ref_target);
+            }
+        } break;
+        
+        case NCDVAL_LIST: {
+            while (value_list_len(v) > 0) {
+                struct value *ev = value_list_at(v, 0);
+                value_list_remove(v, ev);
+                value_cleanup(ev);
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            while (value_map_len(v) > 0) {
+                struct value *ev = value_map_at(v, 0);
+                value_map_remove(v, ev);
+                value_cleanup(ev);
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    free(v);
+}
+
+static void value_delete (struct value *v)
+{
+    if (v->parent) {
+        switch (v->parent->type) {
+            case NCDVAL_LIST: {
+                value_list_remove(v->parent, v);
+            } break;
+            case NCDVAL_MAP: {
+                value_map_remove(v->parent, v);
+            } break;
+            default: ASSERT(0);
+        }
+    }
+    
+    LinkedList0Node *ln;
+    while (ln = LinkedList0_GetFirst(&v->refs_list)) {
+        struct valref *r = UPPER_OBJECT(ln, struct valref, refs_list_node);
+        ASSERT(r->v == v)
+        valref_break(r);
+    }
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE: {
+            BFree(v->storedstring.data);
+        } break;
+        
+        case IDSTRING_TYPE: {
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            if (v->externalstring.ref_target) {
+                BRefTarget_Deref(v->externalstring.ref_target);
+            }
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            if (v->composedstring.resource.ref_target) {
+                BRefTarget_Deref(v->composedstring.resource.ref_target);
+            }
+        } break;
+        
+        case NCDVAL_LIST: {
+            while (value_list_len(v) > 0) {
+                struct value *ev = value_list_at(v, 0);
+                value_delete(ev);
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            while (value_map_len(v) > 0) {
+                struct value *ev = value_map_at(v, 0);
+                value_delete(ev);
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    free(v);
+}
+
+static struct value * value_init_storedstring (NCDModuleInst *i, const char *str, size_t len)
+{
+    struct value *v = malloc(sizeof(*v));
+    if (!v) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    LinkedList0_Init(&v->refs_list);
+    v->parent = NULL;
+    v->type = STOREDSTRING_TYPE;
+    
+    if (!(v->storedstring.data = BAlloc(len))) {
+        ModuleLog(i, BLOG_ERROR, "BAlloc failed");
+        goto fail1;
+    }
+    
+    if (str) {
+        memcpy(v->storedstring.data, str, len);
+    }
+    
+    v->storedstring.length = len;
+    
+    return v;
+    
+fail1:
+    free(v);
+fail0:
+    return NULL;
+}
+
+static struct value * value_init_idstring (NCDModuleInst *i, NCD_string_id_t id, NCDStringIndex *string_index)
+{
+    ASSERT(string_index == i->params->iparams->string_index)
+    
+    struct value *v = malloc(sizeof(*v));
+    if (!v) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    LinkedList0_Init(&v->refs_list);
+    v->parent = NULL;
+    v->type = IDSTRING_TYPE;
+    
+    v->idstring.id = id;
+    v->idstring.string_index = string_index;
+    
+    return v;
+    
+fail0:
+    return NULL;
+}
+
+static struct value * value_init_externalstring (NCDModuleInst *i, const char *data, size_t length, BRefTarget *ref_target)
+{
+    struct value *v = malloc(sizeof(*v));
+    if (!v) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    if (ref_target) {
+        if (!BRefTarget_Ref(ref_target)) {
+            ModuleLog(i, BLOG_ERROR, "BRefTarget_Ref failed");
+            goto fail1;
+        }
+    }
+    
+    LinkedList0_Init(&v->refs_list);
+    v->parent = NULL;
+    v->type = EXTERNALSTRING_TYPE;
+    
+    v->externalstring.data = data;
+    v->externalstring.length = length;
+    v->externalstring.ref_target = ref_target;
+    
+    return v;
+    
+fail1:
+    free(v);
+fail0:
+    return NULL;
+}
+
+static struct value * value_init_composedstring (NCDModuleInst *i, NCDValComposedStringResource resource, size_t offset, size_t length)
+{
+    struct value *v = malloc(sizeof(*v));
+    if (!v) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    if (resource.ref_target) {
+        if (!BRefTarget_Ref(resource.ref_target)) {
+            ModuleLog(i, BLOG_ERROR, "BRefTarget_Ref failed");
+            goto fail1;
+        }
+    }
+    
+    LinkedList0_Init(&v->refs_list);
+    v->parent = NULL;
+    v->type = COMPOSEDSTRING_TYPE;
+    
+    v->composedstring.resource = resource;
+    v->composedstring.offset = offset;
+    v->composedstring.length = length;
+    
+    return v;
+    
+fail1:
+    free(v);
+fail0:
+    return NULL;
+}
+
+static int value_is_string (struct value *v)
+{
+    switch (v->type) {
+        case STOREDSTRING_TYPE:
+        case IDSTRING_TYPE:
+        case EXTERNALSTRING_TYPE:
+        case COMPOSEDSTRING_TYPE:
+            return 1;
+        default:
+            return 0;
+    }
+}
+
+static size_t value_string_length (struct value *v)
+{
+    ASSERT(value_is_string(v))
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE:
+            return v->storedstring.length;
+        case IDSTRING_TYPE:
+            return NCDStringIndex_Length(v->idstring.string_index, v->idstring.id);
+        case EXTERNALSTRING_TYPE:
+            return v->externalstring.length;
+        case COMPOSEDSTRING_TYPE:
+            return v->composedstring.length;
+        default:
+            ASSERT(0);
+            return 0;
+    }
+}
+
+static void value_string_copy_out (struct value *v, char *dest)
+{
+    ASSERT(value_is_string(v))
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE:
+            memcpy(dest, v->storedstring.data, v->storedstring.length);
+            break;
+        case IDSTRING_TYPE:
+            memcpy(dest, NCDStringIndex_Value(v->idstring.string_index, v->idstring.id), NCDStringIndex_Length(v->idstring.string_index, v->idstring.id));
+            break;
+        case EXTERNALSTRING_TYPE:
+            memcpy(dest, v->externalstring.data, v->externalstring.length);
+            break;
+        case COMPOSEDSTRING_TYPE: {
+            b_cstring cstr = NCDValComposedStringResource_Cstring(v->composedstring.resource, v->composedstring.offset, v->composedstring.length);
+            b_cstring_copy_to_buf(cstr, 0, cstr.length, dest);
+        } break;
+        default:
+            ASSERT(0);
+    }
+}
+
+static void value_string_set_allocd (struct value *v, char *data, size_t length)
+{
+    ASSERT(value_is_string(v))
+    ASSERT(data)
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE: {
+            BFree(v->storedstring.data);
+        } break;
+        
+        case IDSTRING_TYPE: {
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            if (v->externalstring.ref_target) {
+                BRefTarget_Deref(v->externalstring.ref_target);
+            }
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            if (v->composedstring.resource.ref_target) {
+                BRefTarget_Deref(v->composedstring.resource.ref_target);
+            }
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+    
+    v->type = STOREDSTRING_TYPE;
+    v->storedstring.data = data;
+    v->storedstring.length = length;
+}
+
+static struct value * value_init_list (NCDModuleInst *i)
+{
+    struct value *v = malloc(sizeof(*v));
+    if (!v) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        return NULL;
+    }
+    
+    LinkedList0_Init(&v->refs_list);
+    v->parent = NULL;
+    v->type = NCDVAL_LIST;
+    
+    IndexedList_Init(&v->list.list_contents_il);
+    
+    return v;
+}
+
+static size_t value_list_len (struct value *v)
+{
+    ASSERT(v->type == NCDVAL_LIST)
+    
+    return IndexedList_Count(&v->list.list_contents_il);
+}
+
+static struct value * value_list_at (struct value *v, size_t index)
+{
+    ASSERT(v->type == NCDVAL_LIST)
+    ASSERT(index < value_list_len(v))
+    
+    IndexedListNode *iln = IndexedList_GetAt(&v->list.list_contents_il, index);
+    ASSERT(iln)
+    
+    struct value *e = UPPER_OBJECT(iln, struct value, list_parent.list_contents_il_node);
+    ASSERT(e->parent == v)
+    
+    return e;
+}
+
+static size_t value_list_indexof (struct value *v, struct value *ev)
+{
+    ASSERT(v->type == NCDVAL_LIST)
+    ASSERT(ev->parent == v)
+    
+    uint64_t index = IndexedList_IndexOf(&v->list.list_contents_il, &ev->list_parent.list_contents_il_node);
+    ASSERT(index < value_list_len(v))
+    
+    return index;
+}
+
+static int value_list_insert (NCDModuleInst *i, struct value *list, struct value *v, size_t index)
+{
+    ASSERT(list->type == NCDVAL_LIST)
+    ASSERT(!v->parent)
+    ASSERT(index <= value_list_len(list))
+    
+    if (value_list_len(list) == SIZE_MAX) {
+        ModuleLog(i, BLOG_ERROR, "list has too many elements");
+        return 0;
+    }
+    
+    IndexedList_InsertAt(&list->list.list_contents_il, &v->list_parent.list_contents_il_node, index);
+    v->parent = list;
+    
+    return 1;
+}
+
+static void value_list_remove (struct value *list, struct value *v)
+{
+    ASSERT(list->type == NCDVAL_LIST)
+    ASSERT(v->parent == list)
+    
+    IndexedList_Remove(&list->list.list_contents_il, &v->list_parent.list_contents_il_node);
+    v->parent = NULL;
+}
+
+static struct value * value_init_map (NCDModuleInst *i)
+{
+    struct value *v = malloc(sizeof(*v));
+    if (!v) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        return NULL;
+    }
+    
+    LinkedList0_Init(&v->refs_list);
+    v->parent = NULL;
+    v->type = NCDVAL_MAP;
+    
+    MapTree_Init(&v->map.map_tree);
+    
+    return v;
+}
+
+static size_t value_map_len (struct value *map)
+{
+    ASSERT(map->type == NCDVAL_MAP)
+    
+    return MapTree_Count(&map->map.map_tree, 0);
+}
+
+static struct value * value_map_at (struct value *map, size_t index)
+{
+    ASSERT(map->type == NCDVAL_MAP)
+    ASSERT(index < value_map_len(map))
+    
+    struct value *e =  MapTree_GetAt(&map->map.map_tree, 0, index);
+    ASSERT(e)
+    ASSERT(e->parent == map)
+    
+    return e;
+}
+
+static struct value * value_map_find (struct value *map, NCDValRef key)
+{
+    ASSERT(map->type == NCDVAL_MAP)
+    ASSERT(NCDVal_Type(key))
+    
+    struct value *e = MapTree_LookupExact(&map->map.map_tree, 0, key);
+    ASSERT(!e || e->parent == map)
+    
+    return e;
+}
+
+static int value_map_insert (struct value *map, struct value *v, NCDValMem mem, NCDValSafeRef key, NCDModuleInst *i)
+{
+    ASSERT(map->type == NCDVAL_MAP)
+    ASSERT(!v->parent)
+    ASSERT((NCDVal_Type(NCDVal_FromSafe(&mem, key)), 1))
+    ASSERT(!value_map_find(map, NCDVal_FromSafe(&mem, key)))
+    
+    if (value_map_len(map) == SIZE_MAX) {
+        ModuleLog(i, BLOG_ERROR, "map has too many elements");
+        return 0;
+    }
+    
+    v->map_parent.key_mem = mem;
+    v->map_parent.key = NCDVal_FromSafe(&v->map_parent.key_mem, key);
+    int res = MapTree_Insert(&map->map.map_tree, 0, v, NULL);
+    ASSERT_EXECUTE(res)
+    v->parent = map;
+    
+    return 1;
+}
+
+static void value_map_remove (struct value *map, struct value *v)
+{
+    ASSERT(map->type == NCDVAL_MAP)
+    ASSERT(v->parent == map)
+    
+    MapTree_Remove(&map->map.map_tree, 0, v);
+    NCDValMem_Free(&v->map_parent.key_mem);
+    v->parent = NULL;
+}
+
+static void value_map_remove2 (struct value *map, struct value *v, NCDValMem *out_mem, NCDValSafeRef *out_key)
+{
+    ASSERT(map->type == NCDVAL_MAP)
+    ASSERT(v->parent == map)
+    ASSERT(out_mem)
+    ASSERT(out_key)
+    
+    MapTree_Remove(&map->map.map_tree, 0, v);
+    *out_mem = v->map_parent.key_mem;
+    *out_key = NCDVal_ToSafe(v->map_parent.key);
+    v->parent = NULL;
+}
+
+static struct value * value_init_fromvalue (NCDModuleInst *i, NCDValRef value)
+{
+    ASSERT((NCDVal_Type(value), 1))
+    
+    struct value *v;
+    
+    switch (NCDVal_Type(value)) {
+        case NCDVAL_STRING: {
+            if (NCDVal_IsStoredString(value)) {
+                v = value_init_storedstring(i, NCDVal_StringData(value), NCDVal_StringLength(value));
+            } else if (NCDVal_IsIdString(value)) {
+                v = value_init_idstring(i, NCDVal_IdStringId(value), NCDVal_IdStringStringIndex(value));
+            } else if (NCDVal_IsExternalString(value)) {
+                v = value_init_externalstring(i, NCDVal_StringData(value), NCDVal_StringLength(value), NCDVal_ExternalStringTarget(value));
+            } else if (NCDVal_IsComposedString(value)) {
+                v = value_init_composedstring(i, NCDVal_ComposedStringResource(value), NCDVal_ComposedStringOffset(value), NCDVal_StringLength(value));
+            } else {
+                size_t length = NCDVal_StringLength(value);
+                v = value_init_storedstring(i, NULL, length);
+                if (v) {
+                    NCDVal_StringCopyOut(value, 0, length, v->storedstring.data);
+                }
+            }
+            if (!v) {
+                goto fail0;
+            }
+        } break;
+        
+        case NCDVAL_LIST: {
+            if (!(v = value_init_list(i))) {
+                goto fail0;
+            }
+            
+            size_t count = NCDVal_ListCount(value);
+            
+            for (size_t j = 0; j < count; j++) {
+                struct value *ev = value_init_fromvalue(i, NCDVal_ListGet(value, j));
+                if (!ev) {
+                    goto fail1;
+                }
+                
+                if (!value_list_insert(i, v, ev, value_list_len(v))) {
+                    value_cleanup(ev);
+                    goto fail1;
+                }
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            if (!(v = value_init_map(i))) {
+                goto fail0;
+            }
+            
+            for (NCDValMapElem e = NCDVal_MapFirst(value); !NCDVal_MapElemInvalid(e); e = NCDVal_MapNext(value, e)) {
+                NCDValRef ekey = NCDVal_MapElemKey(value, e);
+                NCDValRef eval = NCDVal_MapElemVal(value, e);
+                
+                NCDValMem key_mem;
+                NCDValMem_Init(&key_mem);
+                
+                NCDValRef key = NCDVal_NewCopy(&key_mem, ekey);
+                if (NCDVal_IsInvalid(key)) {
+                    NCDValMem_Free(&key_mem);
+                    goto fail1;
+                }
+                
+                struct value *ev = value_init_fromvalue(i, eval);
+                if (!ev) {
+                    NCDValMem_Free(&key_mem);
+                    goto fail1;
+                }
+                
+                if (!value_map_insert(v, ev, key_mem, NCDVal_ToSafe(key), i)) {
+                    NCDValMem_Free(&key_mem);
+                    value_cleanup(ev);
+                    goto fail1;
+                }
+            }
+        } break;
+        
+        default:
+            ASSERT(0);
+            return NULL;
+    }
+    
+    return v;
+    
+fail1:
+    value_cleanup(v);
+fail0:
+    return NULL;
+}
+
+static int value_to_value (NCDModuleInst *i, struct value *v, NCDValMem *mem, NCDValRef *out_value)
+{
+    ASSERT(mem)
+    ASSERT(out_value)
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE: {
+            *out_value = NCDVal_NewStringBin(mem, (const uint8_t *)v->storedstring.data, v->storedstring.length);
+            if (NCDVal_IsInvalid(*out_value)) {
+                goto fail;
+            }
+        } break;
+        
+        case IDSTRING_TYPE: {
+            *out_value = NCDVal_NewIdString(mem, v->idstring.id, v->idstring.string_index);
+            if (NCDVal_IsInvalid(*out_value)) {
+                goto fail;
+            }
+        } break;
+        
+        case EXTERNALSTRING_TYPE: {
+            *out_value = NCDVal_NewExternalString(mem, v->externalstring.data, v->externalstring.length, v->externalstring.ref_target);
+            if (NCDVal_IsInvalid(*out_value)) {
+                goto fail;
+            }
+        } break;
+        
+        case COMPOSEDSTRING_TYPE: {
+            *out_value = NCDVal_NewComposedString(mem, v->composedstring.resource, v->composedstring.offset, v->composedstring.length);
+            if (NCDVal_IsInvalid(*out_value)) {
+                goto fail;
+            }
+        } break;
+        
+        case NCDVAL_LIST: {
+            *out_value = NCDVal_NewList(mem, value_list_len(v));
+            if (NCDVal_IsInvalid(*out_value)) {
+                goto fail;
+            }
+            
+            for (size_t index = 0; index < value_list_len(v); index++) {
+                NCDValRef eval;
+                if (!value_to_value(i, value_list_at(v, index), mem, &eval)) {
+                    goto fail;
+                }
+                
+                if (!NCDVal_ListAppend(*out_value, eval)) {
+                    goto fail;
+                }
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            *out_value = NCDVal_NewMap(mem, value_map_len(v));
+            if (NCDVal_IsInvalid(*out_value)) {
+                goto fail;
+            }
+            
+            for (size_t index = 0; index < value_map_len(v); index++) {
+                struct value *ev = value_map_at(v, index);
+                
+                NCDValRef key = NCDVal_NewCopy(mem, ev->map_parent.key);
+                if (NCDVal_IsInvalid(key)) {
+                    goto fail;
+                }
+                
+                NCDValRef val;
+                if (!value_to_value(i, ev, mem, &val)) {
+                    goto fail;
+                }
+                
+                int inserted;
+                if (!NCDVal_MapInsert(*out_value, key, val, &inserted)) {
+                    goto fail;
+                }
+                ASSERT_EXECUTE(inserted)
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+static struct value * value_get (NCDModuleInst *i, struct value *v, NCDValRef where, int no_error)
+{
+    ASSERT((NCDVal_Type(where), 1))
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE:
+        case IDSTRING_TYPE:
+        case EXTERNALSTRING_TYPE:
+        case COMPOSEDSTRING_TYPE: {
+            if (!no_error) ModuleLog(i, BLOG_ERROR, "cannot resolve into a string");
+            goto fail;
+        } break;
+        
+        case NCDVAL_LIST: {
+            uintmax_t index;
+            if (!NCDVal_IsString(where) || !ncd_read_uintmax(where, &index)) {
+                if (!no_error) ModuleLog(i, BLOG_ERROR, "index is not a valid number (resolving into list)");
+                goto fail;
+            }
+            
+            if (index >= value_list_len(v)) {
+                if (!no_error) ModuleLog(i, BLOG_ERROR, "index is out of bounds (resolving into list)");
+                goto fail;
+            }
+            
+            v = value_list_at(v, index);
+        } break;
+        
+        case NCDVAL_MAP: {
+            v = value_map_find(v, where);
+            if (!v) {
+                if (!no_error) ModuleLog(i, BLOG_ERROR, "key does not exist (resolving into map)");
+                goto fail;
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    return v;
+    
+fail:
+    return NULL;
+}
+
+static struct value * value_get_path (NCDModuleInst *i, struct value *v, NCDValRef path)
+{
+    ASSERT(NCDVal_IsList(path))
+    
+    size_t count = NCDVal_ListCount(path);
+    
+    for (size_t j = 0; j < count; j++) {
+        if (!(v = value_get(i, v, NCDVal_ListGet(path, j), 0))) {
+            goto fail;
+        }
+    }
+    
+    return v;
+    
+fail:
+    return NULL;
+}
+
+static struct value * value_insert (NCDModuleInst *i, struct value *v, NCDValRef where, NCDValRef what, int is_replace, struct value **out_oldv)
+{
+    ASSERT(v)
+    ASSERT((NCDVal_Type(where), 1))
+    ASSERT((NCDVal_Type(what), 1))
+    ASSERT(is_replace == !!is_replace)
+    
+    struct value *nv = value_init_fromvalue(i, what);
+    if (!nv) {
+        goto fail0;
+    }
+    
+    struct value *oldv = NULL;
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE:
+        case IDSTRING_TYPE:
+        case EXTERNALSTRING_TYPE:
+        case COMPOSEDSTRING_TYPE: {
+            ModuleLog(i, BLOG_ERROR, "cannot insert into a string");
+            goto fail1;
+        } break;
+        
+        case NCDVAL_LIST: {
+            uintmax_t index;
+            if (!NCDVal_IsString(where) || !ncd_read_uintmax(where, &index)) {
+                ModuleLog(i, BLOG_ERROR, "index is not a valid number (inserting into list)");
+                goto fail1;
+            }
+            
+            if (index > value_list_len(v)) {
+                ModuleLog(i, BLOG_ERROR, "index is out of bounds (inserting into list)");
+                goto fail1;
+            }
+            
+            if (is_replace && index < value_list_len(v)) {
+                oldv = value_list_at(v, index);
+                
+                value_list_remove(v, oldv);
+                
+                int res = value_list_insert(i, v, nv, index);
+                ASSERT_EXECUTE(res)
+            } else {
+                if (!value_list_insert(i, v, nv, index)) {
+                    goto fail1;
+                }
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            oldv = value_map_find(v, where);
+            
+            if (!oldv && value_map_len(v) == SIZE_MAX) {
+                ModuleLog(i, BLOG_ERROR, "map has too many elements");
+                goto fail1;
+            }
+            
+            NCDValMem key_mem;
+            NCDValMem_Init(&key_mem);
+            
+            NCDValRef key = NCDVal_NewCopy(&key_mem, where);
+            if (NCDVal_IsInvalid(key)) {
+                NCDValMem_Free(&key_mem);
+                goto fail1;
+            }
+            
+            if (oldv) {
+                value_map_remove(v, oldv);
+            }
+            
+            int res = value_map_insert(v, nv, key_mem, NCDVal_ToSafe(key), i);
+            ASSERT_EXECUTE(res)
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    if (out_oldv) {
+        *out_oldv = oldv;
+    }
+    else if (oldv) {
+        value_cleanup(oldv);
+    }
+    
+    return nv;
+    
+fail1:
+    value_cleanup(nv);
+fail0:
+    return NULL;
+}
+
+static struct value * value_insert_simple (NCDModuleInst *i, struct value *v, NCDValRef what)
+{
+    ASSERT(v)
+    ASSERT((NCDVal_Type(what), 1))
+    
+    struct value *nv = value_init_fromvalue(i, what);
+    if (!nv) {
+        goto fail0;
+    }
+    
+    switch (v->type) {
+        case NCDVAL_LIST: {
+            if (!value_list_insert(i, v, nv, value_list_len(v))) {
+                goto fail1;
+            }
+        } break;
+        
+        default:
+            ModuleLog(i, BLOG_ERROR, "one-argument insert is only defined for lists");
+            return NULL;
+    }
+    
+    return nv;
+    
+fail1:
+    value_cleanup(nv);
+fail0:
+    return NULL;
+}
+
+static int value_remove (NCDModuleInst *i, struct value *v, NCDValRef where)
+{
+    ASSERT(v)
+    ASSERT((NCDVal_Type(where), 1))
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE:
+        case IDSTRING_TYPE:
+        case EXTERNALSTRING_TYPE:
+        case COMPOSEDSTRING_TYPE: {
+            ModuleLog(i, BLOG_ERROR, "cannot remove from a string");
+            goto fail;
+        } break;
+        
+        case NCDVAL_LIST: {
+            uintmax_t index;
+            if (!NCDVal_IsString(where) || !ncd_read_uintmax(where, &index)) {
+                ModuleLog(i, BLOG_ERROR, "index is not a valid number (removing from list)");
+                goto fail;
+            }
+            
+            if (index >= value_list_len(v)) {
+                ModuleLog(i, BLOG_ERROR, "index is out of bounds (removing from list)");
+                goto fail;
+            }
+            
+            struct value *ov = value_list_at(v, index);
+            
+            value_list_remove(v, ov);
+            value_cleanup(ov);
+        } break;
+        
+        case NCDVAL_MAP: {
+            struct value *ov = value_map_find(v, where);
+            if (!ov) {
+                ModuleLog(i, BLOG_ERROR, "key does not exist (removing from map)");
+                goto fail;
+            }
+            
+            value_map_remove(v, ov);
+            value_cleanup(ov);
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+static int value_append (NCDModuleInst *i, struct value *v, NCDValRef data)
+{
+    ASSERT(v)
+    ASSERT((NCDVal_Type(data), 1))
+    
+    switch (v->type) {
+        case STOREDSTRING_TYPE: {
+            if (!NCDVal_IsString(data)) {
+                ModuleLog(i, BLOG_ERROR, "cannot append non-string to string");
+                return 0;
+            }
+            
+            size_t append_length = NCDVal_StringLength(data);
+            
+            if (append_length > SIZE_MAX - v->storedstring.length) {
+                ModuleLog(i, BLOG_ERROR, "too much data to append");
+                return 0;
+            }
+            size_t new_length = v->storedstring.length + append_length;
+            
+            char *new_string = BRealloc(v->storedstring.data, new_length);
+            if (!new_string) {
+                ModuleLog(i, BLOG_ERROR, "BRealloc failed");
+                return 0;
+            }
+            
+            NCDVal_StringCopyOut(data, 0, append_length, new_string + v->storedstring.length);
+            
+            v->storedstring.data = new_string;
+            v->storedstring.length = new_length;
+        } break;
+        
+        case IDSTRING_TYPE:
+        case EXTERNALSTRING_TYPE:
+        case COMPOSEDSTRING_TYPE: {
+            if (!NCDVal_IsString(data)) {
+                ModuleLog(i, BLOG_ERROR, "cannot append non-string to string");
+                return 0;
+            }
+            
+            size_t length = value_string_length(v);
+            size_t append_length = NCDVal_StringLength(data);
+            
+            if (append_length > SIZE_MAX - length) {
+                ModuleLog(i, BLOG_ERROR, "too much data to append");
+                return 0;
+            }
+            size_t new_length = length + append_length;
+            
+            char *new_string = BAlloc(new_length);
+            if (!new_string) {
+                ModuleLog(i, BLOG_ERROR, "BAlloc failed");
+                return 0;
+            }
+            
+            value_string_copy_out(v, new_string);
+            NCDVal_StringCopyOut(data, 0, append_length, new_string + length);
+            
+            value_string_set_allocd(v, new_string, new_length);
+        } break;
+        
+        case NCDVAL_LIST: {
+            struct value *nv = value_init_fromvalue(i, data);
+            if (!nv) {
+                return 0;
+            }
+            
+            if (!value_list_insert(i, v, nv, value_list_len(v))) {
+                value_cleanup(nv);
+                return 0;
+            }
+        } break;
+        
+        default:
+            ModuleLog(i, BLOG_ERROR, "append is only defined for strings and lists");
+            return 0;
+    }
+    
+    return 1;
+}
+
+static void valref_init (struct valref *r, struct value *v)
+{
+    r->v = v;
+    
+    if (v) {
+        LinkedList0_Prepend(&v->refs_list, &r->refs_list_node);
+    }
+}
+
+static void valref_free (struct valref *r)
+{
+    if (r->v) {
+        LinkedList0_Remove(&r->v->refs_list, &r->refs_list_node);
+        value_cleanup(r->v);
+    }
+}
+
+static struct value * valref_val (struct valref *r)
+{
+    return r->v;
+}
+
+static void valref_break (struct valref *r)
+{
+    ASSERT(r->v)
+    
+    LinkedList0_Remove(&r->v->refs_list, &r->refs_list_node);
+    r->v = NULL;
+}
+
+static void func_new_common (void *vo, NCDModuleInst *i, struct value *v, value_deinit_func deinit_func, void *deinit_data)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // init value references
+    valref_init(&o->ref, v);
+    
+    // remember deinit
+    o->deinit_func = deinit_func;
+    o->deinit_data = deinit_data;
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // deinit
+    if (o->deinit_func) {
+        o->deinit_func(o->deinit_data, o->i);
+    }
+    
+    // free value reference
+    valref_free(&o->ref);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    struct value *v = valref_val(&o->ref);
+    
+    if (name == ModuleString(o->i, STRING_EXISTS)) {
+        *out = ncd_make_boolean(mem, !!v, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    if (name != NCD_STRING_TYPE && name != NCD_STRING_LENGTH &&
+        name != ModuleString(o->i, STRING_KEYS) && name != NCD_STRING_EMPTY) {
+        return 0;
+    }
+    
+    if (!v) {
+        ModuleLog(o->i, BLOG_ERROR, "value was deleted");
+        return 0;
+    }
+    
+    if (name == NCD_STRING_TYPE) {
+        *out = NCDVal_NewString(mem, get_type_str(v->type));
+    }
+    else if (name == NCD_STRING_LENGTH) {
+        size_t len = 0; // to remove warning
+        switch (v->type) {
+            case NCDVAL_LIST:
+                len = value_list_len(v);
+                break;
+            case NCDVAL_MAP:
+                len = value_map_len(v);
+                break;
+            default:
+                ASSERT(value_is_string(v))
+                len = value_string_length(v);
+                break;
+        }
+        
+        *out = ncd_make_uintmax(mem, len);
+    }
+    else if (name == ModuleString(o->i, STRING_KEYS)) {
+        if (v->type != NCDVAL_MAP) {
+            ModuleLog(o->i, BLOG_ERROR, "value is not a map (reading keys variable)");
+            return 0;
+        }
+        
+        *out = NCDVal_NewList(mem, value_map_len(v));
+        if (NCDVal_IsInvalid(*out)) {
+            goto fail;
+        }
+        
+        for (size_t j = 0; j < value_map_len(v); j++) {
+            struct value *ev = value_map_at(v, j);
+            
+            NCDValRef key = NCDVal_NewCopy(mem, ev->map_parent.key);
+            if (NCDVal_IsInvalid(key)) {
+                goto fail;
+            }
+            
+            if (!NCDVal_ListAppend(*out, key)) {
+                goto fail;
+            }
+        }
+    }
+    else if (name == NCD_STRING_EMPTY) {
+        if (!value_to_value(o->i, v, mem, out)) {
+            return 0;
+        }
+    }
+    else {
+        ASSERT(0);
+    }
+    
+    return 1;
+    
+fail:
+    *out = NCDVal_NewInvalid();
+    return 1;
+}
+
+static void func_new_value (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef value_arg;
+    if (!NCDVal_ListRead(params->args, 1, &value_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct value *v = value_init_fromvalue(i, value_arg);
+    if (!v) {
+        goto fail0;
+    }
+    
+    func_new_common(vo, i, v, NULL, NULL);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_get (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef where_arg;
+    if (!NCDVal_ListRead(params->args, 1, &where_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    struct value *v = value_get(i, mov, where_arg, 0);
+    if (!v) {
+        goto fail0;
+    }
+    
+    func_new_common(vo, i, v, NULL, NULL);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_try_get (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef where_arg;
+    if (!NCDVal_ListRead(params->args, 1, &where_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    struct value *v = value_get(i, mov, where_arg, 1);
+    
+    func_new_common(vo, i, v, NULL, NULL);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_getpath (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef path_arg;
+    if (!NCDVal_ListRead(params->args, 1, &path_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsList(path_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    struct value *v = value_get_path(i, mov, path_arg);
+    if (!v) {
+        goto fail0;
+    }
+    
+    func_new_common(vo, i, v, NULL, NULL);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_insert_replace_common (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_replace)
+{
+    NCDValRef where_arg = NCDVal_NewInvalid();
+    NCDValRef what_arg;
+    if (!(!is_replace && NCDVal_ListRead(params->args, 1, &what_arg)) &&
+        !NCDVal_ListRead(params->args, 2, &where_arg, &what_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    struct value *v;
+    
+    if (NCDVal_IsInvalid(where_arg)) {
+        v = value_insert_simple(i, mov, what_arg);
+    } else {
+        v = value_insert(i, mov, where_arg, what_arg, is_replace, NULL);
+    }
+    if (!v) {
+        goto fail0;
+    }
+    
+    func_new_common(vo, i, v, NULL, NULL);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_insert (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_insert_replace_common(vo, i, params, 0);
+}
+
+static void func_new_replace (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_insert_replace_common(vo, i, params, 1);
+}
+
+struct insert_undo_deinit_data {
+    struct valref val_ref;
+    struct valref oldval_ref;
+};
+
+static void undo_deinit_func (struct insert_undo_deinit_data *data, NCDModuleInst *i)
+{
+    struct value *val = valref_val(&data->val_ref);
+    struct value *oldval = valref_val(&data->oldval_ref);
+    
+    if (val && val->parent && (!oldval || !oldval->parent)) {
+        // get parent
+        struct value *parent = val->parent;
+        
+        // remove this value from parent and restore saved one (or none)
+        switch (parent->type) {
+            case NCDVAL_LIST: {
+                size_t index = value_list_indexof(parent, val);
+                value_list_remove(parent, val);
+                if (oldval) {
+                    int res = value_list_insert(i, parent, oldval, index);
+                    ASSERT_EXECUTE(res)
+                }
+            } break;
+            
+            case NCDVAL_MAP: {
+                NCDValMem key_mem;
+                NCDValSafeRef key;
+                value_map_remove2(parent, val, &key_mem, &key);
+                if (oldval) {
+                    int res = value_map_insert(parent, oldval, key_mem, key, i);
+                    ASSERT_EXECUTE(res)
+                } else {
+                    NCDValMem_Free(&key_mem);
+                }
+            } break;
+            
+            default: ASSERT(0);
+        }
+    }
+    
+    valref_free(&data->oldval_ref);
+    valref_free(&data->val_ref);
+    free(data);
+}
+
+static void func_new_insert_replace_undo_common (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, int is_replace)
+{
+    NCDValRef where_arg = NCDVal_NewInvalid();
+    NCDValRef what_arg;
+    if (!(!is_replace && NCDVal_ListRead(params->args, 1, &what_arg)) &&
+        !NCDVal_ListRead(params->args, 2, &where_arg, &what_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    struct insert_undo_deinit_data *data = malloc(sizeof(*data));
+    if (!data) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    struct value *oldv;
+    struct value *v;
+    
+    if (NCDVal_IsInvalid(where_arg)) {
+        oldv = NULL;
+        v = value_insert_simple(i, mov, what_arg);
+    } else {
+        v = value_insert(i, mov, where_arg, what_arg, is_replace, &oldv);
+    }
+    if (!v) {
+        goto fail1;
+    }
+    
+    valref_init(&data->val_ref, v);
+    valref_init(&data->oldval_ref, oldv);
+    
+    func_new_common(vo, i, v, (value_deinit_func)undo_deinit_func, data);
+    return;
+    
+fail1:
+    free(data);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_insert_undo (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_insert_replace_undo_common(vo, i, params, 0);
+}
+
+static void func_new_replace_undo (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    func_new_insert_replace_undo_common(vo, i, params, 1);
+}
+
+static void func_new_replace_this (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef value_arg;
+    if (!NCDVal_ListRead(params->args, 1, &value_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    struct value *v = value_init_fromvalue(i, value_arg);
+    if (!v) {
+        goto fail0;
+    }
+    
+    if (mov->parent) {
+        struct value *parent = mov->parent;
+        
+        switch (parent->type) {
+            case NCDVAL_LIST: {
+                size_t index = value_list_indexof(parent, mov);
+                value_list_remove(parent, mov);
+                int res = value_list_insert(i, parent, v, index);
+                ASSERT_EXECUTE(res)
+            } break;
+            
+            case NCDVAL_MAP: {
+                NCDValMem key_mem;
+                NCDValSafeRef key;
+                value_map_remove2(parent, mov, &key_mem, &key);
+                int res = value_map_insert(parent, v, key_mem, key, i);
+                ASSERT_EXECUTE(res)
+            } break;
+            
+            default: ASSERT(0);
+        }
+        
+        value_cleanup(mov);
+    }
+    
+    func_new_common(vo, i, v, NULL, NULL);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_replace_this_undo (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef value_arg;
+    if (!NCDVal_ListRead(params->args, 1, &value_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    struct value *v = value_init_fromvalue(i, value_arg);
+    if (!v) {
+        goto fail0;
+    }
+    
+    struct insert_undo_deinit_data *data = malloc(sizeof(*data));
+    if (!data) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        goto fail1;
+    }
+    
+    valref_init(&data->val_ref, v);
+    valref_init(&data->oldval_ref, mov);
+    
+    if (mov->parent) {
+        struct value *parent = mov->parent;
+        
+        switch (parent->type) {
+            case NCDVAL_LIST: {
+                size_t index = value_list_indexof(parent, mov);
+                value_list_remove(parent, mov);
+                int res = value_list_insert(i, parent, v, index);
+                ASSERT_EXECUTE(res)
+            } break;
+            
+            case NCDVAL_MAP: {
+                NCDValMem key_mem;
+                NCDValSafeRef key;
+                value_map_remove2(parent, mov, &key_mem, &key);
+                int res = value_map_insert(parent, v, key_mem, key, i);
+                ASSERT_EXECUTE(res)
+            } break;
+            
+            default: ASSERT(0);
+        }
+    }
+    
+    func_new_common(vo, i, v, (value_deinit_func)undo_deinit_func, data);
+    return;
+    
+fail1:
+    value_cleanup(v);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_new_substr (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef start_arg;
+    NCDValRef length_arg = NCDVal_NewInvalid();
+    if (!NCDVal_ListRead(params->args, 1, &start_arg) &&
+        !NCDVal_ListRead(params->args, 2, &start_arg, &length_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    if (!NCDVal_IsString(start_arg) || (!NCDVal_IsInvalid(length_arg) && !NCDVal_IsString(length_arg))) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail0;
+    }
+    
+    uintmax_t start;
+    if (!ncd_read_uintmax(start_arg, &start)) {
+        ModuleLog(i, BLOG_ERROR, "start is not a number");
+        goto fail0;
+    }
+    
+    uintmax_t length = UINTMAX_MAX;
+    if (!NCDVal_IsInvalid(length_arg) && !ncd_read_uintmax(length_arg, &length)) {
+        ModuleLog(i, BLOG_ERROR, "length is not a number");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    if (!value_is_string(mov)) {
+        ModuleLog(i, BLOG_ERROR, "value is not a string");
+        goto fail0;
+    }
+    
+    size_t string_len = value_string_length(mov);
+    
+    if (start > string_len) {
+        ModuleLog(i, BLOG_ERROR, "start is out of range");
+        goto fail0;
+    }
+    
+    size_t remain = string_len - start;
+    size_t amount = length < remain ? length : remain;
+    
+    struct value *v = NULL;
+    switch (mov->type) {
+        case STOREDSTRING_TYPE: {
+            v = value_init_storedstring(i, mov->storedstring.data + start, amount);
+        } break;
+        case IDSTRING_TYPE: {
+            v = value_init_storedstring(i, NCDStringIndex_Value(mov->idstring.string_index, mov->idstring.id) + start, amount);
+        } break;
+        case EXTERNALSTRING_TYPE: {
+            v = value_init_externalstring(i, mov->externalstring.data + start, amount, mov->externalstring.ref_target);
+        } break;
+        case COMPOSEDSTRING_TYPE: {
+            v = value_init_composedstring(i, mov->composedstring.resource, mov->composedstring.offset + start, amount);
+        } break;
+        default:
+            ASSERT(0);
+    }
+    
+    if (!v) {
+        goto fail0;
+    }
+    
+    func_new_common(vo, i, v, NULL, NULL);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void remove_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef where_arg;
+    if (!NCDVal_ListRead(params->args, 1, &where_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    if (!value_remove(i, mov, where_arg)) {
+        goto fail0;
+    }
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void delete_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    if (!NCDVal_ListRead(params->args, 0)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    value_delete(mov);
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void reset_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef what_arg;
+    if (!NCDVal_ListRead(params->args, 1, &what_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // build value from argument
+    struct value *newv = value_init_fromvalue(i, what_arg);
+    if (!newv) {
+        goto fail0;
+    }
+    
+    // deinit
+    if (mo->deinit_func) {
+        mo->deinit_func(mo->deinit_data, i);
+    }
+    
+    // free value reference
+    valref_free(&mo->ref);
+    
+    // set up value reference
+    valref_init(&mo->ref, newv);
+    
+    // set no deinit function
+    mo->deinit_func = NULL;
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void append_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    NCDValRef data_arg;
+    if (!NCDVal_ListRead(params->args, 1, &data_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    struct value *mov = valref_val(&mo->ref);
+    
+    if (!mov) {
+        ModuleLog(i, BLOG_ERROR, "value was deleted");
+        goto fail0;
+    }
+    
+    if (!value_append(i, mov, data_arg)) {
+        goto fail0;
+    }
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "value",
+        .func_new2 = func_new_value,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::get",
+        .base_type = "value",
+        .func_new2 = func_new_get,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::try_get",
+        .base_type = "value",
+        .func_new2 = func_new_try_get,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::getpath",
+        .base_type = "value",
+        .func_new2 = func_new_getpath,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::insert",
+        .base_type = "value",
+        .func_new2 = func_new_insert,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::replace",
+        .base_type = "value",
+        .func_new2 = func_new_replace,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::replace_this",
+        .base_type = "value",
+        .func_new2 = func_new_replace_this,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::insert_undo",
+        .base_type = "value",
+        .func_new2 = func_new_insert_undo,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::replace_undo",
+        .base_type = "value",
+        .func_new2 = func_new_replace_undo,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::replace_this_undo",
+        .base_type = "value",
+        .func_new2 = func_new_replace_this_undo,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::remove",
+        .func_new2 = remove_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::delete",
+        .func_new2 = delete_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::reset",
+        .func_new2 = reset_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::substr",
+        .base_type = "value",
+        .func_new2 = func_new_substr,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance),
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = "value::append",
+        .func_new2 = append_func_new,
+        .flags = NCDMODULE_FLAG_ACCEPT_NON_CONTINUOUS_STRINGS
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_value = {
+    .modules = modules,
+    .strings = strings
+};
diff --git a/external/badvpn_dns/ncd/modules/value_maptree.h b/external/badvpn_dns/ncd/modules/value_maptree.h
new file mode 100644
index 0000000..e6f69f9
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/value_maptree.h
@@ -0,0 +1,11 @@
+#define SAVL_PARAM_NAME MapTree
+#define SAVL_PARAM_FEATURE_COUNTS 1
+#define SAVL_PARAM_FEATURE_NOKEYS 0
+#define SAVL_PARAM_TYPE_ENTRY struct value
+#define SAVL_PARAM_TYPE_KEY NCDValRef
+#define SAVL_PARAM_TYPE_ARG int
+#define SAVL_PARAM_TYPE_COUNT size_t
+#define SAVL_PARAM_VALUE_COUNT_MAX SIZE_MAX
+#define SAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) NCDVal_Compare((entry1)->map_parent.key, (entry2)->map_parent.key)
+#define SAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) NCDVal_Compare((key1), (entry2)->map_parent.key)
+#define SAVL_PARAM_MEMBER_NODE map_parent.maptree_node
diff --git a/external/badvpn_dns/ncd/modules/valuemetic.c b/external/badvpn_dns/ncd/modules/valuemetic.c
new file mode 100644
index 0000000..a87f73b
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/valuemetic.c
@@ -0,0 +1,219 @@
+/**
+ * @file valuemetic.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Comparison functions for values.
+ * 
+ * Synopsis:
+ *   val_lesser(v1, v2)
+ *   val_greater(v1, v2)
+ *   val_lesser_equal(v1, v2)
+ *   val_greater_equal(v1, v2)
+ *   val_equal(v1, v2)
+ *   val_different(v1, v2)
+ * 
+ * Variables:
+ *   (empty) - "true" or "false", reflecting the value of the relation in question
+ * 
+ * Description:
+ *   These statements perform comparisons of values. Order of values is defined by the
+ *   following rules:
+ *   1. Values of different types have the following order: strings, lists, maps.
+ *   2. String values are ordered lexicographically, with respect to the numeric values
+ *      of their bytes.
+ *   3. List values are ordered lexicographically, where the order of the elements is
+ *      defined by recursive application of these rules.
+ *   4. Map values are ordered lexicographically, as if a map was a list of (key, value)
+ *      pairs ordered by key, where the order of both keys and values is defined by
+ *      recursive application of these rules.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+#include <ncd/extra/value_utils.h>
+
+#include <generated/blog_channel_ncd_valuemetic.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    int result;
+};
+
+typedef int (*compute_func) (NCDValRef v1, NCDValRef v2);
+
+static int compute_lesser (NCDValRef v1, NCDValRef v2)
+{
+    return NCDVal_Compare(v1, v2) < 0;
+}
+
+static int compute_greater (NCDValRef v1, NCDValRef v2)
+{
+    return NCDVal_Compare(v1, v2) > 0;
+}
+
+static int compute_lesser_equal (NCDValRef v1, NCDValRef v2)
+{
+    return NCDVal_Compare(v1, v2) <= 0;
+}
+
+static int compute_greater_equal (NCDValRef v1, NCDValRef v2)
+{
+    return NCDVal_Compare(v1, v2) >= 0;
+}
+
+static int compute_equal (NCDValRef v1, NCDValRef v2)
+{
+    return NCDVal_Compare(v1, v2) == 0;
+}
+
+static int compute_different (NCDValRef v1, NCDValRef v2)
+{
+    return NCDVal_Compare(v1, v2) != 0;
+}
+
+static void new_templ (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params, compute_func cfunc)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    NCDValRef v1_arg;
+    NCDValRef v2_arg;
+    if (!NCDVal_ListRead(params->args, 2, &v1_arg, &v2_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    o->result = cfunc(v1_arg, v2_arg);
+    
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = ncd_make_boolean(mem, o->result, o->i->params->iparams->string_index);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void func_new_lesser (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, compute_lesser);
+}
+
+static void func_new_greater (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, compute_greater);
+}
+
+static void func_new_lesser_equal (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, compute_lesser_equal);
+}
+
+static void func_new_greater_equal (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, compute_greater_equal);
+}
+
+static void func_new_equal (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, compute_equal);
+}
+
+static void func_new_different (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    new_templ(vo, i, params, compute_different);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "val_lesser",
+        .func_new2 = func_new_lesser,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "val_greater",
+        .func_new2 = func_new_greater,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "val_lesser_equal",
+        .func_new2 = func_new_lesser_equal,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "val_greater_equal",
+        .func_new2 = func_new_greater_equal,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "val_equal",
+        .func_new2 = func_new_equal,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "val_different",
+        .func_new2 = func_new_different,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_valuemetic = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/modules/var.c b/external/badvpn_dns/ncd/modules/var.c
new file mode 100644
index 0000000..83abd44
--- /dev/null
+++ b/external/badvpn_dns/ncd/modules/var.c
@@ -0,0 +1,163 @@
+/**
+ * @file var.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Variable module.
+ * 
+ * Synopsis: var(value)
+ * Variables:
+ *   (empty) - value
+ * 
+ * Synopsis: var::set(value)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ncd/NCDModule.h>
+#include <ncd/static_strings.h>
+
+#include <generated/blog_channel_ncd_var.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+struct instance {
+    NCDModuleInst *i;
+    NCDValMem mem;
+    NCDValRef value;
+};
+
+static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    struct instance *o = vo;
+    o->i = i;
+    
+    // read argument
+    NCDValRef value_arg;
+    if (!NCDVal_ListRead(params->args, 1, &value_arg)) {
+        ModuleLog(o->i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // init mem
+    NCDValMem_Init(&o->mem);
+    
+    // copy value
+    o->value = NCDVal_NewCopy(&o->mem, value_arg);
+    if (NCDVal_IsInvalid(o->value)) {
+        goto fail1;
+    }
+    
+    // signal up
+    NCDModuleInst_Backend_Up(o->i);
+    return;
+    
+fail1:
+    NCDValMem_Free(&o->mem);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    
+    // free mem
+    NCDValMem_Free(&o->mem);
+    
+    NCDModuleInst_Backend_Dead(o->i);
+}
+
+static int func_getvar2 (void *vo, NCD_string_id_t name, NCDValMem *mem, NCDValRef *out)
+{
+    struct instance *o = vo;
+    
+    if (name == NCD_STRING_EMPTY) {
+        *out = NCDVal_NewCopy(mem, o->value);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void set_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
+{
+    // read arguments
+    NCDValRef value_arg;
+    if (!NCDVal_ListRead(params->args, 1, &value_arg)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail0;
+    }
+    
+    // get method object
+    struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
+    
+    // allocate new mem
+    NCDValMem mem;
+    NCDValMem_Init(&mem);
+    
+    // copy value
+    NCDValRef copy = NCDVal_NewCopy(&mem, value_arg);
+    if (NCDVal_IsInvalid(copy)) {
+        goto fail1;
+    }
+    
+    // replace value in var
+    NCDValMem_Free(&mo->mem);
+    mo->mem = mem;
+    mo->value = NCDVal_Moved(&mo->mem, copy);
+    
+    // signal up
+    NCDModuleInst_Backend_Up(i);
+    return;
+    
+fail1:
+    NCDValMem_Free(&mem);
+fail0:
+    NCDModuleInst_Backend_DeadError(i);
+}
+
+static struct NCDModule modules[] = {
+    {
+        .type = "var",
+        .func_new2 = func_new,
+        .func_die = func_die,
+        .func_getvar2 = func_getvar2,
+        .alloc_size = sizeof(struct instance)
+    }, {
+        .type = "var::set",
+        .func_new2 = set_func_new
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_var = {
+    .modules = modules
+};
diff --git a/external/badvpn_dns/ncd/ncd.c b/external/badvpn_dns/ncd/ncd.c
new file mode 100644
index 0000000..b3270fc
--- /dev/null
+++ b/external/badvpn_dns/ncd/ncd.c
@@ -0,0 +1,463 @@
+/**
+ * @file ncd.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <misc/version.h>
+#include <misc/loglevel.h>
+#include <misc/open_standard_streams.h>
+#include <misc/string_begins_with.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BSignal.h>
+#include <system/BProcess.h>
+#include <udevmonitor/NCDUdevManager.h>
+#include <random/BRandom2.h>
+#include <ncd/NCDInterpreter.h>
+#include <ncd/NCDBuildProgram.h>
+
+#ifdef BADVPN_USE_SYSLOG
+#include <base/BLog_syslog.h>
+#endif
+
+#include "ncd.h"
+
+#include <generated/blog_channel_ncd.h>
+
+#define LOGGER_STDOUT 1
+#define LOGGER_STDERR 2
+#define LOGGER_SYSLOG 3
+
+// command-line options
+static struct {
+    int help;
+    int version;
+    int logger;
+#ifdef BADVPN_USE_SYSLOG
+    char *logger_syslog_facility;
+    char *logger_syslog_ident;
+#endif
+    int loglevel;
+    int loglevels[BLOG_NUM_CHANNELS];
+    char *config_file;
+    int syntax_only;
+    int retry_time;
+    int no_udev;
+    char **extra_args;
+    int num_extra_args;
+} options;
+
+// reactor
+static BReactor reactor;
+
+// process manager
+static BProcessManager manager;
+
+// udev manager
+static NCDUdevManager umanager;
+
+// random number generator
+static BRandom2 random2;
+
+// interpreter
+static NCDInterpreter interpreter;
+
+// forward declarations of functions
+static void print_help (const char *name);
+static void print_version (void);
+static int parse_arguments (int argc, char *argv[]);
+static void signal_handler (void *unused);
+static void interpreter_handler_finished (void *user, int exit_code);
+
+int main (int argc, char **argv)
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    int main_exit_code = 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;
+        case LOGGER_STDERR:
+            BLog_InitStderr();
+            break;
+#ifdef BADVPN_USE_SYSLOG
+        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);
+        } else {
+            BLog_SetChannelLoglevel(i, DEFAULT_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();
+    
+    // init reactor
+    if (!BReactor_Init(&reactor)) {
+        BLog(BLOG_ERROR, "BReactor_Init failed");
+        goto fail1;
+    }
+    
+    // init process manager
+    if (!BProcessManager_Init(&manager, &reactor)) {
+        BLog(BLOG_ERROR, "BProcessManager_Init failed");
+        goto fail2;
+    }
+    
+    // init udev manager
+    NCDUdevManager_Init(&umanager, options.no_udev, &reactor, &manager);
+    
+    // init random number generator
+    if (!BRandom2_Init(&random2, BRANDOM2_INIT_LAZY)) {
+        BLog(BLOG_ERROR, "BRandom2_Init failed");
+        goto fail3;
+    }
+    
+    // setup signal handler
+    if (!BSignal_Init(&reactor, signal_handler, NULL)) {
+        BLog(BLOG_ERROR, "BSignal_Init failed");
+        goto fail4;
+    }
+    
+    // build program
+    NCDProgram program;
+    if (!NCDBuildProgram_Build(options.config_file, &program)) {
+        BLog(BLOG_ERROR, "failed to build program");
+        goto fail5;
+    }
+    
+    // setup interpreter parameters
+    struct NCDInterpreter_params params;
+    params.handler_finished = interpreter_handler_finished;
+    params.user = NULL;
+    params.retry_time = options.retry_time;
+    params.extra_args = options.extra_args;
+    params.num_extra_args = options.num_extra_args;
+    params.reactor = &reactor;
+    params.manager = &manager;
+    params.umanager = &umanager;
+    params.random2 = &random2;
+    
+    // initialize interpreter
+    if (!NCDInterpreter_Init(&interpreter, program, params)) {
+        goto fail5;
+    }
+    
+    // don't enter event loop if syntax check is requested
+    if (options.syntax_only) {
+        main_exit_code = 0;
+        goto fail6;
+    }
+    
+    BLog(BLOG_NOTICE, "entering event loop");
+    
+    // enter event loop
+    main_exit_code = BReactor_Exec(&reactor);
+    
+fail6:
+    // free interpreter
+    NCDInterpreter_Free(&interpreter);
+fail5:
+    // remove signal handler
+    BSignal_Finish();
+fail4:
+    // free random number generator
+    BRandom2_Free(&random2);
+fail3:
+    // free udev manager
+    NCDUdevManager_Free(&umanager);
+    
+    // free process manager
+    BProcessManager_Free(&manager);
+fail2:
+    // free reactor
+    BReactor_Free(&reactor);
+fail1:
+    // free logger
+    BLog(BLOG_NOTICE, "exiting");
+    BLog_Free();
+fail0:
+    // finish objects
+    DebugObjectGlobal_Finish();
+    
+    return main_exit_code;
+}
+
+void print_help (const char *name)
+{
+    printf(
+        "Usage:\n"
+        "    %s\n"
+        "        [--help]\n"
+        "        [--version]\n"
+        "        [--logger <stdout/stderr/syslog>]\n"
+        "        (logger=syslog?\n"
+        "            [--syslog-facility <string>]\n"
+        "            [--syslog-ident <string>]\n"
+        "        )\n"
+        "        [--loglevel <0-5/none/error/warning/notice/info/debug>]\n"
+        "        [--channel-loglevel <channel-name> <0-5/none/error/warning/notice/info/debug>] ...\n"
+        "        [--retry-time <ms>]\n"
+        "        [--no-udev]\n"
+        "        [--config-file <ncd_program_file>]\n"
+        "        [--syntax-only]\n"
+        "        [-- program_args...]\n"
+        "        [<ncd_program_file> program_args...]\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_STDERR;
+#ifdef BADVPN_USE_SYSLOG
+    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.config_file = NULL;
+    options.syntax_only = 0;
+    options.retry_time = DEFAULT_RETRY_TIME;
+    options.no_udev = 0;
+    options.extra_args = NULL;
+    options.num_extra_args = 0;
+    
+    for (int 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;
+            }
+            else if (!strcmp(arg2, "stderr")) {
+                options.logger = LOGGER_STDERR;
+            }
+#ifdef BADVPN_USE_SYSLOG
+            else if (!strcmp(arg2, "syslog")) {
+                options.logger = LOGGER_SYSLOG;
+            }
+#endif
+            else {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+#ifdef BADVPN_USE_SYSLOG
+        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, "--config-file")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.config_file = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--syntax-only")) {
+            options.syntax_only = 1;
+        }
+        else if (!strcmp(arg, "--retry-time")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.retry_time = atoi(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--no-udev")) {
+            options.no_udev = 1;
+        }
+        else if (!strcmp(arg, "--")) {
+            options.extra_args = &argv[i + 1];
+            options.num_extra_args = argc - i - 1;
+            i += options.num_extra_args;
+        }
+        else if (!string_begins_with(arg, "--")) {
+            if (options.config_file) {
+                fprintf(stderr, "%s: program is already specified (did you mean to use -- ?)\n", arg);
+                return 0;
+            }
+            options.config_file = argv[i];
+            options.extra_args = &argv[i + 1];
+            options.num_extra_args = argc - i - 1;
+            i += options.num_extra_args;
+        }
+        else {
+            fprintf(stderr, "unknown option: %s\n", arg);
+            return 0;
+        }
+    }
+    
+    if (options.help || options.version) {
+        return 1;
+    }
+    
+    if (!options.config_file) {
+        fprintf(stderr, "No program is specified.\n");
+        return 0;
+    }
+    
+    return 1;
+}
+
+void signal_handler (void *unused)
+{
+    BLog(BLOG_NOTICE, "termination requested");
+    
+    NCDInterpreter_RequestShutdown(&interpreter, 1);
+}
+
+void interpreter_handler_finished (void *user, int exit_code)
+{
+    BReactor_Quit(&reactor, exit_code);
+}
diff --git a/external/badvpn_dns/ncd/ncd.h b/external/badvpn_dns/ncd/ncd.h
new file mode 100644
index 0000000..88c4070
--- /dev/null
+++ b/external/badvpn_dns/ncd/ncd.h
@@ -0,0 +1,37 @@
+/**
+ * @file ncd.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "ncd"
+
+// how long to wait after an error before retrying
+#define DEFAULT_RETRY_TIME 5000
+
+// default loglevel
+#define DEFAULT_LOGLEVEL BLOG_WARNING
diff --git a/external/badvpn_dns/ncd/parse_linux_input.sh b/external/badvpn_dns/ncd/parse_linux_input.sh
new file mode 100755
index 0000000..f277484
--- /dev/null
+++ b/external/badvpn_dns/ncd/parse_linux_input.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+INPUT=$1
+OUTPUT=$2
+
+types=""
+keys=""
+rels=""
+abss=""
+sws=""
+mscs=""
+leds=""
+reps=""
+snds=""
+ffstatuss=""
+
+while read LINE; do
+    tab=$'\t'
+    space="[ ${tab}]"
+    regex="^#define ((EV|KEY|BTN|REL|ABS|SW|MSC|LED|REP|SND|FF_STATUS)_[A-Z0-9_]+)${space}"
+    if [[ $LINE =~ $regex ]]; then
+        type=${BASH_REMATCH[2]}
+        name=${BASH_REMATCH[1]}
+        if [[ $type = "EV" ]]; then
+            if [[ $name != "EV_VERSION" ]]; then
+                types="${types}    [${name}] = \"${name}\",
+"
+            fi
+        elif [[ $type = "KEY" ]] || [[ $type = "BTN" ]]; then
+            if [[ $name != "KEY_MIN_INTERESTING" ]]; then
+                keys="${keys}    [${name}] = \"${name}\",
+"
+            fi
+        elif [[ $type = "REL" ]]; then
+            rels="${rels}    [${name}] = \"${name}\",
+"
+        elif [[ $type = "ABS" ]]; then
+            abss="${abss}    [${name}] = \"${name}\",
+"
+        elif [[ $type = "SW" ]]; then
+            sws="${sws}    [${name}] = \"${name}\",
+"
+        elif [[ $type = "MSC" ]]; then
+            mscs="${mscs}    [${name}] = \"${name}\",
+"
+        elif [[ $type = "LED" ]]; then
+            leds="${leds}    [${name}] = \"${name}\",
+"
+        elif [[ $type = "REP" ]]; then
+            reps="${reps}    [${name}] = \"${name}\",
+"
+        elif [[ $type = "SND" ]]; then
+            snds="${snds}    [${name}] = \"${name}\",
+"
+        elif [[ $type = "FF_STATUS" ]]; then
+            ffstatuss="${ffstatuss}    [${name}] = \"${name}\",
+"
+        fi
+    fi
+done < "${INPUT}"
+
+(
+echo "
+static const char *type_names[] = {
+${types}};
+
+static const char *key_names[] = {
+${keys}};
+
+static const char *rel_names[] = {
+${rels}};
+
+static const char *abs_names[] = {
+${abss}};
+
+static const char *sw_names[] = {
+${sws}};
+
+static const char *msc_names[] = {
+${mscs}};
+
+static const char *led_names[] = {
+${leds}};
+
+static const char *rep_names[] = {
+${reps}};
+
+static const char *snd_names[] = {
+${snds}};
+
+static const char *ffstatus_names[] = {
+${ffstatuss}};
+"
+) >"${OUTPUT}"
diff --git a/external/badvpn_dns/ncd/static_strings.h b/external/badvpn_dns/ncd/static_strings.h
new file mode 100644
index 0000000..a823224
--- /dev/null
+++ b/external/badvpn_dns/ncd/static_strings.h
@@ -0,0 +1,70 @@
+/**
+ * @file static_strings.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_STATIC_STRINGS_H
+#define BADVPN_STATIC_STRINGS_H
+
+// NOTE: keep synchronized with NCDStringIndex.c
+enum {
+    NCD_STRING_EMPTY,
+    NCD_STRING_ARGS,
+    NCD_STRING_ARG0,
+    NCD_STRING_ARG1,
+    NCD_STRING_ARG2,
+    NCD_STRING_ARG3,
+    NCD_STRING_ARG4,
+    NCD_STRING_ARG5,
+    NCD_STRING_ARG6,
+    NCD_STRING_ARG7,
+    NCD_STRING_ARG8,
+    NCD_STRING_ARG9,
+    NCD_STRING_ARG10,
+    NCD_STRING_ARG11,
+    NCD_STRING_ARG12,
+    NCD_STRING_ARG13,
+    NCD_STRING_ARG14,
+    NCD_STRING_ARG15,
+    NCD_STRING_ARG16,
+    NCD_STRING_ARG17,
+    NCD_STRING_ARG18,
+    NCD_STRING_ARG19,
+    NCD_STRING_TRUE,
+    NCD_STRING_FALSE,
+    NCD_STRING_NONE,
+    NCD_STRING_CALLER,
+    NCD_STRING_SUCCEEDED,
+    NCD_STRING_IS_ERROR,
+    NCD_STRING_NOT_EOF,
+    NCD_STRING_LENGTH,
+    NCD_STRING_TYPE,
+    NCD_STRING_EXIT_STATUS,
+    NCD_STRING_SIZE
+};
+
+#endif
diff --git a/external/badvpn_dns/ncd/tests/addr_in_network.ncd b/external/badvpn_dns/ncd/tests/addr_in_network.ncd
new file mode 100644
index 0000000..fc53292
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/addr_in_network.ncd
@@ -0,0 +1,60 @@
+process main {
+    net.ipv4.addr_in_network("192.168.6.0", "192.168.6.0", "24") r;
+    assert(r);
+    
+    net.ipv4.addr_in_network("192.168.6.0", "192.168.6.0/24") r;
+    assert(r);
+    
+    net.ipv4.addr_in_network("192.168.6.1", "192.168.6.0", "24") r;
+    assert(r);
+    
+    net.ipv4.addr_in_network("192.168.6.255", "192.168.6.0", "24") r;
+    assert(r);
+    
+    net.ipv4.addr_in_network("192.168.5.255", "192.168.6.0", "24") r;
+    not(r) r;
+    assert(r);
+    
+    net.ipv4.addr_in_network("192.168.7.0", "192.168.6.0", "24") r;
+    not(r) r;
+    assert(r);
+    
+    net.ipv4.addr_in_network("192.168.7.0", "192.168.6.0/24") r;
+    not(r) r;
+    assert(r);
+    
+    net.ipv4.addr_in_network("0.0.0.0", "192.168.6.0", "0") r;
+    assert(r);
+    
+    net.ipv4.addr_in_network("0.0.0.0", "0.0.0.0", "0") r;
+    assert(r);
+    
+    net.ipv4.addr_in_network("255.255.255.255", "0.0.0.0", "0") r;
+    assert(r);
+    
+    net.ipv6.addr_in_network("::123:0", "::123:0/112") r;
+    assert(r);
+    
+    net.ipv6.addr_in_network("::123:1", "::123:0/112") r;
+    assert(r);
+    
+    net.ipv6.addr_in_network("::123:ffff", "::123:0/112") r;
+    assert(r);
+    
+    net.ipv6.addr_in_network("::123:ffff", "::123:ffff/128") r;
+    assert(r);
+    
+    net.ipv6.addr_in_network("::122:ffff", "::123:0/112") r;
+    not(r) r;
+    assert(r);
+    
+    net.ipv6.addr_in_network("::124:0", "::123:0/112") r;
+    not(r) r;
+    assert(r);
+    
+    net.ipv6.addr_in_network("::123:fffe", "::123:ffff/128") r;
+    not(r) r;
+    assert(r);
+    
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/alias.ncd b/external/badvpn_dns/ncd/tests/alias.ncd
new file mode 100644
index 0000000..624a4ed
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/alias.ncd
@@ -0,0 +1,48 @@
+process foo {
+    var("hello") x;
+    alias("x") y;
+    val_equal(y, "hello") a;
+    assert(a);
+
+    var("hello") x;
+    alias("x") y;
+    y->set("world");
+    val_equal(y, "world") a;
+    assert(a);
+
+    var("hello") x;
+    alias("x") y;
+    alias("y") z;
+    z->set("world");
+    val_equal(x, "world") a;
+    assert(a);
+
+    call("test", {"hello"}) c;
+    alias("c.x") x;
+    val_equal(x, "hello") a;
+    assert(a);
+
+    call("test", {"hello"}) c;
+    alias("c") x;
+    alias("x") y;
+    alias("y.x") z;
+    c.x->set("world");
+    val_equal(z, "world") a;
+    assert(a);
+
+    var("hello") x;
+    call("test2", {"_caller.x"}) c;
+    c.x->set("world");
+    val_equal(x, "world") a;
+    assert(a);
+
+    exit("0");
+}
+
+template test {
+    var(_arg0) x;
+}
+
+template test2 {
+    alias(_arg0) x;
+}
diff --git a/external/badvpn_dns/ncd/tests/arithmetic.ncd b/external/badvpn_dns/ncd/tests/arithmetic.ncd
new file mode 100644
index 0000000..8f82bcc
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/arithmetic.ncd
@@ -0,0 +1,69 @@
+process main {
+    num_lesser("6", "7") r;
+    assert(r);
+
+    num_lesser("7", "7") r;
+    not(r) a;
+    assert(a);
+
+    num_greater("7", "6") r;
+    assert(r);
+
+    num_greater("7", "7") r;
+    not(r) a;
+    assert(a);
+
+    num_lesser_equal("7", "7") r;
+    assert(r);
+
+    num_lesser_equal("8", "7") r;
+    not(r) a;
+    assert(a);
+
+    num_greater_equal("7", "7") r;
+    assert(r);
+
+    num_greater_equal("7", "8") r;
+    not(r) a;
+    assert(a);
+
+    num_equal("7", "7") r;
+    assert(r);
+
+    num_equal("6", "7") r;
+    not(r) a;
+    assert(a);
+
+    num_equal("7", "6") r;
+    not(r) a;
+    assert(a);
+
+    num_different("7", "6") a;
+    assert(a);
+
+    num_different("7", "007") a;
+    not(a) a;
+    assert(a);
+
+    num_add("4", "7") r;
+    strcmp(r, "11") a;
+    assert(a);
+
+    num_subtract("4", "3") r;
+    strcmp(r, "1") a;
+    assert(a);
+
+    num_multiply("4", "5") r;
+    strcmp(r, "20") a;
+    assert(a);
+
+    num_divide("7", "3") r;
+    strcmp(r, "2") a;
+    assert(a);
+
+    num_modulo("7", "3") r;
+    strcmp(r, "1") a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/backtracking.ncd b/external/badvpn_dns/ncd/tests/backtracking.ncd
new file mode 100644
index 0000000..1faf3b7
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/backtracking.ncd
@@ -0,0 +1,31 @@
+process main {
+    value({}) list;
+    var("0") i;
+    backtrack_point() point;
+    num_lesser(i, "100") do_more;
+    If (do_more) {
+        list->insert(i);
+        num_add(i, "1") new_i;
+        i->set(new_i);
+        point->go();
+    };
+    val_equal(list.length, "100") a;
+    assert(a);
+    
+    value({}) list;
+    var("0") i;
+    blocker() blk;
+    blk->up();
+    blk->use();
+    num_lesser(i, "100") do_more;
+    If (do_more) {
+        list->insert(i);
+        num_add(i, "1") new_i;
+        i->set(new_i);
+        blk->downup();
+    };
+    val_equal(list.length, "100") a;
+    assert(a);
+    
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/buffer.ncd b/external/badvpn_dns/ncd/tests/buffer.ncd
new file mode 100644
index 0000000..1af0ea7
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/buffer.ncd
@@ -0,0 +1,54 @@
+process main {
+    buffer() buf;
+    val_equal(buf, "") a;
+    assert(a);
+    
+    buf->append("12");
+    val_equal(buf, "12") a;
+    assert(a);
+    
+    buf->append("345");
+    val_equal(buf, "12345") a;
+    assert(a);
+    
+    buf->consume("1");
+    val_equal(buf, "2345") a;
+    assert(a);
+    
+    buf->consume("1");
+    val_equal(buf, "345") a;
+    assert(a);
+    
+    buf->consume("3");
+    val_equal(buf, "") a;
+    assert(a);
+    
+    buf->append("6");
+    val_equal(buf, "6") a;
+    assert(a);
+    
+    buf->append("7890");
+    val_equal(buf, "67890") a;
+    assert(a);
+    
+    buf->append("");
+    val_equal(buf, "67890") a;
+    assert(a);
+    
+    buf->consume("4");
+    val_equal(buf, "0") a;
+    assert(a);
+    
+    buf->append("1234567890");
+    val_equal(buf, "01234567890") a;
+    assert(a);
+    
+    val_equal(buf.length, "11") a;
+    assert(a);
+    
+    buffer("hello") buf2;
+    val_equal(buf2, "hello") a;
+    assert(a);
+    
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/call.ncd b/external/badvpn_dns/ncd/tests/call.ncd
new file mode 100644
index 0000000..273ed68
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/call.ncd
@@ -0,0 +1,18 @@
+process main {
+    var("bad_x") x;
+    var("good_x") y;
+    call("helper_func", {}) helper;
+    call_with_caller_target("func1", {}, "helper") c;
+    val_equal(c.x, "good_x") a;
+    assert(a);
+    
+    exit("0");
+}
+
+template helper_func {
+    var(_caller.y) x;
+}
+
+template func1 {
+    var(_caller.x) x;
+}
diff --git a/external/badvpn_dns/ncd/tests/concat.ncd b/external/badvpn_dns/ncd/tests/concat.ncd
new file mode 100644
index 0000000..72b256e
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/concat.ncd
@@ -0,0 +1,19 @@
+process main {
+    concat("Hello", "", "World") x;
+    strcmp(x, "HelloWorld") a;
+    assert(a);
+
+    concat("\x00\x00", "\x00") x;
+    strcmp(x, "\x00\x00\x00") a;
+    assert(a);
+
+    concatv({"Hello", "", "World"}) x;
+    strcmp(x, "HelloWorld") a;
+    assert(a);
+
+    concatv({"\x00\x00", "\x00"}) x;
+    strcmp(x, "\x00\x00\x00") a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/depend.ncd b/external/badvpn_dns/ncd/tests/depend.ncd
new file mode 100644
index 0000000..c1a34b6
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/depend.ncd
@@ -0,0 +1,64 @@
+process main {
+    var("hello") x;
+    provide("A");
+    depend("A") d;
+    val_equal(d.x, "hello") a;
+    assert(a);
+    d.x->set("world");
+    val_equal(d.x, "world") a;
+    assert(a);
+
+    var("hello") x;
+    provide("B");
+    val_equal(x, "world") a;
+    assert(a);
+
+    var("hello") x;
+    provide("C");
+    val_equal(x, "hello") a;
+    assert(a);
+    depend("C_done");
+    val_equal(x, "world") a;
+    assert(a);
+
+    var("hello") x;
+    blocker() blk;
+    provide("D");
+    val_equal(x, "hello") a;
+    assert(a);
+    blk->up();
+    val_equal(x, "0") a;
+    assert(a);
+    blk->down();
+    blk->up();
+    val_equal(x, "1") a;
+    assert(a);
+
+    exit("0");
+}
+
+process proc1 {
+    depend("B") dep;
+    dep.x->set("world");
+}
+
+process proc2 {
+    depend("C") dep;
+    sleep("0", "0");
+    dep.x->set("world");
+    provide("C_done");
+}
+
+process proc3 {
+    depend("D") dep;
+    dep.blk->use();
+    provide("E");
+}
+
+process proc4 {
+    var("0") i;
+    depend("E") dep;
+    dep.dep.x->set(i);
+    num_add(i, "1") j;
+    i->set(j);
+}
diff --git a/external/badvpn_dns/ncd/tests/depend_scope.ncd b/external/badvpn_dns/ncd/tests/depend_scope.ncd
new file mode 100644
index 0000000..a29a9a6
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/depend_scope.ncd
@@ -0,0 +1,31 @@
+process main {
+    depend_scope() scope;
+    var("0") x;
+    process_manager() mgr;
+    
+    var("false") backtrack_check;
+    backtrack_point() point;
+    If (backtrack_check) {
+        val_equal(x, "2") a; # must not have rebound temporarily to A during backtracking
+        assert(a);
+        exit("0");
+    };
+    
+    scope->provide("A");
+    mgr->start("t1", "t1", {});
+    val_equal(x, "1") a; # must have bound to A immediately
+    assert(a);
+    
+    scope->provide("B") mgr;
+    val_equal(x, "2") a; # must have rebound to B immediately
+    assert(a);
+    
+    backtrack_check->set("true");
+    point->go();
+}
+
+template t1 {
+    _caller.scope->depend({"B", "A"}) dep;
+    num_add(dep.x, "1") new_x;
+    dep.x->set(new_x);
+}
diff --git a/external/badvpn_dns/ncd/tests/escape_and_nulls.ncd b/external/badvpn_dns/ncd/tests/escape_and_nulls.ncd
new file mode 100644
index 0000000..741bf8d
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/escape_and_nulls.ncd
@@ -0,0 +1,38 @@
+process main {
+    value("ab\0") str1;
+    value("ab") str2;
+
+    strcmp(str1.length, "3") a;
+    assert(a);
+
+    strcmp(str2.length, "2") a;
+    assert(a);
+
+    strcmp(str1, str2) a;
+    not(a) a;
+    assert(a);
+
+    concat(str1, str2) strc;
+    strcmp(strc, "ab\0ab") a;
+    assert(a);
+
+    concat(str2, str1) strc;
+    strcmp(strc, "abab\0") a;
+    assert(a);
+
+    value("") str1;
+    value("\x00\x00") str2;
+    value("\x00\x01") str3;
+    value("\x01") str4;
+
+    val_lesser(str1, str2) a;
+    assert(a);
+
+    val_lesser(str2, str3) a;
+    assert(a);
+
+    val_lesser(str3, str4) a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/explode.ncd b/external/badvpn_dns/ncd/tests/explode.ncd
new file mode 100644
index 0000000..20913a8
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/explode.ncd
@@ -0,0 +1,23 @@
+process main {
+    explode("FOO", "aaaFOObbbFOOcccFOOddd") l;
+    val_equal(l, {"aaa", "bbb", "ccc", "ddd"}) a;
+    assert(a);
+
+    explode("FOO", "FOObbbFOOFOO") l;
+    val_equal(l, {"", "bbb", "", ""}) a;
+    assert(a);
+
+    explode("FOO", "foo") l;
+    val_equal(l, {"foo"}) a;
+    assert(a);
+
+    explode("FOO", "FOO") l;
+    val_equal(l, {"", ""}) a;
+    assert(a);
+
+    explode("participate in parachute", "parachute in participation of participate in parachuteparparticipate in parachute participate in parachut") l;
+    val_equal(l, {"parachute in participation of ", "par", " participate in parachut"}) a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/foreach.ncd b/external/badvpn_dns/ncd/tests/foreach.ncd
new file mode 100644
index 0000000..33ad042
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/foreach.ncd
@@ -0,0 +1,35 @@
+process main {
+    var({"a", "b", "c", "d"}) list;
+    value(["a":"1", "b":"2", "c":"3", "d":"4"]) map;
+
+    value({}) new;
+    Foreach (list As value) {
+        new->insert(new.length, value);
+    };
+    val_equal(new, list) a;
+    assert(a);
+
+    value({}) new;
+    Foreach (list As index:value) {
+        new->insert(index, value);
+    };
+    val_equal(new, list) a;
+    assert(a);
+
+    value([]) new;
+    Foreach (map As key) {
+        map->get(key) value;
+        new->insert(key, value);
+    };
+    val_equal(new, map) a;
+    assert(a);
+
+    value([]) new;
+    Foreach (map As key:value) {
+        new->insert(key, value);
+    };
+    val_equal(new, map) a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/if.ncd b/external/badvpn_dns/ncd/tests/if.ncd
new file mode 100644
index 0000000..4597adc
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/if.ncd
@@ -0,0 +1,38 @@
+process foo {
+    If ("true") {
+        If ("truee") {
+            var("A1") y;
+        } else {
+            If ("true") {
+                var("A11") q;
+            } else {
+                var("A22") q;
+            } t;
+            var(t.q) y;
+        } s;
+        var(s.y) x;
+    } elif ("true") {
+        var("B") x;
+    } else {
+        var("C") x;
+    } ifs;
+
+    val_equal(ifs.x, "A11") a;
+    assert(a);
+
+    var("a") v;
+    If ("false") {
+        v->set("b");
+    };
+    val_equal(v, "a") a;
+    assert(a);
+
+    var("a") v;
+    If ("true") {
+        v->set("b");
+    };
+    val_equal(v, "b") a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/implode.ncd b/external/badvpn_dns/ncd/tests/implode.ncd
new file mode 100644
index 0000000..2197c17
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/implode.ncd
@@ -0,0 +1,15 @@
+process main {
+    implode("X", {"a", "bb", "", "c"}) str;
+    strcmp(str, "aXbbXXc") a;
+    assert(a);
+
+    implode("", {"a", "b"}) str;
+    strcmp(str, "ab") a;
+    assert(a);
+
+    implode("X", {}) str;
+    strcmp(str, "") a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/include.ncd b/external/badvpn_dns/ncd/tests/include.ncd
new file mode 100644
index 0000000..eccb76d
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/include.ncd
@@ -0,0 +1,16 @@
+include "include_included.ncdi"
+include "include_included.ncdi"
+include "include_included2.ncdi"
+include "include_included2.ncdi"
+
+process main {
+    call("incl_tmpl", {}) c;
+    val_equal(c.x, "good") a;
+    assert(a);
+    
+    call("incl_tmpl2", {}) c;
+    val_equal(c.x, "good2") a;
+    assert(a);
+    
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/include_included.ncdi b/external/badvpn_dns/ncd/tests/include_included.ncdi
new file mode 100644
index 0000000..5fbfd62
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/include_included.ncdi
@@ -0,0 +1,5 @@
+include_guard "include_included"
+
+template incl_tmpl {
+    var("good") x;
+}
diff --git a/external/badvpn_dns/ncd/tests/include_included2.ncdi b/external/badvpn_dns/ncd/tests/include_included2.ncdi
new file mode 100644
index 0000000..d765301
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/include_included2.ncdi
@@ -0,0 +1,5 @@
+include_guard "include_included2"
+
+template incl_tmpl2 {
+    var("good2") x;
+}
diff --git a/external/badvpn_dns/ncd/tests/logical.ncd b/external/badvpn_dns/ncd/tests/logical.ncd
new file mode 100644
index 0000000..e8956ec
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/logical.ncd
@@ -0,0 +1,46 @@
+process main {
+    var("true") t;
+    var("Faalse") f;
+
+    and(t, f) r;
+    strcmp(r, "false") a;
+    assert(a);
+
+    and(f, t) r;
+    strcmp(r, "false") a;
+    assert(a);
+
+    and(f, f) r;
+    strcmp(r, "false") a;
+    assert(a);
+
+    and(t, t) r;
+    strcmp(r, "true") a;
+    assert(a);
+
+    or(t, f) r;
+    strcmp(r, "true") a;
+    assert(a);
+
+    or(f, t) r;
+    strcmp(r, "true") a;
+    assert(a);
+
+    or(t, t) r;
+    strcmp(r, "true") a;
+    assert(a);
+
+    or(f, f) r;
+    strcmp(r, "false") a;
+    assert(a);
+
+    not(f) r;
+    strcmp(r, "true") a;
+    assert(a);
+
+    not(t) r;
+    strcmp(r, "false") a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/multidepend.ncd b/external/badvpn_dns/ncd/tests/multidepend.ncd
new file mode 100644
index 0000000..b641f39
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/multidepend.ncd
@@ -0,0 +1,30 @@
+process main {
+    var("0") x;
+    process_manager() mgr;
+    
+    var("false") backtrack_check;
+    backtrack_point() point;
+    If (backtrack_check) {
+        val_equal(x, "2") a; # must not have rebound temporarily to A during backtracking
+        assert(a);
+        exit("0");
+    };
+    
+    multiprovide("A");
+    mgr->start("t1", "t1", {});
+    val_equal(x, "1") a; # must have bound to A immediately
+    assert(a);
+    
+    multiprovide("B") mgr;
+    val_equal(x, "2") a; # must have rebound to B immediately
+    assert(a);
+    
+    backtrack_check->set("true");
+    point->go();
+}
+
+template t1 {
+    multidepend({"B", "A"}) dep;
+    num_add(dep.x, "1") new_x;
+    dep.x->set(new_x);
+}
diff --git a/external/badvpn_dns/ncd/tests/netmask.ncd b/external/badvpn_dns/ncd/tests/netmask.ncd
new file mode 100644
index 0000000..90cd744
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/netmask.ncd
@@ -0,0 +1,15 @@
+process main {
+    ipv4_prefix_to_mask("16") mask;
+    strcmp(mask, "255.255.0.0") a;
+    assert(a);
+
+    ipv4_mask_to_prefix("128.0.0.0") prefix;
+    strcmp(prefix, "1") a;
+    assert(a);
+
+    ipv4_net_from_addr_and_prefix("192.168.1.4", "24") net;
+    strcmp(net, "192.168.1.0") a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/parse.ncd b/external/badvpn_dns/ncd/tests/parse.ncd
new file mode 100644
index 0000000..ae4820c
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/parse.ncd
@@ -0,0 +1,85 @@
+process main {
+    parse_number("awfa") x;
+    not(x.succeeded) a;
+    assert(a);
+
+    parse_number("023182") x;
+    assert(x.succeeded);
+    val_equal(x, "23182") a;
+    assert(a);
+
+    parse_ipv4_addr("192.168.61.007") x;
+    assert(x.succeeded);
+    val_equal(x, "192.168.61.7") a;
+    assert(a);
+
+    parse_ipv6_addr("1234:0000::abcd") x;
+    assert(x.succeeded);
+    val_equal(x, "1234::abcd") a;
+    assert(a);
+
+    parse_value("{\"Hello World\", {}}") x;
+    assert(x.succeeded);
+    val_equal(x, {"Hello World", {}}) a;
+    assert(a);
+
+    var({"Hello", "fw", {}, {}, ["key":{{}}, [[]:[]]:["k":"v"]], {"st", {"ri", {"ng", [[{}:{}]:[]]}}}}) v;
+    to_string(v) str;
+    from_string(str) v2;
+    to_string(v2) str2;
+    val_equal(v, v2) a;
+    assert(a);
+    val_equal(str, str2) a;
+    assert(a);
+    
+    parse_value("{\"Hello\", \"fw\", {}, {}, [\"key\":{{}}, [[]:[]]:[\"k\":\"v\"]], {\"st\", {\"ri\", {\"ng\", [[{}:{}]:[]]}}}}") x;
+    assert(x.succeeded);
+    
+    parse_value("{\"Hello\", \"fw\", {}, {}, \"key\":{{}}, [[]:[]]:[\"k\":\"v\"]], {\"st, {\"ri\", {\"ng\", [[{}:{}]:[]]}}}}") x;
+    not(x.succeeded) a;
+    assert(a);
+    
+    parse_value("{\"Hello\", \"fw\", {}, {}, [\"key\":{{}}, [[]:[]]:[\"k\":\"v\"]], {\"st\", \"ri\", \"ng\", [[{}:{}]:[]]}}}}") x;
+    not(x.succeeded) a;
+    assert(a);
+    
+    parse_value("{\"Hello\", \"fw\", {}, {}, [\"key\":{{}}, [[]:[]]:[\"k\":\"v\"]], {\"st\", {\"ri\", {\"ng\", [[{}:{}]:[]]}}}}}") x;
+    not(x.succeeded) a;
+    assert(a);
+    
+    parse_value("{\"Hello\", \"fw\", {}, {}, [\"key\":{{}}, [[]:[]]:[\"k\":\"v\"]], {\"st\", {\"ri\", {\"ng\", [[{}:{}]:[]]}}}") x;
+    not(x.succeeded) a;
+    assert(a);
+    
+    parse_value("{syntax error") x;
+    not(x.succeeded) a;
+    assert(a);
+    
+    parse_ipv4_cidr_addr("192.168.61.007/24") x;
+    assert(x.succeeded);
+    val_equal(x, "192.168.61.7/24") a;
+    assert(a);
+    val_equal(x.addr, "192.168.61.7") a;
+    assert(a);
+    val_equal(x.prefix, "24") a;
+    assert(a);
+    
+    parse_ipv4_cidr_addr("192.168.61.007/33") x;
+    not(x.succeeded) a;
+    assert(a);
+    
+    parse_ipv6_cidr_addr("1234:0000::abcd/41") x;
+    assert(x.succeeded);
+    val_equal(x, "1234::abcd/41") a;
+    assert(a);
+    val_equal(x.addr, "1234::abcd") a;
+    assert(a);
+    val_equal(x.prefix, "41") a;
+    assert(a);
+    
+    parse_ipv6_cidr_addr("1234:0000::abcd/129") x;
+    not(x.succeeded) a;
+    assert(a);
+    
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/process_manager.ncd b/external/badvpn_dns/ncd/tests/process_manager.ncd
new file mode 100644
index 0000000..98f0819
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/process_manager.ncd
@@ -0,0 +1,112 @@
+process main {
+    var("0") x;
+    
+    var("false") backtrack_check;
+    backtrack_point() point;
+    If (backtrack_check) {
+        val_equal(x, "10") a;
+        assert(a);
+        call("phase2", {});
+    };
+    
+    process_manager() mgr;
+    
+    mgr->start("name", "increment", {"1", "2", "false"});
+    val_equal(x, "1") a;
+    assert(a);
+    
+    mgr->stop("name");
+    val_equal(x, "3") a;
+    assert(a);
+    
+    mgr->start("name", "increment", {"3", "4", "true"});
+    val_equal(x, "6") a;
+    assert(a);
+    
+    mgr->stop("name");
+    val_equal(x, "6") a;
+    assert(a);
+    
+    mgr->start("name", "increment", {"5", "6", "false"});
+    val_equal(x, "6") a;
+    assert(a);
+    
+    backtrack_check->set("true");
+    point->go();
+}
+
+template phase2 {
+    var("0") x;
+    
+    var("false") backtrack_check;
+    backtrack_point() point;
+    If (backtrack_check) {
+        val_equal(x, "10") a;
+        assert(a);
+        call("phase3", {});
+    };
+    
+    process_manager() mgr;
+    
+    mgr->start("name", "increment", {"1", "2", "true"});
+    val_equal(x, "1") a;
+    assert(a);
+    
+    mgr->stop("name");
+    val_equal(x, "1") a;
+    assert(a);
+    
+    mgr->start("name", "increment", {"3", "4", "true"});
+    val_equal(x, "1") a;
+    assert(a);
+    
+    depend("INC_DONE");
+    val_equal(x, "6") a;
+    assert(a);
+    
+    backtrack_check->set("true");
+    point->go();
+}
+
+template phase3 {
+    var("0") x;
+    
+    var("false") backtrack_check;
+    backtrack_point() point;
+    If (backtrack_check) {
+        val_equal(x, "10") a;
+        assert(a);
+        exit("0");
+    };
+    
+    process_manager() mgr;
+    
+    mgr->start("increment", {"1", "2", "false"});
+    val_equal(x, "1") a;
+    assert(a);
+    
+    mgr->start("increment", {"3", "4", "false"});
+    val_equal(x, "4") a;
+    assert(a);
+
+    backtrack_check->set("true");
+    point->go();
+}
+
+template increment {
+    var(_arg0) amount;
+    var(_arg1) amount_deinit;
+    var(_arg2) do_sleep;
+    imperative("<none>", {}, "increment_deinit_inc", {}, "10000");
+    num_add(_caller.x, amount) new_x;
+    _caller.x->set(new_x);
+    If (do_sleep) {
+        sleep("0", "0");
+    };
+    provide("INC_DONE");
+}
+
+template increment_deinit_inc {
+    num_add(_caller._caller.x, _caller.amount_deinit) new_x;
+    _caller._caller.x->set(new_x);
+}
diff --git a/external/badvpn_dns/ncd/tests/regex.ncd b/external/badvpn_dns/ncd/tests/regex.ncd
new file mode 100644
index 0000000..3175ebe
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/regex.ncd
@@ -0,0 +1,48 @@
+process main {
+    var("FOO BAR BAZ QUX goo") x;
+    regex_replace(x, {"FOO", "BAR", "goo"}, {"BAR", "bar", "GOO"}) y;
+    strcmp(y, "BAR bar BAZ QUX GOO") a;
+    assert(a);
+
+    var("hello world") x;
+    regex_replace(x, {"^hello"}, {"Hello,"}) y;
+    strcmp(y, "Hello, world") a;
+    assert(a);
+
+    var("hello world") x;
+    regex_replace(x, {"goodbye"}, {"hello"}) y;
+    strcmp(y, "hello world") a;
+    assert(a);
+
+    var("hello world") x;
+    regex_replace(x, {"hello world"}, {"hello NCD"}) y;
+    strcmp(y, "hello NCD") a;
+    assert(a);
+
+    var("hello world") x;
+    regex_replace(x, {"wor"}, {"Wor"}) y;
+    strcmp(y, "hello World") a;
+    assert(a);
+
+    var("hello world") x;
+    regex_replace(x, {"ell", "llo"}, {"ELL", "LLO"}) y;
+    strcmp(y, "hELLo world") a;
+    assert(a);
+
+    var("hello world") x;
+    regex_replace(x, {"ell", "el"}, {"ELL", "EL"}) y;
+    strcmp(y, "hELLo world") a;
+    assert(a);
+
+    var("hello world") x;
+    regex_replace(x, {"el", "lo"}, {"EL", "LO"}) y;
+    strcmp(y, "hELLO world") a;
+    assert(a);
+
+    var("hello world") x;
+    regex_replace(x, {"ell", "ll"}, {"ELL", "LL"}) y;
+    strcmp(y, "hELLo world") a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/ncd/tests/run_tests b/external/badvpn_dns/ncd/tests/run_tests
new file mode 100755
index 0000000..071fd20
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/run_tests
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+NCD=$1
+USE_VALGRIND=$2
+
+if [[ -z $NCD ]] || [[ -n $USE_VALGRIND && $USE_VALGRIND != use_valgrind ]]; then
+	echo "Usage: $0 <ncd_command> [use_valgrind]"
+	exit 1
+fi
+
+if [[ ! -e ./run_tests ]]; then
+	echo "Must run from the tests directory"
+	exit 1
+fi
+
+failed=0
+
+for file in ./*.ncd; do
+	echo "Running: $file"
+	if [[ $USE_VALGRIND = use_valgrind ]]; then
+		valgrind --error-exitcode=1 --leak-check=full "$NCD" --loglevel none --config-file "$file"
+	else
+		"$NCD" --loglevel none --config-file "$file"
+	fi
+	res=$?
+	if [[ ! $res -eq 0 ]]; then
+		echo "FAILED"
+		let failed+=1
+	fi
+done
+
+if [[ $failed -gt 0 ]]; then
+	echo "$failed tests FAILED"
+	exit 1
+fi
+
+echo "all tests passed"
+exit 0
diff --git a/external/badvpn_dns/ncd/tests/strings.ncd b/external/badvpn_dns/ncd/tests/strings.ncd
new file mode 100644
index 0000000..7be031b
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/strings.ncd
@@ -0,0 +1,47 @@
+process main {
+    buffer() buf;
+    buf->append("12");
+    buf->append("345");
+    buf->append("6");
+    num_equal(buf, "123456") a;
+    assert(a);
+    
+    var("false") check;
+    call("test_func", {});
+    assert(check);
+    
+    buffer() buf;
+    buf->append("test_func");
+    var("false") check;
+    call(buf, {});
+    assert(check);
+    
+    concat("test_func") cnc;
+    var("false") check;
+    call(cnc, {});
+    assert(check);
+    
+    buffer() buf;
+    buf->append("test_func");
+    var("false") check;
+    process_manager() mgr;
+    mgr->start(buf, {});
+    assert(check);
+    
+    buffer() buf;
+    buf->append("/bin/echo");
+    runonce({buf, buf});
+    
+    buffer() buf;
+    buf->append("12");
+    buf->append("345");
+    to_string(buf) str;
+    val_equal(str, "\"12345\"") a;
+    assert(a);
+    
+    exit("0");
+}
+
+template test_func {
+    _caller.check->set("true");
+}
diff --git a/external/badvpn_dns/ncd/tests/substr.ncd b/external/badvpn_dns/ncd/tests/substr.ncd
new file mode 100644
index 0000000..a3b07b1
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/substr.ncd
@@ -0,0 +1,37 @@
+process main {
+    var("0123456789") str;
+    concat(str) external_str;
+    
+    call("do_test", {"_caller.str"});
+    call("do_test", {"_caller.external_str"});
+    
+    exit("0");
+}
+
+template do_test {
+    alias(_arg0) str;
+    
+    substr(str, "0") sub;
+    val_equal(sub, "0123456789") a;
+    assert(a);
+    
+    substr(str, "2") sub;
+    val_equal(sub, "23456789") a;
+    assert(a);
+    
+    substr(str, "3", "0") sub;
+    val_equal(sub, "") a;
+    assert(a);
+    
+    substr(str, "3", "6") sub;
+    val_equal(sub, "345678") a;
+    assert(a);
+    
+    substr(str, "3", "7") sub;
+    val_equal(sub, "3456789") a;
+    assert(a);
+    
+    substr(str, "3", "8") sub;
+    val_equal(sub, "3456789") a;
+    assert(a);
+}
diff --git a/external/badvpn_dns/ncd/tests/turing.ncd b/external/badvpn_dns/ncd/tests/turing.ncd
new file mode 100644
index 0000000..4e764f1
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/turing.ncd
@@ -0,0 +1,138 @@
+process main {
+    # Turing machine specification.
+    var("B") blank;
+    var([
+        {"0", "0"}:{"0", "0", "right"},
+        {"0", "1"}:{"1", "x", "right"},
+        {"1", "1"}:{"1", "1", "right"},
+        {"1", "0"}:{"2", "0", "right"},
+        {"2", "0"}:{"2", "0", "right"},
+        {"2", "1"}:{"3", "1", "right"},
+        {"3", "1"}:{"3", "1", "right"},
+        {"3", "0"}:{"4", "1", "left"},
+        {"3", "B"}:{"4", "1", "left"},
+        {"4", "1"}:{"4", "1", "left"},
+        {"4", "0"}:{"5", "0", "left"},
+        {"5", "0"}:{"5", "0", "left"},
+        {"5", "1"}:{"6", "1", "left"},
+        {"5", "x"}:{"h", "x", "stay"},
+        {"6", "1"}:{"6", "1", "left"},
+        {"6", "x"}:{"0", "x", "right"},
+        {"6", "0"}:{"0", "0", "right"}
+    ]) rules;
+    var("0") initial_state;
+    var({}) initial_tape_left;
+    var({
+        "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1",
+        "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1",
+        "0", "0",
+        "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1",
+        "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1"
+    }) initial_tape_right;
+
+    # Perform the computation, stopping when no rule matches.
+    call("turing", {blank, rules, initial_state, initial_tape_left, initial_tape_right}) results;
+
+    # Check results.
+
+    val_equal(results.tape_left, {"B"}) a;
+    assert(a);
+
+    val_equal(results.tape_right,
+        {"x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x",
+         "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x",
+         "0", "0", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1",
+         "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1",
+         "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1",
+         "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1",
+         "1", "1"}
+    ) a;
+    assert(a);
+
+    val_equal({results.side, results.pos}, {"right", "55"}) a;
+    assert(a);
+
+    val_equal(results.state, "h") a;
+    assert(a);
+
+    exit("0");
+}
+
+template turing {
+    alias("_arg0") blank;
+    value(_arg1) rules;
+    alias("_arg2") initial_state;
+    alias("_arg3") initial_tape_left;
+    alias("_arg4") initial_tape_right;
+
+    # Head state.
+    var(initial_state) state;
+
+    # Tape. Positions go like this: ... L2 L1 L0 R0 R1 R2 ... 
+    value(initial_tape_left) tape_left;
+    value(initial_tape_right) tape_right;
+
+    # Make sure each side of the tape has at least one symbol so we can flip easily.
+    tape_left->insert(tape_left.length, blank);
+    tape_right->insert(tape_right.length, blank);
+
+    # Head position.
+    var("right") side;
+    var("0") pos;
+
+    # Enter loop.
+    blocker() loop_blk;
+    loop_blk->up();
+    loop_blk->use();
+
+    # Get symbol under head.
+    concat("tape_", side) tape_name;
+    alias(tape_name) cur_tape;
+    cur_tape->get(pos) symbol;
+
+    # Look for a matching rule.
+    rules->try_get({state, symbol}) rule;
+
+    If (rule.exists) {
+        # Extract directions from rule.
+        rule->get("0") new_state;
+        rule->get("1") new_symbol;
+        rule->get("2") move;
+
+        # Change head state.
+        state->set(new_state);
+
+        # Replace symbol under head.
+        cur_tape->remove(pos);
+        cur_tape->insert(pos, new_symbol);
+
+        # Branch based on how we move.
+        strcmp(move, side) is_outside;
+        strcmp(move, "stay") is_stay;
+        strcmp(pos, "0") is_zero;
+        If (is_outside) {
+            # Increment position.
+            num_add(pos, "1") new_pos;
+            pos->set(new_pos);
+
+            # If the new position is out of range, extend tape.
+            strcmp(pos, cur_tape.length) need_extend;
+            If (need_extend) {
+                cur_tape->insert(pos, blank);
+            };
+        } elif (is_stay) {
+            # Nop.
+            getargs();
+        } elif (is_zero) {
+            # Flip side, leave pos at zero.
+            side->set(move);
+        } else {
+            # Decrement position.
+            num_subtract(pos, "1") new_pos;
+            pos->set(new_pos);
+        };
+
+        # Continue loop.
+        loop_blk->downup();
+    };
+}
diff --git a/external/badvpn_dns/ncd/tests/value.ncd b/external/badvpn_dns/ncd/tests/value.ncd
new file mode 100644
index 0000000..e483b3f
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/value.ncd
@@ -0,0 +1,258 @@
+process main {
+    value({"A", {"B", "C"}, {{"D"}, "E"}}) v;
+    val_equal(v, {"A", {"B", "C"}, {{"D"}, "E"}}) a;
+    assert(a);
+
+    v->get("1") w;
+    val_equal(w, {"B", "C"}) a;
+    assert(a);
+
+    w->delete();
+    val_equal(v, {"A", {{"D"}, "E"}}) a;
+    assert(a);
+
+    v->getpath({"1", "1"}) f;
+    val_equal(f, "E") a;
+    assert(a);
+
+    value(["hello":{"Hello", "Good evening!"}, "goodbye":{"Bye", "See you"}]) v;
+    val_equal(v, ["hello":{"Hello", "Good evening!"}, "goodbye":{"Bye", "See you"}]) a;
+    assert(a);
+
+    val_equal(v.keys, {"goodbye", "hello"}) a;
+    assert(a);
+
+    v->get("hello") h;
+    val_equal(h, {"Hello", "Good evening!"}) a;
+    assert(a);
+
+    v->get("goodbye") g;
+    val_equal(g, {"Bye", "See you"}) a;
+    assert(a);
+
+    g->delete();
+    val_equal(v, ["hello":{"Hello", "Good evening!"}]) a;
+    assert(a);
+
+    h->delete();
+    val_equal(v, []);
+    assert(a);
+
+    v->delete();
+    strcmp(v.exists, "false") a;
+    assert(a);
+
+    value({"D", "F", "H"}) v;
+    v->insert("0", "A") a; # ADFH
+    v->insert("1", "B") b; # ABDFH
+    v->insert("5", "I") i; # ABDFHI
+    val_equal(v, {"A", "B", "D", "F", "H", "I"}) a;
+    assert(a);
+
+    value(["k1":"v1", "k2":"v2", "k3":"v3"]) v;
+    v->insert("k0", "v0") v0; # k0=v0 k1=v1 k2=v2 k3=v3
+    v->insert("k0", "V0") V0; # k0=V0 k1=v1 k2=v2 k3=v3
+    val_equal(v0, "v0") a;
+    assert(a);
+    val_equal(V0, "V0") a;
+    assert(a);
+
+    value({"D", "F", "H"}) v;
+    v->remove("0"); # FH
+    v->remove("1"); # F
+    val_equal(v, {"F"}) a;
+    assert(a);
+
+    value(["k1":"v1", "k2":"v2", "k3":"v3"]) v;
+    v->remove("k1");
+    v->remove("k3");
+    val_equal(v, ["k2":"v2"]) a;
+    assert(a);
+
+    value(["k1":"v1", "k2":"v2", "k3":"v3"]) v;
+    v->try_get("k1") v1;
+    v->try_get("k7") v7;
+    val_equal(v1.exists, "true") a;
+    assert(a);
+    val_equal(v7.exists, "false") a;
+    assert(a);
+
+    value(["k1":"v1", "k2":"v2", "k3":"v3"]) v;
+    imperative("<none>", {}, "check1", {}, "10000");
+    v->insert_undo("k1", "V1") V1;
+    strcmp(V1, "V1") a;
+    assert(a);
+    v->insert_undo("k4", "V4");
+    v->remove("k2");
+    val_equal(v, ["k1":"V1", "k3":"v3", "k4":"V4"]) a;
+    assert(a);
+
+    value({"a", "b", "c"}) v;
+    v->replace("0", "A");
+    v->replace("1", "B");
+    v->replace("2", "C");
+    v->replace("3", "D");
+    val_equal(v, {"A", "B", "C", "D"}) a;
+    assert(a);
+
+    value({"a", "b", "c"}) v;
+    imperative("<none>", {}, "check2", {}, "10000");
+    v->replace_undo("0", "A");
+    v->replace_undo("1", "B");
+    v->replace_undo("2", "C");
+    v->replace_undo("3", "D");
+    val_equal(v, {"A", "B", "C", "D"}) a;
+    assert(a);
+
+    value("A") v;
+    v->reset("B");
+    val_equal(v, "B") a;
+    assert(a);
+
+    value({"a", "c"}) v;
+    v->insert_undo("1", "b") vb;
+    val_equal(v, {"a", "b", "c"}) a;
+    assert(a);
+    val_equal(vb, "b") a;
+    assert(a);
+    vb->reset("B");
+    val_equal(v, {"a", "c"}) a;
+    assert(a);
+    val_equal(vb, "B") a;
+    assert(a);
+
+    value({"a", "b", "c"}) v;
+    v->get("1") vb;
+    vb->replace_this("B") vB;
+    val_equal(vb, "b") a;
+    assert(a);
+    val_equal(vB, "B") a;
+    assert(a);
+    v->get("1") vB2;
+    val_equal(vB2, "B") a;
+    assert(a);
+
+    value(["a":"va", "b":"vb", "c":"vc"]) v;
+    v->get("b") vb;
+    vb->replace_this("vB") vB;
+    val_equal(vB, "vB") a;
+    assert(a);
+    val_equal(vb, "vb") a;
+    assert(a);
+    v->get("b") vB2;
+    val_equal(vB2, "vB") a;
+    assert(a);
+
+    value({"a", "b", "c"}) v;
+    v->get("1") vb;
+    imperative("<none>", {}, "check3", {}, "10000");
+    vb->replace_this_undo("B") vB;
+    val_equal(vb, "b") a;
+    assert(a);
+    val_equal(vB, "B") a;
+    assert(a);
+    v->get("1") vB2;
+    val_equal(vB2, "B") a;
+    assert(a);
+
+    value(["a":"va", "b":"vb", "c":"vc"]) v;
+    v->get("b") vb;
+    imperative("<none>", {}, "check4", {}, "10000");
+    vb->replace_this_undo("vB") vB;
+    val_equal(vB, "vB") a;
+    assert(a);
+    val_equal(vb, "vb") a;
+    assert(a);
+    v->get("b") vB2;
+    val_equal(vB2, "vB") a;
+    assert(a);
+    
+    value("") v;
+    v->append("ab");
+    v->append("cde");
+    v->append(v);
+    v->append("");
+    v->append("f");
+    val_equal(v, "abcdeabcdef") a;
+    assert(a);
+    
+    value({}) v;
+    v->insert("1") ins_v;
+    v->insert("2");
+    v->insert("3");
+    v->insert("4");
+    v->append("5");
+    v->append("6");
+    val_equal(v, {"1", "2", "3", "4", "5", "6"})  a;
+    assert(a);
+    val_equal(ins_v, "1") a;
+    assert(a);
+    
+    value({}) v;
+    imperative("<none>", {}, "check5", {}, "10000");
+    v->insert_undo("1");
+    v->insert_undo("2");
+    v->insert_undo("3");
+    v->insert_undo("4");
+    val_equal(v, {"1", "2", "3", "4"})  a;
+    assert(a);
+    
+    buffer() buf;
+    buf->append("123");
+    value(buf) v;
+    val_equal(v, "123") a;
+    assert(a);
+    v->substr("2") sub_v;
+    val_equal(sub_v, "3") a;
+    assert(a);
+    v->append("456789012345");
+    val_equal(v, "123456789012345") a;
+    assert(a);
+    buffer() numbuf;
+    numbuf->append("1");
+    numbuf->append("2");
+    v->substr(numbuf) sub_v;
+    val_equal(sub_v, "345") a;
+    assert(a);
+    
+    concat("hello", "world") cnc;
+    value(cnc) v;
+    val_equal(v, "helloworld") a;
+    assert(a);
+    v->substr("2") sub_v;
+    val_equal(sub_v, "lloworld") a;
+    assert(a);
+    v->append("!!");
+    val_equal(v, "helloworld!!") a;
+    assert(a);
+    v->substr("1") sub_v;
+    val_equal(sub_v, "elloworld!!") a;
+    assert(a);
+    
+    exit("0");
+}
+
+template check1 {
+    val_equal(_caller.v, ["k1":"v1", "k3":"v3"]) a;
+    assert(a);
+}
+
+template check2 {
+    val_equal(_caller.v, {"a", "b", "c"}) a;
+    assert(a);
+}
+
+template check3 {
+    val_equal(_caller.v, {"a", "b", "c"}) a;
+    assert(a);
+}
+
+template check4 {
+    val_equal(_caller.v, ["a":"va", "b":"vb", "c":"vc"]) a;
+    assert(a);
+}
+
+template check5 {
+    val_equal(_caller.v, {}) a;
+    assert(a);
+}
diff --git a/external/badvpn_dns/ncd/tests/value_substr.ncd b/external/badvpn_dns/ncd/tests/value_substr.ncd
new file mode 100644
index 0000000..ea46e8a
--- /dev/null
+++ b/external/badvpn_dns/ncd/tests/value_substr.ncd
@@ -0,0 +1,25 @@
+process foo {
+    value("0123456789") str;
+
+    str->substr("0") sub;
+    strcmp(sub, str) a;
+    assert(a);
+
+    str->substr("1") sub;
+    strcmp(sub, "123456789") a;
+    assert(a);
+
+    str->substr("1", "0") sub;
+    strcmp(sub, "") a;
+    assert(a);
+
+    str->substr("1", "9") sub;
+    strcmp(sub, "123456789") a;
+    assert(a);
+
+    str->substr("1", "8") sub;
+    strcmp(sub, "12345678") a;
+    assert(a);
+
+    exit("0");
+}
diff --git a/external/badvpn_dns/nspr_support/BSSLConnection.c b/external/badvpn_dns/nspr_support/BSSLConnection.c
new file mode 100644
index 0000000..fea9c94
--- /dev/null
+++ b/external/badvpn_dns/nspr_support/BSSLConnection.c
@@ -0,0 +1,1024 @@
+/**
+ * @file BSSLConnection.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <prerror.h>
+#include <ssl.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <misc/print_macros.h>
+#include <base/BLog.h>
+
+#include "BSSLConnection.h"
+
+#include <generated/blog_channel_BSSLConnection.h>
+
+#define THREADWORK_STATE_NONE 0
+#define THREADWORK_STATE_HANDSHAKE 1
+#define THREADWORK_STATE_READ 2
+#define THREADWORK_STATE_WRITE 3
+
+static void backend_threadwork_start (struct BSSLConnection_backend *b, int op);
+static int backend_threadwork_do_io (struct BSSLConnection_backend *b);
+static void connection_init_job_handler (BSSLConnection *o);
+static void connection_init_up (BSSLConnection *o);
+static void connection_try_io (BSSLConnection *o);
+static void connection_threadwork_func_work (void *user);
+static void connection_threadwork_handler_done (void *user);
+static void connection_recv_job_handler (BSSLConnection *o);
+static void connection_try_handshake (BSSLConnection *o);
+static void connection_try_send (BSSLConnection *o);
+static void connection_try_recv (BSSLConnection *o);
+static void connection_send_if_handler_send (BSSLConnection *o, uint8_t *data, int data_len);
+static void connection_recv_if_handler_recv (BSSLConnection *o, uint8_t *data, int data_len);
+
+int bprconnection_initialized = 0;
+PRDescIdentity bprconnection_identity;
+
+static PRFileDesc * get_bottom (PRFileDesc *layer)
+{
+    while (layer->lower) {
+        layer = layer->lower;
+    }
+    
+    return layer;
+}
+
+static PRStatus method_close (PRFileDesc *fd)
+{
+    struct BSSLConnection_backend *b = (struct BSSLConnection_backend *)fd->secret;
+    ASSERT(!b->con)
+    ASSERT(b->threadwork_state == THREADWORK_STATE_NONE)
+    
+    // free mutexes
+    if ((b->flags & BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE) || (b->flags & BSSLCONNECTION_FLAG_THREADWORK_IO)) {
+        BMutex_Free(&b->recv_buf_mutex);
+        BMutex_Free(&b->send_buf_mutex);
+    }
+    
+    // free backend
+    free(b);
+    
+    // set no secret
+    fd->secret = NULL;
+    
+    return PR_SUCCESS;
+}
+
+static PRInt32 method_read (PRFileDesc *fd, void *buf, PRInt32 amount)
+{
+    struct BSSLConnection_backend *b = (struct BSSLConnection_backend *)fd->secret;
+    ASSERT(amount > 0)
+    
+    if (b->threadwork_state != THREADWORK_STATE_NONE) {
+        BMutex_Lock(&b->recv_buf_mutex);
+    }
+    
+    // if we are receiving into buffer or buffer has no data left, refuse recv
+    if (b->recv_busy || b->recv_pos == b->recv_len) {
+        if (b->threadwork_state != THREADWORK_STATE_NONE) {
+            b->threadwork_want_recv = 1;
+            BMutex_Unlock(&b->recv_buf_mutex);
+        } else {
+            // start receiving if not already
+            if (!b->recv_busy) {
+                // set recv busy
+                b->recv_busy = 1;
+                
+                // receive into buffer
+                StreamRecvInterface_Receiver_Recv(b->recv_if, b->recv_buf, BSSLCONNECTION_BUF_SIZE);
+            }
+        }
+        PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+        return -1;
+    }
+    
+    // limit amount to available data
+    if (amount > b->recv_len - b->recv_pos) {
+        amount = b->recv_len - b->recv_pos;
+    }
+    
+    // copy data
+    memcpy(buf, b->recv_buf + b->recv_pos, amount);
+    
+    // update buffer
+    b->recv_pos += amount;
+    
+    if (b->threadwork_state != THREADWORK_STATE_NONE) {
+        BMutex_Unlock(&b->recv_buf_mutex);
+    }
+    
+    return amount;
+}
+
+static PRInt32 method_write (PRFileDesc *fd, const void *buf, PRInt32 amount)
+{
+    struct BSSLConnection_backend *b = (struct BSSLConnection_backend *)fd->secret;
+    ASSERT(amount > 0)
+    
+    if (b->threadwork_state != THREADWORK_STATE_NONE) {
+        BMutex_Lock(&b->send_buf_mutex);
+    }
+    
+    ASSERT(!b->send_busy || b->send_pos < b->send_len)
+    
+    // if there is data in buffer, refuse send
+    if (b->send_pos < b->send_len) {
+        if (b->threadwork_state != THREADWORK_STATE_NONE) {
+            b->threadwork_want_send = 1;
+            BMutex_Unlock(&b->send_buf_mutex);
+        }
+        PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+        return -1;
+    }
+    
+    // limit amount to buffer size
+    if (amount > BSSLCONNECTION_BUF_SIZE) {
+        amount = BSSLCONNECTION_BUF_SIZE;
+    }
+    
+    // init buffer
+    memcpy(b->send_buf, buf, amount);
+    b->send_pos = 0;
+    b->send_len = amount;
+    
+    if (b->threadwork_state != THREADWORK_STATE_NONE) {
+        BMutex_Unlock(&b->send_buf_mutex);
+    } else {
+        // start sending
+        b->send_busy = 1;
+        StreamPassInterface_Sender_Send(b->send_if, b->send_buf + b->send_pos, b->send_len - b->send_pos);
+    }
+    
+    return amount;
+}
+
+static PRStatus method_shutdown (PRFileDesc *fd, PRIntn how)
+{
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return PR_FAILURE;
+}
+
+static PRInt32 method_recv (PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout)
+{
+    ASSERT(flags == 0)
+    
+    return method_read(fd, buf, amount);
+}
+
+static PRInt32 method_send (PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout)
+{
+    ASSERT(flags == 0)
+    
+    return method_write(fd, buf, amount);
+}
+
+static PRInt16 method_poll (PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags)
+{
+    *out_flags = 0;
+    return in_flags;
+}
+
+static PRStatus method_getpeername (PRFileDesc *fd, PRNetAddr *addr)
+{
+    memset(addr, 0, sizeof(*addr));
+    addr->raw.family = PR_AF_INET;
+    return PR_SUCCESS;
+}
+
+static PRStatus method_getsocketoption (PRFileDesc *fd, PRSocketOptionData *data)
+{
+    switch (data->option) {
+        case PR_SockOpt_Nonblocking:
+            data->value.non_blocking = PR_TRUE;
+            return PR_SUCCESS;
+    }
+    
+    PR_SetError(PR_UNKNOWN_ERROR, 0);
+    return PR_FAILURE;
+}
+
+static PRStatus method_setsocketoption (PRFileDesc *fd, const PRSocketOptionData *data)
+{
+    PR_SetError(PR_UNKNOWN_ERROR, 0);
+    return PR_FAILURE;
+}
+
+static PRIntn _PR_InvalidIntn (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PRInt32 _PR_InvalidInt32 (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PRInt64 _PR_InvalidInt64 (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PROffset32 _PR_InvalidOffset32 (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PROffset64 _PR_InvalidOffset64 (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PRStatus _PR_InvalidStatus (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return PR_FAILURE;
+}
+
+static PRFileDesc *_PR_InvalidDesc (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return NULL;
+}
+
+static PRIOMethods methods = {
+    (PRDescType)0,
+    method_close,
+    method_read,
+    method_write,
+    (PRAvailableFN)_PR_InvalidInt32,
+    (PRAvailable64FN)_PR_InvalidInt64,
+    (PRFsyncFN)_PR_InvalidStatus,
+    (PRSeekFN)_PR_InvalidOffset32,
+    (PRSeek64FN)_PR_InvalidOffset64,
+    (PRFileInfoFN)_PR_InvalidStatus,
+    (PRFileInfo64FN)_PR_InvalidStatus,
+    (PRWritevFN)_PR_InvalidInt32,
+    (PRConnectFN)_PR_InvalidStatus,
+    (PRAcceptFN)_PR_InvalidDesc,
+    (PRBindFN)_PR_InvalidStatus,
+    (PRListenFN)_PR_InvalidStatus,
+    method_shutdown,
+    method_recv,
+    method_send,
+    (PRRecvfromFN)_PR_InvalidInt32,
+    (PRSendtoFN)_PR_InvalidInt32,
+    method_poll,
+    (PRAcceptreadFN)_PR_InvalidInt32,
+    (PRTransmitfileFN)_PR_InvalidInt32,
+    (PRGetsocknameFN)_PR_InvalidStatus,
+    method_getpeername,
+    (PRReservedFN)_PR_InvalidIntn,
+    (PRReservedFN)_PR_InvalidIntn,
+    method_getsocketoption,
+    method_setsocketoption,
+    (PRSendfileFN)_PR_InvalidInt32,
+    (PRConnectcontinueFN)_PR_InvalidStatus,
+    (PRReservedFN)_PR_InvalidIntn,
+    (PRReservedFN)_PR_InvalidIntn,
+    (PRReservedFN)_PR_InvalidIntn,
+    (PRReservedFN)_PR_InvalidIntn
+};
+
+static void backend_send_if_handler_done (struct BSSLConnection_backend *b, int data_len)
+{
+    ASSERT(b->send_busy)
+    ASSERT(b->send_len > 0)
+    ASSERT(b->send_pos < b->send_len)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= b->send_len - b->send_pos)
+    
+    if (b->threadwork_state != THREADWORK_STATE_NONE) {
+        BMutex_Lock(&b->send_buf_mutex);
+    }
+    
+    // update buffer
+    b->send_pos += data_len;
+    
+    // send more if needed
+    if (b->send_pos < b->send_len) {
+        StreamPassInterface_Sender_Send(b->send_if, b->send_buf + b->send_pos, b->send_len - b->send_pos);
+        if (b->threadwork_state != THREADWORK_STATE_NONE) {
+            BMutex_Unlock(&b->send_buf_mutex);
+        }
+        return;
+    }
+    
+    // set send not busy
+    b->send_busy = 0;
+    
+    if (b->threadwork_state != THREADWORK_STATE_NONE) {
+        BMutex_Unlock(&b->send_buf_mutex);
+    }
+    
+    // notify connection
+    if (b->con && !b->con->have_error) {
+        connection_try_io(b->con);
+        return;
+    }
+}
+
+static void backend_recv_if_handler_done (struct BSSLConnection_backend *b, int data_len)
+{
+    ASSERT(b->recv_busy)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= BSSLCONNECTION_BUF_SIZE)
+    
+    if (b->threadwork_state != THREADWORK_STATE_NONE) {
+        BMutex_Lock(&b->recv_buf_mutex);
+    }
+    
+    // init buffer
+    b->recv_busy = 0;
+    b->recv_pos = 0;
+    b->recv_len = data_len;
+    
+    if (b->threadwork_state != THREADWORK_STATE_NONE) {
+        BMutex_Unlock(&b->recv_buf_mutex);
+    }
+    
+    // notify connection
+    if (b->con && !b->con->have_error) {
+        connection_try_io(b->con);
+        return;
+    }
+}
+
+static void backend_threadwork_start (struct BSSLConnection_backend *b, int op)
+{
+    ASSERT(b->con)
+    ASSERT(b->threadwork_state == THREADWORK_STATE_NONE)
+    ASSERT(op == THREADWORK_STATE_HANDSHAKE || op == THREADWORK_STATE_READ || op == THREADWORK_STATE_WRITE)
+    
+    b->threadwork_state = op;
+    b->threadwork_want_recv = 0;
+    b->threadwork_want_send = 0;
+    BThreadWork_Init(&b->threadwork, b->twd, connection_threadwork_handler_done, b->con, connection_threadwork_func_work, b->con);
+}
+
+static int backend_threadwork_do_io (struct BSSLConnection_backend *b)
+{
+    ASSERT(b->con)
+    ASSERT(b->threadwork_state == THREADWORK_STATE_NONE)
+    
+    int io_ready = (b->threadwork_want_recv && !b->recv_busy && b->recv_pos < b->recv_len) ||
+                   (b->threadwork_want_send && b->send_pos == b->send_len);
+    
+    if (b->threadwork_want_recv && b->recv_pos == b->recv_len && !b->recv_busy) {
+        b->recv_busy = 1;
+        StreamRecvInterface_Receiver_Recv(b->recv_if, b->recv_buf, BSSLCONNECTION_BUF_SIZE);
+    }
+    
+    if (b->send_pos < b->send_len && !b->send_busy) {
+        b->send_busy = 1;
+        StreamPassInterface_Sender_Send(b->send_if, b->send_buf + b->send_pos, b->send_len - b->send_pos);
+    }
+    
+    return io_ready;
+}
+
+static void connection_report_error (BSSLConnection *o)
+{
+    ASSERT(!o->have_error)
+    
+    // set error
+    o->have_error = 1;
+    
+    // report error
+    DEBUGERROR(&o->d_err, o->handler(o->user, BSSLCONNECTION_EVENT_ERROR));
+}
+
+static void connection_init_job_handler (BSSLConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->have_error)
+    ASSERT(!o->up)
+    
+    connection_try_handshake(o);
+}
+
+static void connection_init_up (BSSLConnection *o)
+{
+    // unset init job
+    // (just in the impossible case that handshake completed before the init job executed)
+    BPending_Unset(&o->init_job);
+    
+    // init send interface
+    StreamPassInterface_Init(&o->send_if, (StreamPassInterface_handler_send)connection_send_if_handler_send, o, o->pg);
+    
+    // init recv interface
+    StreamRecvInterface_Init(&o->recv_if, (StreamRecvInterface_handler_recv)connection_recv_if_handler_recv, o, o->pg);
+    
+    // init recv job
+    BPending_Init(&o->recv_job, o->pg, (BPending_handler)connection_recv_job_handler, o);
+    
+    // set no send data
+    o->send_len = -1;
+    
+    // set no recv data
+    o->recv_avail = -1;
+    
+    // set up
+    o->up = 1;
+}
+
+static void connection_try_io (BSSLConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->have_error)
+    
+    if (!o->up) {
+        connection_try_handshake(o);
+        return;
+    }
+    
+    if (o->send_len > 0) {
+        if (o->recv_avail > 0) {
+            BPending_Set(&o->recv_job);
+        }
+        
+        connection_try_send(o);
+        return;
+    }
+    
+    if (o->recv_avail > 0) {
+        connection_try_recv(o);
+        return;
+    }
+}
+
+static void connection_threadwork_func_work (void *user)
+{
+    BSSLConnection *o = (BSSLConnection *)user;
+    struct BSSLConnection_backend *b = o->backend;
+    ASSERT(b->threadwork_state != THREADWORK_STATE_NONE)
+    
+    switch (b->threadwork_state) {
+        case THREADWORK_STATE_HANDSHAKE:
+            b->threadwork_result_sec = SSL_ForceHandshake(o->prfd);
+            break;
+        case THREADWORK_STATE_WRITE:
+            b->threadwork_result_pr = PR_Write(o->prfd, o->send_data, o->send_len);
+            break;
+        case THREADWORK_STATE_READ:
+            b->threadwork_result_pr = PR_Read(o->prfd, o->recv_data, o->recv_avail);
+            break;
+        default:
+            ASSERT(0);
+    }
+    
+    b->threadwork_error = PR_GetError();
+}
+
+static void connection_threadwork_handler_done (void *user)
+{
+    BSSLConnection *o = (BSSLConnection *)user;
+    struct BSSLConnection_backend *b = o->backend;
+    ASSERT(b->threadwork_state != THREADWORK_STATE_NONE)
+    
+    // remember what operation the threadwork was performing
+    int op = b->threadwork_state;
+    
+    // free threadwork
+    BThreadWork_Free(&b->threadwork);
+    b->threadwork_state = THREADWORK_STATE_NONE;
+    
+    // start any necessary backend I/O operations, and determine if any of the requested
+    // backend I/O that was not available at the time is now available
+    int io_ready = backend_threadwork_do_io(b);
+    
+    switch (op) {
+        case THREADWORK_STATE_HANDSHAKE: {
+            ASSERT(!o->up)
+            ASSERT((b->flags & BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE))
+            
+            if (b->threadwork_result_sec == SECFailure) {
+                if (b->threadwork_error == PR_WOULD_BLOCK_ERROR) {
+                    if (io_ready) {
+                        // requested backend I/O got ready, try again
+                        backend_threadwork_start(o->backend, THREADWORK_STATE_HANDSHAKE);
+                    }
+                    return;
+                }
+                BLog(BLOG_ERROR, "SSL_ForceHandshake failed (%"PRIi32")", b->threadwork_error);
+                connection_report_error(o);
+                return;
+            }
+            
+            // init up
+            connection_init_up(o);
+            
+            // report up
+            o->handler(o->user, BSSLCONNECTION_EVENT_UP);
+            return;
+        } break;
+        
+        case THREADWORK_STATE_WRITE: {
+            ASSERT(o->up)
+            ASSERT((b->flags & BSSLCONNECTION_FLAG_THREADWORK_IO))
+            ASSERT(o->send_len > 0)
+            
+            PRInt32 result = b->threadwork_result_pr;
+            PRErrorCode error = b->threadwork_error;
+            
+            if (result < 0) {
+                if (error == PR_WOULD_BLOCK_ERROR) {
+                    if (io_ready) {
+                        // requested backend I/O got ready, try again
+                        backend_threadwork_start(o->backend, THREADWORK_STATE_WRITE);
+                    } else if (o->recv_avail > 0) {
+                        // don't forget about receiving
+                        backend_threadwork_start(o->backend, THREADWORK_STATE_READ);
+                    }
+                    return;
+                }
+                BLog(BLOG_ERROR, "PR_Write failed (%"PRIi32")", error);
+                connection_report_error(o);
+                return;
+            }
+            
+            ASSERT(result > 0)
+            ASSERT(result <= o->send_len)
+            
+            // set no send data
+            o->send_len = -1;
+            
+            // don't forget about receiving
+            if (o->recv_avail > 0) {
+                backend_threadwork_start(o->backend, THREADWORK_STATE_READ);
+            }
+            
+            // finish send operation
+            StreamPassInterface_Done(&o->send_if, result);
+        } break;
+        
+        case THREADWORK_STATE_READ: {
+            ASSERT(o->up)
+            ASSERT((b->flags & BSSLCONNECTION_FLAG_THREADWORK_IO))
+            ASSERT(o->recv_avail > 0)
+            
+            PRInt32 result = b->threadwork_result_pr;
+            PRErrorCode error = b->threadwork_error;
+            
+            if (result < 0) {
+                if (error == PR_WOULD_BLOCK_ERROR) {
+                    if (io_ready) {
+                        // requested backend I/O got ready, try again
+                        backend_threadwork_start(o->backend, THREADWORK_STATE_READ);
+                    } else if (o->send_len > 0) {
+                        // don't forget about sending
+                        backend_threadwork_start(o->backend, THREADWORK_STATE_WRITE);
+                    }
+                    return;
+                }
+                BLog(BLOG_ERROR, "PR_Read failed (%"PRIi32")", error);
+                connection_report_error(o);
+                return;
+            }
+            
+            if (result == 0) {
+                BLog(BLOG_ERROR, "PR_Read returned 0");
+                connection_report_error(o);
+                return;
+            }
+            
+            ASSERT(result > 0)
+            ASSERT(result <= o->recv_avail)
+            
+            // set no recv data
+            o->recv_avail = -1;
+            
+            // don't forget about sending
+            if (o->send_len > 0) {
+                backend_threadwork_start(o->backend, THREADWORK_STATE_WRITE);
+            }
+            
+            // finish receive operation
+            StreamRecvInterface_Done(&o->recv_if, result);
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+    
+    return;
+}
+
+static void connection_recv_job_handler (BSSLConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->have_error)
+    ASSERT(o->up)
+    ASSERT(o->recv_avail > 0)
+    
+    connection_try_recv(o);
+    return;
+}
+
+static void connection_try_handshake (BSSLConnection *o)
+{
+    ASSERT(!o->have_error)
+    ASSERT(!o->up)
+    
+    // continue in threadwork if requested
+    if ((o->backend->flags & BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE)) {
+        if (o->backend->threadwork_state == THREADWORK_STATE_NONE) {
+            backend_threadwork_start(o->backend, THREADWORK_STATE_HANDSHAKE);
+        }
+        return;
+    }
+    
+    // try handshake
+    SECStatus res = SSL_ForceHandshake(o->prfd);
+    if (res == SECFailure) {
+        PRErrorCode error = PR_GetError();
+        if (error == PR_WOULD_BLOCK_ERROR) {
+            return;
+        }
+        BLog(BLOG_ERROR, "SSL_ForceHandshake failed (%"PRIi32")", error);
+        connection_report_error(o);
+        return;
+    }
+    
+    // init up
+    connection_init_up(o);
+    
+    // report up
+    o->handler(o->user, BSSLCONNECTION_EVENT_UP);
+    return;
+}
+
+static void connection_try_send (BSSLConnection *o)
+{
+    ASSERT(!o->have_error)
+    ASSERT(o->up)
+    ASSERT(o->send_len > 0)
+    
+    // continue in threadwork if requested
+    if ((o->backend->flags & BSSLCONNECTION_FLAG_THREADWORK_IO)) {
+        if (o->backend->threadwork_state == THREADWORK_STATE_NONE) {
+            backend_threadwork_start(o->backend, THREADWORK_STATE_WRITE);
+        }
+        return;
+    }
+    
+    // send
+    PRInt32 res = PR_Write(o->prfd, o->send_data, o->send_len);
+    if (res < 0) {
+        PRErrorCode error = PR_GetError();
+        if (error == PR_WOULD_BLOCK_ERROR) {
+            return;
+        }
+        BLog(BLOG_ERROR, "PR_Write failed (%"PRIi32")", error);
+        connection_report_error(o);
+        return;
+    }
+    
+    ASSERT(res > 0)
+    ASSERT(res <= o->send_len)
+    
+    // set no send data
+    o->send_len = -1;
+    
+    // done
+    StreamPassInterface_Done(&o->send_if, res);
+}
+
+static void connection_try_recv (BSSLConnection *o)
+{
+    ASSERT(!o->have_error)
+    ASSERT(o->up)
+    ASSERT(o->recv_avail > 0)
+    
+    // unset recv job
+    BPending_Unset(&o->recv_job);
+    
+    // continue in threadwork if requested
+    if ((o->backend->flags & BSSLCONNECTION_FLAG_THREADWORK_IO)) {
+        if (o->backend->threadwork_state == THREADWORK_STATE_NONE) {
+            backend_threadwork_start(o->backend, THREADWORK_STATE_READ);
+        }
+        return;
+    }
+    
+    // recv
+    PRInt32 res = PR_Read(o->prfd, o->recv_data, o->recv_avail);
+    if (res < 0) {
+        PRErrorCode error = PR_GetError();
+        if (error == PR_WOULD_BLOCK_ERROR) {
+            return;
+        }
+        BLog(BLOG_ERROR, "PR_Read failed (%"PRIi32")", error);
+        connection_report_error(o);
+        return;
+    }
+    
+    if (res == 0) {
+        BLog(BLOG_ERROR, "PR_Read returned 0");
+        connection_report_error(o);
+        return;
+    }
+    
+    ASSERT(res > 0)
+    ASSERT(res <= o->recv_avail)
+    
+    // set no recv data
+    o->recv_avail = -1;
+    
+    // done
+    StreamRecvInterface_Done(&o->recv_if, res);
+}
+
+static void connection_send_if_handler_send (BSSLConnection *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->have_error)
+    ASSERT(o->up)
+    ASSERT(o->send_len == -1)
+    ASSERT(data_len > 0)
+    
+#ifndef NDEBUG
+    ASSERT(!o->releasebuffers_called)
+    o->user_io_started = 1;
+#endif
+    
+    // limit amount for PR_Write
+    if (data_len > INT32_MAX) {
+        data_len = INT32_MAX;
+    }
+    
+    // set send data
+    o->send_data = data;
+    o->send_len = data_len;
+    
+    // start sending
+    connection_try_send(o);
+}
+
+static void connection_recv_if_handler_recv (BSSLConnection *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->have_error)
+    ASSERT(o->up)
+    ASSERT(o->recv_avail == -1)
+    ASSERT(data_len > 0)
+    
+#ifndef NDEBUG
+    ASSERT(!o->releasebuffers_called)
+    o->user_io_started = 1;
+#endif
+    
+    // limit amount for PR_Read
+    if (data_len > INT32_MAX) {
+        data_len = INT32_MAX;
+    }
+    
+    // set recv data
+    o->recv_data = data;
+    o->recv_avail = data_len;
+    
+    // start receiving
+    connection_try_recv(o);
+}
+
+int BSSLConnection_GlobalInit (void)
+{
+    ASSERT(!bprconnection_initialized)
+    
+    if ((bprconnection_identity = PR_GetUniqueIdentity("BSSLConnection")) == PR_INVALID_IO_LAYER) {
+        BLog(BLOG_ERROR, "PR_GetUniqueIdentity failed");
+        return 0;
+    }
+    
+    bprconnection_initialized = 1;
+    
+    return 1;
+}
+
+int BSSLConnection_MakeBackend (PRFileDesc *prfd, StreamPassInterface *send_if, StreamRecvInterface *recv_if, BThreadWorkDispatcher *twd, int flags)
+{
+    ASSERT(bprconnection_initialized)
+    ASSERT(!(flags & ~(BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE | BSSLCONNECTION_FLAG_THREADWORK_IO)))
+    ASSERT(!(flags & BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE) || twd)
+    ASSERT(!(flags & BSSLCONNECTION_FLAG_THREADWORK_IO) || twd)
+    
+    // don't do stuff in threads if threads aren't available
+    if (((flags & BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE) || (flags & BSSLCONNECTION_FLAG_THREADWORK_IO)) &&
+        !BThreadWorkDispatcher_UsingThreads(twd)
+    ) {
+        BLog(BLOG_WARNING, "SSL operations in threads requested but threads are not available");
+        flags &= ~(BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE | BSSLCONNECTION_FLAG_THREADWORK_IO);
+    }
+    
+    // allocate backend
+    struct BSSLConnection_backend *b = (struct BSSLConnection_backend *)malloc(sizeof(*b));
+    if (!b) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // init mutexes
+    if ((flags & BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE) || (flags & BSSLCONNECTION_FLAG_THREADWORK_IO)) {
+        if (!BMutex_Init(&b->send_buf_mutex)) {
+            BLog(BLOG_ERROR, "BMutex_Init failed");
+            goto fail1;
+        }
+        
+        if (!BMutex_Init(&b->recv_buf_mutex)) {
+            BLog(BLOG_ERROR, "BMutex_Init failed");
+            goto fail2;
+        }
+    }
+    
+    // init arguments
+    b->send_if = send_if;
+    b->recv_if = recv_if;
+    b->twd = twd;
+    b->flags = flags;
+    
+    // init interfaces
+    StreamPassInterface_Sender_Init(b->send_if, (StreamPassInterface_handler_done)backend_send_if_handler_done, b);
+    StreamRecvInterface_Receiver_Init(b->recv_if, (StreamRecvInterface_handler_done)backend_recv_if_handler_done, b);
+    
+    // set no connection
+    b->con = NULL;
+    
+    // init send buffer
+    b->send_busy = 0;
+    b->send_len = 0;
+    b->send_pos = 0;
+    
+    // init recv buffer
+    b->recv_busy = 0;
+    b->recv_pos = 0;
+    b->recv_len = 0;
+    
+    // set threadwork state
+    b->threadwork_state = THREADWORK_STATE_NONE;
+    
+    // init prfd
+    memset(prfd, 0, sizeof(*prfd));
+    prfd->methods = &methods;
+    prfd->secret = (PRFilePrivate *)b;
+    prfd->identity = bprconnection_identity;
+    
+    return 1;
+    
+    if ((flags & BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE) || (flags & BSSLCONNECTION_FLAG_THREADWORK_IO)) {
+fail2:
+        BMutex_Free(&b->send_buf_mutex);
+    }
+fail1:
+    free(b);
+fail0:
+    return 0;
+}
+
+void BSSLConnection_Init (BSSLConnection *o, PRFileDesc *prfd, int force_handshake, BPendingGroup *pg, void *user,
+                          BSSLConnection_handler handler)
+{
+    ASSERT(force_handshake == 0 || force_handshake == 1)
+    ASSERT(handler)
+    ASSERT(bprconnection_initialized)
+    ASSERT(get_bottom(prfd)->identity == bprconnection_identity)
+    ASSERT(!((struct BSSLConnection_backend *)(get_bottom(prfd)->secret))->con)
+    
+    // init arguments
+    o->prfd = prfd;
+    o->pg = pg;
+    o->user = user;
+    o->handler = handler;
+    
+    // set backend
+    o->backend = (struct BSSLConnection_backend *)(get_bottom(prfd)->secret);
+    ASSERT(!o->backend->con)
+    ASSERT(o->backend->threadwork_state == THREADWORK_STATE_NONE)
+    
+    // set have no error
+    o->have_error = 0;
+    
+    // init init job
+    BPending_Init(&o->init_job, o->pg, (BPending_handler)connection_init_job_handler, o);
+    
+    if (force_handshake) {
+        // set not up
+        o->up = 0;
+        
+        // set init job
+        BPending_Set(&o->init_job);
+    } else {
+        // init up
+        connection_init_up(o);
+    }
+    
+    // set backend connection
+    o->backend->con = o;
+    
+#ifndef NDEBUG
+    o->user_io_started = 0;
+    o->releasebuffers_called = 0;
+#endif
+    
+    DebugError_Init(&o->d_err, o->pg);
+    DebugObject_Init(&o->d_obj);
+}
+
+void BSSLConnection_Free (BSSLConnection *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+#ifndef NDEBUG
+    ASSERT(o->releasebuffers_called || !o->user_io_started)
+#endif
+    ASSERT(o->backend->threadwork_state == THREADWORK_STATE_NONE)
+    
+    if (o->up) {
+        // free recv job
+        BPending_Free(&o->recv_job);
+        
+        // free recv interface
+        StreamRecvInterface_Free(&o->recv_if);
+        
+        // free send interface
+        StreamPassInterface_Free(&o->send_if);
+    }
+    
+    // free init job
+    BPending_Free(&o->init_job);
+    
+    // unset backend connection
+    o->backend->con = NULL;
+}
+
+void BSSLConnection_ReleaseBuffers (BSSLConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+#ifndef NDEBUG
+    ASSERT(!o->releasebuffers_called)
+#endif
+    
+    // wait for threadwork to finish
+    if (o->backend->threadwork_state != THREADWORK_STATE_NONE) {
+        BThreadWork_Free(&o->backend->threadwork);
+        o->backend->threadwork_state = THREADWORK_STATE_NONE;
+    }
+    
+#ifndef NDEBUG
+    o->releasebuffers_called = 1;
+#endif
+}
+
+StreamPassInterface * BSSLConnection_GetSendIf (BSSLConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->up)
+    
+    return &o->send_if;
+}
+
+StreamRecvInterface * BSSLConnection_GetRecvIf (BSSLConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->up)
+    
+    return &o->recv_if;
+}
diff --git a/external/badvpn_dns/nspr_support/BSSLConnection.h b/external/badvpn_dns/nspr_support/BSSLConnection.h
new file mode 100644
index 0000000..1152cac
--- /dev/null
+++ b/external/badvpn_dns/nspr_support/BSSLConnection.h
@@ -0,0 +1,116 @@
+/**
+ * @file BSSLConnection.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_BSSLCONNECTION_H
+#define BADVPN_BSSLCONNECTION_H
+
+#include <prio.h>
+#include <ssl.h>
+
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+#include <base/BMutex.h>
+#include <flow/StreamPassInterface.h>
+#include <flow/StreamRecvInterface.h>
+#include <threadwork/BThreadWork.h>
+
+#define BSSLCONNECTION_EVENT_UP 1
+#define BSSLCONNECTION_EVENT_ERROR 2
+
+#define BSSLCONNECTION_BUF_SIZE 4096
+
+#define BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE (1 << 0)
+#define BSSLCONNECTION_FLAG_THREADWORK_IO (1 << 1)
+
+typedef void (*BSSLConnection_handler) (void *user, int event);
+
+struct BSSLConnection_backend;
+
+typedef struct {
+    PRFileDesc *prfd;
+    BPendingGroup *pg;
+    void *user;
+    BSSLConnection_handler handler;
+    struct BSSLConnection_backend *backend;
+    int have_error;
+    int up;
+    BPending init_job;
+    StreamPassInterface send_if;
+    StreamRecvInterface recv_if;
+    BPending recv_job;
+    const uint8_t *send_data;
+    int send_len;
+    uint8_t *recv_data;
+    int recv_avail;
+#ifndef NDEBUG
+    int user_io_started;
+    int releasebuffers_called;
+#endif
+    DebugError d_err;
+    DebugObject d_obj;
+} BSSLConnection;
+
+struct BSSLConnection_backend {
+    StreamPassInterface *send_if;
+    StreamRecvInterface *recv_if;
+    BThreadWorkDispatcher *twd;
+    int flags;
+    BSSLConnection *con;
+    uint8_t send_buf[BSSLCONNECTION_BUF_SIZE];
+    int send_busy;
+    int send_pos;
+    int send_len;
+    uint8_t recv_buf[BSSLCONNECTION_BUF_SIZE];
+    int recv_busy;
+    int recv_pos;
+    int recv_len;
+    int threadwork_state;
+    int threadwork_want_recv;
+    int threadwork_want_send;
+    BThreadWork threadwork;
+    SECStatus threadwork_result_sec;
+    PRInt32 threadwork_result_pr;
+    PRErrorCode threadwork_error;
+    BMutex send_buf_mutex;
+    BMutex recv_buf_mutex;
+};
+
+int BSSLConnection_GlobalInit (void) WARN_UNUSED;
+int BSSLConnection_MakeBackend (PRFileDesc *prfd, StreamPassInterface *send_if, StreamRecvInterface *recv_if, BThreadWorkDispatcher *twd, int flags) WARN_UNUSED;
+
+void BSSLConnection_Init (BSSLConnection *o, PRFileDesc *prfd, int force_handshake, BPendingGroup *pg, void *user,
+                          BSSLConnection_handler handler);
+void BSSLConnection_Free (BSSLConnection *o);
+void BSSLConnection_ReleaseBuffers (BSSLConnection *o);
+StreamPassInterface * BSSLConnection_GetSendIf (BSSLConnection *o);
+StreamRecvInterface * BSSLConnection_GetRecvIf (BSSLConnection *o);
+
+#endif
diff --git a/external/badvpn_dns/nspr_support/CMakeLists.txt b/external/badvpn_dns/nspr_support/CMakeLists.txt
new file mode 100644
index 0000000..d2eb3e7
--- /dev/null
+++ b/external/badvpn_dns/nspr_support/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(NSPRSUPPORT_SOURCES
+    DummyPRFileDesc.c
+    BSSLConnection.c
+)
+badvpn_add_library(nspr_support "system;flow;threadwork" "${NSPR_LIBRARIES};${NSS_LIBRARIES}" "${NSPRSUPPORT_SOURCES}")
diff --git a/external/badvpn_dns/nspr_support/DummyPRFileDesc.c b/external/badvpn_dns/nspr_support/DummyPRFileDesc.c
new file mode 100644
index 0000000..543a3d5
--- /dev/null
+++ b/external/badvpn_dns/nspr_support/DummyPRFileDesc.c
@@ -0,0 +1,176 @@
+/**
+ * @file DummyPRFileDesc.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <prerror.h>
+#include <prmem.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+
+#include <nspr_support/DummyPRFileDesc.h>
+
+#ifndef NDEBUG
+int dummyprfiledesc_initialized = 0;
+#endif
+PRDescIdentity dummyprfiledesc_identity;
+
+static PRStatus method_close (PRFileDesc *fd)
+{
+    return PR_SUCCESS;
+}
+
+static PRStatus method_getpeername (PRFileDesc *fd, PRNetAddr *addr)
+{
+    PR_SetError(PR_UNKNOWN_ERROR, 0);
+    return PR_FAILURE;
+}
+
+static PRIntn _PR_InvalidIntn (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PRInt16 _PR_InvalidInt16 (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PRInt32 _PR_InvalidInt32 (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PRInt64 _PR_InvalidInt64 (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PROffset32 _PR_InvalidOffset32 (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PROffset64 _PR_InvalidOffset64 (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return -1;
+}
+
+static PRStatus _PR_InvalidStatus (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return PR_FAILURE;
+}
+
+static PRFileDesc *_PR_InvalidDesc (void)
+{
+    ASSERT(0)
+    PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+    return NULL;
+}
+
+static PRIOMethods methods = {
+    (PRDescType)0,
+    method_close,
+    (PRReadFN)_PR_InvalidInt32,
+    (PRWriteFN)_PR_InvalidInt32,
+    (PRAvailableFN)_PR_InvalidInt32,
+    (PRAvailable64FN)_PR_InvalidInt64,
+    (PRFsyncFN)_PR_InvalidStatus,
+    (PRSeekFN)_PR_InvalidOffset32,
+    (PRSeek64FN)_PR_InvalidOffset64,
+    (PRFileInfoFN)_PR_InvalidStatus,
+    (PRFileInfo64FN)_PR_InvalidStatus,
+    (PRWritevFN)_PR_InvalidInt32,
+    (PRConnectFN)_PR_InvalidStatus,
+    (PRAcceptFN)_PR_InvalidDesc,
+    (PRBindFN)_PR_InvalidStatus,
+    (PRListenFN)_PR_InvalidStatus,
+    (PRShutdownFN)_PR_InvalidStatus,
+    (PRRecvFN)_PR_InvalidInt32,
+    (PRSendFN)_PR_InvalidInt32,
+    (PRRecvfromFN)_PR_InvalidInt32,
+    (PRSendtoFN)_PR_InvalidInt32,
+    (PRPollFN)_PR_InvalidInt16,
+    (PRAcceptreadFN)_PR_InvalidInt32,
+    (PRTransmitfileFN)_PR_InvalidInt32,
+    (PRGetsocknameFN)_PR_InvalidStatus,
+    method_getpeername,
+    (PRReservedFN)_PR_InvalidIntn,
+    (PRReservedFN)_PR_InvalidIntn,
+    (PRGetsocketoptionFN)_PR_InvalidStatus,
+    (PRSetsocketoptionFN)_PR_InvalidStatus,
+    (PRSendfileFN)_PR_InvalidInt32,
+    (PRConnectcontinueFN)_PR_InvalidStatus,
+    (PRReservedFN)_PR_InvalidIntn,
+    (PRReservedFN)_PR_InvalidIntn,
+    (PRReservedFN)_PR_InvalidIntn,
+    (PRReservedFN)_PR_InvalidIntn
+};
+
+int DummyPRFileDesc_GlobalInit (void)
+{
+    ASSERT(!dummyprfiledesc_initialized)
+    
+    if ((dummyprfiledesc_identity = PR_GetUniqueIdentity("DummyPRFileDesc")) == PR_INVALID_IO_LAYER) {
+        return 0;
+    }
+    
+    #ifndef NDEBUG
+    dummyprfiledesc_initialized = 1;
+    #endif
+    
+    return 1;
+}
+
+void DummyPRFileDesc_Create (PRFileDesc *prfd)
+{
+    ASSERT(dummyprfiledesc_initialized)
+    
+    memset(prfd, 0, sizeof(*prfd));
+    prfd->methods = &methods;
+    prfd->secret = NULL;
+    prfd->identity = dummyprfiledesc_identity;
+}
diff --git a/external/badvpn_dns/nspr_support/DummyPRFileDesc.h b/external/badvpn_dns/nspr_support/DummyPRFileDesc.h
new file mode 100644
index 0000000..413f105
--- /dev/null
+++ b/external/badvpn_dns/nspr_support/DummyPRFileDesc.h
@@ -0,0 +1,61 @@
+/**
+ * @file DummyPRFileDesc.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Dummy NSPR file descriptor (PRFileDesc).
+ * Used for creating a model SSL file descriptor to cache various stuff
+ * to improve performance.
+ */
+
+#ifndef BADVPN_NSPRSUPPORT_DUMMYPRFILEDESC_H
+#define BADVPN_NSPRSUPPORT_DUMMYPRFILEDESC_H
+
+#include <prio.h>
+
+#include <misc/debug.h>
+
+extern PRDescIdentity dummyprfiledesc_identity;
+
+/**
+ * Globally initialize the dummy NSPR file descriptor backend.
+ * Must not have been called successfully.
+ *
+ * @return 1 on success, 0 on failure
+ */
+int DummyPRFileDesc_GlobalInit (void) WARN_UNUSED;
+
+/**
+ * Creates a dummy NSPR file descriptor.
+ * {@link DummyPRFileDesc_GlobalInit} must have been done.
+ *
+ * @param prfd uninitialized PRFileDesc structure
+ */
+void DummyPRFileDesc_Create (PRFileDesc *prfd);
+
+#endif
diff --git a/external/badvpn_dns/predicate/BPredicate.c b/external/badvpn_dns/predicate/BPredicate.c
new file mode 100644
index 0000000..5d5dadb
--- /dev/null
+++ b/external/badvpn_dns/predicate/BPredicate.c
@@ -0,0 +1,284 @@
+/**
+ * @file BPredicate.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stdio.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/balloc.h>
+#include <misc/compare.h>
+#include <predicate/BPredicate_internal.h>
+#include <predicate/BPredicate_parser.h>
+#include <predicate/LexMemoryBufferInput.h>
+#include <base/BLog.h>
+
+#include <predicate/BPredicate.h>
+
+#include <generated/blog_channel_BPredicate.h>
+
+static int eval_predicate_node (BPredicate *p, struct predicate_node *root);
+
+void yyerror (YYLTYPE *yylloc, yyscan_t scanner, struct predicate_node **result, char *str)
+{
+}
+
+static int string_comparator (void *user, char *s1, char *s2)
+{
+    int cmp = strcmp(s1, s2);
+    return B_COMPARE(cmp, 0);
+}
+
+static int eval_function (BPredicate *p, struct predicate_node *root)
+{
+    ASSERT(root->type == NODE_FUNCTION)
+    
+    // lookup function by name
+    ASSERT(root->function.name)
+    BAVLNode *tree_node;
+    if (!(tree_node = BAVL_LookupExact(&p->functions_tree, root->function.name))) {
+        BLog(BLOG_WARNING, "unknown function");
+        return 0;
+    }
+    BPredicateFunction *func = UPPER_OBJECT(tree_node, BPredicateFunction, tree_node);
+    
+    // evaluate arguments
+    struct arguments_node *arg = root->function.args;
+    void *args[PREDICATE_MAX_ARGS];
+    for (int i = 0; i < func->num_args; i++) {
+        if (!arg) {
+            BLog(BLOG_WARNING, "not enough arguments");
+            return 0;
+        }
+        switch (func->args[i]) {
+            case PREDICATE_TYPE_BOOL:
+                if (arg->arg.type != ARGUMENT_PREDICATE) {
+                    BLog(BLOG_WARNING, "expecting predicate argument");
+                    return 0;
+                }
+                if (!eval_predicate_node(p, arg->arg.predicate)) {
+                    return 0;
+                }
+                args[i] = &arg->arg.predicate->eval_value;
+                break;
+            case PREDICATE_TYPE_STRING:
+                if (arg->arg.type != ARGUMENT_STRING) {
+                    BLog(BLOG_WARNING, "expecting string argument");
+                    return 0;
+                }
+                args[i] = arg->arg.string;
+                break;
+            default:
+                ASSERT(0);
+        }
+        arg = arg->next;
+    }
+    
+    if (arg) {
+        BLog(BLOG_WARNING, "too many arguments");
+        return 0;
+    }
+    
+    // call callback
+    #ifndef NDEBUG
+    p->in_function = 1;
+    #endif
+    int res = func->callback(func->user, args);
+    #ifndef NDEBUG
+    p->in_function = 0;
+    #endif
+    if (res != 0 && res != 1) {
+        BLog(BLOG_WARNING, "callback returned non-boolean");
+        return 0;
+    }
+    
+    root->eval_value = res;
+    return 1;
+}
+
+int eval_predicate_node (BPredicate *p, struct predicate_node *root)
+{
+    ASSERT(root)
+    
+    switch (root->type) {
+        case NODE_CONSTANT:
+            root->eval_value = root->constant.val;
+            return 1;
+        case NODE_NEG:
+            if (!eval_predicate_node(p, root->neg.op)) {
+                return 0;
+            }
+            root->eval_value = !root->neg.op->eval_value;
+            return 1;
+        case NODE_CONJUNCT:
+            if (!eval_predicate_node(p, root->conjunct.op1)) {
+                return 0;
+            }
+            if (!root->conjunct.op1->eval_value) {
+                root->eval_value = 0;
+                return 1;
+            }
+            if (!eval_predicate_node(p, root->conjunct.op2)) {
+                return 0;
+            }
+            if (!root->conjunct.op2->eval_value) {
+                root->eval_value = 0;
+                return 1;
+            }
+            root->eval_value = 1;
+            return 1;
+        case NODE_DISJUNCT:
+            if (!eval_predicate_node(p, root->disjunct.op1)) {
+                return 0;
+            }
+            if (root->disjunct.op1->eval_value) {
+                root->eval_value = 1;
+                return 1;
+            }
+            if (!eval_predicate_node(p, root->disjunct.op2)) {
+                return 0;
+            }
+            if (root->disjunct.op2->eval_value) {
+                root->eval_value = 1;
+                return 1;
+            }
+            root->eval_value = 0;
+            return 1;
+        case NODE_FUNCTION:
+            return eval_function(p, root);
+        default:
+            ASSERT(0)
+            return 0;
+    }
+}
+
+int BPredicate_Init (BPredicate *p, char *str)
+{
+    // initialize input buffer object
+    LexMemoryBufferInput input;
+    LexMemoryBufferInput_Init(&input, str, strlen(str));
+    
+    // initialize lexical analyzer
+    yyscan_t scanner;
+    yylex_init_extra(&input, &scanner);
+    
+    // parse
+    struct predicate_node *root = NULL;
+    int result = yyparse(scanner, &root);
+    
+    // free lexical analyzer
+    yylex_destroy(scanner);
+    
+    // check for errors
+    if (LexMemoryBufferInput_HasError(&input) || result != 0 || !root) {
+        if (root) {
+            free_predicate_node(root);
+        }
+        return 0;
+    }
+    
+    // init tree
+    p->root = root;
+    
+    // init functions tree
+    BAVL_Init(&p->functions_tree, OFFSET_DIFF(BPredicateFunction, name, tree_node), (BAVL_comparator)string_comparator, NULL);
+    
+    // init debuggind
+    #ifndef NDEBUG
+    p->in_function = 0;
+    #endif
+    
+    // init debug object
+    DebugObject_Init(&p->d_obj);
+    
+    return 1;
+}
+
+void BPredicate_Free (BPredicate *p)
+{
+    ASSERT(BAVL_IsEmpty(&p->functions_tree))
+    ASSERT(!p->in_function)
+    
+    // free debug object
+    DebugObject_Free(&p->d_obj);
+    
+    // free tree
+    free_predicate_node((struct predicate_node *)p->root);
+}
+
+int BPredicate_Eval (BPredicate *p)
+{
+    ASSERT(!p->in_function)
+    
+    if (!eval_predicate_node(p, (struct predicate_node *)p->root)) {
+        return -1;
+    }
+    
+    return ((struct predicate_node *)p->root)->eval_value;
+}
+
+void BPredicateFunction_Init (BPredicateFunction *o, BPredicate *p, char *name, int *args, int num_args, BPredicate_callback callback, void *user)
+{
+    ASSERT(strlen(name) <= PREDICATE_MAX_NAME)
+    ASSERT(!BAVL_LookupExact(&p->functions_tree, name))
+    ASSERT(num_args >= 0)
+    ASSERT(num_args <= PREDICATE_MAX_ARGS)
+    for (int i = 0; i < num_args; i++) {
+        ASSERT(args[i] == PREDICATE_TYPE_BOOL || args[i] == PREDICATE_TYPE_STRING)
+    }
+    ASSERT(!p->in_function)
+    
+    // init arguments
+    o->p = p;
+    strcpy(o->name, name);
+    memcpy(o->args, args, num_args * sizeof(int));
+    o->num_args = num_args;
+    o->callback = callback;
+    o->user = user;
+    
+    // add to tree
+    ASSERT_EXECUTE(BAVL_Insert(&p->functions_tree, &o->tree_node, NULL))
+    
+    // init debug object
+    DebugObject_Init(&o->d_obj);
+}
+
+void BPredicateFunction_Free (BPredicateFunction *o)
+{
+    ASSERT(!o->p->in_function)
+    
+    BPredicate *p = o->p;
+    
+    // free debug object
+    DebugObject_Free(&o->d_obj);
+    
+    // remove from tree
+    BAVL_Remove(&p->functions_tree, &o->tree_node);
+}
diff --git a/external/badvpn_dns/predicate/BPredicate.h b/external/badvpn_dns/predicate/BPredicate.h
new file mode 100644
index 0000000..04b3aa5
--- /dev/null
+++ b/external/badvpn_dns/predicate/BPredicate.h
@@ -0,0 +1,177 @@
+/**
+ * @file BPredicate.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 that parses and evaluates a logical expression.
+ * Allows the user to define custom functions than can be
+ * used in the expression.
+ * 
+ * Syntax and semantics for logical expressions:
+ * 
+ *   - true
+ *     Logical true constant. Evaluates to 1.
+ * 
+ *   - false
+ *     Logical false constant. Evaluates to 0.
+ * 
+ *   - NOT expression
+ *     Logical negation. If the expression evaluates to error, the
+ *     negation evaluates to error.
+ * 
+ *   - expression OR expression
+ *     Logical disjunction. The second expression is only evaluated
+ *     if the first expression evaluates to false. If a sub-expression
+ *     evaluates to error, the disjunction evaluates to error.
+ * 
+ *   - expression AND expression
+ *     Logical conjunction. The second expression is only evaluated
+ *     if the first expression evaluates to true. If a sub-expression
+ *     evaluates to error, the conjunction evaluates to error.
+ * 
+ *   - function(arg, ..., arg)
+ *     Evaluation of a user-provided function (function is the name of the
+ *     function, [a-zA-Z0-9_]+).
+ *     If the function with the given name does not exist, it evaluates to
+ *     error.
+ *     Arguments are evaluated from left to right. Each argument can either
+ *     be a logical expression or a string (characters enclosed in double
+ *     quotes, without any double quote).
+ *     If an argument is encountered, but all needed arguments have already
+ *     been evaluated, the function evaluates to error.
+ *     If an argument is of wrong type, it is not evaluated and the function
+ *     evaluates to error.
+ *     If an argument evaluates to error, the function evaluates to error.
+ *     If after all arguments have been evaluated, the function needs more
+ *     arguments, it evaluates to error.
+ *     Then the handler function is called. If it returns anything other
+ *     than 1 and 0, the function evaluates to error. Otherwise it evaluates
+ *     to what the handler function returned.
+ */
+
+#ifndef BADVPN_PREDICATE_BPREDICATE_H
+#define BADVPN_PREDICATE_BPREDICATE_H
+
+#include <misc/debug.h>
+#include <structure/BAVL.h>
+#include <base/DebugObject.h>
+
+#define PREDICATE_TYPE_BOOL 1
+#define PREDICATE_TYPE_STRING 2
+
+#define PREDICATE_MAX_NAME 16
+#define PREDICATE_MAX_ARGS 16
+
+/**
+ * Handler function called when evaluating a custom function in the predicate.
+ * 
+ * @param user value passed to {@link BPredicateFunction_Init}
+ * @param args arguments to the function. Points to an array of pointers (as many as the
+ *             function has arguments), where each pointer points to either to an int or
+ *             a zero-terminated string (depending on the type of the argument).
+ * @return 1 for true, 0 for false, -1 for error
+ */
+typedef int (*BPredicate_callback) (void *user, void **args);
+
+/**
+ * Object that parses and evaluates a logical expression.
+ * Allows the user to define custom functions than can be
+ * used in the expression.
+ */
+typedef struct {
+    DebugObject d_obj;
+    void *root;
+    BAVL functions_tree;
+    #ifndef NDEBUG
+    int in_function;
+    #endif
+} BPredicate;
+
+/**
+ * Object that represents a custom function in {@link BPredicate}.
+ */
+typedef struct {
+    DebugObject d_obj;
+    BPredicate *p;
+    char name[PREDICATE_MAX_NAME + 1];
+    int args[PREDICATE_MAX_ARGS];
+    int num_args;
+    BPredicate_callback callback;
+    void *user;
+    BAVLNode tree_node;
+} BPredicateFunction;
+
+/**
+ * Initializes the object.
+ * 
+ * @param p the object
+ * @param str logical expression
+ * @return 1 on success, 0 on failure
+ */
+int BPredicate_Init (BPredicate *p, char *str) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * Must have no custom functions.
+ * Must not be called from function handlers.
+ * 
+ * @param p the object
+ */
+void BPredicate_Free (BPredicate *p);
+
+/**
+ * Evaluates the logical expression.
+ * Must not be called from function handlers.
+ * 
+ * @param p the object
+ * @return 1 for true, 0 for false, -1 for error
+ */
+int BPredicate_Eval (BPredicate *p);
+
+/**
+ * Registers a custom function for {@link BPredicate}.
+ * Must not be called from function handlers.
+ * 
+ * @param o the object
+ * @param p predicate to register the function for
+ * @param args array of argument types. Each type is either PREDICATE_TYPE_BOOL or PREDICATE_TYPE_STRING.
+ * @param num_args number of arguments for the function. Must be >=0 and <=PREDICATE_MAX_ARGS.
+ * @param callback handler to call to evaluate the function
+ * @param user value to pass to handler
+ */
+void BPredicateFunction_Init (BPredicateFunction *o, BPredicate *p, char *name, int *args, int num_args, BPredicate_callback callback, void *user);
+
+/**
+ * Removes a custom function for {@link BPredicate}.
+ * Must not be called from function handlers.
+ * 
+ * @param o the object
+ */
+void BPredicateFunction_Free (BPredicateFunction *o);
+
+#endif
diff --git a/external/badvpn_dns/predicate/BPredicate.l b/external/badvpn_dns/predicate/BPredicate.l
new file mode 100644
index 0000000..71bfd2f
--- /dev/null
+++ b/external/badvpn_dns/predicate/BPredicate.l
@@ -0,0 +1,83 @@
+/**
+ * @file BPredicate.l
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * {@link BPredicate} lexer file.
+ */
+
+%{
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <predicate/LexMemoryBufferInput.h>
+#include <predicate/BPredicate_internal.h>
+
+#include <generated/bison_BPredicate.h>
+
+#define YY_INPUT(buffer, res, max_size) \
+    int bytes_read = LexMemoryBufferInput_Read((LexMemoryBufferInput *)yyget_extra(yyscanner), buffer, max_size); \
+    res = (bytes_read == 0 ? YY_NULL : bytes_read);
+
+%}
+
+%option reentrant stack noyywrap bison-bridge bison-locations never-interactive nounistd
+
+%%
+\(              return SPAR;
+\)              return EPAR;
+,               return COMMA;
+AND             return AND;
+OR              return OR;
+NOT             return NOT;
+true            return CONSTANT_TRUE;
+false           return CONSTANT_FALSE;
+[a-zA-Z0-9_]+   {
+                    int l = strlen(yytext);
+                    char *p = (char *)malloc(l + 1);
+                    if (p) {
+                        memcpy(p, yytext, l);
+                        p[l] = '\0';
+                    }
+                    yylval->text = p;
+                    return NAME;
+                }
+\"[^\"]*\"      {
+                    int l = strlen(yytext);
+                    char *p = (char *)malloc(l - 1);
+                    if (p) {
+                        memcpy(p, yytext + 1, l - 2);
+                        p[l - 2] = '\0';
+                    }
+                    yylval->text = p;
+                    return STRING;
+                }
+[ \t\n]+        ;
+.               LexMemoryBufferInput_SetError((LexMemoryBufferInput *)yyget_extra(yyscanner)); return 0; // remember failure and report EOF
+%%
diff --git a/external/badvpn_dns/predicate/BPredicate.y b/external/badvpn_dns/predicate/BPredicate.y
new file mode 100644
index 0000000..f48df45
--- /dev/null
+++ b/external/badvpn_dns/predicate/BPredicate.y
@@ -0,0 +1,345 @@
+/**
+ * @file BPredicate.y
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * {@link BPredicate} grammar file.
+ */
+
+%{
+
+#include <stdlib.h>
+
+#include <predicate/BPredicate_internal.h>
+#include <predicate/BPredicate_parser.h>
+
+#define YYLEX_PARAM scanner
+
+static struct predicate_node * make_constant (int val)
+{
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        return NULL;
+    }
+
+    n->type = NODE_CONSTANT;
+    n->constant.val = val;
+
+    return n;
+}
+
+static struct predicate_node * make_negation (struct predicate_node *op)
+{
+    if (!op) {
+        goto fail;
+    }
+
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->type = NODE_NEG;
+    n->neg.op = op;
+
+    return n;
+
+fail:
+    if (op) {
+        free_predicate_node(op);
+    }
+    return NULL;
+}
+
+static struct predicate_node * make_conjunction (struct predicate_node *op1, struct predicate_node *op2)
+{
+    if (!op1 || !op2) {
+        goto fail;
+    }
+
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->type = NODE_CONJUNCT;
+    n->conjunct.op1 = op1;
+    n->conjunct.op2 = op2;
+
+    return n;
+
+fail:
+    if (op1) {
+        free_predicate_node(op1);
+    }
+    if (op2) {
+        free_predicate_node(op2);
+    }
+    return NULL;
+}
+
+static struct predicate_node * make_disjunction (struct predicate_node *op1, struct predicate_node *op2)
+{
+    if (!op1 || !op2) {
+        goto fail;
+    }
+
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->type = NODE_DISJUNCT;
+    n->disjunct.op1 = op1;
+    n->disjunct.op2 = op2;
+
+    return n;
+
+fail:
+    if (op1) {
+        free_predicate_node(op1);
+    }
+    if (op2) {
+        free_predicate_node(op2);
+    }
+    return NULL;
+}
+
+static struct predicate_node * make_function (char *name, struct arguments_node *args, int need_args)
+{
+    if (!name || (!args && need_args)) {
+        goto fail;
+    }
+
+    struct predicate_node *n = (struct predicate_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->type = NODE_FUNCTION;
+    n->function.name = name;
+    n->function.args = args;
+
+    return n;
+
+fail:
+    if (name) {
+        free(name);
+    }
+    if (args) {
+        free_arguments_node(args);
+    }
+    return NULL;
+}
+
+static struct arguments_node * make_arguments (struct arguments_arg arg, struct arguments_node *next, int need_next)
+{
+    if (arg.type == ARGUMENT_INVALID || (!next && need_next)) {
+        goto fail;
+    }
+
+    struct arguments_node *n = (struct arguments_node *)malloc(sizeof(*n));
+    if (!n) {
+        goto fail;
+    }
+
+    n->arg = arg;
+    n->next = next;
+
+    return n;
+
+fail:
+    free_argument(arg);
+    if (next) {
+        free_arguments_node(next);
+    }
+    return NULL;
+}
+
+static struct arguments_arg make_argument_predicate (struct predicate_node *pr)
+{
+    struct arguments_arg ret;
+
+    if (!pr) {
+        goto fail;
+    }
+
+    ret.type = ARGUMENT_PREDICATE;
+    ret.predicate = pr;
+
+    return ret;
+
+fail:
+    ret.type = ARGUMENT_INVALID;
+    return ret;
+}
+
+static struct arguments_arg make_argument_string (char *string)
+{
+    struct arguments_arg ret;
+
+    if (!string) {
+        goto fail;
+    }
+
+    ret.type = ARGUMENT_STRING;
+    ret.string = string;
+
+    return ret;
+
+fail:
+    ret.type = ARGUMENT_INVALID;
+    return ret;
+}
+
+%}
+
+%pure-parser
+%locations
+%parse-param {void *scanner}
+%parse-param {struct predicate_node **result}
+
+%union {
+    char *text;
+    struct predicate_node *node;
+    struct arguments_node *arg_node;
+    struct predicate_node nfaw;
+    struct arguments_arg arg_arg;
+};
+
+// token types
+%token <text> STRING NAME
+%token PEER1_NAME PEER2_NAME AND OR NOT SPAR EPAR CONSTANT_TRUE CONSTANT_FALSE COMMA
+
+// string token destructor
+%destructor {
+    free($$);
+} STRING NAME
+
+// return values
+%type <node> predicate constant parentheses neg conjunct disjunct function
+%type <arg_node> arguments
+%type <arg_arg> argument
+
+// predicate node destructor
+%destructor {
+    if ($$) {
+        free_predicate_node($$);
+    }
+} predicate constant parentheses neg conjunct disjunct function
+
+// argument node destructor
+%destructor {
+    if ($$) {
+        free_arguments_node($$);
+    }
+} arguments
+
+// argument argument destructor
+%destructor {
+    free_argument($$);
+} argument
+
+%left OR
+%left AND
+%nonassoc NOT
+%right COMMA
+
+%%
+
+input:
+    predicate {
+        *result = $1;
+    }
+    ;
+
+predicate: constant | parentheses | neg | conjunct | disjunct | function;
+
+constant:
+    CONSTANT_TRUE {
+        $$ = make_constant(1);
+    }
+    |
+    CONSTANT_FALSE {
+        $$ = make_constant(0);
+    }
+    ;
+
+parentheses:
+    SPAR predicate EPAR {
+        $$ = $2;
+    }
+    ;
+
+neg:
+    NOT predicate {
+        $$ = make_negation($2);
+    }
+    ;
+
+conjunct:
+    predicate AND predicate {
+        $$ = make_conjunction($1, $3);
+    }
+    ;
+
+disjunct:
+    predicate OR predicate {
+        $$ = make_disjunction($1, $3);
+    }
+    ;
+
+function:
+    NAME SPAR EPAR {
+        $$ = make_function($1, NULL, 0);
+    }
+    |
+    NAME SPAR arguments EPAR {
+        $$ = make_function($1, $3, 1);
+    }
+    ;
+
+arguments:
+    argument {
+        $$ = make_arguments($1, NULL, 0);
+    }
+    |
+    argument COMMA arguments {
+        $$ = make_arguments($1, $3, 1);
+    }
+    ;
+
+argument:
+    predicate {
+        $$ = make_argument_predicate($1);
+    }
+    |
+    STRING {
+        $$ = make_argument_string($1);
+    }
+    ;
diff --git a/external/badvpn_dns/predicate/BPredicate_internal.h b/external/badvpn_dns/predicate/BPredicate_internal.h
new file mode 100644
index 0000000..12db554
--- /dev/null
+++ b/external/badvpn_dns/predicate/BPredicate_internal.h
@@ -0,0 +1,154 @@
+/**
+ * @file BPredicate_internal.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * {@link BPredicate} expression tree definitions and functions.
+ */
+
+#ifndef BADVPN_PREDICATE_BPREDICATE_INTERNAL_H
+#define BADVPN_PREDICATE_BPREDICATE_INTERNAL_H
+
+#include <misc/debug.h>
+
+#define NODE_CONSTANT 0
+#define NODE_NEG 2
+#define NODE_CONJUNCT 3
+#define NODE_DISJUNCT 4
+#define NODE_FUNCTION 5
+
+struct arguments_node;
+
+struct predicate_node {
+    int type;
+    union {
+        struct {
+            int val;
+        } constant;
+        struct {
+            struct predicate_node *op;
+        } neg;
+        struct {
+            struct predicate_node *op1;
+            struct predicate_node *op2;
+        } conjunct;
+        struct {
+            struct predicate_node *op1;
+            struct predicate_node *op2;
+        } disjunct;
+        struct {
+            char *name;
+            struct arguments_node *args;
+        } function;
+    };
+    int eval_value;
+};
+
+#define ARGUMENT_INVALID 0
+#define ARGUMENT_PREDICATE 1
+#define ARGUMENT_STRING 2
+
+struct arguments_arg {
+    int type;
+    union {
+        struct predicate_node *predicate;
+        char *string;
+    };
+};
+
+struct arguments_node {
+    struct arguments_arg arg;
+    struct arguments_node *next;
+};
+
+static void free_predicate_node (struct predicate_node *root);
+static void free_argument (struct arguments_arg arg);
+static void free_arguments_node (struct arguments_node *n);
+
+void free_predicate_node (struct predicate_node *root)
+{
+    ASSERT(root)
+    
+    switch (root->type) {
+        case NODE_CONSTANT:
+            break;
+        case NODE_NEG:
+            free_predicate_node(root->neg.op);
+            break;
+        case NODE_CONJUNCT:
+            free_predicate_node(root->conjunct.op1);
+            free_predicate_node(root->conjunct.op2);
+            break;
+        case NODE_DISJUNCT:
+            free_predicate_node(root->disjunct.op1);
+            free_predicate_node(root->disjunct.op2);
+            break;
+        case NODE_FUNCTION:
+            free(root->function.name);
+            if (root->function.args) {
+                free_arguments_node(root->function.args);
+            }
+            break;
+        default:
+            ASSERT(0)
+            break;
+    }
+    
+    free(root);
+}
+
+void free_argument (struct arguments_arg arg)
+{
+    switch (arg.type) {
+        case ARGUMENT_INVALID:
+            break;
+        case ARGUMENT_PREDICATE:
+            free_predicate_node(arg.predicate);
+            break;
+        case ARGUMENT_STRING:
+            free(arg.string);
+            break;
+        default:
+            ASSERT(0);
+    }
+}
+
+void free_arguments_node (struct arguments_node *n)
+{
+    ASSERT(n)
+    
+    free_argument(n->arg);
+    
+    if (n->next) {
+        free_arguments_node(n->next);
+    }
+    
+    free(n);
+}
+
+#endif
diff --git a/external/badvpn_dns/predicate/BPredicate_parser.h b/external/badvpn_dns/predicate/BPredicate_parser.h
new file mode 100644
index 0000000..e7f4a7b
--- /dev/null
+++ b/external/badvpn_dns/predicate/BPredicate_parser.h
@@ -0,0 +1,47 @@
+/**
+ * @file BPredicate_parser.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * {@link BPredicate} parser definitions.
+ */
+
+#ifndef BADVPN_PREDICATE_BPREDICATE_PARSER_H
+#define BADVPN_PREDICATE_BPREDICATE_PARSER_H
+
+#define YY_NO_UNISTD_H
+
+#include <predicate/BPredicate_internal.h>
+
+#include <generated/bison_BPredicate.h>
+#include <generated/flex_BPredicate.h>
+
+// implemented in BPredicate.c
+void yyerror (YYLTYPE *yylloc, yyscan_t scanner, struct predicate_node **result, char *str);
+
+#endif
diff --git a/external/badvpn_dns/predicate/CMakeLists.txt b/external/badvpn_dns/predicate/CMakeLists.txt
new file mode 100644
index 0000000..dfd852e
--- /dev/null
+++ b/external/badvpn_dns/predicate/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(PREDICATE_SOURCES
+    BPredicate.c
+    ${PROJECT_SOURCE_DIR}/generated/flex_BPredicate.c
+    ${PROJECT_SOURCE_DIR}/generated/bison_BPredicate.c
+)
+badvpn_add_library(predicate "system" "" "${PREDICATE_SOURCES}")
diff --git a/external/badvpn_dns/predicate/LexMemoryBufferInput.h b/external/badvpn_dns/predicate/LexMemoryBufferInput.h
new file mode 100644
index 0000000..e8f1730
--- /dev/null
+++ b/external/badvpn_dns/predicate/LexMemoryBufferInput.h
@@ -0,0 +1,86 @@
+/**
+ * @file LexMemoryBufferInput.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 that can be used by a lexer to read input from a memory buffer.
+ */
+
+#ifndef BADVPN_PREDICATE_LEXMEMORYBUFFERINPUT_H
+#define BADVPN_PREDICATE_LEXMEMORYBUFFERINPUT_H
+
+#include <string.h>
+
+#include <misc/debug.h>
+
+typedef struct {
+    char *buf;
+    int len;
+    int pos;
+    int error;
+} LexMemoryBufferInput;
+
+static void LexMemoryBufferInput_Init (LexMemoryBufferInput *input, char *buf, int len)
+{
+    input->buf = buf;
+    input->len = len;
+    input->pos = 0;
+    input->error = 0;
+}
+
+static int LexMemoryBufferInput_Read (LexMemoryBufferInput *input, char *dest, int len)
+{
+    ASSERT(dest)
+    ASSERT(len > 0)
+    
+    if (input->pos >= input->len) {
+        return 0;
+    }
+    
+    int to_read = input->len - input->pos;
+    if (to_read > len) {
+        to_read = len;
+    }
+    
+    memcpy(dest, input->buf + input->pos, to_read);
+    input->pos += to_read;
+    
+    return to_read;
+}
+
+static void LexMemoryBufferInput_SetError (LexMemoryBufferInput *input)
+{
+    input->error = 1;
+}
+
+static int LexMemoryBufferInput_HasError (LexMemoryBufferInput *input)
+{
+    return input->error;
+}
+
+#endif
diff --git a/external/badvpn_dns/protocol/addr.bproto b/external/badvpn_dns/protocol/addr.bproto
new file mode 100644
index 0000000..f020350
--- /dev/null
+++ b/external/badvpn_dns/protocol/addr.bproto
@@ -0,0 +1,11 @@
+// message for an AddrProto address
+message addr {
+    // address type. from addr.h
+    required uint8 type = 1;
+    // for IPv4 and IPv6 addresses, the port (network byte order)
+    optional data("2") ip_port = 2;
+    // for IPv4 addresses, the IP address
+    optional data("4") ipv4_addr = 3;
+    // for IPv6 addresses, the IP address
+    optional data("16") ipv6_addr = 4;
+};
diff --git a/external/badvpn_dns/protocol/addr.h b/external/badvpn_dns/protocol/addr.h
new file mode 100644
index 0000000..9d20265
--- /dev/null
+++ b/external/badvpn_dns/protocol/addr.h
@@ -0,0 +1,207 @@
+/**
+ * @file addr.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * AddrProto, a protocol for encoding network addresses.
+ * 
+ * AddrProto is built with BProto, the protocol and code generator for building
+ * custom message protocols. The BProto specification file is addr.bproto.
+ */
+
+#ifndef BADVPN_PROTOCOL_ADDR_H
+#define BADVPN_PROTOCOL_ADDR_H
+
+#include <stdint.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <system/BAddr.h>
+#include <base/BLog.h>
+
+#include <generated/bproto_addr.h>
+
+#include <generated/blog_channel_addr.h>
+
+#define ADDR_TYPE_IPV4 1
+#define ADDR_TYPE_IPV6 2
+
+#define ADDR_SIZE_IPV4 (addr_SIZEtype + addr_SIZEip_port + addr_SIZEipv4_addr)
+#define ADDR_SIZE_IPV6 (addr_SIZEtype + addr_SIZEip_port + addr_SIZEipv6_addr)
+
+/**
+ * Determines if the given address is supported by AddrProto.
+ * Depends only on the type of the address.
+ *
+ * @param addr address to check. Must be recognized according to {@link BAddr_IsRecognized}.
+ * @return 1 if supported, 0 if not
+ */
+static int addr_supported (BAddr addr);
+
+/**
+ * Determines the size of the given address when encoded by AddrProto.
+ * Depends only on the type of the address.
+ *
+ * @param addr address to check. Must be supported according to {@link addr_supported}.
+ * @return encoded size
+ */
+static int addr_size (BAddr addr);
+
+/**
+ * Encodes an address according to AddrProto.
+ *
+ * @param out output buffer. Must have at least addr_size(addr) space.
+ * @param addr address to encode. Must be supported according to {@link addr_supported}.
+ */
+static void addr_write (uint8_t *out, BAddr addr);
+
+/**
+ * Decodes an address according to AddrProto.
+ *
+ * @param data input buffer containing the address to decode
+ * @param data_len size of input. Must be >=0.
+ * @param out_addr the decoded address will be stored here on success
+ * @return 1 on success, 0 on failure
+ */
+static int addr_read (uint8_t *data, int data_len, BAddr *out_addr) WARN_UNUSED;
+
+int addr_supported (BAddr addr)
+{
+    BAddr_Assert(&addr);
+    
+    switch (addr.type) {
+        case BADDR_TYPE_IPV4:
+        case BADDR_TYPE_IPV6:
+            return 1;
+        default:
+            return 0;
+    }
+}
+
+int addr_size (BAddr addr)
+{
+    ASSERT(addr_supported(addr))
+    
+    switch (addr.type) {
+        case BADDR_TYPE_IPV4:
+            return ADDR_SIZE_IPV4;
+        case BADDR_TYPE_IPV6:
+            return ADDR_SIZE_IPV6;
+        default:
+            ASSERT(0)
+            return 0;
+    }
+}
+
+void addr_write (uint8_t *out, BAddr addr)
+{
+    ASSERT(addr_supported(addr))
+    
+    addrWriter writer;
+    addrWriter_Init(&writer, out);
+    
+    switch (addr.type) {
+        case BADDR_TYPE_IPV4: {
+            addrWriter_Addtype(&writer, ADDR_TYPE_IPV4);
+            uint8_t *out_port = addrWriter_Addip_port(&writer);
+            memcpy(out_port, &addr.ipv4.port, sizeof(addr.ipv4.port));
+            uint8_t *out_addr = addrWriter_Addipv4_addr(&writer);
+            memcpy(out_addr, &addr.ipv4.ip, sizeof(addr.ipv4.ip));
+        } break;
+        case BADDR_TYPE_IPV6: {
+            addrWriter_Addtype(&writer, ADDR_TYPE_IPV6);
+            uint8_t *out_port = addrWriter_Addip_port(&writer);
+            memcpy(out_port, &addr.ipv6.port, sizeof(addr.ipv6.port));
+            uint8_t *out_addr = addrWriter_Addipv6_addr(&writer);
+            memcpy(out_addr, addr.ipv6.ip, sizeof(addr.ipv6.ip));
+        } break;
+        default:
+            ASSERT(0);
+    }
+    
+    int len = addrWriter_Finish(&writer);
+    B_USE(len)
+    
+    ASSERT(len == addr_size(addr))
+}
+
+int addr_read (uint8_t *data, int data_len, BAddr *out_addr)
+{
+    ASSERT(data_len >= 0)
+    
+    addrParser parser;
+    if (!addrParser_Init(&parser, data, data_len)) {
+        BLog(BLOG_ERROR, "failed to parse addr");
+        return 0;
+    }
+    
+    uint8_t type = 0; // to remove warning
+    addrParser_Gettype(&parser, &type);
+    
+    switch (type) {
+        case ADDR_TYPE_IPV4: {
+            uint8_t *port_data;
+            if (!addrParser_Getip_port(&parser, &port_data)) {
+                BLog(BLOG_ERROR, "port missing for IPv4 address");
+                return 0;
+            }
+            uint8_t *addr_data;
+            if (!addrParser_Getipv4_addr(&parser, &addr_data)) {
+                BLog(BLOG_ERROR, "address missing for IPv4 address");
+                return 0;
+            }
+            uint16_t port;
+            uint32_t addr;
+            memcpy(&port, port_data, sizeof(port));
+            memcpy(&addr, addr_data, sizeof(addr));
+            BAddr_InitIPv4(out_addr, addr, port);
+        } break;
+        case ADDR_TYPE_IPV6: {
+            uint8_t *port_data;
+            if (!addrParser_Getip_port(&parser, &port_data)) {
+                BLog(BLOG_ERROR, "port missing for IPv6 address");
+                return 0;
+            }
+            uint8_t *addr_data;
+            if (!addrParser_Getipv6_addr(&parser, &addr_data)) {
+                BLog(BLOG_ERROR, "address missing for IPv6 address");
+                return 0;
+            }
+            uint16_t port;
+            memcpy(&port, port_data, sizeof(port));
+            BAddr_InitIPv6(out_addr, addr_data, port);
+        } break;
+        default:
+            BLog(BLOG_ERROR, "unknown address type %d", (int)type);
+            return 0;
+    }
+    
+    return 1;
+}
+
+#endif
diff --git a/external/badvpn_dns/protocol/dataproto.h b/external/badvpn_dns/protocol/dataproto.h
new file mode 100644
index 0000000..998d953
--- /dev/null
+++ b/external/badvpn_dns/protocol/dataproto.h
@@ -0,0 +1,91 @@
+/**
+ * @file dataproto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 DataProto, the protocol for data transport between VPN peers.
+ * 
+ * All multi-byte integers in structs are little-endian, unless stated otherwise.
+ * 
+ * A DataProto packet consists of:
+ *   - the header (struct {@link dataproto_header})
+ *   - between zero and DATAPROTO_MAX_PEER_IDS destination peer IDs (struct {@link dataproto_peer_id})
+ *   - the payload, e.g. Ethernet frame
+ */
+
+#ifndef BADVPN_PROTOCOL_DATAPROTO_H
+#define BADVPN_PROTOCOL_DATAPROTO_H
+
+#include <stdint.h>
+
+#include <protocol/scproto.h>
+#include <misc/packed.h>
+
+#define DATAPROTO_MAX_PEER_IDS 1
+
+#define DATAPROTO_FLAGS_RECEIVING_KEEPALIVES 1
+
+/**
+ * DataProto header.
+ */
+B_START_PACKED
+struct dataproto_header {
+    /**
+     * Bitwise OR of flags. Possible flags:
+     *   - DATAPROTO_FLAGS_RECEIVING_KEEPALIVES
+     *     Indicates that when the peer sent this packet, it has received at least
+     *     one packet from the other peer in the last keep-alive tolerance time.
+     */
+    uint8_t flags;
+    
+    /**
+     * ID of the peer this frame originates from.
+     */
+    peerid_t from_id;
+    
+    /**
+     * Number of destination peer IDs that follow.
+     * Must be <=DATAPROTO_MAX_PEER_IDS.
+     */
+    peerid_t num_peer_ids;
+} B_PACKED;
+B_END_PACKED
+
+/**
+ * Structure for a destination peer ID in DataProto.
+ * Wraps a single peerid_t in a packed struct for easy access.
+ */
+B_START_PACKED
+struct dataproto_peer_id {
+    peerid_t id;
+} B_PACKED;
+B_END_PACKED
+
+#define DATAPROTO_MAX_OVERHEAD (sizeof(struct dataproto_header) + DATAPROTO_MAX_PEER_IDS * sizeof(struct dataproto_peer_id))
+
+#endif
diff --git a/external/badvpn_dns/protocol/fragmentproto.h b/external/badvpn_dns/protocol/fragmentproto.h
new file mode 100644
index 0000000..4d2315e
--- /dev/null
+++ b/external/badvpn_dns/protocol/fragmentproto.h
@@ -0,0 +1,100 @@
+/**
+ * @file fragmentproto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 FragmentProto, a protocol that allows sending of arbitrarily sized packets over
+ * a link with a fixed MTU.
+ * 
+ * All multi-byte integers in structs are little-endian, unless stated otherwise.
+ * 
+ * A FragmentProto packet consists of a number of chunks.
+ * Each chunk consists of:
+ *   - the chunk header (struct {@link fragmentproto_chunk_header})
+ *   - the chunk payload, i.e. part of the frame specified in the header
+ */
+
+#ifndef BADVPN_PROTOCOL_FRAGMENTPROTO_H
+#define BADVPN_PROTOCOL_FRAGMENTPROTO_H
+
+#include <stdint.h>
+
+#include <misc/balign.h>
+#include <misc/packed.h>
+
+typedef uint16_t fragmentproto_frameid;
+
+/**
+ * FragmentProto chunk header.
+ */
+B_START_PACKED
+struct fragmentproto_chunk_header {
+    /**
+     * Identifier of the frame this chunk belongs to.
+     * Frames should be given ascending identifiers as they are encoded
+     * into chunks (except when the ID wraps to zero).
+     */
+    fragmentproto_frameid frame_id;
+    
+    /**
+     * Position in the frame where this chunk starts.
+     */
+    uint16_t chunk_start;
+    
+    /**
+     * Length of the chunk's payload.
+     */
+    uint16_t chunk_len;
+    
+    /**
+     * Whether this is the last chunk of the frame, i.e.
+     * the total length of the frame is chunk_start + chunk_len.
+     */
+    uint8_t is_last;
+} B_PACKED;
+B_END_PACKED
+
+/**
+ * Calculates how many chunks are needed at most for encoding one frame of the
+ * given maximum size with FragmentProto onto a carrier with a given MTU.
+ * This includes the case when the first chunk of a frame is not the first chunk
+ * in a FragmentProto packet.
+ * 
+ * @param carrier_mtu MTU of the carrier, i.e. maximum length of FragmentProto packets. Must be >sizeof(struct fragmentproto_chunk_header).
+ * @param frame_mtu maximum frame size. Must be >=0.
+ * @return maximum number of chunks needed. Will be >0.
+ */
+static int fragmentproto_max_chunks_for_frame (int carrier_mtu, int frame_mtu)
+{
+    ASSERT(carrier_mtu > sizeof(struct fragmentproto_chunk_header))
+    ASSERT(frame_mtu >= 0)
+    
+    return (bdivide_up(frame_mtu, (carrier_mtu - sizeof(struct fragmentproto_chunk_header))) + 1);
+}
+
+#endif
diff --git a/external/badvpn_dns/protocol/msgproto.bproto b/external/badvpn_dns/protocol/msgproto.bproto
new file mode 100644
index 0000000..202931e
--- /dev/null
+++ b/external/badvpn_dns/protocol/msgproto.bproto
@@ -0,0 +1,43 @@
+// message for all MsgProto messages
+message msg {
+    // message type, from msgproto.h
+    required uint16 type = 1;
+    // message payload. Is itself one of the messages below
+    // for "youconnect", "seed" and "confirmseed" messages,
+    // and empty for other messages
+    required data payload = 2;
+};
+
+// "youconnect" message payload
+message msg_youconnect {
+    // external addresses to try; one or more msg_youconnect_addr messages
+    required repeated data addr = 1;
+    // encryption key if using UDP and encryption is enabled
+    optional data key = 2;
+    // password if using TCP
+    optional uint64 password = 3;
+};
+
+// an external address
+message msg_youconnect_addr {
+    // scope name for this address
+    required data name = 1;
+    // address according to AddrProto
+    required data addr = 2;
+};
+
+// "seed" message payload
+message msg_seed {
+    // identifier for the seed being send
+    required uint16 seed_id = 1;
+    // seed encryption key
+    required data key = 2;
+    // seed IV
+    required data iv = 3;
+};
+
+// "confirmseed" message payload
+message msg_confirmseed {
+    // identifier for the seed being confirmed
+    required uint16 seed_id = 1;
+};
diff --git a/external/badvpn_dns/protocol/msgproto.h b/external/badvpn_dns/protocol/msgproto.h
new file mode 100644
index 0000000..286abb0
--- /dev/null
+++ b/external/badvpn_dns/protocol/msgproto.h
@@ -0,0 +1,76 @@
+/**
+ * @file msgproto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * MsgProto is used by each pair of VPN peers as messages through the server, in order to
+ * establish a direct data connection. MsgProto operates on top of the SCProto message
+ * service, optionally secured with SSL; see {@link scproto.h} for details.
+ * 
+ * MsgProto is built with BProto, the protocol and code generator for building
+ * custom message protocols. The BProto specification file is msgproto.bproto.
+ * 
+ * It goes roughly like that:
+ * 
+ * We name one peer the master and the other the slave. The master is the one with
+ * greater ID.
+ * When the peers get to know about each other, the master starts the binding procedure.
+ * It binds/listens to an address, and sends the slave the "youconnect" message. It
+ * contains a list of external addresses for that bind address and additional parameters.
+ * Each external address includes a string called a scope name. The slave, which receives
+ * the "youconnect" message, finds the first external address whose scope it recognizes,
+ * and attempts to establish connection to that address. If it finds an address, buf fails
+ * at connecting, it sends "youretry", which makes the master restart the binding procedure
+ * after some time. If it however does not recognize any external address, it sends
+ * "cannotconnect" back to the master.
+ * When the master receives the "cannotconnect", it tries the next bind address, as described
+ * above. When the master runs out of bind addresses, it sends "cannotbind" to the slave.
+ * When the slave receives the "cannotbind", it starts its own binding procedure, similarly
+ * to what is described above, with master and slave reversed. First difference is if the
+ * master fails to connect to a recognized address, it doesn't send "youretry", but rather
+ * simply restarts the whole procedure after some time. The other difference is when the
+ * slave runs out of bind addresses, it not only sends "cannotbind" to the master, but
+ * registers relaying to the master. And in this case, when the master receives the "cannotbind",
+ * it doesn't start the binding procedure all all over, but registers relaying to the slave.
+ */
+
+#ifndef BADVPN_PROTOCOL_MSGPROTO_H
+#define BADVPN_PROTOCOL_MSGPROTO_H
+
+#include <generated/bproto_msgproto.h>
+
+#define MSGID_YOUCONNECT 1
+#define MSGID_CANNOTCONNECT 2
+#define MSGID_CANNOTBIND 3
+#define MSGID_YOURETRY 5
+#define MSGID_SEED 6
+#define MSGID_CONFIRMSEED 7
+
+#define MSG_MAX_PAYLOAD (SC_MAX_MSGLEN - msg_SIZEtype - msg_SIZEpayload(0))
+
+#endif
diff --git a/external/badvpn_dns/protocol/packetproto.h b/external/badvpn_dns/protocol/packetproto.h
new file mode 100644
index 0000000..0f0982b
--- /dev/null
+++ b/external/badvpn_dns/protocol/packetproto.h
@@ -0,0 +1,68 @@
+/**
+ * @file packetproto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 PacketProto, a protocol that allows sending of packets
+ * over a reliable stream connection.
+ * 
+ * All multi-byte integers in structs are little-endian, unless stated otherwise.
+ * 
+ * Packets are encoded into a stream by representing each packet with:
+ *   - a 16-bit little-endian unsigned integer representing the length
+ *     of the payload
+ *   - that many bytes of payload
+ */
+
+#ifndef BADVPN_PROTOCOL_PACKETPROTO_H
+#define BADVPN_PROTOCOL_PACKETPROTO_H
+
+#include <stdint.h>
+#include <limits.h>
+
+#include <misc/packed.h>
+
+/**
+ * PacketProto packet header.
+ * Wraps a single uint16_t in a packed struct for easy access.
+ */
+B_START_PACKED
+struct packetproto_header
+{
+    /**
+     * Length of the packet payload that follows.
+     */
+    uint16_t len;
+} B_PACKED;
+B_END_PACKED
+
+#define PACKETPROTO_ENCLEN(_len) (sizeof(struct packetproto_header) + (_len))
+
+#define PACKETPROTO_MAXPAYLOAD UINT16_MAX
+
+#endif
diff --git a/external/badvpn_dns/protocol/requestproto.h b/external/badvpn_dns/protocol/requestproto.h
new file mode 100644
index 0000000..2ec3d0d
--- /dev/null
+++ b/external/badvpn_dns/protocol/requestproto.h
@@ -0,0 +1,50 @@
+/**
+ * @file requestproto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_REQUESTPROTO_H
+#define BADVPN_REQUESTPROTO_H
+
+#include <stdint.h>
+
+#include <misc/packed.h>
+
+#define REQUESTPROTO_TYPE_CLIENT_REQUEST 1
+#define REQUESTPROTO_TYPE_CLIENT_ABORT 2
+#define REQUESTPROTO_TYPE_SERVER_REPLY 3
+#define REQUESTPROTO_TYPE_SERVER_FINISHED 4
+#define REQUESTPROTO_TYPE_SERVER_ERROR 5
+
+B_START_PACKED
+struct requestproto_header {
+    uint32_t request_id;
+    uint32_t type;
+} B_PACKED;
+B_END_PACKED
+
+#endif
diff --git a/external/badvpn_dns/protocol/scproto.h b/external/badvpn_dns/protocol/scproto.h
new file mode 100644
index 0000000..f138e0a
--- /dev/null
+++ b/external/badvpn_dns/protocol/scproto.h
@@ -0,0 +1,266 @@
+/**
+ * @file scproto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 SCProto, the protocol that the clients communicate in
+ * with the server.
+ * 
+ * All multi-byte integers in structs are little-endian, unless stated otherwise.
+ * 
+ * A SCProto packet consists of:
+ *   - a header (struct {@link sc_header}) which contains the type of the
+ *     packet
+ *   - the payload
+ * 
+ * It goes roughly like that:
+ * 
+ * When the client connects to the server, it sends a "clienthello" packet
+ * to the server. The packet contains the protocol version the client is using.
+ * When the server receives the "clienthello" packet, it checks the version.
+ * If it doesn't match, it disconnects the client. Otherwise the server sends
+ * the client a "serverhello" packet to the client. That packet contains
+ * the ID of the client and possibly its IPv4 address as the server sees it
+ * (zero if not applicable).
+ * 
+ * The server than proceeds to synchronize the peers' knowledge of each other.
+ * It does that by sending a "newclient" messages to a client to inform it of
+ * another peer, and "endclient" messages to inform it that a peer is gone.
+ * Each client, upon receiving a "newclient" message, MUST sent a corresponding
+ * "acceptpeer" message, before sending any messages to the new peer.
+ * The server forwards messages between synchronized peers to allow them to
+ * communicate. A peer sends a message to another peer by sending the "outmsg"
+ * packet to the server, and the server delivers a message to a peer by sending
+ * it the "inmsg" packet.
+ * 
+ * The message service is reliable; messages from one client to another are
+ * expected to arrive unmodified and in the same order. There is, however,
+ * no flow control. This means that messages can not be used for bulk transfers
+ * between the clients (and they are not). If the server runs out of buffer for
+ * messages from one client to another, it will stop forwarding messages, and
+ * will reset knowledge of the two clients after some delay. Similarly, if one
+ * of the clients runs out of buffer locally, it will send the "resetpeer"
+ * packet to make the server reset knowledge.
+ * 
+ * The messages transport either:
+ * 
+ * - If the relevant "newclient" packets do not contain the
+ *   SCID_NEWCLIENT_FLAG_SSL flag, then plaintext MsgProto messages.
+ * 
+ * - If the relevant "newclient" packets do contain the SCID_NEWCLIENT_FLAG_SSL
+ *   flag, then SSL, broken down into packets, PacketProto inside SSL, and finally
+ *   MsgProto inside PacketProto. The master peer (one with higher ID) acts as an
+ *   SSL server, and the other acts as an SSL client. The peers must identify with
+ *   the same certificate they used when connecting to the server, and each peer
+ *   must byte-compare the other's certificate agains the one provided to it by
+ *   by the server in the relevent "newclient" message.
+ */
+
+#ifndef BADVPN_PROTOCOL_SCPROTO_H
+#define BADVPN_PROTOCOL_SCPROTO_H
+
+#include <stdint.h>
+
+#include <misc/packed.h>
+
+#define SC_VERSION 29
+#define SC_OLDVERSION_NOSSL 27
+#define SC_OLDVERSION_BROKENCERT 26
+
+#define SC_KEEPALIVE_INTERVAL 10000
+
+/**
+ * SCProto packet header.
+ * Follows up to SC_MAX_PAYLOAD bytes of payload.
+ */
+B_START_PACKED
+struct sc_header {
+    /**
+     * Message type.
+     */
+    uint8_t type;
+} B_PACKED;
+B_END_PACKED
+
+#define SC_MAX_PAYLOAD 2000
+#define SC_MAX_ENC (sizeof(struct sc_header) + SC_MAX_PAYLOAD)
+
+typedef uint16_t peerid_t;
+
+#define SCID_KEEPALIVE 0
+#define SCID_CLIENTHELLO 1
+#define SCID_SERVERHELLO 2
+#define SCID_NEWCLIENT 3
+#define SCID_ENDCLIENT 4
+#define SCID_OUTMSG 5
+#define SCID_INMSG 6
+#define SCID_RESETPEER 7
+#define SCID_ACCEPTPEER 8
+
+/**
+ * "clienthello" client packet payload.
+ * Packet type is SCID_CLIENTHELLO.
+ */
+B_START_PACKED
+struct sc_client_hello {
+    /**
+     * Protocol version the client is using.
+     */
+    uint16_t version;
+} B_PACKED;
+B_END_PACKED
+
+/**
+ * "serverhello" server packet payload.
+ * Packet type is SCID_SERVERHELLO.
+ */
+B_START_PACKED
+struct sc_server_hello {
+    /**
+     * Flags. Not used yet.
+     */
+    uint16_t flags;
+    
+    /**
+     * Peer ID of the client.
+     */
+    peerid_t id;
+    
+    /**
+     * IPv4 address of the client as seen by the server
+     * (network byte order). Zero if not applicable.
+     */
+    uint32_t clientAddr;
+} B_PACKED;
+B_END_PACKED
+
+/**
+ * "newclient" server packet payload.
+ * Packet type is SCID_NEWCLIENT.
+ * If the server is using TLS, follows up to SCID_NEWCLIENT_MAX_CERT_LEN
+ * bytes of the new client's certificate (encoded in DER).
+ */
+B_START_PACKED
+struct sc_server_newclient {
+    /**
+     * ID of the new peer.
+     */
+    peerid_t id;
+    
+    /**
+     * Flags. Possible flags:
+     *   - SCID_NEWCLIENT_FLAG_RELAY_SERVER
+     *     You can relay frames to other peers through this peer.
+     *   - SCID_NEWCLIENT_FLAG_RELAY_CLIENT
+     *     You must allow this peer to relay frames to other peers through you.
+     *   - SCID_NEWCLIENT_FLAG_SSL
+     *     SSL must be used to talk to this peer through messages.
+     */
+    uint16_t flags;
+} B_PACKED;
+B_END_PACKED
+
+#define SCID_NEWCLIENT_FLAG_RELAY_SERVER 1
+#define SCID_NEWCLIENT_FLAG_RELAY_CLIENT 2
+#define SCID_NEWCLIENT_FLAG_SSL 4
+
+#define SCID_NEWCLIENT_MAX_CERT_LEN (SC_MAX_PAYLOAD - sizeof(struct sc_server_newclient))
+
+/**
+ * "endclient" server packet payload.
+ * Packet type is SCID_ENDCLIENT.
+ */
+B_START_PACKED
+struct sc_server_endclient {
+    /**
+     * ID of the removed peer.
+     */
+    peerid_t id;
+} B_PACKED;
+B_END_PACKED
+
+/**
+ * "outmsg" client packet header.
+ * Packet type is SCID_OUTMSG.
+ * Follows up to SC_MAX_MSGLEN bytes of message payload.
+ */
+B_START_PACKED
+struct sc_client_outmsg {
+    /**
+     * ID of the destionation peer.
+     */
+    peerid_t clientid;
+} B_PACKED;
+B_END_PACKED
+
+/**
+ * "inmsg" server packet payload.
+ * Packet type is SCID_INMSG.
+ * Follows up to SC_MAX_MSGLEN bytes of message payload.
+ */
+B_START_PACKED
+struct sc_server_inmsg {
+    /**
+     * ID of the source peer.
+     */
+    peerid_t clientid;
+} B_PACKED;
+B_END_PACKED
+
+#define _SC_MAX_OUTMSGLEN (SC_MAX_PAYLOAD - sizeof(struct sc_client_outmsg))
+#define _SC_MAX_INMSGLEN (SC_MAX_PAYLOAD - sizeof(struct sc_server_inmsg))
+
+#define SC_MAX_MSGLEN (_SC_MAX_OUTMSGLEN < _SC_MAX_INMSGLEN ? _SC_MAX_OUTMSGLEN : _SC_MAX_INMSGLEN)
+
+/**
+ * "resetpeer" client packet header.
+ * Packet type is SCID_RESETPEER.
+ */
+B_START_PACKED
+struct sc_client_resetpeer {
+    /**
+     * ID of the peer to reset.
+     */
+    peerid_t clientid;
+} B_PACKED;
+B_END_PACKED
+
+/**
+ * "acceptpeer" client packet payload.
+ * Packet type is SCID_ACCEPTPEER.
+ */
+B_START_PACKED
+struct sc_client_acceptpeer {
+    /**
+     * ID of the peer to accept.
+     */
+    peerid_t clientid;
+} B_PACKED;
+B_END_PACKED
+
+#endif
diff --git a/external/badvpn_dns/protocol/spproto.h b/external/badvpn_dns/protocol/spproto.h
new file mode 100644
index 0000000..4b5bf5d
--- /dev/null
+++ b/external/badvpn_dns/protocol/spproto.h
@@ -0,0 +1,195 @@
+/**
+ * @file spproto.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Protocol for securing datagram communication.
+ * 
+ * Security features implemented:
+ *   - Encryption. Encrypts packets with a block cipher.
+ *     Protects against a third party from seeing the data
+ *     being transmitted.
+ *   - Hashes. Adds a hash of the packet into the packet.
+ *     Combined with encryption, protects against tampering
+ *     with packets and crafting new packets.
+ *   - One-time passwords. Adds a password to each packet
+ *     for the receiver to recognize. Protects agains replaying
+ *     packets and crafting new packets.
+ * 
+ * A SPProto plaintext packet contains the following, in order:
+ *   - if OTPs are used, a struct {@link spproto_otpdata} which contains
+ *     the seed ID and the OTP,
+ *   - if hashes are used, the hash,
+ *   - payload data.
+ * 
+ * If encryption is used:
+ *   - the plaintext is padded by appending a 0x01 byte and as many 0x00
+ *     bytes as needed to align to block size,
+ *   - the padded plaintext is encrypted, and
+ *   - the initialization vector (IV) is prepended.
+ */
+
+#ifndef BADVPN_PROTOCOL_SPPROTO_H
+#define BADVPN_PROTOCOL_SPPROTO_H
+
+#include <stdint.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/balign.h>
+#include <misc/packed.h>
+#include <security/BHash.h>
+#include <security/BEncryption.h>
+#include <security/OTPCalculator.h>
+
+#define SPPROTO_HASH_MODE_NONE 0
+#define SPPROTO_ENCRYPTION_MODE_NONE 0
+#define SPPROTO_OTP_MODE_NONE 0
+
+/**
+ * Stores security parameters for SPProto.
+ */
+struct spproto_security_params {
+    /**
+     * Hash mode.
+     * Either SPPROTO_HASH_MODE_NONE for no hashes, or a valid bhash
+     * hash mode.
+     */
+    int hash_mode;
+    
+    /**
+     * Encryption mode.
+     * Either SPPROTO_ENCRYPTION_MODE_NONE for no encryption, or a valid
+     * {@link BEncryption} cipher.
+     */
+    int encryption_mode;
+    
+    /**
+     * One-time password (OTP) mode.
+     * Either SPPROTO_OTP_MODE_NONE for no OTPs, or a valid
+     * {@link BEncryption} cipher.
+     */
+    int otp_mode;
+    
+    /**
+     * If OTPs are used (otp_mode != SPPROTO_OTP_MODE_NONE), number of
+     * OTPs generated from a single seed.
+     */
+    int otp_num;
+};
+
+#define SPPROTO_HAVE_HASH(_params) ((_params).hash_mode != SPPROTO_HASH_MODE_NONE)
+#define SPPROTO_HASH_SIZE(_params) ( \
+    SPPROTO_HAVE_HASH(_params) ? \
+    BHash_size((_params).hash_mode) : \
+    0 \
+)
+
+#define SPPROTO_HAVE_ENCRYPTION(_params) ((_params).encryption_mode != SPPROTO_ENCRYPTION_MODE_NONE)
+
+#define SPPROTO_HAVE_OTP(_params) ((_params).otp_mode != SPPROTO_OTP_MODE_NONE)
+
+B_START_PACKED
+struct spproto_otpdata {
+    uint16_t seed_id;
+    otp_t otp;
+} B_PACKED;
+B_END_PACKED
+
+#define SPPROTO_HEADER_OTPDATA_OFF(_params) 0
+#define SPPROTO_HEADER_OTPDATA_LEN(_params) (SPPROTO_HAVE_OTP(_params) ? sizeof(struct spproto_otpdata) : 0)
+#define SPPROTO_HEADER_HASH_OFF(_params) (SPPROTO_HEADER_OTPDATA_OFF(_params) + SPPROTO_HEADER_OTPDATA_LEN(_params))
+#define SPPROTO_HEADER_HASH_LEN(_params) SPPROTO_HASH_SIZE(_params)
+#define SPPROTO_HEADER_LEN(_params) (SPPROTO_HEADER_HASH_OFF(_params) + SPPROTO_HEADER_HASH_LEN(_params))
+
+/**
+ * Asserts that the given SPProto security parameters are valid.
+ * 
+ * @param params security parameters
+ */
+static void spproto_assert_security_params (struct spproto_security_params params)
+{
+    ASSERT(params.hash_mode == SPPROTO_HASH_MODE_NONE || BHash_type_valid(params.hash_mode))
+    ASSERT(params.encryption_mode == SPPROTO_ENCRYPTION_MODE_NONE || BEncryption_cipher_valid(params.encryption_mode))
+    ASSERT(params.otp_mode == SPPROTO_OTP_MODE_NONE || BEncryption_cipher_valid(params.otp_mode))
+    ASSERT(params.otp_mode == SPPROTO_OTP_MODE_NONE || params.otp_num > 0)
+}
+
+/**
+ * Calculates the maximum payload size for SPProto given the
+ * security parameters and the maximum encoded packet size.
+ * 
+ * @param params security parameters
+ * @param carrier_mtu maximum encoded packet size. Must be >=0.
+ * @return maximum payload size. Negative means is is impossible
+ *         to encode anything.
+ */
+static int spproto_payload_mtu_for_carrier_mtu (struct spproto_security_params params, int carrier_mtu)
+{
+    spproto_assert_security_params(params);
+    ASSERT(carrier_mtu >= 0)
+    
+    if (params.encryption_mode == SPPROTO_ENCRYPTION_MODE_NONE) {
+        return (carrier_mtu - SPPROTO_HEADER_LEN(params));
+    } else {
+        int block_size = BEncryption_cipher_block_size(params.encryption_mode);
+        return (balign_down(carrier_mtu, block_size) - block_size - SPPROTO_HEADER_LEN(params) - 1);
+    }
+}
+
+/**
+ * Calculates the maximum encoded packet size for SPProto given the
+ * security parameters and the maximum payload size.
+ * 
+ * @param params security parameters
+ * @param payload_mtu maximum payload size. Must be >=0.
+ * @return maximum encoded packet size, -1 if payload_mtu is too large
+ */
+static int spproto_carrier_mtu_for_payload_mtu (struct spproto_security_params params, int payload_mtu)
+{
+    spproto_assert_security_params(params);
+    ASSERT(payload_mtu >= 0)
+    
+    if (params.encryption_mode == SPPROTO_ENCRYPTION_MODE_NONE) {
+        if (payload_mtu > INT_MAX - SPPROTO_HEADER_LEN(params)) {
+            return -1;
+        }
+        
+        return (SPPROTO_HEADER_LEN(params) + payload_mtu);
+    } else {
+        int block_size = BEncryption_cipher_block_size(params.encryption_mode);
+        
+        if (payload_mtu > INT_MAX - (block_size + SPPROTO_HEADER_LEN(params) + block_size)) {
+            return -1;
+        }
+        
+        return (block_size + balign_up((SPPROTO_HEADER_LEN(params) + payload_mtu + 1), block_size));
+    }
+}
+
+#endif
diff --git a/external/badvpn_dns/protocol/udpgw_proto.h b/external/badvpn_dns/protocol/udpgw_proto.h
new file mode 100644
index 0000000..606fe6c
--- /dev/null
+++ b/external/badvpn_dns/protocol/udpgw_proto.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * Contributions:
+ * Transparent DNS: Copyright (C) Kerem Hadimli <kerem.hadimli@xxxxxxxxx>
+ * 
+ * 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_PROTOCOL_UDPGW_PROTO_H
+#define BADVPN_PROTOCOL_UDPGW_PROTO_H
+
+#include <stdint.h>
+
+#include <misc/bsize.h>
+#include <misc/packed.h>
+
+#define UDPGW_CLIENT_FLAG_KEEPALIVE (1 << 0)
+#define UDPGW_CLIENT_FLAG_REBIND (1 << 1)
+#define UDPGW_CLIENT_FLAG_DNS (1 << 2)
+#define UDPGW_CLIENT_FLAG_IPV6 (1 << 3)
+
+B_START_PACKED
+struct udpgw_header {
+    uint8_t flags;
+    uint16_t conid;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct udpgw_addr_ipv4 {
+    uint32_t addr_ip;
+    uint16_t addr_port;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct udpgw_addr_ipv6 {
+    uint8_t addr_ip[16];
+    uint16_t addr_port;
+} B_PACKED;
+B_END_PACKED
+
+static int udpgw_compute_mtu (int dgram_mtu)
+{
+    bsize_t bs = bsize_add(
+        bsize_fromsize(sizeof(struct udpgw_header)),
+        bsize_add(
+            bsize_max(
+                bsize_fromsize(sizeof(struct udpgw_addr_ipv4)),
+                bsize_fromsize(sizeof(struct udpgw_addr_ipv6))
+            ), 
+            bsize_fromint(dgram_mtu)
+        )
+    );
+    
+    int s;
+    if (!bsize_toint(bs, &s)) {
+        return -1;
+    }
+    
+    return s;
+}
+
+#endif
diff --git a/external/badvpn_dns/random/BRandom2.c b/external/badvpn_dns/random/BRandom2.c
new file mode 100644
index 0000000..a0761de
--- /dev/null
+++ b/external/badvpn_dns/random/BRandom2.c
@@ -0,0 +1,90 @@
+/**
+ * @file BRandom2.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "BRandom2.h"
+
+static int do_init (BRandom2 *o)
+{
+    if (o->initialized) {
+        return 1;
+    }
+    
+    o->urandom_fd = open("/dev/urandom", O_RDONLY);
+    if (o->urandom_fd < 0) {
+        return 0;
+    }
+    
+    o->initialized = 1;
+    
+    return 1;
+}
+
+int BRandom2_Init (BRandom2 *o, int flags)
+{
+    ASSERT((flags & ~(BRANDOM2_INIT_LAZY)) == 0)
+    
+    o->initialized = 0;
+    
+    if (!(flags & BRANDOM2_INIT_LAZY) && !do_init(o)) {
+        return 0;
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+}
+
+void BRandom2_Free (BRandom2 *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    if (o->initialized) {
+        close(o->urandom_fd);
+    }
+}
+
+int BRandom2_GenBytes (BRandom2 *o, void *out, size_t len)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    if (!do_init(o)) {
+        return 0;
+    }
+    
+    ssize_t res = read(o->urandom_fd, out, len);
+    if (res < 0 || res != len) {
+        return 0;
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/random/BRandom2.h b/external/badvpn_dns/random/BRandom2.h
new file mode 100644
index 0000000..6851b84
--- /dev/null
+++ b/external/badvpn_dns/random/BRandom2.h
@@ -0,0 +1,50 @@
+/**
+ * @file BRandom2.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_RANDOM2_H
+#define BADVPN_RANDOM2_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+
+#define BRANDOM2_INIT_LAZY (1 << 0)
+
+typedef struct {
+    int initialized;
+    int urandom_fd;
+    DebugObject d_obj;
+} BRandom2;
+
+int BRandom2_Init (BRandom2 *o, int flags) WARN_UNUSED;
+void BRandom2_Free (BRandom2 *o);
+int BRandom2_GenBytes (BRandom2 *o, void *out, size_t len) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/random/CMakeLists.txt b/external/badvpn_dns/random/CMakeLists.txt
new file mode 100644
index 0000000..76a4821
--- /dev/null
+++ b/external/badvpn_dns/random/CMakeLists.txt
@@ -0,0 +1 @@
+badvpn_add_library(badvpn_random "base" "" BRandom2.c)
diff --git a/external/badvpn_dns/scripts/cmake b/external/badvpn_dns/scripts/cmake
new file mode 100755
index 0000000..51af777
--- /dev/null
+++ b/external/badvpn_dns/scripts/cmake
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+export ROOT="<root>"
+export MINGW="/home/<user>/mingw/cross_win32"
+
+export PATH="$MINGW/bin:$PATH"
+
+exec /usr/bin/cmake -DCMAKE_TOOLCHAIN_FILE="$ROOT/toolchain.cmake" "$@"
diff --git a/external/badvpn_dns/scripts/copy_nss b/external/badvpn_dns/scripts/copy_nss
new file mode 100755
index 0000000..9b52112
--- /dev/null
+++ b/external/badvpn_dns/scripts/copy_nss
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+NSSDIST="$1"
+DEST="$2"
+
+if [ -z "${NSSDIST}" ] || [ -z "${DEST}" ]; then
+    echo "Copies a Windows build of NSS such that it can be found by the BadVPN build system"
+    echo "Usage: $0 <nss dist dir> <dest>"
+    exit 1
+fi
+
+NSSOBJ="${NSSDIST}/WINNT5.1_OPT.OBJ"
+
+set -e
+
+mkdir -p "${DEST}"/include
+cp -r "${NSSOBJ}/include"/* "${DEST}"/include/
+cp -r "${NSSDIST}/public/nss"/* "${DEST}"/include/
+mkdir -p "${DEST}"/lib
+cp "${NSSOBJ}/lib"/{libnspr4,libplc4,libplds4,ssl3,smime3,nss3}.lib "${DEST}"/lib/
+mkdir -p "${DEST}"/bin
+cp "${NSSOBJ}/lib"/*.dll "${DEST}"/bin/
+cp "${NSSOBJ}/bin"/*.exe "${DEST}"/bin/
diff --git a/external/badvpn_dns/scripts/toolchain.cmake b/external/badvpn_dns/scripts/toolchain.cmake
new file mode 100644
index 0000000..5f4a90a
--- /dev/null
+++ b/external/badvpn_dns/scripts/toolchain.cmake
@@ -0,0 +1,6 @@
+SET(CMAKE_SYSTEM_NAME Windows)
+SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+SET(CMAKE_FIND_ROOT_PATH $ENV{ROOT})
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)
+SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
diff --git a/external/badvpn_dns/security/BEncryption.c b/external/badvpn_dns/security/BEncryption.c
new file mode 100644
index 0000000..f0f476c
--- /dev/null
+++ b/external/badvpn_dns/security/BEncryption.c
@@ -0,0 +1,240 @@
+/**
+ * @file BEncryption.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <base/BLog.h>
+
+#include <security/BEncryption.h>
+
+#include <generated/blog_channel_BEncryption.h>
+
+int BEncryption_cipher_valid (int cipher)
+{
+    switch (cipher) {
+        case BENCRYPTION_CIPHER_BLOWFISH:
+        case BENCRYPTION_CIPHER_AES:
+            return 1;
+        default:
+            return 0;
+    }
+}
+
+int BEncryption_cipher_block_size (int cipher)
+{
+    switch (cipher) {
+        case BENCRYPTION_CIPHER_BLOWFISH:
+            return BENCRYPTION_CIPHER_BLOWFISH_BLOCK_SIZE;
+        case BENCRYPTION_CIPHER_AES:
+            return BENCRYPTION_CIPHER_AES_BLOCK_SIZE;
+        default:
+            ASSERT(0)
+            return 0;
+    }
+}
+
+int BEncryption_cipher_key_size (int cipher)
+{
+    switch (cipher) {
+        case BENCRYPTION_CIPHER_BLOWFISH:
+            return BENCRYPTION_CIPHER_BLOWFISH_KEY_SIZE;
+        case BENCRYPTION_CIPHER_AES:
+            return BENCRYPTION_CIPHER_AES_KEY_SIZE;
+        default:
+            ASSERT(0)
+            return 0;
+    }
+}
+
+void BEncryption_Init (BEncryption *enc, int mode, int cipher, uint8_t *key)
+{
+    ASSERT(!(mode&~(BENCRYPTION_MODE_ENCRYPT|BENCRYPTION_MODE_DECRYPT)))
+    ASSERT((mode&BENCRYPTION_MODE_ENCRYPT) || (mode&BENCRYPTION_MODE_DECRYPT))
+    
+    enc->mode = mode;
+    enc->cipher = cipher;
+    
+    #ifdef BADVPN_USE_CRYPTODEV
+    
+    switch (enc->cipher) {
+        case BENCRYPTION_CIPHER_AES:
+            enc->cryptodev.cipher = CRYPTO_AES_CBC;
+            break;
+        default:
+            goto fail1;
+    }
+    
+    if ((enc->cryptodev.fd = open("/dev/crypto", O_RDWR, 0)) < 0) {
+        BLog(BLOG_ERROR, "failed to open /dev/crypto");
+        goto fail1;
+    }
+    
+    if (ioctl(enc->cryptodev.fd, CRIOGET, &enc->cryptodev.cfd)) {
+        BLog(BLOG_ERROR, "failed ioctl(CRIOGET)");
+        goto fail2;
+    }
+    
+    struct session_op sess;
+    memset(&sess, 0, sizeof(sess));
+    sess.cipher = enc->cryptodev.cipher;
+    sess.keylen = BEncryption_cipher_key_size(enc->cipher);
+    sess.key = key;
+    if (ioctl(enc->cryptodev.cfd, CIOCGSESSION, &sess)) {
+        BLog(BLOG_ERROR, "failed ioctl(CIOCGSESSION)");
+        goto fail3;
+    }
+    
+    enc->cryptodev.ses = sess.ses;
+    enc->use_cryptodev = 1;
+    
+    goto success;
+    
+fail3:
+    ASSERT_FORCE(close(enc->cryptodev.cfd) == 0)
+fail2:
+    ASSERT_FORCE(close(enc->cryptodev.fd) == 0)
+fail1:
+    
+    enc->use_cryptodev = 0;
+    
+    #endif
+    
+    int res;
+    
+    switch (enc->cipher) {
+        case BENCRYPTION_CIPHER_BLOWFISH:
+            BF_set_key(&enc->blowfish, BENCRYPTION_CIPHER_BLOWFISH_KEY_SIZE, key);
+            break;
+        case BENCRYPTION_CIPHER_AES:
+            if (enc->mode&BENCRYPTION_MODE_ENCRYPT) {
+                res = AES_set_encrypt_key(key, 128, &enc->aes.encrypt);
+                ASSERT_EXECUTE(res >= 0)
+            }
+            if (enc->mode&BENCRYPTION_MODE_DECRYPT) {
+                res = AES_set_decrypt_key(key, 128, &enc->aes.decrypt);
+                ASSERT_EXECUTE(res >= 0)
+            }
+            break;
+        default:
+            ASSERT(0)
+            ;
+    }
+    
+    #ifdef BADVPN_USE_CRYPTODEV
+success:
+    #endif
+    // init debug object
+    DebugObject_Init(&enc->d_obj);
+}
+
+void BEncryption_Free (BEncryption *enc)
+{
+    // free debug object
+    DebugObject_Free(&enc->d_obj);
+    
+    #ifdef BADVPN_USE_CRYPTODEV
+    
+    if (enc->use_cryptodev) {
+        ASSERT_FORCE(ioctl(enc->cryptodev.cfd, CIOCFSESSION, &enc->cryptodev.ses) == 0)
+        ASSERT_FORCE(close(enc->cryptodev.cfd) == 0)
+        ASSERT_FORCE(close(enc->cryptodev.fd) == 0)
+    }
+    
+    #endif
+}
+
+void BEncryption_Encrypt (BEncryption *enc, uint8_t *in, uint8_t *out, int len, uint8_t *iv)
+{
+    ASSERT(enc->mode&BENCRYPTION_MODE_ENCRYPT)
+    ASSERT(len >= 0)
+    ASSERT(len % BEncryption_cipher_block_size(enc->cipher) == 0)
+    
+    #ifdef BADVPN_USE_CRYPTODEV
+    
+    if (enc->use_cryptodev) {
+        struct crypt_op cryp;
+        memset(&cryp, 0, sizeof(cryp));
+        cryp.ses = enc->cryptodev.ses;
+        cryp.len = len;
+        cryp.src = in;
+        cryp.dst = out;
+        cryp.iv = iv;
+        cryp.op = COP_ENCRYPT;
+        ASSERT_FORCE(ioctl(enc->cryptodev.cfd, CIOCCRYPT, &cryp) == 0)
+        
+        return;
+    }
+    
+    #endif
+    
+    switch (enc->cipher) {
+        case BENCRYPTION_CIPHER_BLOWFISH:
+            BF_cbc_encrypt(in, out, len, &enc->blowfish, iv, BF_ENCRYPT);
+            break;
+        case BENCRYPTION_CIPHER_AES:
+            AES_cbc_encrypt(in, out, len, &enc->aes.encrypt, iv, AES_ENCRYPT);
+            break;
+        default:
+            ASSERT(0);
+    }
+}
+
+void BEncryption_Decrypt (BEncryption *enc, uint8_t *in, uint8_t *out, int len, uint8_t *iv)
+{
+    ASSERT(enc->mode&BENCRYPTION_MODE_DECRYPT)
+    ASSERT(len >= 0)
+    ASSERT(len % BEncryption_cipher_block_size(enc->cipher) == 0)
+    
+    #ifdef BADVPN_USE_CRYPTODEV
+    
+    if (enc->use_cryptodev) {
+        struct crypt_op cryp;
+        memset(&cryp, 0, sizeof(cryp));
+        cryp.ses = enc->cryptodev.ses;
+        cryp.len = len;
+        cryp.src = in;
+        cryp.dst = out;
+        cryp.iv = iv;
+        cryp.op = COP_DECRYPT;
+        ASSERT_FORCE(ioctl(enc->cryptodev.cfd, CIOCCRYPT, &cryp) == 0)
+        
+        return;
+    }
+    
+    #endif
+    
+    switch (enc->cipher) {
+        case BENCRYPTION_CIPHER_BLOWFISH:
+            BF_cbc_encrypt(in, out, len, &enc->blowfish, iv, BF_DECRYPT);
+            break;
+        case BENCRYPTION_CIPHER_AES:
+            AES_cbc_encrypt(in, out, len, &enc->aes.decrypt, iv, AES_DECRYPT);
+            break;
+        default:
+            ASSERT(0);
+    }
+}
diff --git a/external/badvpn_dns/security/BEncryption.h b/external/badvpn_dns/security/BEncryption.h
new file mode 100644
index 0000000..60a4fdc
--- /dev/null
+++ b/external/badvpn_dns/security/BEncryption.h
@@ -0,0 +1,175 @@
+/**
+ * @file BEncryption.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Block cipher encryption abstraction.
+ */
+
+#ifndef BADVPN_SECURITY_BENCRYPTION_H
+#define BADVPN_SECURITY_BENCRYPTION_H
+
+#include <stdint.h>
+#include <string.h>
+
+#ifdef BADVPN_USE_CRYPTODEV
+#include <crypto/cryptodev.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#endif
+
+#include <openssl/blowfish.h>
+#include <openssl/aes.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+
+#define BENCRYPTION_MODE_ENCRYPT 1
+#define BENCRYPTION_MODE_DECRYPT 2
+
+#define BENCRYPTION_MAX_BLOCK_SIZE 16
+#define BENCRYPTION_MAX_KEY_SIZE 16
+
+#define BENCRYPTION_CIPHER_BLOWFISH 1
+#define BENCRYPTION_CIPHER_BLOWFISH_BLOCK_SIZE 8
+#define BENCRYPTION_CIPHER_BLOWFISH_KEY_SIZE 16
+
+#define BENCRYPTION_CIPHER_AES 2
+#define BENCRYPTION_CIPHER_AES_BLOCK_SIZE 16
+#define BENCRYPTION_CIPHER_AES_KEY_SIZE 16
+
+// NOTE: update the maximums above when adding a cipher!
+
+/**
+ * Block cipher encryption abstraction.
+ */
+typedef struct {
+    DebugObject d_obj;
+    int mode;
+    int cipher;
+    #ifdef BADVPN_USE_CRYPTODEV
+    int use_cryptodev;
+    #endif
+    union {
+        BF_KEY blowfish;
+        struct {
+            AES_KEY encrypt;
+            AES_KEY decrypt;
+        } aes;
+        #ifdef BADVPN_USE_CRYPTODEV
+        struct {
+            int fd;
+            int cfd;
+            int cipher;
+            uint32_t ses;
+        } cryptodev;
+        #endif
+    };
+} BEncryption;
+
+/**
+ * Checks if the given cipher number is valid.
+ * 
+ * @param cipher cipher number
+ * @return 1 if valid, 0 if not
+ */
+int BEncryption_cipher_valid (int cipher);
+
+/**
+ * Returns the block size of a cipher.
+ * 
+ * @param cipher cipher number. Must be valid.
+ * @return block size in bytes
+ */
+int BEncryption_cipher_block_size (int cipher);
+
+/**
+ * Returns the key size of a cipher.
+ * 
+ * @param cipher cipher number. Must be valid.
+ * @return key size in bytes
+ */
+int BEncryption_cipher_key_size (int cipher);
+
+/**
+ * Initializes the object.
+ * {@link BSecurity_GlobalInitThreadSafe} must have been done if this object
+ * will be used from a non-main thread.
+ * 
+ * @param enc the object
+ * @param mode whether encryption or decryption is to be done, or both.
+ *             Must be a bitwise-OR of at least one of BENCRYPTION_MODE_ENCRYPT
+ *             and BENCRYPTION_MODE_DECRYPT.
+ * @param cipher cipher number. Must be valid.
+ * @param key encryption key
+ */
+void BEncryption_Init (BEncryption *enc, int mode, int cipher, uint8_t *key);
+
+/**
+ * Frees the object.
+ * 
+ * @param enc the object
+ */
+void BEncryption_Free (BEncryption *enc);
+
+/**
+ * Encrypts data.
+ * The object must have been initialized with mode including
+ * BENCRYPTION_MODE_ENCRYPT.
+ * 
+ * @param enc the object
+ * @param in data to encrypt
+ * @param out ciphertext output
+ * @param len number of bytes to encrypt. Must be >=0 and a multiple of
+ *            block size.
+ * @param iv initialization vector. Updated such that continuing a previous encryption
+ *           starting with the updated IV is equivalent to performing just one encryption.
+ */
+void BEncryption_Encrypt (BEncryption *enc, uint8_t *in, uint8_t *out, int len, uint8_t *iv);
+
+/**
+ * Decrypts data.
+ * The object must have been initialized with mode including
+ * BENCRYPTION_MODE_DECRYPT.
+ * 
+ * @param enc the object
+ * @param in data to decrypt
+ * @param out plaintext output
+ * @param len number of bytes to decrypt. Must be >=0 and a multiple of
+ *            block size.
+ * @param iv initialization vector. Updated such that continuing a previous decryption
+ *           starting with the updated IV is equivalent to performing just one decryption.
+ */
+void BEncryption_Decrypt (BEncryption *enc, uint8_t *in, uint8_t *out, int len, uint8_t *iv);
+
+#endif
diff --git a/external/badvpn_dns/security/BHash.c b/external/badvpn_dns/security/BHash.c
new file mode 100644
index 0000000..fec2616
--- /dev/null
+++ b/external/badvpn_dns/security/BHash.c
@@ -0,0 +1,69 @@
+/**
+ * @file BHash.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <security/BHash.h>
+
+int BHash_type_valid (int type)
+{
+    switch (type) {
+        case BHASH_TYPE_MD5:
+        case BHASH_TYPE_SHA1:
+            return 1;
+        default:
+            return 0;
+    }
+}
+
+int BHash_size (int type)
+{
+    switch (type) {
+        case BHASH_TYPE_MD5:
+            return BHASH_TYPE_MD5_SIZE;
+        case BHASH_TYPE_SHA1:
+            return BHASH_TYPE_SHA1_SIZE;
+        default:
+            ASSERT(0)
+            return 0;
+    }
+}
+
+void BHash_calculate (int type, uint8_t *data, int data_len, uint8_t *out)
+{
+    switch (type) {
+        case BHASH_TYPE_MD5:
+            MD5(data, data_len, out);
+            break;
+        case BHASH_TYPE_SHA1:
+            SHA1(data, data_len, out);
+            break;
+        default:
+            ASSERT(0)
+            ;
+    }
+}
diff --git a/external/badvpn_dns/security/BHash.h b/external/badvpn_dns/security/BHash.h
new file mode 100644
index 0000000..6fb6f9a
--- /dev/null
+++ b/external/badvpn_dns/security/BHash.h
@@ -0,0 +1,80 @@
+/**
+ * @file BHash.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Cryptographic hash funtions abstraction.
+ */
+
+#ifndef BADVPN_SECURITY_BHASH_H
+#define BADVPN_SECURITY_BHASH_H
+
+#include <stdint.h>
+
+#include <openssl/md5.h>
+#include <openssl/sha.h>
+
+#include <misc/debug.h>
+
+#define BHASH_TYPE_MD5 1
+#define BHASH_TYPE_MD5_SIZE 16
+
+#define BHASH_TYPE_SHA1 2
+#define BHASH_TYPE_SHA1_SIZE 20
+
+#define BHASH_MAX_SIZE 20
+
+/**
+ * Checks if the given hash type number is valid.
+ * 
+ * @param type hash type number
+ * @return 1 if valid, 0 if not
+ */
+int BHash_type_valid (int type);
+
+/**
+ * Returns the size of a hash.
+ * 
+ * @param cipher hash type number. Must be valid.
+ * @return hash size in bytes
+ */
+int BHash_size (int type);
+
+/**
+ * Calculates a hash.
+ * {@link BSecurity_GlobalInitThreadSafe} must have been done if this is
+ * being called from a non-main thread.
+ * 
+ * @param type hash type number. Must be valid.
+ * @param data data to calculate the hash of
+ * @param data_len length of data
+ * @param out the hash will be written here. Must not overlap with data.
+ */
+void BHash_calculate (int type, uint8_t *data, int data_len, uint8_t *out);
+
+#endif
diff --git a/external/badvpn_dns/security/BRandom.c b/external/badvpn_dns/security/BRandom.c
new file mode 100644
index 0000000..ea9f276
--- /dev/null
+++ b/external/badvpn_dns/security/BRandom.c
@@ -0,0 +1,42 @@
+/**
+ * @file BRandom.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <openssl/rand.h>
+
+#include <misc/debug.h>
+
+#include <security/BRandom.h>
+
+void BRandom_randomize (uint8_t *buf, int len)
+{
+    ASSERT(len >= 0)
+    
+    DEBUG_ZERO_MEMORY(buf, len)
+    ASSERT_FORCE(RAND_bytes(buf, len) == 1)
+}
diff --git a/external/badvpn_dns/security/BRandom.h b/external/badvpn_dns/security/BRandom.h
new file mode 100644
index 0000000..3529b82
--- /dev/null
+++ b/external/badvpn_dns/security/BRandom.h
@@ -0,0 +1,49 @@
+/**
+ * @file BRandom.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Random data generation function.
+ */
+
+#ifndef BADVPN_SECURITY_BRANDOM_H
+#define BADVPN_SECURITY_BRANDOM_H
+
+#include <stdint.h>
+
+/**
+ * Generates random data.
+ * {@link BSecurity_GlobalInitThreadSafe} must have been done if this is
+ * being called from a non-main thread.
+ * 
+ * @param buf buffer to write data into
+ * @param len number of bytes to generate. Must be >=0.
+ */
+void BRandom_randomize (uint8_t *buf, int len);
+
+#endif
diff --git a/external/badvpn_dns/security/BSecurity.c b/external/badvpn_dns/security/BSecurity.c
new file mode 100644
index 0000000..f3fb7c8
--- /dev/null
+++ b/external/badvpn_dns/security/BSecurity.c
@@ -0,0 +1,149 @@
+/**
+ * @file BSecurity.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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>
+
+#ifdef BADVPN_THREADWORK_USE_PTHREAD
+    #include <pthread.h>
+#endif
+
+#include <openssl/crypto.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+
+#include <security/BSecurity.h>
+
+int bsecurity_initialized = 0;
+
+#ifdef BADVPN_THREADWORK_USE_PTHREAD
+pthread_mutex_t *bsecurity_locks;
+int bsecurity_num_locks;
+#endif
+
+#ifdef BADVPN_THREADWORK_USE_PTHREAD
+
+static unsigned long id_callback (void)
+{
+    ASSERT(bsecurity_initialized)
+    
+    return (unsigned long)pthread_self();
+}
+
+static void locking_callback (int mode, int type, const char *file, int line)
+{
+    ASSERT(bsecurity_initialized)
+    ASSERT(type >= 0)
+    ASSERT(type < bsecurity_num_locks)
+    
+    if ((mode & CRYPTO_LOCK)) {
+        ASSERT_FORCE(pthread_mutex_lock(&bsecurity_locks[type]) == 0)
+    } else {
+        ASSERT_FORCE(pthread_mutex_unlock(&bsecurity_locks[type]) == 0)
+    }
+}
+
+#endif
+
+int BSecurity_GlobalInitThreadSafe (void)
+{
+    ASSERT(!bsecurity_initialized)
+    
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    
+    // get number of locks
+    int num_locks = CRYPTO_num_locks();
+    ASSERT_FORCE(num_locks >= 0)
+    
+    // alloc locks array
+    if (!(bsecurity_locks = BAllocArray(num_locks, sizeof(bsecurity_locks[0])))) {
+        goto fail0;
+    }
+    
+    // init locks
+    bsecurity_num_locks = 0;
+    for (int i = 0; i < num_locks; i++) {
+        if (pthread_mutex_init(&bsecurity_locks[i], NULL) != 0) {
+            goto fail1;
+        }
+        bsecurity_num_locks++;
+    }
+    
+    #endif
+    
+    bsecurity_initialized = 1;
+    
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    CRYPTO_set_id_callback(id_callback);
+    CRYPTO_set_locking_callback(locking_callback);
+    #endif
+    
+    return 1;
+    
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+fail1:
+    while (bsecurity_num_locks > 0) {
+        ASSERT_FORCE(pthread_mutex_destroy(&bsecurity_locks[bsecurity_num_locks - 1]) == 0)
+        bsecurity_num_locks--;
+    }
+    BFree(bsecurity_locks);
+fail0:
+    return 0;
+    #endif
+}
+
+void BSecurity_GlobalFreeThreadSafe (void)
+{
+    ASSERT(bsecurity_initialized)
+    
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    
+    // remove callbacks
+    CRYPTO_set_locking_callback(NULL);
+    CRYPTO_set_id_callback(NULL);
+    
+    // free locks
+    while (bsecurity_num_locks > 0) {
+        ASSERT_FORCE(pthread_mutex_destroy(&bsecurity_locks[bsecurity_num_locks - 1]) == 0)
+        bsecurity_num_locks--;
+    }
+    
+    // free locks array
+    BFree(bsecurity_locks);
+    
+    #endif
+    
+    bsecurity_initialized = 0;
+}
+
+void BSecurity_GlobalAssertThreadSafe (int thread_safe)
+{
+    ASSERT(thread_safe == 0 || thread_safe == 1)
+    ASSERT(!(thread_safe) || bsecurity_initialized)
+}
diff --git a/external/badvpn_dns/security/BSecurity.h b/external/badvpn_dns/security/BSecurity.h
new file mode 100644
index 0000000..c429e4e
--- /dev/null
+++ b/external/badvpn_dns/security/BSecurity.h
@@ -0,0 +1,60 @@
+/**
+ * @file BSecurity.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Initialization of OpenSSL for security functions.
+ */
+
+#ifndef BADVPN_SECURITY_BSECURITY_H
+#define BADVPN_SECURITY_BSECURITY_H
+
+/**
+ * Initializes thread safety for security functions.
+ * Thread safety must not be initialized.
+ * 
+ * @return 1 on success, 0 on failure
+ */
+int BSecurity_GlobalInitThreadSafe (void);
+
+/**
+ * Deinitializes thread safety for security functions.
+ * Thread safety must be initialized.
+ */
+void BSecurity_GlobalFreeThreadSafe (void);
+
+/**
+ * Asserts that {@link BSecurity_GlobalInitThreadSafe} was done,
+ * if thread_safe=1.
+ * 
+ * @param thread_safe whether thread safety is to be asserted.
+ *                    Must be 0 or 1.
+ */
+void BSecurity_GlobalAssertThreadSafe (int thread_safe);
+
+#endif
diff --git a/external/badvpn_dns/security/CMakeLists.txt b/external/badvpn_dns/security/CMakeLists.txt
new file mode 100644
index 0000000..fe29a51
--- /dev/null
+++ b/external/badvpn_dns/security/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SECURITY_SOURCES
+    BSecurity.c
+    BEncryption.c
+    BHash.c
+    BRandom.c
+    OTPCalculator.c
+    OTPChecker.c
+    OTPGenerator.c
+)
+badvpn_add_library(security "system;threadwork" "${LIBCRYPTO_LIBRARIES}" "${SECURITY_SOURCES}")
diff --git a/external/badvpn_dns/security/OTPCalculator.c b/external/badvpn_dns/security/OTPCalculator.c
new file mode 100644
index 0000000..069dbe6
--- /dev/null
+++ b/external/badvpn_dns/security/OTPCalculator.c
@@ -0,0 +1,118 @@
+/**
+ * @file OTPCalculator.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <misc/balloc.h>
+
+#include <security/OTPCalculator.h>
+
+int OTPCalculator_Init (OTPCalculator *calc, int num_otps, int cipher)
+{
+    ASSERT(num_otps >= 0)
+    ASSERT(BEncryption_cipher_valid(cipher))
+    
+    // init arguments
+    calc->num_otps = num_otps;
+    calc->cipher = cipher;
+    
+    // remember block size
+    calc->block_size = BEncryption_cipher_block_size(calc->cipher);
+    
+    // calculate number of blocks
+    if (calc->num_otps > SIZE_MAX / sizeof(otp_t)) {
+        goto fail0;
+    }
+    calc->num_blocks = bdivide_up(calc->num_otps * sizeof(otp_t), calc->block_size);
+    
+    // allocate buffer
+    if (!(calc->data = (otp_t *)BAllocArray(calc->num_blocks, calc->block_size))) {
+        goto fail0;
+    }
+    
+    // init debug object
+    DebugObject_Init(&calc->d_obj);
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void OTPCalculator_Free (OTPCalculator *calc)
+{
+    // free debug object
+    DebugObject_Free(&calc->d_obj);
+    
+    // free buffer
+    BFree(calc->data);
+}
+
+otp_t * OTPCalculator_Generate (OTPCalculator *calc, uint8_t *key, uint8_t *iv, int shuffle)
+{
+    ASSERT(shuffle == 0 || shuffle == 1)
+    
+    // copy IV so it can be updated
+    uint8_t iv_work[BENCRYPTION_MAX_BLOCK_SIZE];
+    memcpy(iv_work, iv, calc->block_size);
+    
+    // create zero block
+    uint8_t zero[BENCRYPTION_MAX_BLOCK_SIZE];
+    memset(zero, 0, calc->block_size);
+    
+    // init encryptor
+    BEncryption encryptor;
+    BEncryption_Init(&encryptor, BENCRYPTION_MODE_ENCRYPT, calc->cipher, key);
+    
+    // encrypt zero blocks
+    for (size_t i = 0; i < calc->num_blocks; i++) {
+        BEncryption_Encrypt(&encryptor, zero, (uint8_t *)calc->data + i * calc->block_size, calc->block_size, iv_work);
+    }
+    
+    // free encryptor
+    BEncryption_Free(&encryptor);
+    
+    // shuffle if requested
+    if (shuffle) {
+        int i = 0;
+        while (i < calc->num_otps) {
+            uint16_t ints[256];
+            BRandom_randomize((uint8_t *)ints, sizeof(ints));
+            for (int j = 0; j < 256 && i < calc->num_otps; j++) {
+                int newIndex = i + (ints[j] % (calc->num_otps - i));
+                otp_t temp = calc->data[i];
+                calc->data[i] = calc->data[newIndex];
+                calc->data[newIndex] = temp;
+                i++;
+            }
+        }
+    }
+    
+    return calc->data;
+}
diff --git a/external/badvpn_dns/security/OTPCalculator.h b/external/badvpn_dns/security/OTPCalculator.h
new file mode 100644
index 0000000..f15f845
--- /dev/null
+++ b/external/badvpn_dns/security/OTPCalculator.h
@@ -0,0 +1,96 @@
+/**
+ * @file OTPCalculator.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 that calculates OTPs.
+ */
+
+#ifndef BADVPN_SECURITY_OTPCALCULATOR_H
+#define BADVPN_SECURITY_OTPCALCULATOR_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/balign.h>
+#include <misc/debug.h>
+#include <security/BRandom.h>
+#include <security/BEncryption.h>
+#include <base/DebugObject.h>
+
+/**
+ * Type for an OTP.
+ */
+typedef uint32_t otp_t;
+
+/**
+ * Object that calculates OTPs.
+ */
+typedef struct {
+    DebugObject d_obj;
+    int num_otps;
+    int cipher;
+    int block_size;
+    size_t num_blocks;
+    otp_t *data;
+} OTPCalculator;
+
+/**
+ * Initializes the calculator.
+ * {@link BSecurity_GlobalInitThreadSafe} must have been done if this object
+ * will be used from a non-main thread.
+ *
+ * @param calc the object
+ * @param num_otps number of OTPs to generate from a seed. Must be >=0.
+ * @param cipher encryption cipher for calculating the OTPs. Must be valid
+ *               according to {@link BEncryption_cipher_valid}.
+ * @return 1 on success, 0 on failure
+ */
+int OTPCalculator_Init (OTPCalculator *calc, int num_otps, int cipher) WARN_UNUSED;
+
+/**
+ * Frees the calculator.
+ *
+ * @param calc the object
+ */
+void OTPCalculator_Free (OTPCalculator *calc);
+
+/**
+ * Generates OTPs from the given key and IV.
+ *
+ * @param calc the object
+ * @param key encryption key
+ * @param iv initialization vector
+ * @param shuffle whether to shuffle the OTPs. Must be 1 or 0.
+ * @return pointer to an array of 32-bit OPTs. Constains as many OTPs as was specified
+ *         in {@link OTPCalculator_Init}. Valid until the next generation or
+ *         until the object is freed.
+ */
+otp_t * OTPCalculator_Generate (OTPCalculator *calc, uint8_t *key, uint8_t *iv, int shuffle);
+
+#endif
diff --git a/external/badvpn_dns/security/OTPChecker.c b/external/badvpn_dns/security/OTPChecker.c
new file mode 100644
index 0000000..8606a31
--- /dev/null
+++ b/external/badvpn_dns/security/OTPChecker.c
@@ -0,0 +1,297 @@
+/**
+ * @file OTPChecker.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/balloc.h>
+
+#include <security/OTPChecker.h>
+
+static void OTPChecker_Table_Empty (OTPChecker *mc, struct OTPChecker_table *t);
+static void OTPChecker_Table_AddOTP (OTPChecker *mc, struct OTPChecker_table *t, otp_t otp);
+static void OTPChecker_Table_Generate (OTPChecker *mc, struct OTPChecker_table *t, OTPCalculator *calc, uint8_t *key, uint8_t *iv);
+static int OTPChecker_Table_CheckOTP (OTPChecker *mc, struct OTPChecker_table *t, otp_t otp);
+
+void OTPChecker_Table_Empty (OTPChecker *mc, struct OTPChecker_table *t)
+{
+    for (int i = 0; i < mc->num_entries; i++) {
+        t->entries[i].avail = -1;
+    }
+}
+
+void OTPChecker_Table_AddOTP (OTPChecker *mc, struct OTPChecker_table *t, otp_t otp)
+{
+    // calculate starting index
+    int start_index = otp % mc->num_entries;
+    
+    // try indexes starting with the base position
+    for (int i = 0; i < mc->num_entries; i++) {
+        int index = bmodadd_int(start_index, i, mc->num_entries);
+        struct OTPChecker_entry *entry = &t->entries[index];
+        
+        // if we find a free index, use it
+        if (entry->avail < 0) {
+            entry->otp = otp;
+            entry->avail = 1;
+            return;
+        }
+        
+        // if we find a used index with the same mac,
+        // use it by incrementing its count
+        if (entry->otp == otp) {
+            entry->avail++;
+            return;
+        }
+    }
+    
+    // will never add more macs than we can hold
+    ASSERT(0)
+}
+
+void OTPChecker_Table_Generate (OTPChecker *mc, struct OTPChecker_table *t, OTPCalculator *calc, uint8_t *key, uint8_t *iv)
+{
+    // calculate values
+    otp_t *otps = OTPCalculator_Generate(calc, key, iv, 0);
+    
+    // empty table
+    OTPChecker_Table_Empty(mc ,t);
+    
+    // add calculated values to table
+    for (int i = 0; i < mc->num_otps; i++) {
+        OTPChecker_Table_AddOTP(mc, t, otps[i]);
+    }
+}
+
+int OTPChecker_Table_CheckOTP (OTPChecker *mc, struct OTPChecker_table *t, otp_t otp)
+{
+    // calculate starting index
+    int start_index = otp % mc->num_entries;
+    
+    // try indexes starting with the base position
+    for (int i = 0; i < mc->num_entries; i++) {
+        int index = bmodadd_int(start_index, i, mc->num_entries);
+        struct OTPChecker_entry *entry = &t->entries[index];
+        
+        // if we find an empty entry, there is no such mac
+        if (entry->avail < 0) {
+            return 0;
+        }
+        
+        // if we find a matching entry, check its count
+        if (entry->otp == otp) {
+            if (entry->avail > 0) {
+                entry->avail--;
+                return 1;
+            }
+            return 0;
+        }
+    }
+    
+    // there are always empty slots
+    ASSERT(0)
+    return 0;
+}
+
+static void work_func (OTPChecker *mc)
+{
+    struct OTPChecker_table *table = &mc->tables[mc->next_table];
+    OTPChecker_Table_Generate(mc, table, &mc->calc, mc->tw_key, mc->tw_iv);
+}
+
+static void work_done_handler (OTPChecker *mc)
+{
+    ASSERT(mc->tw_have)
+    DebugObject_Access(&mc->d_obj);
+    
+    // free work
+    BThreadWork_Free(&mc->tw);
+    mc->tw_have = 0;
+    
+    // update next table number
+    mc->next_table = bmodadd_int(mc->next_table, 1, mc->num_tables);
+    
+    // update number of used tables if not all are used yet
+    if (mc->tables_used < mc->num_tables) {
+        mc->tables_used++;
+    }
+    
+    // call handler
+    if (mc->handler) {
+        mc->handler(mc->user);
+        return;
+    }
+}
+
+int OTPChecker_Init (OTPChecker *mc, int num_otps, int cipher, int num_tables, BThreadWorkDispatcher *twd)
+{
+    ASSERT(num_otps > 0)
+    ASSERT(BEncryption_cipher_valid(cipher))
+    ASSERT(num_tables > 0)
+    
+    // init arguments
+    mc->num_otps = num_otps;
+    mc->cipher = cipher;
+    mc->num_tables = num_tables;
+    mc->twd = twd;
+    
+    // set no handlers
+    mc->handler = NULL;
+    
+    // set number of entries
+    if (mc->num_otps > INT_MAX / 2) {
+        goto fail0;
+    }
+    mc->num_entries = 2 * mc->num_otps;
+    
+    // set no tables used
+    mc->tables_used = 0;
+    mc->next_table = 0;
+    
+    // initialize calculator
+    if (!OTPCalculator_Init(&mc->calc, mc->num_otps, cipher)) {
+        goto fail0;
+    }
+    
+    // allocate tables
+    if (!(mc->tables = (struct OTPChecker_table *)BAllocArray(mc->num_tables, sizeof(mc->tables[0])))) {
+        goto fail1;
+    }
+    
+    // allocate entries
+    if (!(mc->entries = (struct OTPChecker_entry *)BAllocArray2(mc->num_tables, mc->num_entries, sizeof(mc->entries[0])))) {
+        goto fail2;
+    }
+    
+    // initialize tables
+    for (int i = 0; i < mc->num_tables; i++) {
+        struct OTPChecker_table *table = &mc->tables[i];
+        table->entries = mc->entries + (size_t)i * mc->num_entries;
+        OTPChecker_Table_Empty(mc, table);
+    }
+    
+    // have no work
+    mc->tw_have = 0;
+    
+    DebugObject_Init(&mc->d_obj);
+    return 1;
+    
+fail2:
+    BFree(mc->tables);
+fail1:
+    OTPCalculator_Free(&mc->calc);
+fail0:
+    return 0;
+}
+
+void OTPChecker_Free (OTPChecker *mc)
+{
+    DebugObject_Free(&mc->d_obj);
+    
+    // free work
+    if (mc->tw_have) {
+        BThreadWork_Free(&mc->tw);
+    }
+    
+    // free entries
+    BFree(mc->entries);
+    
+    // free tables
+    BFree(mc->tables);
+    
+    // free calculator
+    OTPCalculator_Free(&mc->calc);
+}
+
+void OTPChecker_AddSeed (OTPChecker *mc, uint16_t seed_id, uint8_t *key, uint8_t *iv)
+{
+    ASSERT(mc->next_table >= 0)
+    ASSERT(mc->next_table < mc->num_tables)
+    DebugObject_Access(&mc->d_obj);
+    
+    // free existing work
+    if (mc->tw_have) {
+        BThreadWork_Free(&mc->tw);
+    }
+    
+    // set table's seed ID
+    mc->tables[mc->next_table].id = seed_id;
+    
+    // copy key and IV
+    memcpy(mc->tw_key, key, BEncryption_cipher_key_size(mc->cipher));
+    memcpy(mc->tw_iv, iv, BEncryption_cipher_block_size(mc->cipher));
+    
+    // start work
+    BThreadWork_Init(&mc->tw, mc->twd, (BThreadWork_handler_done)work_done_handler, mc, (BThreadWork_work_func)work_func, mc);
+    
+    // set have work
+    mc->tw_have = 1;
+}
+
+void OTPChecker_RemoveSeeds (OTPChecker *mc)
+{
+    DebugObject_Access(&mc->d_obj);
+    
+    // free existing work
+    if (mc->tw_have) {
+        BThreadWork_Free(&mc->tw);
+        mc->tw_have = 0;
+    }
+    
+    mc->tables_used = 0;
+    mc->next_table = 0;
+}
+
+int OTPChecker_CheckOTP (OTPChecker *mc, uint16_t seed_id, otp_t otp)
+{
+    DebugObject_Access(&mc->d_obj);
+    
+    // try tables in reverse order
+    for (int i = 1; i <= mc->tables_used; i++) {
+        int table_index = bmodadd_int(mc->next_table, mc->num_tables - i, mc->num_tables);
+        if (table_index == mc->next_table && mc->tw_have) {
+            // ignore table that is being generated
+            continue;
+        }
+        
+        struct OTPChecker_table *table = &mc->tables[table_index];
+        if (table->id == seed_id) {
+            return OTPChecker_Table_CheckOTP(mc, table, otp);
+        }
+    }
+    
+    return 0;
+}
+
+void OTPChecker_SetHandlers (OTPChecker *mc, OTPChecker_handler handler, void *user)
+{
+    DebugObject_Access(&mc->d_obj);
+    
+    mc->handler = handler;
+    mc->user = user;
+}
diff --git a/external/badvpn_dns/security/OTPChecker.h b/external/badvpn_dns/security/OTPChecker.h
new file mode 100644
index 0000000..d81a91c
--- /dev/null
+++ b/external/badvpn_dns/security/OTPChecker.h
@@ -0,0 +1,148 @@
+/**
+ * @file OTPChecker.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 that checks OTPs agains known seeds.
+ */
+
+#ifndef BADVPN_SECURITY_OTPCHECKER_H
+#define BADVPN_SECURITY_OTPCHECKER_H
+
+#include <stdint.h>
+
+#include <misc/balign.h>
+#include <misc/debug.h>
+#include <misc/modadd.h>
+#include <security/OTPCalculator.h>
+#include <base/DebugObject.h>
+#include <threadwork/BThreadWork.h>
+
+struct OTPChecker_entry {
+    otp_t otp;
+    int avail;
+};
+
+struct OTPChecker_table {
+    uint16_t id;
+    struct OTPChecker_entry *entries;
+};
+
+/**
+ * Handler called when OTP generation for a seed is finished and new OTPs
+ * can be recognized.
+ * 
+ * @param user as in {@link OTPChecker_Init}
+ */
+typedef void (*OTPChecker_handler) (void *user);
+
+/**
+ * Object that checks OTPs agains known seeds.
+ */
+typedef struct {
+    BThreadWorkDispatcher *twd;
+    OTPChecker_handler handler;
+    void *user;
+    int num_otps;
+    int cipher;
+    int num_entries;
+    int num_tables;
+    int tables_used;
+    int next_table;
+    OTPCalculator calc;
+    struct OTPChecker_table *tables;
+    struct OTPChecker_entry *entries;
+    int tw_have;
+    BThreadWork tw;
+    uint8_t tw_key[BENCRYPTION_MAX_KEY_SIZE];
+    uint8_t tw_iv[BENCRYPTION_MAX_BLOCK_SIZE];
+    DebugObject d_obj;
+} OTPChecker;
+
+/**
+ * Initializes the checker.
+ * {@link BSecurity_GlobalInitThreadSafe} must have been done if
+ * {@link BThreadWorkDispatcher_UsingThreads}(twd) = 1.
+ *
+ * @param mc the object
+ * @param num_otps number of OTPs to generate from a seed. Must be >0.
+ * @param cipher encryption cipher for calculating the OTPs. Must be valid
+ *               according to {@link BEncryption_cipher_valid}.
+ * @param num_tables number of tables to keep, each for one seed. Must be >0.
+ * @param twd thread work dispatcher
+ * @return 1 on success, 0 on failure
+ */
+int OTPChecker_Init (OTPChecker *mc, int num_otps, int cipher, int num_tables, BThreadWorkDispatcher *twd) WARN_UNUSED;
+
+/**
+ * Frees the checker.
+ *
+ * @param mc the object
+ */
+void OTPChecker_Free (OTPChecker *mc);
+
+/**
+ * Starts generating OTPs to recognize for a seed.
+ * OTPs for this seed will not be recognized until the {@link OTPChecker_handler} handler is called.
+ * If OTPs are still being generated for a previous seed, it will be forgotten.
+ *
+ * @param mc the object
+ * @param seed_id seed identifier
+ * @param key encryption key
+ * @param iv initialization vector
+ */
+void OTPChecker_AddSeed (OTPChecker *mc, uint16_t seed_id, uint8_t *key, uint8_t *iv);
+
+/**
+ * Removes all active seeds.
+ *
+ * @param mc the object
+ */
+void OTPChecker_RemoveSeeds (OTPChecker *mc);
+
+/**
+ * Checks an OTP.
+ *
+ * @param mc the object
+ * @param seed_id identifer of seed whom the OTP is claimed to belong to
+ * @param otp OTP to check
+ * @return 1 if the OTP is valid, 0 if not
+ */
+int OTPChecker_CheckOTP (OTPChecker *mc, uint16_t seed_id, otp_t otp);
+
+/**
+ * Sets handlers.
+ *
+ * @param mc the object
+ * @param handler handler to call when generation of new OTPs is complete,
+ *                after {@link OTPChecker_AddSeed} was called.
+ * @param user argument to handler
+ */
+void OTPChecker_SetHandlers (OTPChecker *mc, OTPChecker_handler handler, void *user);
+
+#endif
diff --git a/external/badvpn_dns/security/OTPGenerator.c b/external/badvpn_dns/security/OTPGenerator.c
new file mode 100644
index 0000000..58a59f5
--- /dev/null
+++ b/external/badvpn_dns/security/OTPGenerator.c
@@ -0,0 +1,159 @@
+/**
+ * @file OTPGenerator.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <security/OTPGenerator.h>
+
+static void work_func (OTPGenerator *g)
+{
+    g->otps[!g->cur_calc] = OTPCalculator_Generate(&g->calc[!g->cur_calc], g->tw_key, g->tw_iv, 1);
+}
+
+static void work_done_handler (OTPGenerator *g)
+{
+    ASSERT(g->tw_have)
+    DebugObject_Access(&g->d_obj);
+    
+    // free work
+    BThreadWork_Free(&g->tw);
+    g->tw_have = 0;
+    
+    // use new OTPs
+    g->cur_calc = !g->cur_calc;
+    g->position = 0;
+    
+    // call handler
+    g->handler(g->user);
+    return;
+}
+
+int OTPGenerator_Init (OTPGenerator *g, int num_otps, int cipher, BThreadWorkDispatcher *twd, OTPGenerator_handler handler, void *user)
+{
+    ASSERT(num_otps >= 0)
+    ASSERT(BEncryption_cipher_valid(cipher))
+    
+    // init arguments
+    g->num_otps = num_otps;
+    g->cipher = cipher;
+    g->twd = twd;
+    g->handler = handler;
+    g->user = user;
+    
+    // init position
+    g->position = g->num_otps;
+    
+    // init calculator
+    if (!OTPCalculator_Init(&g->calc[0], g->num_otps, g->cipher)) {
+        goto fail0;
+    }
+    
+    // init calculator
+    if (!OTPCalculator_Init(&g->calc[1], g->num_otps, g->cipher)) {
+        goto fail1;
+    }
+    
+    // set current calculator
+    g->cur_calc = 0;
+    
+    // have no work
+    g->tw_have = 0;
+    
+    DebugObject_Init(&g->d_obj);
+    return 1;
+    
+fail1:
+    OTPCalculator_Free(&g->calc[0]);
+fail0:
+    return 0;
+}
+
+void OTPGenerator_Free (OTPGenerator *g)
+{
+    DebugObject_Free(&g->d_obj);
+    
+    // free work
+    if (g->tw_have) {
+        BThreadWork_Free(&g->tw);
+    }
+    
+    // free calculator
+    OTPCalculator_Free(&g->calc[1]);
+    
+    // free calculator
+    OTPCalculator_Free(&g->calc[0]);
+}
+
+void OTPGenerator_SetSeed (OTPGenerator *g, uint8_t *key, uint8_t *iv)
+{
+    DebugObject_Access(&g->d_obj);
+    
+    // free existing work
+    if (g->tw_have) {
+        BThreadWork_Free(&g->tw);
+    }
+    
+    // copy key and IV
+    memcpy(g->tw_key, key, BEncryption_cipher_key_size(g->cipher));
+    memcpy(g->tw_iv, iv, BEncryption_cipher_block_size(g->cipher));
+    
+    // start work
+    BThreadWork_Init(&g->tw, g->twd, (BThreadWork_handler_done)work_done_handler, g, (BThreadWork_work_func)work_func, g);
+    
+    // set have work
+    g->tw_have = 1;
+}
+
+int OTPGenerator_GetPosition (OTPGenerator *g)
+{
+    DebugObject_Access(&g->d_obj);
+    
+    return g->position;
+}
+
+void OTPGenerator_Reset (OTPGenerator *g)
+{
+    DebugObject_Access(&g->d_obj);
+    
+    // free existing work
+    if (g->tw_have) {
+        BThreadWork_Free(&g->tw);
+        g->tw_have = 0;
+    }
+    
+    g->position = g->num_otps;
+}
+
+otp_t OTPGenerator_GetOTP (OTPGenerator *g)
+{
+    ASSERT(g->position < g->num_otps)
+    DebugObject_Access(&g->d_obj);
+    
+    return g->otps[g->cur_calc][g->position++];
+}
diff --git a/external/badvpn_dns/security/OTPGenerator.h b/external/badvpn_dns/security/OTPGenerator.h
new file mode 100644
index 0000000..f2c83dd
--- /dev/null
+++ b/external/badvpn_dns/security/OTPGenerator.h
@@ -0,0 +1,134 @@
+/**
+ * @file OTPGenerator.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 generates OTPs for use in sending packets.
+ */
+
+#ifndef BADVPN_SECURITY_OTPGENERATOR_H
+#define BADVPN_SECURITY_OTPGENERATOR_H
+
+#include <misc/debug.h>
+#include <security/OTPCalculator.h>
+#include <base/DebugObject.h>
+#include <threadwork/BThreadWork.h>
+
+/**
+ * Handler called when OTP generation for a seed is finished.
+ * The OTP position is reset to zero before the handler is called.
+ * 
+ * @param user as in {@link OTPGenerator_Init}
+ */
+typedef void (*OTPGenerator_handler) (void *user);
+
+/**
+ * Object which generates OTPs for use in sending packets.
+ */
+typedef struct {
+    int num_otps;
+    int cipher;
+    BThreadWorkDispatcher *twd;
+    OTPGenerator_handler handler;
+    void *user;
+    int position;
+    int cur_calc;
+    OTPCalculator calc[2];
+    otp_t *otps[2];
+    int tw_have;
+    BThreadWork tw;
+    uint8_t tw_key[BENCRYPTION_MAX_KEY_SIZE];
+    uint8_t tw_iv[BENCRYPTION_MAX_BLOCK_SIZE];
+    DebugObject d_obj;
+} OTPGenerator;
+
+/**
+ * Initializes the generator.
+ * The object is initialized with number of used OTPs = num_otps.
+ * {@link BSecurity_GlobalInitThreadSafe} must have been done if
+ * {@link BThreadWorkDispatcher_UsingThreads}(twd) = 1.
+ *
+ * @param g the object
+ * @param num_otps number of OTPs to generate from a seed. Must be >=0.
+ * @param cipher encryption cipher for calculating the OTPs. Must be valid
+ *               according to {@link BEncryption_cipher_valid}.
+ * @param twd thread work dispatcher
+ * @param handler handler to call when generation of new OTPs is complete,
+ *                after {@link OTPGenerator_SetSeed} was called.
+ * @param user argument to handler
+ * @return 1 on success, 0 on failure
+ */
+int OTPGenerator_Init (OTPGenerator *g, int num_otps, int cipher, BThreadWorkDispatcher *twd, OTPGenerator_handler handler, void *user) WARN_UNUSED;
+
+/**
+ * Frees the generator.
+ *
+ * @param g the object
+ */
+void OTPGenerator_Free (OTPGenerator *g);
+
+/**
+ * Starts generating OTPs for a seed.
+ * When generation is complete and the new OTPs may be used, the {@link OTPGenerator_handler}
+ * handler will be called.
+ * If OTPs are still being generated for a previous seed, it will be forgotten.
+ * This call by itself does not affect the OTP position; rather the position is set to zero
+ * before the handler is called.
+ *
+ * @param g the object
+ * @param key encryption key
+ * @param iv initialization vector
+ */
+void OTPGenerator_SetSeed (OTPGenerator *g, uint8_t *key, uint8_t *iv);
+
+/**
+ * Returns the number of OTPs used up from the current seed so far.
+ * If there is no seed yet, returns num_otps.
+ *
+ * @param g the object
+ * @return number of used OTPs
+ */
+int OTPGenerator_GetPosition (OTPGenerator *g);
+
+/**
+ * Sets the number of used OTPs to num_otps.
+ *
+ * @param g the object
+ */
+void OTPGenerator_Reset (OTPGenerator *g);
+
+/**
+ * Generates a single OTP.
+ * The number of used OTPs must be < num_otps.
+ * The number of used OTPs is incremented.
+ *
+ * @param g the object
+ */
+otp_t OTPGenerator_GetOTP (OTPGenerator *g);
+
+#endif
diff --git a/external/badvpn_dns/server/CMakeLists.txt b/external/badvpn_dns/server/CMakeLists.txt
new file mode 100644
index 0000000..1d02432
--- /dev/null
+++ b/external/badvpn_dns/server/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_executable(badvpn-server server.c)
+target_link_libraries(badvpn-server system flow flowextra nspr_support predicate security ${NSPR_LIBRARIES} ${NSS_LIBRARIES})
+
+install(
+    TARGETS badvpn-server
+    RUNTIME DESTINATION bin
+)
+
+install(
+    FILES badvpn-server.8
+    DESTINATION share/man/man8
+)
diff --git a/external/badvpn_dns/server/badvpn-server.8 b/external/badvpn_dns/server/badvpn-server.8
new file mode 100644
index 0000000..b8c60e5
--- /dev/null
+++ b/external/badvpn_dns/server/badvpn-server.8
@@ -0,0 +1,190 @@
+.TH badvpn-server 8 "21 June 2011"
+.SH NAME
+badvpn-server \- chat server for the BadVPN peer-to-peer VPN system
+.SH SYNOPSIS
+.B badvpn-server
+.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 "[" --listen-addr " <addr>] ..."
+.br
+.RB "[" --ssl " " --nssdb " <string> " --server-cert-name " <string>]"
+.br
+.RB "[" --comm-predicate " <string>]"
+.br
+.RB "[" --relay-predicate " <string>]"
+.br
+.RB "[" --client-socket-sndbuf " <bytes / 0>]"
+.br
+.RE
+.SH INTRODUCTION
+.P
+This page documents the BadVPN server, which is used in a BadVPN VPN network by peers to
+talk to each other in order to establish data connections. For a general description of
+BadVPN, see
+.BR badvpn (7).
+.SH DESCRIPTION
+.P
+The BadVPN server is a chat server used by nodes in the VPN network to talk to each other
+in order to establish data connections. Once it initializes, the server only terminates
+if a signal is received.
+.SH OPTIONS
+.P
+The BadVPN server 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 --listen-addr " <addr>"
+Add an address for the server to listen on. See below for address format.
+.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 --server-cert-name " <string>"
+When using TLS, the name of the certificate to use. The certificate must be readily accessible.
+.TP
+.BR --comm-predicate " <string>"
+Set a predicate to define which pairs of clients are allowed to communicate. The predicate is a
+logical expression; see below for details. Available functions:
+.br
+.BR p1name "(string)"
+- true if the TLS common name of peer 1 equals the given string. If TLS is not used, the common
+name is assumed to be an empty string.
+.br
+.BR p1addr "(string)"
+- true if the IP address of peer 1 equals the given string. The string must not be a name.
+.br
+.BR p2name "(string)"
+- true if the TLS common name of peer 2 equals the given string. If TLS is not used, the common
+name is assumed to be an empty string.
+.br
+.BR p2addr "(string)"
+- true if the IP address of peer 2 equals the given string. The string must not be a name.
+.br
+There is no rule as to which is peer 1 and which peer 2. When the server needs to determine
+whether to allow two peers to communicate, it evaluates the predicate once and in no specific order.
+.TP
+.BR --relay-predicate " <string>"
+Set a predicate to define how peers can relay data through other peers. The predicate is a
+logical expression; see below for details. If the predicate evaluates to true, peer P can relay data
+through peer R. Available functions:
+.br
+.BR pname "(string)"
+- true if the TLS common name of peer P peer equals the given string. If TLS is not used, the common
+name is assumed to be an empty string.
+.br
+.BR paddr "(string)"
+- true if the IP address of peer P equals the given string. The string must not be a name.
+.br
+.BR rname "(string)"
+- true if the TLS common name of peer R peer equals the given string. If TLS is not used, the common
+name is assumed to be an empty string.
+.br
+.BR raddr "(string)"
+- true if the IP address of peer R equals the given string. The string must not be a name.
+.br
+.TP
+.BR --client-socket-sndbuf " <bytes / 0>"
+Sets the value of the SO_SNDBUF socket option for client TCP sockets (zero to not set). Lower values
+will improve fairness when data from multiple peers is being sent to a given peer, but may result in lower
+bandwidth if the network's bandwidth-delay product to too big.
+.SH "EXIT CODE"
+.P
+If initialization fails, exits with code 1. Otherwise runs until termination is requested 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 PREDICATES
+.P
+The BadVPN server includes a small predicate language used to define certain policies.
+Syntax and semantics of the language are described here.
+.TP
+.BR true
+Logical true constant. Evaluates to 1.
+.TP
+.BR false
+Logical false constant. Evaluates to 0.
+.TP
+.BR NOT " expression"
+Logical negation. If the expression evaluates to error, the
+negation evaluates to error.
+.TP
+.RB "expression " OR " expression"
+Logical disjunction. The second expression is only evaluated
+if the first expression evaluates to false. If a sub-expression
+evaluates to error, the disjunction evaluates to error.
+.TP
+.RB "expression " AND " expression"
+Logical conjunction. The second expression is only evaluated
+if the first expression evaluates to true. If a sub-expression
+evaluates to error, the conjunction evaluates to error.
+.TP
+.RB function "(" "arg" "," " ..." "," " arg" ")"
+Evaluation of a user-provided function (function is the name of the
+function, [a-zA-Z0-9_]+).
+If the function with the given name does not exist, it evaluates to
+error.
+Arguments are evaluated from left to right. Each argument can either
+be a logical expression or a string (characters enclosed in double
+quotes, without any double quote).
+If an argument is encountered, but all needed arguments have already
+been evaluated, the function evaluates to error.
+If an argument is of wrong type, it is not evaluated and the function
+evaluates to error.
+If an argument evaluates to error, the function evaluates to error.
+If after all arguments have been evaluated, the function needs more
+arguments, it evaluates to error.
+Then the handler function is called. If it returns anything other
+than 1 and 0, the function evaluates to error. Otherwise it evaluates
+to what the handler function returned.
+.SH "EXAMPLES"
+.P
+For examples of using BadVPN, see
+.BR badvpn (7).
+.SH "SEE ALSO"
+.BR badvpn-client (8),
+.BR badvpn (7)
+.SH AUTHORS
+Ambroz Bizjak <ambrop7@xxxxxxxxx>
diff --git a/external/badvpn_dns/server/server.c b/external/badvpn_dns/server/server.c
new file mode 100644
index 0000000..2b22101
--- /dev/null
+++ b/external/badvpn_dns/server/server.c
@@ -0,0 +1,2394 @@
+/**
+ * @file server.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stddef.h>
+#include <stdarg.h>
+
+// NSPR and NSS
+#include <prinit.h>
+#include <prio.h>
+#include <prerror.h>
+#include <prtypes.h>
+#include <nss.h>
+#include <ssl.h>
+#include <cert.h>
+#include <keyhi.h>
+#include <secasn1.h>
+
+// BadVPN
+#include <misc/version.h>
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/nsskey.h>
+#include <misc/byteorder.h>
+#include <misc/loglevel.h>
+#include <misc/loggers_string.h>
+#include <misc/open_standard_streams.h>
+#include <misc/compare.h>
+#include <misc/bsize.h>
+#include <predicate/BPredicate.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <system/BSignal.h>
+#include <system/BTime.h>
+#include <system/BNetwork.h>
+#include <security/BRandom.h>
+#include <nspr_support/DummyPRFileDesc.h>
+#include <threadwork/BThreadWork.h>
+
+#ifndef BADVPN_USE_WINAPI
+#include <base/BLog_syslog.h>
+#endif
+
+#include <server/server.h>
+
+#include <generated/blog_channel_server.h>
+
+#define LOGGER_STDOUT 1
+#define LOGGER_SYSLOG 2
+
+// parsed 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 threads;
+    int use_threads_for_ssl_handshake;
+    int use_threads_for_ssl_data;
+    int ssl;
+    char *nssdb;
+    char *server_cert_name;
+    char *listen_addrs[MAX_LISTEN_ADDRS];
+    int num_listen_addrs;
+    char *comm_predicate;
+    char *relay_predicate;
+    int client_socket_sndbuf;
+    int max_clients;
+} options;
+
+// listen addresses
+BAddr listen_addrs[MAX_LISTEN_ADDRS];
+int num_listen_addrs;
+
+// communication predicate
+BPredicate comm_predicate;
+
+// communication predicate functions
+BPredicateFunction comm_predicate_func_p1name;
+BPredicateFunction comm_predicate_func_p2name;
+BPredicateFunction comm_predicate_func_p1addr;
+BPredicateFunction comm_predicate_func_p2addr;
+
+// variables when evaluating the predicate, adjusted before every evaluation
+const char *comm_predicate_p1name;
+const char *comm_predicate_p2name;
+BIPAddr comm_predicate_p1addr;
+BIPAddr comm_predicate_p2addr;
+
+// relay predicate
+BPredicate relay_predicate;
+
+// gateway predicate functions
+BPredicateFunction relay_predicate_func_pname;
+BPredicateFunction relay_predicate_func_rname;
+BPredicateFunction relay_predicate_func_paddr;
+BPredicateFunction relay_predicate_func_raddr;
+
+// variables when evaluating the comm_predicate, adjusted before every evaluation
+const char *relay_predicate_pname;
+const char *relay_predicate_rname;
+BIPAddr relay_predicate_paddr;
+BIPAddr relay_predicate_raddr;
+
+// i/o system
+BReactor ss;
+
+// thread work dispatcher
+BThreadWorkDispatcher twd;
+
+// server certificate if using SSL
+CERTCertificate *server_cert;
+
+// server private key if using SSL
+SECKEYPrivateKey *server_key;
+
+// model NSPR file descriptor to speed up client initialization
+PRFileDesc model_dprfd;
+PRFileDesc *model_prfd;
+
+// listeners
+BListener listeners[MAX_LISTEN_ADDRS];
+int num_listeners;
+
+// number of connected clients
+int clients_num;
+
+// ID assigned to last connected client
+peerid_t clients_nextid;
+
+// clients list
+LinkedList1 clients;
+
+// clients tree (by ID)
+BAVL clients_tree;
+
+// prints help text 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);
+
+// listener handler, accepts new clients
+static void listener_handler (BListener *listener);
+
+// frees resources used by a client
+static void client_dealloc (struct client_data *client);
+
+static int client_compute_buffer_size (struct client_data *client);
+
+// initializes the I/O porition of the client
+static int client_init_io (struct client_data *client);
+
+// deallocates the I/O portion of the client. Must have no outgoing flows.
+static void client_dealloc_io (struct client_data *client);
+
+// removes a client
+static void client_remove (struct client_data *client);
+
+// job to finish removal after clients are informed
+static void client_dying_job (struct client_data *client);
+
+// appends client log prefix
+static void client_logfunc (struct client_data *client);
+
+// passes a message to the logger, prepending about the client
+static void client_log (struct client_data *client, int level, const char *fmt, ...);
+
+// client activity timer handler. Removes the client.
+static void client_disconnect_timer_handler (struct client_data *client);
+
+// BConnection handler
+static void client_connection_handler (struct client_data *client, int event);
+
+// BSSLConnection handler
+static void client_sslcon_handler (struct client_data *client, int event);
+
+// decoder handler
+static void client_decoder_handler_error (struct client_data *client);
+
+// provides a buffer for sending a control packet to the client
+static int client_start_control_packet (struct client_data *client, void **data, int len);
+
+// submits a packet written after client_start_control_packet
+static void client_end_control_packet (struct client_data *client, uint8_t id);
+
+// sends a newclient message to a client
+static int client_send_newclient (struct client_data *client, struct client_data *nc, int relay_server, int relay_client);
+
+// sends an endclient message to a client
+static int client_send_endclient (struct client_data *client, peerid_t end_id);
+
+// handler for packets received from the client
+static void client_input_handler_send (struct client_data *client, uint8_t *data, int data_len);
+
+// processes hello packets from clients
+static void process_packet_hello (struct client_data *client, uint8_t *data, int data_len);
+
+// processes outmsg packets from clients
+static void process_packet_outmsg (struct client_data *client, uint8_t *data, int data_len);
+
+// processes resetpeer packets from clients
+static void process_packet_resetpeer (struct client_data *client, uint8_t *data, int data_len);
+
+// processes acceptpeer packets from clients
+static void process_packet_acceptpeer (struct client_data *client, uint8_t *data, int data_len);
+
+// creates a peer flow
+static struct peer_flow * peer_flow_create (struct client_data *src_client, struct client_data *dest_client);
+
+// deallocates a peer flow
+static void peer_flow_dealloc (struct peer_flow *flow);
+
+static int peer_flow_init_io (struct peer_flow *flow);
+static void peer_flow_free_io (struct peer_flow *flow);
+
+// disconnects the source client from a peer flow
+static void peer_flow_disconnect (struct peer_flow *flow);
+
+// provides a buffer for sending a peer-to-peer packet
+static int peer_flow_start_packet (struct peer_flow *flow, void **data, int len);
+
+// submits a peer-to-peer packet written after peer_flow_start_packet
+static void peer_flow_end_packet (struct peer_flow *flow, uint8_t type);
+
+// handler called by the queue when a peer flow can be freed after its source has gone away
+static void peer_flow_handler_canremove (struct peer_flow *flow);
+
+static void peer_flow_start_reset (struct peer_flow *flow);
+static void peer_flow_drive_reset (struct peer_flow *flow);
+
+static void peer_flow_reset_qflow_handler_busy (struct peer_flow *flow);
+
+// resets clients knowledge after the timer expires
+static void peer_flow_reset_timer_handler (struct peer_flow *flow);
+
+// generates a client ID to be used for a newly connected client
+static peerid_t new_client_id (void);
+
+// finds a client by its ID
+static struct client_data * find_client_by_id (peerid_t id);
+
+// checks if two clients are allowed to communicate. May depend on the order
+// of the clients.
+static int clients_allowed (struct client_data *client1, struct client_data *client2);
+
+// communication predicate function p1name
+static int comm_predicate_func_p1name_cb (void *user, void **args);
+
+// communication predicate function p2name
+static int comm_predicate_func_p2name_cb (void *user, void **args);
+
+// communication predicate function p1addr
+static int comm_predicate_func_p1addr_cb (void *user, void **args);
+
+// communication predicate function p2addr
+static int comm_predicate_func_p2addr_cb (void *user, void **args);
+
+// checks if relay is allowed for a client through another client
+static int relay_allowed (struct client_data *client, struct client_data *relay);
+
+// relay predicate function pname
+static int relay_predicate_func_pname_cb (void *user, void **args);
+
+// relay predicate function rname
+static int relay_predicate_func_rname_cb (void *user, void **args);
+
+// relay predicate function paddr
+static int relay_predicate_func_paddr_cb (void *user, void **args);
+
+// relay predicate function raddr
+static int relay_predicate_func_raddr_cb (void *user, void **args);
+
+// comparator for peerid_t used in AVL tree
+static int peerid_comparator (void *unused, peerid_t *p1, peerid_t *p2);
+
+static struct peer_know * create_know (struct client_data *from, struct client_data *to, int relay_server, int relay_client);
+static void remove_know (struct peer_know *k);
+static void know_inform_job_handler (struct peer_know *k);
+static void uninform_know (struct peer_know *k);
+static void know_uninform_job_handler (struct peer_know *k);
+
+static int launch_pair (struct peer_flow *flow_to);
+
+// find flow from a client to some client
+static struct peer_flow * find_flow (struct client_data *client, peerid_t dest_id);
+
+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) {
+        // initialize NSPR
+        PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+        
+        // initialize i/o layer types
+        if (!DummyPRFileDesc_GlobalInit()) {
+            BLog(BLOG_ERROR, "DummyPRFileDesc_GlobalInit failed");
+            goto fail01;
+        }
+        if (!BSSLConnection_GlobalInit()) {
+            BLog(BLOG_ERROR, "BSSLConnection_GlobalInit failed");
+            goto fail01;
+        }
+        
+        // initialize NSS
+        if (NSS_Init(options.nssdb) != SECSuccess) {
+            BLog(BLOG_ERROR, "NSS_Init failed (%d)", (int)PR_GetError());
+            goto fail01;
+        }
+        if (NSS_SetDomesticPolicy() != SECSuccess) {
+            BLog(BLOG_ERROR, "NSS_SetDomesticPolicy failed (%d)", (int)PR_GetError());
+            goto fail02;
+        }
+        
+        // initialize 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.server_cert_name, &server_cert, &server_key)) {
+            BLog(BLOG_ERROR, "Cannot open certificate and key");
+            goto fail03;
+        }
+        
+        // initialize model SSL fd
+        DummyPRFileDesc_Create(&model_dprfd);
+        if (!(model_prfd = SSL_ImportFD(NULL, &model_dprfd))) {
+            BLog(BLOG_ERROR, "SSL_ImportFD failed");
+            ASSERT_FORCE(PR_Close(&model_dprfd) == PR_SUCCESS)
+            goto fail04;
+        }
+        
+        // set server certificate
+        if (SSL_ConfigSecureServer(model_prfd, server_cert, server_key, NSS_FindCertKEAType(server_cert)) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_ConfigSecureServer failed");
+            goto fail05;
+        }
+    }
+    
+    // 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 communication predicate
+    if (options.comm_predicate) {
+        // init predicate
+        if (!BPredicate_Init(&comm_predicate, options.comm_predicate)) {
+            BLog(BLOG_ERROR, "BPredicate_Init failed");
+            goto fail1;
+        }
+        
+        // init functions
+        int args[] = {PREDICATE_TYPE_STRING};
+        BPredicateFunction_Init(&comm_predicate_func_p1name, &comm_predicate, "p1name", args, 1, comm_predicate_func_p1name_cb, NULL);
+        BPredicateFunction_Init(&comm_predicate_func_p2name, &comm_predicate, "p2name", args, 1, comm_predicate_func_p2name_cb, NULL);
+        BPredicateFunction_Init(&comm_predicate_func_p1addr, &comm_predicate, "p1addr", args, 1, comm_predicate_func_p1addr_cb, NULL);
+        BPredicateFunction_Init(&comm_predicate_func_p2addr, &comm_predicate, "p2addr", args, 1, comm_predicate_func_p2addr_cb, NULL);
+    }
+    
+    // init relay predicate
+    if (options.relay_predicate) {
+        // init predicate
+        if (!BPredicate_Init(&relay_predicate, options.relay_predicate)) {
+            BLog(BLOG_ERROR, "BPredicate_Init failed");
+            goto fail2;
+        }
+        
+        // init functions
+        int args[] = {PREDICATE_TYPE_STRING};
+        BPredicateFunction_Init(&relay_predicate_func_pname, &relay_predicate, "pname", args, 1, relay_predicate_func_pname_cb, NULL);
+        BPredicateFunction_Init(&relay_predicate_func_rname, &relay_predicate, "rname", args, 1, relay_predicate_func_rname_cb, NULL);
+        BPredicateFunction_Init(&relay_predicate_func_paddr, &relay_predicate, "paddr", args, 1, relay_predicate_func_paddr_cb, NULL);
+        BPredicateFunction_Init(&relay_predicate_func_raddr, &relay_predicate, "raddr", args, 1, relay_predicate_func_raddr_cb, NULL);
+    }
+    
+    // init time
+    BTime_Init();
+    
+    // initialize reactor
+    if (!BReactor_Init(&ss)) {
+        BLog(BLOG_ERROR, "BReactor_Init failed");
+        goto fail3;
+    }
+    
+    // init thread work dispatcher
+    if (!BThreadWorkDispatcher_Init(&twd, &ss, options.threads)) {
+        BLog(BLOG_ERROR, "BThreadWorkDispatcher_Init failed");
+        goto fail3a;
+    }
+    
+    // setup signal handler
+    if (!BSignal_Init(&ss, signal_handler, NULL)) {
+        BLog(BLOG_ERROR, "BSignal_Init failed");
+        goto fail4;
+    }
+    
+    // initialize number of clients
+    clients_num = 0;
+    
+    // first client ID will be zero
+    clients_nextid = 0;
+    
+    // initialize clients linked list
+    LinkedList1_Init(&clients);
+    
+    // initialize clients tree
+    BAVL_Init(&clients_tree, OFFSET_DIFF(struct client_data, id, tree_node), (BAVL_comparator)peerid_comparator, NULL);
+    
+    // initialize listeners
+    num_listeners = 0;
+    while (num_listeners < num_listen_addrs) {
+        if (!BListener_Init(&listeners[num_listeners], listen_addrs[num_listeners], &ss, &listeners[num_listeners], (BListener_handler)listener_handler)) {
+            BLog(BLOG_ERROR, "BListener_Init failed");
+            goto fail10;
+        }
+        num_listeners++;
+    }
+    
+    // enter event loop
+    BLog(BLOG_NOTICE, "entering event loop");
+    BReactor_Exec(&ss);
+    
+    // free clients
+    LinkedList1Node *node;
+    while (node = LinkedList1_GetFirst(&clients)) {
+        struct client_data *client = UPPER_OBJECT(node, struct client_data, list_node);
+        
+        // remove outgoing knows
+        LinkedList1Node *node2;
+        while (node2 = LinkedList1_GetFirst(&client->know_out_list)) {
+            struct peer_know *k = UPPER_OBJECT(node2, struct peer_know, from_node);
+            remove_know(k);
+        }
+        
+        // remove incoming knows
+        LinkedList1Node *node3;
+        while (node3 = LinkedList1_GetFirst(&client->know_in_list)) {
+            struct peer_know *k = UPPER_OBJECT(node3, struct peer_know, to_node);
+            remove_know(k);
+        }
+        
+        // remove outgoing flows
+        LinkedList1Node *flow_node;
+        while (flow_node = LinkedList1_GetFirst(&client->peer_out_flows_list)) {
+            struct peer_flow *flow = UPPER_OBJECT(flow_node, struct peer_flow, src_list_node);
+            ASSERT(flow->src_client == client)
+            
+            // allow freeing queue flows at dest
+            PacketPassFairQueue_PrepareFree(&flow->dest_client->output_peers_fairqueue);
+            
+            // deallocate flow
+            peer_flow_dealloc(flow);
+        }
+        
+        // deallocate client
+        client_dealloc(client);
+    }
+fail10:
+    while (num_listeners > 0) {
+        num_listeners--;
+        BListener_Free(&listeners[num_listeners]);
+    }
+    
+    BSignal_Finish();
+fail4:
+    BThreadWorkDispatcher_Free(&twd);
+fail3a:
+    BReactor_Free(&ss);
+fail3:
+    if (options.relay_predicate) {
+        BPredicateFunction_Free(&relay_predicate_func_raddr);
+        BPredicateFunction_Free(&relay_predicate_func_paddr);
+        BPredicateFunction_Free(&relay_predicate_func_rname);
+        BPredicateFunction_Free(&relay_predicate_func_pname);
+        BPredicate_Free(&relay_predicate);
+    }
+fail2:
+    if (options.comm_predicate) {
+        BPredicateFunction_Free(&comm_predicate_func_p2addr);
+        BPredicateFunction_Free(&comm_predicate_func_p1addr);
+        BPredicateFunction_Free(&comm_predicate_func_p2name);
+        BPredicateFunction_Free(&comm_predicate_func_p1name);
+        BPredicate_Free(&comm_predicate);
+    }
+fail1:
+    if (options.ssl) {
+fail05:
+        ASSERT_FORCE(PR_Close(model_prfd) == PR_SUCCESS)
+fail04:
+        CERT_DestroyCertificate(server_cert);
+        SECKEY_DestroyPrivateKey(server_key);
+fail03:
+        ASSERT_FORCE(SSL_ShutdownServerSessionIDCache() == SECSuccess)
+fail02:
+        ASSERT_FORCE(NSS_Shutdown() == SECSuccess)
+fail01:
+        ASSERT_FORCE(PR_Cleanup() == PR_SUCCESS)
+        PL_ArenaFinish();
+    }
+    BLog(BLOG_NOTICE, "exiting");
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    
+    return 1;
+}
+
+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"
+        "        [--listen-addr <addr>] ...\n"
+        "        [--ssl --nssdb <string> --server-cert-name <string>]\n"
+        "        [--comm-predicate <string>]\n"
+        "        [--relay-predicate <string>]\n"
+        "        [--client-socket-sndbuf <bytes / 0>]\n"
+        "        [--max-clients <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[])
+{
+    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.server_cert_name = NULL;
+    options.num_listen_addrs = 0;
+    options.comm_predicate = NULL;
+    options.relay_predicate = NULL;
+    options.client_socket_sndbuf = CLIENT_DEFAULT_SOCKET_SNDBUF;
+    options.max_clients = DEFAULT_MAX_CLIENTS;
+    
+    for (int 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 (i + 1 >= argc) {
+                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 (i + 1 >= argc) {
+                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 (i + 1 >= argc) {
+                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, "--server-cert-name")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.server_cert_name = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--listen-addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.num_listen_addrs == MAX_LISTEN_ADDRS) {
+                fprintf(stderr, "%s: too many\n", arg);
+                return 0;
+            }
+            options.listen_addrs[options.num_listen_addrs] = argv[i + 1];
+            options.num_listen_addrs++;
+            i++;
+        }
+        else if (!strcmp(arg, "--comm-predicate")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.comm_predicate = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--relay-predicate")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.relay_predicate = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--client-socket-sndbuf")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.client_socket_sndbuf = atoi(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            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 {
+            fprintf(stderr, "%s: unknown option\n", arg);
+            return 0;
+        }
+    }
+    
+    if (options.help || options.version) {
+        return 1;
+    }
+    
+    if (!!options.nssdb != options.ssl) {
+        fprintf(stderr, "--ssl and --nssdb must be used together\n");
+        return 0;
+    }
+    
+    if (!!options.server_cert_name != options.ssl) {
+        fprintf(stderr, "--ssl and --server-cert-name must be used together\n");
+        return 0;
+    }
+    
+    return 1;
+}
+
+int process_arguments (void)
+{
+    // resolve listen addresses
+    num_listen_addrs = 0;
+    while (num_listen_addrs < options.num_listen_addrs) {
+        if (!BAddr_Parse(&listen_addrs[num_listen_addrs], options.listen_addrs[num_listen_addrs], NULL, 0)) {
+            BLog(BLOG_ERROR, "listen addr: BAddr_Parse failed");
+            return 0;
+        }
+        num_listen_addrs++;
+    }
+    
+    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");
+    
+    // exit event loop
+    BReactor_Quit(&ss, 0);
+}
+
+void listener_handler (BListener *listener)
+{
+    if (clients_num == options.max_clients) {
+        BLog(BLOG_WARNING, "too many clients for new client");
+        goto fail0;
+    }
+    
+    // allocate the client structure
+    struct client_data *client = (struct client_data *)malloc(sizeof(*client));
+    if (!client) {
+        BLog(BLOG_ERROR, "failed to allocate client");
+        goto fail0;
+    }
+    
+    // accept connection
+    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;
+    }
+    
+    // limit socket send buffer, else our scheduling is pointless
+    if (options.client_socket_sndbuf > 0) {
+        if (!BConnection_SetSendBuffer(&client->con, options.client_socket_sndbuf)) {
+            BLog(BLOG_WARNING, "BConnection_SetSendBuffer failed");
+        }
+    }
+    
+    // assign ID
+    client->id = new_client_id();
+    
+    // set no common name
+    client->common_name = NULL;
+    
+    // now client_log() works
+    
+    // init connection interfaces
+    BConnection_SendAsync_Init(&client->con);
+    BConnection_RecvAsync_Init(&client->con);
+    
+    if (options.ssl) {
+        // create bottom NSPR file descriptor
+        if (!BSSLConnection_MakeBackend(&client->bottom_prfd, BConnection_SendAsync_GetIf(&client->con), BConnection_RecvAsync_GetIf(&client->con), &twd, ssl_flags())) {
+            client_log(client, BLOG_ERROR, "BSSLConnection_MakeBackend failed");
+            goto fail2;
+        }
+        
+        // create SSL file descriptor from the bottom NSPR file descriptor
+        if (!(client->ssl_prfd = SSL_ImportFD(model_prfd, &client->bottom_prfd))) {
+            client_log(client, BLOG_ERROR, "SSL_ImportFD failed");
+            ASSERT_FORCE(PR_Close(&client->bottom_prfd) == PR_SUCCESS)
+            goto fail2;
+        }
+        
+        // set server mode
+        if (SSL_ResetHandshake(client->ssl_prfd, PR_TRUE) != SECSuccess) {
+            client_log(client, BLOG_ERROR, "SSL_ResetHandshake failed");
+            goto fail3;
+        }
+        
+        // set require client certificate
+        if (SSL_OptionSet(client->ssl_prfd, SSL_REQUEST_CERTIFICATE, PR_TRUE) != SECSuccess) {
+            client_log(client, BLOG_ERROR, "SSL_OptionSet(SSL_REQUEST_CERTIFICATE) failed");
+            goto fail3;
+        }
+        if (SSL_OptionSet(client->ssl_prfd, SSL_REQUIRE_CERTIFICATE, PR_TRUE) != SECSuccess) {
+            client_log(client, BLOG_ERROR, "SSL_OptionSet(SSL_REQUIRE_CERTIFICATE) failed");
+            goto fail3;
+        }
+        
+        // init SSL connection
+        BSSLConnection_Init(&client->sslcon, client->ssl_prfd, 1, BReactor_PendingGroup(&ss), client, (BSSLConnection_handler)client_sslcon_handler);
+    } else {
+        // initialize I/O
+        if (!client_init_io(client)) {
+            goto fail2;
+        }
+    }
+    
+    // start disconnect timer
+    BTimer_Init(&client->disconnect_timer, CLIENT_NO_DATA_TIME_LIMIT, (BTimer_handler)client_disconnect_timer_handler, client);
+    BReactor_SetTimer(&ss, &client->disconnect_timer);
+    
+    // link in
+    clients_num++;
+    LinkedList1_Append(&clients, &client->list_node);
+    ASSERT_EXECUTE(BAVL_Insert(&clients_tree, &client->tree_node, NULL))
+    
+    // init knowledge lists
+    LinkedList1_Init(&client->know_out_list);
+    LinkedList1_Init(&client->know_in_list);
+    
+    // initialize peer flows from us list and tree (flows for sending messages to other clients)
+    LinkedList1_Init(&client->peer_out_flows_list);
+    BAVL_Init(&client->peer_out_flows_tree, OFFSET_DIFF(struct peer_flow, dest_client_id, src_tree_node), (BAVL_comparator)peerid_comparator, NULL);
+    
+    // init dying
+    client->dying = 0;
+    BPending_Init(&client->dying_job, BReactor_PendingGroup(&ss), (BPending_handler)client_dying_job, client);
+    
+    // set state
+    client->initstatus = (options.ssl ? INITSTATUS_HANDSHAKE : INITSTATUS_WAITHELLO);
+    
+    client_log(client, BLOG_INFO, "initialized");
+    
+    return;
+    
+    if (options.ssl) {
+fail3:
+        ASSERT_FORCE(PR_Close(client->ssl_prfd) == PR_SUCCESS)
+    }
+fail2:
+    BConnection_RecvAsync_Free(&client->con);
+    BConnection_SendAsync_Free(&client->con);
+    BConnection_Free(&client->con);
+fail1:
+    free(client);
+fail0:
+    return;
+}
+
+void client_dealloc (struct client_data *client)
+{
+    ASSERT(LinkedList1_IsEmpty(&client->know_out_list))
+    ASSERT(LinkedList1_IsEmpty(&client->know_in_list))
+    ASSERT(LinkedList1_IsEmpty(&client->peer_out_flows_list))
+    
+    // free I/O
+    if (client->initstatus >= INITSTATUS_WAITHELLO && !client->dying) {
+        client_dealloc_io(client);
+    }
+    
+    // free dying
+    BPending_Free(&client->dying_job);
+    
+    // link out
+    BAVL_Remove(&clients_tree, &client->tree_node);
+    LinkedList1_Remove(&clients, &client->list_node);
+    clients_num--;
+    
+    // stop disconnect timer
+    BReactor_RemoveTimer(&ss, &client->disconnect_timer);
+    
+    // free SSL
+    if (options.ssl) {
+        BSSLConnection_Free(&client->sslcon);
+        ASSERT_FORCE(PR_Close(client->ssl_prfd) == PR_SUCCESS)
+    }
+    
+    // free common name
+    if (client->common_name) {
+        PORT_Free(client->common_name);
+    }
+    
+    // free connection interfaces
+    BConnection_RecvAsync_Free(&client->con);
+    BConnection_SendAsync_Free(&client->con);
+    
+    // free connection
+    BConnection_Free(&client->con);
+    
+    // free memory
+    free(client);
+}
+
+int client_compute_buffer_size (struct client_data *client)
+{
+    bsize_t s = bsize_add(bsize_fromsize(1), bsize_mul(bsize_fromsize(2), bsize_fromsize(options.max_clients - 1)));
+    
+    if (s.is_overflow || s.value > INT_MAX) {
+        return INT_MAX;
+    } else {
+        return s.value;
+    }
+}
+
+int client_init_io (struct client_data *client)
+{
+    StreamPassInterface *send_if = (options.ssl ? BSSLConnection_GetSendIf(&client->sslcon) : BConnection_SendAsync_GetIf(&client->con));
+    StreamRecvInterface *recv_if = (options.ssl ? BSSLConnection_GetRecvIf(&client->sslcon) : BConnection_RecvAsync_GetIf(&client->con));
+    
+    // init input
+    
+    // init interface
+    PacketPassInterface_Init(&client->input_interface, SC_MAX_ENC, (PacketPassInterface_handler_send)client_input_handler_send, client, BReactor_PendingGroup(&ss));
+    
+    // init decoder
+    if (!PacketProtoDecoder_Init(&client->input_decoder, recv_if, &client->input_interface, BReactor_PendingGroup(&ss), client,
+        (PacketProtoDecoder_handler_error)client_decoder_handler_error
+    )) {
+        client_log(client, BLOG_ERROR, "PacketProtoDecoder_Init failed");
+        goto fail1;
+    }
+    
+    // init output common
+    
+    // init sender
+    PacketStreamSender_Init(&client->output_sender, send_if, PACKETPROTO_ENCLEN(SC_MAX_ENC), BReactor_PendingGroup(&ss));
+    
+    // init queue
+    PacketPassPriorityQueue_Init(&client->output_priorityqueue, PacketStreamSender_GetInput(&client->output_sender), BReactor_PendingGroup(&ss), 0);
+    
+    // init output control flow
+    
+    // init queue flow
+    PacketPassPriorityQueueFlow_Init(&client->output_control_qflow, &client->output_priorityqueue, -1);
+    
+    // init PacketProtoFlow
+    if (!PacketProtoFlow_Init(
+        &client->output_control_oflow, SC_MAX_ENC, client_compute_buffer_size(client),
+        PacketPassPriorityQueueFlow_GetInput(&client->output_control_qflow), BReactor_PendingGroup(&ss)
+    )) {
+        client_log(client, BLOG_ERROR, "PacketProtoFlow_Init failed");
+        goto fail2;
+    }
+    client->output_control_input = PacketProtoFlow_GetInput(&client->output_control_oflow);
+    client->output_control_packet_len = -1;
+    
+    // init output peers flow
+    
+    // init queue flow
+    // use lower priority than control flow (higher number)
+    PacketPassPriorityQueueFlow_Init(&client->output_peers_qflow, &client->output_priorityqueue, 0);
+    
+    // init fair queue (for different peers)
+    if (!PacketPassFairQueue_Init(&client->output_peers_fairqueue, PacketPassPriorityQueueFlow_GetInput(&client->output_peers_qflow), BReactor_PendingGroup(&ss), 0, 1)) {
+        client_log(client, BLOG_ERROR, "PacketPassFairQueue_Init failed");
+        goto fail3;
+    }
+    
+    // init list of flows
+    LinkedList1_Init(&client->output_peers_flows);
+    
+    return 1;
+    
+fail3:
+    PacketPassPriorityQueueFlow_Free(&client->output_peers_qflow);
+    PacketProtoFlow_Free(&client->output_control_oflow);
+fail2:
+    PacketPassPriorityQueueFlow_Free(&client->output_control_qflow);
+    // free output common
+    PacketPassPriorityQueue_Free(&client->output_priorityqueue);
+    PacketStreamSender_Free(&client->output_sender);
+    // free input
+    PacketProtoDecoder_Free(&client->input_decoder);
+fail1:
+    PacketPassInterface_Free(&client->input_interface);
+    return 0;
+}
+
+void client_dealloc_io (struct client_data *client)
+{
+    // stop using any buffers before they get freed
+    if (options.ssl) {
+        BSSLConnection_ReleaseBuffers(&client->sslcon);
+    }
+    
+    // allow freeing fair queue flows
+    PacketPassFairQueue_PrepareFree(&client->output_peers_fairqueue);
+    
+    // remove flows to us
+    LinkedList1Node *node;
+    while (node = LinkedList1_GetFirst(&client->output_peers_flows)) {
+        struct peer_flow *flow = UPPER_OBJECT(node, struct peer_flow, dest_list_node);
+        ASSERT(flow->dest_client == client)
+        peer_flow_dealloc(flow);
+    }
+    
+    // allow freeing priority queue flows
+    PacketPassPriorityQueue_PrepareFree(&client->output_priorityqueue);
+    
+    // free output peers flow
+    PacketPassFairQueue_Free(&client->output_peers_fairqueue);
+    PacketPassPriorityQueueFlow_Free(&client->output_peers_qflow);
+    
+    // free output control flow
+    PacketProtoFlow_Free(&client->output_control_oflow);
+    PacketPassPriorityQueueFlow_Free(&client->output_control_qflow);
+    
+    // free output common
+    PacketPassPriorityQueue_Free(&client->output_priorityqueue);
+    PacketStreamSender_Free(&client->output_sender);
+    
+    // free input
+    PacketProtoDecoder_Free(&client->input_decoder);
+    PacketPassInterface_Free(&client->input_interface);
+}
+
+void client_remove (struct client_data *client)
+{
+    ASSERT(!client->dying)
+    
+    client_log(client, BLOG_INFO, "removing");
+    
+    // set dying to prevent sending this client anything
+    client->dying = 1;
+    
+    // free I/O now, removing incoming flows
+    if (client->initstatus >= INITSTATUS_WAITHELLO) {
+        client_dealloc_io(client);
+    }
+    
+    // remove outgoing knows
+    LinkedList1Node *node;
+    while (node = LinkedList1_GetFirst(&client->know_out_list)) {
+        struct peer_know *k = UPPER_OBJECT(node, struct peer_know, from_node);
+        remove_know(k);
+    }
+    
+    // remove outgoing flows
+    while (node = LinkedList1_GetFirst(&client->peer_out_flows_list)) {
+        struct peer_flow *flow = UPPER_OBJECT(node, struct peer_flow, src_list_node);
+        ASSERT(flow->src_client == client)
+        ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE)
+        ASSERT(!flow->dest_client->dying)
+        
+        if (flow->have_io && PacketPassFairQueueFlow_IsBusy(&flow->qflow)) {
+            client_log(client, BLOG_DEBUG, "removing flow to %d later", (int)flow->dest_client->id);
+            peer_flow_disconnect(flow);
+        } else {
+            client_log(client, BLOG_DEBUG, "removing flow to %d now", (int)flow->dest_client->id);
+            peer_flow_dealloc(flow);
+        }
+    }
+    
+    // schedule job to finish removal after clients are informed
+    BPending_Set(&client->dying_job);
+    
+    // inform other clients that 'client' is no more
+    node = LinkedList1_GetFirst(&client->know_in_list);
+    while (node) {
+        LinkedList1Node *next = LinkedList1Node_Next(node);
+        struct peer_know *k = UPPER_OBJECT(node, struct peer_know, to_node);
+        uninform_know(k);
+        node = next;
+    }
+}
+
+void client_dying_job (struct client_data *client)
+{
+    ASSERT(client->dying)
+    ASSERT(LinkedList1_IsEmpty(&client->know_in_list))
+    
+    client_dealloc(client);
+    return;
+}
+
+void client_logfunc (struct client_data *client)
+{
+    char addr[BADDR_MAX_PRINT_LEN];
+    BAddr_Print(&client->addr, addr);
+    
+    BLog_Append("client %d (%s)", (int)client->id, addr);
+    if (client->common_name) {
+        BLog_Append(" (%s)", client->common_name);
+    }
+    BLog_Append(": ");
+}
+
+void client_log (struct client_data *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_data *client)
+{
+    ASSERT(!client->dying)
+    
+    client_log(client, BLOG_INFO, "timed out");
+    
+    client_remove(client);
+    return;
+}
+
+void client_connection_handler (struct client_data *client, int event)
+{
+    ASSERT(!client->dying)
+    
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        client_log(client, BLOG_INFO, "connection closed");
+    } else {
+        client_log(client, BLOG_INFO, "connection error");
+    }
+    
+    client_remove(client);
+    return;
+}
+
+void client_sslcon_handler (struct client_data *client, int event)
+{
+    ASSERT(options.ssl)
+    ASSERT(!client->dying)
+    ASSERT(event == BSSLCONNECTION_EVENT_UP || event == BSSLCONNECTION_EVENT_ERROR)
+    ASSERT(!(event == BSSLCONNECTION_EVENT_UP) || client->initstatus == INITSTATUS_HANDSHAKE)
+    
+    if (event == BSSLCONNECTION_EVENT_ERROR) {
+        client_log(client, BLOG_ERROR, "SSL error");
+        client_remove(client);
+        return;
+    }
+    
+    // get client certificate
+    CERTCertificate *cert = SSL_PeerCertificate(client->ssl_prfd);
+    if (!cert) {
+        client_log(client, BLOG_ERROR, "SSL_PeerCertificate failed");
+        goto fail0;
+    }
+    
+    // remember common name
+    if (!(client->common_name = CERT_GetCommonName(&cert->subject))) {
+        client_log(client, BLOG_NOTICE, "CERT_GetCommonName failed");
+        goto fail1;
+    }
+    
+    // store certificate
+    SECItem der = cert->derCert;
+    if (der.len > sizeof(client->cert)) {
+        client_log(client, BLOG_NOTICE, "client certificate too big");
+        goto fail1;
+    }
+    memcpy(client->cert, der.data, der.len);
+    client->cert_len = der.len;
+    
+    PRArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if (!arena) {
+        client_log(client, BLOG_ERROR, "PORT_NewArena failed");
+        goto fail1;
+    }
+    
+    // encode certificate
+    memset(&der, 0, sizeof(der));
+    if (!SEC_ASN1EncodeItem(arena, &der, cert, SEC_ASN1_GET(CERT_CertificateTemplate))) {
+        client_log(client, BLOG_ERROR, "SEC_ASN1EncodeItem failed");
+        goto fail2;
+    }
+    
+    // store re-encoded certificate (for compatibility with old clients)
+    if (der.len > sizeof(client->cert_old)) {
+        client_log(client, BLOG_NOTICE, "client certificate too big");
+        goto fail2;
+    }
+    memcpy(client->cert_old, der.data, der.len);
+    client->cert_old_len = der.len;
+    
+    // init I/O chains
+    if (!client_init_io(client)) {
+        goto fail2;
+    }
+    
+    PORT_FreeArena(arena, PR_FALSE);
+    CERT_DestroyCertificate(cert);
+    
+    // set client state
+    client->initstatus = INITSTATUS_WAITHELLO;
+    
+    client_log(client, BLOG_INFO, "handshake complete");
+    
+    return;
+    
+    // handle errors
+fail2:
+    PORT_FreeArena(arena, PR_FALSE);
+fail1:
+    CERT_DestroyCertificate(cert);
+fail0:
+    client_remove(client);
+}
+
+void client_decoder_handler_error (struct client_data *client)
+{
+    ASSERT(INITSTATUS_HASLINK(client->initstatus))
+    ASSERT(!client->dying)
+    
+    client_log(client, BLOG_ERROR, "decoder error");
+    
+    client_remove(client);
+    return;
+}
+
+int client_start_control_packet (struct client_data *client, void **data, int len)
+{
+    ASSERT(len >= 0)
+    ASSERT(len <= SC_MAX_PAYLOAD)
+    ASSERT(!(len > 0) || data)
+    ASSERT(INITSTATUS_HASLINK(client->initstatus))
+    ASSERT(!client->dying)
+    ASSERT(client->output_control_packet_len == -1)
+    
+#ifdef SIMULATE_OUT_OF_CONTROL_BUFFER
+    uint8_t x;
+    BRandom_randomize(&x, sizeof(x));
+    if (x < SIMULATE_OUT_OF_CONTROL_BUFFER) {
+        client_log(client, BLOG_INFO, "out of control buffer, removing");
+        client_remove(client);
+        return -1;
+    }
+#endif
+    
+    // obtain location for writing the packet
+    if (!BufferWriter_StartPacket(client->output_control_input, &client->output_control_packet)) {
+        // out of buffer, kill client
+        client_log(client, BLOG_INFO, "out of control buffer, removing");
+        client_remove(client);
+        return -1;
+    }
+    
+    client->output_control_packet_len = len;
+    
+    if (data) {
+        *data = client->output_control_packet + sizeof(struct sc_header);
+    }
+    
+    return 0;
+}
+
+void client_end_control_packet (struct client_data *client, uint8_t type)
+{
+    ASSERT(INITSTATUS_HASLINK(client->initstatus))
+    ASSERT(!client->dying)
+    ASSERT(client->output_control_packet_len >= 0)
+    ASSERT(client->output_control_packet_len <= SC_MAX_PAYLOAD)
+    
+    // write header
+    struct sc_header header;
+    header.type = htol8(type);
+    memcpy(client->output_control_packet, &header, sizeof(header));
+    
+    // finish writing packet
+    BufferWriter_EndPacket(client->output_control_input, sizeof(struct sc_header) + client->output_control_packet_len);
+    
+    client->output_control_packet_len = -1;
+}
+
+int client_send_newclient (struct client_data *client, struct client_data *nc, int relay_server, int relay_client)
+{
+    ASSERT(client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!client->dying)
+    ASSERT(nc->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!nc->dying)
+    
+    int flags = 0;
+    if (relay_server) {
+        flags |= SCID_NEWCLIENT_FLAG_RELAY_SERVER;
+    }
+    if (relay_client) {
+        flags |= SCID_NEWCLIENT_FLAG_RELAY_CLIENT;
+    }
+    if (options.ssl && client->version > SC_OLDVERSION_NOSSL && nc->version > SC_OLDVERSION_NOSSL) {
+        flags |= SCID_NEWCLIENT_FLAG_SSL;
+    }
+    
+    uint8_t *cert_data = NULL;
+    int cert_len = 0;
+    if (options.ssl) {
+        cert_data = (client->version == SC_OLDVERSION_BROKENCERT ?  nc->cert_old : nc->cert);
+        cert_len = (client->version == SC_OLDVERSION_BROKENCERT ?  nc->cert_old_len : nc->cert_len);
+    }
+    
+    struct sc_server_newclient omsg;
+    void *pack;
+    if (client_start_control_packet(client, &pack, sizeof(omsg) + cert_len) < 0) {
+        return -1;
+    }
+    omsg.id = htol16(nc->id);
+    omsg.flags = htol16(flags);
+    memcpy(pack, &omsg, sizeof(omsg));
+    if (cert_len > 0) {
+        memcpy((char *)pack + sizeof(omsg), cert_data, cert_len);
+    }
+    client_end_control_packet(client, SCID_NEWCLIENT);
+    
+    return 0;
+}
+
+int client_send_endclient (struct client_data *client, peerid_t end_id)
+{
+    ASSERT(client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!client->dying)
+    
+    struct sc_server_endclient omsg;
+    void *pack;
+    if (client_start_control_packet(client, &pack, sizeof(omsg)) < 0) {
+        return -1;
+    }
+    omsg.id = htol16(end_id);
+    memcpy(pack, &omsg, sizeof(omsg));
+    client_end_control_packet(client, SCID_ENDCLIENT);
+    
+    return 0;
+}
+
+void client_input_handler_send (struct client_data *client, uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= SC_MAX_ENC)
+    ASSERT(INITSTATUS_HASLINK(client->initstatus))
+    ASSERT(!client->dying)
+    
+    // accept packet
+    PacketPassInterface_Done(&client->input_interface);
+    
+    // restart disconnect timer
+    BReactor_SetTimer(&ss, &client->disconnect_timer);
+    
+    // parse header
+    if (data_len < sizeof(struct sc_header)) {
+        client_log(client, BLOG_NOTICE, "packet too short");
+        client_remove(client);
+        return;
+    }
+    struct sc_header header;
+    memcpy(&header, data, sizeof(header));
+    data += sizeof(header);
+    data_len -= sizeof(header);
+    uint8_t type = ltoh8(header.type);
+    
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= SC_MAX_PAYLOAD)
+    
+    // perform action based on packet type
+    switch (type) {
+        case SCID_KEEPALIVE:
+            client_log(client, BLOG_DEBUG, "received keep-alive");
+            return;
+        case SCID_CLIENTHELLO:
+            process_packet_hello(client, data, data_len);
+            return;
+        case SCID_OUTMSG:
+            process_packet_outmsg(client, data, data_len);
+            return;
+        case SCID_RESETPEER:
+            process_packet_resetpeer(client, data, data_len);
+            return;
+        case SCID_ACCEPTPEER:
+            process_packet_acceptpeer(client, data, data_len);
+            return;
+        default:
+            client_log(client, BLOG_NOTICE, "unknown packet type %d, removing", (int)type);
+            client_remove(client);
+            return;
+    }
+}
+
+void process_packet_hello (struct client_data *client, uint8_t *data, int data_len)
+{
+    if (client->initstatus != INITSTATUS_WAITHELLO) {
+        client_log(client, BLOG_NOTICE, "hello: not expected");
+        client_remove(client);
+        return;
+    }
+    
+    if (data_len != sizeof(struct sc_client_hello)) {
+        client_log(client, BLOG_NOTICE, "hello: invalid length");
+        client_remove(client);
+        return;
+    }
+    
+    struct sc_client_hello msg;
+    memcpy(&msg, data, sizeof(msg));
+    client->version = ltoh16(msg.version);
+    
+    switch (client->version) {
+        case SC_VERSION:
+        case SC_OLDVERSION_NOSSL:
+        case SC_OLDVERSION_BROKENCERT:
+            break;
+        default:
+            client_log(client, BLOG_ERROR, "hello: unknown version (%d)", client->version);
+            client_remove(client);
+            return;
+    }
+    
+    client_log(client, BLOG_INFO, "received hello");
+    
+    // set client state to complete
+    client->initstatus = INITSTATUS_COMPLETE;
+    
+    // publish client
+    for (LinkedList1Node *list_node = LinkedList1_GetFirst(&clients); list_node; list_node = LinkedList1Node_Next(list_node)) {
+        struct client_data *client2 = UPPER_OBJECT(list_node, struct client_data, list_node);
+        if (client2 == client || client2->initstatus != INITSTATUS_COMPLETE || client2->dying || !clients_allowed(client, client2)) {
+            continue;
+        }
+        
+        // create flow from client to client2
+        struct peer_flow *flow_to = peer_flow_create(client, client2);
+        if (!flow_to) {
+            client_log(client, BLOG_ERROR, "failed to allocate flow to %d", (int)client2->id);
+            goto fail;
+        }
+        
+        // create flow from client2 to client
+        struct peer_flow *flow_from = peer_flow_create(client2, client);
+        if (!flow_from) {
+            client_log(client, BLOG_ERROR, "failed to allocate flow from %d", (int)client2->id);
+            goto fail;
+        }
+        
+        // set opposite flow pointers
+        flow_to->opposite = flow_from;
+        flow_from->opposite = flow_to;
+        
+        // launch pair
+        if (!launch_pair(flow_to)) {
+            return;
+        }
+    }
+    
+    // send hello
+    struct sc_server_hello omsg;
+    void *pack;
+    if (client_start_control_packet(client, &pack, sizeof(omsg)) < 0) {
+        return;
+    }
+    omsg.flags = htol16(0);
+    omsg.id = htol16(client->id);
+    omsg.clientAddr = (client->addr.type == BADDR_TYPE_IPV4 ? client->addr.ipv4.ip : hton32(0));
+    memcpy(pack, &omsg, sizeof(omsg));
+    client_end_control_packet(client, SCID_SERVERHELLO);
+    
+    return;
+    
+fail:
+    client_remove(client);
+}
+
+void process_packet_outmsg (struct client_data *client, uint8_t *data, int data_len)
+{
+    if (client->initstatus != INITSTATUS_COMPLETE) {
+        client_log(client, BLOG_NOTICE, "outmsg: not expected");
+        client_remove(client);
+        return;
+    }
+    
+    if (data_len < sizeof(struct sc_client_outmsg)) {
+        client_log(client, BLOG_NOTICE, "outmsg: wrong size");
+        client_remove(client);
+        return;
+    }
+    
+    struct sc_client_outmsg msg;
+    memcpy(&msg, data, sizeof(msg));
+    peerid_t id = ltoh16(msg.clientid);
+    int payload_size = data_len - sizeof(struct sc_client_outmsg);
+    
+    if (payload_size > SC_MAX_MSGLEN) {
+        client_log(client, BLOG_NOTICE, "outmsg: too large payload");
+        client_remove(client);
+        return;
+    }
+    
+    uint8_t *payload = data + sizeof(struct sc_client_outmsg);
+    
+    // lookup flow to destination client
+    struct peer_flow *flow = find_flow(client, id);
+    if (!flow) {
+        client_log(client, BLOG_INFO, "no flow for message to %d", (int)id);
+        return;
+    }
+    
+    // if pair is resetting, ignore message
+    if (flow->resetting || flow->opposite->resetting) {
+        client_log(client, BLOG_INFO, "pair is resetting; not forwarding message to %d", (int)id);
+        return;
+    }
+    
+    // if sending client hasn't accepted yet, ignore message
+    if (!flow->accepted) {
+        client_log(client, BLOG_INFO, "client hasn't accepted; not forwarding message to %d", (int)id);
+        return;
+    }
+    
+#ifdef SIMULATE_OUT_OF_FLOW_BUFFER
+    uint8_t x;
+    BRandom_randomize(&x, sizeof(x));
+    if (x < SIMULATE_OUT_OF_FLOW_BUFFER) {
+        client_log(client, BLOG_WARNING, "simulating error; resetting to %d", (int)flow->dest_client->id);
+        peer_flow_start_reset(flow);
+        return;
+    }
+#endif
+    
+    // send packet
+    struct sc_server_inmsg omsg;
+    void *pack;
+    if (!peer_flow_start_packet(flow, &pack, sizeof(omsg) + payload_size)) {
+        // out of buffer, reset these two clients
+        client_log(client, BLOG_WARNING, "out of buffer; resetting to %d", (int)flow->dest_client->id);
+        peer_flow_start_reset(flow);
+        return;
+    }
+    omsg.clientid = htol16(client->id);
+    memcpy(pack, &omsg, sizeof(omsg));
+    memcpy((char *)pack + sizeof(omsg), payload, payload_size);
+    peer_flow_end_packet(flow, SCID_INMSG);
+}
+
+void process_packet_resetpeer (struct client_data *client, uint8_t *data, int data_len)
+{
+    if (client->initstatus != INITSTATUS_COMPLETE) {
+        client_log(client, BLOG_NOTICE, "resetpeer: not expected");
+        client_remove(client);
+        return;
+    }
+    
+    if (data_len != sizeof(struct sc_client_resetpeer)) {
+        client_log(client, BLOG_NOTICE, "resetpeer: wrong size");
+        client_remove(client);
+        return;
+    }
+    
+    struct sc_client_resetpeer msg;
+    memcpy(&msg, data, sizeof(msg));
+    peerid_t id = ltoh16(msg.clientid);
+    
+    // lookup flow to destination client
+    struct peer_flow *flow = find_flow(client, id);
+    if (!flow) {
+        client_log(client, BLOG_INFO, "no flow for reset to %d", (int)id);
+        return;
+    }
+    
+    // if pair is resetting, ignore message
+    if (flow->resetting || flow->opposite->resetting) {
+        client_log(client, BLOG_INFO, "pair is resetting; not resetting to %d", (int)id);
+        return;
+    }
+    
+    // if sending client hasn't accepted yet, ignore message
+    if (!flow->accepted) {
+        client_log(client, BLOG_INFO, "client hasn't accepted; not resetting to %d", (int)id);
+        return;
+    }
+    
+    client_log(client, BLOG_WARNING, "resetting to %d", (int)flow->dest_client->id);
+    
+    // reset clients
+    peer_flow_start_reset(flow);
+}
+
+void process_packet_acceptpeer (struct client_data *client, uint8_t *data, int data_len)
+{
+    if (client->initstatus != INITSTATUS_COMPLETE) {
+        client_log(client, BLOG_NOTICE, "acceptpeer: not expected");
+        client_remove(client);
+        return;
+    }
+    
+    if (data_len != sizeof(struct sc_client_acceptpeer)) {
+        client_log(client, BLOG_NOTICE, "acceptpeer: wrong size");
+        client_remove(client);
+        return;
+    }
+    
+    struct sc_client_acceptpeer msg;
+    memcpy(&msg, data, sizeof(msg));
+    peerid_t id = ltoh16(msg.clientid);
+    
+    // lookup flow to destination client
+    struct peer_flow *flow = find_flow(client, id);
+    if (!flow) {
+        // the specified client has probably gone away but the sending client didn't know
+        // that yet; this is expected
+        client_log(client, BLOG_INFO, "acceptpeer: no flow to %d", (int)id);
+        return;
+    }
+    
+    // client can only accept once
+    if (flow->accepted) {
+        // the previous accept is probably from an old client with the same ID as this one;
+        // this is bad, disconnect client
+        client_log(client, BLOG_ERROR, "acceptpeer: already accepted to %d", (int)id);
+        client_remove(client);
+        return;
+    }
+    
+    client_log(client, BLOG_INFO, "accepted %d", (int)id);
+    
+    // set accepted
+    flow->accepted = 1;
+    
+    // if pair is resetting, continue
+    if (flow->resetting) {
+        peer_flow_drive_reset(flow);
+    } else if (flow->opposite->resetting) {
+        peer_flow_drive_reset(flow->opposite);
+    }
+}
+
+struct peer_flow * peer_flow_create (struct client_data *src_client, struct client_data *dest_client)
+{
+    ASSERT(src_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!src_client->dying)
+    ASSERT(dest_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!dest_client->dying)
+    ASSERT(!find_flow(src_client, dest_client->id))
+    
+    // allocate flow structure
+    struct peer_flow *flow = (struct peer_flow *)malloc(sizeof(*flow));
+    if (!flow) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // set source and destination
+    flow->src_client = src_client;
+    flow->dest_client = dest_client;
+    flow->dest_client_id = dest_client->id;
+    
+    // add to source list and tree
+    LinkedList1_Append(&flow->src_client->peer_out_flows_list, &flow->src_list_node);
+    ASSERT_EXECUTE(BAVL_Insert(&flow->src_client->peer_out_flows_tree, &flow->src_tree_node, NULL))
+    
+    // add to destination client list
+    LinkedList1_Append(&flow->dest_client->output_peers_flows, &flow->dest_list_node);
+    
+    // have no I/O
+    flow->have_io = 0;
+    
+    // init reset timer
+    BTimer_Init(&flow->reset_timer, CLIENT_RESET_TIME, (BTimer_handler)peer_flow_reset_timer_handler, flow);
+    
+    return flow;
+    
+fail0:
+    return NULL;
+}
+
+void peer_flow_dealloc (struct peer_flow *flow)
+{
+    if (flow->have_io) { PacketPassFairQueueFlow_AssertFree(&flow->qflow); }
+    
+    // free reset timer
+    BReactor_RemoveTimer(&ss, &flow->reset_timer);
+    
+    // free I/O
+    if (flow->have_io) {
+        peer_flow_free_io(flow);
+    }
+    
+    // remove from destination client list
+    LinkedList1_Remove(&flow->dest_client->output_peers_flows, &flow->dest_list_node);
+    
+    // remove from source list and hash table
+    if (flow->src_client) {
+        BAVL_Remove(&flow->src_client->peer_out_flows_tree, &flow->src_tree_node);
+        LinkedList1_Remove(&flow->src_client->peer_out_flows_list, &flow->src_list_node);
+    }
+    
+    // free memory
+    free(flow);
+}
+
+int peer_flow_init_io (struct peer_flow *flow)
+{
+    ASSERT(!flow->have_io)
+    
+    // init queue flow
+    PacketPassFairQueueFlow_Init(&flow->qflow, &flow->dest_client->output_peers_fairqueue);
+    
+    // init PacketProtoFlow
+    if (!PacketProtoFlow_Init(
+        &flow->oflow, SC_MAX_ENC, CLIENT_PEER_FLOW_BUFFER_MIN_PACKETS,
+        PacketPassFairQueueFlow_GetInput(&flow->qflow), BReactor_PendingGroup(&ss)
+    )) {
+        BLog(BLOG_ERROR, "PacketProtoFlow_Init failed");
+        goto fail1;
+    }
+    flow->input = PacketProtoFlow_GetInput(&flow->oflow);
+    
+    // set no packet
+    flow->packet_len = -1;
+    
+    // set have I/O
+    flow->have_io = 1;
+    
+    return 1;
+    
+fail1:
+    PacketPassFairQueueFlow_Free(&flow->qflow);
+    return 0;
+}
+
+void peer_flow_free_io (struct peer_flow *flow)
+{
+    ASSERT(flow->have_io)
+    PacketPassFairQueueFlow_AssertFree(&flow->qflow);
+    
+    // free PacketProtoFlow
+    PacketProtoFlow_Free(&flow->oflow);
+    
+    // free queue flow
+    PacketPassFairQueueFlow_Free(&flow->qflow);
+    
+    // set have no I/O
+    flow->have_io = 0;
+}
+
+void peer_flow_disconnect (struct peer_flow *flow)
+{
+    ASSERT(flow->src_client)
+    ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->dest_client->dying)
+    ASSERT(flow->have_io)
+    ASSERT(PacketPassFairQueueFlow_IsBusy(&flow->qflow))
+    
+    // stop reset timer
+    BReactor_RemoveTimer(&ss, &flow->reset_timer);
+    
+    // remove from source list and hash table
+    BAVL_Remove(&flow->src_client->peer_out_flows_tree, &flow->src_tree_node);
+    LinkedList1_Remove(&flow->src_client->peer_out_flows_list, &flow->src_list_node);
+    
+    // set no source
+    flow->src_client = NULL;
+    
+    // set busy handler
+    PacketPassFairQueueFlow_SetBusyHandler(&flow->qflow, (PacketPassFairQueue_handler_busy)peer_flow_handler_canremove, flow);
+}
+
+int peer_flow_start_packet (struct peer_flow *flow, void **data, int len)
+{
+    ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->dest_client->dying)
+    ASSERT(flow->src_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->src_client->dying)
+    ASSERT(!flow->resetting)
+    ASSERT(!flow->opposite->resetting)
+    ASSERT(flow->have_io)
+    ASSERT(flow->packet_len == -1)
+    ASSERT(len >= 0)
+    ASSERT(len <= SC_MAX_PAYLOAD)
+    ASSERT(!(len > 0) || data)
+    
+    // obtain location for writing the packet
+    if (!BufferWriter_StartPacket(flow->input, &flow->packet)) {
+        return 0;
+    }
+    
+    // remember packet length
+    flow->packet_len = len;
+    
+    if (data) {
+        *data = flow->packet + sizeof(struct sc_header);
+    }
+    return 1;
+}
+
+void peer_flow_end_packet (struct peer_flow *flow, uint8_t type)
+{
+    ASSERT(flow->have_io)
+    ASSERT(flow->packet_len >= 0)
+    ASSERT(flow->packet_len <= SC_MAX_PAYLOAD)
+    
+    // write header
+    struct sc_header header;
+    header.type = type;
+    memcpy(flow->packet, &header, sizeof(header));
+    
+    // finish writing packet
+    BufferWriter_EndPacket(flow->input, sizeof(struct sc_header) + flow->packet_len);
+    
+    // set have no packet
+    flow->packet_len = -1;
+}
+
+void peer_flow_handler_canremove (struct peer_flow *flow)
+{
+    ASSERT(!flow->src_client)
+    ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->dest_client->dying)
+    ASSERT(flow->have_io)
+    PacketPassFairQueueFlow_AssertFree(&flow->qflow);
+    
+    client_log(flow->dest_client, BLOG_DEBUG, "removing old flow");
+    
+    peer_flow_dealloc(flow);
+    return;
+}
+
+void peer_flow_start_reset (struct peer_flow *flow)
+{
+    ASSERT(flow->src_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->src_client->dying)
+    ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->dest_client->dying)
+    ASSERT(!flow->resetting)
+    ASSERT(!flow->opposite->resetting)
+    ASSERT(flow->have_io)
+    ASSERT(flow->opposite->have_io)
+    
+    client_log(flow->src_client, BLOG_INFO, "starting reset to %d", (int)flow->dest_client->id);
+    
+    // set resetting
+    flow->resetting = 1;
+    
+    peer_flow_drive_reset(flow);
+}
+
+void peer_flow_drive_reset (struct peer_flow *flow)
+{
+    ASSERT(flow->src_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->src_client->dying)
+    ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->dest_client->dying)
+    ASSERT(flow->resetting)
+    ASSERT(!flow->opposite->resetting)
+    ASSERT(!BTimer_IsRunning(&flow->reset_timer))
+    
+    // try to free I/O
+    if (flow->have_io) {
+        if (PacketPassFairQueueFlow_IsBusy(&flow->qflow)) {
+            PacketPassFairQueueFlow_SetBusyHandler(&flow->qflow, (PacketPassFairQueue_handler_busy)peer_flow_reset_qflow_handler_busy, flow);
+        } else {
+            peer_flow_free_io(flow);
+        }
+    }
+    
+    // try to free opposite I/O
+    if (flow->opposite->have_io) {
+        if (PacketPassFairQueueFlow_IsBusy(&flow->opposite->qflow)) {
+            PacketPassFairQueueFlow_SetBusyHandler(&flow->opposite->qflow, (PacketPassFairQueue_handler_busy)peer_flow_reset_qflow_handler_busy, flow->opposite);
+        } else {
+            peer_flow_free_io(flow->opposite);
+        }
+    }
+    
+    // if we still got some I/O, or some client hasn't accepted yet, wait
+    if (flow->have_io || flow->opposite->have_io || !flow->accepted || !flow->opposite->accepted) {
+        return;
+    }
+    
+    // set reset timer
+    BReactor_SetTimer(&ss, &flow->reset_timer);
+}
+
+void peer_flow_reset_qflow_handler_busy (struct peer_flow *flow)
+{
+    ASSERT(flow->src_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->src_client->dying)
+    ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->dest_client->dying)
+    ASSERT(flow->resetting || flow->opposite->resetting)
+    ASSERT(flow->have_io)
+    ASSERT(!PacketPassFairQueueFlow_IsBusy(&flow->qflow))
+    
+    if (flow->resetting) {
+        peer_flow_drive_reset(flow);
+    } else {
+        peer_flow_drive_reset(flow->opposite);
+    }
+}
+
+void peer_flow_reset_timer_handler (struct peer_flow *flow)
+{
+    ASSERT(flow->src_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->src_client->dying)
+    ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->dest_client->dying)
+    ASSERT(flow->resetting)
+    ASSERT(!flow->opposite->resetting)
+    ASSERT(!flow->have_io)
+    ASSERT(!flow->opposite->have_io)
+    ASSERT(flow->accepted)
+    ASSERT(flow->opposite->accepted)
+    
+    client_log(flow->src_client, BLOG_INFO, "finally resetting to %d", (int)flow->dest_client->id);
+    
+    struct peer_know *know = flow->know;
+    struct peer_know *know_opposite = flow->opposite->know;
+    
+    // launch pair
+    if (!launch_pair(flow)) {
+        return;
+    }
+    
+    // remove old knows
+    uninform_know(know);
+    uninform_know(know_opposite);
+}
+
+peerid_t new_client_id (void)
+{
+    ASSERT(clients_num < options.max_clients)
+    
+    for (int i = 0; i < options.max_clients; i++) {
+        peerid_t id = clients_nextid++;
+        if (!find_client_by_id(id)) {
+            return id;
+        }
+    }
+    
+    ASSERT(0)
+    return 42;
+}
+
+struct client_data * find_client_by_id (peerid_t id)
+{
+    BAVLNode *node;
+    if (!(node = BAVL_LookupExact(&clients_tree, &id))) {
+        return NULL;
+    }
+    
+    return UPPER_OBJECT(node, struct client_data, tree_node);
+}
+
+int clients_allowed (struct client_data *client1, struct client_data *client2)
+{
+    ASSERT(client1->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!client1->dying)
+    ASSERT(client2->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!client2->dying)
+    
+    if (!options.comm_predicate) {
+        return 1;
+    }
+    
+    // set values to compare against
+    comm_predicate_p1name = (client1->common_name ? client1->common_name : "");
+    comm_predicate_p2name = (client2->common_name ? client2->common_name : "");
+    BAddr_GetIPAddr(&client1->addr, &comm_predicate_p1addr);
+    BAddr_GetIPAddr(&client2->addr, &comm_predicate_p2addr);
+    
+    // evaluate predicate
+    int res = BPredicate_Eval(&comm_predicate);
+    if (res < 0) {
+        return 0;
+    }
+    
+    return res;
+}
+
+int comm_predicate_func_p1name_cb (void *user, void **args)
+{
+    char *arg = (char *)args[0];
+    
+    return (!strcmp(arg, comm_predicate_p1name));
+}
+
+int comm_predicate_func_p2name_cb (void *user, void **args)
+{
+    char *arg = (char *)args[0];
+    
+    return (!strcmp(arg, comm_predicate_p2name));
+}
+
+int comm_predicate_func_p1addr_cb (void *user, void **args)
+{
+    char *arg = (char *)args[0];
+    
+    BIPAddr addr;
+    if (!BIPAddr_Resolve(&addr, arg, 1)) {
+        BLog(BLOG_WARNING, "failed to parse address");
+        return -1;
+    }
+    
+    return BIPAddr_Compare(&addr, &comm_predicate_p1addr);
+}
+
+int comm_predicate_func_p2addr_cb (void *user, void **args)
+{
+    char *arg = (char *)args[0];
+    
+    BIPAddr addr;
+    if (!BIPAddr_Resolve(&addr, arg, 1)) {
+        BLog(BLOG_WARNING, "failed to parse address");
+        return -1;
+    }
+    
+    return BIPAddr_Compare(&addr, &comm_predicate_p2addr);
+}
+
+int relay_allowed (struct client_data *client, struct client_data *relay)
+{
+    if (!options.relay_predicate) {
+        return 0;
+    }
+    
+    // set values to compare against
+    relay_predicate_pname = (client->common_name ? client->common_name : "");
+    relay_predicate_rname = (relay->common_name ? relay->common_name : "");
+    BAddr_GetIPAddr(&client->addr, &relay_predicate_paddr);
+    BAddr_GetIPAddr(&relay->addr, &relay_predicate_raddr);
+    
+    // evaluate predicate
+    int res = BPredicate_Eval(&relay_predicate);
+    if (res < 0) {
+        return 0;
+    }
+    
+    return res;
+}
+
+int relay_predicate_func_pname_cb (void *user, void **args)
+{
+    char *arg = (char *)args[0];
+    
+    return (!strcmp(arg, relay_predicate_pname));
+}
+
+int relay_predicate_func_rname_cb (void *user, void **args)
+{
+    char *arg = (char *)args[0];
+    
+    return (!strcmp(arg, relay_predicate_rname));
+}
+
+int relay_predicate_func_paddr_cb (void *user, void **args)
+{
+    char *arg = (char *)args[0];
+    
+    BIPAddr addr;
+    if (!BIPAddr_Resolve(&addr, arg, 1)) {
+        BLog(BLOG_ERROR, "paddr: failed to parse address");
+        return -1;
+    }
+    
+    return BIPAddr_Compare(&addr, &relay_predicate_paddr);
+}
+
+int relay_predicate_func_raddr_cb (void *user, void **args)
+{
+    char *arg = (char *)args[0];
+    
+    BIPAddr addr;
+    if (!BIPAddr_Resolve(&addr, arg, 1)) {
+        BLog(BLOG_ERROR, "raddr: failed to parse address");
+        return -1;
+    }
+    
+    return BIPAddr_Compare(&addr, &relay_predicate_raddr);
+}
+
+int peerid_comparator (void *unused, peerid_t *p1, peerid_t *p2)
+{
+    return B_COMPARE(*p1, *p2);
+}
+
+struct peer_know * create_know (struct client_data *from, struct client_data *to, int relay_server, int relay_client)
+{
+    ASSERT(from->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!from->dying)
+    ASSERT(to->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!to->dying)
+    
+    // allocate structure
+    struct peer_know *k = (struct peer_know *)malloc(sizeof(*k));
+    if (!k) {
+        return NULL;
+    }
+    
+    // init arguments
+    k->from = from;
+    k->to = to;
+    k->relay_server = relay_server;
+    k->relay_client = relay_client;
+    
+    // append to lists
+    LinkedList1_Append(&from->know_out_list, &k->from_node);
+    LinkedList1_Append(&to->know_in_list, &k->to_node);
+    
+    // init and set inform job to inform client 'from' about client 'to'
+    BPending_Init(&k->inform_job, BReactor_PendingGroup(&ss), (BPending_handler)know_inform_job_handler, k);
+    BPending_Set(&k->inform_job);
+    
+    // init uninform job
+    BPending_Init(&k->uninform_job, BReactor_PendingGroup(&ss), (BPending_handler)know_uninform_job_handler, k);
+    
+    return k;
+}
+
+void remove_know (struct peer_know *k)
+{
+    // free uninform job
+    BPending_Free(&k->uninform_job);
+    
+    // free inform job
+    BPending_Free(&k->inform_job);
+    
+    // remove from lists
+    LinkedList1_Remove(&k->to->know_in_list, &k->to_node);
+    LinkedList1_Remove(&k->from->know_out_list, &k->from_node);
+    
+    // free structure
+    free(k);
+}
+
+void know_inform_job_handler (struct peer_know *k)
+{
+    ASSERT(!k->from->dying)
+    ASSERT(!k->to->dying)
+    
+    client_send_newclient(k->from, k->to, k->relay_server, k->relay_client);
+    return;
+}
+
+void uninform_know (struct peer_know *k)
+{
+    ASSERT(!k->from->dying)
+    
+    // if 'from' has not been informed about 'to' yet, remove know, otherwise
+    // schedule informing 'from' that 'to' is no more
+    if (BPending_IsSet(&k->inform_job)) {
+        remove_know(k);
+    } else {
+        BPending_Set(&k->uninform_job);
+    }
+}
+
+void know_uninform_job_handler (struct peer_know *k)
+{
+    ASSERT(!k->from->dying)
+    ASSERT(!BPending_IsSet(&k->inform_job))
+    
+    struct client_data *from = k->from;
+    struct client_data *to = k->to;
+    
+    // remove know
+    remove_know(k);
+    
+    // uninform
+    client_send_endclient(from, to->id);
+}
+
+int launch_pair (struct peer_flow *flow_to)
+{
+    struct client_data *client = flow_to->src_client;
+    struct client_data *client2 = flow_to->dest_client;
+    ASSERT(client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!client->dying)
+    ASSERT(client2->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!client2->dying)
+    ASSERT(!flow_to->have_io)
+    ASSERT(!flow_to->opposite->have_io)
+    ASSERT(!BTimer_IsRunning(&flow_to->reset_timer))
+    ASSERT(!BTimer_IsRunning(&flow_to->opposite->reset_timer))
+    
+    // init I/O
+    if (!peer_flow_init_io(flow_to)) {
+        goto fail;
+    }
+    
+    // init opposite I/O
+    if (!peer_flow_init_io(flow_to->opposite)) {
+        goto fail;
+    }
+    
+    // determine relay relations
+    int relay_to = relay_allowed(client, client2);
+    int relay_from = relay_allowed(client2, client);
+    
+    // create know to
+    struct peer_know *know_to = create_know(client, client2, relay_to, relay_from);
+    if (!know_to) {
+        client_log(client, BLOG_ERROR, "failed to allocate know to %d", (int)client2->id);
+        goto fail;
+    }
+    
+    // create know from
+    struct peer_know *know_from = create_know(client2, client, relay_from, relay_to);
+    if (!know_from) {
+        client_log(client, BLOG_ERROR, "failed to allocate know from %d", (int)client2->id);
+        goto fail;
+    }
+    
+    // set know pointers in flows
+    flow_to->know = know_to;
+    flow_to->opposite->know = know_from;
+    
+    // set not accepted, or assume accepted for old version
+    flow_to->accepted = (flow_to->src_client->version <= SC_OLDVERSION_NOSSL);
+    flow_to->opposite->accepted = (flow_to->opposite->src_client->version <= SC_OLDVERSION_NOSSL);
+    
+    // set not resetting
+    flow_to->resetting = 0;
+    flow_to->opposite->resetting = 0;
+    
+    return 1;
+    
+fail:
+    client_remove(client);
+    return 0;
+}
+
+struct peer_flow * find_flow (struct client_data *client, peerid_t dest_id)
+{
+    ASSERT(client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!client->dying)
+    
+    BAVLNode *node = BAVL_LookupExact(&client->peer_out_flows_tree, &dest_id);
+    if (!node) {
+        return NULL;
+    }
+    struct peer_flow *flow = UPPER_OBJECT(node, struct peer_flow, src_tree_node);
+    
+    ASSERT(flow->dest_client->id == dest_id)
+    ASSERT(flow->dest_client->initstatus == INITSTATUS_COMPLETE)
+    ASSERT(!flow->dest_client->dying)
+    
+    return flow;
+}
diff --git a/external/badvpn_dns/server/server.h b/external/badvpn_dns/server/server.h
new file mode 100644
index 0000000..66103a9
--- /dev/null
+++ b/external/badvpn_dns/server/server.h
@@ -0,0 +1,186 @@
+/**
+ * @file server.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <protocol/scproto.h>
+#include <structure/LinkedList1.h>
+#include <structure/BAVL.h>
+#include <flow/PacketProtoDecoder.h>
+#include <flow/PacketStreamSender.h>
+#include <flow/PacketPassPriorityQueue.h>
+#include <flow/PacketPassFairQueue.h>
+#include <flow/PacketProtoFlow.h>
+#include <system/BReactor.h>
+#include <system/BConnection.h>
+#include <nspr_support/BSSLConnection.h>
+
+// name of the program
+#define PROGRAM_NAME "server"
+
+// maxiumum number of connected clients. Must be <=2^16.
+#define DEFAULT_MAX_CLIENTS 30
+// client output control flow buffer size in packets
+// it must hold: initdata, newclient's, endclient's (if other peers die when informing them)
+// make it big enough to hold the initial packet burst (initdata, newclient's),
+#define CLIENT_CONTROL_BUFFER_MIN_PACKETS (1 + 2*(MAX_CLIENTS - 1))
+// size of client-to-client buffers in packets
+#define CLIENT_PEER_FLOW_BUFFER_MIN_PACKETS 10
+// after how long of not hearing anything from the client we disconnect it
+#define CLIENT_NO_DATA_TIME_LIMIT 30000
+// SO_SNDBFUF socket option for clients
+#define CLIENT_DEFAULT_SOCKET_SNDBUF 16384
+// reset time when a buffer runs out or when we get the resetpeer message
+#define CLIENT_RESET_TIME 30000
+
+// maxiumum listen addresses
+#define MAX_LISTEN_ADDRS 16
+
+//#define SIMULATE_OUT_OF_CONTROL_BUFFER 20
+//#define SIMULATE_OUT_OF_FLOW_BUFFER 100
+
+
+// performing SSL handshake
+#define INITSTATUS_HANDSHAKE 1
+// waiting for clienthello
+#define INITSTATUS_WAITHELLO 2
+// initialisation was complete
+#define INITSTATUS_COMPLETE 3
+
+#define INITSTATUS_HASLINK(status) ((status) == INITSTATUS_WAITHELLO || (status) == INITSTATUS_COMPLETE)
+
+struct client_data;
+struct peer_know;
+
+struct peer_flow {
+    // source client
+    struct client_data *src_client;
+    // destination client
+    struct client_data *dest_client;
+    peerid_t dest_client_id;
+    // node in source client hash table (by destination), only when src_client != NULL
+    BAVLNode src_tree_node;
+    // node in source client list, only when src_client != NULL
+    LinkedList1Node src_list_node;
+    // node in destination client list
+    LinkedList1Node dest_list_node;
+    // output chain
+    int have_io;
+    PacketPassFairQueueFlow qflow;
+    PacketProtoFlow oflow;
+    BufferWriter *input;
+    int packet_len;
+    uint8_t *packet;
+    // reset timer
+    BTimer reset_timer;
+    // opposite flow
+    struct peer_flow *opposite;
+    // pair data
+    struct peer_know *know;
+    int accepted;
+    int resetting;
+};
+
+struct peer_know {
+    struct client_data *from;
+    struct client_data *to;
+    int relay_server;
+    int relay_client;
+    LinkedList1Node from_node;
+    LinkedList1Node to_node;
+    BPending inform_job;
+    BPending uninform_job;
+};
+
+struct client_data {
+    // socket
+    BConnection con;
+    BAddr addr;
+    
+    // SSL connection, if using SSL
+    PRFileDesc bottom_prfd;
+    PRFileDesc *ssl_prfd;
+    BSSLConnection sslcon;
+    
+    // initialization state
+    int initstatus;
+    
+    // client data if using SSL
+    uint8_t cert[SCID_NEWCLIENT_MAX_CERT_LEN];
+    int cert_len;
+    uint8_t cert_old[SCID_NEWCLIENT_MAX_CERT_LEN];
+    int cert_old_len;
+    char *common_name;
+    
+    // client version
+    int version;
+    
+    // no data timer
+    BTimer disconnect_timer;
+    
+    // client ID
+    peerid_t id;
+    
+    // node in clients linked list
+    LinkedList1Node list_node;
+    // node in clients tree (by ID)
+    BAVLNode tree_node;
+    
+    // knowledge lists
+    LinkedList1 know_out_list;
+    LinkedList1 know_in_list;
+    
+    // flows from us
+    LinkedList1 peer_out_flows_list;
+    BAVL peer_out_flows_tree;
+    
+    // whether it's being removed
+    int dying;
+    BPending dying_job;
+    
+    // input
+    PacketProtoDecoder input_decoder;
+    PacketPassInterface input_interface;
+    
+    // output common
+    PacketStreamSender output_sender;
+    PacketPassPriorityQueue output_priorityqueue;
+    
+    // output control flow
+    PacketPassPriorityQueueFlow output_control_qflow;
+    PacketProtoFlow output_control_oflow;
+    BufferWriter *output_control_input;
+    int output_control_packet_len;
+    uint8_t *output_control_packet;
+    
+    // output peers flow
+    PacketPassPriorityQueueFlow output_peers_qflow;
+    PacketPassFairQueue output_peers_fairqueue;
+    LinkedList1 output_peers_flows;
+};
diff --git a/external/badvpn_dns/server_connection/CMakeLists.txt b/external/badvpn_dns/server_connection/CMakeLists.txt
new file mode 100644
index 0000000..4ae12ad
--- /dev/null
+++ b/external/badvpn_dns/server_connection/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(SERVERCONNECTION_SOURCES
+    ServerConnection.c
+    SCKeepaliveSource.c
+)
+badvpn_add_library(server_conection "system;flow;flowextra;nspr_support" "${NSPR_LIBRARIES};${NSS_LIBRARIES}" "${SERVERCONNECTION_SOURCES}")
diff --git a/external/badvpn_dns/server_connection/SCKeepaliveSource.c b/external/badvpn_dns/server_connection/SCKeepaliveSource.c
new file mode 100644
index 0000000..7c6469a
--- /dev/null
+++ b/external/badvpn_dns/server_connection/SCKeepaliveSource.c
@@ -0,0 +1,69 @@
+/**
+ * @file SCKeepaliveSource.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/scproto.h>
+#include <misc/byteorder.h>
+
+#include "SCKeepaliveSource.h"
+
+static void output_handler_recv (SCKeepaliveSource *o, uint8_t *data)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    struct sc_header header;
+    header.type = htol8(SCID_KEEPALIVE);
+    memcpy(data, &header, sizeof(header));
+    
+    PacketRecvInterface_Done(&o->output, sizeof(struct sc_header));
+}
+
+void SCKeepaliveSource_Init (SCKeepaliveSource *o, BPendingGroup *pg)
+{
+    // init output
+    PacketRecvInterface_Init(&o->output, sizeof(struct sc_header), (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void SCKeepaliveSource_Free (SCKeepaliveSource *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // free output
+    PacketRecvInterface_Free(&o->output);
+}
+
+PacketRecvInterface * SCKeepaliveSource_GetOutput (SCKeepaliveSource *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
diff --git a/external/badvpn_dns/server_connection/SCKeepaliveSource.h b/external/badvpn_dns/server_connection/SCKeepaliveSource.h
new file mode 100644
index 0000000..1c0951e
--- /dev/null
+++ b/external/badvpn_dns/server_connection/SCKeepaliveSource.h
@@ -0,0 +1,72 @@
+/**
+ * @file SCKeepaliveSource.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 SCProto keepalive packets.
+ */
+
+#ifndef BADVPN_SCKEEPALIVESOURCE_H
+#define BADVPN_SCKEEPALIVESOURCE_H
+
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * A {@link PacketRecvInterface} source which provides SCProto keepalive packets.
+ */
+typedef struct {
+    DebugObject d_obj;
+    PacketRecvInterface output;
+} SCKeepaliveSource;
+
+/**
+ * Initializes the object.
+ *
+ * @param o the object
+ * @param pg pending group
+ */
+void SCKeepaliveSource_Init (SCKeepaliveSource *o, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void SCKeepaliveSource_Free (SCKeepaliveSource *o);
+
+/**
+ * Returns the output interface.
+ * The MTU of the output interface will be sizeof(struct sc_header).
+ *
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * SCKeepaliveSource_GetOutput (SCKeepaliveSource *o);
+
+#endif
diff --git a/external/badvpn_dns/server_connection/ServerConnection.c b/external/badvpn_dns/server_connection/ServerConnection.c
new file mode 100644
index 0000000..f07e224
--- /dev/null
+++ b/external/badvpn_dns/server_connection/ServerConnection.c
@@ -0,0 +1,669 @@
+/**
+ * @file ServerConnection.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stddef.h>
+
+#include <misc/debug.h>
+#include <misc/strdup.h>
+#include <base/BLog.h>
+
+#include <server_connection/ServerConnection.h>
+
+#include <generated/blog_channel_ServerConnection.h>
+
+#define STATE_CONNECTING 1
+#define STATE_WAITINIT 2
+#define STATE_COMPLETE 3
+
+static void report_error (ServerConnection *o);
+static void connector_handler (ServerConnection *o, int is_error);
+static void pending_handler (ServerConnection *o);
+static SECStatus client_auth_data_callback (ServerConnection *o, PRFileDesc *fd, CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static void connection_handler (ServerConnection *o, int event);
+static void sslcon_handler (ServerConnection *o, int event);
+static void decoder_handler_error (ServerConnection *o);
+static void input_handler_send (ServerConnection *o, uint8_t *data, int data_len);
+static void packet_hello (ServerConnection *o, uint8_t *data, int data_len);
+static void packet_newclient (ServerConnection *o, uint8_t *data, int data_len);
+static void packet_endclient (ServerConnection *o, uint8_t *data, int data_len);
+static void packet_inmsg (ServerConnection *o, uint8_t *data, int data_len);
+static int start_packet (ServerConnection *o, void **data, int len);
+static void end_packet (ServerConnection *o, uint8_t type);
+static void newclient_job_handler (ServerConnection *o);
+
+void report_error (ServerConnection *o)
+{
+    DEBUGERROR(&o->d_err, o->handler_error(o->user))
+}
+
+void connector_handler (ServerConnection *o, int is_error)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state == STATE_CONNECTING)
+    ASSERT(!o->buffers_released)
+    
+    // check connection attempt result
+    if (is_error) {
+        BLog(BLOG_ERROR, "connection failed");
+        goto fail0;
+    }
+    
+    BLog(BLOG_NOTICE, "connected");
+    
+    // init connection
+    if (!BConnection_Init(&o->con, BConnection_source_connector(&o->connector), o->reactor, o, (BConnection_handler)connection_handler)) {
+        BLog(BLOG_ERROR, "BConnection_Init failed");
+        goto fail0;
+    }
+    
+    // init connection interfaces
+    BConnection_SendAsync_Init(&o->con);
+    BConnection_RecvAsync_Init(&o->con);
+    
+    StreamPassInterface *send_iface = BConnection_SendAsync_GetIf(&o->con);
+    StreamRecvInterface *recv_iface = BConnection_RecvAsync_GetIf(&o->con);
+    
+    if (o->have_ssl) {
+        // create bottom NSPR file descriptor
+        if (!BSSLConnection_MakeBackend(&o->bottom_prfd, send_iface, recv_iface, o->twd, o->ssl_flags)) {
+            BLog(BLOG_ERROR, "BSSLConnection_MakeBackend failed");
+            goto fail0a;
+        }
+        
+        // create SSL file descriptor from the bottom NSPR file descriptor
+        if (!(o->ssl_prfd = SSL_ImportFD(NULL, &o->bottom_prfd))) {
+            BLog(BLOG_ERROR, "SSL_ImportFD failed");
+            ASSERT_FORCE(PR_Close(&o->bottom_prfd) == PR_SUCCESS)
+            goto fail0a;
+        }
+        
+        // set client mode
+        if (SSL_ResetHandshake(o->ssl_prfd, PR_FALSE) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_ResetHandshake failed");
+            goto fail1;
+        }
+        
+        // set server name
+        if (SSL_SetURL(o->ssl_prfd, o->server_name) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_SetURL failed");
+            goto fail1;
+        }
+        
+        // set client certificate callback
+        if (SSL_GetClientAuthDataHook(o->ssl_prfd, (SSLGetClientAuthData)client_auth_data_callback, o) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_GetClientAuthDataHook failed");
+            goto fail1;
+        }
+        
+        // init BSSLConnection
+        BSSLConnection_Init(&o->sslcon, o->ssl_prfd, 0, BReactor_PendingGroup(o->reactor), o, (BSSLConnection_handler)sslcon_handler);
+        
+        send_iface = BSSLConnection_GetSendIf(&o->sslcon);
+        recv_iface = BSSLConnection_GetRecvIf(&o->sslcon);
+    }
+    
+    // init input chain
+    PacketPassInterface_Init(&o->input_interface, SC_MAX_ENC, (PacketPassInterface_handler_send)input_handler_send, o, BReactor_PendingGroup(o->reactor));
+    if (!PacketProtoDecoder_Init(&o->input_decoder, recv_iface, &o->input_interface, BReactor_PendingGroup(o->reactor), o, (PacketProtoDecoder_handler_error)decoder_handler_error)) {
+        BLog(BLOG_ERROR, "PacketProtoDecoder_Init failed");
+        goto fail2;
+    }
+    
+    // set job to send hello
+    // this needs to be in here because hello sending must be done after sending started (so we can write into the send buffer),
+    // but before receiving started (so we don't get into conflict with the user sending packets)
+    BPending_Init(&o->start_job, BReactor_PendingGroup(o->reactor), (BPending_handler)pending_handler, o);
+    BPending_Set(&o->start_job);
+    
+    // init keepalive output branch
+    SCKeepaliveSource_Init(&o->output_ka_zero, BReactor_PendingGroup(o->reactor));
+    PacketProtoEncoder_Init(&o->output_ka_encoder, SCKeepaliveSource_GetOutput(&o->output_ka_zero), BReactor_PendingGroup(o->reactor));
+    
+    // init output common
+    
+    // init sender
+    PacketStreamSender_Init(&o->output_sender, send_iface, PACKETPROTO_ENCLEN(SC_MAX_ENC), BReactor_PendingGroup(o->reactor));
+    
+    // init keepalives
+    if (!KeepaliveIO_Init(&o->output_keepaliveio, o->reactor, PacketStreamSender_GetInput(&o->output_sender), PacketProtoEncoder_GetOutput(&o->output_ka_encoder), o->keepalive_interval)) {
+        BLog(BLOG_ERROR, "KeepaliveIO_Init failed");
+        goto fail3;
+    }
+    
+    // init queue
+    PacketPassPriorityQueue_Init(&o->output_queue, KeepaliveIO_GetInput(&o->output_keepaliveio), BReactor_PendingGroup(o->reactor), 0);
+    
+    // init output local flow
+    
+    // init queue flow
+    PacketPassPriorityQueueFlow_Init(&o->output_local_qflow, &o->output_queue, 0);
+    
+    // init PacketProtoFlow
+    if (!PacketProtoFlow_Init(&o->output_local_oflow, SC_MAX_ENC, o->buffer_size, PacketPassPriorityQueueFlow_GetInput(&o->output_local_qflow), BReactor_PendingGroup(o->reactor))) {
+        BLog(BLOG_ERROR, "PacketProtoFlow_Init failed");
+        goto fail4;
+    }
+    o->output_local_if = PacketProtoFlow_GetInput(&o->output_local_oflow);
+    
+    // have no output packet
+    o->output_local_packet_len = -1;
+    
+    // init output user flow
+    PacketPassPriorityQueueFlow_Init(&o->output_user_qflow, &o->output_queue, 1);
+    
+    // update state
+    o->state = STATE_WAITINIT;
+    
+    return;
+    
+fail4:
+    PacketPassPriorityQueueFlow_Free(&o->output_local_qflow);
+    PacketPassPriorityQueue_Free(&o->output_queue);
+    KeepaliveIO_Free(&o->output_keepaliveio);
+fail3:
+    PacketStreamSender_Free(&o->output_sender);
+    PacketProtoEncoder_Free(&o->output_ka_encoder);
+    SCKeepaliveSource_Free(&o->output_ka_zero);
+    BPending_Free(&o->start_job);
+    PacketProtoDecoder_Free(&o->input_decoder);
+fail2:
+    PacketPassInterface_Free(&o->input_interface);
+    if (o->have_ssl) {
+        BSSLConnection_Free(&o->sslcon);
+fail1:
+        ASSERT_FORCE(PR_Close(o->ssl_prfd) == PR_SUCCESS)
+    }
+fail0a:
+    BConnection_RecvAsync_Free(&o->con);
+    BConnection_SendAsync_Free(&o->con);
+    BConnection_Free(&o->con);
+fail0:
+    // report error
+    report_error(o);
+}
+
+void pending_handler (ServerConnection *o)
+{
+    ASSERT(o->state == STATE_WAITINIT)
+    ASSERT(!o->buffers_released)
+    DebugObject_Access(&o->d_obj);
+    
+    // send hello
+    struct sc_client_hello omsg;
+    void *packet;
+    if (!start_packet(o, &packet, sizeof(omsg))) {
+        BLog(BLOG_ERROR, "no buffer for hello");
+        report_error(o);
+        return;
+    }
+    omsg.version = htol16(SC_VERSION);
+    memcpy(packet, &omsg, sizeof(omsg));
+    end_packet(o, SCID_CLIENTHELLO);
+}
+
+SECStatus client_auth_data_callback (ServerConnection *o, PRFileDesc *fd, CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+    ASSERT(o->have_ssl)
+    DebugObject_Access(&o->d_obj);
+    
+    CERTCertificate *newcert;
+    if (!(newcert = CERT_DupCertificate(o->client_cert))) {
+        return SECFailure;
+    }
+    
+    SECKEYPrivateKey *newkey;
+    if (!(newkey = SECKEY_CopyPrivateKey(o->client_key))) {
+        CERT_DestroyCertificate(newcert);
+        return SECFailure;
+    }
+    
+    *pRetCert = newcert;
+    *pRetKey = newkey;
+    return SECSuccess;
+}
+
+void connection_handler (ServerConnection *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state >= STATE_WAITINIT)
+    ASSERT(!o->buffers_released)
+    
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        BLog(BLOG_INFO, "connection closed");
+    } else {
+        BLog(BLOG_INFO, "connection error");
+    }
+    
+    report_error(o);
+    return;
+}
+
+void sslcon_handler (ServerConnection *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_ssl)
+    ASSERT(o->state >= STATE_WAITINIT)
+    ASSERT(!o->buffers_released)
+    ASSERT(event == BSSLCONNECTION_EVENT_ERROR)
+    
+    BLog(BLOG_ERROR, "SSL error");
+    
+    report_error(o);
+    return;
+}
+
+void decoder_handler_error (ServerConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state >= STATE_WAITINIT)
+    ASSERT(!o->buffers_released)
+    
+    BLog(BLOG_ERROR, "decoder error");
+    
+    report_error(o);
+    return;
+}
+
+void input_handler_send (ServerConnection *o, uint8_t *data, int data_len)
+{
+    ASSERT(o->state >= STATE_WAITINIT)
+    ASSERT(!o->buffers_released)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= SC_MAX_ENC)
+    DebugObject_Access(&o->d_obj);
+    
+    // accept packet
+    PacketPassInterface_Done(&o->input_interface);
+    
+    // parse header
+    if (data_len < sizeof(struct sc_header)) {
+        BLog(BLOG_ERROR, "packet too short (no sc header)");
+        report_error(o);
+        return;
+    }
+    struct sc_header header;
+    memcpy(&header, data, sizeof(header));
+    data += sizeof(header);
+    data_len -= sizeof(header);
+    uint8_t type = ltoh8(header.type);
+    
+    // call appropriate handler based on packet type
+    switch (type) {
+        case SCID_SERVERHELLO:
+            packet_hello(o, data, data_len);
+            return;
+        case SCID_NEWCLIENT:
+            packet_newclient(o, data, data_len);
+            return;
+        case SCID_ENDCLIENT:
+            packet_endclient(o, data, data_len);
+            return;
+        case SCID_INMSG:
+            packet_inmsg(o, data, data_len);
+            return;
+        default:
+            BLog(BLOG_ERROR, "unknown packet type %d", (int)type);
+            report_error(o);
+            return;
+    }
+}
+
+void packet_hello (ServerConnection *o, uint8_t *data, int data_len)
+{
+    if (o->state != STATE_WAITINIT) {
+        BLog(BLOG_ERROR, "hello: not expected");
+        report_error(o);
+        return;
+    }
+    
+    if (data_len != sizeof(struct sc_server_hello)) {
+        BLog(BLOG_ERROR, "hello: invalid length");
+        report_error(o);
+        return;
+    }
+    struct sc_server_hello msg;
+    memcpy(&msg, data, sizeof(msg));
+    peerid_t id = ltoh16(msg.id);
+    
+    // change state
+    o->state = STATE_COMPLETE;
+    
+    // report
+    o->handler_ready(o->user, id, msg.clientAddr);
+    return;
+}
+
+void packet_newclient (ServerConnection *o, uint8_t *data, int data_len)
+{
+    if (o->state != STATE_COMPLETE) {
+        BLog(BLOG_ERROR, "newclient: not expected");
+        report_error(o);
+        return;
+    }
+    
+    if (data_len < sizeof(struct sc_server_newclient) || data_len > sizeof(struct sc_server_newclient) + SCID_NEWCLIENT_MAX_CERT_LEN) {
+        BLog(BLOG_ERROR, "newclient: invalid length");
+        report_error(o);
+        return;
+    }
+    
+    struct sc_server_newclient msg;
+    memcpy(&msg, data, sizeof(msg));
+    peerid_t id = ltoh16(msg.id);
+    
+    // schedule reporting new client
+    o->newclient_data = data;
+    o->newclient_data_len = data_len;
+    BPending_Set(&o->newclient_job);
+    
+    // send acceptpeer
+    struct sc_client_acceptpeer omsg;
+    void *packet;
+    if (!start_packet(o, &packet, sizeof(omsg))) {
+        BLog(BLOG_ERROR, "newclient: out of buffer for acceptpeer");
+        report_error(o);
+        return;
+    }
+    omsg.clientid = htol16(id);
+    memcpy(packet, &omsg, sizeof(omsg));
+    end_packet(o, SCID_ACCEPTPEER);
+}
+
+void packet_endclient (ServerConnection *o, uint8_t *data, int data_len)
+{
+    if (o->state != STATE_COMPLETE) {
+        BLog(BLOG_ERROR, "endclient: not expected");
+        report_error(o);
+        return;
+    }
+    
+    if (data_len != sizeof(struct sc_server_endclient)) {
+        BLog(BLOG_ERROR, "endclient: invalid length");
+        report_error(o);
+        return;
+    }
+    
+    struct sc_server_endclient msg;
+    memcpy(&msg, data, sizeof(msg));
+    peerid_t id = ltoh16(msg.id);
+    
+    // report
+    o->handler_endclient(o->user, id);
+    return;
+}
+
+void packet_inmsg (ServerConnection *o, uint8_t *data, int data_len)
+{
+    if (o->state != STATE_COMPLETE) {
+        BLog(BLOG_ERROR, "inmsg: not expected");
+        report_error(o);
+        return;
+    }
+    
+    if (data_len < sizeof(struct sc_server_inmsg)) {
+        BLog(BLOG_ERROR, "inmsg: missing header");
+        report_error(o);
+        return;
+    }
+    
+    if (data_len > sizeof(struct sc_server_inmsg) + SC_MAX_MSGLEN) {
+        BLog(BLOG_ERROR, "inmsg: too long");
+        report_error(o);
+        return;
+    }
+    
+    struct sc_server_inmsg msg;
+    memcpy(&msg, data, sizeof(msg));
+    peerid_t peer_id = ltoh16(msg.clientid);
+    uint8_t *payload = data + sizeof(struct sc_server_inmsg);
+    int payload_len = data_len - sizeof(struct sc_server_inmsg);
+    
+    // report
+    o->handler_message(o->user, peer_id, payload, payload_len);
+    return;
+}
+
+int start_packet (ServerConnection *o, void **data, int len)
+{
+    ASSERT(o->state >= STATE_WAITINIT)
+    ASSERT(o->output_local_packet_len == -1)
+    ASSERT(len >= 0)
+    ASSERT(len <= SC_MAX_PAYLOAD)
+    ASSERT(data || len == 0)
+    
+    // obtain memory location
+    if (!BufferWriter_StartPacket(o->output_local_if, &o->output_local_packet)) {
+        BLog(BLOG_ERROR, "out of buffer");
+        return 0;
+    }
+    
+    o->output_local_packet_len = len;
+    
+    if (data) {
+        *data = o->output_local_packet + sizeof(struct sc_header);
+    }
+    
+    return 1;
+}
+
+void end_packet (ServerConnection *o, uint8_t type)
+{
+    ASSERT(o->state >= STATE_WAITINIT)
+    ASSERT(o->output_local_packet_len >= 0)
+    ASSERT(o->output_local_packet_len <= SC_MAX_PAYLOAD)
+    
+    // write header
+    struct sc_header header;
+    header.type = htol8(type);
+    memcpy(o->output_local_packet, &header, sizeof(header));
+    
+    // finish writing packet
+    BufferWriter_EndPacket(o->output_local_if, sizeof(struct sc_header) + o->output_local_packet_len);
+    
+    o->output_local_packet_len = -1;
+}
+
+int ServerConnection_Init (
+    ServerConnection *o,
+    BReactor *reactor,
+    BThreadWorkDispatcher *twd,
+    BAddr addr,
+    int keepalive_interval,
+    int buffer_size,
+    int have_ssl,
+    int ssl_flags,
+    CERTCertificate *client_cert,
+    SECKEYPrivateKey *client_key,
+    const char *server_name,
+    void *user,
+    ServerConnection_handler_error handler_error,
+    ServerConnection_handler_ready handler_ready,
+    ServerConnection_handler_newclient handler_newclient,
+    ServerConnection_handler_endclient handler_endclient,
+    ServerConnection_handler_message handler_message
+)
+{
+    ASSERT(keepalive_interval > 0)
+    ASSERT(buffer_size > 0)
+    ASSERT(have_ssl == 0 || have_ssl == 1)
+    ASSERT(!have_ssl || server_name)
+    
+    // init arguments
+    o->reactor = reactor;
+    o->twd = twd;
+    o->keepalive_interval = keepalive_interval;
+    o->buffer_size = buffer_size;
+    o->have_ssl = have_ssl;
+    if (have_ssl) {
+        o->ssl_flags = ssl_flags;
+        o->client_cert = client_cert;
+        o->client_key = client_key;
+    }
+    o->user = user;
+    o->handler_error = handler_error;
+    o->handler_ready = handler_ready;
+    o->handler_newclient = handler_newclient;
+    o->handler_endclient = handler_endclient;
+    o->handler_message = handler_message;
+    
+    o->server_name = NULL;
+    if (have_ssl && !(o->server_name = b_strdup(server_name))) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    if (!BConnection_AddressSupported(addr)) {
+        BLog(BLOG_ERROR, "BConnection_AddressSupported failed");
+        goto fail1;
+    }
+    
+    // init connector
+    if (!BConnector_Init(&o->connector, addr, o->reactor, o, (BConnector_handler)connector_handler)) {
+        BLog(BLOG_ERROR, "BConnector_Init failed");
+        goto fail1;
+    }
+    
+    // init newclient job
+    BPending_Init(&o->newclient_job, BReactor_PendingGroup(o->reactor), (BPending_handler)newclient_job_handler, o);
+    
+    // set state
+    o->state = STATE_CONNECTING;
+    o->buffers_released = 0;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    free(o->server_name);
+fail0:
+    return 0;
+}
+
+void ServerConnection_Free (ServerConnection *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    
+    if (o->state > STATE_CONNECTING) {
+        // allow freeing queue flows
+        PacketPassPriorityQueue_PrepareFree(&o->output_queue);
+        
+        // stop using any buffers before they get freed
+        if (o->have_ssl && !o->buffers_released) {
+            BSSLConnection_ReleaseBuffers(&o->sslcon);
+        }
+        
+        // free output user flow
+        PacketPassPriorityQueueFlow_Free(&o->output_user_qflow);
+        
+        // free output local flow
+        PacketProtoFlow_Free(&o->output_local_oflow);
+        PacketPassPriorityQueueFlow_Free(&o->output_local_qflow);
+        
+        // free output common
+        PacketPassPriorityQueue_Free(&o->output_queue);
+        KeepaliveIO_Free(&o->output_keepaliveio);
+        PacketStreamSender_Free(&o->output_sender);
+        
+        // free output keep-alive branch
+        PacketProtoEncoder_Free(&o->output_ka_encoder);
+        SCKeepaliveSource_Free(&o->output_ka_zero);
+        
+        // free job
+        BPending_Free(&o->start_job);
+        
+        // free input chain
+        PacketProtoDecoder_Free(&o->input_decoder);
+        PacketPassInterface_Free(&o->input_interface);
+        
+        // free SSL
+        if (o->have_ssl) {
+            BSSLConnection_Free(&o->sslcon);
+            ASSERT_FORCE(PR_Close(o->ssl_prfd) == PR_SUCCESS)
+        }
+        
+        // free connection interfaces
+        BConnection_RecvAsync_Free(&o->con);
+        BConnection_SendAsync_Free(&o->con);
+        
+        // free connection
+        BConnection_Free(&o->con);
+    }
+    
+    // free newclient job
+    BPending_Free(&o->newclient_job);
+    
+    // free connector
+    BConnector_Free(&o->connector);
+    
+    // free server name
+    free(o->server_name);
+}
+
+void ServerConnection_ReleaseBuffers (ServerConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->buffers_released)
+    
+    if (o->state > STATE_CONNECTING && o->have_ssl) {
+        BSSLConnection_ReleaseBuffers(&o->sslcon);
+    }
+    
+    o->buffers_released = 1;
+}
+
+PacketPassInterface * ServerConnection_GetSendInterface (ServerConnection *o)
+{
+    ASSERT(o->state == STATE_COMPLETE)
+    DebugError_AssertNoError(&o->d_err);
+    DebugObject_Access(&o->d_obj);
+    
+    return PacketPassPriorityQueueFlow_GetInput(&o->output_user_qflow);
+}
+
+void newclient_job_handler (ServerConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state == STATE_COMPLETE)
+    
+    struct sc_server_newclient msg;
+    memcpy(&msg, o->newclient_data, sizeof(msg));
+    peerid_t id = ltoh16(msg.id);
+    int flags = ltoh16(msg.flags);
+    
+    uint8_t *cert_data = o->newclient_data + sizeof(msg);
+    int cert_len = o->newclient_data_len - sizeof(msg);
+    
+    // report new client
+    o->handler_newclient(o->user, id, flags, cert_data, cert_len);
+    return;
+}
diff --git a/external/badvpn_dns/server_connection/ServerConnection.h b/external/badvpn_dns/server_connection/ServerConnection.h
new file mode 100644
index 0000000..1245c84
--- /dev/null
+++ b/external/badvpn_dns/server_connection/ServerConnection.h
@@ -0,0 +1,289 @@
+/**
+ * @file ServerConnection.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 communicate with a VPN chat server.
+ */
+
+#ifndef BADVPN_SERVERCONNECTION_SERVERCONNECTION_H
+#define BADVPN_SERVERCONNECTION_SERVERCONNECTION_H
+
+#include <stdint.h>
+
+#include <prinit.h>
+#include <prio.h>
+#include <prerror.h>
+#include <prtypes.h>
+#include <nss.h>
+#include <ssl.h>
+#include <pk11func.h>
+#include <cert.h>
+#include <keyhi.h>
+
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <protocol/scproto.h>
+#include <protocol/msgproto.h>
+#include <base/DebugObject.h>
+#include <system/BConnection.h>
+#include <flow/PacketProtoEncoder.h>
+#include <flow/PacketStreamSender.h>
+#include <flow/PacketProtoDecoder.h>
+#include <flow/PacketPassPriorityQueue.h>
+#include <flow/PacketProtoFlow.h>
+#include <flowextra/KeepaliveIO.h>
+#include <nspr_support/BSSLConnection.h>
+#include <server_connection/SCKeepaliveSource.h>
+
+/**
+ * Handler function invoked when an error occurs.
+ * The object must be freed from withing this function.
+ *
+ * @param user value passed to {@link ServerConnection_Init}
+ */
+typedef void (*ServerConnection_handler_error) (void *user);
+
+/**
+ * Handler function invoked when the server becomes ready, i.e.
+ * the hello packet has been received.
+ * The object was in not ready state before.
+ * The object enters ready state before the handler is invoked.
+ *
+ * @param user value passed to {@link ServerConnection_Init}
+ * @param my_id our ID as reported by the server
+ * @param ext_ip the clientAddr field in the server's hello packet
+ */
+typedef void (*ServerConnection_handler_ready) (void *user, peerid_t my_id, uint32_t ext_ip);
+
+/**
+ * Handler function invoked when a newclient packet is received.
+ * The object was in ready state.
+ *
+ * @param user value passed to {@link ServerConnection_Init}
+ * @param peer_id ID of the peer
+ * @param flags flags field from the newclient message
+ * @param cert peer's certificate (if any)
+ * @param cert_len certificate length. Will be >=0.
+ */
+typedef void (*ServerConnection_handler_newclient) (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len);
+
+/**
+ * Handler function invoked when an enclient packet is received.
+ * The object was in ready state.
+ *
+ * @param user value passed to {@link ServerConnection_Init}
+ * @param peer_id ID of the peer
+ */
+typedef void (*ServerConnection_handler_endclient) (void *user, peerid_t peer_id);
+
+/**
+ * Handler function invoked when an inmsg packet is received.
+ * The object was in ready state.
+ *
+ * @param user value passed to {@link ServerConnection_Init}
+ * @param peer_id ID of the peer from which the message came
+ * @param data message payload
+ * @param data_len message length. Will be >=0.
+ */
+typedef void (*ServerConnection_handler_message) (void *user, peerid_t peer_id, uint8_t *data, int data_len);
+
+/**
+ * Object used to communicate with a VPN chat server.
+ */
+typedef struct {
+    // global resources
+    BReactor *reactor;
+    BThreadWorkDispatcher *twd;
+    
+    // keepalive interval
+    int keepalive_interval;
+    
+    // send buffer size
+    int buffer_size;
+    
+    // whether we use SSL
+    int have_ssl;
+    
+    // ssl flags
+    int ssl_flags;
+    
+    // client certificate if using SSL
+    CERTCertificate *client_cert;
+
+    // client private key if using SSL
+    SECKEYPrivateKey *client_key;
+    
+    // server name if using SSL
+    char *server_name;
+    
+    // handlers
+    void *user;
+    ServerConnection_handler_error handler_error;
+    ServerConnection_handler_ready handler_ready;
+    ServerConnection_handler_newclient handler_newclient;
+    ServerConnection_handler_endclient handler_endclient;
+    ServerConnection_handler_message handler_message;
+    
+    // socket
+    BConnector connector;
+    BConnection con;
+    
+    // job to report new client after sending acceptpeer
+    BPending newclient_job;
+    uint8_t *newclient_data;
+    int newclient_data_len;
+    
+    // state
+    int state;
+    int buffers_released;
+    
+    // whether an error is being reported
+    int error;
+    
+    // defined when state > SERVERCONNECTION_STATE_CONNECTING
+    
+    // SSL file descriptor, defined only if using SSL
+    PRFileDesc bottom_prfd;
+    PRFileDesc *ssl_prfd;
+    BSSLConnection sslcon;
+    
+    // input
+    PacketProtoDecoder input_decoder;
+    PacketPassInterface input_interface;
+    
+    // keepalive output branch
+    SCKeepaliveSource output_ka_zero;
+    PacketProtoEncoder output_ka_encoder;
+    
+    // output common
+    PacketPassPriorityQueue output_queue;
+    KeepaliveIO output_keepaliveio;
+    PacketStreamSender output_sender;
+    
+    // output local flow
+    int output_local_packet_len;
+    uint8_t *output_local_packet;
+    BufferWriter *output_local_if;
+    PacketProtoFlow output_local_oflow;
+    PacketPassPriorityQueueFlow output_local_qflow;
+    
+    // output user flow
+    PacketPassPriorityQueueFlow output_user_qflow;
+    
+    // job to start client I/O
+    BPending start_job;
+    
+    DebugError d_err;
+    DebugObject d_obj;
+} ServerConnection;
+
+/**
+ * Initializes the object.
+ * The object is initialized in not ready 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 o the object
+ * @param reactor {@link BReactor} we live in
+ * @param twd thread work dispatcher. May be NULL if ssl_flags does not request performing SSL
+ *            operations in threads.
+ * @param addr address to connect to
+ * @param keepalive_interval keep-alive sending interval. Must be >0.
+ * @param buffer_size minimum size of send buffer in number of packets. Must be >0.
+ * @param have_ssl whether to use SSL for connecting to the server. 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 client_cert if using SSL, client certificate to use. Must remain valid as
+ *                    long as this object is alive.
+ * @param client_key if using SSL, prvate ket to use. Must remain valid as
+ *                   long as this object is alive.
+ * @param server_name if using SSL, the name of the server. The string is copied.
+ * @param user value passed to callback functions
+ * @param handler_error error handler. The object must be freed from within the error
+ *                      handler before doing anything else with this object.
+ * @param handler_ready handler when the server becomes ready, i.e. the hello message has
+ *                      been received.
+ * @param handler_newclient handler when a newclient message has been received
+ * @param handler_endclient handler when an endclient message has been received
+ * @param handler_message handler when a peer message has been reveived
+ * @return 1 on success, 0 on failure
+ */
+int ServerConnection_Init (
+    ServerConnection *o,
+    BReactor *reactor,
+    BThreadWorkDispatcher *twd,
+    BAddr addr,
+    int keepalive_interval,
+    int buffer_size,
+    int have_ssl,
+    int ssl_flags,
+    CERTCertificate *client_cert,
+    SECKEYPrivateKey *client_key,
+    const char *server_name,
+    void *user,
+    ServerConnection_handler_error handler_error,
+    ServerConnection_handler_ready handler_ready,
+    ServerConnection_handler_newclient handler_newclient,
+    ServerConnection_handler_endclient handler_endclient,
+    ServerConnection_handler_message handler_message
+) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * {@link ServerConnection_ReleaseBuffers} must have been called if the
+ * send interface obtained from {@link ServerConnection_GetSendInterface}
+ * was used.
+ *
+ * @param o the object
+ */
+void ServerConnection_Free (ServerConnection *o);
+
+/**
+ * Stops using any buffers passed to the send interface obtained from
+ * {@link ServerConnection_GetSendInterface}. If the send interface
+ * has been used, this must be called at appropriate time before this
+ * object is freed.
+ */
+void ServerConnection_ReleaseBuffers (ServerConnection *o);
+
+/**
+ * Returns an interface for sending data to the server (just one).
+ * This goes directly into the link (i.e. TCP, possibly via SSL), so packets
+ * need to be manually encoded according to PacketProto.
+ * The interface must not be used after an error was reported.
+ * The object must be in ready state.
+ * Must not be called from the error handler.
+ *
+ * @param o the object
+ * @return the interface
+ */
+PacketPassInterface * ServerConnection_GetSendInterface (ServerConnection *o);
+
+#endif
diff --git a/external/badvpn_dns/socksclient/BSocksClient.c b/external/badvpn_dns/socksclient/BSocksClient.c
new file mode 100644
index 0000000..21415af
--- /dev/null
+++ b/external/badvpn_dns/socksclient/BSocksClient.c
@@ -0,0 +1,608 @@
+/**
+ * @file BSocksClient.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/byteorder.h>
+#include <misc/balloc.h>
+#include <base/BLog.h>
+
+#include <socksclient/BSocksClient.h>
+
+#include <generated/blog_channel_BSocksClient.h>
+
+#define STATE_CONNECTING 1
+#define STATE_SENDING_HELLO 2
+#define STATE_SENT_HELLO 3
+#define STATE_SENDING_PASSWORD 10
+#define STATE_SENT_PASSWORD 11
+#define STATE_SENDING_REQUEST 4
+#define STATE_SENT_REQUEST 5
+#define STATE_RECEIVED_REPLY_HEADER 6
+#define STATE_UP 7
+
+static void report_error (BSocksClient *o, int error);
+static void init_control_io (BSocksClient *o);
+static void free_control_io (BSocksClient *o);
+static void init_up_io (BSocksClient *o);
+static void free_up_io (BSocksClient *o);
+static int reserve_buffer (BSocksClient *o, bsize_t size);
+static void start_receive (BSocksClient *o, uint8_t *dest, int total);
+static void do_receive (BSocksClient *o);
+static void connector_handler (BSocksClient* o, int is_error);
+static void connection_handler (BSocksClient* o, int event);
+static void recv_handler_done (BSocksClient *o, int data_len);
+static void send_handler_done (BSocksClient *o);
+static void auth_finished (BSocksClient *p);
+
+void report_error (BSocksClient *o, int error)
+{
+    DEBUGERROR(&o->d_err, o->handler(o->user, error))
+}
+
+void init_control_io (BSocksClient *o)
+{
+    // init receiving
+    BConnection_RecvAsync_Init(&o->con);
+    o->control.recv_if = BConnection_RecvAsync_GetIf(&o->con);
+    StreamRecvInterface_Receiver_Init(o->control.recv_if, (StreamRecvInterface_handler_done)recv_handler_done, o);
+    
+    // init sending
+    BConnection_SendAsync_Init(&o->con);
+    PacketStreamSender_Init(&o->control.send_sender, BConnection_SendAsync_GetIf(&o->con), INT_MAX, BReactor_PendingGroup(o->reactor));
+    o->control.send_if = PacketStreamSender_GetInput(&o->control.send_sender);
+    PacketPassInterface_Sender_Init(o->control.send_if, (PacketPassInterface_handler_done)send_handler_done, o);
+}
+
+void free_control_io (BSocksClient *o)
+{
+    // free sending
+    PacketStreamSender_Free(&o->control.send_sender);
+    BConnection_SendAsync_Free(&o->con);
+    
+    // free receiving
+    BConnection_RecvAsync_Free(&o->con);
+}
+
+void init_up_io (BSocksClient *o)
+{
+    // init receiving
+    BConnection_RecvAsync_Init(&o->con);
+    
+    // init sending
+    BConnection_SendAsync_Init(&o->con);
+}
+
+void free_up_io (BSocksClient *o)
+{
+    // free sending
+    BConnection_SendAsync_Free(&o->con);
+    
+    // free receiving
+    BConnection_RecvAsync_Free(&o->con);
+}
+
+int reserve_buffer (BSocksClient *o, bsize_t size)
+{
+    if (size.is_overflow) {
+        BLog(BLOG_ERROR, "size overflow");
+        return 0;
+    }
+    
+    char *buffer = (char *)BRealloc(o->buffer, size.value);
+    if (!buffer) {
+        BLog(BLOG_ERROR, "BRealloc failed");
+        return 0;
+    }
+    
+    o->buffer = buffer;
+    
+    return 1;
+}
+
+void start_receive (BSocksClient *o, uint8_t *dest, int total)
+{
+    ASSERT(total > 0)
+    
+    o->control.recv_dest = dest;
+    o->control.recv_len = 0;
+    o->control.recv_total = total;
+    
+    do_receive(o);
+}
+
+void do_receive (BSocksClient *o)
+{
+    ASSERT(o->control.recv_len < o->control.recv_total)
+    
+    StreamRecvInterface_Receiver_Recv(o->control.recv_if, o->control.recv_dest + o->control.recv_len, o->control.recv_total - o->control.recv_len);
+}
+
+void connector_handler (BSocksClient* o, int is_error)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state == STATE_CONNECTING)
+    
+    // check connection result
+    if (is_error) {
+        BLog(BLOG_ERROR, "connection failed");
+        goto fail0;
+    }
+    
+    // init connection
+    if (!BConnection_Init(&o->con, BConnection_source_connector(&o->connector), o->reactor, o, (BConnection_handler)connection_handler)) {
+        BLog(BLOG_ERROR, "BConnection_Init failed");
+        goto fail0;
+    }
+    
+    BLog(BLOG_DEBUG, "connected");
+    
+    // init control I/O
+    init_control_io(o);
+    
+    // check number of methods
+    if (o->num_auth_info == 0 || o->num_auth_info > 255) {
+        BLog(BLOG_ERROR, "invalid number of authentication methods");
+        goto fail1;
+    }
+    
+    // allocate buffer for sending hello
+    bsize_t size = bsize_add(
+        bsize_fromsize(sizeof(struct socks_client_hello_header)), 
+        bsize_mul(
+            bsize_fromsize(o->num_auth_info),
+            bsize_fromsize(sizeof(struct socks_client_hello_method))
+        )
+    );
+    if (!reserve_buffer(o, size)) {
+        goto fail1;
+    }
+    
+    // write hello header
+    struct socks_client_hello_header header;
+    header.ver = hton8(SOCKS_VERSION);
+    header.nmethods = hton8(o->num_auth_info);
+    memcpy(o->buffer, &header, sizeof(header));
+    
+    // write hello methods
+    for (size_t i = 0; i < o->num_auth_info; i++) {
+        struct socks_client_hello_method method;
+        method.method = hton8(o->auth_info[i].auth_type);
+        memcpy(o->buffer + sizeof(header) + i * sizeof(method), &method, sizeof(method));
+    }
+    
+    // send
+    PacketPassInterface_Sender_Send(o->control.send_if, (uint8_t *)o->buffer, size.value);
+    
+    // set state
+    o->state = STATE_SENDING_HELLO;
+    
+    return;
+    
+fail1:
+    free_control_io(o);
+    BConnection_Free(&o->con);
+fail0:
+    report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+    return;
+}
+
+void connection_handler (BSocksClient* o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state != STATE_CONNECTING)
+    
+    if (o->state == STATE_UP && event == BCONNECTION_EVENT_RECVCLOSED) {
+        report_error(o, BSOCKSCLIENT_EVENT_ERROR_CLOSED);
+        return;
+    }
+    
+    report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+    return;
+}
+
+void recv_handler_done (BSocksClient *o, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->control.recv_total - o->control.recv_len)
+    DebugObject_Access(&o->d_obj);
+    
+    o->control.recv_len += data_len;
+    
+    if (o->control.recv_len < o->control.recv_total) {
+        do_receive(o);
+        return;
+    }
+    
+    switch (o->state) {
+        case STATE_SENT_HELLO: {
+            BLog(BLOG_DEBUG, "received hello");
+            
+            struct socks_server_hello imsg;
+            memcpy(&imsg, o->buffer, sizeof(imsg));
+            
+            if (ntoh8(imsg.ver) != SOCKS_VERSION) {
+                BLog(BLOG_NOTICE, "wrong version");
+                goto fail;
+            }
+            
+            size_t auth_index;
+            for (auth_index = 0; auth_index < o->num_auth_info; auth_index++) {
+                if (o->auth_info[auth_index].auth_type == ntoh8(imsg.method)) {
+                    break;
+                }
+            }
+            
+            if (auth_index == o->num_auth_info) {
+                BLog(BLOG_NOTICE, "server didn't accept any authentication method");
+                goto fail;
+            }
+            
+            const struct BSocksClient_auth_info *ai = &o->auth_info[auth_index];
+            
+            switch (ai->auth_type) {
+                case SOCKS_METHOD_NO_AUTHENTICATION_REQUIRED: {
+                    BLog(BLOG_DEBUG, "no authentication");
+                    
+                    auth_finished(o);
+                } break;
+                
+                case SOCKS_METHOD_USERNAME_PASSWORD: {
+                    BLog(BLOG_DEBUG, "password authentication");
+                    
+                    if (ai->password.username_len == 0 || ai->password.username_len > 255 ||
+                        ai->password.password_len == 0 || ai->password.password_len > 255
+                    ) {
+                        BLog(BLOG_NOTICE, "invalid username/password length");
+                        goto fail;
+                    }
+                    
+                    // allocate password packet
+                    bsize_t size = bsize_fromsize(1 + 1 + ai->password.username_len + 1 + ai->password.password_len);
+                    if (!reserve_buffer(o, size)) {
+                        goto fail;
+                    }
+                    
+                    // write password packet
+                    char *ptr = o->buffer;
+                    *ptr++ = 1;
+                    *ptr++ = ai->password.username_len;
+                    memcpy(ptr, ai->password.username, ai->password.username_len);
+                    ptr += ai->password.username_len;
+                    *ptr++ = ai->password.password_len;
+                    memcpy(ptr, ai->password.password, ai->password.password_len);
+                    ptr += ai->password.password_len;
+                    
+                    // start sending
+                    PacketPassInterface_Sender_Send(o->control.send_if, (uint8_t *)o->buffer, size.value);
+                    
+                    // set state
+                    o->state = STATE_SENDING_PASSWORD;
+                } break;
+                
+                default: ASSERT(0);
+            }
+        } break;
+        
+        case STATE_SENT_REQUEST: {
+            BLog(BLOG_DEBUG, "received reply header");
+            
+            struct socks_reply_header imsg;
+            memcpy(&imsg, o->buffer, sizeof(imsg));
+            
+            if (ntoh8(imsg.ver) != SOCKS_VERSION) {
+                BLog(BLOG_NOTICE, "wrong version");
+                goto fail;
+            }
+            
+            if (ntoh8(imsg.rep) != SOCKS_REP_SUCCEEDED) {
+                BLog(BLOG_NOTICE, "reply not successful");
+                goto fail;
+            }
+            
+            int addr_len;
+            switch (ntoh8(imsg.atyp)) {
+                case SOCKS_ATYP_IPV4:
+                    addr_len = sizeof(struct socks_addr_ipv4);
+                    break;
+                case SOCKS_ATYP_IPV6:
+                    addr_len = sizeof(struct socks_addr_ipv6);
+                    break;
+                default:
+                    BLog(BLOG_NOTICE, "reply has unknown address type");
+                    goto fail;
+            }
+            
+            // receive the rest of the reply
+            start_receive(o, (uint8_t *)o->buffer + sizeof(imsg), addr_len);
+            
+            // set state
+            o->state = STATE_RECEIVED_REPLY_HEADER;
+        } break;
+        
+        case STATE_SENT_PASSWORD: {
+            BLog(BLOG_DEBUG, "received password reply");
+            
+            if (o->buffer[0] != 1) {
+                BLog(BLOG_NOTICE, "password reply has unknown version");
+                goto fail;
+            }
+            
+            if (o->buffer[1] != 0) {
+                BLog(BLOG_NOTICE, "password reply is negative");
+                goto fail;
+            }
+            
+            auth_finished(o);
+        } break;
+        
+        case STATE_RECEIVED_REPLY_HEADER: {
+            BLog(BLOG_DEBUG, "received reply rest");
+            
+            // free buffer
+            BFree(o->buffer);
+            o->buffer = NULL;
+            
+            // free control I/O
+            free_control_io(o);
+            
+            // init up I/O
+            init_up_io(o);
+            
+            // set state
+            o->state = STATE_UP;
+            
+            // call handler
+            o->handler(o->user, BSOCKSCLIENT_EVENT_UP);
+            return;
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+    
+    return;
+    
+fail:
+    report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+}
+
+void send_handler_done (BSocksClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->buffer)
+    
+    switch (o->state) {
+        case STATE_SENDING_HELLO: {
+            BLog(BLOG_DEBUG, "sent hello");
+            
+            // allocate buffer for receiving hello
+            bsize_t size = bsize_fromsize(sizeof(struct socks_server_hello));
+            if (!reserve_buffer(o, size)) {
+                goto fail;
+            }
+            
+            // receive hello
+            start_receive(o, (uint8_t *)o->buffer, size.value);
+            
+            // set state
+            o->state = STATE_SENT_HELLO;
+        } break;
+        
+        case STATE_SENDING_REQUEST: {
+            BLog(BLOG_DEBUG, "sent request");
+            
+            // allocate buffer for receiving reply
+            bsize_t size = bsize_add(
+                bsize_fromsize(sizeof(struct socks_reply_header)),
+                bsize_max(bsize_fromsize(sizeof(struct socks_addr_ipv4)), bsize_fromsize(sizeof(struct socks_addr_ipv6)))
+            );
+            if (!reserve_buffer(o, size)) {
+                goto fail;
+            }
+            
+            // receive reply header
+            start_receive(o, (uint8_t *)o->buffer, sizeof(struct socks_reply_header));
+            
+            // set state
+            o->state = STATE_SENT_REQUEST;
+        } break;
+        
+        case STATE_SENDING_PASSWORD: {
+            BLog(BLOG_DEBUG, "send password");
+            
+            // allocate buffer for receiving reply
+            bsize_t size = bsize_fromsize(2);
+            if (!reserve_buffer(o, size)) {
+                goto fail;
+            }
+            
+            // receive reply header
+            start_receive(o, (uint8_t *)o->buffer, size.value);
+            
+            // set state
+            o->state = STATE_SENT_PASSWORD;
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+    
+    return;
+    
+fail:
+    report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+}
+
+void auth_finished (BSocksClient *o)
+{
+    // allocate request buffer
+    bsize_t size = bsize_fromsize(sizeof(struct socks_request_header));
+    switch (o->dest_addr.type) {
+        case BADDR_TYPE_IPV4: size = bsize_add(size, bsize_fromsize(sizeof(struct socks_addr_ipv4))); break;
+        case BADDR_TYPE_IPV6: size = bsize_add(size, bsize_fromsize(sizeof(struct socks_addr_ipv6))); break;
+    }
+    if (!reserve_buffer(o, size)) {
+        report_error(o, BSOCKSCLIENT_EVENT_ERROR);
+        return;
+    }
+    
+    // write request
+    struct socks_request_header header;
+    header.ver = hton8(SOCKS_VERSION);
+    header.cmd = hton8(SOCKS_CMD_CONNECT);
+    header.rsv = hton8(0);
+    switch (o->dest_addr.type) {
+        case BADDR_TYPE_IPV4: {
+            header.atyp = hton8(SOCKS_ATYP_IPV4);
+            struct socks_addr_ipv4 addr;
+            addr.addr = o->dest_addr.ipv4.ip;
+            addr.port = o->dest_addr.ipv4.port;
+            memcpy(o->buffer + sizeof(header), &addr, sizeof(addr));
+        } break;
+        case BADDR_TYPE_IPV6: {
+            header.atyp = hton8(SOCKS_ATYP_IPV6);
+            struct socks_addr_ipv6 addr;
+            memcpy(addr.addr, o->dest_addr.ipv6.ip, sizeof(o->dest_addr.ipv6.ip));
+            addr.port = o->dest_addr.ipv6.port;
+            memcpy(o->buffer + sizeof(header), &addr, sizeof(addr));
+        } break;
+        default:
+            ASSERT(0);
+    }
+    memcpy(o->buffer, &header, sizeof(header));
+    
+    // send request
+    PacketPassInterface_Sender_Send(o->control.send_if, (uint8_t *)o->buffer, size.value);
+    
+    // set state
+    o->state = STATE_SENDING_REQUEST;
+}
+
+struct BSocksClient_auth_info BSocksClient_auth_none (void)
+{
+    struct BSocksClient_auth_info info;
+    info.auth_type = SOCKS_METHOD_NO_AUTHENTICATION_REQUIRED;
+    return info;
+}
+
+struct BSocksClient_auth_info BSocksClient_auth_password (const char *username, size_t username_len, const char *password, size_t password_len)
+{
+    struct BSocksClient_auth_info info;
+    info.auth_type = SOCKS_METHOD_USERNAME_PASSWORD;
+    info.password.username = username;
+    info.password.username_len = username_len;
+    info.password.password = password;
+    info.password.password_len = password_len;
+    return info;
+}
+
+int BSocksClient_Init (BSocksClient *o,
+                       BAddr server_addr, const struct BSocksClient_auth_info *auth_info, size_t num_auth_info,
+                       BAddr dest_addr, BSocksClient_handler handler, void *user, BReactor *reactor)
+{
+    ASSERT(!BAddr_IsInvalid(&server_addr))
+    ASSERT(dest_addr.type == BADDR_TYPE_IPV4 || dest_addr.type == BADDR_TYPE_IPV6)
+#ifndef NDEBUG
+    for (size_t i = 0; i < num_auth_info; i++) {
+        ASSERT(auth_info[i].auth_type == SOCKS_METHOD_NO_AUTHENTICATION_REQUIRED ||
+               auth_info[i].auth_type == SOCKS_METHOD_USERNAME_PASSWORD)
+    }
+#endif
+    
+    // init arguments
+    o->auth_info = auth_info;
+    o->num_auth_info = num_auth_info;
+    o->dest_addr = dest_addr;
+    o->handler = handler;
+    o->user = user;
+    o->reactor = reactor;
+    
+    // set no buffer
+    o->buffer = NULL;
+    
+    // init connector
+    if (!BConnector_Init(&o->connector, server_addr, o->reactor, o, (BConnector_handler)connector_handler)) {
+        BLog(BLOG_ERROR, "BConnector_Init failed");
+        goto fail0;
+    }
+    
+    // set state
+    o->state = STATE_CONNECTING;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void BSocksClient_Free (BSocksClient *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    
+    if (o->state != STATE_CONNECTING) {
+        if (o->state == STATE_UP) {
+            // free up I/O
+            free_up_io(o);
+        } else {
+            // free control I/O
+            free_control_io(o);
+        }
+        
+        // free connection
+        BConnection_Free(&o->con);
+    }
+    
+    // free connector
+    BConnector_Free(&o->connector);
+    
+    // free buffer
+    if (o->buffer) {
+        BFree(o->buffer);
+    }
+}
+
+StreamPassInterface * BSocksClient_GetSendInterface (BSocksClient *o)
+{
+    ASSERT(o->state == STATE_UP)
+    DebugObject_Access(&o->d_obj);
+    
+    return BConnection_SendAsync_GetIf(&o->con);
+}
+
+StreamRecvInterface * BSocksClient_GetRecvInterface (BSocksClient *o)
+{
+    ASSERT(o->state == STATE_UP)
+    DebugObject_Access(&o->d_obj);
+    
+    return BConnection_RecvAsync_GetIf(&o->con);
+}
diff --git a/external/badvpn_dns/socksclient/BSocksClient.h b/external/badvpn_dns/socksclient/BSocksClient.h
new file mode 100644
index 0000000..f19b3a8
--- /dev/null
+++ b/external/badvpn_dns/socksclient/BSocksClient.h
@@ -0,0 +1,147 @@
+/**
+ * @file BSocksClient.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * SOCKS5 client. TCP only, no authentication.
+ */
+
+#ifndef BADVPN_SOCKS_BSOCKSCLIENT_H
+#define BADVPN_SOCKS_BSOCKSCLIENT_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <misc/socks_proto.h>
+#include <misc/packed.h>
+#include <base/DebugObject.h>
+#include <system/BConnection.h>
+#include <flow/PacketStreamSender.h>
+
+#define BSOCKSCLIENT_EVENT_ERROR 1
+#define BSOCKSCLIENT_EVENT_UP 2
+#define BSOCKSCLIENT_EVENT_ERROR_CLOSED 3
+
+/**
+ * Handler for events generated by the SOCKS client.
+ * 
+ * @param user as in {@link BSocksClient_Init}
+ * @param event event type. One of BSOCKSCLIENT_EVENT_ERROR, BSOCKSCLIENT_EVENT_UP
+ *              and BSOCKSCLIENT_EVENT_ERROR_CLOSED.
+ *              If event is BSOCKSCLIENT_EVENT_UP, the object was previously in down
+ *              state and has transitioned to up state; I/O can be done from this point on.
+ *              If event is BSOCKSCLIENT_EVENT_ERROR or BSOCKSCLIENT_EVENT_ERROR_CLOSED,
+ *              the object must be freed from within the job closure of this handler,
+ *              and no further I/O must be attempted.
+ */
+typedef void (*BSocksClient_handler) (void *user, int event);
+
+struct BSocksClient_auth_info {
+    int auth_type;
+    union {
+        struct {
+            const char *username;
+            size_t username_len;
+            const char *password;
+            size_t password_len;
+        } password;
+    };
+};
+
+typedef struct {
+    const struct BSocksClient_auth_info *auth_info;
+    size_t num_auth_info;
+    BAddr dest_addr;
+    BSocksClient_handler handler;
+    void *user;
+    BReactor *reactor;
+    int state;
+    char *buffer;
+    BConnector connector;
+    BConnection con;
+    union {
+        struct {
+            PacketPassInterface *send_if;
+            PacketStreamSender send_sender;
+            StreamRecvInterface *recv_if;
+            uint8_t *recv_dest;
+            int recv_len;
+            int recv_total;
+        } control;
+    };
+    DebugError d_err;
+    DebugObject d_obj;
+} BSocksClient;
+
+struct BSocksClient_auth_info BSocksClient_auth_none (void);
+struct BSocksClient_auth_info BSocksClient_auth_password (const char *username, size_t username_len, const char *password, size_t password_len);
+
+/**
+ * Initializes the object.
+ * The object is initialized in down state. The object must transition to up
+ * state before the user may begin any I/O.
+ * 
+ * @param o the object
+ * @param server_addr SOCKS5 server address
+ * @param dest_addr remote address
+ * @param handler handler for up and error events
+ * @param user value passed to handler
+ * @param reactor reactor we live in
+ * @return 1 on success, 0 on failure
+ */
+int BSocksClient_Init (BSocksClient *o,
+                       BAddr server_addr, const struct BSocksClient_auth_info *auth_info, size_t num_auth_info,
+                       BAddr dest_addr, BSocksClient_handler handler, void *user, BReactor *reactor) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void BSocksClient_Free (BSocksClient *o);
+
+/**
+ * Returns the send interface.
+ * The object must be in up state.
+ * 
+ * @param o the object
+ * @return send interface
+ */
+StreamPassInterface * BSocksClient_GetSendInterface (BSocksClient *o);
+
+/**
+ * Returns the receive interface.
+ * The object must be in up state.
+ * 
+ * @param o the object
+ * @return receive interface
+ */
+StreamRecvInterface * BSocksClient_GetRecvInterface (BSocksClient *o);
+
+#endif
diff --git a/external/badvpn_dns/socksclient/CMakeLists.txt b/external/badvpn_dns/socksclient/CMakeLists.txt
new file mode 100644
index 0000000..7b88d8e
--- /dev/null
+++ b/external/badvpn_dns/socksclient/CMakeLists.txt
@@ -0,0 +1 @@
+badvpn_add_library(socksclient "system;flow;flowextra" "" BSocksClient.c)
diff --git a/external/badvpn_dns/stringmap/BStringMap.c b/external/badvpn_dns/stringmap/BStringMap.c
new file mode 100644
index 0000000..d53f10e
--- /dev/null
+++ b/external/badvpn_dns/stringmap/BStringMap.c
@@ -0,0 +1,198 @@
+/**
+ * @file BStringMap.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/compare.h>
+
+#include <stringmap/BStringMap.h>
+
+static int string_comparator (void *unused, char **str1, char **str2)
+{
+    int c = strcmp(*str1, *str2);
+    return B_COMPARE(c, 0);
+}
+
+static void free_entry (BStringMap *o, struct BStringMap_entry *e)
+{
+    BAVL_Remove(&o->tree, &e->tree_node);
+    free(e->value);
+    free(e->key);
+    free(e);
+}
+
+void BStringMap_Init (BStringMap *o)
+{
+    // init tree
+    BAVL_Init(&o->tree, OFFSET_DIFF(struct BStringMap_entry, key, tree_node), (BAVL_comparator)string_comparator, NULL);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+int BStringMap_InitCopy (BStringMap *o, const BStringMap *src)
+{
+    BStringMap_Init(o);
+    
+    const char *key = BStringMap_First(src);
+    while (key) {
+        if (!BStringMap_Set(o, key, BStringMap_Get(src, key))) {
+            goto fail1;
+        }
+        key = BStringMap_Next(src, key);
+    }
+    
+    return 1;
+    
+fail1:
+    BStringMap_Free(o);
+    return 0;
+}
+
+void BStringMap_Free (BStringMap *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free entries
+    BAVLNode *tree_node;
+    while (tree_node = BAVL_GetFirst(&o->tree)) {
+        struct BStringMap_entry *e = UPPER_OBJECT(tree_node, struct BStringMap_entry, tree_node);
+        free_entry(o, e);
+    }
+}
+
+const char * BStringMap_Get (const BStringMap *o, const char *key)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(key)
+    
+    // lookup
+    BAVLNode *tree_node = BAVL_LookupExact(&o->tree, &key);
+    if (!tree_node) {
+        return NULL;
+    }
+    struct BStringMap_entry *e = UPPER_OBJECT(tree_node, struct BStringMap_entry, tree_node);
+    
+    return e->value;
+}
+
+int BStringMap_Set (BStringMap *o, const char *key, const char *value)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(key)
+    ASSERT(value)
+    
+    // alloc entry
+    struct BStringMap_entry *e = malloc(sizeof(*e));
+    if (!e) {
+        goto fail0;
+    }
+    
+    // alloc and set key
+    if (!(e->key = malloc(strlen(key) + 1))) {
+        goto fail1;
+    }
+    strcpy(e->key, key);
+    
+    // alloc and set value
+    if (!(e->value = malloc(strlen(value) + 1))) {
+        goto fail2;
+    }
+    strcpy(e->value, value);
+    
+    // try inserting to tree
+    BAVLNode *ex_tree_node;
+    if (!BAVL_Insert(&o->tree, &e->tree_node, &ex_tree_node)) {
+        // remove existing entry
+        struct BStringMap_entry *ex_e = UPPER_OBJECT(ex_tree_node, struct BStringMap_entry, tree_node);
+        free_entry(o, ex_e);
+        
+        // insert new node
+        ASSERT_EXECUTE(BAVL_Insert(&o->tree, &e->tree_node, NULL))
+    }
+    
+    return 1;
+    
+fail2:
+    free(e->key);
+fail1:
+    free(e);
+fail0:
+    return 0;
+}
+
+void BStringMap_Unset (BStringMap *o, const char *key)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(key)
+    
+    // lookup
+    BAVLNode *tree_node = BAVL_LookupExact(&o->tree, &key);
+    if (!tree_node) {
+        return;
+    }
+    struct BStringMap_entry *e = UPPER_OBJECT(tree_node, struct BStringMap_entry, tree_node);
+    
+    // remove
+    free_entry(o, e);
+}
+
+const char * BStringMap_First (const BStringMap *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // get first
+    BAVLNode *tree_node = BAVL_GetFirst(&o->tree);
+    if (!tree_node) {
+        return NULL;
+    }
+    struct BStringMap_entry *e = UPPER_OBJECT(tree_node, struct BStringMap_entry, tree_node);
+    
+    return e->key;
+}
+
+const char * BStringMap_Next (const BStringMap *o, const char *key)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(key)
+    ASSERT(BAVL_LookupExact(&o->tree, &key))
+    
+    // get entry
+    struct BStringMap_entry *e = UPPER_OBJECT(BAVL_LookupExact(&o->tree, &key), struct BStringMap_entry, tree_node);
+    
+    // get next
+    BAVLNode *tree_node = BAVL_GetNext(&o->tree, &e->tree_node);
+    if (!tree_node) {
+        return NULL;
+    }
+    struct BStringMap_entry *next_e = UPPER_OBJECT(tree_node, struct BStringMap_entry, tree_node);
+    
+    return next_e->key;
+}
diff --git a/external/badvpn_dns/stringmap/BStringMap.h b/external/badvpn_dns/stringmap/BStringMap.h
new file mode 100644
index 0000000..39b2071
--- /dev/null
+++ b/external/badvpn_dns/stringmap/BStringMap.h
@@ -0,0 +1,57 @@
+/**
+ * @file BStringMap.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_STRINGMAP_BSTRINGMAP_H
+#define BADVPN_STRINGMAP_BSTRINGMAP_H
+
+#include <misc/debug.h>
+#include <structure/BAVL.h>
+#include <base/DebugObject.h>
+
+struct BStringMap_entry {
+    char *key;
+    char *value;
+    BAVLNode tree_node;
+};
+
+typedef struct {
+    BAVL tree;
+    DebugObject d_obj;
+} BStringMap;
+
+void BStringMap_Init (BStringMap *o);
+int BStringMap_InitCopy (BStringMap *o, const BStringMap *src) WARN_UNUSED;
+void BStringMap_Free (BStringMap *o);
+const char * BStringMap_Get (const BStringMap *o, const char *key);
+int BStringMap_Set (BStringMap *o, const char *key, const char *value) WARN_UNUSED;
+void BStringMap_Unset (BStringMap *o, const char *key);
+const char * BStringMap_First (const BStringMap *o);
+const char * BStringMap_Next (const BStringMap *o, const char *key);
+
+#endif
diff --git a/external/badvpn_dns/stringmap/CMakeLists.txt b/external/badvpn_dns/stringmap/CMakeLists.txt
new file mode 100644
index 0000000..74dd8a1
--- /dev/null
+++ b/external/badvpn_dns/stringmap/CMakeLists.txt
@@ -0,0 +1 @@
+badvpn_add_library(stringmap "" "" BStringMap.c)
diff --git a/external/badvpn_dns/structure/BAVL.h b/external/badvpn_dns/structure/BAVL.h
new file mode 100644
index 0000000..66993b3
--- /dev/null
+++ b/external/badvpn_dns/structure/BAVL.h
@@ -0,0 +1,797 @@
+/**
+ * @file BAVL.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * AVL tree.
+ */
+
+#ifndef BADVPN_STRUCTURE_BAVL_H
+#define BADVPN_STRUCTURE_BAVL_H
+
+//#define BAVL_DEBUG
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+
+/**
+ * Handler function called by tree algorithms to compare two values.
+ * For any two values, the comparator must always return the same result.
+ * The <= relation defined by the comparator must be a total order.
+ * Values are obtained like that:
+ *   - The value of a node in the tree, or a node that is being inserted is:
+ *     (uint8_t *)node + offset.
+ *   - The value being looked up is the same as given to the lookup function.
+ * 
+ * @param user as in {@link BAVL_Init}
+ * @param val1 first value
+ * @param val2 second value
+ * @return -1 if val1 < val2, 0 if val1 = val2, 1 if val1 > val2
+ */
+typedef int (*BAVL_comparator) (void *user, void *val1, void *val2);
+
+struct BAVLNode;
+
+/**
+ * AVL tree.
+ */
+typedef struct {
+    int offset;
+    BAVL_comparator comparator;
+    void *user;
+    struct BAVLNode *root;
+} BAVL;
+
+/**
+ * AVL tree node.
+ */
+typedef struct BAVLNode {
+    struct BAVLNode *parent;
+    struct BAVLNode *link[2];
+    int8_t balance;
+#ifdef BAVL_COUNT
+    uint64_t count;
+#endif
+} BAVLNode;
+
+/**
+ * Initializes the tree.
+ * 
+ * @param o tree to initialize
+ * @param offset offset of a value from its node
+ * @param comparator value comparator handler function
+ * @param user value to pass to comparator
+ */
+static void BAVL_Init (BAVL *o, int offset, BAVL_comparator comparator, void *user);
+
+/**
+ * Inserts a node into the tree.
+ * Must not be called from comparator.
+ * 
+ * @param o the tree
+ * @param node uninitialized node to insert. Must have a valid value (its value
+ *             may be passed to the comparator during insertion).
+ * @param ref if not NULL, will return (regardless if insertion succeeded):
+ *              - the greatest node lesser than the inserted value, or (not in order)
+ *              - the smallest node greater than the inserted value, or
+ *              - NULL meaning there were no nodes in the tree.
+ * @param 1 on success, 0 if an element with an equal value is already in the tree
+ */
+static int BAVL_Insert (BAVL *o, BAVLNode *node, BAVLNode **ref);
+
+/**
+ * Removes a node from the tree.
+ * Must not be called from comparator.
+ * 
+ * @param o the tree
+ * @param node node to remove
+ */
+static void BAVL_Remove (BAVL *o, BAVLNode *node);
+
+/**
+ * Checks if the tree is empty.
+ * Must not be called from comparator.
+ * 
+ * @param o the tree
+ * @return 1 if empty, 0 if not
+ */
+static int BAVL_IsEmpty (const BAVL *o);
+
+/**
+ * Looks for a value in the tree.
+ * Must not be called from comparator.
+ * 
+ * @param o the tree
+ * @param val value to look for
+ * @return If a node is in the thee with an equal value, that node.
+ *         Else if the tree is not empty:
+ *           - the greatest node lesser than the given value, or (not in order)
+ *           - the smallest node greater than the given value.
+ *         NULL if the tree is empty.
+ */
+static BAVLNode * BAVL_Lookup (const BAVL *o, void *val);
+
+/**
+ * Looks for a value in the tree.
+ * Must not be called from comparator.
+ * 
+ * @param o the tree
+ * @param val value to look for
+ * @return If a node is in the thee with an equal value, that node.
+ *         Else NULL.
+ */
+static BAVLNode * BAVL_LookupExact (const BAVL *o, void *val);
+
+/**
+ * Returns the smallest node in the tree, or NULL if the tree is empty.
+ * 
+ * @param o the tree
+ * @return smallest node or NULL
+ */
+static BAVLNode * BAVL_GetFirst (const BAVL *o);
+
+/**
+ * Returns the greatest node in the tree, or NULL if the tree is empty.
+ * 
+ * @param o the tree
+ * @return greatest node or NULL
+ */
+static BAVLNode * BAVL_GetLast (const BAVL *o);
+
+/**
+ * Returns the node that follows the given node, or NULL if it's the
+ * last node.
+ * 
+ * @param o the tree
+ * @param n node
+ * @return next node, or NULL
+ */
+static BAVLNode * BAVL_GetNext (const BAVL *o, BAVLNode *n);
+
+/**
+ * Returns the node that precedes the given node, or NULL if it's the
+ * first node.
+ * 
+ * @param o the tree
+ * @param n node
+ * @return previous node, or NULL
+ */
+static BAVLNode * BAVL_GetPrev (const BAVL *o, BAVLNode *n);
+
+#ifdef BAVL_COUNT
+static uint64_t BAVL_Count (const BAVL *o);
+static uint64_t BAVL_IndexOf (const BAVL *o, BAVLNode *n);
+static BAVLNode * BAVL_GetAt (const BAVL *o, uint64_t index);
+#endif
+
+static void BAVL_Verify (BAVL *o);
+
+#define BAVL_MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b))
+#define BAVL_OPTNEG(_a, _neg) ((_neg) ? -(_a) : (_a))
+
+static void * _BAVL_node_value (const BAVL *o, BAVLNode *n)
+{
+    return ((uint8_t *)n + o->offset);
+}
+
+static int _BAVL_compare_values (const BAVL *o, void *v1, void *v2)
+{
+    int res = o->comparator(o->user, v1, v2);
+    
+    ASSERT(res == -1 || res == 0 || res == 1)
+    
+    return res;
+}
+
+static int _BAVL_compare_nodes (BAVL *o, BAVLNode *n1, BAVLNode *n2)
+{
+    return _BAVL_compare_values(o, _BAVL_node_value(o, n1), _BAVL_node_value(o, n2));
+}
+
+#ifdef BAVL_AUTO_VERIFY
+#define BAVL_ASSERT(_h) BAVL_Verify((_h));
+#else
+#define BAVL_ASSERT(_h)
+#endif
+
+static int _BAVL_assert_recurser (BAVL *o, BAVLNode *n)
+{
+    ASSERT_FORCE(n->balance >= -1)
+    ASSERT_FORCE(n->balance <= 1)
+    
+    int height_left = 0;
+    int height_right = 0;
+#ifdef BAVL_COUNT
+    uint64_t count_left = 0;
+    uint64_t count_right = 0;
+#endif
+    
+    // check left subtree
+    if (n->link[0]) {
+        // check parent link
+        ASSERT_FORCE(n->link[0]->parent == n)
+        // check binary search tree
+        ASSERT_FORCE(_BAVL_compare_nodes(o, n->link[0], n) == -1)
+        // recursively calculate height
+        height_left = _BAVL_assert_recurser(o, n->link[0]);
+#ifdef BAVL_COUNT
+        count_left = n->link[0]->count;
+#endif
+    }
+    
+    // check right subtree
+    if (n->link[1]) {
+        // check parent link
+        ASSERT_FORCE(n->link[1]->parent == n)
+        // check binary search tree
+        ASSERT_FORCE(_BAVL_compare_nodes(o, n->link[1], n) == 1)
+        // recursively calculate height
+        height_right = _BAVL_assert_recurser(o, n->link[1]);
+#ifdef BAVL_COUNT
+        count_right = n->link[1]->count;
+#endif
+    }
+    
+    // check balance factor
+    ASSERT_FORCE(n->balance == height_right - height_left)
+    
+#ifdef BAVL_COUNT
+    // check count
+    ASSERT_FORCE(n->count == 1 + count_left + count_right)
+#endif
+    
+    return (BAVL_MAX(height_left, height_right) + 1);
+}
+
+#ifdef BAVL_COUNT
+static void _BAVL_update_count_from_children (BAVLNode *n)
+{
+    n->count = 1 + (n->link[0] ? n->link[0]->count : 0) + (n->link[1] ? n->link[1]->count : 0);
+}
+#endif
+
+static void _BAVL_rotate (BAVL *tree, BAVLNode *r, uint8_t dir)
+{
+    BAVLNode *nr = r->link[!dir];
+    
+    r->link[!dir] = nr->link[dir];
+    if (r->link[!dir]) {
+        r->link[!dir]->parent = r;
+    }
+    nr->link[dir] = r;
+    nr->parent = r->parent;
+    if (nr->parent) {
+        nr->parent->link[r == r->parent->link[1]] = nr;
+    } else {
+        tree->root = nr;
+    }
+    r->parent = nr;
+    
+#ifdef BAVL_COUNT
+    // update counts
+    _BAVL_update_count_from_children(r); // first r!
+    _BAVL_update_count_from_children(nr);
+#endif
+}
+
+static BAVLNode * _BAVL_subtree_max (BAVLNode *n)
+{
+    ASSERT(n)
+    while (n->link[1]) {
+        n = n->link[1];
+    }
+    return n;
+}
+
+static void _BAVL_replace_subtree (BAVL *tree, BAVLNode *dest, BAVLNode *n)
+{
+    ASSERT(dest)
+    
+    if (dest->parent) {
+        dest->parent->link[dest == dest->parent->link[1]] = n;
+    } else {
+        tree->root = n;
+    }
+    if (n) {
+        n->parent = dest->parent;
+    }
+    
+#ifdef BAVL_COUNT
+    // update counts
+    for (BAVLNode *c = dest->parent; c; c = c->parent) {
+        ASSERT(c->count >= dest->count)
+        c->count -= dest->count;
+        if (n) {
+            ASSERT(n->count <= UINT64_MAX - c->count)
+            c->count += n->count;
+        }
+    }
+#endif
+}
+
+static void _BAVL_swap_nodes (BAVL *tree, BAVLNode *n1, BAVLNode *n2)
+{
+    if (n2->parent == n1 || n1->parent == n2) {
+        // when the nodes are directly connected we need special handling
+        // make sure n1 is above n2
+        if (n1->parent == n2) {
+            BAVLNode *t = n1;
+            n1 = n2;
+            n2 = t;
+        }
+        
+        uint8_t side = (n2 == n1->link[1]);
+        BAVLNode *c = n1->link[!side];
+        
+        if (n1->link[0] = n2->link[0]) {
+            n1->link[0]->parent = n1;
+        }
+        if (n1->link[1] = n2->link[1]) {
+            n1->link[1]->parent = n1;
+        }
+        
+        if (n2->parent = n1->parent) {
+            n2->parent->link[n1 == n1->parent->link[1]] = n2;
+        } else {
+            tree->root = n2;
+        }
+        
+        n2->link[side] = n1;
+        n1->parent = n2;
+        if (n2->link[!side] = c) {
+            c->parent = n2;
+        }
+    } else {
+        BAVLNode *temp;
+        
+        // swap parents
+        temp = n1->parent;
+        if (n1->parent = n2->parent) {
+            n1->parent->link[n2 == n2->parent->link[1]] = n1;
+        } else {
+            tree->root = n1;
+        }
+        if (n2->parent = temp) {
+            n2->parent->link[n1 == temp->link[1]] = n2;
+        } else {
+            tree->root = n2;
+        }
+        
+        // swap left children
+        temp = n1->link[0];
+        if (n1->link[0] = n2->link[0]) {
+            n1->link[0]->parent = n1;
+        }
+        if (n2->link[0] = temp) {
+            n2->link[0]->parent = n2;
+        }
+        
+        // swap right children
+        temp = n1->link[1];
+        if (n1->link[1] = n2->link[1]) {
+            n1->link[1]->parent = n1;
+        }
+        if (n2->link[1] = temp) {
+            n2->link[1]->parent = n2;
+        }
+    }
+    
+    // swap balance factors
+    int8_t b = n1->balance;
+    n1->balance = n2->balance;
+    n2->balance = b;
+    
+#ifdef BAVL_COUNT
+    // swap counts
+    uint64_t c = n1->count;
+    n1->count = n2->count;
+    n2->count = c;
+#endif
+}
+
+static void _BAVL_rebalance (BAVL *o, BAVLNode *node, uint8_t side, int8_t deltac)
+{
+    ASSERT(side == 0 || side == 1)
+    ASSERT(deltac >= -1 && deltac <= 1)
+    ASSERT(node->balance >= -1 && node->balance <= 1)
+    
+    // if no subtree changed its height, no more rebalancing is needed
+    if (deltac == 0) {
+        return;
+    }
+    
+    // calculate how much our height changed
+    int8_t delta = BAVL_MAX(deltac, BAVL_OPTNEG(node->balance, side)) - BAVL_MAX(0, BAVL_OPTNEG(node->balance, side));
+    ASSERT(delta >= -1 && delta <= 1)
+    
+    // update our balance factor
+    node->balance -= BAVL_OPTNEG(deltac, side);
+    
+    BAVLNode *child;
+    BAVLNode *gchild;
+    
+    // perform transformations if the balance factor is wrong
+    if (node->balance == 2 || node->balance == -2) {
+        uint8_t bside;
+        int8_t bsidef;
+        if (node->balance == 2) {
+            bside = 1;
+            bsidef = 1;
+        } else {
+            bside = 0;
+            bsidef = -1;
+        }
+        
+        ASSERT(node->link[bside])
+        child = node->link[bside];
+        switch (child->balance * bsidef) {
+            case 1:
+                _BAVL_rotate(o, node, !bside);
+                node->balance = 0;
+                child->balance = 0;
+                node = child;
+                delta -= 1;
+                break;
+            case 0:
+                _BAVL_rotate(o, node, !bside);
+                node->balance = 1 * bsidef;
+                child->balance = -1 * bsidef;
+                node = child;
+                break;
+            case -1:
+                ASSERT(child->link[!bside])
+                gchild = child->link[!bside];
+                _BAVL_rotate(o, child, bside);
+                _BAVL_rotate(o, node, !bside);
+                node->balance = -BAVL_MAX(0, gchild->balance * bsidef) * bsidef;
+                child->balance = BAVL_MAX(0, -gchild->balance * bsidef) * bsidef;
+                gchild->balance = 0;
+                node = gchild;
+                delta -= 1;
+                break;
+            default:
+                ASSERT(0);
+        }
+    }
+    
+    ASSERT(delta >= -1 && delta <= 1)
+    // Transformations above preserve this. Proof:
+    //     - if a child subtree gained 1 height and rebalancing was needed,
+    //       it was the heavier subtree. Then delta was was originally 1, because
+    //       the heaviest subtree gained one height. If the transformation reduces
+    //       delta by one, it becomes 0.
+    //     - if a child subtree lost 1 height and rebalancing was needed, it
+    //       was the lighter subtree. Then delta was originally 0, because
+    //       the height of the heaviest subtree was unchanged. If the transformation
+    //       reduces delta by one, it becomes -1.
+    
+    if (node->parent) {
+        _BAVL_rebalance(o, node->parent, node == node->parent->link[1], delta);
+    }
+}
+
+void BAVL_Init (BAVL *o, int offset, BAVL_comparator comparator, void *user)
+{
+    o->offset = offset;
+    o->comparator = comparator;
+    o->user = user;
+    o->root = NULL;
+    
+    BAVL_ASSERT(o)
+}
+
+int BAVL_Insert (BAVL *o, BAVLNode *node, BAVLNode **ref)
+{
+    // insert to root?
+    if (!o->root) {
+        o->root = node;
+        node->parent = NULL;
+        node->link[0] = NULL;
+        node->link[1] = NULL;
+        node->balance = 0;
+#ifdef BAVL_COUNT
+        node->count = 1;
+#endif
+        
+        BAVL_ASSERT(o)
+        
+        if (ref) {
+            *ref = NULL;
+        }
+        return 1;
+    }
+    
+    // find node to insert to
+    BAVLNode *c = o->root;
+    int side;
+    while (1) {
+        // compare
+        int comp = _BAVL_compare_nodes(o, node, c);
+        
+        // have we found a node that compares equal?
+        if (comp == 0) {
+            if (ref) {
+                *ref = c;
+            }
+            return 0;
+        }
+        
+        side = (comp == 1);
+        
+        // have we reached a leaf?
+        if (!c->link[side]) {
+            break;
+        }
+        
+        c = c->link[side];
+    }
+    
+    // insert
+    c->link[side] = node;
+    node->parent = c;
+    node->link[0] = NULL;
+    node->link[1] = NULL;
+    node->balance = 0;
+#ifdef BAVL_COUNT
+    node->count = 1;
+#endif
+    
+#ifdef BAVL_COUNT
+    // update counts
+    for (BAVLNode *p = c; p; p = p->parent) {
+        ASSERT(p->count < UINT64_MAX)
+        p->count++;
+    }
+#endif
+    
+    // rebalance
+    _BAVL_rebalance(o, c, side, 1);
+    
+    BAVL_ASSERT(o)
+    
+    if (ref) {
+        *ref = c;
+    }
+    return 1;
+}
+
+void BAVL_Remove (BAVL *o, BAVLNode *node)
+{
+    // if we have both subtrees, swap the node and the largest node
+    // in the left subtree, so we have at most one subtree
+    if (node->link[0] && node->link[1]) {
+        // find the largest node in the left subtree
+        BAVLNode *max = _BAVL_subtree_max(node->link[0]);
+        // swap the nodes
+        _BAVL_swap_nodes(o, node, max);
+    }
+    
+    // have at most one child now
+    ASSERT(!(node->link[0] && node->link[1]))
+    
+    BAVLNode *parent = node->parent;
+    BAVLNode *child = (node->link[0] ? node->link[0] : node->link[1]);
+    
+    if (parent) {
+        // remember on which side node is
+        int side = (node == parent->link[1]);
+        // replace node with child
+        _BAVL_replace_subtree(o, node, child);
+        // rebalance
+        _BAVL_rebalance(o, parent, side, -1);
+    } else {
+        // replace node with child
+        _BAVL_replace_subtree(o, node, child);
+    }
+    
+    BAVL_ASSERT(o)
+}
+
+int BAVL_IsEmpty (const BAVL *o)
+{
+    return (!o->root);
+}
+
+BAVLNode * BAVL_Lookup (const BAVL *o, void *val)
+{
+    if (!o->root) {
+        return NULL;
+    }
+    
+    BAVLNode *c = o->root;
+    while (1) {
+        // compare
+        int comp = _BAVL_compare_values(o, val, _BAVL_node_value(o, c));
+        
+        // have we found a node that compares equal?
+        if (comp == 0) {
+            return c;
+        }
+        
+        int side = (comp == 1);
+        
+        // have we reached a leaf?
+        if (!c->link[side]) {
+            return c;
+        }
+        
+        c = c->link[side];
+    }
+}
+
+BAVLNode * BAVL_LookupExact (const BAVL *o, void *val)
+{
+    if (!o->root) {
+        return NULL;
+    }
+    
+    BAVLNode *c = o->root;
+    while (1) {
+        // compare
+        int comp = _BAVL_compare_values(o, val, _BAVL_node_value(o, c));
+        
+        // have we found a node that compares equal?
+        if (comp == 0) {
+            return c;
+        }
+        
+        int side = (comp == 1);
+        
+        // have we reached a leaf?
+        if (!c->link[side]) {
+            return NULL;
+        }
+        
+        c = c->link[side];
+    }
+}
+
+BAVLNode * BAVL_GetFirst (const BAVL *o)
+{
+    if (!o->root) {
+        return NULL;
+    }
+    
+    BAVLNode *n = o->root;
+    while (n->link[0]) {
+        n = n->link[0];
+    }
+    
+    return n;
+}
+
+BAVLNode * BAVL_GetLast (const BAVL *o)
+{
+    if (!o->root) {
+        return NULL;
+    }
+    
+    BAVLNode *n = o->root;
+    while (n->link[1]) {
+        n = n->link[1];
+    }
+    
+    return n;
+}
+
+BAVLNode * BAVL_GetNext (const BAVL *o, BAVLNode *n)
+{
+    if (n->link[1]) {
+        n = n->link[1];
+        while (n->link[0]) {
+            n = n->link[0];
+        }
+    } else {
+        while (n->parent && n == n->parent->link[1]) {
+            n = n->parent;
+        }
+        n = n->parent;
+    }
+    
+    return n;
+}
+
+BAVLNode * BAVL_GetPrev (const BAVL *o, BAVLNode *n)
+{
+    if (n->link[0]) {
+        n = n->link[0];
+        while (n->link[1]) {
+            n = n->link[1];
+        }
+    } else {
+        while (n->parent && n == n->parent->link[0]) {
+            n = n->parent;
+        }
+        n = n->parent;
+    }
+    
+    return n;
+}
+
+#ifdef BAVL_COUNT
+
+static uint64_t BAVL_Count (const BAVL *o)
+{
+    return (o->root ? o->root->count : 0);
+}
+
+static uint64_t BAVL_IndexOf (const BAVL *o, BAVLNode *n)
+{
+    uint64_t index = (n->link[0] ? n->link[0]->count : 0);
+    
+    for (BAVLNode *c = n; c->parent; c = c->parent) {
+        if (c == c->parent->link[1]) {
+            ASSERT(c->parent->count > c->count)
+            ASSERT(c->parent->count - c->count <= UINT64_MAX - index)
+            index += c->parent->count - c->count;
+        }
+    }
+    
+    return index;
+}
+
+static BAVLNode * BAVL_GetAt (const BAVL *o, uint64_t index)
+{
+    if (index >= BAVL_Count(o)) {
+        return NULL;
+    }
+    
+    BAVLNode *c = o->root;
+    
+    while (1) {
+        ASSERT(c)
+        ASSERT(index < c->count)
+        
+        uint64_t left_count = (c->link[0] ? c->link[0]->count : 0);
+        
+        if (index == left_count) {
+            return c;
+        }
+        
+        if (index < left_count) {
+            c = c->link[0];
+        } else {
+            c = c->link[1];
+            index -= left_count + 1;
+        }
+    }
+}
+
+#endif
+
+static void BAVL_Verify (BAVL *o)
+{
+    if (o->root) {
+        ASSERT(!o->root->parent)
+        _BAVL_assert_recurser(o, o->root);
+    }
+}
+
+#endif
diff --git a/external/badvpn_dns/structure/CAvl.h b/external/badvpn_dns/structure/CAvl.h
new file mode 100644
index 0000000..ea33a3e
--- /dev/null
+++ b/external/badvpn_dns/structure/CAvl.h
@@ -0,0 +1,36 @@
+/**
+ * @file CAvl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_CAVL_H
+#define BADVPN_CAVL_H
+
+#include <misc/debug.h>
+#include <misc/merge.h>
+
+#endif
diff --git a/external/badvpn_dns/structure/CAvl_decl.h b/external/badvpn_dns/structure/CAvl_decl.h
new file mode 100644
index 0000000..7d54a81
--- /dev/null
+++ b/external/badvpn_dns/structure/CAvl_decl.h
@@ -0,0 +1,77 @@
+/**
+ * @file CAvl_decl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "CAvl_header.h"
+
+typedef struct {
+    CAvlLink root;
+} CAvl;
+
+typedef struct {
+    CAvlEntry *ptr;
+    CAvlLink link;
+} CAvlRef;
+
+static int CAvlIsNullRef (CAvlRef node);
+static int CAvlIsValidRef (CAvlRef node);
+static CAvlRef CAvlDeref (CAvlArg arg, CAvlLink link);
+
+static void CAvl_Init (CAvl *o);
+#if !CAVL_PARAM_FEATURE_KEYS_ARE_INDICES
+static int CAvl_Insert (CAvl *o, CAvlArg arg, CAvlRef node, CAvlRef *out_ref);
+#else
+static void CAvl_InsertAt (CAvl *o, CAvlArg arg, CAvlRef node, CAvlCount index);
+#endif
+static void CAvl_Remove (CAvl *o, CAvlArg arg, CAvlRef node);
+#if !CAVL_PARAM_FEATURE_KEYS_ARE_INDICES && !CAVL_PARAM_FEATURE_NOKEYS
+static CAvlRef CAvl_Lookup (const CAvl *o, CAvlArg arg, CAvlKey key);
+static CAvlRef CAvl_LookupExact (const CAvl *o, CAvlArg arg, CAvlKey key);
+static CAvlRef CAvl_GetFirstGreater (const CAvl *o, CAvlArg arg, CAvlKey key);
+static CAvlRef CAvl_GetLastLesser (const CAvl *o, CAvlArg arg, CAvlKey key);
+static CAvlRef CAvl_GetFirstGreaterEqual (const CAvl *o, CAvlArg arg, CAvlKey key);
+static CAvlRef CAvl_GetLastLesserEqual (const CAvl *o, CAvlArg arg, CAvlKey key);
+#endif
+static CAvlRef CAvl_GetFirst (const CAvl *o, CAvlArg arg);
+static CAvlRef CAvl_GetLast (const CAvl *o, CAvlArg arg);
+static CAvlRef CAvl_GetNext (const CAvl *o, CAvlArg arg, CAvlRef node);
+static CAvlRef CAvl_GetPrev (const CAvl *o, CAvlArg arg, CAvlRef node);
+static int CAvl_IsEmpty (const CAvl *o);
+static void CAvl_Verify (const CAvl *o, CAvlArg arg);
+#if CAVL_PARAM_FEATURE_COUNTS
+static CAvlCount CAvl_Count (const CAvl *o, CAvlArg arg);
+static CAvlCount CAvl_IndexOf (const CAvl *o, CAvlArg arg, CAvlRef node);
+static CAvlRef CAvl_GetAt (const CAvl *o, CAvlArg arg, CAvlCount index);
+#endif
+#if CAVL_PARAM_FEATURE_ASSOC
+static CAvlAssoc CAvl_AssocSum (const CAvl *o, CAvlArg arg);
+static CAvlAssoc CAvl_ExclusiveAssocPrefixSum (const CAvl *o, CAvlArg arg, CAvlRef node);
+static CAvlRef CAvl_FindLastExclusiveAssocPrefixSumLesserEqual (const CAvl *o, CAvlArg arg, CAvlAssoc sum, int (*sum_less) (void *, CAvlAssoc, CAvlAssoc), void *user);
+#endif
+
+#include "CAvl_footer.h"
diff --git a/external/badvpn_dns/structure/CAvl_footer.h b/external/badvpn_dns/structure/CAvl_footer.h
new file mode 100644
index 0000000..43b85c3
--- /dev/null
+++ b/external/badvpn_dns/structure/CAvl_footer.h
@@ -0,0 +1,113 @@
+/**
+ * @file CAvl_footer.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+#undef CAVL_PARAM_NAME
+#undef CAVL_PARAM_FEATURE_COUNTS
+#undef CAVL_PARAM_FEATURE_KEYS_ARE_INDICES
+#undef CAVL_PARAM_FEATURE_NOKEYS
+#undef CAVL_PARAM_FEATURE_ASSOC
+#undef CAVL_PARAM_TYPE_ENTRY
+#undef CAVL_PARAM_TYPE_LINK
+#undef CAVL_PARAM_TYPE_KEY
+#undef CAVL_PARAM_TYPE_ARG
+#undef CAVL_PARAM_TYPE_COUNT
+#undef CAVL_PARAM_TYPE_ASSOC
+#undef CAVL_PARAM_VALUE_COUNT_MAX
+#undef CAVL_PARAM_VALUE_NULL
+#undef CAVL_PARAM_VALUE_ASSOC_ZERO
+#undef CAVL_PARAM_FUN_DEREF
+#undef CAVL_PARAM_FUN_COMPARE_ENTRIES
+#undef CAVL_PARAM_FUN_COMPARE_KEY_ENTRY
+#undef CAVL_PARAM_FUN_ASSOC_VALUE
+#undef CAVL_PARAM_FUN_ASSOC_OPER
+#undef CAVL_PARAM_MEMBER_CHILD
+#undef CAVL_PARAM_MEMBER_BALANCE
+#undef CAVL_PARAM_MEMBER_PARENT
+#undef CAVL_PARAM_MEMBER_COUNT
+#undef CAVL_PARAM_MEMBER_ASSOC
+
+#undef CAvl
+#undef CAvlEntry
+#undef CAvlLink
+#undef CAvlRef
+#undef CAvlArg
+#undef CAvlKey
+#undef CAvlCount
+#undef CAvlAssoc
+
+#undef CAvlIsNullRef
+#undef CAvlIsValidRef
+#undef CAvlDeref
+
+#undef CAvl_Init
+#undef CAvl_Insert
+#undef CAvl_InsertAt
+#undef CAvl_Remove
+#undef CAvl_Lookup
+#undef CAvl_LookupExact
+#undef CAvl_GetFirstGreater
+#undef CAvl_GetLastLesser
+#undef CAvl_GetFirstGreaterEqual
+#undef CAvl_GetLastLesserEqual
+#undef CAvl_GetFirst
+#undef CAvl_GetLast
+#undef CAvl_GetNext
+#undef CAvl_GetPrev
+#undef CAvl_IsEmpty
+#undef CAvl_Verify
+#undef CAvl_Count
+#undef CAvl_IndexOf
+#undef CAvl_GetAt
+#undef CAvl_AssocSum
+#undef CAvl_ExclusiveAssocPrefixSum
+#undef CAvl_FindLastExclusiveAssocPrefixSumLesserEqual
+
+#undef CAvl_link
+#undef CAvl_balance
+#undef CAvl_parent
+#undef CAvl_count
+#undef CAvl_assoc
+#undef CAvl_nulllink
+#undef CAvl_nullref
+#undef CAvl_compare_entries
+#undef CAvl_compare_key_entry
+#undef CAvl_compute_node_assoc
+#undef CAvl_check_parent
+#undef CAvl_verify_recurser
+#undef CAvl_assert_tree
+#undef CAvl_update_count_from_children
+#undef CAvl_rotate
+#undef CAvl_subtree_min
+#undef CAvl_subtree_max
+#undef CAvl_replace_subtree_fix_assoc
+#undef CAvl_swap_for_remove
+#undef CAvl_rebalance
+#undef CAvl_child_count
+#undef CAvl_MAX
+#undef CAvl_OPTNEG
diff --git a/external/badvpn_dns/structure/CAvl_header.h b/external/badvpn_dns/structure/CAvl_header.h
new file mode 100644
index 0000000..91ea7df
--- /dev/null
+++ b/external/badvpn_dns/structure/CAvl_header.h
@@ -0,0 +1,141 @@
+/**
+ * @file CAvl_header.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+// Preprocessor inputs:
+// CAVL_PARAM_NAME - name of this data structure
+// CAVL_PARAM_FEATURE_COUNTS - whether to keep count information (0 or 1)
+// CAVL_PARAM_FEATURE_KEYS_ARE_INDICES - (0 or 1) whether to assume the keys are entry indices
+//   (number of entries lesser than given entry). If yes, CAVL_PARAM_TYPE_KEY is unused.
+//   Requires CAVL_PARAM_FEATURE_COUNTS.
+// CAVL_PARAM_FEATURE_NOKEYS - define to 1 if there is no need for a lookup operation
+// CAVL_PARAM_FEATURE_ASSOC - define to 1 for computation of an associative operation on subtrees.
+//   If enabled, the following macros must be defined: CAVL_PARAM_TYPE_ASSOC,
+//   CAVL_PARAM_VALUE_ASSOC_ZERO, CAVL_PARAM_FUN_ASSOC_VALUE,
+//   CAVL_PARAM_FUN_ASSOC_OPER, CAVL_PARAM_MEMBER_ASSOC.
+// CAVL_PARAM_TYPE_ENTRY - type of entry
+// CAVL_PARAM_TYPE_LINK - type of entry link (usually pointer or index)
+// CAVL_PARAM_TYPE_KEY - type of key (only if not CAVL_PARAM_FEATURE_KEYS_ARE_INDICES and
+//   not CAVL_PARAM_FEATURE_NOKEYS)
+// CAVL_PARAM_TYPE_ARG - type of argument pass through to callbacks
+// CAVL_PARAM_TYPE_COUNT - type of count (only if CAVL_PARAM_FEATURE_COUNTS)
+// CAVL_PARAM_TYPE_ASSOC - type of associative operation result
+// CAVL_PARAM_VALUE_COUNT_MAX - maximum value of count (type is CAVL_PARAM_TYPE_COUNT)
+// CAVL_PARAM_VALUE_NULL - value of invalid link (type is CAVL_PARAM_TYPE_LINK)
+// CAVL_PARAM_VALUE_ASSOC_ZERO - zero value for associative operation (type is CAVL_PARAM_TYPE_ASSOC).
+//   This must be both a left- and right-identity for the associative operation.
+// CAVL_PARAM_FUN_DEREF(arg, link) - dereference a non-null link; returns pointer to CAVL_PARAM_TYPE_LINK
+// CAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) - compare to entries; returns -1/0/1
+// CAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) - compare key and entry; returns -1/0/1
+// CAVL_PARAM_FUN_ASSOC_VALUE(arg, entry) - get value of a node for associative operation.
+//   The result will be cast to CAVL_PARAM_TYPE_ASSOC.
+// CAVL_PARAM_FUN_ASSOC_OPER(arg, value1, value2) - compute the associative operation on two values.
+//   The type of the two values is CAVL_PARAM_TYPE_ASSOC, and the result will be cast to
+//   CAVL_PARAM_TYPE_ASSOC.
+// CAVL_PARAM_MEMBER_CHILD - name of the child member in entry (type is CAVL_PARAM_TYPE_LINK[2])
+// CAVL_PARAM_MEMBER_BALANCE - name of the balance member in entry (type is any signed integer)
+// CAVL_PARAM_MEMBER_PARENT - name of the parent member in entry (type is CAVL_PARAM_TYPE_LINK)
+// CAVL_PARAM_MEMBER_COUNT - name of the count member in entry (type is CAVL_PARAM_TYPE_COUNT)
+//   (only if CAVL_PARAM_FEATURE_COUNTS)
+// CAVL_PARAM_MEMBER_ASSOC - name of assoc member in entry (type is CAVL_PARAM_TYPE_ASSOC)
+
+#ifndef BADVPN_CAVL_H
+#error CAvl.h has not been included
+#endif
+
+#if CAVL_PARAM_FEATURE_KEYS_ARE_INDICES && !CAVL_PARAM_FEATURE_COUNTS
+#error CAVL_PARAM_FEATURE_KEYS_ARE_INDICES requires CAVL_PARAM_FEATURE_COUNTS
+#endif
+
+#if CAVL_PARAM_FEATURE_KEYS_ARE_INDICES && CAVL_PARAM_FEATURE_NOKEYS
+#error CAVL_PARAM_FEATURE_KEYS_ARE_INDICES and CAVL_PARAM_FEATURE_NOKEYS cannot be used together
+#endif
+
+// types
+#define CAvl CAVL_PARAM_NAME
+#define CAvlEntry CAVL_PARAM_TYPE_ENTRY
+#define CAvlLink CAVL_PARAM_TYPE_LINK
+#define CAvlRef MERGE(CAVL_PARAM_NAME, Ref)
+#define CAvlArg CAVL_PARAM_TYPE_ARG
+#define CAvlKey CAVL_PARAM_TYPE_KEY
+#define CAvlCount CAVL_PARAM_TYPE_COUNT
+#define CAvlAssoc CAVL_PARAM_TYPE_ASSOC
+
+// non-object public functions
+#define CAvlIsNullRef MERGE(CAvl, IsNullRef)
+#define CAvlIsValidRef MERGE(CAvl, IsValidRef)
+#define CAvlDeref MERGE(CAvl, Deref)
+
+// public functions
+#define CAvl_Init MERGE(CAvl, _Init)
+#define CAvl_Insert MERGE(CAvl, _Insert)
+#define CAvl_InsertAt MERGE(CAvl, _InsertAt)
+#define CAvl_Remove MERGE(CAvl, _Remove)
+#define CAvl_Lookup MERGE(CAvl, _Lookup)
+#define CAvl_LookupExact MERGE(CAvl, _LookupExact)
+#define CAvl_GetFirstGreater MERGE(CAvl, _GetFirstGreater)
+#define CAvl_GetLastLesser MERGE(CAvl, _GetLastLesser)
+#define CAvl_GetFirstGreaterEqual MERGE(CAvl, _GetFirstGreaterEqual)
+#define CAvl_GetLastLesserEqual MERGE(CAvl, _GetLastLesserEqual)
+#define CAvl_GetFirst MERGE(CAvl, _GetFirst)
+#define CAvl_GetLast MERGE(CAvl, _GetLast)
+#define CAvl_GetNext MERGE(CAvl, _GetNext)
+#define CAvl_GetPrev MERGE(CAvl, _GetPrev)
+#define CAvl_IsEmpty MERGE(CAvl, _IsEmpty)
+#define CAvl_Verify MERGE(CAvl, _Verify)
+#define CAvl_Count MERGE(CAvl, _Count)
+#define CAvl_IndexOf MERGE(CAvl, _IndexOf)
+#define CAvl_GetAt MERGE(CAvl, _GetAt)
+#define CAvl_AssocSum MERGE(CAvl, _AssocSum)
+#define CAvl_ExclusiveAssocPrefixSum MERGE(CAvl, _ExclusiveAssocPrefixSum)
+#define CAvl_FindLastExclusiveAssocPrefixSumLesserEqual MERGE(CAvl, _FindLastExclusiveAssocPrefixSumLesserEqual)
+
+// private stuff
+#define CAvl_link(entry) ((entry).ptr->CAVL_PARAM_MEMBER_CHILD)
+#define CAvl_balance(entry) ((entry).ptr->CAVL_PARAM_MEMBER_BALANCE)
+#define CAvl_parent(entry) ((entry).ptr->CAVL_PARAM_MEMBER_PARENT)
+#define CAvl_count(entry) ((entry).ptr->CAVL_PARAM_MEMBER_COUNT)
+#define CAvl_assoc(entry) ((entry).ptr->CAVL_PARAM_MEMBER_ASSOC)
+#define CAvl_nulllink MERGE(CAvl, __nulllink)
+#define CAvl_nullref MERGE(CAvl, __nullref)
+#define CAvl_compare_entries MERGE(CAVL_PARAM_NAME, _compare_entries)
+#define CAvl_compare_key_entry MERGE(CAVL_PARAM_NAME, _compare_key_entry)
+#define CAvl_compute_node_assoc MERGE(CAVL_PARAM_NAME, _compute_node_assoc)
+#define CAvl_check_parent MERGE(CAVL_PARAM_NAME, _check_parent)
+#define CAvl_verify_recurser MERGE(CAVL_PARAM_NAME, _verify_recurser)
+#define CAvl_assert_tree MERGE(CAVL_PARAM_NAME, _assert_tree)
+#define CAvl_update_count_from_children MERGE(CAVL_PARAM_NAME, _update_count_from_children)
+#define CAvl_rotate MERGE(CAVL_PARAM_NAME, _rotate)
+#define CAvl_subtree_min MERGE(CAVL_PARAM_NAME, _subtree_min)
+#define CAvl_subtree_max MERGE(CAVL_PARAM_NAME, _subtree_max)
+#define CAvl_replace_subtree_fix_assoc MERGE(CAVL_PARAM_NAME, _replace_subtree_fix_counts)
+#define CAvl_swap_for_remove MERGE(CAVL_PARAM_NAME, _swap_entries)
+#define CAvl_rebalance MERGE(CAVL_PARAM_NAME, _rebalance)
+#define CAvl_child_count MERGE(CAvl, __child_count)
+#define CAvl_MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b))
+#define CAvl_OPTNEG(_a, _neg) ((_neg) ? -(_a) : (_a))
diff --git a/external/badvpn_dns/structure/CAvl_impl.h b/external/badvpn_dns/structure/CAvl_impl.h
new file mode 100644
index 0000000..984bdea
--- /dev/null
+++ b/external/badvpn_dns/structure/CAvl_impl.h
@@ -0,0 +1,949 @@
+/**
+ * @file CAvl_impl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "CAvl_header.h"
+
+static CAvlLink CAvl_nulllink (void)
+{
+    return CAVL_PARAM_VALUE_NULL;
+}
+
+static CAvlRef CAvl_nullref (void)
+{
+    CAvlRef n;
+    n.link = CAVL_PARAM_VALUE_NULL;
+    n.ptr = NULL;
+    return n;
+}
+
+#if !CAVL_PARAM_FEATURE_KEYS_ARE_INDICES
+
+static int CAvl_compare_entries (CAvlArg arg, CAvlRef node1, CAvlRef node2)
+{
+    int res = CAVL_PARAM_FUN_COMPARE_ENTRIES(arg, node1, node2);
+    ASSERT(res >= -1)
+    ASSERT(res <= 1)
+    
+    return res;
+}
+
+#if !CAVL_PARAM_FEATURE_NOKEYS
+
+static int CAvl_compare_key_entry (CAvlArg arg, CAvlKey key1, CAvlRef node2)
+{
+    int res = CAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, node2);
+    ASSERT(res >= -1)
+    ASSERT(res <= 1)
+    
+    return res;
+}
+
+#endif
+
+#endif
+
+#if CAVL_PARAM_FEATURE_ASSOC
+
+static CAvlAssoc CAvl_compute_node_assoc (CAvlArg arg, CAvlRef node)
+{
+    CAvlAssoc sum = CAVL_PARAM_FUN_ASSOC_VALUE(arg, node);
+    if (CAvl_link(node)[0] != CAvl_nulllink()) {
+        sum = CAVL_PARAM_FUN_ASSOC_OPER(arg, CAvl_assoc(CAvlDeref(arg, CAvl_link(node)[0])), sum);
+    }
+    if (CAvl_link(node)[1] != CAvl_nulllink()) {
+        sum = CAVL_PARAM_FUN_ASSOC_OPER(arg, sum, CAvl_assoc(CAvlDeref(arg, CAvl_link(node)[1])));
+    }
+    return sum;
+}
+
+#endif
+
+static int CAvl_check_parent (CAvlRef p, CAvlRef c)
+{
+    return (p.link == CAvl_parent(c)) && (p.link == CAvl_nulllink() || c.link == CAvl_link(p)[0] || c.link == CAvl_link(p)[1]);
+}
+
+static int CAvl_verify_recurser (CAvlArg arg, CAvlRef n)
+{
+    ASSERT_FORCE(CAvl_balance(n) >= -1)
+    ASSERT_FORCE(CAvl_balance(n) <= 1)
+    
+    int height_left = 0;
+    int height_right = 0;
+#if CAVL_PARAM_FEATURE_COUNTS
+    CAvlCount count_left = 0;
+    CAvlCount count_right = 0;
+#endif
+    
+    // check left subtree
+    if (CAvl_link(n)[0] != CAvl_nulllink()) {
+        // check parent link
+        ASSERT_FORCE(CAvl_parent(CAvlDeref(arg, CAvl_link(n)[0])) == n.link)
+        // check binary search tree
+#if !CAVL_PARAM_FEATURE_KEYS_ARE_INDICES
+        ASSERT_FORCE(CAvl_compare_entries(arg, CAvlDeref(arg, CAvl_link(n)[0]), n) == -1)
+#endif
+        // recursively calculate height
+        height_left = CAvl_verify_recurser(arg, CAvlDeref(arg, CAvl_link(n)[0]));
+#if CAVL_PARAM_FEATURE_COUNTS
+        count_left = CAvl_count(CAvlDeref(arg, CAvl_link(n)[0]));
+#endif
+    }
+    
+    // check right subtree
+    if (CAvl_link(n)[1] != CAvl_nulllink()) {
+        // check parent link
+        ASSERT_FORCE(CAvl_parent(CAvlDeref(arg, CAvl_link(n)[1])) == n.link)
+        // check binary search tree
+#if !CAVL_PARAM_FEATURE_KEYS_ARE_INDICES
+        ASSERT_FORCE(CAvl_compare_entries(arg, CAvlDeref(arg, CAvl_link(n)[1]), n) == 1)
+#endif
+        // recursively calculate height
+        height_right = CAvl_verify_recurser(arg, CAvlDeref(arg, CAvl_link(n)[1]));
+#if CAVL_PARAM_FEATURE_COUNTS
+        count_right = CAvl_count(CAvlDeref(arg, CAvl_link(n)[1]));
+#endif
+    }
+    
+    // check balance factor
+    ASSERT_FORCE(CAvl_balance(n) == height_right - height_left)
+    
+#if CAVL_PARAM_FEATURE_COUNTS
+    // check count
+    ASSERT_FORCE(CAvl_count(n) == 1 + count_left + count_right)
+#endif
+    
+#if CAVL_PARAM_FEATURE_ASSOC
+    // check assoc
+    ASSERT_FORCE(CAvl_assoc(n) == CAvl_compute_node_assoc(arg, n))
+#endif
+    
+    return CAvl_MAX(height_left, height_right) + 1;
+}
+
+static void CAvl_assert_tree (CAvl *o, CAvlArg arg)
+{
+#ifdef CAVL_AUTO_VERIFY
+    CAvl_Verify(o, arg);
+#endif
+}
+
+#if CAVL_PARAM_FEATURE_COUNTS
+static void CAvl_update_count_from_children (CAvlArg arg, CAvlRef n)
+{
+    CAvlCount left_count = CAvl_link(n)[0] != CAvl_nulllink() ? CAvl_count(CAvlDeref(arg, CAvl_link(n)[0])) : 0;
+    CAvlCount right_count = CAvl_link(n)[1] != CAvl_nulllink() ? CAvl_count(CAvlDeref(arg, CAvl_link(n)[1])) : 0;
+    CAvl_count(n) = 1 + left_count + right_count;
+}
+#endif
+
+static void CAvl_rotate (CAvl *o, CAvlArg arg, CAvlRef r, uint8_t dir, CAvlRef r_parent)
+{
+    ASSERT(CAvl_check_parent(r_parent, r))
+    CAvlRef nr = CAvlDeref(arg, CAvl_link(r)[!dir]);
+    
+    CAvl_link(r)[!dir] = CAvl_link(nr)[dir];
+    if (CAvl_link(r)[!dir] != CAvl_nulllink()) {
+        CAvl_parent(CAvlDeref(arg, CAvl_link(r)[!dir])) = r.link;
+    }
+    CAvl_link(nr)[dir] = r.link;
+    CAvl_parent(nr) = r_parent.link;
+    if (r_parent.link != CAvl_nulllink()) {
+        CAvl_link(r_parent)[r.link == CAvl_link(r_parent)[1]] = nr.link;
+    } else {
+        o->root = nr.link;
+    }
+    CAvl_parent(r) = nr.link;
+    
+#if CAVL_PARAM_FEATURE_COUNTS
+    CAvl_update_count_from_children(arg, r);
+    CAvl_update_count_from_children(arg, nr);
+#endif
+    
+#if CAVL_PARAM_FEATURE_ASSOC
+    CAvl_assoc(r) = CAvl_compute_node_assoc(arg, r);
+    CAvl_assoc(nr) = CAvl_compute_node_assoc(arg, nr);
+#endif
+}
+
+static CAvlRef CAvl_subtree_min (CAvlArg arg, CAvlRef n)
+{
+    ASSERT(n.link != CAvl_nulllink())
+    
+    while (CAvl_link(n)[0] != CAvl_nulllink()) {
+        n = CAvlDeref(arg, CAvl_link(n)[0]);
+    }
+    
+    return n;
+}
+
+static CAvlRef CAvl_subtree_max (CAvlArg arg, CAvlRef n)
+{
+    ASSERT(n.link != CAvl_nulllink())
+    
+    while (CAvl_link(n)[1] != CAvl_nulllink()) {
+        n = CAvlDeref(arg, CAvl_link(n)[1]);
+    }
+    
+    return n;
+}
+
+static void CAvl_replace_subtree_fix_assoc (CAvl *o, CAvlArg arg, CAvlRef dest, CAvlRef n, CAvlRef dest_parent)
+{
+    ASSERT(dest.link != CAvl_nulllink())
+    ASSERT(CAvl_check_parent(dest_parent, dest))
+    
+    if (dest_parent.link != CAvl_nulllink()) {
+        CAvl_link(dest_parent)[dest.link == CAvl_link(dest_parent)[1]] = n.link;
+    } else {
+        o->root = n.link;
+    }
+    if (n.link != CAvl_nulllink()) {
+        CAvl_parent(n) = CAvl_parent(dest);
+    }
+    
+#if CAVL_PARAM_FEATURE_COUNTS || CAVL_PARAM_FEATURE_ASSOC
+    for (CAvlRef c = dest_parent; c.link != CAvl_nulllink(); c = CAvlDeref(arg, CAvl_parent(c))) {
+#if CAVL_PARAM_FEATURE_COUNTS
+        ASSERT(CAvl_count(c) >= CAvl_count(dest))
+        CAvl_count(c) -= CAvl_count(dest);
+        if (n.link != CAvl_nulllink()) {
+            ASSERT(CAvl_count(n) <= CAVL_PARAM_VALUE_COUNT_MAX - CAvl_count(c))
+            CAvl_count(c) += CAvl_count(n);
+        }
+#endif
+#if CAVL_PARAM_FEATURE_ASSOC
+        CAvl_assoc(c) = CAvl_compute_node_assoc(arg, c);
+#endif
+    }
+#endif
+}
+
+static void CAvl_swap_for_remove (CAvl *o, CAvlArg arg, CAvlRef node, CAvlRef enode, CAvlRef node_parent, CAvlRef enode_parent)
+{
+    ASSERT(CAvl_check_parent(node_parent, node))
+    ASSERT(CAvl_check_parent(enode_parent, enode))
+    
+    if (enode_parent.link == node.link) {
+        // when the nodes are directly connected we need special handling
+        
+        uint8_t side = (enode.link == CAvl_link(node)[1]);
+        CAvlRef c = CAvlDeref(arg, CAvl_link(node)[!side]);
+        
+        if ((CAvl_link(node)[0] = CAvl_link(enode)[0]) != CAvl_nulllink()) {
+            CAvl_parent(CAvlDeref(arg, CAvl_link(node)[0])) = node.link;
+        }
+        if ((CAvl_link(node)[1] = CAvl_link(enode)[1]) != CAvl_nulllink()) {
+            CAvl_parent(CAvlDeref(arg, CAvl_link(node)[1])) = node.link;
+        }
+        
+        CAvl_parent(enode) = CAvl_parent(node);
+        if (node_parent.link != CAvl_nulllink()) {
+            CAvl_link(node_parent)[node.link == CAvl_link(node_parent)[1]] = enode.link;
+        } else {
+            o->root = enode.link;
+        }
+        
+        CAvl_link(enode)[side] = node.link;
+        CAvl_parent(node) = enode.link;
+        if ((CAvl_link(enode)[!side] = c.link) != CAvl_nulllink()) {
+            CAvl_parent(c) = enode.link;
+        }
+    } else {
+        CAvlRef temp;
+        
+        // swap parents
+        temp = node_parent;
+        CAvl_parent(node) = CAvl_parent(enode);
+        if (enode_parent.link != CAvl_nulllink()) {
+            CAvl_link(enode_parent)[enode.link == CAvl_link(enode_parent)[1]] = node.link;
+        } else {
+            o->root = node.link;
+        }
+        CAvl_parent(enode) = temp.link;
+        if (temp.link != CAvl_nulllink()) {
+            CAvl_link(temp)[node.link == CAvl_link(temp)[1]] = enode.link;
+        } else {
+            o->root = enode.link;
+        }
+        
+        // swap left children
+        temp = CAvlDeref(arg, CAvl_link(node)[0]);
+        if ((CAvl_link(node)[0] = CAvl_link(enode)[0]) != CAvl_nulllink()) {
+            CAvl_parent(CAvlDeref(arg, CAvl_link(node)[0])) = node.link;
+        }
+        if ((CAvl_link(enode)[0] = temp.link) != CAvl_nulllink()) {
+            CAvl_parent(CAvlDeref(arg, CAvl_link(enode)[0])) = enode.link;
+        }
+        
+        // swap right children
+        temp = CAvlDeref(arg, CAvl_link(node)[1]);
+        if ((CAvl_link(node)[1] = CAvl_link(enode)[1]) != CAvl_nulllink()) {
+            CAvl_parent(CAvlDeref(arg, CAvl_link(node)[1])) = node.link;
+        }
+        if ((CAvl_link(enode)[1] = temp.link) != CAvl_nulllink()) {
+            CAvl_parent(CAvlDeref(arg, CAvl_link(enode)[1])) = enode.link;
+        }
+    }
+    
+    // swap balance factors
+    int8_t b = CAvl_balance(node);
+    CAvl_balance(node) = CAvl_balance(enode);
+    CAvl_balance(enode) = b;
+    
+#if CAVL_PARAM_FEATURE_COUNTS
+    // swap counts
+    CAvlCount c = CAvl_count(node);
+    CAvl_count(node) = CAvl_count(enode);
+    CAvl_count(enode) = c;
+#endif
+    
+    // not fixing assoc values here because CAvl_replace_subtree_fix_assoc() will do it
+}
+
+static void CAvl_rebalance (CAvl *o, CAvlArg arg, CAvlRef node, uint8_t side, int8_t deltac)
+{
+    ASSERT(side == 0 || side == 1)
+    ASSERT(deltac >= -1 && deltac <= 1)
+    ASSERT(CAvl_balance(node) >= -1 && CAvl_balance(node) <= 1)
+    
+    // if no subtree changed its height, no more rebalancing is needed
+    if (deltac == 0) {
+        return;
+    }
+    
+    // calculate how much our height changed
+    int8_t delta = CAvl_MAX(deltac, CAvl_OPTNEG(CAvl_balance(node), side)) - CAvl_MAX(0, CAvl_OPTNEG(CAvl_balance(node), side));
+    ASSERT(delta >= -1 && delta <= 1)
+    
+    // update our balance factor
+    CAvl_balance(node) -= CAvl_OPTNEG(deltac, side);
+    
+    CAvlRef child;
+    CAvlRef gchild;
+    
+    // perform transformations if the balance factor is wrong
+    if (CAvl_balance(node) == 2 || CAvl_balance(node) == -2) {
+        uint8_t bside;
+        int8_t bsidef;
+        if (CAvl_balance(node) == 2) {
+            bside = 1;
+            bsidef = 1;
+        } else {
+            bside = 0;
+            bsidef = -1;
+        }
+        
+        ASSERT(CAvl_link(node)[bside] != CAvl_nulllink())
+        child = CAvlDeref(arg, CAvl_link(node)[bside]);
+        
+        switch (CAvl_balance(child) * bsidef) {
+            case 1:
+                CAvl_rotate(o, arg, node, !bside, CAvlDeref(arg, CAvl_parent(node)));
+                CAvl_balance(node) = 0;
+                CAvl_balance(child) = 0;
+                node = child;
+                delta -= 1;
+                break;
+            case 0:
+                CAvl_rotate(o, arg, node, !bside, CAvlDeref(arg, CAvl_parent(node)));
+                CAvl_balance(node) = 1 * bsidef;
+                CAvl_balance(child) = -1 * bsidef;
+                node = child;
+                break;
+            case -1:
+                ASSERT(CAvl_link(child)[!bside] != CAvl_nulllink())
+                gchild = CAvlDeref(arg, CAvl_link(child)[!bside]);
+                CAvl_rotate(o, arg, child, bside, node);
+                CAvl_rotate(o, arg, node, !bside, CAvlDeref(arg, CAvl_parent(node)));
+                CAvl_balance(node) = -CAvl_MAX(0, CAvl_balance(gchild) * bsidef) * bsidef;
+                CAvl_balance(child) = CAvl_MAX(0, -CAvl_balance(gchild) * bsidef) * bsidef;
+                CAvl_balance(gchild) = 0;
+                node = gchild;
+                delta -= 1;
+                break;
+            default:
+                ASSERT(0);
+        }
+    }
+    
+    ASSERT(delta >= -1 && delta <= 1)
+    // Transformations above preserve this. Proof:
+    //     - if a child subtree gained 1 height and rebalancing was needed,
+    //       it was the heavier subtree. Then delta was was originally 1, because
+    //       the heaviest subtree gained one height. If the transformation reduces
+    //       delta by one, it becomes 0.
+    //     - if a child subtree lost 1 height and rebalancing was needed, it
+    //       was the lighter subtree. Then delta was originally 0, because
+    //       the height of the heaviest subtree was unchanged. If the transformation
+    //       reduces delta by one, it becomes -1.
+    
+    if (CAvl_parent(node) != CAvl_nulllink()) {
+        CAvlRef node_parent = CAvlDeref(arg, CAvl_parent(node));
+        CAvl_rebalance(o, arg, node_parent, node.link == CAvl_link(node_parent)[1], delta);
+    }
+}
+
+#if CAVL_PARAM_FEATURE_KEYS_ARE_INDICES
+static CAvlCount CAvl_child_count (CAvlArg arg, CAvlRef n, int dir)
+{
+    return (CAvl_link(n)[dir] != CAvl_nulllink() ? CAvl_count(CAvlDeref(arg, CAvl_link(n)[dir])) : 0);
+}
+#endif
+
+static int CAvlIsNullRef (CAvlRef node)
+{
+    return node.link == CAvl_nulllink();
+}
+
+static int CAvlIsValidRef (CAvlRef node)
+{
+    return node.link != CAvl_nulllink();
+}
+
+static CAvlRef CAvlDeref (CAvlArg arg, CAvlLink link)
+{
+    if (link == CAvl_nulllink()) {
+        return CAvl_nullref();
+    }
+    
+    CAvlRef n;
+    n.ptr = CAVL_PARAM_FUN_DEREF(arg, link);
+    n.link = link;
+    
+    ASSERT(n.ptr)
+    
+    return n;
+}
+
+static void CAvl_Init (CAvl *o)
+{
+    o->root = CAvl_nulllink();
+}
+
+#if !CAVL_PARAM_FEATURE_KEYS_ARE_INDICES
+
+static int CAvl_Insert (CAvl *o, CAvlArg arg, CAvlRef node, CAvlRef *out_ref)
+{
+    ASSERT(node.link != CAvl_nulllink())
+#if CAVL_PARAM_FEATURE_COUNTS
+    ASSERT(CAvl_Count(o, arg) < CAVL_PARAM_VALUE_COUNT_MAX)
+#endif
+    
+    // insert to root?
+    if (o->root == CAvl_nulllink()) {
+        o->root = node.link;
+        CAvl_parent(node) = CAvl_nulllink();
+        CAvl_link(node)[0] = CAvl_nulllink();
+        CAvl_link(node)[1] = CAvl_nulllink();
+        CAvl_balance(node) = 0;
+#if CAVL_PARAM_FEATURE_COUNTS
+        CAvl_count(node) = 1;
+#endif
+#if CAVL_PARAM_FEATURE_ASSOC
+        CAvl_assoc(node) = CAVL_PARAM_FUN_ASSOC_VALUE(arg, node);
+#endif
+        
+        CAvl_assert_tree(o, arg);
+        
+        if (out_ref) {
+            *out_ref = CAvl_nullref();
+        }
+        return 1;
+    }
+    
+    CAvlRef c = CAvlDeref(arg, o->root);
+    int side;
+    while (1) {
+        int comp = CAvl_compare_entries(arg, node, c);
+        
+        if (comp == 0) {
+            if (out_ref) {
+                *out_ref = c;
+            }
+            return 0;
+        }
+        
+        side = (comp == 1);
+        
+        if (CAvl_link(c)[side] == CAvl_nulllink()) {
+            break;
+        }
+        
+        c = CAvlDeref(arg, CAvl_link(c)[side]);
+    }
+    
+    CAvl_link(c)[side] = node.link;
+    CAvl_parent(node) = c.link;
+    CAvl_link(node)[0] = CAvl_nulllink();
+    CAvl_link(node)[1] = CAvl_nulllink();
+    CAvl_balance(node) = 0;
+#if CAVL_PARAM_FEATURE_COUNTS
+    CAvl_count(node) = 1;
+#endif
+#if CAVL_PARAM_FEATURE_ASSOC
+    CAvl_assoc(node) = CAVL_PARAM_FUN_ASSOC_VALUE(arg, node);
+#endif
+    
+#if CAVL_PARAM_FEATURE_COUNTS || CAVL_PARAM_FEATURE_ASSOC
+    for (CAvlRef p = c; p.link != CAvl_nulllink(); p = CAvlDeref(arg, CAvl_parent(p))) {
+#if CAVL_PARAM_FEATURE_COUNTS
+        CAvl_count(p)++;
+#endif
+#if CAVL_PARAM_FEATURE_ASSOC
+        CAvl_assoc(p) = CAvl_compute_node_assoc(arg, p);
+#endif
+    }
+#endif
+    
+    CAvl_rebalance(o, arg, c, side, 1);
+    
+    CAvl_assert_tree(o, arg);
+    
+    if (out_ref) {
+        *out_ref = c;
+    }
+    return 1;
+}
+
+#else
+
+static void CAvl_InsertAt (CAvl *o, CAvlArg arg, CAvlRef node, CAvlCount index)
+{
+    ASSERT(node.link != CAvl_nulllink())
+    ASSERT(index <= CAvl_Count(o, arg))
+    ASSERT(CAvl_Count(o, arg) < CAVL_PARAM_VALUE_COUNT_MAX)
+    
+    // insert to root?
+    if (o->root == CAvl_nulllink()) {
+        o->root = node.link;
+        CAvl_parent(node) = CAvl_nulllink();
+        CAvl_link(node)[0] = CAvl_nulllink();
+        CAvl_link(node)[1] = CAvl_nulllink();
+        CAvl_balance(node) = 0;
+        CAvl_count(node) = 1;
+#if CAVL_PARAM_FEATURE_ASSOC
+        CAvl_assoc(node) = CAVL_PARAM_FUN_ASSOC_VALUE(arg, node);
+#endif
+        
+        CAvl_assert_tree(o, arg);
+        return;
+    }
+    
+    CAvlRef c = CAvlDeref(arg, o->root);
+    CAvlCount c_idx = CAvl_child_count(arg, c, 0);
+    int side;
+    while (1) {
+        side = (index > c_idx);
+        
+        if (CAvl_link(c)[side] == CAvl_nulllink()) {
+            break;
+        }
+        
+        c = CAvlDeref(arg, CAvl_link(c)[side]);
+        
+        if (side == 0) {
+            c_idx -= 1 + CAvl_child_count(arg, c, 1);
+        } else {
+            c_idx += 1 + CAvl_child_count(arg, c, 0);
+        }
+    }
+    
+    CAvl_link(c)[side] = node.link;
+    CAvl_parent(node) = c.link;
+    CAvl_link(node)[0] = CAvl_nulllink();
+    CAvl_link(node)[1] = CAvl_nulllink();
+    CAvl_balance(node) = 0;
+    CAvl_count(node) = 1;
+#if CAVL_PARAM_FEATURE_ASSOC
+    CAvl_assoc(node) = CAVL_PARAM_FUN_ASSOC_VALUE(arg, node);
+#endif
+    
+    for (CAvlRef p = c; p.link != CAvl_nulllink(); p = CAvlDeref(arg, CAvl_parent(p))) {
+        CAvl_count(p)++;
+#if CAVL_PARAM_FEATURE_ASSOC
+        CAvl_assoc(p) = CAvl_compute_node_assoc(arg, p);
+#endif
+    }
+    
+    CAvl_rebalance(o, arg, c, side, 1);
+    
+    CAvl_assert_tree(o, arg);
+    return;
+}
+
+#endif
+
+static void CAvl_Remove (CAvl *o, CAvlArg arg, CAvlRef node)
+{
+    ASSERT(node.link != CAvl_nulllink())
+    ASSERT(o->root != CAvl_nulllink())
+    
+    if (CAvl_link(node)[0] != CAvl_nulllink() && CAvl_link(node)[1] != CAvl_nulllink()) {
+        CAvlRef max = CAvl_subtree_max(arg, CAvlDeref(arg, CAvl_link(node)[0]));
+        CAvl_swap_for_remove(o, arg, node, max, CAvlDeref(arg, CAvl_parent(node)), CAvlDeref(arg, CAvl_parent(max)));
+    }
+    
+    ASSERT(CAvl_link(node)[0] == CAvl_nulllink() || CAvl_link(node)[1] == CAvl_nulllink())
+    
+    CAvlRef paren = CAvlDeref(arg, CAvl_parent(node));
+    CAvlRef child = (CAvl_link(node)[0] != CAvl_nulllink() ? CAvlDeref(arg, CAvl_link(node)[0]) : CAvlDeref(arg, CAvl_link(node)[1]));
+    
+    if (paren.link != CAvl_nulllink()) {
+        int side = (node.link == CAvl_link(paren)[1]);
+        CAvl_replace_subtree_fix_assoc(o, arg, node, child, paren);
+        CAvl_rebalance(o, arg, paren, side, -1);
+    } else {
+        CAvl_replace_subtree_fix_assoc(o, arg, node, child, paren);
+    }
+    
+    CAvl_assert_tree(o, arg);
+}
+
+#if !CAVL_PARAM_FEATURE_KEYS_ARE_INDICES && !CAVL_PARAM_FEATURE_NOKEYS
+
+static CAvlRef CAvl_Lookup (const CAvl *o, CAvlArg arg, CAvlKey key)
+{
+    if (o->root == CAvl_nulllink()) {
+        return CAvl_nullref();
+    }
+    
+    CAvlRef c = CAvlDeref(arg, o->root);
+    while (1) {
+        // compare
+        int comp = CAvl_compare_key_entry(arg, key, c);
+        
+        // have we found a node that compares equal?
+        if (comp == 0) {
+            return c;
+        }
+        
+        int side = (comp == 1);
+        
+        // have we reached a leaf?
+        if (CAvl_link(c)[side] == CAvl_nulllink()) {
+            return c;
+        }
+        
+        c = CAvlDeref(arg, CAvl_link(c)[side]);
+    }
+}
+
+static CAvlRef CAvl_LookupExact (const CAvl *o, CAvlArg arg, CAvlKey key)
+{
+    if (o->root == CAvl_nulllink()) {
+        return CAvl_nullref();
+    }
+    
+    CAvlRef c = CAvlDeref(arg, o->root);
+    while (1) {
+        // compare
+        int comp = CAvl_compare_key_entry(arg, key, c);
+        
+        // have we found a node that compares equal?
+        if (comp == 0) {
+            return c;
+        }
+        
+        int side = (comp == 1);
+        
+        // have we reached a leaf?
+        if (CAvl_link(c)[side] == CAvl_nulllink()) {
+            return CAvl_nullref();
+        }
+        
+        c = CAvlDeref(arg, CAvl_link(c)[side]);
+    }
+}
+
+static CAvlRef CAvl_GetFirstGreater (const CAvl *o, CAvlArg arg, CAvlKey key)
+{
+    CAvlRef c = CAvl_Lookup(o, arg, key);
+    if (CAvlIsNullRef(c)) {
+        return CAvl_nullref();
+    }
+    
+    if (CAvl_compare_key_entry(arg, key, c) >= 0) {
+        c = CAvl_GetNext(o, arg, c);
+        if (CAvlIsNullRef(c)) {
+            return CAvl_nullref();
+        }
+    }
+    
+    ASSERT(CAvl_compare_key_entry(arg, key, c) < 0);
+    
+    return c;
+}
+
+static CAvlRef CAvl_GetLastLesser (const CAvl *o, CAvlArg arg, CAvlKey key)
+{
+    CAvlRef c = CAvl_Lookup(o, arg, key);
+    if (CAvlIsNullRef(c)) {
+        return CAvl_nullref();
+    }
+    
+    if (CAvl_compare_key_entry(arg, key, c) <= 0) {
+        c = CAvl_GetPrev(o, arg, c);
+        if (CAvlIsNullRef(c)) {
+            return CAvl_nullref();
+        }
+    }
+    
+    ASSERT(CAvl_compare_key_entry(arg, key, c) > 0);
+    
+    return c;
+}
+
+static CAvlRef CAvl_GetFirstGreaterEqual (const CAvl *o, CAvlArg arg, CAvlKey key)
+{
+    CAvlRef c = CAvl_Lookup(o, arg, key);
+    if (CAvlIsNullRef(c)) {
+        return CAvl_nullref();
+    }
+    
+    if (CAvl_compare_key_entry(arg, key, c) > 0) {
+        c = CAvl_GetNext(o, arg, c);
+        if (CAvlIsNullRef(c)) {
+            return CAvl_nullref();
+        }
+    }
+    
+    ASSERT(CAvl_compare_key_entry(arg, key, c) <= 0);
+    
+    return c;
+}
+
+static CAvlRef CAvl_GetLastLesserEqual (const CAvl *o, CAvlArg arg, CAvlKey key)
+{
+    CAvlRef c = CAvl_Lookup(o, arg, key);
+    if (CAvlIsNullRef(c)) {
+        return CAvl_nullref();
+    }
+    
+    if (CAvl_compare_key_entry(arg, key, c) < 0) {
+        c = CAvl_GetPrev(o, arg, c);
+        if (CAvlIsNullRef(c)) {
+            return CAvl_nullref();
+        }
+    }
+    
+    ASSERT(CAvl_compare_key_entry(arg, key, c) >= 0);
+    
+    return c;
+}
+
+#endif
+
+static CAvlRef CAvl_GetFirst (const CAvl *o, CAvlArg arg)
+{
+    if (o->root == CAvl_nulllink()) {
+        return CAvl_nullref();
+    }
+    
+    return CAvl_subtree_min(arg, CAvlDeref(arg, o->root));
+}
+
+static CAvlRef CAvl_GetLast (const CAvl *o, CAvlArg arg)
+{
+    if (o->root == CAvl_nulllink()) {
+        return CAvl_nullref();
+    }
+    
+    return CAvl_subtree_max(arg, CAvlDeref(arg, o->root));
+}
+
+static CAvlRef CAvl_GetNext (const CAvl *o, CAvlArg arg, CAvlRef node)
+{
+    ASSERT(node.link != CAvl_nulllink())
+    ASSERT(o->root != CAvl_nulllink())
+    
+    if (CAvl_link(node)[1] != CAvl_nulllink()) {
+        node = CAvlDeref(arg, CAvl_link(node)[1]);
+        while (CAvl_link(node)[0] != CAvl_nulllink()) {
+            node = CAvlDeref(arg, CAvl_link(node)[0]);
+        }
+    } else {
+        while (CAvl_parent(node) != CAvl_nulllink() && node.link == CAvl_link(CAvlDeref(arg, CAvl_parent(node)))[1]) {
+            node = CAvlDeref(arg, CAvl_parent(node));
+        }
+        node = CAvlDeref(arg, CAvl_parent(node));
+    }
+    
+    return node;
+}
+
+static CAvlRef CAvl_GetPrev (const CAvl *o, CAvlArg arg, CAvlRef node)
+{
+    ASSERT(node.link != CAvl_nulllink())
+    ASSERT(o->root != CAvl_nulllink())
+    
+    if (CAvl_link(node)[0] != CAvl_nulllink()) {
+        node = CAvlDeref(arg, CAvl_link(node)[0]);
+        while (CAvl_link(node)[1] != CAvl_nulllink()) {
+            node = CAvlDeref(arg, CAvl_link(node)[1]);
+        }
+    } else {
+        while (CAvl_parent(node) != CAvl_nulllink() && node.link == CAvl_link(CAvlDeref(arg, CAvl_parent(node)))[0]) {
+            node = CAvlDeref(arg, CAvl_parent(node));
+        }
+        node = CAvlDeref(arg, CAvl_parent(node));
+    }
+    
+    return node;
+}
+
+static int CAvl_IsEmpty (const CAvl *o)
+{
+    return o->root == CAvl_nulllink();
+}
+
+static void CAvl_Verify (const CAvl *o, CAvlArg arg)
+{
+    if (o->root != CAvl_nulllink()) {
+        CAvlRef root = CAvlDeref(arg, o->root);
+        ASSERT(CAvl_parent(root) == CAvl_nulllink())
+        CAvl_verify_recurser(arg, root);
+    }
+}
+
+#if CAVL_PARAM_FEATURE_COUNTS
+
+static CAvlCount CAvl_Count (const CAvl *o, CAvlArg arg)
+{
+    return (o->root != CAvl_nulllink() ? CAvl_count(CAvlDeref(arg, o->root)) : 0);
+}
+
+static CAvlCount CAvl_IndexOf (const CAvl *o, CAvlArg arg, CAvlRef node)
+{
+    ASSERT(node.link != CAvl_nulllink())
+    ASSERT(o->root != CAvl_nulllink())
+    
+    CAvlCount index = (CAvl_link(node)[0] != CAvl_nulllink() ? CAvl_count(CAvlDeref(arg, CAvl_link(node)[0])) : 0);
+    
+    CAvlRef paren = CAvlDeref(arg, CAvl_parent(node));
+    
+    for (CAvlRef c = node; paren.link != CAvl_nulllink(); c = paren, paren = CAvlDeref(arg, CAvl_parent(c))) {
+        if (c.link == CAvl_link(paren)[1]) {
+            ASSERT(CAvl_count(paren) > CAvl_count(c))
+            ASSERT(CAvl_count(paren) - CAvl_count(c) <= CAVL_PARAM_VALUE_COUNT_MAX - index)
+            index += CAvl_count(paren) - CAvl_count(c);
+        }
+    }
+    
+    return index;
+}
+
+static CAvlRef CAvl_GetAt (const CAvl *o, CAvlArg arg, CAvlCount index)
+{
+    if (index >= CAvl_Count(o, arg)) {
+        return CAvl_nullref();
+    }
+    
+    CAvlRef c = CAvlDeref(arg, o->root);
+    
+    while (1) {
+        ASSERT(c.link != CAvl_nulllink())
+        ASSERT(index < CAvl_count(c))
+        
+        CAvlCount left_count = (CAvl_link(c)[0] != CAvl_nulllink() ? CAvl_count(CAvlDeref(arg, CAvl_link(c)[0])) : 0);
+        
+        if (index == left_count) {
+            return c;
+        }
+        
+        if (index < left_count) {
+            c = CAvlDeref(arg, CAvl_link(c)[0]);
+        } else {
+            c = CAvlDeref(arg, CAvl_link(c)[1]);
+            index -= left_count + 1;
+        }
+    }
+}
+
+#endif
+
+#if CAVL_PARAM_FEATURE_ASSOC
+
+static CAvlAssoc CAvl_AssocSum (const CAvl *o, CAvlArg arg)
+{
+    if (o->root == CAvl_nulllink()) {
+        return CAVL_PARAM_VALUE_ASSOC_ZERO;
+    }
+    CAvlRef root = CAvlDeref(arg, o->root);
+    return CAvl_assoc(root);
+}
+
+static CAvlAssoc CAvl_ExclusiveAssocPrefixSum (const CAvl *o, CAvlArg arg, CAvlRef node)
+{
+    ASSERT(node.link != CAvl_nulllink())
+    ASSERT(o->root != CAvl_nulllink())
+    
+    CAvlAssoc sum = (CAvl_link(node)[0] != CAvl_nulllink() ? CAvl_assoc(CAvlDeref(arg, CAvl_link(node)[0])) : CAVL_PARAM_VALUE_ASSOC_ZERO);
+    
+    CAvlRef paren = CAvlDeref(arg, CAvl_parent(node));
+    
+    for (CAvlRef c = node; paren.link != CAvl_nulllink(); c = paren, paren = CAvlDeref(arg, CAvl_parent(c))) {
+        if (c.link == CAvl_link(paren)[1]) {
+            CAvlAssoc c_val = CAVL_PARAM_FUN_ASSOC_VALUE(arg, paren);
+            sum = CAVL_PARAM_FUN_ASSOC_OPER(arg, c_val, sum);
+            if (CAvl_link(paren)[0] != CAvl_nulllink()) {
+                sum = CAVL_PARAM_FUN_ASSOC_OPER(arg, CAvl_assoc(CAvlDeref(arg, CAvl_link(paren)[0])), sum);
+            }
+        }
+    }
+    
+    return sum;
+}
+
+static CAvlRef CAvl_FindLastExclusiveAssocPrefixSumLesserEqual (const CAvl *o, CAvlArg arg, CAvlAssoc sum, int (*sum_less) (void *, CAvlAssoc, CAvlAssoc), void *user)
+{
+    CAvlRef result = CAvl_nullref();
+    CAvlRef c = CAvlDeref(arg, o->root);
+    CAvlAssoc sum_offset = CAVL_PARAM_VALUE_ASSOC_ZERO;
+    
+    while (c.link != CAvl_nulllink()) {
+        CAvlAssoc left_sum = (CAvl_link(c)[0] != CAvl_nulllink() ? CAvl_assoc(CAvlDeref(arg, CAvl_link(c)[0])) : CAVL_PARAM_VALUE_ASSOC_ZERO);
+        CAvlAssoc c_prefixsum = CAVL_PARAM_FUN_ASSOC_OPER(arg, sum_offset, left_sum);
+        
+        if (sum_less(user, sum, c_prefixsum)) {
+            c = CAvlDeref(arg, CAvl_link(c)[0]);
+        } else {
+            result = c;
+            CAvlAssoc c_val = CAVL_PARAM_FUN_ASSOC_VALUE(arg, c);
+            sum_offset = CAVL_PARAM_FUN_ASSOC_OPER(arg, c_prefixsum, c_val);
+            c = CAvlDeref(arg, CAvl_link(c)[1]);
+        }
+    }
+    
+    return result;
+}
+
+#endif
+
+#include "CAvl_footer.h"
diff --git a/external/badvpn_dns/structure/CHash.h b/external/badvpn_dns/structure/CHash.h
new file mode 100644
index 0000000..45fef7a
--- /dev/null
+++ b/external/badvpn_dns/structure/CHash.h
@@ -0,0 +1,39 @@
+/**
+ * @file CHash.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_CHASH_H
+#define BADVPN_CHASH_H
+
+#include <stdlib.h>
+
+#include <misc/debug.h>
+#include <misc/merge.h>
+#include <misc/balloc.h>
+
+#endif
diff --git a/external/badvpn_dns/structure/CHash_decl.h b/external/badvpn_dns/structure/CHash_decl.h
new file mode 100644
index 0000000..d47702a
--- /dev/null
+++ b/external/badvpn_dns/structure/CHash_decl.h
@@ -0,0 +1,59 @@
+/**
+ * @file CHash_decl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "CHash_header.h"
+
+typedef struct {
+    CHashLink *buckets;
+    size_t num_buckets;
+} CHash;
+
+typedef struct {
+    CHashEntry *ptr;
+    CHashLink link;
+} CHashRef;
+
+static CHashLink CHashNullLink (void);
+static CHashRef CHashNullRef (void);
+static int CHashIsNullLink (CHashLink link);
+static int CHashIsNullRef (CHashRef ref);
+static CHashRef CHashDerefMayNull (CHashArg arg, CHashLink link);
+static CHashRef CHashDerefNonNull (CHashArg arg, CHashLink link);
+
+static int CHash_Init (CHash *o, size_t num_buckets);
+static void CHash_Free (CHash *o);
+static int CHash_Insert (CHash *o, CHashArg arg, CHashRef entry, CHashRef *out_existing);
+static void CHash_InsertMulti (CHash *o, CHashArg arg, CHashRef entry);
+static void CHash_Remove (CHash *o, CHashArg arg, CHashRef entry);
+static CHashRef CHash_Lookup (const CHash *o, CHashArg arg, CHashKey key);
+static CHashRef CHash_GetNextEqual (const CHash *o, CHashArg arg, CHashRef entry);
+static int CHash_MultiplyBuckets (CHash *o, CHashArg arg, int exp);
+static void CHash_Verify (const CHash *o, CHashArg arg);
+
+#include "CHash_footer.h"
diff --git a/external/badvpn_dns/structure/CHash_footer.h b/external/badvpn_dns/structure/CHash_footer.h
new file mode 100644
index 0000000..cb95daf
--- /dev/null
+++ b/external/badvpn_dns/structure/CHash_footer.h
@@ -0,0 +1,74 @@
+/**
+ * @file CHash_footer.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+// preprocessor inputs
+#undef CHASH_PARAM_NAME
+#undef CHASH_PARAM_ENTRY
+#undef CHASH_PARAM_LINK
+#undef CHASH_PARAM_KEY
+#undef CHASH_PARAM_ARG
+#undef CHASH_PARAM_NULL
+#undef CHASH_PARAM_DEREF
+#undef CHASH_PARAM_ENTRYHASH
+#undef CHASH_PARAM_KEYHASH
+#undef CHASH_PARAM_ENTRYHASH_IS_CHEAP
+#undef CHASH_PARAM_COMPARE_ENTRIES
+#undef CHASH_PARAM_COMPARE_KEY_ENTRY
+#undef CHASH_PARAM_ENTRY_NEXT
+
+// types
+#undef CHash
+#undef CHashEntry
+#undef CHashLink
+#undef CHashRef
+#undef CHashArg
+#undef CHashKey
+
+// non-object public functions
+#undef CHashNullLink
+#undef CHashNullRef
+#undef CHashIsNullLink
+#undef CHashIsNullRef
+#undef CHashDerefMayNull
+#undef CHashDerefNonNull
+
+// public functions
+#undef CHash_Init
+#undef CHash_Free
+#undef CHash_Insert
+#undef CHash_InsertMulti
+#undef CHash_Remove
+#undef CHash_Lookup
+#undef CHash_GetNextEqual
+#undef CHash_MultiplyBuckets
+#undef CHash_Verify
+
+// private things
+#undef CHash_next
+#undef CHash_assert_valid_entry
diff --git a/external/badvpn_dns/structure/CHash_header.h b/external/badvpn_dns/structure/CHash_header.h
new file mode 100644
index 0000000..27800ac
--- /dev/null
+++ b/external/badvpn_dns/structure/CHash_header.h
@@ -0,0 +1,78 @@
+/**
+ * @file CHash_header.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+// Preprocessor inputs:
+// CHASH_PARAM_NAME - name of this data structure
+// CHASH_PARAM_ENTRY - type of entry
+// CHASH_PARAM_LINK - type of entry link (usually a pointer or index to an array)
+// CHASH_PARAM_KEY - type of key
+// CHASH_PARAM_ARG - type of argument pass through to comparisons
+// CHASH_PARAM_NULL - invalid link
+// CHASH_PARAM_DEREF(arg, link) - dereference a non-null link
+// CHASH_PARAM_ENTRYHASH(arg, entry) - hash function for entries; returns size_t
+// CHASH_PARAM_KEYHASH(arg, key) - hash function for keys; returns size_t
+// CHASH_PARAM_ENTRYHASH_IS_CHEAP - define to 1 if CHASH_PARAM_ENTRYHASH is cheap (e.g. hashes are precomputed)
+// CHASH_PARAM_COMPARE_ENTRIES(arg, entry1, entry2) - compares two entries; returns 1 for equality, 0 otherwise
+// CHASH_PARAM_COMPARE_KEY_ENTRY(arg, key1, entry2) - compares key and entry; returns 1 for equality, 0 otherwise
+// CHASH_PARAM_ENTRY_NEXT - next member in entry
+
+#ifndef BADVPN_CHASH_H
+#error CHash.h has not been included
+#endif
+
+// types
+#define CHash CHASH_PARAM_NAME
+#define CHashEntry CHASH_PARAM_ENTRY
+#define CHashLink CHASH_PARAM_LINK
+#define CHashRef MERGE(CHash, Ref)
+#define CHashArg CHASH_PARAM_ARG
+#define CHashKey CHASH_PARAM_KEY
+
+// non-object public functions
+#define CHashNullLink MERGE(CHash, NullLink)
+#define CHashNullRef MERGE(CHash, NullRef)
+#define CHashIsNullLink MERGE(CHash, IsNullLink)
+#define CHashIsNullRef MERGE(CHash, IsNullRef)
+#define CHashDerefMayNull MERGE(CHash, DerefMayNull)
+#define CHashDerefNonNull MERGE(CHash, DerefNonNull)
+
+// public functions
+#define CHash_Init MERGE(CHash, _Init)
+#define CHash_Free MERGE(CHash, _Free)
+#define CHash_Insert MERGE(CHash, _Insert)
+#define CHash_InsertMulti MERGE(CHash, _InsertMulti)
+#define CHash_Remove MERGE(CHash, _Remove)
+#define CHash_Lookup MERGE(CHash, _Lookup)
+#define CHash_GetNextEqual MERGE(CHash, _GetNextEqual)
+#define CHash_MultiplyBuckets MERGE(CHash, _MultiplyBuckets)
+#define CHash_Verify MERGE(CHash, _Verify)
+
+// private things
+#define CHash_next(entry) ((entry).ptr->CHASH_PARAM_ENTRY_NEXT)
+#define CHash_assert_valid_entry MERGE(CHash, _assert_valid_entry)
diff --git a/external/badvpn_dns/structure/CHash_impl.h b/external/badvpn_dns/structure/CHash_impl.h
new file mode 100644
index 0000000..0bded84
--- /dev/null
+++ b/external/badvpn_dns/structure/CHash_impl.h
@@ -0,0 +1,312 @@
+/**
+ * @file CHash_impl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "CHash_header.h"
+
+static void CHash_assert_valid_entry (CHashArg arg, CHashRef entry)
+{
+    ASSERT(entry.link != CHashNullLink())
+    ASSERT(entry.ptr == CHASH_PARAM_DEREF(arg, entry.link))
+}
+
+static CHashLink CHashNullLink (void)
+{
+    return CHASH_PARAM_NULL;
+}
+
+static CHashRef CHashNullRef (void)
+{
+    CHashRef entry = {NULL, CHashNullLink()};
+    return entry;
+}
+
+static int CHashIsNullLink (CHashLink link)
+{
+    return (link == CHashNullLink());
+}
+
+static int CHashIsNullRef (CHashRef ref)
+{
+    return CHashIsNullLink(ref.link);
+}
+
+static CHashRef CHashDerefMayNull (CHashArg arg, CHashLink link)
+{
+    if (link == CHashNullLink()) {
+        return CHashNullRef();
+    }
+    
+    CHashRef entry = {CHASH_PARAM_DEREF(arg, link), link};
+    ASSERT(entry.ptr)
+    
+    return entry;
+}
+
+static CHashRef CHashDerefNonNull (CHashArg arg, CHashLink link)
+{
+    ASSERT(link != CHashNullLink())
+    
+    CHashRef entry = {CHASH_PARAM_DEREF(arg, link), link};
+    ASSERT(entry.ptr)
+    
+    return entry;
+}
+
+static int CHash_Init (CHash *o, size_t num_buckets)
+{
+    if (num_buckets == 0) {
+        num_buckets = 1;
+    }
+    
+    o->num_buckets = num_buckets;
+    
+    o->buckets = (CHashLink *)BAllocArray(o->num_buckets, sizeof(o->buckets[0])); 
+    if (!o->buckets) {
+        return 0;
+    }
+    
+    for (size_t i = 0; i < o->num_buckets; i++) {
+        o->buckets[i] = CHashNullLink();
+    }
+    
+    return 1;
+}
+
+static void CHash_Free (CHash *o)
+{
+    BFree(o->buckets);
+}
+
+static int CHash_Insert (CHash *o, CHashArg arg, CHashRef entry, CHashRef *out_existing)
+{
+    CHash_assert_valid_entry(arg, entry);
+    
+    size_t index = CHASH_PARAM_ENTRYHASH(arg, entry) % o->num_buckets;
+    
+    CHashLink link = o->buckets[index];
+    while (link != CHashNullLink()) {
+        CHashRef cur = CHashDerefNonNull(arg, link);
+        if (CHASH_PARAM_COMPARE_ENTRIES(arg, cur, entry)) {
+            if (out_existing) {
+                *out_existing = cur;
+            }
+            return 0;
+        }
+        link = CHash_next(cur);
+    }
+    
+    CHash_next(entry) = o->buckets[index];
+    o->buckets[index] = entry.link;
+    
+    return 1;
+}
+
+static void CHash_InsertMulti (CHash *o, CHashArg arg, CHashRef entry)
+{
+    CHash_assert_valid_entry(arg, entry);
+    
+    size_t index = CHASH_PARAM_ENTRYHASH(arg, entry) % o->num_buckets;
+    
+    CHashRef prev = CHashNullRef();
+    CHashLink link = o->buckets[index];
+    while (link != CHashNullLink()) {
+        CHashRef cur = CHashDerefNonNull(arg, link);
+        if (CHASH_PARAM_COMPARE_ENTRIES(arg, cur, entry)) {
+            break;
+        }
+        prev = cur;
+        link = CHash_next(cur);
+    }
+    
+    if (link == CHashNullLink() || prev.link == CHashNullLink()) {
+        CHash_next(entry) = o->buckets[index];
+        o->buckets[index] = entry.link;
+    } else {
+        CHash_next(entry) = link;
+        CHash_next(prev) = entry.link;
+    }
+}
+
+static void CHash_Remove (CHash *o, CHashArg arg, CHashRef entry)
+{
+    CHash_assert_valid_entry(arg, entry);
+    
+    size_t index = CHASH_PARAM_ENTRYHASH(arg, entry) % o->num_buckets;
+    
+    CHashRef prev = CHashNullRef();
+    CHashLink link = o->buckets[index];
+    while (link != entry.link) {
+        CHashRef cur = CHashDerefNonNull(arg, link);
+        prev = cur;
+        link = CHash_next(cur);
+        ASSERT(link != CHashNullLink())
+    }
+    
+    if (prev.link == CHashNullLink()) {
+        o->buckets[index] = CHash_next(entry);
+    } else {
+        CHash_next(prev) = CHash_next(entry);
+    }
+}
+
+static CHashRef CHash_Lookup (const CHash *o, CHashArg arg, CHashKey key) 
+{
+    size_t hash = CHASH_PARAM_KEYHASH(arg, key);
+    size_t index = hash % o->num_buckets;
+    
+    CHashLink link = o->buckets[index];
+    while (link != CHashNullLink()) {
+        CHashRef cur = CHashDerefNonNull(arg, link);
+#if CHASH_PARAM_ENTRYHASH_IS_CHEAP
+        if (CHASH_PARAM_ENTRYHASH(arg, cur) == hash && CHASH_PARAM_COMPARE_KEY_ENTRY(arg, key, cur)) {
+#else
+        if (CHASH_PARAM_COMPARE_KEY_ENTRY(arg, key, cur)) {
+#endif
+            return cur;
+        }
+        link = CHash_next(cur);
+    }
+    
+    return CHashNullRef();
+}
+
+static CHashRef CHash_GetNextEqual (const CHash *o, CHashArg arg, CHashRef entry)
+{
+    CHash_assert_valid_entry(arg, entry);
+    
+    CHashLink next = CHash_next(entry);
+    
+    if (next == CHashNullLink()) {
+        return CHashNullRef();
+    }
+    
+    CHashRef next_ref = CHashDerefNonNull(arg, next);
+    if (!CHASH_PARAM_COMPARE_ENTRIES(arg, next_ref, entry)) {
+        return CHashNullRef();
+    }
+    
+    return next_ref;
+}
+
+static int CHash_MultiplyBuckets (CHash *o, CHashArg arg, int exp)
+{
+    ASSERT(exp > 0)
+    
+    size_t new_num_buckets = o->num_buckets;
+    while (exp-- > 0) {
+        if (new_num_buckets > SIZE_MAX / 2) {
+            return 0;
+        }
+        new_num_buckets *= 2;
+    }
+    
+    CHashLink *new_buckets = (CHashLink *)BReallocArray(o->buckets, new_num_buckets, sizeof(new_buckets[0]));
+    if (!new_buckets) {
+        return 0;
+    }
+    o->buckets = new_buckets;
+    
+    for (size_t i = o->num_buckets; i < new_num_buckets; i++) {
+        o->buckets[i] = CHashNullLink();
+    }
+    
+    for (size_t i = 0; i < o->num_buckets; i++) {
+        CHashRef prev = CHashNullRef();
+        CHashLink link = o->buckets[i];
+        
+        while (link != CHashNullLink()) {
+            CHashRef cur = CHashDerefNonNull(arg, link);
+            link = CHash_next(cur);
+            
+            size_t new_index = CHASH_PARAM_ENTRYHASH(arg, cur) % new_num_buckets;
+            if (new_index == i) {
+                prev = cur;
+                continue;
+            }
+            
+            if (CHashIsNullRef(prev)) {
+                o->buckets[i] = CHash_next(cur);
+            } else {
+                CHash_next(prev) = CHash_next(cur);
+            }
+            
+            CHash_next(cur) = o->buckets[new_index];
+            o->buckets[new_index] = cur.link;
+        }
+    }
+    
+    for (size_t i = o->num_buckets; i < new_num_buckets; i++) {
+        CHashLink new_bucket_link = CHashNullLink();
+        
+        CHashLink link = o->buckets[i];
+        while (link != CHashNullLink()) {
+            CHashRef cur = CHashDerefNonNull(arg, link);
+            link = CHash_next(cur);
+            
+            CHash_next(cur) = new_bucket_link;
+            new_bucket_link = cur.link;
+        }
+        
+        o->buckets[i] = new_bucket_link;
+    }
+    
+    o->num_buckets = new_num_buckets;
+    
+    return 1;
+}
+
+static void CHash_Verify (const CHash *o, CHashArg arg)
+{
+    ASSERT_FORCE(o->num_buckets > 0)
+    ASSERT_FORCE(o->buckets)
+    
+    for (size_t i = 0; i < o->num_buckets; i++) {
+        CHashRef cur = CHashDerefMayNull(arg, o->buckets[i]);
+        CHashRef same_first = cur;
+        
+        while (!CHashIsNullRef(cur)) {
+            size_t index = CHASH_PARAM_ENTRYHASH(arg, cur) % o->num_buckets;
+            ASSERT_FORCE(index == i)
+            
+            if (!CHASH_PARAM_COMPARE_ENTRIES(arg, cur, same_first)) {
+                same_first = cur;
+            }
+            
+            CHashRef ccur = CHashDerefNonNull(arg, o->buckets[i]);
+            while (ccur.link != same_first.link) {
+                ASSERT_FORCE(!CHASH_PARAM_COMPARE_ENTRIES(arg, ccur, cur))
+                ccur = CHashDerefMayNull(arg, CHash_next(ccur));
+            }
+            
+            cur = CHashDerefMayNull(arg, CHash_next(cur));
+        }
+    }
+}
+
+#include "CHash_footer.h"
diff --git a/external/badvpn_dns/structure/ChunkBuffer2.h b/external/badvpn_dns/structure/ChunkBuffer2.h
new file mode 100644
index 0000000..98073ad
--- /dev/null
+++ b/external/badvpn_dns/structure/ChunkBuffer2.h
@@ -0,0 +1,317 @@
+/**
+ * @file ChunkBuffer2.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Circular packet buffer
+ */
+
+#ifndef BADVPN_STRUCTURE_CHUNKBUFFER2_H
+#define BADVPN_STRUCTURE_CHUNKBUFFER2_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include <misc/balign.h>
+#include <misc/debug.h>
+
+#ifndef NDEBUG
+#define CHUNKBUFFER2_ASSERT_BUFFER(_buf) _ChunkBuffer2_assert_buffer(_buf);
+#define CHUNKBUFFER2_ASSERT_IO(_buf) _ChunkBuffer2_assert_io(_buf);
+#else
+#define CHUNKBUFFER2_ASSERT_BUFFER(_buf)
+#define CHUNKBUFFER2_ASSERT_IO(_buf)
+#endif
+
+struct ChunkBuffer2_block {
+    int len;
+};
+
+typedef struct {
+    struct ChunkBuffer2_block *buffer;
+    int size;
+    int wrap;
+    int start;
+    int used;
+    int mtu;
+    uint8_t *input_dest;
+    int input_avail;
+    uint8_t *output_dest;
+    int output_avail;
+} ChunkBuffer2;
+
+// calculates a buffer size needed to hold at least 'num' packets long at least 'chunk_len'
+static int ChunkBuffer2_calc_blocks (int chunk_len, int num);
+
+// initialize
+static void ChunkBuffer2_Init (ChunkBuffer2 *buf, struct ChunkBuffer2_block *buffer, int blocks, int mtu);
+
+// submit a packet written to the buffer
+static void ChunkBuffer2_SubmitPacket (ChunkBuffer2 *buf, int len);
+
+// remove the first packet
+static void ChunkBuffer2_ConsumePacket (ChunkBuffer2 *buf);
+
+static int _ChunkBuffer2_end (ChunkBuffer2 *buf)
+{
+    if (buf->used >= buf->wrap - buf->start) {
+        return (buf->used - (buf->wrap - buf->start));
+    } else {
+        return (buf->start + buf->used);
+    }
+}
+
+#ifndef NDEBUG
+
+static void _ChunkBuffer2_assert_buffer (ChunkBuffer2 *buf)
+{
+    ASSERT(buf->size > 0)
+    ASSERT(buf->wrap > 0)
+    ASSERT(buf->wrap <= buf->size)
+    ASSERT(buf->start >= 0)
+    ASSERT(buf->start < buf->wrap)
+    ASSERT(buf->used >= 0)
+    ASSERT(buf->used <= buf->wrap)
+    ASSERT(buf->wrap == buf->size || buf->used >= buf->wrap - buf->start)
+    ASSERT(buf->mtu >= 0)
+}
+
+static void _ChunkBuffer2_assert_io (ChunkBuffer2 *buf)
+{
+    // check input
+    
+    int end = _ChunkBuffer2_end(buf);
+    
+    if (buf->size - end - 1 < buf->mtu) {
+        // it will never be possible to write a MTU long packet here
+        ASSERT(!buf->input_dest)
+        ASSERT(buf->input_avail == -1)
+    } else {
+        // calculate number of free blocks
+        int free;
+        if (buf->used >= buf->wrap - buf->start) {
+            free = buf->start - end;
+        } else {
+            free = buf->size - end;
+        }
+        
+        if (free > 0) {
+            // got space at least for a header. More space will become available as packets are
+            // read from the buffer, up to MTU.
+            ASSERT(buf->input_dest == (uint8_t *)&buf->buffer[end + 1])
+            ASSERT(buf->input_avail == (free - 1) * sizeof(struct ChunkBuffer2_block))
+        } else {
+            // no space
+            ASSERT(!buf->input_dest)
+            ASSERT(buf->input_avail == -1)
+        }
+    }
+    
+    // check output
+    
+    if (buf->used > 0) {
+        int datalen = buf->buffer[buf->start].len;
+        ASSERT(datalen >= 0)
+        int blocklen = bdivide_up(datalen, sizeof(struct ChunkBuffer2_block));
+        ASSERT(blocklen <= buf->used - 1)
+        ASSERT(blocklen <= buf->wrap - buf->start - 1)
+        ASSERT(buf->output_dest == (uint8_t *)&buf->buffer[buf->start + 1])
+        ASSERT(buf->output_avail == datalen)
+    } else {
+        ASSERT(!buf->output_dest)
+        ASSERT(buf->output_avail == -1)
+    }
+}
+
+#endif
+
+static void _ChunkBuffer2_update_input (ChunkBuffer2 *buf)
+{
+    int end = _ChunkBuffer2_end(buf);
+    
+    if (buf->size - end - 1 < buf->mtu) {
+        // it will never be possible to write a MTU long packet here
+        buf->input_dest = NULL;
+        buf->input_avail = -1;
+        return;
+    }
+    
+    // calculate number of free blocks
+    int free;
+    if (buf->used >= buf->wrap - buf->start) {
+        free = buf->start - end;
+    } else {
+        free = buf->size - end;
+    }
+    
+    if (free > 0) {
+        // got space at least for a header. More space will become available as packets are
+        // read from the buffer, up to MTU.
+        buf->input_dest = (uint8_t *)&buf->buffer[end + 1];
+        buf->input_avail = (free - 1) * sizeof(struct ChunkBuffer2_block);
+    } else {
+        // no space
+        buf->input_dest = NULL;
+        buf->input_avail = -1;
+    }
+}
+
+static void _ChunkBuffer2_update_output (ChunkBuffer2 *buf)
+{
+    if (buf->used > 0) {
+        int datalen = buf->buffer[buf->start].len;
+        ASSERT(datalen >= 0)
+#ifndef NDEBUG
+        int blocklen = bdivide_up(datalen, sizeof(struct ChunkBuffer2_block));
+        ASSERT(blocklen <= buf->used - 1)
+        ASSERT(blocklen <= buf->wrap - buf->start - 1)
+#endif
+        buf->output_dest = (uint8_t *)&buf->buffer[buf->start + 1];
+        buf->output_avail = datalen;
+    } else {
+        buf->output_dest = NULL;
+        buf->output_avail = -1;
+    }
+}
+
+int ChunkBuffer2_calc_blocks (int chunk_len, int num)
+{
+    int chunk_data_blocks = bdivide_up(chunk_len, sizeof(struct ChunkBuffer2_block));
+    
+    if (chunk_data_blocks > INT_MAX - 1) {
+        return -1;
+    }
+    int chunk_blocks = 1 + chunk_data_blocks;
+    
+    if (num > INT_MAX - 1) {
+        return -1;
+    }
+    int num_chunks = num + 1;
+    
+    if (chunk_blocks > INT_MAX / num_chunks) {
+        return -1;
+    }
+    int blocks = chunk_blocks * num_chunks;
+    
+    return blocks;
+}
+
+void ChunkBuffer2_Init (ChunkBuffer2 *buf, struct ChunkBuffer2_block *buffer, int blocks, int mtu)
+{
+    ASSERT(blocks > 0)
+    ASSERT(mtu >= 0)
+    
+    buf->buffer = buffer;
+    buf->size = blocks;
+    buf->wrap = blocks;
+    buf->start = 0;
+    buf->used = 0;
+    buf->mtu = bdivide_up(mtu, sizeof(struct ChunkBuffer2_block));
+    
+    CHUNKBUFFER2_ASSERT_BUFFER(buf)
+    
+    _ChunkBuffer2_update_input(buf);
+    _ChunkBuffer2_update_output(buf);
+    
+    CHUNKBUFFER2_ASSERT_IO(buf)
+}
+
+void ChunkBuffer2_SubmitPacket (ChunkBuffer2 *buf, int len)
+{
+    ASSERT(buf->input_dest)
+    ASSERT(len >= 0)
+    ASSERT(len <= buf->input_avail)
+    
+    CHUNKBUFFER2_ASSERT_BUFFER(buf)
+    CHUNKBUFFER2_ASSERT_IO(buf)
+    
+    int end = _ChunkBuffer2_end(buf);
+    int blocklen = bdivide_up(len, sizeof(struct ChunkBuffer2_block));
+    
+    ASSERT(blocklen <= buf->size - end - 1)
+    ASSERT(buf->used < buf->wrap - buf->start || blocklen <= buf->start - end - 1)
+    
+    buf->buffer[end].len = len;
+    buf->used += 1 + blocklen;
+    
+    if (buf->used <= buf->wrap - buf->start && buf->mtu > buf->size - (end + 1 + blocklen) - 1) {
+        buf->wrap = end + 1 + blocklen;
+    }
+    
+    CHUNKBUFFER2_ASSERT_BUFFER(buf)
+    
+    // update input
+    _ChunkBuffer2_update_input(buf);
+    
+    // update output
+    if (buf->used == 1 + blocklen) {
+        _ChunkBuffer2_update_output(buf);
+    }
+    
+    CHUNKBUFFER2_ASSERT_IO(buf)
+}
+
+void ChunkBuffer2_ConsumePacket (ChunkBuffer2 *buf)
+{
+    ASSERT(buf->output_dest)
+    
+    CHUNKBUFFER2_ASSERT_BUFFER(buf)
+    CHUNKBUFFER2_ASSERT_IO(buf)
+    
+    ASSERT(1 <= buf->wrap - buf->start)
+    ASSERT(1 <= buf->used)
+    
+    int blocklen = bdivide_up(buf->buffer[buf->start].len, sizeof(struct ChunkBuffer2_block));
+    
+    ASSERT(blocklen <= buf->wrap - buf->start - 1)
+    ASSERT(blocklen <= buf->used - 1)
+    
+    int data_wrapped = (buf->used >= buf->wrap - buf->start);
+    
+    buf->start += 1 + blocklen;
+    buf->used -= 1 + blocklen;
+    if (buf->start == buf->wrap) {
+        buf->start = 0;
+        buf->wrap = buf->size;
+    }
+    
+    CHUNKBUFFER2_ASSERT_BUFFER(buf)
+    
+    // update input
+    if (data_wrapped) {
+        _ChunkBuffer2_update_input(buf);
+    }
+    
+    // update output
+    _ChunkBuffer2_update_output(buf);
+    
+    CHUNKBUFFER2_ASSERT_IO(buf)
+}
+
+#endif
diff --git a/external/badvpn_dns/structure/IndexedList.h b/external/badvpn_dns/structure/IndexedList.h
new file mode 100644
index 0000000..ca611e9
--- /dev/null
+++ b/external/badvpn_dns/structure/IndexedList.h
@@ -0,0 +1,225 @@
+/**
+ * @file IndexedList.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 data structure similar to a list, but with efficient index-based access.
+ */
+
+#ifndef BADVPN_INDEXEDLIST_H
+#define BADVPN_INDEXEDLIST_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <structure/CAvl.h>
+
+typedef struct IndexedList_s IndexedList;
+typedef struct IndexedListNode_s IndexedListNode;
+
+typedef IndexedListNode *IndexedList__tree_link;
+
+#include "IndexedList_tree.h"
+#include <structure/CAvl_decl.h>
+
+struct IndexedList_s {
+    IndexedList__Tree tree;
+};
+
+struct IndexedListNode_s {
+    IndexedListNode *tree_child[2];
+    IndexedListNode *tree_parent;
+    int8_t tree_balance;
+    uint64_t tree_count;
+};
+
+/**
+ * Initializes the indexed list.
+ * 
+ * @param o uninitialized list object to initialize
+ */
+static void IndexedList_Init (IndexedList *o);
+
+/**
+ * Inserts a node into the indexed list.
+ * 
+ * @param o indexed list to insert into
+ * @param node uninitialized node to insert
+ * @param index index to insert at (starting with zero). Any existing elements
+ *              at or after this index will be shifted forward, i.e. their
+ *              indices will be incremented by one. Must be <=count.
+ */
+static void IndexedList_InsertAt (IndexedList *o, IndexedListNode *node, uint64_t index);
+
+/**
+ * Removes a nove from the indexed list.
+ * 
+ * @param o indexed list to remove from
+ * @param node node in the list to remove
+ */
+static void IndexedList_Remove (IndexedList *o, IndexedListNode *node);
+
+/**
+ * Returns the number of nodes in the indexed list.
+ * 
+ * @param o indexed list
+ * @return number of nodes
+ */
+static uint64_t IndexedList_Count (IndexedList *o);
+
+/**
+ * Returns the index of a node in the indexed list.
+ * 
+ * @param o indexed list
+ * @param node node in the list to get index of
+ * @return index of the node
+ */
+static uint64_t IndexedList_IndexOf (IndexedList *o, IndexedListNode *node);
+
+/**
+ * Returns the node at the specified index in the indexed list.
+ * 
+ * @param o indexed list
+ * @param index index of the node to return. Must be < count.
+ * @return node at the specified index
+ */
+static IndexedListNode * IndexedList_GetAt (IndexedList *o, uint64_t index);
+
+/**
+ * Returns the first node, or NULL if the list is empty.
+ * 
+ * @param o indexed list
+ * @return first node, or NULL
+ */
+static IndexedListNode * IndexedList_GetFirst (IndexedList *o);
+
+/**
+ * Returns the last node, or NULL if the list is empty.
+ * 
+ * @param o indexed list
+ * @return last node, or NULL
+ */
+static IndexedListNode * IndexedList_GetLast (IndexedList *o);
+
+/**
+ * Returns the next node of a given node, or NULL this is the last node.
+ * 
+ * @param o indexed list
+ * @param node existing node
+ * @return next node, or NULL
+ */
+static IndexedListNode * IndexedList_GetNext (IndexedList *o, IndexedListNode *node);
+
+/**
+ * Returns the previous node of a given node, or NULL this is the first node.
+ * 
+ * @param o indexed list
+ * @param node existing node
+ * @return previous node, or NULL
+ */
+static IndexedListNode * IndexedList_GetPrev (IndexedList *o, IndexedListNode *node);
+
+#include "IndexedList_tree.h"
+#include <structure/CAvl_impl.h>
+
+static IndexedListNode * IndexedList__deref (IndexedList__TreeRef ref)
+{
+    return ref.link;
+}
+
+static void IndexedList_Init (IndexedList *o)
+{
+    IndexedList__Tree_Init(&o->tree);
+}
+
+static void IndexedList_InsertAt (IndexedList *o, IndexedListNode *node, uint64_t index)
+{
+    ASSERT(index <= IndexedList__Tree_Count(&o->tree, 0))
+    ASSERT(IndexedList__Tree_Count(&o->tree, 0) < UINT64_MAX - 1)
+    
+    uint64_t orig_count = IndexedList__Tree_Count(&o->tree, 0);
+    B_USE(orig_count)
+    
+    IndexedList__Tree_InsertAt(&o->tree, 0, IndexedList__TreeDeref(0, node), index);
+    
+    ASSERT(IndexedList__Tree_IndexOf(&o->tree, 0, IndexedList__TreeDeref(0, node)) == index)
+    ASSERT(IndexedList__Tree_Count(&o->tree, 0) == orig_count + 1)
+}
+
+static void IndexedList_Remove (IndexedList *o, IndexedListNode *node)
+{
+    IndexedList__Tree_Remove(&o->tree, 0, IndexedList__TreeDeref(0, node));
+}
+
+static uint64_t IndexedList_Count (IndexedList *o)
+{
+    return IndexedList__Tree_Count(&o->tree, 0);
+}
+
+static uint64_t IndexedList_IndexOf (IndexedList *o, IndexedListNode *node)
+{
+    return IndexedList__Tree_IndexOf(&o->tree, 0, IndexedList__TreeDeref(0, node));
+}
+
+static IndexedListNode * IndexedList_GetAt (IndexedList *o, uint64_t index)
+{
+    ASSERT(index < IndexedList__Tree_Count(&o->tree, 0))
+    
+    IndexedList__TreeRef ref = IndexedList__Tree_GetAt(&o->tree, 0, index);
+    ASSERT(!IndexedList__TreeIsNullRef(ref))
+    
+    return ref.ptr;
+}
+
+static IndexedListNode * IndexedList_GetFirst (IndexedList *o)
+{
+    return IndexedList__deref(IndexedList__Tree_GetFirst(&o->tree, 0));
+}
+
+static IndexedListNode * IndexedList_GetLast (IndexedList *o)
+{
+    return IndexedList__deref(IndexedList__Tree_GetLast(&o->tree, 0));
+}
+
+static IndexedListNode * IndexedList_GetNext (IndexedList *o, IndexedListNode *node)
+{
+    ASSERT(node)
+    
+    return IndexedList__deref(IndexedList__Tree_GetNext(&o->tree, 0, IndexedList__TreeDeref(0, node)));
+}
+
+static IndexedListNode * IndexedList_GetPrev (IndexedList *o, IndexedListNode *node)
+{
+    ASSERT(node)
+    
+    return IndexedList__deref(IndexedList__Tree_GetPrev(&o->tree, 0, IndexedList__TreeDeref(0, node)));
+}
+
+#endif
diff --git a/external/badvpn_dns/structure/IndexedList_tree.h b/external/badvpn_dns/structure/IndexedList_tree.h
new file mode 100644
index 0000000..130f00f
--- /dev/null
+++ b/external/badvpn_dns/structure/IndexedList_tree.h
@@ -0,0 +1,15 @@
+#define CAVL_PARAM_NAME IndexedList__Tree
+#define CAVL_PARAM_FEATURE_COUNTS 1
+#define CAVL_PARAM_FEATURE_KEYS_ARE_INDICES 1
+#define CAVL_PARAM_FEATURE_NOKEYS 0
+#define CAVL_PARAM_TYPE_ENTRY IndexedListNode
+#define CAVL_PARAM_TYPE_LINK IndexedList__tree_link
+#define CAVL_PARAM_TYPE_ARG int
+#define CAVL_PARAM_TYPE_COUNT uint64_t
+#define CAVL_PARAM_VALUE_COUNT_MAX UINT64_MAX
+#define CAVL_PARAM_VALUE_NULL ((IndexedList__tree_link)NULL)
+#define CAVL_PARAM_FUN_DEREF(arg, link) (link)
+#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
diff --git a/external/badvpn_dns/structure/LinkedList0.h b/external/badvpn_dns/structure/LinkedList0.h
new file mode 100644
index 0000000..feef4d7
--- /dev/null
+++ b/external/badvpn_dns/structure/LinkedList0.h
@@ -0,0 +1,202 @@
+/**
+ * @file LinkedList0.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Very simple doubly linked list, with only a 'first' pointer an no 'last'
+ * pointer.
+ */
+
+#ifndef BADVPN_STRUCTURE_LINKEDLIST0_H
+#define BADVPN_STRUCTURE_LINKEDLIST0_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+
+/**
+ * Linked list node.
+ */
+typedef struct LinkedList0Node_t
+{
+    struct LinkedList0Node_t *p;
+    struct LinkedList0Node_t *n;
+} LinkedList0Node;
+
+/**
+ * Simple doubly linked list.
+ */
+typedef struct
+{
+    LinkedList0Node *first;
+} LinkedList0;
+
+/**
+ * Initializes the linked list.
+ * 
+ * @param list list to initialize
+ */
+static void LinkedList0_Init (LinkedList0 *list);
+
+/**
+ * Determines if the list is empty.
+ * 
+ * @param list the list
+ * @return 1 if empty, 0 if not
+ */
+static int LinkedList0_IsEmpty (LinkedList0 *list);
+
+/**
+ * Returns the first node of the list.
+ * 
+ * @param list the list
+ * @return first node of the list, or NULL if the list is empty
+ */
+static LinkedList0Node * LinkedList0_GetFirst (LinkedList0 *list);
+
+/**
+ * Inserts a node to the beginning of the list.
+ * 
+ * @param list the list
+ * @param node uninitialized node to insert
+ */
+static void LinkedList0_Prepend (LinkedList0 *list, LinkedList0Node *node);
+
+/**
+ * Inserts a node before a given node.
+ * 
+ * @param list the list
+ * @param node uninitialized node to insert
+ * @param target node in the list to insert before
+ */
+static void LinkedList0_InsertBefore (LinkedList0 *list, LinkedList0Node *node, LinkedList0Node *target);
+
+/**
+ * Inserts a node after a given node.
+ * 
+ * @param list the list
+ * @param node uninitialized node to insert
+ * @param target node in the list to insert after
+ */
+static void LinkedList0_InsertAfter (LinkedList0 *list, LinkedList0Node *node, LinkedList0Node *target);
+
+/**
+ * Removes a node from the list.
+ * 
+ * @param list the list
+ * @param node node to remove
+ */
+static void LinkedList0_Remove (LinkedList0 *list, LinkedList0Node *node);
+
+/**
+ * Returns the next node of a given node.
+ * 
+ * @param node reference node
+ * @return next node, or NULL if none
+ */
+static LinkedList0Node * LinkedList0Node_Next (LinkedList0Node *node);
+
+/**
+ * Returns the previous node of a given node.
+ * 
+ * @param node reference node
+ * @return previous node, or NULL if none
+ */
+static LinkedList0Node * LinkedList0Node_Prev (LinkedList0Node *node);
+
+void LinkedList0_Init (LinkedList0 *list)
+{
+    list->first = NULL;
+}
+
+int LinkedList0_IsEmpty (LinkedList0 *list)
+{
+    return (!list->first);
+}
+
+LinkedList0Node * LinkedList0_GetFirst (LinkedList0 *list)
+{
+    return (list->first);
+}
+
+void LinkedList0_Prepend (LinkedList0 *list, LinkedList0Node *node)
+{
+    node->p = NULL;
+    node->n = list->first;
+    if (list->first) {
+        list->first->p = node;
+    }
+    list->first = node;
+}
+
+void LinkedList0_InsertBefore (LinkedList0 *list, LinkedList0Node *node, LinkedList0Node *target)
+{
+    node->p = target->p;
+    node->n = target;
+    if (target->p) {
+        target->p->n = node;
+    } else {
+        list->first = node;
+    }
+    target->p = node;
+}
+
+void LinkedList0_InsertAfter (LinkedList0 *list, LinkedList0Node *node, LinkedList0Node *target)
+{
+    node->p = target;
+    node->n = target->n;
+    if (target->n) {
+        target->n->p = node;
+    }
+    target->n = node;
+}
+
+void LinkedList0_Remove (LinkedList0 *list, LinkedList0Node *node)
+{
+    // remove from list
+    if (node->p) {
+        node->p->n = node->n;
+    } else {
+        list->first = node->n;
+    }
+    if (node->n) {
+        node->n->p = node->p;
+    }
+}
+
+LinkedList0Node * LinkedList0Node_Next (LinkedList0Node *node)
+{
+    return node->n;
+}
+
+LinkedList0Node * LinkedList0Node_Prev (LinkedList0Node *node)
+{
+    return node->p;
+}
+
+#endif
diff --git a/external/badvpn_dns/structure/LinkedList1.h b/external/badvpn_dns/structure/LinkedList1.h
new file mode 100644
index 0000000..bdb7161
--- /dev/null
+++ b/external/badvpn_dns/structure/LinkedList1.h
@@ -0,0 +1,275 @@
+/**
+ * @file LinkedList1.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Simple doubly linked list.
+ */
+
+#ifndef BADVPN_STRUCTURE_LINKEDLIST1_H
+#define BADVPN_STRUCTURE_LINKEDLIST1_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+
+/**
+ * Linked list node.
+ */
+typedef struct LinkedList1Node_t
+{
+    struct LinkedList1Node_t *p;
+    struct LinkedList1Node_t *n;
+} LinkedList1Node;
+
+/**
+ * Simple doubly linked list.
+ */
+typedef struct
+{
+    LinkedList1Node *first;
+    LinkedList1Node *last;
+} LinkedList1;
+
+/**
+ * Initializes the linked list.
+ * 
+ * @param list list to initialize
+ */
+static void LinkedList1_Init (LinkedList1 *list);
+
+/**
+ * Determines if the list is empty.
+ * 
+ * @param list the list
+ * @return 1 if empty, 0 if not
+ */
+static int LinkedList1_IsEmpty (LinkedList1 *list);
+
+/**
+ * Returns the first node of the list.
+ * 
+ * @param list the list
+ * @return first node of the list, or NULL if the list is empty
+ */
+static LinkedList1Node * LinkedList1_GetFirst (LinkedList1 *list);
+
+/**
+ * Returns the last node of the list.
+ * 
+ * @param list the list
+ * @return last node of the list, or NULL if the list is empty
+ */
+static LinkedList1Node * LinkedList1_GetLast (LinkedList1 *list);
+
+/**
+ * Inserts a node to the beginning of the list.
+ * 
+ * @param list the list
+ * @param node uninitialized node to insert
+ */
+static void LinkedList1_Prepend (LinkedList1 *list, LinkedList1Node *node);
+
+/**
+ * Inserts a node to the end of the list.
+ * 
+ * @param list the list
+ * @param node uninitialized node to insert
+ */
+static void LinkedList1_Append (LinkedList1 *list, LinkedList1Node *node);
+
+/**
+ * Inserts a node before a given node.
+ * 
+ * @param list the list
+ * @param node uninitialized node to insert
+ * @param target node in the list to insert before
+ */
+static void LinkedList1_InsertBefore (LinkedList1 *list, LinkedList1Node *node, LinkedList1Node *target);
+
+/**
+ * Inserts a node after a given node.
+ * 
+ * @param list the list
+ * @param node uninitialized node to insert
+ * @param target node in the list to insert after
+ */
+static void LinkedList1_InsertAfter (LinkedList1 *list, LinkedList1Node *node, LinkedList1Node *target);
+
+/**
+ * Removes a node from the list.
+ * 
+ * @param list the list
+ * @param node node to remove
+ */
+static void LinkedList1_Remove (LinkedList1 *list, LinkedList1Node *node);
+
+/**
+ * Inserts the nodes in the list \a ins_list into this list, after the node \a target.
+ * If \a target is NULL, the nodes are inserted to the beginning.
+ * Note that because the first and last node in \a ins_list are modified
+ * (unless the list is empty), \a ins_list is invalidated and must no longer
+ * be used to access the inserted nodes.
+ */
+static void LinkedList1_InsertListAfter (LinkedList1 *list, LinkedList1 ins_list, LinkedList1Node *target);
+
+/**
+ * Returns the next node of a given node.
+ * 
+ * @param node reference node
+ * @return next node, or NULL if none
+ */
+static LinkedList1Node * LinkedList1Node_Next (LinkedList1Node *node);
+
+/**
+ * Returns the previous node of a given node.
+ * 
+ * @param node reference node
+ * @return previous node, or NULL if none
+ */
+static LinkedList1Node * LinkedList1Node_Prev (LinkedList1Node *node);
+
+void LinkedList1_Init (LinkedList1 *list)
+{
+    list->first = NULL;
+    list->last = NULL;
+}
+
+int LinkedList1_IsEmpty (LinkedList1 *list)
+{
+    return (!list->first);
+}
+
+LinkedList1Node * LinkedList1_GetFirst (LinkedList1 *list)
+{
+    return (list->first);
+}
+
+LinkedList1Node * LinkedList1_GetLast (LinkedList1 *list)
+{
+    return (list->last);
+}
+
+void LinkedList1_Prepend (LinkedList1 *list, LinkedList1Node *node)
+{
+    node->p = NULL;
+    node->n = list->first;
+    if (list->first) {
+        list->first->p = node;
+    } else {
+        list->last = node;
+    }
+    list->first = node;
+}
+
+void LinkedList1_Append (LinkedList1 *list, LinkedList1Node *node)
+{
+    node->p = list->last;
+    node->n = NULL;
+    if (list->last) {
+        list->last->n = node;
+    } else {
+        list->first = node;
+    }
+    list->last = node;
+}
+
+void LinkedList1_InsertBefore (LinkedList1 *list, LinkedList1Node *node, LinkedList1Node *target)
+{
+    node->p = target->p;
+    node->n = target;
+    if (target->p) {
+        target->p->n = node;
+    } else {
+        list->first = node;
+    }
+    target->p = node;
+}
+
+void LinkedList1_InsertAfter (LinkedList1 *list, LinkedList1Node *node, LinkedList1Node *target)
+{
+    node->p = target;
+    node->n = target->n;
+    if (target->n) {
+        target->n->p = node;
+    } else {
+        list->last = node;
+    }
+    target->n = node;
+}
+
+void LinkedList1_Remove (LinkedList1 *list, LinkedList1Node *node)
+{
+    // remove from list
+    if (node->p) {
+        node->p->n = node->n;
+    } else {
+        list->first = node->n;
+    }
+    if (node->n) {
+        node->n->p = node->p;
+    } else {
+        list->last = node->p;
+    }
+}
+
+void LinkedList1_InsertListAfter (LinkedList1 *list, LinkedList1 ins_list, LinkedList1Node *target)
+{
+    if (!ins_list.first) {
+        return;
+    }
+    
+    LinkedList1Node *t_next = (target ? target->n : list->first);
+    
+    ins_list.first->p = target;
+    ins_list.last->n = t_next;
+    
+    if (target) {
+        target->n = ins_list.first;
+    } else {
+        list->first = ins_list.first;
+    }
+    
+    if (t_next) {
+        t_next->p = ins_list.last;
+    } else {
+        list->last = ins_list.last;
+    }
+}
+
+LinkedList1Node * LinkedList1Node_Next (LinkedList1Node *node)
+{
+    return node->n;
+}
+
+LinkedList1Node * LinkedList1Node_Prev (LinkedList1Node *node)
+{
+    return node->p;
+}
+
+#endif
diff --git a/external/badvpn_dns/structure/LinkedList3.h b/external/badvpn_dns/structure/LinkedList3.h
new file mode 100644
index 0000000..8f54cdb
--- /dev/null
+++ b/external/badvpn_dns/structure/LinkedList3.h
@@ -0,0 +1,362 @@
+/**
+ * @file LinkedList3.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Doubly linked list that support multiple iterations and removing
+ * aritrary elements during iteration, without a central object to
+ * represent the list.
+ */
+
+#ifndef BADVPN_STRUCTURE_LINKEDLIST3_H
+#define BADVPN_STRUCTURE_LINKEDLIST3_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <misc/debug.h>
+
+struct _LinkedList3Iterator;
+
+/**
+ * Linked list node.
+ */
+typedef struct _LinkedList3Node {
+    struct _LinkedList3Node *p;
+    struct _LinkedList3Node *n;
+    struct _LinkedList3Iterator *it;
+} LinkedList3Node;
+
+/**
+ * Linked list iterator.
+ */
+typedef struct _LinkedList3Iterator {
+    int8_t dir;
+    struct _LinkedList3Node *e;
+    struct _LinkedList3Iterator *pi;
+    struct _LinkedList3Iterator *ni;
+} LinkedList3Iterator;
+
+/**
+ * Initializes a list node to form a new list consisting of a
+ * single node.
+ * 
+ * @param node list node structure to initialize. The node must remain
+ *        available until it is freed with {@link LinkedList3Node_Free},
+ *        or the list is no longer required.
+ */
+static void LinkedList3Node_InitLonely (LinkedList3Node *node);
+
+/**
+ * Initializes a list node to go after an existing node.
+ * 
+ * @param node list node structure to initialize. The node must remain
+ *        available until it is freed with {@link LinkedList3Node_Free},
+ *        or the list is no longer required.
+ * @param ref existing list node
+ */
+static void LinkedList3Node_InitAfter (LinkedList3Node *node, LinkedList3Node *ref);
+
+/**
+ * Initializes a list node to go before an existing node.
+ * 
+ * @param node list node structure to initialize. The node must remain
+ *        available until it is freed with {@link LinkedList3Node_Free},
+ *        or the list is no longer required.
+ * @param ref existing list node
+ */
+static void LinkedList3Node_InitBefore (LinkedList3Node *node, LinkedList3Node *ref);
+
+/**
+ * Frees a list node, removing it a list (if there were other nodes
+ * in the list).
+ * 
+ * @param node list node to free
+ */
+static void LinkedList3Node_Free (LinkedList3Node *node);
+
+/**
+ * Determines if a list node is a single node in a list.
+ * 
+ * @param node list node
+ * @return 1 if the node ia a single node, 0 if not
+ */
+static int LinkedList3Node_IsLonely (LinkedList3Node *node);
+
+/**
+ * Returnes the node preceding this node (if there is one),
+ * the node following this node (if there is one), or NULL,
+ * respectively.
+ * 
+ * @param node list node
+ * @return neighbour node or NULL if none
+ */
+static LinkedList3Node * LinkedList3Node_PrevOrNext (LinkedList3Node *node);
+
+/**
+ * Returnes the node following this node (if there is one),
+ * the node preceding this node (if there is one), or NULL,
+ * respectively.
+ * 
+ * @param node list node
+ * @return neighbour node or NULL if none
+ */
+static LinkedList3Node * LinkedList3Node_NextOrPrev (LinkedList3Node *node);
+
+/**
+ * Returns the node preceding this node, or NULL if there is none.
+ * 
+ * @param node list node
+ * @return left neighbour, or NULL if none
+ */
+static LinkedList3Node * LinkedList3Node_Prev (LinkedList3Node *node);
+
+/**
+ * Returns the node following this node, or NULL if there is none.
+ * 
+ * @param node list node
+ * @return right neighbour, or NULL if none
+ */
+static LinkedList3Node * LinkedList3Node_Next (LinkedList3Node *node);
+
+/**
+ * Returns the first node in the list which this node is part of.
+ * It is found by iterating the list from this node to the beginning.
+ * 
+ * @param node list node
+ * @return first node in the list
+ */
+static LinkedList3Node * LinkedList3Node_First (LinkedList3Node *node);
+
+/**
+ * Returns the last node in the list which this node is part of.
+ * It is found by iterating the list from this node to the end.
+ * 
+ * @param node list node
+ * @return last node in the list
+ */
+static LinkedList3Node * LinkedList3Node_Last (LinkedList3Node *node);
+
+/**
+ * Initializes a linked list iterator.
+ * The iterator structure must remain available until either of these occurs:
+ *   - the list is no longer needed, or
+ *   - the iterator is freed with {@link LinkedList3Iterator_Free}, or
+ *   - the iterator reaches the end of iteration.
+ * 
+ * @param it uninitialized iterator to initialize
+ * @param e initial position of the iterator. NULL for end of iteration.
+ * @param dir direction of iteration. Must be 1 (forward) or -1 (backward).
+ */
+static void LinkedList3Iterator_Init (LinkedList3Iterator *it, LinkedList3Node *e, int dir);
+
+/**
+ * Frees a linked list iterator.
+ * 
+ * @param it iterator to free
+ */
+static void LinkedList3Iterator_Free (LinkedList3Iterator *it);
+
+/**
+ * Moves the iterator one node forward or backward (depending on its direction), or,
+ * if it's at the last or first node (depending on the direction), it reaches
+ * the end of iteration, or, if it's at the end of iteration, it remains there.
+ * Returns the the previous position.
+ * 
+ * @param it the iterator
+ * @return node on the position of iterator before it was (possibly) moved, or NULL
+ *         if it was at the end of iteration
+ */
+static LinkedList3Node * LinkedList3Iterator_Next (LinkedList3Iterator *it);
+
+void LinkedList3Node_InitLonely (LinkedList3Node *node)
+{
+    node->p = NULL;
+    node->n = NULL;
+    node->it = NULL;
+}
+
+void LinkedList3Node_InitAfter (LinkedList3Node *node, LinkedList3Node *ref)
+{
+    ASSERT(ref)
+    
+    node->p = ref;
+    node->n = ref->n;
+    ref->n = node;
+    if (node->n) {
+        node->n->p = node;
+    }
+    node->it = NULL;
+}
+
+void LinkedList3Node_InitBefore (LinkedList3Node *node, LinkedList3Node *ref)
+{
+    ASSERT(ref)
+    
+    node->n = ref;
+    node->p = ref->p;
+    ref->p = node;
+    if (node->p) {
+        node->p->n = node;
+    }
+    node->it = NULL;
+}
+
+void LinkedList3Node_Free (LinkedList3Node *node)
+{
+    // jump iterators
+    while (node->it) {
+        LinkedList3Iterator_Next(node->it);
+    }
+    
+    if (node->p) {
+        node->p->n = node->n;
+    }
+    if (node->n) {
+        node->n->p = node->p;
+    }
+}
+
+int LinkedList3Node_IsLonely (LinkedList3Node *node)
+{
+    return (!node->p && !node->n);
+}
+
+LinkedList3Node * LinkedList3Node_PrevOrNext (LinkedList3Node *node)
+{
+    if (node->p) {
+        return node->p;
+    }
+    if (node->n) {
+        return node->n;
+    }
+    return NULL;
+}
+
+LinkedList3Node * LinkedList3Node_NextOrPrev (LinkedList3Node *node)
+{
+    if (node->n) {
+        return node->n;
+    }
+    if (node->p) {
+        return node->p;
+    }
+    return NULL;
+}
+
+LinkedList3Node * LinkedList3Node_Prev (LinkedList3Node *node)
+{
+    return node->p;
+}
+
+LinkedList3Node * LinkedList3Node_Next (LinkedList3Node *node)
+{
+    return node->n;
+}
+
+LinkedList3Node * LinkedList3Node_First (LinkedList3Node *node)
+{
+    while (node->p) {
+        node = node->p;
+    }
+    
+    return node;
+}
+
+LinkedList3Node * LinkedList3Node_Last (LinkedList3Node *node)
+{
+    while (node->n) {
+        node = node->n;
+    }
+    
+    return node;
+}
+
+void LinkedList3Iterator_Init (LinkedList3Iterator *it, LinkedList3Node *e, int dir)
+{
+    ASSERT(dir == -1 || dir == 1)
+    
+    it->dir = dir;
+    it->e = e;
+    
+    if (e) {
+        // link into node's iterator list
+        it->pi = NULL;
+        it->ni = e->it;
+        if (e->it) {
+            e->it->pi = it;
+        }
+        e->it = it;
+    }
+}
+
+void LinkedList3Iterator_Free (LinkedList3Iterator *it)
+{
+    if (it->e) {
+        // remove from node's iterator list
+        if (it->ni) {
+            it->ni->pi = it->pi;
+        }
+        if (it->pi) {
+            it->pi->ni = it->ni;
+        } else {
+            it->e->it = it->ni;
+        }
+    }
+}
+
+LinkedList3Node * LinkedList3Iterator_Next (LinkedList3Iterator *it)
+{
+    // remember original entry
+    LinkedList3Node *orig = it->e;
+    
+    // jump to next entry
+    if (it->e) {
+        // get next entry
+        LinkedList3Node *next = NULL; // to remove warning
+        switch (it->dir) {
+            case 1:
+                next = it->e->n;
+                break;
+            case -1:
+                next = it->e->p;
+                break;
+            default:
+                ASSERT(0);
+        }
+        // destroy interator
+        LinkedList3Iterator_Free(it);
+        // re-initialize at next entry
+        LinkedList3Iterator_Init(it, next, it->dir);
+    }
+    
+    // return original entry
+    return orig;
+}
+
+#endif
diff --git a/external/badvpn_dns/structure/SAvl.h b/external/badvpn_dns/structure/SAvl.h
new file mode 100644
index 0000000..6ea1399
--- /dev/null
+++ b/external/badvpn_dns/structure/SAvl.h
@@ -0,0 +1,40 @@
+/**
+ * @file SAvl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_SAVL_H
+#define BADVPN_SAVL_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <structure/CAvl.h>
+#include <misc/merge.h>
+#include <misc/debug.h>
+
+#endif
diff --git a/external/badvpn_dns/structure/SAvl_decl.h b/external/badvpn_dns/structure/SAvl_decl.h
new file mode 100644
index 0000000..8a13e49
--- /dev/null
+++ b/external/badvpn_dns/structure/SAvl_decl.h
@@ -0,0 +1,73 @@
+/**
+ * @file SAvl_decl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "SAvl_header.h"
+
+typedef SAvlEntry *SAvl__TreeLink;
+
+#include "SAvl_tree.h"
+#include <structure/CAvl_decl.h>
+
+typedef struct {
+    SAvl__Tree tree;
+} SAvl;
+
+typedef struct {
+    SAvlEntry *child[2];
+    SAvlEntry *parent;
+    int8_t balance;
+#if SAVL_PARAM_FEATURE_COUNTS
+    SAvlCount count;
+#endif
+} SAvlNode;
+
+static void SAvl_Init (SAvl *o);
+static int SAvl_Insert (SAvl *o, SAvlArg arg, SAvlEntry *entry, SAvlEntry **out_existing);
+static void SAvl_Remove (SAvl *o, SAvlArg arg, SAvlEntry *entry);
+#if !SAVL_PARAM_FEATURE_NOKEYS
+static SAvlEntry * SAvl_Lookup (const SAvl *o, SAvlArg arg, SAvlKey key);
+static SAvlEntry * SAvl_LookupExact (const SAvl *o, SAvlArg arg, SAvlKey key);
+static SAvlEntry * SAvl_GetFirstGreater (const SAvl *o, SAvlArg arg, SAvlKey key);
+static SAvlEntry * SAvl_GetLastLesser (const SAvl *o, SAvlArg arg, SAvlKey key);
+static SAvlEntry * SAvl_GetFirstGreaterEqual (const SAvl *o, SAvlArg arg, SAvlKey key);
+static SAvlEntry * SAvl_GetLastLesserEqual (const SAvl *o, SAvlArg arg, SAvlKey key);
+#endif
+static SAvlEntry * SAvl_GetFirst (const SAvl *o, SAvlArg arg);
+static SAvlEntry * SAvl_GetLast (const SAvl *o, SAvlArg arg);
+static SAvlEntry * SAvl_GetNext (const SAvl *o, SAvlArg arg, SAvlEntry *entry);
+static SAvlEntry * SAvl_GetPrev (const SAvl *o, SAvlArg arg, SAvlEntry *entry);
+static int SAvl_IsEmpty (const SAvl *o);
+static void SAvl_Verify (const SAvl *o, SAvlArg arg);
+#if SAVL_PARAM_FEATURE_COUNTS
+static SAvlCount SAvl_Count (const SAvl *o, SAvlArg arg);
+static SAvlCount SAvl_IndexOf (const SAvl *o, SAvlArg arg, SAvlEntry *entry);
+static SAvlEntry * SAvl_GetAt (const SAvl *o, SAvlArg arg, SAvlCount index);
+#endif
+
+#include "SAvl_footer.h"
diff --git a/external/badvpn_dns/structure/SAvl_footer.h b/external/badvpn_dns/structure/SAvl_footer.h
new file mode 100644
index 0000000..ad90e40
--- /dev/null
+++ b/external/badvpn_dns/structure/SAvl_footer.h
@@ -0,0 +1,89 @@
+/**
+ * @file SAvl_footer.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+#undef SAVL_PARAM_NAME
+#undef SAVL_PARAM_FEATURE_COUNTS
+#undef SAVL_PARAM_FEATURE_NOKEYS
+#undef SAVL_PARAM_TYPE_ENTRY
+#undef SAVL_PARAM_TYPE_KEY
+#undef SAVL_PARAM_TYPE_ARG
+#undef SAVL_PARAM_TYPE_COUNT
+#undef SAVL_PARAM_VALUE_COUNT_MAX
+#undef SAVL_PARAM_FUN_COMPARE_ENTRIES
+#undef SAVL_PARAM_FUN_COMPARE_KEY_ENTRY
+#undef SAVL_PARAM_MEMBER_NODE
+
+#undef SAvl
+#undef SAvlEntry
+#undef SAvlArg
+#undef SAvlKey
+#undef SAvlCount
+#undef SAvlNode
+
+#undef SAvl_Init
+#undef SAvl_Insert
+#undef SAvl_Remove
+#undef SAvl_Lookup
+#undef SAvl_LookupExact
+#undef SAvl_GetFirstGreater
+#undef SAvl_GetLastLesser
+#undef SAvl_GetFirstGreaterEqual
+#undef SAvl_GetLastLesserEqual
+#undef SAvl_GetFirst
+#undef SAvl_GetLast
+#undef SAvl_GetNext
+#undef SAvl_GetPrev
+#undef SAvl_IsEmpty
+#undef SAvl_Verify
+#undef SAvl_Count
+#undef SAvl_IndexOf
+#undef SAvl_GetAt
+
+#undef SAvl__Tree
+#undef SAvl__TreeRef
+#undef SAvl__TreeLink
+#undef SAvl__TreeDeref
+#undef SAvl__Tree_Init
+#undef SAvl__Tree_Insert
+#undef SAvl__Tree_Remove
+#undef SAvl__Tree_Lookup
+#undef SAvl__Tree_LookupExact
+#undef SAvl__Tree_GetFirstGreater
+#undef SAvl__Tree_GetLastLesser
+#undef SAvl__Tree_GetFirstGreaterEqual
+#undef SAvl__Tree_GetLastLesserEqual
+#undef SAvl__Tree_GetFirst
+#undef SAvl__Tree_GetLast
+#undef SAvl__Tree_GetNext
+#undef SAvl__Tree_GetPrev
+#undef SAvl__Tree_IsEmpty
+#undef SAvl__Tree_Verify
+#undef SAvl__Tree_Count
+#undef SAvl__Tree_IndexOf
+#undef SAvl__Tree_GetAt
diff --git a/external/badvpn_dns/structure/SAvl_header.h b/external/badvpn_dns/structure/SAvl_header.h
new file mode 100644
index 0000000..5d7d9b1
--- /dev/null
+++ b/external/badvpn_dns/structure/SAvl_header.h
@@ -0,0 +1,93 @@
+/**
+ * @file SAvl_header.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+// Preprocessor inputs. All types must be typedef names unless stated otherwise.
+// SAVL_PARAM_NAME - name of this data structure
+// SAVL_PARAM_FEATURE_COUNTS - whether to keep count information (0 or 1)
+// SAVL_PARAM_FEATURE_NOKEYS - define to 1 if there is no need for a lookup operation
+// SAVL_PARAM_TYPE_ENTRY - type of entry. This can also be a struct (e.g.: struct Foo).
+// SAVL_PARAM_TYPE_KEY - type of key (ignored if SAVL_PARAM_FEATURE_NOKEYS)
+// SAVL_PARAM_TYPE_ARG - type of argument pass through to callbacks
+// SAVL_PARAM_TYPE_COUNT - type of count (only if SAVL_PARAM_FEATURE_COUNTS)
+// SAVL_PARAM_VALUE_COUNT_MAX - maximum value of count (of type SAVL_PARAM_TYPE_COUNT) (only if SAVL_PARAM_FEATURE_COUNTS)
+// SAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) - compare two entries; returns -1/0/1
+// SAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) - compare key and entry; returns -1/0/1 (ignored if SAVL_PARAM_FEATURE_NOKEYS) 
+// SAVL_PARAM_MEMBER_NODE - node member in entry
+
+// types
+#define SAvl SAVL_PARAM_NAME
+#define SAvlEntry SAVL_PARAM_TYPE_ENTRY
+#define SAvlArg SAVL_PARAM_TYPE_ARG
+#define SAvlKey SAVL_PARAM_TYPE_KEY
+#define SAvlCount SAVL_PARAM_TYPE_COUNT
+#define SAvlNode MERGE(SAvl, Node)
+
+// public functions
+#define SAvl_Init MERGE(SAvl, _Init)
+#define SAvl_Insert MERGE(SAvl, _Insert)
+#define SAvl_Remove MERGE(SAvl, _Remove)
+#define SAvl_Lookup MERGE(SAvl, _Lookup)
+#define SAvl_LookupExact MERGE(SAvl, _LookupExact)
+#define SAvl_GetFirstGreater MERGE(SAvl, _GetFirstGreater)
+#define SAvl_GetLastLesser MERGE(SAvl, _GetLastLesser)
+#define SAvl_GetFirstGreaterEqual MERGE(SAvl, _GetFirstGreaterEqual)
+#define SAvl_GetLastLesserEqual MERGE(SAvl, _GetLastLesserEqual)
+#define SAvl_GetFirst MERGE(SAvl, _GetFirst)
+#define SAvl_GetLast MERGE(SAvl, _GetLast)
+#define SAvl_GetNext MERGE(SAvl, _GetNext)
+#define SAvl_GetPrev MERGE(SAvl, _GetPrev)
+#define SAvl_IsEmpty MERGE(SAvl, _IsEmpty)
+#define SAvl_Verify MERGE(SAvl, _Verify)
+#define SAvl_Count MERGE(SAvl, _Count)
+#define SAvl_IndexOf MERGE(SAvl, _IndexOf)
+#define SAvl_GetAt MERGE(SAvl, _GetAt)
+
+// internal stuff
+#define SAvl__Tree MERGE(SAvl, __Tree)
+#define SAvl__TreeRef MERGE(SAvl, __TreeRef)
+#define SAvl__TreeLink MERGE(SAvl, __TreeLink)
+#define SAvl__TreeDeref MERGE(SAvl, __TreeDeref)
+#define SAvl__Tree_Init MERGE(SAvl, __Tree_Init)
+#define SAvl__Tree_Insert MERGE(SAvl, __Tree_Insert)
+#define SAvl__Tree_Remove MERGE(SAvl, __Tree_Remove)
+#define SAvl__Tree_Lookup MERGE(SAvl, __Tree_Lookup)
+#define SAvl__Tree_LookupExact MERGE(SAvl, __Tree_LookupExact)
+#define SAvl__Tree_GetFirstGreater MERGE(SAvl, __Tree_GetFirstGreater)
+#define SAvl__Tree_GetLastLesser MERGE(SAvl, __Tree_GetLastLesser)
+#define SAvl__Tree_GetFirstGreaterEqual MERGE(SAvl, __Tree_GetFirstGreaterEqual)
+#define SAvl__Tree_GetLastLesserEqual MERGE(SAvl, __Tree_GetLastLesserEqual)
+#define SAvl__Tree_GetFirst MERGE(SAvl, __Tree_GetFirst)
+#define SAvl__Tree_GetLast MERGE(SAvl, __Tree_GetLast)
+#define SAvl__Tree_GetNext MERGE(SAvl, __Tree_GetNext)
+#define SAvl__Tree_GetPrev MERGE(SAvl, __Tree_GetPrev)
+#define SAvl__Tree_IsEmpty MERGE(SAvl, __Tree_IsEmpty)
+#define SAvl__Tree_Verify MERGE(SAvl, __Tree_Verify)
+#define SAvl__Tree_Count MERGE(SAvl, __Tree_Count)
+#define SAvl__Tree_IndexOf MERGE(SAvl, __Tree_IndexOf)
+#define SAvl__Tree_GetAt MERGE(SAvl, __Tree_GetAt)
diff --git a/external/badvpn_dns/structure/SAvl_impl.h b/external/badvpn_dns/structure/SAvl_impl.h
new file mode 100644
index 0000000..0d3973a
--- /dev/null
+++ b/external/badvpn_dns/structure/SAvl_impl.h
@@ -0,0 +1,164 @@
+/**
+ * @file SAvl_impl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "SAvl_header.h"
+
+#include "SAvl_tree.h"
+#include <structure/CAvl_impl.h>
+
+static void SAvl_Init (SAvl *o)
+{
+    SAvl__Tree_Init(&o->tree);
+}
+
+static int SAvl_Insert (SAvl *o, SAvlArg arg, SAvlEntry *entry, SAvlEntry **out_existing)
+{
+    ASSERT(entry)
+#if SAVL_PARAM_FEATURE_COUNTS
+    ASSERT(SAvl_Count(o, arg) < SAVL_PARAM_VALUE_COUNT_MAX)
+#endif
+    
+    SAvl__TreeRef out_ref;
+    int res = SAvl__Tree_Insert(&o->tree, arg, SAvl__TreeDeref(arg, entry), &out_ref);
+    
+    if (out_existing) {
+        *out_existing = out_ref.link;
+    }
+    
+    return res;
+}
+
+static void SAvl_Remove (SAvl *o, SAvlArg arg, SAvlEntry *entry)
+{
+    ASSERT(entry)
+    
+    SAvl__Tree_Remove(&o->tree, arg, SAvl__TreeDeref(arg, entry));
+}
+
+#if !SAVL_PARAM_FEATURE_NOKEYS
+
+static SAvlEntry * SAvl_Lookup (const SAvl *o, SAvlArg arg, SAvlKey key)
+{
+    SAvl__TreeRef ref = SAvl__Tree_Lookup(&o->tree, arg, key);
+    return ref.link;
+}
+
+static SAvlEntry * SAvl_LookupExact (const SAvl *o, SAvlArg arg, SAvlKey key)
+{
+    SAvl__TreeRef ref = SAvl__Tree_LookupExact(&o->tree, arg, key);
+    return ref.link;
+}
+
+static SAvlEntry * SAvl_GetFirstGreater (const SAvl *o, SAvlArg arg, SAvlKey key)
+{
+    SAvl__TreeRef ref = SAvl__Tree_GetFirstGreater(&o->tree, arg, key);
+    return ref.link;
+}
+
+static SAvlEntry * SAvl_GetLastLesser (const SAvl *o, SAvlArg arg, SAvlKey key)
+{
+    SAvl__TreeRef ref = SAvl__Tree_GetLastLesser(&o->tree, arg, key);
+    return ref.link;
+}
+
+static SAvlEntry * SAvl_GetFirstGreaterEqual (const SAvl *o, SAvlArg arg, SAvlKey key)
+{
+    SAvl__TreeRef ref = SAvl__Tree_GetFirstGreaterEqual(&o->tree, arg, key);
+    return ref.link;
+}
+
+static SAvlEntry * SAvl_GetLastLesserEqual (const SAvl *o, SAvlArg arg, SAvlKey key)
+{
+    SAvl__TreeRef ref = SAvl__Tree_GetLastLesserEqual(&o->tree, arg, key);
+    return ref.link;
+}
+
+#endif
+
+static SAvlEntry * SAvl_GetFirst (const SAvl *o, SAvlArg arg)
+{
+    SAvl__TreeRef ref = SAvl__Tree_GetFirst(&o->tree, arg);
+    return ref.link;
+}
+
+static SAvlEntry * SAvl_GetLast (const SAvl *o, SAvlArg arg)
+{
+    SAvl__TreeRef ref = SAvl__Tree_GetLast(&o->tree, arg);
+    return ref.link;
+}
+
+static SAvlEntry * SAvl_GetNext (const SAvl *o, SAvlArg arg, SAvlEntry *entry)
+{
+    ASSERT(entry)
+    
+    SAvl__TreeRef ref = SAvl__Tree_GetNext(&o->tree, arg, SAvl__TreeDeref(arg, entry));
+    return ref.link;
+}
+
+static SAvlEntry * SAvl_GetPrev (const SAvl *o, SAvlArg arg, SAvlEntry *entry)
+{
+    ASSERT(entry)
+    
+    SAvl__TreeRef ref = SAvl__Tree_GetPrev(&o->tree, arg, SAvl__TreeDeref(arg, entry));
+    return ref.link;
+}
+
+static int SAvl_IsEmpty (const SAvl *o)
+{
+    return SAvl__Tree_IsEmpty(&o->tree);
+}
+
+static void SAvl_Verify (const SAvl *o, SAvlArg arg)
+{
+    return SAvl__Tree_Verify(&o->tree, arg);
+}
+
+#if SAVL_PARAM_FEATURE_COUNTS
+
+static SAvlCount SAvl_Count (const SAvl *o, SAvlArg arg)
+{
+    return SAvl__Tree_Count(&o->tree, arg);
+}
+
+static SAvlCount SAvl_IndexOf (const SAvl *o, SAvlArg arg, SAvlEntry *entry)
+{
+    ASSERT(entry)
+    
+    return SAvl__Tree_IndexOf(&o->tree, arg, SAvl__TreeDeref(arg, entry));
+}
+
+static SAvlEntry * SAvl_GetAt (const SAvl *o, SAvlArg arg, SAvlCount index)
+{
+    SAvl__TreeRef ref = SAvl__Tree_GetAt(&o->tree, arg, index);
+    return ref.link;
+}
+
+#endif
+
+#include "SAvl_footer.h"
diff --git a/external/badvpn_dns/structure/SAvl_tree.h b/external/badvpn_dns/structure/SAvl_tree.h
new file mode 100644
index 0000000..5e5b18b
--- /dev/null
+++ b/external/badvpn_dns/structure/SAvl_tree.h
@@ -0,0 +1,18 @@
+#define CAVL_PARAM_NAME SAvl__Tree
+#define CAVL_PARAM_FEATURE_COUNTS SAVL_PARAM_FEATURE_COUNTS
+#define CAVL_PARAM_FEATURE_KEYS_ARE_INDICES 0
+#define CAVL_PARAM_FEATURE_NOKEYS SAVL_PARAM_FEATURE_NOKEYS
+#define CAVL_PARAM_TYPE_ENTRY SAvlEntry
+#define CAVL_PARAM_TYPE_LINK SAvl__TreeLink
+#define CAVL_PARAM_TYPE_KEY SAvlKey
+#define CAVL_PARAM_TYPE_ARG SAvlArg
+#define CAVL_PARAM_TYPE_COUNT SAvlCount
+#define CAVL_PARAM_VALUE_COUNT_MAX SAVL_PARAM_VALUE_COUNT_MAX
+#define CAVL_PARAM_VALUE_NULL ((SAvl__TreeLink)NULL)
+#define CAVL_PARAM_FUN_DEREF(arg, link) (link)
+#define CAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) SAVL_PARAM_FUN_COMPARE_ENTRIES((arg), (entry1).link, (entry2).link)
+#define CAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) SAVL_PARAM_FUN_COMPARE_KEY_ENTRY((arg), (key1), (entry2).link)
+#define CAVL_PARAM_MEMBER_CHILD SAVL_PARAM_MEMBER_NODE . child
+#define CAVL_PARAM_MEMBER_BALANCE SAVL_PARAM_MEMBER_NODE . balance
+#define CAVL_PARAM_MEMBER_PARENT SAVL_PARAM_MEMBER_NODE . parent
+#define CAVL_PARAM_MEMBER_COUNT SAVL_PARAM_MEMBER_NODE . count
diff --git a/external/badvpn_dns/structure/SLinkedList.h b/external/badvpn_dns/structure/SLinkedList.h
new file mode 100644
index 0000000..1edb016
--- /dev/null
+++ b/external/badvpn_dns/structure/SLinkedList.h
@@ -0,0 +1,38 @@
+/**
+ * @file SLinkedList.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_SLINKEDLIST_H
+#define BADVPN_SLINKEDLIST_H
+
+#include <stddef.h>
+
+#include <misc/merge.h>
+#include <misc/debug.h>
+
+#endif
diff --git a/external/badvpn_dns/structure/SLinkedList_decl.h b/external/badvpn_dns/structure/SLinkedList_decl.h
new file mode 100644
index 0000000..4a0ade4
--- /dev/null
+++ b/external/badvpn_dns/structure/SLinkedList_decl.h
@@ -0,0 +1,67 @@
+/**
+ * @file SLinkedList_decl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "SLinkedList_header.h"
+
+typedef struct {
+    SLinkedListEntry *first;
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+    SLinkedListEntry *last;
+#endif
+} SLinkedList;
+
+typedef struct {
+    SLinkedListEntry *prev;
+    SLinkedListEntry *next;
+} SLinkedListNode;
+
+static void SLinkedListMarkRemoved (SLinkedListEntry *entry);
+static int SLinkedListIsRemoved (SLinkedListEntry *entry);
+
+static void SLinkedList_Init (SLinkedList *o);
+static SLinkedListEntry * SLinkedList_Next (SLinkedList *o, SLinkedListEntry *entry);
+static SLinkedListEntry * SLinkedList_Prev (SLinkedList *o, SLinkedListEntry *entry);
+static void SLinkedList_Prepend (SLinkedList *o, SLinkedListEntry *entry);
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+static void SLinkedList_Append (SLinkedList *o, SLinkedListEntry *entry);
+#endif
+static void SLinkedList_InsertBefore (SLinkedList *o, SLinkedListEntry *entry, SLinkedListEntry *before_entry);
+static void SLinkedList_InsertAfter (SLinkedList *o, SLinkedListEntry *entry, SLinkedListEntry *after_entry);
+static void SLinkedList_Remove (SLinkedList *o, SLinkedListEntry *entry);
+static void SLinkedList_RemoveFirst (SLinkedList *o);
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+static void SLinkedList_RemoveLast (SLinkedList *o);
+#endif
+static SLinkedListEntry * SLinkedList_First (const SLinkedList *o);
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+static SLinkedListEntry * SLinkedList_Last (const SLinkedList *o);
+#endif
+static int SLinkedList_IsEmpty (const SLinkedList *o);
+
+#include "SLinkedList_footer.h"
diff --git a/external/badvpn_dns/structure/SLinkedList_footer.h b/external/badvpn_dns/structure/SLinkedList_footer.h
new file mode 100644
index 0000000..10ab1e3
--- /dev/null
+++ b/external/badvpn_dns/structure/SLinkedList_footer.h
@@ -0,0 +1,57 @@
+/**
+ * @file SLinkedList_footer.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+#undef SLINKEDLIST_PARAM_NAME
+#undef SLINKEDLIST_PARAM_FEATURE_LAST
+#undef SLINKEDLIST_PARAM_TYPE_ENTRY
+#undef SLINKEDLIST_PARAM_MEMBER_NODE
+
+#undef SLinkedList
+#undef SLinkedListEntry
+#undef SLinkedListNode
+
+#undef SLinkedListMarkRemoved
+#undef SLinkedListIsRemoved
+
+#undef SLinkedList_Init
+#undef SLinkedList_Next
+#undef SLinkedList_Prev
+#undef SLinkedList_Prepend
+#undef SLinkedList_Append
+#undef SLinkedList_InsertBefore
+#undef SLinkedList_InsertAfter
+#undef SLinkedList_Remove
+#undef SLinkedList_RemoveFirst
+#undef SLinkedList_RemoveLast
+#undef SLinkedList_First
+#undef SLinkedList_Last
+#undef SLinkedList_IsEmpty
+
+#undef SLinkedList_prev
+#undef SLinkedList_next
diff --git a/external/badvpn_dns/structure/SLinkedList_header.h b/external/badvpn_dns/structure/SLinkedList_header.h
new file mode 100644
index 0000000..4a0fd54
--- /dev/null
+++ b/external/badvpn_dns/structure/SLinkedList_header.h
@@ -0,0 +1,62 @@
+/**
+ * @file SLinkedList_header.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+// Preprocessor inputs:
+// SLINKEDLIST_PARAM_NAME - name of this data structure
+// SLINKEDLIST_PARAM_FEATURE_LAST - whether to keep track of the last entry in the list (0/1)
+// SLINKEDLIST_PARAM_TYPE_ENTRY - type of entry
+// SLINKEDLIST_PARAM_MEMBER_NODE - name of the node member in entry
+
+// types
+#define SLinkedList SLINKEDLIST_PARAM_NAME
+#define SLinkedListEntry SLINKEDLIST_PARAM_TYPE_ENTRY
+#define SLinkedListNode MERGE(SLinkedList, Node)
+
+// non-object public functions
+#define SLinkedListMarkRemoved MERGE(SLinkedList, MarkRemoved)
+#define SLinkedListIsRemoved MERGE(SLinkedList, IsRemoved)
+
+// public functions
+#define SLinkedList_Init MERGE(SLinkedList, _Init)
+#define SLinkedList_Next MERGE(SLinkedList, _Next)
+#define SLinkedList_Prev MERGE(SLinkedList, _Prev)
+#define SLinkedList_Prepend MERGE(SLinkedList, _Prepend)
+#define SLinkedList_Append MERGE(SLinkedList, _Append)
+#define SLinkedList_InsertBefore MERGE(SLinkedList, _InsertBefore)
+#define SLinkedList_InsertAfter MERGE(SLinkedList, _InsertAfter)
+#define SLinkedList_Remove MERGE(SLinkedList, _Remove)
+#define SLinkedList_RemoveFirst MERGE(SLinkedList, _RemoveFirst)
+#define SLinkedList_RemoveLast MERGE(SLinkedList, _RemoveLast)
+#define SLinkedList_First MERGE(SLinkedList, _First)
+#define SLinkedList_Last MERGE(SLinkedList, _Last)
+#define SLinkedList_IsEmpty MERGE(SLinkedList, _IsEmpty)
+
+// private things
+#define SLinkedList_prev(entry) ((entry)->SLINKEDLIST_PARAM_MEMBER_NODE . prev)
+#define SLinkedList_next(entry) ((entry)->SLINKEDLIST_PARAM_MEMBER_NODE . next)
diff --git a/external/badvpn_dns/structure/SLinkedList_impl.h b/external/badvpn_dns/structure/SLinkedList_impl.h
new file mode 100644
index 0000000..1bbce9a
--- /dev/null
+++ b/external/badvpn_dns/structure/SLinkedList_impl.h
@@ -0,0 +1,182 @@
+/**
+ * @file SLinkedList_impl.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "SLinkedList_header.h"
+
+static void SLinkedListMarkRemoved (SLinkedListEntry *entry)
+{
+    ASSERT(entry)
+    
+    SLinkedList_next(entry) = entry;
+}
+
+static int SLinkedListIsRemoved (SLinkedListEntry *entry)
+{
+    ASSERT(entry)
+    
+    return (entry == SLinkedList_next(entry));
+}
+
+static void SLinkedList_Init (SLinkedList *o)
+{
+    o->first = NULL;
+}
+
+static SLinkedListEntry * SLinkedList_Next (SLinkedList *o, SLinkedListEntry *entry)
+{
+    ASSERT(entry)
+    
+    return SLinkedList_next(entry);
+}
+
+static SLinkedListEntry * SLinkedList_Prev (SLinkedList *o, SLinkedListEntry *entry)
+{
+    ASSERT(entry)
+    
+    return (entry == o->first) ? NULL : SLinkedList_prev(entry);
+}
+
+static void SLinkedList_Prepend (SLinkedList *o, SLinkedListEntry *entry)
+{
+    ASSERT(entry)
+
+    SLinkedList_next(entry) = o->first;
+    if (o->first) {
+        SLinkedList_prev(o->first) = entry;
+    } else {
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+        o->last = entry;
+#endif
+    }
+    o->first = entry;
+}
+
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+static void SLinkedList_Append (SLinkedList *o, SLinkedListEntry *entry)
+{
+    ASSERT(entry)
+
+    SLinkedList_next(entry) = NULL;
+    if (o->first) {
+        SLinkedList_prev(entry) = o->last;
+        SLinkedList_next(o->last) = entry;
+    } else {
+        o->first = entry;
+    }
+    o->last = entry;
+}
+#endif
+
+static void SLinkedList_InsertBefore (SLinkedList *o, SLinkedListEntry *entry, SLinkedListEntry *before_entry)
+{
+    ASSERT(entry)
+    ASSERT(before_entry)
+    
+    SLinkedList_next(entry) = before_entry;
+    if (before_entry != o->first) {
+        SLinkedList_prev(entry) = SLinkedList_prev(before_entry);
+        SLinkedList_next(SLinkedList_prev(before_entry)) = entry;
+    } else {
+        o->first = entry;
+    }
+    SLinkedList_prev(before_entry) = entry;
+}
+
+static void SLinkedList_InsertAfter (SLinkedList *o, SLinkedListEntry *entry, SLinkedListEntry *after_entry)
+{
+    ASSERT(entry)
+    ASSERT(after_entry)
+    
+    SLinkedList_next(entry) = SLinkedList_next(after_entry);
+    SLinkedList_prev(entry) = after_entry;
+    if (SLinkedList_next(after_entry)) {
+        SLinkedList_prev(SLinkedList_next(after_entry)) = entry;
+    } else {
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+        o->last = entry;
+#endif
+    }
+    SLinkedList_next(after_entry) = entry;
+}
+
+static void SLinkedList_Remove (SLinkedList *o, SLinkedListEntry *entry)
+{
+    if (entry != o->first) {
+        SLinkedList_next(SLinkedList_prev(entry)) = SLinkedList_next(entry);
+        if (SLinkedList_next(entry)) {
+            SLinkedList_prev(SLinkedList_next(entry)) = SLinkedList_prev(entry);
+        } else {
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+            o->last = SLinkedList_prev(entry);
+#endif
+        }
+    } else {
+        o->first = SLinkedList_next(entry);
+    }
+}
+
+static void SLinkedList_RemoveFirst (SLinkedList *o)
+{
+    ASSERT(o->first)
+    
+    o->first = SLinkedList_next(o->first);
+}
+
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+static void SLinkedList_RemoveLast (SLinkedList *o)
+{
+    ASSERT(o->first)
+    
+    if (o->last == o->first) {
+        o->first = NULL;
+    } else {
+        SLinkedList_next(SLinkedList_prev(o->last)) = NULL;
+        o->last = SLinkedList_prev(o->last);
+    }
+}
+#endif
+
+static SLinkedListEntry * SLinkedList_First (const SLinkedList *o)
+{
+    return o->first;
+}
+
+#if SLINKEDLIST_PARAM_FEATURE_LAST
+static SLinkedListEntry * SLinkedList_Last (const SLinkedList *o)
+{
+    return (!o->first ? NULL : o->last);
+}
+#endif
+
+static int SLinkedList_IsEmpty (const SLinkedList *o)
+{
+    return !o->first;
+}
+
+#include "SLinkedList_footer.h"
diff --git a/external/badvpn_dns/system/BAddr.h b/external/badvpn_dns/system/BAddr.h
new file mode 100644
index 0000000..a5f3f10
--- /dev/null
+++ b/external/badvpn_dns/system/BAddr.h
@@ -0,0 +1,808 @@
+/**
+ * @file BAddr.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Network address abstractions.
+ */
+
+#ifndef BADVPN_SYSTEM_BADDR_H
+#define BADVPN_SYSTEM_BADDR_H
+
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef BADVPN_USE_WINAPI
+#include <ws2tcpip.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#endif
+
+#include <misc/byteorder.h>
+#include <misc/debug.h>
+#include <misc/print_macros.h>
+#include <misc/read_write_int.h>
+#include <misc/compare.h>
+
+#define BADDR_TYPE_NONE 0
+#define BADDR_TYPE_IPV4 1
+#define BADDR_TYPE_IPV6 2
+#ifdef BADVPN_LINUX
+    #define BADDR_TYPE_PACKET 5
+#endif
+
+#define BADDR_MAX_ADDR_LEN 128
+
+#define BIPADDR_MAX_PRINT_LEN 40
+#define BADDR_MAX_PRINT_LEN 120
+
+#define BADDR_PACKET_HEADER_TYPE_ETHERNET 1
+
+#define BADDR_PACKET_PACKET_TYPE_HOST 1
+#define BADDR_PACKET_PACKET_TYPE_BROADCAST 2
+#define BADDR_PACKET_PACKET_TYPE_MULTICAST 3
+#define BADDR_PACKET_PACKET_TYPE_OTHERHOST 4
+#define BADDR_PACKET_PACKET_TYPE_OUTGOING 5
+
+typedef struct {
+    int type;
+    union {
+        uint32_t ipv4;
+        uint8_t ipv6[16];
+    };
+} BIPAddr;
+
+static void BIPAddr_InitInvalid (BIPAddr *addr);
+
+static void BIPAddr_InitIPv4 (BIPAddr *addr, uint32_t ip);
+
+static void BIPAddr_InitIPv6 (BIPAddr *addr, uint8_t *ip);
+
+static void BIPAddr_Assert (BIPAddr *addr);
+
+static int BIPAddr_IsInvalid (BIPAddr *addr);
+
+static int BIPAddr_Resolve (BIPAddr *addr, char *str, int noresolve) WARN_UNUSED;
+
+static int BIPAddr_Compare (BIPAddr *addr1, BIPAddr *addr2);
+
+/**
+ * Converts an IP address to human readable form.
+ *
+ * @param addr IP address to convert
+ * @param out destination buffer. Must be at least BIPADDR_MAX_PRINT_LEN characters long.
+ */
+static void BIPAddr_Print (BIPAddr *addr, char *out);
+
+/**
+ * Socket address - IP address and transport protocol port number
+ */
+typedef struct {
+    int type;
+    union {
+        struct {
+            uint32_t ip;
+            uint16_t port;
+        } ipv4;
+        struct {
+            uint8_t ip[16];
+            uint16_t port;
+        } ipv6;
+        struct {
+            uint16_t phys_proto;
+            int interface_index;
+            int header_type;
+            int packet_type;
+            uint8_t phys_addr[8];
+        } packet;
+    };
+} BAddr;
+
+/**
+ * Makes an invalid address.
+ */
+static BAddr BAddr_MakeNone (void);
+
+/**
+ * Makes an IPv4 address.
+ *
+ * @param ip IP address in network byte order
+ * @param port port number in network byte order
+ */
+static BAddr BAddr_MakeIPv4 (uint32_t ip, uint16_t port);
+
+/**
+ * Makes an IPv6 address.
+ *
+ * @param ip IP address (16 bytes)
+ * @param port port number in network byte order
+ */
+static BAddr BAddr_MakeIPv6 (const uint8_t *ip, uint16_t port);
+
+/**
+ * Makes an address from a BIPAddr and port number.
+ *
+ * @param ipaddr the BIPAddr
+ * @param port port number in network byte order
+ */
+static BAddr BAddr_MakeFromIpaddrAndPort (BIPAddr ipaddr, uint16_t port);
+
+/**
+ * Deprecated, use BAddr_MakeNone.
+ */
+static void BAddr_InitNone (BAddr *addr);
+
+/**
+ * Deprecated, use BAddr_MakeIPv4.
+ */
+static void BAddr_InitIPv4 (BAddr *addr, uint32_t ip, uint16_t port);
+
+/**
+ * Deprecated, use BAddr_MakeIPv6.
+ */
+static void BAddr_InitIPv6 (BAddr *addr, uint8_t *ip, uint16_t port);
+
+/**
+ * Deprecated, use BAddr_MakeFromIpaddrAndPort.
+ */
+static void BAddr_InitFromIpaddrAndPort (BAddr *addr, BIPAddr ipaddr, uint16_t port);
+
+/**
+ * Initializes a packet socket (data link layer) address.
+ * Only Ethernet addresses are supported.
+ * 
+ * @param addr the object
+ * @param phys_proto identifier for the upper protocol, network byte order (EtherType)
+ * @param interface_index network interface index
+ * @param header_type data link layer header type. Must be BADDR_PACKET_HEADER_TYPE_ETHERNET.
+ * @param packet_type the manner in which packets are sent/received. Must be one of
+ *   BADDR_PACKET_PACKET_TYPE_HOST, BADDR_PACKET_PACKET_TYPE_BROADCAST,
+ *   BADDR_PACKET_PACKET_TYPE_MULTICAST, BADDR_PACKET_PACKET_TYPE_OTHERHOST,
+ *   BADDR_PACKET_PACKET_TYPE_OUTGOING.
+ * @param phys_addr data link layer address (MAC address)
+ */
+static void BAddr_InitPacket (BAddr *addr, uint16_t phys_proto, int interface_index, int header_type, int packet_type, uint8_t *phys_addr);
+
+/**
+ * Does nothing.
+ *
+ * @param addr the object
+ */
+static void BAddr_Assert (BAddr *addr);
+
+/**
+ * Determines whether the address is an invalid address.
+ *
+ * @param addr the object
+ * @return 1 if invalid, 0 if invalid
+ **/
+static int BAddr_IsInvalid (BAddr *addr);
+
+/**
+ * Returns the port number in the address.
+ *
+ * @param addr the object
+ *             Must be an IPv4 or IPv6 address.
+ * @return port number, in network byte order
+ */
+static uint16_t BAddr_GetPort (BAddr *addr);
+
+/**
+ * Returns the IP address in the address.
+ *
+ * @param addr the object
+ * @param ipaddr IP address will be returned here. If \a addr is not
+ *               an IPv4 or IPv6 address, an invalid address will be
+ *               returned.
+ */
+static void BAddr_GetIPAddr (BAddr *addr, BIPAddr *ipaddr);
+
+/**
+ * Sets the port number in the address.
+ *
+ * @param addr the object
+ *             Must be an IPv4 or IPv6 address.
+ * @param port port number, in network byte order
+ */
+static void BAddr_SetPort (BAddr *addr, uint16_t port);
+
+/**
+ * Converts an IP address to human readable form.
+ *
+ * @param addr address to convert
+ * @param out destination buffer. Must be at least BADDR_MAX_PRINT_LEN characters long.
+ */
+static void BAddr_Print (BAddr *addr, char *out);
+
+/**
+ * Resolves an address string.
+ * Format is "addr:port" for IPv4, "[addr]:port" for IPv6.
+ * addr is be a numeric address or a name.
+ * port is a numeric port number.
+ *
+ * @param addr output address
+ * @param name if not NULL, the name portion of the address will be
+ *             stored here
+ * @param name_len if name is not NULL, the size of the name buffer
+ * @param noresolve only accept numeric addresses. Avoids blocking the caller.
+ * @return 1 on success, 0 on parse error
+ */
+static int BAddr_Parse2 (BAddr *addr, char *str, char *name, int name_len, int noresolve) WARN_UNUSED;
+
+/**
+ * Resolves an address string.
+ * IPv4 input format is "a.b.c.d:p", where a.b.c.d is the IP address
+ * and d is the port number.
+ * IPv6 input format is "[addr]:p", where addr is an IPv6 addres in
+ * standard notation and p is the port number.
+ *
+ * @param addr output address
+ * @param name if not NULL, the name portion of the address will be
+ *             stored here
+ * @param name_len if name is not NULL, the size of the name buffer
+ * @return 1 on success, 0 on parse error
+ */
+static int BAddr_Parse (BAddr *addr, char *str, char *name, int name_len) WARN_UNUSED;
+
+static int BAddr_Compare (BAddr *addr1, BAddr *addr2);
+
+static int BAddr_CompareOrder (BAddr *addr1, BAddr *addr2);
+
+void BIPAddr_InitInvalid (BIPAddr *addr)
+{
+    addr->type = BADDR_TYPE_NONE;
+}
+
+void BIPAddr_InitIPv4 (BIPAddr *addr, uint32_t ip)
+{
+    addr->type = BADDR_TYPE_IPV4;
+    addr->ipv4 = ip;
+}
+
+void BIPAddr_InitIPv6 (BIPAddr *addr, uint8_t *ip)
+{
+    addr->type = BADDR_TYPE_IPV6;
+    memcpy(addr->ipv6, ip, 16);
+}
+
+void BIPAddr_Assert (BIPAddr *addr)
+{
+    switch (addr->type) {
+        case BADDR_TYPE_NONE:
+        case BADDR_TYPE_IPV4:
+        case BADDR_TYPE_IPV6:
+            return;
+        default:
+            ASSERT(0);
+    }
+}
+
+int BIPAddr_IsInvalid (BIPAddr *addr)
+{
+    BIPAddr_Assert(addr);
+    
+    return (addr->type == BADDR_TYPE_NONE);
+}
+
+int BIPAddr_Resolve (BIPAddr *addr, char *str, int noresolve)
+{
+    int len = strlen(str);
+    
+    char *addr_start;
+    int addr_len;
+    
+    // determine address type
+    if (len >= 1 && str[0] == '[' && str[len - 1] == ']') {
+        addr->type = BADDR_TYPE_IPV6;
+        addr_start = str + 1;
+        addr_len = len - 2;
+    } else {
+        addr->type = BADDR_TYPE_IPV4;
+        addr_start = str;
+        addr_len = len;
+    }
+    
+    // copy
+    char addr_str[BADDR_MAX_ADDR_LEN + 1];
+    if (addr_len > BADDR_MAX_ADDR_LEN) {
+        return 0;
+    }
+    memcpy(addr_str, addr_start, addr_len);
+    addr_str[addr_len] = '\0';
+    
+    // initialize hints
+    struct addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    switch (addr->type) {
+        case BADDR_TYPE_IPV6:
+            hints.ai_family = AF_INET6;
+            break;
+        case BADDR_TYPE_IPV4:
+            hints.ai_family = AF_INET;
+            break;
+    }
+    if (noresolve) {
+        hints.ai_flags |= AI_NUMERICHOST;
+    }
+    
+    // call getaddrinfo
+    struct addrinfo *addrs;
+    int res;
+    if ((res = getaddrinfo(addr_str, NULL, &hints, &addrs)) != 0) {
+        return 0;
+    }
+    
+    // set address
+    switch (addr->type) {
+        case BADDR_TYPE_IPV6:
+            memcpy(addr->ipv6, ((struct sockaddr_in6 *)addrs->ai_addr)->sin6_addr.s6_addr, sizeof(addr->ipv6));
+            break;
+        case BADDR_TYPE_IPV4:
+            addr->ipv4 = ((struct sockaddr_in *)addrs->ai_addr)->sin_addr.s_addr;
+            break;
+    }
+    
+    freeaddrinfo(addrs);
+    
+    return 1;
+}
+
+int BIPAddr_Compare (BIPAddr *addr1, BIPAddr *addr2)
+{
+    BIPAddr_Assert(addr1);
+    BIPAddr_Assert(addr2);
+    
+    if (addr1->type != addr2->type) {
+        return 0;
+    }
+    
+    switch (addr1->type) {
+        case BADDR_TYPE_NONE:
+            return 0;
+        case BADDR_TYPE_IPV4:
+            return (addr1->ipv4 == addr2->ipv4);
+        case BADDR_TYPE_IPV6:
+            return (!memcmp(addr1->ipv6, addr2->ipv6, sizeof(addr1->ipv6)));
+        default:
+            ASSERT(0)
+            return 0;
+    }
+}
+
+uint16_t BAddr_GetPort (BAddr *addr)
+{
+    BAddr_Assert(addr);
+    ASSERT(addr->type == BADDR_TYPE_IPV4 || addr->type == BADDR_TYPE_IPV6)
+    
+    switch (addr->type) {
+        case BADDR_TYPE_IPV4:
+            return addr->ipv4.port;
+        case BADDR_TYPE_IPV6:
+            return addr->ipv6.port;
+        default:
+            ASSERT(0)
+            return 0;
+    }
+}
+
+void BAddr_GetIPAddr (BAddr *addr, BIPAddr *ipaddr)
+{
+    BAddr_Assert(addr);
+    
+    switch (addr->type) {
+        case BADDR_TYPE_IPV4:
+            BIPAddr_InitIPv4(ipaddr, addr->ipv4.ip);
+            return;
+        case BADDR_TYPE_IPV6:
+            BIPAddr_InitIPv6(ipaddr, addr->ipv6.ip);
+            return;
+        default:
+            BIPAddr_InitInvalid(ipaddr);
+    }
+}
+
+void BAddr_SetPort (BAddr *addr, uint16_t port)
+{
+    BAddr_Assert(addr);
+    ASSERT(addr->type == BADDR_TYPE_IPV4 || addr->type == BADDR_TYPE_IPV6)
+    
+    switch (addr->type) {
+        case BADDR_TYPE_IPV4:
+            addr->ipv4.port = port;
+            break;
+        case BADDR_TYPE_IPV6:
+            addr->ipv6.port = port;
+            break;
+        default:
+            ASSERT(0);
+    }
+}
+
+void BIPAddr_Print (BIPAddr *addr, char *out)
+{
+    switch (addr->type) {
+        case BADDR_TYPE_NONE:
+            sprintf(out, "(none)");
+            break;
+        case BADDR_TYPE_IPV4:
+            sprintf(out, "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8,
+                *((uint8_t *)&addr->ipv4 + 0),
+                *((uint8_t *)&addr->ipv4 + 1),
+                *((uint8_t *)&addr->ipv4 + 2),
+                *((uint8_t *)&addr->ipv4 + 3)
+            );
+            break;
+        case BADDR_TYPE_IPV6: {
+            const char *ptr = (const char *)addr->ipv6;
+            sprintf(out,
+                "%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16":"
+                "%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16,
+                badvpn_read_be16(ptr + 0),
+                badvpn_read_be16(ptr + 2),
+                badvpn_read_be16(ptr + 4),
+                badvpn_read_be16(ptr + 6),
+                badvpn_read_be16(ptr + 8),
+                badvpn_read_be16(ptr + 10),
+                badvpn_read_be16(ptr + 12),
+                badvpn_read_be16(ptr + 14)
+            );
+        } break;
+        default:
+            ASSERT(0);
+    }
+}
+
+BAddr BAddr_MakeNone (void)
+{
+    BAddr addr;
+    addr.type = BADDR_TYPE_NONE;
+    return addr;
+}
+
+BAddr BAddr_MakeIPv4 (uint32_t ip, uint16_t port)
+{
+    BAddr addr;
+    addr.type = BADDR_TYPE_IPV4;
+    addr.ipv4.ip = ip;
+    addr.ipv4.port = port;
+    return addr;
+}
+
+BAddr BAddr_MakeIPv6 (const uint8_t *ip, uint16_t port)
+{
+    BAddr addr;
+    addr.type = BADDR_TYPE_IPV6;
+    memcpy(addr.ipv6.ip, ip, 16);
+    addr.ipv6.port = port;
+    return addr;
+}
+
+BAddr BAddr_MakeFromIpaddrAndPort (BIPAddr ipaddr, uint16_t port)
+{
+    BIPAddr_Assert(&ipaddr);
+    
+    switch (ipaddr.type) {
+        case BADDR_TYPE_NONE:
+            return BAddr_MakeNone();
+        case BADDR_TYPE_IPV4:
+            return BAddr_MakeIPv4(ipaddr.ipv4, port);
+        case BADDR_TYPE_IPV6:
+            return BAddr_MakeIPv6(ipaddr.ipv6, port);
+        default:
+            ASSERT(0);
+            return BAddr_MakeNone();
+    }
+}
+
+void BAddr_InitNone (BAddr *addr)
+{
+    *addr = BAddr_MakeNone();
+}
+
+void BAddr_InitIPv4 (BAddr *addr, uint32_t ip, uint16_t port)
+{
+    *addr = BAddr_MakeIPv4(ip, port);
+}
+
+void BAddr_InitIPv6 (BAddr *addr, uint8_t *ip, uint16_t port)
+{
+    *addr = BAddr_MakeIPv6(ip, port);
+}
+
+void BAddr_InitFromIpaddrAndPort (BAddr *addr, BIPAddr ipaddr, uint16_t port)
+{
+    BIPAddr_Assert(&ipaddr);
+    
+    *addr = BAddr_MakeFromIpaddrAndPort(ipaddr, port);
+}
+
+#ifdef BADVPN_LINUX
+
+void BAddr_InitPacket (BAddr *addr, uint16_t phys_proto, int interface_index, int header_type, int packet_type, uint8_t *phys_addr)
+{
+    ASSERT(header_type == BADDR_PACKET_HEADER_TYPE_ETHERNET)
+    ASSERT(packet_type == BADDR_PACKET_PACKET_TYPE_HOST || packet_type == BADDR_PACKET_PACKET_TYPE_BROADCAST || 
+           packet_type == BADDR_PACKET_PACKET_TYPE_MULTICAST || packet_type == BADDR_PACKET_PACKET_TYPE_OTHERHOST ||
+           packet_type == BADDR_PACKET_PACKET_TYPE_OUTGOING)
+    
+    addr->type = BADDR_TYPE_PACKET;
+    addr->packet.phys_proto = phys_proto;
+    addr->packet.interface_index = interface_index;
+    addr->packet.header_type = header_type;
+    addr->packet.packet_type = packet_type;
+    memcpy(addr->packet.phys_addr, phys_addr, 6);
+}
+
+#endif
+
+void BAddr_Assert (BAddr *addr)
+{
+    switch (addr->type) {
+        case BADDR_TYPE_NONE:
+        case BADDR_TYPE_IPV4:
+        case BADDR_TYPE_IPV6:
+        #ifdef BADVPN_LINUX
+        case BADDR_TYPE_PACKET:
+        #endif
+            return;
+        default:
+            ASSERT(0);
+    }
+}
+
+int BAddr_IsInvalid (BAddr *addr)
+{
+    BAddr_Assert(addr);
+    
+    return (addr->type == BADDR_TYPE_NONE);
+}
+
+void BAddr_Print (BAddr *addr, char *out)
+{
+    BAddr_Assert(addr);
+    
+    BIPAddr ipaddr;
+    
+    switch (addr->type) {
+        case BADDR_TYPE_NONE:
+            sprintf(out, "(none)");
+            break;
+        case BADDR_TYPE_IPV4:
+            BIPAddr_InitIPv4(&ipaddr, addr->ipv4.ip);
+            BIPAddr_Print(&ipaddr, out);
+            sprintf(out + strlen(out), ":%"PRIu16, ntoh16(addr->ipv4.port));
+            break;
+        case BADDR_TYPE_IPV6:
+            BIPAddr_InitIPv6(&ipaddr, addr->ipv6.ip);
+            BIPAddr_Print(&ipaddr, out);
+            sprintf(out + strlen(out), ":%"PRIu16, ntoh16(addr->ipv6.port));
+            break;
+        #ifdef BADVPN_LINUX
+        case BADDR_TYPE_PACKET:
+            ASSERT(addr->packet.header_type == BADDR_PACKET_HEADER_TYPE_ETHERNET)
+            sprintf(out, "proto=%"PRIu16",ifindex=%d,htype=eth,ptype=%d,addr=%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8,
+                    addr->packet.phys_proto, (int)addr->packet.interface_index, (int)addr->packet.packet_type,
+                    addr->packet.phys_addr[0], addr->packet.phys_addr[1], addr->packet.phys_addr[2],
+                    addr->packet.phys_addr[3], addr->packet.phys_addr[4], addr->packet.phys_addr[5]);
+            break;
+        #endif
+        default:
+            ASSERT(0);
+    }
+}
+
+int BAddr_Parse2 (BAddr *addr, char *str, char *name, int name_len, int noresolve)
+{
+    int len = strlen(str);
+    if (len < 1 || len > 1000) {
+        return 0;
+    }
+    
+    int addr_start;
+    int addr_len;
+    int port_start;
+    int port_len;
+    
+    // leading '[' indicates an IPv6 address
+    if (str[0] == '[') {
+        addr->type = BADDR_TYPE_IPV6;
+        // find ']'
+        int i=1;
+        while (i < len && str[i] != ']') i++;
+        if (i >= len) {
+            return 0;
+        }
+        addr_start = 1;
+        addr_len = i - addr_start;
+        // follows ':' and port number
+        if (i + 1 >= len || str[i + 1] != ':') {
+            return 0;
+        }
+        port_start = i + 2;
+        port_len = len - port_start;
+    }
+    // otherwise it's an IPv4 address
+    else {
+        addr->type = BADDR_TYPE_IPV4;
+        // find ':'
+        int i=0;
+        while (i < len && str[i] != ':') i++;
+        if (i >= len) {
+            return 0;
+        }
+        addr_start = 0;
+        addr_len = i - addr_start;
+        port_start = i + 1;
+        port_len = len - port_start;
+    }
+    
+    // copy address and port to zero-terminated buffers
+    
+    char addr_str[128];
+    if (addr_len >= sizeof(addr_str)) {
+        return 0;
+    }
+    memcpy(addr_str, str + addr_start, addr_len);
+    addr_str[addr_len] = '\0';
+    
+    char port_str[6];
+    if (port_len >= sizeof(port_str)) {
+        return 0;
+    }
+    memcpy(port_str, str + port_start, port_len);
+    port_str[port_len] = '\0';
+    
+    // parse port
+    char *err;
+    long int conv_res = strtol(port_str, &err, 10);
+    if (port_str[0] == '\0' || *err != '\0') {
+        return 0;
+    }
+    if (conv_res < 0 || conv_res > UINT16_MAX) {
+        return 0;
+    }
+    uint16_t port = conv_res;
+    port = hton16(port);
+    
+    // initialize hints
+    struct addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    switch (addr->type) {
+        case BADDR_TYPE_IPV6:
+            hints.ai_family = AF_INET6;
+            break;
+        case BADDR_TYPE_IPV4:
+            hints.ai_family = AF_INET;
+            break;
+    }
+    if (noresolve) {
+        hints.ai_flags |= AI_NUMERICHOST;
+    }
+    
+    // call getaddrinfo
+    struct addrinfo *addrs;
+    int res;
+    if ((res = getaddrinfo(addr_str, NULL, &hints, &addrs)) != 0) {
+        return 0;
+    }
+    
+    // set address
+    switch (addr->type) {
+        case BADDR_TYPE_IPV6:
+            memcpy(addr->ipv6.ip, ((struct sockaddr_in6 *)addrs->ai_addr)->sin6_addr.s6_addr, sizeof(addr->ipv6.ip));
+            addr->ipv6.port = port;
+            break;
+        case BADDR_TYPE_IPV4:
+            addr->ipv4.ip = ((struct sockaddr_in *)addrs->ai_addr)->sin_addr.s_addr;
+            addr->ipv4.port = port;
+            break;
+    }
+    
+    freeaddrinfo(addrs);
+    
+    if (name) {
+        if (strlen(addr_str) >= name_len) {
+            return 0;
+        }
+        strcpy(name, addr_str);
+    }
+    
+    return 1;
+}
+
+int BAddr_Parse (BAddr *addr, char *str, char *name, int name_len)
+{
+    return BAddr_Parse2(addr, str, name, name_len, 0);
+}
+
+int BAddr_Compare (BAddr *addr1, BAddr *addr2)
+{
+    BAddr_Assert(addr1);
+    BAddr_Assert(addr2);
+    
+    if (addr1->type != addr2->type) {
+        return 0;
+    }
+    
+    switch (addr1->type) {
+        case BADDR_TYPE_IPV4:
+            return (addr1->ipv4.ip == addr2->ipv4.ip && addr1->ipv4.port == addr2->ipv4.port);
+        case BADDR_TYPE_IPV6:
+            return (!memcmp(addr1->ipv6.ip, addr2->ipv6.ip, sizeof(addr1->ipv6.ip)) && addr1->ipv6.port == addr2->ipv6.port);
+        default:
+            return 0;
+    }
+}
+
+int BAddr_CompareOrder (BAddr *addr1, BAddr *addr2)
+{
+    BAddr_Assert(addr1);
+    BAddr_Assert(addr2);
+    
+    int cmp = B_COMPARE(addr1->type, addr2->type);
+    if (cmp) {
+        return cmp;
+    }
+    
+    switch (addr1->type) {
+        case BADDR_TYPE_NONE: {
+            return 0;
+        } break;
+        case BADDR_TYPE_IPV4: {
+            uint32_t ip1 = ntoh32(addr1->ipv4.ip);
+            uint32_t ip2 = ntoh32(addr2->ipv4.ip);
+            cmp = B_COMPARE(ip1, ip2);
+            if (cmp) {
+                return cmp;
+            }
+            uint16_t port1 = ntoh16(addr1->ipv4.port);
+            uint16_t port2 = ntoh16(addr2->ipv4.port);
+            return B_COMPARE(port1, port2);
+        } break;
+        case BADDR_TYPE_IPV6: {
+            cmp = memcmp(addr1->ipv6.ip, addr2->ipv6.ip, sizeof(addr1->ipv6.ip));
+            if (cmp) {
+                return B_COMPARE(cmp, 0);
+            }
+            uint16_t port1 = ntoh16(addr1->ipv6.port);
+            uint16_t port2 = ntoh16(addr2->ipv6.port);
+            return B_COMPARE(port1, port2);
+        } break;
+        default: {
+            return 0;
+        } break;
+    }
+}
+
+#endif
diff --git a/external/badvpn_dns/system/BConnection.h b/external/badvpn_dns/system/BConnection.h
new file mode 100644
index 0000000..eac25ec
--- /dev/null
+++ b/external/badvpn_dns/system/BConnection.h
@@ -0,0 +1,369 @@
+/**
+ * @file BConnection.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_SYSTEM_BCONNECTION
+#define BADVPN_SYSTEM_BCONNECTION
+
+#include <misc/debug.h>
+#include <flow/StreamPassInterface.h>
+#include <flow/StreamRecvInterface.h>
+#include <system/BAddr.h>
+#include <system/BReactor.h>
+#include <system/BNetwork.h>
+
+
+
+/**
+ * Checks if the given address is supported by {@link BConnection} and related objects.
+ * 
+ * @param addr address to check. Must be a proper {@link BAddr} object according to
+ *             {@link BIPAddr_Assert}.
+ * @return 1 if supported, 0 if not
+ */
+int BConnection_AddressSupported (BAddr addr);
+
+
+
+struct BListener_s;
+
+/**
+ * Object which listens for connections on an address.
+ * When a connection is ready, the {@link BListener_handler} handler is called, from which
+ * the connection can be accepted into a new {@link BConnection} object.
+ */
+typedef struct BListener_s BListener;
+
+/**
+ * Handler called when a new connection is ready.
+ * The connection can be accepted by calling {@link BConnection_Init} with the a
+ * BCONNECTION_SOURCE_LISTENER 'source' argument.
+ * If no attempt is made to accept the connection from the job closure of this handler,
+ * the connection will be discarded.
+ * 
+ * @param user as in {@link BListener_Init}
+ */
+typedef void (*BListener_handler) (void *user);
+
+/**
+ * Initializes the object.
+ * {@link BNetwork_GlobalInit} must have been done.
+ * 
+ * @param o the object
+ * @param addr address to listen on
+ * @param reactor reactor we live in
+ * @param user argument to handler
+ * @param handler handler called when a connection can be accepted
+ * @return 1 on success, 0 on failure
+ */
+int BListener_Init (BListener *o, BAddr addr, BReactor *reactor, void *user,
+                    BListener_handler handler) WARN_UNUSED;
+
+#ifndef BADVPN_USE_WINAPI
+/**
+ * Initializes the object for listening on a Unix socket.
+ * {@link BNetwork_GlobalInit} must have been done.
+ * 
+ * @param o the object
+ * @param socket_path socket path for listening
+ * @param reactor reactor we live in
+ * @param user argument to handler
+ * @param handler handler called when a connection can be accepted
+ * @return 1 on success, 0 on failure
+ */
+int BListener_InitUnix (BListener *o, const char *socket_path, BReactor *reactor, void *user,
+                        BListener_handler handler) WARN_UNUSED;
+#endif
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void BListener_Free (BListener *o);
+
+
+
+struct BConnector_s;
+
+/**
+ * Object which connects to an address.
+ * When the connection attempt finishes, the {@link BConnector_handler} handler is called, from which,
+ * if successful, the resulting connection can be moved to a new {@link BConnection} object.
+ */
+typedef struct BConnector_s BConnector;
+
+/**
+ * Handler called when the connection attempt finishes.
+ * If the connection attempt succeeded (is_error==0), the new connection can be used by calling
+ * {@link BConnection_Init} with a BCONNECTION_SOURCE_TYPE_CONNECTOR 'source' argument.
+ * This handler will be called at most once. The connector object need not be freed after it
+ * is called. 
+ * 
+ * @param user as in {@link BConnector_Init}
+ * @param is_error whether the connection attempt succeeded (0) or failed (1)
+ */
+typedef void (*BConnector_handler) (void *user, int is_error);
+
+/**
+ * Initializes the object.
+ * {@link BNetwork_GlobalInit} must have been done.
+ * 
+ * @param o the object
+ * @param addr address to connect to
+ * @param reactor reactor we live in
+ * @param user argument to handler
+ * @param handler handler called when the connection attempt finishes
+ * @return 1 on success, 0 on failure
+ */
+int BConnector_Init (BConnector *o, BAddr addr, BReactor *reactor, void *user,
+                     BConnector_handler handler) WARN_UNUSED;
+
+#ifndef BADVPN_USE_WINAPI
+/**
+ * Initializes the object for connecting to a Unix socket.
+ * {@link BNetwork_GlobalInit} must have been done.
+ * 
+ * @param o the object
+ * @param socket_path socket path for connecting
+ * @param reactor reactor we live in
+ * @param user argument to handler
+ * @param handler handler called when the connection attempt finishes
+ * @return 1 on success, 0 on failure
+ */
+int BConnector_InitUnix (BConnector *o, const char *socket_path, BReactor *reactor, void *user,
+                         BConnector_handler handler) WARN_UNUSED;
+#endif
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void BConnector_Free (BConnector *o);
+
+
+
+#define BCONNECTION_SOURCE_TYPE_LISTENER 1
+#define BCONNECTION_SOURCE_TYPE_CONNECTOR 2
+#define BCONNECTION_SOURCE_TYPE_PIPE 3
+
+struct BConnection_source {
+    int type;
+    union {
+        struct {
+            BListener *listener;
+            BAddr *out_addr;
+        } listener;
+        struct {
+            BConnector *connector;
+        } connector;
+#ifndef BADVPN_USE_WINAPI
+        struct {
+            int pipefd;
+        } pipe;
+#endif
+    } u;
+};
+
+static struct BConnection_source BConnection_source_listener (BListener *listener, BAddr *out_addr)
+{
+    struct BConnection_source s;
+    s.type = BCONNECTION_SOURCE_TYPE_LISTENER;
+    s.u.listener.listener = listener;
+    s.u.listener.out_addr = out_addr;
+    return s;
+}
+
+static struct BConnection_source BConnection_source_connector (BConnector *connector)
+{
+    struct BConnection_source s;
+    s.type = BCONNECTION_SOURCE_TYPE_CONNECTOR;
+    s.u.connector.connector = connector;
+    return s;
+}
+
+#ifndef BADVPN_USE_WINAPI
+static struct BConnection_source BConnection_source_pipe (int pipefd)
+{
+    struct BConnection_source s;
+    s.type = BCONNECTION_SOURCE_TYPE_PIPE;
+    s.u.pipe.pipefd = pipefd;
+    return s;
+}
+#endif
+
+struct BConnection_s;
+
+/**
+ * Object which represents a stream connection. This is usually a TCP connection, either client
+ * or server, but may also be used with any file descriptor (e.g. pipe) on Unix-like systems.
+ * Sending and receiving is performed via {@link StreamPassInterface} and {@link StreamRecvInterface},
+ * respectively.
+ */
+typedef struct BConnection_s BConnection;
+
+#define BCONNECTION_EVENT_ERROR 1
+#define BCONNECTION_EVENT_RECVCLOSED 2
+
+/**
+ * Handler called when an error occurs or the receive end of the connection was closed
+ * by the remote peer.
+ * - If event is BCONNECTION_EVENT_ERROR, the connection is no longer usable and must be freed
+ *   from withing the job closure of this handler. No further I/O or interface initialization
+ *   must occur.
+ * - If event is BCONNECTION_EVENT_RECVCLOSED, no further receive I/O or receive interface
+ *   initialization must occur. It is guarantted that the receive interface was initialized.
+ * 
+ * @param user as in {@link BConnection_Init} or {@link BConnection_SetHandlers}
+ * @param event what happened - BCONNECTION_EVENT_ERROR or BCONNECTION_EVENT_RECVCLOSED
+ */
+typedef void (*BConnection_handler) (void *user, int event);
+
+/**
+ * Initializes the object.
+ * {@link BNetwork_GlobalInit} must have been done.
+ * 
+ * @param o the object
+ * @param source specifies what the connection comes from. This argument must be created with one of the
+ *               following macros:
+ *               - BCONNECTION_SOURCE_LISTENER(BListener *, BAddr *)
+ *                 Accepts a connection ready on a {@link BListener} object. Must be called from the job
+ *                 closure of the listener's {@link BListener_handler}, and must be the first attempt
+ *                 for this handler invocation. The address of the client is written if the address
+ *                 argument is not NULL (theoretically an invalid address may be returned).
+ *               - BCONNECTION_SOURCE_CONNECTOR(Bconnector *)
+ *                 Uses a connection establised with {@link BConnector}. Must be called from the job
+ *                 closure of the connector's {@link BConnector_handler}, the handler must be reporting
+ *                 successful connection, and must be the first attempt for this handler invocation.
+ *               - BCONNECTION_SOURCE_PIPE(int)
+ *                 On Unix-like systems, uses the provided file descriptor. The file descriptor number must
+ *                 be >=0.
+ * @param reactor reactor we live in
+ * @param user argument to handler
+ * @param handler handler called when an error occurs or the receive end of the connection was closed
+ *                by the remote peer.
+ * @return 1 on success, 0 on failure
+ */
+int BConnection_Init (BConnection *o, struct BConnection_source source, BReactor *reactor, void *user,
+                      BConnection_handler handler) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * The send and receive interfaces must not be initialized.
+ * If the connection was created with a BCONNECTION_SOURCE_PIPE 'source' argument, the file descriptor
+ * will not be closed.
+ * 
+ * @param o the object
+ */
+void BConnection_Free (BConnection *o);
+
+/**
+ * Updates the handler function.
+ * 
+ * @param o the object
+ * @param user argument to handler
+ * @param handler new handler function, as in {@link BConnection_Init}. Additionally, may be NULL to
+ *                remove the current handler. In this case, a proper handler must be set before anything
+ *                can happen with the connection. This is used when moving the connection ownership from
+ *                one module to another.
+ */
+void BConnection_SetHandlers (BConnection *o, void *user, BConnection_handler handler);
+
+/**
+ * Sets the SO_SNDBUF socket option.
+ * 
+ * @param o the object
+ * @param buf_size value for SO_SNDBUF option
+ * @return 1 on success, 0 on failure
+ */
+int BConnection_SetSendBuffer (BConnection *o, int buf_size);
+
+/**
+ * Initializes the send interface for the connection.
+ * The send interface must not be initialized.
+ * 
+ * @param o the object
+ */
+void BConnection_SendAsync_Init (BConnection *o);
+
+/**
+ * Frees the send interface for the connection.
+ * The send interface must be initialized.
+ * If the send interface was busy when this is called, the connection is no longer usable and must be
+ * freed before any further I/O or interface initialization.
+ * 
+ * @param o the object
+ */
+void BConnection_SendAsync_Free (BConnection *o);
+
+/**
+ * Returns the send interface.
+ * The send interface must be initialized.
+ * 
+ * @param o the object
+ * @return send interface
+ */
+StreamPassInterface * BConnection_SendAsync_GetIf (BConnection *o);
+
+/**
+ * Initializes the receive interface for the connection.
+ * The receive interface must not be initialized.
+ * 
+ * @param o the object
+ */
+void BConnection_RecvAsync_Init (BConnection *o);
+
+/**
+ * Frees the receive interface for the connection.
+ * The receive interface must be initialized.
+ * If the receive interface was busy when this is called, the connection is no longer usable and must be
+ * freed before any further I/O or interface initialization.
+ * 
+ * @param o the object
+ */
+void BConnection_RecvAsync_Free (BConnection *o);
+
+/**
+ * Returns the receive interface.
+ * The receive interface must be initialized.
+ * 
+ * @param o the object
+ * @return receive interface
+ */
+StreamRecvInterface * BConnection_RecvAsync_GetIf (BConnection *o);
+
+
+
+#ifdef BADVPN_USE_WINAPI
+#include "BConnection_win.h"
+#else
+#include "BConnection_unix.h"
+#endif
+
+#endif
diff --git a/external/badvpn_dns/system/BConnectionGeneric.h b/external/badvpn_dns/system/BConnectionGeneric.h
new file mode 100644
index 0000000..9417555
--- /dev/null
+++ b/external/badvpn_dns/system/BConnectionGeneric.h
@@ -0,0 +1,144 @@
+/**
+ * @file BConnectionGeneric.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_BCONNECTION_GENERIC_H
+#define BADVPN_BCONNECTION_GENERIC_H
+
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/strdup.h>
+#include <base/BLog.h>
+#include <system/BConnection.h>
+
+#define BCONNECTION_ADDR_TYPE_BADDR 1
+#define BCONNECTION_ADDR_TYPE_UNIX 2
+
+struct BConnection_addr {
+    int type;
+    union {
+        BAddr baddr;
+        struct {
+            const char *str;
+            size_t len;
+        } unix_socket_path;
+    } u;
+};
+
+static struct BConnection_addr BConnection_addr_baddr (BAddr baddr)
+{
+    struct BConnection_addr addr;
+    addr.type = BCONNECTION_ADDR_TYPE_BADDR;
+    addr.u.baddr = baddr;
+    return addr;
+}
+
+static struct BConnection_addr BConnection_addr_unix (const char *unix_socket_path, size_t len)
+{
+    struct BConnection_addr addr;
+    addr.type = BCONNECTION_ADDR_TYPE_UNIX;
+    addr.u.unix_socket_path.str = unix_socket_path;
+    addr.u.unix_socket_path.len = len;
+    return addr;
+}
+
+static int BListener_InitGeneric (BListener *o, struct BConnection_addr addr, BReactor *reactor, void *user,
+                                  BListener_handler handler) WARN_UNUSED;
+static int BConnector_InitGeneric (BConnector *o, struct BConnection_addr addr, BReactor *reactor, void *user,
+                                   BConnector_handler handler) WARN_UNUSED;
+
+static int BListener_InitGeneric (BListener *o, struct BConnection_addr addr, BReactor *reactor, void *user,
+                                  BListener_handler handler)
+{
+    ASSERT(handler)
+    BNetwork_Assert();
+    ASSERT(addr.type != BCONNECTION_ADDR_TYPE_UNIX || !memchr(addr.u.unix_socket_path.str, '\0', addr.u.unix_socket_path.len))
+    
+    switch (addr.type) {
+        case BCONNECTION_ADDR_TYPE_BADDR: {
+            return BListener_Init(o, addr.u.baddr, reactor, user, handler);
+        } break;
+        
+        case BCONNECTION_ADDR_TYPE_UNIX: {
+#ifdef BADVPN_USE_WINAPI
+            BLog_LogToChannel(BLOG_CHANNEL_BConnection, BLOG_ERROR, "unix sockets not supported");
+            return 0;
+#else
+            char *str = b_strdup_bin(addr.u.unix_socket_path.str, addr.u.unix_socket_path.len);
+            if (!str) {
+                BLog_LogToChannel(BLOG_CHANNEL_BConnection, BLOG_ERROR, "b_strdup_bin failed");
+                return 0;
+            }
+            int res = BListener_InitUnix(o, str, reactor, user, handler);
+            free(str);
+            return res;
+#endif
+        } break;
+        
+        default:
+            ASSERT(0);
+            return 0;
+    }
+}
+
+static int BConnector_InitGeneric (BConnector *o, struct BConnection_addr addr, BReactor *reactor, void *user,
+                                   BConnector_handler handler)
+{
+    ASSERT(handler)
+    BNetwork_Assert();
+    ASSERT(addr.type != BCONNECTION_ADDR_TYPE_UNIX || !memchr(addr.u.unix_socket_path.str, '\0', addr.u.unix_socket_path.len))
+    
+    switch (addr.type) {
+        case BCONNECTION_ADDR_TYPE_BADDR: {
+            return BConnector_Init(o, addr.u.baddr, reactor, user, handler);
+        } break;
+        
+        case BCONNECTION_ADDR_TYPE_UNIX: {
+#ifdef BADVPN_USE_WINAPI
+            BLog_LogToChannel(BLOG_CHANNEL_BConnection, BLOG_ERROR, "unix sockets not supported");
+            return 0;
+#else
+            char *str = b_strdup_bin(addr.u.unix_socket_path.str, addr.u.unix_socket_path.len);
+            if (!str) {
+                BLog_LogToChannel(BLOG_CHANNEL_BConnection, BLOG_ERROR, "b_strdup_bin failed");
+                return 0;
+            }
+            int res = BConnector_InitUnix(o, str, reactor, user, handler);
+            free(str);
+            return res;
+#endif
+        } break;
+        
+        default:
+            ASSERT(0);
+            return 0;
+    }
+}
+
+#endif
diff --git a/external/badvpn_dns/system/BConnection_unix.c b/external/badvpn_dns/system/BConnection_unix.c
new file mode 100644
index 0000000..f6fa356
--- /dev/null
+++ b/external/badvpn_dns/system/BConnection_unix.c
@@ -0,0 +1,1057 @@
+/**
+ * @file BConnection_unix.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <misc/nonblocking.h>
+#include <misc/strdup.h>
+#include <base/BLog.h>
+
+#include "BConnection.h"
+
+#include <generated/blog_channel_BConnection.h>
+
+#define MAX_UNIX_SOCKET_PATH 200
+
+#define SEND_STATE_NOT_INITED 0
+#define SEND_STATE_READY 1
+#define SEND_STATE_BUSY 2
+
+#define RECV_STATE_NOT_INITED 0
+#define RECV_STATE_READY 1
+#define RECV_STATE_BUSY 2
+#define RECV_STATE_INITED_CLOSED 3
+#define RECV_STATE_NOT_INITED_CLOSED 4
+
+struct sys_addr {
+    socklen_t len;
+    union {
+        struct sockaddr generic;
+        struct sockaddr_in ipv4;
+        struct sockaddr_in6 ipv6;
+    } addr;
+};
+
+struct unix_addr {
+    socklen_t len;
+    union {
+        struct sockaddr_un addr;
+        uint8_t bytes[offsetof(struct sockaddr_un, sun_path) + MAX_UNIX_SOCKET_PATH + 1];
+    } u;
+};
+
+static int build_unix_address (struct unix_addr *out, const char *socket_path);
+static void addr_socket_to_sys (struct sys_addr *out, BAddr addr);
+static void addr_sys_to_socket (BAddr *out, struct sys_addr addr);
+static void listener_fd_handler (BListener *o, int events);
+static void listener_default_job_handler (BListener *o);
+static void connector_fd_handler (BConnector *o, int events);
+static void connector_job_handler (BConnector *o);
+static void connection_report_error (BConnection *o);
+static void connection_send (BConnection *o);
+static void connection_recv (BConnection *o);
+static void connection_fd_handler (BConnection *o, int events);
+static void connection_send_job_handler (BConnection *o);
+static void connection_recv_job_handler (BConnection *o);
+static void connection_send_if_handler_send (BConnection *o, uint8_t *data, int data_len);
+static void connection_recv_if_handler_recv (BConnection *o, uint8_t *data, int data_len);
+
+static int build_unix_address (struct unix_addr *out, const char *socket_path)
+{
+    ASSERT(socket_path);
+    
+    if (strlen(socket_path) > MAX_UNIX_SOCKET_PATH) {
+        return 0;
+    }
+    
+    out->len = offsetof(struct sockaddr_un, sun_path) + strlen(socket_path) + 1;
+    out->u.addr.sun_family = AF_UNIX;
+    strcpy(out->u.addr.sun_path, socket_path);
+    
+    return 1;
+}
+
+static void addr_socket_to_sys (struct sys_addr *out, BAddr addr)
+{
+    switch (addr.type) {
+        case BADDR_TYPE_IPV4: {
+            out->len = sizeof(out->addr.ipv4);
+            memset(&out->addr.ipv4, 0, sizeof(out->addr.ipv4));
+            out->addr.ipv4.sin_family = AF_INET;
+            out->addr.ipv4.sin_port = addr.ipv4.port;
+            out->addr.ipv4.sin_addr.s_addr = addr.ipv4.ip;
+        } break;
+        
+        case BADDR_TYPE_IPV6: {
+            out->len = sizeof(out->addr.ipv6);
+            memset(&out->addr.ipv6, 0, sizeof(out->addr.ipv6));
+            out->addr.ipv6.sin6_family = AF_INET6;
+            out->addr.ipv6.sin6_port = addr.ipv6.port;
+            out->addr.ipv6.sin6_flowinfo = 0;
+            memcpy(out->addr.ipv6.sin6_addr.s6_addr, addr.ipv6.ip, 16);
+            out->addr.ipv6.sin6_scope_id = 0;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void addr_sys_to_socket (BAddr *out, struct sys_addr addr)
+{
+    switch (addr.addr.generic.sa_family) {
+        case AF_INET: {
+            ASSERT(addr.len == sizeof(struct sockaddr_in))
+            BAddr_InitIPv4(out, addr.addr.ipv4.sin_addr.s_addr, addr.addr.ipv4.sin_port);
+        } break;
+        
+        case AF_INET6: {
+            ASSERT(addr.len == sizeof(struct sockaddr_in6))
+            BAddr_InitIPv6(out, addr.addr.ipv6.sin6_addr.s6_addr, addr.addr.ipv6.sin6_port);
+        } break;
+        
+        default: {
+            BAddr_InitNone(out);
+        } break;
+    }
+}
+
+static void listener_fd_handler (BListener *o, int events)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // set default job
+    BPending_Set(&o->default_job);
+    
+    // call handler
+    o->handler(o->user);
+    return;
+}
+
+static void listener_default_job_handler (BListener *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    BLog(BLOG_ERROR, "discarding connection");
+    
+    // accept
+    int newfd = accept(o->fd, NULL, NULL);
+    if (newfd < 0) {
+        BLog(BLOG_ERROR, "accept failed");
+        return;
+    }
+    
+    // close new fd
+    if (close(newfd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+}
+
+static void connector_fd_handler (BConnector *o, int events)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->fd >= 0)
+    ASSERT(!o->connected)
+    ASSERT(o->have_bfd)
+    
+    // free BFileDescriptor
+    BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+    
+    // set have no BFileDescriptor
+    o->have_bfd = 0;
+    
+    // read connection result
+    int result;
+    socklen_t result_len = sizeof(result);
+    if (getsockopt(o->fd, SOL_SOCKET, SO_ERROR, &result, &result_len) < 0) {
+        BLog(BLOG_ERROR, "getsockopt failed");
+        goto fail0;
+    }
+    ASSERT_FORCE(result_len == sizeof(result))
+    
+    if (result != 0) {
+        BLog(BLOG_ERROR, "connection failed");
+        goto fail0;
+    }
+    
+    // set connected
+    o->connected = 1;
+    
+fail0:
+    // call handler
+    o->handler(o->user, !o->connected);
+    return;
+}
+
+static void connector_job_handler (BConnector *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->fd >= 0)
+    ASSERT(o->connected)
+    ASSERT(!o->have_bfd)
+    
+    // call handler
+    o->handler(o->user, 0);
+    return;
+}
+
+static void connection_report_error (BConnection *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->handler)
+    
+    // report error
+    DEBUGERROR(&o->d_err, o->handler(o->user, BCONNECTION_EVENT_ERROR));
+    return;
+}
+
+static void connection_send (BConnection *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->send.state == SEND_STATE_BUSY)
+    
+    // limit
+    if (!o->is_hupd) {
+        if (!BReactorLimit_Increment(&o->send.limit)) {
+            // wait for fd
+            o->wait_events |= BREACTOR_WRITE;
+            BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+            return;
+        }
+    }
+    
+    // send
+    int bytes = write(o->fd, o->send.busy_data, o->send.busy_data_len);
+    if (bytes < 0) {
+        if (!o->is_hupd && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+            // wait for fd
+            o->wait_events |= BREACTOR_WRITE;
+            BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+            return;
+        }
+        
+        BLog(BLOG_ERROR, "send failed");
+        connection_report_error(o);
+        return;
+    }
+    
+    ASSERT(bytes > 0)
+    ASSERT(bytes <= o->send.busy_data_len)
+    
+    // set ready
+    o->send.state = SEND_STATE_READY;
+    
+    // done
+    StreamPassInterface_Done(&o->send.iface, bytes);
+}
+
+static void connection_recv (BConnection *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->recv.state == RECV_STATE_BUSY)
+    
+    // limit
+    if (!o->is_hupd) {
+        if (!BReactorLimit_Increment(&o->recv.limit)) {
+            // wait for fd
+            o->wait_events |= BREACTOR_READ;
+            BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+            return;
+        }
+    }
+    
+    // recv
+    int bytes = read(o->fd, o->recv.busy_data, o->recv.busy_data_avail);
+    if (bytes < 0) {
+        if (!o->is_hupd && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+            // wait for fd
+            o->wait_events |= BREACTOR_READ;
+            BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+            return;
+        }
+        
+        BLog(BLOG_ERROR, "recv failed");
+        connection_report_error(o);
+        return;
+    }
+    
+    if (bytes == 0) {
+        // set recv inited closed
+        o->recv.state = RECV_STATE_INITED_CLOSED;
+        
+        // report recv closed
+        o->handler(o->user, BCONNECTION_EVENT_RECVCLOSED);
+        return;
+    }
+    
+    ASSERT(bytes > 0)
+    ASSERT(bytes <= o->recv.busy_data_avail)
+    
+    // set not busy
+    o->recv.state = RECV_STATE_READY;
+    
+    // done
+    StreamRecvInterface_Done(&o->recv.iface, bytes);
+}
+
+static void connection_fd_handler (BConnection *o, int events)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->is_hupd)
+    
+    // clear handled events
+    o->wait_events &= ~events;
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+    
+    int have_send = 0;
+    int have_recv = 0;
+    
+    // if we got a HUP event, stop monitoring the file descriptor
+    if ((events & BREACTOR_HUP)) {
+        BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+        o->is_hupd = 1;
+    }
+    
+    if ((events & BREACTOR_WRITE) || ((events & (BREACTOR_ERROR|BREACTOR_HUP)) && o->send.state == SEND_STATE_BUSY)) {
+        ASSERT(o->send.state == SEND_STATE_BUSY)
+        have_send = 1;
+    }
+    
+    if ((events & BREACTOR_READ) || ((events & (BREACTOR_ERROR|BREACTOR_HUP)) && o->recv.state == RECV_STATE_BUSY)) {
+        ASSERT(o->recv.state == RECV_STATE_BUSY)
+        have_recv = 1;
+    }
+    
+    if (have_send) {
+        if (have_recv) {
+            BPending_Set(&o->recv.job);
+        }
+        
+        connection_send(o);
+        return;
+    }
+    
+    if (have_recv) {
+        connection_recv(o);
+        return;
+    }
+    
+    if (!o->is_hupd) {
+        BLog(BLOG_ERROR, "fd error event");
+        connection_report_error(o);
+        return;
+    }
+}
+
+static void connection_send_job_handler (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->send.state == SEND_STATE_BUSY)
+    
+    connection_send(o);
+    return;
+}
+
+static void connection_recv_job_handler (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->recv.state == RECV_STATE_BUSY)
+    
+    connection_recv(o);
+    return;
+}
+
+static void connection_send_if_handler_send (BConnection *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->send.state == SEND_STATE_READY)
+    ASSERT(data_len > 0)
+    
+    // remember data
+    o->send.busy_data = data;
+    o->send.busy_data_len = data_len;
+    
+    // set busy
+    o->send.state = SEND_STATE_BUSY;
+    
+    connection_send(o);
+    return;
+}
+
+static void connection_recv_if_handler_recv (BConnection *o, uint8_t *data, int data_avail)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->recv.state == RECV_STATE_READY)
+    ASSERT(data_avail > 0)
+    
+    // remember data
+    o->recv.busy_data = data;
+    o->recv.busy_data_avail = data_avail;
+    
+    // set busy
+    o->recv.state = RECV_STATE_BUSY;
+    
+    connection_recv(o);
+    return;
+}
+
+int BConnection_AddressSupported (BAddr addr)
+{
+    BAddr_Assert(&addr);
+    
+    return (addr.type == BADDR_TYPE_IPV4 || addr.type == BADDR_TYPE_IPV6);
+}
+
+int BListener_Init (BListener *o, BAddr addr, BReactor *reactor, void *user,
+                    BListener_handler handler)
+{
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // set no unix socket path
+    o->unix_socket_path = NULL;
+    
+    // check address
+    if (!BConnection_AddressSupported(addr)) {
+        BLog(BLOG_ERROR, "address not supported");
+        goto fail0;
+    }
+    
+    // convert address
+    struct sys_addr sysaddr;
+    addr_socket_to_sys(&sysaddr, addr);
+    
+    // init fd
+    if ((o->fd = socket(sysaddr.addr.generic.sa_family, SOCK_STREAM, 0)) < 0) {
+        BLog(BLOG_ERROR, "socket failed");
+        goto fail0;
+    }
+    
+    // set non-blocking
+    if (!badvpn_set_nonblocking(o->fd)) {
+        BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail1;
+    }
+    
+    // set SO_REUSEADDR
+    int optval = 1;
+    if (setsockopt(o->fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
+        BLog(BLOG_ERROR, "setsockopt(SO_REUSEADDR) failed");
+    }
+    
+    // bind
+    if (bind(o->fd, &sysaddr.addr.generic, sysaddr.len) < 0) {
+        BLog(BLOG_ERROR, "bind failed");
+        goto fail1;
+    }
+    
+    // listen
+    if (listen(o->fd, BCONNECTION_LISTEN_BACKLOG) < 0) {
+        BLog(BLOG_ERROR, "listen failed");
+        goto fail1;
+    }
+    
+    // init BFileDescriptor
+    BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)listener_fd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, BREACTOR_READ);
+    
+    // init default job
+    BPending_Init(&o->default_job, BReactor_PendingGroup(o->reactor), (BPending_handler)listener_default_job_handler, o);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    if (close(o->fd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+fail0:
+    return 0;
+}
+
+int BListener_InitUnix (BListener *o, const char *socket_path, BReactor *reactor, void *user,
+                        BListener_handler handler)
+{
+    ASSERT(socket_path)
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // copy socket path
+    o->unix_socket_path = b_strdup(socket_path);
+    if (!o->unix_socket_path) {
+        BLog(BLOG_ERROR, "b_strdup failed");
+        goto fail0;
+    }
+    
+    // build address
+    struct unix_addr addr;
+    if (!build_unix_address(&addr, socket_path)) {
+        BLog(BLOG_ERROR, "build_unix_address failed");
+        goto fail1;
+    }
+    
+    // init fd
+    if ((o->fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+        BLog(BLOG_ERROR, "socket failed");
+        goto fail1;
+    }
+    
+    // set non-blocking
+    if (!badvpn_set_nonblocking(o->fd)) {
+        BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail2;
+    }
+    
+    // unlink existing socket
+    if (unlink(o->unix_socket_path) < 0 && errno != ENOENT) {
+        BLog(BLOG_ERROR, "unlink existing socket failed");
+        goto fail2;
+    }
+    
+    // bind
+    if (bind(o->fd, (struct sockaddr *)&addr.u.addr, addr.len) < 0) {
+        BLog(BLOG_ERROR, "bind failed");
+        goto fail2;
+    }
+    
+    // listen
+    if (listen(o->fd, BCONNECTION_LISTEN_BACKLOG) < 0) {
+        BLog(BLOG_ERROR, "listen failed");
+        goto fail3;
+    }
+    
+    // init BFileDescriptor
+    BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)listener_fd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail3;
+    }
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, BREACTOR_READ);
+    
+    // init default job
+    BPending_Init(&o->default_job, BReactor_PendingGroup(o->reactor), (BPending_handler)listener_default_job_handler, o);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail3:
+    if (unlink(o->unix_socket_path) < 0) {
+        BLog(BLOG_ERROR, "unlink socket failed");
+    }
+fail2:
+    if (close(o->fd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+fail1:
+    free(o->unix_socket_path);
+fail0:
+    return 0;
+}
+
+void BListener_Free (BListener *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free default job
+    BPending_Free(&o->default_job);
+    
+    // free BFileDescriptor
+    BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+    
+    // free fd
+    if (close(o->fd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+    
+    // unlink unix socket
+    if (o->unix_socket_path) {
+        if (unlink(o->unix_socket_path) < 0) {
+            BLog(BLOG_ERROR, "unlink socket failed");
+        }
+    }
+    
+    // free unix socket path
+    if (o->unix_socket_path) {
+        free(o->unix_socket_path);
+    }
+}
+
+int BConnector_Init (BConnector *o, BAddr addr, BReactor *reactor, void *user,
+                     BConnector_handler handler)
+{
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // check address
+    if (!BConnection_AddressSupported(addr)) {
+        BLog(BLOG_ERROR, "address not supported");
+        goto fail0;
+    }
+    
+    // convert address
+    struct sys_addr sysaddr;
+    addr_socket_to_sys(&sysaddr, addr);
+    
+    // init job
+    BPending_Init(&o->job, BReactor_PendingGroup(o->reactor), (BPending_handler)connector_job_handler, o);
+    
+    // init fd
+    if ((o->fd = socket(sysaddr.addr.generic.sa_family, SOCK_STREAM, 0)) < 0) {
+        BLog(BLOG_ERROR, "socket failed");
+        goto fail1;
+    }
+    
+    // set fd non-blocking
+    if (!badvpn_set_nonblocking(o->fd)) {
+        BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail2;
+    }
+    
+    // connect fd
+    int res = connect(o->fd, &sysaddr.addr.generic, sysaddr.len);
+    if (res < 0 && errno != EINPROGRESS) {
+        BLog(BLOG_ERROR, "connect failed");
+        goto fail2;
+    }
+    
+    // set not connected
+    o->connected = 0;
+    
+    // set have no BFileDescriptor
+    o->have_bfd = 0;
+    
+    if (res < 0) {
+        // init BFileDescriptor
+        BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)connector_fd_handler, o);
+        if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+            BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+            goto fail2;
+        }
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, BREACTOR_WRITE);
+        
+        // set have BFileDescriptor
+        o->have_bfd = 1;
+    } else {
+        // set connected
+        o->connected = 1;
+        
+        // set job
+        BPending_Set(&o->job);
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail2:
+    if (close(o->fd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+fail1:
+    BPending_Free(&o->job);
+fail0:
+    return 0;
+}
+
+int BConnector_InitUnix (BConnector *o, const char *socket_path, BReactor *reactor, void *user,
+                         BConnector_handler handler)
+{
+    ASSERT(socket_path)
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // build address
+    struct unix_addr addr;
+    if (!build_unix_address(&addr, socket_path)) {
+        BLog(BLOG_ERROR, "build_unix_address failed");
+        goto fail0;
+    }
+    
+    // init job
+    BPending_Init(&o->job, BReactor_PendingGroup(o->reactor), (BPending_handler)connector_job_handler, o);
+    
+    // init fd
+    if ((o->fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+        BLog(BLOG_ERROR, "socket failed");
+        goto fail1;
+    }
+    
+    // set fd non-blocking
+    if (!badvpn_set_nonblocking(o->fd)) {
+        BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail2;
+    }
+    
+    // connect fd
+    int res = connect(o->fd, (struct sockaddr *)&addr.u.addr, addr.len);
+    if (res < 0 && errno != EINPROGRESS) {
+        BLog(BLOG_ERROR, "connect failed");
+        goto fail2;
+    }
+    
+    // set not connected
+    o->connected = 0;
+    
+    // set have no BFileDescriptor
+    o->have_bfd = 0;
+    
+    if (res < 0) {
+        // init BFileDescriptor
+        BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)connector_fd_handler, o);
+        if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+            BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+            goto fail2;
+        }
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, BREACTOR_WRITE);
+        
+        // set have BFileDescriptor
+        o->have_bfd = 1;
+    } else {
+        // set connected
+        o->connected = 1;
+        
+        // set job
+        BPending_Set(&o->job);
+    }
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail2:
+    if (close(o->fd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+fail1:
+    BPending_Free(&o->job);
+fail0:
+    return 0;
+}
+
+void BConnector_Free (BConnector *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free BFileDescriptor
+    if (o->have_bfd) {
+        BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+    }
+    
+    // close fd
+    if (o->fd != -1) {
+        if (close(o->fd) < 0) {
+            BLog(BLOG_ERROR, "close failed");
+        }
+    }
+    
+    // free job
+    BPending_Free(&o->job);
+}
+
+int BConnection_Init (BConnection *o, struct BConnection_source source, BReactor *reactor, void *user,
+                      BConnection_handler handler)
+{
+    switch (source.type) {
+        case BCONNECTION_SOURCE_TYPE_LISTENER: {
+            BListener *listener = source.u.listener.listener;
+            DebugObject_Access(&listener->d_obj);
+            ASSERT(BPending_IsSet(&listener->default_job))
+        } break;
+        case BCONNECTION_SOURCE_TYPE_CONNECTOR: {
+            BConnector *connector = source.u.connector.connector;
+            DebugObject_Access(&connector->d_obj);
+            ASSERT(connector->fd >= 0)
+            ASSERT(connector->connected)
+            ASSERT(!connector->have_bfd)
+            ASSERT(!BPending_IsSet(&connector->job))
+        } break;
+        case BCONNECTION_SOURCE_TYPE_PIPE: {
+            ASSERT(source.u.pipe.pipefd >= 0)
+        } break;
+        default: ASSERT(0);
+    }
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    switch (source.type) {
+        case BCONNECTION_SOURCE_TYPE_LISTENER: {
+            BListener *listener = source.u.listener.listener;
+            
+            // unset listener's default job
+            BPending_Unset(&listener->default_job);
+            
+            // accept
+            struct sys_addr sysaddr;
+            sysaddr.len = sizeof(sysaddr.addr);
+            if ((o->fd = accept(listener->fd, &sysaddr.addr.generic, &sysaddr.len)) < 0) {
+                BLog(BLOG_ERROR, "accept failed");
+                goto fail0;
+            }
+            o->close_fd = 1;
+            
+            // set non-blocking
+            if (!badvpn_set_nonblocking(o->fd)) {
+                BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+                goto fail1;
+            }
+            
+            // return address
+            if (source.u.listener.out_addr) {
+                addr_sys_to_socket(source.u.listener.out_addr, sysaddr);
+            }
+        } break;
+        
+        case BCONNECTION_SOURCE_TYPE_CONNECTOR: {
+            BConnector *connector = source.u.connector.connector;
+            
+            // grab fd from connector
+            o->fd = connector->fd;
+            connector->fd = -1;
+            o->close_fd = 1;
+        } break;
+        
+        case BCONNECTION_SOURCE_TYPE_PIPE: {
+            // use user-provided fd
+            o->fd = source.u.pipe.pipefd;
+            o->close_fd = 0;
+            
+            // set non-blocking
+            if (!badvpn_set_nonblocking(o->fd)) {
+                BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+                goto fail1;
+            }
+        } break;
+    }
+    
+    // set not HUPd
+    o->is_hupd = 0;
+    
+    // init BFileDescriptor
+    BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)connection_fd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    
+    // set no wait events
+    o->wait_events = 0;
+    
+    // init limits
+    BReactorLimit_Init(&o->send.limit, o->reactor, BCONNECTION_SEND_LIMIT);
+    BReactorLimit_Init(&o->recv.limit, o->reactor, BCONNECTION_RECV_LIMIT);
+    
+    // set send and recv not inited
+    o->send.state = SEND_STATE_NOT_INITED;
+    o->recv.state = RECV_STATE_NOT_INITED;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    if (o->close_fd) {
+        if (close(o->fd) < 0) {
+            BLog(BLOG_ERROR, "close failed");
+        }
+    }
+fail0:
+    return 0;
+}
+
+void BConnection_Free (BConnection *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    ASSERT(o->send.state == SEND_STATE_NOT_INITED)
+    ASSERT(o->recv.state == RECV_STATE_NOT_INITED || o->recv.state == RECV_STATE_NOT_INITED_CLOSED)
+    
+    // free limits
+    BReactorLimit_Free(&o->recv.limit);
+    BReactorLimit_Free(&o->send.limit);
+    
+    // free BFileDescriptor
+    if (!o->is_hupd) {
+        BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+    }
+    
+    // close fd
+    if (o->close_fd) {
+        if (close(o->fd) < 0) {
+            BLog(BLOG_ERROR, "close failed");
+        }
+    }
+}
+
+void BConnection_SetHandlers (BConnection *o, void *user, BConnection_handler handler)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // set handlers
+    o->user = user;
+    o->handler = handler;
+}
+
+int BConnection_SetSendBuffer (BConnection *o, int buf_size)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    if (setsockopt(o->fd, SOL_SOCKET, SO_SNDBUF, (void *)&buf_size, sizeof(buf_size)) < 0) {
+        BLog(BLOG_ERROR, "setsockopt failed");
+        return 0;
+    }
+    
+    return 1;
+}
+
+void BConnection_SendAsync_Init (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->send.state == SEND_STATE_NOT_INITED)
+    
+    // init interface
+    StreamPassInterface_Init(&o->send.iface, (StreamPassInterface_handler_send)connection_send_if_handler_send, o, BReactor_PendingGroup(o->reactor));
+    
+    // init job
+    BPending_Init(&o->send.job, BReactor_PendingGroup(o->reactor), (BPending_handler)connection_send_job_handler, o);
+    
+    // set ready
+    o->send.state = SEND_STATE_READY;
+}
+
+void BConnection_SendAsync_Free (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->send.state == SEND_STATE_READY || o->send.state == SEND_STATE_BUSY)
+    
+    // update events
+    if (!o->is_hupd) {
+        o->wait_events &= ~BREACTOR_WRITE;
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+    }
+    
+    // free job
+    BPending_Free(&o->send.job);
+    
+    // free interface
+    StreamPassInterface_Free(&o->send.iface);
+    
+    // set not inited
+    o->send.state = SEND_STATE_NOT_INITED;
+}
+
+StreamPassInterface * BConnection_SendAsync_GetIf (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->send.state == SEND_STATE_READY || o->send.state == SEND_STATE_BUSY)
+    
+    return &o->send.iface;
+}
+
+void BConnection_RecvAsync_Init (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->recv.state == RECV_STATE_NOT_INITED)
+    
+    // init interface
+    StreamRecvInterface_Init(&o->recv.iface, (StreamRecvInterface_handler_recv)connection_recv_if_handler_recv, o, BReactor_PendingGroup(o->reactor));
+    
+    // init job
+    BPending_Init(&o->recv.job, BReactor_PendingGroup(o->reactor), (BPending_handler)connection_recv_job_handler, o);
+    
+    // set ready
+    o->recv.state = RECV_STATE_READY;
+}
+
+void BConnection_RecvAsync_Free (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->recv.state == RECV_STATE_READY || o->recv.state == RECV_STATE_BUSY || o->recv.state == RECV_STATE_INITED_CLOSED)
+    
+    // update events
+    if (!o->is_hupd) {
+        o->wait_events &= ~BREACTOR_READ;
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+    }
+    
+    // free job
+    BPending_Free(&o->recv.job);
+    
+    // free interface
+    StreamRecvInterface_Free(&o->recv.iface);
+    
+    // set not inited
+    o->recv.state = RECV_STATE_NOT_INITED;
+}
+
+StreamRecvInterface * BConnection_RecvAsync_GetIf (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->recv.state == RECV_STATE_READY || o->recv.state == RECV_STATE_BUSY || o->recv.state == RECV_STATE_INITED_CLOSED)
+    
+    return &o->recv.iface;
+}
diff --git a/external/badvpn_dns/system/BConnection_unix.h b/external/badvpn_dns/system/BConnection_unix.h
new file mode 100644
index 0000000..e134f6c
--- /dev/null
+++ b/external/badvpn_dns/system/BConnection_unix.h
@@ -0,0 +1,87 @@
+/**
+ * @file BConnection_unix.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/debugerror.h>
+#include <base/DebugObject.h>
+
+#define BCONNECTION_SEND_LIMIT 2
+#define BCONNECTION_RECV_LIMIT 2
+#define BCONNECTION_LISTEN_BACKLOG 128
+
+struct BListener_s {
+    BReactor *reactor;
+    void *user;
+    BListener_handler handler;
+    char *unix_socket_path;
+    int fd;
+    BFileDescriptor bfd;
+    BPending default_job;
+    DebugObject d_obj;
+};
+
+struct BConnector_s {
+    BReactor *reactor;
+    void *user;
+    BConnector_handler handler;
+    BPending job;
+    int fd;
+    int connected;
+    int have_bfd;
+    BFileDescriptor bfd;
+    DebugObject d_obj;
+};
+
+struct BConnection_s {
+    BReactor *reactor;
+    void *user;
+    BConnection_handler handler;
+    int fd;
+    int close_fd;
+    int is_hupd;
+    BFileDescriptor bfd;
+    int wait_events;
+    struct {
+        BReactorLimit limit;
+        StreamPassInterface iface;
+        BPending job;
+        const uint8_t *busy_data;
+        int busy_data_len;
+        int state;
+    } send;
+    struct {
+        BReactorLimit limit;
+        StreamRecvInterface iface;
+        BPending job;
+        uint8_t *busy_data;
+        int busy_data_avail;
+        int state;
+    } recv;
+    DebugError d_err;
+    DebugObject d_obj;
+};
diff --git a/external/badvpn_dns/system/BConnection_win.c b/external/badvpn_dns/system/BConnection_win.c
new file mode 100644
index 0000000..17ae727
--- /dev/null
+++ b/external/badvpn_dns/system/BConnection_win.c
@@ -0,0 +1,875 @@
+/**
+ * @file BConnection_win.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <base/BLog.h>
+
+#include "BConnection.h"
+
+#include <generated/blog_channel_BConnection.h>
+
+#define LISTEN_BACKLOG 128
+
+struct sys_addr {
+    int len;
+    union {
+        struct sockaddr generic;
+        struct sockaddr_in ipv4;
+        struct sockaddr_in6 ipv6;
+    } addr;
+};
+
+static void addr_socket_to_sys (struct sys_addr *out, BAddr addr);
+static void addr_any_to_sys (struct sys_addr *out, int family);
+static void addr_sys_to_socket (BAddr *out, struct sys_addr addr);
+static void listener_next_job_handler (BListener *o);
+static void listener_olap_handler (BListener *o, int event, DWORD bytes);
+static void connector_olap_handler (BConnector *o, int event, DWORD bytes);
+static void connector_abort (BConnector *o);
+static void connection_report_error (BConnection *o);
+static void connection_abort (BConnection *o);
+static void connection_send_iface_handler_send (BConnection *o, uint8_t *data, int data_len);
+static void connection_recv_iface_handler_recv (BConnection *o, uint8_t *data, int data_len);
+static void connection_send_olap_handler (BConnection *o, int event, DWORD bytes);
+static void connection_recv_olap_handler (BConnection *o, int event, DWORD bytes);
+
+static void addr_socket_to_sys (struct sys_addr *out, BAddr addr)
+{
+    switch (addr.type) {
+        case BADDR_TYPE_IPV4: {
+            out->len = sizeof(out->addr.ipv4);
+            memset(&out->addr.ipv4, 0, sizeof(out->addr.ipv4));
+            out->addr.ipv4.sin_family = AF_INET;
+            out->addr.ipv4.sin_port = addr.ipv4.port;
+            out->addr.ipv4.sin_addr.s_addr = addr.ipv4.ip;
+        } break;
+        
+        case BADDR_TYPE_IPV6: {
+            out->len = sizeof(out->addr.ipv6);
+            memset(&out->addr.ipv6, 0, sizeof(out->addr.ipv6));
+            out->addr.ipv6.sin6_family = AF_INET6;
+            out->addr.ipv6.sin6_port = addr.ipv6.port;
+            out->addr.ipv6.sin6_flowinfo = 0;
+            memcpy(out->addr.ipv6.sin6_addr.s6_addr, addr.ipv6.ip, 16);
+            out->addr.ipv6.sin6_scope_id = 0;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void addr_any_to_sys (struct sys_addr *out, int family)
+{
+    switch (family) {
+        case BADDR_TYPE_IPV4: {
+            out->len = sizeof(out->addr.ipv4);
+            memset(&out->addr.ipv4, 0, sizeof(out->addr.ipv4));
+            out->addr.ipv4.sin_family = AF_INET;
+            out->addr.ipv4.sin_port = 0;
+            out->addr.ipv4.sin_addr.s_addr = INADDR_ANY;
+        } break;
+        
+        case BADDR_TYPE_IPV6: {
+            out->len = sizeof(out->addr.ipv6);
+            memset(&out->addr.ipv6, 0, sizeof(out->addr.ipv6));
+            out->addr.ipv6.sin6_family = AF_INET6;
+            out->addr.ipv6.sin6_port = 0;
+            out->addr.ipv6.sin6_flowinfo = 0;
+            struct in6_addr any = IN6ADDR_ANY_INIT;
+            out->addr.ipv6.sin6_addr = any;
+            out->addr.ipv6.sin6_scope_id = 0;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void addr_sys_to_socket (BAddr *out, struct sys_addr addr)
+{
+    switch (addr.addr.generic.sa_family) {
+        case AF_INET: {
+            ASSERT(addr.len == sizeof(struct sockaddr_in))
+            BAddr_InitIPv4(out, addr.addr.ipv4.sin_addr.s_addr, addr.addr.ipv4.sin_port);
+        } break;
+        
+        case AF_INET6: {
+            ASSERT(addr.len == sizeof(struct sockaddr_in6))
+            BAddr_InitIPv6(out, addr.addr.ipv6.sin6_addr.s6_addr, addr.addr.ipv6.sin6_port);
+        } break;
+        
+        default: {
+            BAddr_InitNone(out);
+        } break;
+    }
+}
+
+static void listener_next_job_handler (BListener *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->busy)
+    
+    // free ready socket
+    if (o->ready) {
+        BLog(BLOG_ERROR, "discarding connection");
+        
+        // close new socket
+        if (closesocket(o->newsock) == SOCKET_ERROR) {
+            BLog(BLOG_ERROR, "closesocket failed");
+        }
+        
+        // set not ready
+        o->ready = 0;
+    }
+    
+    // create new socket
+    if ((o->newsock = WSASocket(o->sys_family, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) {
+        BLog(BLOG_ERROR, "WSASocket failed");
+        goto fail0;
+    }
+    
+    // start accept operation
+    while (1) {
+        memset(&o->olap.olap, 0, sizeof(o->olap.olap));
+        DWORD bytes;
+        BOOL res = o->fnAcceptEx(o->sock, o->newsock, o->addrbuf, 0, sizeof(struct BListener_addrbuf_stub), sizeof(struct BListener_addrbuf_stub), &bytes, &o->olap.olap);
+        if (res == FALSE && WSAGetLastError() != ERROR_IO_PENDING) {
+            BLog(BLOG_ERROR, "AcceptEx failed");
+            continue;
+        }
+        break;
+    }
+    
+    // set busy
+    o->busy = 1;
+    
+    return;
+    
+fail0:
+    return;
+}
+
+static void listener_olap_handler (BListener *o, int event, DWORD bytes)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->busy)
+    ASSERT(!o->ready)
+    ASSERT(event == BREACTOR_IOCP_EVENT_SUCCEEDED || event == BREACTOR_IOCP_EVENT_FAILED)
+    
+    // set not busy
+    o->busy = 0;
+    
+    // schedule next accept
+    BPending_Set(&o->next_job);
+    
+    if (event == BREACTOR_IOCP_EVENT_FAILED) {
+        BLog(BLOG_ERROR, "accepting failed");
+        
+        // close new socket
+        if (closesocket(o->newsock) == SOCKET_ERROR) {
+            BLog(BLOG_ERROR, "closesocket failed");
+        }
+        
+        return;
+    }
+    
+    BLog(BLOG_INFO, "connection accepted");
+    
+    // set ready
+    o->ready = 1;
+    
+    // call handler
+    o->handler(o->user);
+    return;
+}
+
+static void connector_olap_handler (BConnector *o, int event, DWORD bytes)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->sock != INVALID_SOCKET)
+    ASSERT(o->busy)
+    ASSERT(!o->ready)
+    ASSERT(event == BREACTOR_IOCP_EVENT_SUCCEEDED || event == BREACTOR_IOCP_EVENT_FAILED)
+    
+    // set not busy
+    o->busy = 0;
+    
+    if (event == BREACTOR_IOCP_EVENT_FAILED) {
+        BLog(BLOG_ERROR, "connection failed");
+    } else {
+        // set ready
+        o->ready = 1;
+    }
+    
+    // call handler
+    o->handler(o->user, !o->ready);
+    return;
+}
+
+static void connector_abort (BConnector *o)
+{
+    if (o->sock != INVALID_SOCKET) {
+        // cancel I/O
+        if (o->busy) {
+            if (!CancelIo((HANDLE)o->sock)) {
+                BLog(BLOG_ERROR, "CancelIo failed");
+            }
+        }
+        
+        // close socket
+        if (closesocket(o->sock) == SOCKET_ERROR) {
+            BLog(BLOG_ERROR, "closesocket failed");
+        }
+    }
+    
+    // wait for connect operation to finish
+    if (o->busy) {
+        BReactorIOCPOverlapped_Wait(&o->olap, NULL, NULL);
+    }
+    
+    // free olap
+    BReactorIOCPOverlapped_Free(&o->olap);
+}
+
+static void connection_report_error (BConnection *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->handler)
+    
+    // report error
+    DEBUGERROR(&o->d_err, o->handler(o->user, BCONNECTION_EVENT_ERROR));
+    return;
+}
+
+static void connection_abort (BConnection *o)
+{
+    ASSERT(!o->aborted)
+    
+    // cancel I/O
+    if ((o->recv.inited && o->recv.busy) || (o->send.inited && o->send.busy)) {
+        if (!CancelIo((HANDLE)o->sock)) {
+            BLog(BLOG_ERROR, "CancelIo failed");
+        }
+    }
+    
+    // close socket
+    if (closesocket(o->sock) == SOCKET_ERROR) {
+        BLog(BLOG_ERROR, "closesocket failed");
+    }
+    
+    // wait for receiving to complete
+    if (o->recv.inited && o->recv.busy) {
+        BReactorIOCPOverlapped_Wait(&o->recv.olap, NULL, NULL);
+    }
+    
+    // wait for sending to complete
+    if (o->send.inited && o->send.busy) {
+        BReactorIOCPOverlapped_Wait(&o->send.olap, NULL, NULL);
+    }
+    
+    // free recv olap
+    BReactorIOCPOverlapped_Free(&o->recv.olap);
+    
+    // free send olap
+    BReactorIOCPOverlapped_Free(&o->send.olap);
+    
+    // set aborted
+    o->aborted = 1;
+}
+
+static void connection_send_iface_handler_send (BConnection *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->send.inited)
+    ASSERT(!o->send.busy)
+    ASSERT(data_len > 0)
+    
+    if (data_len > ULONG_MAX) {
+        data_len = ULONG_MAX;
+    }
+    
+    WSABUF buf;
+    buf.buf = (char *)data;
+    buf.len = data_len;
+    
+    memset(&o->send.olap.olap, 0, sizeof(o->send.olap.olap));
+    
+    // send
+    int res = WSASend(o->sock, &buf, 1, NULL, 0, &o->send.olap.olap, NULL);
+    if (res == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
+        BLog(BLOG_ERROR, "WSASend failed (%d)", WSAGetLastError());
+        connection_report_error(o);
+        return;
+    }
+    
+    // set busy
+    o->send.busy = 1;
+    o->send.busy_data_len = data_len;
+}
+
+static void connection_recv_iface_handler_recv (BConnection *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->recv.closed)
+    ASSERT(!o->aborted)
+    ASSERT(o->recv.inited)
+    ASSERT(!o->recv.busy)
+    ASSERT(data_len > 0)
+    
+    if (data_len > ULONG_MAX) {
+        data_len = ULONG_MAX;
+    }
+    
+    WSABUF buf;
+    buf.buf = (char *)data;
+    buf.len = data_len;
+    
+    memset(&o->recv.olap.olap, 0, sizeof(o->recv.olap.olap));
+    
+    // recv
+    DWORD flags = 0;
+    int res = WSARecv(o->sock, &buf, 1, NULL, &flags, &o->recv.olap.olap, NULL);
+    if (res == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
+        BLog(BLOG_ERROR, "WSARecv failed (%d)", WSAGetLastError());
+        connection_report_error(o);
+        return;
+    }
+    
+    // set busy
+    o->recv.busy = 1;
+    o->recv.busy_data_len = data_len;
+}
+
+static void connection_send_olap_handler (BConnection *o, int event, DWORD bytes)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->send.inited)
+    ASSERT(o->send.busy)
+    ASSERT(event == BREACTOR_IOCP_EVENT_SUCCEEDED || event == BREACTOR_IOCP_EVENT_FAILED)
+    
+    // set not busy
+    o->send.busy = 0;
+    
+    if (event == BREACTOR_IOCP_EVENT_FAILED) {
+        BLog(BLOG_ERROR, "sending failed");
+        connection_report_error(o);
+        return;
+    }
+    
+    ASSERT(bytes > 0)
+    ASSERT(bytes <= o->send.busy_data_len)
+    
+    // done
+    StreamPassInterface_Done(&o->send.iface, bytes);
+}
+
+static void connection_recv_olap_handler (BConnection *o, int event, DWORD bytes)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->recv.closed)
+    ASSERT(!o->aborted)
+    ASSERT(o->recv.inited)
+    ASSERT(o->recv.busy)
+    ASSERT(event == BREACTOR_IOCP_EVENT_SUCCEEDED || event == BREACTOR_IOCP_EVENT_FAILED)
+    
+    // set not busy
+    o->recv.busy = 0;
+    
+    if (event == BREACTOR_IOCP_EVENT_FAILED) {
+        BLog(BLOG_ERROR, "receiving failed");
+        connection_report_error(o);
+        return;
+    }
+    
+    if (bytes == 0) {
+        // set closed
+        o->recv.closed = 1;
+        
+        // report recv closed
+        o->handler(o->user, BCONNECTION_EVENT_RECVCLOSED);
+        return;
+    }
+    
+    ASSERT(bytes > 0)
+    ASSERT(bytes <= o->recv.busy_data_len)
+    
+    // done
+    StreamRecvInterface_Done(&o->recv.iface, bytes);
+}
+
+int BConnection_AddressSupported (BAddr addr)
+{
+    BAddr_Assert(&addr);
+    
+    return (addr.type == BADDR_TYPE_IPV4 || addr.type == BADDR_TYPE_IPV6);
+}
+
+int BListener_Init (BListener *o, BAddr addr, BReactor *reactor, void *user,
+                    BListener_handler handler)
+{
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // check address
+    if (!BConnection_AddressSupported(addr)) {
+        BLog(BLOG_ERROR, "address not supported");
+        goto fail0;
+    }
+    
+    // convert address
+    struct sys_addr sysaddr;
+    addr_socket_to_sys(&sysaddr, addr);
+    
+    // remember family
+    o->sys_family = sysaddr.addr.generic.sa_family;
+    
+    // init socket
+    if ((o->sock = WSASocket(sysaddr.addr.generic.sa_family, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) {
+        BLog(BLOG_ERROR, "WSASocket failed");
+        goto fail0;
+    }
+    
+    // associate with IOCP
+    if (!CreateIoCompletionPort((HANDLE)o->sock, BReactor_GetIOCPHandle(o->reactor), 0, 0)) {
+        BLog(BLOG_ERROR, "CreateIoCompletionPort failed");
+        goto fail1;
+    }
+    
+    // bind
+    if (bind(o->sock, &sysaddr.addr.generic, sysaddr.len) < 0) {
+        BLog(BLOG_ERROR, "bind failed");
+        goto fail1;
+    }
+    
+    // listen
+    if (listen(o->sock, LISTEN_BACKLOG) < 0) {
+        BLog(BLOG_ERROR, "listen failed");
+        goto fail1;
+    }
+    
+    DWORD out_bytes;
+    
+    // obtain AcceptEx
+    GUID guid1 = WSAID_ACCEPTEX;
+    if (WSAIoctl(o->sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid1, sizeof(guid1), &o->fnAcceptEx, sizeof(o->fnAcceptEx), &out_bytes, NULL, NULL) != 0) {
+        BLog(BLOG_ERROR, "faild to obtain AcceptEx");
+        goto fail1;
+    }
+    
+    // obtain GetAcceptExSockaddrs
+    GUID guid2 = WSAID_GETACCEPTEXSOCKADDRS;
+    if (WSAIoctl(o->sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid2, sizeof(guid2), &o->fnGetAcceptExSockaddrs, sizeof(o->fnGetAcceptExSockaddrs), &out_bytes, NULL, NULL) != 0) {
+        BLog(BLOG_ERROR, "faild to obtain GetAcceptExSockaddrs");
+        goto fail1;
+    }
+    
+    // init olap
+    BReactorIOCPOverlapped_Init(&o->olap, o->reactor, o, (BReactorIOCPOverlapped_handler)listener_olap_handler);
+    
+    // init next job
+    BPending_Init(&o->next_job, BReactor_PendingGroup(o->reactor), (BPending_handler)listener_next_job_handler, o);
+    
+    // set not busy
+    o->busy = 0;
+    
+    // set not ready
+    o->ready = 0;
+    
+    // set next job
+    BPending_Set(&o->next_job);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    if (closesocket(o->sock) == SOCKET_ERROR) {
+        BLog(BLOG_ERROR, "closesocket failed");
+    }
+fail0:
+    return 0;
+}
+
+void BListener_Free (BListener *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // cancel I/O
+    if (o->busy) {
+        if (!CancelIo((HANDLE)o->sock)) {
+            BLog(BLOG_ERROR, "CancelIo failed");
+        }
+    }
+    
+    // close socket
+    if (closesocket(o->sock) == SOCKET_ERROR) {
+        BLog(BLOG_ERROR, "closesocket failed");
+    }
+    
+    // wait for accept operation to finish
+    if (o->busy) {
+        BReactorIOCPOverlapped_Wait(&o->olap, NULL, NULL);
+    }
+    
+    // close new socket
+    if (o->busy || o->ready) {
+        if (closesocket(o->newsock) == SOCKET_ERROR) {
+            BLog(BLOG_ERROR, "closesocket failed");
+        }
+    }
+    
+    // free next job
+    BPending_Free(&o->next_job);
+    
+    // free olap
+    BReactorIOCPOverlapped_Free(&o->olap);
+}
+
+int BConnector_Init (BConnector *o, BAddr addr, BReactor *reactor, void *user,
+                     BConnector_handler handler)
+{
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // check address
+    if (!BConnection_AddressSupported(addr)) {
+        BLog(BLOG_ERROR, "address not supported");
+        goto fail0;
+    }
+    
+    // convert address
+    struct sys_addr sysaddr;
+    addr_socket_to_sys(&sysaddr, addr);
+    
+    // create local any address
+    struct sys_addr local_sysaddr;
+    addr_any_to_sys(&local_sysaddr, addr.type);
+    
+    // init socket
+    if ((o->sock = WSASocket(sysaddr.addr.generic.sa_family, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) {
+        BLog(BLOG_ERROR, "WSASocket failed");
+        goto fail0;
+    }
+    
+    // associate with IOCP
+    if (!CreateIoCompletionPort((HANDLE)o->sock, BReactor_GetIOCPHandle(o->reactor), 0, 0)) {
+        BLog(BLOG_ERROR, "CreateIoCompletionPort failed");
+        goto fail1;
+    }
+    
+    // bind socket
+    if (bind(o->sock, &local_sysaddr.addr.generic, local_sysaddr.len) < 0) {
+        BLog(BLOG_ERROR, "bind failed");
+        goto fail1;
+    }
+    
+    // obtain ConnectEx
+    GUID guid = WSAID_CONNECTEX;
+    DWORD out_bytes;
+    if (WSAIoctl(o->sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &o->fnConnectEx, sizeof(o->fnConnectEx), &out_bytes, NULL, NULL) != 0) {
+        BLog(BLOG_ERROR, "faild to get ConnectEx");
+        goto fail1;
+    }
+    
+    // init olap
+    BReactorIOCPOverlapped_Init(&o->olap, o->reactor, o, (BReactorIOCPOverlapped_handler)connector_olap_handler);
+    
+    // start connect operation
+    BOOL res = o->fnConnectEx(o->sock, &sysaddr.addr.generic, sysaddr.len, NULL, 0, NULL, &o->olap.olap);
+    if (res == FALSE && WSAGetLastError() != ERROR_IO_PENDING) {
+        BLog(BLOG_ERROR, "ConnectEx failed (%d)", WSAGetLastError());
+        goto fail2;
+    }
+    
+    // set busy
+    o->busy = 1;
+    
+    // set not ready
+    o->ready = 0;
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail2:
+    BReactorIOCPOverlapped_Free(&o->olap);
+fail1:
+    if (closesocket(o->sock) == SOCKET_ERROR) {
+        BLog(BLOG_ERROR, "closesocket failed");
+    }
+fail0:
+    return 0;
+}
+
+void BConnector_Free (BConnector *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    if (o->sock != INVALID_SOCKET) {
+        connector_abort(o);
+    }
+}
+
+int BConnection_Init (BConnection *o, struct BConnection_source source, BReactor *reactor, void *user,
+                      BConnection_handler handler)
+{
+    switch (source.type) {
+        case BCONNECTION_SOURCE_TYPE_LISTENER: {
+            BListener *listener = source.u.listener.listener;
+            DebugObject_Access(&listener->d_obj);
+            ASSERT(BPending_IsSet(&listener->next_job))
+            ASSERT(!listener->busy)
+            ASSERT(listener->ready)
+        } break;
+        case BCONNECTION_SOURCE_TYPE_CONNECTOR: {
+            BConnector *connector = source.u.connector.connector;
+            DebugObject_Access(&connector->d_obj);
+            ASSERT(connector->reactor == reactor)
+            ASSERT(connector->sock != INVALID_SOCKET)
+            ASSERT(!connector->busy)
+            ASSERT(connector->ready)
+        } break;
+        default: ASSERT(0);
+    }
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    switch (source.type) {
+        case BCONNECTION_SOURCE_TYPE_LISTENER: {
+            BListener *listener = source.u.listener.listener;
+            
+            // grab new socket from listener
+            o->sock = listener->newsock;
+            listener->ready = 0;
+            
+            // associate with IOCP
+            if (!CreateIoCompletionPort((HANDLE)o->sock, BReactor_GetIOCPHandle(o->reactor), 0, 0)) {
+                BLog(BLOG_ERROR, "CreateIoCompletionPort failed");
+                goto fail1;
+            }
+            
+            // return address
+            if (source.u.listener.out_addr) {
+                struct sockaddr *addr_local;
+                struct sockaddr *addr_remote;
+                int len_local;
+                int len_remote;
+                listener->fnGetAcceptExSockaddrs(listener->addrbuf, 0, sizeof(struct BListener_addrbuf_stub), sizeof(struct BListener_addrbuf_stub),
+                                                 &addr_local, &len_local, &addr_remote, &len_remote);
+                
+                struct sys_addr sysaddr;
+                
+                ASSERT_FORCE(len_remote >= 0)
+                ASSERT_FORCE(len_remote <= sizeof(sysaddr.addr))
+                
+                memcpy((uint8_t *)&sysaddr.addr, (uint8_t *)addr_remote, len_remote);
+                sysaddr.len = len_remote;
+                
+                addr_sys_to_socket(source.u.listener.out_addr, sysaddr);
+            }
+        } break;
+        
+        case BCONNECTION_SOURCE_TYPE_CONNECTOR: {
+            BConnector *connector = source.u.connector.connector;
+            
+            // grab fd from connector
+            o->sock = connector->sock;
+            connector->sock = INVALID_SOCKET;
+            
+            // release connector resources
+            connector_abort(connector);
+        } break;
+    }
+    
+    // set not aborted
+    o->aborted = 0;
+    
+    // init send olap
+    BReactorIOCPOverlapped_Init(&o->send.olap, o->reactor, o, (BReactorIOCPOverlapped_handler)connection_send_olap_handler);
+    
+    // set send not inited
+    o->send.inited = 0;
+    
+    // init recv olap
+    BReactorIOCPOverlapped_Init(&o->recv.olap, o->reactor, o, (BReactorIOCPOverlapped_handler)connection_recv_olap_handler);
+    
+    // set recv not closed
+    o->recv.closed = 0;
+    
+    // set recv not inited
+    o->recv.inited = 0;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    if (closesocket(o->sock) == SOCKET_ERROR) {
+        BLog(BLOG_ERROR, "closesocket failed");
+    }
+    return 0;
+}
+
+void BConnection_Free (BConnection *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    ASSERT(!o->recv.inited)
+    ASSERT(!o->send.inited)
+    
+    if (!o->aborted) {
+        connection_abort(o);
+    }
+}
+
+void BConnection_SetHandlers (BConnection *o, void *user, BConnection_handler handler)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // set handlers
+    o->user = user;
+    o->handler = handler;
+}
+
+int BConnection_SetSendBuffer (BConnection *o, int buf_size)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    if (setsockopt(o->sock, SOL_SOCKET, SO_SNDBUF, (char *)&buf_size, sizeof(buf_size)) < 0) {
+        BLog(BLOG_ERROR, "setsockopt failed");
+        return 0;
+    }
+    
+    return 1;
+}
+
+void BConnection_SendAsync_Init (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(!o->send.inited)
+    
+    // init interface
+    StreamPassInterface_Init(&o->send.iface, (StreamPassInterface_handler_send)connection_send_iface_handler_send, o, BReactor_PendingGroup(o->reactor));
+    
+    // set not busy
+    o->send.busy = 0;
+    
+    // set inited
+    o->send.inited = 1;
+}
+
+void BConnection_SendAsync_Free (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->send.inited)
+    
+    // abort if busy
+    if (o->send.busy && !o->aborted) {
+        connection_abort(o);
+    }
+    
+    // free interface
+    StreamPassInterface_Free(&o->send.iface);
+    
+    // set not inited
+    o->send.inited = 0;
+}
+
+StreamPassInterface * BConnection_SendAsync_GetIf (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->send.inited)
+    
+    return &o->send.iface;
+}
+
+void BConnection_RecvAsync_Init (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->recv.closed)
+    ASSERT(!o->aborted)
+    ASSERT(!o->recv.inited)
+    
+    // init interface
+    StreamRecvInterface_Init(&o->recv.iface, (StreamRecvInterface_handler_recv)connection_recv_iface_handler_recv, o, BReactor_PendingGroup(o->reactor));
+    
+    // set not busy
+    o->recv.busy = 0;
+    
+    // set inited
+    o->recv.inited = 1;
+}
+
+void BConnection_RecvAsync_Free (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->recv.inited)
+    
+    // abort if busy
+    if (o->recv.busy && !o->aborted) {
+        connection_abort(o);
+    }
+    
+    // free interface
+    StreamRecvInterface_Free(&o->recv.iface);
+    
+    // set not inited
+    o->recv.inited = 0;
+}
+
+StreamRecvInterface * BConnection_RecvAsync_GetIf (BConnection *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->recv.inited)
+    
+    return &o->recv.iface;
+}
diff --git a/external/badvpn_dns/system/BConnection_win.h b/external/badvpn_dns/system/BConnection_win.h
new file mode 100644
index 0000000..da9a8ed
--- /dev/null
+++ b/external/badvpn_dns/system/BConnection_win.h
@@ -0,0 +1,101 @@
+/**
+ * @file BConnection_win.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <windows.h>
+#include <winsock2.h>
+#ifdef BADVPN_USE_SHIPPED_MSWSOCK
+#    include <misc/mswsock.h>
+#else
+#    include <mswsock.h>
+#endif
+
+#include <misc/debugerror.h>
+#include <base/DebugObject.h>
+
+struct BListener_addrbuf_stub {
+    union {
+        struct sockaddr_in ipv4;
+        struct sockaddr_in6 ipv6;
+    } addr;
+    uint8_t extra[16];
+};
+
+struct BListener_s {
+    BReactor *reactor;
+    void *user;
+    BListener_handler handler;
+    int sys_family;
+    SOCKET sock;
+    LPFN_ACCEPTEX fnAcceptEx;
+    LPFN_GETACCEPTEXSOCKADDRS fnGetAcceptExSockaddrs;
+    BReactorIOCPOverlapped olap;
+    SOCKET newsock;
+    uint8_t addrbuf[2 * sizeof(struct BListener_addrbuf_stub)];
+    BPending next_job;
+    int busy;
+    int ready;
+    DebugObject d_obj;
+};
+
+struct BConnector_s {
+    BReactor *reactor;
+    void *user;
+    BConnector_handler handler;
+    SOCKET sock;
+    LPFN_CONNECTEX fnConnectEx;
+    BReactorIOCPOverlapped olap;
+    int busy;
+    int ready;
+    DebugObject d_obj;
+};
+
+struct BConnection_s {
+    BReactor *reactor;
+    void *user;
+    BConnection_handler handler;
+    SOCKET sock;
+    int aborted;
+    struct {
+        BReactorIOCPOverlapped olap;
+        int inited;
+        StreamPassInterface iface;
+        int busy;
+        int busy_data_len;
+    } send;
+    struct {
+        BReactorIOCPOverlapped olap;
+        int closed;
+        int inited;
+        StreamRecvInterface iface;
+        int busy;
+        int busy_data_len;
+    } recv;
+    DebugError d_err;
+    DebugObject d_obj;
+};
diff --git a/external/badvpn_dns/system/BDatagram.h b/external/badvpn_dns/system/BDatagram.h
new file mode 100644
index 0000000..33efb45
--- /dev/null
+++ b/external/badvpn_dns/system/BDatagram.h
@@ -0,0 +1,209 @@
+/**
+ * @file BDatagram.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_SYSTEM_BDATAGRAM
+#define BADVPN_SYSTEM_BDATAGRAM
+
+#include <misc/debug.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketRecvInterface.h>
+#include <system/BAddr.h>
+#include <system/BReactor.h>
+#include <system/BNetwork.h>
+
+struct BDatagram_s;
+
+/**
+ * Represents datagram communication. This is usually UDP, but may also be Linux packet sockets.
+ * Sending and receiving is performed via {@link PacketPassInterface} and {@link PacketRecvInterface},
+ * respectively.
+ */
+typedef struct BDatagram_s BDatagram;
+
+#define BDATAGRAM_EVENT_ERROR 1
+
+/**
+ * Handler called when an error occurs with the datagram object.
+ * The datagram object is no longer usable and must be freed from withing the job closure of
+ * this handler. No further I/O, interface initialization, binding and send address setting
+ * must occur.
+ * 
+ * @param user as in {@link BDatagram_Init}
+ * @param event always BDATAGRAM_EVENT_ERROR
+ */
+typedef void (*BDatagram_handler) (void *user, int event);
+
+/**
+ * Checks if the given address family (from {@link BAddr.h}) is supported by {@link BDatagram}
+ * and related objects.
+ * 
+ * @param family family to check
+ * @return 1 if supported, 0 if not
+ */
+int BDatagram_AddressFamilySupported (int family);
+
+/**
+ * Initializes the object.
+ * {@link BNetwork_GlobalInit} must have been done.
+ * 
+ * @param o the object
+ * @param family address family. Must be supported according to {@link BDatagram_AddressFamilySupported}.
+ * @param reactor reactor we live in
+ * @param user argument to handler
+ * @param handler handler called when an error occurs
+ * @return 1 on success, 0 on failure
+ */
+int BDatagram_Init (BDatagram *o, int family, BReactor *reactor, void *user,
+                    BDatagram_handler handler) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * The send and receive interfaces must not be initialized.
+ * 
+ * @param o the object
+ */
+void BDatagram_Free (BDatagram *o);
+
+/**
+ * Binds to the given local address.
+ * May initiate I/O.
+ * 
+ * @param o the object
+ * @param addr address to bind to. Its family must be supported according to {@link BDatagram_AddressFamilySupported}.
+ * @return 1 on success, 0 on failure
+ */
+int BDatagram_Bind (BDatagram *o, BAddr addr) WARN_UNUSED;
+
+/**
+ * Sets addresses for sending.
+ * May initiate I/O.
+ * 
+ * @param o the object
+ * @param remote_addr destination address for sending datagrams. Its family must be supported according
+ *                    to {@link BDatagram_AddressFamilySupported}.
+ * @param local_addr local source IP address. May be an invalid address, otherwise its family must be
+ *                   supported according to {@link BDatagram_AddressFamilySupported}.
+ */
+void BDatagram_SetSendAddrs (BDatagram *o, BAddr remote_addr, BIPAddr local_addr);
+
+/**
+ * Returns the remote and local address of the last datagram received.
+ * Fails if and only if no datagrams have been received yet.
+ * 
+ * @param o the object
+ * @param remote_addr returns the remote source address of the datagram. May be an invalid address, theoretically.
+ * @param local_addr returns the local destination IP address. May be an invalid address.
+ * @return 1 on success, 0 on failure
+ */
+int BDatagram_GetLastReceiveAddrs (BDatagram *o, BAddr *remote_addr, BIPAddr *local_addr);
+
+#ifndef BADVPN_USE_WINAPI
+/**
+ * Returns the underlying socket file descriptor of the datagram object.
+ * Available on Unix-like systems only.
+ * 
+ * @param o the object
+ * @return file descriptor
+ */
+int BDatagram_GetFd (BDatagram *o);
+#endif
+
+/**
+ * Sets the SO_REUSEADDR option for the underlying socket.
+ * 
+ * @param o the object
+ * @param reuse value of the option. Must be 0 or 1.
+ */
+int BDatagram_SetReuseAddr (BDatagram *o, int reuse);
+
+/**
+ * Initializes the send interface.
+ * The send interface must not be initialized.
+ * 
+ * @param o the object
+ * @param mtu maximum transmission unit. Must be >=0.
+ */
+void BDatagram_SendAsync_Init (BDatagram *o, int mtu);
+
+/**
+ * Frees the send interface.
+ * The send interface must be initialized.
+ * If the send interface was busy when this is called, the datagram object is no longer usable and must be
+ * freed before any further I/O or interface initialization.
+ * 
+ * @param o the object
+ */
+void BDatagram_SendAsync_Free (BDatagram *o);
+
+/**
+ * Returns the send interface.
+ * The send interface must be initialized.
+ * The MTU of the interface will be as in {@link BDatagram_SendAsync_Init}.
+ * 
+ * @param o the object
+ * @return send interface
+ */
+PacketPassInterface * BDatagram_SendAsync_GetIf (BDatagram *o);
+
+/**
+ * Initializes the receive interface.
+ * The receive interface must not be initialized.
+ * 
+ * @param o the object
+ * @param mtu maximum transmission unit. Must be >=0.
+ */
+void BDatagram_RecvAsync_Init (BDatagram *o, int mtu);
+
+/**
+ * Frees the receive interface.
+ * The receive interface must be initialized.
+ * If the receive interface was busy when this is called, the datagram object is no longer usable and must be
+ * freed before any further I/O or interface initialization.
+ * 
+ * @param o the object
+ */
+void BDatagram_RecvAsync_Free (BDatagram *o);
+
+/**
+ * Returns the receive interface.
+ * The receive interface must be initialized.
+ * The MTU of the interface will be as in {@link BDatagram_RecvAsync_Init}.
+ * 
+ * @param o the object
+ * @return receive interface
+ */
+PacketRecvInterface * BDatagram_RecvAsync_GetIf (BDatagram *o);
+
+#ifdef BADVPN_USE_WINAPI
+#include "BDatagram_win.h"
+#else
+#include "BDatagram_unix.h"
+#endif
+
+#endif
diff --git a/external/badvpn_dns/system/BDatagram_unix.c b/external/badvpn_dns/system/BDatagram_unix.c
new file mode 100644
index 0000000..67853db
--- /dev/null
+++ b/external/badvpn_dns/system/BDatagram_unix.c
@@ -0,0 +1,855 @@
+/**
+ * @file BDatagram_unix.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#ifdef BADVPN_LINUX
+#    include <netpacket/packet.h>
+#    include <net/ethernet.h>
+#endif
+
+#include <misc/nonblocking.h>
+#include <base/BLog.h>
+
+#include "BDatagram.h"
+
+#include <generated/blog_channel_BDatagram.h>
+
+struct sys_addr {
+    socklen_t len;
+    union {
+        struct sockaddr generic;
+        struct sockaddr_in ipv4;
+        struct sockaddr_in6 ipv6;
+#ifdef BADVPN_LINUX
+        struct sockaddr_ll packet;
+#endif
+    } addr;
+};
+
+static int family_socket_to_sys (int family);
+static void addr_socket_to_sys (struct sys_addr *out, BAddr addr);
+static void addr_sys_to_socket (BAddr *out, struct sys_addr addr);
+static void set_pktinfo (int fd, int family);
+static void report_error (BDatagram *o);
+static void do_send (BDatagram *o);
+static void do_recv (BDatagram *o);
+static void fd_handler (BDatagram *o, int events);
+static void send_job_handler (BDatagram *o);
+static void recv_job_handler (BDatagram *o);
+static void send_if_handler_send (BDatagram *o, uint8_t *data, int data_len);
+static void recv_if_handler_recv (BDatagram *o, uint8_t *data);
+
+static int family_socket_to_sys (int family)
+{
+    switch (family) {
+        case BADDR_TYPE_IPV4:
+            return AF_INET;
+        case BADDR_TYPE_IPV6:
+            return AF_INET6;
+#ifdef BADVPN_LINUX
+        case BADDR_TYPE_PACKET:
+            return AF_PACKET;
+#endif
+    }
+    
+    ASSERT(0);
+    return 0;
+}
+
+static void addr_socket_to_sys (struct sys_addr *out, BAddr addr)
+{
+    switch (addr.type) {
+        case BADDR_TYPE_IPV4: {
+            out->len = sizeof(out->addr.ipv4);
+            memset(&out->addr.ipv4, 0, sizeof(out->addr.ipv4));
+            out->addr.ipv4.sin_family = AF_INET;
+            out->addr.ipv4.sin_port = addr.ipv4.port;
+            out->addr.ipv4.sin_addr.s_addr = addr.ipv4.ip;
+        } break;
+        
+        case BADDR_TYPE_IPV6: {
+            out->len = sizeof(out->addr.ipv6);
+            memset(&out->addr.ipv6, 0, sizeof(out->addr.ipv6));
+            out->addr.ipv6.sin6_family = AF_INET6;
+            out->addr.ipv6.sin6_port = addr.ipv6.port;
+            out->addr.ipv6.sin6_flowinfo = 0;
+            memcpy(out->addr.ipv6.sin6_addr.s6_addr, addr.ipv6.ip, 16);
+            out->addr.ipv6.sin6_scope_id = 0;
+        } break;
+        
+#ifdef BADVPN_LINUX
+        case BADDR_TYPE_PACKET: {
+            ASSERT(addr.packet.header_type == BADDR_PACKET_HEADER_TYPE_ETHERNET)
+            memset(&out->addr.packet, 0, sizeof(out->addr.packet));
+            out->len = sizeof(out->addr.packet);
+            out->addr.packet.sll_family = AF_PACKET;
+            out->addr.packet.sll_protocol = addr.packet.phys_proto;
+            out->addr.packet.sll_ifindex = addr.packet.interface_index;
+            out->addr.packet.sll_hatype = 1; // linux/if_arp.h: #define ARPHRD_ETHER 1
+            switch (addr.packet.packet_type) {
+                case BADDR_PACKET_PACKET_TYPE_HOST:
+                    out->addr.packet.sll_pkttype = PACKET_HOST;
+                    break;
+                case BADDR_PACKET_PACKET_TYPE_BROADCAST:
+                    out->addr.packet.sll_pkttype = PACKET_BROADCAST;
+                    break;
+                case BADDR_PACKET_PACKET_TYPE_MULTICAST:
+                    out->addr.packet.sll_pkttype = PACKET_MULTICAST;
+                    break;
+                case BADDR_PACKET_PACKET_TYPE_OTHERHOST:
+                    out->addr.packet.sll_pkttype = PACKET_OTHERHOST;
+                    break;
+                case BADDR_PACKET_PACKET_TYPE_OUTGOING:
+                    out->addr.packet.sll_pkttype = PACKET_OUTGOING;
+                    break;
+                default:
+                    ASSERT(0);
+            }
+            out->addr.packet.sll_halen = 6;
+            memcpy(out->addr.packet.sll_addr, addr.packet.phys_addr, 6);
+        } break;
+#endif
+        
+        default: ASSERT(0);
+    }
+}
+
+static void addr_sys_to_socket (BAddr *out, struct sys_addr addr)
+{
+    switch (addr.addr.generic.sa_family) {
+        case AF_INET: {
+            ASSERT(addr.len == sizeof(struct sockaddr_in))
+            BAddr_InitIPv4(out, addr.addr.ipv4.sin_addr.s_addr, addr.addr.ipv4.sin_port);
+        } break;
+        
+        case AF_INET6: {
+            ASSERT(addr.len == sizeof(struct sockaddr_in6))
+            BAddr_InitIPv6(out, addr.addr.ipv6.sin6_addr.s6_addr, addr.addr.ipv6.sin6_port);
+        } break;
+        
+#ifdef BADVPN_LINUX
+        case AF_PACKET: {
+            if (addr.len < offsetof(struct sockaddr_ll, sll_addr) + 6) {
+                goto fail;
+            }
+            if (addr.addr.packet.sll_hatype != 1) { // linux/if_arp.h: #define ARPHRD_ETHER 1
+                goto fail;
+            }
+            int packet_type;
+            switch (addr.addr.packet.sll_pkttype) {
+                case PACKET_HOST:
+                    packet_type = BADDR_PACKET_PACKET_TYPE_HOST;
+                    break;
+                case PACKET_BROADCAST:
+                    packet_type = BADDR_PACKET_PACKET_TYPE_BROADCAST;
+                    break;
+                case PACKET_MULTICAST:
+                    packet_type = BADDR_PACKET_PACKET_TYPE_MULTICAST;
+                    break;
+                case PACKET_OTHERHOST:
+                    packet_type = BADDR_PACKET_PACKET_TYPE_OTHERHOST;
+                    break;
+                case PACKET_OUTGOING:
+                    packet_type = BADDR_PACKET_PACKET_TYPE_OUTGOING;
+                    break;
+                default:
+                    goto fail;
+            }
+            if (addr.addr.packet.sll_halen != 6) {
+                goto fail;
+            }
+            BAddr_InitPacket(out, addr.addr.packet.sll_protocol, addr.addr.packet.sll_ifindex, BADDR_PACKET_HEADER_TYPE_ETHERNET, packet_type, addr.addr.packet.sll_addr);
+        } break;
+#endif
+        
+        fail:
+        default: {
+            BAddr_InitNone(out);
+        } break;
+    }
+}
+
+static void set_pktinfo (int fd, int family)
+{
+    int opt = 1;
+    
+    switch (family) {
+        case BADDR_TYPE_IPV4: {
+#ifdef BADVPN_FREEBSD
+            if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt)) < 0) {
+                BLog(BLOG_ERROR, "setsockopt(IP_RECVDSTADDR) failed");
+            }
+#else
+            if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) < 0) {
+                BLog(BLOG_ERROR, "setsockopt(IP_PKTINFO) failed");
+            }
+#endif
+        } break;
+        
+#ifdef IPV6_RECVPKTINFO
+        case BADDR_TYPE_IPV6: {
+            if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof(opt)) < 0) {
+                BLog(BLOG_ERROR, "setsockopt(IPV6_RECVPKTINFO) failed");
+            }
+        } break;
+#endif
+    }
+}
+
+static void report_error (BDatagram *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    
+    // report error
+    DEBUGERROR(&o->d_err, o->handler(o->user, BDATAGRAM_EVENT_ERROR));
+    return;
+}
+
+static void do_send (BDatagram *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->send.inited)
+    ASSERT(o->send.busy)
+    ASSERT(o->send.have_addrs)
+    
+    // limit
+    if (!BReactorLimit_Increment(&o->send.limit)) {
+        // wait for fd
+        o->wait_events |= BREACTOR_WRITE;
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+        return;
+    }
+    
+    // convert destination address
+    struct sys_addr sysaddr;
+    addr_socket_to_sys(&sysaddr, o->send.remote_addr);
+    
+    struct iovec iov;
+    iov.iov_base = (uint8_t *)o->send.busy_data;
+    iov.iov_len = o->send.busy_data_len;
+    
+    union {
+#ifdef BADVPN_FREEBSD
+        char in[CMSG_SPACE(sizeof(struct in_addr))];
+#else
+        char in[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#endif
+        char in6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+    } cdata;
+    
+    struct msghdr msg;
+    memset(&msg, 0, sizeof(msg));
+    msg.msg_name = &sysaddr.addr.generic;
+    msg.msg_namelen = sysaddr.len;
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+    msg.msg_control = &cdata;
+    msg.msg_controllen = sizeof(cdata);
+    
+    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+    
+    size_t controllen = 0;
+    
+    switch (o->send.local_addr.type) {
+        case BADDR_TYPE_IPV4: {
+#ifdef BADVPN_FREEBSD
+            memset(cmsg, 0, CMSG_SPACE(sizeof(struct in_addr)));
+            cmsg->cmsg_level = IPPROTO_IP;
+            cmsg->cmsg_type = IP_SENDSRCADDR;
+            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
+            struct in_addr *addrinfo = (struct in_addr *)CMSG_DATA(cmsg);
+            addrinfo->s_addr = o->send.local_addr.ipv4;
+            controllen += CMSG_SPACE(sizeof(struct in_addr));
+#else
+            memset(cmsg, 0, CMSG_SPACE(sizeof(struct in_pktinfo)));
+            cmsg->cmsg_level = IPPROTO_IP;
+            cmsg->cmsg_type = IP_PKTINFO;
+            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+            struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg);
+            pktinfo->ipi_spec_dst.s_addr = o->send.local_addr.ipv4;
+            controllen += CMSG_SPACE(sizeof(struct in_pktinfo));
+#endif
+        } break;
+        
+        case BADDR_TYPE_IPV6: {
+            memset(cmsg, 0, CMSG_SPACE(sizeof(struct in6_pktinfo)));
+            cmsg->cmsg_level = IPPROTO_IPV6;
+            cmsg->cmsg_type = IPV6_PKTINFO;
+            cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+            struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+            memcpy(pktinfo->ipi6_addr.s6_addr, o->send.local_addr.ipv6, 16);
+            controllen += CMSG_SPACE(sizeof(struct in6_pktinfo));
+        } break;
+    }
+    
+    msg.msg_controllen = controllen;
+    
+    if (msg.msg_controllen == 0) {
+        msg.msg_control = NULL;
+    }
+    
+    // send
+    int bytes = sendmsg(o->fd, &msg, 0);
+    if (bytes < 0) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            // wait for fd
+            o->wait_events |= BREACTOR_WRITE;
+            BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+            return;
+        }
+        
+        BLog(BLOG_ERROR, "send failed");
+        report_error(o);
+        return;
+    }
+    
+    ASSERT(bytes >= 0)
+    ASSERT(bytes <= o->send.busy_data_len)
+    
+    if (bytes < o->send.busy_data_len) {
+        BLog(BLOG_ERROR, "send sent too little");
+    }
+    
+    // if recv wasn't started yet, start it
+    if (!o->recv.started) {
+        // set recv started
+        o->recv.started = 1;
+        
+        // continue receiving
+        if (o->recv.inited && o->recv.busy) {
+            BPending_Set(&o->recv.job);
+        }
+    }
+    
+    // set not busy
+    o->send.busy = 0;
+    
+    // done
+    PacketPassInterface_Done(&o->send.iface);
+}
+
+static void do_recv (BDatagram *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->recv.inited)
+    ASSERT(o->recv.busy)
+    ASSERT(o->recv.started)
+    
+    // limit
+    if (!BReactorLimit_Increment(&o->recv.limit)) {
+        // wait for fd
+        o->wait_events |= BREACTOR_READ;
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+        return;
+    }
+    
+    struct sys_addr sysaddr;
+    
+    struct iovec iov;
+    iov.iov_base = o->recv.busy_data;
+    iov.iov_len = o->recv.mtu;
+    
+    union {
+#ifdef BADVPN_FREEBSD
+        char in[CMSG_SPACE(sizeof(struct in_addr))];
+#else
+        char in[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#endif
+        char in6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+    } cdata;
+    
+    struct msghdr msg;
+    memset(&msg, 0, sizeof(msg));
+    msg.msg_name = &sysaddr.addr.generic;
+    msg.msg_namelen = sizeof(sysaddr.addr);
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+    msg.msg_control = &cdata;
+    msg.msg_controllen = sizeof(cdata);
+    
+    // recv
+    int bytes = recvmsg(o->fd, &msg, 0);
+    if (bytes < 0) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            // wait for fd
+            o->wait_events |= BREACTOR_READ;
+            BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+            return;
+        }
+        
+        BLog(BLOG_ERROR, "recv failed");
+        report_error(o);
+        return;
+    }
+    
+    ASSERT(bytes >= 0)
+    ASSERT(bytes <= o->recv.mtu)
+    
+    // read returned address
+    sysaddr.len = msg.msg_namelen;
+    addr_sys_to_socket(&o->recv.remote_addr, sysaddr);
+    
+    // read returned local address
+    BIPAddr_InitInvalid(&o->recv.local_addr);
+    for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+#ifdef BADVPN_FREEBSD
+        if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR) {
+            struct in_addr *addrinfo = (struct in_addr *)CMSG_DATA(cmsg);
+            BIPAddr_InitIPv4(&o->recv.local_addr, addrinfo->s_addr);
+        }
+#else
+        if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
+            struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg);
+            BIPAddr_InitIPv4(&o->recv.local_addr, pktinfo->ipi_addr.s_addr);
+        }
+#endif
+        else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
+            struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+            BIPAddr_InitIPv6(&o->recv.local_addr, pktinfo->ipi6_addr.s6_addr);
+        }
+    }
+    
+    // set have addresses
+    o->recv.have_addrs = 1;
+    
+    // set not busy
+    o->recv.busy = 0;
+    
+    // done
+    PacketRecvInterface_Done(&o->recv.iface, bytes);
+}
+
+static void fd_handler (BDatagram *o, int events)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    
+    // clear handled events
+    o->wait_events &= ~events;
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+    
+    int have_send = 0;
+    int have_recv = 0;
+    
+    if ((events & BREACTOR_WRITE) || ((events & (BREACTOR_ERROR|BREACTOR_HUP)) && o->send.inited && o->send.busy && o->send.have_addrs)) {
+        ASSERT(o->send.inited)
+        ASSERT(o->send.busy)
+        ASSERT(o->send.have_addrs)
+        
+        have_send = 1;
+    }
+    
+    if ((events & BREACTOR_READ) || ((events & (BREACTOR_ERROR|BREACTOR_HUP)) && o->recv.inited && o->recv.busy && o->recv.started)) {
+        ASSERT(o->recv.inited)
+        ASSERT(o->recv.busy)
+        ASSERT(o->recv.started)
+        
+        have_recv = 1;
+    }
+    
+    if (have_send) {
+        if (have_recv) {
+            BPending_Set(&o->recv.job);
+        }
+        
+        do_send(o);
+        return;
+    }
+    
+    if (have_recv) {
+        do_recv(o);
+        return;
+    }
+    
+    BLog(BLOG_ERROR, "fd error event");
+    report_error(o);
+    return;
+}
+
+static void send_job_handler (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->send.inited)
+    ASSERT(o->send.busy)
+    ASSERT(o->send.have_addrs)
+    
+    do_send(o);
+    return;
+}
+
+static void recv_job_handler (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->recv.inited)
+    ASSERT(o->recv.busy)
+    ASSERT(o->recv.started)
+    
+    do_recv(o);
+    return;
+}
+
+static void send_if_handler_send (BDatagram *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->send.inited)
+    ASSERT(!o->send.busy)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->send.mtu)
+    
+    // remember data
+    o->send.busy_data = data;
+    o->send.busy_data_len = data_len;
+    
+    // set busy
+    o->send.busy = 1;
+    
+    // if have no addresses, wait
+    if (!o->send.have_addrs) {
+        return;
+    }
+    
+    // set job
+    BPending_Set(&o->send.job);
+}
+
+static void recv_if_handler_recv (BDatagram *o, uint8_t *data)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->recv.inited)
+    ASSERT(!o->recv.busy)
+    
+    // remember data
+    o->recv.busy_data = data;
+    
+    // set busy
+    o->recv.busy = 1;
+    
+    // if recv not started yet, wait
+    if (!o->recv.started) {
+        return;
+    }
+    
+    // set job
+    BPending_Set(&o->recv.job);
+}
+
+int BDatagram_AddressFamilySupported (int family)
+{
+    switch (family) {
+        case BADDR_TYPE_IPV4:
+        case BADDR_TYPE_IPV6:
+#ifdef BADVPN_LINUX
+        case BADDR_TYPE_PACKET:
+#endif
+            return 1;
+    }
+    
+    return 0;
+}
+
+int BDatagram_Init (BDatagram *o, int family, BReactor *reactor, void *user,
+                    BDatagram_handler handler)
+{
+    ASSERT(BDatagram_AddressFamilySupported(family))
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // init fd
+    if ((o->fd = socket(family_socket_to_sys(family), SOCK_DGRAM, 0)) < 0) {
+        BLog(BLOG_ERROR, "socket failed");
+        goto fail0;
+    }
+    
+    // set fd non-blocking
+    if (!badvpn_set_nonblocking(o->fd)) {
+        BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail1;
+    }
+    
+    // enable receiving pktinfo
+    set_pktinfo(o->fd, family);
+    
+    // init BFileDescriptor
+    BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)fd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    
+    // set no wait events
+    o->wait_events = 0;
+    
+    // init limits
+    BReactorLimit_Init(&o->send.limit, o->reactor, BDATAGRAM_SEND_LIMIT);
+    BReactorLimit_Init(&o->recv.limit, o->reactor, BDATAGRAM_RECV_LIMIT);
+    
+    // set have no send and recv addresses
+    o->send.have_addrs = 0;
+    o->recv.have_addrs = 0;
+    
+    // set recv not started
+    o->recv.started = 0;
+    
+    // set send and recv not inited
+    o->send.inited = 0;
+    o->recv.inited = 0;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    if (close(o->fd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+fail0:
+    return 0;
+}
+
+void BDatagram_Free (BDatagram *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    ASSERT(!o->recv.inited)
+    ASSERT(!o->send.inited)
+    
+    // free limits
+    BReactorLimit_Free(&o->recv.limit);
+    BReactorLimit_Free(&o->send.limit);
+    
+    // free BFileDescriptor
+    BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+    
+    // free fd
+    if (close(o->fd) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+}
+
+int BDatagram_Bind (BDatagram *o, BAddr addr)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(BDatagram_AddressFamilySupported(addr.type))
+    
+    // translate address
+    struct sys_addr sysaddr;
+    addr_socket_to_sys(&sysaddr, addr);
+    
+    // bind
+    if (bind(o->fd, &sysaddr.addr.generic, sysaddr.len) < 0) {
+        BLog(BLOG_ERROR, "bind failed");
+        return 0;
+    }
+    
+    // if recv wasn't started yet, start it
+    if (!o->recv.started) {
+        // set recv started
+        o->recv.started = 1;
+        
+        // continue receiving
+        if (o->recv.inited && o->recv.busy) {
+            BPending_Set(&o->recv.job);
+        }
+    }
+    
+    return 1;
+}
+
+void BDatagram_SetSendAddrs (BDatagram *o, BAddr remote_addr, BIPAddr local_addr)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(BDatagram_AddressFamilySupported(remote_addr.type))
+    ASSERT(local_addr.type == BADDR_TYPE_NONE || BDatagram_AddressFamilySupported(local_addr.type))
+    
+    // set addresses
+    o->send.remote_addr = remote_addr;
+    o->send.local_addr = local_addr;
+    
+    if (!o->send.have_addrs) {
+        // set have addresses
+        o->send.have_addrs = 1;
+        
+        // start sending
+        if (o->send.inited && o->send.busy) {
+            BPending_Set(&o->send.job);
+        }
+    }
+}
+
+int BDatagram_GetLastReceiveAddrs (BDatagram *o, BAddr *remote_addr, BIPAddr *local_addr)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    if (!o->recv.have_addrs) {
+        return 0;
+    }
+    
+    *remote_addr = o->recv.remote_addr;
+    *local_addr = o->recv.local_addr;
+    return 1;
+}
+
+int BDatagram_GetFd (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return o->fd;
+}
+
+int BDatagram_SetReuseAddr (BDatagram *o, int reuse)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(reuse == 0 || reuse == 1)
+    
+    if (setsockopt(o->fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+void BDatagram_SendAsync_Init (BDatagram *o, int mtu)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->send.inited)
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    o->send.mtu = mtu;
+    
+    // init interface
+    PacketPassInterface_Init(&o->send.iface, o->send.mtu, (PacketPassInterface_handler_send)send_if_handler_send, o, BReactor_PendingGroup(o->reactor));
+    
+    // init job
+    BPending_Init(&o->send.job, BReactor_PendingGroup(o->reactor), (BPending_handler)send_job_handler, o);
+    
+    // set not busy
+    o->send.busy = 0;
+    
+    // set inited
+    o->send.inited = 1;
+}
+
+void BDatagram_SendAsync_Free (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->send.inited)
+    
+    // update events
+    o->wait_events &= ~BREACTOR_WRITE;
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+    
+    // free job
+    BPending_Free(&o->send.job);
+    
+    // free interface
+    PacketPassInterface_Free(&o->send.iface);
+    
+    // set not inited
+    o->send.inited = 0;
+}
+
+PacketPassInterface * BDatagram_SendAsync_GetIf (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->send.inited)
+    
+    return &o->send.iface;
+}
+
+void BDatagram_RecvAsync_Init (BDatagram *o, int mtu)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->recv.inited)
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    o->recv.mtu = mtu;
+    
+    // init interface
+    PacketRecvInterface_Init(&o->recv.iface, o->recv.mtu, (PacketRecvInterface_handler_recv)recv_if_handler_recv, o, BReactor_PendingGroup(o->reactor));
+    
+    // init job
+    BPending_Init(&o->recv.job, BReactor_PendingGroup(o->reactor), (BPending_handler)recv_job_handler, o);
+    
+    // set not busy
+    o->recv.busy = 0;
+    
+    // set inited
+    o->recv.inited = 1;
+}
+
+void BDatagram_RecvAsync_Free (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->recv.inited)
+    
+    // update events
+    o->wait_events &= ~BREACTOR_READ;
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->wait_events);
+    
+    // free job
+    BPending_Free(&o->recv.job);
+    
+    // free interface
+    PacketRecvInterface_Free(&o->recv.iface);
+    
+    // set not inited
+    o->recv.inited = 0;
+}
+
+PacketRecvInterface * BDatagram_RecvAsync_GetIf (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->recv.inited)
+    
+    return &o->recv.iface;
+}
diff --git a/external/badvpn_dns/system/BDatagram_unix.h b/external/badvpn_dns/system/BDatagram_unix.h
new file mode 100644
index 0000000..3ef0262
--- /dev/null
+++ b/external/badvpn_dns/system/BDatagram_unix.h
@@ -0,0 +1,71 @@
+/**
+ * @file BDatagram_unix.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/debugerror.h>
+#include <base/DebugObject.h>
+
+#define BDATAGRAM_SEND_LIMIT 2
+#define BDATAGRAM_RECV_LIMIT 2
+
+struct BDatagram_s {
+    BReactor *reactor;
+    void *user;
+    BDatagram_handler handler;
+    int fd;
+    BFileDescriptor bfd;
+    int wait_events;
+    struct {
+        BReactorLimit limit;
+        int have_addrs;
+        BAddr remote_addr;
+        BIPAddr local_addr;
+        int inited;
+        int mtu;
+        PacketPassInterface iface;
+        BPending job;
+        int busy;
+        const uint8_t *busy_data;
+        int busy_data_len;
+    } send;
+    struct {
+        BReactorLimit limit;
+        int started;
+        int have_addrs;
+        BAddr remote_addr;
+        BIPAddr local_addr;
+        int inited;
+        int mtu;
+        PacketRecvInterface iface;
+        BPending job;
+        int busy;
+        uint8_t *busy_data;
+    } recv;
+    DebugError d_err;
+    DebugObject d_obj;
+};
diff --git a/external/badvpn_dns/system/BDatagram_win.c b/external/badvpn_dns/system/BDatagram_win.c
new file mode 100644
index 0000000..0528866
--- /dev/null
+++ b/external/badvpn_dns/system/BDatagram_win.c
@@ -0,0 +1,755 @@
+/**
+ * @file BDatagram_win.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <base/BLog.h>
+
+#include "BDatagram.h"
+
+#include <generated/blog_channel_BDatagram.h>
+
+static int family_socket_to_sys (int family);
+static void addr_socket_to_sys (struct BDatagram_sys_addr *out, BAddr addr);
+static void addr_sys_to_socket (BAddr *out, struct BDatagram_sys_addr addr);
+static void set_pktinfo (SOCKET sock, int family);
+static void report_error (BDatagram *o);
+static void datagram_abort (BDatagram *o);
+static void start_send (BDatagram *o);
+static void start_recv (BDatagram *o);
+static void send_job_handler (BDatagram *o);
+static void recv_job_handler (BDatagram *o);
+static void send_if_handler_send (BDatagram *o, uint8_t *data, int data_len);
+static void recv_if_handler_recv (BDatagram *o, uint8_t *data);
+static void send_olap_handler (BDatagram *o, int event, DWORD bytes);
+static void recv_olap_handler (BDatagram *o, int event, DWORD bytes);
+
+static int family_socket_to_sys (int family)
+{
+    switch (family) {
+        case BADDR_TYPE_IPV4:
+            return AF_INET;
+        case BADDR_TYPE_IPV6:
+            return AF_INET6;
+    }
+    
+    ASSERT(0);
+    return 0;
+}
+
+static void addr_socket_to_sys (struct BDatagram_sys_addr *out, BAddr addr)
+{
+    switch (addr.type) {
+        case BADDR_TYPE_IPV4: {
+            out->len = sizeof(out->addr.ipv4);
+            memset(&out->addr.ipv4, 0, sizeof(out->addr.ipv4));
+            out->addr.ipv4.sin_family = AF_INET;
+            out->addr.ipv4.sin_port = addr.ipv4.port;
+            out->addr.ipv4.sin_addr.s_addr = addr.ipv4.ip;
+        } break;
+        
+        case BADDR_TYPE_IPV6: {
+            out->len = sizeof(out->addr.ipv6);
+            memset(&out->addr.ipv6, 0, sizeof(out->addr.ipv6));
+            out->addr.ipv6.sin6_family = AF_INET6;
+            out->addr.ipv6.sin6_port = addr.ipv6.port;
+            out->addr.ipv6.sin6_flowinfo = 0;
+            memcpy(out->addr.ipv6.sin6_addr.s6_addr, addr.ipv6.ip, 16);
+            out->addr.ipv6.sin6_scope_id = 0;
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void addr_sys_to_socket (BAddr *out, struct BDatagram_sys_addr addr)
+{
+    switch (addr.addr.generic.sa_family) {
+        case AF_INET: {
+            ASSERT(addr.len == sizeof(struct sockaddr_in))
+            BAddr_InitIPv4(out, addr.addr.ipv4.sin_addr.s_addr, addr.addr.ipv4.sin_port);
+        } break;
+        
+        case AF_INET6: {
+            ASSERT(addr.len == sizeof(struct sockaddr_in6))
+            BAddr_InitIPv6(out, addr.addr.ipv6.sin6_addr.s6_addr, addr.addr.ipv6.sin6_port);
+        } break;
+        
+        default: {
+            BAddr_InitNone(out);
+        } break;
+    }
+}
+
+static void set_pktinfo (SOCKET sock, int family)
+{
+    DWORD opt = 1;
+    
+    switch (family) {
+        case BADDR_TYPE_IPV4: {
+            if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, (char *)&opt, sizeof(opt)) < 0) {
+                BLog(BLOG_ERROR, "setsockopt(IP_PKTINFO) failed");
+            }
+        } break;
+        
+        case BADDR_TYPE_IPV6: {
+            if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, (char *)&opt, sizeof(opt)) < 0) {
+                BLog(BLOG_ERROR, "setsockopt(IPV6_PKTINFO) failed");
+            }
+        } break;
+    }
+}
+
+static void report_error (BDatagram *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    
+    // report error
+    DEBUGERROR(&o->d_err, o->handler(o->user, BDATAGRAM_EVENT_ERROR));
+    return;
+}
+
+static void datagram_abort (BDatagram *o)
+{
+    ASSERT(!o->aborted)
+    
+    // cancel I/O
+    if ((o->recv.inited && o->recv.data_have && o->recv.data_busy) || (o->send.inited && o->send.data_len >= 0 && o->send.data_busy)) {
+        if (!CancelIo((HANDLE)o->sock)) {
+            BLog(BLOG_ERROR, "CancelIo failed");
+        }
+    }
+    
+    // close socket
+    if (closesocket(o->sock) == SOCKET_ERROR) {
+        BLog(BLOG_ERROR, "closesocket failed");
+    }
+    
+    // wait for receiving to complete
+    if (o->recv.inited && o->recv.data_have && o->recv.data_busy) {
+        BReactorIOCPOverlapped_Wait(&o->recv.olap, NULL, NULL);
+    }
+    
+    // wait for sending to complete
+    if (o->send.inited && o->send.data_len >= 0 && o->send.data_busy) {
+        BReactorIOCPOverlapped_Wait(&o->send.olap, NULL, NULL);
+    }
+    
+    // free recv olap
+    BReactorIOCPOverlapped_Free(&o->recv.olap);
+    
+    // free send olap
+    BReactorIOCPOverlapped_Free(&o->send.olap);
+    
+    // set aborted
+    o->aborted = 1;
+}
+
+static void start_send (BDatagram *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->send.inited)
+    ASSERT(o->send.data_len >= 0)
+    ASSERT(!o->send.data_busy)
+    ASSERT(o->send.have_addrs)
+    
+    // convert destination address
+    addr_socket_to_sys(&o->send.sysaddr, o->send.remote_addr);
+    
+    WSABUF buf;
+    buf.buf = (char *)o->send.data;
+    buf.len = (o->send.data_len > ULONG_MAX ? ULONG_MAX : o->send.data_len);
+    
+    memset(&o->send.olap.olap, 0, sizeof(o->send.olap.olap));
+    
+    if (o->fnWSASendMsg) {
+        o->send.msg.name = &o->send.sysaddr.addr.generic;
+        o->send.msg.namelen = o->send.sysaddr.len;
+        o->send.msg.lpBuffers = &buf;
+        o->send.msg.dwBufferCount = 1;
+        o->send.msg.Control.buf = (char *)&o->send.cdata;
+        o->send.msg.Control.len = sizeof(o->send.cdata);
+        o->send.msg.dwFlags = 0;
+        
+        int sum = 0;
+        
+        WSACMSGHDR *cmsg = WSA_CMSG_FIRSTHDR(&o->send.msg);
+        
+        switch (o->send.local_addr.type) {
+            case BADDR_TYPE_IPV4: {
+                memset(cmsg, 0, WSA_CMSG_SPACE(sizeof(struct in_pktinfo)));
+                cmsg->cmsg_level = IPPROTO_IP;
+                cmsg->cmsg_type = IP_PKTINFO;
+                cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(struct in_pktinfo));
+                struct in_pktinfo *pktinfo = (struct in_pktinfo *)WSA_CMSG_DATA(cmsg);
+                pktinfo->ipi_addr.s_addr = o->send.local_addr.ipv4;
+                sum += WSA_CMSG_SPACE(sizeof(struct in_pktinfo));
+            } break;
+            case BADDR_TYPE_IPV6: {
+                memset(cmsg, 0, WSA_CMSG_SPACE(sizeof(struct in6_pktinfo)));
+                cmsg->cmsg_level = IPPROTO_IPV6;
+                cmsg->cmsg_type = IPV6_PKTINFO;
+                cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(struct in6_pktinfo));
+                struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)WSA_CMSG_DATA(cmsg);
+                memcpy(pktinfo->ipi6_addr.s6_addr, o->send.local_addr.ipv6, 16);
+                sum += WSA_CMSG_SPACE(sizeof(struct in6_pktinfo));
+            } break;
+        }
+        
+        o->send.msg.Control.len = sum;
+        
+        if (o->send.msg.Control.len == 0) {
+            o->send.msg.Control.buf = NULL;
+        }
+        
+        // send
+        int res = o->fnWSASendMsg(o->sock, &o->send.msg, 0, NULL, &o->send.olap.olap, NULL);
+        if (res == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
+            BLog(BLOG_ERROR, "WSASendMsg failed (%d)", WSAGetLastError());
+            report_error(o);
+            return;
+        }
+    } else {
+        // send
+        int res = WSASendTo(o->sock, &buf, 1, NULL, 0, &o->send.sysaddr.addr.generic, o->send.sysaddr.len, &o->send.olap.olap, NULL);
+        if (res == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
+            BLog(BLOG_ERROR, "WSASendTo failed (%d)", WSAGetLastError());
+            report_error(o);
+            return;
+        }
+    }
+    
+    // set busy
+    o->send.data_busy = 1;
+}
+
+static void start_recv (BDatagram *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->recv.inited)
+    ASSERT(o->recv.data_have)
+    ASSERT(!o->recv.data_busy)
+    ASSERT(o->recv.started)
+    
+    WSABUF buf;
+    buf.buf = (char *)o->recv.data;
+    buf.len = (o->recv.mtu > ULONG_MAX ? ULONG_MAX : o->recv.mtu);
+    
+    memset(&o->recv.olap.olap, 0, sizeof(o->recv.olap.olap));
+    
+    if (o->fnWSARecvMsg) {
+        o->recv.msg.name = &o->recv.sysaddr.addr.generic;
+        o->recv.msg.namelen = sizeof(o->recv.sysaddr.addr);
+        o->recv.msg.lpBuffers = &buf;
+        o->recv.msg.dwBufferCount = 1;
+        o->recv.msg.Control.buf = (char *)&o->recv.cdata;
+        o->recv.msg.Control.len = sizeof(o->recv.cdata);
+        o->recv.msg.dwFlags = 0;
+        
+        // recv
+        int res = o->fnWSARecvMsg(o->sock, &o->recv.msg, NULL, &o->recv.olap.olap, NULL);
+        if (res == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
+            BLog(BLOG_ERROR, "WSARecvMsg failed (%d)", WSAGetLastError());
+            report_error(o);
+            return;
+        }
+    } else {
+        o->recv.sysaddr.len = sizeof(o->recv.sysaddr.addr);
+        
+        // recv
+        DWORD flags = 0;
+        int res = WSARecvFrom(o->sock, &buf, 1, NULL, &flags, &o->recv.sysaddr.addr.generic, &o->recv.sysaddr.len, &o->recv.olap.olap, NULL);
+        if (res == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
+            BLog(BLOG_ERROR, "WSARecvFrom failed (%d)", WSAGetLastError());
+            report_error(o);
+            return;
+        }
+    }
+    
+    // set busy
+    o->recv.data_busy = 1;
+}
+
+static void send_job_handler (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->send.inited)
+    ASSERT(o->send.data_len >= 0)
+    ASSERT(!o->send.data_busy)
+    ASSERT(o->send.have_addrs)
+    
+    // send
+    start_send(o);
+    return;
+}
+
+static void recv_job_handler (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->recv.inited)
+    ASSERT(o->recv.data_have)
+    ASSERT(!o->recv.data_busy)
+    ASSERT(o->recv.started)
+    
+    // recv
+    start_recv(o);
+    return;
+}
+
+static void send_if_handler_send (BDatagram *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->send.inited)
+    ASSERT(o->send.data_len == -1)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->send.mtu)
+    
+    // remember data
+    o->send.data = data;
+    o->send.data_len = data_len;
+    o->send.data_busy = 0;
+    
+    // if have no addresses, wait
+    if (!o->send.have_addrs) {
+        return;
+    }
+    
+    // send
+    start_send(o);
+    return;
+}
+
+static void recv_if_handler_recv (BDatagram *o, uint8_t *data)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->recv.inited)
+    ASSERT(!o->recv.data_have)
+    
+    // remember data
+    o->recv.data = data;
+    o->recv.data_have = 1;
+    o->recv.data_busy = 0;
+    
+    // if recv not started yet, wait
+    if (!o->recv.started) {
+        return;
+    }
+    
+    // recv
+    start_recv(o);
+    return;
+}
+
+static void send_olap_handler (BDatagram *o, int event, DWORD bytes)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->send.inited)
+    ASSERT(o->send.data_len >= 0)
+    ASSERT(o->send.data_busy)
+    ASSERT(event == BREACTOR_IOCP_EVENT_SUCCEEDED || event == BREACTOR_IOCP_EVENT_FAILED)
+    
+    // set not busy
+    o->send.data_busy = 0;
+    
+    if (event == BREACTOR_IOCP_EVENT_FAILED) {
+        BLog(BLOG_ERROR, "sending failed");
+        report_error(o);
+        return;
+    }
+    
+    ASSERT(bytes >= 0)
+    ASSERT(bytes <= o->send.data_len)
+    
+    if (bytes < o->send.data_len) {
+        BLog(BLOG_ERROR, "sent too little");
+    }
+    
+    // if recv wasn't started yet, start it
+    if (!o->recv.started) {
+        // set recv started
+        o->recv.started = 1;
+        
+        // continue receiving
+        if (o->recv.inited && o->recv.data_have) {
+            ASSERT(!o->recv.data_busy)
+            
+            BPending_Set(&o->recv.job);
+        }
+    }
+    
+    // set no data
+    o->send.data_len = -1;
+    
+    // done
+    PacketPassInterface_Done(&o->send.iface);
+}
+
+static void recv_olap_handler (BDatagram *o, int event, DWORD bytes)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(o->recv.inited)
+    ASSERT(o->recv.data_have)
+    ASSERT(o->recv.data_busy)
+    ASSERT(event == BREACTOR_IOCP_EVENT_SUCCEEDED || event == BREACTOR_IOCP_EVENT_FAILED)
+    
+    // set not busy
+    o->recv.data_busy = 0;
+    
+    if (event == BREACTOR_IOCP_EVENT_FAILED) {
+        BLog(BLOG_ERROR, "receiving failed");
+        report_error(o);
+        return;
+    }
+    
+    ASSERT(bytes >= 0)
+    ASSERT(bytes <= o->recv.mtu)
+    
+    if (o->fnWSARecvMsg) {
+        o->recv.sysaddr.len = o->recv.msg.namelen;
+    }
+    
+    // read remote address
+    addr_sys_to_socket(&o->recv.remote_addr, o->recv.sysaddr);
+    
+    // read local address
+    BIPAddr_InitInvalid(&o->recv.local_addr);
+    if (o->fnWSARecvMsg) {
+        for (WSACMSGHDR *cmsg = WSA_CMSG_FIRSTHDR(&o->recv.msg); cmsg; cmsg = WSA_CMSG_NXTHDR(&o->recv.msg, cmsg)) {
+            if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
+                struct in_pktinfo *pktinfo = (struct in_pktinfo *)WSA_CMSG_DATA(cmsg);
+                BIPAddr_InitIPv4(&o->recv.local_addr, pktinfo->ipi_addr.s_addr);
+            }
+            else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
+                struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)WSA_CMSG_DATA(cmsg);
+                BIPAddr_InitIPv6(&o->recv.local_addr, pktinfo->ipi6_addr.s6_addr);
+            }
+        }
+    }
+    
+    // set have addresses
+    o->recv.have_addrs = 1;
+    
+    // set no data
+    o->recv.data_have = 0;
+    
+    // done
+    PacketRecvInterface_Done(&o->recv.iface, bytes);
+}
+
+int BDatagram_AddressFamilySupported (int family)
+{
+    return (family == BADDR_TYPE_IPV4 || family == BADDR_TYPE_IPV6);
+}
+
+int BDatagram_Init (BDatagram *o, int family, BReactor *reactor, void *user,
+                    BDatagram_handler handler)
+{
+    ASSERT(BDatagram_AddressFamilySupported(family))
+    ASSERT(handler)
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // init socket
+    if ((o->sock = WSASocket(family_socket_to_sys(family), SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) {
+        BLog(BLOG_ERROR, "WSASocket failed");
+        goto fail0;
+    }
+    
+    DWORD out_bytes;
+    
+    // obtain WSASendMsg
+    GUID guid1 = WSAID_WSASENDMSG;
+    if (WSAIoctl(o->sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid1, sizeof(guid1), &o->fnWSASendMsg, sizeof(o->fnWSASendMsg), &out_bytes, NULL, NULL) != 0) {
+        o->fnWSASendMsg = NULL;
+    }
+    
+    // obtain WSARecvMsg
+    GUID guid2 = WSAID_WSARECVMSG;
+    if (WSAIoctl(o->sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid2, sizeof(guid2), &o->fnWSARecvMsg, sizeof(o->fnWSARecvMsg), &out_bytes, NULL, NULL) != 0) {
+        BLog(BLOG_ERROR, "failed to obtain WSARecvMsg");
+        o->fnWSARecvMsg = NULL;
+    }
+    
+    // associate with IOCP
+    if (!CreateIoCompletionPort((HANDLE)o->sock, BReactor_GetIOCPHandle(o->reactor), 0, 0)) {
+        BLog(BLOG_ERROR, "CreateIoCompletionPort failed");
+        goto fail1;
+    }
+    
+    // enable receiving pktinfo
+    set_pktinfo(o->sock, family);
+    
+    // set not aborted
+    o->aborted = 0;
+    
+    // init send olap
+    BReactorIOCPOverlapped_Init(&o->send.olap, o->reactor, o, (BReactorIOCPOverlapped_handler)send_olap_handler);
+    
+    // set have no send addrs
+    o->send.have_addrs = 0;
+    
+    // set send not inited
+    o->send.inited = 0;
+    
+    // init recv olap
+    BReactorIOCPOverlapped_Init(&o->recv.olap, o->reactor, o, (BReactorIOCPOverlapped_handler)recv_olap_handler);
+    
+    // set recv not started
+    o->recv.started = 0;
+    
+    // set have no recv addrs
+    o->recv.have_addrs = 0;
+    
+    // set recv not inited
+    o->recv.inited = 0;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    if (closesocket(o->sock) == SOCKET_ERROR) {
+        BLog(BLOG_ERROR, "closesocket failed");
+    }
+fail0:
+    return 0;
+}
+
+void BDatagram_Free (BDatagram *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    ASSERT(!o->recv.inited)
+    ASSERT(!o->send.inited)
+    
+    if (!o->aborted) {
+        datagram_abort(o);
+    }
+}
+
+int BDatagram_Bind (BDatagram *o, BAddr addr)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(BDatagram_AddressFamilySupported(addr.type))
+    
+    // translate address
+    struct BDatagram_sys_addr sysaddr;
+    addr_socket_to_sys(&sysaddr, addr);
+    
+    // bind
+    if (bind(o->sock, &sysaddr.addr.generic, sysaddr.len) < 0) {
+        BLog(BLOG_ERROR, "bind failed");
+        return 0;
+    }
+    
+    // if recv wasn't started yet, start it
+    if (!o->recv.started) {
+        // set recv started
+        o->recv.started = 1;
+        
+        // continue receiving
+        if (o->recv.inited && o->recv.data_have) {
+            ASSERT(!o->recv.data_busy)
+            
+            BPending_Set(&o->recv.job);
+        }
+    }
+    
+    return 1;
+}
+
+void BDatagram_SetSendAddrs (BDatagram *o, BAddr remote_addr, BIPAddr local_addr)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(BDatagram_AddressFamilySupported(remote_addr.type))
+    ASSERT(local_addr.type == BADDR_TYPE_NONE || BDatagram_AddressFamilySupported(local_addr.type))
+    
+    // set addresses
+    o->send.remote_addr = remote_addr;
+    o->send.local_addr = local_addr;
+    
+    // set have addresses
+    o->send.have_addrs = 1;
+    
+    // start sending
+    if (o->send.inited && o->send.data_len >= 0 && !o->send.data_busy) {
+        BPending_Set(&o->send.job);
+    }
+}
+
+int BDatagram_GetLastReceiveAddrs (BDatagram *o, BAddr *remote_addr, BIPAddr *local_addr)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    if (!o->recv.have_addrs) {
+        return 0;
+    }
+    
+    *remote_addr = o->recv.remote_addr;
+    *local_addr = o->recv.local_addr;
+    return 1;
+}
+
+int BDatagram_SetReuseAddr (BDatagram *o, int reuse)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(reuse == 0 || reuse == 1)
+    
+    if (setsockopt(o->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0) {
+        return 0;
+    }
+    
+    return 1;
+}
+
+void BDatagram_SendAsync_Init (BDatagram *o, int mtu)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(!o->send.inited)
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    o->send.mtu = mtu;
+    
+    // init interface
+    PacketPassInterface_Init(&o->send.iface, o->send.mtu, (PacketPassInterface_handler_send)send_if_handler_send, o, BReactor_PendingGroup(o->reactor));
+    
+    // init job
+    BPending_Init(&o->send.job, BReactor_PendingGroup(o->reactor), (BPending_handler)send_job_handler, o);
+    
+    // set have no data
+    o->send.data_len = -1;
+    
+    // set inited
+    o->send.inited = 1;
+}
+
+void BDatagram_SendAsync_Free (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->send.inited)
+    
+    // abort if busy
+    if (o->send.data_len >= 0 && o->send.data_busy && !o->aborted) {
+        datagram_abort(o);
+    }
+    
+    // free job
+    BPending_Free(&o->send.job);
+    
+    // free interface
+    PacketPassInterface_Free(&o->send.iface);
+    
+    // set not inited
+    o->send.inited = 0;
+}
+
+PacketPassInterface * BDatagram_SendAsync_GetIf (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->send.inited)
+    
+    return &o->send.iface;
+}
+
+void BDatagram_RecvAsync_Init (BDatagram *o, int mtu)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(!o->aborted)
+    ASSERT(!o->recv.inited)
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    o->recv.mtu = mtu;
+    
+    // init interface
+    PacketRecvInterface_Init(&o->recv.iface, o->recv.mtu, (PacketRecvInterface_handler_recv)recv_if_handler_recv, o, BReactor_PendingGroup(o->reactor));
+    
+    // init job
+    BPending_Init(&o->recv.job, BReactor_PendingGroup(o->reactor), (BPending_handler)recv_job_handler, o);
+    
+    // set have no data
+    o->recv.data_have = 0;
+    
+    // set inited
+    o->recv.inited = 1;
+}
+
+void BDatagram_RecvAsync_Free (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->recv.inited)
+    
+    // abort if busy
+    if (o->recv.data_have && o->recv.data_busy && !o->aborted) {
+        datagram_abort(o);
+    }
+    
+    // free job
+    BPending_Free(&o->recv.job);
+    
+    // free interface
+    PacketRecvInterface_Free(&o->recv.iface);
+    
+    // set not inited
+    o->recv.inited = 0;
+}
+
+PacketRecvInterface * BDatagram_RecvAsync_GetIf (BDatagram *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->recv.inited)
+    
+    return &o->recv.iface;
+}
diff --git a/external/badvpn_dns/system/BDatagram_win.h b/external/badvpn_dns/system/BDatagram_win.h
new file mode 100644
index 0000000..9831946
--- /dev/null
+++ b/external/badvpn_dns/system/BDatagram_win.h
@@ -0,0 +1,99 @@
+/**
+ * @file BDatagram_win.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <windows.h>
+#include <winsock2.h>
+#ifdef BADVPN_USE_SHIPPED_MSWSOCK
+#    include <misc/mswsock.h>
+#else
+#    include <mswsock.h>
+#endif
+
+#include <misc/debugerror.h>
+#include <base/DebugObject.h>
+
+struct BDatagram_sys_addr {
+    int len;
+    union {
+        struct sockaddr generic;
+        struct sockaddr_in ipv4;
+        struct sockaddr_in6 ipv6;
+    } addr;
+};
+
+struct BDatagram_s {
+    BReactor *reactor;
+    void *user;
+    BDatagram_handler handler;
+    SOCKET sock;
+    LPFN_WSASENDMSG fnWSASendMsg;
+    LPFN_WSARECVMSG fnWSARecvMsg;
+    int aborted;
+    struct {
+        BReactorIOCPOverlapped olap;
+        int have_addrs;
+        BAddr remote_addr;
+        BIPAddr local_addr;
+        int inited;
+        int mtu;
+        PacketPassInterface iface;
+        BPending job;
+        int data_len;
+        uint8_t *data;
+        int data_busy;
+        struct BDatagram_sys_addr sysaddr;
+        union {
+            char in[WSA_CMSG_SPACE(sizeof(struct in_pktinfo))];
+            char in6[WSA_CMSG_SPACE(sizeof(struct in6_pktinfo))];
+        } cdata;
+        WSAMSG msg;
+    } send;
+    struct {
+        BReactorIOCPOverlapped olap;
+        int started;
+        int have_addrs;
+        BAddr remote_addr;
+        BIPAddr local_addr;
+        int inited;
+        int mtu;
+        PacketRecvInterface iface;
+        BPending job;
+        int data_have;
+        uint8_t *data;
+        int data_busy;
+        struct BDatagram_sys_addr sysaddr;
+        union {
+            char in[WSA_CMSG_SPACE(sizeof(struct in_pktinfo))];
+            char in6[WSA_CMSG_SPACE(sizeof(struct in6_pktinfo))];
+        } cdata;
+        WSAMSG msg;
+    } recv;
+    DebugError d_err;
+    DebugObject d_obj;
+};
diff --git a/external/badvpn_dns/system/BInputProcess.c b/external/badvpn_dns/system/BInputProcess.c
new file mode 100644
index 0000000..b3d096a
--- /dev/null
+++ b/external/badvpn_dns/system/BInputProcess.c
@@ -0,0 +1,211 @@
+/**
+ * @file BInputProcess.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <unistd.h>
+
+#include <base/BLog.h>
+#include <system/BNetwork.h>
+
+#include "BInputProcess.h"
+
+#include <generated/blog_channel_BInputProcess.h>
+
+static void connection_handler (BInputProcess *o, int event);
+static void process_handler (BInputProcess *o, int normally, uint8_t normally_exit_status);
+
+void connection_handler (BInputProcess *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->pipe_fd >= 0)
+    
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        BLog(BLOG_INFO, "pipe closed");
+    } else {
+        BLog(BLOG_ERROR, "pipe error");
+    }
+    
+    // free pipe connection read interface
+    BConnection_RecvAsync_Free(&o->pipe_con);
+    
+    // free pipe connection
+    BConnection_Free(&o->pipe_con);
+    
+    // close pipe read end
+    ASSERT_FORCE(close(o->pipe_fd) == 0)
+    
+    // forget pipe
+    o->pipe_fd = -1;
+    
+    // call closed handler
+    o->handler_closed(o->user, (event != BCONNECTION_EVENT_RECVCLOSED));
+    return;
+}
+
+void process_handler (BInputProcess *o, int normally, uint8_t normally_exit_status)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->started)
+    ASSERT(o->have_process)
+    
+    // free process
+    BProcess_Free(&o->process);
+    
+    // set not have process
+    o->have_process = 0;
+    
+    // call terminated handler
+    o->handler_terminated(o->user, normally, normally_exit_status);
+    return;
+}
+
+int BInputProcess_Init (BInputProcess *o, BReactor *reactor, BProcessManager *manager, void *user,
+                        BInputProcess_handler_terminated handler_terminated,
+                        BInputProcess_handler_closed handler_closed)
+{
+    BNetwork_Assert();
+    
+    // init arguments
+    o->reactor = reactor;
+    o->manager = manager;
+    o->user = user;
+    o->handler_terminated = handler_terminated;
+    o->handler_closed = handler_closed;
+    
+    // create pipe
+    int pipefds[2];
+    if (pipe(pipefds) < 0) {
+        BLog(BLOG_ERROR, "pipe failed");
+        goto fail0;
+    }
+    
+    // init pipe connection
+    if (!BConnection_Init(&o->pipe_con, BConnection_source_pipe(pipefds[0]), o->reactor, o, (BConnection_handler)connection_handler)) {
+        BLog(BLOG_ERROR, "BConnection_Init failed");
+        goto fail1;
+    }
+    
+    // init pipe connection read interface
+    BConnection_RecvAsync_Init(&o->pipe_con);
+    
+    // remember pipe fds
+    o->pipe_fd = pipefds[0];
+    o->pipe_write_fd = pipefds[1];
+    
+    // set not started
+    o->started = 0;
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    ASSERT_FORCE(close(pipefds[0]) == 0)
+    ASSERT_FORCE(close(pipefds[1]) == 0)
+fail0:
+    return 0;
+}
+
+void BInputProcess_Free (BInputProcess *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    if (!o->started) {
+        // close pipe write end
+        ASSERT_FORCE(close(o->pipe_write_fd) == 0)
+    } else {
+        // free process
+        if (o->have_process) {
+            BProcess_Free(&o->process);
+        }
+    }
+    
+    if (o->pipe_fd >= 0) {
+        // free pipe connection read interface
+        BConnection_RecvAsync_Free(&o->pipe_con);
+        
+        // free pipe connection
+        BConnection_Free(&o->pipe_con);
+        
+        // close pipe read end
+        ASSERT_FORCE(close(o->pipe_fd) == 0)
+    }
+}
+
+int BInputProcess_Start (BInputProcess *o, const char *file, char *const argv[], const char *username)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->started)
+    
+    // start process
+    int fds[] = { o->pipe_write_fd, -1 };
+    int fds_map[] = { 1 };
+    if (!BProcess_InitWithFds(&o->process, o->manager, (BProcess_handler)process_handler, o, file, argv, username, fds, fds_map)) {
+        BLog(BLOG_ERROR, "BProcess_Init failed");
+        goto fail0;
+    }
+    
+    // close pipe write end
+    ASSERT_FORCE(close(o->pipe_write_fd) == 0)
+    
+    // set started
+    o->started = 1;
+    
+    // set have process
+    o->have_process = 1;
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+int BInputProcess_Terminate (BInputProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->started)
+    ASSERT(o->have_process)
+    
+    return BProcess_Terminate(&o->process);
+}
+
+int BInputProcess_Kill (BInputProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->started)
+    ASSERT(o->have_process)
+    
+    return BProcess_Kill(&o->process);
+}
+
+StreamRecvInterface * BInputProcess_GetInput (BInputProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->pipe_fd >= 0)
+    
+    return BConnection_RecvAsync_GetIf(&o->pipe_con);
+}
diff --git a/external/badvpn_dns/system/BInputProcess.h b/external/badvpn_dns/system/BInputProcess.h
new file mode 100644
index 0000000..7317a5d
--- /dev/null
+++ b/external/badvpn_dns/system/BInputProcess.h
@@ -0,0 +1,65 @@
+/**
+ * @file BInputProcess.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_BINPUTPROCESS_H
+#define BADVPN_BINPUTPROCESS_H
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <system/BConnection.h>
+#include <system/BProcess.h>
+
+typedef void (*BInputProcess_handler_terminated) (void *user, int normally, uint8_t normally_exit_status);
+typedef void (*BInputProcess_handler_closed) (void *user, int is_error);
+
+typedef struct {
+    BReactor *reactor;
+    BProcessManager *manager;
+    void *user;
+    BInputProcess_handler_terminated handler_terminated;
+    BInputProcess_handler_closed handler_closed;
+    int pipe_write_fd;
+    int started;
+    int have_process;
+    BProcess process;
+    int pipe_fd;
+    BConnection pipe_con;
+    DebugObject d_obj;
+} BInputProcess;
+
+int BInputProcess_Init (BInputProcess *o, BReactor *reactor, BProcessManager *manager, void *user,
+                        BInputProcess_handler_terminated handler_terminated,
+                        BInputProcess_handler_closed handler_closed) WARN_UNUSED;
+void BInputProcess_Free (BInputProcess *o);
+int BInputProcess_Start (BInputProcess *o, const char *file, char *const argv[], const char *username);
+int BInputProcess_Terminate (BInputProcess *o);
+int BInputProcess_Kill (BInputProcess *o);
+StreamRecvInterface * BInputProcess_GetInput (BInputProcess *o);
+
+#endif
diff --git a/external/badvpn_dns/system/BLockReactor.c b/external/badvpn_dns/system/BLockReactor.c
new file mode 100644
index 0000000..e9a2724
--- /dev/null
+++ b/external/badvpn_dns/system/BLockReactor.c
@@ -0,0 +1,131 @@
+/**
+ * @file BLockReactor.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <base/BLog.h>
+
+#include "BLockReactor.h"
+
+#include <generated/blog_channel_BLockReactor.h>
+
+static void thread_signal_handler (BThreadSignal *thread_signal)
+{
+    BLockReactor *o = UPPER_OBJECT(thread_signal, BLockReactor, thread_signal);
+    DebugObject_Access(&o->d_obj);
+    
+    ASSERT_FORCE(sem_post(&o->sem1) == 0)
+    ASSERT_FORCE(sem_wait(&o->sem2) == 0)
+}
+
+int BLockReactor_Init (BLockReactor *o, BReactor *reactor)
+{
+    o->reactor = reactor;
+    
+    if (!BThreadSignal_Init(&o->thread_signal, reactor, thread_signal_handler)) {
+        BLog(BLOG_ERROR, "BThreadSignal_Init failed");
+        goto fail0;
+    }
+    
+    if (sem_init(&o->sem1, 0, 0) < 0) {
+        BLog(BLOG_ERROR, "sem_init failed");
+        goto fail1;
+    }
+    
+    if (sem_init(&o->sem2, 0, 0) < 0) {
+        BLog(BLOG_ERROR, "sem_init failed");
+        goto fail2;
+    }
+    
+#ifndef NDEBUG
+    o->locked = 0;
+#endif
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail2:
+    if (sem_close(&o->sem1) < 0) {
+        BLog(BLOG_ERROR, "sem_close failed");
+    }
+fail1:
+    BThreadSignal_Free(&o->thread_signal);
+fail0:
+    return 0;
+}
+
+void BLockReactor_Free (BLockReactor *o)
+{
+    DebugObject_Free(&o->d_obj);
+#ifndef NDEBUG
+    ASSERT(!o->locked)
+#endif
+    
+    if (sem_destroy(&o->sem2) < 0) {
+        BLog(BLOG_ERROR, "sem_close failed");
+    }
+    if (sem_destroy(&o->sem1) < 0) {
+        BLog(BLOG_ERROR, "sem_close failed");
+    }
+    BThreadSignal_Free(&o->thread_signal);
+}
+
+int BLockReactor_Thread_Lock (BLockReactor *o)
+{
+    DebugObject_Access(&o->d_obj);
+#ifndef NDEBUG
+    ASSERT(!o->locked)
+#endif
+    
+    if (!BThreadSignal_Thread_Signal(&o->thread_signal)) {
+        return 0;
+    }
+    
+    ASSERT_FORCE(sem_wait(&o->sem1) == 0)
+    
+#ifndef NDEBUG
+    o->locked = 1;
+#endif
+    
+    return 1;
+}
+
+void BLockReactor_Thread_Unlock (BLockReactor *o)
+{
+    DebugObject_Access(&o->d_obj);
+#ifndef NDEBUG
+    ASSERT(o->locked)
+#endif
+    
+#ifndef NDEBUG
+    o->locked = 0;
+#endif
+    
+    ASSERT_FORCE(sem_post(&o->sem2) == 0)
+}
diff --git a/external/badvpn_dns/system/BLockReactor.h b/external/badvpn_dns/system/BLockReactor.h
new file mode 100644
index 0000000..84acab8
--- /dev/null
+++ b/external/badvpn_dns/system/BLockReactor.h
@@ -0,0 +1,58 @@
+/**
+ * @file BLockReactor.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_B_LOCK_REACTOR_H
+#define BADVPN_B_LOCK_REACTOR_H
+
+#include <semaphore.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+#include <system/BThreadSignal.h>
+
+typedef struct BLockReactor_s BLockReactor;
+
+struct BLockReactor_s {
+    BReactor *reactor;
+    BThreadSignal thread_signal;
+    sem_t sem1;
+    sem_t sem2;
+#ifndef NDEBUG
+    int locked;
+#endif
+    DebugObject d_obj;
+};
+
+int BLockReactor_Init (BLockReactor *o, BReactor *reactor) WARN_UNUSED;
+void BLockReactor_Free (BLockReactor *o);
+int BLockReactor_Thread_Lock (BLockReactor *o);
+void BLockReactor_Thread_Unlock (BLockReactor *o);
+
+#endif
diff --git a/external/badvpn_dns/system/BNetwork.c b/external/badvpn_dns/system/BNetwork.c
new file mode 100644
index 0000000..b48ae61
--- /dev/null
+++ b/external/badvpn_dns/system/BNetwork.c
@@ -0,0 +1,99 @@
+/**
+ * @file BNetwork.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+#ifdef BADVPN_USE_WINAPI
+#include <windows.h>
+#include <winsock2.h>
+#include <mswsock.h>
+#else
+#include <string.h>
+#include <signal.h>
+#endif
+
+#include <misc/debug.h>
+#include <base/BLog.h>
+
+#include <system/BNetwork.h>
+
+#include <generated/blog_channel_BNetwork.h>
+
+extern int bnetwork_initialized;
+
+#ifndef BADVPN_PLUGIN
+int bnetwork_initialized = 0;
+#endif
+
+int BNetwork_GlobalInit (void)
+{
+    ASSERT(!bnetwork_initialized)
+    
+#ifdef BADVPN_USE_WINAPI
+    
+    WORD requested = MAKEWORD(2, 2);
+    WSADATA wsadata;
+    if (WSAStartup(requested, &wsadata) != 0) {
+        BLog(BLOG_ERROR, "WSAStartup failed");
+        goto fail0;
+    }
+    if (wsadata.wVersion != requested) {
+        BLog(BLOG_ERROR, "WSAStartup returned wrong version");
+        goto fail1;
+    }
+    
+#else
+    
+    struct sigaction act;
+    memset(&act, 0, sizeof(act));
+    act.sa_handler = SIG_IGN;
+    sigemptyset(&act.sa_mask);
+    act.sa_flags = 0;
+    if (sigaction(SIGPIPE, &act, NULL) < 0) {
+        BLog(BLOG_ERROR, "sigaction failed");
+        goto fail0;
+    }
+    
+#endif
+    
+    bnetwork_initialized = 1;
+    
+    return 1;
+    
+#ifdef BADVPN_USE_WINAPI
+fail1:
+    WSACleanup();
+#endif
+    
+fail0:
+    return 0;
+}
+
+void BNetwork_Assert (void)
+{
+    ASSERT(bnetwork_initialized)
+}
diff --git a/external/badvpn_dns/system/BNetwork.h b/external/badvpn_dns/system/BNetwork.h
new file mode 100644
index 0000000..0db1744
--- /dev/null
+++ b/external/badvpn_dns/system/BNetwork.h
@@ -0,0 +1,36 @@
+/**
+ * @file BNetwork.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_SYSTEM_BNETWORK_H
+#define BADVPN_SYSTEM_BNETWORK_H
+
+int BNetwork_GlobalInit (void);
+void BNetwork_Assert (void);
+
+#endif
diff --git a/external/badvpn_dns/system/BProcess.c b/external/badvpn_dns/system/BProcess.c
new file mode 100644
index 0000000..7efea25
--- /dev/null
+++ b/external/badvpn_dns/system/BProcess.c
@@ -0,0 +1,400 @@
+/**
+ * @file BProcess.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <inttypes.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <grp.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <misc/offset.h>
+#include <misc/open_standard_streams.h>
+#include <base/BLog.h>
+
+#include "BProcess.h"
+
+#include <generated/blog_channel_BProcess.h>
+
+static void call_handler (BProcess *o, int normally, uint8_t normally_exit_status)
+{
+    DEBUGERROR(&o->d_err, o->handler(o->user, normally, normally_exit_status))
+}
+
+static BProcess * find_process (BProcessManager *o, pid_t pid)
+{
+    for (LinkedList1Node *node = LinkedList1_GetFirst(&o->processes); node; node = LinkedList1Node_Next(node)) {
+        BProcess *p = UPPER_OBJECT(node, BProcess, list_node);
+        if (p->pid == pid) {
+            return p;
+        }
+    }
+    
+    return NULL;
+}
+
+static void work_signals (BProcessManager *o)
+{
+    // read exit status with waitpid()
+    int status;
+    pid_t pid = waitpid(-1, &status, WNOHANG);
+    if (pid <= 0) {
+        return;
+    }
+    
+    // schedule next waitpid
+    BPending_Set(&o->wait_job);
+    
+    // find process
+    BProcess *p = find_process(o, pid);
+    if (!p) {
+        BLog(BLOG_DEBUG, "unknown child %p");
+    }
+    
+    if (WIFEXITED(status)) {
+        uint8_t exit_status = WEXITSTATUS(status);
+        
+        BLog(BLOG_INFO, "child %"PRIiMAX" exited with status %"PRIu8, (intmax_t)pid, exit_status);
+        
+        if (p) {
+            call_handler(p, 1, exit_status);
+            return;
+        }
+    }
+    else if (WIFSIGNALED(status)) {
+        int signo = WTERMSIG(status);
+        
+        BLog(BLOG_INFO, "child %"PRIiMAX" exited with signal %d", (intmax_t)pid, signo);
+        
+        if (p) {
+            call_handler(p, 0, 0);
+            return;
+        }
+    }
+    else {
+        BLog(BLOG_ERROR, "unknown wait status type for pid %"PRIiMAX" (%d)", (intmax_t)pid, status);
+    }
+}
+
+static void wait_job_handler (BProcessManager *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    work_signals(o);
+    return;
+}
+
+static void signal_handler (BProcessManager *o, int signo)
+{
+    ASSERT(signo == SIGCHLD)
+    DebugObject_Access(&o->d_obj);
+    
+    work_signals(o);
+    return;
+}
+
+int BProcessManager_Init (BProcessManager *o, BReactor *reactor)
+{
+    // init arguments
+    o->reactor = reactor;
+    
+    // init signal handling
+    sigset_t sset;
+    ASSERT_FORCE(sigemptyset(&sset) == 0)
+    ASSERT_FORCE(sigaddset(&sset, SIGCHLD) == 0)
+    if (!BUnixSignal_Init(&o->signal, o->reactor, sset, (BUnixSignal_handler)signal_handler, o)) {
+        BLog(BLOG_ERROR, "BUnixSignal_Init failed");
+        goto fail0;
+    }
+    
+    // init processes list
+    LinkedList1_Init(&o->processes);
+    
+    // init wait job
+    BPending_Init(&o->wait_job, BReactor_PendingGroup(o->reactor), (BPending_handler)wait_job_handler, o);
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void BProcessManager_Free (BProcessManager *o)
+{
+    ASSERT(LinkedList1_IsEmpty(&o->processes))
+    DebugObject_Free(&o->d_obj);
+    
+    // free wait job
+    BPending_Free(&o->wait_job);
+    
+    // free signal handling
+    BUnixSignal_Free(&o->signal, 1);
+}
+
+static int fds_contains (const int *fds, int fd, size_t *pos)
+{
+    for (size_t i = 0; fds[i] >= 0; i++) {
+        if (fds[i] == fd) {
+            if (pos) {
+                *pos = i;
+            }
+            
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+int BProcess_Init2 (BProcess *o, BProcessManager *m, BProcess_handler handler, void *user, const char *file, char *const argv[], struct BProcess_params params)
+{
+    // init arguments
+    o->m = m;
+    o->handler = handler;
+    o->user = user;
+    
+    // count fds
+    size_t num_fds;
+    for (num_fds = 0; params.fds[num_fds] >= 0; num_fds++);
+    
+    // block signals
+    // needed to prevent parent's signal handlers from being called
+    // in the child
+    sigset_t sset_all;
+    sigfillset(&sset_all);
+    sigset_t sset_old;
+    if (sigprocmask(SIG_SETMASK, &sset_all, &sset_old) < 0) {
+        BLog(BLOG_ERROR, "sigprocmask failed");
+        goto fail0;
+    }
+    
+    // fork
+    pid_t pid = fork();
+    
+    if (pid == 0) {
+        // this is child
+        
+        // restore signal dispositions
+        for (int i = 1; i < NSIG; i++) {
+            struct sigaction sa;
+            memset(&sa, 0, sizeof(sa));
+            sa.sa_handler = SIG_DFL;
+            sa.sa_flags = 0;
+            sigaction(i, &sa, NULL);
+        }
+        
+        // unblock signals
+        sigset_t sset_none;
+        sigemptyset(&sset_none);
+        if (sigprocmask(SIG_SETMASK, &sset_none, NULL) < 0) {
+            abort();
+        }
+        
+        // copy fds array
+        int *fds2 = malloc((num_fds + 1) * sizeof(fds2[0]));
+        if (!fds2) {
+            abort();
+        }
+        memcpy(fds2, params.fds, (num_fds + 1) * sizeof(fds2[0]));
+        
+        // find maximum file descriptors
+        int max_fd = sysconf(_SC_OPEN_MAX);
+        if (max_fd < 0) {
+            abort();
+        }
+        
+        // close file descriptors
+        for (int i = 0; i < max_fd; i++) {
+            // if it's one of the given fds, don't close it
+            if (fds_contains(fds2, i, NULL)) {
+                continue;
+            }
+            
+            close(i);
+        }
+        
+        // map fds to requested fd numbers
+        while (*fds2 >= 0) {
+            // resolve possible conflict
+            size_t cpos;
+            if (fds_contains(fds2 + 1, *params.fds_map, &cpos)) {
+                // dup() the fd to a new number; the old one will be closed
+                // in the following dup2()
+                if ((fds2[1 + cpos] = dup(fds2[1 + cpos])) < 0) {
+                    abort();
+                }
+            }
+            
+            if (*fds2 != *params.fds_map) {
+                // dup fd
+                if (dup2(*fds2, *params.fds_map) < 0) {
+                    abort();
+                }
+                
+                // close original fd
+                close(*fds2);
+            }
+            
+            fds2++;
+            params.fds_map++;
+        }
+        
+        // make sure standard streams are open
+        open_standard_streams();
+        
+        // make session leader if requested
+        if (params.do_setsid) {
+            setsid();
+        }
+        
+        // assume identity of username, if requested
+        if (params.username) {
+            long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+            if (bufsize < 0) {
+                bufsize = 16384;
+            }
+            
+            char *buf = malloc(bufsize);
+            if (!buf) {
+                abort();
+            }
+            
+            struct passwd pwd;
+            struct passwd *res;
+            getpwnam_r(params.username, &pwd, buf, bufsize, &res);
+            if (!res) {
+                abort();
+            }
+            
+            if (initgroups(params.username, pwd.pw_gid) < 0) {
+                abort();
+            }
+            
+            if (setgid(pwd.pw_gid) < 0) {
+                abort();
+            }
+            
+            if (setuid(pwd.pw_uid) < 0) {
+                abort();
+            }
+        }
+        
+        execv(file, argv);
+        
+        abort();
+    }
+    
+    // restore original signal mask
+    ASSERT_FORCE(sigprocmask(SIG_SETMASK, &sset_old, NULL) == 0)
+    
+    if (pid < 0) {
+        BLog(BLOG_ERROR, "fork failed");
+        goto fail0;
+    }
+    
+    // remember pid
+    o->pid = pid;
+    
+    // add to processes list
+    LinkedList1_Append(&o->m->processes, &o->list_node);
+    
+    DebugObject_Init(&o->d_obj);
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(m->reactor));
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+int BProcess_InitWithFds (BProcess *o, BProcessManager *m, BProcess_handler handler, void *user, const char *file, char *const argv[], const char *username, const int *fds, const int *fds_map)
+{
+    struct BProcess_params params;
+    params.username = username;
+    params.fds = fds;
+    params.fds_map = fds_map;
+    params.do_setsid = 0;
+    
+    return BProcess_Init2(o, m, handler, user, file, argv, params);
+}
+
+int BProcess_Init (BProcess *o, BProcessManager *m, BProcess_handler handler, void *user, const char *file, char *const argv[], const char *username)
+{
+    int fds[] = {-1};
+    
+    return BProcess_InitWithFds(o, m, handler, user, file, argv, username, fds, NULL);
+}
+
+void BProcess_Free (BProcess *o)
+{
+    DebugError_Free(&o->d_err);
+    DebugObject_Free(&o->d_obj);
+    
+    // remove from processes list
+    LinkedList1_Remove(&o->m->processes, &o->list_node);
+}
+
+int BProcess_Terminate (BProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    
+    ASSERT(o->pid > 0)
+    
+    if (kill(o->pid, SIGTERM) < 0) {
+        BLog(BLOG_ERROR, "kill(%"PRIiMAX", SIGTERM) failed", (intmax_t)o->pid);
+        return 0;
+    }
+    
+    return 1;
+}
+
+int BProcess_Kill (BProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    
+    ASSERT(o->pid > 0)
+    
+    if (kill(o->pid, SIGKILL) < 0) {
+        BLog(BLOG_ERROR, "kill(%"PRIiMAX", SIGKILL) failed", (intmax_t)o->pid);
+        return 0;
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/system/BProcess.h b/external/badvpn_dns/system/BProcess.h
new file mode 100644
index 0000000..35993fe
--- /dev/null
+++ b/external/badvpn_dns/system/BProcess.h
@@ -0,0 +1,200 @@
+/**
+ * @file BProcess.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_BPROCESS_H
+#define BADVPN_BPROCESS_H
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <system/BUnixSignal.h>
+#include <base/BPending.h>
+
+/**
+ * Manages child processes.
+ * There may be at most one process manager at any given time. This restriction is not
+ * enforced, however.
+ */
+typedef struct {
+    BReactor *reactor;
+    BUnixSignal signal;
+    LinkedList1 processes;
+    BPending wait_job;
+    DebugObject d_obj;
+} BProcessManager;
+
+/**
+ * Handler called when the process terminates.
+ * The process object must be freed from the job context of this handler.
+ * {@link BProcess_Terminate} or {@link BProcess_Kill} must not be called
+ * after this handler is called.
+ * 
+ * @param user as in {@link BProcess_InitWithFds} or {@link BProcess_Init}
+ * @param normally whether the child process terminated normally (0 or 1)
+ * @param normally_exit_status if the child process terminated normally, its exit
+ *                             status; otherwise undefined
+ */
+typedef void (*BProcess_handler) (void *user, int normally, uint8_t normally_exit_status);
+
+/**
+ * Represents a child process.
+ */
+typedef struct {
+    BProcessManager *m;
+    BProcess_handler handler;
+    void *user;
+    pid_t pid;
+    LinkedList1Node list_node; // node in BProcessManager.processes
+    DebugObject d_obj;
+    DebugError d_err;
+} BProcess;
+
+/**
+ * Initializes the process manager.
+ * There may be at most one process manager at any given time. This restriction is not
+ * enforced, however.
+ * 
+ * @param o the object
+ * @param reactor reactor we live in
+ * @return 1 on success, 0 on failure
+ */
+int BProcessManager_Init (BProcessManager *o, BReactor *reactor) WARN_UNUSED;
+
+/**
+ * Frees the process manager.
+ * There must be no {@link BProcess} objects using this process manager.
+ * 
+ * @param o the object
+ */
+void BProcessManager_Free (BProcessManager *o);
+
+struct BProcess_params {
+    const char *username;
+    const int *fds;
+    const int *fds_map;
+    int do_setsid;
+};
+
+/**
+ * Initializes the process.
+ * 'file', 'argv', 'username', 'fds' and 'fds_map' arguments are only used during this
+ * function call.
+ * If no file descriptor is mapped to a standard stream (file descriptors 0, 1, 2),
+ * then /dev/null will be opened in the child for that standard stream.
+ * 
+ * @param o the object
+ * @param m process manager
+ * @param handler handler called when the process terminates
+ * @param user argument to handler
+ * @param file path to executable file
+ * @param argv arguments array, including the zeroth argument, terminated with a NULL pointer
+ * @param params.username user account to run the program as, or NULL to not switch user
+ * @param params.fds array of file descriptors in the parent to map to file descriptors in the child,
+ *            terminated with -1
+ * @param params.fds_map array of file descriptors in the child that file descriptors in 'fds' will
+ *                be mapped to, in the same order. Must contain the same number of file descriptors
+ *                as the 'fds' argument, and does not have to be terminated with -1.
+ * @param params.do_setsid if set to non-zero, will make the child call setsid() before exec'ing.
+ *                         Failure of setsid() will be ignored.
+ * @return 1 on success, 0 on failure
+ */
+int BProcess_Init2 (BProcess *o, BProcessManager *m, BProcess_handler handler, void *user, const char *file, char *const argv[], struct BProcess_params params) WARN_UNUSED;
+
+/**
+ * Initializes the process.
+ * 'file', 'argv', 'username', 'fds' and 'fds_map' arguments are only used during this
+ * function call.
+ * If no file descriptor is mapped to a standard stream (file descriptors 0, 1, 2),
+ * then /dev/null will be opened in the child for that standard stream.
+ * 
+ * @param o the object
+ * @param m process manager
+ * @param handler handler called when the process terminates
+ * @param user argument to handler
+ * @param file path to executable file
+ * @param argv arguments array, including the zeroth argument, terminated with a NULL pointer
+ * @param username user account to run the program as, or NULL to not switch user
+ * @param fds array of file descriptors in the parent to map to file descriptors in the child,
+ *            terminated with -1
+ * @param fds_map array of file descriptors in the child that file descriptors in 'fds' will
+ *                be mapped to, in the same order. Must contain the same number of file descriptors
+ *                as the 'fds' argument, and does not have to be terminated with -1.
+ * @return 1 on success, 0 on failure
+ */
+int BProcess_InitWithFds (BProcess *o, BProcessManager *m, BProcess_handler handler, void *user, const char *file, char *const argv[], const char *username, const int *fds, const int *fds_map) WARN_UNUSED;
+
+/**
+ * Initializes the process.
+ * Like {@link BProcess_InitWithFds}, but without file descriptor mapping.
+ * 'file', 'argv' and 'username' arguments are only used during this function call.
+ * 
+ * @param o the object
+ * @param m process manager
+ * @param handler handler called when the process terminates
+ * @param user argument to handler
+ * @param file path to executable file
+ * @param argv arguments array, including the zeroth argument, terminated with a NULL pointer
+ * @param username user account to run the program as, or NULL to not switch user
+ * @return 1 on success, 0 on failure
+ */
+int BProcess_Init (BProcess *o, BProcessManager *m, BProcess_handler handler, void *user, const char *file, char *const argv[], const char *username) WARN_UNUSED;
+
+/**
+ * Frees the process.
+ * This does not do anything with the actual child process; it only prevents the user to wait
+ * for its termination. If the process terminates while a process manager is running, it will still
+ * be waited for (and will not become a zombie).
+ * 
+ * @param o the object
+ */
+void BProcess_Free (BProcess *o);
+
+/**
+ * Sends the process the SIGTERM signal.
+ * Success of this action does NOT mean that the child has terminated.
+ * 
+ * @param o the object
+ * @return 1 on success, 0 on failure
+ */
+int BProcess_Terminate (BProcess *o);
+
+/**
+ * Sends the process the SIGKILL signal.
+ * Success of this action does NOT mean that the child has terminated.
+ * 
+ * @param o the object
+ * @return 1 on success, 0 on failure
+ */
+int BProcess_Kill (BProcess *o);
+
+#endif
diff --git a/external/badvpn_dns/system/BReactor.h b/external/badvpn_dns/system/BReactor.h
new file mode 100644
index 0000000..4c0fa5b
--- /dev/null
+++ b/external/badvpn_dns/system/BReactor.h
@@ -0,0 +1,11 @@
+#if defined(BADVPN_BREACTOR_BADVPN) + defined(BADVPN_BREACTOR_GLIB) + defined(BADVPN_BREACTOR_EMSCRIPTEN) != 1
+#error No reactor backend or too many reactor backens
+#endif
+
+#if defined(BADVPN_BREACTOR_BADVPN)
+#include "BReactor_badvpn.h"
+#elif defined(BADVPN_BREACTOR_GLIB)
+#include "BReactor_glib.h"
+#elif defined(BADVPN_BREACTOR_EMSCRIPTEN)
+#include "BReactor_emscripten.h"
+#endif
diff --git a/external/badvpn_dns/system/BReactor_badvpn.c b/external/badvpn_dns/system/BReactor_badvpn.c
new file mode 100644
index 0000000..1b03d58
--- /dev/null
+++ b/external/badvpn_dns/system/BReactor_badvpn.c
@@ -0,0 +1,1430 @@
+/**
+ * @file BReactor_badvpn.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stddef.h>
+
+#ifdef BADVPN_USE_WINAPI
+#include <windows.h>
+#else
+#include <limits.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#endif
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/balloc.h>
+#include <misc/compare.h>
+#include <base/BLog.h>
+
+#include <system/BReactor.h>
+
+#include <generated/blog_channel_BReactor.h>
+
+#define KEVENT_TAG_FD 1
+#define KEVENT_TAG_KEVENT 2
+
+#define TIMER_STATE_INACTIVE 1
+#define TIMER_STATE_RUNNING 2
+#define TIMER_STATE_EXPIRED 3
+
+static int compare_timers (BSmallTimer *t1, BSmallTimer *t2)
+{
+    int cmp = B_COMPARE(t1->absTime, t2->absTime);
+    if (cmp) {
+        return cmp;
+    }
+    
+    return B_COMPARE((uintptr_t)t1, (uintptr_t)t2);
+}
+
+#include "BReactor_badvpn_timerstree.h"
+#include <structure/CAvl_impl.h>
+
+static void assert_timer (BSmallTimer *bt)
+{
+    ASSERT(bt->state == TIMER_STATE_INACTIVE || bt->state == TIMER_STATE_RUNNING ||
+           bt->state == TIMER_STATE_EXPIRED)
+}
+
+static int move_expired_timers (BReactor *bsys, btime_t now)
+{
+    int moved = 0;
+    
+    // move timed out timers to the expired list
+    BReactor__TimersTreeRef ref;
+    BSmallTimer *timer;
+    while (timer = (ref = BReactor__TimersTree_GetFirst(&bsys->timers_tree, 0)).link) {
+        ASSERT(timer->state == TIMER_STATE_RUNNING)
+        
+        // if it's in the future, stop
+        if (timer->absTime > now) {
+            break;
+        }
+        moved = 1;
+        
+        // remove from running timers tree
+        BReactor__TimersTree_Remove(&bsys->timers_tree, 0, ref);
+        
+        // add to expired timers list
+        LinkedList1_Append(&bsys->timers_expired_list, &timer->u.list_node);
+
+        // set expired
+        timer->state = TIMER_STATE_EXPIRED;
+    }
+
+    return moved;
+}
+
+static void move_first_timers (BReactor *bsys)
+{
+    BReactor__TimersTreeRef ref;
+    
+    // get the time of the first timer
+    BSmallTimer *first_timer = (ref = BReactor__TimersTree_GetFirst(&bsys->timers_tree, 0)).link;
+    ASSERT(first_timer)
+    ASSERT(first_timer->state == TIMER_STATE_RUNNING)
+    btime_t first_time = first_timer->absTime;
+    
+    // remove from running timers tree
+    BReactor__TimersTree_Remove(&bsys->timers_tree, 0, ref);
+    
+    // add to expired timers list
+    LinkedList1_Append(&bsys->timers_expired_list, &first_timer->u.list_node);
+    
+    // set expired
+    first_timer->state = TIMER_STATE_EXPIRED;
+    
+    // also move other timers with the same timeout
+    BSmallTimer *timer;
+    while (timer = (ref = BReactor__TimersTree_GetFirst(&bsys->timers_tree, 0)).link) {
+        ASSERT(timer->state == TIMER_STATE_RUNNING)
+        ASSERT(timer->absTime >= first_time)
+        
+        // if it's in the future, stop
+        if (timer->absTime > first_time) {
+            break;
+        }
+        
+        // remove from running timers tree
+        BReactor__TimersTree_Remove(&bsys->timers_tree, 0, ref);
+        
+        // add to expired timers list
+        LinkedList1_Append(&bsys->timers_expired_list, &timer->u.list_node);
+        
+        // set expired
+        timer->state = TIMER_STATE_EXPIRED;
+    }
+}
+
+#ifdef BADVPN_USE_WINAPI
+
+static void set_iocp_ready (BReactorIOCPOverlapped *olap, int succeeded, DWORD bytes)
+{
+    BReactor *reactor = olap->reactor;
+    ASSERT(!olap->is_ready)
+    
+    // set parameters
+    olap->ready_succeeded = succeeded;
+    olap->ready_bytes = bytes;
+    
+    // insert to IOCP ready list
+    LinkedList1_Append(&reactor->iocp_ready_list, &olap->ready_list_node);
+    
+    // set ready
+    olap->is_ready = 1;
+}
+
+#endif
+
+#ifdef BADVPN_USE_EPOLL
+
+static void set_epoll_fd_pointers (BReactor *bsys)
+{
+    // Write pointers to our entry pointers into file descriptors.
+    // If a handler function frees some other file descriptor, the
+    // free routine will set our pointer to NULL so we don't dispatch it.
+    for (int i = 0; i < bsys->epoll_results_num; i++) {
+        struct epoll_event *event = &bsys->epoll_results[i];
+        ASSERT(event->data.ptr)
+        BFileDescriptor *bfd = (BFileDescriptor *)event->data.ptr;
+        ASSERT(bfd->active)
+        ASSERT(!bfd->epoll_returned_ptr)
+        bfd->epoll_returned_ptr = (BFileDescriptor **)&event->data.ptr;
+    }
+}
+
+#endif
+
+#ifdef BADVPN_USE_KEVENT
+
+static void set_kevent_fd_pointers (BReactor *bsys)
+{
+    for (int i = 0; i < bsys->kevent_results_num; i++) {
+        struct kevent *event = &bsys->kevent_results[i];
+        ASSERT(event->udata)
+        int *tag = event->udata;
+        switch (*tag) {
+            case KEVENT_TAG_FD: {
+                BFileDescriptor *bfd = UPPER_OBJECT(tag, BFileDescriptor, kevent_tag);
+                ASSERT(bfd->active)
+                ASSERT(!bfd->kevent_returned_ptr)
+                bfd->kevent_returned_ptr = (int **)&event->udata;
+            } break;
+            
+            case KEVENT_TAG_KEVENT: {
+                BReactorKEvent *kev = UPPER_OBJECT(tag, BReactorKEvent, kevent_tag);
+                ASSERT(kev->reactor == bsys)
+                ASSERT(!kev->kevent_returned_ptr)
+                kev->kevent_returned_ptr = (int **)&event->udata;
+            } break;
+            
+            default:
+                ASSERT(0);
+        }
+    }
+}
+
+static void update_kevent_fd_events (BReactor *bsys, BFileDescriptor *bs, int events)
+{
+    struct kevent event;
+    
+    if (!(bs->waitEvents & BREACTOR_READ) && (events & BREACTOR_READ)) {
+        memset(&event, 0, sizeof(event));
+        event.ident = bs->fd;
+        event.filter = EVFILT_READ;
+        event.flags = EV_ADD;
+        event.udata = &bs->kevent_tag;
+        ASSERT_FORCE(kevent(bsys->kqueue_fd, &event, 1, NULL, 0, NULL) == 0)
+    }
+    else if ((bs->waitEvents & BREACTOR_READ) && !(events & BREACTOR_READ)) {
+        memset(&event, 0, sizeof(event));
+        event.ident = bs->fd;
+        event.filter = EVFILT_READ;
+        event.flags = EV_DELETE;
+        ASSERT_FORCE(kevent(bsys->kqueue_fd, &event, 1, NULL, 0, NULL) == 0)
+    }
+    
+    if (!(bs->waitEvents & BREACTOR_WRITE) && (events & BREACTOR_WRITE)) {
+        memset(&event, 0, sizeof(event));
+        event.ident = bs->fd;
+        event.filter = EVFILT_WRITE;
+        event.flags = EV_ADD;
+        event.udata = &bs->kevent_tag;
+        ASSERT_FORCE(kevent(bsys->kqueue_fd, &event, 1, NULL, 0, NULL) == 0)
+    }
+    else if ((bs->waitEvents & BREACTOR_WRITE) && !(events & BREACTOR_WRITE)) {
+        memset(&event, 0, sizeof(event));
+        event.ident = bs->fd;
+        event.filter = EVFILT_WRITE;
+        event.flags = EV_DELETE;
+        ASSERT_FORCE(kevent(bsys->kqueue_fd, &event, 1, NULL, 0, NULL) == 0)
+    }
+}
+
+#endif
+
+#ifdef BADVPN_USE_POLL
+
+static void set_poll_fd_pointers (BReactor *bsys)
+{
+    for (int i = 0; i < bsys->poll_results_num; i++) {
+        BFileDescriptor *bfd = bsys->poll_results_bfds[i];
+        ASSERT(bfd)
+        ASSERT(bfd->active)
+        ASSERT(bfd->poll_returned_index == -1)
+        bfd->poll_returned_index = i;
+    }
+}
+
+#endif
+
+static void wait_for_events (BReactor *bsys)
+{
+    // must have processed all pending events
+    ASSERT(!BPendingGroup_HasJobs(&bsys->pending_jobs))
+    ASSERT(LinkedList1_IsEmpty(&bsys->timers_expired_list))
+    #ifdef BADVPN_USE_WINAPI
+    ASSERT(LinkedList1_IsEmpty(&bsys->iocp_ready_list))
+    #endif
+    #ifdef BADVPN_USE_EPOLL
+    ASSERT(bsys->epoll_results_pos == bsys->epoll_results_num)
+    #endif
+    #ifdef BADVPN_USE_KEVENT
+    ASSERT(bsys->kevent_results_pos == bsys->kevent_results_num)
+    #endif
+    #ifdef BADVPN_USE_POLL
+    ASSERT(bsys->poll_results_pos == bsys->poll_results_num)
+    #endif
+
+    // clean up epoll results
+    #ifdef BADVPN_USE_EPOLL
+    bsys->epoll_results_num = 0;
+    bsys->epoll_results_pos = 0;
+    #endif
+    
+    // clean up kevent results
+    #ifdef BADVPN_USE_KEVENT
+    bsys->kevent_results_num = 0;
+    bsys->kevent_results_pos = 0;
+    #endif
+    
+    // clean up poll results
+    #ifdef BADVPN_USE_POLL
+    bsys->poll_results_num = 0;
+    bsys->poll_results_pos = 0;
+    #endif
+    
+    // timeout vars
+    int have_timeout = 0;
+    btime_t timeout_abs;
+    btime_t now = 0; // to remove warning
+    
+    // compute timeout
+    BSmallTimer *first_timer = BReactor__TimersTree_GetFirst(&bsys->timers_tree, 0).link;
+    if (first_timer) {
+        ASSERT(first_timer->state == TIMER_STATE_RUNNING)
+        
+        // get current time
+        now = btime_gettime();
+        
+        // if some timers have already timed out, return them immediately
+        if (move_expired_timers(bsys, now)) {
+            BLog(BLOG_DEBUG, "Got already expired timers");
+            return;
+        }
+        
+        // timeout is first timer, remember absolute time
+        have_timeout = 1;
+        timeout_abs = first_timer->absTime;
+    }
+    
+    // wait until the timeout is reached or the file descriptor / handle in ready
+    while (1) {
+        // compute timeout
+        btime_t timeout_rel = 0; // to remove warning
+        btime_t timeout_rel_trunc = 0; // to remove warning
+        if (have_timeout) {
+            timeout_rel = timeout_abs - now;
+            timeout_rel_trunc = timeout_rel;
+        }
+        
+        // perform wait
+        
+        #ifdef BADVPN_USE_WINAPI
+        
+        if (have_timeout) {
+            if (timeout_rel_trunc > INFINITE - 1) {
+                timeout_rel_trunc = INFINITE - 1;
+            }
+        }
+        
+        DWORD bytes = 0;
+        ULONG_PTR key;
+        BReactorIOCPOverlapped *olap = NULL;
+        BOOL res = GetQueuedCompletionStatus(bsys->iocp_handle, &bytes, &key, (OVERLAPPED **)&olap, (have_timeout ? timeout_rel_trunc : INFINITE));
+        
+        ASSERT_FORCE(olap || have_timeout)
+        
+        if (olap || timeout_rel_trunc == timeout_rel) {
+            if (olap) {
+                BLog(BLOG_DEBUG, "GetQueuedCompletionStatus returned event");
+                
+                DebugObject_Access(&olap->d_obj);
+                ASSERT(olap->reactor == bsys)
+                ASSERT(!olap->is_ready)
+                
+                set_iocp_ready(olap, (res == TRUE), bytes);
+            } else {
+                BLog(BLOG_DEBUG, "GetQueuedCompletionStatus timed out");
+                move_first_timers(bsys);
+            }
+            break;
+        }
+        
+        #endif
+        
+        #ifdef BADVPN_USE_EPOLL
+        
+        if (have_timeout) {
+            if (timeout_rel_trunc > INT_MAX) {
+                timeout_rel_trunc = INT_MAX;
+            }
+        }
+        
+        BLog(BLOG_DEBUG, "Calling epoll_wait");
+        
+        int waitres = epoll_wait(bsys->efd, bsys->epoll_results, BSYSTEM_MAX_RESULTS, (have_timeout ? timeout_rel_trunc : -1));
+        if (waitres < 0) {
+            int error = errno;
+            if (error == EINTR) {
+                BLog(BLOG_DEBUG, "epoll_wait interrupted");
+                goto try_again;
+            }
+            perror("epoll_wait");
+            ASSERT_FORCE(0)
+        }
+        
+        ASSERT_FORCE(!(waitres == 0) || have_timeout)
+        ASSERT_FORCE(waitres <= BSYSTEM_MAX_RESULTS)
+        
+        if (waitres != 0 || timeout_rel_trunc == timeout_rel) {
+            if (waitres != 0) {
+                BLog(BLOG_DEBUG, "epoll_wait returned %d file descriptors", waitres);
+                bsys->epoll_results_num = waitres;
+                set_epoll_fd_pointers(bsys);
+            } else {
+                BLog(BLOG_DEBUG, "epoll_wait timed out");
+                move_first_timers(bsys);
+            }
+            break;
+        }
+        
+        #endif
+        
+        #ifdef BADVPN_USE_KEVENT
+        
+        struct timespec ts;
+        if (have_timeout) {
+            if (timeout_rel_trunc > 86400000) {
+                timeout_rel_trunc = 86400000;
+            }
+            ts.tv_sec = timeout_rel_trunc / 1000;
+            ts.tv_nsec = (timeout_rel_trunc % 1000) * 1000000;
+        }
+        
+        BLog(BLOG_DEBUG, "Calling kevent");
+        
+        int waitres = kevent(bsys->kqueue_fd, NULL, 0, bsys->kevent_results, BSYSTEM_MAX_RESULTS, (have_timeout ? &ts : NULL));
+        if (waitres < 0) {
+            int error = errno;
+            if (error == EINTR) {
+                BLog(BLOG_DEBUG, "kevent interrupted");
+                goto try_again;
+            }
+            perror("kevent");
+            ASSERT_FORCE(0)
+        }
+        
+        ASSERT_FORCE(!(waitres == 0) || have_timeout)
+        ASSERT_FORCE(waitres <= BSYSTEM_MAX_RESULTS)
+        
+        if (waitres != 0 || timeout_rel_trunc == timeout_rel) {
+            if (waitres != 0) {
+                BLog(BLOG_DEBUG, "kevent returned %d events", waitres);
+                bsys->kevent_results_num = waitres;
+                set_kevent_fd_pointers(bsys);
+            } else {
+                BLog(BLOG_DEBUG, "kevent timed out");
+                move_first_timers(bsys);
+            }
+            break;
+        }
+        
+        #endif
+        
+        #ifdef BADVPN_USE_POLL
+        
+        if (have_timeout) {
+            if (timeout_rel_trunc > INT_MAX) {
+                timeout_rel_trunc = INT_MAX;
+            }
+        }
+        
+        ASSERT(bsys->poll_num_enabled_fds >= 0)
+        ASSERT(bsys->poll_num_enabled_fds <= BSYSTEM_MAX_POLL_FDS)
+        int num_fds = 0;
+        
+        LinkedList1Node *list_node = LinkedList1_GetFirst(&bsys->poll_enabled_fds_list);
+        while (list_node) {
+            BFileDescriptor *bfd = UPPER_OBJECT(list_node, BFileDescriptor, poll_enabled_fds_list_node);
+            ASSERT(bfd->active)
+            ASSERT(bfd->poll_returned_index == -1)
+            
+            // calculate poll events
+            int pevents = 0;
+            if ((bfd->waitEvents & BREACTOR_READ)) {
+                pevents |= POLLIN;
+            }
+            if ((bfd->waitEvents & BREACTOR_WRITE)) {
+                pevents |= POLLOUT;
+            }
+            
+            // write pollfd entry
+            struct pollfd *pfd = &bsys->poll_results_pollfds[num_fds];
+            pfd->fd = bfd->fd;
+            pfd->events = pevents;
+            pfd->revents = 0;
+            
+            // write BFileDescriptor reference entry
+            bsys->poll_results_bfds[num_fds] = bfd;
+            
+            // increment number of fds in array
+            num_fds++;
+            
+            list_node = LinkedList1Node_Next(list_node);
+        }
+        
+        BLog(BLOG_DEBUG, "Calling poll");
+        
+        int waitres = poll(bsys->poll_results_pollfds, num_fds, (have_timeout ? timeout_rel_trunc : -1));
+        if (waitres < 0) {
+            int error = errno;
+            if (error == EINTR) {
+                BLog(BLOG_DEBUG, "poll interrupted");
+                goto try_again;
+            }
+            perror("poll");
+            ASSERT_FORCE(0)
+        }
+        
+        ASSERT_FORCE(!(waitres == 0) || have_timeout)
+        
+        if (waitres != 0 || timeout_rel_trunc == timeout_rel) {
+            if (waitres != 0) {
+                BLog(BLOG_DEBUG, "poll returned %d file descriptors", waitres);
+                bsys->poll_results_num = num_fds;
+                bsys->poll_results_pos = 0;
+                set_poll_fd_pointers(bsys);
+            } else {
+                BLog(BLOG_DEBUG, "poll timed out");
+                move_first_timers(bsys);
+            }
+            break;
+        }
+        
+        #endif
+        
+    try_again:
+        if (have_timeout) {
+            // get current time
+            now = btime_gettime();
+            // check if we already reached the time we're waiting for
+            if (now >= timeout_abs) {
+                BLog(BLOG_DEBUG, "already timed out while trying again");
+                move_first_timers(bsys);
+                break;
+            }
+        }
+    }
+    
+    // reset limit objects
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&bsys->active_limits_list)) {
+        BReactorLimit *limit = UPPER_OBJECT(list_node, BReactorLimit, active_limits_list_node);
+        ASSERT(limit->count > 0)
+        limit->count = 0;
+        LinkedList1_Remove(&bsys->active_limits_list, &limit->active_limits_list_node);
+    }
+}
+
+#ifndef BADVPN_USE_WINAPI
+
+void BFileDescriptor_Init (BFileDescriptor *bs, int fd, BFileDescriptor_handler handler, void *user)
+{
+    bs->fd = fd;
+    bs->handler = handler;
+    bs->user = user;
+    bs->active = 0;
+}
+
+#endif
+
+void BSmallTimer_Init (BSmallTimer *bt, BSmallTimer_handler handler)
+{
+    bt->handler.smalll = handler;
+    bt->state = TIMER_STATE_INACTIVE;
+    bt->is_small = 1;
+}
+
+int BSmallTimer_IsRunning (BSmallTimer *bt)
+{
+    assert_timer(bt);
+    
+    return (bt->state != TIMER_STATE_INACTIVE);
+}
+
+void BTimer_Init (BTimer *bt, btime_t msTime, BTimer_handler handler, void *user)
+{
+    bt->base.handler.heavy = handler;
+    bt->base.state = TIMER_STATE_INACTIVE;
+    bt->base.is_small = 0;
+    bt->user = user;
+    bt->msTime = msTime;
+}
+
+int BTimer_IsRunning (BTimer *bt)
+{
+    return BSmallTimer_IsRunning(&bt->base);
+}
+
+int BReactor_Init (BReactor *bsys)
+{
+    BLog(BLOG_DEBUG, "Reactor initializing");
+    
+    // set not exiting
+    bsys->exiting = 0;
+    
+    // init jobs
+    BPendingGroup_Init(&bsys->pending_jobs);
+    
+    // init timers
+    BReactor__TimersTree_Init(&bsys->timers_tree);
+    LinkedList1_Init(&bsys->timers_expired_list);
+    
+    // init limits
+    LinkedList1_Init(&bsys->active_limits_list);
+    
+    #ifdef BADVPN_USE_WINAPI
+    
+    // init IOCP list
+    LinkedList1_Init(&bsys->iocp_list);
+    
+    // init IOCP handle
+    if (!(bsys->iocp_handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1))) {
+        BLog(BLOG_ERROR, "CreateIoCompletionPort failed");
+        goto fail0;
+    }
+    
+    // init IOCP ready list
+    LinkedList1_Init(&bsys->iocp_ready_list);
+    
+    #endif
+    
+    #ifdef BADVPN_USE_EPOLL
+    
+    // create epoll fd
+    if ((bsys->efd = epoll_create(10)) < 0) {
+        BLog(BLOG_ERROR, "epoll_create failed");
+        goto fail0;
+    }
+    
+    // init results array
+    bsys->epoll_results_num = 0;
+    bsys->epoll_results_pos = 0;
+    
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    
+    // create kqueue fd
+    if ((bsys->kqueue_fd = kqueue()) < 0) {
+        BLog(BLOG_ERROR, "kqueue failed");
+        goto fail0;
+    }
+    
+    // init results array
+    bsys->kevent_results_num = 0;
+    bsys->kevent_results_pos = 0;
+    
+    #endif
+    
+    #ifdef BADVPN_USE_POLL
+    
+    // init enabled fds list
+    LinkedList1_Init(&bsys->poll_enabled_fds_list);
+    
+    // set zero enabled fds
+    bsys->poll_num_enabled_fds = 0;
+    
+    // allocate results arrays
+    if (!(bsys->poll_results_pollfds = BAllocArray(BSYSTEM_MAX_POLL_FDS, sizeof(bsys->poll_results_pollfds[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail0;
+    }
+    if (!(bsys->poll_results_bfds = BAllocArray(BSYSTEM_MAX_POLL_FDS, sizeof(bsys->poll_results_bfds[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail1;
+    }
+    
+    // init results array
+    bsys->poll_results_num = 0;
+    bsys->poll_results_pos = 0;
+    
+    #endif
+    
+    DebugObject_Init(&bsys->d_obj);
+    #ifndef BADVPN_USE_WINAPI
+    DebugCounter_Init(&bsys->d_fds_counter);
+    #endif
+    #ifdef BADVPN_USE_KEVENT
+    DebugCounter_Init(&bsys->d_kevent_ctr);
+    #endif
+    DebugCounter_Init(&bsys->d_limits_ctr);
+    
+    return 1;
+    
+    #ifdef BADVPN_USE_POLL
+fail1:
+    BFree(bsys->poll_results_pollfds);
+    #endif
+fail0:
+    BPendingGroup_Free(&bsys->pending_jobs);
+    BLog(BLOG_ERROR, "Reactor failed to initialize");
+    return 0;
+}
+
+void BReactor_Free (BReactor *bsys)
+{
+    DebugObject_Access(&bsys->d_obj);
+    
+    #ifdef BADVPN_USE_WINAPI
+    while (!LinkedList1_IsEmpty(&bsys->iocp_list)) {
+        BReactorIOCPOverlapped *olap = UPPER_OBJECT(LinkedList1_GetLast(&bsys->iocp_list), BReactorIOCPOverlapped, iocp_list_node);
+        ASSERT(olap->reactor == bsys)
+        olap->handler(olap->user, BREACTOR_IOCP_EVENT_EXITING, 0);
+    }
+    #endif
+    
+    // {pending group has no BPending objects}
+    ASSERT(!BPendingGroup_HasJobs(&bsys->pending_jobs))
+    ASSERT(BReactor__TimersTree_IsEmpty(&bsys->timers_tree))
+    ASSERT(LinkedList1_IsEmpty(&bsys->timers_expired_list))
+    ASSERT(LinkedList1_IsEmpty(&bsys->active_limits_list))
+    DebugObject_Free(&bsys->d_obj);
+    #ifdef BADVPN_USE_WINAPI
+    ASSERT(LinkedList1_IsEmpty(&bsys->iocp_ready_list))
+    ASSERT(LinkedList1_IsEmpty(&bsys->iocp_list))
+    #endif
+    #ifndef BADVPN_USE_WINAPI
+    DebugCounter_Free(&bsys->d_fds_counter);
+    #endif
+    #ifdef BADVPN_USE_KEVENT
+    DebugCounter_Free(&bsys->d_kevent_ctr);
+    #endif
+    DebugCounter_Free(&bsys->d_limits_ctr);
+    #ifdef BADVPN_USE_POLL
+    ASSERT(bsys->poll_num_enabled_fds == 0)
+    ASSERT(LinkedList1_IsEmpty(&bsys->poll_enabled_fds_list))
+    #endif
+    
+    BLog(BLOG_DEBUG, "Reactor freeing");
+    
+    #ifdef BADVPN_USE_WINAPI
+    
+    // close IOCP handle
+    ASSERT_FORCE(CloseHandle(bsys->iocp_handle))
+    
+    #endif
+    
+    #ifdef BADVPN_USE_EPOLL
+    
+    // close epoll fd
+    ASSERT_FORCE(close(bsys->efd) == 0)
+    
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    
+    // close kqueue fd
+    ASSERT_FORCE(close(bsys->kqueue_fd) == 0)
+    
+    #endif
+    
+    #ifdef BADVPN_USE_POLL
+    
+    // free results arrays
+    BFree(bsys->poll_results_bfds);
+    BFree(bsys->poll_results_pollfds);
+    
+    #endif
+    
+    // free jobs
+    BPendingGroup_Free(&bsys->pending_jobs);
+}
+
+int BReactor_Exec (BReactor *bsys)
+{
+    BLog(BLOG_DEBUG, "Entering event loop");
+    
+    while (!bsys->exiting) {
+        // dispatch job
+        if (BPendingGroup_HasJobs(&bsys->pending_jobs)) {
+            BPendingGroup_ExecuteJob(&bsys->pending_jobs);
+            continue;
+        }
+        
+        // dispatch timer
+        LinkedList1Node *list_node = LinkedList1_GetFirst(&bsys->timers_expired_list);
+        if (list_node) {
+            BSmallTimer *timer = UPPER_OBJECT(list_node, BSmallTimer, u.list_node);
+            ASSERT(timer->state == TIMER_STATE_EXPIRED)
+            
+            // remove from expired list
+            LinkedList1_Remove(&bsys->timers_expired_list, &timer->u.list_node);
+            
+            // set inactive
+            timer->state = TIMER_STATE_INACTIVE;
+            
+            // call handler
+            BLog(BLOG_DEBUG, "Dispatching timer");
+            if (timer->is_small) {
+                timer->handler.smalll(timer);
+            } else {
+                BTimer *btimer = UPPER_OBJECT(timer, BTimer, base);
+                timer->handler.heavy(btimer->user);
+            }
+            continue;
+        }
+        
+        #ifdef BADVPN_USE_WINAPI
+        
+        if (!LinkedList1_IsEmpty(&bsys->iocp_ready_list)) {
+            BReactorIOCPOverlapped *olap = UPPER_OBJECT(LinkedList1_GetFirst(&bsys->iocp_ready_list), BReactorIOCPOverlapped, ready_list_node);
+            ASSERT(olap->is_ready)
+            ASSERT(olap->handler)
+            
+            // remove from ready list
+            LinkedList1_Remove(&bsys->iocp_ready_list, &olap->ready_list_node);
+            
+            // set not ready
+            olap->is_ready = 0;
+            
+            int event = (olap->ready_succeeded ? BREACTOR_IOCP_EVENT_SUCCEEDED : BREACTOR_IOCP_EVENT_FAILED);
+            
+            // call handler
+            olap->handler(olap->user, event, olap->ready_bytes);
+            continue;
+        }
+        
+        #endif
+        
+        #ifdef BADVPN_USE_EPOLL
+        
+        // dispatch file descriptor
+        if (bsys->epoll_results_pos < bsys->epoll_results_num) {
+            // grab event
+            struct epoll_event *event = &bsys->epoll_results[bsys->epoll_results_pos];
+            bsys->epoll_results_pos++;
+            
+            // check if the BFileDescriptor was removed
+            if (!event->data.ptr) {
+                continue;
+            }
+            
+            // get BFileDescriptor
+            BFileDescriptor *bfd = (BFileDescriptor *)event->data.ptr;
+            ASSERT(bfd->active)
+            ASSERT(bfd->epoll_returned_ptr == (BFileDescriptor **)&event->data.ptr)
+            
+            // zero pointer to the epoll entry
+            bfd->epoll_returned_ptr = NULL;
+            
+            // calculate events to report
+            int events = 0;
+            if ((bfd->waitEvents&BREACTOR_READ) && (event->events&EPOLLIN)) {
+                events |= BREACTOR_READ;
+            }
+            if ((bfd->waitEvents&BREACTOR_WRITE) && (event->events&EPOLLOUT)) {
+                events |= BREACTOR_WRITE;
+            }
+            if ((event->events&EPOLLERR)) {
+                events |= BREACTOR_ERROR;
+            }
+            if ((event->events&EPOLLHUP)) {
+                events |= BREACTOR_HUP;
+            }
+            
+            if (!events) {
+                BLog(BLOG_ERROR, "no events detected?");
+                continue;
+            }
+            
+            // call handler
+            BLog(BLOG_DEBUG, "Dispatching file descriptor");
+            bfd->handler(bfd->user, events);
+            continue;
+        }
+        
+        #endif
+        
+        #ifdef BADVPN_USE_KEVENT
+        
+        // dispatch kevent
+        if (bsys->kevent_results_pos < bsys->kevent_results_num) {
+            // grab event
+            struct kevent *event = &bsys->kevent_results[bsys->kevent_results_pos];
+            bsys->kevent_results_pos++;
+            
+            // check if the event was removed
+            if (!event->udata) {
+                continue;
+            }
+            
+            // check tag
+            int *tag = event->udata;
+            switch (*tag) {
+                case KEVENT_TAG_FD: {
+                    // get BFileDescriptor
+                    BFileDescriptor *bfd = UPPER_OBJECT(tag, BFileDescriptor, kevent_tag);
+                    ASSERT(bfd->active)
+                    ASSERT(bfd->kevent_returned_ptr == (int **)&event->udata)
+                    
+                    // zero pointer to the kevent entry
+                    bfd->kevent_returned_ptr = NULL;
+                    
+                    // calculate event to report
+                    int events = 0;
+                    if ((bfd->waitEvents&BREACTOR_READ) && event->filter == EVFILT_READ) {
+                        events |= BREACTOR_READ;
+                    }
+                    if ((bfd->waitEvents&BREACTOR_WRITE) && event->filter == EVFILT_WRITE) {
+                        events |= BREACTOR_WRITE;
+                    }
+                    
+                    if (!events) {
+                        BLog(BLOG_ERROR, "no events detected?");
+                        continue;
+                    }
+                    
+                    // call handler
+                    BLog(BLOG_DEBUG, "Dispatching file descriptor");
+                    bfd->handler(bfd->user, events);
+                    continue;
+                } break;
+                
+                case KEVENT_TAG_KEVENT: {
+                    // get BReactorKEvent
+                    BReactorKEvent *kev = UPPER_OBJECT(tag, BReactorKEvent, kevent_tag);
+                    ASSERT(kev->reactor == bsys)
+                    ASSERT(kev->kevent_returned_ptr == (int **)&event->udata)
+                    
+                    // zero pointer to the kevent entry
+                    kev->kevent_returned_ptr = NULL;
+                    
+                    // call handler
+                    BLog(BLOG_DEBUG, "Dispatching kevent");
+                    kev->handler(kev->user, event->fflags, event->data);
+                    continue;
+                } break;
+                
+                default:
+                    ASSERT(0);
+            }
+        }
+        
+        #endif
+        
+        #ifdef BADVPN_USE_POLL
+        
+        if (bsys->poll_results_pos < bsys->poll_results_num) {
+            // grab event
+            struct pollfd *pfd = &bsys->poll_results_pollfds[bsys->poll_results_pos];
+            BFileDescriptor *bfd = bsys->poll_results_bfds[bsys->poll_results_pos];
+            bsys->poll_results_pos++;
+            
+            // skip removed entry
+            if (!bfd) {
+                continue;
+            }
+            
+            ASSERT(bfd->active)
+            ASSERT(bfd->poll_returned_index == bsys->poll_results_pos - 1)
+            
+            // remove result reference
+            bfd->poll_returned_index = -1;
+            
+            // calculate events to report
+            int events = 0;
+            if ((bfd->waitEvents & BREACTOR_READ) && (pfd->revents & POLLIN)) {
+                events |= BREACTOR_READ;
+            }
+            if ((bfd->waitEvents & BREACTOR_WRITE) && (pfd->revents & POLLOUT)) {
+                events |= BREACTOR_WRITE;
+            }
+            if ((pfd->revents & POLLERR) || (pfd->revents & POLLHUP)) {
+                events |= BREACTOR_ERROR;
+            }
+            
+            if (!events) {
+                continue;
+            }
+            
+            // call handler
+            BLog(BLOG_DEBUG, "Dispatching file descriptor");
+            bfd->handler(bfd->user, events);
+            continue;
+        }
+        
+        #endif
+        
+        wait_for_events(bsys);
+    }
+
+    BLog(BLOG_DEBUG, "Exiting event loop, exit code %d", bsys->exit_code);
+
+    return bsys->exit_code;
+}
+
+void BReactor_Quit (BReactor *bsys, int code)
+{
+    bsys->exiting = 1;
+    bsys->exit_code = code;
+}
+
+void BReactor_SetSmallTimer (BReactor *bsys, BSmallTimer *bt, int mode, btime_t time)
+{
+    assert_timer(bt);
+    ASSERT(mode == BTIMER_SET_ABSOLUTE || mode == BTIMER_SET_RELATIVE)
+    
+    // unlink it if it's already in the list
+    BReactor_RemoveSmallTimer(bsys, bt);
+    
+    // if mode is relative, add current time
+    if (mode == BTIMER_SET_RELATIVE) {
+        time = btime_add(btime_gettime(), time);
+    }
+    
+    // set time
+    bt->absTime = time;
+    
+    // set running
+    bt->state = TIMER_STATE_RUNNING;
+    
+    // insert to running timers tree
+    BReactor__TimersTreeRef ref = {bt, bt};
+    int res = BReactor__TimersTree_Insert(&bsys->timers_tree, 0, ref, NULL);
+    ASSERT_EXECUTE(res)
+}
+
+void BReactor_RemoveSmallTimer (BReactor *bsys, BSmallTimer *bt)
+{
+    assert_timer(bt);
+    
+    if (bt->state == TIMER_STATE_INACTIVE) {
+        return;
+    }
+
+    if (bt->state == TIMER_STATE_EXPIRED) {
+        // remove from expired list
+        LinkedList1_Remove(&bsys->timers_expired_list, &bt->u.list_node);
+    } else {
+        // remove from running tree
+        BReactor__TimersTreeRef ref = {bt, bt};
+        BReactor__TimersTree_Remove(&bsys->timers_tree, 0, ref);
+    }
+
+    // set inactive
+    bt->state = TIMER_STATE_INACTIVE;
+}
+
+void BReactor_SetTimer (BReactor *bsys, BTimer *bt)
+{
+    BReactor_SetSmallTimer(bsys, &bt->base, BTIMER_SET_RELATIVE, bt->msTime);
+}
+
+void BReactor_SetTimerAfter (BReactor *bsys, BTimer *bt, btime_t after)
+{
+    BReactor_SetSmallTimer(bsys, &bt->base, BTIMER_SET_RELATIVE, after);
+}
+
+void BReactor_SetTimerAbsolute (BReactor *bsys, BTimer *bt, btime_t time)
+{
+    BReactor_SetSmallTimer(bsys, &bt->base, BTIMER_SET_ABSOLUTE, time);
+}
+
+void BReactor_RemoveTimer (BReactor *bsys, BTimer *bt)
+{
+    return BReactor_RemoveSmallTimer(bsys, &bt->base);
+}
+
+BPendingGroup * BReactor_PendingGroup (BReactor *bsys)
+{
+    return &bsys->pending_jobs;
+}
+
+int BReactor_Synchronize (BReactor *bsys, BSmallPending *ref)
+{
+    ASSERT(ref)
+    
+    while (!bsys->exiting) {
+        ASSERT(BPendingGroup_HasJobs(&bsys->pending_jobs))
+        
+        if (BPendingGroup_PeekJob(&bsys->pending_jobs) == ref) {
+            return 1;
+        }
+        
+        BPendingGroup_ExecuteJob(&bsys->pending_jobs);
+    }
+    
+    return 0;
+}
+
+#ifndef BADVPN_USE_WINAPI
+
+int BReactor_AddFileDescriptor (BReactor *bsys, BFileDescriptor *bs)
+{
+    ASSERT(!bs->active)
+    
+    #ifdef BADVPN_USE_EPOLL
+    
+    // add epoll entry
+    struct epoll_event event;
+    memset(&event, 0, sizeof(event));
+    event.events = 0;
+    event.data.ptr = bs;
+    if (epoll_ctl(bsys->efd, EPOLL_CTL_ADD, bs->fd, &event) < 0) {
+        int error = errno;
+        BLog(BLOG_ERROR, "epoll_ctl failed: %d", error);
+        return 0;
+    }
+    
+    // set epoll returned pointer
+    bs->epoll_returned_ptr = NULL;
+    
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    
+    // set kevent tag
+    bs->kevent_tag = KEVENT_TAG_FD;
+    
+    // set kevent returned pointer
+    bs->kevent_returned_ptr = NULL;
+    
+    #endif
+    
+    #ifdef BADVPN_USE_POLL
+    
+    if (bsys->poll_num_enabled_fds == BSYSTEM_MAX_POLL_FDS) {
+        BLog(BLOG_ERROR, "too many fds");
+        return 0;
+    }
+    
+    // append to enabled fds list
+    LinkedList1_Append(&bsys->poll_enabled_fds_list, &bs->poll_enabled_fds_list_node);
+    bsys->poll_num_enabled_fds++;
+    
+    // set not returned
+    bs->poll_returned_index = -1;
+    
+    #endif
+    
+    bs->active = 1;
+    bs->waitEvents = 0;
+    
+    DebugCounter_Increment(&bsys->d_fds_counter);
+    return 1;
+}
+
+void BReactor_RemoveFileDescriptor (BReactor *bsys, BFileDescriptor *bs)
+{
+    ASSERT(bs->active)
+    DebugCounter_Decrement(&bsys->d_fds_counter);
+
+    bs->active = 0;
+
+    #ifdef BADVPN_USE_EPOLL
+    
+    // delete epoll entry
+    struct epoll_event event;
+    memset(&event, 0, sizeof(event));
+    ASSERT_FORCE(epoll_ctl(bsys->efd, EPOLL_CTL_DEL, bs->fd, &event) == 0)
+    
+    // write through epoll returned pointer
+    if (bs->epoll_returned_ptr) {
+        *bs->epoll_returned_ptr = NULL;
+    }
+    
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    
+    // delete kevents
+    update_kevent_fd_events(bsys, bs, 0);
+    
+    // write through kevent returned pointer
+    if (bs->kevent_returned_ptr) {
+        *bs->kevent_returned_ptr = NULL;
+    }
+    
+    #endif
+    
+    #ifdef BADVPN_USE_POLL
+    
+    // invalidate results entry
+    if (bs->poll_returned_index != -1) {
+        ASSERT(bs->poll_returned_index >= bsys->poll_results_pos)
+        ASSERT(bs->poll_returned_index < bsys->poll_results_num)
+        ASSERT(bsys->poll_results_bfds[bs->poll_returned_index] == bs)
+        
+        bsys->poll_results_bfds[bs->poll_returned_index] = NULL;
+    }
+    
+    // remove from enabled fds list
+    LinkedList1_Remove(&bsys->poll_enabled_fds_list, &bs->poll_enabled_fds_list_node);
+    bsys->poll_num_enabled_fds--;
+    
+    #endif
+}
+
+void BReactor_SetFileDescriptorEvents (BReactor *bsys, BFileDescriptor *bs, int events)
+{
+    ASSERT(bs->active)
+    ASSERT(!(events&~(BREACTOR_READ|BREACTOR_WRITE)))
+    
+    if (bs->waitEvents == events) {
+        return;
+    }
+    
+    #ifdef BADVPN_USE_EPOLL
+    
+    // calculate epoll events
+    int eevents = 0;
+    if ((events & BREACTOR_READ)) {
+        eevents |= EPOLLIN;
+    }
+    if ((events & BREACTOR_WRITE)) {
+        eevents |= EPOLLOUT;
+    }
+    
+    // update epoll entry
+    struct epoll_event event;
+    memset(&event, 0, sizeof(event));
+    event.events = eevents;
+    event.data.ptr = bs;
+    ASSERT_FORCE(epoll_ctl(bsys->efd, EPOLL_CTL_MOD, bs->fd, &event) == 0)
+    
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    
+    update_kevent_fd_events(bsys, bs, events);
+    
+    #endif
+    
+    // update events
+    bs->waitEvents = events;
+}
+
+#endif
+
+void BReactorLimit_Init (BReactorLimit *o, BReactor *reactor, int limit)
+{
+    DebugObject_Access(&reactor->d_obj);
+    ASSERT(limit > 0)
+    
+    // init arguments
+    o->reactor = reactor;
+    o->limit = limit;
+    
+    // set count zero
+    o->count = 0;
+    
+    DebugCounter_Increment(&reactor->d_limits_ctr);
+    DebugObject_Init(&o->d_obj);
+}
+
+void BReactorLimit_Free (BReactorLimit *o)
+{
+    BReactor *reactor = o->reactor;
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Decrement(&reactor->d_limits_ctr);
+    
+    // remove from active limits list
+    if (o->count > 0) {
+        LinkedList1_Remove(&reactor->active_limits_list, &o->active_limits_list_node);
+    }
+}
+
+int BReactorLimit_Increment (BReactorLimit *o)
+{
+    BReactor *reactor = o->reactor;
+    DebugObject_Access(&o->d_obj);
+    
+    // check count against limit
+    if (o->count >= o->limit) {
+        return 0;
+    }
+    
+    // increment count
+    o->count++;
+    
+    // if limit was zero, add to active limits list
+    if (o->count == 1) {
+        LinkedList1_Append(&reactor->active_limits_list, &o->active_limits_list_node);
+    }
+    
+    return 1;
+}
+
+void BReactorLimit_SetLimit (BReactorLimit *o, int limit)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(limit > 0)
+    
+    // set limit
+    o->limit = limit;
+}
+
+#ifdef BADVPN_USE_KEVENT
+
+int BReactorKEvent_Init (BReactorKEvent *o, BReactor *reactor, BReactorKEvent_handler handler, void *user, uintptr_t ident, short filter, u_int fflags, intptr_t data)
+{
+    DebugObject_Access(&reactor->d_obj);
+    
+    // init arguments
+    o->reactor = reactor;
+    o->handler = handler;
+    o->user = user;
+    o->ident = ident;
+    o->filter = filter;
+    
+    // add kevent
+    struct kevent event;
+    memset(&event, 0, sizeof(event));
+    event.ident = o->ident;
+    event.filter = o->filter;
+    event.flags = EV_ADD;
+    event.fflags = fflags;
+    event.data = data;
+    event.udata = &o->kevent_tag;
+    if (kevent(o->reactor->kqueue_fd, &event, 1, NULL, 0, NULL) < 0) {
+        return 0;
+    }
+    
+    // set kevent tag
+    o->kevent_tag = KEVENT_TAG_KEVENT;
+    
+    // set kevent returned pointer
+    o->kevent_returned_ptr = NULL;
+    
+    DebugObject_Init(&o->d_obj);
+    DebugCounter_Increment(&o->reactor->d_kevent_ctr);
+    return 1;
+}
+
+void BReactorKEvent_Free (BReactorKEvent *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Decrement(&o->reactor->d_kevent_ctr);
+    
+    // write through kevent returned pointer
+    if (o->kevent_returned_ptr) {
+        *o->kevent_returned_ptr = NULL;
+    }
+    
+    // delete kevent
+    struct kevent event;
+    memset(&event, 0, sizeof(event));
+    event.ident = o->ident;
+    event.filter = o->filter;
+    event.flags = EV_DELETE;
+    ASSERT_FORCE(kevent(o->reactor->kqueue_fd, &event, 1, NULL, 0, NULL) == 0)
+}
+
+#endif
+
+#ifdef BADVPN_USE_WINAPI
+
+HANDLE BReactor_GetIOCPHandle (BReactor *reactor)
+{
+    DebugObject_Access(&reactor->d_obj);
+    
+    return reactor->iocp_handle;
+}
+
+void BReactorIOCPOverlapped_Init (BReactorIOCPOverlapped *o, BReactor *reactor, void *user, BReactorIOCPOverlapped_handler handler)
+{
+    DebugObject_Access(&reactor->d_obj);
+    
+    // init arguments
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // zero overlapped
+    memset(&o->olap, 0, sizeof(o->olap));
+    
+    // append to IOCP list
+    LinkedList1_Append(&reactor->iocp_list, &o->iocp_list_node);
+    
+    // set not ready
+    o->is_ready = 0;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void BReactorIOCPOverlapped_Free (BReactorIOCPOverlapped *o)
+{
+    BReactor *reactor = o->reactor;
+    DebugObject_Free(&o->d_obj);
+    
+    // remove from IOCP ready list
+    if (o->is_ready) {
+        LinkedList1_Remove(&reactor->iocp_ready_list, &o->ready_list_node);
+    }
+    
+    // remove from IOCP list
+    LinkedList1_Remove(&reactor->iocp_list, &o->iocp_list_node);
+}
+
+void BReactorIOCPOverlapped_Wait (BReactorIOCPOverlapped *o, int *out_succeeded, DWORD *out_bytes)
+{
+    BReactor *reactor = o->reactor;
+    DebugObject_Access(&o->d_obj);
+    
+    // wait for IOCP events until we get an event for this olap
+    while (!o->is_ready) {
+        DWORD bytes = 0;
+        ULONG_PTR key;
+        BReactorIOCPOverlapped *olap = NULL;
+        BOOL res = GetQueuedCompletionStatus(reactor->iocp_handle, &bytes, &key, (OVERLAPPED **)&olap, INFINITE);
+        
+        ASSERT_FORCE(olap)
+        DebugObject_Access(&olap->d_obj);
+        ASSERT(olap->reactor == reactor)
+        
+        // regular I/O should be done synchronously, so we shoudln't ever get a second completion before an
+        // existing one is dispatched. If however PostQueuedCompletionStatus is being used to signal events,
+        // just discard any excess events.
+        if (!olap->is_ready) {
+            set_iocp_ready(olap, (res == TRUE), bytes);
+        }
+    }
+    
+    // remove from IOCP ready list
+    LinkedList1_Remove(&reactor->iocp_ready_list, &o->ready_list_node);
+    
+    // set not ready
+    o->is_ready = 0;
+    
+    if (out_succeeded) {
+        *out_succeeded = o->ready_succeeded;
+    }
+    if (out_bytes) {
+        *out_bytes = o->ready_bytes;
+    }
+}
+
+#endif
diff --git a/external/badvpn_dns/system/BReactor_badvpn.h b/external/badvpn_dns/system/BReactor_badvpn.h
new file mode 100644
index 0000000..2c5ab4c
--- /dev/null
+++ b/external/badvpn_dns/system/BReactor_badvpn.h
@@ -0,0 +1,572 @@
+/**
+ * @file BReactor_badvpn.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * Event loop that supports file desciptor (Linux) or HANDLE (Windows) events
+ * and timers.
+ */
+
+#ifndef BADVPN_SYSTEM_BREACTOR_H
+#define BADVPN_SYSTEM_BREACTOR_H
+
+#if (defined(BADVPN_USE_WINAPI) + defined(BADVPN_USE_EPOLL) + defined(BADVPN_USE_KEVENT) + defined(BADVPN_USE_POLL)) != 1
+#error Unknown event backend or too many event backends
+#endif
+
+#ifdef BADVPN_USE_WINAPI
+#include <windows.h>
+#endif
+
+#ifdef BADVPN_USE_EPOLL
+#include <sys/epoll.h>
+#endif
+
+#ifdef BADVPN_USE_KEVENT
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#endif
+
+#ifdef BADVPN_USE_POLL
+#include <poll.h>
+#endif
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/debugcounter.h>
+#include <base/DebugObject.h>
+#include <structure/LinkedList1.h>
+#include <structure/CAvl.h>
+#include <system/BTime.h>
+#include <base/BPending.h>
+
+struct BSmallTimer_t;
+typedef struct BSmallTimer_t *BReactor_timerstree_link;
+
+#include "BReactor_badvpn_timerstree.h"
+#include <structure/CAvl_decl.h>
+
+#define BTIMER_SET_ABSOLUTE 1
+#define BTIMER_SET_RELATIVE 2
+
+/**
+ * Handler function invoked when the timer expires.
+ * The timer was in running state.
+ * The timer enters not running state before this function is invoked.
+ * This function is being called from within the timer's previosly
+ * associated reactor.
+ *
+ * @param timer pointer to the timer. Use the {@link UPPER_OBJECT} macro
+ *              to obtain the pointer to the containing structure.
+ */
+typedef void (*BSmallTimer_handler) (struct BSmallTimer_t *timer);
+
+/**
+ * Handler function invoked when the timer expires.
+ * The timer was in running state.
+ * The timer enters not running state before this function is invoked.
+ * This function is being called from within the timer's previosly
+ * associated reactor.
+ *
+ * @param user value passed to {@link BTimer_Init}
+ */
+typedef void (*BTimer_handler) (void *user);
+
+/**
+ * Timer object used with {@link BReactor}.
+ */
+typedef struct BSmallTimer_t {
+    union {
+        BSmallTimer_handler smalll; // MSVC doesn't like "small"
+        BTimer_handler heavy;
+    } handler;
+    union {
+        LinkedList1Node list_node;
+        struct BSmallTimer_t *tree_child[2];
+    } u;
+    struct BSmallTimer_t *tree_parent;
+    btime_t absTime;
+    int8_t tree_balance;
+    uint8_t state;
+    uint8_t is_small;
+} BSmallTimer;
+
+/**
+ * Initializes the timer object.
+ * The timer object is initialized in not running state.
+ *
+ * @param bt the object
+ * @param handler handler function invoked when the timer expires
+ */
+void BSmallTimer_Init (BSmallTimer *bt, BSmallTimer_handler handler);
+
+/**
+ * Checks if the timer is running.
+ *
+ * @param bt the object
+ * @return 1 if running, 0 if not running
+ */
+int BSmallTimer_IsRunning (BSmallTimer *bt);
+
+/**
+ * Timer object used with {@link BReactor}. This is a legacy wrapper
+ * around {@link BSmallTimer} with an extra field for the default time.
+ */
+typedef struct {
+    BSmallTimer base;
+    void *user;
+    btime_t msTime;
+} BTimer;
+
+/**
+ * Initializes the timer object.
+ * The timer object is initialized in not running state.
+ *
+ * @param bt the object
+ * @param msTime default timeout in milliseconds
+ * @param handler handler function invoked when the timer expires
+ * @param user value to pass to the handler function
+ */
+void BTimer_Init (BTimer *bt, btime_t msTime, BTimer_handler handler, void *user);
+
+/**
+ * Checks if the timer is running.
+ *
+ * @param bt the object
+ * @return 1 if running, 0 if not running
+ */
+int BTimer_IsRunning (BTimer *bt);
+
+#ifndef BADVPN_USE_WINAPI
+
+struct BFileDescriptor_t;
+
+#define BREACTOR_READ (1 << 0)
+#define BREACTOR_WRITE (1 << 1)
+#define BREACTOR_ERROR (1 << 2)
+#define BREACTOR_HUP (1 << 3)
+
+/**
+ * Handler function invoked by the reactor when one or more events are detected.
+ * The events argument will contain a subset of the monitored events (BREACTOR_READ, BREACTOR_WRITE),
+ * plus possibly the error event (BREACTOR_ERROR).
+ * The file descriptor object is in active state, being called from within
+ * the associated reactor.
+ *
+ * @param user value passed to {@link BFileDescriptor_Init}
+ * @param events bitmask composed of a subset of monitored events (BREACTOR_READ, BREACTOR_WRITE),
+ *               and possibly the error event BREACTOR_ERROR and the hang-up event BREACTOR_HUP.
+ *               Will be nonzero.
+ */
+typedef void (*BFileDescriptor_handler) (void *user, int events);
+
+/**
+ * File descriptor object used with {@link BReactor}.
+ */
+typedef struct BFileDescriptor_t {
+    int fd;
+    BFileDescriptor_handler handler;
+    void *user;
+    int active;
+    int waitEvents;
+    
+    #ifdef BADVPN_USE_EPOLL
+    struct BFileDescriptor_t **epoll_returned_ptr;
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    int kevent_tag;
+    int **kevent_returned_ptr;
+    #endif
+    
+    #ifdef BADVPN_USE_POLL
+    LinkedList1Node poll_enabled_fds_list_node;
+    int poll_returned_index;
+    #endif
+} BFileDescriptor;
+
+/**
+ * Intializes the file descriptor object.
+ * The object is initialized in not active state.
+ *
+ * @param bs file descriptor object to initialize
+ * @param fb file descriptor to represent
+ * @param handler handler function invoked by the reactor when a monitored event is detected
+ * @param user value passed to the handler functuon
+ */
+void BFileDescriptor_Init (BFileDescriptor *bs, int fd, BFileDescriptor_handler handler, void *user);
+
+#endif
+
+// BReactor
+
+#define BSYSTEM_MAX_RESULTS 64
+#define BSYSTEM_MAX_HANDLES 64
+#define BSYSTEM_MAX_POLL_FDS 4096
+
+/**
+ * Event loop that supports file desciptor (Linux) or HANDLE (Windows) events
+ * and timers.
+ */
+typedef struct {
+    int exiting;
+    int exit_code;
+    
+    // jobs
+    BPendingGroup pending_jobs;
+    
+    // timers
+    BReactor__TimersTree timers_tree;
+    LinkedList1 timers_expired_list;
+    
+    // limits
+    LinkedList1 active_limits_list;
+    
+    #ifdef BADVPN_USE_WINAPI
+    LinkedList1 iocp_list;
+    HANDLE iocp_handle;
+    LinkedList1 iocp_ready_list;
+    #endif
+    
+    #ifdef BADVPN_USE_EPOLL
+    int efd; // epoll fd
+    struct epoll_event epoll_results[BSYSTEM_MAX_RESULTS]; // epoll returned events buffer
+    int epoll_results_num; // number of events in the array
+    int epoll_results_pos; // number of events processed so far
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    int kqueue_fd;
+    struct kevent kevent_results[BSYSTEM_MAX_RESULTS];
+    int kevent_results_num;
+    int kevent_results_pos;
+    #endif
+    
+    #ifdef BADVPN_USE_POLL
+    LinkedList1 poll_enabled_fds_list;
+    int poll_num_enabled_fds;
+    int poll_results_num;
+    int poll_results_pos;
+    struct pollfd *poll_results_pollfds;
+    BFileDescriptor **poll_results_bfds;
+    #endif
+    
+    DebugObject d_obj;
+    #ifndef BADVPN_USE_WINAPI
+    DebugCounter d_fds_counter;
+    #endif
+    #ifdef BADVPN_USE_KEVENT
+    DebugCounter d_kevent_ctr;
+    #endif
+    DebugCounter d_limits_ctr;
+} BReactor;
+
+/**
+ * Initializes the reactor.
+ * {@link BLog_Init} must have been done.
+ * {@link BTime_Init} must have been done.
+ *
+ * @param bsys the object
+ * @return 1 on success, 0 on failure
+ */
+int BReactor_Init (BReactor *bsys) WARN_UNUSED;
+
+/**
+ * Frees the reactor.
+ * Must not be called from within the event loop ({@link BReactor_Exec}).
+ * There must be no {@link BPending} or {@link BSmallPending} objects using the
+ * pending group returned by {@link BReactor_PendingGroup}.
+ * There must be no running timers in this reactor.
+ * There must be no limit objects in this reactor.
+ * There must be no file descriptors or handles registered
+ * with this reactor.
+ * There must be no {@link BReactorKEvent} objects in this reactor.
+ *
+ * @param bsys the object
+ */
+void BReactor_Free (BReactor *bsys);
+
+/**
+ * Runs the event loop.
+ *
+ * @param bsys the object
+ * @return value passed to {@link BReactor_Quit}
+ */
+int BReactor_Exec (BReactor *bsys);
+
+/**
+ * Causes the event loop ({@link BReactor_Exec}) to cease
+ * dispatching events and return.
+ * Any further calls of {@link BReactor_Exec} will return immediately.
+ *
+ * @param bsys the object
+ * @param code value {@link BReactor_Exec} should return. If this is
+ *             called more than once, it will return the last code.
+ */
+void BReactor_Quit (BReactor *bsys, int code);
+
+/**
+ * Starts a timer to expire at the specified time.
+ * The timer must have been initialized with {@link BSmallTimer_Init}.
+ * If the timer is in running state, it must be associated with this reactor.
+ * The timer enters running state, associated with this reactor.
+ *
+ * @param bsys the object
+ * @param bt timer to start
+ * @param mode interpretation of time (BTIMER_SET_ABSOLUTE or BTIMER_SET_RELATIVE)
+ * @param time absolute or relative expiration time
+ */
+void BReactor_SetSmallTimer (BReactor *bsys, BSmallTimer *bt, int mode, btime_t time);
+
+/**
+ * Stops a timer.
+ * If the timer is in running state, it must be associated with this reactor.
+ * The timer enters not running state.
+ *
+ * @param bsys the object
+ * @param bt timer to stop
+ */
+void BReactor_RemoveSmallTimer (BReactor *bsys, BSmallTimer *bt);
+
+/**
+ * Starts a timer to expire after its default time.
+ * The timer must have been initialized with {@link BTimer_Init}.
+ * If the timer is in running state, it must be associated with this reactor.
+ * The timer enters running state, associated with this reactor.
+ *
+ * @param bsys the object
+ * @param bt timer to start
+ */
+void BReactor_SetTimer (BReactor *bsys, BTimer *bt);
+
+/**
+ * Starts a timer to expire after a given time.
+ * The timer must have been initialized with {@link BTimer_Init}.
+ * If the timer is in running state, it must be associated with this reactor.
+ * The timer enters running state, associated with this reactor.
+ *
+ * @param bsys the object
+ * @param bt timer to start
+ * @param after relative expiration time
+ */
+void BReactor_SetTimerAfter (BReactor *bsys, BTimer *bt, btime_t after);
+
+/**
+ * Starts a timer to expire at the specified time.
+ * The timer must have been initialized with {@link BTimer_Init}.
+ * If the timer is in running state, it must be associated with this reactor.
+ * The timer enters running state, associated with this reactor.
+ * The timer's expiration time is set to the time argument.
+ *
+ * @param bsys the object
+ * @param bt timer to start
+ * @param time absolute expiration time (according to {@link btime_gettime})
+ */
+void BReactor_SetTimerAbsolute (BReactor *bsys, BTimer *bt, btime_t time);
+
+/**
+ * Stops a timer.
+ * If the timer is in running state, it must be associated with this reactor.
+ * The timer enters not running state.
+ *
+ * @param bsys the object
+ * @param bt timer to stop
+ */
+void BReactor_RemoveTimer (BReactor *bsys, BTimer *bt);
+
+/**
+ * Returns a {@link BPendingGroup} object that can be used to schedule jobs for
+ * the reactor to execute. These jobs have complete priority over other events
+ * (timers, file descriptors and Windows handles).
+ * The returned pending group may only be used as an argument to {@link BPending_Init},
+ * and must not be accessed by other means.
+ * All {@link BPending} and {@link BSmallPending} objects using this group must be
+ * freed before freeing the reactor.
+ * 
+ * @param bsys the object
+ * @return pending group for scheduling jobs for the reactor to execute
+ */
+BPendingGroup * BReactor_PendingGroup (BReactor *bsys);
+
+/**
+ * Executes pending jobs until either:
+ *   - the reference job is reached, or
+ *   - {@link BReactor_Quit} is called.
+ * The reference job must be reached before the job list empties.
+ * The reference job will not be executed.
+ * 
+ * WARNING: Use with care. This should only be used to to work around third-party software
+ * that does not integrade into the jobs system. In particular, you should think about:
+ *   - the effects the jobs to be executed may have, and
+ *   - the environment those jobs expect to be executed in.
+ * 
+ * @param bsys the object
+ * @param ref reference job. It is not accessed in any way, only its address is compared to
+ *            pending jobs before they are executed.
+ * @return 1 if the reference job was reached,
+ *         0 if {@link BReactor_Quit} was called (either while executing a job, or before)
+ */
+int BReactor_Synchronize (BReactor *bsys, BSmallPending *ref);
+
+#ifndef BADVPN_USE_WINAPI
+
+/**
+ * Starts monitoring a file descriptor.
+ *
+ * @param bsys the object
+ * @param bs file descriptor object. Must have been initialized with
+ *           {@link BFileDescriptor_Init} Must be in not active state.
+ *           On success, the file descriptor object enters active state,
+ *           associated with this reactor.
+ * @return 1 on success, 0 on failure
+ */
+int BReactor_AddFileDescriptor (BReactor *bsys, BFileDescriptor *bs) WARN_UNUSED;
+
+/**
+ * Stops monitoring a file descriptor.
+ *
+ * @param bsys the object
+ * @param bs {@link BFileDescriptor} object. Must be in active state,
+ *           associated with this reactor. The file descriptor object
+ *           enters not active state.
+ */
+void BReactor_RemoveFileDescriptor (BReactor *bsys, BFileDescriptor *bs);
+
+/**
+ * Sets monitored file descriptor events.
+ *
+ * @param bsys the object
+ * @param bs {@link BFileDescriptor} object. Must be in active state,
+ *           associated with this reactor.
+ * @param events events to watch for. Must not have any bits other than
+ *               BREACTOR_READ and BREACTOR_WRITE.
+ *               This overrides previosly monitored events.
+ */
+void BReactor_SetFileDescriptorEvents (BReactor *bsys, BFileDescriptor *bs, int events);
+
+#endif
+
+typedef struct {
+    BReactor *reactor;
+    int limit;
+    int count;
+    LinkedList1Node active_limits_list_node;
+    DebugObject d_obj;
+} BReactorLimit;
+
+/**
+ * Initializes a limit object.
+ * A limit object consists of a counter integer, which is initialized to
+ * zero, is incremented by {@link BReactorLimit_Increment} up to \a limit,
+ * and is reset to zero every time the event loop performs a wait.
+ * If the event loop has processed all detected events, and before performing
+ * a wait, it determines that timers have expired, the counter will not be reset.
+ * 
+ * @param o the object
+ * @param reactor reactor the object is tied to
+ * @param limit maximum counter value. Must be >0.
+ */
+void BReactorLimit_Init (BReactorLimit *o, BReactor *reactor, int limit);
+
+/**
+ * Frees a limit object.
+ * 
+ * @param o the object
+ */
+void BReactorLimit_Free (BReactorLimit *o);
+
+/**
+ * Attempts to increment the counter of a limit object.
+ * 
+ * @param o the object
+ * @return 1 if the counter was lesser than the limit and was incremented,
+ *         0 if the counter was greater or equal to the limit and could not be
+ *           incremented
+ */
+int BReactorLimit_Increment (BReactorLimit *o);
+
+/**
+ * Sets the limit of a limit object.
+ * 
+ * @param o the object
+ * @param limit new limit. Must be >0.
+ */
+void BReactorLimit_SetLimit (BReactorLimit *o, int limit);
+
+#ifdef BADVPN_USE_KEVENT
+
+typedef void (*BReactorKEvent_handler) (void *user, u_int fflags, intptr_t data);
+
+typedef struct {
+    BReactor *reactor;
+    BReactorKEvent_handler handler;
+    void *user;
+    uintptr_t ident;
+    short filter;
+    int kevent_tag;
+    int **kevent_returned_ptr;
+    DebugObject d_obj;
+} BReactorKEvent;
+
+int BReactorKEvent_Init (BReactorKEvent *o, BReactor *reactor, BReactorKEvent_handler handler, void *user, uintptr_t ident, short filter, u_int fflags, intptr_t data);
+void BReactorKEvent_Free (BReactorKEvent *o);
+
+#endif
+
+#ifdef BADVPN_USE_WINAPI
+
+#define BREACTOR_IOCP_EVENT_SUCCEEDED 1
+#define BREACTOR_IOCP_EVENT_FAILED 2
+#define BREACTOR_IOCP_EVENT_EXITING 3
+
+typedef void (*BReactorIOCPOverlapped_handler) (void *user, int event, DWORD bytes);
+
+typedef struct {
+    OVERLAPPED olap;
+    BReactor *reactor;
+    void *user;
+    BReactorIOCPOverlapped_handler handler;
+    LinkedList1Node iocp_list_node;
+    int is_ready;
+    LinkedList1Node ready_list_node;
+    int ready_succeeded;
+    DWORD ready_bytes;
+    DebugObject d_obj;
+} BReactorIOCPOverlapped;
+
+HANDLE BReactor_GetIOCPHandle (BReactor *reactor);
+
+void BReactorIOCPOverlapped_Init (BReactorIOCPOverlapped *o, BReactor *reactor, void *user, BReactorIOCPOverlapped_handler handler);
+void BReactorIOCPOverlapped_Free (BReactorIOCPOverlapped *o);
+void BReactorIOCPOverlapped_Wait (BReactorIOCPOverlapped *o, int *out_succeeded, DWORD *out_bytes);
+
+#endif
+
+#endif
diff --git a/external/badvpn_dns/system/BReactor_badvpn_timerstree.h b/external/badvpn_dns/system/BReactor_badvpn_timerstree.h
new file mode 100644
index 0000000..3cecd75
--- /dev/null
+++ b/external/badvpn_dns/system/BReactor_badvpn_timerstree.h
@@ -0,0 +1,13 @@
+#define CAVL_PARAM_NAME BReactor__TimersTree
+#define CAVL_PARAM_FEATURE_COUNTS 0
+#define CAVL_PARAM_FEATURE_KEYS_ARE_INDICES 0
+#define CAVL_PARAM_FEATURE_NOKEYS 1
+#define CAVL_PARAM_TYPE_ENTRY struct BSmallTimer_t
+#define CAVL_PARAM_TYPE_LINK BReactor_timerstree_link
+#define CAVL_PARAM_TYPE_ARG int
+#define CAVL_PARAM_VALUE_NULL ((BReactor_timerstree_link)NULL)
+#define CAVL_PARAM_FUN_DEREF(arg, link) (link)
+#define CAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) compare_timers((entry1).link, (entry2).link)
+#define CAVL_PARAM_MEMBER_CHILD u.tree_child
+#define CAVL_PARAM_MEMBER_BALANCE tree_balance
+#define CAVL_PARAM_MEMBER_PARENT tree_parent
diff --git a/external/badvpn_dns/system/BReactor_emscripten.c b/external/badvpn_dns/system/BReactor_emscripten.c
new file mode 100644
index 0000000..f90af55
--- /dev/null
+++ b/external/badvpn_dns/system/BReactor_emscripten.c
@@ -0,0 +1,176 @@
+/**
+ * @file BReactor_emscripten.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <emscripten/emscripten.h>
+
+#include "BReactor_emscripten.h"
+
+#include <misc/debug.h>
+
+#include <generated/blog_channel_BReactor.h>
+
+static void assert_timer (BTimer *bt, BReactor *reactor)
+{
+    ASSERT(bt->active == 0 || bt->active == 1);
+    ASSERT(bt->active == 0 || bt->reactor);
+    ASSERT(bt->active == 0 || (!reactor || bt->reactor == reactor));
+}
+
+static void dispatch_pending (BReactor *o)
+{
+    while (BPendingGroup_HasJobs(&o->pending_jobs)) {
+        BPendingGroup_ExecuteJob(&o->pending_jobs);
+    }
+}
+
+__attribute__((used))
+void breactor_timer_cb (BReactor *reactor, BTimer *bt)
+{
+    assert_timer(bt, reactor);
+    ASSERT(bt->active);
+    ASSERT(!BPendingGroup_HasJobs(&reactor->pending_jobs));
+    
+    bt->active = 0;
+    
+    bt->handler(bt->handler_pointer);
+    dispatch_pending(reactor);
+}
+
+static void small_timer_handler (void *vbt)
+{
+    BSmallTimer *bt = vbt;
+    
+    bt->handler(bt);
+}
+
+void BTimer_Init (BTimer *bt, btime_t msTime, BTimer_handler handler, void *user)
+{
+    bt->msTime = msTime;
+    bt->handler = handler;
+    bt->handler_pointer = user;
+    bt->active = 0;
+}
+
+int BTimer_IsRunning (BTimer *bt)
+{
+    assert_timer(bt, NULL);
+    
+    return bt->active;
+}
+
+void BSmallTimer_Init (BSmallTimer *bt, BSmallTimer_handler handler)
+{
+    BTimer_Init(&bt->timer, 0, small_timer_handler, bt);
+    bt->handler = handler;
+}
+
+int BSmallTimer_IsRunning (BSmallTimer *bt)
+{
+    return BTimer_IsRunning(&bt->timer);
+}
+
+void BReactor_EmscriptenInit (BReactor *bsys)
+{
+    BPendingGroup_Init(&bsys->pending_jobs);
+    
+    DebugObject_Init(&bsys->d_obj);
+}
+
+void BReactor_EmscriptenFree (BReactor *bsys)
+{
+    DebugObject_Free(&bsys->d_obj);
+    ASSERT(!BPendingGroup_HasJobs(&bsys->pending_jobs));
+}
+
+void BReactor_EmscriptenSync (BReactor *bsys)
+{
+    DebugObject_Access(&bsys->d_obj);
+    
+    dispatch_pending(bsys);
+}
+
+BPendingGroup * BReactor_PendingGroup (BReactor *bsys)
+{
+    DebugObject_Access(&bsys->d_obj);
+    
+    return &bsys->pending_jobs;
+}
+
+void BReactor_SetTimer (BReactor *bsys, BTimer *bt)
+{
+    BReactor_SetTimerAfter(bsys, bt, bt->msTime);
+}
+
+void BReactor_SetTimerAfter (BReactor *bsys, BTimer *bt, btime_t after)
+{
+    DebugObject_Access(&bsys->d_obj);
+    assert_timer(bt, bsys);
+    
+    if (bt->active) {
+        BReactor_RemoveTimer(bsys, bt);
+    }
+    
+    char cmd[120];
+    sprintf(cmd, "setTimeout(function(){Module.ccall('breactor_timer_cb','null',['number','number'],[%d,%d]);},%"PRIi64")", (int)bsys, (int)bt, after);
+    
+    bt->active = 1;
+    bt->timerid = emscripten_run_script_int(cmd);
+    bt->reactor = bsys;
+}
+
+void BReactor_RemoveTimer (BReactor *bsys, BTimer *bt)
+{
+    DebugObject_Access(&bsys->d_obj);
+    assert_timer(bt, bsys);
+    
+    if (!bt->active) {
+        return;
+    }
+    
+    char cmd[30];
+    sprintf(cmd, "clearTimeout(%d)", bt->timerid);
+    
+    emscripten_run_script(cmd);
+    bt->active = 0;
+}
+
+void BReactor_SetSmallTimer (BReactor *bsys, BSmallTimer *bt, int mode, btime_t time)
+{
+    ASSERT(mode == BTIMER_SET_RELATIVE)
+    
+    BReactor_SetTimerAfter(bsys, &bt->timer, time);
+}
+
+void BReactor_RemoveSmallTimer (BReactor *bsys, BSmallTimer *bt)
+{
+    BReactor_RemoveTimer(bsys, &bt->timer);
+}
diff --git a/external/badvpn_dns/system/BReactor_emscripten.h b/external/badvpn_dns/system/BReactor_emscripten.h
new file mode 100644
index 0000000..c004d16
--- /dev/null
+++ b/external/badvpn_dns/system/BReactor_emscripten.h
@@ -0,0 +1,87 @@
+/**
+ * @file BReactor_emscripten.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_SYSTEM_BREACTOR_H
+#define BADVPN_SYSTEM_BREACTOR_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+#include <system/BTime.h>
+
+#define BTIMER_SET_RELATIVE 2
+
+typedef struct BReactor_s BReactor;
+
+typedef void (*BTimer_handler) (void *user);
+
+typedef struct BTimer_t {
+    btime_t msTime;
+    BTimer_handler handler;
+    void *handler_pointer;
+    uint8_t active;
+    int timerid;
+    BReactor *reactor;
+} BTimer;
+
+void BTimer_Init (BTimer *bt, btime_t msTime, BTimer_handler handler, void *user);
+int BTimer_IsRunning (BTimer *bt);
+
+struct BSmallTimer_t;
+
+typedef void (*BSmallTimer_handler) (struct BSmallTimer_t *timer);
+
+typedef struct BSmallTimer_t {
+    BTimer timer;
+    BSmallTimer_handler handler;
+} BSmallTimer;
+
+void BSmallTimer_Init (BSmallTimer *bt, BSmallTimer_handler handler);
+int BSmallTimer_IsRunning (BSmallTimer *bt);
+
+struct BReactor_s {
+    BPendingGroup pending_jobs;
+    DebugObject d_obj;
+};
+
+void BReactor_EmscriptenInit (BReactor *bsys);
+void BReactor_EmscriptenFree (BReactor *bsys);
+void BReactor_EmscriptenSync (BReactor *bsys);
+
+BPendingGroup * BReactor_PendingGroup (BReactor *bsys);
+
+void BReactor_SetTimer (BReactor *bsys, BTimer *bt);
+void BReactor_SetTimerAfter (BReactor *bsys, BTimer *bt, btime_t after);
+void BReactor_RemoveTimer (BReactor *bsys, BTimer *bt);
+
+void BReactor_SetSmallTimer (BReactor *bsys, BSmallTimer *bt, int mode, btime_t time);
+void BReactor_RemoveSmallTimer (BReactor *bsys, BSmallTimer *bt);
+
+#endif
diff --git a/external/badvpn_dns/system/BReactor_glib.c b/external/badvpn_dns/system/BReactor_glib.c
new file mode 100644
index 0000000..63f0fd6
--- /dev/null
+++ b/external/badvpn_dns/system/BReactor_glib.c
@@ -0,0 +1,524 @@
+/**
+ * @file BReactor_glib.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 "BReactor_glib.h"
+
+#include <generated/blog_channel_BReactor.h>
+
+struct fd_source {
+    GSource source;
+    BFileDescriptor *bfd;
+};
+
+static void assert_timer (BSmallTimer *bt)
+{
+    ASSERT(bt->is_small == 0 || bt->is_small == 1)
+    ASSERT(bt->active == 0 || bt->active == 1)
+    ASSERT(!bt->active || bt->reactor)
+    ASSERT(!bt->active || bt->source)
+}
+
+static void dispatch_pending (BReactor *o)
+{
+    while (!o->exiting && BPendingGroup_HasJobs(&o->pending_jobs)) {
+        BPendingGroup_ExecuteJob(&o->pending_jobs);
+    }
+}
+
+static void reset_limits (BReactor *o)
+{
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&o->active_limits_list)) {
+        BReactorLimit *limit = UPPER_OBJECT(list_node, BReactorLimit, active_limits_list_node);
+        ASSERT(limit->count > 0)
+        limit->count = 0;
+        LinkedList1_Remove(&o->active_limits_list, &limit->active_limits_list_node);
+    }
+}
+
+static gushort get_glib_wait_events (int ev)
+{
+    gushort gev = G_IO_ERR | G_IO_HUP;
+    
+    if (ev & BREACTOR_READ) {
+        gev |= G_IO_IN;
+    }
+    
+    if (ev & BREACTOR_WRITE) {
+        gev |= G_IO_OUT;
+    }
+    
+    return gev;
+}
+
+static int get_fd_dispatchable_events (BFileDescriptor *bfd)
+{
+    ASSERT(bfd->active)
+    
+    int ev = 0;
+    
+    if ((bfd->waitEvents & BREACTOR_READ) && (bfd->pollfd.revents & G_IO_IN)) {
+        ev |= BREACTOR_READ;
+    }
+    
+    if ((bfd->waitEvents & BREACTOR_WRITE) && (bfd->pollfd.revents & G_IO_OUT)) {
+        ev |= BREACTOR_WRITE;
+    }
+    
+    if ((bfd->pollfd.revents & G_IO_ERR)) {
+        ev |= BREACTOR_ERROR;
+    }
+    
+    if ((bfd->pollfd.revents & G_IO_HUP)) {
+        ev |= BREACTOR_HUP;
+    }
+    
+    return ev;
+}
+
+static gboolean timer_source_handler (gpointer data)
+{
+    BSmallTimer *bt = (void *)data;
+    assert_timer(bt);
+    ASSERT(bt->active)
+    
+    BReactor *reactor = bt->reactor;
+    
+    if (reactor->exiting) {
+        return FALSE;
+    }
+    
+    g_source_destroy(bt->source);
+    g_source_unref(bt->source);
+    bt->active = 0;
+    DebugCounter_Decrement(&reactor->d_timers_ctr);
+    
+    if (bt->is_small) {
+        bt->handler.smalll(bt);
+    } else {
+        BTimer *btimer = UPPER_OBJECT(bt, BTimer, base);
+        bt->handler.heavy(btimer->user);
+    }
+    
+    dispatch_pending(reactor);
+    reset_limits(reactor);
+    
+    return FALSE;
+}
+
+static gboolean fd_source_func_prepare (GSource *source, gint *timeout)
+{
+    BFileDescriptor *bfd = ((struct fd_source *)source)->bfd;
+    ASSERT(bfd->active)
+    ASSERT(bfd->source == source)
+    
+    *timeout = -1;
+    return FALSE;
+}
+
+static gboolean fd_source_func_check (GSource *source)
+{
+    BFileDescriptor *bfd = ((struct fd_source *)source)->bfd;
+    ASSERT(bfd->active)
+    ASSERT(bfd->source == source)
+    
+    return (get_fd_dispatchable_events(bfd) ? TRUE : FALSE);
+}
+
+static gboolean fd_source_func_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
+{
+    BFileDescriptor *bfd = ((struct fd_source *)source)->bfd;
+    BReactor *reactor = bfd->reactor;
+    ASSERT(bfd->active)
+    ASSERT(bfd->source == source)
+    
+    if (reactor->exiting) {
+        return TRUE;
+    }
+    
+    int events = get_fd_dispatchable_events(bfd);
+    if (!events) {
+        return TRUE;
+    }
+    
+    bfd->handler(bfd->user, events);
+    dispatch_pending(reactor);
+    reset_limits(reactor);
+    
+    return TRUE;
+}
+
+void BSmallTimer_Init (BSmallTimer *bt, BSmallTimer_handler handler)
+{
+    bt->handler.smalll = handler;
+    bt->active = 0;
+    bt->is_small = 1;
+}
+
+int BSmallTimer_IsRunning (BSmallTimer *bt)
+{
+    assert_timer(bt);
+    
+    return bt->active;
+}
+
+void BTimer_Init (BTimer *bt, btime_t msTime, BTimer_handler handler, void *user)
+{
+    bt->base.handler.heavy = handler;
+    bt->base.active = 0;
+    bt->base.is_small = 0;
+    bt->user = user;
+    bt->msTime = msTime;
+}
+
+int BTimer_IsRunning (BTimer *bt)
+{
+    return BSmallTimer_IsRunning(&bt->base);
+}
+
+void BFileDescriptor_Init (BFileDescriptor *bs, int fd, BFileDescriptor_handler handler, void *user)
+{
+    bs->fd = fd;
+    bs->handler = handler;
+    bs->user = user;
+    bs->active = 0;
+}
+
+int BReactor_Init (BReactor *bsys)
+{
+    return BReactor_InitFromExistingGMainLoop(bsys, g_main_loop_new(NULL, FALSE), 1);
+}
+
+void BReactor_Free (BReactor *bsys)
+{
+    DebugObject_Free(&bsys->d_obj);
+    DebugCounter_Free(&bsys->d_timers_ctr);
+    DebugCounter_Free(&bsys->d_limits_ctr);
+    DebugCounter_Free(&bsys->d_fds_counter);
+    ASSERT(!BPendingGroup_HasJobs(&bsys->pending_jobs))
+    ASSERT(LinkedList1_IsEmpty(&bsys->active_limits_list))
+    
+    // free job queue
+    BPendingGroup_Free(&bsys->pending_jobs);
+    
+    // unref main loop if needed
+    if (bsys->unref_gloop_on_free) {
+        g_main_loop_unref(bsys->gloop);
+    }
+}
+
+int BReactor_Exec (BReactor *bsys)
+{
+    DebugObject_Access(&bsys->d_obj);
+    
+    // dispatch pending jobs (until exiting) and reset limits
+    dispatch_pending(bsys);
+    reset_limits(bsys);
+    
+    // if exiting, do not enter glib loop
+    if (bsys->exiting) {
+        return bsys->exit_code;
+    }
+    
+    // enter glib loop
+    g_main_loop_run(bsys->gloop);
+    
+    ASSERT(bsys->exiting)
+    
+    return bsys->exit_code;
+}
+
+void BReactor_Quit (BReactor *bsys, int code)
+{
+    DebugObject_Access(&bsys->d_obj);
+    
+    // remember exiting
+    bsys->exiting = 1;
+    bsys->exit_code = code;
+    
+    // request termination of glib loop
+    g_main_loop_quit(bsys->gloop);
+}
+
+void BReactor_SetSmallTimer (BReactor *bsys, BSmallTimer *bt, int mode, btime_t time)
+{
+    DebugObject_Access(&bsys->d_obj);
+    assert_timer(bt);
+    
+    // remove timer if it's already set
+    BReactor_RemoveSmallTimer(bsys, bt);
+    
+    // if mode is absolute, subtract current time
+    if (mode == BTIMER_SET_ABSOLUTE) {
+        btime_t now = btime_gettime();
+        time = (time < now ? 0 : time - now);
+    }
+    
+    // set active and reactor
+    bt->active = 1;
+    bt->reactor = bsys;
+    
+    // init source
+    bt->source = g_timeout_source_new(time);
+    g_source_set_callback(bt->source, timer_source_handler, bt, NULL);
+    g_source_attach(bt->source, g_main_loop_get_context(bsys->gloop));
+    
+    DebugCounter_Increment(&bsys->d_timers_ctr);
+}
+
+void BReactor_RemoveSmallTimer (BReactor *bsys, BSmallTimer *bt)
+{
+    DebugObject_Access(&bsys->d_obj);
+    assert_timer(bt);
+    
+    // do nothing if timer is not active
+    if (!bt->active) {
+        return;
+    }
+    
+    // free source
+    g_source_destroy(bt->source);
+    g_source_unref(bt->source);
+    
+    // set not active
+    bt->active = 0;
+    
+    DebugCounter_Decrement(&bsys->d_timers_ctr);
+}
+
+void BReactor_SetTimer (BReactor *bsys, BTimer *bt)
+{
+    BReactor_SetSmallTimer(bsys, &bt->base, BTIMER_SET_RELATIVE, bt->msTime);
+}
+
+void BReactor_SetTimerAfter (BReactor *bsys, BTimer *bt, btime_t after)
+{
+    BReactor_SetSmallTimer(bsys, &bt->base, BTIMER_SET_RELATIVE, after);
+}
+
+void BReactor_SetTimerAbsolute (BReactor *bsys, BTimer *bt, btime_t time)
+{
+    BReactor_SetSmallTimer(bsys, &bt->base, BTIMER_SET_ABSOLUTE, time);
+}
+
+void BReactor_RemoveTimer (BReactor *bsys, BTimer *bt)
+{
+    return BReactor_RemoveSmallTimer(bsys, &bt->base);
+}
+
+BPendingGroup * BReactor_PendingGroup (BReactor *bsys)
+{
+    DebugObject_Access(&bsys->d_obj);
+    
+    return &bsys->pending_jobs;
+}
+
+int BReactor_Synchronize (BReactor *bsys, BSmallPending *ref)
+{
+    DebugObject_Access(&bsys->d_obj);
+    ASSERT(ref)
+    
+    while (!bsys->exiting) {
+        ASSERT(BPendingGroup_HasJobs(&bsys->pending_jobs))
+        
+        if (BPendingGroup_PeekJob(&bsys->pending_jobs) == ref) {
+            return 1;
+        }
+        
+        BPendingGroup_ExecuteJob(&bsys->pending_jobs);
+    }
+    
+    return 0;
+}
+
+int BReactor_AddFileDescriptor (BReactor *bsys, BFileDescriptor *bs)
+{
+    DebugObject_Access(&bsys->d_obj);
+    ASSERT(!bs->active)
+    
+    // set active, no wait events, and set reactor
+    bs->active = 1;
+    bs->waitEvents = 0;
+    bs->reactor = bsys;
+    
+    // create source
+    bs->source = g_source_new(&bsys->fd_source_funcs, sizeof(struct fd_source));
+    ((struct fd_source *)bs->source)->bfd = bs;
+    
+    // init pollfd
+    bs->pollfd.fd = bs->fd;
+    bs->pollfd.events = get_glib_wait_events(bs->waitEvents);
+    bs->pollfd.revents = 0;
+    
+    // start source
+    g_source_add_poll(bs->source, &bs->pollfd);
+    g_source_attach(bs->source, g_main_loop_get_context(bsys->gloop));
+    
+    DebugCounter_Increment(&bsys->d_fds_counter);
+    return 1;
+}
+
+void BReactor_RemoveFileDescriptor (BReactor *bsys, BFileDescriptor *bs)
+{
+    DebugObject_Access(&bsys->d_obj);
+    DebugCounter_Decrement(&bsys->d_fds_counter);
+    ASSERT(bs->active)
+    
+    // free source
+    g_source_destroy(bs->source);
+    g_source_unref(bs->source);
+    
+    // set not active
+    bs->active = 0;
+}
+
+void BReactor_SetFileDescriptorEvents (BReactor *bsys, BFileDescriptor *bs, int events)
+{
+    DebugObject_Access(&bsys->d_obj);
+    ASSERT(bs->active)
+    ASSERT(!(events&~(BREACTOR_READ|BREACTOR_WRITE)))
+    
+    // set new wait events
+    bs->waitEvents = events;
+    
+    // update pollfd wait events
+    bs->pollfd.events = get_glib_wait_events(bs->waitEvents);
+}
+
+int BReactor_InitFromExistingGMainLoop (BReactor *bsys, GMainLoop *gloop, int unref_gloop_on_free)
+{
+    ASSERT(gloop)
+    ASSERT(unref_gloop_on_free == !!unref_gloop_on_free)
+    
+    // set not exiting
+    bsys->exiting = 0;
+    
+    // set gloop and unref on free flag
+    bsys->gloop = gloop;
+    bsys->unref_gloop_on_free = unref_gloop_on_free;
+    
+    // init fd source functions table
+    memset(&bsys->fd_source_funcs, 0, sizeof(bsys->fd_source_funcs));
+    bsys->fd_source_funcs.prepare = fd_source_func_prepare;
+    bsys->fd_source_funcs.check = fd_source_func_check;
+    bsys->fd_source_funcs.dispatch = fd_source_func_dispatch;
+    bsys->fd_source_funcs.finalize = NULL;
+    
+    // init job queue
+    BPendingGroup_Init(&bsys->pending_jobs);
+    
+    // init active limits list
+    LinkedList1_Init(&bsys->active_limits_list);
+    
+    DebugCounter_Init(&bsys->d_fds_counter);
+    DebugCounter_Init(&bsys->d_limits_ctr);
+    DebugCounter_Init(&bsys->d_timers_ctr);
+    DebugObject_Init(&bsys->d_obj);
+    return 1;
+}
+
+GMainLoop * BReactor_GetGMainLoop (BReactor *bsys)
+{
+    DebugObject_Access(&bsys->d_obj);
+    
+    return bsys->gloop;
+}
+
+int BReactor_SynchronizeAll (BReactor *bsys)
+{
+    DebugObject_Access(&bsys->d_obj);
+    
+    dispatch_pending(bsys);
+    
+    return !bsys->exiting;
+}
+
+void BReactorLimit_Init (BReactorLimit *o, BReactor *reactor, int limit)
+{
+    DebugObject_Access(&reactor->d_obj);
+    ASSERT(limit > 0)
+    
+    // init arguments
+    o->reactor = reactor;
+    o->limit = limit;
+    
+    // set count zero
+    o->count = 0;
+    
+    DebugCounter_Increment(&reactor->d_limits_ctr);
+    DebugObject_Init(&o->d_obj);
+}
+
+void BReactorLimit_Free (BReactorLimit *o)
+{
+    BReactor *reactor = o->reactor;
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Decrement(&reactor->d_limits_ctr);
+    
+    // remove from active limits list
+    if (o->count > 0) {
+        LinkedList1_Remove(&reactor->active_limits_list, &o->active_limits_list_node);
+    }
+}
+
+int BReactorLimit_Increment (BReactorLimit *o)
+{
+    BReactor *reactor = o->reactor;
+    DebugObject_Access(&o->d_obj);
+    
+    // check count against limit
+    if (o->count >= o->limit) {
+        return 0;
+    }
+    
+    // increment count
+    o->count++;
+    
+    // if limit was zero, add to active limits list
+    if (o->count == 1) {
+        LinkedList1_Append(&reactor->active_limits_list, &o->active_limits_list_node);
+    }
+    
+    return 1;
+}
+
+void BReactorLimit_SetLimit (BReactorLimit *o, int limit)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(limit > 0)
+    
+    // set limit
+    o->limit = limit;
+}
diff --git a/external/badvpn_dns/system/BReactor_glib.h b/external/badvpn_dns/system/BReactor_glib.h
new file mode 100644
index 0000000..0102be9
--- /dev/null
+++ b/external/badvpn_dns/system/BReactor_glib.h
@@ -0,0 +1,148 @@
+/**
+ * @file BReactor_glib.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_SYSTEM_BREACTOR_H
+#define BADVPN_SYSTEM_BREACTOR_H
+
+#include <stdint.h>
+
+#include <glib.h>
+
+#include <misc/debug.h>
+#include <misc/debugcounter.h>
+#include <misc/offset.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+#include <system/BTime.h>
+
+typedef struct BReactor_s BReactor;
+
+struct BSmallTimer_t;
+
+#define BTIMER_SET_ABSOLUTE 1
+#define BTIMER_SET_RELATIVE 2
+
+typedef void (*BSmallTimer_handler) (struct BSmallTimer_t *timer);
+typedef void (*BTimer_handler) (void *user);
+
+typedef struct BSmallTimer_t {
+    union {
+        BSmallTimer_handler smalll; // MSVC doesn't like "small"
+        BTimer_handler heavy;
+    } handler;
+    BReactor *reactor;
+    GSource *source;
+    uint8_t active;
+    uint8_t is_small;
+} BSmallTimer;
+
+void BSmallTimer_Init (BSmallTimer *bt, BSmallTimer_handler handler);
+int BSmallTimer_IsRunning (BSmallTimer *bt);
+
+typedef struct {
+    BSmallTimer base;
+    void *user;
+    btime_t msTime;
+} BTimer;
+
+void BTimer_Init (BTimer *bt, btime_t msTime, BTimer_handler handler, void *user);
+int BTimer_IsRunning (BTimer *bt);
+
+struct BFileDescriptor_t;
+
+#define BREACTOR_READ (1 << 0)
+#define BREACTOR_WRITE (1 << 1)
+#define BREACTOR_ERROR (1 << 2)
+#define BREACTOR_HUP (1 << 3)
+
+typedef void (*BFileDescriptor_handler) (void *user, int events);
+
+typedef struct BFileDescriptor_t {
+    int fd;
+    BFileDescriptor_handler handler;
+    void *user;
+    int active;
+    int waitEvents;
+    BReactor *reactor;
+    GSource *source;
+    GPollFD pollfd;
+} BFileDescriptor;
+
+void BFileDescriptor_Init (BFileDescriptor *bs, int fd, BFileDescriptor_handler handler, void *user);
+
+struct BReactor_s {
+    int exiting;
+    int exit_code;
+    GMainLoop *gloop;
+    int unref_gloop_on_free;
+    GSourceFuncs fd_source_funcs;
+    BPendingGroup pending_jobs;
+    LinkedList1 active_limits_list;
+    
+    DebugCounter d_fds_counter;
+    DebugCounter d_limits_ctr;
+    DebugCounter d_timers_ctr;
+    DebugObject d_obj;
+};
+
+int BReactor_Init (BReactor *bsys) WARN_UNUSED;
+void BReactor_Free (BReactor *bsys);
+int BReactor_Exec (BReactor *bsys);
+void BReactor_Quit (BReactor *bsys, int code);
+void BReactor_SetSmallTimer (BReactor *bsys, BSmallTimer *bt, int mode, btime_t time);
+void BReactor_RemoveSmallTimer (BReactor *bsys, BSmallTimer *bt);
+void BReactor_SetTimer (BReactor *bsys, BTimer *bt);
+void BReactor_SetTimerAfter (BReactor *bsys, BTimer *bt, btime_t after);
+void BReactor_SetTimerAbsolute (BReactor *bsys, BTimer *bt, btime_t time);
+void BReactor_RemoveTimer (BReactor *bsys, BTimer *bt);
+BPendingGroup * BReactor_PendingGroup (BReactor *bsys);
+int BReactor_Synchronize (BReactor *bsys, BSmallPending *ref);
+int BReactor_AddFileDescriptor (BReactor *bsys, BFileDescriptor *bs) WARN_UNUSED;
+void BReactor_RemoveFileDescriptor (BReactor *bsys, BFileDescriptor *bs);
+void BReactor_SetFileDescriptorEvents (BReactor *bsys, BFileDescriptor *bs, int events);
+
+int BReactor_InitFromExistingGMainLoop (BReactor *bsys, GMainLoop *gloop, int unref_gloop_on_free);
+GMainLoop * BReactor_GetGMainLoop (BReactor *bsys);
+int BReactor_SynchronizeAll (BReactor *bsys);
+
+typedef struct {
+    BReactor *reactor;
+    int limit;
+    int count;
+    LinkedList1Node active_limits_list_node;
+    DebugObject d_obj;
+} BReactorLimit;
+
+void BReactorLimit_Init (BReactorLimit *o, BReactor *reactor, int limit);
+void BReactorLimit_Free (BReactorLimit *o);
+int BReactorLimit_Increment (BReactorLimit *o);
+void BReactorLimit_SetLimit (BReactorLimit *o, int limit);
+
+#endif
diff --git a/external/badvpn_dns/system/BSignal.c b/external/badvpn_dns/system/BSignal.c
new file mode 100644
index 0000000..520a167
--- /dev/null
+++ b/external/badvpn_dns/system/BSignal.c
@@ -0,0 +1,188 @@
+/**
+ * @file BSignal.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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.
+ */
+
+#ifdef BADVPN_USE_WINAPI
+#include <windows.h>
+#else
+#include <signal.h>
+#include <system/BUnixSignal.h>
+#endif
+
+#include <misc/debug.h>
+#include <base/BLog.h>
+
+#include <system/BSignal.h>
+
+#include <generated/blog_channel_BSignal.h>
+
+static struct {
+    int initialized;
+    int finished;
+    BReactor *reactor;
+    BSignal_handler handler;
+    void *user;
+    #ifdef BADVPN_USE_WINAPI
+    BReactorIOCPOverlapped olap;
+    CRITICAL_SECTION iocp_handle_mutex;
+    HANDLE iocp_handle;
+    #else
+    BUnixSignal signal;
+    #endif
+} bsignal_global = {
+    0
+};
+
+#ifdef BADVPN_USE_WINAPI
+
+static void olap_handler (void *user, int event, DWORD bytes)
+{
+    ASSERT(bsignal_global.initialized)
+    ASSERT(!(event == BREACTOR_IOCP_EVENT_EXITING) || bsignal_global.finished)
+    
+    if (event == BREACTOR_IOCP_EVENT_EXITING) {
+        BReactorIOCPOverlapped_Free(&bsignal_global.olap);
+        return;
+    }
+    
+    if (!bsignal_global.finished) {
+        // call handler
+        bsignal_global.handler(bsignal_global.user);
+        return;
+    }
+}
+
+static BOOL WINAPI ctrl_handler (DWORD type)
+{
+    EnterCriticalSection(&bsignal_global.iocp_handle_mutex);
+    
+    if (bsignal_global.iocp_handle) {
+        PostQueuedCompletionStatus(bsignal_global.iocp_handle, 0, 0, &bsignal_global.olap.olap);
+    }
+    
+    LeaveCriticalSection(&bsignal_global.iocp_handle_mutex);
+    
+    return TRUE;
+}
+
+#else
+
+static void unix_signal_handler (void *user, int signo)
+{
+    ASSERT(signo == SIGTERM || signo == SIGINT)
+    ASSERT(bsignal_global.initialized)
+    ASSERT(!bsignal_global.finished)
+    
+    BLog(BLOG_DEBUG, "Dispatching signal");
+    
+    // call handler
+    bsignal_global.handler(bsignal_global.user);
+    return;
+}
+
+#endif
+
+int BSignal_Init (BReactor *reactor, BSignal_handler handler, void *user) 
+{
+    ASSERT(!bsignal_global.initialized)
+    
+    // init arguments
+    bsignal_global.reactor = reactor;
+    bsignal_global.handler = handler;
+    bsignal_global.user = user;
+    
+    BLog(BLOG_DEBUG, "BSignal initializing");
+    
+    #ifdef BADVPN_USE_WINAPI
+    
+    // init olap
+    BReactorIOCPOverlapped_Init(&bsignal_global.olap, bsignal_global.reactor, NULL, olap_handler);
+    
+    // init handler mutex
+    InitializeCriticalSection(&bsignal_global.iocp_handle_mutex);
+    
+    // remember IOCP handle
+    bsignal_global.iocp_handle = BReactor_GetIOCPHandle(bsignal_global.reactor);
+    
+    // configure ctrl handler
+    if (!SetConsoleCtrlHandler(ctrl_handler, TRUE)) {
+        BLog(BLOG_ERROR, "SetConsoleCtrlHandler failed");
+        goto fail1;
+    }
+    
+    #else
+    
+    sigset_t sset;
+    ASSERT_FORCE(sigemptyset(&sset) == 0)
+    ASSERT_FORCE(sigaddset(&sset, SIGTERM) == 0)
+    ASSERT_FORCE(sigaddset(&sset, SIGINT) == 0)
+    
+    // init BUnixSignal
+    if (!BUnixSignal_Init(&bsignal_global.signal, bsignal_global.reactor, sset, unix_signal_handler, NULL)) {
+        BLog(BLOG_ERROR, "BUnixSignal_Init failed");
+        goto fail0;
+    }
+    
+    #endif
+    
+    bsignal_global.initialized = 1;
+    bsignal_global.finished = 0;
+    
+    return 1;
+    
+    #ifdef BADVPN_USE_WINAPI
+fail1:
+    DeleteCriticalSection(&bsignal_global.iocp_handle_mutex);
+    BReactorIOCPOverlapped_Free(&bsignal_global.olap);
+    #endif
+    
+fail0:
+    return 0;
+}
+
+void BSignal_Finish (void)
+{
+    ASSERT(bsignal_global.initialized)
+    ASSERT(!bsignal_global.finished)
+    
+    #ifdef BADVPN_USE_WINAPI
+    
+    // forget IOCP handle
+    EnterCriticalSection(&bsignal_global.iocp_handle_mutex);
+    bsignal_global.iocp_handle = NULL;
+    LeaveCriticalSection(&bsignal_global.iocp_handle_mutex);
+    
+    #else
+    
+    // free BUnixSignal
+    BUnixSignal_Free(&bsignal_global.signal, 0);
+    
+    #endif
+    
+    bsignal_global.finished = 1;
+}
diff --git a/external/badvpn_dns/system/BSignal.h b/external/badvpn_dns/system/BSignal.h
new file mode 100644
index 0000000..41f17e2
--- /dev/null
+++ b/external/badvpn_dns/system/BSignal.h
@@ -0,0 +1,64 @@
+/**
+ * @file BSignal.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 catching program termination requests.
+ */
+
+#ifndef BADVPN_SYSTEM_BSIGNAL_H
+#define BADVPN_SYSTEM_BSIGNAL_H
+
+#include <misc/debug.h>
+#include <system/BReactor.h>
+
+typedef void (*BSignal_handler) (void *user);
+
+/**
+ * Initializes signal handling.
+ * The object is created in not capturing state.
+ * {@link BLog_Init} must have been done.
+ * 
+ * WARNING: make sure this won't interfere with other components:
+ *   - on Linux, this uses {@link BUnixSignal} to catch SIGTERM and SIGINT,
+ *   - on Windows, this sets up a handler with SetConsoleCtrlHandler.
+ *
+ * @param reactor {@link BReactor} from which the handler will be called
+ * @param handler callback function invoked from the reactor
+ * @param user value passed to callback function
+ * @return 1 on success, 0 on failure
+ */
+int BSignal_Init (BReactor *reactor, BSignal_handler handler, void *user) WARN_UNUSED;
+
+/**
+ * Finishes signal handling.
+ * {@link BSignal_Init} must not be called again.
+ */
+void BSignal_Finish (void);
+
+#endif
diff --git a/external/badvpn_dns/system/BThreadSignal.c b/external/badvpn_dns/system/BThreadSignal.c
new file mode 100644
index 0000000..7d68c1b
--- /dev/null
+++ b/external/badvpn_dns/system/BThreadSignal.c
@@ -0,0 +1,136 @@
+/**
+ * @file BThreadSignal.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <errno.h>
+#include <unistd.h>
+
+#include <misc/debug.h>
+#include <misc/nonblocking.h>
+#include <base/BLog.h>
+
+#include "BThreadSignal.h"
+
+#include <generated/blog_channel_BThreadSignal.h>
+
+static void bfd_handler (void *user, int events)
+{
+    BThreadSignal *o = user;
+    DebugObject_Access(&o->d_obj);
+    
+    char byte;
+    ssize_t res = read(o->pipe[0], &byte, sizeof(byte));
+    
+    if (res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+        return;
+    }
+    
+    if (res < 0) {
+        BLog(BLOG_ERROR, "read failed");
+        return;
+    }
+    
+    if (res != sizeof(byte)) {
+        BLog(BLOG_ERROR, "bad read");
+        return;
+    }
+    
+    o->handler(o);
+}
+
+int BThreadSignal_Init (BThreadSignal *o, BReactor *reactor, BThreadSignal_handler handler)
+{
+    o->reactor = reactor;
+    o->handler = handler;
+    
+    if (pipe(o->pipe) < 0) {
+        BLog(BLOG_ERROR, "pipe failed");
+        goto fail0;
+    }
+    
+    if (!badvpn_set_nonblocking(o->pipe[0]) || !badvpn_set_nonblocking(o->pipe[1])) {
+        BLog(BLOG_ERROR, "badvpn_set_nonblocking failed");
+        goto fail1;
+    }
+    
+    BFileDescriptor_Init(&o->bfd, o->pipe[0], bfd_handler, o);
+    
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, BREACTOR_READ);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    if (close(o->pipe[1]) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+    if (close(o->pipe[0]) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+fail0:
+    return 0;
+}
+
+void BThreadSignal_Free (BThreadSignal *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+    
+    if (close(o->pipe[1]) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+    if (close(o->pipe[0]) < 0) {
+        BLog(BLOG_ERROR, "close failed");
+    }
+}
+
+int BThreadSignal_Thread_Signal (BThreadSignal *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    char byte = 0;
+    ssize_t res = write(o->pipe[1], &byte, sizeof(byte));
+    
+    if (res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+        return 0;
+    }
+    
+    if (res < 0) {
+        BLog(BLOG_ERROR, "write failed");
+    } else if (res != sizeof(byte)) {
+        BLog(BLOG_ERROR, "bad write");
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/system/BThreadSignal.h b/external/badvpn_dns/system/BThreadSignal.h
new file mode 100644
index 0000000..2c8d816
--- /dev/null
+++ b/external/badvpn_dns/system/BThreadSignal.h
@@ -0,0 +1,53 @@
+/**
+ * @file BThreadSignal.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_B_THREAD_SIGNAL_H
+#define BADVPN_B_THREAD_SIGNAL_H
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+
+typedef struct BThreadSignal_s BThreadSignal;
+
+typedef void (*BThreadSignal_handler) (BThreadSignal *thread_signal);
+
+struct BThreadSignal_s {
+    BReactor *reactor;
+    BThreadSignal_handler handler;
+    int pipe[2];
+    BFileDescriptor bfd;
+    DebugObject d_obj;
+};
+
+int BThreadSignal_Init (BThreadSignal *o, BReactor *reactor, BThreadSignal_handler handler) WARN_UNUSED;
+void BThreadSignal_Free (BThreadSignal *o);
+int BThreadSignal_Thread_Signal (BThreadSignal *o);
+
+#endif
diff --git a/external/badvpn_dns/system/BTime.c b/external/badvpn_dns/system/BTime.c
new file mode 100644
index 0000000..a1fa9e7
--- /dev/null
+++ b/external/badvpn_dns/system/BTime.c
@@ -0,0 +1,38 @@
+/**
+ * @file BTime.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <system/BTime.h>
+
+#ifndef BADVPN_PLUGIN
+struct _BTime_global btime_global = {
+    #ifndef NDEBUG
+    0
+    #endif
+};
+#endif
diff --git a/external/badvpn_dns/system/BTime.h b/external/badvpn_dns/system/BTime.h
new file mode 100644
index 0000000..6998814
--- /dev/null
+++ b/external/badvpn_dns/system/BTime.h
@@ -0,0 +1,163 @@
+/**
+ * @file BTime.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * System time abstraction used by {@link BReactor}.
+ */
+
+#ifndef BADVPN_SYSTEM_BTIME_H
+#define BADVPN_SYSTEM_BTIME_H
+
+#if defined(BADVPN_USE_WINAPI)
+#include <windows.h>
+#elif defined(BADVPN_EMSCRIPTEN)
+#include <emscripten/emscripten.h>
+#else
+#include <time.h>
+#include <sys/time.h>
+#endif
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/overflow.h>
+#include <base/BLog.h>
+
+#include <generated/blog_channel_BTime.h>
+
+typedef int64_t btime_t;
+
+#define BTIME_MIN INT64_MIN
+
+struct _BTime_global {
+    #ifndef NDEBUG
+    int initialized; // initialized statically
+    #endif
+    #if defined(BADVPN_USE_WINAPI)
+    LARGE_INTEGER start_time;
+    #elif defined(BADVPN_EMSCRIPTEN)
+    btime_t start_time;
+    #else
+    btime_t start_time;
+    int use_gettimeofday;
+    #endif
+};
+
+extern struct _BTime_global btime_global;
+
+static void BTime_Init (void)
+{
+    ASSERT(!btime_global.initialized)
+    
+    #if defined(BADVPN_USE_WINAPI)
+    
+    ASSERT_FORCE(QueryPerformanceCounter(&btime_global.start_time))
+    
+    #elif defined(BADVPN_EMSCRIPTEN)
+    
+    btime_global.start_time = emscripten_get_now();
+    
+    #else
+    
+    struct timespec ts;
+    if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
+        BLog(BLOG_WARNING, "CLOCK_MONOTONIC is not available. Timers will be confused by clock changes.");
+        
+        struct timeval tv;
+        ASSERT_FORCE(gettimeofday(&tv, NULL) == 0)
+        
+        btime_global.start_time = (int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec/1000;
+        btime_global.use_gettimeofday = 1;
+    } else {
+        btime_global.start_time = (int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec/1000000;
+        btime_global.use_gettimeofday = 0;
+    }
+    
+    #endif
+    
+    #ifndef NDEBUG
+    btime_global.initialized = 1;
+    #endif
+}
+
+static btime_t btime_gettime (void)
+{
+    ASSERT(btime_global.initialized)
+    
+    #if defined(BADVPN_USE_WINAPI)
+    
+    LARGE_INTEGER count;
+    LARGE_INTEGER freq;
+    ASSERT_FORCE(QueryPerformanceCounter(&count))
+    ASSERT_FORCE(QueryPerformanceFrequency(&freq))
+    return (((count.QuadPart - btime_global.start_time.QuadPart) * 1000) / freq.QuadPart);
+    
+    #elif defined(BADVPN_EMSCRIPTEN)
+    
+    return (btime_t)emscripten_get_now() - btime_global.start_time;
+    
+    #else
+    
+    if (btime_global.use_gettimeofday) {
+        struct timeval tv;
+        ASSERT_FORCE(gettimeofday(&tv, NULL) == 0)
+        return ((int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec/1000);
+    } else {
+        struct timespec ts;
+        ASSERT_FORCE(clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
+        return (((int64_t)ts.tv_sec * 1000 + (int64_t)ts.tv_nsec/1000000) - btime_global.start_time);
+    }
+    
+    #endif
+}
+
+static btime_t btime_add (btime_t t1, btime_t t2)
+{
+    // handle overflow
+    int overflows = add_int64_overflows(t1, t2);
+    btime_t sum;
+    if (overflows != 0) {
+        if (overflows > 0) {
+            sum = INT64_MAX;
+        } else {
+            sum = INT64_MIN;
+        }
+    } else {
+        sum = t1 + t2;
+    }
+    
+    return sum;
+}
+
+static btime_t btime_getpast (void)
+{
+    return INT64_MIN;
+}
+
+#endif
diff --git a/external/badvpn_dns/system/BUnixSignal.c b/external/badvpn_dns/system/BUnixSignal.c
new file mode 100644
index 0000000..94edd6b
--- /dev/null
+++ b/external/badvpn_dns/system/BUnixSignal.c
@@ -0,0 +1,406 @@
+/**
+ * @file BUnixSignal.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+
+#ifdef BADVPN_USE_SIGNALFD
+#include <sys/signalfd.h>
+#endif
+
+#include <misc/balloc.h>
+#include <misc/nonblocking.h>
+#include <base/BLog.h>
+
+#include <system/BUnixSignal.h>
+
+#include <generated/blog_channel_BUnixSignal.h>
+
+#define BUNIXSIGNAL_MAX_SIGNALS 64
+
+#ifdef BADVPN_USE_SIGNALFD
+
+static void signalfd_handler (BUnixSignal *o, int events)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // read a signal
+    struct signalfd_siginfo siginfo;
+    int bytes = read(o->signalfd_fd, &siginfo, sizeof(siginfo));
+    if (bytes < 0) {
+        int error = errno;
+        if (error == EAGAIN || error == EWOULDBLOCK) {
+            return;
+        }
+        BLog(BLOG_ERROR, "read failed (%d)", error);
+        return;
+    }
+    ASSERT_FORCE(bytes == sizeof(siginfo))
+    
+    // check signal
+    if (siginfo.ssi_signo > INT_MAX) {
+        BLog(BLOG_ERROR, "read returned out of int range signo (%"PRIu32")", siginfo.ssi_signo);
+        return;
+    }
+    int signo = siginfo.ssi_signo;
+    if (sigismember(&o->signals, signo) <= 0) {
+        BLog(BLOG_ERROR, "read returned wrong signo (%d)", signo);
+        return;
+    }
+    
+    BLog(BLOG_DEBUG, "dispatching signal %d", signo);
+    
+    // call handler
+    o->handler(o->user, signo);
+    return;
+}
+
+#endif
+
+#ifdef BADVPN_USE_KEVENT
+
+static void kevent_handler (struct BUnixSignal_kevent_entry *entry, u_int fflags, intptr_t data)
+{
+    BUnixSignal *o = entry->parent;
+    DebugObject_Access(&o->d_obj);
+    
+    // call signal
+    o->handler(o->user, entry->signo);
+    return;
+}
+
+#endif
+
+#ifdef BADVPN_USE_SELFPIPE
+
+struct BUnixSignal_selfpipe_entry *bunixsignal_selfpipe_entries[BUNIXSIGNAL_MAX_SIGNALS];
+
+static void free_selfpipe_entry (struct BUnixSignal_selfpipe_entry *entry)
+{
+    BUnixSignal *o = entry->parent;
+    
+    // uninstall signal handler
+    struct sigaction act;
+    memset(&act, 0, sizeof(act));
+    act.sa_handler = SIG_DFL;
+    sigemptyset(&act.sa_mask);
+    ASSERT_FORCE(sigaction(entry->signo, &act, NULL) == 0)
+    
+    // free BFileDescriptor
+    BReactor_RemoveFileDescriptor(o->reactor, &entry->pipe_read_bfd);
+    
+    // close pipe
+    ASSERT_FORCE(close(entry->pipefds[0]) == 0)
+    ASSERT_FORCE(close(entry->pipefds[1]) == 0)
+}
+
+static void pipe_read_fd_handler (struct BUnixSignal_selfpipe_entry *entry, int events)
+{
+    BUnixSignal *o = entry->parent;
+    DebugObject_Access(&o->d_obj);
+    
+    // read a byte
+    uint8_t b;
+    if (read(entry->pipefds[0], &b, sizeof(b)) < 0) {
+        int error = errno;
+        if (error == EAGAIN || error == EWOULDBLOCK) {
+            return;
+        }
+        BLog(BLOG_ERROR, "read failed (%d)", error);
+        return;
+    }
+    
+    // call handler
+    o->handler(o->user, entry->signo);
+    return;
+}
+
+static void signal_handler (int signo)
+{
+    ASSERT(signo >= 0)
+    ASSERT(signo < BUNIXSIGNAL_MAX_SIGNALS)
+    
+    struct BUnixSignal_selfpipe_entry *entry = bunixsignal_selfpipe_entries[signo];
+    
+    uint8_t b = 0;
+    write(entry->pipefds[1], &b, sizeof(b));
+}
+
+#endif
+
+int BUnixSignal_Init (BUnixSignal *o, BReactor *reactor, sigset_t signals, BUnixSignal_handler handler, void *user)
+{
+    // init arguments
+    o->reactor = reactor;
+    o->signals = signals;
+    o->handler = handler;
+    o->user = user;
+    
+    #ifdef BADVPN_USE_SIGNALFD
+    
+    // init signalfd fd
+    if ((o->signalfd_fd = signalfd(-1, &o->signals, 0)) < 0) {
+        BLog(BLOG_ERROR, "signalfd failed");
+        goto fail0;
+    }
+    
+    // set non-blocking
+    if (fcntl(o->signalfd_fd, F_SETFL, O_NONBLOCK) < 0) {
+        BLog(BLOG_ERROR, "cannot set non-blocking");
+        goto fail1;
+    }
+    
+    // init signalfd BFileDescriptor
+    BFileDescriptor_Init(&o->signalfd_bfd, o->signalfd_fd, (BFileDescriptor_handler)signalfd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->signalfd_bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    BReactor_SetFileDescriptorEvents(o->reactor, &o->signalfd_bfd, BREACTOR_READ);
+    
+    // block signals
+    if (sigprocmask(SIG_BLOCK, &o->signals, 0) < 0) {
+        BLog(BLOG_ERROR, "sigprocmask block failed");
+        goto fail2;
+    }
+    
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    
+    // count signals
+    int num_signals = 0;
+    for (int i = 0; i < BUNIXSIGNAL_MAX_SIGNALS; i++) {
+        if (!sigismember(&o->signals, i)) {
+            continue;
+        }
+        num_signals++;
+    }
+    
+    // allocate array
+    if (!(o->entries = BAllocArray(num_signals, sizeof(o->entries[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail0;
+    }
+    
+    // init kevents
+    o->num_entries = 0;
+    for (int i = 0; i < BUNIXSIGNAL_MAX_SIGNALS; i++) {
+        if (!sigismember(&o->signals, i)) {
+            continue;
+        }
+        struct BUnixSignal_kevent_entry *entry = &o->entries[o->num_entries];
+        entry->parent = o;
+        entry->signo = i;
+        if (!BReactorKEvent_Init(&entry->kevent, o->reactor, (BReactorKEvent_handler)kevent_handler, entry, entry->signo, EVFILT_SIGNAL, 0, 0)) {
+            BLog(BLOG_ERROR, "BReactorKEvent_Init failed");
+            goto fail2;
+        }
+        o->num_entries++;
+    }
+    
+    // block signals
+    if (sigprocmask(SIG_BLOCK, &o->signals, 0) < 0) {
+        BLog(BLOG_ERROR, "sigprocmask block failed");
+        goto fail2;
+    }
+    
+    #endif
+    
+    #ifdef BADVPN_USE_SELFPIPE
+    
+    // count signals
+    int num_signals = 0;
+    for (int i = 1; i < BUNIXSIGNAL_MAX_SIGNALS; i++) {
+        if (!sigismember(&o->signals, i)) {
+            continue;
+        }
+        num_signals++;
+    }
+    
+    // allocate array
+    if (!(o->entries = BAllocArray(num_signals, sizeof(o->entries[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail0;
+    }
+    
+    // init entries
+    o->num_entries = 0;
+    for (int i = 1; i < BUNIXSIGNAL_MAX_SIGNALS; i++) {
+        if (!sigismember(&o->signals, i)) {
+            continue;
+        }
+        
+        struct BUnixSignal_selfpipe_entry *entry = &o->entries[o->num_entries];
+        entry->parent = o;
+        entry->signo = i;
+        
+        // init pipe
+        if (pipe(entry->pipefds) < 0) {
+            BLog(BLOG_ERROR, "pipe failed");
+            goto loop_fail0;
+        }
+        
+        // set pipe ends non-blocking
+        if (!badvpn_set_nonblocking(entry->pipefds[0]) || !badvpn_set_nonblocking(entry->pipefds[1])) {
+            BLog(BLOG_ERROR, "set nonblocking failed");
+            goto loop_fail1;
+        }
+        
+        // init read end BFileDescriptor
+        BFileDescriptor_Init(&entry->pipe_read_bfd, entry->pipefds[0], (BFileDescriptor_handler)pipe_read_fd_handler, entry);
+        if (!BReactor_AddFileDescriptor(o->reactor, &entry->pipe_read_bfd)) {
+            BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+            goto loop_fail1;
+        }
+        BReactor_SetFileDescriptorEvents(o->reactor, &entry->pipe_read_bfd, BREACTOR_READ);
+        
+        // set global entry pointer
+        bunixsignal_selfpipe_entries[entry->signo] = entry;
+        
+        // install signal handler
+        struct sigaction act;
+        memset(&act, 0, sizeof(act));
+        act.sa_handler = signal_handler;
+        sigemptyset(&act.sa_mask);
+        if (sigaction(entry->signo, &act, NULL) < 0) {
+            BLog(BLOG_ERROR, "sigaction failed");
+            goto loop_fail2;
+        }
+        
+        o->num_entries++;
+        
+        continue;
+        
+    loop_fail2:
+        BReactor_RemoveFileDescriptor(o->reactor, &entry->pipe_read_bfd);
+    loop_fail1:
+        ASSERT_FORCE(close(entry->pipefds[0]) == 0)
+        ASSERT_FORCE(close(entry->pipefds[1]) == 0)
+    loop_fail0:
+        goto fail2;
+    }
+    
+    #endif
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+    #ifdef BADVPN_USE_SIGNALFD
+fail2:
+    BReactor_RemoveFileDescriptor(o->reactor, &o->signalfd_bfd);
+fail1:
+    ASSERT_FORCE(close(o->signalfd_fd) == 0)
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+fail2:
+    while (o->num_entries > 0) {
+        BReactorKEvent_Free(&o->entries[o->num_entries - 1].kevent);
+        o->num_entries--;
+    }
+    BFree(o->entries);
+    #endif
+    
+    #ifdef BADVPN_USE_SELFPIPE
+fail2:
+    while (o->num_entries > 0) {
+        free_selfpipe_entry(&o->entries[o->num_entries - 1]);
+        o->num_entries--;
+    }
+    BFree(o->entries);
+    #endif
+    
+fail0:
+    return 0;
+}
+
+void BUnixSignal_Free (BUnixSignal *o, int unblock)
+{
+    ASSERT(unblock == 0 || unblock == 1)
+    DebugObject_Free(&o->d_obj);
+    
+    #ifdef BADVPN_USE_SIGNALFD
+    
+    if (unblock) {
+        // unblock signals
+        ASSERT_FORCE(sigprocmask(SIG_UNBLOCK, &o->signals, 0) == 0)
+    }
+    
+    // free signalfd BFileDescriptor
+    BReactor_RemoveFileDescriptor(o->reactor, &o->signalfd_bfd);
+    
+    // free signalfd fd
+    ASSERT_FORCE(close(o->signalfd_fd) == 0)
+    
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    
+    if (unblock) {
+        // unblock signals
+        ASSERT_FORCE(sigprocmask(SIG_UNBLOCK, &o->signals, 0) == 0)
+    }
+    
+    // free kevents
+    while (o->num_entries > 0) {
+        BReactorKEvent_Free(&o->entries[o->num_entries - 1].kevent);
+        o->num_entries--;
+    }
+    
+    // free array
+    BFree(o->entries);
+    
+    #endif
+    
+    #ifdef BADVPN_USE_SELFPIPE
+    
+    if (!unblock) {
+        // block signals
+        if (sigprocmask(SIG_BLOCK, &o->signals, 0) < 0) {
+            BLog(BLOG_ERROR, "sigprocmask block failed");
+        }
+    }
+    
+    // free entries
+    while (o->num_entries > 0) {
+        free_selfpipe_entry(&o->entries[o->num_entries - 1]);
+        o->num_entries--;
+    }
+    
+    // free array
+    BFree(o->entries);
+    
+    #endif
+}
diff --git a/external/badvpn_dns/system/BUnixSignal.h b/external/badvpn_dns/system/BUnixSignal.h
new file mode 100644
index 0000000..2438be9
--- /dev/null
+++ b/external/badvpn_dns/system/BUnixSignal.h
@@ -0,0 +1,132 @@
+/**
+ * @file BUnixSignal.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 catching unix signals.
+ */
+
+#ifndef BADVPN_SYSTEM_BUNIXSIGNAL_H
+#define BADVPN_SYSTEM_BUNIXSIGNAL_H
+
+#if (defined(BADVPN_USE_SIGNALFD) + defined(BADVPN_USE_KEVENT) + defined(BADVPN_USE_SELFPIPE)) != 1
+#error Unknown signal backend or too many signal backends
+#endif
+
+#include <unistd.h>
+#include <signal.h>
+
+#include <misc/debug.h>
+#include <system/BReactor.h>
+#include <base/DebugObject.h>
+
+struct BUnixSignal_s;
+
+/**
+ * Handler function called when a signal is received.
+ * 
+ * @param user as in {@link BUnixSignal_Init}
+ * @param signo signal number. Will be one of the signals provided to {@link signals}.
+ */
+typedef void (*BUnixSignal_handler) (void *user, int signo);
+
+#ifdef BADVPN_USE_KEVENT
+struct BUnixSignal_kevent_entry {
+    struct BUnixSignal_s *parent;
+    int signo;
+    BReactorKEvent kevent;
+};
+#endif
+
+#ifdef BADVPN_USE_SELFPIPE
+struct BUnixSignal_selfpipe_entry {
+    struct BUnixSignal_s *parent;
+    int signo;
+    int pipefds[2];
+    BFileDescriptor pipe_read_bfd;
+};
+#endif
+
+/**
+ * Object for catching unix signals.
+ */
+typedef struct BUnixSignal_s {
+    BReactor *reactor;
+    sigset_t signals;
+    BUnixSignal_handler handler;
+    void *user;
+    
+    #ifdef BADVPN_USE_SIGNALFD
+    int signalfd_fd;
+    BFileDescriptor signalfd_bfd;
+    #endif
+    
+    #ifdef BADVPN_USE_KEVENT
+    struct BUnixSignal_kevent_entry *entries;
+    int num_entries;
+    #endif
+    
+    #ifdef BADVPN_USE_SELFPIPE
+    struct BUnixSignal_selfpipe_entry *entries;
+    int num_entries;
+    #endif
+    
+    DebugObject d_obj;
+} BUnixSignal;
+
+/**
+ * Initializes the object.
+ * {@link BLog_Init} must have been done.
+ * 
+ * WARNING: for every signal number there should be at most one {@link BUnixSignal}
+ * object handling it (or anything else that could interfere).
+ * 
+ * This blocks the signal using sigprocmask() and sets up signalfd() for receiving
+ * signals.
+ *
+ * @param o the object
+ * @param reactor reactor we live in
+ * @param signals signals to handle. See man 3 sigsetops.
+ * @param handler handler function to call when a signal is received
+ * @param user value passed to callback function
+ * @return 1 on success, 0 on failure
+ */
+int BUnixSignal_Init (BUnixSignal *o, BReactor *reactor, sigset_t signals, BUnixSignal_handler handler, void *user) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ * @param unblock whether to unblock the signals using sigprocmask(). Not unblocking it
+ *                can be used while the program is exiting gracefully to prevent the
+ *                signals from being handled handled according to its default disposition
+ *                after this function is called. Must be 0 or 1.
+ */
+void BUnixSignal_Free (BUnixSignal *o, int unblock);
+
+#endif
diff --git a/external/badvpn_dns/system/CMakeLists.txt b/external/badvpn_dns/system/CMakeLists.txt
new file mode 100644
index 0000000..99f7333
--- /dev/null
+++ b/external/badvpn_dns/system/CMakeLists.txt
@@ -0,0 +1,44 @@
+set(BSYSTEM_ADDITIONAL_LIBS)
+set(BSYSTEM_ADDITIONAL_SOURCES)
+
+if (NOT EMSCRIPTEN)
+    list(APPEND BSYSTEM_ADDITIONAL_SOURCES
+        BSignal.c
+        BNetwork.c
+    )
+
+    if (WIN32)
+        list(APPEND BSYSTEM_ADDITIONAL_LIBS ws2_32 mswsock)
+        list(APPEND BSYSTEM_ADDITIONAL_SOURCES
+            BConnection_win.c
+            BDatagram_win.c
+        )
+    endif ()
+
+    if (NOT WIN32)
+        list(APPEND BSYSTEM_ADDITIONAL_SOURCES
+            BUnixSignal.c
+            BConnection_unix.c
+            BDatagram_unix.c
+            BProcess.c
+            BInputProcess.c
+            BThreadSignal.c
+            BLockReactor.c
+        )
+    endif ()
+endif ()
+
+if (BREACTOR_BACKEND STREQUAL "badvpn")
+    list(APPEND BSYSTEM_ADDITIONAL_SOURCES BReactor_badvpn.c)
+elseif (BREACTOR_BACKEND STREQUAL "glib")
+    list(APPEND BSYSTEM_ADDITIONAL_SOURCES BReactor_glib.c)
+    list(APPEND BSYSTEM_ADDITIONAL_LIBS ${GLIB2_LIBRARIES})
+elseif (BREACTOR_BACKEND STREQUAL "emscripten")
+    list(APPEND BSYSTEM_ADDITIONAL_SOURCES BReactor_emscripten.c)
+endif ()
+
+set(SYSTEM_SOURCES
+    BTime.c
+    ${BSYSTEM_ADDITIONAL_SOURCES}
+)
+badvpn_add_library(system "base;flow" "${BSYSTEM_ADDITIONAL_LIBS}" "${SYSTEM_SOURCES}")
diff --git a/external/badvpn_dns/tests/CMakeLists.txt b/external/badvpn_dns/tests/CMakeLists.txt
new file mode 100644
index 0000000..ebb0d62
--- /dev/null
+++ b/external/badvpn_dns/tests/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_executable(chunkbuffer2_test chunkbuffer2_test.c)
+
+add_executable(bproto_test bproto_test.c)
+
+if (BUILDING_THREADWORK)
+    add_executable(threadwork_test threadwork_test.c)
+    target_link_libraries(threadwork_test threadwork)
+endif ()
diff --git a/external/badvpn_dns/tests/bproto_test.bproto b/external/badvpn_dns/tests/bproto_test.bproto
new file mode 100644
index 0000000..4641a74
--- /dev/null
+++ b/external/badvpn_dns/tests/bproto_test.bproto
@@ -0,0 +1,9 @@
+message msg1 {
+    required uint16 a = 5;
+    optional uint32 b = 6;
+    required repeated uint64 c = 7;
+    repeated uint16 d = 8;
+    required uint8 e = 9;
+    required data f = 10;
+    required data("4") g = 11;
+};
diff --git a/external/badvpn_dns/tests/bproto_test.c b/external/badvpn_dns/tests/bproto_test.c
new file mode 100644
index 0000000..067695b
--- /dev/null
+++ b/external/badvpn_dns/tests/bproto_test.c
@@ -0,0 +1,76 @@
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+
+#include <generated/bproto_bproto_test.h>
+
+int main ()
+{
+    uint16_t a = 17501;
+    uint64_t c = 82688926;
+    uint16_t d1 = 1517;
+    uint16_t d2 = 1518;
+    uint8_t e = 72;
+    const char *f = "hello world";
+    const char *g = "helo";
+    
+    // encode message
+    
+    int len = msg1_SIZEa + msg1_SIZEc + msg1_SIZEd + msg1_SIZEd + msg1_SIZEe + msg1_SIZEf(strlen(f)) + msg1_SIZEg;
+    
+    uint8_t *msg = (uint8_t *)BAlloc(len);
+    ASSERT_FORCE(msg)
+    msg1Writer writer;
+    msg1Writer_Init(&writer, msg);
+    msg1Writer_Adda(&writer, a);
+    msg1Writer_Addc(&writer, c);
+    msg1Writer_Addd(&writer, d1);
+    msg1Writer_Addd(&writer, d2);
+    msg1Writer_Adde(&writer, e);
+    uint8_t *f_dst = msg1Writer_Addf(&writer, strlen(f));
+    memcpy(f_dst, f, strlen(f));
+    uint8_t *g_dst = msg1Writer_Addg(&writer);
+    memcpy(g_dst, g, strlen(g));
+    int len2 = msg1Writer_Finish(&writer);
+    ASSERT_EXECUTE(len2 == len)
+    
+    // parse message
+    
+    msg1Parser parser;
+    ASSERT_EXECUTE(msg1Parser_Init(&parser, msg, len))
+    
+    // check parse results
+    
+    uint16_t p_a;
+    uint64_t p_c;
+    uint16_t p_d1;
+    uint16_t p_d2;
+    uint8_t p_e;
+    uint8_t *p_f;
+    int p_f_len;
+    uint8_t *p_g;
+    ASSERT_EXECUTE(msg1Parser_Geta(&parser, &p_a))
+    ASSERT_EXECUTE(msg1Parser_Getc(&parser, &p_c))
+    ASSERT_EXECUTE(msg1Parser_Getd(&parser, &p_d1))
+    ASSERT_EXECUTE(msg1Parser_Getd(&parser, &p_d2))
+    ASSERT_EXECUTE(msg1Parser_Gete(&parser, &p_e))
+    ASSERT_EXECUTE(msg1Parser_Getf(&parser, &p_f, &p_f_len))
+    ASSERT_EXECUTE(msg1Parser_Getg(&parser, &p_g))
+    
+    ASSERT(p_a == a)
+    ASSERT(p_c == c)
+    ASSERT(p_d1 == d1)
+    ASSERT(p_d2 == d2)
+    ASSERT(p_e == e)
+    ASSERT(p_f_len == strlen(f) && !memcmp(p_f, f, p_f_len))
+    ASSERT(!memcmp(p_g, g, strlen(g)))
+    
+    ASSERT(msg1Parser_GotEverything(&parser))
+    
+    BFree(msg);
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/tests/chunkbuffer2_test.c b/external/badvpn_dns/tests/chunkbuffer2_test.c
new file mode 100644
index 0000000..ff94bb3
--- /dev/null
+++ b/external/badvpn_dns/tests/chunkbuffer2_test.c
@@ -0,0 +1,86 @@
+#include <misc/debug.h>
+#include <structure/ChunkBuffer2.h>
+
+int main ()
+{
+    struct ChunkBuffer2_block blocks[16];
+    ChunkBuffer2 buf;
+    ChunkBuffer2_Init(&buf, blocks, 16, 4 * sizeof(struct ChunkBuffer2_block));
+    
+    ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.input_avail == 15 * sizeof(struct ChunkBuffer2_block))
+    ASSERT_FORCE(buf.output_dest == NULL)
+    ASSERT_FORCE(buf.output_avail == -1)
+    
+    ChunkBuffer2_SubmitPacket(&buf, sizeof(struct ChunkBuffer2_block));
+    
+    ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[3])
+    ASSERT_FORCE(buf.input_avail == 13 * sizeof(struct ChunkBuffer2_block))
+    ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.output_avail == 1 * sizeof(struct ChunkBuffer2_block))
+    
+    ChunkBuffer2_SubmitPacket(&buf, 8 * sizeof(struct ChunkBuffer2_block));
+    
+    ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[12])
+    ASSERT_FORCE(buf.input_avail == 4 * sizeof(struct ChunkBuffer2_block))
+    ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.output_avail == 1 * sizeof(struct ChunkBuffer2_block))
+    
+    ChunkBuffer2_SubmitPacket(&buf, 4 * sizeof(struct ChunkBuffer2_block));
+    
+    ASSERT_FORCE(buf.input_dest == NULL)
+    ASSERT_FORCE(buf.input_avail == -1)
+    ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.output_avail == 1 * sizeof(struct ChunkBuffer2_block))
+    
+    ChunkBuffer2_ConsumePacket(&buf);
+    
+    ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.input_avail == 1 * sizeof(struct ChunkBuffer2_block))
+    ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[3])
+    ASSERT_FORCE(buf.output_avail == 8 * sizeof(struct ChunkBuffer2_block))
+    
+    ChunkBuffer2_ConsumePacket(&buf);
+    
+    ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.input_avail == 10 * sizeof(struct ChunkBuffer2_block))
+    ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[12])
+    ASSERT_FORCE(buf.output_avail == 4 * sizeof(struct ChunkBuffer2_block))
+    
+    ChunkBuffer2_SubmitPacket(&buf, 9 * sizeof(struct ChunkBuffer2_block));
+    
+    ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[11])
+    ASSERT_FORCE(buf.input_avail == 0 * sizeof(struct ChunkBuffer2_block))
+    ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[12])
+    ASSERT_FORCE(buf.output_avail == 4 * sizeof(struct ChunkBuffer2_block))
+    
+    ChunkBuffer2_ConsumePacket(&buf);
+    
+    ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[11])
+    ASSERT_FORCE(buf.input_avail == 5 * sizeof(struct ChunkBuffer2_block))
+    ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.output_avail == 9 * sizeof(struct ChunkBuffer2_block))
+    
+    ChunkBuffer2_SubmitPacket(&buf, 1 * sizeof(struct ChunkBuffer2_block));
+    
+    ASSERT_FORCE(buf.input_dest == NULL)
+    ASSERT_FORCE(buf.input_avail == -1)
+    ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.output_avail == 9 * sizeof(struct ChunkBuffer2_block))
+    
+    ChunkBuffer2_ConsumePacket(&buf);
+    
+    ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.input_avail == 9 * sizeof(struct ChunkBuffer2_block))
+    ASSERT_FORCE(buf.output_dest == (uint8_t *)&blocks[11])
+    ASSERT_FORCE(buf.output_avail == 1 * sizeof(struct ChunkBuffer2_block))
+    
+    ChunkBuffer2_ConsumePacket(&buf);
+    
+    ASSERT_FORCE(buf.input_dest == (uint8_t *)&blocks[1])
+    ASSERT_FORCE(buf.input_avail == 15 * sizeof(struct ChunkBuffer2_block))
+    ASSERT_FORCE(buf.output_dest == NULL)
+    ASSERT_FORCE(buf.output_avail == -1)
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/tests/threadwork_test.c b/external/badvpn_dns/tests/threadwork_test.c
new file mode 100644
index 0000000..9c18b4b
--- /dev/null
+++ b/external/badvpn_dns/tests/threadwork_test.c
@@ -0,0 +1,87 @@
+#include <stddef.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <base/BLog.h>
+#include <base/DebugObject.h>
+#include <threadwork/BThreadWork.h>
+
+BReactor reactor;
+BThreadWorkDispatcher twd;
+BThreadWork tw1;
+BThreadWork tw2;
+BThreadWork tw3;
+int num_left;
+
+static void handler_done (void *user)
+{
+    printf("work done\n");
+    
+    num_left--;
+    
+    if (num_left == 0) {
+        printf("all works done, quitting\n");
+        BReactor_Quit(&reactor, 0);
+    }
+}
+
+static void work_func (void *user)
+{
+    unsigned int x = 0;
+    
+    for (int i = 0; i < 10000; i++) {
+        for (int j = 0; j < 10000; j++) {
+            x++;
+        }
+    }
+}
+
+static void dummy_works (int n)
+{
+    for (int i = 0; i < n; i++) {
+        BThreadWork tw_tmp;
+        BThreadWork_Init(&tw_tmp, &twd, handler_done, NULL, work_func, NULL);
+        BThreadWork_Free(&tw_tmp);
+    }
+}
+
+int main ()
+{
+    BLog_InitStdout();
+    BLog_SetChannelLoglevel(BLOG_CHANNEL_BThreadWork, BLOG_DEBUG);
+    
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        goto fail1;
+    }
+    
+    if (!BThreadWorkDispatcher_Init(&twd, &reactor, 1)) {
+        DEBUG("BThreadWorkDispatcher_Init failed");
+        goto fail2;
+    }
+    
+    dummy_works(200);
+    
+    BThreadWork_Init(&tw1, &twd, handler_done, NULL, work_func, NULL);
+    
+    BThreadWork_Init(&tw2, &twd, handler_done, NULL, work_func, NULL);
+    
+    BThreadWork_Init(&tw3, &twd, handler_done, NULL, work_func, NULL);
+    
+    dummy_works(200);
+    
+    num_left = 3;
+    
+    BReactor_Exec(&reactor);
+    
+    BThreadWork_Free(&tw3);
+    BThreadWork_Free(&tw2);
+    BThreadWork_Free(&tw1);
+    BThreadWorkDispatcher_Free(&twd);
+fail2:
+    BReactor_Free(&reactor);
+fail1:
+    BLog_Free();
+    DebugObjectGlobal_Finish();
+    return 0;
+}
diff --git a/external/badvpn_dns/threadwork/BThreadWork.c b/external/badvpn_dns/threadwork/BThreadWork.c
new file mode 100644
index 0000000..3c29f95
--- /dev/null
+++ b/external/badvpn_dns/threadwork/BThreadWork.c
@@ -0,0 +1,451 @@
+/**
+ * @file BThreadWork.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stddef.h>
+
+#ifdef BADVPN_THREADWORK_USE_PTHREAD
+    #include <unistd.h>
+    #include <errno.h>
+    #include <fcntl.h>
+#endif
+
+#include <misc/offset.h>
+#include <base/BLog.h>
+
+#include <generated/blog_channel_BThreadWork.h>
+
+#include <threadwork/BThreadWork.h>
+
+#ifdef BADVPN_THREADWORK_USE_PTHREAD
+
+static void * dispatcher_thread (struct BThreadWorkDispatcher_thread *t)
+{
+    BThreadWorkDispatcher *o = t->d;
+    
+    ASSERT_FORCE(pthread_mutex_lock(&o->mutex) == 0)
+    
+    while (1) {
+        // exit if requested
+        if (o->cancel) {
+            break;
+        }
+        
+        if (LinkedList1_IsEmpty(&o->pending_list)) {
+            // wait for event
+            ASSERT_FORCE(pthread_cond_wait(&t->new_cond, &o->mutex) == 0)
+            continue;
+        }
+        
+        // grab the work
+        BThreadWork *w = UPPER_OBJECT(LinkedList1_GetFirst(&o->pending_list), BThreadWork, list_node);
+        ASSERT(w->state == BTHREADWORK_STATE_PENDING)
+        LinkedList1_Remove(&o->pending_list, &w->list_node);
+        t->running_work = w;
+        w->state = BTHREADWORK_STATE_RUNNING;
+        
+        // do the work
+        ASSERT_FORCE(pthread_mutex_unlock(&o->mutex) == 0)
+        w->work_func(w->work_func_user);
+        ASSERT_FORCE(pthread_mutex_lock(&o->mutex) == 0)
+        
+        // release the work
+        t->running_work = NULL;
+        LinkedList1_Append(&o->finished_list, &w->list_node);
+        w->state = BTHREADWORK_STATE_FINISHED;
+        ASSERT_FORCE(sem_post(&w->finished_sem) == 0)
+        
+        // write to pipe
+        uint8_t b = 0;
+        int res = write(o->pipe[1], &b, sizeof(b));
+        if (res < 0) {
+            int error = errno;
+            ASSERT_FORCE(error == EAGAIN || error == EWOULDBLOCK)
+        }
+    }
+    
+    ASSERT_FORCE(pthread_mutex_unlock(&o->mutex) == 0)
+    
+    return NULL;
+}
+
+static void dispatch_job (BThreadWorkDispatcher *o)
+{
+    ASSERT(o->num_threads > 0)
+    
+    // lock
+    ASSERT_FORCE(pthread_mutex_lock(&o->mutex) == 0)
+    
+    // check for finished job
+    if (LinkedList1_IsEmpty(&o->finished_list)) {
+        ASSERT_FORCE(pthread_mutex_unlock(&o->mutex) == 0)
+        return;
+    }
+    
+    // grab finished job
+    BThreadWork *w = UPPER_OBJECT(LinkedList1_GetFirst(&o->finished_list), BThreadWork, list_node);
+    ASSERT(w->state == BTHREADWORK_STATE_FINISHED)
+    LinkedList1_Remove(&o->finished_list, &w->list_node);
+    
+    // schedule more
+    if (!LinkedList1_IsEmpty(&o->finished_list)) {
+        BPending_Set(&o->more_job);
+    }
+    
+    // set state forgotten
+    w->state = BTHREADWORK_STATE_FORGOTTEN;
+    
+    // unlock
+    ASSERT_FORCE(pthread_mutex_unlock(&o->mutex) == 0)
+    
+    // call handler
+    w->handler_done(w->user);
+    return;
+}
+
+static void pipe_fd_handler (BThreadWorkDispatcher *o, int events)
+{
+    ASSERT(o->num_threads > 0)
+    DebugObject_Access(&o->d_obj);
+    
+    // read data from pipe
+    uint8_t b[64];
+    int res = read(o->pipe[0], b, sizeof(b));
+    if (res < 0) {
+        int error = errno;
+        ASSERT_FORCE(error == EAGAIN || error == EWOULDBLOCK)
+    } else {
+        ASSERT(res > 0)
+    }
+    
+    dispatch_job(o);
+    return;
+}
+
+static void more_job_handler (BThreadWorkDispatcher *o)
+{
+    ASSERT(o->num_threads > 0)
+    DebugObject_Access(&o->d_obj);
+    
+    dispatch_job(o);
+    return;
+}
+
+static void stop_threads (BThreadWorkDispatcher *o)
+{
+    // set cancelling
+    ASSERT_FORCE(pthread_mutex_lock(&o->mutex) == 0)
+    o->cancel = 1;
+    ASSERT_FORCE(pthread_mutex_unlock(&o->mutex) == 0)
+    
+    while (o->num_threads > 0) {
+        struct BThreadWorkDispatcher_thread *t = &o->threads[o->num_threads - 1];
+        
+        // wake up thread
+        ASSERT_FORCE(pthread_cond_signal(&t->new_cond) == 0)
+        
+        // wait for thread to exit
+        ASSERT_FORCE(pthread_join(t->thread, NULL) == 0)
+        
+        // free condition variable
+        ASSERT_FORCE(pthread_cond_destroy(&t->new_cond) == 0)
+        
+        o->num_threads--;
+    }
+}
+
+#endif
+
+static void work_job_handler (BThreadWork *o)
+{
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    ASSERT(o->d->num_threads == 0)
+    #endif
+    DebugObject_Access(&o->d_obj);
+    
+    // do the work
+    o->work_func(o->work_func_user);
+    
+    // call handler
+    o->handler_done(o->user);
+    return;
+}
+
+int BThreadWorkDispatcher_Init (BThreadWorkDispatcher *o, BReactor *reactor, int num_threads_hint)
+{
+    // init arguments
+    o->reactor = reactor;
+    
+    if (num_threads_hint < 0) {
+        num_threads_hint = 2;
+    }
+    if (num_threads_hint > BTHREADWORK_MAX_THREADS) {
+        num_threads_hint = BTHREADWORK_MAX_THREADS;
+    }
+    
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    
+    if (num_threads_hint > 0) {
+        // init pending list
+        LinkedList1_Init(&o->pending_list);
+        
+        // init finished list
+        LinkedList1_Init(&o->finished_list);
+        
+        // init mutex
+        if (pthread_mutex_init(&o->mutex, NULL) != 0) {
+            BLog(BLOG_ERROR, "pthread_mutex_init failed");
+            goto fail0;
+        }
+        
+        // init pipe
+        if (pipe(o->pipe) < 0) {
+            BLog(BLOG_ERROR, "pipe failed");
+            goto fail1;
+        }
+        
+        // set read end non-blocking
+        if (fcntl(o->pipe[0], F_SETFL, O_NONBLOCK) < 0) {
+            BLog(BLOG_ERROR, "fcntl failed");
+            goto fail2;
+        }
+        
+        // set write end non-blocking
+        if (fcntl(o->pipe[1], F_SETFL, O_NONBLOCK) < 0) {
+            BLog(BLOG_ERROR, "fcntl failed");
+            goto fail2;
+        }
+        
+        // init BFileDescriptor
+        BFileDescriptor_Init(&o->bfd, o->pipe[0], (BFileDescriptor_handler)pipe_fd_handler, o);
+        if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+            BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+            goto fail2;
+        }
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, BREACTOR_READ);
+        
+        // init more job
+        BPending_Init(&o->more_job, BReactor_PendingGroup(o->reactor), (BPending_handler)more_job_handler, o);
+        
+        // set not cancelling
+        o->cancel = 0;
+        
+        // init threads
+        o->num_threads = 0;
+        for (int i = 0; i < num_threads_hint; i++) {
+            struct BThreadWorkDispatcher_thread *t = &o->threads[i];
+            
+            // set parent pointer
+            t->d = o;
+            
+            // set no running work
+            t->running_work = NULL;
+            
+            // init condition variable
+            if (pthread_cond_init(&t->new_cond, NULL) != 0) {
+                BLog(BLOG_ERROR, "pthread_cond_init failed");
+                goto fail3;
+            }
+            
+            // init thread
+            if (pthread_create(&t->thread, NULL, (void * (*) (void *))dispatcher_thread, t) != 0) {
+                BLog(BLOG_ERROR, "pthread_create failed");
+                ASSERT_FORCE(pthread_cond_destroy(&t->new_cond) == 0)
+                goto fail3;
+            }
+        
+            o->num_threads++;
+        }
+    }
+    
+    #endif
+    
+    DebugObject_Init(&o->d_obj);
+    DebugCounter_Init(&o->d_ctr);
+    return 1;
+    
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+fail3:
+    stop_threads(o);
+    BPending_Free(&o->more_job);
+    BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+fail2:
+    ASSERT_FORCE(close(o->pipe[0]) == 0)
+    ASSERT_FORCE(close(o->pipe[1]) == 0)
+fail1:
+    ASSERT_FORCE(pthread_mutex_destroy(&o->mutex) == 0)
+fail0:
+    return 0;
+    #endif
+}
+
+void BThreadWorkDispatcher_Free (BThreadWorkDispatcher *o)
+{
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    if (o->num_threads > 0) {
+        ASSERT(LinkedList1_IsEmpty(&o->pending_list))
+        for (int i = 0; i < o->num_threads; i++) { ASSERT(!o->threads[i].running_work) }
+        ASSERT(LinkedList1_IsEmpty(&o->finished_list))
+    }
+    #endif
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Free(&o->d_ctr);
+    
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    
+    if (o->num_threads > 0) {
+        // stop threads
+        stop_threads(o);
+        
+        // free more job
+        BPending_Free(&o->more_job);
+        
+        // free BFileDescriptor
+        BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+        
+        // free pipe
+        ASSERT_FORCE(close(o->pipe[0]) == 0)
+        ASSERT_FORCE(close(o->pipe[1]) == 0)
+        
+        // free mutex
+        ASSERT_FORCE(pthread_mutex_destroy(&o->mutex) == 0)
+    }
+    
+    #endif
+}
+
+int BThreadWorkDispatcher_UsingThreads (BThreadWorkDispatcher *o)
+{
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    return (o->num_threads > 0);
+    #else
+    return 0;
+    #endif
+}
+
+void BThreadWork_Init (BThreadWork *o, BThreadWorkDispatcher *d, BThreadWork_handler_done handler_done, void *user, BThreadWork_work_func work_func, void *work_func_user)
+{
+    DebugObject_Access(&d->d_obj);
+    
+    // init arguments
+    o->d = d;
+    o->handler_done = handler_done;
+    o->user = user;
+    o->work_func = work_func;
+    o->work_func_user = work_func_user;
+    
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    if (d->num_threads > 0) {
+        // set state
+        o->state = BTHREADWORK_STATE_PENDING;
+        
+        // init finished semaphore
+        ASSERT_FORCE(sem_init(&o->finished_sem, 0, 0) == 0)
+        
+        // post work
+        ASSERT_FORCE(pthread_mutex_lock(&d->mutex) == 0)
+        LinkedList1_Append(&d->pending_list, &o->list_node);
+        for (int i = 0; i < d->num_threads; i++) {
+            if (!d->threads[i].running_work) {
+                ASSERT_FORCE(pthread_cond_signal(&d->threads[i].new_cond) == 0)
+                break;
+            }
+        }
+        ASSERT_FORCE(pthread_mutex_unlock(&d->mutex) == 0)
+    } else {
+    #endif
+        // schedule job
+        BPending_Init(&o->job, BReactor_PendingGroup(d->reactor), (BPending_handler)work_job_handler, o);
+        BPending_Set(&o->job);
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    }
+    #endif
+    
+    DebugObject_Init(&o->d_obj);
+    DebugCounter_Increment(&d->d_ctr);
+}
+
+void BThreadWork_Free (BThreadWork *o)
+{
+    BThreadWorkDispatcher *d = o->d;
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Decrement(&d->d_ctr);
+    
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    if (d->num_threads > 0) {
+        ASSERT_FORCE(pthread_mutex_lock(&d->mutex) == 0)
+        
+        switch (o->state) {
+            case BTHREADWORK_STATE_PENDING: {
+                BLog(BLOG_DEBUG, "remove pending work");
+                
+                // remove from pending list
+                LinkedList1_Remove(&d->pending_list, &o->list_node);
+            } break;
+            
+            case BTHREADWORK_STATE_RUNNING: {
+                BLog(BLOG_DEBUG, "remove running work");
+                
+                // wait for the work to finish running
+                ASSERT_FORCE(pthread_mutex_unlock(&d->mutex) == 0)
+                ASSERT_FORCE(sem_wait(&o->finished_sem) == 0)
+                ASSERT_FORCE(pthread_mutex_lock(&d->mutex) == 0)
+                
+                ASSERT(o->state == BTHREADWORK_STATE_FINISHED)
+                
+                // remove from finished list
+                LinkedList1_Remove(&d->finished_list, &o->list_node);
+            } break;
+            
+            case BTHREADWORK_STATE_FINISHED: {
+                BLog(BLOG_DEBUG, "remove finished work");
+                
+                // remove from finished list
+                LinkedList1_Remove(&d->finished_list, &o->list_node);
+            } break;
+            
+            case BTHREADWORK_STATE_FORGOTTEN: {
+                BLog(BLOG_DEBUG, "remove forgotten work");
+            } break;
+            
+            default:
+                ASSERT(0);
+        }
+        
+        ASSERT_FORCE(pthread_mutex_unlock(&d->mutex) == 0)
+        
+        // free finished semaphore
+        ASSERT_FORCE(sem_destroy(&o->finished_sem) == 0)
+    } else {
+    #endif
+        BPending_Free(&o->job);
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    }
+    #endif
+}
diff --git a/external/badvpn_dns/threadwork/BThreadWork.h b/external/badvpn_dns/threadwork/BThreadWork.h
new file mode 100644
index 0000000..e29c080
--- /dev/null
+++ b/external/badvpn_dns/threadwork/BThreadWork.h
@@ -0,0 +1,171 @@
+/**
+ * @file BThreadWork.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * System for performing computations (possibly) in parallel with the event loop
+ * in a different thread.
+ */
+
+#ifndef BADVPN_BTHREADWORK_BTHREADWORK_H
+#define BADVPN_BTHREADWORK_BTHREADWORK_H
+
+#ifdef BADVPN_THREADWORK_USE_PTHREAD
+    #include <pthread.h>
+    #include <semaphore.h>
+#endif
+
+#include <misc/debug.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+
+#define BTHREADWORK_STATE_PENDING 1
+#define BTHREADWORK_STATE_RUNNING 2
+#define BTHREADWORK_STATE_FINISHED 3
+#define BTHREADWORK_STATE_FORGOTTEN 4
+
+#define BTHREADWORK_MAX_THREADS 8
+
+struct BThreadWork_s;
+struct BThreadWorkDispatcher_s;
+
+/**
+ * Function called to do the work for a {@link BThreadWork}.
+ * The function may be called in another thread, in parallel with the event loop.
+ * 
+ * @param user as work_func_user in {@link BThreadWork_Init}
+ */
+typedef void (*BThreadWork_work_func) (void *user);
+
+/**
+ * Handler called when a {@link BThreadWork} work is done.
+ * 
+ * @param user as in {@link BThreadWork_Init}
+ */
+typedef void (*BThreadWork_handler_done) (void *user);
+
+#ifdef BADVPN_THREADWORK_USE_PTHREAD
+struct BThreadWorkDispatcher_thread {
+    struct BThreadWorkDispatcher_s *d;
+    struct BThreadWork_s *running_work;
+    pthread_cond_t new_cond;
+    pthread_t thread;
+};
+#endif
+
+typedef struct BThreadWorkDispatcher_s {
+    BReactor *reactor;
+    #ifdef BADVPN_THREADWORK_USE_PTHREAD
+    LinkedList1 pending_list;
+    LinkedList1 finished_list;
+    pthread_mutex_t mutex;
+    int pipe[2];
+    BFileDescriptor bfd;
+    BPending more_job;
+    int cancel;
+    int num_threads;
+    struct BThreadWorkDispatcher_thread threads[BTHREADWORK_MAX_THREADS];
+    #endif
+    DebugObject d_obj;
+    DebugCounter d_ctr;
+} BThreadWorkDispatcher;
+
+typedef struct BThreadWork_s {
+    BThreadWorkDispatcher *d;
+    BThreadWork_handler_done handler_done;
+    void *user;
+    BThreadWork_work_func work_func;
+    void *work_func_user;
+    union {
+        #ifdef BADVPN_THREADWORK_USE_PTHREAD
+        struct {
+            LinkedList1Node list_node;
+            int state;
+            sem_t finished_sem;
+        };
+        #endif
+        struct {
+            BPending job;
+        };
+    };
+    DebugObject d_obj;
+} BThreadWork;
+
+/**
+ * Initializes the work dispatcher.
+ * Works may be started using {@link BThreadWork_Init}.
+ * 
+ * @param o the object
+ * @param reactor reactor we live in
+ * @param num_threads_hint hint for the number of threads to use:
+ *                         <0 - A choice will be made automatically, probably based on the number of CPUs.
+ *                         0 - No additional threads will be used, and computations will be performed directly
+ *                             in the event loop in job handlers.
+ * @return 1 on success, 0 on failure
+ */
+int BThreadWorkDispatcher_Init (BThreadWorkDispatcher *o, BReactor *reactor, int num_threads_hint) WARN_UNUSED;
+
+/**
+ * Frees the work dispatcher.
+ * There must be no {@link BThreadWork}'s with this dispatcher.
+ * 
+ * @param o the object
+ */
+void BThreadWorkDispatcher_Free (BThreadWorkDispatcher *o);
+
+/**
+ * Determines whether threads are being used for computations, or computations
+ * are done in the event loop.
+ * 
+ * @return 1 if threads are being used, 0 if not
+ */
+int BThreadWorkDispatcher_UsingThreads (BThreadWorkDispatcher *o);
+
+/**
+ * Initializes the work.
+ * 
+ * @param o the object
+ * @param d work dispatcher
+ * @param handler_done handler to call when the work is done
+ * @param user argument to handler
+ * @param work_func function that will do the work, possibly from another thread
+ * @param work_func_user argument to work_func
+ */
+void BThreadWork_Init (BThreadWork *o, BThreadWorkDispatcher *d, BThreadWork_handler_done handler_done, void *user, BThreadWork_work_func work_func, void *work_func_user);
+
+/**
+ * Frees the work.
+ * After this function returns, the work function will either have fully executed,
+ * or not called at all, and never will be.
+ * 
+ * @param o the object
+ */
+void BThreadWork_Free (BThreadWork *o);
+
+#endif
diff --git a/external/badvpn_dns/threadwork/CMakeLists.txt b/external/badvpn_dns/threadwork/CMakeLists.txt
new file mode 100644
index 0000000..5f223ae
--- /dev/null
+++ b/external/badvpn_dns/threadwork/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(BADVPN_THREADWORK_EXTRA_LIBS)
+if (BADVPN_THREADWORK_USE_PTHREAD)
+    list(APPEND BADVPN_THREADWORK_EXTRA_LIBS pthread)
+endif ()
+
+badvpn_add_library(threadwork "system" "${BADVPN_THREADWORK_EXTRA_LIBS}" BThreadWork.c)
diff --git a/external/badvpn_dns/tun2socks/CMakeLists.txt b/external/badvpn_dns/tun2socks/CMakeLists.txt
new file mode 100644
index 0000000..8c8597c
--- /dev/null
+++ b/external/badvpn_dns/tun2socks/CMakeLists.txt
@@ -0,0 +1,15 @@
+add_executable(badvpn-tun2socks
+    tun2socks.c
+    SocksUdpGwClient.c
+)
+target_link_libraries(badvpn-tun2socks system flow tuntap lwip socksclient udpgw_client)
+
+install(
+    TARGETS badvpn-tun2socks
+    RUNTIME DESTINATION bin
+)
+
+install(
+    FILES badvpn-tun2socks.8
+    DESTINATION share/man/man8
+)
diff --git a/external/badvpn_dns/tun2socks/SocksUdpGwClient.c b/external/badvpn_dns/tun2socks/SocksUdpGwClient.c
new file mode 100644
index 0000000..949d114
--- /dev/null
+++ b/external/badvpn_dns/tun2socks/SocksUdpGwClient.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * Contributions:
+ * Transparent DNS: Copyright (C) Kerem Hadimli <kerem.hadimli@xxxxxxxxx>
+ * 
+ * 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 <base/BLog.h>
+
+#include <tun2socks/SocksUdpGwClient.h>
+
+#include <generated/blog_channel_SocksUdpGwClient.h>
+
+static void free_socks (SocksUdpGwClient *o);
+static void try_connect (SocksUdpGwClient *o);
+static void reconnect_timer_handler (SocksUdpGwClient *o);
+static void socks_client_handler (SocksUdpGwClient *o, int event);
+static void udpgw_handler_servererror (SocksUdpGwClient *o);
+static void udpgw_handler_received (SocksUdpGwClient *o, BAddr local_addr, BAddr remote_addr, const uint8_t *data, int data_len);
+
+static void free_socks (SocksUdpGwClient *o)
+{
+    ASSERT(o->have_socks)
+    
+    // disconnect udpgw client from SOCKS
+    if (o->socks_up) {
+        UdpGwClient_DisconnectServer(&o->udpgw_client);
+    }
+    
+    // free SOCKS client
+    BSocksClient_Free(&o->socks_client);
+    
+    // set have no SOCKS
+    o->have_socks = 0;
+}
+
+static void try_connect (SocksUdpGwClient *o)
+{
+    ASSERT(!o->have_socks)
+    ASSERT(!BTimer_IsRunning(&o->reconnect_timer))
+    
+    // init SOCKS client
+    if (!BSocksClient_Init(&o->socks_client, o->socks_server_addr, o->auth_info, o->num_auth_info, o->remote_udpgw_addr, (BSocksClient_handler)socks_client_handler, o, o->reactor)) {
+        BLog(BLOG_ERROR, "BSocksClient_Init failed");
+        goto fail0;
+    }
+    
+    // set have SOCKS
+    o->have_socks = 1;
+    
+    // set SOCKS not up
+    o->socks_up = 0;
+    
+    return;
+    
+fail0:
+    // set reconnect timer
+    BReactor_SetTimer(o->reactor, &o->reconnect_timer);
+}
+
+static void reconnect_timer_handler (SocksUdpGwClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->have_socks)
+    
+    // try connecting
+    try_connect(o);
+}
+
+static void socks_client_handler (SocksUdpGwClient *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_socks)
+    
+    switch (event) {
+        case BSOCKSCLIENT_EVENT_UP: {
+            ASSERT(!o->socks_up)
+            
+            BLog(BLOG_INFO, "SOCKS up");
+            
+            // connect udpgw client to SOCKS
+            if (!UdpGwClient_ConnectServer(&o->udpgw_client, BSocksClient_GetSendInterface(&o->socks_client), BSocksClient_GetRecvInterface(&o->socks_client))) {
+                BLog(BLOG_ERROR, "UdpGwClient_ConnectServer failed");
+                goto fail0;
+            }
+            
+            // set SOCKS up
+            o->socks_up = 1;
+            
+            return;
+            
+        fail0:
+            // free SOCKS
+            free_socks(o);
+            
+            // set reconnect timer
+            BReactor_SetTimer(o->reactor, &o->reconnect_timer);
+        } break;
+        
+        case BSOCKSCLIENT_EVENT_ERROR:
+        case BSOCKSCLIENT_EVENT_ERROR_CLOSED: {
+            BLog(BLOG_INFO, "SOCKS error");
+            
+            // free SOCKS
+            free_socks(o);
+            
+            // set reconnect timer
+            BReactor_SetTimer(o->reactor, &o->reconnect_timer);
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+static void udpgw_handler_servererror (SocksUdpGwClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_socks)
+    ASSERT(o->socks_up)
+    
+    BLog(BLOG_ERROR, "client reports server error");
+    
+    // free SOCKS
+    free_socks(o);
+    
+    // set reconnect timer
+    BReactor_SetTimer(o->reactor, &o->reconnect_timer);
+}
+
+static void udpgw_handler_received (SocksUdpGwClient *o, BAddr local_addr, BAddr remote_addr, const uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // submit to user
+    o->handler_received(o->user, local_addr, remote_addr, data, data_len);
+    return;
+}
+
+int SocksUdpGwClient_Init (SocksUdpGwClient *o, int udp_mtu, int max_connections, int send_buffer_size, btime_t keepalive_time,
+                           BAddr socks_server_addr, const struct BSocksClient_auth_info *auth_info, size_t num_auth_info,
+                           BAddr remote_udpgw_addr, btime_t reconnect_time, BReactor *reactor, void *user,
+                           SocksUdpGwClient_handler_received handler_received)
+{
+    // see asserts in UdpGwClient_Init
+    ASSERT(!BAddr_IsInvalid(&socks_server_addr))
+    ASSERT(remote_udpgw_addr.type == BADDR_TYPE_IPV4 || remote_udpgw_addr.type == BADDR_TYPE_IPV6)
+    
+    // init arguments
+    o->udp_mtu = udp_mtu;
+    o->socks_server_addr = socks_server_addr;
+    o->auth_info = auth_info;
+    o->num_auth_info = num_auth_info;
+    o->remote_udpgw_addr = remote_udpgw_addr;
+    o->reactor = reactor;
+    o->user = user;
+    o->handler_received = handler_received;
+    
+    // init udpgw client
+    if (!UdpGwClient_Init(&o->udpgw_client, udp_mtu, max_connections, send_buffer_size, keepalive_time, o->reactor, o,
+                          (UdpGwClient_handler_servererror)udpgw_handler_servererror,
+                          (UdpGwClient_handler_received)udpgw_handler_received
+    )) {
+        goto fail0;
+    }
+    
+    // init reconnect timer
+    BTimer_Init(&o->reconnect_timer, reconnect_time, (BTimer_handler)reconnect_timer_handler, o);
+    
+    // set have no SOCKS
+    o->have_socks = 0;
+    
+    // try connecting
+    try_connect(o);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void SocksUdpGwClient_Free (SocksUdpGwClient *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free SOCKS
+    if (o->have_socks) {
+        free_socks(o);
+    }
+    
+    // free reconnect timer
+    BReactor_RemoveTimer(o->reactor, &o->reconnect_timer);
+    
+    // free udpgw client
+    UdpGwClient_Free(&o->udpgw_client);
+}
+
+void SocksUdpGwClient_SubmitPacket (SocksUdpGwClient *o, BAddr local_addr, BAddr remote_addr, int is_dns, const uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    // see asserts in UdpGwClient_SubmitPacket
+    
+    // submit to udpgw client
+    UdpGwClient_SubmitPacket(&o->udpgw_client, local_addr, remote_addr, is_dns, data, data_len);
+}
+
diff --git a/external/badvpn_dns/tun2socks/SocksUdpGwClient.h b/external/badvpn_dns/tun2socks/SocksUdpGwClient.h
new file mode 100644
index 0000000..217e0ec
--- /dev/null
+++ b/external/badvpn_dns/tun2socks/SocksUdpGwClient.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * Contributions:
+ * Transparent DNS: Copyright (C) Kerem Hadimli <kerem.hadimli@xxxxxxxxx>
+ * 
+ * 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_TUN2SOCKS_SOCKSUDPGWCLIENT_H
+#define BADVPN_TUN2SOCKS_SOCKSUDPGWCLIENT_H
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+#include <udpgw_client/UdpGwClient.h>
+#include <socksclient/BSocksClient.h>
+
+typedef void (*SocksUdpGwClient_handler_received) (void *user, BAddr local_addr, BAddr remote_addr, const uint8_t *data, int data_len);
+
+typedef struct {
+    int udp_mtu;
+    BAddr socks_server_addr;
+    const struct BSocksClient_auth_info *auth_info;
+    size_t num_auth_info;
+    BAddr remote_udpgw_addr;
+    BReactor *reactor;
+    void *user;
+    SocksUdpGwClient_handler_received handler_received;
+    UdpGwClient udpgw_client;
+    BTimer reconnect_timer;
+    int have_socks;
+    BSocksClient socks_client;
+    int socks_up;
+    DebugObject d_obj;
+} SocksUdpGwClient;
+
+int SocksUdpGwClient_Init (SocksUdpGwClient *o, int udp_mtu, int max_connections, int send_buffer_size, btime_t keepalive_time,
+                           BAddr socks_server_addr, const struct BSocksClient_auth_info *auth_info, size_t num_auth_info,
+                           BAddr remote_udpgw_addr, btime_t reconnect_time, BReactor *reactor, void *user,
+                           SocksUdpGwClient_handler_received handler_received) WARN_UNUSED;
+void SocksUdpGwClient_Free (SocksUdpGwClient *o);
+void SocksUdpGwClient_SubmitPacket (SocksUdpGwClient *o, BAddr local_addr, BAddr remote_addr, int is_dns, const uint8_t *data, int data_len);
+
+#endif
diff --git a/external/badvpn_dns/tun2socks/badvpn-tun2socks.8 b/external/badvpn_dns/tun2socks/badvpn-tun2socks.8
new file mode 100644
index 0000000..d1ab50c
--- /dev/null
+++ b/external/badvpn_dns/tun2socks/badvpn-tun2socks.8
@@ -0,0 +1,126 @@
+.TH badvpn-tun2socks 8 "February 2012"
+.SH NAME
+badvpn-tun2socks \- create a TUN device to route TCP traffic through a SOCKS server
+.SH SYNOPSIS
+.PP
+.B
+badvpn-tun2socks
+.br
+  [\fB\-\-help\fR]
+.br
+  [\fB\-\-version\fR]
+.br
+  [\fB\-\-logger\fR <stdout/syslog>]
+.br
+  [\fB\-\-syslog-facility\fR <string>] [\fB\-\-syslog-ident\fR <string>]
+.br
+  [\fB\-\-loglevel\fR <0-5/none/error/warning/notice/info/debug>]
+.br
+  [\fB\-\-channel-loglevel\fR <channel-name> <0-5/none/error/warning/notice/info/debug>] ...
+.br
+  [\fB\-\-tundev\fR <name>]
+.br
+  \fB\-\-netif\-ipaddr\fR <ipaddr>
+.br
+  \fB\-\-netif\-netmask\fR <ipnetmask>
+.br
+  \fB\-\-socks\-server\-addr\fR <addr>
+.br
+  [\fB\-\-udpgw-remote-server-addr\fR <addr>]
+.br
+  [\fB\-\-udpgw-max-connections\fR <number>]
+.br
+  [\fB\-\-udpgw-connection-buffer-size\fR <number>]
+.PP
+Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).
+.SH DESCRIPTION
+.PP
+badvpn-tun2socks
+is a network utility used to "socksify" TCP connections at the network
+layer. It implements a TUN device which accepts all incoming TCP
+connections (regardless of destination IP), and forwards them through
+a SOCKS server. This allows you to forward all connections through
+SOCKS, without any need for application support. It can be used, for
+example, to forward connections through a remote SSH server.
+.SH EXAMPLE
+.PP
+This example demonstrates using tun2socks in combination with SSH's dynamic forwarding feature.
+
+Connect to the SSH server, passing -D localhost:1080 to the ssh
+command to enable dynamic forwarding. This will make ssh open a local
+SOCKS server which tun2socks forward connection through.
+
+First create a TUN device (eg. using openvpn):
+
+.nf
+  openvpn --mktun --dev tun0 --user <someuser>
+.fi
+
+Configure the IP of the new tun device:
+
+.nf
+  ifconfig tun0 10.0.0.1 netmask 255.255.255.0
+.fi
+
+Now start the badvpn-tun2socks program:
+
+.nf
+  badvpn-tun2socks --tundev tun0 --netif-ipaddr 10.0.0.2 --netif-netmask 255.255.255.0 \\
+                   --socks-server-addr 127.0.0.1:1080
+.fi
+
+Note that the address 10.0.0.2 is not a typo. It specifies the IP address of the virtual
+router inside the TUN device, and must be different from the IP of the
+TUN interface itself (but in the same subnet).
+
+Now you should be able to ping the virtual router's IP (10.0.0.2):
+
+.nf
+  ping -n 10.0.0.2
+.fi
+
+All that remains is to route connections through the TUN device
+instead of the existing default gateway. This is done as follows:
+
+1. Add a route to the SSH server through your existing gateway, with a
+lower metric than the original default route.
+
+2. If your DNS servers are in a network that is not direcly attached (e.g. in the Internet),
+also add routes for them (like for the SSH server). This is
+needed because tun2socks does not forward UDP by default (see below).
+
+3. Add a default route through the virtual router in the TUN device,
+with a lower metric than the original default route, but higher than
+the SSH and DNS routes.
+
+This will make all external connections go through the TUN device,
+except for the SSH connection (else SSH would go through the TUN
+device, which would go through... SSH).
+
+For example (assuming there are no existing default routes with metric
+<=6; otherwise remove them or change their metrics):
+
+.nf
+  route add <IP_of_SSH_server> gw <IP_of_original_gateway> metric 5
+  <same for DNS>
+  route add default gw 10.0.0.2 metric 6
+.fi
+.SH UDP FORWARDING
+tun2socks can forward UDP, however this requires a forwarder daemon, badvpn-udpgw to run
+on the remote SSH server:
+
+.nf
+  badvpn-udpgw --listen-addr 127.0.0.1:7300
+.fi
+
+Then tell tun2socks to forward UDP via the forwarder:
+
+.nf
+  --udpgw-remote-server-addr 127.0.0.1:7300 
+.fi
+.SH COPYRIGHT
+.PP
+Copyright \(co 2010 Ambroz Bizjak <ambrop7@xxxxxxxxx>
+.br
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/external/badvpn_dns/tun2socks/tun2socks.c b/external/badvpn_dns/tun2socks/tun2socks.c
new file mode 100644
index 0000000..51c3fb9
--- /dev/null
+++ b/external/badvpn_dns/tun2socks/tun2socks.c
@@ -0,0 +1,2138 @@
+/*
+ * Copyright (C) Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * Contributions:
+ * Transparent DNS: Copyright (C) Kerem Hadimli <kerem.hadimli@xxxxxxxxx>
+ * 
+ * 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 <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <limits.h>
+
+// PSIPHON
+#include "jni.h"
+
+#include <misc/version.h>
+#include <misc/loggers_string.h>
+#include <misc/loglevel.h>
+#include <misc/minmax.h>
+#include <misc/offset.h>
+#include <misc/dead.h>
+#include <misc/ipv4_proto.h>
+#include <misc/ipv6_proto.h>
+#include <misc/udp_proto.h>
+#include <misc/byteorder.h>
+#include <misc/balloc.h>
+#include <misc/open_standard_streams.h>
+#include <misc/read_file.h>
+#include <misc/ipaddr6.h>
+#include <misc/concat_strings.h>
+#include <structure/LinkedList1.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BSignal.h>
+#include <system/BAddr.h>
+#include <system/BNetwork.h>
+#include <flow/SinglePacketBuffer.h>
+#include <socksclient/BSocksClient.h>
+#include <tuntap/BTap.h>
+#include <lwip/init.h>
+#include <lwip/tcp_impl.h>
+#include <lwip/netif.h>
+#include <lwip/tcp.h>
+#include <tun2socks/SocksUdpGwClient.h>
+
+#ifndef BADVPN_USE_WINAPI
+#include <base/BLog_syslog.h>
+#endif
+
+#include <tun2socks/tun2socks.h>
+
+#include <generated/blog_channel_tun2socks.h>
+
+#define LOGGER_STDOUT 1
+#define LOGGER_SYSLOG 2
+
+#define SYNC_DECL \
+    BPending sync_mark; \
+
+#define SYNC_FROMHERE \
+    BPending_Init(&sync_mark, BReactor_PendingGroup(&ss), NULL, NULL); \
+    BPending_Set(&sync_mark);
+
+#define SYNC_BREAK \
+    BPending_Free(&sync_mark);
+
+#define SYNC_COMMIT \
+    BReactor_Synchronize(&ss, &sync_mark.base); \
+    BPending_Free(&sync_mark);
+
+
+// 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];
+    char *tundev;
+    char *netif_ipaddr;
+    char *netif_netmask;
+    char *netif_ip6addr;
+    char *socks_server_addr;
+    char *username;
+    char *password;
+    char *password_file;
+    int append_source_to_username;
+    char *udpgw_remote_server_addr;
+    int udpgw_max_connections;
+    int udpgw_connection_buffer_size;
+    int udpgw_transparent_dns;
+
+    // ==== PSIPHON ====
+    int tun_fd;
+    int tun_mtu;
+    int set_signal;
+    // ==== PSIPHON ====
+} options;
+
+// TCP client
+struct tcp_client {
+    dead_t dead;
+    dead_t dead_client;
+    LinkedList1Node list_node;
+    BAddr local_addr;
+    BAddr remote_addr;
+    struct tcp_pcb *pcb;
+    int client_closed;
+    uint8_t buf[TCP_WND];
+    int buf_used;
+    char *socks_username;
+    BSocksClient socks_client;
+    int socks_up;
+    int socks_closed;
+    StreamPassInterface *socks_send_if;
+    StreamRecvInterface *socks_recv_if;
+    uint8_t socks_recv_buf[CLIENT_SOCKS_RECV_BUF_SIZE];
+    int socks_recv_buf_used;
+    int socks_recv_buf_sent;
+    int socks_recv_waiting;
+    int socks_recv_tcp_pending;
+};
+
+// IP address of netif
+BIPAddr netif_ipaddr;
+
+// netmask of netif
+BIPAddr netif_netmask;
+
+// IP6 address of netif
+struct ipv6_addr netif_ip6addr;
+
+// SOCKS server address
+BAddr socks_server_addr;
+
+// allocated password file contents
+uint8_t *password_file_contents;
+
+// SOCKS authentication information
+struct BSocksClient_auth_info socks_auth_info[2];
+size_t socks_num_auth_info;
+
+// remote udpgw server addr, if provided
+BAddr udpgw_remote_server_addr;
+
+// reactor
+BReactor ss;
+
+// set to 1 by terminate
+int quitting;
+
+// TUN device
+BTap device;
+
+// device write buffer
+uint8_t *device_write_buf;
+
+// device reading
+SinglePacketBuffer device_read_buffer;
+PacketPassInterface device_read_interface;
+
+// udpgw client
+SocksUdpGwClient udpgw_client;
+int udp_mtu;
+
+// TCP timer
+BTimer tcp_timer;
+
+// job for initializing lwip
+BPending lwip_init_job;
+
+// lwip netif
+int have_netif;
+struct netif netif;
+
+// lwip TCP listener
+struct tcp_pcb *listener;
+
+// lwip TCP/IPv6 listener
+struct tcp_pcb *listener_ip6;
+
+// TCP clients
+LinkedList1 tcp_clients;
+
+// number of clients
+int num_clients;
+
+// ==== PSIPHON ====
+static void run (void);
+static void init_arguments (const char* program_name);
+// ==== PSIPHON ====
+
+static void terminate (void);
+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 BAddr baddr_from_lwip (int is_ipv6, const ipX_addr_t *ipx_addr, uint16_t port_hostorder);
+static void lwip_init_job_hadler (void *unused);
+static void tcp_timer_handler (void *unused);
+static void device_error_handler (void *unused);
+static void device_read_handler_send (void *unused, uint8_t *data, int data_len);
+static int process_device_udp_packet (uint8_t *data, int data_len);
+static err_t netif_init_func (struct netif *netif);
+static err_t netif_output_func (struct netif *netif, struct pbuf *p, ip_addr_t *ipaddr);
+static err_t netif_output_ip6_func (struct netif *netif, struct pbuf *p, ip6_addr_t *ipaddr);
+static err_t common_netif_output (struct netif *netif, struct pbuf *p);
+static err_t netif_input_func (struct pbuf *p, struct netif *inp);
+static void client_logfunc (struct tcp_client *client);
+static void client_log (struct tcp_client *client, int level, const char *fmt, ...);
+static err_t listener_accept_func (void *arg, struct tcp_pcb *newpcb, err_t err);
+static void client_handle_freed_client (struct tcp_client *client);
+static void client_free_client (struct tcp_client *client);
+static void client_abort_client (struct tcp_client *client);
+static void client_free_socks (struct tcp_client *client);
+static void client_murder (struct tcp_client *client);
+static void client_dealloc (struct tcp_client *client);
+static void client_err_func (void *arg, err_t err);
+static err_t client_recv_func (void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
+static void client_socks_handler (struct tcp_client *client, int event);
+static void client_send_to_socks (struct tcp_client *client);
+static void client_socks_send_handler_done (struct tcp_client *client, int data_len);
+static void client_socks_recv_initiate (struct tcp_client *client);
+static void client_socks_recv_handler_done (struct tcp_client *client, int data_len);
+static int client_socks_recv_send_out (struct tcp_client *client);
+static err_t client_sent_func (void *arg, struct tcp_pcb *tpcb, u16_t len);
+static void udpgw_client_handler_received (void *unused, BAddr local_addr, BAddr remote_addr, const uint8_t *data, int data_len);
+
+
+//==== PSIPHON ====
+
+
+JNIEnv* g_env = 0;
+
+void PsiphonLog(const char *levelStr, const char *channelStr, const char *msgStr)
+{
+    if (!g_env)
+    {
+        return;
+    }
+    // Note: we could cache the class and method references if log is called frequently
+
+    jstring level = (*g_env)->NewStringUTF(g_env, levelStr);
+    jstring channel = (*g_env)->NewStringUTF(g_env, channelStr);
+    jstring msg = (*g_env)->NewStringUTF(g_env, msgStr);
+
+    jclass cls = (*g_env)->FindClass(g_env, "org/torproject/android/vpn/Tun2Socks");
+    jmethodID logMethod = (*g_env)->GetStaticMethodID(g_env, cls, "logTun2Socks", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+    (*g_env)->CallStaticVoidMethod(g_env, cls, logMethod, level, channel, msg);
+
+    (*g_env)->DeleteLocalRef(g_env, cls);
+
+    (*g_env)->DeleteLocalRef(g_env, level);
+    (*g_env)->DeleteLocalRef(g_env, channel);
+    (*g_env)->DeleteLocalRef(g_env, msg);
+}
+
+// org.torproject.android.vpn.Tun2Socks.runTun2Socks
+JNIEXPORT jint JNICALL Java_org_torproject_android_vpn_Tun2Socks_runTun2Socks(
+    JNIEnv* env,
+    jclass cls,
+    jint vpnInterfaceFileDescriptor,
+    jint vpnInterfaceMTU,
+    jstring vpnIpAddress,
+    jstring vpnNetMask,
+    jstring socksServerAddress,
+    jstring udpgwServerAddress,
+    jint udpgwTransparentDNS)
+{
+    g_env = env;
+
+    const char* vpnIpAddressStr = (*env)->GetStringUTFChars(env, vpnIpAddress, 0);
+    const char* vpnNetMaskStr = (*env)->GetStringUTFChars(env, vpnNetMask, 0);
+    const char* socksServerAddressStr = (*env)->GetStringUTFChars(env, socksServerAddress, 0);
+    const char* udpgwServerAddressStr = (*env)->GetStringUTFChars(env, udpgwServerAddress, 0);
+
+    init_arguments("Drobot tun2socks");
+
+    options.netif_ipaddr = (char*)vpnIpAddressStr;
+    options.netif_netmask = (char*)vpnNetMaskStr;
+    options.socks_server_addr = (char*)socksServerAddressStr;
+    options.udpgw_remote_server_addr = (char*)udpgwServerAddressStr;
+    options.udpgw_transparent_dns = udpgwTransparentDNS;
+    options.tun_fd = vpnInterfaceFileDescriptor;
+    options.tun_mtu = vpnInterfaceMTU;
+    options.set_signal = 0;
+    options.loglevel = 4;
+
+    BLog_InitPsiphon();
+
+    run();
+
+    (*env)->ReleaseStringUTFChars(env, vpnIpAddress, vpnIpAddressStr);
+    (*env)->ReleaseStringUTFChars(env, vpnNetMask, vpnNetMaskStr);
+    (*env)->ReleaseStringUTFChars(env, socksServerAddress, socksServerAddressStr);
+    (*env)->ReleaseStringUTFChars(env, udpgwServerAddress, udpgwServerAddressStr);
+
+    g_env = 0;
+
+    // TODO: return success/error
+
+    return 1;
+}
+
+JNIEXPORT jint JNICALL Java_org_torproject_android_vpn_Tun2Socks_terminateTun2Socks(
+    jclass cls,
+    JNIEnv* env)
+{
+    terminate();
+    return 0;
+}
+
+// from tcp_helper.c
+/** Remove all pcbs on the given list. */
+static void tcp_remove(struct tcp_pcb* pcb_list)
+{
+    struct tcp_pcb *pcb = pcb_list;
+    struct tcp_pcb *pcb2;
+
+    while(pcb != NULL)
+    {
+        pcb2 = pcb;
+        pcb = pcb->next;
+        tcp_abort(pcb2);
+    }
+}
+
+
+
+void run()
+{
+    // 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);
+    
+    // clear password contents pointer
+    password_file_contents = NULL;
+    
+    // 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;
+    }
+    
+    // set not quitting
+    quitting = 0;
+    
+    // PSIPHON
+    if (options.set_signal) {
+        // setup signal handler
+        if (!BSignal_Init(&ss, signal_handler, NULL)) {
+            BLog(BLOG_ERROR, "BSignal_Init failed");
+            goto fail2;
+        }
+    }
+    
+    // PSIPHON
+    if (options.tun_fd) {
+        // use supplied file descriptor
+        if (!BTap_InitWithFD(&device, &ss, options.tun_fd, options.tun_mtu, device_error_handler, NULL, 1)) {
+            BLog(BLOG_ERROR, "BTap_InitWithFD failed");
+            goto fail3;
+        }
+    } else {
+        // init TUN device
+        if (!BTap_Init(&device, &ss, options.tundev, device_error_handler, NULL, 1)) {
+            BLog(BLOG_ERROR, "BTap_Init failed");
+            goto fail3;
+        }
+    }
+    
+    // NOTE: the order of the following is important:
+    // first device writing must evaluate,
+    // then lwip (so it can send packets to the device),
+    // then device reading (so it can pass received packets to lwip).
+    
+    // init device reading
+    PacketPassInterface_Init(&device_read_interface, BTap_GetMTU(&device), device_read_handler_send, NULL, BReactor_PendingGroup(&ss));
+    if (!SinglePacketBuffer_Init(&device_read_buffer, BTap_GetOutput(&device), &device_read_interface, BReactor_PendingGroup(&ss))) {
+        BLog(BLOG_ERROR, "SinglePacketBuffer_Init failed");
+        goto fail4;
+    }
+    
+    if (options.udpgw_remote_server_addr && !options.udpgw_transparent_dns) {
+        // compute maximum UDP payload size we need to pass through udpgw
+        udp_mtu = BTap_GetMTU(&device) - (int)(sizeof(struct ipv4_header) + sizeof(struct udp_header));
+        if (options.netif_ip6addr) {
+            int udp_ip6_mtu = BTap_GetMTU(&device) - (int)(sizeof(struct ipv6_header) + sizeof(struct udp_header));
+            if (udp_mtu < udp_ip6_mtu) {
+                udp_mtu = udp_ip6_mtu;
+            }
+        }
+        if (udp_mtu < 0) {
+            udp_mtu = 0;
+        }
+        
+        // make sure our UDP payloads aren't too large for udpgw
+        int udpgw_mtu = udpgw_compute_mtu(udp_mtu);
+        if (udpgw_mtu < 0 || udpgw_mtu > PACKETPROTO_MAXPAYLOAD) {
+            BLog(BLOG_ERROR, "device MTU is too large for UDP");
+            goto fail4a;
+        }
+        
+        // init udpgw client
+        if (!SocksUdpGwClient_Init(&udpgw_client, udp_mtu, DEFAULT_UDPGW_MAX_CONNECTIONS, options.udpgw_connection_buffer_size, UDPGW_KEEPALIVE_TIME,
+                                   socks_server_addr, socks_auth_info, socks_num_auth_info,
+                                   udpgw_remote_server_addr, UDPGW_RECONNECT_TIME, &ss, NULL, udpgw_client_handler_received
+        )) {
+            BLog(BLOG_ERROR, "SocksUdpGwClient_Init failed");
+            goto fail4a;
+        }
+    }
+    
+    // init lwip init job
+    BPending_Init(&lwip_init_job, BReactor_PendingGroup(&ss), lwip_init_job_hadler, NULL);
+    BPending_Set(&lwip_init_job);
+    
+    // init device write buffer
+    if (!(device_write_buf = (uint8_t *)BAlloc(BTap_GetMTU(&device)))) {
+        BLog(BLOG_ERROR, "BAlloc failed");
+        goto fail5;
+    }
+    
+    // init TCP timer
+    // it won't trigger before lwip is initialized, becuase the lwip init is a job
+    BTimer_Init(&tcp_timer, TCP_TMR_INTERVAL, tcp_timer_handler, NULL);
+    BReactor_SetTimer(&ss, &tcp_timer);
+    
+    // set no netif
+    have_netif = 0;
+    
+    // set no listener
+    listener = NULL;
+    listener_ip6 = NULL;
+    
+    // init clients list
+    LinkedList1_Init(&tcp_clients);
+    
+    // init number of clients
+    num_clients = 0;
+    
+    // enter event loop
+    BLog(BLOG_NOTICE, "entering event loop");
+    BReactor_Exec(&ss);
+    
+    // free clients
+    LinkedList1Node *node;
+    while (node = LinkedList1_GetFirst(&tcp_clients)) {
+        struct tcp_client *client = UPPER_OBJECT(node, struct tcp_client, list_node);
+        client_murder(client);
+    }
+    
+    // free listener
+    if (listener_ip6) {
+        tcp_close(listener_ip6);
+    }
+    if (listener) {
+        tcp_close(listener);
+    }
+    
+    // free netif
+    if (have_netif) {
+        netif_remove(&netif);
+    }
+
+    // ==== PSIPHON ====
+    // The existing tun2socks cleanup sometimes leaves some TCP connections
+    // in the TIME_WAIT state. With regular tun2socks, these will be cleaned up
+    // by process termination. Since we re-init tun2socks within one process,
+    // and tcp_bind_to_netif requires no TCP connections bound to the network
+    // interface, we need to explicitly clean these up. Since we're also closing
+    // both sources of tunneled packets (VPN fd and SOCKS sockets), there should
+    // be no need to keep these TCP connections in TIME_WAIT between tun2socks
+    // invocations.
+    // After further testing, we found at least one TCP connection left in the
+    // active list (with state SYN_RCVD). Now we're aborting the active list
+    // as well, and the bound list for good measure.
+    tcp_remove(tcp_bound_pcbs);
+    tcp_remove(tcp_active_pcbs);
+    tcp_remove(tcp_tw_pcbs);
+    // ==== PSIPHON ====
+    
+
+    BReactor_RemoveTimer(&ss, &tcp_timer);
+    BFree(device_write_buf);
+fail5:
+    BPending_Free(&lwip_init_job);
+    if (options.udpgw_remote_server_addr && !options.udpgw_transparent_dns) {
+        SocksUdpGwClient_Free(&udpgw_client);
+    }
+fail4a:
+    SinglePacketBuffer_Free(&device_read_buffer);
+fail4:
+    PacketPassInterface_Free(&device_read_interface);
+    BTap_Free(&device);
+fail3:
+    BSignal_Finish();
+fail2:
+    BReactor_Free(&ss);
+fail1:
+    BFree(password_file_contents);
+    BLog(BLOG_NOTICE, "exiting");
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+}
+
+void terminate (void)
+{
+    ASSERT(!quitting)
+    
+    BLog(BLOG_NOTICE, "tearing down");
+    
+    // set quitting
+    quitting = 1;
+    
+    // exit event loop
+    BReactor_Quit(&ss, 1);
+}
+
+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"
+        "        [--tundev <name>]\n"
+        "        --netif-ipaddr <ipaddr>\n"
+        "        --netif-netmask <ipnetmask>\n"
+        "        --socks-server-addr <addr>\n"
+        "        [--netif-ip6addr <addr>]\n"
+        "        [--username <username>]\n"
+        "        [--password <password>]\n"
+        "        [--password-file <file>]\n"
+        "        [--append-source-to-username]\n"
+        "        [--udpgw-remote-server-addr <addr>]\n"
+        "        [--udpgw-max-connections <number>]\n"
+        "        [--udpgw-connection-buffer-size <number>]\n"
+        "        [--udpgw-transparent-dns]\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");
+}
+
+//==== PSIPHON ====
+
+void init_arguments (const char* program_name)
+{
+    options.help = 0;
+    options.version = 0;
+    options.logger = LOGGER_STDOUT;
+    #ifndef BADVPN_USE_WINAPI
+    options.logger_syslog_facility = "daemon";
+    options.logger_syslog_ident = (char*)program_name;
+    #endif
+    options.loglevel = -1;
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        options.loglevels[i] = -1;
+    }
+    options.tundev = NULL;
+    options.netif_ipaddr = NULL;
+    options.netif_netmask = NULL;
+    options.netif_ip6addr = NULL;
+    options.socks_server_addr = NULL;
+    options.username = NULL;
+    options.password = NULL;
+    options.password_file = NULL;
+    options.append_source_to_username = 0;
+    options.udpgw_remote_server_addr = NULL;
+    options.udpgw_max_connections = DEFAULT_UDPGW_MAX_CONNECTIONS;
+    options.udpgw_connection_buffer_size = DEFAULT_UDPGW_CONNECTION_BUFFER_SIZE;
+    options.udpgw_transparent_dns = 0;
+
+    options.tun_fd = 0;
+    options.set_signal = 1;
+}
+
+//==== PSIPHON ====
+
+int parse_arguments (int argc, char *argv[])
+{
+    if (argc <= 0) {
+        return 0;
+    }
+    
+    // PSIPHON
+    init_arguments(argv[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, "--tundev")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.tundev = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--netif-ipaddr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.netif_ipaddr = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--netif-netmask")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.netif_netmask = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--netif-ip6addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.netif_ip6addr = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--socks-server-addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.socks_server_addr = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--username")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.username = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--password")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.password = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--password-file")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.password_file = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--append-source-to-username")) {
+            options.append_source_to_username = 1;
+        }
+        else if (!strcmp(arg, "--udpgw-remote-server-addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.udpgw_remote_server_addr = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--udpgw-max-connections")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.udpgw_max_connections = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--udpgw-connection-buffer-size")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.udpgw_connection_buffer_size = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--udpgw-transparent-dns")) {
+            options.udpgw_transparent_dns = 1;
+        }
+        else {
+            fprintf(stderr, "unknown option: %s\n", arg);
+            return 0;
+        }
+    }
+    
+    if (options.help || options.version) {
+        return 1;
+    }
+    
+    if (!options.netif_ipaddr) {
+        fprintf(stderr, "--netif-ipaddr is required\n");
+        return 0;
+    }
+    
+    if (!options.netif_netmask) {
+        fprintf(stderr, "--netif-netmask is required\n");
+        return 0;
+    }
+    
+    if (!options.socks_server_addr) {
+        fprintf(stderr, "--socks-server-addr is required\n");
+        return 0;
+    }
+    
+    if (options.username) {
+        if (!options.password && !options.password_file) {
+            fprintf(stderr, "username given but password not given\n");
+            return 0;
+        }
+        
+        if (options.password && options.password_file) {
+            fprintf(stderr, "--password and --password-file cannot both be given\n");
+            return 0;
+        }
+    }
+    
+    return 1;
+}
+
+int process_arguments (void)
+{
+    ASSERT(!password_file_contents)
+    
+    // resolve netif ipaddr
+    if (!BIPAddr_Resolve(&netif_ipaddr, options.netif_ipaddr, 0)) {
+        BLog(BLOG_ERROR, "netif ipaddr: BIPAddr_Resolve failed");
+        return 0;
+    }
+    if (netif_ipaddr.type != BADDR_TYPE_IPV4) {
+        BLog(BLOG_ERROR, "netif ipaddr: must be an IPv4 address");
+        return 0;
+    }
+    
+    // resolve netif netmask
+    if (!BIPAddr_Resolve(&netif_netmask, options.netif_netmask, 0)) {
+        BLog(BLOG_ERROR, "netif netmask: BIPAddr_Resolve failed");
+        return 0;
+    }
+    if (netif_netmask.type != BADDR_TYPE_IPV4) {
+        BLog(BLOG_ERROR, "netif netmask: must be an IPv4 address");
+        return 0;
+    }
+    
+    // parse IP6 address
+    if (options.netif_ip6addr) {
+        if (!ipaddr6_parse_ipv6_addr(options.netif_ip6addr, &netif_ip6addr)) {
+            BLog(BLOG_ERROR, "netif ip6addr: incorrect");
+            return 0;
+        }
+    }
+    
+    // resolve SOCKS server address
+    if (!BAddr_Parse2(&socks_server_addr, options.socks_server_addr, NULL, 0, 0)) {
+        BLog(BLOG_ERROR, "socks server addr: BAddr_Parse2 failed");
+        return 0;
+    }
+    
+    // add none socks authentication method
+    socks_auth_info[0] = BSocksClient_auth_none();
+    socks_num_auth_info = 1;
+    
+    // add password socks authentication method
+    if (options.username) {
+        const char *password;
+        size_t password_len;
+        if (options.password) {
+            password = options.password;
+            password_len = strlen(options.password);
+        } else {
+            if (!read_file(options.password_file, &password_file_contents, &password_len)) {
+                BLog(BLOG_ERROR, "failed to read password file");
+                return 0;
+            }
+            password = (char *)password_file_contents;
+        }
+        
+        socks_auth_info[socks_num_auth_info++] = BSocksClient_auth_password(
+            options.username, strlen(options.username),
+            password, password_len
+        );
+    }
+    
+    // resolve remote udpgw server address
+    if (options.udpgw_remote_server_addr) {
+        if (!BAddr_Parse2(&udpgw_remote_server_addr, options.udpgw_remote_server_addr, NULL, 0, 0)) {
+            BLog(BLOG_ERROR, "remote udpgw server addr: BAddr_Parse2 failed");
+            return 0;
+        }
+    }
+    
+    return 1;
+}
+
+void signal_handler (void *unused)
+{
+    ASSERT(!quitting)
+    
+    BLog(BLOG_NOTICE, "termination requested");
+    
+    terminate();
+}
+
+BAddr baddr_from_lwip (int is_ipv6, const ipX_addr_t *ipx_addr, uint16_t port_hostorder)
+{
+    BAddr addr;
+    if (is_ipv6) {
+        BAddr_InitIPv6(&addr, (uint8_t *)ipx_addr->ip6.addr, hton16(port_hostorder));
+    } else {
+        BAddr_InitIPv4(&addr, ipx_addr->ip4.addr, hton16(port_hostorder));
+    }
+    return addr;
+}
+
+void lwip_init_job_hadler (void *unused)
+{
+    ASSERT(!quitting)
+    ASSERT(netif_ipaddr.type == BADDR_TYPE_IPV4)
+    ASSERT(netif_netmask.type == BADDR_TYPE_IPV4)
+    ASSERT(!have_netif)
+    ASSERT(!listener)
+    ASSERT(!listener_ip6)
+    
+    BLog(BLOG_DEBUG, "lwip init");
+    
+    // NOTE: the device may fail during this, but there's no harm in not checking
+    // for that at every step
+    
+    // init lwip
+    lwip_init();
+    
+    // make addresses for netif
+    ip_addr_t addr;
+    addr.addr = netif_ipaddr.ipv4;
+    ip_addr_t netmask;
+    netmask.addr = netif_netmask.ipv4;
+    ip_addr_t gw;
+    ip_addr_set_any(&gw);
+    
+    // init netif
+    if (!netif_add(&netif, &addr, &netmask, &gw, NULL, netif_init_func, netif_input_func)) {
+        BLog(BLOG_ERROR, "netif_add failed");
+        goto fail;
+    }
+    have_netif = 1;
+    
+    // set netif up
+    netif_set_up(&netif);
+    
+    // set netif pretend TCP
+    netif_set_pretend_tcp(&netif, 1);
+    
+    // set netif default
+    netif_set_default(&netif);
+    
+    if (options.netif_ip6addr) {
+        // add IPv6 address
+        memcpy(netif_ip6_addr(&netif, 0), netif_ip6addr.bytes, sizeof(netif_ip6addr.bytes));
+        netif_ip6_addr_set_state(&netif, 0, IP6_ADDR_VALID);
+    }
+    
+    // init listener
+    struct tcp_pcb *l = tcp_new();
+    if (!l) {
+        BLog(BLOG_ERROR, "tcp_new failed");
+        goto fail;
+    }
+    
+    // bind listener
+    if (tcp_bind_to_netif(l, "ho0") != ERR_OK) {
+        BLog(BLOG_ERROR, "tcp_bind_to_netif failed");
+        tcp_close(l);
+        goto fail;
+    }
+    
+    // listen listener
+    if (!(listener = tcp_listen(l))) {
+        BLog(BLOG_ERROR, "tcp_listen failed");
+        tcp_close(l);
+        goto fail;
+    }
+    
+    // setup listener accept handler
+    tcp_accept(listener, listener_accept_func);
+    
+    if (options.netif_ip6addr) {
+        struct tcp_pcb *l_ip6 = tcp_new_ip6();
+        if (!l_ip6) {
+            BLog(BLOG_ERROR, "tcp_new_ip6 failed");
+            goto fail;
+        }
+        
+        if (tcp_bind_to_netif(l_ip6, "ho0") != ERR_OK) {
+            BLog(BLOG_ERROR, "tcp_bind_to_netif failed");
+            tcp_close(l_ip6);
+            goto fail;
+        }
+        
+        if (!(listener_ip6 = tcp_listen(l_ip6))) {
+            BLog(BLOG_ERROR, "tcp_listen failed");
+            tcp_close(l_ip6);
+            goto fail;
+        }
+        
+        tcp_accept(listener_ip6, listener_accept_func);
+    }
+    
+    return;
+    
+fail:
+    if (!quitting) {
+        terminate();
+    }
+}
+
+void tcp_timer_handler (void *unused)
+{
+    ASSERT(!quitting)
+    
+    BLog(BLOG_DEBUG, "TCP timer");
+    
+    // schedule next timer
+    // TODO: calculate timeout so we don't drift
+    BReactor_SetTimer(&ss, &tcp_timer);
+    
+    tcp_tmr();
+    return;
+}
+
+void device_error_handler (void *unused)
+{
+    ASSERT(!quitting)
+    
+    BLog(BLOG_ERROR, "device error");
+    
+    terminate();
+    return;
+}
+
+void device_read_handler_send (void *unused, uint8_t *data, int data_len)
+{
+    ASSERT(!quitting)
+    ASSERT(data_len >= 0)
+    
+    BLog(BLOG_DEBUG, "device: received packet");
+    
+    // accept packet
+    PacketPassInterface_Done(&device_read_interface);
+    
+    // process DNS directly
+    if (process_device_dns_packet(data, data_len)) {
+    	BLog(BLOG_INFO, "end processing dns packet");
+    	return;
+    }
+
+    // process UDP directly
+    if (process_device_udp_packet(data, data_len)) {
+        return;
+    }
+    
+    // obtain pbuf
+    if (data_len > UINT16_MAX) {
+        BLog(BLOG_WARNING, "device read: packet too large");
+        return;
+    }
+    struct pbuf *p = pbuf_alloc(PBUF_RAW, data_len, PBUF_POOL);
+    if (!p) {
+        BLog(BLOG_WARNING, "device read: pbuf_alloc failed");
+        return;
+    }
+    
+    // write packet to pbuf
+    ASSERT_FORCE(pbuf_take(p, data, data_len) == ERR_OK)
+    
+    // pass pbuf to input
+    if (netif.input(p, &netif) != ERR_OK) {
+        BLog(BLOG_WARNING, "device read: input failed");
+        pbuf_free(p);
+    }
+}
+
+int process_device_dns_packet (uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+
+    // do nothing if we don't have dnsgw
+    if (!options.udpgw_remote_server_addr || !options.udpgw_transparent_dns) {
+    	BLog(BLOG_WARNING, "No dnsgw to process dns packet");
+    	goto fail;
+    }
+
+    static BAddr local_addr;
+    static BAddr remote_addr;
+    static int init = 0;
+
+    int to_dns;
+    int from_dns;
+    int packet_length = 0;
+
+    uint8_t ip_version = 0;
+    if (data_len > 0) {
+        ip_version = (data[0] >> 4);
+    }
+
+    switch (ip_version) {
+        case 4: {
+            // ignore non-UDP packets
+            if (data_len < sizeof(struct ipv4_header) || data[offsetof(struct ipv4_header, protocol)] != IPV4_PROTOCOL_UDP) {
+                goto fail;
+            }
+
+            // parse IPv4 header
+            struct ipv4_header ipv4_header;
+            if (!ipv4_check(data, data_len, &ipv4_header, &data, &data_len)) {
+                goto fail;
+            }
+
+            // parse UDP
+            struct udp_header udp_header;
+            if (!udp_check(data, data_len, &udp_header, &data, &data_len)) {
+                goto fail;
+            }
+
+            // verify UDP checksum
+            uint16_t checksum_in_packet = udp_header.checksum;
+            udp_header.checksum = 0;
+            uint16_t checksum_computed = udp_checksum(&udp_header, data, data_len, ipv4_header.source_address, ipv4_header.destination_address);
+            if (checksum_in_packet != checksum_computed) {
+                goto fail;
+            }
+
+            // to port 53 is considered a DNS packet
+            to_dns = udp_header.dest_port == hton16(53);
+
+            // from port 8153 is considered a DNS packet
+            from_dns = udp_header.source_port == udpgw_remote_server_addr.ipv4.port;
+
+            // if not DNS packet, just bypass it.
+            if (!to_dns && !from_dns) {
+            	BLog(BLOG_WARNING, "No to_dns and from_dns packet: bypass");
+            	goto fail;
+            }
+
+            // modify DNS packet
+            if (to_dns) {
+                BLog(BLOG_INFO, "UDP: to DNS %d bytes", data_len);
+
+                // construct addresses
+                if (!init) {
+                    init = 1;
+                    BAddr_InitIPv4(&local_addr, ipv4_header.source_address, udp_header.source_port);
+                    BAddr_InitIPv4(&remote_addr, ipv4_header.destination_address, udp_header.dest_port);
+                }
+
+                // build IP header
+                ipv4_header.destination_address = udpgw_remote_server_addr.ipv4.ip;
+                ipv4_header.source_address = netif_ipaddr.ipv4;
+
+                // build UDP header
+                udp_header.dest_port = udpgw_remote_server_addr.ipv4.port;
+
+            } else if (from_dns) {
+
+                // if not initialized
+                if (!init) {
+                    goto fail;
+                }
+
+                BLog(BLOG_INFO, "UDP: from DNS %d bytes", data_len);
+
+                // build IP header
+                ipv4_header.source_address = remote_addr.ipv4.ip;
+                ipv4_header.destination_address = local_addr.ipv4.ip;
+
+                // build UDP header
+                udp_header.source_port = remote_addr.ipv4.port;
+
+            }
+
+            // update IPv4 header's checksum
+            ipv4_header.checksum = hton16(0);
+            ipv4_header.checksum = ipv4_checksum(&ipv4_header, NULL, 0);
+
+            // update UDP header's checksum
+            udp_header.checksum = hton16(0);
+            udp_header.checksum = udp_checksum(&udp_header, data, data_len,
+                    ipv4_header.source_address, ipv4_header.destination_address);
+
+            // write packet
+            memcpy(device_write_buf, &ipv4_header, sizeof(ipv4_header));
+            memcpy(device_write_buf + sizeof(ipv4_header), &udp_header, sizeof(udp_header));
+            memcpy(device_write_buf + sizeof(ipv4_header) + sizeof(udp_header), data, data_len);
+            packet_length = sizeof(ipv4_header) + sizeof(udp_header) + data_len;
+
+        } break;
+
+        case 6: {
+            // TODO: support IPv6 DNS Gateway
+            goto fail;
+        } break;
+
+        default: {
+            goto fail;
+        } break;
+    }
+
+    // submit packet
+    BTap_Send(&device, device_write_buf, packet_length);
+
+    return 1;
+
+fail:
+    return 0;
+}
+
+int process_device_udp_packet (uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    
+    // do nothing if we don't have udpgw
+    if (!options.udpgw_remote_server_addr || options.udpgw_transparent_dns) {
+        goto fail;
+    }
+    
+    BAddr local_addr;
+    BAddr remote_addr;
+    int is_dns;
+    
+    uint8_t ip_version = 0;
+    if (data_len > 0) {
+        ip_version = (data[0] >> 4);
+    }
+    
+    switch (ip_version) {
+        case 4: {
+            // ignore non-UDP packets
+            if (data_len < sizeof(struct ipv4_header) || data[offsetof(struct ipv4_header, protocol)] != IPV4_PROTOCOL_UDP) {
+                goto fail;
+            }
+            
+            // parse IPv4 header
+            struct ipv4_header ipv4_header;
+            if (!ipv4_check(data, data_len, &ipv4_header, &data, &data_len)) {
+                goto fail;
+            }
+            
+            // parse UDP
+            struct udp_header udp_header;
+            if (!udp_check(data, data_len, &udp_header, &data, &data_len)) {
+                goto fail;
+            }
+            
+            // verify UDP checksum
+            uint16_t checksum_in_packet = udp_header.checksum;
+            udp_header.checksum = 0;
+            uint16_t checksum_computed = udp_checksum(&udp_header, data, data_len, ipv4_header.source_address, ipv4_header.destination_address);
+            if (checksum_in_packet != checksum_computed) {
+                goto fail;
+            }
+            
+            BLog(BLOG_INFO, "UDP: from device %d bytes", data_len);
+            
+            // construct addresses
+            BAddr_InitIPv4(&local_addr, ipv4_header.source_address, udp_header.source_port);
+          //  BAddr_InitIPv4(&remote_addr, ipv4_header.destination_address, udp_header.dest_port);
+            
+            // if transparent DNS is enabled, any packet arriving at out netif
+            // address to port 53 is considered a DNS packet
+            is_dns = (options.udpgw_transparent_dns &&
+                      ipv4_header.destination_address == netif_ipaddr.ipv4 &&
+                      udp_header.dest_port == hton16(53));
+
+            if (is_dns)
+				  {//change DNS port to 5400 for Orbot Tor access
+
+            	BAddr_InitIPv4(&remote_addr, ipv4_header.destination_address,udp_header.dest_port);
+            	//BAddr_InitIPv4(&remote_addr, ipv4_header.source_address,hton16(5400));
+
+				  }
+			  else
+				  {
+			  BAddr_InitIPv4(&remote_addr, ipv4_header.destination_address, udp_header.dest_port);
+
+				  }
+
+        } break;
+        
+        case 6: {
+            // ignore if IPv6 support is disabled
+            if (!options.netif_ip6addr) {
+                goto fail;
+            }
+            
+            // ignore non-UDP packets
+            if (data_len < sizeof(struct ipv6_header) || data[offsetof(struct ipv6_header, next_header)] != IPV6_NEXT_UDP) {
+                goto fail;
+            }
+            
+            // parse IPv6 header
+            struct ipv6_header ipv6_header;
+            if (!ipv6_check(data, data_len, &ipv6_header, &data, &data_len)) {
+                goto fail;
+            }
+            
+            // parse UDP
+            struct udp_header udp_header;
+            if (!udp_check(data, data_len, &udp_header, &data, &data_len)) {
+                goto fail;
+            }
+            
+            // verify UDP checksum
+            uint16_t checksum_in_packet = udp_header.checksum;
+            udp_header.checksum = 0;
+            uint16_t checksum_computed = udp_ip6_checksum(&udp_header, data, data_len, ipv6_header.source_address, ipv6_header.destination_address);
+            if (checksum_in_packet != checksum_computed) {
+                goto fail;
+            }
+            
+            BLog(BLOG_INFO, "UDP/IPv6: from device %d bytes", data_len);
+            
+            // construct addresses
+            BAddr_InitIPv6(&local_addr, ipv6_header.source_address, udp_header.source_port);
+            BAddr_InitIPv6(&remote_addr, ipv6_header.destination_address, udp_header.dest_port);
+            
+            // TODO dns
+            is_dns = 0;
+        } break;
+        
+        default: {
+            goto fail;
+        } break;
+    }
+    
+    // check payload length
+    if (data_len > udp_mtu) {
+        BLog(BLOG_ERROR, "packet is too large, cannot send to udpgw");
+        goto fail;
+    }
+    
+    // submit packet to udpgw
+    SocksUdpGwClient_SubmitPacket(&udpgw_client, local_addr, remote_addr, is_dns, data, data_len);
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+err_t netif_init_func (struct netif *netif)
+{
+    BLog(BLOG_DEBUG, "netif func init");
+    
+    netif->name[0] = 'h';
+    netif->name[1] = 'o';
+    netif->output = netif_output_func;
+    netif->output_ip6 = netif_output_ip6_func;
+    
+    return ERR_OK;
+}
+
+err_t netif_output_func (struct netif *netif, struct pbuf *p, ip_addr_t *ipaddr)
+{
+    return common_netif_output(netif, p);
+}
+
+err_t netif_output_ip6_func (struct netif *netif, struct pbuf *p, ip6_addr_t *ipaddr)
+{
+    return common_netif_output(netif, p);
+}
+
+err_t common_netif_output (struct netif *netif, struct pbuf *p)
+{
+    SYNC_DECL
+    
+    BLog(BLOG_DEBUG, "device write: send packet");
+    
+    if (quitting) {
+        return ERR_OK;
+    }
+    
+    // if there is just one chunk, send it directly, else via buffer
+    if (!p->next) {
+        if (p->len > BTap_GetMTU(&device)) {
+            BLog(BLOG_WARNING, "netif func output: no space left");
+            goto out;
+        }
+        
+        SYNC_FROMHERE
+        BTap_Send(&device, (uint8_t *)p->payload, p->len);
+        SYNC_COMMIT
+    } else {
+        int len = 0;
+        do {
+            if (p->len > BTap_GetMTU(&device) - len) {
+                BLog(BLOG_WARNING, "netif func output: no space left");
+                goto out;
+            }
+            memcpy(device_write_buf + len, p->payload, p->len);
+            len += p->len;
+        } while (p = p->next);
+        
+        SYNC_FROMHERE
+        BTap_Send(&device, device_write_buf, len);
+        SYNC_COMMIT
+    }
+    
+out:
+    return ERR_OK;
+}
+
+err_t netif_input_func (struct pbuf *p, struct netif *inp)
+{
+    uint8_t ip_version = 0;
+    if (p->len > 0) {
+        ip_version = (((uint8_t *)p->payload)[0] >> 4);
+    }
+    
+    switch (ip_version) {
+        case 4: {
+            return ip_input(p, inp);
+        } break;
+        case 6: {
+            if (options.netif_ip6addr) {
+                return ip6_input(p, inp);
+            }
+        } break;
+    }
+    
+    pbuf_free(p);
+    return ERR_OK;
+}
+
+void client_logfunc (struct tcp_client *client)
+{
+    char local_addr_s[BADDR_MAX_PRINT_LEN];
+    BAddr_Print(&client->local_addr, local_addr_s);
+    char remote_addr_s[BADDR_MAX_PRINT_LEN];
+    BAddr_Print(&client->remote_addr, remote_addr_s);
+    
+    BLog_Append("%05d (%s %s): ", num_clients, local_addr_s, remote_addr_s);
+}
+
+void client_log (struct tcp_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);
+}
+
+err_t listener_accept_func (void *arg, struct tcp_pcb *newpcb, err_t err)
+{
+    ASSERT(err == ERR_OK)
+    
+    // signal accepted
+    struct tcp_pcb *this_listener = (PCB_ISIPV6(newpcb) ? listener_ip6 : listener);
+    tcp_accepted(this_listener);
+    
+    // allocate client structure
+    struct tcp_client *client = (struct tcp_client *)malloc(sizeof(*client));
+    if (!client) {
+        BLog(BLOG_ERROR, "listener accept: malloc failed");
+        goto fail0;
+    }
+    client->socks_username = NULL;
+    
+    SYNC_DECL
+    SYNC_FROMHERE
+    
+    // read addresses
+    client->local_addr = baddr_from_lwip(PCB_ISIPV6(newpcb), &newpcb->local_ip, newpcb->local_port);
+    client->remote_addr = baddr_from_lwip(PCB_ISIPV6(newpcb), &newpcb->remote_ip, newpcb->remote_port);
+    
+    // get destination address
+    BAddr addr = client->local_addr;
+#ifdef OVERRIDE_DEST_ADDR
+    ASSERT_FORCE(BAddr_Parse2(&addr, OVERRIDE_DEST_ADDR, NULL, 0, 1))
+#endif
+    
+    // add source address to username if requested
+    if (options.username && options.append_source_to_username) {
+        char addr_str[BADDR_MAX_PRINT_LEN];
+        BAddr_Print(&client->remote_addr, addr_str);
+        client->socks_username = concat_strings(3, options.username, "@", addr_str);
+        if (!client->socks_username) {
+            goto fail1;
+        }
+        socks_auth_info[1].password.username = client->socks_username;
+        socks_auth_info[1].password.username_len = strlen(client->socks_username);
+    }
+    
+    // init SOCKS
+    if (!BSocksClient_Init(&client->socks_client, socks_server_addr, socks_auth_info, socks_num_auth_info,
+                           addr, (BSocksClient_handler)client_socks_handler, client, &ss)) {
+        BLog(BLOG_ERROR, "listener accept: BSocksClient_Init failed");
+        goto fail1;
+    }
+    
+    // init dead vars
+    DEAD_INIT(client->dead);
+    DEAD_INIT(client->dead_client);
+    
+    // add to linked list
+    LinkedList1_Append(&tcp_clients, &client->list_node);
+    
+    // increment counter
+    ASSERT(num_clients >= 0)
+    num_clients++;
+    
+    // set pcb
+    client->pcb = newpcb;
+    
+    // set client not closed
+    client->client_closed = 0;
+    
+    // setup handler argument
+    tcp_arg(client->pcb, client);
+    
+    // setup handlers
+    tcp_err(client->pcb, client_err_func);
+    tcp_recv(client->pcb, client_recv_func);
+    
+    // setup buffer
+    client->buf_used = 0;
+    
+    // set SOCKS not up, not closed
+    client->socks_up = 0;
+    client->socks_closed = 0;
+    
+    client_log(client, BLOG_INFO, "accepted");
+    
+    DEAD_ENTER(client->dead_client)
+    SYNC_COMMIT
+    DEAD_LEAVE2(client->dead_client)
+    if (DEAD_KILLED) {
+        return ERR_ABRT;
+    }
+    
+    return ERR_OK;
+    
+fail1:
+    SYNC_BREAK
+    free(client->socks_username);
+    free(client);
+fail0:
+    return ERR_MEM;
+}
+
+void client_handle_freed_client (struct tcp_client *client)
+{
+    ASSERT(!client->client_closed)
+    
+    // pcb was taken care of by the caller
+    
+    // kill client dead var
+    DEAD_KILL(client->dead_client);
+    
+    // set client closed
+    client->client_closed = 1;
+    
+    // if we have data to be sent to SOCKS and can send it, keep sending
+    if (client->buf_used > 0 && !client->socks_closed) {
+        client_log(client, BLOG_INFO, "waiting untill buffered data is sent to SOCKS");
+    } else {
+        if (!client->socks_closed) {
+            client_free_socks(client);
+        } else {
+            client_dealloc(client);
+        }
+    }
+}
+
+void client_free_client (struct tcp_client *client)
+{
+    ASSERT(!client->client_closed)
+    
+    // remove callbacks
+    tcp_err(client->pcb, NULL);
+    tcp_recv(client->pcb, NULL);
+    tcp_sent(client->pcb, NULL);
+    
+    // free pcb
+    err_t err = tcp_close(client->pcb);
+    if (err != ERR_OK) {
+        client_log(client, BLOG_ERROR, "tcp_close failed (%d)", err);
+        tcp_abort(client->pcb);
+    }
+    
+    client_handle_freed_client(client);
+}
+
+void client_abort_client (struct tcp_client *client)
+{
+    ASSERT(!client->client_closed)
+    
+    // remove callbacks
+    tcp_err(client->pcb, NULL);
+    tcp_recv(client->pcb, NULL);
+    tcp_sent(client->pcb, NULL);
+    
+    // free pcb
+    tcp_abort(client->pcb);
+    
+    client_handle_freed_client(client);
+}
+
+void client_free_socks (struct tcp_client *client)
+{
+    ASSERT(!client->socks_closed)
+    
+    // stop sending to SOCKS
+    if (client->socks_up) {
+        // stop receiving from client
+        if (!client->client_closed) {
+            tcp_recv(client->pcb, NULL);
+        }
+    }
+    
+    // free SOCKS
+    BSocksClient_Free(&client->socks_client);
+    
+    // set SOCKS closed
+    client->socks_closed = 1;
+    
+    // if we have data to be sent to the client and we can send it, keep sending
+    if (client->socks_up && (client->socks_recv_buf_used >= 0 || client->socks_recv_tcp_pending > 0) && !client->client_closed) {
+        client_log(client, BLOG_INFO, "waiting until buffered data is sent to client");
+    } else {
+        if (!client->client_closed) {
+            client_free_client(client);
+        } else {
+            client_dealloc(client);
+        }
+    }
+}
+
+void client_murder (struct tcp_client *client)
+{
+    // free client
+    if (!client->client_closed) {
+        // remove callbacks
+        tcp_err(client->pcb, NULL);
+        tcp_recv(client->pcb, NULL);
+        tcp_sent(client->pcb, NULL);
+        
+        // abort
+        tcp_abort(client->pcb);
+        
+        // kill client dead var
+        DEAD_KILL(client->dead_client);
+        
+        // set client closed
+        client->client_closed = 1;
+    }
+    
+    // free SOCKS
+    if (!client->socks_closed) {
+        // free SOCKS
+        BSocksClient_Free(&client->socks_client);
+        
+        // set SOCKS closed
+        client->socks_closed = 1;
+    }
+    
+    // dealloc entry
+    client_dealloc(client);
+}
+
+void client_dealloc (struct tcp_client *client)
+{
+    ASSERT(client->client_closed)
+    ASSERT(client->socks_closed)
+    
+    // decrement counter
+    ASSERT(num_clients > 0)
+    num_clients--;
+    
+    // remove client entry
+    LinkedList1_Remove(&tcp_clients, &client->list_node);
+    
+    // kill dead var
+    DEAD_KILL(client->dead);
+    
+    // free memory
+    free(client->socks_username);
+    free(client);
+}
+
+void client_err_func (void *arg, err_t err)
+{
+    struct tcp_client *client = (struct tcp_client *)arg;
+    ASSERT(!client->client_closed)
+    
+    client_log(client, BLOG_INFO, "client error (%d)", (int)err);
+    
+    // the pcb was taken care of by the caller
+    client_handle_freed_client(client);
+}
+
+err_t client_recv_func (void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
+{
+    struct tcp_client *client = (struct tcp_client *)arg;
+    ASSERT(!client->client_closed)
+    ASSERT(err == ERR_OK) // checked in lwIP source. Otherwise, I've no idea what should
+                          // be done with the pbuf in case of an error.
+    
+    if (!p) {
+        client_log(client, BLOG_INFO, "client closed");
+        client_free_client(client);
+        return ERR_ABRT;
+    }
+    
+    ASSERT(p->tot_len > 0)
+    
+    // check if we have enough buffer
+    if (p->tot_len > sizeof(client->buf) - client->buf_used) {
+        client_log(client, BLOG_ERROR, "no buffer for data !?!");
+        return ERR_MEM;
+    }
+    
+    // copy data to buffer
+    ASSERT_EXECUTE(pbuf_copy_partial(p, client->buf + client->buf_used, p->tot_len, 0) == p->tot_len)
+    client->buf_used += p->tot_len;
+    
+    // if there was nothing in the buffer before, and SOCKS is up, start send data
+    if (client->buf_used == p->tot_len && client->socks_up) {
+        ASSERT(!client->socks_closed) // this callback is removed when SOCKS is closed
+        
+        SYNC_DECL
+        SYNC_FROMHERE
+        client_send_to_socks(client);
+        DEAD_ENTER(client->dead_client)
+        SYNC_COMMIT
+        DEAD_LEAVE2(client->dead_client)
+        if (DEAD_KILLED) {
+            return ERR_ABRT;
+        }
+    }
+    
+    // free pbuff
+    pbuf_free(p);
+    
+    return ERR_OK;
+}
+
+void client_socks_handler (struct tcp_client *client, int event)
+{
+    ASSERT(!client->socks_closed)
+    
+    switch (event) {
+        case BSOCKSCLIENT_EVENT_ERROR: {
+            client_log(client, BLOG_INFO, "SOCKS error");
+            
+            client_free_socks(client);
+        } break;
+        
+        case BSOCKSCLIENT_EVENT_UP: {
+            ASSERT(!client->socks_up)
+            
+            client_log(client, BLOG_INFO, "SOCKS up");
+            
+            // init sending
+            client->socks_send_if = BSocksClient_GetSendInterface(&client->socks_client);
+            StreamPassInterface_Sender_Init(client->socks_send_if, (StreamPassInterface_handler_done)client_socks_send_handler_done, client);
+            
+            // init receiving
+            client->socks_recv_if = BSocksClient_GetRecvInterface(&client->socks_client);
+            StreamRecvInterface_Receiver_Init(client->socks_recv_if, (StreamRecvInterface_handler_done)client_socks_recv_handler_done, client);
+            client->socks_recv_buf_used = -1;
+            client->socks_recv_tcp_pending = 0;
+            if (!client->client_closed) {
+                tcp_sent(client->pcb, client_sent_func);
+            }
+            
+            // set up
+            client->socks_up = 1;
+            
+            // start sending data if there is any
+            if (client->buf_used > 0) {
+                client_send_to_socks(client);
+            }
+            
+            // start receiving data if client is still up
+            if (!client->client_closed) {
+                client_socks_recv_initiate(client);
+            }
+        } break;
+        
+        case BSOCKSCLIENT_EVENT_ERROR_CLOSED: {
+            ASSERT(client->socks_up)
+            
+            client_log(client, BLOG_INFO, "SOCKS closed");
+            
+            client_free_socks(client);
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}
+
+void client_send_to_socks (struct tcp_client *client)
+{
+    ASSERT(!client->socks_closed)
+    ASSERT(client->socks_up)
+    ASSERT(client->buf_used > 0)
+    
+    // schedule sending
+    StreamPassInterface_Sender_Send(client->socks_send_if, client->buf, client->buf_used);
+}
+
+void client_socks_send_handler_done (struct tcp_client *client, int data_len)
+{
+    ASSERT(!client->socks_closed)
+    ASSERT(client->socks_up)
+    ASSERT(client->buf_used > 0)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= client->buf_used)
+    
+    // remove sent data from buffer
+    memmove(client->buf, client->buf + data_len, client->buf_used - data_len);
+    client->buf_used -= data_len;
+    
+    if (!client->client_closed) {
+        // confirm sent data
+        tcp_recved(client->pcb, data_len);
+    }
+    
+    if (client->buf_used > 0) {
+        // send any further data
+        StreamPassInterface_Sender_Send(client->socks_send_if, client->buf, client->buf_used);
+    }
+    else if (client->client_closed) {
+        // client was closed we've sent everything we had buffered; we're done with it
+        client_log(client, BLOG_INFO, "removing after client went down");
+        
+        client_free_socks(client);
+    }
+}
+
+void client_socks_recv_initiate (struct tcp_client *client)
+{
+    ASSERT(!client->client_closed)
+    ASSERT(!client->socks_closed)
+    ASSERT(client->socks_up)
+    ASSERT(client->socks_recv_buf_used == -1)
+    
+    StreamRecvInterface_Receiver_Recv(client->socks_recv_if, client->socks_recv_buf, sizeof(client->socks_recv_buf));
+}
+
+void client_socks_recv_handler_done (struct tcp_client *client, int data_len)
+{
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= sizeof(client->socks_recv_buf))
+    ASSERT(!client->socks_closed)
+    ASSERT(client->socks_up)
+    ASSERT(client->socks_recv_buf_used == -1)
+    
+    // if client was closed, stop receiving
+    if (client->client_closed) {
+        return;
+    }
+    
+    // set amount of data in buffer
+    client->socks_recv_buf_used = data_len;
+    client->socks_recv_buf_sent = 0;
+    client->socks_recv_waiting = 0;
+    
+    // send to client
+    if (client_socks_recv_send_out(client) < 0) {
+        return;
+    }
+    
+    // continue receiving if needed
+    if (client->socks_recv_buf_used == -1) {
+        client_socks_recv_initiate(client);
+    }
+}
+
+int client_socks_recv_send_out (struct tcp_client *client)
+{
+    ASSERT(!client->client_closed)
+    ASSERT(client->socks_up)
+    ASSERT(client->socks_recv_buf_used > 0)
+    ASSERT(client->socks_recv_buf_sent < client->socks_recv_buf_used)
+    ASSERT(!client->socks_recv_waiting)
+    
+    // return value -1 means tcp_abort() was done,
+    // 0 means it wasn't and the client (pcb) is still up
+    
+    do {
+        int to_write = bmin_int(client->socks_recv_buf_used - client->socks_recv_buf_sent, tcp_sndbuf(client->pcb));
+        if (to_write == 0) {
+            break;
+        }
+        
+        err_t err = tcp_write(client->pcb, client->socks_recv_buf + client->socks_recv_buf_sent, to_write, TCP_WRITE_FLAG_COPY);
+        if (err != ERR_OK) {
+            if (err == ERR_MEM) {
+                break;
+            }
+            
+            client_log(client, BLOG_INFO, "tcp_write failed (%d)", (int)err);
+            
+            client_abort_client(client);
+            return -1;
+        }
+        
+        client->socks_recv_buf_sent += to_write;
+        client->socks_recv_tcp_pending += to_write;
+    } while (client->socks_recv_buf_sent < client->socks_recv_buf_used);
+    
+    // start sending now
+    err_t err = tcp_output(client->pcb);
+    if (err != ERR_OK) {
+        client_log(client, BLOG_INFO, "tcp_output failed (%d)", (int)err);
+        
+        client_abort_client(client);
+        return -1;
+    }
+    
+    // more data to queue?
+    if (client->socks_recv_buf_sent < client->socks_recv_buf_used) {
+        if (client->socks_recv_tcp_pending == 0) {
+            client_log(client, BLOG_ERROR, "can't queue data, but all data was confirmed !?!");
+            
+            client_abort_client(client);
+            return -1;
+        }
+        
+        // set waiting, continue in client_sent_func
+        client->socks_recv_waiting = 1;
+        return 0;
+    }
+    
+    // everything was queued
+    client->socks_recv_buf_used = -1;
+    
+    return 0;
+}
+
+err_t client_sent_func (void *arg, struct tcp_pcb *tpcb, u16_t len)
+{
+    struct tcp_client *client = (struct tcp_client *)arg;
+    
+    ASSERT(!client->client_closed)
+    ASSERT(client->socks_up)
+    ASSERT(len > 0)
+    ASSERT(len <= client->socks_recv_tcp_pending)
+    
+    // decrement pending
+    client->socks_recv_tcp_pending -= len;
+    
+    // continue queuing
+    if (client->socks_recv_buf_used > 0) {
+        ASSERT(client->socks_recv_waiting)
+        ASSERT(client->socks_recv_buf_sent < client->socks_recv_buf_used)
+        
+        // set not waiting
+        client->socks_recv_waiting = 0;
+        
+        // possibly send more data
+        if (client_socks_recv_send_out(client) < 0) {
+            return ERR_ABRT;
+        }
+        
+        // we just queued some data, so it can't have been confirmed yet
+        ASSERT(client->socks_recv_tcp_pending > 0)
+        
+        // continue receiving if needed
+        if (client->socks_recv_buf_used == -1 && !client->socks_closed) {
+            SYNC_DECL
+            SYNC_FROMHERE
+            client_socks_recv_initiate(client);
+            DEAD_ENTER(client->dead_client)
+            SYNC_COMMIT
+            DEAD_LEAVE2(client->dead_client)
+            if (DEAD_KILLED) {
+                return ERR_ABRT;
+            }
+        }
+        
+        return ERR_OK;
+    }
+    
+    // have we sent everything after SOCKS was closed?
+    if (client->socks_closed && client->socks_recv_tcp_pending == 0) {
+        client_log(client, BLOG_INFO, "removing after SOCKS went down");
+        client_free_client(client);
+        return ERR_ABRT;
+    }
+    
+    return ERR_OK;
+}
+
+void udpgw_client_handler_received (void *unused, BAddr local_addr, BAddr remote_addr, const uint8_t *data, int data_len)
+{
+    ASSERT(options.udpgw_remote_server_addr)
+    ASSERT(local_addr.type == BADDR_TYPE_IPV4 || local_addr.type == BADDR_TYPE_IPV6)
+    ASSERT(local_addr.type == remote_addr.type)
+    ASSERT(data_len >= 0)
+    
+    int packet_length = 0;
+    
+    switch (local_addr.type) {
+        case BADDR_TYPE_IPV4: {
+            BLog(BLOG_INFO, "UDP: from udpgw %d bytes", data_len);
+            
+            if (data_len > UINT16_MAX - (sizeof(struct ipv4_header) + sizeof(struct udp_header)) ||
+                data_len > BTap_GetMTU(&device) - (int)(sizeof(struct ipv4_header) + sizeof(struct udp_header))
+            ) {
+                BLog(BLOG_ERROR, "UDP: packet is too large");
+                return;
+            }
+            
+            // build IP header
+            struct ipv4_header iph;
+            iph.version4_ihl4 = IPV4_MAKE_VERSION_IHL(sizeof(iph));
+            iph.ds = hton8(0);
+            iph.total_length = hton16(sizeof(iph) + sizeof(struct udp_header) + 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 = remote_addr.ipv4.ip;
+            iph.destination_address = local_addr.ipv4.ip;
+            iph.checksum = ipv4_checksum(&iph, NULL, 0);
+            
+            // build UDP header
+            struct udp_header udph;
+            udph.source_port = remote_addr.ipv4.port;
+            udph.dest_port = local_addr.ipv4.port;
+            udph.length = hton16(sizeof(udph) + data_len);
+            udph.checksum = hton16(0);
+            udph.checksum = udp_checksum(&udph, data, data_len, iph.source_address, iph.destination_address);
+            
+            // write packet
+            memcpy(device_write_buf, &iph, sizeof(iph));
+            memcpy(device_write_buf + sizeof(iph), &udph, sizeof(udph));
+            memcpy(device_write_buf + sizeof(iph) + sizeof(udph), data, data_len);
+            packet_length = sizeof(iph) + sizeof(udph) + data_len;
+        } break;
+        
+        case BADDR_TYPE_IPV6: {
+            BLog(BLOG_INFO, "UDP/IPv6: from udpgw %d bytes", data_len);
+            
+            if (!options.netif_ip6addr) {
+                BLog(BLOG_ERROR, "got IPv6 packet from udpgw but IPv6 is disabled");
+                return;
+            }
+            
+            if (data_len > UINT16_MAX - sizeof(struct udp_header) ||
+                data_len > BTap_GetMTU(&device) - (int)(sizeof(struct ipv6_header) + sizeof(struct udp_header))
+            ) {
+                BLog(BLOG_ERROR, "UDP/IPv6: packet is too large");
+                return;
+            }
+            
+            // build IPv6 header
+            struct ipv6_header iph;
+            iph.version4_tc4 = hton8((6 << 4));
+            iph.tc4_fl4 = hton8(0);
+            iph.fl = hton16(0);
+            iph.payload_length = hton16(sizeof(struct udp_header) + data_len);
+            iph.next_header = hton8(IPV6_NEXT_UDP);
+            iph.hop_limit = hton8(64);
+            memcpy(iph.source_address, remote_addr.ipv6.ip, 16);
+            memcpy(iph.destination_address, local_addr.ipv6.ip, 16);
+            
+            // build UDP header
+            struct udp_header udph;
+            udph.source_port = remote_addr.ipv6.port;
+            udph.dest_port = local_addr.ipv6.port;
+            udph.length = hton16(sizeof(udph) + data_len);
+            udph.checksum = hton16(0);
+            udph.checksum = udp_ip6_checksum(&udph, data, data_len, iph.source_address, iph.destination_address);
+            
+            // write packet
+            memcpy(device_write_buf, &iph, sizeof(iph));
+            memcpy(device_write_buf + sizeof(iph), &udph, sizeof(udph));
+            memcpy(device_write_buf + sizeof(iph) + sizeof(udph), data, data_len);
+            packet_length = sizeof(iph) + sizeof(udph) + data_len;
+        } break;
+    }
+    
+    // submit packet
+    BTap_Send(&device, device_write_buf, packet_length);
+}
diff --git a/external/badvpn_dns/tun2socks/tun2socks.h b/external/badvpn_dns/tun2socks/tun2socks.h
new file mode 100644
index 0000000..caf5778
--- /dev/null
+++ b/external/badvpn_dns/tun2socks/tun2socks.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * 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 "tun2socks"
+
+// size of temporary buffer for passing data from the SOCKS server to TCP for sending
+#define CLIENT_SOCKS_RECV_BUF_SIZE 8192
+
+// maximum number of udpgw connections
+#define DEFAULT_UDPGW_MAX_CONNECTIONS 256
+
+// udpgw per-connection send buffer size, in number of packets
+#define DEFAULT_UDPGW_CONNECTION_BUFFER_SIZE 8
+
+// udpgw reconnect time after connection fails
+#define UDPGW_RECONNECT_TIME 5000
+
+// udpgw keepalive sending interval
+#define UDPGW_KEEPALIVE_TIME 10000
+
+// option to override the destination addresses to give the SOCKS server
+//#define OVERRIDE_DEST_ADDR "10.111.0.2:2000"
diff --git a/external/badvpn_dns/tunctl/CMakeLists.txt b/external/badvpn_dns/tunctl/CMakeLists.txt
new file mode 100644
index 0000000..4cbebc8
--- /dev/null
+++ b/external/badvpn_dns/tunctl/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_executable(badvpn-tunctl tunctl.c)
+
+install(
+    TARGETS badvpn-tunctl
+    RUNTIME DESTINATION bin
+)
diff --git a/external/badvpn_dns/tunctl/tunctl.c b/external/badvpn_dns/tunctl/tunctl.c
new file mode 100644
index 0000000..4490adc
--- /dev/null
+++ b/external/badvpn_dns/tunctl/tunctl.c
@@ -0,0 +1,352 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <linux/if_tun.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <misc/version.h>
+#include <misc/open_standard_streams.h>
+
+#define PROGRAM_NAME "tunctl"
+
+#define TUN_DEVNODE "/dev/net/tun"
+
+struct {
+    int help;
+    int version;
+    int op;
+    char *device_name;
+    char *user;
+    char *group;
+} options;
+
+#define OP_MKTUN 1
+#define OP_MKTAP 2
+#define OP_RMTUN 3
+#define OP_RMTAP 4
+
+static void print_help (const char *name);
+static void print_version (void);
+static int parse_arguments (int argc, char *argv[]);
+static int make_tuntap (const char *ifname, int is_tun, const char *user, const char *group);
+static int remove_tuntap (const char *ifname, int is_tun);
+
+int main (int argc, char *argv[])
+{
+    int res = 1;
+    
+    // open standard streams
+    open_standard_streams();
+    
+    // parse command-line arguments
+    if (!parse_arguments(argc, argv)) {
+        fprintf(stderr, "Error: 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;
+    }
+    
+    if (options.op == OP_MKTUN || options.op == OP_MKTAP) {
+        if (!options.user && !options.group) {
+            fprintf(stderr, "WARNING: with neither --user nor --group, anyone will be able to use the device!\n");
+        }
+        res = !make_tuntap(options.device_name, options.op == OP_MKTUN, options.user, options.group);
+    } else {
+        res = !remove_tuntap(options.device_name, options.op == OP_RMTUN);
+    }
+    
+fail0:
+    return res;
+}
+
+void print_help (const char *name)
+{
+    printf(
+        "Usage:\n"
+        "    %s [--help] [--version]\n"
+        "    %s --mktun <device_name> [--user <username>] [--group <groupname>]\n"
+        "    %s --mktap <device_name> [--user <username>] [--group <groupname>]\n"
+        "    %s --rmtun <device_name>\n"
+        "    %s --rmtap <device_name>\n",
+        name, name, name, name, 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.op = -1;
+    options.device_name = NULL;
+    options.user = NULL;
+    options.group = NULL;
+    
+    for (int 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, "--mktun")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.op >= 0) {
+                fprintf(stderr, "%s: can only do one operation\n", arg);
+                return 0;
+            }
+            options.op = OP_MKTUN;
+            options.device_name = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--mktap")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.op >= 0) {
+                fprintf(stderr, "%s: can only do one operation\n", arg);
+                return 0;
+            }
+            options.op = OP_MKTAP;
+            options.device_name = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--rmtun")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.op >= 0) {
+                fprintf(stderr, "%s: can only do one operation\n", arg);
+                return 0;
+            }
+            options.op = OP_RMTUN;
+            options.device_name = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--rmtap")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.op >= 0) {
+                fprintf(stderr, "%s: can only do one operation\n", arg);
+                return 0;
+            }
+            options.op = OP_RMTAP;
+            options.device_name = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--user")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.user = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--group")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.group = argv[i + 1];
+            i++;
+        }
+        else {
+            fprintf(stderr, "unknown option: %s\n", arg);
+            return 0;
+        }
+    }
+    
+    if (options.help || options.version) {
+        return 1;
+    }
+    
+    if (options.op < 0) {
+        fprintf(stderr, "--mktun, --mktap --rmtun or --rmtap is required\n");
+        return 0;
+    }
+    
+    if ((options.user || options.group) && options.op != OP_MKTUN && options.op != OP_MKTAP) {
+        fprintf(stderr, "--user and --group only make sense for --mktun and --mktap\n");
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int make_tuntap (const char *ifname, int is_tun, const char *user, const char *group)
+{
+    int res = 0;
+    
+    if (strlen(ifname) >= IFNAMSIZ) {
+        fprintf(stderr, "Error: ifname too long\n");
+        goto fail0;
+    }
+    
+    int fd = open(TUN_DEVNODE, O_RDWR);
+    if (fd < 0) {
+        perror("open");
+        fprintf(stderr, "Error: open tun failed\n");
+        goto fail0;
+    }
+    
+    struct ifreq ifr;
+    memset(&ifr, 0, sizeof(ifr));
+    ifr.ifr_flags = (is_tun ? IFF_TUN : IFF_TAP);
+    snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
+    
+    if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) {
+        perror("ioctl(TUNSETIFF)");
+        fprintf(stderr, "Error: TUNSETIFF failed\n");
+        goto fail1;
+    }
+    
+    uid_t uid = -1;
+    gid_t gid = -1;
+    
+    if (user) {
+        long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+        if (bufsize < 0) {
+            bufsize = 16384;
+        }
+        
+        char *buf = malloc(bufsize);
+        if (!buf) {
+            fprintf(stderr, "Error: malloc failed\n");
+            goto fail1;
+        }
+        
+        struct passwd pwd;
+        struct passwd *res;
+        getpwnam_r(user, &pwd, buf, bufsize, &res);
+        if (!res) {
+            fprintf(stderr, "Error: getpwnam_r failed\n");
+            free(buf);
+            goto fail1;
+        }
+        
+        uid = pwd.pw_uid;
+        free(buf);
+    }
+    
+    if (group) {
+        long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
+        if (bufsize < 0) {
+            bufsize = 16384;
+        }
+        
+        char *buf = malloc(bufsize);
+        if (!buf) {
+            fprintf(stderr, "Error: malloc failed\n");
+            goto fail1;
+        }
+        
+        struct group grp;
+        struct group *res;
+        getgrnam_r(group, &grp, buf, bufsize, &res);
+        if (!res) {
+            fprintf(stderr, "Error: getgrnam_r failed\n");
+            free(buf);
+            goto fail1;
+        }
+        
+        gid = grp.gr_gid;
+        free(buf);
+    }
+    
+    if (ioctl(fd, TUNSETOWNER, uid) < 0) {
+        perror("ioctl(TUNSETOWNER)");
+        fprintf(stderr, "Error: TUNSETOWNER failed\n");
+        goto fail1;
+    }
+    
+    if (ioctl(fd, TUNSETGROUP, gid) < 0) {
+        perror("ioctl(TUNSETGROUP)");
+        fprintf(stderr, "Error: TUNSETGROUP failed\n");
+        goto fail1;
+    }
+    
+    if (ioctl(fd, TUNSETPERSIST, (void *)1) < 0) {
+        perror("ioctl(TUNSETPERSIST)");
+        fprintf(stderr, "Error: TUNSETPERSIST failed\n");
+        goto fail1;
+    }
+    
+    res = 1;
+    
+fail1:
+    close(fd);
+fail0:
+    return res;
+}
+
+static int remove_tuntap (const char *ifname, int is_tun)
+{
+    int res = 0;
+    
+    if (strlen(ifname) >= IFNAMSIZ) {
+        fprintf(stderr, "Error: ifname too long\n");
+        goto fail0;
+    }
+    
+    int fd = open(TUN_DEVNODE, O_RDWR);
+    if (fd < 0) {
+        perror("open");
+        fprintf(stderr, "Error: open tun failed\n");
+        goto fail0;
+    }
+    
+    struct ifreq ifr;
+    memset(&ifr, 0, sizeof(ifr));
+    ifr.ifr_flags = (is_tun ? IFF_TUN : IFF_TAP);
+    snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
+    
+    if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) {
+        perror("ioctl(TUNSETIFF)");
+        fprintf(stderr, "Error: TUNSETIFF failed\n");
+        goto fail1;
+    }
+    
+    if (ioctl(fd, TUNSETPERSIST, (void *)0) < 0) {
+        perror("ioctl(TUNSETPERSIST)");
+        fprintf(stderr, "Error: TUNSETPERSIST failed\n");
+        goto fail1;
+    }
+    
+    res = 1;
+    
+fail1:
+    close(fd);
+fail0:
+    return res;
+}
diff --git a/external/badvpn_dns/tuntap/BTap.c b/external/badvpn_dns/tuntap/BTap.c
new file mode 100644
index 0000000..af12558
--- /dev/null
+++ b/external/badvpn_dns/tuntap/BTap.c
@@ -0,0 +1,631 @@
+/**
+ * @file BTap.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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>
+
+#ifdef BADVPN_USE_WINAPI
+    #include <windows.h>
+    #include <winioctl.h>
+    #include <objbase.h>
+    #include <wtypes.h>
+    #include "wintap-common.h"
+    #include <tuntap/tapwin32-funcs.h>
+#else
+    #include <fcntl.h>
+    #include <unistd.h>
+    #include <errno.h>
+    #include <sys/ioctl.h>
+    #include <sys/types.h>
+    #include <sys/stat.h>
+    #include <sys/socket.h>
+    #include <net/if.h>
+    #include <net/if_arp.h>
+    #ifdef BADVPN_LINUX
+        #include <linux/if_tun.h>
+    #endif
+    #ifdef BADVPN_FREEBSD
+        #include <net/if_tun.h>
+        #include <net/if_tap.h>
+    #endif
+#endif
+
+#include <base/BLog.h>
+
+#include <tuntap/BTap.h>
+
+#include <generated/blog_channel_BTap.h>
+
+static void report_error (BTap *o);
+static void output_handler_recv (BTap *o, uint8_t *data);
+
+#ifdef BADVPN_USE_WINAPI
+
+static void recv_olap_handler (BTap *o, int event, DWORD bytes)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->output_packet)
+    ASSERT(event == BREACTOR_IOCP_EVENT_SUCCEEDED || event == BREACTOR_IOCP_EVENT_FAILED)
+    
+    // set no output packet
+    o->output_packet = NULL;
+    
+    if (event == BREACTOR_IOCP_EVENT_FAILED) {
+        BLog(BLOG_ERROR, "read operation failed");
+        report_error(o);
+        return;
+    }
+    
+    ASSERT(bytes >= 0)
+    ASSERT(bytes <= o->frame_mtu)
+    
+    // done
+    PacketRecvInterface_Done(&o->output, bytes);
+}
+
+#else
+
+static void fd_handler (BTap *o, int events)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    
+    if (events&(BREACTOR_ERROR|BREACTOR_HUP)) {
+        BLog(BLOG_WARNING, "device fd reports error?");
+    }
+    
+    if (events&BREACTOR_READ) do {
+        ASSERT(o->output_packet)
+        
+        // try reading into the buffer
+        int bytes = read(o->fd, o->output_packet, o->frame_mtu);
+        if (bytes < 0) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                // retry later
+                break;
+            }
+            // report fatal error
+            report_error(o);
+            return;
+        }
+        
+        ASSERT_FORCE(bytes <= o->frame_mtu)
+        
+        // set no output packet
+        o->output_packet = NULL;
+        
+        // update events
+        o->poll_events &= ~BREACTOR_READ;
+        BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->poll_events);
+        
+        // inform receiver we finished the packet
+        PacketRecvInterface_Done(&o->output, bytes);
+    } while (0);
+}
+
+#endif
+
+void report_error (BTap *o)
+{
+    DEBUGERROR(&o->d_err, o->handler_error(o->handler_error_user));
+}
+
+void output_handler_recv (BTap *o, uint8_t *data)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(data)
+    ASSERT(!o->output_packet)
+    
+#ifdef BADVPN_USE_WINAPI
+    
+    memset(&o->recv_olap.olap, 0, sizeof(o->recv_olap.olap));
+    
+    // read
+    BOOL res = ReadFile(o->device, data, o->frame_mtu, NULL, &o->recv_olap.olap);
+    if (res == FALSE && GetLastError() != ERROR_IO_PENDING) {
+        BLog(BLOG_ERROR, "ReadFile failed (%u)", GetLastError());
+        report_error(o);
+        return;
+    }
+    
+    o->output_packet = data;
+    
+#else
+    
+    // attempt read
+    int bytes = read(o->fd, data, o->frame_mtu);
+    if (bytes < 0) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            // retry later in fd_handler
+            // remember packet
+            o->output_packet = data;
+            // update events
+            o->poll_events |= BREACTOR_READ;
+            BReactor_SetFileDescriptorEvents(o->reactor, &o->bfd, o->poll_events);
+            return;
+        }
+        // report fatal error
+        report_error(o);
+        return;
+    }
+    
+    ASSERT_FORCE(bytes <= o->frame_mtu)
+    
+    PacketRecvInterface_Done(&o->output, bytes);
+    
+#endif
+}
+
+int BTap_Init (BTap *o, BReactor *reactor, char *devname, BTap_handler_error handler_error, void *handler_error_user, int tun)
+{
+    ASSERT(tun == 0 || tun == 1)
+    
+    struct BTap_init_data init_data;
+    init_data.dev_type = tun ? BTAP_DEV_TUN : BTAP_DEV_TAP;
+    init_data.init_type = BTAP_INIT_STRING;
+    init_data.init.string = devname;
+    
+    return BTap_Init2(o, reactor, init_data, handler_error, handler_error_user);
+}
+
+int BTap_Init2 (BTap *o, BReactor *reactor, struct BTap_init_data init_data, BTap_handler_error handler_error, void *handler_error_user)
+{
+    ASSERT(init_data.dev_type == BTAP_DEV_TUN || init_data.dev_type == BTAP_DEV_TAP)
+    
+    // init arguments
+    o->reactor = reactor;
+    o->handler_error = handler_error;
+    o->handler_error_user = handler_error_user;
+    
+    #ifdef BADVPN_USE_WINAPI
+    
+    ASSERT(init_data.init_type == BTAP_INIT_STRING)
+    
+    // parse device specification
+    
+    if (!init_data.init.string) {
+        BLog(BLOG_ERROR, "no device specification provided");
+        goto fail0;
+    }
+    
+    char *device_component_id;
+    char *device_name;
+    uint32_t tun_addrs[3];
+    
+    if (init_data.dev_type == BTAP_DEV_TUN) {
+        if (!tapwin32_parse_tun_spec(init_data.init.string, &device_component_id, &device_name, tun_addrs)) {
+            BLog(BLOG_ERROR, "failed to parse TUN device specification");
+            goto fail0;
+        }
+    } else {
+        if (!tapwin32_parse_tap_spec(init_data.init.string, &device_component_id, &device_name)) {
+            BLog(BLOG_ERROR, "failed to parse TAP device specification");
+            goto fail0;
+        }
+    }
+    
+    // locate device path
+    
+    char device_path[TAPWIN32_MAX_REG_SIZE];
+    
+    BLog(BLOG_INFO, "Looking for TAP-Win32 with component ID %s, name %s", device_component_id, device_name);
+    
+    if (!tapwin32_find_device(device_component_id, device_name, &device_path)) {
+        BLog(BLOG_ERROR, "Could not find device");
+        goto fail1;
+    }
+    
+    // open device
+    
+    BLog(BLOG_INFO, "Opening device %s", device_path);
+    
+    o->device = CreateFile(device_path, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM|FILE_FLAG_OVERLAPPED, 0);
+    if (o->device == INVALID_HANDLE_VALUE) {
+        BLog(BLOG_ERROR, "CreateFile failed");
+        goto fail1;
+    }
+    
+    // set TUN if needed
+    
+    DWORD len;
+    
+    if (init_data.dev_type == BTAP_DEV_TUN) {
+        if (!DeviceIoControl(o->device, TAP_IOCTL_CONFIG_TUN, tun_addrs, sizeof(tun_addrs), tun_addrs, sizeof(tun_addrs), &len, NULL)) {
+            BLog(BLOG_ERROR, "DeviceIoControl(TAP_IOCTL_CONFIG_TUN) failed");
+            goto fail2;
+        }
+    }
+    
+    // get MTU
+    
+    ULONG umtu;
+    
+    if (!DeviceIoControl(o->device, TAP_IOCTL_GET_MTU, NULL, 0, &umtu, sizeof(umtu), &len, NULL)) {
+        BLog(BLOG_ERROR, "DeviceIoControl(TAP_IOCTL_GET_MTU) failed");
+        goto fail2;
+    }
+    
+    if (init_data.dev_type == BTAP_DEV_TUN) {
+        o->frame_mtu = umtu;
+    } else {
+        o->frame_mtu = umtu + BTAP_ETHERNET_HEADER_LENGTH;
+    }
+    
+    // set connected
+    
+    ULONG upstatus = TRUE;
+    if (!DeviceIoControl(o->device, TAP_IOCTL_SET_MEDIA_STATUS, &upstatus, sizeof(upstatus), &upstatus, sizeof(upstatus), &len, NULL)) {
+        BLog(BLOG_ERROR, "DeviceIoControl(TAP_IOCTL_SET_MEDIA_STATUS) failed");
+        goto fail2;
+    }
+    
+    BLog(BLOG_INFO, "Device opened");
+    
+    // associate device with IOCP
+    
+    if (!CreateIoCompletionPort(o->device, BReactor_GetIOCPHandle(o->reactor), 0, 0)) {
+        BLog(BLOG_ERROR, "CreateIoCompletionPort failed");
+        goto fail2;
+    }
+    
+    // init send olap
+    BReactorIOCPOverlapped_Init(&o->send_olap, o->reactor, o, NULL);
+    
+    // init recv olap
+    BReactorIOCPOverlapped_Init(&o->recv_olap, o->reactor, o, (BReactorIOCPOverlapped_handler)recv_olap_handler);
+    
+    free(device_name);
+    free(device_component_id);
+    
+    goto success;
+    
+fail2:
+    ASSERT_FORCE(CloseHandle(o->device))
+fail1:
+    free(device_name);
+    free(device_component_id);
+fail0:
+    return 0;
+    
+    #endif
+    
+    #if defined(BADVPN_LINUX) || defined(BADVPN_FREEBSD)
+    
+    o->close_fd = (init_data.init_type != BTAP_INIT_FD);
+    
+    switch (init_data.init_type) {
+        case BTAP_INIT_FD: {
+            ASSERT(init_data.init.fd.fd >= 0)
+            ASSERT(init_data.init.fd.mtu >= 0)
+            ASSERT(init_data.dev_type != BTAP_DEV_TAP || init_data.init.fd.mtu >= BTAP_ETHERNET_HEADER_LENGTH)
+            
+            o->fd = init_data.init.fd.fd;
+            o->frame_mtu = init_data.init.fd.mtu;
+        } break;
+        
+        case BTAP_INIT_STRING: {
+            char devname_real[IFNAMSIZ];
+            
+            #ifdef BADVPN_LINUX
+            
+            // open device
+            
+            if ((o->fd = open("/dev/net/tun", O_RDWR)) < 0) {
+                BLog(BLOG_ERROR, "error opening device");
+                goto fail0;
+            }
+            
+            // configure device
+            
+            struct ifreq ifr;
+            memset(&ifr, 0, sizeof(ifr));
+            ifr.ifr_flags |= IFF_NO_PI;
+            if (init_data.dev_type == BTAP_DEV_TUN) {
+                ifr.ifr_flags |= IFF_TUN;
+            } else {
+                ifr.ifr_flags |= IFF_TAP;
+            }
+            if (init_data.init.string) {
+                snprintf(ifr.ifr_name, IFNAMSIZ, "%s", init_data.init.string);
+            }
+            
+            if (ioctl(o->fd, TUNSETIFF, (void *)&ifr) < 0) {
+                BLog(BLOG_ERROR, "error configuring device");
+                goto fail1;
+            }
+            
+            strcpy(devname_real, ifr.ifr_name);
+            
+            #endif
+            
+            #ifdef BADVPN_FREEBSD
+            
+            if (init_data.dev_type == BTAP_DEV_TUN) {
+                BLog(BLOG_ERROR, "TUN not supported on FreeBSD");
+                goto fail0;
+            }
+            
+            if (!init_data.init.string) {
+                BLog(BLOG_ERROR, "no device specified");
+                goto fail0;
+            }
+            
+            // open device
+            
+            char devnode[10 + IFNAMSIZ];
+            snprintf(devnode, sizeof(devnode), "/dev/%s", init_data.init.string);
+            
+            if ((o->fd = open(devnode, O_RDWR)) < 0) {
+                BLog(BLOG_ERROR, "error opening device");
+                goto fail0;
+            }
+            
+            // get name
+            
+            struct ifreq ifr;
+            memset(&ifr, 0, sizeof(ifr));
+            if (ioctl(o->fd, TAPGIFNAME, (void *)&ifr) < 0) {
+                BLog(BLOG_ERROR, "error configuring device");
+                goto fail1;
+            }
+            
+            strcpy(devname_real, ifr.ifr_name);
+            
+            #endif
+            
+            // get MTU
+            
+            // open dummy socket for ioctls
+            int sock = socket(AF_INET, SOCK_DGRAM, 0);
+            if (sock < 0) {
+                BLog(BLOG_ERROR, "socket failed");
+                goto fail1;
+            }
+            
+            memset(&ifr, 0, sizeof(ifr));
+            strcpy(ifr.ifr_name, devname_real);
+            
+            if (ioctl(sock, SIOCGIFMTU, (void *)&ifr) < 0) {
+                BLog(BLOG_ERROR, "error getting MTU");
+                close(sock);
+                goto fail1;
+            }
+            
+            if (init_data.dev_type == BTAP_DEV_TUN) {
+                o->frame_mtu = ifr.ifr_mtu;
+            } else {
+                o->frame_mtu = ifr.ifr_mtu + BTAP_ETHERNET_HEADER_LENGTH;
+            }
+            
+            close(sock);
+        } break;
+        
+        default: ASSERT(0);
+    }
+        
+    // set non-blocking
+    if (fcntl(o->fd, F_SETFL, O_NONBLOCK) < 0) {
+        BLog(BLOG_ERROR, "cannot set non-blocking");
+        goto fail1;
+    }
+    
+    // init file descriptor object
+    BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)fd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    o->poll_events = 0;
+    
+    goto success;
+    
+fail1:
+    if (o->close_fd) {
+        ASSERT_FORCE(close(o->fd) == 0)
+    }
+fail0:
+    return 0;
+    
+    #endif
+    
+success:
+    // init output
+    PacketRecvInterface_Init(&o->output, o->frame_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o, BReactor_PendingGroup(o->reactor));
+    
+    // set no output packet
+    o->output_packet = NULL;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+}
+
+// ==== PSIPHON ====
+
+int BTap_InitWithFD (BTap *o, BReactor *reactor, int fd, int mtu, BTap_handler_error handler_error, void *handler_error_user, int tun)
+{
+    ASSERT(tun == 0 || tun == 1)
+
+    #ifndef BADVPN_LINUX
+
+    return 0;
+
+    #endif
+
+    o->reactor = reactor;
+    o->handler_error = handler_error;
+    o->handler_error_user = handler_error_user;
+    o->frame_mtu = mtu;
+    o->fd = fd;
+    o->close_fd = 1;
+
+    // TODO: use BTap_Init2? Still some different behavior (we don't want the fcntl block; we do want close to be called)
+
+    // The following is identical to BTap_Init...
+
+    // init file descriptor object
+    BFileDescriptor_Init(&o->bfd, o->fd, (BFileDescriptor_handler)fd_handler, o);
+    if (!BReactor_AddFileDescriptor(o->reactor, &o->bfd)) {
+        BLog(BLOG_ERROR, "BReactor_AddFileDescriptor failed");
+        goto fail1;
+    }
+    o->poll_events = 0;
+
+    goto success;
+
+fail1:
+    if (o->close_fd) {
+        ASSERT_FORCE(close(o->fd) == 0)
+    }
+fail0:
+    return 0;
+
+success:
+    // init output
+    PacketRecvInterface_Init(&o->output, o->frame_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o, BReactor_PendingGroup(o->reactor));
+
+    // set no output packet
+    o->output_packet = NULL;
+
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+}
+
+// ==== PSIPHON ====
+
+void BTap_Free (BTap *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    
+    // free output
+    PacketRecvInterface_Free(&o->output);
+    
+#ifdef BADVPN_USE_WINAPI
+    
+    // cancel I/O
+    ASSERT_FORCE(CancelIo(o->device))
+    
+    // wait receiving to finish
+    if (o->output_packet) {
+        BLog(BLOG_DEBUG, "waiting for receiving to finish");
+        BReactorIOCPOverlapped_Wait(&o->recv_olap, NULL, NULL);
+    }
+    
+    // free recv olap
+    BReactorIOCPOverlapped_Free(&o->recv_olap);
+    
+    // free send olap
+    BReactorIOCPOverlapped_Free(&o->send_olap);
+    
+    // close device
+    ASSERT_FORCE(CloseHandle(o->device))
+    
+#else
+    
+    // free BFileDescriptor
+    BReactor_RemoveFileDescriptor(o->reactor, &o->bfd);
+    
+    if (o->close_fd) {
+        // close file descriptor
+        ASSERT_FORCE(close(o->fd) == 0)
+    }
+    
+#endif
+}
+
+int BTap_GetMTU (BTap *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return o->frame_mtu;
+}
+
+void BTap_Send (BTap *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->frame_mtu)
+    
+#ifdef BADVPN_USE_WINAPI
+    
+    // ignore frames without an Ethernet header, or we get errors in WriteFile
+    if (data_len < 14) {
+        return;
+    }
+    
+    memset(&o->send_olap.olap, 0, sizeof(o->send_olap.olap));
+    
+    // write
+    BOOL res = WriteFile(o->device, data, data_len, NULL, &o->send_olap.olap);
+    if (res == FALSE && GetLastError() != ERROR_IO_PENDING) {
+        BLog(BLOG_ERROR, "WriteFile failed (%u)", GetLastError());
+        return;
+    }
+    
+    // wait
+    int succeeded;
+    DWORD bytes;
+    BReactorIOCPOverlapped_Wait(&o->send_olap, &succeeded, &bytes);
+    
+    if (!succeeded) {
+        BLog(BLOG_ERROR, "write operation failed");
+    } else {
+        ASSERT(bytes >= 0)
+        ASSERT(bytes <= data_len)
+        
+        if (bytes < data_len) {
+            BLog(BLOG_ERROR, "write operation didn't write everything");
+        }
+    }
+    
+#else
+    
+    int bytes = write(o->fd, data, data_len);
+    if (bytes < 0) {
+        // malformed packets will cause errors, ignore them and act like
+        // the packet was accepeted
+    } else {
+        if (bytes != data_len) {
+            BLog(BLOG_WARNING, "written %d expected %d", bytes, data_len);
+        }
+    }
+    
+#endif
+}
+
+PacketRecvInterface * BTap_GetOutput (BTap *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
diff --git a/external/badvpn_dns/tuntap/BTap.h b/external/badvpn_dns/tuntap/BTap.h
new file mode 100644
index 0000000..d9fa524
--- /dev/null
+++ b/external/badvpn_dns/tuntap/BTap.h
@@ -0,0 +1,199 @@
+/**
+ * @file BTap.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * TAP device abstraction.
+ */
+
+#ifndef BADVPN_TUNTAP_BTAP_H
+#define BADVPN_TUNTAP_BTAP_H
+
+#if (defined(BADVPN_USE_WINAPI) + defined(BADVPN_LINUX) + defined(BADVPN_FREEBSD)) != 1
+#error Unknown TAP backend or too many TAP backends
+#endif
+
+#include <stdint.h>
+
+#ifdef BADVPN_USE_WINAPI
+#else
+#include <net/if.h>
+#endif
+
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+#include <flow/PacketRecvInterface.h>
+
+#define BTAP_ETHERNET_HEADER_LENGTH 14
+
+/**
+ * Handler called when an error occurs on the device.
+ * The object must be destroyed from the job context of this
+ * handler, and no further I/O may occur.
+ * 
+ * @param user as in {@link BTap_Init}
+ */
+typedef void (*BTap_handler_error) (void *used);
+
+typedef struct {
+    BReactor *reactor;
+    BTap_handler_error handler_error;
+    void *handler_error_user;
+    int frame_mtu;
+    PacketRecvInterface output;
+    uint8_t *output_packet;
+    
+#ifdef BADVPN_USE_WINAPI
+    HANDLE device;
+    BReactorIOCPOverlapped send_olap;
+    BReactorIOCPOverlapped recv_olap;
+#else
+    int close_fd;
+    int fd;
+    BFileDescriptor bfd;
+    int poll_events;
+#endif
+    
+    DebugError d_err;
+    DebugObject d_obj;
+} BTap;
+
+/**
+ * Initializes the TAP device.
+ *
+ * @param o the object
+ * @param BReactor {@link BReactor} we live in
+ * @param devname name of the devece to open.
+ *                On Linux: a network interface name. If it is NULL, no
+ *                specific device will be requested, and the operating system
+ *                may create a new device.
+ *                On Windows: a string "component_id:device_name", where
+ *                component_id is a string identifying the driver, and device_name
+ *                is the name of the network interface. If component_id is empty,
+ *                a hardcoded default will be used instead. If device_name is empty,
+ *                the first device found with a matching component_id will be used.
+ *                Specifying a NULL devname is equivalent to specifying ":".
+ * @param handler_error error handler function
+ * @param handler_error_user value passed to error handler
+ * @param tun whether to create a TUN (IP) device or a TAP (Ethernet) device. Must be 0 or 1.
+ * @return 1 on success, 0 on failure
+ */
+int BTap_Init (BTap *o, BReactor *bsys, char *devname, BTap_handler_error handler_error, void *handler_error_user, int tun) WARN_UNUSED;
+
+// PSIPHON
+int BTap_InitWithFD (BTap *o, BReactor *bsys, int fd, int mtu, BTap_handler_error handler_error, void *handler_error_user, int tun) WARN_UNUSED;
+
+enum BTap_dev_type {BTAP_DEV_TUN, BTAP_DEV_TAP};
+
+enum BTap_init_type {
+    BTAP_INIT_STRING,
+#ifndef BADVPN_USE_WINAPI
+    BTAP_INIT_FD,
+#endif
+};
+
+struct BTap_init_data {
+    enum BTap_dev_type dev_type;
+    enum BTap_init_type init_type;
+    union {
+        char *string;
+        struct {
+            int fd;
+            int mtu;
+        } fd;
+    } init;
+};
+
+/**
+ * Initializes the TAP device.
+ *
+ * @param o the object
+ * @param BReactor {@link BReactor} we live in
+ * @param init_data struct containing initialization parameters (to allow transparent passing).
+ *                  init.data.dev_type must be either BTAP_DEV_TUN for an IP device, or
+ *                  BTAP_DEV_TAP for an Ethernet device.
+ *                  init_data.init_type must be either BTAP_INIT_STRING or BTAP_INIT_FD.
+ *                  For BTAP_INIT_STRING, init_data.init.string specifies the TUN or TAP
+ *                  device, as described next.
+ *                  On Linux: a network interface name. If it is NULL, no
+ *                  specific device will be requested, and the operating system
+ *                  may create a new device.
+ *                  On Windows: a string "component_id:device_name", where
+ *                  component_id is a string identifying the driver, and device_name
+ *                  is the name of the network interface. If component_id is empty,
+ *                  a hardcoded default will be used instead. If device_name is empty,
+ *                  the first device found with a matching component_id will be used.
+ *                  Specifying NULL is equivalent to specifying ":".
+ *                  For BTAP_INIT_FD, the device is initialized using a file descriptor.
+ *                  In this case, init_data.init.fd.fd must be set to the file descriptor,
+ *                  and init_data.init.fd.mtu must be set to the largest IP packet or
+ *                  Ethernet frame supported, for a TUN or TAP device, respectively.
+ *                  File descriptor initialization is not supported on Windows.
+ * @param handler_error error handler function
+ * @param handler_error_user value passed to error handler
+ * @return 1 on success, 0 on failure
+ */
+int BTap_Init2 (BTap *o, BReactor *reactor, struct BTap_init_data init_data, BTap_handler_error handler_error, void *handler_error_user) WARN_UNUSED;
+
+/**
+ * Frees the TAP device.
+ *
+ * @param o the object
+ */
+void BTap_Free (BTap *o);
+
+/**
+ * Returns the device's maximum transmission unit (including any protocol headers).
+ *
+ * @param o the object
+ * @return device's MTU
+ */
+int BTap_GetMTU (BTap *o);
+
+/**
+ * Sends a packet to the device.
+ * Any errors will be reported via a job.
+ * 
+ * @param o the object
+ * @param data packet to send
+ * @param data_len length of packet. Must be >=0 and <=MTU, as reported by {@link BTap_GetMTU}.
+ */
+void BTap_Send (BTap *o, uint8_t *data, int data_len);
+
+/**
+ * Returns a {@link PacketRecvInterface} for reading packets from the device.
+ * The MTU of the interface will be {@link BTap_GetMTU}.
+ * 
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * BTap_GetOutput (BTap *o);
+
+#endif
diff --git a/external/badvpn_dns/tuntap/CMakeLists.txt b/external/badvpn_dns/tuntap/CMakeLists.txt
new file mode 100644
index 0000000..fd451c1
--- /dev/null
+++ b/external/badvpn_dns/tuntap/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(TUNTAP_ADDITIONAL_SOURCES)
+if (WIN32)
+    list(APPEND TUNTAP_ADDITIONAL_SOURCES tapwin32-funcs.c)
+endif ()
+
+set(TUNTAP_SOURCES
+    BTap.c
+    ${TUNTAP_ADDITIONAL_SOURCES}
+)
+badvpn_add_library(tuntap "system;flow" "" "${TUNTAP_SOURCES}")
diff --git a/external/badvpn_dns/tuntap/tapwin32-funcs.c b/external/badvpn_dns/tuntap/tapwin32-funcs.c
new file mode 100644
index 0000000..37ce05d
--- /dev/null
+++ b/external/badvpn_dns/tuntap/tapwin32-funcs.c
@@ -0,0 +1,227 @@
+/**
+ * @file tapwin32-funcs.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/ipaddr.h>
+#include <misc/maxalign.h>
+#include <misc/strdup.h>
+
+#include "wintap-common.h"
+
+#include <tuntap/tapwin32-funcs.h>
+
+static int split_spec (char *name, char *sep, char **out_fields[], int num_fields)
+{
+    ASSERT(num_fields > 0)
+    ASSERT(strlen(sep) > 0)
+    
+    size_t seplen = strlen(sep);
+    
+    int i = 0;
+    while (i < num_fields - 1) {
+        char *s = strstr(name, sep);
+        if (!s) {
+            DEBUG("missing separator number %d", (i + 1));
+            goto fail;
+        }
+        
+        if (!(*out_fields[i] = b_strdup_bin(name, s - name))) {
+            DEBUG("b_strdup_bin failed");
+            goto fail;
+        }
+        
+        name = s + seplen;
+        i++;
+    }
+    
+    if (!(*out_fields[i] = b_strdup(name))) {
+        DEBUG("b_strdup_bin failed");
+        goto fail;
+    }
+    
+    return 1;
+    
+fail:
+    while (i-- > 0) {
+        free(*out_fields[i]);
+    }
+    return 0;
+}
+
+int tapwin32_parse_tap_spec (char *name, char **out_component_id, char **out_human_name)
+{
+    char **out_fields[2];
+    out_fields[0] = out_component_id;
+    out_fields[1] = out_human_name;
+    
+    return split_spec(name, ":", out_fields, 2);
+}
+
+int tapwin32_parse_tun_spec (char *name, char **out_component_id, char **out_human_name, uint32_t out_addrs[3])
+{
+    char *addr_strs[3];
+    
+    char **out_fields[5];
+    out_fields[0] = out_component_id;
+    out_fields[1] = out_human_name;
+    out_fields[2] = &addr_strs[0];
+    out_fields[3] = &addr_strs[1];
+    out_fields[4] = &addr_strs[2];
+    
+    if (!split_spec(name, ":", out_fields, 5)) {
+        goto fail0;
+    }
+    
+    for (int i = 0; i < 3; i++) {
+        if (!ipaddr_parse_ipv4_addr(addr_strs[i], &out_addrs[i])) {
+            goto fail1;
+        }
+    }
+    
+    free(addr_strs[0]);
+    free(addr_strs[1]);
+    free(addr_strs[2]);
+    
+    return 1;
+    
+fail1:
+    free(*out_component_id);
+    free(*out_human_name);
+    free(addr_strs[0]);
+    free(addr_strs[1]);
+    free(addr_strs[2]);
+fail0:
+    return 0;
+}
+
+int tapwin32_find_device (char *device_component_id, char *device_name, char (*device_path)[TAPWIN32_MAX_REG_SIZE])
+{
+    // open adapter key
+    // used to find all devices with the given ComponentId
+    HKEY adapter_key;
+    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, ADAPTER_KEY, 0, KEY_READ, &adapter_key) != ERROR_SUCCESS) {
+        DEBUG("Error opening adapter key");
+        return 0;
+    }
+    
+    char net_cfg_instance_id[TAPWIN32_MAX_REG_SIZE];
+    int found = 0;
+    int pres;
+    
+    DWORD i;
+    for (i = 0;; i++) {
+        DWORD len;
+        DWORD type;
+        
+        char key_name[TAPWIN32_MAX_REG_SIZE];
+        len = sizeof(key_name);
+        if (RegEnumKeyEx(adapter_key, i, key_name, &len, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) {
+            break;
+        }
+        
+        char unit_string[TAPWIN32_MAX_REG_SIZE];
+        pres = _snprintf(unit_string, sizeof(unit_string), "%s\\%s", ADAPTER_KEY, key_name);
+        if (pres < 0 || pres == sizeof(unit_string)) {
+            continue;
+        }
+        HKEY unit_key;
+        if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, unit_string, 0, KEY_READ, &unit_key) != ERROR_SUCCESS) {
+            continue;
+        }
+        
+        char component_id[TAPWIN32_MAX_REG_SIZE];
+        len = sizeof(component_id);
+        if (RegQueryValueEx(unit_key, "ComponentId", NULL, &type, (LPBYTE)component_id, &len) != ERROR_SUCCESS || type != REG_SZ) {
+            ASSERT_FORCE(RegCloseKey(unit_key) == ERROR_SUCCESS)
+            continue;
+        }
+        
+        len = sizeof(net_cfg_instance_id);
+        if (RegQueryValueEx(unit_key, "NetCfgInstanceId", NULL, &type, (LPBYTE)net_cfg_instance_id, &len) != ERROR_SUCCESS || type != REG_SZ) {
+            ASSERT_FORCE(RegCloseKey(unit_key) == ERROR_SUCCESS)
+            continue;
+        }
+        
+        RegCloseKey(unit_key);
+        
+        // check if ComponentId matches
+        if (!strcmp(component_id, device_component_id)) {
+            // if no name was given, use the first device with the given ComponentId
+            if (!device_name) {
+                found = 1;
+                break;
+            }
+            
+            // open connection key
+            char conn_string[TAPWIN32_MAX_REG_SIZE];
+            pres = _snprintf(conn_string, sizeof(conn_string), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, net_cfg_instance_id);
+            if (pres < 0 || pres == sizeof(conn_string)) {
+                continue;
+            }
+            HKEY conn_key;
+            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, conn_string, 0, KEY_READ, &conn_key) != ERROR_SUCCESS) {
+                continue;
+            }
+            
+            // read name
+            char name[TAPWIN32_MAX_REG_SIZE];
+            len = sizeof(name);
+            if (RegQueryValueEx(conn_key, "Name", NULL, &type, (LPBYTE)name, &len) != ERROR_SUCCESS || type != REG_SZ) {
+                ASSERT_FORCE(RegCloseKey(conn_key) == ERROR_SUCCESS)
+                continue;
+            }
+            
+            ASSERT_FORCE(RegCloseKey(conn_key) == ERROR_SUCCESS)
+            
+            // check name
+            if (!strcmp(name, device_name)) {
+                found = 1;
+                break;
+            }
+        }
+    }
+    
+    ASSERT_FORCE(RegCloseKey(adapter_key) == ERROR_SUCCESS)
+    
+    if (!found) {
+        return 0;
+    }
+    
+    pres = _snprintf(*device_path, sizeof(*device_path), "\\\\.\\Global\\%s.tap", net_cfg_instance_id);
+    if (pres < 0 || pres == sizeof(*device_path)) {
+        return 0;
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/tuntap/tapwin32-funcs.h b/external/badvpn_dns/tuntap/tapwin32-funcs.h
new file mode 100644
index 0000000..fed66de
--- /dev/null
+++ b/external/badvpn_dns/tuntap/tapwin32-funcs.h
@@ -0,0 +1,42 @@
+/**
+ * @file tapwin32-funcs.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_TUNTAP_TAPWIN32_FUNCS_H
+#define BADVPN_TUNTAP_TAPWIN32_FUNCS_H
+
+#include <stdint.h>
+#include <windows.h>
+
+#define TAPWIN32_MAX_REG_SIZE 256
+
+int tapwin32_parse_tap_spec (char *name, char **out_component_id, char **out_human_name);
+int tapwin32_parse_tun_spec (char *name, char **out_component_id, char **out_human_name, uint32_t out_addrs[3]);
+int tapwin32_find_device (char *device_component_id, char *device_name, char (*device_path)[TAPWIN32_MAX_REG_SIZE]);
+
+#endif
diff --git a/external/badvpn_dns/tuntap/wintap-common.h b/external/badvpn_dns/tuntap/wintap-common.h
new file mode 100644
index 0000000..922c851
--- /dev/null
+++ b/external/badvpn_dns/tuntap/wintap-common.h
@@ -0,0 +1,39 @@
+/**
+ * @file wintap-common.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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
+ * 
+ * API definitions for TAP-Win32.
+ */
+
+#define TAP_IOCTL_CONFIG_TUN            CTL_CODE(FILE_DEVICE_UNKNOWN, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define TAP_IOCTL_GET_MTU               CTL_CODE(FILE_DEVICE_UNKNOWN, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define TAP_IOCTL_SET_MEDIA_STATUS      CTL_CODE(FILE_DEVICE_UNKNOWN, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define ADAPTER_KEY "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}"
+#define NETWORK_CONNECTIONS_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}"
diff --git a/external/badvpn_dns/udevmonitor/CMakeLists.txt b/external/badvpn_dns/udevmonitor/CMakeLists.txt
new file mode 100644
index 0000000..a95f605
--- /dev/null
+++ b/external/badvpn_dns/udevmonitor/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(UDEVMONITOR_SOURCES
+    NCDUdevMonitorParser.c
+    NCDUdevMonitor.c
+    NCDUdevCache.c
+    NCDUdevManager.c
+)
+badvpn_add_library(udevmonitor "system;flow;stringmap" "" "${UDEVMONITOR_SOURCES}")
diff --git a/external/badvpn_dns/udevmonitor/NCDUdevCache.c b/external/badvpn_dns/udevmonitor/NCDUdevCache.c
new file mode 100644
index 0000000..cb88821
--- /dev/null
+++ b/external/badvpn_dns/udevmonitor/NCDUdevCache.c
@@ -0,0 +1,417 @@
+/**
+ * @file NCDUdevCache.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/string_begins_with.h>
+#include <misc/concat_strings.h>
+#include <misc/compare.h>
+#include <base/BLog.h>
+
+#include <udevmonitor/NCDUdevCache.h>
+
+#include <generated/blog_channel_NCDUdevCache.h>
+
+static int string_comparator (void *unused, const char **str1, const char **str2)
+{
+    int c = strcmp(*str1, *str2);
+    return B_COMPARE(c, 0);
+}
+
+static void free_device (NCDUdevCache *o, struct NCDUdevCache_device *device)
+{
+    if (device->is_cleaned) {
+        // remove from cleaned devices list
+        LinkedList1_Remove(&o->cleaned_devices_list, &device->cleaned_devices_list_node);
+    } else {
+        // remove from devices tree
+        BAVL_Remove(&o->devices_tree, &device->devices_tree_node);
+    }
+    
+    // free map
+    BStringMap_Free(&device->map);
+    
+    // free structure
+    free(device);
+}
+
+static struct NCDUdevCache_device * lookup_device (NCDUdevCache *o, const char *devpath)
+{
+    BAVLNode *tree_node = BAVL_LookupExact(&o->devices_tree, &devpath);
+    if (!tree_node) {
+        return NULL;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+    ASSERT(!device->is_cleaned)
+    
+    return device;
+}
+
+static void rename_devices (NCDUdevCache *o, const char *prefix, const char *new_prefix)
+{
+    ASSERT(strlen(prefix) > 0)
+    
+    size_t prefix_len = strlen(prefix);
+    
+    // lookup prefix
+    BAVLNode *tree_node = BAVL_Lookup(&o->devices_tree, &prefix);
+    if (!tree_node) {
+        return;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+    ASSERT(!device->is_cleaned)
+    
+    // if the result does not begin with prefix, we might gave gotten the device before all
+    // devices beginning with prefix, so skip it
+    if (!string_begins_with(device->devpath, prefix)) {
+        tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
+    }
+    
+    while (tree_node) {
+        // get next node (must be here because we rename this device)
+        BAVLNode *next_tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
+        
+        // get device
+        device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!device->is_cleaned)
+        
+        // if it doesn't begin with prefix, we're done
+        if (!string_begins_with(device->devpath, prefix)) {
+            break;
+        }
+        
+        // build new devpath
+        char *new_devpath = concat_strings(2, new_prefix, device->devpath + prefix_len);
+        if (!new_devpath) {
+            BLog(BLOG_ERROR, "concat_strings failed");
+            goto fail_loop0;
+        }
+        
+        // make sure the new name does not exist
+        if (BAVL_LookupExact(&o->devices_tree, &new_devpath)) {
+            BLog(BLOG_ERROR, "rename destination already exists");
+            goto fail_loop1;
+        }
+        
+        BLog(BLOG_DEBUG, "rename %s -> %s", device->devpath, new_devpath);
+        
+        // remove from tree
+        BAVL_Remove(&o->devices_tree, &device->devices_tree_node);
+        
+        // update devpath in map
+        if (!BStringMap_Set(&device->map, "DEVPATH", new_devpath)) {
+            BLog(BLOG_ERROR, "BStringMap_Set failed");
+            ASSERT_EXECUTE(BAVL_Insert(&o->devices_tree, &device->devices_tree_node, NULL))
+            goto fail_loop1;
+        }
+        
+        // update devpath pointer
+        device->devpath = BStringMap_Get(&device->map, "DEVPATH");
+        ASSERT(device->devpath)
+        
+        // insert to tree
+        ASSERT_EXECUTE(BAVL_Insert(&o->devices_tree, &device->devices_tree_node, NULL))
+        
+    fail_loop1:
+        free(new_devpath);
+    fail_loop0:
+        tree_node = next_tree_node;
+    }
+}
+
+static int add_device (NCDUdevCache *o, BStringMap map)
+{
+    ASSERT(BStringMap_Get(&map, "DEVPATH"))
+    
+    // alloc structure
+    struct NCDUdevCache_device *device = malloc(sizeof(*device));
+    if (!device) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // init map
+    device->map = map;
+    
+    // set device path
+    device->devpath = BStringMap_Get(&device->map, "DEVPATH");
+    
+    // insert to devices tree
+    BAVLNode *ex_node;
+    if (!BAVL_Insert(&o->devices_tree, &device->devices_tree_node, &ex_node)) {
+        BLog(BLOG_DEBUG, "update %s", device->devpath);
+        
+        // get existing device
+        struct NCDUdevCache_device *ex_device = UPPER_OBJECT(ex_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!ex_device->is_cleaned)
+        
+        // remove exiting device
+        free_device(o, ex_device);
+        
+        // insert
+        ASSERT_EXECUTE(BAVL_Insert(&o->devices_tree, &device->devices_tree_node, NULL))
+    } else {
+        BLog(BLOG_DEBUG, "add %s", device->devpath);
+    }
+    
+    // set not cleaned
+    device->is_cleaned = 0;
+    
+    // set refreshed
+    device->is_refreshed = 1;
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void NCDUdevCache_Init (NCDUdevCache *o)
+{
+    // init devices tree
+    BAVL_Init(&o->devices_tree, OFFSET_DIFF(struct NCDUdevCache_device, devpath, devices_tree_node), (BAVL_comparator)string_comparator, NULL);
+    
+    // init cleaned devices list
+    LinkedList1_Init(&o->cleaned_devices_list);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void NCDUdevCache_Free (NCDUdevCache *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free cleaned devices
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&o->cleaned_devices_list)) {
+        struct NCDUdevCache_device *device = UPPER_OBJECT(list_node, struct NCDUdevCache_device, cleaned_devices_list_node);
+        ASSERT(device->is_cleaned)
+        free_device(o, device);
+    }
+    
+    // free devices
+    BAVLNode *tree_node;
+    while (tree_node = BAVL_GetFirst(&o->devices_tree)) {
+        struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!device->is_cleaned)
+        free_device(o, device);
+    }
+}
+
+const BStringMap * NCDUdevCache_Query (NCDUdevCache *o, const char *devpath)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // lookup device
+    struct NCDUdevCache_device *device = lookup_device(o, devpath);
+    if (!device) {
+        return NULL;
+    }
+    
+    // return map
+    return &device->map;
+}
+
+int NCDUdevCache_Event (NCDUdevCache *o, BStringMap map)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // get device path
+    const char *devpath = BStringMap_Get(&map, "DEVPATH");
+    if (!devpath) {
+        BLog(BLOG_ERROR, "missing DEVPATH");
+        goto fail;
+    }
+    
+    // get action
+    const char *action = BStringMap_Get(&map, "ACTION");
+    
+    // if this is a remove event, remove device if we have it
+    if (action && !strcmp(action, "remove")) {
+        // remove existing device
+        struct NCDUdevCache_device *device = lookup_device(o, devpath);
+        if (device) {
+            BLog(BLOG_DEBUG, "remove %s", devpath);
+            free_device(o, device);
+        } else {
+            BLog(BLOG_DEBUG, "remove unknown %s", devpath);
+        }
+        
+        // eat map
+        BStringMap_Free(&map);
+        
+        return 1;
+    }
+    
+    // if this is a move event, remove old device and contaned devices
+    if (action && !strcmp(action, "move")) {
+        const char *devpath_old = BStringMap_Get(&map, "DEVPATH_OLD");
+        if (!devpath_old) {
+            goto fail_rename0;
+        }
+        
+        // remove old device
+        struct NCDUdevCache_device *old_device = lookup_device(o, devpath_old);
+        if (old_device) {
+            BLog(BLOG_DEBUG, "remove moved %s", old_device->devpath);
+            free_device(o, old_device);
+        }
+        
+        // construct prefix "<devpath_old>/" and new prefix "<devpath>/"
+        char *prefix = concat_strings(2, devpath_old, "/");
+        if (!prefix) {
+            BLog(BLOG_ERROR, "concat_strings failed");
+            goto fail_rename0;;
+        }
+        char *new_prefix = concat_strings(2, devpath, "/");
+        if (!new_prefix) {
+            BLog(BLOG_ERROR, "concat_strings failed");
+            goto fail_rename1;
+        }
+        
+        // rename devices with paths starting with prefix
+        rename_devices(o, prefix, new_prefix);
+        
+        free(new_prefix);
+    fail_rename1:
+        free(prefix);
+    fail_rename0:;
+    }
+    
+    // add device
+    if (!add_device(o, map)) {
+        BLog(BLOG_DEBUG, "failed to add device %s", devpath);
+        goto fail;
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+void NCDUdevCache_StartClean (NCDUdevCache *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // mark all devices not refreshed
+    BAVLNode *tree_node = BAVL_GetFirst(&o->devices_tree);
+    while (tree_node) {
+        struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!device->is_cleaned)
+        
+        // set device not refreshed
+        device->is_refreshed = 0;
+        
+        tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
+    }
+}
+
+void NCDUdevCache_FinishClean (NCDUdevCache *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // move all devices not marked refreshed to the cleaned devices list
+    BAVLNode *tree_node = BAVL_GetFirst(&o->devices_tree);
+    while (tree_node) {
+        BAVLNode *next_tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
+        
+        struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!device->is_cleaned)
+        
+        if (!device->is_refreshed) {
+            BLog(BLOG_DEBUG, "clean %s", device->devpath);
+            
+            // remove from devices tree
+            BAVL_Remove(&o->devices_tree, &device->devices_tree_node);
+            
+            // insert to cleaned devices list
+            LinkedList1_Append(&o->cleaned_devices_list, &device->cleaned_devices_list_node);
+            
+            // set device cleaned
+            device->is_cleaned = 1;
+        }
+        
+        tree_node = next_tree_node;
+    }
+}
+
+int NCDUdevCache_GetCleanedDevice (NCDUdevCache *o, BStringMap *out_map)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // get cleaned device
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->cleaned_devices_list);
+    if (!list_node) {
+        return 0;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(list_node, struct NCDUdevCache_device, cleaned_devices_list_node);
+    ASSERT(device->is_cleaned)
+    
+    // remove from cleaned devices list
+    LinkedList1_Remove(&o->cleaned_devices_list, &device->cleaned_devices_list_node);
+    
+    // give away map
+    *out_map = device->map;
+    
+    // free structure
+    free(device);
+    
+    return 1;
+}
+
+const char * NCDUdevCache_First (NCDUdevCache *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    BAVLNode *tree_node = BAVL_GetFirst(&o->devices_tree);
+    if (!tree_node) {
+        return NULL;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+    ASSERT(!device->is_cleaned)
+    
+    return device->devpath;
+}
+
+const char * NCDUdevCache_Next (NCDUdevCache *o, const char *key)
+{
+    ASSERT(BAVL_LookupExact(&o->devices_tree, &key))
+    
+    BAVLNode *tree_node = BAVL_GetNext(&o->devices_tree, BAVL_LookupExact(&o->devices_tree, &key));
+    if (!tree_node) {
+        return NULL;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+    ASSERT(!device->is_cleaned)
+    
+    return device->devpath;
+}
diff --git a/external/badvpn_dns/udevmonitor/NCDUdevCache.h b/external/badvpn_dns/udevmonitor/NCDUdevCache.h
new file mode 100644
index 0000000..2d9e8d8
--- /dev/null
+++ b/external/badvpn_dns/udevmonitor/NCDUdevCache.h
@@ -0,0 +1,66 @@
+/**
+ * @file NCDUdevCache.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_UDEVMONITOR_NCDUDEVCACHE_H
+#define BADVPN_UDEVMONITOR_NCDUDEVCACHE_H
+
+#include <misc/debug.h>
+#include <structure/BAVL.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <stringmap/BStringMap.h>
+
+struct NCDUdevCache_device {
+    BStringMap map;
+    const char *devpath;
+    int is_cleaned;
+    union {
+        BAVLNode devices_tree_node;
+        LinkedList1Node cleaned_devices_list_node;
+    };
+    int is_refreshed;
+};
+
+typedef struct {
+    BAVL devices_tree;
+    LinkedList1 cleaned_devices_list;
+    DebugObject d_obj;
+} NCDUdevCache;
+
+void NCDUdevCache_Init (NCDUdevCache *o);
+void NCDUdevCache_Free (NCDUdevCache *o);
+const BStringMap * NCDUdevCache_Query (NCDUdevCache *o, const char *devpath);
+int NCDUdevCache_Event (NCDUdevCache *o, BStringMap map) WARN_UNUSED;
+void NCDUdevCache_StartClean (NCDUdevCache *o);
+void NCDUdevCache_FinishClean (NCDUdevCache *o);
+int NCDUdevCache_GetCleanedDevice (NCDUdevCache *o, BStringMap *out_map);
+const char * NCDUdevCache_First (NCDUdevCache *o);
+const char * NCDUdevCache_Next (NCDUdevCache *o, const char *key);
+
+#endif
diff --git a/external/badvpn_dns/udevmonitor/NCDUdevManager.c b/external/badvpn_dns/udevmonitor/NCDUdevManager.c
new file mode 100644
index 0000000..4a44fb7
--- /dev/null
+++ b/external/badvpn_dns/udevmonitor/NCDUdevManager.c
@@ -0,0 +1,547 @@
+/**
+ * @file NCDUdevManager.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <udevmonitor/NCDUdevManager.h>
+
+#include <generated/blog_channel_NCDUdevManager.h>
+
+#define RESTART_TIMER_TIME 5000
+
+static int event_to_map (NCDUdevMonitor *monitor, BStringMap *out_map);
+static void free_event (NCDUdevClient *o, struct NCDUdevClient_event *e);
+static void queue_event (NCDUdevManager *o, NCDUdevMonitor *monitor, NCDUdevClient *client);
+static void queue_mapless_event (NCDUdevManager *o, const char *devpath, NCDUdevClient *client);
+static void process_event (NCDUdevManager *o, NCDUdevMonitor *monitor);
+static void try_monitor (NCDUdevManager *o);
+static void reset_monitor (NCDUdevManager *o);
+static void timer_handler (NCDUdevManager *o);
+static void monitor_handler_event (NCDUdevManager *o);
+static void monitor_handler_error (NCDUdevManager *o, int is_error);
+static void info_monitor_handler_event (NCDUdevManager *o);
+static void info_monitor_handler_error (NCDUdevManager *o, int is_error);
+static void next_job_handler (NCDUdevClient *o);
+
+static int event_to_map (NCDUdevMonitor *monitor, BStringMap *out_map)
+{
+    NCDUdevMonitor_AssertReady(monitor);
+    
+    // init map
+    BStringMap_Init(out_map);
+    
+    // insert properties to map
+    int num_properties = NCDUdevMonitor_GetNumProperties(monitor);
+    for (int i = 0; i < num_properties; i++) {
+        const char *name;
+        const char *value;
+        NCDUdevMonitor_GetProperty(monitor, i, &name, &value);
+        
+        if (!BStringMap_Set(out_map, name, value)) {
+            BLog(BLOG_ERROR, "BStringMap_Set failed");
+            goto fail1;
+        }
+    }
+    
+    return 1;
+    
+fail1:
+    BStringMap_Free(out_map);
+    return 0;
+}
+
+static void free_event (NCDUdevClient *o, struct NCDUdevClient_event *e)
+{
+    // remove from events list
+    LinkedList1_Remove(&o->events_list, &e->events_list_node);
+    
+    // free map
+    if (e->have_map) {
+        BStringMap_Free(&e->map);
+    }
+    
+    // free devpath
+    free(e->devpath);
+    
+    // free structure
+    free(e);
+}
+
+static void queue_event (NCDUdevManager *o, NCDUdevMonitor *monitor, NCDUdevClient *client)
+{
+    NCDUdevMonitor_AssertReady(monitor);
+    
+    // alloc event
+    struct NCDUdevClient_event *e = malloc(sizeof(*e));
+    if (!e) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // build map
+    if (!event_to_map(monitor, &e->map)) {
+        goto fail1;
+    }
+    
+    // set have map
+    e->have_map = 1;
+    
+    // get devpath
+    const char *devpath = BStringMap_Get(&e->map, "DEVPATH");
+    if (!devpath) {
+        BLog(BLOG_ERROR, "DEVPATH missing");
+        goto fail2;
+    }
+    
+    // copy devpath
+    if (!(e->devpath = strdup(devpath))) {
+        BLog(BLOG_ERROR, "strdup failed");
+        goto fail2;
+    }
+    
+    // insert to client's events list
+    LinkedList1_Append(&client->events_list, &e->events_list_node);
+    
+    // if client is running, set next job
+    if (client->running) {
+        BPending_Set(&client->next_job);
+    }
+    
+    return;
+    
+fail2:
+    BStringMap_Free(&e->map);
+fail1:
+    free(e);
+fail0:
+    return;
+}
+
+static void queue_mapless_event (NCDUdevManager *o, const char *devpath, NCDUdevClient *client)
+{
+    // alloc event
+    struct NCDUdevClient_event *e = malloc(sizeof(*e));
+    if (!e) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // set have no map
+    e->have_map = 0;
+    
+    // copy devpath
+    if (!(e->devpath = strdup(devpath))) {
+        BLog(BLOG_ERROR, "strdup failed");
+        goto fail1;
+    }
+    
+    // insert to client's events list
+    LinkedList1_Append(&client->events_list, &e->events_list_node);
+    
+    // if client is running, set next job
+    if (client->running) {
+        BPending_Set(&client->next_job);
+    }
+    
+    return;
+    
+fail1:
+    free(e);
+fail0:
+    return;
+}
+
+static void process_event (NCDUdevManager *o, NCDUdevMonitor *monitor)
+{
+    NCDUdevMonitor_AssertReady(monitor);
+    
+    // build map from event
+    BStringMap map;
+    if (!event_to_map(monitor, &map)) {
+        BLog(BLOG_ERROR, "failed to build map");
+        return;
+    }
+    
+    // pass event to cache
+    if (!NCDUdevCache_Event(&o->cache, map)) {
+        BLog(BLOG_ERROR, "failed to cache");
+        BStringMap_Free(&map);
+        return;
+    }
+    
+    // queue event to clients
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->clients_list);
+    while (list_node) {
+        NCDUdevClient *client = UPPER_OBJECT(list_node, NCDUdevClient, clients_list_node);
+        queue_event(o, monitor, client);
+        list_node = LinkedList1Node_Next(list_node);
+    }
+}
+
+static void try_monitor (NCDUdevManager *o)
+{
+    ASSERT(!o->have_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    int mode = (o->no_udev ? NCDUDEVMONITOR_MODE_MONITOR_KERNEL : NCDUDEVMONITOR_MODE_MONITOR_UDEV);
+    
+    // init monitor
+    if (!NCDUdevMonitor_Init(&o->monitor, o->reactor, o->manager, mode, o,
+        (NCDUdevMonitor_handler_event)monitor_handler_event,
+        (NCDUdevMonitor_handler_error)monitor_handler_error
+    )) {
+        BLog(BLOG_ERROR, "NCDUdevMonitor_Init failed");
+        
+        // set restart timer
+        BReactor_SetTimer(o->reactor, &o->restart_timer);
+        return;
+    }
+    
+    // set have monitor
+    o->have_monitor = 1;
+    
+    // set not have info monitor
+    o->have_info_monitor = 0;
+}
+
+static void reset_monitor (NCDUdevManager *o)
+{
+    ASSERT(o->have_monitor)
+    ASSERT(!o->have_info_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    // free monitor
+    NCDUdevMonitor_Free(&o->monitor);
+    
+    // set have no monitor
+    o->have_monitor = 0;
+    
+    // set restart timer
+    BReactor_SetTimer(o->reactor, &o->restart_timer);
+}
+
+static void timer_handler (NCDUdevManager *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->have_monitor)
+    
+    // try again
+    try_monitor(o);
+}
+
+static void monitor_handler_event (NCDUdevManager *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_monitor)
+    ASSERT(!o->have_info_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    if (NCDUdevMonitor_IsReadyEvent(&o->monitor)) {
+        BLog(BLOG_INFO, "monitor ready");
+        
+        // init info monitor
+        if (!NCDUdevMonitor_Init(&o->info_monitor, o->reactor, o->manager, NCDUDEVMONITOR_MODE_INFO, o,
+            (NCDUdevMonitor_handler_event)info_monitor_handler_event,
+            (NCDUdevMonitor_handler_error)info_monitor_handler_error
+        )) {
+            BLog(BLOG_ERROR, "NCDUdevMonitor_Init failed");
+            reset_monitor(o);
+            return;
+        }
+        
+        // set have info monitor
+        o->have_info_monitor = 1;
+        
+        // start cache cleanup
+        NCDUdevCache_StartClean(&o->cache);
+        
+        // hold processing monitor events until info monitor is done
+        return;
+    }
+    
+    // accept event
+    NCDUdevMonitor_Done(&o->monitor);
+    
+    // process event
+    process_event(o, &o->monitor);
+}
+
+static void monitor_handler_error (NCDUdevManager *o, int is_error)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    BLog(BLOG_ERROR, "monitor error");
+    
+    if (o->have_info_monitor) {
+        // free info monitor
+        NCDUdevMonitor_Free(&o->info_monitor);
+        
+        // set have no info monitor
+        o->have_info_monitor = 0;
+    }
+    
+    // reset monitor
+    reset_monitor(o);
+}
+
+static void info_monitor_handler_event (NCDUdevManager *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_monitor)
+    ASSERT(o->have_info_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    // accept event
+    NCDUdevMonitor_Done(&o->info_monitor);
+    
+    // process event
+    process_event(o, &o->info_monitor);
+}
+
+static void info_monitor_handler_error (NCDUdevManager *o, int is_error)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_monitor)
+    ASSERT(o->have_info_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    if (is_error) {
+        BLog(BLOG_ERROR, "info monitor error");
+    } else {
+        BLog(BLOG_INFO, "info monitor finished");
+    }
+    
+    // free info monitor
+    NCDUdevMonitor_Free(&o->info_monitor);
+    
+    // set have no info monitor
+    o->have_info_monitor = 0;
+    
+    if (is_error) {
+        // reset monitor
+        reset_monitor(o);
+    } else {
+        // continue processing monitor events
+        NCDUdevMonitor_Done(&o->monitor);
+        
+        // finish cache cleanup
+        NCDUdevCache_FinishClean(&o->cache);
+        
+        // collect cleaned devices
+        BStringMap map;
+        while (NCDUdevCache_GetCleanedDevice(&o->cache, &map)) {
+            // get devpath
+            const char *devpath = BStringMap_Get(&map, "DEVPATH");
+            ASSERT(devpath)
+            
+            // queue mapless event to clients
+            LinkedList1Node *list_node = LinkedList1_GetFirst(&o->clients_list);
+            while (list_node) {
+                NCDUdevClient *client = UPPER_OBJECT(list_node, NCDUdevClient, clients_list_node);
+                queue_mapless_event(o, devpath, client);
+                list_node = LinkedList1Node_Next(list_node);
+            }
+            
+            BStringMap_Free(&map);
+        }
+    }
+}
+
+static void next_job_handler (NCDUdevClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!LinkedList1_IsEmpty(&o->events_list))
+    ASSERT(o->running)
+    
+    // get event
+    struct NCDUdevClient_event *e = UPPER_OBJECT(LinkedList1_GetFirst(&o->events_list), struct NCDUdevClient_event, events_list_node);
+    
+    // grab map from event
+    int have_map = e->have_map;
+    BStringMap map = e->map;
+    
+    // grab devpath from event
+    char *devpath = e->devpath;
+    
+    // remove from events list
+    LinkedList1_Remove(&o->events_list, &e->events_list_node);
+    
+    // free structure
+    free(e);
+    
+    // schedule next event if needed
+    if (!LinkedList1_IsEmpty(&o->events_list)) {
+        BPending_Set(&o->next_job);
+    }
+    
+    // give map to handler
+    o->handler(o->user, devpath, have_map, map);
+    return;
+}
+
+void NCDUdevManager_Init (NCDUdevManager *o, int no_udev, BReactor *reactor, BProcessManager *manager)
+{
+    ASSERT(no_udev == 0 || no_udev == 1)
+    
+    // init arguments
+    o->no_udev = no_udev;
+    o->reactor = reactor;
+    o->manager = manager;
+    
+    // init clients list
+    LinkedList1_Init(&o->clients_list);
+    
+    // init cache
+    NCDUdevCache_Init(&o->cache);
+    
+    // init restart timer
+    BTimer_Init(&o->restart_timer, RESTART_TIMER_TIME, (BTimer_handler)timer_handler, o);
+    
+    // set have no monitor
+    o->have_monitor = 0;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void NCDUdevManager_Free (NCDUdevManager *o)
+{
+    DebugObject_Free(&o->d_obj);
+    ASSERT(LinkedList1_IsEmpty(&o->clients_list))
+    
+    if (o->have_monitor) {
+        // free info monitor
+        if (o->have_info_monitor) {
+            NCDUdevMonitor_Free(&o->info_monitor);
+        }
+        
+        // free monitor
+        NCDUdevMonitor_Free(&o->monitor);
+    }
+    
+    // free restart timer
+    BReactor_RemoveTimer(o->reactor, &o->restart_timer);
+    
+    // free cache
+    NCDUdevCache_Free(&o->cache);
+}
+
+const BStringMap * NCDUdevManager_Query (NCDUdevManager *o, const char *devpath)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return NCDUdevCache_Query(&o->cache, devpath);
+}
+
+void NCDUdevClient_Init (NCDUdevClient *o, NCDUdevManager *m, void *user,
+                         NCDUdevClient_handler handler)
+{
+    DebugObject_Access(&m->d_obj);
+    
+    // init arguments
+    o->m = m;
+    o->user = user;
+    o->handler = handler;
+    
+    // insert to manager's list
+    LinkedList1_Append(&m->clients_list, &o->clients_list_node);
+    
+    // init events list
+    LinkedList1_Init(&o->events_list);
+    
+    // init next job
+    BPending_Init(&o->next_job, BReactor_PendingGroup(m->reactor), (BPending_handler)next_job_handler, o);
+    
+    // set running
+    o->running = 1;
+    
+    // queue all devices from cache
+    const char *devpath = NCDUdevCache_First(&m->cache);
+    while (devpath) {
+        queue_mapless_event(m, devpath, o);
+        devpath = NCDUdevCache_Next(&m->cache, devpath);
+    }
+    
+    // if this is the first client, init monitor
+    if (!m->have_monitor && !BTimer_IsRunning(&m->restart_timer)) {
+        try_monitor(m);
+    }
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void NCDUdevClient_Free (NCDUdevClient *o)
+{
+    DebugObject_Free(&o->d_obj);
+    NCDUdevManager *m = o->m;
+    
+    // free events
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&o->events_list)) {
+        struct NCDUdevClient_event *e = UPPER_OBJECT(list_node, struct NCDUdevClient_event, events_list_node);
+        free_event(o, e);
+    }
+    
+    // free next job
+    BPending_Free(&o->next_job);
+    
+    // remove from manager's list
+    LinkedList1_Remove(&m->clients_list, &o->clients_list_node);
+}
+
+void NCDUdevClient_Pause (NCDUdevClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->running)
+    
+    // set not running
+    o->running = 0;
+    
+    // unset next job to avoid reporting queued events
+    BPending_Unset(&o->next_job);
+}
+
+void NCDUdevClient_Continue (NCDUdevClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->running)
+    
+    // set running
+    o->running = 1;
+    
+    // set next job if we have events queued
+    if (!LinkedList1_IsEmpty(&o->events_list)) {
+        BPending_Set(&o->next_job);
+    }
+}
diff --git a/external/badvpn_dns/udevmonitor/NCDUdevManager.h b/external/badvpn_dns/udevmonitor/NCDUdevManager.h
new file mode 100644
index 0000000..26ecfc6
--- /dev/null
+++ b/external/badvpn_dns/udevmonitor/NCDUdevManager.h
@@ -0,0 +1,84 @@
+/**
+ * @file NCDUdevManager.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_UDEVMONITOR_NCDUDEVMANAGER_H
+#define BADVPN_UDEVMONITOR_NCDUDEVMANAGER_H
+
+#include <misc/debug.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <udevmonitor/NCDUdevMonitor.h>
+#include <udevmonitor/NCDUdevCache.h>
+#include <stringmap/BStringMap.h>
+
+typedef void (*NCDUdevClient_handler) (void *user, char *devpath, int have_map, BStringMap map);
+
+typedef struct {
+    int no_udev;
+    BReactor *reactor;
+    BProcessManager *manager;
+    LinkedList1 clients_list;
+    NCDUdevCache cache;
+    BTimer restart_timer;
+    int have_monitor;
+    NCDUdevMonitor monitor;
+    int have_info_monitor;
+    NCDUdevMonitor info_monitor;
+    DebugObject d_obj;
+} NCDUdevManager;
+
+typedef struct {
+    NCDUdevManager *m;
+    void *user;
+    NCDUdevClient_handler handler;
+    LinkedList1Node clients_list_node;
+    LinkedList1 events_list;
+    BPending next_job;
+    int running;
+    DebugObject d_obj;
+} NCDUdevClient;
+
+struct NCDUdevClient_event {
+    char *devpath;
+    int have_map;
+    BStringMap map;
+    LinkedList1Node events_list_node;
+};
+
+void NCDUdevManager_Init (NCDUdevManager *o, int no_udev, BReactor *reactor, BProcessManager *manager);
+void NCDUdevManager_Free (NCDUdevManager *o);
+const BStringMap * NCDUdevManager_Query (NCDUdevManager *o, const char *devpath);
+
+void NCDUdevClient_Init (NCDUdevClient *o, NCDUdevManager *m, void *user,
+                         NCDUdevClient_handler handler);
+void NCDUdevClient_Free (NCDUdevClient *o);
+void NCDUdevClient_Pause (NCDUdevClient *o);
+void NCDUdevClient_Continue (NCDUdevClient *o);
+
+#endif
diff --git a/external/badvpn_dns/udevmonitor/NCDUdevMonitor.c b/external/badvpn_dns/udevmonitor/NCDUdevMonitor.c
new file mode 100644
index 0000000..56970cd
--- /dev/null
+++ b/external/badvpn_dns/udevmonitor/NCDUdevMonitor.c
@@ -0,0 +1,250 @@
+/**
+ * @file NCDUdevMonitor.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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 <base/BLog.h>
+#include <misc/find_program.h>
+
+#include <udevmonitor/NCDUdevMonitor.h>
+
+#include <generated/blog_channel_NCDUdevMonitor.h>
+
+#define PARSER_BUF_SIZE 16384
+#define PARSER_MAX_PROPERTIES 256
+
+static void report_error (NCDUdevMonitor *o)
+{
+    ASSERT(!o->process_running)
+    ASSERT(!o->input_running)
+    
+    DEBUGERROR(&o->d_err, o->handler_error(o->user, (o->process_was_error || o->input_was_error)));
+}
+
+static void process_handler_terminated (NCDUdevMonitor *o, int normally, uint8_t normally_exit_status)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->process_running)
+    
+    BLog(BLOG_INFO, "process terminated");
+    
+    // set process not running (so we don't try to kill it)
+    o->process_running = 0;
+    
+    // remember process error
+    o->process_was_error = !(normally && normally_exit_status == 0);
+    
+    if (!o->input_running) {
+        report_error(o);
+        return;
+    }
+}
+
+static void process_handler_closed (NCDUdevMonitor *o, int is_error)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->input_running)
+    
+    if (is_error) {
+        BLog(BLOG_ERROR, "pipe error");
+    } else {
+        BLog(BLOG_INFO, "pipe closed");
+    }
+    
+    // disconnect connector
+    StreamRecvConnector_DisconnectInput(&o->connector);
+    
+    // set input not running
+    o->input_running = 0;
+    
+    // remember input error
+    o->input_was_error = is_error;
+    
+    if (!o->process_running) {
+        report_error(o);
+        return;
+    }
+}
+
+static void parser_handler (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    o->handler_event(o->user);
+    return;
+}
+
+int NCDUdevMonitor_Init (NCDUdevMonitor *o, BReactor *reactor, BProcessManager *manager, int mode, void *user,
+                         NCDUdevMonitor_handler_event handler_event,
+                         NCDUdevMonitor_handler_error handler_error)
+{
+    ASSERT(mode == NCDUDEVMONITOR_MODE_MONITOR_UDEV || mode == NCDUDEVMONITOR_MODE_INFO || mode == NCDUDEVMONITOR_MODE_MONITOR_KERNEL)
+    
+    // init arguments
+    o->user = user;
+    o->handler_event = handler_event;
+    o->handler_error = handler_error;
+    
+    // find programs
+    char *stdbuf_exec = badvpn_find_program("stdbuf");
+    char *udevadm_exec = badvpn_find_program("udevadm");
+    if (!stdbuf_exec) {
+        BLog(BLOG_ERROR, "failed to find stdbuf program");
+        goto fail0;
+    }
+    if (!udevadm_exec) {
+        BLog(BLOG_ERROR, "failed to find udevadm program");
+        goto fail0;
+    }
+    
+    // construct arguments
+    const char *argv_monitor_udev[] = {stdbuf_exec, "-o", "L", udevadm_exec, "monitor", "--udev", "--environment", NULL};
+    const char *argv_monitor_kernel[] = {stdbuf_exec, "-o", "L", udevadm_exec, "monitor", "--kernel", "--environment", NULL};
+    const char *argv_info[] = {stdbuf_exec, "-o", "L", udevadm_exec, "info", "--query", "all", "--export-db", NULL};
+    
+    // choose arguments based on mode
+    const char **argv = NULL; // to remove warning
+    switch (mode) {
+        case NCDUDEVMONITOR_MODE_MONITOR_UDEV:   argv = argv_monitor_udev; break;
+        case NCDUDEVMONITOR_MODE_INFO:           argv = argv_info; break;
+        case NCDUDEVMONITOR_MODE_MONITOR_KERNEL: argv = argv_monitor_kernel; break;
+        default: ASSERT(0);
+    }
+    
+    // init process
+    if (!BInputProcess_Init(&o->process, reactor, manager, o,
+                            (BInputProcess_handler_terminated)process_handler_terminated,
+                            (BInputProcess_handler_closed)process_handler_closed
+    )) {
+        BLog(BLOG_ERROR, "BInputProcess_Init failed");
+        goto fail0;
+    }
+    
+    // init connector
+    StreamRecvConnector_Init(&o->connector, BReactor_PendingGroup(reactor));
+    StreamRecvConnector_ConnectInput(&o->connector, BInputProcess_GetInput(&o->process));
+    
+    // init parser
+    if (!NCDUdevMonitorParser_Init(&o->parser, StreamRecvConnector_GetOutput(&o->connector), PARSER_BUF_SIZE, PARSER_MAX_PROPERTIES,
+                                   (mode == NCDUDEVMONITOR_MODE_INFO), BReactor_PendingGroup(reactor), o,
+                                   (NCDUdevMonitorParser_handler)parser_handler
+    )) {
+        BLog(BLOG_ERROR, "NCDUdevMonitorParser_Init failed");
+        goto fail1;
+    }
+    
+    // start process
+    if (!BInputProcess_Start(&o->process, stdbuf_exec, (char **)argv, NULL)) {
+        BLog(BLOG_ERROR, "BInputProcess_Start failed");
+        goto fail2;
+    }
+    
+    // set process running, input running
+    o->process_running = 1;
+    o->input_running = 1;
+    
+    free(udevadm_exec);
+    free(stdbuf_exec);
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail2:
+    NCDUdevMonitorParser_Free(&o->parser);
+fail1:
+    StreamRecvConnector_Free(&o->connector);
+    BInputProcess_Free(&o->process);
+fail0:
+    free(udevadm_exec);
+    free(stdbuf_exec);
+    return 0;
+}
+
+void NCDUdevMonitor_Free (NCDUdevMonitor *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    
+    // free parser
+    NCDUdevMonitorParser_Free(&o->parser);
+    
+    // free connector
+    StreamRecvConnector_Free(&o->connector);
+    
+    // kill process it it's running
+    if (o->process_running) {
+        BInputProcess_Kill(&o->process);
+    }
+    
+    // free process
+    BInputProcess_Free(&o->process);
+}
+
+void NCDUdevMonitor_Done (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+    
+    NCDUdevMonitorParser_Done(&o->parser);
+}
+
+int NCDUdevMonitor_IsReadyEvent (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+    
+    return NCDUdevMonitorParser_IsReadyEvent(&o->parser);
+}
+void NCDUdevMonitor_AssertReady (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+}
+
+int NCDUdevMonitor_GetNumProperties (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+    
+    return NCDUdevMonitorParser_GetNumProperties(&o->parser);
+}
+
+void NCDUdevMonitor_GetProperty (NCDUdevMonitor *o, int index, const char **name, const char **value)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+    
+    NCDUdevMonitorParser_GetProperty(&o->parser, index, name, value);
+}
diff --git a/external/badvpn_dns/udevmonitor/NCDUdevMonitor.h b/external/badvpn_dns/udevmonitor/NCDUdevMonitor.h
new file mode 100644
index 0000000..eae5747
--- /dev/null
+++ b/external/badvpn_dns/udevmonitor/NCDUdevMonitor.h
@@ -0,0 +1,71 @@
+/**
+ * @file NCDUdevMonitor.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_UDEVMONITOR_NCDUDEVMONITOR_H
+#define BADVPN_UDEVMONITOR_NCDUDEVMONITOR_H
+
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <flow/StreamRecvConnector.h>
+#include <system/BInputProcess.h>
+#include <udevmonitor/NCDUdevMonitorParser.h>
+
+#define NCDUDEVMONITOR_MODE_MONITOR_UDEV 0
+#define NCDUDEVMONITOR_MODE_INFO 1
+#define NCDUDEVMONITOR_MODE_MONITOR_KERNEL 2
+
+typedef void (*NCDUdevMonitor_handler_event) (void *user);
+typedef void (*NCDUdevMonitor_handler_error) (void *user, int is_error);
+
+typedef struct {
+    void *user;
+    NCDUdevMonitor_handler_event handler_event;
+    NCDUdevMonitor_handler_error handler_error;
+    BInputProcess process;
+    int process_running;
+    int process_was_error;
+    int input_running;
+    int input_was_error;
+    StreamRecvConnector connector;
+    NCDUdevMonitorParser parser;
+    DebugObject d_obj;
+    DebugError d_err;
+} NCDUdevMonitor;
+
+int NCDUdevMonitor_Init (NCDUdevMonitor *o, BReactor *reactor, BProcessManager *manager, int mode, void *user,
+                         NCDUdevMonitor_handler_event handler_event,
+                         NCDUdevMonitor_handler_error handler_error) WARN_UNUSED;
+void NCDUdevMonitor_Free (NCDUdevMonitor *o);
+void NCDUdevMonitor_Done (NCDUdevMonitor *o);
+void NCDUdevMonitor_AssertReady (NCDUdevMonitor *o);
+int NCDUdevMonitor_IsReadyEvent (NCDUdevMonitor *o);
+int NCDUdevMonitor_GetNumProperties (NCDUdevMonitor *o);
+void NCDUdevMonitor_GetProperty (NCDUdevMonitor *o, int index, const char **name, const char **value);
+
+#endif
diff --git a/external/badvpn_dns/udevmonitor/NCDUdevMonitorParser.c b/external/badvpn_dns/udevmonitor/NCDUdevMonitorParser.c
new file mode 100644
index 0000000..9ec0ef1
--- /dev/null
+++ b/external/badvpn_dns/udevmonitor/NCDUdevMonitorParser.c
@@ -0,0 +1,358 @@
+/**
+ * @file NCDUdevMonitorParser.c
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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/string_begins_with.h>
+#include <misc/balloc.h>
+#include <base/BLog.h>
+
+#include <udevmonitor/NCDUdevMonitorParser.h>
+
+#include <generated/blog_channel_NCDUdevMonitorParser.h>
+
+#define PROPERTY_REGEX "^([^=]+)=(.*)$"
+
+static uint8_t * find_end (uint8_t *buf, size_t len)
+{
+    while (len >= 2) {
+        if (buf[0] == '\n' && buf[1] == '\n') {
+            return (buf + 2);
+        }
+        buf++;
+        len--;
+    }
+    
+    return NULL;
+}
+
+static int parse_property (NCDUdevMonitorParser *o, char *data)
+{
+    ASSERT(o->ready_num_properties >= 0)
+    ASSERT(o->ready_num_properties <= o->max_properties)
+    
+    if (o->ready_num_properties == o->max_properties) {
+        BLog(BLOG_ERROR, "too many properties");
+        return 0;
+    }
+    struct NCDUdevMonitorParser_property *prop = &o->ready_properties[o->ready_num_properties];
+    
+    // execute property regex
+    regmatch_t matches[3];
+    if (regexec(&o->property_regex, data, 3, matches, 0) != 0) {
+        BLog(BLOG_ERROR, "failed to parse property");
+        return 0;
+    }
+    
+    // extract components
+    prop->name = data + matches[1].rm_so;
+    *(data + matches[1].rm_eo) = '\0';
+    prop->value = data + matches[2].rm_so;
+    *(data + matches[2].rm_eo) = '\0';
+    
+    // register property
+    o->ready_num_properties++;
+    
+    return 1;
+}
+
+static int parse_message (NCDUdevMonitorParser *o)
+{
+    ASSERT(!o->is_ready)
+    ASSERT(o->ready_len >= 2)
+    ASSERT(o->buf[o->ready_len - 2] == '\n')
+    ASSERT(o->buf[o->ready_len - 1] == '\n')
+    
+    // zero terminate message (replacing the second newline)
+    o->buf[o->ready_len - 1] = '\0';
+    
+    // start parsing
+    char *line = (char *)o->buf;
+    int first_line = 1;
+    
+    // set is not ready event
+    o->ready_is_ready_event = 0;
+    
+    // init properties
+    o->ready_num_properties = 0;
+    
+    do {
+        // find end of line
+        char *line_end = strchr(line, '\n');
+        ASSERT(line_end)
+        
+        // zero terminate line
+        *line_end = '\0';
+        
+        if (o->is_info_mode) {
+            // ignore W: entries with missing space
+            if (string_begins_with(line, "W:")) {
+                goto nextline;
+            }
+            
+            // parse prefix
+            if (strlen(line) < 3 || line[1] != ':' || line[2] != ' ') {
+                BLog(BLOG_ERROR, "failed to parse head");
+                return 0;
+            }
+            char line_type = line[0];
+            char *line_value = line + 3;
+            
+            if (first_line) {
+                if (line_type != 'P') {
+                    BLog(BLOG_ERROR, "wrong first line type");
+                    return 0;
+                }
+            } else {
+                if (line_type == 'E') {
+                    if (!parse_property(o, line_value)) {
+                        return 0;
+                    }
+                }
+            }
+        } else {
+            if (first_line) {
+                // is this the initial informational message?
+                if (string_begins_with(line, "monitor")) {
+                    o->ready_is_ready_event = 1;
+                    break;
+                }
+                
+                // check first line
+                if (!string_begins_with(line, "UDEV  ") && !string_begins_with(line, "KERNEL")) {
+                    BLog(BLOG_ERROR, "failed to parse head");
+                    return 0;
+                }
+            } else {
+                if (!parse_property(o, line)) {
+                    return 0;
+                }
+            }
+        }
+    nextline:
+        
+        first_line = 0;
+        line = line_end + 1;
+    } while (*line);
+    
+    // set ready
+    o->is_ready = 1;
+    
+    return 1;
+}
+
+static void process_data (NCDUdevMonitorParser *o)
+{
+    ASSERT(!o->is_ready)
+    
+    while (1) {
+        // look for end of event
+        uint8_t *c = find_end(o->buf, o->buf_used);
+        if (!c) {
+            // check for out of buffer condition
+            if (o->buf_used == o->buf_size) {
+                BLog(BLOG_ERROR, "out of buffer");
+                o->buf_used = 0;
+            }
+            
+            // receive more data
+            StreamRecvInterface_Receiver_Recv(o->input, o->buf + o->buf_used, o->buf_size - o->buf_used);
+            return;
+        }
+        
+        // remember message length
+        o->ready_len = c - o->buf;
+        
+        // parse message
+        if (parse_message(o)) {
+            break;
+        }
+        
+        // shift buffer
+        memmove(o->buf, o->buf + o->ready_len, o->buf_used - o->ready_len);
+        o->buf_used -= o->ready_len;
+    }
+    
+    // call handler
+    o->handler(o->user);
+    return;
+}
+
+static void input_handler_done (NCDUdevMonitorParser *o, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->is_ready)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->buf_size - o->buf_used)
+    
+    // increment buffer position
+    o->buf_used += data_len;
+    
+    // process data
+    process_data(o);
+    return;
+}
+
+static void done_job_handler (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    
+    // shift buffer
+    memmove(o->buf, o->buf + o->ready_len, o->buf_used - o->ready_len);
+    o->buf_used -= o->ready_len;
+    
+    // set not ready
+    o->is_ready = 0;
+    
+    // process data
+    process_data(o);
+    return;
+}
+
+int NCDUdevMonitorParser_Init (NCDUdevMonitorParser *o, StreamRecvInterface *input, int buf_size, int max_properties,
+                               int is_info_mode, BPendingGroup *pg, void *user,
+                               NCDUdevMonitorParser_handler handler)
+{
+    ASSERT(buf_size > 0)
+    ASSERT(max_properties >= 0)
+    ASSERT(is_info_mode == 0 || is_info_mode == 1)
+    
+    // init arguments
+    o->input = input;
+    o->buf_size = buf_size;
+    o->max_properties = max_properties;
+    o->is_info_mode = is_info_mode;
+    o->user = user;
+    o->handler = handler;
+    
+    // init input
+    StreamRecvInterface_Receiver_Init(o->input, (StreamRecvInterface_handler_done)input_handler_done, o);
+    
+    // init property regex
+    if (regcomp(&o->property_regex, PROPERTY_REGEX, REG_EXTENDED) != 0) {
+        BLog(BLOG_ERROR, "regcomp failed");
+        goto fail1;
+    }
+    
+    // init done job
+    BPending_Init(&o->done_job, pg, (BPending_handler)done_job_handler, o);
+    
+    // allocate buffer
+    if (!(o->buf = malloc(o->buf_size))) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail2;
+    }
+    
+    // set buffer position
+    o->buf_used = 0;
+    
+    // set not ready
+    o->is_ready = 0;
+    
+    // allocate properties
+    if (!(o->ready_properties = BAllocArray(o->max_properties, sizeof(o->ready_properties[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail3;
+    }
+    
+    // start receiving
+    StreamRecvInterface_Receiver_Recv(o->input, o->buf, o->buf_size);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail3:
+    free(o->buf);
+fail2:
+    BPending_Free(&o->done_job);
+    regfree(&o->property_regex);
+fail1:
+    return 0;
+}
+
+void NCDUdevMonitorParser_Free (NCDUdevMonitorParser *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free properties
+    BFree(o->ready_properties);
+    
+    // free buffer
+    free(o->buf);
+    
+    // free done job
+    BPending_Free(&o->done_job);
+    
+    // free property regex
+    regfree(&o->property_regex);
+}
+
+void NCDUdevMonitorParser_AssertReady (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+}
+
+void NCDUdevMonitorParser_Done (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    
+    // schedule done job
+    BPending_Set(&o->done_job);
+}
+
+int NCDUdevMonitorParser_IsReadyEvent (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    
+    return o->ready_is_ready_event;
+}
+
+int NCDUdevMonitorParser_GetNumProperties (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    
+    return o->ready_num_properties;
+}
+
+void NCDUdevMonitorParser_GetProperty (NCDUdevMonitorParser *o, int index, const char **name, const char **value)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    ASSERT(index >= 0)
+    ASSERT(index < o->ready_num_properties)
+    
+    *name = o->ready_properties[index].name;
+    *value = o->ready_properties[index].value;
+}
diff --git a/external/badvpn_dns/udevmonitor/NCDUdevMonitorParser.h b/external/badvpn_dns/udevmonitor/NCDUdevMonitorParser.h
new file mode 100644
index 0000000..910bbe0
--- /dev/null
+++ b/external/badvpn_dns/udevmonitor/NCDUdevMonitorParser.h
@@ -0,0 +1,76 @@
+/**
+ * @file NCDUdevMonitorParser.h
+ * @author Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * @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_UDEVMONITOR_NCDUDEVMONITORPARSER_H
+#define BADVPN_UDEVMONITOR_NCDUDEVMONITORPARSER_H
+
+#include <stdint.h>
+#include <regex.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <flow/StreamRecvInterface.h>
+
+typedef void (*NCDUdevMonitorParser_handler) (void *user);
+
+struct NCDUdevMonitorParser_property {
+    char *name;
+    char *value;
+};
+
+typedef struct {
+    StreamRecvInterface *input;
+    int buf_size;
+    int max_properties;
+    int is_info_mode;
+    void *user;
+    NCDUdevMonitorParser_handler handler;
+    regex_t property_regex;
+    BPending done_job;
+    uint8_t *buf;
+    int buf_used;
+    int is_ready;
+    int ready_len;
+    int ready_is_ready_event;
+    struct NCDUdevMonitorParser_property *ready_properties;
+    int ready_num_properties;
+    DebugObject d_obj;
+} NCDUdevMonitorParser;
+
+int NCDUdevMonitorParser_Init (NCDUdevMonitorParser *o, StreamRecvInterface *input, int buf_size, int max_properties,
+                               int is_info_mode, BPendingGroup *pg, void *user,
+                               NCDUdevMonitorParser_handler handler) WARN_UNUSED;
+void NCDUdevMonitorParser_Free (NCDUdevMonitorParser *o);
+void NCDUdevMonitorParser_AssertReady (NCDUdevMonitorParser *o);
+void NCDUdevMonitorParser_Done (NCDUdevMonitorParser *o);
+int NCDUdevMonitorParser_IsReadyEvent (NCDUdevMonitorParser *o);
+int NCDUdevMonitorParser_GetNumProperties (NCDUdevMonitorParser *o);
+void NCDUdevMonitorParser_GetProperty (NCDUdevMonitorParser *o, int index, const char **name, const char **value);
+
+#endif
diff --git a/external/badvpn_dns/udpgw/CMakeLists.txt b/external/badvpn_dns/udpgw/CMakeLists.txt
new file mode 100644
index 0000000..c8c798c
--- /dev/null
+++ b/external/badvpn_dns/udpgw/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_executable(badvpn-udpgw
+    udpgw.c
+)
+target_link_libraries(badvpn-udpgw system flow flowextra)
+
+install(
+    TARGETS badvpn-udpgw
+    RUNTIME DESTINATION bin
+)
diff --git a/external/badvpn_dns/udpgw/udpgw.c b/external/badvpn_dns/udpgw/udpgw.c
new file mode 100644
index 0000000..9c6a341
--- /dev/null
+++ b/external/badvpn_dns/udpgw/udpgw.c
@@ -0,0 +1,1473 @@
+/*
+ * Copyright (C) Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * Contributions:
+ * Transparent DNS: Copyright (C) Kerem Hadimli <kerem.hadimli@xxxxxxxxx>
+ * 
+ * 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 <limits.h>
+
+#include <protocol/udpgw_proto.h>
+#include <misc/debug.h>
+#include <misc/version.h>
+#include <misc/loggers_string.h>
+#include <misc/loglevel.h>
+#include <misc/offset.h>
+#include <misc/byteorder.h>
+#include <misc/bsize.h>
+#include <misc/open_standard_streams.h>
+#include <misc/balloc.h>
+#include <misc/compare.h>
+#include <misc/print_macros.h>
+#include <structure/LinkedList1.h>
+#include <structure/BAVL.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BNetwork.h>
+#include <system/BConnection.h>
+#include <system/BDatagram.h>
+#include <system/BSignal.h>
+#include <flow/PacketProtoDecoder.h>
+#include <flow/PacketPassFairQueue.h>
+#include <flow/PacketStreamSender.h>
+#include <flow/PacketProtoFlow.h>
+#include <flow/SinglePacketBuffer.h>
+
+#ifndef BADVPN_USE_WINAPI
+#include <base/BLog_syslog.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#endif
+
+#include <udpgw/udpgw.h>
+
+#include <generated/blog_channel_udpgw.h>
+
+#define LOGGER_STDOUT 1
+#define LOGGER_SYSLOG 2
+
+#define DNS_UPDATE_TIME 2000
+
+struct client {
+    BConnection con;
+    BAddr addr;
+    BTimer disconnect_timer;
+    PacketProtoDecoder recv_decoder;
+    PacketPassInterface recv_if;
+    PacketPassFairQueue send_queue;
+    PacketStreamSender send_sender;
+    BAVL connections_tree;
+    LinkedList1 connections_list;
+    int num_connections;
+    LinkedList1 closing_connections_list;
+    LinkedList1Node clients_list_node;
+};
+
+struct connection {
+    struct client *client;
+    uint16_t conid;
+    BAddr addr;
+    BAddr orig_addr;
+    const uint8_t *first_data;
+    int first_data_len;
+    btime_t last_use_time;
+    int closing;
+    BPending first_job;
+    BufferWriter *send_if;
+    PacketProtoFlow send_ppflow;
+    PacketPassFairQueueFlow send_qflow;
+    union {
+        struct {
+            BDatagram udp_dgram;
+            int local_port_index;
+            BufferWriter udp_send_writer;
+            PacketBuffer udp_send_buffer;
+            SinglePacketBuffer udp_recv_buffer;
+            PacketPassInterface udp_recv_if;
+            BAVLNode connections_tree_node;
+            LinkedList1Node connections_list_node;
+        };
+        struct {
+            LinkedList1Node closing_connections_list_node;
+        };
+    };
+};
+
+// 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];
+    char *listen_addrs[MAX_LISTEN_ADDRS];
+    int num_listen_addrs;
+    int udp_mtu;
+    int max_clients;
+    int max_connections_for_client;
+    int client_socket_sndbuf;
+    int local_udp_num_ports;
+    char *local_udp_addr;
+    int local_udp_ip6_num_ports;
+    char *local_udp_ip6_addr;
+    int unique_local_ports;
+} options;
+
+// MTUs
+int udpgw_mtu;
+int pp_mtu;
+
+// listen addresses
+BAddr listen_addrs[MAX_LISTEN_ADDRS];
+int num_listen_addrs;
+
+// local UDP port range, if options.local_udp_num_ports>=0
+BAddr local_udp_addr;
+
+// local UDP/IPv6 port range, if options.local_udp_ip6_num_ports>=0
+BAddr local_udp_ip6_addr;
+
+// DNS forwarding
+BAddr dns_addr;
+btime_t last_dns_update_time;
+
+// reactor
+BReactor ss;
+
+// listeners
+BListener listeners[MAX_LISTEN_ADDRS];
+int num_listeners;
+
+// clients
+LinkedList1 clients_list;
+int num_clients;
+
+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 (BListener *listener);
+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 client_decoder_handler_error (struct client *client);
+static void client_recv_if_handler_send (struct client *client, uint8_t *data, int data_len);
+static int get_local_num_ports (int addr_type);
+static BAddr get_local_addr (int addr_type);
+static uint8_t * build_port_usage_array_and_find_least_used_connection (BAddr remote_addr, struct connection **out_con);
+static void connection_init (struct client *client, uint16_t conid, BAddr addr, BAddr orig_addr, const uint8_t *data, int data_len);
+static void connection_free (struct connection *con);
+static void connection_logfunc (struct connection *con);
+static void connection_log (struct connection *con, int level, const char *fmt, ...);
+static void connection_free_udp (struct connection *con);
+static void connection_first_job_handler (struct connection *con);
+static void connection_send_to_client (struct connection *con, uint8_t flags, const uint8_t *data, int data_len);
+static int connection_send_to_udp (struct connection *con, const uint8_t *data, int data_len);
+static void connection_close (struct connection *con);
+static void connection_send_qflow_busy_handler (struct connection *con);
+static void connection_dgram_handler_event (struct connection *con, int event);
+static void connection_udp_recv_if_handler_send (struct connection *con, uint8_t *data, int data_len);
+static struct connection * find_connection (struct client *client, uint16_t conid);
+static int uint16_comparator (void *unused, uint16_t *v1, uint16_t *v2);
+static void maybe_update_dns (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;
+    }
+    
+    // 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;
+    }
+    
+    // process arguments
+    if (!process_arguments()) {
+        BLog(BLOG_ERROR, "Failed to process arguments");
+        goto fail1;
+    }
+    
+    // compute MTUs
+    udpgw_mtu = udpgw_compute_mtu(options.udp_mtu);
+    if (udpgw_mtu < 0 || udpgw_mtu > PACKETPROTO_MAXPAYLOAD) {
+        udpgw_mtu = PACKETPROTO_MAXPAYLOAD;
+    }
+    pp_mtu = udpgw_mtu + sizeof(struct packetproto_header);
+    
+    // init time
+    BTime_Init();
+    
+    // init DNS forwarding
+    BAddr_InitNone(&dns_addr);
+    last_dns_update_time = INT64_MIN;
+    maybe_update_dns();
+    
+    // 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 listeners
+    num_listeners = 0;
+    while (num_listeners < num_listen_addrs) {
+        if (!BListener_Init(&listeners[num_listeners], listen_addrs[num_listeners], &ss, &listeners[num_listeners], (BListener_handler)listener_handler)) {
+            BLog(BLOG_ERROR, "Listener_Init failed");
+            goto fail3;
+        }
+        num_listeners++;
+    }
+    
+    // init clients list
+    LinkedList1_Init(&clients_list);
+    num_clients = 0;
+    
+    // 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);
+    }
+fail3:
+    // free listeners
+    while (num_listeners > 0) {
+        num_listeners--;
+        BListener_Free(&listeners[num_listeners]);
+    }
+    // finish signal handling
+    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"
+        "        [--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"
+        "        [--listen-addr <addr>] ...\n"
+        "        [--udp-mtu <bytes>]\n"
+        "        [--max-clients <number>]\n"
+        "        [--max-connections-for-client <number>]\n"
+        "        [--client-socket-sndbuf <bytes / 0>]\n"
+        "        [--local-udp-addrs <addr> <num_ports>]\n"
+        "        [--local-udp-ip6-addrs <addr> <num_ports>]\n"
+        "        [--unique-local-ports]\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.num_listen_addrs = 0;
+    options.udp_mtu = DEFAULT_UDP_MTU;
+    options.max_clients = DEFAULT_MAX_CLIENTS;
+    options.max_connections_for_client = DEFAULT_MAX_CONNECTIONS_FOR_CLIENT;
+    options.client_socket_sndbuf = CLIENT_DEFAULT_SOCKET_SEND_BUFFER;
+    options.local_udp_num_ports = -1;
+    options.local_udp_ip6_num_ports = -1;
+    options.unique_local_ports = 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, "--listen-addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.num_listen_addrs == MAX_LISTEN_ADDRS) {
+                fprintf(stderr, "%s: too many\n", arg);
+                return 0;
+            }
+            options.listen_addrs[options.num_listen_addrs] = argv[i + 1];
+            options.num_listen_addrs++;
+            i++;
+        }
+        else if (!strcmp(arg, "--udp-mtu")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.udp_mtu = atoi(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            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, "--max-connections-for-client")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.max_connections_for_client = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--client-socket-sndbuf")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.client_socket_sndbuf = atoi(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--local-udp-addrs")) {
+            if (2 >= argc - i) {
+                fprintf(stderr, "%s: requires two arguments\n", arg);
+                return 0;
+            }
+            options.local_udp_addr = argv[i + 1];
+            if ((options.local_udp_num_ports = atoi(argv[i + 2])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i += 2;
+        }
+        else if (!strcmp(arg, "--local-udp-ip6-addrs")) {
+            if (2 >= argc - i) {
+                fprintf(stderr, "%s: requires two arguments\n", arg);
+                return 0;
+            }
+            options.local_udp_ip6_addr = argv[i + 1];
+            if ((options.local_udp_ip6_num_ports = atoi(argv[i + 2])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i += 2;
+        }
+        else if (!strcmp(arg, "--unique-local-ports")) {
+            options.unique_local_ports = 1;
+        }
+        else {
+            fprintf(stderr, "unknown option: %s\n", arg);
+            return 0;
+        }
+    }
+    
+    if (options.help || options.version) {
+        return 1;
+    }
+    
+    return 1;
+}
+
+int process_arguments (void)
+{
+    // resolve listen addresses
+    num_listen_addrs = 0;
+    while (num_listen_addrs < options.num_listen_addrs) {
+        if (!BAddr_Parse(&listen_addrs[num_listen_addrs], options.listen_addrs[num_listen_addrs], NULL, 0)) {
+            BLog(BLOG_ERROR, "listen addr: BAddr_Parse failed");
+            return 0;
+        }
+        num_listen_addrs++;
+    }
+    
+    // resolve local UDP address
+    if (options.local_udp_num_ports >= 0) {
+        if (!BAddr_Parse(&local_udp_addr, options.local_udp_addr, NULL, 0)) {
+            BLog(BLOG_ERROR, "local udp addr: BAddr_Parse failed");
+            return 0;
+        }
+        if (local_udp_addr.type != BADDR_TYPE_IPV4) {
+            BLog(BLOG_ERROR, "local udp addr: must be an IPv4 address");
+            return 0;
+        }
+    }
+    
+    // resolve local UDP/IPv6 address
+    if (options.local_udp_ip6_num_ports >= 0) {
+        if (!BAddr_Parse(&local_udp_ip6_addr, options.local_udp_ip6_addr, NULL, 0)) {
+            BLog(BLOG_ERROR, "local udp ip6 addr: BAddr_Parse failed");
+            return 0;
+        }
+        if (local_udp_ip6_addr.type != BADDR_TYPE_IPV6) {
+            BLog(BLOG_ERROR, "local udp ip6 addr: must be an IPv6 address");
+            return 0;
+        }
+    }
+    
+    return 1;
+}
+
+void signal_handler (void *unused)
+{
+    BLog(BLOG_NOTICE, "termination requested");
+    
+    // exit event loop
+    BReactor_Quit(&ss, 1);
+}
+
+void listener_handler (BListener *listener)
+{
+    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;
+    }
+    
+    // limit socket send buffer, else our scheduling is pointless
+    if (options.client_socket_sndbuf > 0) {
+        if (!BConnection_SetSendBuffer(&client->con, options.client_socket_sndbuf)) {
+            BLog(BLOG_WARNING, "BConnection_SetSendBuffer failed");
+        }
+    }
+    
+    // init connection interfaces
+    BConnection_SendAsync_Init(&client->con);
+    BConnection_RecvAsync_Init(&client->con);
+    
+    // init disconnect timer
+    BTimer_Init(&client->disconnect_timer, CLIENT_DISCONNECT_TIMEOUT, (BTimer_handler)client_disconnect_timer_handler, client);
+    BReactor_SetTimer(&ss, &client->disconnect_timer);
+    
+    // init recv interface
+    PacketPassInterface_Init(&client->recv_if, udpgw_mtu, (PacketPassInterface_handler_send)client_recv_if_handler_send, client, BReactor_PendingGroup(&ss));
+    
+    // init recv decoder
+    if (!PacketProtoDecoder_Init(&client->recv_decoder, BConnection_RecvAsync_GetIf(&client->con), &client->recv_if, BReactor_PendingGroup(&ss), client,
+        (PacketProtoDecoder_handler_error)client_decoder_handler_error
+    )) {
+        BLog(BLOG_ERROR, "PacketProtoDecoder_Init failed");
+        goto fail2;
+    }
+    
+    // init send sender
+    PacketStreamSender_Init(&client->send_sender, BConnection_SendAsync_GetIf(&client->con), pp_mtu, BReactor_PendingGroup(&ss));
+    
+    // init send queue
+    if (!PacketPassFairQueue_Init(&client->send_queue, PacketStreamSender_GetInput(&client->send_sender), BReactor_PendingGroup(&ss), 0, 1)) {
+        BLog(BLOG_ERROR, "PacketPassFairQueue_Init failed");
+        goto fail3;
+    }
+    
+    // init connections tree
+    BAVL_Init(&client->connections_tree, OFFSET_DIFF(struct connection, conid, connections_tree_node), (BAVL_comparator)uint16_comparator, NULL);
+    
+    // init connections list
+    LinkedList1_Init(&client->connections_list);
+    
+    // set zero connections
+    client->num_connections = 0;
+    
+    // init closing connections list
+    LinkedList1_Init(&client->closing_connections_list);
+    
+    // insert to clients list
+    LinkedList1_Append(&clients_list, &client->clients_list_node);
+    num_clients++;
+    
+    client_log(client, BLOG_INFO, "connected");
+    
+    return;
+    
+fail3:
+    PacketStreamSender_Free(&client->send_sender);
+    PacketProtoDecoder_Free(&client->recv_decoder);
+fail2:
+    PacketPassInterface_Free(&client->recv_if);
+    BReactor_RemoveTimer(&ss, &client->disconnect_timer);
+    BConnection_RecvAsync_Free(&client->con);
+    BConnection_SendAsync_Free(&client->con);
+    BConnection_Free(&client->con);
+fail1:
+    free(client);
+fail0:
+    return;
+}
+
+void client_free (struct client *client)
+{
+    // allow freeing send queue flows
+    PacketPassFairQueue_PrepareFree(&client->send_queue);
+    
+    // free connections
+    while (!LinkedList1_IsEmpty(&client->connections_list)) {
+        struct connection *con = UPPER_OBJECT(LinkedList1_GetFirst(&client->connections_list), struct connection, connections_list_node);
+        connection_free(con);
+    }
+    
+    // free closing connections
+    while (!LinkedList1_IsEmpty(&client->closing_connections_list)) {
+        struct connection *con = UPPER_OBJECT(LinkedList1_GetFirst(&client->closing_connections_list), struct connection, closing_connections_list_node);
+        connection_free(con);
+    }
+    
+    // remove from clients list
+    LinkedList1_Remove(&clients_list, &client->clients_list_node);
+    num_clients--;
+    
+    // free send queue
+    PacketPassFairQueue_Free(&client->send_queue);
+    
+    // free send sender
+    PacketStreamSender_Free(&client->send_sender);
+    
+    // free recv decoder
+    PacketProtoDecoder_Free(&client->recv_decoder);
+    
+    // free recv interface
+    PacketPassInterface_Free(&client->recv_if);
+    
+    // free disconnect timer
+    BReactor_RemoveTimer(&ss, &client->disconnect_timer);
+    
+    // free connection interfaces
+    BConnection_RecvAsync_Free(&client->con);
+    BConnection_SendAsync_Free(&client->con);
+    
+    // free connection
+    BConnection_Free(&client->con);
+    
+    // free structure
+    free(client);
+}
+
+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 client_decoder_handler_error (struct client *client)
+{
+    client_log(client, BLOG_ERROR, "decoder error");
+    
+    // free client
+    client_free(client);
+}
+
+void client_recv_if_handler_send (struct client *client, uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= udpgw_mtu)
+    
+    // accept packet
+    PacketPassInterface_Done(&client->recv_if);
+    
+    // parse header
+    if (data_len < sizeof(struct udpgw_header)) {
+        client_log(client, BLOG_ERROR, "missing header");
+        return;
+    }
+    struct udpgw_header header;
+    memcpy(&header, data, sizeof(header));
+    data += sizeof(header);
+    data_len -= sizeof(header);
+    uint8_t flags = ltoh8(header.flags);
+    uint16_t conid = ltoh16(header.conid);
+    
+    // reset disconnect timer
+    BReactor_SetTimer(&ss, &client->disconnect_timer);
+    
+    // if this is keepalive, ignore any payload
+    if ((flags & UDPGW_CLIENT_FLAG_KEEPALIVE)) {
+        client_log(client, BLOG_DEBUG, "received keepalive");
+        return;
+    }
+    
+    // parse address
+    BAddr orig_addr;
+    if ((flags & UDPGW_CLIENT_FLAG_IPV6)) {
+        if (data_len < sizeof(struct udpgw_addr_ipv6)) {
+            client_log(client, BLOG_ERROR, "missing ipv6 address");
+            return;
+        }
+        struct udpgw_addr_ipv6 addr_ipv6;
+        memcpy(&addr_ipv6, data, sizeof(addr_ipv6));
+        data += sizeof(addr_ipv6);
+        data_len -= sizeof(addr_ipv6);
+        BAddr_InitIPv6(&orig_addr, addr_ipv6.addr_ip, addr_ipv6.addr_port);
+    } else {
+        if (data_len < sizeof(struct udpgw_addr_ipv4)) {
+            client_log(client, BLOG_ERROR, "missing ipv4 address");
+            return;
+        }
+        struct udpgw_addr_ipv4 addr_ipv4;
+        memcpy(&addr_ipv4, data, sizeof(addr_ipv4));
+        data += sizeof(addr_ipv4);
+        data_len -= sizeof(addr_ipv4);
+        BAddr_InitIPv4(&orig_addr, addr_ipv4.addr_ip, addr_ipv4.addr_port);
+    }
+    
+    // check payload length
+    if (data_len > options.udp_mtu) {
+        client_log(client, BLOG_ERROR, "too much data");
+        return;
+    }
+    
+    // find connection
+    struct connection *con = find_connection(client, conid);
+    ASSERT(!con || !con->closing)
+    
+    // if connection exists, close it if needed
+    if (con && ((flags & UDPGW_CLIENT_FLAG_REBIND) || !BAddr_Compare(&con->orig_addr, &orig_addr))) {
+        connection_log(con, BLOG_DEBUG, "close old");
+        connection_close(con);
+        con = NULL;
+    }
+    
+    // if connection doesn't exists, create it
+    if (!con) {
+        // check number of connections
+        if (client->num_connections == options.max_connections_for_client) {
+            // close least recently used connection
+            con = UPPER_OBJECT(LinkedList1_GetFirst(&client->connections_list), struct connection, connections_list_node);
+            connection_close(con);
+        }
+        
+        // if this is DNS, replace actual address, but keep still remember the orig_addr
+        BAddr addr = orig_addr;
+        if ((flags & UDPGW_CLIENT_FLAG_DNS)) {
+            maybe_update_dns();
+            if (dns_addr.type == BADDR_TYPE_NONE) {
+                client_log(client, BLOG_WARNING, "received DNS packet, but no DNS server available");
+            } else {
+                client_log(client, BLOG_DEBUG, "received DNS");
+                addr = dns_addr;
+            }
+        }
+        
+        // create new connection
+        connection_init(client, conid, addr, orig_addr, data, data_len);
+    } else {
+        // submit packet to existing connection
+        connection_send_to_udp(con, data, data_len);
+    }
+}
+
+int get_local_num_ports (int addr_type)
+{
+    switch (addr_type) {
+        case BADDR_TYPE_IPV4: return options.local_udp_num_ports;
+        case BADDR_TYPE_IPV6: return options.local_udp_ip6_num_ports;
+        default: ASSERT(0); return 0;
+    }
+}
+
+BAddr get_local_addr (int addr_type)
+{
+    ASSERT(get_local_num_ports(addr_type) >= 0)
+    
+    switch (addr_type) {
+        case BADDR_TYPE_IPV4: return local_udp_addr;
+        case BADDR_TYPE_IPV6: return local_udp_ip6_addr;
+        default: ASSERT(0); return BAddr_MakeNone();
+    }
+}
+
+uint8_t * build_port_usage_array_and_find_least_used_connection (BAddr remote_addr, struct connection **out_con)
+{
+    ASSERT(remote_addr.type == BADDR_TYPE_IPV4 || remote_addr.type == BADDR_TYPE_IPV6)
+    ASSERT(get_local_num_ports(remote_addr.type) >= 0)
+    
+    int local_num_ports = get_local_num_ports(remote_addr.type);
+    
+    // allocate port usage array
+    uint8_t *port_usage = (uint8_t *)BAllocSize(bsize_fromint(local_num_ports));
+    if (!port_usage) {
+        return NULL;
+    }
+    
+    // zero array
+    memset(port_usage, 0, local_num_ports);
+    
+    struct connection *least_con = NULL;
+    
+    // flag inappropriate ports (those with the same remote address)
+    for (LinkedList1Node *ln = LinkedList1_GetFirst(&clients_list); ln; ln = LinkedList1Node_Next(ln)) {
+        struct client *client = UPPER_OBJECT(ln, struct client, clients_list_node);
+        
+        for (LinkedList1Node *ln2 = LinkedList1_GetFirst(&client->connections_list); ln2; ln2 = LinkedList1Node_Next(ln2)) {
+            struct connection *con = UPPER_OBJECT(ln2, struct connection, connections_list_node);
+            ASSERT(con->client == client)
+            ASSERT(!con->closing)
+            
+            if (con->addr.type != remote_addr.type || con->local_port_index < 0) {
+                continue;
+            }
+            ASSERT(con->local_port_index < local_num_ports)
+            
+            if (options.unique_local_ports) {
+                BIPAddr ip1;
+                BIPAddr ip2;
+                BAddr_GetIPAddr(&con->addr, &ip1);
+                BAddr_GetIPAddr(&remote_addr, &ip2);
+                if (!BIPAddr_Compare(&ip1, &ip2)) {
+                    continue;
+                }
+            } else {
+                if (!BAddr_Compare(&con->addr, &remote_addr)) {
+                    continue;
+                }
+            }
+            
+            port_usage[con->local_port_index] = 1;
+            
+            if (!PacketPassFairQueueFlow_IsBusy(&con->send_qflow)) {
+                if (!least_con || con->last_use_time < least_con->last_use_time) {
+                    least_con = con;
+                }
+            }
+        }
+    }
+    
+    *out_con = least_con;
+    return port_usage;
+}
+
+void connection_init (struct client *client, uint16_t conid, BAddr addr, BAddr orig_addr, const uint8_t *data, int data_len)
+{
+    ASSERT(client->num_connections < options.max_connections_for_client)
+    ASSERT(!find_connection(client, conid))
+    BAddr_Assert(&addr);
+    ASSERT(addr.type == BADDR_TYPE_IPV4 || addr.type == BADDR_TYPE_IPV6)
+    ASSERT(orig_addr.type == BADDR_TYPE_IPV4 || orig_addr.type == BADDR_TYPE_IPV6)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= options.udp_mtu)
+    
+    // allocate structure
+    struct connection *con = (struct connection *)malloc(sizeof(*con));
+    if (!con) {
+        client_log(client, BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // init arguments
+    con->client = client;
+    con->conid = conid;
+    con->addr = addr;
+    con->orig_addr = orig_addr;
+    con->first_data = data;
+    con->first_data_len = data_len;
+    
+    // set last use time
+    con->last_use_time = btime_gettime();
+    
+    // set not closing
+    con->closing = 0;
+    
+    // init first job
+    BPending_Init(&con->first_job, BReactor_PendingGroup(&ss), (BPending_handler)connection_first_job_handler, con);
+    BPending_Set(&con->first_job);
+    
+    // init send queue flow
+    PacketPassFairQueueFlow_Init(&con->send_qflow, &client->send_queue);
+    
+    // init send PacketProtoFlow
+    if (!PacketProtoFlow_Init(&con->send_ppflow, udpgw_mtu, CONNECTION_CLIENT_BUFFER_SIZE, PacketPassFairQueueFlow_GetInput(&con->send_qflow), BReactor_PendingGroup(&ss))) {
+        client_log(client, BLOG_ERROR, "PacketProtoFlow_Init failed");
+        goto fail1;
+    }
+    con->send_if = PacketProtoFlow_GetInput(&con->send_ppflow);
+    
+    // init UDP dgram
+    if (!BDatagram_Init(&con->udp_dgram, addr.type, &ss, con, (BDatagram_handler)connection_dgram_handler_event)) {
+        client_log(client, BLOG_ERROR, "BDatagram_Init failed");
+        goto fail2;
+    }
+    
+    con->local_port_index = -1;
+    
+    int local_num_ports = get_local_num_ports(addr.type);
+    
+    if (local_num_ports >= 0) {
+        // build port usage array, find least used connection
+        struct connection *least_con;
+        uint8_t *port_usage = build_port_usage_array_and_find_least_used_connection(addr, &least_con);
+        if (!port_usage) {
+            client_log(client, BLOG_ERROR, "build_port_usage_array failed");
+            goto failed;
+        }
+        
+        // set SO_REUSEADDR
+        if (!BDatagram_SetReuseAddr(&con->udp_dgram, 1)) {
+            client_log(client, BLOG_ERROR, "set SO_REUSEADDR failed");
+            goto failed;
+        }
+        
+        // get starting local address
+        BAddr local_addr = get_local_addr(addr.type);
+        
+        // try different ports
+        for (int i = 0; i < local_num_ports; i++) {
+            // skip inappropriate ports
+            if (port_usage[i]) {
+                continue;
+            }
+            
+            BAddr bind_addr = local_addr;
+            BAddr_SetPort(&bind_addr, hton16(ntoh16(BAddr_GetPort(&bind_addr)) + (uint16_t)i));
+            if (BDatagram_Bind(&con->udp_dgram, bind_addr)) {
+                // remember which port we're using
+                con->local_port_index = i;
+                goto cont;
+            }
+        }
+        
+        // try closing an unused connection with the same remote addr
+        if (!least_con) {
+            goto failed;
+        }
+        
+        ASSERT(least_con->addr.type == addr.type)
+        ASSERT(least_con->local_port_index >= 0)
+        ASSERT(least_con->local_port_index < local_num_ports)
+        ASSERT(!PacketPassFairQueueFlow_IsBusy(&least_con->send_qflow))
+        
+        int i = least_con->local_port_index;
+        
+        BLog(BLOG_INFO, "closing connection for its remote address");
+        
+        // close the offending connection
+        connection_close(least_con);
+        
+        // try binding to its port
+        BAddr bind_addr = local_addr;
+        BAddr_SetPort(&bind_addr, hton16(ntoh16(BAddr_GetPort(&bind_addr)) + (uint16_t)i));
+        if (BDatagram_Bind(&con->udp_dgram, bind_addr)) {
+            // remember which port we're using
+            con->local_port_index = i;
+            goto cont;
+        }
+        
+    failed:
+        client_log(client, BLOG_WARNING, "failed to bind to any local address; proceeding regardless");
+    cont:;
+        BFree(port_usage);
+    }
+    
+    // set UDP dgram send address
+    BIPAddr ipaddr;
+    BIPAddr_InitInvalid(&ipaddr);
+    BDatagram_SetSendAddrs(&con->udp_dgram, addr, ipaddr);
+    
+    // init UDP dgram interfaces
+    BDatagram_SendAsync_Init(&con->udp_dgram, options.udp_mtu);
+    BDatagram_RecvAsync_Init(&con->udp_dgram, options.udp_mtu);
+    
+    // init UDP writer
+    BufferWriter_Init(&con->udp_send_writer, options.udp_mtu, BReactor_PendingGroup(&ss));
+    
+    // init UDP buffer
+    if (!PacketBuffer_Init(&con->udp_send_buffer, BufferWriter_GetOutput(&con->udp_send_writer), BDatagram_SendAsync_GetIf(&con->udp_dgram), CONNECTION_UDP_BUFFER_SIZE, BReactor_PendingGroup(&ss))) {
+        client_log(client, BLOG_ERROR, "PacketBuffer_Init failed");
+        goto fail4;
+    }
+    
+    // init UDP recv interface
+    PacketPassInterface_Init(&con->udp_recv_if, options.udp_mtu, (PacketPassInterface_handler_send)connection_udp_recv_if_handler_send, con, BReactor_PendingGroup(&ss));
+    
+    // init UDP recv buffer
+    if (!SinglePacketBuffer_Init(&con->udp_recv_buffer, BDatagram_RecvAsync_GetIf(&con->udp_dgram), &con->udp_recv_if, BReactor_PendingGroup(&ss))) {
+        client_log(client, BLOG_ERROR, "SinglePacketBuffer_Init failed");
+        goto fail5;
+    }
+    
+    // insert to client's connections tree
+    ASSERT_EXECUTE(BAVL_Insert(&client->connections_tree, &con->connections_tree_node, NULL))
+    
+    // insert to client's connections list
+    LinkedList1_Append(&client->connections_list, &con->connections_list_node);
+    
+    // increment number of connections
+    client->num_connections++;
+    
+    connection_log(con, BLOG_DEBUG, "initialized");
+    
+    return;
+    
+fail5:
+    PacketPassInterface_Free(&con->udp_recv_if);
+    PacketBuffer_Free(&con->udp_send_buffer);
+fail4:
+    BufferWriter_Free(&con->udp_send_writer);
+    BDatagram_RecvAsync_Free(&con->udp_dgram);
+    BDatagram_SendAsync_Free(&con->udp_dgram);
+    BDatagram_Free(&con->udp_dgram);
+fail2:
+    PacketProtoFlow_Free(&con->send_ppflow);
+fail1:
+    PacketPassFairQueueFlow_Free(&con->send_qflow);
+    BPending_Free(&con->first_job);
+    free(con);
+fail0:
+    return;
+}
+
+void connection_free (struct connection *con)
+{
+    struct client *client = con->client;
+    PacketPassFairQueueFlow_AssertFree(&con->send_qflow);
+    
+    if (con->closing) {
+        // remove from client's closing connections list
+        LinkedList1_Remove(&client->closing_connections_list, &con->closing_connections_list_node);
+    } else {
+        // decrement number of connections
+        client->num_connections--;
+        
+        // remove from client's connections list
+        LinkedList1_Remove(&client->connections_list, &con->connections_list_node);
+        
+        // remove from client's connections tree
+        BAVL_Remove(&client->connections_tree, &con->connections_tree_node);
+        
+        // free UDP
+        connection_free_udp(con);
+    }
+    
+    // free send PacketProtoFlow
+    PacketProtoFlow_Free(&con->send_ppflow);
+    
+    // free send queue flow
+    PacketPassFairQueueFlow_Free(&con->send_qflow);
+    
+    // free first job
+    BPending_Free(&con->first_job);
+    
+    // free structure
+    free(con);
+}
+
+void connection_logfunc (struct connection *con)
+{
+    client_logfunc(con->client);
+    
+    if (con->closing) {
+        BLog_Append("old connection %"PRIu16": ", con->conid);
+    } else {
+        BLog_Append("connection %"PRIu16": ", con->conid);
+    }
+}
+
+void connection_log (struct connection *con, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_LogViaFuncVarArg((BLog_logfunc)connection_logfunc, con, BLOG_CURRENT_CHANNEL, level, fmt, vl);
+    va_end(vl);
+}
+
+void connection_free_udp (struct connection *con)
+{
+    // free UDP receive buffer
+    SinglePacketBuffer_Free(&con->udp_recv_buffer);
+    
+    // free UDP receive interface
+    PacketPassInterface_Free(&con->udp_recv_if);
+    
+    // free UDP buffer
+    PacketBuffer_Free(&con->udp_send_buffer);
+    
+    // free UDP writer
+    BufferWriter_Free(&con->udp_send_writer);
+    
+    // free UDP dgram interfaces
+    BDatagram_RecvAsync_Free(&con->udp_dgram);
+    BDatagram_SendAsync_Free(&con->udp_dgram);
+    
+    // free UDP dgram
+    BDatagram_Free(&con->udp_dgram);
+}
+
+void connection_first_job_handler (struct connection *con)
+{
+    ASSERT(!con->closing)
+    
+    connection_send_to_udp(con, con->first_data, con->first_data_len);
+}
+
+void connection_send_to_client (struct connection *con, uint8_t flags, const uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= options.udp_mtu)
+    
+    size_t addr_len = (con->orig_addr.type == BADDR_TYPE_IPV6) ? sizeof(struct udpgw_addr_ipv6) :
+                      (con->orig_addr.type == BADDR_TYPE_IPV4) ? sizeof(struct udpgw_addr_ipv4) : 0;
+    if (data_len > udpgw_mtu - (int)(sizeof(struct udpgw_header) + addr_len)) {
+        connection_log(con, BLOG_WARNING, "packet is too large, cannot send to client");
+        return;
+    }
+    
+    // get buffer location
+    uint8_t *out;
+    if (!BufferWriter_StartPacket(con->send_if, &out)) {
+        connection_log(con, BLOG_ERROR, "out of client buffer");
+        return;
+    }
+    int out_pos = 0;
+    
+    if (con->orig_addr.type == BADDR_TYPE_IPV6) {
+        flags |= UDPGW_CLIENT_FLAG_IPV6;
+    }
+    
+    // write header
+    struct udpgw_header header;
+    header.flags = htol8(flags);
+    header.conid = htol16(con->conid);
+    memcpy(out + out_pos, &header, sizeof(header));
+    out_pos += sizeof(header);
+    
+    // write address
+    switch (con->orig_addr.type) {
+        case BADDR_TYPE_IPV4: {
+            struct udpgw_addr_ipv4 addr_ipv4;
+            addr_ipv4.addr_ip = con->orig_addr.ipv4.ip;
+            addr_ipv4.addr_port = con->orig_addr.ipv4.port;
+            memcpy(out + out_pos, &addr_ipv4, sizeof(addr_ipv4));
+            out_pos += sizeof(addr_ipv4);
+        } break;
+        case BADDR_TYPE_IPV6: {
+            struct udpgw_addr_ipv6 addr_ipv6;
+            memcpy(addr_ipv6.addr_ip, con->orig_addr.ipv6.ip, sizeof(addr_ipv6.addr_ip));
+            addr_ipv6.addr_port = con->orig_addr.ipv6.port;
+            memcpy(out + out_pos, &addr_ipv6, sizeof(addr_ipv6));
+            out_pos += sizeof(addr_ipv6);
+        } break;
+    }
+    
+    // write message
+    memcpy(out + out_pos, data, data_len);
+    out_pos += data_len;
+    
+    // submit written message
+    ASSERT(out_pos <= udpgw_mtu)
+    BufferWriter_EndPacket(con->send_if, out_pos);
+}
+
+int connection_send_to_udp (struct connection *con, const uint8_t *data, int data_len)
+{
+    struct client *client = con->client;
+    ASSERT(!con->closing)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= options.udp_mtu)
+    
+    connection_log(con, BLOG_DEBUG, "from client %d bytes", data_len);
+    
+    // set last use time
+    con->last_use_time = btime_gettime();
+    
+    // move connection to front
+    LinkedList1_Remove(&client->connections_list, &con->connections_list_node);
+    LinkedList1_Append(&client->connections_list, &con->connections_list_node);
+    
+    // get buffer location
+    uint8_t *out;
+    if (!BufferWriter_StartPacket(&con->udp_send_writer, &out)) {
+        connection_log(con, BLOG_ERROR, "out of UDP buffer");
+        return 0;
+    }
+    
+    // write message
+    memcpy(out, data, data_len);
+    
+    // submit written message
+    BufferWriter_EndPacket(&con->udp_send_writer, data_len);
+    
+    return 1;
+}
+
+void connection_close (struct connection *con)
+{
+    struct client *client = con->client;
+    ASSERT(!con->closing)
+    
+    // if possible, free connection immediately
+    if (!PacketPassFairQueueFlow_IsBusy(&con->send_qflow)) {
+        connection_free(con);
+        return;
+    }
+    
+    connection_log(con, BLOG_DEBUG, "closing later");
+    
+    // decrement number of connections
+    client->num_connections--;
+    
+    // remove from client's connections list
+    LinkedList1_Remove(&client->connections_list, &con->connections_list_node);
+    
+    // remove from client's connections tree
+    BAVL_Remove(&client->connections_tree, &con->connections_tree_node);
+    
+    // free UDP
+    connection_free_udp(con);
+    
+    // insert to client's closing connections list
+    LinkedList1_Append(&client->closing_connections_list, &con->closing_connections_list_node);
+    
+    // set busy handler
+    PacketPassFairQueueFlow_SetBusyHandler(&con->send_qflow, (PacketPassFairQueue_handler_busy)connection_send_qflow_busy_handler, con);
+    
+    // unset first job
+    BPending_Unset(&con->first_job);
+    
+    // set closing
+    con->closing = 1;
+}
+
+void connection_send_qflow_busy_handler (struct connection *con)
+{
+    ASSERT(con->closing)
+    PacketPassFairQueueFlow_AssertFree(&con->send_qflow);
+    
+    connection_log(con, BLOG_DEBUG, "closing finally");
+    
+    // free connection
+    connection_free(con);
+}
+
+void connection_dgram_handler_event (struct connection *con, int event)
+{
+    ASSERT(!con->closing)
+    
+    connection_log(con, BLOG_INFO, "UDP error");
+    
+    // close connection
+    connection_close(con);
+}
+
+void connection_udp_recv_if_handler_send (struct connection *con, uint8_t *data, int data_len)
+{
+    struct client *client = con->client;
+    ASSERT(!con->closing)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= options.udp_mtu)
+    
+    connection_log(con, BLOG_DEBUG, "from UDP %d bytes", data_len);
+    
+    // set last use time
+    con->last_use_time = btime_gettime();
+    
+    // move connection to front
+    LinkedList1_Remove(&client->connections_list, &con->connections_list_node);
+    LinkedList1_Append(&client->connections_list, &con->connections_list_node);
+    
+    // accept packet
+    PacketPassInterface_Done(&con->udp_recv_if);
+    
+    // send packet to client
+    connection_send_to_client(con, 0, data, data_len);
+}
+
+struct connection * find_connection (struct client *client, uint16_t conid)
+{
+    BAVLNode *tree_node = BAVL_LookupExact(&client->connections_tree, &conid);
+    if (!tree_node) {
+        return NULL;
+    }
+    struct connection *con = UPPER_OBJECT(tree_node, struct connection, connections_tree_node);
+    ASSERT(con->conid == conid)
+    ASSERT(!con->closing)
+    
+    return con;
+}
+
+int uint16_comparator (void *unused, uint16_t *v1, uint16_t *v2)
+{
+    return B_COMPARE(*v1, *v2);
+}
+
+void maybe_update_dns (void)
+{
+#ifndef BADVPN_USE_WINAPI
+    btime_t now = btime_gettime();
+    if (now < btime_add(last_dns_update_time, DNS_UPDATE_TIME)) {
+        return;
+    }
+    last_dns_update_time = now;
+    BLog(BLOG_DEBUG, "update dns");
+    
+    if (res_init() != 0) {
+        BLog(BLOG_ERROR, "res_init failed");
+        goto fail;
+    }
+    
+    if (_res.nscount == 0) {
+        BLog(BLOG_ERROR, "no name servers available");
+        goto fail;
+    }
+    
+    BAddr addr;
+    BAddr_InitIPv4(&addr, _res.nsaddr_list[0].sin_addr.s_addr, hton16(53));
+    
+    if (!BAddr_Compare(&addr, &dns_addr)) {
+        char str[BADDR_MAX_PRINT_LEN];
+        BAddr_Print(&addr, str);
+        BLog(BLOG_INFO, "using DNS server %s", str);
+    }
+    
+    dns_addr = addr;
+    return;
+    
+fail:
+    BAddr_InitNone(&dns_addr);
+#endif
+}
diff --git a/external/badvpn_dns/udpgw/udpgw.h b/external/badvpn_dns/udpgw/udpgw.h
new file mode 100644
index 0000000..f63f857
--- /dev/null
+++ b/external/badvpn_dns/udpgw/udpgw.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * 
+ * 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 "udpgw"
+
+// maxiumum listen addresses
+#define MAX_LISTEN_ADDRS 16
+
+// maximum datagram size
+#define DEFAULT_UDP_MTU 65520
+
+// connection buffer size for sending to client, in packets
+#define CONNECTION_CLIENT_BUFFER_SIZE 1
+
+// connection buffer size for sending to UDP, in packets
+#define CONNECTION_UDP_BUFFER_SIZE 1
+
+// maximum number of clients
+#define DEFAULT_MAX_CLIENTS 3
+
+// maximum connections for client
+#define DEFAULT_MAX_CONNECTIONS_FOR_CLIENT 256
+
+// how long after nothing has been received to disconnect a client
+#define CLIENT_DISCONNECT_TIMEOUT 20000
+
+// SO_SNDBFUF socket option for clients, 0 to not set
+#define CLIENT_DEFAULT_SOCKET_SEND_BUFFER 1048576
diff --git a/external/badvpn_dns/udpgw_client/CMakeLists.txt b/external/badvpn_dns/udpgw_client/CMakeLists.txt
new file mode 100644
index 0000000..59ac636
--- /dev/null
+++ b/external/badvpn_dns/udpgw_client/CMakeLists.txt
@@ -0,0 +1 @@
+badvpn_add_library(udpgw_client "system;flow;flowextra" "" UdpGwClient.c)
diff --git a/external/badvpn_dns/udpgw_client/UdpGwClient.c b/external/badvpn_dns/udpgw_client/UdpGwClient.c
new file mode 100644
index 0000000..85d069a
--- /dev/null
+++ b/external/badvpn_dns/udpgw_client/UdpGwClient.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * Contributions:
+ * Transparent DNS: Copyright (C) Kerem Hadimli <kerem.hadimli@xxxxxxxxx>
+ * 
+ * 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/compare.h>
+#include <base/BLog.h>
+
+#include <udpgw_client/UdpGwClient.h>
+
+#include <generated/blog_channel_UdpGwClient.h>
+
+static int uint16_comparator (void *unused, uint16_t *v1, uint16_t *v2);
+static int conaddr_comparator (void *unused, struct UdpGwClient_conaddr *v1, struct UdpGwClient_conaddr *v2);
+static void free_server (UdpGwClient *o);
+static void decoder_handler_error (UdpGwClient *o);
+static void recv_interface_handler_send (UdpGwClient *o, uint8_t *data, int data_len);
+static void send_monitor_handler (UdpGwClient *o);
+static void keepalive_if_handler_done (UdpGwClient *o);
+static struct UdpGwClient_connection * find_connection_by_conaddr (UdpGwClient *o, struct UdpGwClient_conaddr conaddr);
+static struct UdpGwClient_connection * find_connection_by_conid (UdpGwClient *o, uint16_t conid);
+static uint16_t find_unused_conid (UdpGwClient *o);
+static void connection_init (UdpGwClient *o, struct UdpGwClient_conaddr conaddr, uint8_t flags, const uint8_t *data, int data_len);
+static void connection_free (struct UdpGwClient_connection *con);
+static void connection_first_job_handler (struct UdpGwClient_connection *con);
+static void connection_send (struct UdpGwClient_connection *con, uint8_t flags, const uint8_t *data, int data_len);
+static struct UdpGwClient_connection * reuse_connection (UdpGwClient *o, struct UdpGwClient_conaddr conaddr);
+
+static int uint16_comparator (void *unused, uint16_t *v1, uint16_t *v2)
+{
+    return B_COMPARE(*v1, *v2);
+}
+
+static int conaddr_comparator (void *unused, struct UdpGwClient_conaddr *v1, struct UdpGwClient_conaddr *v2)
+{
+    int r = BAddr_CompareOrder(&v1->remote_addr, &v2->remote_addr);
+    if (r) {
+        return r;
+    }
+    return BAddr_CompareOrder(&v1->local_addr, &v2->local_addr);
+}
+
+static void free_server (UdpGwClient *o)
+{
+    // disconnect send connector
+    PacketPassConnector_DisconnectOutput(&o->send_connector);
+    
+    // free send sender
+    PacketStreamSender_Free(&o->send_sender);
+    
+    // free receive decoder
+    PacketProtoDecoder_Free(&o->recv_decoder);
+    
+    // free receive interface
+    PacketPassInterface_Free(&o->recv_if);
+}
+
+static void decoder_handler_error (UdpGwClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_server)
+    
+    BLog(BLOG_ERROR, "decoder error");
+    
+    // report error
+    o->handler_servererror(o->user);
+    return;
+}
+
+static void recv_interface_handler_send (UdpGwClient *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_server)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->udpgw_mtu)
+    
+    // accept packet
+    PacketPassInterface_Done(&o->recv_if);
+    
+    // check header
+    if (data_len < sizeof(struct udpgw_header)) {
+        BLog(BLOG_ERROR, "missing header");
+        return;
+    }
+    struct udpgw_header header;
+    memcpy(&header, data, sizeof(header));
+    data += sizeof(header);
+    data_len -= sizeof(header);
+    uint8_t flags = ltoh8(header.flags);
+    uint16_t conid = ltoh16(header.conid);
+    
+    // parse address
+    BAddr remote_addr;
+    if ((flags & UDPGW_CLIENT_FLAG_IPV6)) {
+        if (data_len < sizeof(struct udpgw_addr_ipv6)) {
+            BLog(BLOG_ERROR, "missing ipv6 address");
+            return;
+        }
+        struct udpgw_addr_ipv6 addr_ipv6;
+        memcpy(&addr_ipv6, data, sizeof(addr_ipv6));
+        data += sizeof(addr_ipv6);
+        data_len -= sizeof(addr_ipv6);
+        BAddr_InitIPv6(&remote_addr, addr_ipv6.addr_ip, addr_ipv6.addr_port);
+    } else {
+        if (data_len < sizeof(struct udpgw_addr_ipv4)) {
+            BLog(BLOG_ERROR, "missing ipv4 address");
+            return;
+        }
+        struct udpgw_addr_ipv4 addr_ipv4;
+        memcpy(&addr_ipv4, data, sizeof(addr_ipv4));
+        data += sizeof(addr_ipv4);
+        data_len -= sizeof(addr_ipv4);
+        BAddr_InitIPv4(&remote_addr, addr_ipv4.addr_ip, addr_ipv4.addr_port);
+    }
+    
+    // check remaining data
+    if (data_len > o->udp_mtu) {
+        BLog(BLOG_ERROR, "too much data");
+        return;
+    }
+    
+    // find connection
+    struct UdpGwClient_connection *con = find_connection_by_conid(o, conid);
+    if (!con) {
+        BLog(BLOG_ERROR, "unknown conid");
+        return;
+    }
+    
+    // check remote address
+    if (BAddr_CompareOrder(&con->conaddr.remote_addr, &remote_addr) != 0) {
+        BLog(BLOG_ERROR, "wrong remote address");
+        return;
+    }
+    
+    // move connection to front of the list
+    LinkedList1_Remove(&o->connections_list, &con->connections_list_node);
+    LinkedList1_Append(&o->connections_list, &con->connections_list_node);
+    
+    // pass packet to user
+    o->handler_received(o->user, con->conaddr.local_addr, con->conaddr.remote_addr, data, data_len);
+    return;
+}
+
+static void send_monitor_handler (UdpGwClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    if (o->keepalive_sending) {
+        return;
+    }
+    
+    BLog(BLOG_INFO, "keepalive");
+    
+    // send keepalive
+    PacketPassInterface_Sender_Send(o->keepalive_if, (uint8_t *)&o->keepalive_packet, sizeof(o->keepalive_packet));
+    
+    // set sending keep-alive
+    o->keepalive_sending = 1;
+}
+
+static void keepalive_if_handler_done (UdpGwClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->keepalive_sending)
+    
+    // set not sending keepalive
+    o->keepalive_sending = 0;
+}
+
+static struct UdpGwClient_connection * find_connection_by_conaddr (UdpGwClient *o, struct UdpGwClient_conaddr conaddr)
+{
+    BAVLNode *tree_node = BAVL_LookupExact(&o->connections_tree_by_conaddr, &conaddr);
+    if (!tree_node) {
+        return NULL;
+    }
+    
+    return UPPER_OBJECT(tree_node, struct UdpGwClient_connection, connections_tree_by_conaddr_node);
+}
+
+static struct UdpGwClient_connection * find_connection_by_conid (UdpGwClient *o, uint16_t conid)
+{
+    BAVLNode *tree_node = BAVL_LookupExact(&o->connections_tree_by_conid, &conid);
+    if (!tree_node) {
+        return NULL;
+    }
+    
+    return UPPER_OBJECT(tree_node, struct UdpGwClient_connection, connections_tree_by_conid_node);
+}
+
+static uint16_t find_unused_conid (UdpGwClient *o)
+{
+    ASSERT(o->num_connections < o->max_connections)
+    
+    while (1) {
+        if (!find_connection_by_conid(o, o->next_conid)) {
+            return o->next_conid;
+        }
+        
+        if (o->next_conid == o->max_connections - 1) {
+            o->next_conid = 0;
+        } else {
+            o->next_conid++;
+        }
+    }
+}
+
+static void connection_init (UdpGwClient *o, struct UdpGwClient_conaddr conaddr, uint8_t flags, const uint8_t *data, int data_len)
+{
+    ASSERT(o->num_connections < o->max_connections)
+    ASSERT(!find_connection_by_conaddr(o, conaddr))
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->udp_mtu)
+    
+    // allocate structure
+    struct UdpGwClient_connection *con = (struct UdpGwClient_connection *)malloc(sizeof(*con));
+    if (!con) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // init arguments
+    con->client = o;
+    con->conaddr = conaddr;
+    con->first_flags = flags;
+    con->first_data = data;
+    con->first_data_len = data_len;
+    
+    // allocate conid
+    con->conid = find_unused_conid(o);
+    
+    // init first job
+    BPending_Init(&con->first_job, BReactor_PendingGroup(o->reactor), (BPending_handler)connection_first_job_handler, con);
+    BPending_Set(&con->first_job);
+    
+    // init queue flow
+    PacketPassFairQueueFlow_Init(&con->send_qflow, &o->send_queue);
+    
+    // init PacketProtoFlow
+    if (!PacketProtoFlow_Init(&con->send_ppflow, o->udpgw_mtu, o->send_buffer_size, PacketPassFairQueueFlow_GetInput(&con->send_qflow), BReactor_PendingGroup(o->reactor))) {
+        BLog(BLOG_ERROR, "PacketProtoFlow_Init failed");
+        goto fail1;
+    }
+    con->send_if = PacketProtoFlow_GetInput(&con->send_ppflow);
+    
+    // insert to connections tree by conaddr
+    ASSERT_EXECUTE(BAVL_Insert(&o->connections_tree_by_conaddr, &con->connections_tree_by_conaddr_node, NULL))
+    
+    // insert to connections tree by conid
+    ASSERT_EXECUTE(BAVL_Insert(&o->connections_tree_by_conid, &con->connections_tree_by_conid_node, NULL))
+    
+    // insert to connections list
+    LinkedList1_Append(&o->connections_list, &con->connections_list_node);
+    
+    // increment number of connections
+    o->num_connections++;
+    
+    return;
+    
+fail1:
+    PacketPassFairQueueFlow_Free(&con->send_qflow);
+    BPending_Free(&con->first_job);
+    free(con);
+fail0:
+    return;
+}
+
+static void connection_free (struct UdpGwClient_connection *con)
+{
+    UdpGwClient *o = con->client;
+    PacketPassFairQueueFlow_AssertFree(&con->send_qflow);
+    
+    // decrement number of connections
+    o->num_connections--;
+    
+    // remove from connections list
+    LinkedList1_Remove(&o->connections_list, &con->connections_list_node);
+    
+    // remove from connections tree by conid
+    BAVL_Remove(&o->connections_tree_by_conid, &con->connections_tree_by_conid_node);
+    
+    // remove from connections tree by conaddr
+    BAVL_Remove(&o->connections_tree_by_conaddr, &con->connections_tree_by_conaddr_node);
+    
+    // free PacketProtoFlow
+    PacketProtoFlow_Free(&con->send_ppflow);
+    
+    // free queue flow
+    PacketPassFairQueueFlow_Free(&con->send_qflow);
+    
+    // free first job
+    BPending_Free(&con->first_job);
+    
+    // free structure
+    free(con);
+}
+
+static void connection_first_job_handler (struct UdpGwClient_connection *con)
+{
+    connection_send(con, UDPGW_CLIENT_FLAG_REBIND|con->first_flags, con->first_data, con->first_data_len);
+}
+
+static void connection_send (struct UdpGwClient_connection *con, uint8_t flags, const uint8_t *data, int data_len)
+{
+    UdpGwClient *o = con->client;
+    B_USE(o)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->udp_mtu)
+    
+    // get buffer location
+    uint8_t *out;
+    if (!BufferWriter_StartPacket(con->send_if, &out)) {
+        BLog(BLOG_ERROR, "out of buffer");
+        return;
+    }
+    int out_pos = 0;
+    
+    if (con->conaddr.remote_addr.type == BADDR_TYPE_IPV6) {
+        flags |= UDPGW_CLIENT_FLAG_IPV6;
+    }
+    
+    // write header
+    struct udpgw_header header;
+    header.flags = ltoh8(flags);
+    header.conid = ltoh16(con->conid);
+    memcpy(out + out_pos, &header, sizeof(header));
+    out_pos += sizeof(header);
+    
+    // write address
+    switch (con->conaddr.remote_addr.type) {
+        case BADDR_TYPE_IPV4: {
+            struct udpgw_addr_ipv4 addr_ipv4;
+            addr_ipv4.addr_ip = con->conaddr.remote_addr.ipv4.ip;
+            addr_ipv4.addr_port = con->conaddr.remote_addr.ipv4.port;
+            memcpy(out + out_pos, &addr_ipv4, sizeof(addr_ipv4));
+            out_pos += sizeof(addr_ipv4);
+        } break;
+        case BADDR_TYPE_IPV6: {
+            struct udpgw_addr_ipv6 addr_ipv6;
+            memcpy(addr_ipv6.addr_ip, con->conaddr.remote_addr.ipv6.ip, sizeof(addr_ipv6.addr_ip));
+            addr_ipv6.addr_port = con->conaddr.remote_addr.ipv6.port;
+            memcpy(out + out_pos, &addr_ipv6, sizeof(addr_ipv6));
+            out_pos += sizeof(addr_ipv6);
+        } break;
+    }
+    
+    // write packet to buffer
+    memcpy(out + out_pos, data, data_len);
+    out_pos += data_len;
+    
+    // submit packet to buffer
+    BufferWriter_EndPacket(con->send_if, out_pos);
+}
+
+static struct UdpGwClient_connection * reuse_connection (UdpGwClient *o, struct UdpGwClient_conaddr conaddr)
+{
+    ASSERT(!find_connection_by_conaddr(o, conaddr))
+    ASSERT(o->num_connections > 0)
+    
+    // get least recently used connection
+    struct UdpGwClient_connection *con = UPPER_OBJECT(LinkedList1_GetFirst(&o->connections_list), struct UdpGwClient_connection, connections_list_node);
+    
+    // remove from connections tree by conaddr
+    BAVL_Remove(&o->connections_tree_by_conaddr, &con->connections_tree_by_conaddr_node);
+    
+    // set new conaddr
+    con->conaddr = conaddr;
+    
+    // insert to connections tree by conaddr
+    ASSERT_EXECUTE(BAVL_Insert(&o->connections_tree_by_conaddr, &con->connections_tree_by_conaddr_node, NULL))
+    
+    return con;
+}
+
+int UdpGwClient_Init (UdpGwClient *o, int udp_mtu, int max_connections, int send_buffer_size, btime_t keepalive_time, BReactor *reactor, void *user,
+                      UdpGwClient_handler_servererror handler_servererror,
+                      UdpGwClient_handler_received handler_received)
+{
+    ASSERT(udp_mtu >= 0)
+    ASSERT(udpgw_compute_mtu(udp_mtu) >= 0)
+    ASSERT(udpgw_compute_mtu(udp_mtu) <= PACKETPROTO_MAXPAYLOAD)
+    ASSERT(max_connections > 0)
+    ASSERT(send_buffer_size > 0)
+    
+    // init arguments
+    o->udp_mtu = udp_mtu;
+    o->max_connections = max_connections;
+    o->send_buffer_size = send_buffer_size;
+    o->keepalive_time = keepalive_time;
+    o->reactor = reactor;
+    o->user = user;
+    o->handler_servererror = handler_servererror;
+    o->handler_received = handler_received;
+    
+    // limit max connections to number of conid's
+    if (o->max_connections > UINT16_MAX + 1) {
+        o->max_connections = UINT16_MAX + 1;
+    }
+    
+    // compute MTUs
+    o->udpgw_mtu = udpgw_compute_mtu(o->udp_mtu);
+    o->pp_mtu = o->udpgw_mtu + sizeof(struct packetproto_header);
+    
+    // init connections tree by conaddr
+    BAVL_Init(&o->connections_tree_by_conaddr, OFFSET_DIFF(struct UdpGwClient_connection, conaddr, connections_tree_by_conaddr_node), (BAVL_comparator)conaddr_comparator, NULL);
+    
+    // init connections tree by conid
+    BAVL_Init(&o->connections_tree_by_conid, OFFSET_DIFF(struct UdpGwClient_connection, conid, connections_tree_by_conid_node), (BAVL_comparator)uint16_comparator, NULL);
+    
+    // init connections list
+    LinkedList1_Init(&o->connections_list);
+    
+    // set zero connections
+    o->num_connections = 0;
+    
+    // set next conid
+    o->next_conid = 0;
+    
+    // init send connector
+    PacketPassConnector_Init(&o->send_connector, o->pp_mtu, BReactor_PendingGroup(o->reactor));
+    
+    // init send monitor
+    PacketPassInactivityMonitor_Init(&o->send_monitor, PacketPassConnector_GetInput(&o->send_connector), o->reactor, o->keepalive_time, (PacketPassInactivityMonitor_handler)send_monitor_handler, o);
+    
+    // init send queue
+    if (!PacketPassFairQueue_Init(&o->send_queue, PacketPassInactivityMonitor_GetInput(&o->send_monitor), BReactor_PendingGroup(o->reactor), 0, 1)) {
+        goto fail0;
+    }
+    
+    // construct keepalive packet
+    o->keepalive_packet.pp.len = sizeof(o->keepalive_packet.udpgw);
+    memset(&o->keepalive_packet.udpgw, 0, sizeof(o->keepalive_packet.udpgw));
+    o->keepalive_packet.udpgw.flags = UDPGW_CLIENT_FLAG_KEEPALIVE;
+    
+    // init keepalive queue flow
+    PacketPassFairQueueFlow_Init(&o->keepalive_qflow, &o->send_queue);
+    o->keepalive_if = PacketPassFairQueueFlow_GetInput(&o->keepalive_qflow);
+    
+    // init keepalive output
+    PacketPassInterface_Sender_Init(o->keepalive_if, (PacketPassInterface_handler_done)keepalive_if_handler_done, o);
+    
+    // set not sending keepalive
+    o->keepalive_sending = 0;
+    
+    // set have no server
+    o->have_server = 0;
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail0:
+    PacketPassInactivityMonitor_Free(&o->send_monitor);
+    PacketPassConnector_Free(&o->send_connector);
+    return 0;
+}
+
+void UdpGwClient_Free (UdpGwClient *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // allow freeing send queue flows
+    PacketPassFairQueue_PrepareFree(&o->send_queue);
+    
+    // free connections
+    while (!LinkedList1_IsEmpty(&o->connections_list)) {
+        struct UdpGwClient_connection *con = UPPER_OBJECT(LinkedList1_GetFirst(&o->connections_list), struct UdpGwClient_connection, connections_list_node);
+        connection_free(con);
+    }
+    
+    // free server
+    if (o->have_server) {
+        free_server(o);
+    }
+    
+    // free keepalive queue flow
+    PacketPassFairQueueFlow_Free(&o->keepalive_qflow);
+    
+    // free send queue
+    PacketPassFairQueue_Free(&o->send_queue);
+    
+    // free send
+    PacketPassInactivityMonitor_Free(&o->send_monitor);
+    
+    // free send connector
+    PacketPassConnector_Free(&o->send_connector);
+}
+
+void UdpGwClient_SubmitPacket (UdpGwClient *o, BAddr local_addr, BAddr remote_addr, int is_dns, const uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(local_addr.type == BADDR_TYPE_IPV4 || local_addr.type == BADDR_TYPE_IPV6)
+    ASSERT(remote_addr.type == BADDR_TYPE_IPV4 || remote_addr.type == BADDR_TYPE_IPV6)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->udp_mtu)
+    
+    // build conaddr
+    struct UdpGwClient_conaddr conaddr;
+    conaddr.local_addr = local_addr;
+    conaddr.remote_addr = remote_addr;
+    
+    // lookup connection
+    struct UdpGwClient_connection *con = find_connection_by_conaddr(o, conaddr);
+    
+    uint8_t flags = 0;
+
+    if (is_dns) {
+        // route to remote DNS server instead of provided address
+        flags |= UDPGW_CLIENT_FLAG_DNS;
+    }
+    
+    // if no connection and can't create a new one, reuse the least recently used une
+    if (!con && o->num_connections == o->max_connections) {
+        con = reuse_connection(o, conaddr);
+        flags |= UDPGW_CLIENT_FLAG_REBIND;
+    }
+    
+    if (!con) {
+        // create new connection
+        connection_init(o, conaddr, flags, data, data_len);
+    } else {
+        // move connection to front of the list
+        LinkedList1_Remove(&o->connections_list, &con->connections_list_node);
+        LinkedList1_Append(&o->connections_list, &con->connections_list_node);
+        
+        // send packet to existing connection
+        connection_send(con, flags, data, data_len);
+    }
+}
+
+int UdpGwClient_ConnectServer (UdpGwClient *o, StreamPassInterface *send_if, StreamRecvInterface *recv_if)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->have_server)
+    
+    // init receive interface
+    PacketPassInterface_Init(&o->recv_if, o->udpgw_mtu, (PacketPassInterface_handler_send)recv_interface_handler_send, o, BReactor_PendingGroup(o->reactor));
+    
+    // init receive decoder
+    if (!PacketProtoDecoder_Init(&o->recv_decoder, recv_if, &o->recv_if, BReactor_PendingGroup(o->reactor), o, (PacketProtoDecoder_handler_error)decoder_handler_error)) {
+        BLog(BLOG_ERROR, "PacketProtoDecoder_Init failed");
+        goto fail1;
+    }
+    
+    // init send sender
+    PacketStreamSender_Init(&o->send_sender, send_if, o->pp_mtu, BReactor_PendingGroup(o->reactor));
+    
+    // connect send connector
+    PacketPassConnector_ConnectOutput(&o->send_connector, PacketStreamSender_GetInput(&o->send_sender));
+    
+    // set have server
+    o->have_server = 1;
+    
+    return 1;
+    
+fail1:
+    PacketPassInterface_Free(&o->recv_if);
+    return 0;
+}
+
+void UdpGwClient_DisconnectServer (UdpGwClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_server)
+    
+    // free server
+    free_server(o);
+    
+    // set have no server
+    o->have_server = 0;
+}
diff --git a/external/badvpn_dns/udpgw_client/UdpGwClient.h b/external/badvpn_dns/udpgw_client/UdpGwClient.h
new file mode 100644
index 0000000..0a1086b
--- /dev/null
+++ b/external/badvpn_dns/udpgw_client/UdpGwClient.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) Ambroz Bizjak <ambrop7@xxxxxxxxx>
+ * Contributions:
+ * Transparent DNS: Copyright (C) Kerem Hadimli <kerem.hadimli@xxxxxxxxx>
+ * 
+ * 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_UDPGW_CLIENT_UDPGWCLIENT_H
+#define BADVPN_UDPGW_CLIENT_UDPGWCLIENT_H
+
+#include <stdint.h>
+
+#include <protocol/udpgw_proto.h>
+#include <misc/debug.h>
+#include <misc/packed.h>
+#include <structure/BAVL.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <system/BAddr.h>
+#include <base/BPending.h>
+#include <flow/PacketPassFairQueue.h>
+#include <flow/PacketStreamSender.h>
+#include <flow/PacketProtoFlow.h>
+#include <flow/PacketProtoDecoder.h>
+#include <flow/PacketPassConnector.h>
+#include <flowextra/PacketPassInactivityMonitor.h>
+
+typedef void (*UdpGwClient_handler_servererror) (void *user);
+typedef void (*UdpGwClient_handler_received) (void *user, BAddr local_addr, BAddr remote_addr, const uint8_t *data, int data_len);
+
+B_START_PACKED
+struct UdpGwClient__keepalive_packet {
+    struct packetproto_header pp;
+    struct udpgw_header udpgw;
+} B_PACKED;
+B_END_PACKED
+
+typedef struct {
+    int udp_mtu;
+    int max_connections;
+    int send_buffer_size;
+    btime_t keepalive_time;
+    BReactor *reactor;
+    void *user;
+    UdpGwClient_handler_servererror handler_servererror;
+    UdpGwClient_handler_received handler_received;
+    int udpgw_mtu;
+    int pp_mtu;
+    BAVL connections_tree_by_conaddr;
+    BAVL connections_tree_by_conid;
+    LinkedList1 connections_list;
+    int num_connections;
+    int next_conid;
+    PacketPassFairQueue send_queue;
+    PacketPassInactivityMonitor send_monitor;
+    PacketPassConnector send_connector;
+    struct UdpGwClient__keepalive_packet keepalive_packet;
+    PacketPassInterface *keepalive_if;
+    PacketPassFairQueueFlow keepalive_qflow;
+    int keepalive_sending;
+    int have_server;
+    PacketStreamSender send_sender;
+    PacketProtoDecoder recv_decoder;
+    PacketPassInterface recv_if;
+    DebugObject d_obj;
+} UdpGwClient;
+
+struct UdpGwClient_conaddr {
+    BAddr local_addr;
+    BAddr remote_addr;
+};
+
+struct UdpGwClient_connection {
+    UdpGwClient *client;
+    struct UdpGwClient_conaddr conaddr;
+    uint8_t first_flags;
+    const uint8_t *first_data;
+    int first_data_len;
+    uint16_t conid;
+    BPending first_job;
+    BufferWriter *send_if;
+    PacketProtoFlow send_ppflow;
+    PacketPassFairQueueFlow send_qflow;
+    BAVLNode connections_tree_by_conaddr_node;
+    BAVLNode connections_tree_by_conid_node;
+    LinkedList1Node connections_list_node;
+};
+
+int UdpGwClient_Init (UdpGwClient *o, int udp_mtu, int max_connections, int send_buffer_size, btime_t keepalive_time, BReactor *reactor, void *user,
+                      UdpGwClient_handler_servererror handler_servererror,
+                      UdpGwClient_handler_received handler_received) WARN_UNUSED;
+void UdpGwClient_Free (UdpGwClient *o);
+void UdpGwClient_SubmitPacket (UdpGwClient *o, BAddr local_addr, BAddr remote_addr, int is_dns, const uint8_t *data, int data_len);
+int UdpGwClient_ConnectServer (UdpGwClient *o, StreamPassInterface *send_if, StreamRecvInterface *recv_if) WARN_UNUSED;
+void UdpGwClient_DisconnectServer (UdpGwClient *o);
+
+#endif
diff --git a/jni/Android.mk b/jni/Android.mk
index 178e692..f8ca80f 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -1,3 +1,3 @@
 ##include ../OriginalDest/Android.mk
-include ./external/badvpn_dnsfix/Android.mk
+include ./external/badvpn_dns/Android.mk
 ##include ../kalium-jni/jni/Android.mk



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits