Release v2.0.3_20180813
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..48611fd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,35 @@
+---
+name: Bug report
+about: Create a report to help us improve the quality of our software
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Caution**
+The Issues are strictly limited for the reporting of problem encountered with the software provided in this project.
+For any other problem related to the STM32 product, the performance, the hardware characteristics and boards, the tools the environment in general, please post a topic in the [ST Community/STM32 MCUs forum](https://community.st.com/s/group/0F90X000000AXsASAW/stm32-mcus).
+
+**Describe the set-up**
+ * The board (either ST RPN reference or your custom board).
+ * IDE or at least the compiler and its version.
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**How To Reproduce**
+1. Indicate the global behavior of your application project.
+
+2. The modules that you suspect to be the cause of the problem (Driver, BSP, MW ...).
+
+3. The use case that generates the problem.
+
+4. How we can reproduce the problem.
+
+
+**Additional context**
+If you have a first analysis or patch correction, thank you to share your proposal.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
diff --git a/.github/ISSUE_TEMPLATE/other-issue.md b/.github/ISSUE_TEMPLATE/other-issue.md
new file mode 100644
index 0000000..d72cf58
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/other-issue.md
@@ -0,0 +1,22 @@
+---
+name: 'Other Issue '
+about: Generic issue description
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Caution**
+The Issues are strictly limited for the reporting of problem encountered with the software provided in this project.
+For any other problem related to the STM32 product, the performance, the hardware characteristics and boards, the tools the environment in general, please post a topic in the [ST Community/STM32 MCUs forum](https://community.st.com/s/group/0F90X000000AXsASAW/stm32-mcus).
+
+**Describe the set-up**
+ * The board (either ST RPN reference or your custom board).
+ * IDE or at least the compiler and its version.
+
+**Additional context**
+If you have a first analysis or a patch proposal, thank you to share your proposal.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..e7f1cb0
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,5 @@
+## IMPORTANT INFORMATION
+
+### Contributor License Agreement (CLA)
+* The Pull Request feature will be considered by STMicroelectronics after the signature of a **Contributor License Agreement (CLA)** by the submitter.
+* If you did not sign such agreement, please follow the steps mentioned in the [CONTRIBUTING.md](https://github.com/STMicroelectronics/stm32_mw_lwip/blob/master/CONTRIBUTING.md) file.
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..306217f
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,4295 @@
+HISTORY
+
+(git master)
+
+  * [Enter new changes just after this line - do not remove this line]
+
+(STABLE-2.0.3)
+
+  ++ Bugfixes:
+
+  2017-09-11: Simon Goldschmidt
+  * tcp_in.c: fix bug #51937 (leaking tcp_pcbs on passive close with unacked data)
+
+  2017-08-02: Abroz Bizjak/Simon Goldschmidt
+  * multiple fixes in IPv4 reassembly (leading to corrupted datagrams received)
+  
+  2017-03-30: Simon Goldschmidt
+  * dhcp.c: return ERR_VAL instead of asserting on offset-out-of-pbuf
+
+  2017-03-23: Dirk Ziegelmeier
+  * dhcp.h: fix bug #50618 (dhcp_remove_struct() macro does not work)
+
+(STABLE-2.0.2)
+
+  ++ New features:
+
+  2017-02-10: Dirk Ziegelmeier
+  * Implement task #14367: Hooks need a better place to be defined:
+    We now have a #define for a header file name that is #included in every .c
+    file that provides hooks.
+
+  2017-02-10: Simon Goldschmidt
+  * tcp_close does not fail on memory error (instead, FIN is sent from tcp_tmr)
+
+  ++ Bugfixes:
+
+  2017-03-08
+  * tcp: do not keep sending SYNs when getting ACKs
+
+  2017-03-08: Joel Cunningham
+  * tcp: Initialize ssthresh to TCP_SND_BUF (bug #50476)
+
+  2017-03-01: Simon Goldschmidt
+  * httpd: LWIP_HTTPD_POST_MANUAL_WND: fixed double-free when httpd_post_data_recved
+    is called nested from httpd_post_receive_data() (bug #50424)
+
+  2017-02-28: David van Moolenbroek/Simon Goldschmidt
+  * tcp: fixed bug #50418: LWIP_EVENT_API: fix invalid calbacks for SYN_RCVD pcb
+
+  2017-02-17: Simon Goldschmidt
+  * dns: Improved DNS_LOCAL_HOSTLIST interface (bug #50325)
+
+  2017-02-16: Simon Goldschmidt
+  * LWIP_NETCONN_FULLDUPLEX: fixed shutdown during write (bug #50274)
+
+  2017-02-13: Simon Goldschmidt/Dirk Ziegelmeier
+  * For tiny targtes, LWIP_RAND is optional (fix compile time checks)
+
+  2017-02-10: Simon Goldschmidt
+  * tcp: Fixed bug #47485 (tcp_close() should not fail on memory error) by retrying
+    to send FIN from tcp_fasttmr
+
+  2017-02-09: Simon Goldschmidt
+  * sockets: Fixed bug #44032 (LWIP_NETCONN_FULLDUPLEX: select might work on
+    invalid/reused socket) by not allowing to reallocate a socket that has
+    "select_waiting != 0"
+
+  2017-02-09: Simon Goldschmidt
+  * httpd: Fixed bug #50059 (httpd LWIP_HTTPD_SUPPORT_11_KEEPALIVE vs.
+    LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED)
+
+  2017-02-08: Dirk Ziegelmeier
+  * Rename "IPv6 mapped IPv4 addresses" to their correct name from RFC4191:
+    "IPv4-mapped IPv6 address"
+
+  2017-02-08: Luc Revardel
+  * mld6.c: Fix bug #50220 (mld6_leavegroup does not send ICMP6_TYPE_MLD, even
+    if last reporter)
+
+  2017-02-08: David van Moolenbroek
+  * ip6.c: Patch #9250: fix source substitution in ip6_output_if()
+
+  2017-02-08: Simon Goldschmidt
+  * tcp_out.c: Fixed bug #50090 (last_unsent->oversize_left can become wrong value
+    in tcp_write error path)
+
+  2017-02-02: Dirk Ziegelmeier
+  * Fix bug #50206: UDP Netconn bind to IP6_ADDR_ANY fails
+
+  2017-01-18: Dirk Ziegelmeier
+  * Fix zero-copy RX, see bug bug #50064. PBUF_REFs were not supported as ARP requests.
+
+  2017-01-15: Axel Lin, Dirk Ziegelmeier
+  * minor bug fixes in mqtt
+
+  2017-01-11: Knut Andre Tidemann
+  * sockets/netconn: fix broken default ICMPv6 handling of checksums
+
+(STABLE-2.0.1)
+
+  ++ New features:
+
+  2016-12-31: Simon Goldschmidt
+  * tcp.h/.c: added function tcp_listen_with_backlog_and_err() to get the error
+    reason when listening fails (bug #49861)
+
+  2016-12-20: Erik Andersen
+  * Add MQTT client
+
+  2016-12-14: Jan Breuer:
+  * opt.h, ndc.h/.c: add support for RDNSS option (as per RFC 6106)
+
+  2016-12-14: David van Moolenbroek
+  * opt.h, nd6.c: Added LWIP_HOOK_ND6_GET_GW()
+
+  2016-12-09: Dirk Ziegelmeier
+  * ip6_frag.c: Implemented support for LWIP_NETIF_TX_SINGLE_PBUF
+
+  2016-12-09: Simon Goldschmidt
+  * dns.c: added one-shot multicast DNS queries
+
+  2016-11-24: Ambroz Bizjak, David van Moolenbroek
+  * tcp_out.c: Optimize passing contiguous nocopy buffers to tcp_write (bug #46290)
+
+  2016-11-16: Dirk Ziegelmeier
+  * sockets.c: added support for IPv6 mapped IPv4 addresses
+
+  ++ Bugfixes:
+
+  2016-12-16: Thomas Mueller
+  * api_lib.c: fixed race condition in return value of netconn_gethostbyname()
+    (and thus also lwip_gethostbyname/_r() and lwip_getaddrinfo())
+
+  2016-12-15: David van Moolenbroek
+  * opt.h, tcp: added LWIP_HOOK_TCP_ISN() to implement less predictable initial
+    sequence numbers (see contrib/addons/tcp_isn for an example implementation)
+
+  2016-12-05: Dirk Ziegelmeier
+  * fixed compiling with IPv4 disabled (IPv6 only case)
+
+  2016-11-28: Simon Goldschmidt
+  * api_lib.c: fixed bug #49725 (send-timeout: netconn_write() can return
+    ERR_OK without all bytes being written)
+
+  2016-11-28: Ambroz Bizjak
+  * tcpi_in.c: fixed bug #49717 (window size in received SYN and SYN-ACK
+    assumed scaled)
+
+  2016-11-25: Simon Goldschmidt
+  * dhcp.c: fixed bug #49676 (Possible endless loop when parsing dhcp options)
+
+  2016-11-23: Dirk Ziegelmeier
+  * udp.c: fixed bug #49662: multicast traffic is now only received on a UDP PCB
+   (and therefore on a UDP socket/netconn) when the PCB is bound to IP_ADDR_ANY
+
+  2016-11-16: Dirk Ziegelmeier
+  * *: Fixed dual-stack behaviour, IPv6 mapped IPv4 support in socket API
+
+  2016-11-14: Joel Cunningham
+  * tcp_out.c: fixed bug #49533 (start persist timer when unsent seg can't fit
+    in window) 
+
+  2016-11-16: Roberto Barbieri Carrera
+  * autoip.c: fixed bug #49610 (sometimes AutoIP fails to reuse the same address)
+
+  2016-11-11: Dirk Ziegelmeier
+  * sockets.c: fixed bug #49578 (dropping multicast membership does not work
+    with LWIP_SOCKET_OFFSET)
+
+(STABLE-2.0.0)
+
+  ++ New features:
+
+  2016-07-27: Simon Goldschmidt
+  * opt.h, timeouts.h/.c: added LWIP_TIMERS_CUSTOM to override the default
+    implementation of timeouts
+
+  2016-07-xx: Dirk Ziegelmeier
+  * Large overhaul of doxygen documentation
+
+  2016-04-05: Simon Goldschmidt
+  * timers.h/.c: prepare for overriding current timeout implementation: all
+    stack-internal caclic timers are avaliable in the lwip_cyclic_timers array
+
+  2016-03-23: Simon Goldschmidt
+  * tcp: call accept-callback with ERR_MEM when allocating a pcb fails on
+    passive open to inform the application about this error
+    ATTENTION: applications have to handle NULL pcb in accept callback!
+
+  2016-02-22: Ivan Delamer
+  * Initial 6LoWPAN support
+
+  2016-02-XX to 2016-03-XX: Dirk Ziegelmeier
+  * Cleanup TCPIP thread sync methods in a way that it is possibe to use them
+    in arbitrary code that needs things to be done in TCPIP thread. Used to
+    decouple netconn, netif, ppp and 6LoWPAN from LWIP core.
+
+  2016-02-XX: Dirk Ziegelmeier
+  * Implement dual-stack support in RAW, UDP and TCP. Add new IP address
+    type IPADDR_ANY_TYPE for this. Netconn/Socket API: Dual-stack is
+    automatically supported when an IPv6 netconn/socket is created.
+
+  2015-12-26: Martin Hentschel and Dirk Ziegelmeier
+  * Rewrite SNMP agent. SNMPv2c + MIB compiler.
+
+  2015-11-12: Dirk Ziegelmeier
+  * Decouple SNMP stack from lwIP core and move stack to apps/ directory.
+    Breaking change: Users have to call snmp_init() now!
+
+  2015-11-12: Dirk Ziegelmeier
+  * Implement possibility to declare private memory pools. This is useful to
+    decouple some apps from the core (SNMP stack) or make contrib app usage
+    simpler (httpserver_raw)
+
+  2015-10-09: Simon Goldschmidt
+  * started to move "private" header files containing implementation details to
+    "lwip/priv/" include directory to seperate the API from the implementation.
+
+  2015-10-07: Simon Goldschmidt
+  * added sntp client as first "supported" application layer protocol implementation
+    added 'apps' folder
+
+  2015-09-30: Dirk Ziegelmeier
+  * snmp_structs.h, mib_structs.c, mib2.c: snmp: fixed ugly inheritance
+    implementation by aggregating the "base class" (struct mib_node) in all
+    derived node classes to get more type-safe code
+
+  2015-09-23: Simon Goldschmidt
+  * netif.h/.c, nd6.c: task #13729: Convert netif addresses (IPv4 & IPv6) to
+    ip_addr_t (so they can be used without conversion/temporary storage)
+
+  2015-09-08: Dirk Ziegelmeier
+  * snmp: Separate mib2 counter/table callbacks from snmp agent. This both cleans
+    up the code and should allow integration of a 3rd party agent/mib2. Simple
+    counters are kept in MIB2_STATS, tree/table change function prototypes moved to
+    snmp_mib2.h.
+
+  2015-09-03: Simon Goldschmidt
+  * opt.h, dns.h/.c: DNS/IPv6: added support for AAAA records
+
+  2015-09-01: Simon Goldschmidt
+  * task #12178: hardware checksum capabilities can be configured per netif
+   (use NETIF_SET_CHECKSUM_CTRL() in your netif's init function)
+
+  2015-08-30: Simon Goldschmidt
+  * PBUF_REF with "custom" pbufs is now supported for RX pbufs (see pcapif in
+    contrib for an example, LWIP_SUPPORT_CUSTOM_PBUF is required)
+
+  2015-08-30: Simon Goldschmidt
+  * support IPv4 source based routing: define LWIP_HOOK_IP4_ROUTE_SRC to point
+    to a routing function
+
+  2015-08-05: Simon Goldschmidt
+  * many files: allow multicast socket options IP_MULTICAST_TTL, IP_MULTICAST_IF
+    and IP_MULTICAST_LOOP to be used without IGMP
+
+  2015-04-24: Simon Goldschmidt
+  * dhcp.h/c, autoip.h/.c: added functions dhcp/autoip_supplied_address() to
+    check for the source of address assignment (replacement for NETIF_FLAG_DHCP)
+
+  2015-04-10: Simon Goldschmidt
+  * many files: task #13480: added LWIP_IPV4 define - IPv4 can be disabled,
+    leaving an IPv6-only stack
+
+  2015-04-09: Simon Goldschmidt
+  * nearly all files: task #12722 (improve IPv4/v6 address handling): renamed
+    ip_addr_t to ip4_addr_t, renamed ipX_addr_t to ip_addr_t and added IP
+    version; ip_addr_t is used for all generic IP addresses for the API,
+    ip(4/6)_addr_t are only used internally or when initializing netifs or when
+    calling version-related functions
+
+  2015-03-24: Simon Goldschmidt
+  * opt.h, ip4_addr.h, ip4.c, ip6.c: loopif is not required for loopback traffic
+    any more but passed through any netif (ENABLE_LOOPBACK has to be enabled)
+
+  2015-03-23: Simon Goldschmidt
+  * opt.h, etharp.c: with ETHARP_TABLE_MATCH_NETIF== 1, duplicate (Auto)-IP
+    addresses on multiple netifs should now be working correctly (if correctly
+    addressed by routing, that is)
+
+  2015-03-23: Simon Goldschmidt
+  * etharp.c: Stable etharp entries that are about to expire are now refreshed
+    using unicast to prevent unnecessary broadcast. Only if no answer is received
+    after 15 seconds, broadcast is used.
+
+  2015-03-06: Philip Gladstone
+  * netif.h/.c: patch #8359 (Provide utility function to add an IPv6 address to
+    an interface)
+
+  2015-03-05: Simon Goldschmidt
+  * netif.c, ip4.c, dhcp.c, autoip.c: fixed bug #37068 (netif up/down handling
+    is unclear): correclty separated administrative status of a netif (up/down)
+    from 'valid address' status
+    ATTENTION: netif_set_up() now always has to be called, even when dhcp/autoip
+    is used!
+
+  2015-02-26: patch by TabascoEye
+  * netif.c, udp.h/.c: fixed bug #40753 (re-bind UDP pcbs on change of IP address)
+
+  2015-02-22: chrysn, Simon Goldschmidt
+  * *.*: Changed nearly all functions taking 'ip(X)_addr_t' pointer to take
+    const pointers (changed user callbacks: raw_recv_fn, udp_recv_fn; changed
+    port callbacks: netif_output_fn, netif_igmp_mac_filter_fn)
+
+  2015-02-19: Ivan Delamer
+  * netif.h, dhcp.c: Removed unused netif flag for DHCP. The preferred way to evaluate
+    if DHCP is active is through netif->dhcp field.
+
+  2015-02-19: Ivan Delamer
+  * netif.h, slipif.c, ppp.c: Removed unused netif flag for point to point connections
+
+  2015-02-18: Simon Goldschmidt
+  * api_lib.c: fixed bug #37958 "netconn API doesn't handle correctly
+    connections half-closed by peer"
+
+  2015-02-18: Simon Goldschmidt
+  * tcp.c: tcp_alloc() prefers killing CLOSING/LAST_ACK over active connections
+    (see bug #39565)
+
+  2015-02-16: Claudius Zingerli, Sergio Caprile
+  * opt.h, dhcp.h/.c: patch #8361 "Add support for NTP option in DHCP"
+
+  2015-02-14: Simon Goldschmidt
+  * opt.h, snmp*: added support for write-access community and dedicated
+    community for sending traps
+
+  2015-02-13: Simon Goldschmidt
+  * opt.h, memp.c: added hook LWIP_HOOK_MEMP_AVAILABLE() to get informed when
+    a memp pool was empty and an item is now available
+
+  2015-02-13: Simon Goldschmidt
+  * opt.h, pbuf.h/.c, etharp.c: Added the option PBUF_LINK_ENCAPSULATION_HLEN to
+    allocate additional header space for TX on netifs requiring additional headers
+
+  2015-02-12: chrysn
+  * timers.h/.c: introduce sys_timeouts_sleeptime (returns the time left before
+    the next timeout is due, for NO_SYS==1)
+
+  2015-02-11: Nick van Ijzendoorn
+  * opt.h, sockets.h/c: patch #7702 "Include ability to increase the socket number
+    with defined offset"
+
+  2015-02-11: Frederick Baksik
+  * opt.h, def.h, others: patch #8423 "arch/perf.h" should be made an optional item
+
+  2015-02-11: Simon Goldschmidt
+  * api_msg.c, opt.h: started to implement fullduplex sockets/netconns
+    (note that this is highly unstable yet!)
+
+  2015-01-17: Simon Goldschmidt
+  * api: allow enabling socket API without (public) netconn API - netconn API is
+    still used by sockets, but keeping it private (static) should allow better
+    compiler optimizations
+
+  2015-01-16: Simon Goldschmidt
+  * tcp_in.c: fixed bug #20506 "Initial congestion window is very small" again
+    by implementing the calculation formula from RFC3390
+
+  2014-12-10: Simon Goldschmidt
+  * api: added option LWIP_NETCONN_SEM_PER_THREAD to use a semaphore per thread
+    instead of using one per netconn and per select call
+
+  2014-12-08: Simon Goldschmidt
+  * ip6.h: fixed bug #43778: IPv6 header version not set on 16-bit platform
+    (macro IP6H_VTCFL_SET())
+
+  2014-12-08: Simon Goldschmidt
+  * icmp.c, ip4.c, pbuf.c, udp.c, pbuf.h: task #11472 Support PBUF_REF for RX
+    (IPv6 and IPv4/v6 reassembly might not work yet)
+
+  2014-11-06: Simon Goldschmidt
+  * sockets.c/.h, init.c: lwip_socket_init() is not needed any more
+    -> compatibility define
+
+  2014-09-16: Simon Goldschmidt
+  * dns.c, opt.h: reduced ram usage by parsing DNS responses in place
+
+  2014-09-16: Simon Goldschmidt
+  * pbuf.h/.c: added pbuf_take_at() and pbuf_put_at()
+
+  2014-09-15: Simon Goldschmidt
+  * dns.c: added source port randomization to make the DNS client more robust
+    (see bug #43144)
+
+  2013-09-02: Simon Goldschmidt
+  * arch.h and many other files: added optional macros PACK_STRUCT_FLD_8() and
+    PACK_STRUCT_FLD_S() to prevent gcc 4 from warning about struct members that
+    do not need packing
+
+  2013-08-19: Simon Goldschmidt
+  * netif.h: bug #42998: made NETIF_MAX_HWADDR_LEN overridable for some special
+    networks
+
+  2013-03-17: Simon Goldschmidt (patch by Ghobad Emadi)
+  * opt.h, etharp.c: Added LWIP_HOOK_ETHARP_GET_GW to implement IPv4 routing with
+    multiple gateways
+
+  2013-04-20: Fatih Asici
+  * opt.h, etharp.h/.c: patch #7993: Added support for transmitting packets
+    with VLAN headers via hook function LWIP_HOOK_VLAN_SET and to check them
+    via hook function LWIP_HOOK_VLAN_CHECK
+
+  2014-02-20: Simon Goldschmidt (based on patch by Artem Pisarenko)
+  * patch #7885: modification of api modules to support FreeRTOS-MPU
+    (don't pass stack-pointers to other threads)
+
+  2014-02-05: Simon Goldschmidt (patch by "xtian" and "alex_ab")
+  * patch #6537/#7858: TCP window scaling support
+
+  2014-01-17: Jiri Engelthaler
+  * icmp, icmp6, opt.h: patch #8027: Completed HW checksuming for IPv4 and
+    IPv6 ICMP's
+
+  2012-08-22: Sylvain Rochet
+  * New PPP stack for lwIP, developed in ppp-new branch.
+    Based from pppd 2.4.5, released 2009-11-17, with huge changes to match
+    code size and memory requirements for embedded devices, including:
+    - Gluing together the previous low-level PPP code in lwIP to pppd 2.4.5, which
+      is more or less what pppd sys-* files are, so that we get something working
+      using the unix port.
+    - Merged some patchs from lwIP Git repository which add interesting features
+      or fix bugs.
+    - Merged some patchs from Debian pppd package which add interesting features
+      or fix bugs.
+    - Ported PPP timeout handling to the lwIP timers system
+    - Disabled all the PPP code using filesystem access, replaced in necessary cases
+      to configuration variables.
+    - Disabled all the PPP code forking processes.
+    - Removed IPX support, lwIP does not support IPX.
+    - Ported and improved random module from the previous PPP port.
+    - Removed samba TDB (file-driven database) usage, because it needs a filesystem.
+    - MS-CHAP required a DES implementation, we added the latest PolarSSL DES
+      implementation which is under a BSD-ish license.
+    - Also switched to PolarSSL MD4,MD5,SHA1 implementations, which are meant to be
+      used in embedded devices with reduced memory footprint.
+    - Removed PPP configuration file parsing support. 
+    - Added macro definition EAP_SUPPORT to make EAP support optional.
+    - Added macro definition CHAP_SUPPORT to make CHAP support optional.
+    - Added macro definition MSCHAP_SUPPORT to make MSCHAP support optional.
+    - Added macro definition PAP_SUPPORT to make PAP support optional.
+    - Cleared all Linux syscall calls.
+    - Disabled demand support using a macro, so that it can be ported later.
+    - Disabled ECP support using a macro, so that it can be ported later.
+    - Disabled CCP support using a macro, so that it can be ported later.
+    - Disabled CBCP support using a macro, so that it can be ported later.
+    - Disabled LQR support using a macro, so that it can be ported later.
+    - Print packet debug feature optional, through PRINTPKT_SUPPORT
+    - Removed POSIX signal usage.
+    - Fully ported PPPoS code from the previous port.
+    - Fully ported PPPoE code from the previous port.
+    - Fully ported VJ compression protocol code from the previous port.
+    - Removed all malloc()/free() use from PPP, replaced by stack usage or PBUF.
+    - Disabled PPP server support using a macro, so that it can be ported later.
+    - Switched all PPP debug to lwIP debug system.
+    - Created PPP Control Block (PPP PCB), removed PPP unit integer everywhere,
+      removed all global variables everywhere, did everything necessary for
+      the PPP stack to support more than one PPP session (pppd only support
+      one session per process).
+    - Removed the statically allocated output buffer, now using PBUF.
+    - Improved structure size of all PPP modules, deep analyze of code to reduce
+      variables size to the bare minimum. Switched all boolean type (char type in
+      most architecture) to compiler generated bitfields.
+    - Added PPP IPv6 support, glued lwIP IPv6 support to PPP.
+    - Now using a persistent netif interface which can then be used in lwIP
+      functions requiring a netif.
+    - Now initializing PPP in lwip_init() function.
+    - Reworked completely the PPP state machine, so that we don't end up in
+      anymore in inconsistent state, especially with PPPoE.
+    - Improved the way we handle PPP reconnection after disconnect, cleaning
+      everything required so that we start the PPP connection again from a
+      clean state.
+    - Added PPP holdoff support, allow the lwIP user to wait a little bit before
+      reconnecting, prevents connection flood, especially when using PPPoL2TP.
+    - Added PPPoL2TP LAC support (a.k.a. UDP tunnels), adding a VPN client
+      feature to lwIP, L2TP being a widely used tunnel protocol.
+    - Switched all used PPP types to lwIP types (u8t, u16t, u32t, ...)
+    - Added PPP API "sequential" thread-safe API, based from NETIFAPI.
+
+  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-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 :-)
+
+
+  ++ Bugfixes:
+
+  2016-08-23: Simon Goldschmidt
+  * etharp: removed ETHARP_TRUST_IP_MAC since it is insecure and we don't need
+    it any more after implementing unicast ARP renewal towards arp entry timeout
+
+  2016-07-20: Simon Goldschmidt
+  * memp.h/.c: fixed bug #48442 (memp stats don't work for MEMP_MEM_MALLOC)
+
+  2016-07-21: Simon Goldschmidt (patch by Ambroz Bizjak)
+  * tcp_in.c, tcp_out.c: fixed bug #48543 (TCP sent callback may prematurely
+    report sent data when only part of a segment is acked) and don't include
+    SYN/FIN in snd_buf counter
+
+  2016-07-19: Simon Goldschmidt
+  * etharp.c: fixed bug #48477 (ARP input packet might update static entry)
+
+  2016-07-11: Simon Goldschmidt
+  * tcp_in.c: fixed bug #48476 (TCP sent callback called wrongly due to picking
+    up old pcb->acked
+
+  2016-06-30: Simon Goldschmidt (original patch by Fabian Koch)
+  * tcp_in.c: fixed bug #48170 (Vulnerable to TCP RST spoofing)
+
+  2016-05-20: Dirk Ziegelmeier
+  * sntp.h/.c: Fix return value of sntp_getserver() call to return a pointer
+
+  2016-04-05: Simon Goldschmidt (patch by Philip Gladstone)
+  * udp.c: patch #8358: allow more combinations of listening PCB for IPv6
+
+  2016-04-05: Simon Goldschmidt
+  * netconn/socket API: fixed bug# 43739 (Accept not reporting errors about
+    aborted connections): netconn_accept() returns ERR_ABRT (sockets: ECONNABORTED)
+    for aborted connections, ERR_CLSD (sockets: EINVAL) if the listening netconn
+    is closed, which better seems to follow the standard.
+
+  2016-03-23: Florent Matignon
+  * dhcp.c: fixed bug #38203: DHCP options are not recorded in all DHCP ack messages
+
+  2016-03-22: Simon Goldschmidt
+  * tcp: changed accept handling to be done internally: the application does not
+    have to call tcp_accepted() any more. Instead, when delaying accept (e.g. sockets
+    do), call tcp_backlog_delayed()/tcp_backlog_accepted() (fixes bug #46696)
+
+  2016-03-22: Simon Goldschmidt
+  * dns.c: ignore dns response parsing errors, only abort resolving for correct
+    responses or error responses from correct server (bug #47459)
+
+  2016-03-17: Simon Goldschmidt
+  * api_msg.c: fixed bug #47448 (netconn/socket leak if RST is received during close)
+
+  2016-03-17: Joel Cunningham
+  * api_msg.c: don't fail closing a socket/netconn when failing to allocate the
+    FIN segment; blocking the calling thread for a while is better than risking
+    leaking a netconn/socket (see bug #46701)
+
+  2016-03-16: Joel Cunningham
+  * tcp_out.c: reset rto timer on fast retransmission
+
+  2016-03-16: Deomid Ryabkov
+  * tcp_out.c: fixed bug #46384 Segment size calculation bug with MSS != TCP_MSS
+
+  2016-03-05: Simon Goldschmidt
+  * err.h/.c, sockets.c: ERR_IF is not necessarily a fatal error
+
+  2015-11-19: fix by Kerem Hadimli
+  * sockets.c: fixed bug #46471: lwip_accept() leaks socket descriptors if new
+    netconn was already closed because of peer behavior
+
+  2015-11-12: fix by Valery Ushakov
+  * tcp_in.c: fixed bug #46365 tcp_accept_null() should call tcp_abort()
+
+  2015-10-02: Dirk Ziegelmeier/Simon Goldschmidt
+  * snmp: cleaned up snmp structs API (fixed race conditions from bug #46089,
+    reduce ram/rom usage of tables): incompatible change for private MIBs
+
+  2015-09-30: Simon Goldschmidt
+  * ip4_addr.c: fixed bug #46072: ip4addr_aton() does not check the number range
+    of all address parts
+
+  2015-08-28: Simon Goldschmidt
+  * tcp.c, tcp_in.c: fixed bug #44023: TCP ssthresh value is unclear: ssthresh
+    is set to the full send window for active open, too, and is updated once
+    after SYN to ensure the correct send window is used
+
+  2015-08-28: Simon Goldschmidt
+  * tcp: fixed bug #45559: Window scaling casts u32_t to u16_t without checks
+
+  2015-08-26: Simon Goldschmidt
+  * ip6_frag.h/.c: fixed bug bug #41009: IPv6 reassembly broken on 64-bit platforms:
+    define IPV6_FRAG_COPYHEADER==1 on these platforms to copy the IPv6 header
+    instead of referencing it, which gives more room for struct ip6_reass_helper
+
+  2015-08-25: Simon Goldschmidt
+  * sockets.c: fixed bug #45827: recvfrom: TCP window is updated with MSG_PEEK
+
+  2015-08-20: Manoj Kumar
+  * snmp_msg.h, msg_in.c: fixed bug #43790: Sending octet string of Length >255
+    from SNMP agent
+
+  2015-08-19: Jens Nielsen
+  * icmp.c, ip4.c, tcp_in.c, udp.c, raw.c: fixed bug #45120: Broadcast & multiple
+    interfaces handling
+
+  2015-08-19: Simon Goldschmidt (patch by "Sandra")
+  * dns.c: fixed bug #45004: dns response without answer might be discarded
+
+  2015-08-18: Chrysn
+  * timers.c: patch #8704 fix sys_timeouts_sleeptime function
+
+  2015-07-01: Erik Ekman
+  * puf.c: fixed bug #45454 (pbuf_take_at() skips write and returns OK if offset
+    is at start of pbuf in chain)
+
+  2015-05-19: Simon Goldschmidt
+  * dhcp.h/.c: fixed bugs #45140 and #45141 (dhcp was not stopped correctly after
+    fixing bug #38204)
+
+  2015-03-21: Simon Goldschmidt (patch by Homyak)
+  * tcp_in.c: fixed bug #44766 (LWIP_WND_SCALE: tcphdr->wnd was not scaled in
+    two places)
+
+  2015-03-21: Simon Goldschmidt
+  * tcp_impl.h, tcp.c, tcp_in.c: fixed bug #41318 (Bad memory ref in tcp_input()
+    after tcp_close())
+
+  2015-03-21: Simon Goldschmidt
+  * tcp_in.c: fixed bug #38468 (tcp_sent() not called on half-open connection for
+    data ACKed with the same ack as FIN)
+
+  2015-03-21: Simon Goldschmidt (patch by Christoffer Lind)
+  * dhcp.h/.c: fixed bug #38204 (DHCP lease time not handled correctly)
+
+  2015-03-20: Simon Goldschmidt
+  * dhcp.c: fixed bug #38714 (Missing option and client address in DHCPRELEASE message)
+
+  2015-03-19: Simon Goldschmidt
+  * api.h, tcpip.h, api_lib.c, api_msg.c: fixed race conditions in assigning
+    netconn->last_err (fixed bugs #38121 and #37676)
+
+  2015-03-09: Simon Goldschmidt
+  * ip4.c: fixed the IPv4 part of bug #43904 (ip_route() must detect linkup status)
+
+  2015-03-04: Simon Goldschmidt
+  * nd6.c: fixed bug #43784 (a host should send at least one Router Solicitation)
+
+  2015-03-04: Valery Ushakov
+  * ip6.c: fixed bug #41094 (Byte-order bug in IPv6 fragmentation header test)
+
+  2015-03-04: Zach Smith
+  * nd6.c: fixed bug #38153 (nd6_input() byte order issues)
+
+  2015-02-26: Simon Goldschmidt
+  * netif.c, tcp.h/.c: fixed bug #44378 (TCP connections are not aborted on netif
+    remove)
+
+  2015-02-25: Simon Goldschmidt
+  * ip4.c, etharp.c: fixed bug #40177 (System hangs when dealing with corrupted
+    packets), implemented task #12357 (Ensure that malicious packets don't
+    assert-fail): improved some pbuf_header calls to not assert-fail.
+
+  2015-02-25: patch by Joel Cunningham
+  * udp.h/.c, sockets.c: fixed bug #43028 (IP_MULTICAST_TTL affects unicast
+    datagrams)
+
+  2015-02-25: patch by Greg Renda
+  * ip4_frag.c: fixed bug #38210 (ip reassembly while remove oldest datagram)
+
+  2015-02-25: Simon Goldschmidt
+  * sockets.c: fixed bug #38165 (socket with mulicast): ensure igmp membership
+    are dropped when socket (not netconn!) is closed.
+
+  2015-02-25: Simon Goldschmidt
+  * ip4.h/.c, udp.c: fixed bug #38061 (wrong multicast routing in IPv4) by
+    adding an optional default netif for multicast routing
+
+  2015-02-25: Simon Goldschmidt
+  * netconn API: fixed that netconn_connect still used message passing for
+    LWIP_TCPIP_CORE_LOCKING==1
+
+  2015-02-22: patch by Jens Nielsen
+  * icmp.c: fixed bug #38803 (Source address in broadcast ping reply)
+
+  2015-02-22: Simon Goldschmidt
+  * udp.h, sockets.c: added proper accessor functions for pcb->multicast_ip
+    (previously used by get/setsockopt only)
+
+  2015-02-18: Simon Goldschmidt
+  * sockets.c: Fixed select not reporting received FIN as 'readable' in certain
+    rare cases (bug #43779: select(), close(), and TCP retransmission error)
+
+  2015-02-17: Simon Goldschmidt
+  * err.h, sockets.c, api_msg.c: fixed bug #38853 "connect() use a wrong errno":
+    return ERR_ALREADY/EALRADY during connect, ERR_ISCONN/EISCONN when already
+    connected
+
+  2015-02-17: Simon Goldschmidt
+  * tcp_impl.h, tcp_out.c, tcp.c, api_msg.c: fixed bug #37614 "Errors from
+    ipX_output are not processed". Now tcp_output(_segment) checks for the return
+    value of ipX_output and does not try to send more on error. A netif driver
+    can call tcp_txnow() (from tcpip_thread!) to try to send again if TX buffers
+    are available again.
+
+  2015-02-14: patches by Freddie Chopin
+  * snmp*: made community writable, fixed some const pointers
+
+  2015-02-13: Simon Goldschmidt
+  * msg_in.c: fixed bug #22070 "MIB_OBJECT_WRITE_ONLY not implemented in SNMP"
+
+  2015-02-12: Simon Goldschmidt
+  * ip.h, ip4.c, ip6.c: fixed bug #36403 "ip4_input() and ip6_input() always pass
+    inp to higher layers": now the accepting netif is passed up, but the input
+    netif is available through ip_current_input_netif() if required.
+
+  2015-02-11: patch by hichard
+  * tcpip.c: fixed bug #43094 "The function tcpip_input() forget to handle IPv6"
+
+  2015-02-10: Simon Goldschmidt
+  * netconn API: fixed that netconn_close/netconn_delete still used message passing
+    for LWIP_TCPIP_CORE_LOCKING==1
+
+  2015-02-10: Simon Goldschmidt
+  * netconn/socket api: fixed bug #44225 "closing TCP socket should time out
+    eventually", implemented task #6930 "Implement SO_LINGER": closing TCP sockets
+    times out after 20 seconds or after the configured SND_TIMEOUT or depending
+    on the linger settings.
+
+  2015-01-27: Simon Goldschmidt
+  * api_msg.c: fixed that SHUT_RD followed by SHUT_WR was different to SHUT_RDWR,
+    fixed return value of lwip_netconn_do_close on unconnected netconns
+
+  2015-01-17: Simon Goldschmidt
+  * sockets.c: fixed bug #43361 select() crashes with stale FDs
+
+  2015-01-17: Simon Goldschmidt
+  * sockets.c/.h, memp_std.h: fixed bug #40788 "lwip_setsockopt_internal() crashes"
+    by rewriting set/getsockopt functions to combine checks with the actual code
+    and add more NULL checks; this also fixes that CORE_LOCKING used message
+    passing for set/getsockopt.
+
+  2014-12-19: Simon Goldschmidt
+  * opt.h, dhcp.h/.c: prevent dhcp from starting when netif link is down (only
+    when LWIP_DHCP_CHECK_LINK_UP==1, which is disabled by default for
+    compatibility reasons)
+
+  2014-12-17: Simon Goldschmidt
+  * tcp_out.c: fixed bug #43840 Checksum error for TCP_CHECKSUM_ON_COPY==1 for
+    no-copy data with odd length
+
+  2014-12-10: Simon Goldschmidt
+  * sockets.c, tcp.c, others: fixed bug #43797 set/getsockopt: SO_SNDTIMEO/SO_RCVTIMEO
+    take int as option but should take timeval (LWIP_SO_SNDRCVTIMEO_STANDARD==0 can
+    be used to revert to the old 'winsock' style behaviour)
+    Fixed implementation of SO_ACCEPTCONN to just look at the pcb state
+
+  2014-12-09: Simon Goldschmidt
+  * ip4.c: fixed bug #43596 IGMP queries from 0.0.0.0 are discarded
+
+  2014-10-21: Simon Goldschmidt (patch by Joel Cunningham and Albert Huitsing)
+  * sockts.c: fixed bugs #41495 Possible threading issue in select() and #43278
+    event_callback() handle context switch when calling sys_sem_signal()
+
+  2014-10-21: Simon Goldschmidt
+  * api_msg.c: fixed bug #38219 Assert on TCP netconn_write with sndtimeout set
+
+  2014-09-16: Kevin Cernekee
+  * dns.c: patch #8480 Fix handling of dns_seqno wraparound
+
+  2014-09-16: Simon Goldschmidt
+  * tcp_out.c: fixed bug #43192 tcp_enqueue_flags() should not check TCP_SND_QUEUELEN
+    when sending FIN
+
+  2014-09-03: Simon Goldschmidt
+  * msg_in.c: fixed bug #39355 SNMP Memory Leak in case of error
+
+  2014-09-02: Simon Goldschmidt
+  * err.h/.c, sockets.c, api_msg.c: fixed bug #43110 call getpeername() before
+    listen() will cause a error
+
+  2014-09-02: Simon Goldschmidt
+  * sockets.c: fixed bug #42117 lwip_fcntl does not set errno
+
+  2014-09-02: Simon Goldschmidt
+  * tcp.c: fixed bug #42299 tcp_abort() leaves freed pcb on tcp_bound_pcbs list
+
+  2014-08-20: Simon Goldschmidt
+  * dns.c: fixed bug #42987 lwIP is vulnerable to DNS cache poisoning due to
+    non-randomized TXIDs
+
+  2014-06-03: Simon Goldschmidt
+  * tcp_impl.h, tcp_in.c: fixed bug #37969 SYN packet dropped as short packet in
+    tcp_input function
+
+  2014-05-20: Simon Goldschmidt
+  * tcp_out.c: fixed bug #37184 tcp_write problem for pcbs in the SYN_SENT state
+
+  2014-05-19: Simon Goldschmidt
+  * *.h: Fixed bug #35874 reserved identifier violation (removed leading underscores
+    from header include guards)
+
+  2014-04-08: Simon Goldschmidt
+  * tcp.c: Fixed bug #36167 tcp server crash when client closes (maximum window)
+
+  2014-04-06: Simon Goldschmidt
+  * tcp_in.c: Fixed bug #36210 lwIP does not elicit an empty ACK when received
+    unacceptable ACK
+
+  2014-04-06: Simon Goldschmidt
+  * dhcp.c, ip4.c/.h, ip6.c/.h, udp.c/.h, ip.h: Fixed bug #41787 DHCP Discovery
+    is invalid when an IP is set to thet netif.
+
+  2014-03-14: Simon Goldschmidt
+  * tcp_out.c: Fixed bug #36153 TCP Cheksum error if LWIP_CHECKSUM_ON_COPY=1
+
+  2014-03-11: Simon Goldschmidt (patch by Mason)
+  * opt.h, sockets.c: fixed bug #35928 BSD sockets functions must set errno for
+    POSIX-compliance
+
+  2014-02-27: Simon Goldschmidt
+  * dhcp.c: fixed bug #40303 DHCP xid renewed when sending a DHCPREQUEST
+
+  2014-02-27: Simon Goldschmidt
+  * raw.c: fixed bug #41680 raw socket can not receive IPv6 packet when
+    IP_SOF_BROADCAST_RECV==1
+
+  2014-02-27: Simon Goldschmidt
+  * api_msg.c, sockets.c: fixed bug #38404 getpeeraddr returns success on
+    unconnected/listening TCP sockets
+
+  2014-02-27: Simon Goldschmidt
+  * sockets.c: fixed bug #41729 Some socket functions return Exyz instead of -1
+
+  2014-02-25: Simon Goldschmidt
+  * ip4.c: fixed bug #39514 ip_route() may return an IPv6-only interface
+
+  2014-02-25: Simon Goldschmidt, patch by Fatih Asici
+  * pbuf.c: fixed bug #39356 Wrong increment in pbuf_memfind()
+
+  2014-02-25: Simon Goldschmidt
+  * netif.c/.h, udp.c: fixed bug #39225 udp.c uses netif_matches_ip6_addr() incorrectly;
+    renamed function netif_matches_ip6_addr() to netif_get_ip6_addr_match()
+
+  2014-02-25: Simon Goldschmidt
+  * igmp.c: fixed bug #39145 IGMP membership report for 224.0.0.1
+
+  2014-02-22: Simon Goldschmidt (patch by Amir Shalem)
+  * etharp.c, opt.h: fixed bug #34681 Limit ARP queue length by ARP_QUEUE_LEN (=3)
+
+  2014-02-22: Simon Goldschmidt (patch by Amir Shalem)
+  * etharp.h/.c: fixed bug #34682 Limit ARP request flood for unresolved entry
+
+  2014-02-20: Simon Goldschmidt
+  * tcp_out.c: fixed bug #39683 Assertion "seg->tcphdr not aligned" failed with
+    MEM_ALIGNMENT = 8
+
+  2014-02-20: Simon Goldschmidt
+  * sockets.c: fixed bug #39882 No function shall set errno to 0
+
+  2014-02-20: Simon Goldschmidt
+  * mib_structs.c: fixed bug #40050 SNMP problem with MIB arrays > 255
+
+  2014-02-20: Simon Goldschmidt
+  * api.h, sockets.c: fixed bug #41499 netconn::recv_avail can overflow
+
+  2014-01-08: Stathis Voukelatos
+  * memp_std.h: patch #7928 Fixed size calculation in MALLOC memory pool
+    creation macro
+
+  2014-01-18: Brian Fahs
+  * tcp_out.c: patch #8237: tcp_rexmit_rto fails to update pcb->unsent_oversize
+    when necessary
+
+  2014-01-17: Grant Erickson, Jay Logue, Simon Goldschmidt
+  * ipv6.c, netif.c: patch #7913 Enable Support for IPv6 Loopback
+
+  2014-01-16: Stathis Voukelatos
+  * netif.c: patch #7902 Fixed netif_poll() operation when LWIP_LOOPBACK_MAX_PBUFS > 0
+
+  2014-01-14: "Freddie Chopin"
+  * snmp.h, mib2.c: fixed constness and spelling of sysdescr
+
+  2014-01-14: Simon Goldschmidt (patch by Thomas Faber)
+  * tcpip.c: patch #8241: Fix implicit declaration of ip_input with
+    LWIP_TCPIP_CORE_LOCKING_INPUT disabled
+
+  2014-01-14: chrysn
+  * timers.c: patch #8244 make timeouts usable reliably from outside of the
+    timeout routine
+
+  2014-01-10: Simon Goldschmidt
+  * ip_frag.c, ip6_frag.c: fixed bug #41041 Potential use-after-free in IPv6 reassembly
+
+  2014-01-10: Simon Goldschmidt
+  * memp.c: fixed bug #41188 Alignment error in memp_init() when MEMP_SEPARATE_POOLS==1
+
+  2014-01-10: Simon Goldschmidt
+  * tcp.c: fixed bug #39898 tcp_fasttmr() possible lock due to infinte queue process loop
+
+  2013-06-29: Simon Goldschmidt
+  * inet.h, sockets.h: partially fixed bug #37585: IPv6 compatibility (in socket structs)
+
+  2013-06-29: Simon Goldschmidt
+  * inet6.h: bug #37585/task #12600: fixed struct in6_addr.s6_addr to conform to spec
+
+  2013-04-24: patch by Liam <morepork>
+  * api_msg.c: patch #8008 Fix a potential null pointer dereference in assert
+
+  2013-04-24: Simon Goldschmidt
+  * igmp.c: fixed possible division by zero
+
+  2013-04-24: Simon Goldschmidt
+  * ip6.h, some ipv6 C files: fixed bug #38526 Coverity: Recursive Header Inclusion in ip6.h
+
+  2013-04-24: Simon Goldschmidt (patch by Emil Ljungdahl):
+  * netif.c: fixed bug #38586 netif_loop_output() "deadlocks"
+
+  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-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-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-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
+
+  2011-08-24: Simon Goldschmidt
+  * inet6.h: fixed bug #34124 struct in6_addr does not conform to the standard
+
+
+
+(STABLE-1.4.1)
+
+  ++ 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-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-14: Simon Goldschmidt (patch by Stéphane Lesage)
+  * tcpip.c/.h: patch #7449 allow tcpip callback from interrupt with static
+    memory message
+
+
+  ++ Bugfixes:
+
+  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-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-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-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
+  * 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"
+
+   2011-05-25: Simon Goldschmidt
+  * tcp.c: fixed bug #33398 (pointless conversion when checking TCP port range)
+
+
+
+(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@consultant.datasys.swri.edu>
+
+  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@hmgsl.com). 
+    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@mmlab.cse.yzu.edu.tw
+
+  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@gmx.net>
+  * 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@gmx.net>
+   * 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@gmx.net>
+  * 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@gmx.net>
+  * 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@gmx.net>
+  * 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@gmx.net>
+  * 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@gmx.net>
+  * pbuf.c: Fixed missing semicolon after LWIP_DEBUG statement.
+
+  2005-01-03 Leon Woestenberg <leon.woestenberg@gmx.net>
+  * udp.c: UDP pcb->recv() was called even when it was NULL.
+
+(STABLE-1_1_0)
+
+  2004-12-28 Leon Woestenberg <leon.woestenberg@gmx.net>
+  * etharp.*: Disabled multiple packets on the ARP queue.
+    This clashes with TCP queueing.
+
+  2004-11-28 Leon Woestenberg <leon.woestenberg@gmx.net>
+  * 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@gmx.net>
+  * 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@cam.ac.uk>
+  * 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@cam.ac.uk>
+  * 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@cam.ac.uk>
+  * tcp*.*: Retransmit time-out handling improvement by Sam Jansen.
+
+  2004-08-20 Tony Mountifield <tony@softins.co.uk>
+  * etharp.c: Make sure the first pbuf queued on an ARP entry
+    is properly ref counted.
+
+  2004-07-27 Tony Mountifield <tony@softins.co.uk>
+  * 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@softins.co.uk>
+  * 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@softins.co.uk>
+  * 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@gmx.net>
+  * 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@gmx.net>
+  * netif.*: Added netif_set_up()/down().
+  * dhcp.c: Changes to restart program flow.
+
+  2004-05-07 Leon Woestenberg <leon.woestenberg@gmx.net>
+  * 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@gmx.net>
+  * 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@gmx.net>
+  * 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@gmx.net>
+  * 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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..0952b04
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at https://www.st.com/content/st_com/en/contact-us.html. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..4d1e4a1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,31 @@
+## Contributing guide
+This document serves as a checklist before contributing to this repository.
+It includes links to read up on if topics are unclear to you.
+
+This guide mainly focuses on the proper use of Git.
+
+### 1. Before opening an issue
+Please check the following boxes before posting an issue:
+- [ ] `Make sure you are using the latest commit (major releases are Tagged, but corrections are available as new commits).`
+- [ ] `Make sure your issue is a question/feedback/suggestions RELATED TO the software provided in this repository.` Otherwise, it should be discussed on the [ST Community/STM32 MCUs forum](https://community.st.com/s/group/0F90X000000AXsASAW/stm32-mcus).
+- [ ] `Make sure your issue is not already reported/fixed on GitHub or discussed on a previous issue.` Please refer to this [dashboard](https://github.com/orgs/STMicroelectronics/projects/5) for the list of issues and pull-requests. Do not forget to browse into the **closed** issues.
+
+### 2. Posting the issue
+When you have checked the previous boxes. You will find two templates (Bug Report or Other Issue) available in the **Issues** tab of the repository ([link](https://github.com/STMicroelectronics/stm32_mw_lwip/issues/new/choose)).
+
+### 3. Pull Requests
+STMicrolectronics is happy to receive contributions from the community, based on an initial Contributor License Agreement (CLA) procedure.
+
+* If you are an individual writing original source code and you are sure **you own the intellectual property**, then you need to sign an Individual CLA (https://cla.st.com).
+* If you work for a company that wants also to allow you to contribute with your work, your company needs to provide a Corporate CLA (https://cla.st.com) mentioning your GitHub account name.
+* If you are not sure that a CLA (Individual or Corporate) has been signed for your GitHub account you can check here (https://cla.st.com).
+
+Please note that:
+* The Corporate CLA will always take precedence over the Individual CLA.
+* One CLA submission is sufficient, for any project proposed by STMicroelectronics.
+
+#### How to proceed
+
+* We recommend to engage first a communication thru an issue, in order to present your proposal. Just to confirm that it corresponds to STMicroelectronics domain or scope.
+* Then fork the project to your GitHub account to further develop your contribution. Please use the latest commit version.
+* Please, submit one Pull Request for one new feature or proposal. This will ease the analysis and the final merge if accepted.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..e23898b
--- /dev/null
+++ b/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@sics.se>
+ *
+ */
+
+
diff --git a/FILES b/FILES
new file mode 100644
index 0000000..e6e0998
--- /dev/null
+++ b/FILES
@@ -0,0 +1,5 @@
+src/      - The source code for the lwIP TCP/IP stack.
+doc/      - The documentation for lwIP.
+test/     - Some code to test whether the sources do what they should.
+
+See also the FILES file in each subdirectory.
diff --git a/License.md b/License.md
new file mode 100644
index 0000000..d91bddb
--- /dev/null
+++ b/License.md
@@ -0,0 +1,3 @@
+# Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+
+This software component is licensed under the **BSD-3-Clause** license. You may not use this file except in compliance with this license. You may obtain a copy of the license [here](https://opensource.org/licenses/BSD-3-Clause).
diff --git a/README b/README
new file mode 100644
index 0000000..0884d27
--- /dev/null
+++ b/README
@@ -0,0 +1,100 @@
+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, IPv4 and IPv6) 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
+  * MLD (Multicast listener discovery for IPv6). Aims to be compliant with 
+    RFC 2710. No support for MLDv2
+  * ND (Neighbor discovery and stateless address autoconfiguration for IPv6).
+    Aims to be compliant with RFC 4861 (Neighbor discovery) and RFC 4862
+    (Address autoconfiguration)
+  * UDP (User Datagram Protocol) including experimental UDP-lite extensions
+  * TCP (Transmission Control Protocol) with congestion control, RTT estimation
+    and fast recovery/fast retransmit
+  * raw/native API for enhanced performance
+  * Optional Berkeley-like socket API
+  * DNS (Domain names resolver)
+
+
+APPLICATIONS
+
+  * HTTP server with SSI and CGI
+  * SNMPv2c agent with MIB compiler (Simple Network Management Protocol)
+  * SNTP (Simple network time protocol)
+  * NetBIOS name service responder
+  * MDNS (Multicast DNS) responder
+  * iPerf server implementation
+
+
+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, Git and the
+mailing list. A core team of developers will commit changes to the
+Git source tree.
+
+The lwIP TCP/IP stack is maintained in the 'lwip' Git module and
+contributions (such as platform ports) are in the 'contrib' Git module.
+
+See doc/savannah.txt for details on Git server access for users and
+developers.
+
+The current Git trees are web-browsable:
+  http://git.savannah.gnu.org/cgit/lwip.git
+  http://git.savannah.gnu.org/cgit/lwip/lwip-contrib.git
+
+Submit patches and bugs via the lwIP project page:
+  http://savannah.nongnu.org/projects/lwip/
+
+Continuous integration builds (GCC, clang):
+  https://travis-ci.org/yarrick/lwip-merged
+
+
+DOCUMENTATION
+
+Self documentation of the source code is regularly extracted from the current
+Git sources and is available from this web page:
+  http://www.nongnu.org/lwip/
+
+There is now a constantly growing 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/
+
+lwIP was originally written by Adam Dunkels:
+  http://dunkels.com/adam/
+
+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@sics.se>
+Leon Woestenberg <leon.woestenberg@gmx.net>
diff --git a/README.md b/README.md
index f7a0a22..a0ac608 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,50 @@
-# mw_lwip
\ No newline at end of file
+# Middleware LwIP MCU Component
+
+## Overview
+
+**STM32Cube** is an STMicroelectronics original initiative to ease the developers life by reducing efforts, time and cost.
+
+**STM32Cube** covers the overall STM32 products portfolio. It includes a comprehensive embedded software platform, delivered for each STM32 series.
+   * The CMSIS modules (core and device) corresponding to the ARM(tm) core implemented in this STM32 product.
+   * The STM32 HAL-LL drivers : an abstraction drivers layer, the API ensuring maximized portability across the STM32 portfolio.
+   * The BSP Drivers of each evaluation or demonstration board provided by this STM32 series.
+   * A consistent set of middlewares components such as RTOS, USB, FatFS, Graphics, STM32_TouchSensing_Library...
+   * A full set of software projects (basic examples, applications or demonstrations) for each board provided by this STM32 series.
+
+Two models of publication are proposed for the STM32Cube embedded software:
+   * The monolithic **MCU Package** : all STM32Cube software modules of one STM32 series are present (Drivers, Middlewares, Projects, Utilities) in the repo (usual name **STM32Cubexx**, xx corresponding to the STM32 series).
+   * The **MCU component** : progressively from November 2019, each STM32Cube software module being part of the STM32Cube MCU Package, will be delivered as an individual repo, allowing the user to select and get only the required software functions.
+
+## Description
+
+This **stm32_mw_lwip** MCU component repository is one element **common to all** STM32Cube MCU embedded software packages, providing the **LwIP MCU Middleware** part.
+
+## License
+
+Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+
+This software component is licensed under the **BSD-3-Clause** license. You may not use this file except in compliance with this license. You may obtain a copy of the license [here](https://opensource.org/licenses/BSD-3-Clause).
+
+## Release note
+
+Details about the content of this release are available in the release note [here](https://github.com/STMicroelectronics/stm32_mw_lwip/blob/master/CHANGELOG).
+
+Details about the content of this release are available in the release note [here](https://github.com/STMicroelectronics/stm32_mw_lwip/blob/master/st_readme.txt).
+
+## Compatibility information
+
+This table shows the correspondence between the LwIP MW version and the corresponding HAL version of the targeted series. It is **crucial** that you use a consistent set of versions for the MW - HAL, as mentioned in this table.
+
+Note that:
+* in case a series does not support a particular tag, it is not mentioned in front of it.
+* in case a series supports a recent tag, it is not mentioned in front of older ones.
+
+LwIP | HAL |
+---------- | ---------- |
+Tag v2.0.3 | Tag v1.1.4 ([stm32f1xx_hal_driver](https://github.com/STMicroelectronics/stm32f1xx_hal_driver))<br>Tag v1.2.4 ([stm32f2xx_hal_driver](https://github.com/STMicroelectronics/stm32f2xx_hal_driver))<br>
+
+## Troubleshooting
+
+If you have any issue with the **software content** of this repository, you can file an issue [here](https://github.com/STMicroelectronics/stm32_mw_lwip/issues/new/choose).
+
+For any other question related to the product, the tools, the environment, you can submit a topic to the [ST Community](https://community.st.com/s/).
diff --git a/UPGRADING b/UPGRADING
new file mode 100644
index 0000000..60a0438
--- /dev/null
+++ b/UPGRADING
@@ -0,0 +1,243 @@
+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.
+
+
+(git master)
+
+  * [Enter new changes just after this line - do not remove this line]
+
+(2.0.2)
+
+  ++ Application changes:
+
+  * slipif: The way to pass serial port number has changed. netif->num is not
+    supported any more, netif->state is interpreted as an u8_t port number now
+    (it's not a POINTER to an u8_t any more!)
+
+(2.0.1)
+
+  ++ Application changes:
+
+  * UDP does NOT receive multicast traffic from ALL netifs on an UDP PCB bound to a specific
+    netif any more. Users need to bind to IP_ADDR_ANY to receive multicast traffic and compare 
+    ip_current_netif() to the desired netif for every packet.
+    See bug #49662 for an explanation.
+
+(2.0.0)
+
+  ++ Application changes:
+
+  * Changed netif "up" flag handling to be an administrative flag (as opposed to the previous meaning of
+    "ip4-address-valid", a netif will now not be used for transmission if not up) -> even a DHCP netif
+    has to be set "up" before starting the DHCP client
+  * Added IPv6 support (dual-stack or IPv4/IPv6 only)
+  * Changed ip_addr_t to be a union in dual-stack mode (use ip4_addr_t where referring to IPv4 only).
+  * Major rewrite of SNMP (added MIB parser that creates code stubs for custom MIBs);
+    supports SNMPv2c (experimental v3 support)
+  * Moved some core applications from contrib repository to src/apps (and include/lwip/apps)
+
+  +++ Raw API:
+    * Changed TCP listen backlog: removed tcp_accepted(), added the function pair tcp_backlog_delayed()/
+      tcp_backlog_accepted() to explicitly delay backlog handling on a connection pcb
+
+  +++ Socket API:
+    * Added an implementation for posix sendmsg()
+    * Added LWIP_FIONREAD_LINUXMODE that makes ioctl/FIONREAD return the size of the next pending datagram
+
+  ++ Port changes
+
+  +++ new files:
+    * MANY new and moved files! 
+    * Added src/Filelists.mk for use in Makefile projects
+    * Continued moving stack-internal parts from abc.h to abc_priv.h in sub-folder "priv"
+      to let abc.h only contain the actual application programmer's API
+
+  +++ sys layer:
+    * Made LWIP_TCPIP_CORE_LOCKING==1 the default as it usually performs better than
+      the traditional message passing (although with LWIP_COMPAT_MUTEX you are still
+      open to priority inversion, so this is not recommended any more)
+    * Added LWIP_NETCONN_SEM_PER_THREAD to use one "op_completed" semaphore per thread
+      instead of using one per netconn (these semaphores are used even with core locking
+      enabled as some longer lasting functions like big writes still need to delay)
+    * Added generalized abstraction for itoa(), strnicmp(), stricmp() and strnstr()
+      in def.h (to be overridden in cc.h) instead of config 
+      options for netbiosns, httpd, dns, etc. ...
+    * New abstraction for hton* and ntoh* functions in def.h.
+      To override them, use the following in cc.h: 
+      #define lwip_htons(x) <your_htons>
+      #define lwip_htonl(x) <your_htonl>
+
+  +++ new options:
+     * TODO
+
+  +++ new pools:
+     * Added LWIP_MEMPOOL_* (declare/init/alloc/free) to declare private memp pools
+       that share memp.c code but do not have to be made global via lwippools.h
+     * Added pools for IPv6, MPU_COMPATIBLE, dns-api, netif-api, etc.
+     * added hook LWIP_HOOK_MEMP_AVAILABLE() to get informed when a memp pool was empty and an item
+       is now available
+
+  * Signature of LWIP_HOOK_VLAN_SET macro was changed
+
+  * LWIP_DECLARE_MEMORY_ALIGNED() may be used to declare aligned memory buffers (mem/memp)
+    or to move buffers to dedicated memory using compiler attributes
+
+  * Standard C headers are used to define sized types and printf formatters
+    (disable by setting LWIP_NO_STDINT_H=1 or LWIP_NO_INTTYPES_H=1 if your compiler
+    does not support these)
+
+
+  ++ Major bugfixes/improvements
+
+  * Added IPv6 support (dual-stack or IPv4/IPv6 only)
+  * Major rewrite of PPP (incl. keep-up with apache pppd)
+    see doc/ppp.txt for an upgrading how-to
+  * Major rewrite of SNMP (incl. MIB parser)
+  * Fixed timing issues that might have lead to losing a DHCP lease
+  * Made rx processing path more robust against crafted errors
+  * TCP window scaling support
+  * modification of api modules to support FreeRTOS-MPU (don't pass stack-pointers to other threads)
+  * made DNS client more robust
+  * support PBUF_REF for RX packets
+  * LWIP_NETCONN_FULLDUPLEX allows netconn/sockets to be used for reading/writing from separate
+    threads each (needs LWIP_NETCONN_SEM_PER_THREAD)
+  * Moved and reordered stats (mainly memp/mib2)
+
+(1.4.0)
+
+  ++ 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/doc/FILES b/doc/FILES
new file mode 100644
index 0000000..e588575
--- /dev/null
+++ b/doc/FILES
@@ -0,0 +1,9 @@
+doxygen/       - Configuration files and scripts to create the lwIP doxygen source
+                 documentation (found at http://www.nongnu.org/lwip/)
+
+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.
+sys_arch.txt   - The documentation for a system abstraction layer of lwIP.
+ppp.txt        - Documentation of the PPP interface for lwIP.
diff --git a/doc/NO_SYS_SampleCode.c b/doc/NO_SYS_SampleCode.c
new file mode 100644
index 0000000..f0af660
--- /dev/null
+++ b/doc/NO_SYS_SampleCode.c
@@ -0,0 +1,122 @@
+void
+eth_mac_irq()
+{
+  /* Service MAC IRQ here */
+
+  /* Allocate pbuf from pool (avoid using heap in interrupts) */
+  struct pbuf* p = pbuf_alloc(PBUF_RAW, eth_data_count, PBUF_POOL);
+
+  if(p != NULL) {
+    /* Copy ethernet frame into pbuf */
+    pbuf_take(p, eth_data, eth_data_count);
+
+    /* Put in a queue which is processed in main loop */
+    if(!queue_try_put(&queue, p)) {
+      /* queue is full -> packet loss */
+      pbuf_free(p);
+    }
+  }
+}
+
+static err_t 
+netif_output(struct netif *netif, struct pbuf *p)
+{
+  LINK_STATS_INC(link.xmit);
+
+  /* Update SNMP stats (only if you use SNMP) */
+  MIB2_STATS_NETIF_ADD(netif, ifoutoctets, p->tot_len);
+  int unicast = ((p->payload[0] & 0x01) == 0);
+  if (unicast) {
+    MIB2_STATS_NETIF_INC(netif, ifoutucastpkts);
+  } else {
+    MIB2_STATS_NETIF_INC(netif, ifoutnucastpkts);
+  }
+
+  lock_interrupts();
+  pbuf_copy_partial(p, mac_send_buffer, p->tot_len, 0);
+  /* Start MAC transmit here */
+  unlock_interrupts();
+
+  return ERR_OK;
+}
+
+static void 
+netif_status_callback(struct netif *netif)
+{
+  printf("netif status changed %s\n", ip4addr_ntoa(netif_ip4_addr(netif)));
+}
+
+static err_t 
+netif_init(struct netif *netif)
+{
+  netif->linkoutput = netif_output;
+  netif->output     = etharp_output;
+  netif->output_ip6 = ethip6_output;
+  netif->mtu        = ETHERNET_MTU;
+  netif->flags      = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6;
+  MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, 100000000);
+
+  SMEMCPY(netif->hwaddr, your_mac_address_goes_here, sizeof(netif->hwaddr));
+  netif->hwaddr_len = sizeof(netif->hwaddr);
+
+  return ERR_OK;
+}
+
+void 
+main(void)
+{
+  struct netif netif;
+
+  lwip_init();
+
+  netif_add(&netif, IP4_ADDR_ANY, IP4_ADDR_ANY, IP4_ADDR_ANY, NULL, netif_init, netif_input);
+  netif.name[0] = 'e';
+  netif.name[1] = '0';
+  netif_create_ip6_linklocal_address(&netif, 1);
+  netif.ip6_autoconfig_enabled = 1;
+  netif_set_status_callback(&netif, netif_status_callback);
+  netif_set_default(&netif);
+  netif_set_up(&netif);
+  
+  /* Start DHCP and HTTPD */
+  dhcp_start(&netif );
+  httpd_init();
+
+  while(1) {
+    /* Check link state, e.g. via MDIO communication with PHY */
+    if(link_state_changed()) {
+      if(link_is_up()) {
+        netif_set_link_up(&netif);
+      } else {
+        netif_set_link_down(&netif);
+      }
+    }
+
+    /* Check for received frames, feed them to lwIP */
+    lock_interrupts();
+    struct pbuf* p = queue_try_get(&queue);
+    unlock_interrupts();
+
+    if(p != NULL) {
+      LINK_STATS_INC(link.recv);
+ 
+      /* Update SNMP stats (only if you use SNMP) */
+      MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len);
+      int unicast = ((p->payload[0] & 0x01) == 0);
+      if (unicast) {
+        MIB2_STATS_NETIF_INC(netif, ifinucastpkts);
+      } else {
+        MIB2_STATS_NETIF_INC(netif, ifinnucastpkts);
+      }
+
+      if(netif.input(p, &netif) != ERR_OK) {
+        pbuf_free(p);
+      }
+    }
+     
+    /* Cyclic lwIP timers check */
+    sys_check_timeouts();
+     
+    /* your application goes here */
+  }
+}
diff --git a/doc/contrib.txt b/doc/contrib.txt
new file mode 100644
index 0000000..6f0d7bc
--- /dev/null
+++ b/doc/contrib.txt
@@ -0,0 +1,58 @@
+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 Git 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 preferred 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. 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 Git access to submit and maintain your port in the contrib Git module.
diff --git a/doc/doxygen/generate.bat b/doc/doxygen/generate.bat
new file mode 100644
index 0000000..99afb12
--- /dev/null
+++ b/doc/doxygen/generate.bat
@@ -0,0 +1 @@
+doxygen lwip.Doxyfile
diff --git a/doc/doxygen/generate.sh b/doc/doxygen/generate.sh
new file mode 100644
index 0000000..89344b0
--- /dev/null
+++ b/doc/doxygen/generate.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+doxygen lwip.Doxyfile
diff --git a/doc/doxygen/lwip.Doxyfile b/doc/doxygen/lwip.Doxyfile
new file mode 100644
index 0000000..95fa363
--- /dev/null
+++ b/doc/doxygen/lwip.Doxyfile
@@ -0,0 +1,2505 @@
+# Doxyfile 1.8.11
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "lwIP"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = "2.0.3"
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Lightweight IP stack"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = output
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class       " \
+                         "The $name widget       " \
+                         "The $name file       " \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        = ../../
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = YES
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = YES
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = NO
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text       "
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = main_page.h ../../src
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
+# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.idl \
+                         *.odl \
+                         *.inc \
+                         *.m \
+                         *.mm \
+                         *.dox
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                = ../../src/include/netif/ppp/polarssl
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           = ../ ../../
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE = main_page.h
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = NO
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse-libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = NO
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               = lwip.chm
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = YES
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           = ../../src/include
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  = *.h
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             = __DOXYGEN__=1 \
+                         NO_SYS=0 \
+                         SYS_LIGHTWEIGHT_PROT=1 \
+                         LWIP_TCPIP_CORE_LOCKING=1 \
+                         LWIP_IPV4=1 \
+                         LWIP_IPV6=1 \
+                         LWIP_ICMP=1 \
+                         LWIP_RAW=1 \
+                         LWIP_DHCP=1 \
+                         LWIP_UDPLITE=1 \
+                         LWIP_UDP=1 \
+                         LWIP_IGMP=1 \
+                         LWIP_TCP=1 \
+                         TCP_LISTEN_BACKLOG=1 \
+                         LWIP_SNMP=1 \
+                         SNMP_USE_NETCONN=1 \
+                         SNMP_USE_RAW=1 \
+                         MIB2_STATS=1 \
+                         LWIP_MDNS_RESPONDER=1 \
+                         MEMP_OVERFLOW_CHECK=0 \
+                         MEMP_SANITY_CHECK=1 \
+                         LWIP_ARP=1 \
+                         LWIP_HAVE_LOOPIF=1 \
+                         LWIP_NETIF_HOSTNAME=1 \
+                         LWIP_NETIF_API=1 \
+                         LWIP_NETIF_CALLBACK=1 \
+                         LWIP_NETIF_STATUS_CALLBACK=1 \
+                         LWIP_NETIF_REMOVE_CALLBACK=1 \
+                         LWIP_NETIF_LINK_CALLBACK=1 \
+                         LWIP_NUM_NETIF_CLIENT_DATA=1 \
+                         ENABLE_LOOPBACK=1 \
+                         LWIP_AUTOIP=1 \
+                         ARP_QUEUEING=1 \
+                         LWIP_STATS=1 \
+                         MEM_USE_POOLS=0 \
+                         LWIP_DNS=1 \
+                         LWIP_SOCKET=1 \
+                         LWIP_NETCONN=1 \
+                         IP_SOF_BROADCAST=1 \
+                         IP_SOF_BROADCAST_RECV=1 \
+                         LWIP_NETIF_API=1 \
+                         LWIP_SO_SNDTIMEO=1 \
+                         LWIP_SO_RCVBUF=1 \
+                         LWIP_SO_LINGER=1 \
+                         SO_REUSE=1 \
+                         SO_REUSE_RXTOALL=1 \
+                         LWIP_HAVE_SLIPIF=1 \
+                         LWIP_6LOWPAN=1
+ 
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 1000
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
diff --git a/doc/doxygen/main_page.h b/doc/doxygen/main_page.h
new file mode 100644
index 0000000..d35d16e
--- /dev/null
+++ b/doc/doxygen/main_page.h
@@ -0,0 +1,132 @@
+/**
+ * @defgroup lwip lwIP
+ *
+ * @defgroup infrastructure Infrastructure
+ *
+ * @defgroup callbackstyle_api Callback-style APIs
+ * Non thread-safe APIs, callback style for maximum performance and minimum
+ * memory footprint.
+ * 
+ * @defgroup sequential_api Sequential-style APIs
+ * Sequential-style APIs, blocking functions. More overhead, but can be called
+ * from any thread except TCPIP thread.
+ * 
+ * @defgroup addons Addons
+ * 
+ * @defgroup apps Applications
+ */
+
+/**
+ * @mainpage Overview
+ * @verbinclude "README"
+ */
+
+/**
+ * @page upgrading Upgrading
+ * @verbinclude "UPGRADING"
+ */
+
+/**
+ * @page changelog Changelog
+ * @verbinclude "CHANGELOG"
+ */
+
+/**
+ * @page contrib How to contribute to lwIP
+ * @verbinclude "contrib.txt"
+ */
+
+/**
+ * @page pitfalls Common pitfalls
+ *
+ * Multiple Execution Contexts in lwIP code
+ * ========================================
+ *
+ * The most common source of lwIP problems is to have multiple execution contexts
+ * inside the lwIP code.
+ * 
+ * lwIP can be used in two basic modes: @ref lwip_nosys (no OS/RTOS 
+ * running on target system) or @ref lwip_os (there is an OS running
+ * on the target system).
+ *
+ * Mainloop Mode
+ * -------------
+ * In mainloop mode, only @ref callbackstyle_api can be used.
+ * The user has two possibilities to ensure there is only one 
+ * exection context at a time in lwIP:
+ *
+ * 1) Deliver RX ethernet packets directly in interrupt context to lwIP
+ *    by calling netif->input directly in interrupt. This implies all lwIP 
+ *    callback functions are called in IRQ context, which may cause further
+ *    problems in application code: IRQ is blocked for a long time, multiple
+ *    execution contexts in application code etc. When the application wants
+ *    to call lwIP, it only needs to disable interrupts during the call.
+ *    If timers are involved, even more locking code is needed to lock out
+ *    timer IRQ and ethernet IRQ from each other, assuming these may be nested.
+ *
+ * 2) Run lwIP in a mainloop. There is example code here: @ref lwip_nosys.
+ *    lwIP is _ONLY_ called from mainloop callstacks here. The ethernet IRQ
+ *    has to put received telegrams into a queue which is polled in the
+ *    mainloop. Ensure lwIP is _NEVER_ called from an interrupt, e.g.
+ *    some SPI IRQ wants to forward data to udp_send() or tcp_write()!
+ *
+ * OS Mode
+ * -------
+ * In OS mode, @ref callbackstyle_api AND @ref sequential_api can be used.
+ * @ref sequential_api are designed to be called from threads other than
+ * the TCPIP thread, so there is nothing to consider here.
+ * But @ref callbackstyle_api functions must _ONLY_ be called from
+ * TCPIP thread. It is a common error to call these from other threads
+ * or from IRQ contexts. ​Ethernet RX needs to deliver incoming packets
+ * in the correct way by sending a message to TCPIP thread, this is
+ * implemented in tcpip_input().​​
+ * Again, ensure lwIP is _NEVER_ called from an interrupt, e.g.
+ * some SPI IRQ wants to forward data to udp_send() or tcp_write()!
+ * 
+ * 1) tcpip_callback() can be used get called back from TCPIP thread,
+ *    it is safe to call any @ref callbackstyle_api from there.
+ *
+ * 2) Use @ref LWIP_TCPIP_CORE_LOCKING. All @ref callbackstyle_api
+ *    functions can be called when lwIP core lock is aquired, see
+ *    @ref LOCK_TCPIP_CORE() and @ref UNLOCK_TCPIP_CORE().
+ *    These macros cannot be used in an interrupt context!
+ *    Note the OS must correctly handle priority inversion for this.
+ */
+
+/**
+ * @page bugs Reporting bugs
+ * Please report bugs in the lwIP bug tracker at savannah.\n
+ * BEFORE submitting, please check if the bug has already been reported!\n
+ * https://savannah.nongnu.org/bugs/?group=lwip
+ */
+
+/**
+ * @defgroup lwip_nosys Mainloop mode ("NO_SYS")
+ * @ingroup lwip
+ * Use this mode if you do not run an OS on your system. \#define NO_SYS to 1.
+ * Feed incoming packets to netif->input(pbuf, netif) function from mainloop,
+ * *not* *from* *interrupt* *context*. You can allocate a @ref pbuf in interrupt
+ * context and put them into a queue which is processed from mainloop.\n
+ * Call sys_check_timeouts() periodically in the mainloop.\n
+ * Porting: implement all functions in @ref sys_time, @ref sys_prot and 
+ * @ref compiler_abstraction.\n
+ * You can only use @ref callbackstyle_api in this mode.\n
+ * Sample code:\n
+ * @include NO_SYS_SampleCode.c
+ */
+
+/**
+ * @defgroup lwip_os OS mode (TCPIP thread)
+ * @ingroup lwip
+ * Use this mode if you run an OS on your system. It is recommended to
+ * use an RTOS that correctly handles priority inversion and
+ * to use @ref LWIP_TCPIP_CORE_LOCKING.\n
+ * Porting: implement all functions in @ref sys_layer.\n
+ * You can use @ref callbackstyle_api together with @ref tcpip_callback,
+ * and all @ref sequential_api.
+ */
+
+/**
+ * @page raw_api lwIP API
+ * @verbinclude "rawapi.txt"
+ */
diff --git a/doc/doxygen/output/index.html b/doc/doxygen/output/index.html
new file mode 100644
index 0000000..a52e09f
--- /dev/null
+++ b/doc/doxygen/output/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>Redirection</title>
+  <meta http-equiv="refresh" content="0; url=html/index.html" />
+</head>
+<body>
+  <a href="html/index.html">index.html</a>
+</body>
+</html>
diff --git a/doc/doxygen_docs.zip b/doc/doxygen_docs.zip
new file mode 100644
index 0000000..6daa98a
--- /dev/null
+++ b/doc/doxygen_docs.zip
Binary files differ
diff --git a/doc/mdns.txt b/doc/mdns.txt
new file mode 100644
index 0000000..c322843
--- /dev/null
+++ b/doc/mdns.txt
@@ -0,0 +1,113 @@
+Multicast DNS for lwIP
+
+Author: Erik Ekman
+
+
+Note! The MDNS responder does not have all features required by the standards.
+See notes in src/apps/mdns/mdns.c for what is left. It is however usable in normal
+cases - but watch out if many devices on the same network try to use the same
+host/service instance names.
+
+
+How to enable:
+==============
+
+MDNS support does not depend on DNS.
+MDNS supports using IPv4 only, v6 only, or v4+v6.
+
+To enable MDNS responder, set
+  LWIP_MDNS_RESPONDER = 1
+in lwipopts.h and add src/apps/mdns/mdns.c to your list of files to build.
+
+The max number of services supported per netif is defined by MDNS_MAX_SERVICES,
+default is 1.
+
+Increase MEMP_NUM_UDP_PCB by 1. MDNS needs one PCB.
+Increase LWIP_NUM_NETIF_CLIENT_DATA by 1 (MDNS needs one entry on netif).
+
+MDNS with IPv4 requires LWIP_IGMP = 1, and preferably LWIP_AUTOIP = 1.
+MDNS with IPv6 requires LWIP_IPV6_MLD = 1, and that a link-local address is
+generated.
+
+The MDNS code puts its structs on the stack where suitable to reduce dynamic
+memory allocation. It may use up to 1kB of stack.
+
+MDNS needs a strncasecmp() implementation. If you have one, define
+LWIP_MDNS_STRNCASECMP to it. Otherwise the code will provide an implementation
+for you.
+
+
+How to use:
+===========
+
+Call mdns_resp_init() during system initialization.
+This opens UDP sockets on port 5353 for IPv4 and IPv6.
+
+
+To start responding on a netif, run
+  mdns_resp_add_netif(struct netif *netif, char *hostname, u32_t dns_ttl)
+
+The hostname will be copied. If this returns successfully, the netif will join
+the multicast groups and any MDNS/legacy DNS requests sent unicast or multicast
+to port 5353 will be handled:
+- <hostname>.local type A, AAAA or ANY returns relevant IP addresses
+- Reverse lookups (PTR in-addr.arpa, ip6.arpa) of netif addresses
+  returns <hostname>.local
+Answers will use the supplied TTL (in seconds)
+MDNS allows UTF-8 names, but it is recommended to stay within ASCII,
+since the default case-insensitive comparison assumes this.
+
+It is recommended to call this function after an IPv4 address has been set,
+since there is currently no check if the v4 address is valid.
+
+Call mdns_resp_netif_settings_changed() every time the IP address
+on the netif has changed.
+
+To stop responding on a netif, run
+  mdns_resp_remove_netif(struct netif *netif)
+
+
+Adding services:
+================
+
+The netif first needs to be registered. Then run
+  mdns_resp_add_service(struct netif *netif, char *name, char *service,
+      u16_t proto, u16_t port, u32_t dns_ttl,
+      service_get_txt_fn_t txt_fn, void *txt_userdata);
+
+The name and service pointers will be copied. Name refers to the name of the
+service instance, and service is the type of service, like _http
+proto can be DNSSD_PROTO_UDP or DNSSD_PROTO_TCP which represent _udp and _tcp.
+If this call returns successfully, the following queries will be answered:
+- _services._dns-sd._udp.local type PTR returns <service>.<proto>.local
+- <service>.<proto>.local type PTR returns <name>.<service>.<proto>.local
+- <name>.<service>.<proto>.local type SRV returns hostname and port of service
+- <name>.<service>.<proto>.local type TXT builds text strings by calling txt_fn
+  with the supplied userdata. The callback adds strings to the reply by calling
+  mdns_resp_add_service_txtitem(struct mdns_service *service, char *txt,
+   int txt_len). Example callback method:
+
+   static void srv_txt(struct mdns_service *service, void *txt_userdata)
+   {
+     res = mdns_resp_add_service_txtitem(service, "path=/", 6);
+     LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return);
+   }
+
+  Since a hostname struct is used for TXT storage each single item can be max
+  63 bytes long, and  the total max length (including length bytes for each
+  item) is 255 bytes.
+
+If your device runs a webserver on port 80, an example call might be:
+
+  mdns_resp_add_service(netif, "myweb", "_http"
+      DNSSD_PROTO_TCP, 80, 3600, srv_txt, NULL);
+
+which will publish myweb._http._tcp.local for any hosts looking for web servers,
+and point them to <hostname>.local:80
+
+Relevant information will be sent as additional records to reduce number of
+requests required from a client.
+
+Removing services is currently not supported. Services are removed when the
+netif is removed.
+
diff --git a/doc/mqtt_client.txt b/doc/mqtt_client.txt
new file mode 100644
index 0000000..3e67def
--- /dev/null
+++ b/doc/mqtt_client.txt
@@ -0,0 +1,162 @@
+MQTT client for lwIP
+
+Author: Erik Andersson
+
+Details of the MQTT protocol can be found at:
+http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html 
+
+-----------------------------------------------------------------
+1. Initial steps, reserve memory and make connection to server:
+
+1.1: Provide storage
+
+Static allocation:
+  mqtt_client_t static_client;
+  example_do_connect(&static_client);
+
+Dynamic allocation:
+  mqtt_client_t *client = mqtt_client_new();
+  if(client != NULL) {
+    example_do_connect(&client);
+  }
+  
+1.2: Establish Connection with server
+
+void example_do_connect(mqtt_client_t *client)
+{
+  struct mqtt_connect_client_info_t ci;
+  err_t err;
+  
+  /* Setup an empty client info structure */
+  memset(&ci, 0, sizeof(ci));
+  
+  /* Minimal amount of information required is client identifier, so set it here */ 
+  ci.client_id = "lwip_test";
+  
+  /* Initiate client and connect to server, if this fails immediately an error code is returned
+     otherwise mqtt_connection_cb will be called with connection result after attempting 
+     to establish a connection with the server. 
+     For now MQTT version 3.1.1 is always used */
+  
+  err = mqtt_client_connect(client, ip_addr, MQTT_PORT, mqtt_connection_cb, 0, &ci);
+  
+  /* For now just print the result code if something goes wrong
+  if(err != ERR_OK) {
+    printf("mqtt_connect return %d\n", err);
+  }
+}
+
+Connection to server can also be probed by calling mqtt_client_is_connected(client) 
+
+-----------------------------------------------------------------
+2. Implementing the connection status callback
+
+
+static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status)
+{
+  err_t err;
+  if(status == MQTT_CONNECT_ACCEPTED) {
+    printf("mqtt_connection_cb: Successfully connected\n");
+    
+    /* Setup callback for incoming publish requests */
+    mqtt_set_inpub_callback(client, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, arg);
+    
+    /* Subscribe to a topic named "subtopic" with QoS level 1, call mqtt_sub_request_cb with result */ 
+    err = mqtt_subscribe(client, "subtopic", 1, mqtt_sub_request_cb, arg);
+
+    if(err != ERR_OK) {
+      printf("mqtt_subscribe return: %d\n", err);
+    }
+  } else {
+    printf("mqtt_connection_cb: Disconnected, reason: %d\n", status);
+    
+    /* Its more nice to be connected, so try to reconnect */
+    example_do_connect(client);
+  }  
+}
+
+static void mqtt_sub_request_cb(void *arg, err_t result)
+{
+  /* Just print the result code here for simplicity, 
+     normal behaviour would be to take some action if subscribe fails like 
+     notifying user, retry subscribe or disconnect from server */
+  printf("Subscribe result: %d\n", result);
+}
+
+-----------------------------------------------------------------
+3. Implementing callbacks for incoming publish and data
+
+/* The idea is to demultiplex topic and create some reference to be used in data callbacks
+   Example here uses a global variable, better would be to use a member in arg
+   If RAM and CPU budget allows it, the easiest implementation might be to just take a copy of
+   the topic string and use it in mqtt_incoming_data_cb
+*/
+static int inpub_id;
+static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len)
+{
+  printf("Incoming publish at topic %s with total length %u\n", topic, (unsigned int)tot_len);
+
+  /* Decode topic string into a user defined reference */
+  if(strcmp(topic, "print_payload") == 0) {
+    inpub_id = 0;
+  } else if(topic[0] == 'A') {
+    /* All topics starting with 'A' might be handled at the same way */
+    inpub_id = 1;
+  } else {
+    /* For all other topics */
+    inpub_id = 2;
+  }
+}
+
+static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags)
+{
+  printf("Incoming publish payload with length %d, flags %u\n", len, (unsigned int)flags);
+
+  if(flags & MQTT_DATA_FLAG_LAST) {
+    /* Last fragment of payload received (or whole part if payload fits receive buffer
+       See MQTT_VAR_HEADER_BUFFER_LEN)  */
+
+    /* Call function or do action depending on reference, in this case inpub_id */
+    if(inpub_id == 0) {
+      /* Don't trust the publisher, check zero termination */
+      if(data[len-1] == 0) {
+        printf("mqtt_incoming_data_cb: %s\n", (const char *)data);
+      }
+    } else if(inpub_id == 1) {
+      /* Call an 'A' function... */
+    } else {
+      printf("mqtt_incoming_data_cb: Ignoring payload...\n");
+    }
+  } else {
+    /* Handle fragmented payload, store in buffer, write to file or whatever */
+  }
+}
+
+-----------------------------------------------------------------
+4. Using outgoing publish
+
+
+void example_publish(mqtt_client_t *client, void *arg)
+{
+  const char *pub_payload= "PubSubHubLubJub";
+  err_t err;
+  u8_t qos = 2; /* 0 1 or 2, see MQTT specification */
+  u8_t retain = 0; /* No don't retain such crappy payload... */
+  err = mqtt_publish(client, "pub_topic", pub_payload, strlen(pub_payload), qos, retain, mqtt_pub_request_cb, arg);
+  if(err != ERR_OK) {
+    printf("Publish err: %d\n", err);
+  }
+}
+
+/* Called when publish is complete either with sucess or failure */
+static void mqtt_pub_request_cb(void *arg, err_t result)
+{
+  if(result != ERR_OK) {
+    printf("Publish result: %d\n", result);
+  }
+}
+
+-----------------------------------------------------------------
+5. Disconnecting
+
+Simply call mqtt_disconnect(client)
diff --git a/doc/ppp.txt b/doc/ppp.txt
new file mode 100644
index 0000000..8b88b3a
--- /dev/null
+++ b/doc/ppp.txt
@@ -0,0 +1,529 @@
+PPP interface for lwIP
+
+Author: Sylvain Rochet
+
+Table of Contents:
+
+1 - Supported PPP protocols and features
+2 - Raw API PPP example for all protocols
+3 - PPPoS input path (raw API, IRQ safe API, TCPIP API)
+4 - Thread safe PPP API (PPPAPI)
+5 - Notify phase callback (PPP_NOTIFY_PHASE)
+6 - Upgrading from lwIP <= 1.4.x to lwIP >= 2.0.x
+
+
+
+1 Supported PPP protocols and features
+======================================
+
+Supported Low level protocols:
+* PPP over serial using HDLC-like framing, such as wired dialup modems
+  or mobile telecommunications GPRS/EDGE/UMTS/HSPA+/LTE modems
+* PPP over Ethernet, such as xDSL modems
+* PPP over L2TP (Layer 2 Tunneling Protocol) LAC (L2TP Access Concentrator),
+  IP tunnel over UDP, such as VPN access
+
+Supported auth protocols:
+* PAP, Password Authentication Protocol
+* CHAP, Challenge-Handshake Authentication Protocol, also known as CHAP-MD5
+* MSCHAPv1, Microsoft version of CHAP, version 1
+* MSCHAPv2, Microsoft version of CHAP, version 2
+* EAP, Extensible Authentication Protocol
+
+Supported address protocols:
+* IPCP, IP Control Protocol, IPv4 addresses negotiation
+* IP6CP, IPv6 Control Protocol, IPv6 link-local addresses negotiation
+
+Supported encryption protocols:
+* MPPE, Microsoft Point-to-Point Encryption
+
+Supported compression or miscellaneous protocols, for serial links only:
+* PFC, Protocol Field Compression
+* ACFC, Address-and-Control-Field-Compression
+* ACCM, Asynchronous-Control-Character-Map
+* VJ, Van Jacobson TCP/IP Header Compression
+
+
+
+2 Raw API PPP example for all protocols
+=======================================
+
+As usual, raw API for lwIP means the lightweight API which *MUST* only be used
+for NO_SYS=1 systems or called inside lwIP core thread for NO_SYS=0 systems.
+
+/*
+ * Globals
+ * =======
+ */
+
+/* The PPP control block */
+ppp_pcb *ppp;
+
+/* The PPP IP interface */
+struct netif ppp_netif;
+
+
+/*
+ * PPP status callback
+ * ===================
+ *
+ * PPP status callback is called on PPP status change (up, down, …) from lwIP
+ * core thread
+ */
+
+/* PPP status callback example */
+static void status_cb(ppp_pcb *pcb, int err_code, void *ctx) {
+  struct netif *pppif = ppp_netif(pcb);
+  LWIP_UNUSED_ARG(ctx);
+
+  switch(err_code) {
+    case PPPERR_NONE: {
+#if LWIP_DNS
+      const ip_addr_t *ns;
+#endif /* LWIP_DNS */
+      printf("status_cb: Connected\n");
+#if PPP_IPV4_SUPPORT
+      printf("   our_ipaddr  = %s\n", ipaddr_ntoa(&pppif->ip_addr));
+      printf("   his_ipaddr  = %s\n", ipaddr_ntoa(&pppif->gw));
+      printf("   netmask     = %s\n", ipaddr_ntoa(&pppif->netmask));
+#if LWIP_DNS
+      ns = dns_getserver(0);
+      printf("   dns1        = %s\n", ipaddr_ntoa(ns));
+      ns = dns_getserver(1);
+      printf("   dns2        = %s\n", ipaddr_ntoa(ns));
+#endif /* LWIP_DNS */
+#endif /* PPP_IPV4_SUPPORT */
+#if PPP_IPV6_SUPPORT
+      printf("   our6_ipaddr = %s\n", ip6addr_ntoa(netif_ip6_addr(pppif, 0)));
+#endif /* PPP_IPV6_SUPPORT */
+      break;
+    }
+    case PPPERR_PARAM: {
+      printf("status_cb: Invalid parameter\n");
+      break;
+    }
+    case PPPERR_OPEN: {
+      printf("status_cb: Unable to open PPP session\n");
+      break;
+    }
+    case PPPERR_DEVICE: {
+      printf("status_cb: Invalid I/O device for PPP\n");
+      break;
+    }
+    case PPPERR_ALLOC: {
+      printf("status_cb: Unable to allocate resources\n");
+      break;
+    }
+    case PPPERR_USER: {
+      printf("status_cb: User interrupt\n");
+      break;
+    }
+    case PPPERR_CONNECT: {
+      printf("status_cb: Connection lost\n");
+      break;
+    }
+    case PPPERR_AUTHFAIL: {
+      printf("status_cb: Failed authentication challenge\n");
+      break;
+    }
+    case PPPERR_PROTOCOL: {
+      printf("status_cb: Failed to meet protocol\n");
+      break;
+    }
+    case PPPERR_PEERDEAD: {
+      printf("status_cb: Connection timeout\n");
+      break;
+    }
+    case PPPERR_IDLETIMEOUT: {
+      printf("status_cb: Idle Timeout\n");
+      break;
+    }
+    case PPPERR_CONNECTTIME: {
+      printf("status_cb: Max connect time reached\n");
+      break;
+    }
+    case PPPERR_LOOPBACK: {
+      printf("status_cb: Loopback detected\n");
+      break;
+    }
+    default: {
+      printf("status_cb: Unknown error code %d\n", err_code);
+      break;
+    }
+  }
+
+/*
+ * This should be in the switch case, this is put outside of the switch
+ * case for example readability.
+ */
+
+  if (err_code == PPPERR_NONE) {
+    return;
+  }
+
+  /* ppp_close() was previously called, don't reconnect */
+  if (err_code == PPPERR_USER) {
+    /* ppp_free(); -- can be called here */
+    return;
+  }
+
+  /*
+   * Try to reconnect in 30 seconds, if you need a modem chatscript you have
+   * to do a much better signaling here ;-)
+   */
+  ppp_connect(pcb, 30);
+  /* OR ppp_listen(pcb); */
+}
+
+
+/*
+ * Creating a new PPPoS session
+ * ============================
+ *
+ * In lwIP, PPPoS is not PPPoSONET, in lwIP PPPoS is PPPoSerial.
+ */
+
+#include "netif/ppp/pppos.h"
+
+/*
+ * PPPoS serial output callback
+ *
+ * ppp_pcb, PPP control block
+ * data, buffer to write to serial port
+ * len, length of the data buffer
+ * ctx, optional user-provided callback context pointer
+ *
+ * Return value: len if write succeed
+ */
+static u32_t output_cb(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) {
+  return uart_write(UART, data, len);
+}
+
+/*
+ * Create a new PPPoS interface
+ *
+ * ppp_netif, netif to use for this PPP link, i.e. PPP IP interface
+ * output_cb, PPPoS serial output callback
+ * status_cb, PPP status callback, called on PPP status change (up, down, …)
+ * ctx_cb, optional user-provided callback context pointer
+ */
+ppp = pppos_create(&ppp_netif,
+       output_cb, status_cb, ctx_cb);
+
+
+/*
+ * Creating a new PPPoE session
+ * ============================
+ */
+
+#include "netif/ppp/pppoe.h"
+
+/*
+ * Create a new PPPoE interface
+ *
+ * ppp_netif, netif to use for this PPP link, i.e. PPP IP interface
+ * ethif, already existing and setup Ethernet interface to use
+ * service_name, PPPoE service name discriminator (not supported yet)
+ * concentrator_name, PPPoE concentrator name discriminator (not supported yet)
+ * status_cb, PPP status callback, called on PPP status change (up, down, …)
+ * ctx_cb, optional user-provided callback context pointer
+ */
+ppp = pppoe_create(&ppp_netif,
+       &ethif,
+       service_name, concentrator_name,
+       status_cb, ctx_cb);
+
+
+/*
+ * Creating a new PPPoL2TP session
+ * ===============================
+ */
+
+#include "netif/ppp/pppol2tp.h"
+
+/*
+ * Create a new PPPoL2TP interface
+ *
+ * ppp_netif, netif to use for this PPP link, i.e. PPP IP interface
+ * netif, optional already existing and setup output netif, necessary if you
+ *        want to set this interface as default route to settle the chicken
+ *        and egg problem with VPN links
+ * ipaddr, IP to connect to
+ * port, UDP port to connect to (usually 1701)
+ * secret, L2TP secret to use
+ * secret_len, size in bytes of the L2TP secret
+ * status_cb, PPP status callback, called on PPP status change (up, down, …)
+ * ctx_cb, optional user-provided callback context pointer
+ */
+ppp = pppol2tp_create(&ppp_netif,
+       struct netif *netif, ip_addr_t *ipaddr, u16_t port,
+       u8_t *secret, u8_t secret_len,
+       ppp_link_status_cb_fn link_status_cb, void *ctx_cb);
+
+
+/*
+ * Initiate PPP client connection
+ * ==============================
+ */
+
+/* Set this interface as default route */
+ppp_set_default(ppp);
+
+/*
+ * Basic PPP client configuration. Can only be set if PPP session is in the
+ * dead state (i.e. disconnected). We don't need to provide thread-safe
+ * equivalents through PPPAPI because those helpers are only changing
+ * structure members while session is inactive for lwIP core. Configuration
+ * only need to be done once.
+ */
+
+/* Ask the peer for up to 2 DNS server addresses. */
+ppp_set_usepeerdns(ppp, 1);
+
+/* Auth configuration, this is pretty self-explanatory */
+ppp_set_auth(ppp, PPPAUTHTYPE_ANY, "login", "password");
+
+/*
+ * Initiate PPP negotiation, without waiting (holdoff=0), can only be called
+ * if PPP session is in the dead state (i.e. disconnected).
+ */
+u16_t holdoff = 0;
+ppp_connect(ppp, holdoff);
+
+
+/*
+ * Initiate PPP server listener
+ * ============================
+ */
+
+/*
+ * Basic PPP server configuration. Can only be set if PPP session is in the
+ * dead state (i.e. disconnected). We don't need to provide thread-safe
+ * equivalents through PPPAPI because those helpers are only changing
+ * structure members while session is inactive for lwIP core. Configuration
+ * only need to be done once.
+ */
+ip4_addr_t addr;
+
+/* Set our address */
+IP4_ADDR(&addr, 192,168,0,1);
+ppp_set_ipcp_ouraddr(ppp, &addr);
+
+/* Set peer(his) address */
+IP4_ADDR(&addr, 192,168,0,2);
+ppp_set_ipcp_hisaddr(ppp, &addr);
+
+/* Set primary DNS server */
+IP4_ADDR(&addr, 192,168,10,20);
+ppp_set_ipcp_dnsaddr(ppp, 0, &addr);
+
+/* Set secondary DNS server */
+IP4_ADDR(&addr, 192,168,10,21);
+ppp_set_ipcp_dnsaddr(ppp, 1, &addr);
+
+/* Auth configuration, this is pretty self-explanatory */
+ppp_set_auth(ppp, PPPAUTHTYPE_ANY, "login", "password");
+
+/* Require peer to authenticate */
+ppp_set_auth_required(ppp, 1);
+
+/*
+ * Only for PPPoS, the PPP session should be up and waiting for input.
+ *
+ * Note: for PPPoS, ppp_connect() and ppp_listen() are actually the same thing.
+ * The listen call is meant for future support of PPPoE and PPPoL2TP server
+ * mode, where we will need to negotiate the incoming PPPoE session or L2TP
+ * session before initiating PPP itself. We need this call because there is
+ * two passive modes for PPPoS, ppp_set_passive and ppp_set_silent.
+ */
+ppp_set_silent(pppos, 1);
+
+/*
+ * Initiate PPP listener (i.e. wait for an incoming connection), can only
+ * be called if PPP session is in the dead state (i.e. disconnected).
+ */
+ppp_listen(ppp);
+
+
+/*
+ * Closing PPP connection
+ * ======================
+ */
+
+/*
+ * Initiate the end of the PPP session, without carrier lost signal
+ * (nocarrier=0), meaning a clean shutdown of PPP protocols.
+ * You can call this function at anytime.
+ */
+u8_t nocarrier = 0;
+ppp_close(ppp, nocarrier);
+/*
+ * Then you must wait your status_cb() to be called, it may takes from a few
+ * seconds to several tens of seconds depending on the current PPP state.
+ */
+
+/*
+ * Freeing a PPP connection
+ * ========================
+ */
+
+/*
+ * Free the PPP control block, can only be called if PPP session is in the
+ * dead state (i.e. disconnected). You need to call ppp_close() before.
+ */
+ppp_free(ppp);
+
+
+
+3 PPPoS input path (raw API, IRQ safe API, TCPIP API)
+=====================================================
+
+Received data on serial port should be sent to lwIP using the pppos_input()
+function or the pppos_input_tcpip() function.
+
+If NO_SYS is 1 and if PPP_INPROC_IRQ_SAFE is 0 (the default), pppos_input()
+is not IRQ safe and then *MUST* only be called inside your main loop.
+
+Whatever the NO_SYS value, if PPP_INPROC_IRQ_SAFE is 1, pppos_input() is IRQ
+safe and can be safely called from an interrupt context, using that is going
+to reduce your need of buffer if pppos_input() is called byte after byte in
+your rx serial interrupt.
+
+if NO_SYS is 0, the thread safe way outside an interrupt context is to use
+the pppos_input_tcpip() function to pass input data to the lwIP core thread
+using the TCPIP API. This is thread safe in all cases but you should avoid
+passing data byte after byte because it uses heavy locking (mailbox) and it
+allocates pbuf, better fill them !
+
+if NO_SYS is 0 and if PPP_INPROC_IRQ_SAFE is 1, you may also use pppos_input()
+from an RX thread, however pppos_input() is not thread safe by itself. You can
+do that *BUT* you should NEVER call pppos_connect(), pppos_listen() and
+ppp_free() if pppos_input() can still be running, doing this is NOT thread safe
+at all. Using PPP_INPROC_IRQ_SAFE from an RX thread is discouraged unless you
+really know what you are doing, your move ;-)
+
+
+/*
+ * Fonction to call for received data
+ *
+ * ppp, PPP control block
+ * buffer, input buffer
+ * buffer_len, buffer length in bytes
+ */
+void pppos_input(ppp, buffer, buffer_len);
+
+or
+
+void pppos_input_tcpip(ppp, buffer, buffer_len);
+
+
+
+4 Thread safe PPP API (PPPAPI)
+==============================
+
+There is a thread safe API for all corresponding ppp_* functions, you have to
+enable LWIP_PPP_API in your lwipopts.h file, then see
+include/netif/ppp/pppapi.h, this is actually pretty obvious.
+
+
+
+5 Notify phase callback (PPP_NOTIFY_PHASE)
+==========================================
+
+Notify phase callback, enabled using the PPP_NOTIFY_PHASE config option, let
+you configure a callback that is called on each PPP internal state change.
+This is different from the status callback which only warns you about
+up(running) and down(dead) events.
+
+Notify phase callback can be used, for example, to set a LED pattern depending
+on the current phase of the PPP session. Here is a callback example which
+tries to mimic what we usually see on xDSL modems while they are negotiating
+the link, which should be self-explanatory:
+
+static void ppp_notify_phase_cb(ppp_pcb *pcb, u8_t phase, void *ctx) {
+  switch (phase) {
+
+  /* Session is down (either permanently or briefly) */
+  case PPP_PHASE_DEAD:
+    led_set(PPP_LED, LED_OFF);
+    break;
+
+  /* We are between two sessions */
+  case PPP_PHASE_HOLDOFF:
+    led_set(PPP_LED, LED_SLOW_BLINK);
+    break;
+
+  /* Session just started */
+  case PPP_PHASE_INITIALIZE:
+    led_set(PPP_LED, LED_FAST_BLINK);
+    break;
+
+  /* Session is running */
+  case PPP_PHASE_RUNNING:
+    led_set(PPP_LED, LED_ON);
+    break;
+
+  default:
+    break;
+  }
+}
+
+
+
+6 Upgrading from lwIP <= 1.4.x to lwIP >= 2.0.x
+===============================================
+
+PPP API was fully reworked between 1.4.x and 2.0.x releases. However porting
+from previous lwIP version is pretty easy:
+
+* Previous PPP API used an integer to identify PPP sessions, we are now
+  using ppp_pcb* control block, therefore all functions changed from "int ppp"
+  to "ppp_pcb *ppp"
+
+* struct netif was moved outside the PPP structure, you have to provide a netif
+  for PPP interface in pppoX_create() functions
+
+* PPP session are not started automatically after you created them anymore,
+  you have to call ppp_connect(), this way you can configure the session before
+  starting it.
+
+* Previous PPP API used CamelCase, we are now using snake_case.
+
+* Previous PPP API mixed PPPoS and PPPoE calls, this isn't the case anymore,
+  PPPoS functions are now prefixed pppos_ and PPPoE functions are now prefixed
+  pppoe_, common functions are now prefixed ppp_.
+
+* New PPPERR_ error codes added, check you have all of them in your status
+  callback function
+
+* Only the following include files should now be used in user application:
+  #include "netif/ppp/pppapi.h"
+  #include "netif/ppp/pppos.h"
+  #include "netif/ppp/pppoe.h"
+  #include "netif/ppp/pppol2tp.h"
+
+  Functions from ppp.h can be used, but you don't need to include this header
+  file as it is already included by above header files.
+
+* PPP_INPROC_OWNTHREAD was broken by design and was removed, you have to create
+  your own serial rx thread
+
+* PPP_INPROC_MULTITHREADED option was misnamed and confusing and was renamed
+  PPP_INPROC_IRQ_SAFE, please read the "PPPoS input path" documentation above
+  because you might have been fooled by that
+
+* If you used tcpip_callback_with_block() on ppp_ functions you may wish to use
+  the PPPAPI API instead.
+
+* ppp_sighup and ppp_close functions were merged using an optional argument
+  "nocarrier" on ppp_close.
+
+* DNS servers are now only remotely asked if LWIP_DNS is set and if
+  ppp_set_usepeerdns() is set to true, they are now automatically registered
+  using the dns_setserver() function so you don't need to do that in the PPP
+  callback anymore.
+
+* PPPoS does not use the SIO API anymore, as such it now requires a serial
+  output callback in place of sio_write
+
+* PPP_MAXIDLEFLAG is now in ms instead of jiffies
diff --git a/doc/rawapi.txt b/doc/rawapi.txt
new file mode 100644
index 0000000..0cdfdce
--- /dev/null
+++ b/doc/rawapi.txt
@@ -0,0 +1,499 @@
+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 raw API (sometimes called native API) is an event-driven API designed
+to be used without an operating system that implements zero-copy send and
+receive. This API is also used by the core stack for interaction between
+the various protocols. It is the only API available when running lwIP
+without an operating system.
+
+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.
+
+** Multithreading
+
+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"). When running in a multithreaded
+environment, raw API functions MUST only be called from the core thread
+since raw API functions are not protected from concurrent access (aside
+from pbuf- and memory management functions). 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
+      - pppapi.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.
+
+      Netconn or Socket API functions are thread safe against the
+      core thread but they are not reentrant at the control block
+      granularity level. That is, a UDP or TCP control block must
+      not be shared among multiple threads without proper locking.
+
+      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.
+
+All 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.
+
+Do not confuse the lwIP raw API with raw Ethernet or IP sockets.
+The former is a way of interfacing the lwIP network stack (including
+TCP and UDP), the later refers to processing raw Ethernet or IP data
+instead of TCP connections or UDP packets.
+
+Raw API applications may never block since all packet processing
+(input and output) as well as timer processing (TCP mainly) is done
+in a single execution context.
+
+--- Callbacks
+
+Program execution is driven by callbacks functions, which are then
+invoked by the lwIP core when activity related to that application
+occurs. A particular application may register to be notified via a
+callback function for events such as incoming data available, outgoing
+data sent, error notifications, poll timer expiration, connection
+closed, etc. An application can provide a callback function to perform
+processing for any or all of these events. 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_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 omitted,
+    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.
+
+
+--- 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 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:
+
+- lwip_init()
+
+  Initialize the lwIP stack and all of its subsystems.
+
+- netif_add(struct netif *netif, const ip4_addr_t *ipaddr,
+            const ip4_addr_t *netmask, const ip4_addr_t *gw,
+            void *state, netif_init_fn init, netif_input_fn input)
+
+  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 its 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_link_up(struct netif *netif)
+
+  This is the hardware link state; e.g. whether cable is plugged for wired
+  Ethernet interface. This function must be called even if you don't know
+  the current state. Having link up and link down events is optional but
+  DHCP and IPv6 discover benefit well from those events.
+
+- netif_set_up(struct netif *netif)
+
+  This is the administrative (= software) state of the 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.
+  
+  You can peek in the netif->dhcp struct for the actual DHCP status.
+
+- sys_check_timeouts()
+
+  When the system is running, you have to periodically call
+  sys_check_timeouts() which will handle all timers for all protocols in
+  the stack; add this to your main loop or equivalent.
+
+
+--- 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_htons(x) <your_htons>
+#define lwip_htonl(x) <your_htonl>
+If you #define them to htons() and htonl(), you should
+#define LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS to prevent lwIP from
+defining hton*/ntoh* compatibility macros.
+
+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/doc/savannah.txt b/doc/savannah.txt
new file mode 100644
index 0000000..d7d19eb
--- /dev/null
+++ b/doc/savannah.txt
@@ -0,0 +1,120 @@
+Daily Use Guide for using Savannah for lwIP
+
+Table of Contents:
+
+1 - Obtaining lwIP from the Git repository
+2 - Committers/developers Git access using SSH
+3 - Merging a development branch to master branch
+4 - How to release lwIP
+
+
+
+1 Obtaining lwIP from the Git repository
+----------------------------------------
+
+To perform an anonymous Git clone of the master branch (this is where
+bug fixes and incremental enhancements occur), do this:
+ git clone git://git.savannah.nongnu.org/lwip.git
+
+Or, obtain a stable branch (updated with bug fixes only) as follows:
+ git clone --branch DEVEL-1_4_1 git://git.savannah.nongnu.org/lwip.git
+
+Or, obtain a specific (fixed) release as follows:
+ git clone --branch STABLE-1_4_1 git://git.savannah.nongnu.org/lwip.git
+
+
+2 Committers/developers Git access using SSH
+--------------------------------------------
+
+The Savannah server uses SSH (Secure Shell) protocol 2 authentication and encryption.
+As such, Git 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@git.sv.gnu.org
+
+If it tells you:
+ Linux vcs.savannah.gnu.org 2.6.32-5-xen-686 #1 SMP Wed Jun 17 17:10:03 UTC 2015 i686
+
+ Interactive shell login is not possible for security reasons.
+ VCS commands are allowed.
+ Last login: Tue May 15 23:10:12 2012 from 82.245.102.129
+ You tried to execute:
+ Sorry, you are not allowed to execute that command.
+ Shared connection to git.sv.gnu.org closed.
+
+then you could login; Savannah refuses to give you a shell - which is OK, as we
+are allowed to use SSH for Git only. Now, you should be able to do this:
+ git clone your_login@git.sv.gnu.org:/srv/git/lwip.git
+
+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 Git 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 https://savannah.nongnu.org/git/?group=lwip
+
+
+3 - Merging a development branch to master branch
+-------------------------------------------------
+
+Merging is a straightforward process in Git. How to merge all changes in a
+development branch since our last merge from main:
+
+Checkout the master branch:
+ git checkout master
+
+Merge the development branch to master:
+ git merge your-development-branch
+
+Resolve any conflict.
+
+Commit the merge result.
+ git commit -a
+
+Push your commits:
+ git push
+
+
+4 How to release lwIP
+---------------------
+
+First, tag the release using Git: (I use release number 1.4.1 throughout
+this example).
+ git tag -a STABLE-1_4_1
+
+Share the tag reference by pushing it to remote:
+ git push origin STABLE-1_4_1
+
+Prepare the release:
+ cp -r lwip lwip-1.4.1
+ rm -rf lwip-1.4.1/.git lwip-1.4.1/.gitattributes
+
+Archive the current directory using tar, gzip'd, bzip2'd and zip'd.
+ tar czvf lwip-1.4.1.tar.gz lwip-1.4.1
+ tar cjvf lwip-1.4.1.tar.bz2 lwip-1.4.1
+ zip -r lwip-1.4.1.zip lwip-1.4.1
+
+Now, sign the archives with a detached GPG binary signature as follows:
+ gpg -b lwip-1.4.1.tar.gz
+ gpg -b lwip-1.4.1.tar.bz2
+ gpg -b lwip-1.4.1.zip
+
+Upload these files using anonymous FTP:
+ ncftp ftp://savannah.gnu.org/incoming/savannah/lwip
+ ncftp> mput *1.4.1.*
+
+Additionally, you may post a news item on Savannah, like this:
+
+A new 1.4.1 release is now available here:
+http://savannah.nongnu.org/files/?group=lwip&highlight=1.4.1
+
+You will have to submit this via the user News interface, then approve
+this via the Administrator News interface.
diff --git a/doc/sys_arch.txt b/doc/sys_arch.txt
new file mode 100644
index 0000000..4dc727b
--- /dev/null
+++ b/doc/sys_arch.txt
@@ -0,0 +1,303 @@
+sys_arch interface for lwIP
+
+Author: Adam Dunkels
+        Simon Goldschmidt
+
+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, mailboxes and mutexes 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. Timer scheduling is implemented in lwIP, but can be implemented
+by the sys_arch port (LWIP_TIMERS_CUSTOM==1).
+
+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 should be implemented as a queue which allows multiple messages
+to be posted (implementing as a rendez-vous point where only one message can be
+posted at a time can have a highly negative impact on performance). 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". Mutexes are represented by the type "sys_mutex_t".
+lwIP does not place any restrictions on how these types are represented
+internally.
+
+Since lwIP 1.4.0, semaphore, mutexes 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.
+
+- void sys_mutex_new(sys_mutex_t *mutex)
+
+  Creates a new mutex. The mutex is allocated to the memory that 'mutex'
+  points to (which can be both a pointer or the actual OS structure).
+  If the mutex 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_mutex_free(sys_mutex_t *mutex)
+
+  Deallocates a mutex.
+
+- void sys_mutex_lock(sys_mutex_t *mutex)
+  
+  Blocks the thread until the mutex can be grabbed.
+
+- void sys_mutex_unlock(sys_mutex_t *mutex)
+
+  Releases the mutex previously locked through 'sys_mutex_lock()'.
+
+- void sys_mutex_valid(sys_mutex_t *mutex)
+
+  Returns 1 if the mutes 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_mutex_set_invalid(sys_mutex_t *mutex)
+
+  Invalidate a mutex so that sys_mutex_valid() returns 0.
+  ATTENTION: This does NOT mean that the mutex shall be deallocated:
+  sys_mutex_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.
+
+When lwIP is used from more than one context (e.g. from multiple threads OR from
+main-loop and from interrupts), the SYS_LIGHTWEIGHT_PROT protection SHOULD be enabled!
+
+- 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 careful 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/src/FILES b/src/FILES
new file mode 100644
index 0000000..0be0741
--- /dev/null
+++ b/src/FILES
@@ -0,0 +1,15 @@
+api/      - The code for the high-level wrapper API. Not needed if
+            you use the lowel-level call-back/raw API.
+
+apps/     - Higher layer applications that are specifically programmed
+            with the lwIP low-level 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.
+
+For more information on the various subdirectories, check the FILES
+file in each directory.
diff --git a/src/Filelists.mk b/src/Filelists.mk
new file mode 100644
index 0000000..7d30bb8
--- /dev/null
+++ b/src/Filelists.mk
@@ -0,0 +1,181 @@
+#
+# 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@sics.se>
+#
+
+# COREFILES, CORE4FILES: The minimum set of files needed for lwIP.
+COREFILES=$(LWIPDIR)/core/init.c \
+	$(LWIPDIR)/core/def.c \
+	$(LWIPDIR)/core/dns.c \
+	$(LWIPDIR)/core/inet_chksum.c \
+	$(LWIPDIR)/core/ip.c \
+	$(LWIPDIR)/core/mem.c \
+	$(LWIPDIR)/core/memp.c \
+	$(LWIPDIR)/core/netif.c \
+	$(LWIPDIR)/core/pbuf.c \
+	$(LWIPDIR)/core/raw.c \
+	$(LWIPDIR)/core/stats.c \
+	$(LWIPDIR)/core/sys.c \
+	$(LWIPDIR)/core/tcp.c \
+	$(LWIPDIR)/core/tcp_in.c \
+	$(LWIPDIR)/core/tcp_out.c \
+	$(LWIPDIR)/core/timeouts.c \
+	$(LWIPDIR)/core/udp.c
+
+CORE4FILES=$(LWIPDIR)/core/ipv4/autoip.c \
+	$(LWIPDIR)/core/ipv4/dhcp.c \
+	$(LWIPDIR)/core/ipv4/etharp.c \
+	$(LWIPDIR)/core/ipv4/icmp.c \
+	$(LWIPDIR)/core/ipv4/igmp.c \
+	$(LWIPDIR)/core/ipv4/ip4_frag.c \
+	$(LWIPDIR)/core/ipv4/ip4.c \
+	$(LWIPDIR)/core/ipv4/ip4_addr.c
+
+CORE6FILES=$(LWIPDIR)/core/ipv6/dhcp6.c \
+	$(LWIPDIR)/core/ipv6/ethip6.c \
+	$(LWIPDIR)/core/ipv6/icmp6.c \
+	$(LWIPDIR)/core/ipv6/inet6.c \
+	$(LWIPDIR)/core/ipv6/ip6.c \
+	$(LWIPDIR)/core/ipv6/ip6_addr.c \
+	$(LWIPDIR)/core/ipv6/ip6_frag.c \
+	$(LWIPDIR)/core/ipv6/mld6.c \
+	$(LWIPDIR)/core/ipv6/nd6.c
+
+# APIFILES: The files which implement the sequential and socket APIs.
+APIFILES=$(LWIPDIR)/api/api_lib.c \
+	$(LWIPDIR)/api/api_msg.c \
+	$(LWIPDIR)/api/err.c \
+	$(LWIPDIR)/api/netbuf.c \
+	$(LWIPDIR)/api/netdb.c \
+	$(LWIPDIR)/api/netifapi.c \
+	$(LWIPDIR)/api/sockets.c \
+	$(LWIPDIR)/api/tcpip.c
+
+# NETIFFILES: Files implementing various generic network interface functions
+NETIFFILES=$(LWIPDIR)/netif/ethernet.c \
+	$(LWIPDIR)/netif/slipif.c
+
+# SIXLOWPAN: 6LoWPAN
+SIXLOWPAN=$(LWIPDIR)/netif/lowpan6.c \
+
+# PPPFILES: PPP
+PPPFILES=$(LWIPDIR)/netif/ppp/auth.c \
+	$(LWIPDIR)/netif/ppp/ccp.c \
+	$(LWIPDIR)/netif/ppp/chap-md5.c \
+	$(LWIPDIR)/netif/ppp/chap_ms.c \
+	$(LWIPDIR)/netif/ppp/chap-new.c \
+	$(LWIPDIR)/netif/ppp/demand.c \
+	$(LWIPDIR)/netif/ppp/eap.c \
+	$(LWIPDIR)/netif/ppp/ecp.c \
+	$(LWIPDIR)/netif/ppp/eui64.c \
+	$(LWIPDIR)/netif/ppp/fsm.c \
+	$(LWIPDIR)/netif/ppp/ipcp.c \
+	$(LWIPDIR)/netif/ppp/ipv6cp.c \
+	$(LWIPDIR)/netif/ppp/lcp.c \
+	$(LWIPDIR)/netif/ppp/magic.c \
+	$(LWIPDIR)/netif/ppp/mppe.c \
+	$(LWIPDIR)/netif/ppp/multilink.c \
+	$(LWIPDIR)/netif/ppp/ppp.c \
+	$(LWIPDIR)/netif/ppp/pppapi.c \
+	$(LWIPDIR)/netif/ppp/pppcrypt.c \
+	$(LWIPDIR)/netif/ppp/pppoe.c \
+	$(LWIPDIR)/netif/ppp/pppol2tp.c \
+	$(LWIPDIR)/netif/ppp/pppos.c \
+	$(LWIPDIR)/netif/ppp/upap.c \
+	$(LWIPDIR)/netif/ppp/utils.c \
+	$(LWIPDIR)/netif/ppp/vj.c \
+	$(LWIPDIR)/netif/ppp/polarssl/arc4.c \
+	$(LWIPDIR)/netif/ppp/polarssl/des.c \
+	$(LWIPDIR)/netif/ppp/polarssl/md4.c \
+	$(LWIPDIR)/netif/ppp/polarssl/md5.c \
+	$(LWIPDIR)/netif/ppp/polarssl/sha1.c
+
+# LWIPNOAPPSFILES: All LWIP files without apps
+LWIPNOAPPSFILES=$(COREFILES) \
+	$(CORE4FILES) \
+	$(CORE6FILES) \
+	$(APIFILES) \
+	$(NETIFFILES) \
+	$(PPPFILES) \
+	$(SIXLOWPAN)
+
+# SNMPFILES: SNMPv2c agent
+SNMPFILES=$(LWIPDIR)/apps/snmp/snmp_asn1.c \
+	$(LWIPDIR)/apps/snmp/snmp_core.c \
+	$(LWIPDIR)/apps/snmp/snmp_mib2.c \
+	$(LWIPDIR)/apps/snmp/snmp_mib2_icmp.c \
+	$(LWIPDIR)/apps/snmp/snmp_mib2_interfaces.c \
+	$(LWIPDIR)/apps/snmp/snmp_mib2_ip.c \
+	$(LWIPDIR)/apps/snmp/snmp_mib2_snmp.c \
+	$(LWIPDIR)/apps/snmp/snmp_mib2_system.c \
+	$(LWIPDIR)/apps/snmp/snmp_mib2_tcp.c \
+	$(LWIPDIR)/apps/snmp/snmp_mib2_udp.c \
+	$(LWIPDIR)/apps/snmp/snmp_msg.c \
+	$(LWIPDIR)/apps/snmp/snmpv3.c \
+	$(LWIPDIR)/apps/snmp/snmp_netconn.c \
+	$(LWIPDIR)/apps/snmp/snmp_pbuf_stream.c \
+	$(LWIPDIR)/apps/snmp/snmp_raw.c \
+	$(LWIPDIR)/apps/snmp/snmp_scalar.c \
+	$(LWIPDIR)/apps/snmp/snmp_table.c \
+	$(LWIPDIR)/apps/snmp/snmp_threadsync.c \
+	$(LWIPDIR)/apps/snmp/snmp_traps.c \
+	$(LWIPDIR)/apps/snmp/snmpv3_mbedtls.c \
+	$(LWIPDIR)/apps/snmp/snmpv3_dummy.c
+
+# HTTPDFILES: HTTP server
+HTTPDFILES=$(LWIPDIR)/apps/httpd/fs.c \
+	$(LWIPDIR)/apps/httpd/httpd.c
+
+# LWIPERFFILES: IPERF server
+LWIPERFFILES=$(LWIPDIR)/apps/lwiperf/lwiperf.c
+
+# SNTPFILES: SNTP client
+SNTPFILES=$(LWIPDIR)/apps/sntp/sntp.c
+
+# MDNSFILES: MDNS responder
+MDNSFILES=$(LWIPDIR)/apps/mdns/mdns.c
+
+# NETBIOSNSFILES: NetBIOS name server
+NETBIOSNSFILES=$(LWIPDIR)/apps/netbiosns/netbiosns.c
+
+# TFTPFILES: TFTP server files
+TFTPFILES=$(LWIPDIR)/apps/tftp/tftp_server.c
+
+# MQTTFILES: MQTT client files
+MQTTFILES=$(LWIPDIR)/apps/mqtt/mqtt.c
+
+# LWIPAPPFILES: All LWIP APPs
+LWIPAPPFILES=$(SNMPFILES) \
+	$(HTTPDFILES) \
+	$(LWIPERFFILES) \
+	$(SNTPFILES) \
+	$(MDNSFILES) \
+	$(NETBIOSNSFILES) \
+	$(TFTPFILES) \
+	$(MQTTFILES)
diff --git a/src/api/api_lib.c b/src/api/api_lib.c
new file mode 100644
index 0000000..3c1d6a6
--- /dev/null
+++ b/src/api/api_lib.c
@@ -0,0 +1,1010 @@
+/**
+ * @file
+ * Sequential API External module
+ * 
+ * @defgroup netconn Netconn API
+ * @ingroup sequential_api
+ * Thread-safe, to be called from non-TCPIP threads only.
+ * TX/RX handling based on @ref netbuf (containing @ref pbuf)
+ * to avoid copying data around.
+ * 
+ * @defgroup netconn_common Common functions
+ * @ingroup netconn
+ * For use with TCP and UDP
+ * 
+ * @defgroup netconn_tcp TCP only
+ * @ingroup netconn
+ * TCP only functions
+ * 
+ * @defgroup netconn_udp UDP only
+ * @ingroup netconn
+ * UDP only 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@sics.se>
+ */
+
+/* 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/memp.h"
+
+#include "lwip/ip.h"
+#include "lwip/raw.h"
+#include "lwip/udp.h"
+#include "lwip/priv/api_msg.h"
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/priv/tcpip_priv.h"
+
+#include <string.h>
+
+#define API_MSG_VAR_REF(name)               API_VAR_REF(name)
+#define API_MSG_VAR_DECLARE(name)           API_VAR_DECLARE(struct api_msg, name)
+#define API_MSG_VAR_ALLOC(name)             API_VAR_ALLOC(struct api_msg, MEMP_API_MSG, name, ERR_MEM)
+#define API_MSG_VAR_ALLOC_RETURN_NULL(name) API_VAR_ALLOC(struct api_msg, MEMP_API_MSG, name, NULL)
+#define API_MSG_VAR_FREE(name)              API_VAR_FREE(MEMP_API_MSG, name)
+
+static err_t netconn_close_shutdown(struct netconn *conn, u8_t how);
+
+/**
+ * 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 fn function to call
+ * @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
+ */
+static err_t
+netconn_apimsg(tcpip_callback_fn fn, struct api_msg *apimsg)
+{
+  err_t err;
+
+#ifdef LWIP_DEBUG
+  /* catch functions that don't set err */
+  apimsg->err = ERR_VAL;
+#endif /* LWIP_DEBUG */
+
+#if LWIP_NETCONN_SEM_PER_THREAD
+  apimsg->op_completed_sem = LWIP_NETCONN_THREAD_SEM_GET();
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+  err = tcpip_send_msg_wait_sem(fn, apimsg, LWIP_API_MSG_SEM(apimsg));
+  if (err == ERR_OK) {
+    return apimsg->err;
+  }
+  return err;
+}
+
+/**
+ * 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;
+  API_MSG_VAR_DECLARE(msg);
+  API_MSG_VAR_ALLOC_RETURN_NULL(msg);
+
+  conn = netconn_alloc(t, callback);
+  if (conn != NULL) {
+    err_t err;
+
+    API_MSG_VAR_REF(msg).msg.n.proto = proto;
+    API_MSG_VAR_REF(msg).conn = conn;
+    err = netconn_apimsg(lwip_netconn_do_newconn, &API_MSG_VAR_REF(msg));
+    if (err != ERR_OK) {
+      LWIP_ASSERT("freeing conn without freeing pcb", conn->pcb.tcp == NULL);
+      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 */
+#if !LWIP_NETCONN_SEM_PER_THREAD
+      LWIP_ASSERT("conn has no op_completed", sys_sem_valid(&conn->op_completed));
+      sys_sem_free(&conn->op_completed);
+#endif /* !LWIP_NETCONN_SEM_PER_THREAD */
+      sys_mbox_free(&conn->recvmbox);
+      memp_free(MEMP_NETCONN, conn);
+      API_MSG_VAR_FREE(msg);
+      return NULL;
+    }
+  }
+  API_MSG_VAR_FREE(msg);
+  return conn;
+}
+
+/**
+ * @ingroup netconn_common
+ * 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)
+{
+  err_t err;
+  API_MSG_VAR_DECLARE(msg);
+
+  /* No ASSERT here because possible to get a (conn == NULL) if we got an accept error */
+  if (conn == NULL) {
+    return ERR_OK;
+  }
+
+  API_MSG_VAR_ALLOC(msg);
+  API_MSG_VAR_REF(msg).conn = conn;
+#if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER
+  /* get the time we started, which is later compared to
+     sys_now() + conn->send_timeout */
+  API_MSG_VAR_REF(msg).msg.sd.time_started = sys_now();
+#else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
+#if LWIP_TCP
+  API_MSG_VAR_REF(msg).msg.sd.polls_left =
+    ((LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT + TCP_SLOW_INTERVAL - 1) / TCP_SLOW_INTERVAL) + 1;
+#endif /* LWIP_TCP */
+#endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
+  err = netconn_apimsg(lwip_netconn_do_delconn, &API_MSG_VAR_REF(msg));
+  API_MSG_VAR_FREE(msg);
+
+  if (err != ERR_OK) {
+    return err;
+  }
+
+  netconn_free(conn);
+
+  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)
+{
+  API_MSG_VAR_DECLARE(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;);
+
+  API_MSG_VAR_ALLOC(msg);
+  API_MSG_VAR_REF(msg).conn = conn;
+  API_MSG_VAR_REF(msg).msg.ad.local = local;
+#if LWIP_MPU_COMPATIBLE
+  err = netconn_apimsg(lwip_netconn_do_getaddr, &API_MSG_VAR_REF(msg));
+  *addr = msg->msg.ad.ipaddr;
+  *port = msg->msg.ad.port;
+#else /* LWIP_MPU_COMPATIBLE */
+  msg.msg.ad.ipaddr = addr;
+  msg.msg.ad.port = port;
+  err = netconn_apimsg(lwip_netconn_do_getaddr, &msg);
+#endif /* LWIP_MPU_COMPATIBLE */
+  API_MSG_VAR_FREE(msg);
+
+  return err;
+}
+
+/**
+ * @ingroup netconn_common
+ * 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 IP4_ADDR_ANY/IP6_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, const ip_addr_t *addr, u16_t port)
+{
+  API_MSG_VAR_DECLARE(msg);
+  err_t err;
+  
+  LWIP_ERROR("netconn_bind: invalid conn", (conn != NULL), return ERR_ARG;);
+
+#if LWIP_IPV4
+  /* Don't propagate NULL pointer (IP_ADDR_ANY alias) to subsequent functions */
+  if (addr == NULL) {
+    addr = IP4_ADDR_ANY;
+  }
+#endif /* LWIP_IPV4 */
+  
+#if LWIP_IPV4 && LWIP_IPV6
+  /* "Socket API like" dual-stack support: If IP to bind to is IP6_ADDR_ANY,
+   * and NETCONN_FLAG_IPV6_V6ONLY is 0, use IP_ANY_TYPE to bind
+   */
+  if ((netconn_get_ipv6only(conn) == 0) &&
+     ip_addr_cmp(addr, IP6_ADDR_ANY)) {
+    addr = IP_ANY_TYPE;
+  }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+  API_MSG_VAR_ALLOC(msg);
+  API_MSG_VAR_REF(msg).conn = conn;
+  API_MSG_VAR_REF(msg).msg.bc.ipaddr = API_MSG_VAR_REF(addr);
+  API_MSG_VAR_REF(msg).msg.bc.port = port;
+  err = netconn_apimsg(lwip_netconn_do_bind, &API_MSG_VAR_REF(msg));
+  API_MSG_VAR_FREE(msg);
+
+  return err;
+}
+
+/**
+ * @ingroup netconn_common
+ * 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, const ip_addr_t *addr, u16_t port)
+{
+  API_MSG_VAR_DECLARE(msg);
+  err_t err;
+
+  LWIP_ERROR("netconn_connect: invalid conn", (conn != NULL), return ERR_ARG;);
+
+#if LWIP_IPV4
+  /* Don't propagate NULL pointer (IP_ADDR_ANY alias) to subsequent functions */
+  if (addr == NULL) {
+    addr = IP4_ADDR_ANY;
+  }
+#endif /* LWIP_IPV4 */
+
+  API_MSG_VAR_ALLOC(msg);
+  API_MSG_VAR_REF(msg).conn = conn;
+  API_MSG_VAR_REF(msg).msg.bc.ipaddr = API_MSG_VAR_REF(addr);
+  API_MSG_VAR_REF(msg).msg.bc.port = port;
+  err = netconn_apimsg(lwip_netconn_do_connect, &API_MSG_VAR_REF(msg));
+  API_MSG_VAR_FREE(msg);
+
+  return err;
+}
+
+/**
+ * @ingroup netconn_udp
+ * Disconnect a netconn from its current peer (only valid for UDP netconns).
+ *
+ * @param conn the netconn to disconnect
+ * @return See @ref err_t
+ */
+err_t
+netconn_disconnect(struct netconn *conn)
+{
+  API_MSG_VAR_DECLARE(msg);
+  err_t err;
+
+  LWIP_ERROR("netconn_disconnect: invalid conn", (conn != NULL), return ERR_ARG;);
+
+  API_MSG_VAR_ALLOC(msg);
+  API_MSG_VAR_REF(msg).conn = conn;
+  err = netconn_apimsg(lwip_netconn_do_disconnect, &API_MSG_VAR_REF(msg));
+  API_MSG_VAR_FREE(msg);
+
+  return err;
+}
+
+/**
+ * @ingroup netconn_tcp
+ * 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
+  API_MSG_VAR_DECLARE(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;);
+
+  API_MSG_VAR_ALLOC(msg);
+  API_MSG_VAR_REF(msg).conn = conn;
+#if TCP_LISTEN_BACKLOG
+  API_MSG_VAR_REF(msg).msg.lb.backlog = backlog;
+#endif /* TCP_LISTEN_BACKLOG */
+  err = netconn_apimsg(lwip_netconn_do_listen, &API_MSG_VAR_REF(msg));
+  API_MSG_VAR_FREE(msg);
+
+  return err;
+#else /* LWIP_TCP */
+  LWIP_UNUSED_ARG(conn);
+  LWIP_UNUSED_ARG(backlog);
+  return ERR_ARG;
+#endif /* LWIP_TCP */
+}
+
+/**
+ * @ingroup netconn_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
+  void *accept_ptr;
+  struct netconn *newconn;
+#if TCP_LISTEN_BACKLOG
+  API_MSG_VAR_DECLARE(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;);
+
+  if (ERR_IS_FATAL(conn->last_err)) {
+    /* don't recv on fatal errors: this might block the application task
+       waiting on acceptmbox forever! */
+    return conn->last_err;
+  }
+  if (!sys_mbox_valid(&conn->acceptmbox)) {
+    return ERR_CLSD;
+  }
+
+#if TCP_LISTEN_BACKLOG
+  API_MSG_VAR_ALLOC(msg);
+#endif /* TCP_LISTEN_BACKLOG */
+
+#if LWIP_SO_RCVTIMEO
+  if (sys_arch_mbox_fetch(&conn->acceptmbox, &accept_ptr, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {
+#if TCP_LISTEN_BACKLOG
+    API_MSG_VAR_FREE(msg);
+#endif /* TCP_LISTEN_BACKLOG */
+    return ERR_TIMEOUT;
+  }
+#else
+  sys_arch_mbox_fetch(&conn->acceptmbox, &accept_ptr, 0);
+#endif /* LWIP_SO_RCVTIMEO*/
+  newconn = (struct netconn *)accept_ptr;
+  /* Register event with callback */
+  API_EVENT(conn, NETCONN_EVT_RCVMINUS, 0);
+
+  if (accept_ptr == &netconn_aborted) {
+    /* a connection has been aborted: out of pcbs or out of netconns during accept */
+    /* @todo: set netconn error, but this would be fatal and thus block further accepts */
+#if TCP_LISTEN_BACKLOG
+    API_MSG_VAR_FREE(msg);
+#endif /* TCP_LISTEN_BACKLOG */
+    return ERR_ABRT;
+  }
+  if (newconn == NULL) {
+    /* connection has been aborted */
+    /* in this special case, we set the netconn error from application thread, as
+       on a ready-to-accept listening netconn, there should not be anything running
+       in tcpip_thread */
+    NETCONN_SET_SAFE_ERR(conn, ERR_CLSD);
+#if TCP_LISTEN_BACKLOG
+    API_MSG_VAR_FREE(msg);
+#endif /* TCP_LISTEN_BACKLOG */
+    return ERR_CLSD;
+  }
+#if TCP_LISTEN_BACKLOG
+  /* Let the stack know that we have accepted the connection. */
+  API_MSG_VAR_REF(msg).conn = newconn;
+  /* don't care for the return value of lwip_netconn_do_recv */
+  netconn_apimsg(lwip_netconn_do_accepted, &API_MSG_VAR_REF(msg));
+  API_MSG_VAR_FREE(msg);
+#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 */
+}
+
+/**
+ * @ingroup netconn_common
+ * 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;
+#if LWIP_TCP
+  API_MSG_VAR_DECLARE(msg);
+#if LWIP_MPU_COMPATIBLE
+  msg = NULL;
+#endif
+#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;);
+#if LWIP_TCP
+#if (LWIP_UDP || LWIP_RAW)
+  if (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP)
+#endif /* (LWIP_UDP || LWIP_RAW) */
+  {
+    if (!sys_mbox_valid(&conn->recvmbox)) {
+      /* This happens when calling this function after receiving FIN */
+      return sys_mbox_valid(&conn->acceptmbox) ? ERR_CONN : ERR_CLSD;
+    }
+  }
+#endif /* LWIP_TCP */
+  LWIP_ERROR("netconn_recv: invalid recvmbox", sys_mbox_valid(&conn->recvmbox), return ERR_CONN;);
+
+  if (ERR_IS_FATAL(conn->last_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 conn->last_err;
+  }
+#if LWIP_TCP
+#if (LWIP_UDP || LWIP_RAW)
+  if (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP)
+#endif /* (LWIP_UDP || LWIP_RAW) */
+  {
+    API_MSG_VAR_ALLOC(msg);
+  }
+#endif /* LWIP_TCP */
+
+#if LWIP_SO_RCVTIMEO
+  if (sys_arch_mbox_fetch(&conn->recvmbox, &buf, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {
+#if LWIP_TCP
+#if (LWIP_UDP || LWIP_RAW)
+    if (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP)
+#endif /* (LWIP_UDP || LWIP_RAW) */
+    {
+      API_MSG_VAR_FREE(msg);
+    }
+#endif /* LWIP_TCP */
+    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) */
+  {
+    /* 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). */
+    API_MSG_VAR_REF(msg).conn = conn;
+    if (buf != NULL) {
+      API_MSG_VAR_REF(msg).msg.r.len = ((struct pbuf *)buf)->tot_len;
+    } else {
+      API_MSG_VAR_REF(msg).msg.r.len = 1;
+    }
+
+    /* don't care for the return value of lwip_netconn_do_recv */
+    netconn_apimsg(lwip_netconn_do_recv, &API_MSG_VAR_REF(msg));
+    API_MSG_VAR_FREE(msg);
+
+    /* 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);
+      if (conn->pcb.ip == NULL) {
+        /* race condition: RST during recv */
+        return conn->last_err == ERR_OK ? ERR_RST : conn->last_err;
+      }
+      /* RX side is closed, so deallocate the recvmbox */
+      netconn_close_shutdown(conn, NETCONN_SHUT_RD);
+      /* Don' store ERR_CLSD as conn->err since we are only half-closed */
+      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;
+}
+
+/**
+ * @ingroup netconn_tcp
+ * 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);
+}
+
+/**
+ * @ingroup netconn_common
+ * 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;);
+
+#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) {
+      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;
+    ip_addr_set_zero(&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) */
+  }
+}
+
+/**
+ * @ingroup netconn_udp
+ * 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, const ip_addr_t *addr, u16_t port)
+{
+  if (buf != NULL) {
+    ip_addr_set(&buf->addr, addr);
+    buf->port = port;
+    return netconn_send(conn, buf);
+  }
+  return ERR_VAL;
+}
+
+/**
+ * @ingroup netconn_udp
+ * 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)
+{
+  API_MSG_VAR_DECLARE(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));
+
+  API_MSG_VAR_ALLOC(msg);
+  API_MSG_VAR_REF(msg).conn = conn;
+  API_MSG_VAR_REF(msg).msg.b = buf;
+  err = netconn_apimsg(lwip_netconn_do_send, &API_MSG_VAR_REF(msg));
+  API_MSG_VAR_FREE(msg);
+
+  return err;
+}
+
+/**
+ * @ingroup netconn_tcp
+ * 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 data 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)
+{
+  API_MSG_VAR_DECLARE(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 LWIP_SO_SNDTIMEO
+  if (conn->send_timeout != 0) {
+    dontblock = 1;
+  }
+#endif /* LWIP_SO_SNDTIMEO */
+  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;
+  }
+
+  API_MSG_VAR_ALLOC(msg);
+  /* non-blocking write sends as much  */
+  API_MSG_VAR_REF(msg).conn = conn;
+  API_MSG_VAR_REF(msg).msg.w.dataptr = dataptr;
+  API_MSG_VAR_REF(msg).msg.w.apiflags = apiflags;
+  API_MSG_VAR_REF(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 */
+    API_MSG_VAR_REF(msg).msg.w.time_started = sys_now();
+  } else {
+    API_MSG_VAR_REF(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. */
+  err = netconn_apimsg(lwip_netconn_do_write, &API_MSG_VAR_REF(msg));
+  if ((err == ERR_OK) && (bytes_written != NULL)) {
+    if (dontblock) {
+      /* nonblocking write: maybe the data has been sent partly */
+      *bytes_written = API_MSG_VAR_REF(msg).msg.w.len;
+    } else {
+      /* blocking call succeeded: all data has been sent if it */
+      *bytes_written = size;
+    }
+  }
+  API_MSG_VAR_FREE(msg);
+
+  return err;
+}
+
+/**
+ * @ingroup netconn_tcp
+ * Close or 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)
+{
+  API_MSG_VAR_DECLARE(msg);
+  err_t err;
+  LWIP_UNUSED_ARG(how);
+
+  LWIP_ERROR("netconn_close: invalid conn",  (conn != NULL), return ERR_ARG;);
+
+  API_MSG_VAR_ALLOC(msg);
+  API_MSG_VAR_REF(msg).conn = conn;
+#if LWIP_TCP
+  /* shutting down both ends is the same as closing */
+  API_MSG_VAR_REF(msg).msg.sd.shut = how;
+#if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER
+  /* get the time we started, which is later compared to
+     sys_now() + conn->send_timeout */
+  API_MSG_VAR_REF(msg).msg.sd.time_started = sys_now();
+#else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
+  API_MSG_VAR_REF(msg).msg.sd.polls_left =
+    ((LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT + TCP_SLOW_INTERVAL - 1) / TCP_SLOW_INTERVAL) + 1;
+#endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
+#endif /* LWIP_TCP */
+  err = netconn_apimsg(lwip_netconn_do_close, &API_MSG_VAR_REF(msg));
+  API_MSG_VAR_FREE(msg);
+
+  return err;
+}
+
+/**
+ * @ingroup netconn_tcp
+ * 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);
+}
+
+/**
+ * @ingroup netconn_tcp
+ * Shut down one or both sides of a TCP netconn (doesn't delete it).
+ *
+ * @param conn the TCP netconn to shut down
+ * @param shut_rx shut down the RX side (no more read possible after this)
+ * @param shut_tx shut down the TX side (no more write possible after this)
+ * @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)
+/**
+ * @ingroup netconn_udp
+ * 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,
+                         const ip_addr_t *multiaddr,
+                         const ip_addr_t *netif_addr,
+                         enum netconn_igmp join_or_leave)
+{
+  API_MSG_VAR_DECLARE(msg);
+  err_t err;
+
+  LWIP_ERROR("netconn_join_leave_group: invalid conn",  (conn != NULL), return ERR_ARG;);
+
+  API_MSG_VAR_ALLOC(msg);
+
+#if LWIP_IPV4
+  /* Don't propagate NULL pointer (IP_ADDR_ANY alias) to subsequent functions */
+  if (multiaddr == NULL) {
+    multiaddr = IP4_ADDR_ANY;
+  }
+  if (netif_addr == NULL) {
+    netif_addr = IP4_ADDR_ANY;
+  }
+#endif /* LWIP_IPV4 */
+
+  API_MSG_VAR_REF(msg).conn = conn;
+  API_MSG_VAR_REF(msg).msg.jl.multiaddr = API_MSG_VAR_REF(multiaddr);
+  API_MSG_VAR_REF(msg).msg.jl.netif_addr = API_MSG_VAR_REF(netif_addr);
+  API_MSG_VAR_REF(msg).msg.jl.join_or_leave = join_or_leave;
+  err = netconn_apimsg(lwip_netconn_do_join_leave_group, &API_MSG_VAR_REF(msg));
+  API_MSG_VAR_FREE(msg);
+
+  return err;
+}
+#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+
+#if LWIP_DNS
+/**
+ * @ingroup netconn_common
+ * 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
+ * @param dns_addrtype IP address type (IPv4 / IPv6)
+ * @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
+ */
+#if LWIP_IPV4 && LWIP_IPV6
+err_t
+netconn_gethostbyname_addrtype(const char *name, ip_addr_t *addr, u8_t dns_addrtype)
+#else
+err_t
+netconn_gethostbyname(const char *name, ip_addr_t *addr)
+#endif
+{
+  API_VAR_DECLARE(struct dns_api_msg, msg);
+#if !LWIP_MPU_COMPATIBLE
+  sys_sem_t sem;
+#endif /* LWIP_MPU_COMPATIBLE */
+  err_t err;
+  err_t cberr;
+
+  LWIP_ERROR("netconn_gethostbyname: invalid name", (name != NULL), return ERR_ARG;);
+  LWIP_ERROR("netconn_gethostbyname: invalid addr", (addr != NULL), return ERR_ARG;);
+#if LWIP_MPU_COMPATIBLE
+  if (strlen(name) >= DNS_MAX_NAME_LENGTH) {
+    return ERR_ARG;
+  }
+#endif
+
+  API_VAR_ALLOC(struct dns_api_msg, MEMP_DNS_API_MSG, msg, ERR_MEM);
+#if LWIP_MPU_COMPATIBLE
+  strncpy(API_VAR_REF(msg).name, name, DNS_MAX_NAME_LENGTH-1);
+  API_VAR_REF(msg).name[DNS_MAX_NAME_LENGTH-1] = 0;
+#else /* LWIP_MPU_COMPATIBLE */
+  msg.err = &err;
+  msg.sem = &sem;
+  API_VAR_REF(msg).addr = API_VAR_REF(addr);
+  API_VAR_REF(msg).name = name;
+#endif /* LWIP_MPU_COMPATIBLE */
+#if LWIP_IPV4 && LWIP_IPV6
+  API_VAR_REF(msg).dns_addrtype = dns_addrtype;
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+#if LWIP_NETCONN_SEM_PER_THREAD
+  API_VAR_REF(msg).sem = LWIP_NETCONN_THREAD_SEM_GET();
+#else /* LWIP_NETCONN_SEM_PER_THREAD*/
+  err = sys_sem_new(API_EXPR_REF(API_VAR_REF(msg).sem), 0);
+  if (err != ERR_OK) {
+    API_VAR_FREE(MEMP_DNS_API_MSG, msg);
+    return err;
+  }
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+  cberr = tcpip_callback(lwip_netconn_do_gethostbyname, &API_VAR_REF(msg));
+  if (cberr != ERR_OK) {
+#if !LWIP_NETCONN_SEM_PER_THREAD
+    sys_sem_free(API_EXPR_REF(API_VAR_REF(msg).sem));
+#endif /* !LWIP_NETCONN_SEM_PER_THREAD */
+    API_VAR_FREE(MEMP_DNS_API_MSG, msg);
+    return cberr;
+  }
+  sys_sem_wait(API_EXPR_REF_SEM(API_VAR_REF(msg).sem));
+#if !LWIP_NETCONN_SEM_PER_THREAD
+  sys_sem_free(API_EXPR_REF(API_VAR_REF(msg).sem));
+#endif /* !LWIP_NETCONN_SEM_PER_THREAD */
+
+#if LWIP_MPU_COMPATIBLE
+  *addr = msg->addr;
+  err = msg->err;
+#endif /* LWIP_MPU_COMPATIBLE */
+
+  API_VAR_FREE(MEMP_DNS_API_MSG, msg);
+  return err;
+}
+#endif /* LWIP_DNS*/
+
+#if LWIP_NETCONN_SEM_PER_THREAD
+void
+netconn_thread_init(void)
+{
+  sys_sem_t *sem = LWIP_NETCONN_THREAD_SEM_GET();
+  if ((sem == NULL) || !sys_sem_valid(sem)) {
+    /* call alloc only once */
+    LWIP_NETCONN_THREAD_SEM_ALLOC();
+    LWIP_ASSERT("LWIP_NETCONN_THREAD_SEM_ALLOC() failed", sys_sem_valid(LWIP_NETCONN_THREAD_SEM_GET()));
+  }
+}
+
+void
+netconn_thread_cleanup(void)
+{
+  sys_sem_t *sem = LWIP_NETCONN_THREAD_SEM_GET();
+  if ((sem != NULL) && sys_sem_valid(sem)) {
+    /* call free only once */
+    LWIP_NETCONN_THREAD_SEM_FREE();
+  }
+}
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+#endif /* LWIP_NETCONN */
diff --git a/src/api/api_msg.c b/src/api/api_msg.c
new file mode 100644
index 0000000..5a8dad9
--- /dev/null
+++ b/src/api/api_msg.c
@@ -0,0 +1,1953 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_NETCONN /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/priv/api_msg.h"
+
+#include "lwip/ip.h"
+#include "lwip/ip_addr.h"
+#include "lwip/udp.h"
+#include "lwip/tcp.h"
+#include "lwip/raw.h"
+
+#include "lwip/memp.h"
+#include "lwip/igmp.h"
+#include "lwip/dns.h"
+#include "lwip/mld6.h"
+#include "lwip/priv/tcpip_priv.h"
+
+#include <string.h>
+
+/* netconns are polled once per second (e.g. continue write on memory error) */
+#define NETCONN_TCP_POLL_INTERVAL 2
+
+#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
+#if LWIP_TCPIP_CORE_LOCKING
+#define WRITE_DELAYED         , 1
+#define WRITE_DELAYED_PARAM   , u8_t delayed
+#else /* LWIP_TCPIP_CORE_LOCKING */
+#define WRITE_DELAYED
+#define WRITE_DELAYED_PARAM
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+static err_t lwip_netconn_do_writemore(struct netconn *conn  WRITE_DELAYED_PARAM);
+static err_t lwip_netconn_do_close_internal(struct netconn *conn  WRITE_DELAYED_PARAM);
+#endif
+
+#if LWIP_TCPIP_CORE_LOCKING
+#define TCPIP_APIMSG_ACK(m)   NETCONN_SET_SAFE_ERR((m)->conn, (m)->err)
+#else /* LWIP_TCPIP_CORE_LOCKING */
+#define TCPIP_APIMSG_ACK(m)   do { NETCONN_SET_SAFE_ERR((m)->conn, (m)->err); sys_sem_signal(LWIP_API_MSG_SEM(m)); } while(0)
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+
+#if LWIP_TCP
+u8_t netconn_aborted;
+#endif /* LWIP_TCP */
+
+#if LWIP_RAW
+/**
+ * Receive callback function for RAW netconns.
+ * Doesn't 'eat' the packet, only copies 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,
+    const 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;
+      ip_addr_copy(buf->addr, *ip_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,
+   const 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;
+
+  if (conn == NULL) {
+    pbuf_free(p);
+    return;
+  }
+
+  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 (!sys_mbox_valid(&conn->recvmbox) ||
+      ((recv_avail + (int)(p->tot_len)) > conn->recv_bufsize)) {
+#else  /* LWIP_SO_RCVBUF */
+  if (!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;
+    ip_addr_set(&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 = (const struct udp_hdr*)ip_next_header_ptr();
+#if LWIP_CHECKSUM_ON_COPY
+      buf->flags = NETBUF_FLAG_DESTADDR;
+#endif /* LWIP_CHECKSUM_ON_COPY */
+      ip_addr_set(&buf->toaddr, ip_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;
+
+  if (conn == NULL) {
+    return ERR_VAL;
+  }
+  LWIP_ASSERT("recv_tcp: recv for wrong pcb!", conn->pcb.tcp == pcb);
+
+  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! */
+  if (err != ERR_OK) {
+    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  WRITE_DELAYED);
+  } else if (conn->state == NETCONN_CLOSE) {
+#if !LWIP_SO_SNDTIMEO && !LWIP_SO_LINGER
+    if (conn->current_msg && conn->current_msg->msg.sd.polls_left) {
+      conn->current_msg->msg.sd.polls_left--;
+    }
+#endif /* !LWIP_SO_SNDTIMEO && !LWIP_SO_LINGER */
+    lwip_netconn_do_close_internal(conn  WRITE_DELAYED);
+  }
+  /* @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) {
+    if (conn->state == NETCONN_WRITE) {
+      lwip_netconn_do_writemore(conn  WRITE_DELAYED);
+    } else if (conn->state == NETCONN_CLOSE) {
+      lwip_netconn_do_close_internal(conn  WRITE_DELAYED);
+    }
+
+    /* 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;
+
+  conn = (struct netconn *)arg;
+  LWIP_ASSERT("conn != NULL", (conn != NULL));
+
+  conn->pcb.tcp = NULL;
+
+  /* reset conn->state now before waking up other threads */
+  old_state = conn->state;
+  conn->state = NETCONN_NONE;
+
+  if (old_state == NETCONN_CLOSE) {
+    /* RST during close: let close return success & dealloc the netconn */
+    err = ERR_OK;
+    NETCONN_SET_SAFE_ERR(conn, ERR_OK);
+  } else {
+    /* no check since this is always fatal! */
+    SYS_ARCH_SET(conn->last_err, err);
+  }
+
+  /* @todo: the type of NETCONN_EVT created should depend on 'old_state' */
+
+  /* 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) {
+      sys_sem_t* op_completed_sem;
+      /* set error return code */
+      LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL);
+      conn->current_msg->err = err;
+      op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg);
+      LWIP_ASSERT("inavlid op_completed_sem", sys_sem_valid(op_completed_sem));
+      conn->current_msg = NULL;
+      /* wake up the waiting task */
+      NETCONN_SET_SAFE_ERR(conn, err);
+      sys_sem_signal(op_completed_sem);
+    }
+  } 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, NETCONN_TCP_POLL_INTERVAL);
+  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;
+
+  if (conn == NULL) {
+    return ERR_VAL;
+  }
+  if (!sys_mbox_valid(&conn->acceptmbox)) {
+    LWIP_DEBUGF(API_MSG_DEBUG, ("accept_function: acceptmbox already deleted\n"));
+    return ERR_VAL;
+  }
+
+  if (newpcb == NULL) {
+    /* out-of-pcbs during connect: pass on this error to the application */
+    if (sys_mbox_trypost(&conn->acceptmbox, &netconn_aborted) == ERR_OK) {
+      /* Register event with callback */
+      API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0);
+    }
+    return ERR_VAL;
+  }
+
+  LWIP_DEBUGF(API_MSG_DEBUG, ("accept_function: newpcb->tate: %s\n", tcp_debug_state_str(newpcb->state)));
+
+  /* We have to set the callback here even though
+   * the new socket is unknown. newconn->socket is marked as -1. */
+  newconn = netconn_alloc(conn->type, conn->callback);
+  if (newconn == NULL) {
+    /* outof netconns: pass on this error to the application */
+    if (sys_mbox_trypost(&conn->acceptmbox, &netconn_aborted) == ERR_OK) {
+      /* Register event with callback */
+      API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0);
+    }
+    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;
+
+  /* handle backlog counter */
+  tcp_backlog_delayed(newpcb);
+
+  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, 0);
+    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
+ */
+static void
+pcb_new(struct api_msg *msg)
+{
+  enum lwip_ip_addr_type iptype = IPADDR_TYPE_V4;
+
+  LWIP_ASSERT("pcb_new: pcb already allocated", msg->conn->pcb.tcp == NULL);
+ 
+#if LWIP_IPV6 && LWIP_IPV4
+  /* IPv6: Dual-stack by default, unless netconn_set_ipv6only() is called */
+  if(NETCONNTYPE_ISIPV6(netconn_type(msg->conn))) {
+    iptype = IPADDR_TYPE_ANY;
+  }
+#endif
+  
+  /* Allocate a PCB for this connection */
+  switch(NETCONNTYPE_GROUP(msg->conn->type)) {
+#if LWIP_RAW
+  case NETCONN_RAW:
+    msg->conn->pcb.raw = raw_new_ip_type(iptype, msg->msg.n.proto);
+    if (msg->conn->pcb.raw != NULL) {
+#if LWIP_IPV6
+      /* ICMPv6 packets should always have checksum calculated by the stack as per RFC 3542 chapter 3.1 */
+      if (NETCONNTYPE_ISIPV6(msg->conn->type) && msg->conn->pcb.raw->protocol == IP6_NEXTH_ICMP6) {
+        msg->conn->pcb.raw->chksum_reqd = 1;
+        msg->conn->pcb.raw->chksum_offset = 2;
+      }
+#endif /* LWIP_IPV6 */
+      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_ip_type(iptype);
+    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_ip_type(iptype);
+    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;
+  }
+}
+
+/**
+ * Create a new pcb of a specific type inside a netconn.
+ * Called from netconn_new_with_proto_and_callback.
+ *
+ * @param m the api_msg_msg describing the connection type
+ */
+void
+lwip_netconn_do_newconn(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  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 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_mbox_new(&conn->recvmbox, size) != ERR_OK) {
+    goto free_and_return;
+  }
+#if !LWIP_NETCONN_SEM_PER_THREAD
+  if (sys_sem_new(&conn->op_completed, 0) != ERR_OK) {
+    sys_mbox_free(&conn->recvmbox);
+    goto free_and_return;
+  }
+#endif
+
+#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 */
+#if LWIP_SO_LINGER
+  conn->linger = -1;
+#endif /* LWIP_SO_LINGER */
+  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 */
+
+#if !LWIP_NETCONN_SEM_PER_THREAD
+  sys_sem_free(&conn->op_completed);
+  sys_sem_set_invalid(&conn->op_completed);
+#endif
+
+  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) {
+      if (mem != &netconn_aborted) {
+        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() */
+        /* 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 err_t
+lwip_netconn_do_close_internal(struct netconn *conn  WRITE_DELAYED_PARAM)
+{
+  err_t err;
+  u8_t shut, shut_rx, shut_tx, close;
+  u8_t close_finished = 0;
+  struct tcp_pcb* tpcb;
+#if LWIP_SO_LINGER
+  u8_t linger_wait_required = 0;
+#endif /* LWIP_SO_LINGER */
+
+  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);
+
+  tpcb = conn->pcb.tcp;
+  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
+     (also if RD or WR side was shut down before already) */
+  if (shut == NETCONN_SHUT_RDWR) {
+    close = 1;
+  } else if (shut_rx &&
+             ((tpcb->state == FIN_WAIT_1) ||
+              (tpcb->state == FIN_WAIT_2) ||
+              (tpcb->state == CLOSING))) {
+    close = 1;
+  } else if (shut_tx && ((tpcb->flags & TF_RXCLOSED) != 0)) {
+    close = 1;
+  } else {
+    close = 0;
+  }
+
+  /* Set back some callback pointers */
+  if (close) {
+    tcp_arg(tpcb, NULL);
+  }
+  if (tpcb->state == LISTEN) {
+    tcp_accept(tpcb, NULL);
+  } else {
+    /* some callbacks have to be reset if tcp_close is not successful */
+    if (shut_rx) {
+      tcp_recv(tpcb, NULL);
+      tcp_accept(tpcb, NULL);
+    }
+    if (shut_tx) {
+      tcp_sent(tpcb, NULL);
+    }
+    if (close) {
+      tcp_poll(tpcb, NULL, 0);
+      tcp_err(tpcb, NULL);
+    }
+  }
+  /* Try to close the connection */
+  if (close) {
+#if LWIP_SO_LINGER
+    /* check linger possibilites before calling tcp_close */
+    err = ERR_OK;
+    /* linger enabled/required at all? (i.e. is there untransmitted data left?) */
+    if ((conn->linger >= 0) && (conn->pcb.tcp->unsent || conn->pcb.tcp->unacked)) {
+      if ((conn->linger == 0)) {
+        /* data left but linger prevents waiting */
+        tcp_abort(tpcb);
+        tpcb = NULL;
+      } else if (conn->linger > 0) {
+        /* data left and linger says we should wait */
+        if (netconn_is_nonblocking(conn)) {
+          /* data left on a nonblocking netconn -> cannot linger */
+          err = ERR_WOULDBLOCK;
+        } else if ((s32_t)(sys_now() - conn->current_msg->msg.sd.time_started) >=
+          (conn->linger * 1000)) {
+          /* data left but linger timeout has expired (this happens on further
+             calls to this function through poll_tcp */
+          tcp_abort(tpcb);
+          tpcb = NULL;
+        } else {
+          /* data left -> need to wait for ACK after successful close */
+          linger_wait_required = 1;
+        }
+      }
+    }
+    if ((err == ERR_OK) && (tpcb != NULL))
+#endif /* LWIP_SO_LINGER */
+    {
+      err = tcp_close(tpcb);
+    }
+  } else {
+    err = tcp_shutdown(tpcb, shut_rx, shut_tx);
+  }
+  if (err == ERR_OK) {
+    close_finished = 1;
+#if LWIP_SO_LINGER
+    if (linger_wait_required) {
+      /* wait for ACK of all unsent/unacked data by just getting called again */
+      close_finished = 0;
+      err = ERR_INPROGRESS;
+    }
+#endif /* LWIP_SO_LINGER */
+  } else {
+    if (err == ERR_MEM) {
+      /* Closing failed because of memory shortage, try again later. Even for
+         nonblocking netconns, we have to wait since no standard socket application
+         is prepared for close failing because of resource shortage.
+         Check the timeout: this is kind of an lwip addition to the standard sockets:
+         we wait for some time when failing to allocate a segment for the FIN */
+#if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER
+      s32_t close_timeout = LWIP_TCP_CLOSE_TIMEOUT_MS_DEFAULT;
+#if LWIP_SO_SNDTIMEO
+      if (conn->send_timeout > 0) {
+        close_timeout = conn->send_timeout;
+      }
+#endif /* LWIP_SO_SNDTIMEO */
+#if LWIP_SO_LINGER
+      if (conn->linger >= 0) {
+        /* use linger timeout (seconds) */
+        close_timeout = conn->linger * 1000U;
+      }
+#endif
+      if ((s32_t)(sys_now() - conn->current_msg->msg.sd.time_started) >= close_timeout) {
+#else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
+      if (conn->current_msg->msg.sd.polls_left == 0) {
+#endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
+        close_finished = 1;
+        if (close) {
+          /* in this case, we want to RST the connection */
+          tcp_abort(tpcb);
+          err = ERR_OK;
+        }
+      }
+    } else {
+      /* Closing failed for a non-memory error: give up */
+      close_finished = 1;
+    }
+  }
+  if (close_finished) {
+    /* Closing done (succeeded, non-memory error, nonblocking error or timeout) */
+    sys_sem_t* op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg);
+    conn->current_msg->err = err;
+    conn->current_msg = NULL;
+    conn->state = NETCONN_NONE;
+    if (err == ERR_OK) {
+      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);
+      }
+    }
+    NETCONN_SET_SAFE_ERR(conn, err);
+#if LWIP_TCPIP_CORE_LOCKING
+    if (delayed)
+#endif
+    {
+      /* wake up the application task */
+      sys_sem_signal(op_completed_sem);
+    }
+    return ERR_OK;
+  }
+  if (!close_finished) {
+    /* Closing failed and we want to wait: restore some of the callbacks */
+    /* Closing of listen pcb will never fail! */
+    LWIP_ASSERT("Closing a listen pcb may not fail!", (tpcb->state != LISTEN));
+    if (shut_tx) {
+      tcp_sent(tpcb, sent_tcp);
+    }
+    /* when waiting for close, set up poll interval to 500ms */
+    tcp_poll(tpcb, poll_tcp, 1);
+    tcp_err(tpcb, err_tcp);
+    tcp_arg(tpcb, 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 */
+  LWIP_ASSERT("err != ERR_OK", err != ERR_OK);
+  return err;
+}
+#endif /* LWIP_TCP */
+
+/**
+ * Delete the pcb inside a netconn.
+ * Called from netconn_delete.
+ *
+ * @param m the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_delconn(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  enum netconn_state state = msg->conn->state;
+  LWIP_ASSERT("netconn state error", /* this only happens for TCP netconns */
+    (state == NETCONN_NONE) || (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP));
+#if LWIP_NETCONN_FULLDUPLEX
+  /* In full duplex mode, blocking write/connect is aborted with ERR_CLSD */
+  if (state != NETCONN_NONE) {
+    if ((state == NETCONN_WRITE) ||
+        ((state == NETCONN_CONNECT) && !IN_NONBLOCKING_CONNECT(msg->conn))) {
+      /* close requested, abort running write/connect */
+      sys_sem_t* op_completed_sem;
+      LWIP_ASSERT("msg->conn->current_msg != NULL", msg->conn->current_msg != NULL);
+      op_completed_sem = LWIP_API_MSG_SEM(msg->conn->current_msg);
+      msg->conn->current_msg->err = ERR_CLSD;
+      msg->conn->current_msg = NULL;
+      msg->conn->write_offset = 0;
+      msg->conn->state = NETCONN_NONE;
+      NETCONN_SET_SAFE_ERR(msg->conn, ERR_CLSD);
+      sys_sem_signal(op_completed_sem);
+    }
+  }
+#else /* LWIP_NETCONN_FULLDUPLEX */
+  if (((state != NETCONN_NONE) &&
+       (state != NETCONN_LISTEN) &&
+       (state != NETCONN_CONNECT)) ||
+      ((state == NETCONN_CONNECT) && !IN_NONBLOCKING_CONNECT(msg->conn))) {
+    /* This means either a blocking write or blocking connect is running
+       (nonblocking write returns and sets state to NONE) */
+    msg->err = ERR_INPROGRESS;
+  } else
+#endif /* LWIP_NETCONN_FULLDUPLEX */
+  {
+    LWIP_ASSERT("blocking connect in progress",
+      (state != NETCONN_CONNECT) || IN_NONBLOCKING_CONNECT(msg->conn));
+    msg->err = ERR_OK;
+    /* 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;
+#if LWIP_TCPIP_CORE_LOCKING
+        if (lwip_netconn_do_close_internal(msg->conn, 0) != ERR_OK) {
+          LWIP_ASSERT("state!", msg->conn->state == NETCONN_CLOSE);
+          UNLOCK_TCPIP_CORE();
+          sys_arch_sem_wait(LWIP_API_MSG_SEM(msg), 0);
+          LOCK_TCPIP_CORE();
+          LWIP_ASSERT("state!", msg->conn->state == NETCONN_NONE);
+        }
+#else /* LWIP_TCPIP_CORE_LOCKING */
+        lwip_netconn_do_close_internal(msg->conn);
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+        /* 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(LWIP_API_MSG_SEM(msg))) {
+    TCPIP_APIMSG_ACK(msg);
+  }
+}
+
+/**
+ * Bind a pcb contained in a netconn
+ * Called from netconn_bind.
+ *
+ * @param m the api_msg_msg pointing to the connection and containing
+ *          the IP address and port to bind to
+ */
+void
+lwip_netconn_do_bind(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  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, API_EXPR_REF(msg->msg.bc.ipaddr));
+        break;
+#endif /* LWIP_RAW */
+#if LWIP_UDP
+      case NETCONN_UDP:
+        msg->err = udp_bind(msg->conn->pcb.udp, API_EXPR_REF(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, API_EXPR_REF(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;
+  sys_sem_t* op_completed_sem = NULL;
+
+  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;
+    op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg);
+  }
+  if ((NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP) && (err == ERR_OK)) {
+    setup_tcp(conn);
+  }
+  was_blocking = !IN_NONBLOCKING_CONNECT(conn);
+  SET_NONBLOCKING_CONNECT(conn, 0);
+  LWIP_ASSERT("blocking connect state error",
+    (was_blocking && op_completed_sem != NULL) ||
+    (!was_blocking && op_completed_sem == NULL));
+  conn->current_msg = NULL;
+  conn->state = NETCONN_NONE;
+  NETCONN_SET_SAFE_ERR(conn, ERR_OK);
+  API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0);
+
+  if (was_blocking) {
+    sys_sem_signal(op_completed_sem);
+  }
+  return ERR_OK;
+}
+#endif /* LWIP_TCP */
+
+/**
+ * Connect a pcb contained inside a netconn
+ * Called from netconn_connect.
+ *
+ * @param m the api_msg_msg pointing to the connection and containing
+ *          the IP address and port to connect to
+ */
+void
+lwip_netconn_do_connect(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  if (msg->conn->pcb.tcp == NULL) {
+    /* This may happen when calling netconn_connect() a second time */
+    msg->err = ERR_CLSD;
+  } else {
+    switch (NETCONNTYPE_GROUP(msg->conn->type)) {
+#if LWIP_RAW
+    case NETCONN_RAW:
+      msg->err = raw_connect(msg->conn->pcb.raw, API_EXPR_REF(msg->msg.bc.ipaddr));
+      break;
+#endif /* LWIP_RAW */
+#if LWIP_UDP
+    case NETCONN_UDP:
+      msg->err = udp_connect(msg->conn->pcb.udp, API_EXPR_REF(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_CONNECT) {
+        msg->err = ERR_ALREADY;
+      } else if (msg->conn->state != NETCONN_NONE) {
+        msg->err = ERR_ISCONN;
+      } else {
+        setup_tcp(msg->conn);
+        msg->err = tcp_connect(msg->conn->pcb.tcp, API_EXPR_REF(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! */
+#if LWIP_TCPIP_CORE_LOCKING
+            LWIP_ASSERT("state!", msg->conn->state == NETCONN_CONNECT);
+            UNLOCK_TCPIP_CORE();
+            sys_arch_sem_wait(LWIP_API_MSG_SEM(msg), 0);
+            LOCK_TCPIP_CORE();
+            LWIP_ASSERT("state!", msg->conn->state != NETCONN_CONNECT);
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+            return;
+          }
+        }
+      }
+      break;
+#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);
+}
+
+/**
+ * Disconnect a pcb contained inside a netconn
+ * Only used for UDP netconns.
+ * Called from netconn_disconnect.
+ *
+ * @param m the api_msg_msg pointing to the connection to disconnect
+ */
+void
+lwip_netconn_do_disconnect(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+#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 m the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_listen(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  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 (msg->conn->pcb.tcp->state != CLOSED) {
+            /* connection is not closed, cannot listen */
+            msg->err = ERR_VAL;
+          } else {
+            err_t err;
+            u8_t backlog;
+#if TCP_LISTEN_BACKLOG
+            backlog = msg->msg.lb.backlog;
+#else  /* TCP_LISTEN_BACKLOG */
+            backlog = TCP_DEFAULT_LISTEN_BACKLOG;
+#endif /* TCP_LISTEN_BACKLOG */
+#if LWIP_IPV4 && LWIP_IPV6
+            /* "Socket API like" dual-stack support: If IP to listen to is IP6_ADDR_ANY,
+             * and NETCONN_FLAG_IPV6_V6ONLY is NOT set, use IP_ANY_TYPE to listen
+             */
+            if (ip_addr_cmp(&msg->conn->pcb.ip->local_ip, IP6_ADDR_ANY) &&
+                (netconn_get_ipv6only(msg->conn) == 0)) {
+              /* change PCB type to IPADDR_TYPE_ANY */
+              IP_SET_TYPE_VAL(msg->conn->pcb.tcp->local_ip,  IPADDR_TYPE_ANY);
+              IP_SET_TYPE_VAL(msg->conn->pcb.tcp->remote_ip, IPADDR_TYPE_ANY);
+            }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+            lpcb = tcp_listen_with_backlog_and_err(msg->conn->pcb.tcp, backlog, &err);
+
+            if (lpcb == NULL) {
+              /* in this case, the old pcb is still allocated */
+              msg->err = err;
+            } 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 if (msg->conn->state == NETCONN_LISTEN) {
+          /* already listening, allow updating of the backlog */
+          msg->err = ERR_OK;
+          tcp_backlog_set(msg->conn->pcb.tcp, msg->msg.lb.backlog);
+        }
+      } 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 m the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_send(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  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 (ip_addr_isany(&msg->msg.b->addr) || IP_IS_ANY_TYPE_VAL(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, &msg->msg.b->addr);
+        }
+        break;
+#endif
+#if LWIP_UDP
+      case NETCONN_UDP:
+#if LWIP_CHECKSUM_ON_COPY
+        if (ip_addr_isany(&msg->msg.b->addr) || IP_IS_ANY_TYPE_VAL(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,
+            &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 (ip_addr_isany_val(msg->msg.b->addr) || IP_IS_ANY_TYPE_VAL(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, &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 m the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_recv(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  msg->err = ERR_OK;
+  if (msg->conn->pcb.tcp != NULL) {
+    if (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP) {
+      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);
+}
+
+#if TCP_LISTEN_BACKLOG
+/** Indicate that a TCP pcb has been accepted
+ * Called from netconn_accept
+ *
+ * @param m the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_accepted(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  msg->err = ERR_OK;
+  if (msg->conn->pcb.tcp != NULL) {
+    if (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP) {
+      tcp_backlog_accepted(msg->conn->pcb.tcp);
+    }
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+#endif /* TCP_LISTEN_BACKLOG */
+
+/**
+ * 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  WRITE_DELAYED_PARAM)
+{
+  err_t err;
+  const void *dataptr;
+  u16_t len, available;
+  u8_t write_finished = 0;
+  size_t diff;
+  u8_t dontblock;
+  u8_t 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);
+
+  apiflags = conn->current_msg->msg.w.apiflags;
+  dontblock = netconn_is_nonblocking(conn) || (apiflags & NETCONN_DONTBLOCK);
+
+#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;
+      conn->write_offset = 0;
+    }
+  } else
+#endif /* LWIP_SO_SNDTIMEO */
+  {
+    dataptr = (const 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;
+      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 {
+        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) {
+      err_t out_err;
+      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;
+      }
+      out_err = tcp_output(conn->pcb.tcp);
+      if (ERR_IS_FATAL(out_err) || (out_err == ERR_RTE)) {
+        /* If tcp_output fails with fatal error or no route is found,
+           don't try writing any more but return the error
+           to the application thread. */
+        err = out_err;
+        write_finished = 1;
+        conn->current_msg->msg.w.len = 0;
+      }
+    } else if (err == ERR_MEM) {
+      /* If ERR_MEM, we wait for sent_tcp or poll_tcp to be called.
+         For blocking sockets, we do NOT return to the application
+         thread, since ERR_MEM is only a temporary error! Non-blocking
+         will remain non-writable until sent_tcp/poll_tcp is called */
+
+      /* tcp_write returned ERR_MEM, try tcp_output anyway */
+      err_t out_err = tcp_output(conn->pcb.tcp);
+      if (ERR_IS_FATAL(out_err) || (out_err == ERR_RTE)) {
+        /* If tcp_output fails with fatal error or no route is found,
+           don't try writing any more but return the error
+           to the application thread. */
+        err = out_err;
+        write_finished = 1;
+        conn->current_msg->msg.w.len = 0;
+      } else if (dontblock) {
+        /* non-blocking write is done on ERR_MEM */
+        err = ERR_WOULDBLOCK;
+        write_finished = 1;
+        conn->current_msg->msg.w.len = 0;
+      }
+    } 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 */
+    sys_sem_t* op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg);
+    conn->current_msg->err = err;
+    conn->current_msg = NULL;
+    conn->write_offset = 0;
+    conn->state = NETCONN_NONE;
+    NETCONN_SET_SAFE_ERR(conn, err);
+#if LWIP_TCPIP_CORE_LOCKING
+    if (delayed)
+#endif
+    {
+      sys_sem_signal(op_completed_sem);
+    }
+  }
+#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 m the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_write(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  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
+        if (lwip_netconn_do_writemore(msg->conn, 0) != ERR_OK) {
+          LWIP_ASSERT("state!", msg->conn->state == NETCONN_WRITE);
+          UNLOCK_TCPIP_CORE();
+          sys_arch_sem_wait(LWIP_API_MSG_SEM(msg), 0);
+          LOCK_TCPIP_CORE();
+          LWIP_ASSERT("state!", msg->conn->state != NETCONN_WRITE);
+        }
+#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 m the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_getaddr(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  if (msg->conn->pcb.ip != NULL) {
+    if (msg->msg.ad.local) {
+      ip_addr_copy(API_EXPR_DEREF(msg->msg.ad.ipaddr),
+        msg->conn->pcb.ip->local_ip);
+    } else {
+      ip_addr_copy(API_EXPR_DEREF(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) {
+        API_EXPR_DEREF(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) {
+        API_EXPR_DEREF(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 {
+          API_EXPR_DEREF(msg->msg.ad.port) = msg->conn->pcb.udp->remote_port;
+        }
+      }
+      break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+    case NETCONN_TCP:
+      if ((msg->msg.ad.local == 0) &&
+          ((msg->conn->pcb.tcp->state == CLOSED) || (msg->conn->pcb.tcp->state == LISTEN))) {
+        /* pcb is not connected and remote name is requested */
+        msg->err = ERR_CONN;
+      } else {
+        API_EXPR_DEREF(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 or half-shutdown a TCP pcb contained in a netconn
+ * Called from netconn_close
+ * In contrast to closing sockets, the netconn is not deallocated.
+ *
+ * @param m the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_close(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+#if LWIP_TCP
+  enum netconn_state state = msg->conn->state;
+  /* First check if this is a TCP netconn and if it is in a correct state
+      (LISTEN doesn't support half shutdown) */
+  if ((msg->conn->pcb.tcp != NULL) &&
+      (NETCONNTYPE_GROUP(msg->conn->type) == NETCONN_TCP) &&
+      ((msg->msg.sd.shut == NETCONN_SHUT_RDWR) || (state != NETCONN_LISTEN))) {
+    /* Check if we are in a connected state */
+    if (state == NETCONN_CONNECT) {
+      /* TCP connect in progress: cannot shutdown */
+      msg->err = ERR_CONN;
+    } else if (state == NETCONN_WRITE) {
+#if LWIP_NETCONN_FULLDUPLEX
+      if (msg->msg.sd.shut & NETCONN_SHUT_WR) {
+        /* close requested, abort running write */
+        sys_sem_t* write_completed_sem;
+        LWIP_ASSERT("msg->conn->current_msg != NULL", msg->conn->current_msg != NULL);
+        write_completed_sem = LWIP_API_MSG_SEM(msg->conn->current_msg);
+        msg->conn->current_msg->err = ERR_CLSD;
+        msg->conn->current_msg = NULL;
+        msg->conn->write_offset = 0;
+        msg->conn->state = NETCONN_NONE;
+        state = NETCONN_NONE;
+        NETCONN_SET_SAFE_ERR(msg->conn, ERR_CLSD);
+        sys_sem_signal(write_completed_sem);
+      } else {
+        LWIP_ASSERT("msg->msg.sd.shut == NETCONN_SHUT_RD", msg->msg.sd.shut == NETCONN_SHUT_RD);
+        /* In this case, let the write continue and do not interfere with
+           conn->current_msg or conn->state! */
+        msg->err = tcp_shutdown(msg->conn->pcb.tcp, 1, 0);
+      }
+    }
+    if (state == NETCONN_NONE) {
+#else /* LWIP_NETCONN_FULLDUPLEX */
+      msg->err = ERR_INPROGRESS;
+    } else {
+#endif /* LWIP_NETCONN_FULLDUPLEX */
+      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;
+#if LWIP_TCPIP_CORE_LOCKING
+      if (lwip_netconn_do_close_internal(msg->conn, 0) != ERR_OK) {
+        LWIP_ASSERT("state!", msg->conn->state == NETCONN_CLOSE);
+        UNLOCK_TCPIP_CORE();
+        sys_arch_sem_wait(LWIP_API_MSG_SEM(msg), 0);
+        LOCK_TCPIP_CORE();
+        LWIP_ASSERT("state!", msg->conn->state == NETCONN_NONE);
+      }
+#else /* LWIP_TCPIP_CORE_LOCKING */
+      lwip_netconn_do_close_internal(msg->conn);
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+      /* for tcp netconns, lwip_netconn_do_close_internal ACKs the message */
+      return;
+    }
+  } else
+#endif /* LWIP_TCP */
+  {
+    msg->err = ERR_CONN;
+  }
+  TCPIP_APIMSG_ACK(msg);
+}
+
+#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
+/**
+ * Join multicast groups for UDP netconns.
+ * Called from netconn_join_leave_group
+ *
+ * @param m the api_msg_msg pointing to the connection
+ */
+void
+lwip_netconn_do_join_leave_group(void *m)
+{
+  struct api_msg *msg = (struct api_msg*)m;
+
+  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 (NETCONNTYPE_ISIPV6(msg->conn->type)) {
+          if (msg->msg.jl.join_or_leave == NETCONN_JOIN) {
+            msg->err = mld6_joingroup(ip_2_ip6(API_EXPR_REF(msg->msg.jl.netif_addr)),
+              ip_2_ip6(API_EXPR_REF(msg->msg.jl.multiaddr)));
+          } else {
+            msg->err = mld6_leavegroup(ip_2_ip6(API_EXPR_REF(msg->msg.jl.netif_addr)),
+              ip_2_ip6(API_EXPR_REF(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(ip_2_ip4(API_EXPR_REF(msg->msg.jl.netif_addr)),
+              ip_2_ip4(API_EXPR_REF(msg->msg.jl.multiaddr)));
+          } else {
+            msg->err = igmp_leavegroup(ip_2_ip4(API_EXPR_REF(msg->msg.jl.netif_addr)),
+              ip_2_ip4(API_EXPR_REF(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, const ip_addr_t *ipaddr, void *arg)
+{
+  struct dns_api_msg *msg = (struct dns_api_msg*)arg;
+
+  /* we trust the internal implementation to be correct :-) */
+  LWIP_UNUSED_ARG(name);
+
+  if (ipaddr == NULL) {
+    /* timeout or memory error */
+    API_EXPR_DEREF(msg->err) = ERR_VAL;
+  } else {
+    /* address was resolved */
+    API_EXPR_DEREF(msg->err) = ERR_OK;
+    API_EXPR_DEREF(msg->addr) = *ipaddr;
+  }
+  /* wake up the application task waiting in netconn_gethostbyname */
+  sys_sem_signal(API_EXPR_REF_SEM(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;
+  u8_t addrtype =
+#if LWIP_IPV4 && LWIP_IPV6
+    msg->dns_addrtype;
+#else
+    LWIP_DNS_ADDRTYPE_DEFAULT;
+#endif
+
+  API_EXPR_DEREF(msg->err) = dns_gethostbyname_addrtype(msg->name,
+    API_EXPR_REF(msg->addr), lwip_netconn_do_dns_found, msg, addrtype);
+  if (API_EXPR_DEREF(msg->err) != ERR_INPROGRESS) {
+    /* on error or immediate success, wake up the application
+     * task waiting in netconn_gethostbyname */
+    sys_sem_signal(API_EXPR_REF_SEM(msg->sem));
+  }
+}
+#endif /* LWIP_DNS */
+
+#endif /* LWIP_NETCONN */
diff --git a/src/api/err.c b/src/api/err.c
new file mode 100644
index 0000000..35e9c02
--- /dev/null
+++ b/src/api/err.c
@@ -0,0 +1,115 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+#include "lwip/err.h"
+#include "lwip/def.h"
+#include "lwip/sys.h"
+
+#include "lwip/errno.h"
+
+#if !NO_SYS
+/** 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_ALREADY    -9      Already connecting.      */
+  EISCONN,       /* ERR_ISCONN     -10     Conn already established.*/
+  ENOTCONN,      /* ERR_CONN       -11     Not connected.           */
+  -1,            /* ERR_IF         -12     Low-level netif error    */
+  ECONNABORTED,  /* ERR_ABRT       -13     Connection aborted.      */
+  ECONNRESET,    /* ERR_RST        -14     Connection reset.        */
+  ENOTCONN,      /* ERR_CLSD       -15     Connection closed.       */
+  EIO            /* ERR_ARG        -16     Illegal argument.        */
+};
+
+int
+err_to_errno(err_t err)
+{
+  if ((err > 0) || (-err >= (err_t)LWIP_ARRAYSIZE(err_to_errno_table))) {
+    return EIO;
+  }
+  return err_to_errno_table[-err];
+}
+#endif /* !NO_SYS */
+
+#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 connecting.",    /* ERR_ALREADY    -9  */
+           "Already connected.",     /* ERR_ISCONN     -10 */
+           "Not connected.",         /* ERR_CONN       -11 */
+           "Low-level netif error.", /* ERR_IF         -12 */
+           "Connection aborted.",    /* ERR_ABRT       -13 */
+           "Connection reset.",      /* ERR_RST        -14 */
+           "Connection closed.",     /* ERR_CLSD       -15 */
+           "Illegal argument."       /* ERR_ARG        -16 */
+};
+
+/**
+ * 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)
+{
+  if ((err > 0) || (-err >= (err_t)LWIP_ARRAYSIZE(err_strerr))) {
+    return "Unknown error.";
+  }
+  return err_strerr[-err];
+}
+
+#endif /* LWIP_DEBUG */
diff --git a/src/api/netbuf.c b/src/api/netbuf.c
new file mode 100644
index 0000000..eb25011
--- /dev/null
+++ b/src/api/netbuf.c
@@ -0,0 +1,246 @@
+/**
+ * @file
+ * Network buffer management
+ *
+ * @defgroup netbuf Network buffers
+ * @ingroup netconn
+ * Network buffer descriptor for @ref netconn. Based on @ref pbuf internally
+ * to avoid copying data around.\n
+ * Buffers must not be shared accross multiple threads, all functions except
+ * netbuf_new() and netbuf_delete() are not thread-safe.
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+
+#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>
+
+/**
+ * @ingroup netbuf
+ * 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) {
+    memset(buf, 0, sizeof(struct netbuf));
+  }
+  return buf;
+}
+
+/**
+ * @ingroup netbuf
+ * 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);
+  }
+}
+
+/**
+ * @ingroup netbuf
+ * 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;
+}
+
+/**
+ * @ingroup netbuf
+ * 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;
+}
+
+/**
+ * @ingroup netbuf
+ * 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;
+  }
+  ((struct pbuf_rom*)buf->p)->payload = dataptr;
+  buf->p->len = buf->p->tot_len = size;
+  buf->ptr = buf->p;
+  return ERR_OK;
+}
+
+/**
+ * @ingroup netbuf
+ * 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_chain: 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);
+}
+
+/**
+ * @ingroup netbuf
+ * 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 retrieved,
+ *         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;
+}
+
+/**
+ * @ingroup netbuf
+ * 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_next: 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;
+}
+
+/**
+ * @ingroup netbuf
+ * 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_first: invalid buf", (buf != NULL), return;);
+  buf->ptr = buf->p;
+}
+
+#endif /* LWIP_NETCONN */
diff --git a/src/api/netdb.c b/src/api/netdb.c
new file mode 100644
index 0000000..ccd9586
--- /dev/null
+++ b/src/api/netdb.c
@@ -0,0 +1,413 @@
+/**
+ * @file
+ * API functions for name resolving
+ *
+ * @defgroup netdbapi NETDB API
+ * @ingroup socket
+ */
+
+/*
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce 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> /* memset */
+#include <stdlib.h> /* atoi */
+
+/** 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];
+  HOSTENT_STORAGE char s_hostname[DNS_MAX_NAME_LENGTH + 1];
+
+  /* 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;
+  strncpy(s_hostname, name, DNS_MAX_NAME_LENGTH);
+  s_hostname[DNS_MAX_NAME_LENGTH] = 0;
+  s_hostent.h_name = s_hostname;
+  s_aliases = NULL;
+  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", (void*)s_hostent.h_aliases));
+  /* h_aliases are always empty */
+  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", (void*)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, ipaddr_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
+ *
+ * @todo: implement AI_V4MAPPED, AI_ADDRCONFIG
+ */
+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_storage *sa = NULL;
+  int port_nr = 0;
+  size_t total_size;
+  size_t namelen = 0;
+  int ai_family;
+
+  if (res == NULL) {
+    return EAI_FAIL;
+  }
+  *res = NULL;
+  if ((nodename == NULL) && (servname == NULL)) {
+    return EAI_NONAME;
+  }
+
+  if (hints != NULL) {
+    ai_family = hints->ai_family;
+    if ((ai_family != AF_UNSPEC)
+#if LWIP_IPV4
+      && (ai_family != AF_INET)
+#endif /* LWIP_IPV4 */
+#if LWIP_IPV6
+      && (ai_family != AF_INET6)
+#endif /* LWIP_IPV6 */
+      ) {
+      return EAI_FAMILY;
+    }
+  } else {
+    ai_family = AF_UNSPEC;
+  }
+
+  if (servname != NULL) {
+    /* service name specified: convert to port number
+     * @todo?: currently, only ASCII integers (port numbers) are supported (AI_NUMERICSERV)! */
+    port_nr = atoi(servname);
+    if ((port_nr <= 0) || (port_nr > 0xffff)) {
+      return EAI_SERVICE;
+    }
+  }
+
+  if (nodename != NULL) {
+    /* service location specified, try to resolve */
+    if ((hints != NULL) && (hints->ai_flags & AI_NUMERICHOST)) {
+      /* no DNS lookup, just parse for an address string */
+      if (!ipaddr_aton(nodename, &addr)) {
+        return EAI_NONAME;
+      }
+#if LWIP_IPV4 && LWIP_IPV6
+      if ((IP_IS_V6_VAL(addr) && ai_family == AF_INET) ||
+          (IP_IS_V4_VAL(addr) && ai_family == AF_INET6)) {
+        return EAI_NONAME;
+      }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+    } else {
+#if LWIP_IPV4 && LWIP_IPV6
+      /* AF_UNSPEC: prefer IPv4 */
+      u8_t type = NETCONN_DNS_IPV4_IPV6;
+      if (ai_family == AF_INET) {
+        type = NETCONN_DNS_IPV4;
+      } else if (ai_family == AF_INET6) {
+        type = NETCONN_DNS_IPV6;
+      }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+      err = netconn_gethostbyname_addrtype(nodename, &addr, type);
+      if (err != ERR_OK) {
+        return EAI_FAIL;
+      }
+    }
+  } else {
+    /* service location specified, use loopback address */
+    if ((hints != NULL) && (hints->ai_flags & AI_PASSIVE)) {
+      ip_addr_set_any(ai_family == AF_INET6, &addr);
+    } else {
+      ip_addr_set_loopback(ai_family == AF_INET6, &addr);
+    }
+  }
+
+  total_size = sizeof(struct addrinfo) + sizeof(struct sockaddr_storage);
+  if (nodename != NULL) {
+    namelen = strlen(nodename);
+    if (namelen > DNS_MAX_NAME_LENGTH) {
+      /* invalid name length */
+      return EAI_FAIL;
+    }
+    LWIP_ASSERT("namelen is too long", total_size + namelen + 1 > total_size);
+    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) {
+    return EAI_MEMORY;
+  }
+  memset(ai, 0, total_size);
+  /* cast through void* to get rid of alignment warnings */
+  sa = (struct sockaddr_storage *)(void*)((u8_t*)ai + sizeof(struct addrinfo));
+  if (IP_IS_V6_VAL(addr)) {
+#if LWIP_IPV6
+    struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa;
+    /* set up sockaddr */
+    inet6_addr_from_ip6addr(&sa6->sin6_addr, ip_2_ip6(&addr));
+    sa6->sin6_family = AF_INET6;
+    sa6->sin6_len = sizeof(struct sockaddr_in6);
+    sa6->sin6_port = lwip_htons((u16_t)port_nr);
+    ai->ai_family = AF_INET6;
+#endif /* LWIP_IPV6 */
+  } else {
+#if LWIP_IPV4
+    struct sockaddr_in *sa4 = (struct sockaddr_in*)sa;
+    /* set up sockaddr */
+    inet_addr_from_ip4addr(&sa4->sin_addr, ip_2_ip4(&addr));
+    sa4->sin_family = AF_INET;
+    sa4->sin_len = sizeof(struct sockaddr_in);
+    sa4->sin_port = lwip_htons((u16_t)port_nr);
+    ai->ai_family = AF_INET;
+#endif /* LWIP_IPV4 */
+  }
+
+  /* set up addrinfo */
+  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_storage));
+    MEMCPY(ai->ai_canonname, nodename, namelen);
+    ai->ai_canonname[namelen] = 0;
+  }
+  ai->ai_addrlen = sizeof(struct sockaddr_storage);
+  ai->ai_addr = (struct sockaddr*)sa;
+
+  *res = ai;
+
+  return 0;
+}
+
+#endif /* LWIP_DNS && LWIP_SOCKET */
diff --git a/src/api/netifapi.c b/src/api/netifapi.c
new file mode 100644
index 0000000..fef05a3
--- /dev/null
+++ b/src/api/netifapi.c
@@ -0,0 +1,221 @@
+/**
+ * @file
+ * Network Interface Sequential API module
+ *
+ * @defgroup netifapi NETIF API
+ * @ingroup sequential_api
+ * Thread-safe functions to be called from non-TCPIP threads
+ * 
+ * @defgroup netifapi_netif NETIF related
+ * @ingroup netifapi
+ * To be called from non-TCPIP threads 
+ */
+
+/*
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce 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/memp.h"
+#include "lwip/priv/tcpip_priv.h"
+
+#define NETIFAPI_VAR_REF(name)      API_VAR_REF(name)
+#define NETIFAPI_VAR_DECLARE(name)  API_VAR_DECLARE(struct netifapi_msg, name)
+#define NETIFAPI_VAR_ALLOC(name)    API_VAR_ALLOC(struct netifapi_msg, MEMP_NETIFAPI_MSG, name, ERR_MEM)
+#define NETIFAPI_VAR_FREE(name)     API_VAR_FREE(MEMP_NETIFAPI_MSG, name)
+
+/**
+ * Call netif_add() inside the tcpip_thread context.
+ */
+static err_t
+netifapi_do_netif_add(struct tcpip_api_call_data *m)
+{
+  /* cast through void* to silence alignment warnings. 
+   * We know it works because the structs have been instantiated as struct netifapi_msg */
+  struct netifapi_msg *msg = (struct netifapi_msg*)(void*)m;
+  
+  if (!netif_add( msg->netif,
+#if LWIP_IPV4
+                  API_EXPR_REF(msg->msg.add.ipaddr),
+                  API_EXPR_REF(msg->msg.add.netmask),
+                  API_EXPR_REF(msg->msg.add.gw),
+#endif /* LWIP_IPV4 */
+                  msg->msg.add.state,
+                  msg->msg.add.init,
+                  msg->msg.add.input)) {
+    return ERR_IF;
+  } else {
+    return ERR_OK;
+  }
+}
+
+#if LWIP_IPV4
+/**
+ * Call netif_set_addr() inside the tcpip_thread context.
+ */
+static err_t
+netifapi_do_netif_set_addr(struct tcpip_api_call_data *m)
+{
+  /* cast through void* to silence alignment warnings. 
+   * We know it works because the structs have been instantiated as struct netifapi_msg */
+  struct netifapi_msg *msg = (struct netifapi_msg*)(void*)m;
+
+  netif_set_addr( msg->netif,
+                  API_EXPR_REF(msg->msg.add.ipaddr),
+                  API_EXPR_REF(msg->msg.add.netmask),
+                  API_EXPR_REF(msg->msg.add.gw));
+  return ERR_OK;
+}
+#endif /* LWIP_IPV4 */
+
+/**
+ * Call the "errtfunc" (or the "voidfunc" if "errtfunc" is NULL) inside the
+ * tcpip_thread context.
+ */
+static err_t
+netifapi_do_netif_common(struct tcpip_api_call_data *m)
+{
+  /* cast through void* to silence alignment warnings. 
+   * We know it works because the structs have been instantiated as struct netifapi_msg */
+  struct netifapi_msg *msg = (struct netifapi_msg*)(void*)m;
+
+  if (msg->msg.common.errtfunc != NULL) {
+    return msg->msg.common.errtfunc(msg->netif);
+  } else {
+    msg->msg.common.voidfunc(msg->netif);
+    return ERR_OK;
+  }
+}
+
+/**
+ * @ingroup netifapi_netif
+ * 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,
+#if LWIP_IPV4
+                   const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
+#endif /* LWIP_IPV4 */
+                   void *state, netif_init_fn init, netif_input_fn input)
+{
+  err_t err;
+  NETIFAPI_VAR_DECLARE(msg);
+  NETIFAPI_VAR_ALLOC(msg);
+
+#if LWIP_IPV4
+  if (ipaddr == NULL) {
+    ipaddr = IP4_ADDR_ANY4;
+  }
+  if (netmask == NULL) {
+    netmask = IP4_ADDR_ANY4;
+  }
+  if (gw == NULL) {
+    gw = IP4_ADDR_ANY4;
+  }
+#endif /* LWIP_IPV4 */
+
+  NETIFAPI_VAR_REF(msg).netif = netif;
+#if LWIP_IPV4
+  NETIFAPI_VAR_REF(msg).msg.add.ipaddr  = NETIFAPI_VAR_REF(ipaddr);
+  NETIFAPI_VAR_REF(msg).msg.add.netmask = NETIFAPI_VAR_REF(netmask);
+  NETIFAPI_VAR_REF(msg).msg.add.gw      = NETIFAPI_VAR_REF(gw);
+#endif /* LWIP_IPV4 */
+  NETIFAPI_VAR_REF(msg).msg.add.state   = state;
+  NETIFAPI_VAR_REF(msg).msg.add.init    = init;
+  NETIFAPI_VAR_REF(msg).msg.add.input   = input;
+  err = tcpip_api_call(netifapi_do_netif_add, &API_VAR_REF(msg).call);
+  NETIFAPI_VAR_FREE(msg);
+  return err;
+}
+
+#if LWIP_IPV4
+/**
+ * @ingroup netifapi_netif
+ * 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,
+                        const ip4_addr_t *ipaddr,
+                        const ip4_addr_t *netmask,
+                        const ip4_addr_t *gw)
+{
+  err_t err;
+  NETIFAPI_VAR_DECLARE(msg);
+  NETIFAPI_VAR_ALLOC(msg);
+
+  if (ipaddr == NULL) {
+    ipaddr = IP4_ADDR_ANY4;
+  }
+  if (netmask == NULL) {
+    netmask = IP4_ADDR_ANY4;
+  }
+  if (gw == NULL) {
+    gw = IP4_ADDR_ANY4;
+  }
+
+  NETIFAPI_VAR_REF(msg).netif = netif;
+  NETIFAPI_VAR_REF(msg).msg.add.ipaddr  = NETIFAPI_VAR_REF(ipaddr);
+  NETIFAPI_VAR_REF(msg).msg.add.netmask = NETIFAPI_VAR_REF(netmask);
+  NETIFAPI_VAR_REF(msg).msg.add.gw      = NETIFAPI_VAR_REF(gw);
+  err = tcpip_api_call(netifapi_do_netif_set_addr, &API_VAR_REF(msg).call);
+  NETIFAPI_VAR_FREE(msg);
+  return err;
+}
+#endif /* LWIP_IPV4 */
+
+/**
+ * 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)
+{
+  err_t err;
+  NETIFAPI_VAR_DECLARE(msg);
+  NETIFAPI_VAR_ALLOC(msg);
+
+  NETIFAPI_VAR_REF(msg).netif = netif;
+  NETIFAPI_VAR_REF(msg).msg.common.voidfunc = voidfunc;
+  NETIFAPI_VAR_REF(msg).msg.common.errtfunc = errtfunc;
+  err = tcpip_api_call(netifapi_do_netif_common, &API_VAR_REF(msg).call);
+  NETIFAPI_VAR_FREE(msg);
+  return err;
+}
+
+#endif /* LWIP_NETIF_API */
diff --git a/src/api/sockets.c b/src/api/sockets.c
new file mode 100644
index 0000000..b763248
--- /dev/null
+++ b/src/api/sockets.c
@@ -0,0 +1,2827 @@
+/**
+ * @file
+ * Sockets BSD-Like API module
+ *
+ * @defgroup socket Socket API
+ * @ingroup sequential_api
+ * BSD-style socket API.\n
+ * Thread-safe, to be called from non-TCPIP threads only.\n
+ * Can be activated by defining @ref LWIP_SOCKET to 1.\n
+ * Header is in posix/sys/socket.h\b
+ */
+
+/*
+ * 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@sics.se>
+ *
+ * Improved by Marc Boucher <marc@mbsi.ca> and David Haas <dhaas@alum.rpi.edu>
+ *
+ */
+
+#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/memp.h"
+#include "lwip/pbuf.h"
+#include "lwip/priv/tcpip_priv.h"
+#if LWIP_CHECKSUM_ON_COPY
+#include "lwip/inet_chksum.h"
+#endif
+
+#include <string.h>
+
+/* If the netconn API is not required publicly, then we include the necessary
+   files here to get the implementation */
+#if !LWIP_NETCONN
+#undef LWIP_NETCONN
+#define LWIP_NETCONN 1
+#include "api_msg.c"
+#include "api_lib.c"
+#include "netbuf.c"
+#undef LWIP_NETCONN
+#define LWIP_NETCONN 0
+#endif
+
+#if LWIP_IPV4
+#define IP4ADDR_PORT_TO_SOCKADDR(sin, ipaddr, port) do { \
+      (sin)->sin_len = sizeof(struct sockaddr_in); \
+      (sin)->sin_family = AF_INET; \
+      (sin)->sin_port = lwip_htons((port)); \
+      inet_addr_from_ip4addr(&(sin)->sin_addr, ipaddr); \
+      memset((sin)->sin_zero, 0, SIN_ZERO_LEN); }while(0)
+#define SOCKADDR4_TO_IP4ADDR_PORT(sin, ipaddr, port) do { \
+    inet_addr_to_ip4addr(ip_2_ip4(ipaddr), &((sin)->sin_addr)); \
+    (port) = lwip_ntohs((sin)->sin_port); }while(0)
+#endif /* LWIP_IPV4 */
+
+#if LWIP_IPV6
+#define IP6ADDR_PORT_TO_SOCKADDR(sin6, ipaddr, port) do { \
+      (sin6)->sin6_len = sizeof(struct sockaddr_in6); \
+      (sin6)->sin6_family = AF_INET6; \
+      (sin6)->sin6_port = lwip_htons((port)); \
+      (sin6)->sin6_flowinfo = 0; \
+      inet6_addr_from_ip6addr(&(sin6)->sin6_addr, ipaddr); \
+      (sin6)->sin6_scope_id = 0; }while(0)
+#define SOCKADDR6_TO_IP6ADDR_PORT(sin6, ipaddr, port) do { \
+    inet6_addr_to_ip6addr(ip_2_ip6(ipaddr), &((sin6)->sin6_addr)); \
+    (port) = lwip_ntohs((sin6)->sin6_port); }while(0)
+#endif /* LWIP_IPV6 */
+
+#if LWIP_IPV4 && LWIP_IPV6
+static void sockaddr_to_ipaddr_port(const struct sockaddr* sockaddr, ip_addr_t* ipaddr, u16_t* port);
+
+#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 IPADDR_PORT_TO_SOCKADDR(sockaddr, ipaddr, port) do { \
+    if (IP_IS_V6(ipaddr)) { \
+      IP6ADDR_PORT_TO_SOCKADDR((struct sockaddr_in6*)(void*)(sockaddr), ip_2_ip6(ipaddr), port); \
+    } else { \
+      IP4ADDR_PORT_TO_SOCKADDR((struct sockaddr_in*)(void*)(sockaddr), ip_2_ip4(ipaddr), port); \
+    } } while(0)
+#define SOCKADDR_TO_IPADDR_PORT(sockaddr, ipaddr, port) sockaddr_to_ipaddr_port(sockaddr, ipaddr, &(port))
+#define DOMAIN_TO_NETCONN_TYPE(domain, type) (((domain) == AF_INET) ? \
+  (type) : (enum netconn_type)((type) | NETCONN_TYPE_IPV6))
+#elif LWIP_IPV6 /* LWIP_IPV4 && LWIP_IPV6 */
+#define IS_SOCK_ADDR_LEN_VALID(namelen)  ((namelen) == sizeof(struct sockaddr_in6))
+#define IS_SOCK_ADDR_TYPE_VALID(name)    ((name)->sa_family == AF_INET6)
+#define SOCK_ADDR_TYPE_MATCH(name, sock) 1
+#define IPADDR_PORT_TO_SOCKADDR(sockaddr, ipaddr, port) \
+        IP6ADDR_PORT_TO_SOCKADDR((struct sockaddr_in6*)(void*)(sockaddr), ip_2_ip6(ipaddr), port)
+#define SOCKADDR_TO_IPADDR_PORT(sockaddr, ipaddr, port) \
+        SOCKADDR6_TO_IP6ADDR_PORT((const struct sockaddr_in6*)(const void*)(sockaddr), ipaddr, port)
+#define DOMAIN_TO_NETCONN_TYPE(domain, netconn_type) (netconn_type)
+#else /*-> LWIP_IPV4: LWIP_IPV4 && 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 IPADDR_PORT_TO_SOCKADDR(sockaddr, ipaddr, port) \
+        IP4ADDR_PORT_TO_SOCKADDR((struct sockaddr_in*)(void*)(sockaddr), ip_2_ip4(ipaddr), port)
+#define SOCKADDR_TO_IPADDR_PORT(sockaddr, ipaddr, port) \
+        SOCKADDR4_TO_IP4ADDR_PORT((const struct sockaddr_in*)(const void*)(sockaddr), ipaddr, 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 LWIP_SOCKOPT_CHECK_OPTLEN(optlen, opttype) do { if ((optlen) < sizeof(opttype)) { return EINVAL; }}while(0)
+#define LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, optlen, opttype) do { \
+  LWIP_SOCKOPT_CHECK_OPTLEN(optlen, opttype); \
+  if ((sock)->conn == NULL) { return EINVAL; } }while(0)
+#define LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, optlen, opttype) do { \
+  LWIP_SOCKOPT_CHECK_OPTLEN(optlen, opttype); \
+  if (((sock)->conn == NULL) || ((sock)->conn->pcb.tcp == NULL)) { return EINVAL; } }while(0)
+#define LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, opttype, netconntype) do { \
+  LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, optlen, opttype); \
+  if (NETCONNTYPE_GROUP(netconn_type((sock)->conn)) != netconntype) { return ENOPROTOOPT; } }while(0)
+
+
+#define LWIP_SETGETSOCKOPT_DATA_VAR_REF(name)     API_VAR_REF(name)
+#define LWIP_SETGETSOCKOPT_DATA_VAR_DECLARE(name) API_VAR_DECLARE(struct lwip_setgetsockopt_data, name)
+#define LWIP_SETGETSOCKOPT_DATA_VAR_FREE(name)    API_VAR_FREE(MEMP_SOCKET_SETGETSOCKOPT_DATA, name)
+#if LWIP_MPU_COMPATIBLE
+#define LWIP_SETGETSOCKOPT_DATA_VAR_ALLOC(name, sock) do { \
+  name = (struct lwip_setgetsockopt_data *)memp_malloc(MEMP_SOCKET_SETGETSOCKOPT_DATA); \
+  if (name == NULL) { \
+    sock_set_errno(sock, ENOMEM); \
+    return -1; \
+  } }while(0)
+#else /* LWIP_MPU_COMPATIBLE */
+#define LWIP_SETGETSOCKOPT_DATA_VAR_ALLOC(name, sock)
+#endif /* LWIP_MPU_COMPATIBLE */
+
+#if LWIP_SO_SNDRCVTIMEO_NONSTANDARD
+#define LWIP_SO_SNDRCVTIMEO_OPTTYPE int
+#define LWIP_SO_SNDRCVTIMEO_SET(optval, val) (*(int *)(optval) = (val))
+#define LWIP_SO_SNDRCVTIMEO_GET_MS(optval)   ((s32_t)*(const int*)(optval))
+#else
+#define LWIP_SO_SNDRCVTIMEO_OPTTYPE struct timeval
+#define LWIP_SO_SNDRCVTIMEO_SET(optval, val)  do { \
+  s32_t loc = (val); \
+  ((struct timeval *)(optval))->tv_sec = (loc) / 1000U; \
+  ((struct timeval *)(optval))->tv_usec = ((loc) % 1000U) * 1000U; }while(0)
+#define LWIP_SO_SNDRCVTIMEO_GET_MS(optval) ((((const struct timeval *)(optval))->tv_sec * 1000U) + (((const struct timeval *)(optval))->tv_usec / 1000U))
+#endif
+
+#define NUM_SOCKETS MEMP_NUM_NETCONN
+
+/** This is overridable for the rare case where more than 255 threads
+ * select on the same socket...
+ */
+#ifndef SELWAIT_T
+#define SELWAIT_T u8_t
+#endif
+
+/** 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 (in fact, all our errnos fit into an u8_t) */
+  u8_t err;
+  /** counter of how many threads are waiting for this socket using select */
+  SELWAIT_T select_waiting;
+};
+
+#if LWIP_NETCONN_SEM_PER_THREAD
+#define SELECT_SEM_T        sys_sem_t*
+#define SELECT_SEM_PTR(sem) (sem)
+#else /* LWIP_NETCONN_SEM_PER_THREAD */
+#define SELECT_SEM_T        sys_sem_t
+#define SELECT_SEM_PTR(sem) (&(sem))
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+/** 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 */
+  SELECT_SEM_T sem;
+};
+
+/** 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 */
+#if LWIP_IPV4
+   struct sockaddr_in sin;
+#endif /* LWIP_IPV4 */
+};
+
+#if LWIP_IGMP
+/* Define the number of IPv4 multicast memberships, default is one per socket */
+#ifndef LWIP_SOCKET_MAX_MEMBERSHIPS
+#define LWIP_SOCKET_MAX_MEMBERSHIPS NUM_SOCKETS
+#endif
+
+/* This is to keep track of IP_ADD_MEMBERSHIP calls to drop the membership when
+   a socket is closed */
+struct lwip_socket_multicast_pair {
+  /** the socket */
+  struct lwip_sock* sock;
+  /** the interface address */
+  ip4_addr_t if_addr;
+  /** the group address */
+  ip4_addr_t multi_addr;
+};
+
+struct lwip_socket_multicast_pair socket_ipv4_multicast_memberships[LWIP_SOCKET_MAX_MEMBERSHIPS];
+
+static int  lwip_socket_register_membership(int s, const ip4_addr_t *if_addr, const ip4_addr_t *multi_addr);
+static void lwip_socket_unregister_membership(int s, const ip4_addr_t *if_addr, const ip4_addr_t *multi_addr);
+static void lwip_socket_drop_registered_memberships(int s);
+#endif /* LWIP_IGMP */
+
+/** 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 changed
+    and checked in event_callback to see if it has changed. */
+static volatile int select_cb_ctr;
+
+#if LWIP_SOCKET_SET_ERRNO
+#ifndef set_errno
+#define set_errno(err) do { if (err) { errno = (err); } } while(0)
+#endif
+#else /* LWIP_SOCKET_SET_ERRNO */
+#define set_errno(err)
+#endif /* LWIP_SOCKET_SET_ERRNO */
+
+#define sock_set_errno(sk, e) do { \
+  const int sockerr = (e); \
+  sk->err = (u8_t)sockerr; \
+  set_errno(sockerr); \
+} while (0)
+
+/* Forward declaration of some functions */
+static void event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len);
+#if !LWIP_TCPIP_CORE_LOCKING
+static void lwip_getsockopt_callback(void *arg);
+static void lwip_setsockopt_callback(void *arg);
+#endif
+static u8_t lwip_getsockopt_impl(int s, int level, int optname, void *optval, socklen_t *optlen);
+static u8_t lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_t optlen);
+
+#if LWIP_IPV4 && LWIP_IPV6
+static void
+sockaddr_to_ipaddr_port(const struct sockaddr* sockaddr, ip_addr_t* ipaddr, u16_t* port)
+{
+  if ((sockaddr->sa_family) == AF_INET6) {
+    SOCKADDR6_TO_IP6ADDR_PORT((const struct sockaddr_in6*)(const void*)(sockaddr), ipaddr, *port);
+    ipaddr->type = IPADDR_TYPE_V6;
+  } else {
+    SOCKADDR4_TO_IP4ADDR_PORT((const struct sockaddr_in*)(const void*)(sockaddr), ipaddr, *port);
+    ipaddr->type = IPADDR_TYPE_V4;
+  }
+}
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+/** LWIP_NETCONN_SEM_PER_THREAD==1: initialize thread-local semaphore */
+void
+lwip_socket_thread_init(void)
+{
+   netconn_thread_init();
+}
+
+/** LWIP_NETCONN_SEM_PER_THREAD==1: destroy thread-local semaphore */
+void
+lwip_socket_thread_cleanup(void)
+{
+   netconn_thread_cleanup();
+}
+
+/**
+ * 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;
+
+  s -= LWIP_SOCKET_OFFSET;
+
+  if ((s < 0) || (s >= NUM_SOCKETS)) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("get_socket(%d): invalid\n", s + LWIP_SOCKET_OFFSET));
+    set_errno(EBADF);
+    return NULL;
+  }
+
+  sock = &sockets[s];
+
+  if (!sock->conn) {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("get_socket(%d): not active\n", s + LWIP_SOCKET_OFFSET));
+    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)
+{
+  s -= LWIP_SOCKET_OFFSET;
+  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].select_waiting == 0)) {
+      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;
+      return i + LWIP_SOCKET_OFFSET;
+    }
+    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;
+
+  lastdata         = sock->lastdata;
+  sock->lastdata   = NULL;
+  sock->lastoffset = 0;
+  sock->err        = 0;
+
+  /* Protect socket array */
+  SYS_ARCH_SET(sock->conn, NULL);
+  /* 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;
+  ip_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));
+    set_errno(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);
+    } else if (err == ERR_CLSD) {
+      sock_set_errno(sock, EINVAL);
+    } else {
+      sock_set_errno(sock, err_to_errno(err));
+    }
+    return -1;
+  }
+  LWIP_ASSERT("newconn != NULL", newconn != NULL);
+
+  newsock = alloc_socket(newconn, 1);
+  if (newsock == -1) {
+    netconn_delete(newconn);
+    sock_set_errno(sock, ENFILE);
+    return -1;
+  }
+  LWIP_ASSERT("invalid socket index", (newsock >= LWIP_SOCKET_OFFSET) && (newsock < NUM_SOCKETS + LWIP_SOCKET_OFFSET));
+  LWIP_ASSERT("newconn->callback == event_callback", newconn->callback == event_callback);
+  nsock = &sockets[newsock - LWIP_SOCKET_OFFSET];
+
+  /* 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);
+
+  /* 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, &naddr, &port);
+    if (err != ERR_OK) {
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d): netconn_peer failed, err=%d\n", s, err));
+      netconn_delete(newconn);
+      free_socket(nsock, 1);
+      sock_set_errno(sock, err_to_errno(err));
+      return -1;
+    }
+    LWIP_ASSERT("addr valid but addrlen NULL", addrlen != NULL);
+
+    IPADDR_PORT_TO_SOCKADDR(&tempaddr, &naddr, port);
+    if (*addrlen > tempaddr.sa.sa_len) {
+      *addrlen = tempaddr.sa.sa_len;
+    }
+    MEMCPY(addr, &tempaddr, *addrlen);
+
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d) returning new sock=%d addr=", s, newsock));
+    ip_addr_debug_print_val(SOCKETS_DEBUG, naddr);
+    LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F"\n", port));
+  } else {
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d) returning new sock=%d", s, newsock));
+  }
+
+  sock_set_errno(sock, 0);
+  return newsock;
+}
+
+int
+lwip_bind(int s, const struct sockaddr *name, socklen_t namelen)
+{
+  struct lwip_sock *sock;
+  ip_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, family 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_IPADDR_PORT(name, &local_addr, local_port);
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_bind(%d, addr=", s));
+  ip_addr_debug_print_val(SOCKETS_DEBUG, local_addr);
+  LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F")\n", local_port));
+
+#if LWIP_IPV4 && LWIP_IPV6
+  /* Dual-stack: Unmap IPv4 mapped IPv6 addresses */
+  if (IP_IS_V6_VAL(local_addr) && ip6_addr_isipv4mappedipv6(ip_2_ip6(&local_addr))) {
+    unmap_ipv4_mapped_ipv6(ip_2_ip4(&local_addr), ip_2_ip6(&local_addr));
+    IP_SET_TYPE_VAL(local_addr, IPADDR_TYPE_V4);
+  }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+  err = netconn_bind(sock->conn, &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;
+  err_t err;
+
+  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);
+  }
+
+#if LWIP_IGMP
+  /* drop all possibly joined IGMP memberships */
+  lwip_socket_drop_registered_memberships(s);
+#endif /* LWIP_IGMP */
+
+  err = netconn_delete(sock->conn);
+  if (err != ERR_OK) {
+    sock_set_errno(sock, err_to_errno(err));
+    return -1;
+  }
+
+  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;
+  }
+
+  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 {
+    ip_addr_t remote_addr;
+    u16_t remote_port;
+
+    /* check size, family 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;);
+
+    SOCKADDR_TO_IPADDR_PORT(name, &remote_addr, remote_port);
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d, addr=", s));
+    ip_addr_debug_print_val(SOCKETS_DEBUG, remote_addr);
+    LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F")\n", remote_port));
+
+#if LWIP_IPV4 && LWIP_IPV6
+    /* Dual-stack: Unmap IPv4 mapped IPv6 addresses */
+    if (IP_IS_V6_VAL(remote_addr) && ip6_addr_isipv4mappedipv6(ip_2_ip6(&remote_addr))) {
+      unmap_ipv4_mapped_ipv6(ip_2_ip4(&remote_addr), ip_2_ip6(&remote_addr));
+      IP_SET_TYPE_VAL(remote_addr, IPADDR_TYPE_V4);
+    }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+    err = netconn_connect(sock->conn, &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 -1;
+    }
+    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) {
+          /* already received data, return that */
+          sock_set_errno(sock, 0);
+          return off;
+        }
+        LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvfrom(%d): returning EWOULDBLOCK\n", s));
+        set_errno(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) {
+          if (err == ERR_CLSD) {
+            /* closed but already received data, ensure select gets the FIN, too */
+            event_callback(sock->conn, NETCONN_EVT_RCVPLUS, 0);
+          }
+          /* 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;
+        ip_addr_t tmpaddr;
+        ip_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;
+          netconn_getaddr(sock->conn, fromaddr, &port, 0);
+        } else {
+          port = netbuf_fromport((struct netbuf *)buf);
+          fromaddr = netbuf_fromaddr((struct netbuf *)buf);
+        }
+
+#if LWIP_IPV4 && LWIP_IPV6
+        /* Dual-stack: Map IPv4 addresses to IPv4 mapped IPv6 */
+        if (NETCONNTYPE_ISIPV6(netconn_type(sock->conn)) && IP_IS_V4(fromaddr)) {
+          ip4_2_ipv4_mapped_ipv6(ip_2_ip6(fromaddr), ip_2_ip4(fromaddr));
+          IP_SET_TYPE(fromaddr, IPADDR_TYPE_V6);
+        }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+        IPADDR_PORT_TO_SOCKADDR(&saddr, fromaddr, port);
+        ip_addr_debug_print(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);
+        }
+        buf = NULL;
+      }
+    }
+  } while (!done);
+
+  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_sendmsg(int s, const struct msghdr *msg, int flags)
+{
+  struct lwip_sock *sock;
+  int i;
+#if LWIP_TCP
+  u8_t write_flags;
+  size_t written;
+#endif
+  int size = 0;
+  err_t err = ERR_OK;
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  LWIP_ERROR("lwip_sendmsg: invalid msghdr", msg != NULL,
+             sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
+
+  LWIP_UNUSED_ARG(msg->msg_control);
+  LWIP_UNUSED_ARG(msg->msg_controllen);
+  LWIP_UNUSED_ARG(msg->msg_flags);
+  LWIP_ERROR("lwip_sendmsg: invalid msghdr iov", (msg->msg_iov != NULL && msg->msg_iovlen != 0),
+             sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
+
+  if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP) {
+#if LWIP_TCP
+    write_flags = NETCONN_COPY |
+    ((flags & MSG_MORE)     ? NETCONN_MORE      : 0) |
+    ((flags & MSG_DONTWAIT) ? NETCONN_DONTBLOCK : 0);
+
+    for (i = 0; i < msg->msg_iovlen; i++) {
+      u8_t apiflags = write_flags;
+      if (i + 1 < msg->msg_iovlen) {
+        apiflags |= NETCONN_MORE;
+      }
+      written = 0;
+      err = netconn_write_partly(sock->conn, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len, write_flags, &written);
+      if (err == ERR_OK) {
+        size += written;
+        /* check that the entire IO vector was accepected, if not return a partial write */
+        if (written != msg->msg_iov[i].iov_len)
+          break;
+      }
+      /* none of this IO vector was accepted, but previous was, return partial write and conceal ERR_WOULDBLOCK */
+      else if (err == ERR_WOULDBLOCK && size > 0) {
+        err = ERR_OK;
+        /* let ERR_WOULDBLOCK persist on the netconn since we are returning ERR_OK */
+        break;
+      } else {
+        size = -1;
+        break;
+      }
+    }
+    sock_set_errno(sock, err_to_errno(err));
+    return size;
+#else /* LWIP_TCP */
+    sock_set_errno(sock, err_to_errno(ERR_ARG));
+    return -1;
+#endif /* LWIP_TCP */
+  }
+  /* else, UDP and RAW NETCONNs */
+#if LWIP_UDP || LWIP_RAW
+  {
+    struct netbuf *chain_buf;
+
+    LWIP_UNUSED_ARG(flags);
+    LWIP_ERROR("lwip_sendmsg: invalid msghdr name", (((msg->msg_name == NULL) && (msg->msg_namelen == 0)) ||
+               IS_SOCK_ADDR_LEN_VALID(msg->msg_namelen)) ,
+               sock_set_errno(sock, err_to_errno(ERR_ARG)); return -1;);
+
+    /* initialize chain buffer with destination */
+    chain_buf = netbuf_new();
+    if (!chain_buf) {
+      sock_set_errno(sock, err_to_errno(ERR_MEM));
+      return -1;
+    }
+    if (msg->msg_name) {
+      u16_t remote_port;
+      SOCKADDR_TO_IPADDR_PORT((const struct sockaddr *)msg->msg_name, &chain_buf->addr, remote_port);
+      netbuf_fromport(chain_buf) = remote_port;
+    }
+#if LWIP_NETIF_TX_SINGLE_PBUF
+    for (i = 0; i < msg->msg_iovlen; i++) {
+      size += msg->msg_iov[i].iov_len;
+    }
+    /* Allocate a new netbuf and copy the data into it. */
+    if (netbuf_alloc(chain_buf, (u16_t)size) == NULL) {
+       err = ERR_MEM;
+    } else {
+      /* flatten the IO vectors */
+      size_t offset = 0;
+      for (i = 0; i < msg->msg_iovlen; i++) {
+        MEMCPY(&((u8_t*)chain_buf->p->payload)[offset], msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len);
+        offset += msg->msg_iov[i].iov_len;
+      }
+#if LWIP_CHECKSUM_ON_COPY
+      {
+        /* This can be improved by using LWIP_CHKSUM_COPY() and aggregating the checksum for each IO vector */
+        u16_t chksum = ~inet_chksum_pbuf(chain_buf->p);
+        netbuf_set_chksum(chain_buf, chksum);
+      }
+#endif /* LWIP_CHECKSUM_ON_COPY */
+      err = ERR_OK;
+    }
+#else /* LWIP_NETIF_TX_SINGLE_PBUF */
+    /* create a chained netbuf from the IO vectors. NOTE: we assemble a pbuf chain
+       manually to avoid having to allocate, chain, and delete a netbuf for each iov */
+    for (i = 0; i < msg->msg_iovlen; i++) {
+      struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, 0, PBUF_REF);
+      if (p == NULL) {
+        err = ERR_MEM; /* let netbuf_delete() cleanup chain_buf */
+        break;
+      }
+      p->payload = msg->msg_iov[i].iov_base;
+      LWIP_ASSERT("iov_len < u16_t", msg->msg_iov[i].iov_len <= 0xFFFF);
+      p->len = p->tot_len = (u16_t)msg->msg_iov[i].iov_len;
+      /* netbuf empty, add new pbuf */
+      if (chain_buf->p == NULL) {
+        chain_buf->p = chain_buf->ptr = p;
+        /* add pbuf to existing pbuf chain */
+      } else {
+        pbuf_cat(chain_buf->p, p);
+      }
+    }
+    /* save size of total chain */
+    if (err == ERR_OK) {
+      size = netbuf_len(chain_buf);
+    }
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+
+    if (err == ERR_OK) {
+#if LWIP_IPV4 && LWIP_IPV6
+      /* Dual-stack: Unmap IPv4 mapped IPv6 addresses */
+      if (IP_IS_V6_VAL(chain_buf->addr) && ip6_addr_isipv4mappedipv6(ip_2_ip6(&chain_buf->addr))) {
+        unmap_ipv4_mapped_ipv6(ip_2_ip4(&chain_buf->addr), ip_2_ip6(&chain_buf->addr));
+        IP_SET_TYPE_VAL(chain_buf->addr, IPADDR_TYPE_V4);
+      }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+      /* send the data */
+      err = netconn_send(sock->conn, chain_buf);
+    }
+
+    /* deallocated the buffer */
+    netbuf_delete(chain_buf);
+
+    sock_set_errno(sock, err_to_errno(err));
+    return (err == ERR_OK ? size : -1);
+  }
+#else /* LWIP_UDP || LWIP_RAW */
+  sock_set_errno(sock, err_to_errno(ERR_ARG));
+  return -1;
+#endif /* LWIP_UDP || LWIP_RAW */
+}
+
+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;
+  struct netbuf buf;
+
+  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 */
+  }
+
+  /* @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);
+
+  /* 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_IPADDR_PORT(to, &buf.addr, remote_port);
+  } else {
+    remote_port = 0;
+    ip_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));
+  ip_addr_debug_print(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);
+    } else
+#endif /* LWIP_CHECKSUM_ON_COPY */
+    {
+      MEMCPY(buf.p->payload, data, short_size);
+    }
+    err = ERR_OK;
+  }
+#else /* LWIP_NETIF_TX_SINGLE_PBUF */
+  err = netbuf_ref(&buf, data, short_size);
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+  if (err == ERR_OK) {
+#if LWIP_IPV4 && LWIP_IPV6
+    /* Dual-stack: Unmap IPv4 mapped IPv6 addresses */
+    if (IP_IS_V6_VAL(buf.addr) && ip6_addr_isipv4mappedipv6(ip_2_ip6(&buf.addr))) {
+      unmap_ipv4_mapped_ipv6(ip_2_ip4(&buf.addr), ip_2_ip6(&buf.addr));
+      IP_SET_TYPE_VAL(buf.addr, IPADDR_TYPE_V4);
+    }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+    /* send the data */
+    err = netconn_send(sock->conn, &buf);
+  }
+
+  /* deallocated the buffer */
+  netbuf_free(&buf);
+
+  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;
+
+  LWIP_UNUSED_ARG(domain); /* @todo: check this */
+
+  /* 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));
+    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);
+}
+
+int
+lwip_writev(int s, const struct iovec *iov, int iovcnt)
+{
+  struct msghdr msg;
+
+  msg.msg_name = NULL;
+  msg.msg_namelen = 0;
+  /* Hack: we have to cast via number to cast from 'const' pointer to non-const.
+     Blame the opengroup standard for this inconsistency. */
+  msg.msg_iov = LWIP_CONST_CAST(struct iovec *, iov);
+  msg.msg_iovlen = iovcnt;
+  msg.msg_control = NULL;
+  msg.msg_controllen = 0;
+  msg.msg_flags = 0;
+  return lwip_sendmsg(s, &msg, 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.
+ *
+ * @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 = LWIP_SOCKET_OFFSET; i < maxfdp1; i++) {
+    /* if this FD is not in the set, continue */
+    if (!(readset_in && FD_ISSET(i, readset_in)) &&
+        !(writeset_in && FD_ISSET(i, writeset_in)) &&
+        !(exceptset_in && FD_ISSET(i, exceptset_in))) {
+      continue;
+    }
+    /* First get the socket's status (protected)... */
+    SYS_ARCH_PROTECT(lev);
+    sock = tryget_socket(i);
+    if (sock != NULL) {
+      void* lastdata = sock->lastdata;
+      s16_t rcvevent = sock->rcvevent;
+      u16_t sendevent = sock->sendevent;
+      u16_t 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++;
+      }
+    } else {
+      SYS_ARCH_UNPROTECT(lev);
+      /* continue on to next FD in list */
+    }
+  }
+  /* 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;
+}
+
+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;
+  int i;
+  int maxfdp2;
+#if LWIP_NETCONN_SEM_PER_THREAD
+  int waited = 0;
+#endif
+  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;
+#if LWIP_NETCONN_SEM_PER_THREAD
+    select_cb.sem = LWIP_NETCONN_THREAD_SEM_GET();
+#else /* LWIP_NETCONN_SEM_PER_THREAD */
+    if (sys_sem_new(&select_cb.sem, 0) != ERR_OK) {
+      /* failed to create semaphore */
+      set_errno(ENOMEM);
+      return -1;
+    }
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+    /* 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 event_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 */
+    maxfdp2 = maxfdp1;
+    for (i = LWIP_SOCKET_OFFSET; i < maxfdp1; i++) {
+      if ((readset && FD_ISSET(i, readset)) ||
+          (writeset && FD_ISSET(i, writeset)) ||
+          (exceptset && FD_ISSET(i, exceptset))) {
+        struct lwip_sock *sock;
+        SYS_ARCH_PROTECT(lev);
+        sock = tryget_socket(i);
+        if (sock != NULL) {
+          sock->select_waiting++;
+          LWIP_ASSERT("sock->select_waiting > 0", sock->select_waiting > 0);
+        } else {
+          /* Not a valid socket */
+          nready = -1;
+          maxfdp2 = i;
+          SYS_ARCH_UNPROTECT(lev);
+          break;
+        }
+        SYS_ARCH_UNPROTECT(lev);
+      }
+    }
+
+    if (nready >= 0) {
+      /* Call lwip_selscan again: there could have been events between
+         the last scan (without 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_SEM_PTR(select_cb.sem), msectimeout);
+#if LWIP_NETCONN_SEM_PER_THREAD
+        waited = 1;
+#endif
+      }
+    }
+
+    /* Decrease select_waiting for each socket we are interested in */
+    for (i = LWIP_SOCKET_OFFSET; i < maxfdp2; i++) {
+      if ((readset && FD_ISSET(i, readset)) ||
+          (writeset && FD_ISSET(i, writeset)) ||
+          (exceptset && FD_ISSET(i, exceptset))) {
+        struct lwip_sock *sock;
+        SYS_ARCH_PROTECT(lev);
+        sock = tryget_socket(i);
+        if (sock != NULL) {
+          /* for now, handle select_waiting==0... */
+          LWIP_ASSERT("sock->select_waiting > 0", sock->select_waiting > 0);
+          if (sock->select_waiting > 0) {
+            sock->select_waiting--;
+          }
+        } else {
+          /* Not a valid socket */
+          nready = -1;
+        }
+        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 event_callback that the list has changed. */
+    select_cb_ctr++;
+    SYS_ARCH_UNPROTECT(lev);
+
+#if LWIP_NETCONN_SEM_PER_THREAD
+    if (select_cb.sem_signalled && (!waited || (waitres == SYS_ARCH_TIMEOUT))) {
+      /* don't leave the thread-local semaphore signalled */
+      sys_arch_sem_wait(select_cb.sem, 1);
+    }
+#else /* LWIP_NETCONN_SEM_PER_THREAD */
+    sys_sem_free(&select_cb.sem);
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+    if (nready < 0) {
+      /* This happens when a socket got closed while waiting */
+      set_errno(EBADF);
+      return -1;
+    }
+
+    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) {
+    /* remember the state of select_cb_list to detect changes */
+    last_select_cb_ctr = select_cb_ctr;
+    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, invalidating the semaphore. */
+        sys_sem_signal(SELECT_SEM_PTR(scb->sem));
+      }
+    }
+    /* unlock interrupts with each step */
+    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);
+}
+
+/**
+ * Close one end of a full-duplex connection.
+ */
+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 -1;
+    }
+  } else {
+    sock_set_errno(sock, ENOTCONN);
+    return -1;
+  }
+
+  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 -1;
+  }
+  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;
+  ip_addr_t naddr;
+  u16_t port;
+  err_t err;
+
+  sock = get_socket(s);
+  if (!sock) {
+    return -1;
+  }
+
+  /* get the IP address and port */
+  err = netconn_getaddr(sock->conn, &naddr, &port, local);
+  if (err != ERR_OK) {
+    sock_set_errno(sock, err_to_errno(err));
+    return -1;
+  }
+
+#if LWIP_IPV4 && LWIP_IPV6
+  /* Dual-stack: Map IPv4 addresses to IPv4 mapped IPv6 */
+  if (NETCONNTYPE_ISIPV6(netconn_type(sock->conn)) &&
+      IP_IS_V4_VAL(naddr)) {
+    ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&naddr), ip_2_ip4(&naddr));
+    IP_SET_TYPE_VAL(naddr, IPADDR_TYPE_V6);
+  }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+  IPADDR_PORT_TO_SOCKADDR(&saddr, &naddr, port);
+
+  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getaddrname(%d, addr=", s));
+  ip_addr_debug_print_val(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)
+{
+  u8_t err;
+  struct lwip_sock *sock = get_socket(s);
+#if !LWIP_TCPIP_CORE_LOCKING
+  LWIP_SETGETSOCKOPT_DATA_VAR_DECLARE(data);
+#endif /* !LWIP_TCPIP_CORE_LOCKING */
+
+  if (!sock) {
+    return -1;
+  }
+
+  if ((NULL == optval) || (NULL == optlen)) {
+    sock_set_errno(sock, EFAULT);
+    return -1;
+  }
+
+#if LWIP_TCPIP_CORE_LOCKING
+  /* core-locking can just call the -impl function */
+  LOCK_TCPIP_CORE();
+  err = lwip_getsockopt_impl(s, level, optname, optval, optlen);
+  UNLOCK_TCPIP_CORE();
+
+#else /* LWIP_TCPIP_CORE_LOCKING */
+
+#if LWIP_MPU_COMPATIBLE
+  /* MPU_COMPATIBLE copies the optval data, so check for max size here */
+  if (*optlen > LWIP_SETGETSOCKOPT_MAXOPTLEN) {
+    sock_set_errno(sock, ENOBUFS);
+    return -1;
+  }
+#endif /* LWIP_MPU_COMPATIBLE */
+
+  LWIP_SETGETSOCKOPT_DATA_VAR_ALLOC(data, sock);
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).s = s;
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).level = level;
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optname = optname;
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optlen = *optlen;
+#if !LWIP_MPU_COMPATIBLE
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optval.p = optval;
+#endif /* !LWIP_MPU_COMPATIBLE */
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).err = 0;
+#if LWIP_NETCONN_SEM_PER_THREAD
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).completed_sem = LWIP_NETCONN_THREAD_SEM_GET();
+#else
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).completed_sem = &sock->conn->op_completed;
+#endif
+  err = tcpip_callback(lwip_getsockopt_callback, &LWIP_SETGETSOCKOPT_DATA_VAR_REF(data));
+  if (err != ERR_OK) {
+    LWIP_SETGETSOCKOPT_DATA_VAR_FREE(data);
+    sock_set_errno(sock, err_to_errno(err));
+    return -1;
+  }
+  sys_arch_sem_wait((sys_sem_t*)(LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).completed_sem), 0);
+
+  /* write back optlen and optval */
+  *optlen = LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optlen;
+#if LWIP_MPU_COMPATIBLE
+  MEMCPY(optval, LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optval,
+    LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optlen);
+#endif /* LWIP_MPU_COMPATIBLE */
+
+  /* maybe lwip_getsockopt_internal has changed err */
+  err = LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).err;
+  LWIP_SETGETSOCKOPT_DATA_VAR_FREE(data);
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+
+  sock_set_errno(sock, err);
+  return err ? -1 : 0;
+}
+
+#if !LWIP_TCPIP_CORE_LOCKING
+/** lwip_getsockopt_callback: only used without CORE_LOCKING
+ * to get into the tcpip_thread
+ */
+static void
+lwip_getsockopt_callback(void *arg)
+{
+  struct lwip_setgetsockopt_data *data;
+  LWIP_ASSERT("arg != NULL", arg != NULL);
+  data = (struct lwip_setgetsockopt_data*)arg;
+
+  data->err = lwip_getsockopt_impl(data->s, data->level, data->optname,
+#if LWIP_MPU_COMPATIBLE
+    data->optval,
+#else /* LWIP_MPU_COMPATIBLE */
+    data->optval.p,
+#endif /* LWIP_MPU_COMPATIBLE */
+    &data->optlen);
+
+  sys_sem_signal((sys_sem_t*)(data->completed_sem));
+}
+#endif  /* LWIP_TCPIP_CORE_LOCKING */
+
+/** lwip_getsockopt_impl: the actual implementation of getsockopt:
+ * same argument as lwip_getsockopt, either called directly or through callback
+ */
+static u8_t
+lwip_getsockopt_impl(int s, int level, int optname, void *optval, socklen_t *optlen)
+{
+  u8_t err = 0;
+  struct lwip_sock *sock = tryget_socket(s);
+  if (!sock) {
+    return EBADF;
+  }
+
+  switch (level) {
+
+/* Level: SOL_SOCKET */
+  case SOL_SOCKET:
+    switch (optname) {
+
+#if LWIP_TCP
+    case SO_ACCEPTCONN:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, int);
+      if (NETCONNTYPE_GROUP(sock->conn->type) != NETCONN_TCP) {
+        return ENOPROTOOPT;
+      }
+      if ((sock->conn->pcb.tcp != NULL) && (sock->conn->pcb.tcp->state == LISTEN)) {
+        *(int*)optval = 1;
+      } else {
+        *(int*)optval = 0;
+      }
+      break;
+#endif /* LWIP_TCP */
+
+    /* The option flags */
+    case SO_BROADCAST:
+    case SO_KEEPALIVE:
+#if SO_REUSE
+    case SO_REUSEADDR:
+#endif /* SO_REUSE */
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, int);
+      *(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:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, *optlen, int);
+      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:
+      LWIP_SOCKOPT_CHECK_OPTLEN(*optlen, int);
+      /* only overwrite ERR_OK or temporary errors */
+      if (((sock->err == 0) || (sock->err == EINPROGRESS)) && (sock->conn != NULL)) {
+        sock_set_errno(sock, err_to_errno(sock->conn->last_err));
+      }
+      *(int *)optval = (sock->err == 0xFF ? (int)-1 : (int)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:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, *optlen, LWIP_SO_SNDRCVTIMEO_OPTTYPE);
+      LWIP_SO_SNDRCVTIMEO_SET(optval, netconn_get_sendtimeout(sock->conn));
+      break;
+#endif /* LWIP_SO_SNDTIMEO */
+#if LWIP_SO_RCVTIMEO
+    case SO_RCVTIMEO:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, *optlen, LWIP_SO_SNDRCVTIMEO_OPTTYPE);
+      LWIP_SO_SNDRCVTIMEO_SET(optval, netconn_get_recvtimeout(sock->conn));
+      break;
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVBUF
+    case SO_RCVBUF:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, *optlen, int);
+      *(int *)optval = netconn_get_recvbufsize(sock->conn);
+      break;
+#endif /* LWIP_SO_RCVBUF */
+#if LWIP_SO_LINGER
+    case SO_LINGER:
+      {
+        s16_t conn_linger;
+        struct linger* linger = (struct linger*)optval;
+        LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, *optlen, struct linger);
+        conn_linger = sock->conn->linger;
+        if (conn_linger >= 0) {
+          linger->l_onoff = 1;
+          linger->l_linger = (int)conn_linger;
+        } else {
+          linger->l_onoff = 0;
+          linger->l_linger = 0;
+        }
+      }
+      break;
+#endif /* LWIP_SO_LINGER */
+#if LWIP_UDP
+    case SO_NO_CHECK:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, *optlen, int, NETCONN_UDP);
+#if LWIP_UDPLITE
+      if ((udp_flags(sock->conn->pcb.udp) & UDP_FLAGS_UDPLITE) != 0) {
+        /* this flag is only available for UDP, not for UDP lite */
+        return EAFNOSUPPORT;
+      }
+#endif /* LWIP_UDPLITE */
+      *(int*)optval = (udp_flags(sock->conn->pcb.udp) & UDP_FLAGS_NOCHKSUM) ? 1 : 0;
+      break;
+#endif /* LWIP_UDP*/
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, SOL_SOCKET, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+
+/* Level: IPPROTO_IP */
+  case IPPROTO_IP:
+    switch (optname) {
+    case IP_TTL:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, int);
+      *(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:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, int);
+      *(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_MULTICAST_TX_OPTIONS
+    case IP_MULTICAST_TTL:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, u8_t);
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
+        return ENOPROTOOPT;
+      }
+      *(u8_t*)optval = udp_get_multicast_ttl(sock->conn->pcb.udp);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, IP_MULTICAST_TTL) = %d\n",
+                  s, *(int *)optval));
+      break;
+    case IP_MULTICAST_IF:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, struct in_addr);
+      if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
+        return ENOPROTOOPT;
+      }
+      inet_addr_from_ip4addr((struct in_addr*)optval, udp_get_multicast_netif_addr(sock->conn->pcb.udp));
+      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:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, u8_t);
+      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_MULTICAST_TX_OPTIONS */
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IP, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+
+#if LWIP_TCP
+/* Level: IPPROTO_TCP */
+  case IPPROTO_TCP:
+    /* Special case: all IPPROTO_TCP option take an int */
+    LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, *optlen, int, NETCONN_TCP);
+    if (sock->conn->pcb.tcp->state == LISTEN) {
+      return EINVAL;
+    }
+    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_TCP, 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_TCP, 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_TCP, 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_TCP, TCP_KEEPCNT) = %d\n",
+                  s, *(int *)optval));
+      break;
+#endif /* LWIP_TCP_KEEPALIVE */
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_TCP, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_TCP */
+
+#if LWIP_IPV6
+/* Level: IPPROTO_IPV6 */
+  case IPPROTO_IPV6:
+    switch (optname) {
+    case IPV6_V6ONLY:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, *optlen, int);
+      *(int*)optval = (netconn_get_ipv6only(sock->conn) ? 1 : 0);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IPV6, IPV6_V6ONLY) = %d\n",
+                  s, *(int *)optval));
+      break;
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IPV6, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_IPV6 */
+
+#if LWIP_UDP && LWIP_UDPLITE
+  /* Level: IPPROTO_UDPLITE */
+  case IPPROTO_UDPLITE:
+    /* Special case: all IPPROTO_UDPLITE option take an int */
+    LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, int);
+    /* If this is no UDP lite socket, ignore any options. */
+    if (!NETCONNTYPE_ISUDPLITE(netconn_type(sock->conn))) {
+      return ENOPROTOOPT;
+    }
+    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_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_UDPLITE, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_UDP */
+  /* Level: IPPROTO_RAW */
+  case IPPROTO_RAW:
+    switch (optname) {
+#if LWIP_IPV6 && LWIP_RAW
+    case IPV6_CHECKSUM:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, *optlen, int, NETCONN_RAW);
+      if (sock->conn->pcb.raw->chksum_reqd == 0) {
+        *(int *)optval = -1;
+      } else {
+        *(int *)optval = sock->conn->pcb.raw->chksum_offset;
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_RAW, IPV6_CHECKSUM) = %d\n",
+                  s, (*(int*)optval)) );
+      break;
+#endif /* LWIP_IPV6 && LWIP_RAW */
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_RAW, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+  default:
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, level=0x%x, UNIMPL: optname=0x%x, ..)\n",
+                                s, level, optname));
+    err = ENOPROTOOPT;
+    break;
+  } /* switch (level) */
+
+  return err;
+}
+
+int
+lwip_setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen)
+{
+  u8_t err = 0;
+  struct lwip_sock *sock = get_socket(s);
+#if !LWIP_TCPIP_CORE_LOCKING
+  LWIP_SETGETSOCKOPT_DATA_VAR_DECLARE(data);
+#endif /* !LWIP_TCPIP_CORE_LOCKING */
+
+  if (!sock) {
+    return -1;
+  }
+
+  if (NULL == optval) {
+    sock_set_errno(sock, EFAULT);
+    return -1;
+  }
+
+#if LWIP_TCPIP_CORE_LOCKING
+  /* core-locking can just call the -impl function */
+  LOCK_TCPIP_CORE();
+  err = lwip_setsockopt_impl(s, level, optname, optval, optlen);
+  UNLOCK_TCPIP_CORE();
+
+#else /* LWIP_TCPIP_CORE_LOCKING */
+
+#if LWIP_MPU_COMPATIBLE
+  /* MPU_COMPATIBLE copies the optval data, so check for max size here */
+  if (optlen > LWIP_SETGETSOCKOPT_MAXOPTLEN) {
+    sock_set_errno(sock, ENOBUFS);
+    return -1;
+  }
+#endif /* LWIP_MPU_COMPATIBLE */
+
+  LWIP_SETGETSOCKOPT_DATA_VAR_ALLOC(data, sock);
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).s = s;
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).level = level;
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optname = optname;
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optlen = optlen;
+#if LWIP_MPU_COMPATIBLE
+  MEMCPY(LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optval, optval, optlen);
+#else /* LWIP_MPU_COMPATIBLE */
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).optval.pc = (const void*)optval;
+#endif /* LWIP_MPU_COMPATIBLE */
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).err = 0;
+#if LWIP_NETCONN_SEM_PER_THREAD
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).completed_sem = LWIP_NETCONN_THREAD_SEM_GET();
+#else
+  LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).completed_sem = &sock->conn->op_completed;
+#endif
+  err = tcpip_callback(lwip_setsockopt_callback, &LWIP_SETGETSOCKOPT_DATA_VAR_REF(data));
+  if (err != ERR_OK) {
+    LWIP_SETGETSOCKOPT_DATA_VAR_FREE(data);
+    sock_set_errno(sock, err_to_errno(err));
+    return -1;
+  }
+  sys_arch_sem_wait((sys_sem_t*)(LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).completed_sem), 0);
+
+  /* maybe lwip_getsockopt_internal has changed err */
+  err = LWIP_SETGETSOCKOPT_DATA_VAR_REF(data).err;
+  LWIP_SETGETSOCKOPT_DATA_VAR_FREE(data);
+#endif  /* LWIP_TCPIP_CORE_LOCKING */
+
+  sock_set_errno(sock, err);
+  return err ? -1 : 0;
+}
+
+#if !LWIP_TCPIP_CORE_LOCKING
+/** lwip_setsockopt_callback: only used without CORE_LOCKING
+ * to get into the tcpip_thread
+ */
+static void
+lwip_setsockopt_callback(void *arg)
+{
+  struct lwip_setgetsockopt_data *data;
+  LWIP_ASSERT("arg != NULL", arg != NULL);
+  data = (struct lwip_setgetsockopt_data*)arg;
+
+  data->err = lwip_setsockopt_impl(data->s, data->level, data->optname,
+#if LWIP_MPU_COMPATIBLE
+    data->optval,
+#else /* LWIP_MPU_COMPATIBLE */
+    data->optval.pc,
+#endif /* LWIP_MPU_COMPATIBLE */
+    data->optlen);
+
+  sys_sem_signal((sys_sem_t*)(data->completed_sem));
+}
+#endif  /* LWIP_TCPIP_CORE_LOCKING */
+
+/** lwip_setsockopt_impl: the actual implementation of setsockopt:
+ * same argument as lwip_setsockopt, either called directly or through callback
+ */
+static u8_t
+lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_t optlen)
+{
+  u8_t err = 0;
+  struct lwip_sock *sock = tryget_socket(s);
+  if (!sock) {
+    return EBADF;
+  }
+
+  switch (level) {
+
+/* Level: SOL_SOCKET */
+  case SOL_SOCKET:
+    switch (optname) {
+
+    /* SO_ACCEPTCONN is get-only */
+
+    /* The option flags */
+    case SO_BROADCAST:
+    case SO_KEEPALIVE:
+#if SO_REUSE
+    case SO_REUSEADDR:
+#endif /* SO_REUSE */
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, optlen, int);
+      if (*(const 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, (*(const int*)optval?"on":"off")));
+      break;
+
+    /* SO_TYPE is get-only */
+    /* SO_ERROR is get-only */
+
+#if LWIP_SO_SNDTIMEO
+    case SO_SNDTIMEO:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, optlen, LWIP_SO_SNDRCVTIMEO_OPTTYPE);
+      netconn_set_sendtimeout(sock->conn, LWIP_SO_SNDRCVTIMEO_GET_MS(optval));
+      break;
+#endif /* LWIP_SO_SNDTIMEO */
+#if LWIP_SO_RCVTIMEO
+    case SO_RCVTIMEO:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, optlen, LWIP_SO_SNDRCVTIMEO_OPTTYPE);
+      netconn_set_recvtimeout(sock->conn, (int)LWIP_SO_SNDRCVTIMEO_GET_MS(optval));
+      break;
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVBUF
+    case SO_RCVBUF:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, optlen, int);
+      netconn_set_recvbufsize(sock->conn, *(const int*)optval);
+      break;
+#endif /* LWIP_SO_RCVBUF */
+#if LWIP_SO_LINGER
+    case SO_LINGER:
+      {
+        const struct linger* linger = (const struct linger*)optval;
+        LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, optlen, struct linger);
+        if (linger->l_onoff) {
+          int lingersec = linger->l_linger;
+          if (lingersec < 0) {
+            return EINVAL;
+          }
+          if (lingersec > 0xFFFF) {
+            lingersec = 0xFFFF;
+          }
+          sock->conn->linger = (s16_t)lingersec;
+        } else {
+          sock->conn->linger = -1;
+        }
+      }
+      break;
+#endif /* LWIP_SO_LINGER */
+#if LWIP_UDP
+    case SO_NO_CHECK:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, int, NETCONN_UDP);
+#if LWIP_UDPLITE
+      if ((udp_flags(sock->conn->pcb.udp) & UDP_FLAGS_UDPLITE) != 0) {
+        /* this flag is only available for UDP, not for UDP lite */
+        return EAFNOSUPPORT;
+      }
+#endif /* LWIP_UDPLITE */
+      if (*(const 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_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, SOL_SOCKET, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+
+/* Level: IPPROTO_IP */
+  case IPPROTO_IP:
+    switch (optname) {
+    case IP_TTL:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, optlen, int);
+      sock->conn->pcb.ip->ttl = (u8_t)(*(const 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:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, optlen, int);
+      sock->conn->pcb.ip->tos = (u8_t)(*(const int*)optval);
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IP, IP_TOS, ..)-> %d\n",
+                  s, sock->conn->pcb.ip->tos));
+      break;
+#if LWIP_MULTICAST_TX_OPTIONS
+    case IP_MULTICAST_TTL:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, u8_t, NETCONN_UDP);
+      udp_set_multicast_ttl(sock->conn->pcb.udp, (u8_t)(*(const u8_t*)optval));
+      break;
+    case IP_MULTICAST_IF:
+      {
+        ip4_addr_t if_addr;
+        LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, struct in_addr, NETCONN_UDP);
+        inet_addr_to_ip4addr(&if_addr, (const struct in_addr*)optval);
+        udp_set_multicast_netif_addr(sock->conn->pcb.udp, &if_addr);
+      }
+      break;
+    case IP_MULTICAST_LOOP:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, u8_t, NETCONN_UDP);
+      if (*(const 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;
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
+#if LWIP_IGMP
+    case IP_ADD_MEMBERSHIP:
+    case IP_DROP_MEMBERSHIP:
+      {
+        /* If this is a TCP or a RAW socket, ignore these options. */
+        /* @todo: assign membership to this socket so that it is dropped when closing the socket */
+        err_t igmp_err;
+        const struct ip_mreq *imr = (const struct ip_mreq *)optval;
+        ip4_addr_t if_addr;
+        ip4_addr_t multi_addr;
+        LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, struct ip_mreq, NETCONN_UDP);
+        inet_addr_to_ip4addr(&if_addr, &imr->imr_interface);
+        inet_addr_to_ip4addr(&multi_addr, &imr->imr_multiaddr);
+        if (optname == IP_ADD_MEMBERSHIP) {
+          if (!lwip_socket_register_membership(s, &if_addr, &multi_addr)) {
+            /* cannot track membership (out of memory) */
+            err = ENOMEM;
+            igmp_err = ERR_OK;
+          } else {
+            igmp_err = igmp_joingroup(&if_addr, &multi_addr);
+          }
+        } else {
+          igmp_err = igmp_leavegroup(&if_addr, &multi_addr);
+          lwip_socket_unregister_membership(s, &if_addr, &multi_addr);
+        }
+        if (igmp_err != ERR_OK) {
+          err = EADDRNOTAVAIL;
+        }
+      }
+      break;
+#endif /* LWIP_IGMP */
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IP, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+
+#if LWIP_TCP
+/* Level: IPPROTO_TCP */
+  case IPPROTO_TCP:
+    /* Special case: all IPPROTO_TCP option take an int */
+    LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, int, NETCONN_TCP);
+    if (sock->conn->pcb.tcp->state == LISTEN) {
+      return EINVAL;
+    }
+    switch (optname) {
+    case TCP_NODELAY:
+      if (*(const 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, (*(const int *)optval)?"on":"off") );
+      break;
+    case TCP_KEEPALIVE:
+      sock->conn->pcb.tcp->keep_idle = (u32_t)(*(const 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)(*(const 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)(*(const 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)(*(const 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_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_TCP, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_TCP*/
+
+#if LWIP_IPV6
+/* Level: IPPROTO_IPV6 */
+  case IPPROTO_IPV6:
+    switch (optname) {
+    case IPV6_V6ONLY:
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, int, NETCONN_TCP);
+      if (*(const int*)optval) {
+        netconn_set_ipv6only(sock->conn, 1);
+      } else {
+        netconn_set_ipv6only(sock->conn, 0);
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IPV6, IPV6_V6ONLY, ..) -> %d\n",
+                  s, (netconn_get_ipv6only(sock->conn) ? 1 : 0)));
+      break;
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IPV6, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_IPV6 */
+
+#if LWIP_UDP && LWIP_UDPLITE
+  /* Level: IPPROTO_UDPLITE */
+  case IPPROTO_UDPLITE:
+    /* Special case: all IPPROTO_UDPLITE option take an int */
+    LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, optlen, int);
+    /* If this is no UDP lite socket, ignore any options. */
+    if (!NETCONNTYPE_ISUDPLITE(netconn_type(sock->conn))) {
+      return ENOPROTOOPT;
+    }
+    switch (optname) {
+    case UDPLITE_SEND_CSCOV:
+      if ((*(const int*)optval != 0) && ((*(const int*)optval < 8) || (*(const 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)*(const int*)optval;
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV) -> %d\n",
+                  s, (*(const int*)optval)) );
+      break;
+    case UDPLITE_RECV_CSCOV:
+      if ((*(const int*)optval != 0) && ((*(const int*)optval < 8) || (*(const 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)*(const int*)optval;
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV) -> %d\n",
+                  s, (*(const int*)optval)) );
+      break;
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_UDPLITE, UNIMPL: optname=0x%x, ..)\n",
+                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+#endif /* LWIP_UDP */
+  /* Level: IPPROTO_RAW */
+  case IPPROTO_RAW:
+    switch (optname) {
+#if LWIP_IPV6 && LWIP_RAW
+    case IPV6_CHECKSUM:
+      /* It should not be possible to disable the checksum generation with ICMPv6
+       * as per RFC 3542 chapter 3.1 */
+      if(sock->conn->pcb.raw->protocol == IPPROTO_ICMPV6) {
+        return EINVAL;
+      }
+
+      LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, int, NETCONN_RAW);
+      if (*(const int *)optval < 0) {
+        sock->conn->pcb.raw->chksum_reqd = 0;
+      } else if (*(const int *)optval & 1) {
+        /* Per RFC3542, odd offsets are not allowed */
+        return EINVAL;
+      } else {
+        sock->conn->pcb.raw->chksum_reqd = 1;
+        sock->conn->pcb.raw->chksum_offset = (u16_t)*(const int *)optval;
+      }
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_RAW, IPV6_CHECKSUM, ..) -> %d\n",
+                  s, sock->conn->pcb.raw->chksum_reqd));
+      break;
+#endif /* LWIP_IPV6 && LWIP_RAW */
+    default:
+      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_RAW, UNIMPL: optname=0x%x, ..)\n",
+                                  s, optname));
+      err = ENOPROTOOPT;
+      break;
+    }  /* switch (optname) */
+    break;
+  default:
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, level=0x%x, UNIMPL: optname=0x%x, ..)\n",
+                s, level, optname));
+    err = ENOPROTOOPT;
+    break;
+  }  /* switch (level) */
+
+  return err;
+}
+
+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;
+  int 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;
+        *((int*)argp) = p->tot_len - sock->lastoffset;
+      } else {
+        struct netbuf *rxbuf;
+        err_t err;
+        if (sock->rcvevent <= 0) {
+          *((int*)argp) = 0;
+        } else {
+          err = netconn_recv(sock->conn, &rxbuf);
+          if (err != ERR_OK) {
+            *((int*)argp) = 0;
+          } else {
+            sock->lastdata = rxbuf;
+            sock->lastoffset = 0;
+            *((int*)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;
+    }
+    *((int*)argp) = 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;
+
+      *((int*)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 (long)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) {
+    return -1;
+  }
+
+  switch (cmd) {
+  case F_GETFL:
+    ret = netconn_is_nonblocking(sock->conn) ? O_NONBLOCK : 0;
+    sock_set_errno(sock, 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;
+      sock_set_errno(sock, 0);
+    } else {
+      sock_set_errno(sock, ENOSYS); /* not yet implemented */
+    }
+    break;
+  default:
+    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_fcntl(%d, UNIMPL: %d, %d)\n", s, cmd, val));
+    sock_set_errno(sock, ENOSYS); /* not yet implemented */
+    break;
+  }
+  return ret;
+}
+
+#if LWIP_IGMP
+/** Register a new IGMP membership. On socket close, the membership is dropped automatically.
+ *
+ * ATTENTION: this function is called from tcpip_thread (or under CORE_LOCK).
+ *
+ * @return 1 on success, 0 on failure
+ */
+static int
+lwip_socket_register_membership(int s, const ip4_addr_t *if_addr, const ip4_addr_t *multi_addr)
+{
+  struct lwip_sock *sock = get_socket(s);
+  int i;
+
+  if (!sock) {
+    return 0;
+  }
+
+  for (i = 0; i < LWIP_SOCKET_MAX_MEMBERSHIPS; i++) {
+    if (socket_ipv4_multicast_memberships[i].sock == NULL) {
+      socket_ipv4_multicast_memberships[i].sock = sock;
+      ip4_addr_copy(socket_ipv4_multicast_memberships[i].if_addr, *if_addr);
+      ip4_addr_copy(socket_ipv4_multicast_memberships[i].multi_addr, *multi_addr);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+/** Unregister a previously registered membership. This prevents dropping the membership
+ * on socket close.
+ *
+ * ATTENTION: this function is called from tcpip_thread (or under CORE_LOCK).
+ */
+static void
+lwip_socket_unregister_membership(int s, const ip4_addr_t *if_addr, const ip4_addr_t *multi_addr)
+{
+  struct lwip_sock *sock = get_socket(s);
+  int i;
+
+  if (!sock) {
+    return;
+  }
+
+  for (i = 0; i < LWIP_SOCKET_MAX_MEMBERSHIPS; i++) {
+    if ((socket_ipv4_multicast_memberships[i].sock == sock) &&
+        ip4_addr_cmp(&socket_ipv4_multicast_memberships[i].if_addr, if_addr) &&
+        ip4_addr_cmp(&socket_ipv4_multicast_memberships[i].multi_addr, multi_addr)) {
+      socket_ipv4_multicast_memberships[i].sock = NULL;
+      ip4_addr_set_zero(&socket_ipv4_multicast_memberships[i].if_addr);
+      ip4_addr_set_zero(&socket_ipv4_multicast_memberships[i].multi_addr);
+      return;
+    }
+  }
+}
+
+/** Drop all memberships of a socket that were not dropped explicitly via setsockopt.
+ *
+ * ATTENTION: this function is NOT called from tcpip_thread (or under CORE_LOCK).
+ */
+static void
+lwip_socket_drop_registered_memberships(int s)
+{
+  struct lwip_sock *sock = get_socket(s);
+  int i;
+
+  if (!sock) {
+    return;
+  }
+
+  for (i = 0; i < LWIP_SOCKET_MAX_MEMBERSHIPS; i++) {
+    if (socket_ipv4_multicast_memberships[i].sock == sock) {
+      ip_addr_t multi_addr, if_addr;
+      ip_addr_copy_from_ip4(multi_addr, socket_ipv4_multicast_memberships[i].multi_addr);
+      ip_addr_copy_from_ip4(if_addr, socket_ipv4_multicast_memberships[i].if_addr);
+      socket_ipv4_multicast_memberships[i].sock = NULL;
+      ip4_addr_set_zero(&socket_ipv4_multicast_memberships[i].if_addr);
+      ip4_addr_set_zero(&socket_ipv4_multicast_memberships[i].multi_addr);
+
+      netconn_join_leave_group(sock->conn, &multi_addr, &if_addr, NETCONN_LEAVE);
+    }
+  }
+}
+#endif /* LWIP_IGMP */
+#endif /* LWIP_SOCKET */
diff --git a/src/api/tcpip.c b/src/api/tcpip.c
new file mode 100644
index 0000000..07b2f98
--- /dev/null
+++ b/src/api/tcpip.c
@@ -0,0 +1,518 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if !NO_SYS /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/priv/tcpip_priv.h"
+#include "lwip/sys.h"
+#include "lwip/memp.h"
+#include "lwip/mem.h"
+#include "lwip/init.h"
+#include "lwip/ip.h"
+#include "lwip/pbuf.h"
+#include "lwip/etharp.h"
+#include "netif/ethernet.h"
+
+#define TCPIP_MSG_VAR_REF(name)     API_VAR_REF(name)
+#define TCPIP_MSG_VAR_DECLARE(name) API_VAR_DECLARE(struct tcpip_msg, name)
+#define TCPIP_MSG_VAR_ALLOC(name)   API_VAR_ALLOC(struct tcpip_msg, MEMP_TCPIP_MSG_API, name, ERR_MEM)
+#define TCPIP_MSG_VAR_FREE(name)    API_VAR_FREE(MEMP_TCPIP_MSG_API, name)
+
+/* 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 */
+
+#if LWIP_TIMERS
+/* wait for a message, timeouts are processed while waiting */
+#define TCPIP_MBOX_FETCH(mbox, msg) sys_timeouts_mbox_fetch(mbox, msg)
+#else /* LWIP_TIMERS */
+/* wait for a message with timers disabled (e.g. pass a timer-check trigger into tcpip_thread) */
+#define TCPIP_MBOX_FETCH(mbox, msg) sys_mbox_fetch(mbox, msg)
+#endif /* LWIP_TIMERS */
+
+/**
+ * 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 */
+    TCPIP_MBOX_FETCH(&mbox, (void **)&msg);
+    LOCK_TCPIP_CORE();
+    if (msg == NULL) {
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: NULL\n"));
+      LWIP_ASSERT("tcpip_thread: invalid message", 0);
+      continue;
+    }
+    switch (msg->type) {
+#if !LWIP_TCPIP_CORE_LOCKING
+    case TCPIP_MSG_API:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
+      msg->msg.api_msg.function(msg->msg.api_msg.msg);
+      break;
+    case TCPIP_MSG_API_CALL:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API CALL message %p\n", (void *)msg));
+      msg->msg.api_call.arg->err = msg->msg.api_call.function(msg->msg.api_call.arg);
+      sys_sem_signal(msg->msg.api_call.sem);
+      break;
+#endif /* !LWIP_TCPIP_CORE_LOCKING */
+
+#if !LWIP_TCPIP_CORE_LOCKING_INPUT
+    case TCPIP_MSG_INPKT:
+      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
+      msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif);
+      memp_free(MEMP_TCPIP_MSG_INPKT, msg);
+      break;
+#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
+
+#if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
+    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 && LWIP_TIMERS */
+
+    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
+ * @param inp the network interface on which the packet was received
+ * @param input_fn input function to call
+ */
+err_t
+tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
+{
+#if LWIP_TCPIP_CORE_LOCKING_INPUT
+  err_t ret;
+  LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_inpkt: PACKET %p/%p\n", (void *)p, (void *)inp));
+  LOCK_TCPIP_CORE();
+  ret = input_fn(p, inp);
+  UNLOCK_TCPIP_CORE();
+  return ret;
+#else /* LWIP_TCPIP_CORE_LOCKING_INPUT */
+  struct tcpip_msg *msg;
+
+  LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
+
+  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;
+  msg->msg.inp.input_fn = input_fn;
+  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 */
+}
+
+/**
+ * @ingroup lwip_os
+ * Pass a received packet to tcpip_thread for input processing with
+ * ethernet_input or ip_input. Don't call directly, pass to netif_add()
+ * and call netif->input().
+ *
+ * @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_ETHERNET
+  if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
+    return tcpip_inpkt(p, inp, ethernet_input);
+  } else
+#endif /* LWIP_ETHERNET */
+  return tcpip_inpkt(p, inp, ip_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 function 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;
+
+  LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(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;
+}
+
+#if LWIP_TCPIP_TIMEOUT && LWIP_TIMERS
+/**
+ * call sys_timeout in tcpip_thread
+ *
+ * @param msecs 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;
+
+  LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(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;
+}
+
+/**
+ * call sys_untimeout in tcpip_thread
+ *
+ * @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;
+
+  LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(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;
+}
+#endif /* LWIP_TCPIP_TIMEOUT && LWIP_TIMERS */
+
+
+/**
+ * Sends a message to TCPIP thread to call a function. Caller thread blocks on
+ * on a provided semaphore, which ist NOT automatically signalled by TCPIP thread,
+ * this has to be done by the user.
+ * It is recommended to use LWIP_TCPIP_CORE_LOCKING since this is the way
+ * with least runtime overhead.
+ *
+ * @param fn function to be called from TCPIP thread
+ * @param apimsg argument to API function
+ * @param sem semaphore to wait on
+ * @return ERR_OK if the function was called, another err_t if not
+ */
+err_t
+tcpip_send_msg_wait_sem(tcpip_callback_fn fn, void *apimsg, sys_sem_t* sem)
+{
+#if LWIP_TCPIP_CORE_LOCKING
+  LWIP_UNUSED_ARG(sem);
+  LOCK_TCPIP_CORE();
+  fn(apimsg);
+  UNLOCK_TCPIP_CORE();
+  return ERR_OK;
+#else /* LWIP_TCPIP_CORE_LOCKING */
+  TCPIP_MSG_VAR_DECLARE(msg);
+
+  LWIP_ASSERT("semaphore not initialized", sys_sem_valid(sem));
+  LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
+
+  TCPIP_MSG_VAR_ALLOC(msg);
+  TCPIP_MSG_VAR_REF(msg).type = TCPIP_MSG_API;
+  TCPIP_MSG_VAR_REF(msg).msg.api_msg.function = fn;
+  TCPIP_MSG_VAR_REF(msg).msg.api_msg.msg = apimsg;
+  sys_mbox_post(&mbox, &TCPIP_MSG_VAR_REF(msg));
+  sys_arch_sem_wait(sem, 0);
+  TCPIP_MSG_VAR_FREE(msg);
+  return ERR_OK;
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+}
+
+/**
+ * Synchronously calls function in TCPIP thread and waits for its completion.
+ * It is recommended to use LWIP_TCPIP_CORE_LOCKING (preferred) or
+ * LWIP_NETCONN_SEM_PER_THREAD. 
+ * If not, a semaphore is created and destroyed on every call which is usually
+ * an expensive/slow operation.
+ * @param fn Function to call
+ * @param call Call parameters
+ * @return Return value from tcpip_api_call_fn
+ */
+err_t
+tcpip_api_call(tcpip_api_call_fn fn, struct tcpip_api_call_data *call)
+{
+#if LWIP_TCPIP_CORE_LOCKING
+  err_t err;
+  LOCK_TCPIP_CORE();
+  err = fn(call);
+  UNLOCK_TCPIP_CORE();
+  return err;
+#else /* LWIP_TCPIP_CORE_LOCKING */
+  TCPIP_MSG_VAR_DECLARE(msg);
+
+#if !LWIP_NETCONN_SEM_PER_THREAD
+  err_t err = sys_sem_new(&call->sem, 0);
+  if (err != ERR_OK) {
+    return err;
+  }
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+  LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
+
+  TCPIP_MSG_VAR_ALLOC(msg);
+  TCPIP_MSG_VAR_REF(msg).type = TCPIP_MSG_API_CALL;
+  TCPIP_MSG_VAR_REF(msg).msg.api_call.arg = call;
+  TCPIP_MSG_VAR_REF(msg).msg.api_call.function = fn;
+#if LWIP_NETCONN_SEM_PER_THREAD
+  TCPIP_MSG_VAR_REF(msg).msg.api_call.sem = LWIP_NETCONN_THREAD_SEM_GET();
+#else /* LWIP_NETCONN_SEM_PER_THREAD */
+  TCPIP_MSG_VAR_REF(msg).msg.api_call.sem = &call->sem;
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+  sys_mbox_post(&mbox, &TCPIP_MSG_VAR_REF(msg));
+  sys_arch_sem_wait(TCPIP_MSG_VAR_REF(msg).msg.api_call.sem, 0);
+  TCPIP_MSG_VAR_FREE(msg);
+
+#if !LWIP_NETCONN_SEM_PER_THREAD
+  sys_sem_free(&call->sem);
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+  return call->err;
+#endif /* LWIP_TCPIP_CORE_LOCKING */
+}
+
+/**
+ * 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)
+{
+  LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
+  return sys_mbox_trypost(&mbox, msg);
+}
+
+/**
+ * @ingroup lwip_os
+ * 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/src/apps/httpd/fs.c b/src/apps/httpd/fs.c
new file mode 100644
index 0000000..35b5e31
--- /dev/null
+++ b/src/apps/httpd/fs.c
@@ -0,0 +1,179 @@
+/*
+ * 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: Adam Dunkels <adam@sics.se>
+ *
+ */
+
+#include "lwip/apps/httpd_opts.h"
+#include "lwip/def.h"
+#include "lwip/apps/fs.h"
+#include "fsdata.h"
+#include <string.h>
+
+
+#if HTTPD_USE_CUSTOM_FSDATA
+#include "fsdata_custom.c"
+#else /* HTTPD_USE_CUSTOM_FSDATA */
+#include "fsdata.c"
+#endif /* HTTPD_USE_CUSTOM_FSDATA */
+
+/*-----------------------------------------------------------------------------------*/
+
+#if LWIP_HTTPD_CUSTOM_FILES
+int fs_open_custom(struct fs_file *file, const char *name);
+void fs_close_custom(struct fs_file *file);
+#if LWIP_HTTPD_FS_ASYNC_READ
+u8_t fs_canread_custom(struct fs_file *file);
+u8_t fs_wait_read_custom(struct fs_file *file, fs_wait_cb callback_fn, void *callback_arg);
+int fs_read_async_custom(struct fs_file *file, char *buffer, int count, fs_wait_cb callback_fn, void *callback_arg);
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+int fs_read_custom(struct fs_file *file, char *buffer, int count);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+
+/*-----------------------------------------------------------------------------------*/
+err_t
+fs_open(struct fs_file *file, const char *name)
+{
+  const struct fsdata_file *f;
+
+  if ((file == NULL) || (name == NULL)) {
+     return ERR_ARG;
+  }
+
+#if LWIP_HTTPD_CUSTOM_FILES
+  if (fs_open_custom(file, name)) {
+    file->is_custom_file = 1;
+    return ERR_OK;
+  }
+  file->is_custom_file = 0;
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+
+  for (f = FS_ROOT; f != NULL; f = f->next) {
+    if (!strcmp(name, (const char *)f->name)) {
+      file->data = (const char *)f->data;
+      file->len = f->len;
+      file->index = f->len;
+      file->pextension = NULL;
+      file->flags = f->flags;
+#if HTTPD_PRECALCULATED_CHECKSUM
+      file->chksum_count = f->chksum_count;
+      file->chksum = f->chksum;
+#endif /* HTTPD_PRECALCULATED_CHECKSUM */
+#if LWIP_HTTPD_FILE_STATE
+      file->state = fs_state_init(file, name);
+#endif /* #if LWIP_HTTPD_FILE_STATE */
+      return ERR_OK;
+    }
+  }
+  /* file not found */
+  return ERR_VAL;
+}
+
+/*-----------------------------------------------------------------------------------*/
+void
+fs_close(struct fs_file *file)
+{
+#if LWIP_HTTPD_CUSTOM_FILES
+  if (file->is_custom_file) {
+    fs_close_custom(file);
+  }
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+#if LWIP_HTTPD_FILE_STATE
+  fs_state_free(file, file->state);
+#endif /* #if LWIP_HTTPD_FILE_STATE */
+  LWIP_UNUSED_ARG(file);
+}
+/*-----------------------------------------------------------------------------------*/
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+#if LWIP_HTTPD_FS_ASYNC_READ
+int
+fs_read_async(struct fs_file *file, char *buffer, int count, fs_wait_cb callback_fn, void *callback_arg)
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+int
+fs_read(struct fs_file *file, char *buffer, int count)
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+{
+  int read;
+  if(file->index == file->len) {
+    return FS_READ_EOF;
+  }
+#if LWIP_HTTPD_FS_ASYNC_READ
+  LWIP_UNUSED_ARG(callback_fn);
+  LWIP_UNUSED_ARG(callback_arg);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+#if LWIP_HTTPD_CUSTOM_FILES
+  if (file->is_custom_file) {
+#if LWIP_HTTPD_FS_ASYNC_READ
+    return fs_read_async_custom(file, buffer, count, callback_fn, callback_arg);
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+    return fs_read_custom(file, buffer, count);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+  }
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+
+  read = file->len - file->index;
+  if(read > count) {
+    read = count;
+  }
+
+  MEMCPY(buffer, (file->data + file->index), read);
+  file->index += read;
+
+  return(read);
+}
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+/*-----------------------------------------------------------------------------------*/
+#if LWIP_HTTPD_FS_ASYNC_READ
+int
+fs_is_file_ready(struct fs_file *file, fs_wait_cb callback_fn, void *callback_arg)
+{
+  if (file != NULL) {
+#if LWIP_HTTPD_FS_ASYNC_READ
+#if LWIP_HTTPD_CUSTOM_FILES
+    if (!fs_canread_custom(file)) {
+      if (fs_wait_read_custom(file, callback_fn, callback_arg)) {
+        return 0;
+      }
+    }
+#else /* LWIP_HTTPD_CUSTOM_FILES */
+    LWIP_UNUSED_ARG(callback_fn);
+    LWIP_UNUSED_ARG(callback_arg);
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+  }
+  return 1;
+}
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+/*-----------------------------------------------------------------------------------*/
+int
+fs_bytes_left(struct fs_file *file)
+{
+  return file->len - file->index;
+}
diff --git a/src/apps/httpd/fs/404.html b/src/apps/httpd/fs/404.html
new file mode 100644
index 0000000..40b343a
--- /dev/null
+++ b/src/apps/httpd/fs/404.html
@@ -0,0 +1,21 @@
+<html>
+<head><title>lwIP - A Lightweight TCP/IP Stack</title></head>
+<body bgcolor="white" text="black">
+
+    <table width="100%">
+      <tr valign="top"><td width="80">	  
+	  <a href="http://www.sics.se/"><img src="/img/sics.gif"
+	  border="0" alt="SICS logo" title="SICS logo"></a>
+	</td><td width="500">	  
+	  <h1>lwIP - A Lightweight TCP/IP Stack</h1>
+	  <h2>404 - Page not found</h2>
+	  <p>
+	    Sorry, the page you are requesting was not found on this
+	    server. 
+	  </p>
+	</td><td>
+	  &nbsp;
+	</td></tr>
+      </table>
+</body>
+</html>
diff --git a/src/apps/httpd/fs/img/sics.gif b/src/apps/httpd/fs/img/sics.gif
new file mode 100644
index 0000000..0a4fc7b
--- /dev/null
+++ b/src/apps/httpd/fs/img/sics.gif
Binary files differ
diff --git a/src/apps/httpd/fs/index.html b/src/apps/httpd/fs/index.html
new file mode 100644
index 0000000..ab575ef
--- /dev/null
+++ b/src/apps/httpd/fs/index.html
@@ -0,0 +1,47 @@
+<html>
+<head><title>lwIP - A Lightweight TCP/IP Stack</title></head>
+<body bgcolor="white" text="black">
+
+    <table width="100%">
+      <tr valign="top"><td width="80">	  
+	  <a href="http://www.sics.se/"><img src="/img/sics.gif"
+	  border="0" alt="SICS logo" title="SICS logo"></a>
+	</td><td width="500">	  
+	  <h1>lwIP - A Lightweight TCP/IP Stack</h1>
+	  <p>
+	    The web page you are watching was served by a simple web
+	    server running on top of the lightweight TCP/IP stack <a
+	    href="http://www.sics.se/~adam/lwip/">lwIP</a>.
+	  </p>
+	  <p>
+	    lwIP is an open source implementation of the TCP/IP
+	    protocol suite that was originally written by <a
+	    href="http://www.sics.se/~adam/lwip/">Adam Dunkels
+	    of the Swedish Institute of Computer Science</a> but now is
+	    being actively developed by a team of developers
+	    distributed world-wide. Since it's release, lwIP has
+	    spurred a lot of interest and has been ported to several
+	    platforms and operating systems. lwIP can be used either
+	    with or without an underlying OS.
+	  </p>
+	  <p>
+	    The focus of the lwIP TCP/IP implementation is to reduce
+	    the RAM usage while still having a full scale TCP. This
+	    makes lwIP suitable for use in embedded systems with tens
+	    of kilobytes of free RAM and room for around 40 kilobytes
+	    of code ROM.
+	  </p>
+	  <p>
+	    More information about lwIP can be found at the lwIP
+	    homepage at <a
+	    href="http://savannah.nongnu.org/projects/lwip/">http://savannah.nongnu.org/projects/lwip/</a>
+	    or at the lwIP wiki at <a
+	    href="http://lwip.wikia.com/">http://lwip.wikia.com/</a>.
+	  </p>
+	</td><td>
+	  &nbsp;
+	</td></tr>
+      </table>
+</body>
+</html>
+
diff --git a/src/apps/httpd/fsdata.c b/src/apps/httpd/fsdata.c
new file mode 100644
index 0000000..6170ce6
--- /dev/null
+++ b/src/apps/httpd/fsdata.c
@@ -0,0 +1,298 @@
+#include "lwip/apps/fs.h"
+#include "lwip/def.h"
+#include "fsdata.h"
+
+
+#define file_NULL (struct fsdata_file *) NULL
+
+
+static const unsigned int dummy_align__img_sics_gif = 0;
+static const unsigned char data__img_sics_gif[] = {
+/* /img/sics.gif (14 chars) */
+0x2f,0x69,0x6d,0x67,0x2f,0x73,0x69,0x63,0x73,0x2e,0x67,0x69,0x66,0x00,0x00,0x00,
+
+/* HTTP header */
+/* "HTTP/1.0 200 OK
+" (17 bytes) */
+0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x32,0x30,0x30,0x20,0x4f,0x4b,0x0d,
+0x0a,
+/* "Server: lwIP/1.3.1 (http://savannah.nongnu.org/projects/lwip)
+" (63 bytes) */
+0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x31,0x2e,0x33,
+0x2e,0x31,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,0x6e,
+0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,0x70,
+0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a,
+/* "Content-type: image/gif
+
+" (27 bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x74,0x79,0x70,0x65,0x3a,0x20,0x69,0x6d,
+0x61,0x67,0x65,0x2f,0x67,0x69,0x66,0x0d,0x0a,0x0d,0x0a,
+/* raw file data (724 bytes) */
+0x47,0x49,0x46,0x38,0x39,0x61,0x46,0x00,0x22,0x00,0xa5,0x00,0x00,0xd9,0x2b,0x39,
+0x6a,0x6a,0x6a,0xbf,0xbf,0xbf,0x93,0x93,0x93,0x0f,0x0f,0x0f,0xb0,0xb0,0xb0,0xa6,
+0xa6,0xa6,0x80,0x80,0x80,0x76,0x76,0x76,0x1e,0x1e,0x1e,0x9d,0x9d,0x9d,0x2e,0x2e,
+0x2e,0x49,0x49,0x49,0x54,0x54,0x54,0x8a,0x8a,0x8a,0x60,0x60,0x60,0xc6,0xa6,0x99,
+0xbd,0xb5,0xb2,0xc2,0xab,0xa1,0xd9,0x41,0x40,0xd5,0x67,0x55,0xc0,0xb0,0xaa,0xd5,
+0x5e,0x4e,0xd6,0x50,0x45,0xcc,0x93,0x7d,0xc8,0xa1,0x90,0xce,0x8b,0x76,0xd2,0x7b,
+0x65,0xd1,0x84,0x6d,0xc9,0x99,0x86,0x3a,0x3a,0x3a,0x00,0x00,0x00,0xb8,0xb8,0xb8,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x2c,0x00,0x00,
+0x00,0x00,0x46,0x00,0x22,0x00,0x00,0x06,0xfe,0x40,0x90,0x70,0x48,0x2c,0x1a,0x8f,
+0xc8,0xa4,0x72,0xc9,0x6c,0x3a,0x9f,0xd0,0xa8,0x74,0x4a,0xad,0x5a,0xaf,0xd8,0xac,
+0x76,0xa9,0x40,0x04,0xbe,0x83,0xe2,0x60,0x3c,0x50,0x20,0x0d,0x8e,0x6f,0x00,0x31,
+0x28,0x1c,0x0d,0x07,0xb5,0xc3,0x60,0x75,0x24,0x3e,0xf8,0xfc,0x87,0x11,0x06,0xe9,
+0x3d,0x46,0x07,0x0b,0x7a,0x7a,0x7c,0x43,0x06,0x1e,0x84,0x78,0x0b,0x07,0x6e,0x51,
+0x01,0x8a,0x84,0x08,0x7e,0x79,0x80,0x87,0x89,0x91,0x7a,0x93,0x0a,0x04,0x99,0x78,
+0x96,0x4f,0x03,0x9e,0x79,0x01,0x94,0x9f,0x43,0x9c,0xa3,0xa4,0x05,0x77,0xa3,0xa0,
+0x4e,0x98,0x79,0x0b,0x1e,0x83,0xa4,0xa6,0x1f,0x96,0x05,0x9d,0xaa,0x78,0x01,0x07,
+0x84,0x04,0x1e,0x1e,0xbb,0xb8,0x51,0x84,0x0e,0x43,0x05,0x07,0x77,0xa5,0x7f,0x42,
+0xb1,0xb2,0x01,0x63,0x08,0x0d,0xbb,0x01,0x0c,0x7a,0x0d,0x44,0x0e,0xd8,0xaf,0x4c,
+0x05,0x7a,0x04,0x47,0x07,0x07,0xb7,0x80,0xa2,0xe1,0x7d,0x44,0x05,0x01,0x04,0x01,
+0xd0,0xea,0x87,0x93,0x4f,0xe0,0x9a,0x49,0xce,0xd8,0x79,0x04,0x66,0x20,0x15,0x10,
+0x10,0x11,0x92,0x29,0x80,0xb6,0xc0,0x91,0x15,0x45,0x1e,0x90,0x19,0x71,0x46,0xa8,
+0x5c,0x04,0x0e,0x00,0x22,0x4e,0xe8,0x40,0x24,0x9f,0x3e,0x04,0x06,0xa7,0x58,0xd4,
+0x93,0xa0,0x1c,0x91,0x3f,0xe8,0xf0,0x88,0x03,0xb1,0x21,0xa2,0x49,0x00,0x19,0x86,
+0xfc,0x52,0x44,0xe0,0x01,0x9d,0x29,0x21,0x15,0x25,0x50,0xf7,0x67,0x25,0x1e,0x06,
+0xfd,0x4e,0x9a,0xb4,0x90,0xac,0x15,0xfa,0xcb,0x52,0x53,0x1e,0x8c,0xf2,0xf8,0x07,
+0x92,0x2d,0x08,0x3a,0x4d,0x12,0x49,0x95,0x49,0xdb,0x14,0x04,0xc4,0x14,0x85,0x29,
+0xaa,0xe7,0x01,0x08,0xa4,0x49,0x01,0x14,0x51,0xe0,0x53,0x91,0xd5,0x29,0x06,0x1a,
+0x64,0x02,0xf4,0xc7,0x81,0x9e,0x05,0x20,0x22,0x64,0xa5,0x30,0xae,0xab,0x9e,0x97,
+0x53,0xd8,0xb9,0xfd,0x50,0xef,0x93,0x02,0x42,0x74,0x34,0xe8,0x9c,0x20,0x21,0xc9,
+0x01,0x68,0x78,0xe6,0x55,0x29,0x20,0x56,0x4f,0x4c,0x40,0x51,0x71,0x82,0xc0,0x70,
+0x21,0x22,0x85,0xbe,0x4b,0x1c,0x44,0x05,0xea,0xa4,0x01,0xbf,0x22,0xb5,0xf0,0x1c,
+0x06,0x51,0x38,0x8f,0xe0,0x22,0xec,0x18,0xac,0x39,0x22,0xd4,0xd6,0x93,0x44,0x01,
+0x32,0x82,0xc8,0xfc,0x61,0xb3,0x01,0x45,0x0c,0x2e,0x83,0x30,0xd0,0x0e,0x17,0x24,
+0x0f,0x70,0x85,0x94,0xee,0x05,0x05,0x53,0x4b,0x32,0x1b,0x3f,0x98,0xd3,0x1d,0x29,
+0x81,0xb0,0xae,0x1e,0x8c,0x7e,0x68,0xe0,0x60,0x5a,0x54,0x8f,0xb0,0x78,0x69,0x73,
+0x06,0xa2,0x00,0x6b,0x57,0xca,0x3d,0x11,0x50,0xbd,0x04,0x30,0x4b,0x3a,0xd4,0xab,
+0x5f,0x1f,0x9b,0x3d,0x13,0x74,0x27,0x88,0x3c,0x25,0xe0,0x17,0xbe,0x7a,0x79,0x45,
+0x0d,0x0c,0xb0,0x8b,0xda,0x90,0xca,0x80,0x06,0x5d,0x17,0x60,0x1c,0x22,0x4c,0xd8,
+0x57,0x22,0x06,0x20,0x00,0x98,0x07,0x08,0xe4,0x56,0x80,0x80,0x1c,0xc5,0xb7,0xc5,
+0x82,0x0c,0x36,0xe8,0xe0,0x83,0x10,0x46,0x28,0xe1,0x84,0x14,0x56,0x68,0xa1,0x10,
+0x41,0x00,0x00,0x3b,};
+
+static const unsigned int dummy_align__404_html = 1;
+static const unsigned char data__404_html[] = {
+/* /404.html (10 chars) */
+0x2f,0x34,0x30,0x34,0x2e,0x68,0x74,0x6d,0x6c,0x00,0x00,0x00,
+
+/* HTTP header */
+/* "HTTP/1.0 404 File not found
+" (29 bytes) */
+0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x34,0x30,0x34,0x20,0x46,0x69,0x6c,
+0x65,0x20,0x6e,0x6f,0x74,0x20,0x66,0x6f,0x75,0x6e,0x64,0x0d,0x0a,
+/* "Server: lwIP/1.3.1 (http://savannah.nongnu.org/projects/lwip)
+" (63 bytes) */
+0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x31,0x2e,0x33,
+0x2e,0x31,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,0x6e,
+0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,0x70,
+0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a,
+/* "Content-type: text/html
+
+" (27 bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x74,0x79,0x70,0x65,0x3a,0x20,0x74,0x65,
+0x78,0x74,0x2f,0x68,0x74,0x6d,0x6c,0x0d,0x0a,0x0d,0x0a,
+/* raw file data (565 bytes) */
+0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x0d,0x0a,0x3c,0x68,0x65,0x61,0x64,0x3e,0x3c,0x74,
+0x69,0x74,0x6c,0x65,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,0x69,
+0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,0x50,
+0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x74,0x69,0x74,0x6c,0x65,0x3e,0x3c,0x2f,
+0x68,0x65,0x61,0x64,0x3e,0x0d,0x0a,0x3c,0x62,0x6f,0x64,0x79,0x20,0x62,0x67,0x63,
+0x6f,0x6c,0x6f,0x72,0x3d,0x22,0x77,0x68,0x69,0x74,0x65,0x22,0x20,0x74,0x65,0x78,
+0x74,0x3d,0x22,0x62,0x6c,0x61,0x63,0x6b,0x22,0x3e,0x0d,0x0a,0x0d,0x0a,0x20,0x20,
+0x20,0x20,0x3c,0x74,0x61,0x62,0x6c,0x65,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,
+0x31,0x30,0x30,0x25,0x22,0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,
+0x72,0x20,0x76,0x61,0x6c,0x69,0x67,0x6e,0x3d,0x22,0x74,0x6f,0x70,0x22,0x3e,0x3c,
+0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x38,0x30,0x22,0x3e,0x09,0x20,
+0x20,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x61,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,
+0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,
+0x65,0x2f,0x22,0x3e,0x3c,0x69,0x6d,0x67,0x20,0x73,0x72,0x63,0x3d,0x22,0x2f,0x69,
+0x6d,0x67,0x2f,0x73,0x69,0x63,0x73,0x2e,0x67,0x69,0x66,0x22,0x0d,0x0a,0x09,0x20,
+0x20,0x62,0x6f,0x72,0x64,0x65,0x72,0x3d,0x22,0x30,0x22,0x20,0x61,0x6c,0x74,0x3d,
+0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x20,0x74,0x69,0x74,0x6c,
+0x65,0x3d,0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x3e,0x3c,0x2f,
+0x61,0x3e,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x20,0x77,0x69,
+0x64,0x74,0x68,0x3d,0x22,0x35,0x30,0x30,0x22,0x3e,0x09,0x20,0x20,0x0d,0x0a,0x09,
+0x20,0x20,0x3c,0x68,0x31,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,
+0x69,0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,
+0x50,0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x68,0x31,0x3e,0x0d,0x0a,0x09,0x20,
+0x20,0x3c,0x68,0x32,0x3e,0x34,0x30,0x34,0x20,0x2d,0x20,0x50,0x61,0x67,0x65,0x20,
+0x6e,0x6f,0x74,0x20,0x66,0x6f,0x75,0x6e,0x64,0x3c,0x2f,0x68,0x32,0x3e,0x0d,0x0a,
+0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x53,0x6f,0x72,
+0x72,0x79,0x2c,0x20,0x74,0x68,0x65,0x20,0x70,0x61,0x67,0x65,0x20,0x79,0x6f,0x75,
+0x20,0x61,0x72,0x65,0x20,0x72,0x65,0x71,0x75,0x65,0x73,0x74,0x69,0x6e,0x67,0x20,
+0x77,0x61,0x73,0x20,0x6e,0x6f,0x74,0x20,0x66,0x6f,0x75,0x6e,0x64,0x20,0x6f,0x6e,
+0x20,0x74,0x68,0x69,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x73,0x65,0x72,0x76,
+0x65,0x72,0x2e,0x20,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,
+0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x26,0x6e,
+0x62,0x73,0x70,0x3b,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x2f,0x74,0x72,
+0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,
+0x3e,0x0d,0x0a,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x0d,0x0a,0x3c,0x2f,0x68,0x74,
+0x6d,0x6c,0x3e,0x0d,0x0a,};
+
+static const unsigned int dummy_align__index_html = 2;
+static const unsigned char data__index_html[] = {
+/* /index.html (12 chars) */
+0x2f,0x69,0x6e,0x64,0x65,0x78,0x2e,0x68,0x74,0x6d,0x6c,0x00,
+
+/* HTTP header */
+/* "HTTP/1.0 200 OK
+" (17 bytes) */
+0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x32,0x30,0x30,0x20,0x4f,0x4b,0x0d,
+0x0a,
+/* "Server: lwIP/1.3.1 (http://savannah.nongnu.org/projects/lwip)
+" (63 bytes) */
+0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x31,0x2e,0x33,
+0x2e,0x31,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,0x6e,
+0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,0x70,
+0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a,
+/* "Content-type: text/html
+
+" (27 bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x74,0x79,0x70,0x65,0x3a,0x20,0x74,0x65,
+0x78,0x74,0x2f,0x68,0x74,0x6d,0x6c,0x0d,0x0a,0x0d,0x0a,
+/* raw file data (1751 bytes) */
+0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x0d,0x0a,0x3c,0x68,0x65,0x61,0x64,0x3e,0x3c,0x74,
+0x69,0x74,0x6c,0x65,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,0x69,
+0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,0x50,
+0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x74,0x69,0x74,0x6c,0x65,0x3e,0x3c,0x2f,
+0x68,0x65,0x61,0x64,0x3e,0x0d,0x0a,0x3c,0x62,0x6f,0x64,0x79,0x20,0x62,0x67,0x63,
+0x6f,0x6c,0x6f,0x72,0x3d,0x22,0x77,0x68,0x69,0x74,0x65,0x22,0x20,0x74,0x65,0x78,
+0x74,0x3d,0x22,0x62,0x6c,0x61,0x63,0x6b,0x22,0x3e,0x0d,0x0a,0x0d,0x0a,0x20,0x20,
+0x20,0x20,0x3c,0x74,0x61,0x62,0x6c,0x65,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,
+0x31,0x30,0x30,0x25,0x22,0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,
+0x72,0x20,0x76,0x61,0x6c,0x69,0x67,0x6e,0x3d,0x22,0x74,0x6f,0x70,0x22,0x3e,0x3c,
+0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x38,0x30,0x22,0x3e,0x09,0x20,
+0x20,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x61,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,
+0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,
+0x65,0x2f,0x22,0x3e,0x3c,0x69,0x6d,0x67,0x20,0x73,0x72,0x63,0x3d,0x22,0x2f,0x69,
+0x6d,0x67,0x2f,0x73,0x69,0x63,0x73,0x2e,0x67,0x69,0x66,0x22,0x0d,0x0a,0x09,0x20,
+0x20,0x62,0x6f,0x72,0x64,0x65,0x72,0x3d,0x22,0x30,0x22,0x20,0x61,0x6c,0x74,0x3d,
+0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x20,0x74,0x69,0x74,0x6c,
+0x65,0x3d,0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x3e,0x3c,0x2f,
+0x61,0x3e,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x20,0x77,0x69,
+0x64,0x74,0x68,0x3d,0x22,0x35,0x30,0x30,0x22,0x3e,0x09,0x20,0x20,0x0d,0x0a,0x09,
+0x20,0x20,0x3c,0x68,0x31,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,
+0x69,0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,
+0x50,0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x68,0x31,0x3e,0x0d,0x0a,0x09,0x20,
+0x20,0x3c,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x54,0x68,0x65,0x20,0x77,
+0x65,0x62,0x20,0x70,0x61,0x67,0x65,0x20,0x79,0x6f,0x75,0x20,0x61,0x72,0x65,0x20,
+0x77,0x61,0x74,0x63,0x68,0x69,0x6e,0x67,0x20,0x77,0x61,0x73,0x20,0x73,0x65,0x72,
+0x76,0x65,0x64,0x20,0x62,0x79,0x20,0x61,0x20,0x73,0x69,0x6d,0x70,0x6c,0x65,0x20,
+0x77,0x65,0x62,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x73,0x65,0x72,0x76,0x65,0x72,
+0x20,0x72,0x75,0x6e,0x6e,0x69,0x6e,0x67,0x20,0x6f,0x6e,0x20,0x74,0x6f,0x70,0x20,
+0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x6c,0x69,0x67,0x68,0x74,0x77,0x65,0x69,0x67,
+0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,0x50,0x20,0x73,0x74,0x61,0x63,0x6b,0x20,
+0x3c,0x61,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,
+0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,
+0x65,0x2f,0x7e,0x61,0x64,0x61,0x6d,0x2f,0x6c,0x77,0x69,0x70,0x2f,0x22,0x3e,0x6c,
+0x77,0x49,0x50,0x3c,0x2f,0x61,0x3e,0x2e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x70,
+0x3e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,
+0x6c,0x77,0x49,0x50,0x20,0x69,0x73,0x20,0x61,0x6e,0x20,0x6f,0x70,0x65,0x6e,0x20,
+0x73,0x6f,0x75,0x72,0x63,0x65,0x20,0x69,0x6d,0x70,0x6c,0x65,0x6d,0x65,0x6e,0x74,
+0x61,0x74,0x69,0x6f,0x6e,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x54,0x43,0x50,
+0x2f,0x49,0x50,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x70,0x72,0x6f,0x74,0x6f,0x63,
+0x6f,0x6c,0x20,0x73,0x75,0x69,0x74,0x65,0x20,0x74,0x68,0x61,0x74,0x20,0x77,0x61,
+0x73,0x20,0x6f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x6c,0x79,0x20,0x77,0x72,0x69,
+0x74,0x74,0x65,0x6e,0x20,0x62,0x79,0x20,0x3c,0x61,0x0d,0x0a,0x09,0x20,0x20,0x20,
+0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,
+0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,0x65,0x2f,0x7e,0x61,0x64,0x61,0x6d,0x2f,
+0x6c,0x77,0x69,0x70,0x2f,0x22,0x3e,0x41,0x64,0x61,0x6d,0x20,0x44,0x75,0x6e,0x6b,
+0x65,0x6c,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,
+0x20,0x53,0x77,0x65,0x64,0x69,0x73,0x68,0x20,0x49,0x6e,0x73,0x74,0x69,0x74,0x75,
+0x74,0x65,0x20,0x6f,0x66,0x20,0x43,0x6f,0x6d,0x70,0x75,0x74,0x65,0x72,0x20,0x53,
+0x63,0x69,0x65,0x6e,0x63,0x65,0x3c,0x2f,0x61,0x3e,0x20,0x62,0x75,0x74,0x20,0x6e,
+0x6f,0x77,0x20,0x69,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x62,0x65,0x69,0x6e,
+0x67,0x20,0x61,0x63,0x74,0x69,0x76,0x65,0x6c,0x79,0x20,0x64,0x65,0x76,0x65,0x6c,
+0x6f,0x70,0x65,0x64,0x20,0x62,0x79,0x20,0x61,0x20,0x74,0x65,0x61,0x6d,0x20,0x6f,
+0x66,0x20,0x64,0x65,0x76,0x65,0x6c,0x6f,0x70,0x65,0x72,0x73,0x0d,0x0a,0x09,0x20,
+0x20,0x20,0x20,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x77,
+0x6f,0x72,0x6c,0x64,0x2d,0x77,0x69,0x64,0x65,0x2e,0x20,0x53,0x69,0x6e,0x63,0x65,
+0x20,0x69,0x74,0x27,0x73,0x20,0x72,0x65,0x6c,0x65,0x61,0x73,0x65,0x2c,0x20,0x6c,
+0x77,0x49,0x50,0x20,0x68,0x61,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x73,0x70,
+0x75,0x72,0x72,0x65,0x64,0x20,0x61,0x20,0x6c,0x6f,0x74,0x20,0x6f,0x66,0x20,0x69,
+0x6e,0x74,0x65,0x72,0x65,0x73,0x74,0x20,0x61,0x6e,0x64,0x20,0x68,0x61,0x73,0x20,
+0x62,0x65,0x65,0x6e,0x20,0x70,0x6f,0x72,0x74,0x65,0x64,0x20,0x74,0x6f,0x20,0x73,
+0x65,0x76,0x65,0x72,0x61,0x6c,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x70,0x6c,0x61,
+0x74,0x66,0x6f,0x72,0x6d,0x73,0x20,0x61,0x6e,0x64,0x20,0x6f,0x70,0x65,0x72,0x61,
+0x74,0x69,0x6e,0x67,0x20,0x73,0x79,0x73,0x74,0x65,0x6d,0x73,0x2e,0x20,0x6c,0x77,
+0x49,0x50,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x75,0x73,0x65,0x64,0x20,0x65,
+0x69,0x74,0x68,0x65,0x72,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x77,0x69,0x74,0x68,
+0x20,0x6f,0x72,0x20,0x77,0x69,0x74,0x68,0x6f,0x75,0x74,0x20,0x61,0x6e,0x20,0x75,
+0x6e,0x64,0x65,0x72,0x6c,0x79,0x69,0x6e,0x67,0x20,0x4f,0x53,0x2e,0x0d,0x0a,0x09,
+0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,0x0a,
+0x09,0x20,0x20,0x20,0x20,0x54,0x68,0x65,0x20,0x66,0x6f,0x63,0x75,0x73,0x20,0x6f,
+0x66,0x20,0x74,0x68,0x65,0x20,0x6c,0x77,0x49,0x50,0x20,0x54,0x43,0x50,0x2f,0x49,
+0x50,0x20,0x69,0x6d,0x70,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x61,0x74,0x69,0x6f,0x6e,
+0x20,0x69,0x73,0x20,0x74,0x6f,0x20,0x72,0x65,0x64,0x75,0x63,0x65,0x0d,0x0a,0x09,
+0x20,0x20,0x20,0x20,0x74,0x68,0x65,0x20,0x52,0x41,0x4d,0x20,0x75,0x73,0x61,0x67,
+0x65,0x20,0x77,0x68,0x69,0x6c,0x65,0x20,0x73,0x74,0x69,0x6c,0x6c,0x20,0x68,0x61,
+0x76,0x69,0x6e,0x67,0x20,0x61,0x20,0x66,0x75,0x6c,0x6c,0x20,0x73,0x63,0x61,0x6c,
+0x65,0x20,0x54,0x43,0x50,0x2e,0x20,0x54,0x68,0x69,0x73,0x0d,0x0a,0x09,0x20,0x20,
+0x20,0x20,0x6d,0x61,0x6b,0x65,0x73,0x20,0x6c,0x77,0x49,0x50,0x20,0x73,0x75,0x69,
+0x74,0x61,0x62,0x6c,0x65,0x20,0x66,0x6f,0x72,0x20,0x75,0x73,0x65,0x20,0x69,0x6e,
+0x20,0x65,0x6d,0x62,0x65,0x64,0x64,0x65,0x64,0x20,0x73,0x79,0x73,0x74,0x65,0x6d,
+0x73,0x20,0x77,0x69,0x74,0x68,0x20,0x74,0x65,0x6e,0x73,0x0d,0x0a,0x09,0x20,0x20,
+0x20,0x20,0x6f,0x66,0x20,0x6b,0x69,0x6c,0x6f,0x62,0x79,0x74,0x65,0x73,0x20,0x6f,
+0x66,0x20,0x66,0x72,0x65,0x65,0x20,0x52,0x41,0x4d,0x20,0x61,0x6e,0x64,0x20,0x72,
+0x6f,0x6f,0x6d,0x20,0x66,0x6f,0x72,0x20,0x61,0x72,0x6f,0x75,0x6e,0x64,0x20,0x34,
+0x30,0x20,0x6b,0x69,0x6c,0x6f,0x62,0x79,0x74,0x65,0x73,0x0d,0x0a,0x09,0x20,0x20,
+0x20,0x20,0x6f,0x66,0x20,0x63,0x6f,0x64,0x65,0x20,0x52,0x4f,0x4d,0x2e,0x0d,0x0a,
+0x09,0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,
+0x0a,0x09,0x20,0x20,0x20,0x20,0x4d,0x6f,0x72,0x65,0x20,0x69,0x6e,0x66,0x6f,0x72,
+0x6d,0x61,0x74,0x69,0x6f,0x6e,0x20,0x61,0x62,0x6f,0x75,0x74,0x20,0x6c,0x77,0x49,
+0x50,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x66,0x6f,0x75,0x6e,0x64,0x20,0x61,
+0x74,0x20,0x74,0x68,0x65,0x20,0x6c,0x77,0x49,0x50,0x0d,0x0a,0x09,0x20,0x20,0x20,
+0x20,0x68,0x6f,0x6d,0x65,0x70,0x61,0x67,0x65,0x20,0x61,0x74,0x20,0x3c,0x61,0x0d,
+0x0a,0x09,0x20,0x20,0x20,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,0x74,0x74,0x70,
+0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,
+0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,
+0x6c,0x77,0x69,0x70,0x2f,0x22,0x3e,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,
+0x76,0x61,0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,
+0x67,0x2f,0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x2f,
+0x3c,0x2f,0x61,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x6f,0x72,0x20,0x61,0x74,
+0x20,0x74,0x68,0x65,0x20,0x6c,0x77,0x49,0x50,0x20,0x77,0x69,0x6b,0x69,0x20,0x61,
+0x74,0x20,0x3c,0x61,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x68,0x72,0x65,0x66,0x3d,
+0x22,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x6c,0x77,0x69,0x70,0x2e,0x77,0x69,0x6b,
+0x69,0x61,0x2e,0x63,0x6f,0x6d,0x2f,0x22,0x3e,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,
+0x6c,0x77,0x69,0x70,0x2e,0x77,0x69,0x6b,0x69,0x61,0x2e,0x63,0x6f,0x6d,0x2f,0x3c,
+0x2f,0x61,0x3e,0x2e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,
+0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x26,0x6e,
+0x62,0x73,0x70,0x3b,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x2f,0x74,0x72,
+0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,
+0x3e,0x0d,0x0a,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x0d,0x0a,0x3c,0x2f,0x68,0x74,
+0x6d,0x6c,0x3e,0x0d,0x0a,0x0d,0x0a,};
+
+
+
+const struct fsdata_file file__img_sics_gif[] = { {
+file_NULL,
+data__img_sics_gif,
+data__img_sics_gif + 16,
+sizeof(data__img_sics_gif) - 16,
+1,
+}};
+
+const struct fsdata_file file__404_html[] = { {
+file__img_sics_gif,
+data__404_html,
+data__404_html + 12,
+sizeof(data__404_html) - 12,
+1,
+}};
+
+const struct fsdata_file file__index_html[] = { {
+file__404_html,
+data__index_html,
+data__index_html + 12,
+sizeof(data__index_html) - 12,
+1,
+}};
+
+#define FS_ROOT file__index_html
+#define FS_NUMFILES 3
+
diff --git a/src/apps/httpd/fsdata.h b/src/apps/httpd/fsdata.h
new file mode 100644
index 0000000..ac4548c
--- /dev/null
+++ b/src/apps/httpd/fsdata.h
@@ -0,0 +1,50 @@
+/*
+ * 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: Adam Dunkels <adam@sics.se>
+ *
+ */
+#ifndef LWIP_FSDATA_H
+#define LWIP_FSDATA_H
+
+#include "lwip/apps/httpd_opts.h"
+#include "lwip/apps/fs.h"
+
+struct fsdata_file {
+  const struct fsdata_file *next;
+  const unsigned char *name;
+  const unsigned char *data;
+  int len;
+  u8_t flags;
+#if HTTPD_PRECALCULATED_CHECKSUM
+  u16_t chksum_count;
+  const struct fsdata_chksum *chksum;
+#endif /* HTTPD_PRECALCULATED_CHECKSUM */
+};
+
+#endif /* LWIP_FSDATA_H */
diff --git a/src/apps/httpd/httpd.c b/src/apps/httpd/httpd.c
new file mode 100644
index 0000000..ffae27d
--- /dev/null
+++ b/src/apps/httpd/httpd.c
@@ -0,0 +1,2634 @@
+/**
+ * @file
+ * LWIP HTTP server implementation
+ */
+
+/*
+ * 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: Adam Dunkels <adam@sics.se>
+ *         Simon Goldschmidt
+ *
+ */
+
+/**
+ * @defgroup httpd HTTP server
+ * @ingroup apps
+ *
+ * This httpd supports for a
+ * rudimentary server-side-include facility which will replace tags of the form
+ * <!--#tag--> in any file whose extension is .shtml, .shtm or .ssi with
+ * strings provided by an include handler whose pointer is provided to the
+ * module via function http_set_ssi_handler().
+ * Additionally, a simple common
+ * gateway interface (CGI) handling mechanism has been added to allow clients
+ * to hook functions to particular request URIs.
+ *
+ * To enable SSI support, define label LWIP_HTTPD_SSI in lwipopts.h.
+ * To enable CGI support, define label LWIP_HTTPD_CGI in lwipopts.h.
+ *
+ * By default, the server assumes that HTTP headers are already present in
+ * each file stored in the file system.  By defining LWIP_HTTPD_DYNAMIC_HEADERS in
+ * lwipopts.h, this behavior can be changed such that the server inserts the
+ * headers automatically based on the extension of the file being served.  If
+ * this mode is used, be careful to ensure that the file system image used
+ * does not already contain the header information.
+ *
+ * File system images without headers can be created using the makefsfile
+ * tool with the -h command line option.
+ *
+ *
+ * Notes about valid SSI tags
+ * --------------------------
+ *
+ * The following assumptions are made about tags used in SSI markers:
+ *
+ * 1. No tag may contain '-' or whitespace characters within the tag name.
+ * 2. Whitespace is allowed between the tag leadin "<!--#" and the start of
+ *    the tag name and between the tag name and the leadout string "-->".
+ * 3. The maximum tag name length is LWIP_HTTPD_MAX_TAG_NAME_LEN, currently 8 characters.
+ *
+ * Notes on CGI usage
+ * ------------------
+ *
+ * The simple CGI support offered here works with GET method requests only
+ * and can handle up to 16 parameters encoded into the URI. The handler
+ * function may not write directly to the HTTP output but must return a
+ * filename that the HTTP server will send to the browser as a response to
+ * the incoming CGI request.
+ *
+ *
+ *
+ * The list of supported file types is quite short, so if makefsdata complains
+ * about an unknown extension, make sure to add it (and its doctype) to
+ * the 'g_psHTTPHeaders' list.
+ */
+#include "lwip/init.h"
+#include "lwip/apps/httpd.h"
+#include "lwip/debug.h"
+#include "lwip/stats.h"
+#include "lwip/apps/fs.h"
+#include "httpd_structs.h"
+#include "lwip/def.h"
+#include "lwip/ip.h"
+#include "lwip/tcp.h"
+#if LWIP_HTTPD_TIMING
+#include "lwip/sys.h"
+#endif
+
+#include <string.h> /* memset */
+#include <stdlib.h> /* atoi */
+#include <stdio.h>
+
+#if LWIP_TCP && LWIP_CALLBACK_API
+
+/** Minimum length for a valid HTTP/0.9 request: "GET /\r\n" -> 7 bytes */
+#define MIN_REQ_LEN   7
+
+#define CRLF "\r\n"
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+#define HTTP11_CONNECTIONKEEPALIVE  "Connection: keep-alive"
+#define HTTP11_CONNECTIONKEEPALIVE2 "Connection: Keep-Alive"
+#endif
+
+/** These defines check whether tcp_write has to copy data or not */
+
+/** This was TI's check whether to let TCP copy data or not
+ * \#define HTTP_IS_DATA_VOLATILE(hs) ((hs->file < (char *)0x20000000) ? 0 : TCP_WRITE_FLAG_COPY)
+ */
+#ifndef HTTP_IS_DATA_VOLATILE
+#if LWIP_HTTPD_SSI
+/* Copy for SSI files, no copy for non-SSI files */
+#define HTTP_IS_DATA_VOLATILE(hs)   ((hs)->ssi ? TCP_WRITE_FLAG_COPY : 0)
+#else /* LWIP_HTTPD_SSI */
+/** Default: don't copy if the data is sent from file-system directly */
+#define HTTP_IS_DATA_VOLATILE(hs) (((hs->file != NULL) && (hs->handle != NULL) && (hs->file == \
+                                   (const char*)hs->handle->data + hs->handle->len - hs->left)) \
+                                   ? 0 : TCP_WRITE_FLAG_COPY)
+#endif /* LWIP_HTTPD_SSI */
+#endif
+
+/** Default: headers are sent from ROM */
+#ifndef HTTP_IS_HDR_VOLATILE
+#define HTTP_IS_HDR_VOLATILE(hs, ptr) 0
+#endif
+
+/* Return values for http_send_*() */
+#define HTTP_DATA_TO_SEND_BREAK    2
+#define HTTP_DATA_TO_SEND_CONTINUE 1
+#define HTTP_NO_DATA_TO_SEND       0
+
+typedef struct
+{
+  const char *name;
+  u8_t shtml;
+} default_filename;
+
+const default_filename g_psDefaultFilenames[] = {
+  {"/index.shtml", 1 },
+  {"/index.ssi",   1 },
+  {"/index.shtm",  1 },
+  {"/index.html",  0 },
+  {"/index.htm",   0 }
+};
+
+#define NUM_DEFAULT_FILENAMES (sizeof(g_psDefaultFilenames) /   \
+                               sizeof(default_filename))
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+/** HTTP request is copied here from pbufs for simple parsing */
+static char httpd_req_buf[LWIP_HTTPD_MAX_REQ_LENGTH+1];
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+
+#if LWIP_HTTPD_SUPPORT_POST
+#if LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN > LWIP_HTTPD_MAX_REQUEST_URI_LEN
+#define LWIP_HTTPD_URI_BUF_LEN LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN
+#endif
+#endif
+#ifndef LWIP_HTTPD_URI_BUF_LEN
+#define LWIP_HTTPD_URI_BUF_LEN LWIP_HTTPD_MAX_REQUEST_URI_LEN
+#endif
+#if LWIP_HTTPD_URI_BUF_LEN
+/* Filename for response file to send when POST is finished or
+ * search for default files when a directory is requested. */
+static char http_uri_buf[LWIP_HTTPD_URI_BUF_LEN+1];
+#endif
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+/* The number of individual strings that comprise the headers sent before each
+ * requested file.
+ */
+#define NUM_FILE_HDR_STRINGS 5
+#define HDR_STRINGS_IDX_HTTP_STATUS          0 /* e.g. "HTTP/1.0 200 OK\r\n" */
+#define HDR_STRINGS_IDX_SERVER_NAME          1 /* e.g. "Server: "HTTPD_SERVER_AGENT"\r\n" */
+#define HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE 2 /* e.g. "Content-Length: xy\r\n" and/or "Connection: keep-alive\r\n" */
+#define HDR_STRINGS_IDX_CONTENT_LEN_NR       3 /* the byte count, when content-length is used */
+#define HDR_STRINGS_IDX_CONTENT_TYPE         4 /* the content type (or default answer content type including default document) */
+
+/* The dynamically generated Content-Length buffer needs space for CRLF + NULL */
+#define LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET 3
+#ifndef LWIP_HTTPD_MAX_CONTENT_LEN_SIZE
+/* The dynamically generated Content-Length buffer shall be able to work with
+   ~953 MB (9 digits) */
+#define LWIP_HTTPD_MAX_CONTENT_LEN_SIZE   (9 + LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET)
+#endif
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+#if LWIP_HTTPD_SSI
+
+#define HTTPD_LAST_TAG_PART 0xFFFF
+
+enum tag_check_state {
+  TAG_NONE,       /* Not processing an SSI tag */
+  TAG_LEADIN,     /* Tag lead in "<!--#" being processed */
+  TAG_FOUND,      /* Tag name being read, looking for lead-out start */
+  TAG_LEADOUT,    /* Tag lead out "-->" being processed */
+  TAG_SENDING     /* Sending tag replacement string */
+};
+
+struct http_ssi_state {
+  const char *parsed;     /* Pointer to the first unparsed byte in buf. */
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+  const char *tag_started;/* Pointer to the first opening '<' of the tag. */
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG */
+  const char *tag_end;    /* Pointer to char after the closing '>' of the tag. */
+  u32_t parse_left; /* Number of unparsed bytes in buf. */
+  u16_t tag_index;   /* Counter used by tag parsing state machine */
+  u16_t tag_insert_len; /* Length of insert in string tag_insert */
+#if LWIP_HTTPD_SSI_MULTIPART
+  u16_t tag_part; /* Counter passed to and changed by tag insertion function to insert multiple times */
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+  u8_t tag_name_len; /* Length of the tag name in string tag_name */
+  char tag_name[LWIP_HTTPD_MAX_TAG_NAME_LEN + 1]; /* Last tag name extracted */
+  char tag_insert[LWIP_HTTPD_MAX_TAG_INSERT_LEN + 1]; /* Insert string for tag_name */
+  enum tag_check_state tag_state; /* State of the tag processor */
+};
+#endif /* LWIP_HTTPD_SSI */
+
+struct http_state {
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+  struct http_state *next;
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+  struct fs_file file_handle;
+  struct fs_file *handle;
+  const char *file;       /* Pointer to first unsent byte in buf. */
+
+  struct tcp_pcb *pcb;
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+  struct pbuf *req;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+  char *buf;        /* File read buffer. */
+  int buf_len;      /* Size of file read buffer, buf. */
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+  u32_t left;       /* Number of unsent bytes in buf. */
+  u8_t retries;
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+  u8_t keepalive;
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+#if LWIP_HTTPD_SSI
+  struct http_ssi_state *ssi;
+#endif /* LWIP_HTTPD_SSI */
+#if LWIP_HTTPD_CGI
+  char *params[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Params extracted from the request URI */
+  char *param_vals[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Values for each extracted param */
+#endif /* LWIP_HTTPD_CGI */
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+  const char *hdrs[NUM_FILE_HDR_STRINGS]; /* HTTP headers to be sent. */
+  char hdr_content_len[LWIP_HTTPD_MAX_CONTENT_LEN_SIZE];
+  u16_t hdr_pos;     /* The position of the first unsent header byte in the
+                        current string */
+  u16_t hdr_index;   /* The index of the hdr string currently being sent. */
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+#if LWIP_HTTPD_TIMING
+  u32_t time_started;
+#endif /* LWIP_HTTPD_TIMING */
+#if LWIP_HTTPD_SUPPORT_POST
+  u32_t post_content_len_left;
+#if LWIP_HTTPD_POST_MANUAL_WND
+  u32_t unrecved_bytes;
+  u8_t no_auto_wnd;
+  u8_t post_finished;
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+#endif /* LWIP_HTTPD_SUPPORT_POST*/
+};
+
+#if HTTPD_USE_MEM_POOL
+LWIP_MEMPOOL_DECLARE(HTTPD_STATE,     MEMP_NUM_PARALLEL_HTTPD_CONNS,     sizeof(struct http_state),     "HTTPD_STATE")
+#if LWIP_HTTPD_SSI
+LWIP_MEMPOOL_DECLARE(HTTPD_SSI_STATE, MEMP_NUM_PARALLEL_HTTPD_SSI_CONNS, sizeof(struct http_ssi_state), "HTTPD_SSI_STATE")
+#define HTTP_FREE_SSI_STATE(x)  LWIP_MEMPOOL_FREE(HTTPD_SSI_STATE, (x))
+#define HTTP_ALLOC_SSI_STATE()  (struct http_ssi_state *)LWIP_MEMPOOL_ALLOC(HTTPD_SSI_STATE)
+#endif /* LWIP_HTTPD_SSI */
+#define HTTP_ALLOC_HTTP_STATE() (struct http_state *)LWIP_MEMPOOL_ALLOC(HTTPD_STATE)
+#define HTTP_FREE_HTTP_STATE(x) LWIP_MEMPOOL_FREE(HTTPD_STATE, (x))
+#else /* HTTPD_USE_MEM_POOL */
+#define HTTP_ALLOC_HTTP_STATE() (struct http_state *)mem_malloc(sizeof(struct http_state))
+#define HTTP_FREE_HTTP_STATE(x) mem_free(x)
+#if LWIP_HTTPD_SSI
+#define HTTP_ALLOC_SSI_STATE()  (struct http_ssi_state *)mem_malloc(sizeof(struct http_ssi_state))
+#define HTTP_FREE_SSI_STATE(x)  mem_free(x)
+#endif /* LWIP_HTTPD_SSI */
+#endif /* HTTPD_USE_MEM_POOL */
+
+static err_t http_close_conn(struct tcp_pcb *pcb, struct http_state *hs);
+static err_t http_close_or_abort_conn(struct tcp_pcb *pcb, struct http_state *hs, u8_t abort_conn);
+static err_t http_find_file(struct http_state *hs, const char *uri, int is_09);
+static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, u8_t tag_check, char* params);
+static err_t http_poll(void *arg, struct tcp_pcb *pcb);
+static u8_t http_check_eof(struct tcp_pcb *pcb, struct http_state *hs);
+#if LWIP_HTTPD_FS_ASYNC_READ
+static void http_continue(void *connection);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+#if LWIP_HTTPD_SSI
+/* SSI insert handler function pointer. */
+tSSIHandler g_pfnSSIHandler;
+#if !LWIP_HTTPD_SSI_RAW
+int g_iNumTags;
+const char **g_ppcTags;
+#endif /* !LWIP_HTTPD_SSI_RAW */
+
+#define LEN_TAG_LEAD_IN 5
+const char * const g_pcTagLeadIn = "<!--#";
+
+#define LEN_TAG_LEAD_OUT 3
+const char * const g_pcTagLeadOut = "-->";
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_CGI
+/* CGI handler information */
+const tCGI *g_pCGIs;
+int g_iNumCGIs;
+int http_cgi_paramcount;
+#define http_cgi_params     hs->params
+#define http_cgi_param_vals hs->param_vals
+#elif LWIP_HTTPD_CGI_SSI
+char *http_cgi_params[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Params extracted from the request URI */
+char *http_cgi_param_vals[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Values for each extracted param */
+#endif /* LWIP_HTTPD_CGI */
+
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+/** global list of active HTTP connections, use to kill the oldest when
+    running out of memory */
+static struct http_state *http_connections;
+
+static void
+http_add_connection(struct http_state *hs)
+{
+  /* add the connection to the list */
+  hs->next = http_connections;
+  http_connections = hs;
+}
+
+static void
+http_remove_connection(struct http_state *hs)
+{
+  /* take the connection off the list */
+  if (http_connections) {
+    if (http_connections == hs) {
+      http_connections = hs->next;
+    } else {
+      struct http_state *last;
+      for(last = http_connections; last->next != NULL; last = last->next) {
+        if (last->next == hs) {
+          last->next = hs->next;
+          break;
+        }
+      }
+    }
+  }
+}
+
+static void
+http_kill_oldest_connection(u8_t ssi_required)
+{
+  struct http_state *hs = http_connections;
+  struct http_state *hs_free_next = NULL;
+  while(hs && hs->next) {
+#if LWIP_HTTPD_SSI
+    if (ssi_required) {
+      if (hs->next->ssi != NULL) {
+        hs_free_next = hs;
+      }
+    } else
+#else /* LWIP_HTTPD_SSI */
+    LWIP_UNUSED_ARG(ssi_required);
+#endif /* LWIP_HTTPD_SSI */
+    {
+      hs_free_next = hs;
+    }
+    LWIP_ASSERT("broken list", hs != hs->next);
+    hs = hs->next;
+  }
+  if (hs_free_next != NULL) {
+    LWIP_ASSERT("hs_free_next->next != NULL", hs_free_next->next != NULL);
+    LWIP_ASSERT("hs_free_next->next->pcb != NULL", hs_free_next->next->pcb != NULL);
+    /* send RST when killing a connection because of memory shortage */
+    http_close_or_abort_conn(hs_free_next->next->pcb, hs_free_next->next, 1); /* this also unlinks the http_state from the list */
+  }
+}
+#else /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+
+#define http_add_connection(hs)
+#define http_remove_connection(hs)
+
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+
+#if LWIP_HTTPD_SSI
+/** Allocate as struct http_ssi_state. */
+static struct http_ssi_state*
+http_ssi_state_alloc(void)
+{
+  struct http_ssi_state *ret = HTTP_ALLOC_SSI_STATE();
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+  if (ret == NULL) {
+    http_kill_oldest_connection(1);
+    ret = HTTP_ALLOC_SSI_STATE();
+  }
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+  if (ret != NULL) {
+    memset(ret, 0, sizeof(struct http_ssi_state));
+  }
+  return ret;
+}
+
+/** Free a struct http_ssi_state. */
+static void
+http_ssi_state_free(struct http_ssi_state *ssi)
+{
+  if (ssi != NULL) {
+    HTTP_FREE_SSI_STATE(ssi);
+  }
+}
+#endif /* LWIP_HTTPD_SSI */
+
+/** Initialize a struct http_state.
+ */
+static void
+http_state_init(struct http_state* hs)
+{
+  /* Initialize the structure. */
+  memset(hs, 0, sizeof(struct http_state));
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+  /* Indicate that the headers are not yet valid */
+  hs->hdr_index = NUM_FILE_HDR_STRINGS;
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+}
+
+/** Allocate a struct http_state. */
+static struct http_state*
+http_state_alloc(void)
+{
+  struct http_state *ret = HTTP_ALLOC_HTTP_STATE();
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+  if (ret == NULL) {
+    http_kill_oldest_connection(0);
+    ret = HTTP_ALLOC_HTTP_STATE();
+  }
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+  if (ret != NULL) {
+    http_state_init(ret);
+    http_add_connection(ret);
+  }
+  return ret;
+}
+
+/** Free a struct http_state.
+ * Also frees the file data if dynamic.
+ */
+static void
+http_state_eof(struct http_state *hs)
+{
+  if(hs->handle) {
+#if LWIP_HTTPD_TIMING
+    u32_t ms_needed = sys_now() - hs->time_started;
+#if defined(LWIP_DEBUG) && (HTTPD_DEBUG_TIMING == LWIP_DBG_ON)
+    u32_t needed = LWIP_MAX(1, (ms_needed/100));
+    LWIP_DEBUGF(HTTPD_DEBUG_TIMING, ("httpd: needed %"U32_F" ms to send file of %d bytes -> %"U32_F" bytes/sec\n",
+      ms_needed, hs->handle->len, ((((u32_t)hs->handle->len) * 10) / needed)));
+#endif
+#endif /* LWIP_HTTPD_TIMING */
+    fs_close(hs->handle);
+    hs->handle = NULL;
+  }
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+  if (hs->buf != NULL) {
+    mem_free(hs->buf);
+    hs->buf = NULL;
+  }
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+#if LWIP_HTTPD_SSI
+  if (hs->ssi) {
+    http_ssi_state_free(hs->ssi);
+    hs->ssi = NULL;
+  }
+#endif /* LWIP_HTTPD_SSI */
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+  if (hs->req) {
+    pbuf_free(hs->req);
+    hs->req = NULL;
+  }
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+}
+
+/** Free a struct http_state.
+ * Also frees the file data if dynamic.
+ */
+static void
+http_state_free(struct http_state *hs)
+{
+  if (hs != NULL) {
+    http_state_eof(hs);
+    http_remove_connection(hs);
+    HTTP_FREE_HTTP_STATE(hs);
+  }
+}
+
+/** Call tcp_write() in a loop trying smaller and smaller length
+ *
+ * @param pcb tcp_pcb to send
+ * @param ptr Data to send
+ * @param length Length of data to send (in/out: on return, contains the
+ *        amount of data sent)
+ * @param apiflags directly passed to tcp_write
+ * @return the return value of tcp_write
+ */
+static err_t
+http_write(struct tcp_pcb *pcb, const void* ptr, u16_t *length, u8_t apiflags)
+{
+  u16_t len, max_len;
+  err_t err;
+  LWIP_ASSERT("length != NULL", length != NULL);
+  len = *length;
+  if (len == 0) {
+    return ERR_OK;
+  }
+  /* We cannot send more data than space available in the send buffer. */
+  max_len = tcp_sndbuf(pcb);
+  if (max_len < len) {
+    len = max_len;
+  }
+#ifdef HTTPD_MAX_WRITE_LEN
+  /* Additional limitation: e.g. don't enqueue more than 2*mss at once */
+  max_len = HTTPD_MAX_WRITE_LEN(pcb);
+  if(len > max_len) {
+    len = max_len;
+  }
+#endif /* HTTPD_MAX_WRITE_LEN */
+  do {
+    LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Trying go send %d bytes\n", len));
+    err = tcp_write(pcb, ptr, len, apiflags);
+    if (err == ERR_MEM) {
+      if ((tcp_sndbuf(pcb) == 0) ||
+        (tcp_sndqueuelen(pcb) >= TCP_SND_QUEUELEN)) {
+          /* no need to try smaller sizes */
+          len = 1;
+      } else {
+        len /= 2;
+      }
+      LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, 
+        ("Send failed, trying less (%d bytes)\n", len));
+    }
+  } while ((err == ERR_MEM) && (len > 1));
+
+  if (err == ERR_OK) {
+    LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Sent %d bytes\n", len));
+    *length = len;
+  } else {
+    LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Send failed with err %d (\"%s\")\n", err, lwip_strerr(err)));
+    *length = 0;
+  }
+
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+   /* ensure nagle is normally enabled (only disabled for persistent connections
+      when all data has been enqueued but the connection stays open for the next
+      request */
+   tcp_nagle_enable(pcb);
+#endif
+
+  return err;
+}
+
+/**
+ * The connection shall be actively closed (using RST to close from fault states).
+ * Reset the sent- and recv-callbacks.
+ *
+ * @param pcb the tcp pcb to reset callbacks
+ * @param hs connection state to free
+ */
+static err_t
+http_close_or_abort_conn(struct tcp_pcb *pcb, struct http_state *hs, u8_t abort_conn)
+{
+  err_t err;
+  LWIP_DEBUGF(HTTPD_DEBUG, ("Closing connection %p\n", (void*)pcb));
+
+#if LWIP_HTTPD_SUPPORT_POST
+  if (hs != NULL) {
+    if ((hs->post_content_len_left != 0)
+#if LWIP_HTTPD_POST_MANUAL_WND
+       || ((hs->no_auto_wnd != 0) && (hs->unrecved_bytes != 0))
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+       ) {
+      /* make sure the post code knows that the connection is closed */
+      http_uri_buf[0] = 0;
+      httpd_post_finished(hs, http_uri_buf, LWIP_HTTPD_URI_BUF_LEN);
+    }
+  }
+#endif /* LWIP_HTTPD_SUPPORT_POST*/
+
+
+  tcp_arg(pcb, NULL);
+  tcp_recv(pcb, NULL);
+  tcp_err(pcb, NULL);
+  tcp_poll(pcb, NULL, 0);
+  tcp_sent(pcb, NULL);
+  if (hs != NULL) {
+    http_state_free(hs);
+  }
+
+  if (abort_conn) {
+    tcp_abort(pcb);
+    return ERR_OK;
+  }
+  err = tcp_close(pcb);
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("Error %d closing %p\n", err, (void*)pcb));
+    /* error closing, try again later in poll */
+    tcp_poll(pcb, http_poll, HTTPD_POLL_INTERVAL);
+  }
+  return err;
+}
+
+/**
+ * The connection shall be actively closed.
+ * Reset the sent- and recv-callbacks.
+ *
+ * @param pcb the tcp pcb to reset callbacks
+ * @param hs connection state to free
+ */
+static err_t
+http_close_conn(struct tcp_pcb *pcb, struct http_state *hs)
+{
+   return http_close_or_abort_conn(pcb, hs, 0);
+}
+
+/** End of file: either close the connection (Connection: close) or
+ * close the file (Connection: keep-alive)
+ */
+static void
+http_eof(struct tcp_pcb *pcb, struct http_state *hs)
+{
+  /* HTTP/1.1 persistent connection? (Not supported for SSI) */
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+  if (hs->keepalive) {
+    http_remove_connection(hs);
+
+    http_state_eof(hs);
+    http_state_init(hs);
+    /* restore state: */
+    hs->pcb = pcb;
+    hs->keepalive = 1;
+    http_add_connection(hs);
+    /* ensure nagle doesn't interfere with sending all data as fast as possible: */
+    tcp_nagle_disable(pcb);
+  } else
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+  {
+    http_close_conn(pcb, hs);
+  }
+}
+
+#if LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI
+/**
+ * Extract URI parameters from the parameter-part of an URI in the form
+ * "test.cgi?x=y" @todo: better explanation!
+ * Pointers to the parameters are stored in hs->param_vals.
+ *
+ * @param hs http connection state
+ * @param params pointer to the NULL-terminated parameter string from the URI
+ * @return number of parameters extracted
+ */
+static int
+extract_uri_parameters(struct http_state *hs, char *params)
+{
+  char *pair;
+  char *equals;
+  int loop;
+
+  LWIP_UNUSED_ARG(hs);
+
+  /* If we have no parameters at all, return immediately. */
+  if(!params || (params[0] == '\0')) {
+      return(0);
+  }
+
+  /* Get a pointer to our first parameter */
+  pair = params;
+
+  /* Parse up to LWIP_HTTPD_MAX_CGI_PARAMETERS from the passed string and ignore the
+   * remainder (if any) */
+  for(loop = 0; (loop < LWIP_HTTPD_MAX_CGI_PARAMETERS) && pair; loop++) {
+
+    /* Save the name of the parameter */
+    http_cgi_params[loop] = pair;
+
+    /* Remember the start of this name=value pair */
+    equals = pair;
+
+    /* Find the start of the next name=value pair and replace the delimiter
+     * with a 0 to terminate the previous pair string. */
+    pair = strchr(pair, '&');
+    if(pair) {
+      *pair = '\0';
+      pair++;
+    } else {
+       /* We didn't find a new parameter so find the end of the URI and
+        * replace the space with a '\0' */
+        pair = strchr(equals, ' ');
+        if(pair) {
+            *pair = '\0';
+        }
+
+        /* Revert to NULL so that we exit the loop as expected. */
+        pair = NULL;
+    }
+
+    /* Now find the '=' in the previous pair, replace it with '\0' and save
+     * the parameter value string. */
+    equals = strchr(equals, '=');
+    if(equals) {
+      *equals = '\0';
+      http_cgi_param_vals[loop] = equals + 1;
+    } else {
+      http_cgi_param_vals[loop] = NULL;
+    }
+  }
+
+  return loop;
+}
+#endif /* LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI */
+
+#if LWIP_HTTPD_SSI
+/**
+ * Insert a tag (found in an shtml in the form of "<!--#tagname-->" into the file.
+ * The tag's name is stored in ssi->tag_name (NULL-terminated), the replacement
+ * should be written to hs->tag_insert (up to a length of LWIP_HTTPD_MAX_TAG_INSERT_LEN).
+ * The amount of data written is stored to ssi->tag_insert_len.
+ *
+ * @todo: return tag_insert_len - maybe it can be removed from struct http_state?
+ *
+ * @param hs http connection state
+ */
+static void
+get_tag_insert(struct http_state *hs)
+{
+#if LWIP_HTTPD_SSI_RAW
+  const char* tag;
+#else /* LWIP_HTTPD_SSI_RAW */
+  int tag;
+#endif /* LWIP_HTTPD_SSI_RAW */
+  size_t len;
+  struct http_ssi_state *ssi;
+#if LWIP_HTTPD_SSI_MULTIPART
+  u16_t current_tag_part;
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+
+  LWIP_ASSERT("hs != NULL", hs != NULL);
+  ssi = hs->ssi;
+  LWIP_ASSERT("ssi != NULL", ssi != NULL);
+#if LWIP_HTTPD_SSI_MULTIPART
+  current_tag_part = ssi->tag_part;
+  ssi->tag_part = HTTPD_LAST_TAG_PART;
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+#if LWIP_HTTPD_SSI_RAW
+  tag = ssi->tag_name;
+#endif
+
+  if(g_pfnSSIHandler
+#if !LWIP_HTTPD_SSI_RAW
+     && g_ppcTags && g_iNumTags
+#endif /* !LWIP_HTTPD_SSI_RAW */
+     ) {
+
+    /* Find this tag in the list we have been provided. */
+#if LWIP_HTTPD_SSI_RAW
+    {
+#else /* LWIP_HTTPD_SSI_RAW */
+    for(tag = 0; tag < g_iNumTags; tag++) {
+      if(strcmp(ssi->tag_name, g_ppcTags[tag]) == 0)
+#endif /* LWIP_HTTPD_SSI_RAW */
+      {
+        ssi->tag_insert_len = g_pfnSSIHandler(tag, ssi->tag_insert,
+           LWIP_HTTPD_MAX_TAG_INSERT_LEN
+#if LWIP_HTTPD_SSI_MULTIPART
+           , current_tag_part, &ssi->tag_part
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+#if LWIP_HTTPD_FILE_STATE
+           , (hs->handle ? hs->handle->state : NULL)
+#endif /* LWIP_HTTPD_FILE_STATE */
+           );
+#if LWIP_HTTPD_SSI_RAW
+        if (ssi->tag_insert_len != HTTPD_SSI_TAG_UNKNOWN)
+#endif /* LWIP_HTTPD_SSI_RAW */
+        {
+          return;
+        }
+      }
+    }
+  }
+
+  /* If we drop out, we were asked to serve a page which contains tags that
+   * we don't have a handler for. Merely echo back the tags with an error
+   * marker. */
+#define UNKNOWN_TAG1_TEXT "<b>***UNKNOWN TAG "
+#define UNKNOWN_TAG1_LEN  18
+#define UNKNOWN_TAG2_TEXT "***</b>"
+#define UNKNOWN_TAG2_LEN  7
+  len = LWIP_MIN(sizeof(ssi->tag_name), LWIP_MIN(strlen(ssi->tag_name),
+    LWIP_HTTPD_MAX_TAG_INSERT_LEN - (UNKNOWN_TAG1_LEN + UNKNOWN_TAG2_LEN)));
+  MEMCPY(ssi->tag_insert, UNKNOWN_TAG1_TEXT, UNKNOWN_TAG1_LEN);
+  MEMCPY(&ssi->tag_insert[UNKNOWN_TAG1_LEN], ssi->tag_name, len);
+  MEMCPY(&ssi->tag_insert[UNKNOWN_TAG1_LEN + len], UNKNOWN_TAG2_TEXT, UNKNOWN_TAG2_LEN);
+  ssi->tag_insert[UNKNOWN_TAG1_LEN + len + UNKNOWN_TAG2_LEN] = 0;
+
+  len = strlen(ssi->tag_insert);
+  LWIP_ASSERT("len <= 0xffff", len <= 0xffff);
+  ssi->tag_insert_len = (u16_t)len;
+}
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+/**
+ * Generate the relevant HTTP headers for the given filename and write
+ * them into the supplied buffer.
+ */
+static void
+get_http_headers(struct http_state *hs, const char *uri)
+{
+  size_t content_type;
+  char *tmp;
+  char *ext;
+  char *vars;
+  u8_t add_content_len;
+
+  /* In all cases, the second header we send is the server identification
+     so set it here. */
+  hs->hdrs[HDR_STRINGS_IDX_SERVER_NAME] = g_psHTTPHeaderStrings[HTTP_HDR_SERVER];
+  hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE] = NULL;
+  hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_NR] = NULL;
+
+  /* Is this a normal file or the special case we use to send back the
+     default "404: Page not found" response? */
+  if (uri == NULL) {
+    hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND];
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+    if (hs->keepalive) {
+      hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaderStrings[DEFAULT_404_HTML_PERSISTENT];
+    } else
+#endif
+    {
+      hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaderStrings[DEFAULT_404_HTML];
+    }
+
+    /* Set up to send the first header string. */
+    hs->hdr_index = 0;
+    hs->hdr_pos = 0;
+    return;
+  }
+  /* We are dealing with a particular filename. Look for one other
+      special case.  We assume that any filename with "404" in it must be
+      indicative of a 404 server error whereas all other files require
+      the 200 OK header. */
+  if (strstr(uri, "404")) {
+    hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND];
+  } else if (strstr(uri, "400")) {
+    hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_BAD_REQUEST];
+  } else if (strstr(uri, "501")) {
+    hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_IMPL];
+  } else {
+    hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_OK];
+  }
+
+  /* Determine if the URI has any variables and, if so, temporarily remove 
+      them. */
+  vars = strchr(uri, '?');
+  if(vars) {
+    *vars = '\0';
+  }
+
+  /* Get a pointer to the file extension.  We find this by looking for the
+      last occurrence of "." in the filename passed. */
+  ext = NULL;
+  tmp = strchr(uri, '.');
+  while (tmp) {
+    ext = tmp + 1;
+    tmp = strchr(ext, '.');
+  }
+  if (ext != NULL) {
+    /* Now determine the content type and add the relevant header for that. */
+    for (content_type = 0; content_type < NUM_HTTP_HEADERS; content_type++) {
+      /* Have we found a matching extension? */
+      if(!lwip_stricmp(g_psHTTPHeaders[content_type].extension, ext)) {
+        break;
+      }
+    }
+  } else {
+    content_type = NUM_HTTP_HEADERS;
+  }
+
+  /* Reinstate the parameter marker if there was one in the original URI. */
+  if (vars) {
+    *vars = '?';
+  }
+
+#if LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI
+  /* Does the URL passed have any file extension?  If not, we assume it
+     is a special-case URL used for control state notification and we do
+     not send any HTTP headers with the response. */
+  if (!ext) {
+    /* Force the header index to a value indicating that all headers
+       have already been sent. */
+    hs->hdr_index = NUM_FILE_HDR_STRINGS;
+    return;
+  }
+#endif /* LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI */
+  add_content_len = 1;
+  /* Did we find a matching extension? */
+  if(content_type < NUM_HTTP_HEADERS) {
+    /* yes, store it */
+    hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaders[content_type].content_type;
+  } else if (!ext) {
+    /* no, no extension found -> use binary transfer to prevent the browser adding '.txt' on save */
+    hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = HTTP_HDR_APP;
+  } else {
+    /* No - use the default, plain text file type. */
+    hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = HTTP_HDR_DEFAULT_TYPE;
+  }
+  /* Add content-length header? */
+#if LWIP_HTTPD_SSI
+  if (hs->ssi != NULL) {
+    add_content_len = 0; /* @todo: get maximum file length from SSI */
+  } else
+#endif /* LWIP_HTTPD_SSI */
+  if ((hs->handle == NULL) ||
+      ((hs->handle->flags & (FS_FILE_FLAGS_HEADER_INCLUDED|FS_FILE_FLAGS_HEADER_PERSISTENT)) == FS_FILE_FLAGS_HEADER_INCLUDED)) {
+    add_content_len = 0;
+  }
+  if (add_content_len) {
+    size_t len;
+    lwip_itoa(hs->hdr_content_len, (size_t)LWIP_HTTPD_MAX_CONTENT_LEN_SIZE,
+      hs->handle->len);
+    len = strlen(hs->hdr_content_len);
+    if (len <= LWIP_HTTPD_MAX_CONTENT_LEN_SIZE - LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET) {
+      SMEMCPY(&hs->hdr_content_len[len], CRLF "\0", 3);
+      hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_NR] = hs->hdr_content_len;
+    } else {
+      add_content_len = 0;
+    }
+  }
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+  if (add_content_len) {
+    hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_KEEPALIVE_LEN];
+  } else {
+    hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_CONN_CLOSE];
+  }
+#else /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+  if (add_content_len) {
+    hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH];
+  }
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+
+  /* Set up to send the first header string. */
+  hs->hdr_index = 0;
+  hs->hdr_pos = 0;
+}
+
+/** Sub-function of http_send(): send dynamic headers
+ *
+ * @returns: - HTTP_NO_DATA_TO_SEND: no new data has been enqueued
+ *           - HTTP_DATA_TO_SEND_CONTINUE: continue with sending HTTP body
+ *           - HTTP_DATA_TO_SEND_BREAK: data has been enqueued, headers pending,
+ *                                      so don't send HTTP body yet
+ */
+static u8_t
+http_send_headers(struct tcp_pcb *pcb, struct http_state *hs)
+{
+  err_t err;
+  u16_t len;
+  u8_t data_to_send = HTTP_NO_DATA_TO_SEND;
+  u16_t hdrlen, sendlen;
+
+  /* How much data can we send? */
+  len = tcp_sndbuf(pcb);
+  sendlen = len;
+
+  while(len && (hs->hdr_index < NUM_FILE_HDR_STRINGS) && sendlen) {
+    const void *ptr;
+    u16_t old_sendlen;
+    u8_t apiflags;
+    /* How much do we have to send from the current header? */
+    hdrlen = (u16_t)strlen(hs->hdrs[hs->hdr_index]);
+
+    /* How much of this can we send? */
+    sendlen = (len < (hdrlen - hs->hdr_pos)) ? len : (hdrlen - hs->hdr_pos);
+
+    /* Send this amount of data or as much as we can given memory
+     * constraints. */
+    ptr = (const void *)(hs->hdrs[hs->hdr_index] + hs->hdr_pos);
+    old_sendlen = sendlen;
+    apiflags = HTTP_IS_HDR_VOLATILE(hs, ptr);
+    if (hs->hdr_index == HDR_STRINGS_IDX_CONTENT_LEN_NR) {
+      /* content-length is always volatile */
+      apiflags |= TCP_WRITE_FLAG_COPY;
+    }
+    if (hs->hdr_index < NUM_FILE_HDR_STRINGS - 1) {
+      apiflags |= TCP_WRITE_FLAG_MORE;
+    }
+    err = http_write(pcb, ptr, &sendlen, apiflags);
+    if ((err == ERR_OK) && (old_sendlen != sendlen)) {
+      /* Remember that we added some more data to be transmitted. */
+      data_to_send = HTTP_DATA_TO_SEND_CONTINUE;
+    } else if (err != ERR_OK) {
+       /* special case: http_write does not try to send 1 byte */
+      sendlen = 0;
+    }
+
+    /* Fix up the header position for the next time round. */
+    hs->hdr_pos += sendlen;
+    len -= sendlen;
+
+    /* Have we finished sending this string? */
+    if(hs->hdr_pos == hdrlen) {
+      /* Yes - move on to the next one */
+      hs->hdr_index++;
+      /* skip headers that are NULL (not all headers are required) */
+      while ((hs->hdr_index < NUM_FILE_HDR_STRINGS) &&
+         (hs->hdrs[hs->hdr_index] == NULL)) {
+        hs->hdr_index++;
+      }
+      hs->hdr_pos = 0;
+    }
+  }
+
+  if ((hs->hdr_index >= NUM_FILE_HDR_STRINGS) && (hs->file == NULL)) {
+    /* When we are at the end of the headers, check for data to send
+     * instead of waiting for ACK from remote side to continue
+     * (which would happen when sending files from async read). */
+    if(http_check_eof(pcb, hs)) {
+      data_to_send = HTTP_DATA_TO_SEND_CONTINUE;
+    }
+  }
+  /* If we get here and there are still header bytes to send, we send
+   * the header information we just wrote immediately. If there are no
+   * more headers to send, but we do have file data to send, drop through
+   * to try to send some file data too. */
+  if((hs->hdr_index < NUM_FILE_HDR_STRINGS) || !hs->file) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("tcp_output\n"));
+    return HTTP_DATA_TO_SEND_BREAK;
+  }
+  return data_to_send;
+}
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+/** Sub-function of http_send(): end-of-file (or block) is reached,
+ * either close the file or read the next block (if supported).
+ *
+ * @returns: 0 if the file is finished or no data has been read
+ *           1 if the file is not finished and data has been read
+ */
+static u8_t
+http_check_eof(struct tcp_pcb *pcb, struct http_state *hs)
+{
+  int bytes_left;
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+  int count;
+#ifdef HTTPD_MAX_WRITE_LEN
+  int max_write_len;
+#endif /* HTTPD_MAX_WRITE_LEN */
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+
+  /* Do we have a valid file handle? */
+  if (hs->handle == NULL) {
+    /* No - close the connection. */
+    http_eof(pcb, hs);
+    return 0;
+  }
+  bytes_left = fs_bytes_left(hs->handle);
+  if (bytes_left <= 0) {
+    /* We reached the end of the file so this request is done. */
+    LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
+    http_eof(pcb, hs);
+    return 0;
+  }
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+  /* Do we already have a send buffer allocated? */
+  if(hs->buf) {
+    /* Yes - get the length of the buffer */
+    count = LWIP_MIN(hs->buf_len, bytes_left);
+  } else {
+    /* We don't have a send buffer so allocate one now */
+    count = tcp_sndbuf(pcb);
+    if(bytes_left < count) {
+      count = bytes_left;
+    }
+#ifdef HTTPD_MAX_WRITE_LEN
+    /* Additional limitation: e.g. don't enqueue more than 2*mss at once */
+    max_write_len = HTTPD_MAX_WRITE_LEN(pcb);
+    if (count > max_write_len) {
+      count = max_write_len;
+    }
+#endif /* HTTPD_MAX_WRITE_LEN */
+    do {
+      hs->buf = (char*)mem_malloc((mem_size_t)count);
+      if (hs->buf != NULL) {
+        hs->buf_len = count;
+        break;
+      }
+      count = count / 2;
+    } while (count > 100);
+
+    /* Did we get a send buffer? If not, return immediately. */
+    if (hs->buf == NULL) {
+      LWIP_DEBUGF(HTTPD_DEBUG, ("No buff\n"));
+      return 0;
+    }
+  }
+
+  /* Read a block of data from the file. */
+  LWIP_DEBUGF(HTTPD_DEBUG, ("Trying to read %d bytes.\n", count));
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+  count = fs_read_async(hs->handle, hs->buf, count, http_continue, hs);
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+  count = fs_read(hs->handle, hs->buf, count);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+  if (count < 0) {
+    if (count == FS_READ_DELAYED) {
+      /* Delayed read, wait for FS to unblock us */
+      return 0;
+    }
+    /* We reached the end of the file so this request is done.
+     * @todo: close here for HTTP/1.1 when reading file fails */
+    LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
+    http_eof(pcb, hs);
+    return 0;
+  }
+
+  /* Set up to send the block of data we just read */
+  LWIP_DEBUGF(HTTPD_DEBUG, ("Read %d bytes.\n", count));
+  hs->left = count;
+  hs->file = hs->buf;
+#if LWIP_HTTPD_SSI
+  if (hs->ssi) {
+    hs->ssi->parse_left = count;
+    hs->ssi->parsed = hs->buf;
+  }
+#endif /* LWIP_HTTPD_SSI */
+#else /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+  LWIP_ASSERT("SSI and DYNAMIC_HEADERS turned off but eof not reached", 0);
+#endif /* LWIP_HTTPD_SSI || LWIP_HTTPD_DYNAMIC_HEADERS */
+  return 1;
+}
+
+/** Sub-function of http_send(): This is the normal send-routine for non-ssi files
+ *
+ * @returns: - 1: data has been written (so call tcp_ouput)
+ *           - 0: no data has been written (no need to call tcp_output)
+ */
+static u8_t
+http_send_data_nonssi(struct tcp_pcb *pcb, struct http_state *hs)
+{
+  err_t err;
+  u16_t len;
+  u8_t data_to_send = 0;
+
+  /* We are not processing an SHTML file so no tag checking is necessary.
+   * Just send the data as we received it from the file. */
+  len = (u16_t)LWIP_MIN(hs->left, 0xffff);
+
+  err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+  if (err == ERR_OK) {
+    data_to_send = 1;
+    hs->file += len;
+    hs->left -= len;
+  }
+
+  return data_to_send;
+}
+
+#if LWIP_HTTPD_SSI
+/** Sub-function of http_send(): This is the send-routine for ssi files
+ *
+ * @returns: - 1: data has been written (so call tcp_ouput)
+ *           - 0: no data has been written (no need to call tcp_output)
+ */
+static u8_t
+http_send_data_ssi(struct tcp_pcb *pcb, struct http_state *hs)
+{
+  err_t err = ERR_OK;
+  u16_t len;
+  u8_t data_to_send = 0;
+
+  struct http_ssi_state *ssi = hs->ssi;
+  LWIP_ASSERT("ssi != NULL", ssi != NULL);
+  /* We are processing an SHTML file so need to scan for tags and replace
+   * them with insert strings. We need to be careful here since a tag may
+   * straddle the boundary of two blocks read from the file and we may also
+   * have to split the insert string between two tcp_write operations. */
+
+  /* How much data could we send? */
+  len = tcp_sndbuf(pcb);
+
+  /* Do we have remaining data to send before parsing more? */
+  if(ssi->parsed > hs->file) {
+    len = (u16_t)LWIP_MIN(ssi->parsed - hs->file, 0xffff);
+
+    err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+    if (err == ERR_OK) {
+      data_to_send = 1;
+      hs->file += len;
+      hs->left -= len;
+    }
+
+    /* If the send buffer is full, return now. */
+    if(tcp_sndbuf(pcb) == 0) {
+      return data_to_send;
+    }
+  }
+
+  LWIP_DEBUGF(HTTPD_DEBUG, ("State %d, %d left\n", ssi->tag_state, (int)ssi->parse_left));
+
+  /* We have sent all the data that was already parsed so continue parsing
+   * the buffer contents looking for SSI tags. */
+  while((ssi->parse_left) && (err == ERR_OK)) {
+    if (len == 0) {
+      return data_to_send;
+    }
+    switch(ssi->tag_state) {
+      case TAG_NONE:
+        /* We are not currently processing an SSI tag so scan for the
+         * start of the lead-in marker. */
+        if(*ssi->parsed == g_pcTagLeadIn[0]) {
+          /* We found what could be the lead-in for a new tag so change
+           * state appropriately. */
+          ssi->tag_state = TAG_LEADIN;
+          ssi->tag_index = 1;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+          ssi->tag_started = ssi->parsed;
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG */
+        }
+
+        /* Move on to the next character in the buffer */
+        ssi->parse_left--;
+        ssi->parsed++;
+        break;
+
+      case TAG_LEADIN:
+        /* We are processing the lead-in marker, looking for the start of
+         * the tag name. */
+
+        /* Have we reached the end of the leadin? */
+        if(ssi->tag_index == LEN_TAG_LEAD_IN) {
+          ssi->tag_index = 0;
+          ssi->tag_state = TAG_FOUND;
+        } else {
+          /* Have we found the next character we expect for the tag leadin? */
+          if(*ssi->parsed == g_pcTagLeadIn[ssi->tag_index]) {
+            /* Yes - move to the next one unless we have found the complete
+             * leadin, in which case we start looking for the tag itself */
+            ssi->tag_index++;
+          } else {
+            /* We found an unexpected character so this is not a tag. Move
+             * back to idle state. */
+            ssi->tag_state = TAG_NONE;
+          }
+
+          /* Move on to the next character in the buffer */
+          ssi->parse_left--;
+          ssi->parsed++;
+        }
+        break;
+
+      case TAG_FOUND:
+        /* We are reading the tag name, looking for the start of the
+         * lead-out marker and removing any whitespace found. */
+
+        /* Remove leading whitespace between the tag leading and the first
+         * tag name character. */
+        if((ssi->tag_index == 0) && ((*ssi->parsed == ' ') ||
+           (*ssi->parsed == '\t') || (*ssi->parsed == '\n') ||
+           (*ssi->parsed == '\r'))) {
+          /* Move on to the next character in the buffer */
+          ssi->parse_left--;
+          ssi->parsed++;
+          break;
+        }
+
+        /* Have we found the end of the tag name? This is signalled by
+         * us finding the first leadout character or whitespace */
+        if((*ssi->parsed == g_pcTagLeadOut[0]) ||
+           (*ssi->parsed == ' ')  || (*ssi->parsed == '\t') ||
+           (*ssi->parsed == '\n') || (*ssi->parsed == '\r')) {
+
+          if(ssi->tag_index == 0) {
+            /* We read a zero length tag so ignore it. */
+            ssi->tag_state = TAG_NONE;
+          } else {
+            /* We read a non-empty tag so go ahead and look for the
+             * leadout string. */
+            ssi->tag_state = TAG_LEADOUT;
+            LWIP_ASSERT("ssi->tag_index <= 0xff", ssi->tag_index <= 0xff);
+            ssi->tag_name_len = (u8_t)ssi->tag_index;
+            ssi->tag_name[ssi->tag_index] = '\0';
+            if(*ssi->parsed == g_pcTagLeadOut[0]) {
+              ssi->tag_index = 1;
+            } else {
+              ssi->tag_index = 0;
+            }
+          }
+        } else {
+          /* This character is part of the tag name so save it */
+          if(ssi->tag_index < LWIP_HTTPD_MAX_TAG_NAME_LEN) {
+            ssi->tag_name[ssi->tag_index++] = *ssi->parsed;
+          } else {
+            /* The tag was too long so ignore it. */
+            ssi->tag_state = TAG_NONE;
+          }
+        }
+
+        /* Move on to the next character in the buffer */
+        ssi->parse_left--;
+        ssi->parsed++;
+
+        break;
+
+      /* We are looking for the end of the lead-out marker. */
+      case TAG_LEADOUT:
+        /* Remove leading whitespace between the tag leading and the first
+         * tag leadout character. */
+        if((ssi->tag_index == 0) && ((*ssi->parsed == ' ') ||
+           (*ssi->parsed == '\t') || (*ssi->parsed == '\n') ||
+           (*ssi->parsed == '\r'))) {
+          /* Move on to the next character in the buffer */
+          ssi->parse_left--;
+          ssi->parsed++;
+          break;
+        }
+
+        /* Have we found the next character we expect for the tag leadout? */
+        if(*ssi->parsed == g_pcTagLeadOut[ssi->tag_index]) {
+          /* Yes - move to the next one unless we have found the complete
+           * leadout, in which case we need to call the client to process
+           * the tag. */
+
+          /* Move on to the next character in the buffer */
+          ssi->parse_left--;
+          ssi->parsed++;
+
+          if(ssi->tag_index == (LEN_TAG_LEAD_OUT - 1)) {
+            /* Call the client to ask for the insert string for the
+             * tag we just found. */
+#if LWIP_HTTPD_SSI_MULTIPART
+            ssi->tag_part = 0; /* start with tag part 0 */
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+            get_tag_insert(hs);
+
+            /* Next time through, we are going to be sending data
+             * immediately, either the end of the block we start
+             * sending here or the insert string. */
+            ssi->tag_index = 0;
+            ssi->tag_state = TAG_SENDING;
+            ssi->tag_end = ssi->parsed;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+            ssi->parsed = ssi->tag_started;
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+
+            /* If there is any unsent data in the buffer prior to the
+             * tag, we need to send it now. */
+            if (ssi->tag_end > hs->file) {
+              /* How much of the data can we send? */
+#if LWIP_HTTPD_SSI_INCLUDE_TAG
+              len = (u16_t)LWIP_MIN(ssi->tag_end - hs->file, 0xffff);
+#else /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+              /* we would include the tag in sending */
+              len = (u16_t)LWIP_MIN(ssi->tag_started - hs->file, 0xffff);
+#endif /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+
+              err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+              if (err == ERR_OK) {
+                data_to_send = 1;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+                if(ssi->tag_started <= hs->file) {
+                  /* pretend to have sent the tag, too */
+                  len += ssi->tag_end - ssi->tag_started;
+                }
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+                hs->file += len;
+                hs->left -= len;
+              }
+            }
+          } else {
+            ssi->tag_index++;
+          }
+        } else {
+          /* We found an unexpected character so this is not a tag. Move
+           * back to idle state. */
+          ssi->parse_left--;
+          ssi->parsed++;
+          ssi->tag_state = TAG_NONE;
+        }
+        break;
+
+      /*
+       * We have found a valid tag and are in the process of sending
+       * data as a result of that discovery. We send either remaining data
+       * from the file prior to the insert point or the insert string itself.
+       */
+      case TAG_SENDING:
+        /* Do we have any remaining file data to send from the buffer prior
+         * to the tag? */
+        if(ssi->tag_end > hs->file) {
+          /* How much of the data can we send? */
+#if LWIP_HTTPD_SSI_INCLUDE_TAG
+          len = (u16_t)LWIP_MIN(ssi->tag_end - hs->file, 0xffff);
+#else /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+          LWIP_ASSERT("hs->started >= hs->file", ssi->tag_started >= hs->file);
+          /* we would include the tag in sending */
+          len = (u16_t)LWIP_MIN(ssi->tag_started - hs->file, 0xffff);
+#endif /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+          if (len != 0) {
+            err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+          } else {
+            err = ERR_OK;
+          }
+          if (err == ERR_OK) {
+            data_to_send = 1;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+            if(ssi->tag_started <= hs->file) {
+              /* pretend to have sent the tag, too */
+              len += ssi->tag_end - ssi->tag_started;
+            }
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+            hs->file += len;
+            hs->left -= len;
+          }
+        } else {
+#if LWIP_HTTPD_SSI_MULTIPART
+          if(ssi->tag_index >= ssi->tag_insert_len) {
+            /* Did the last SSIHandler have more to send? */
+            if (ssi->tag_part != HTTPD_LAST_TAG_PART) {
+              /* If so, call it again */
+              ssi->tag_index = 0;
+              get_tag_insert(hs);
+            }
+          }
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+
+          /* Do we still have insert data left to send? */
+          if(ssi->tag_index < ssi->tag_insert_len) {
+            /* We are sending the insert string itself. How much of the
+             * insert can we send? */
+            len = (ssi->tag_insert_len - ssi->tag_index);
+
+            /* Note that we set the copy flag here since we only have a
+             * single tag insert buffer per connection. If we don't do
+             * this, insert corruption can occur if more than one insert
+             * is processed before we call tcp_output. */
+            err = http_write(pcb, &(ssi->tag_insert[ssi->tag_index]), &len,
+                             HTTP_IS_TAG_VOLATILE(hs));
+            if (err == ERR_OK) {
+              data_to_send = 1;
+              ssi->tag_index += len;
+              /* Don't return here: keep on sending data */
+            }
+          } else {
+#if LWIP_HTTPD_SSI_MULTIPART
+            if (ssi->tag_part == HTTPD_LAST_TAG_PART)
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+            {
+              /* We have sent all the insert data so go back to looking for
+               * a new tag. */
+              LWIP_DEBUGF(HTTPD_DEBUG, ("Everything sent.\n"));
+              ssi->tag_index = 0;
+              ssi->tag_state = TAG_NONE;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+              ssi->parsed = ssi->tag_end;
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+            }
+          }
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  /* If we drop out of the end of the for loop, this implies we must have
+   * file data to send so send it now. In TAG_SENDING state, we've already
+   * handled this so skip the send if that's the case. */
+  if((ssi->tag_state != TAG_SENDING) && (ssi->parsed > hs->file)) {
+    len = (u16_t)LWIP_MIN(ssi->parsed - hs->file, 0xffff);
+
+    err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+    if (err == ERR_OK) {
+      data_to_send = 1;
+      hs->file += len;
+      hs->left -= len;
+    }
+  }
+  return data_to_send;
+}
+#endif /* LWIP_HTTPD_SSI */
+
+/**
+ * Try to send more data on this pcb.
+ *
+ * @param pcb the pcb to send data
+ * @param hs connection state
+ */
+static u8_t
+http_send(struct tcp_pcb *pcb, struct http_state *hs)
+{
+  u8_t data_to_send = HTTP_NO_DATA_TO_SEND;
+
+  LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_send: pcb=%p hs=%p left=%d\n", (void*)pcb,
+    (void*)hs, hs != NULL ? (int)hs->left : 0));
+
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+  if (hs->unrecved_bytes != 0) {
+    return 0;
+  }
+#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */
+
+  /* If we were passed a NULL state structure pointer, ignore the call. */
+  if (hs == NULL) {
+    return 0;
+  }
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+  /* Check if we are allowed to read from this file.
+     (e.g. SSI might want to delay sending until data is available) */
+  if (!fs_is_file_ready(hs->handle, http_continue, hs)) {
+    return 0;
+  }
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+  /* Do we have any more header data to send for this file? */
+  if (hs->hdr_index < NUM_FILE_HDR_STRINGS) {
+    data_to_send = http_send_headers(pcb, hs);
+    if ((data_to_send != HTTP_DATA_TO_SEND_CONTINUE) &&
+        (hs->hdr_index < NUM_FILE_HDR_STRINGS)) {
+      return data_to_send;
+    }
+  }
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+  /* Have we run out of file data to send? If so, we need to read the next
+   * block from the file. */
+  if (hs->left == 0) {
+    if (!http_check_eof(pcb, hs)) {
+      return 0;
+    }
+  }
+
+#if LWIP_HTTPD_SSI
+  if(hs->ssi) {
+    data_to_send = http_send_data_ssi(pcb, hs);
+  } else
+#endif /* LWIP_HTTPD_SSI */
+  {
+    data_to_send = http_send_data_nonssi(pcb, hs);
+  }
+
+  if((hs->left == 0) && (fs_bytes_left(hs->handle) <= 0)) {
+    /* We reached the end of the file so this request is done.
+     * This adds the FIN flag right into the last data segment. */
+    LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
+    http_eof(pcb, hs);
+    return 0;
+  }
+  LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("send_data end.\n"));
+  return data_to_send;
+}
+
+#if LWIP_HTTPD_SUPPORT_EXTSTATUS
+/** Initialize a http connection with a file to send for an error message
+ *
+ * @param hs http connection state
+ * @param error_nr HTTP error number
+ * @return ERR_OK if file was found and hs has been initialized correctly
+ *         another err_t otherwise
+ */
+static err_t
+http_find_error_file(struct http_state *hs, u16_t error_nr)
+{
+  const char *uri1, *uri2, *uri3;
+  err_t err;
+
+  if (error_nr == 501) {
+    uri1 = "/501.html";
+    uri2 = "/501.htm";
+    uri3 = "/501.shtml";
+  } else {
+    /* 400 (bad request is the default) */
+    uri1 = "/400.html";
+    uri2 = "/400.htm";
+    uri3 = "/400.shtml";
+  }
+  err = fs_open(&hs->file_handle, uri1);
+  if (err != ERR_OK) {
+    err = fs_open(&hs->file_handle, uri2);
+    if (err != ERR_OK) {
+      err = fs_open(&hs->file_handle, uri3);
+      if (err != ERR_OK) {
+        LWIP_DEBUGF(HTTPD_DEBUG, ("Error page for error %"U16_F" not found\n",
+          error_nr));
+        return ERR_ARG;
+      }
+    }
+  }
+  return http_init_file(hs, &hs->file_handle, 0, NULL, 0, NULL);
+}
+#else /* LWIP_HTTPD_SUPPORT_EXTSTATUS */
+#define http_find_error_file(hs, error_nr) ERR_ARG
+#endif /* LWIP_HTTPD_SUPPORT_EXTSTATUS */
+
+/**
+ * Get the file struct for a 404 error page.
+ * Tries some file names and returns NULL if none found.
+ *
+ * @param uri pointer that receives the actual file name URI
+ * @return file struct for the error page or NULL no matching file was found
+ */
+static struct fs_file *
+http_get_404_file(struct http_state *hs, const char **uri)
+{
+  err_t err;
+
+  *uri = "/404.html";
+  err = fs_open(&hs->file_handle, *uri);
+  if (err != ERR_OK) {
+    /* 404.html doesn't exist. Try 404.htm instead. */
+    *uri = "/404.htm";
+    err = fs_open(&hs->file_handle, *uri);
+    if (err != ERR_OK) {
+      /* 404.htm doesn't exist either. Try 404.shtml instead. */
+      *uri = "/404.shtml";
+      err = fs_open(&hs->file_handle, *uri);
+      if (err != ERR_OK) {
+        /* 404.htm doesn't exist either. Indicate to the caller that it should
+         * send back a default 404 page.
+         */
+        *uri = NULL;
+        return NULL;
+      }
+    }
+  }
+
+  return &hs->file_handle;
+}
+
+#if LWIP_HTTPD_SUPPORT_POST
+static err_t
+http_handle_post_finished(struct http_state *hs)
+{
+#if LWIP_HTTPD_POST_MANUAL_WND
+  /* Prevent multiple calls to httpd_post_finished, since it might have already
+     been called before from httpd_post_data_recved(). */
+  if (hs->post_finished) {
+    return ERR_OK;
+  }
+  hs->post_finished = 1;
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+  /* application error or POST finished */
+  /* NULL-terminate the buffer */
+  http_uri_buf[0] = 0;
+  httpd_post_finished(hs, http_uri_buf, LWIP_HTTPD_URI_BUF_LEN);
+  return http_find_file(hs, http_uri_buf, 0);
+}
+
+/** Pass received POST body data to the application and correctly handle
+ * returning a response document or closing the connection.
+ * ATTENTION: The application is responsible for the pbuf now, so don't free it!
+ *
+ * @param hs http connection state
+ * @param p pbuf to pass to the application
+ * @return ERR_OK if passed successfully, another err_t if the response file
+ *         hasn't been found (after POST finished)
+ */
+static err_t
+http_post_rxpbuf(struct http_state *hs, struct pbuf *p)
+{
+  err_t err;
+
+  if (p != NULL) {
+    /* adjust remaining Content-Length */
+    if (hs->post_content_len_left < p->tot_len) {
+      hs->post_content_len_left = 0;
+    } else {
+      hs->post_content_len_left -= p->tot_len;
+    }
+  }
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+  /* prevent connection being closed if httpd_post_data_recved() is called nested */
+  hs->unrecved_bytes++;
+#endif
+  err = httpd_post_receive_data(hs, p);
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+  hs->unrecved_bytes--;
+#endif
+  if (err != ERR_OK) {
+    /* Ignore remaining content in case of application error */
+    hs->post_content_len_left = 0;
+  }
+  if (hs->post_content_len_left == 0) {
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+    if (hs->unrecved_bytes != 0) {
+       return ERR_OK;
+    }
+#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */
+    /* application error or POST finished */
+    return http_handle_post_finished(hs);
+  }
+
+  return ERR_OK;
+}
+
+/** Handle a post request. Called from http_parse_request when method 'POST'
+ * is found.
+ *
+ * @param p The input pbuf (containing the POST header and body).
+ * @param hs The http connection state.
+ * @param data HTTP request (header and part of body) from input pbuf(s).
+ * @param data_len Size of 'data'.
+ * @param uri The HTTP URI parsed from input pbuf(s).
+ * @param uri_end Pointer to the end of 'uri' (here, the rest of the HTTP
+ *                header starts).
+ * @return ERR_OK: POST correctly parsed and accepted by the application.
+ *         ERR_INPROGRESS: POST not completely parsed (no error yet)
+ *         another err_t: Error parsing POST or denied by the application
+ */
+static err_t
+http_post_request(struct pbuf *inp, struct http_state *hs,
+                  char *data, u16_t data_len, char *uri, char *uri_end)
+{
+  err_t err;
+  /* search for end-of-header (first double-CRLF) */
+  char* crlfcrlf = lwip_strnstr(uri_end + 1, CRLF CRLF, data_len - (uri_end + 1 - data));
+
+  if (crlfcrlf != NULL) {
+    /* search for "Content-Length: " */
+#define HTTP_HDR_CONTENT_LEN                "Content-Length: "
+#define HTTP_HDR_CONTENT_LEN_LEN            16
+#define HTTP_HDR_CONTENT_LEN_DIGIT_MAX_LEN  10
+    char *scontent_len = lwip_strnstr(uri_end + 1, HTTP_HDR_CONTENT_LEN, crlfcrlf - (uri_end + 1));
+    if (scontent_len != NULL) {
+      char *scontent_len_end = lwip_strnstr(scontent_len + HTTP_HDR_CONTENT_LEN_LEN, CRLF, HTTP_HDR_CONTENT_LEN_DIGIT_MAX_LEN);
+      if (scontent_len_end != NULL) {
+        int content_len;
+        char *content_len_num = scontent_len + HTTP_HDR_CONTENT_LEN_LEN;
+        content_len = atoi(content_len_num);
+        if (content_len == 0) {
+          /* if atoi returns 0 on error, fix this */
+          if ((content_len_num[0] != '0') || (content_len_num[1] != '\r')) {
+            content_len = -1;
+          }
+        }
+        if (content_len >= 0) {
+          /* adjust length of HTTP header passed to application */
+          const char *hdr_start_after_uri = uri_end + 1;
+          u16_t hdr_len = (u16_t)LWIP_MIN(data_len, crlfcrlf + 4 - data);
+          u16_t hdr_data_len = (u16_t)LWIP_MIN(data_len, crlfcrlf + 4 - hdr_start_after_uri);
+          u8_t post_auto_wnd = 1;
+          http_uri_buf[0] = 0;
+          /* trim http header */
+          *crlfcrlf = 0;
+          err = httpd_post_begin(hs, uri, hdr_start_after_uri, hdr_data_len, content_len,
+            http_uri_buf, LWIP_HTTPD_URI_BUF_LEN, &post_auto_wnd);
+          if (err == ERR_OK) {
+            /* try to pass in data of the first pbuf(s) */
+            struct pbuf *q = inp;
+            u16_t start_offset = hdr_len;
+#if LWIP_HTTPD_POST_MANUAL_WND
+            hs->no_auto_wnd = !post_auto_wnd;
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+            /* set the Content-Length to be received for this POST */
+            hs->post_content_len_left = (u32_t)content_len;
+
+            /* get to the pbuf where the body starts */
+            while((q != NULL) && (q->len <= start_offset)) {
+              start_offset -= q->len;
+              q = q->next;
+            }
+            if (q != NULL) {
+              /* hide the remaining HTTP header */
+              pbuf_header(q, -(s16_t)start_offset);
+#if LWIP_HTTPD_POST_MANUAL_WND
+              if (!post_auto_wnd) {
+                /* already tcp_recved() this data... */
+                hs->unrecved_bytes = q->tot_len;
+              }
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+              pbuf_ref(q);
+              return http_post_rxpbuf(hs, q);
+            } else if (hs->post_content_len_left == 0) {
+              q = pbuf_alloc(PBUF_RAW, 0, PBUF_REF);
+              return http_post_rxpbuf(hs, q);
+            } else {
+              return ERR_OK;
+            }
+          } else {
+            /* return file passed from application */
+            return http_find_file(hs, http_uri_buf, 0);
+          }
+        } else {
+          LWIP_DEBUGF(HTTPD_DEBUG, ("POST received invalid Content-Length: %s\n",
+            content_len_num));
+          return ERR_ARG;
+        }
+      }
+    }
+    /* If we come here, headers are fully received (double-crlf), but Content-Length
+       was not included. Since this is currently the only supported method, we have
+       to fail in this case! */
+    LWIP_DEBUGF(HTTPD_DEBUG, ("Error when parsing Content-Length\n"));
+    return ERR_ARG;
+  }
+  /* if we come here, the POST is incomplete */
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+  return ERR_INPROGRESS;
+#else /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+  return ERR_ARG;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+}
+
+#if LWIP_HTTPD_POST_MANUAL_WND
+/** A POST implementation can call this function to update the TCP window.
+ * This can be used to throttle data reception (e.g. when received data is
+ * programmed to flash and data is received faster than programmed).
+ *
+ * @param connection A connection handle passed to httpd_post_begin for which
+ *        httpd_post_finished has *NOT* been called yet!
+ * @param recved_len Length of data received (for window update)
+ */
+void httpd_post_data_recved(void *connection, u16_t recved_len)
+{
+  struct http_state *hs = (struct http_state*)connection;
+  if (hs != NULL) {
+    if (hs->no_auto_wnd) {
+      u16_t len = recved_len;
+      if (hs->unrecved_bytes >= recved_len) {
+        hs->unrecved_bytes -= recved_len;
+      } else {
+        LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("httpd_post_data_recved: recved_len too big\n"));
+        len = (u16_t)hs->unrecved_bytes;
+        hs->unrecved_bytes = 0;
+      }
+      if (hs->pcb != NULL) {
+        if (len != 0) {
+          tcp_recved(hs->pcb, len);
+        }
+        if ((hs->post_content_len_left == 0) && (hs->unrecved_bytes == 0)) {
+          /* finished handling POST */
+          http_handle_post_finished(hs);
+          http_send(hs->pcb, hs);
+        }
+      }
+    }
+  }
+}
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+/** Try to send more data if file has been blocked before
+ * This is a callback function passed to fs_read_async().
+ */
+static void
+http_continue(void *connection)
+{
+  struct http_state *hs = (struct http_state*)connection;
+  if (hs && (hs->pcb) && (hs->handle)) {
+    LWIP_ASSERT("hs->pcb != NULL", hs->pcb != NULL);
+    LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("httpd_continue: try to send more data\n"));
+    if (http_send(hs->pcb, hs)) {
+      /* If we wrote anything to be sent, go ahead and send it now. */
+      LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("tcp_output\n"));
+      tcp_output(hs->pcb);
+    }
+  }
+}
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+/**
+ * When data has been received in the correct state, try to parse it
+ * as a HTTP request.
+ *
+ * @param inp the received pbuf
+ * @param hs the connection state
+ * @param pcb the tcp_pcb which received this packet
+ * @return ERR_OK if request was OK and hs has been initialized correctly
+ *         ERR_INPROGRESS if request was OK so far but not fully received
+ *         another err_t otherwise
+ */
+static err_t
+http_parse_request(struct pbuf *inp, struct http_state *hs, struct tcp_pcb *pcb)
+{
+  char *data;
+  char *crlf;
+  u16_t data_len;
+  struct pbuf *p = inp;
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+  u16_t clen;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+#if LWIP_HTTPD_SUPPORT_POST
+  err_t err;
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+
+  LWIP_UNUSED_ARG(pcb); /* only used for post */
+  LWIP_ASSERT("p != NULL", p != NULL);
+  LWIP_ASSERT("hs != NULL", hs != NULL);
+
+  if ((hs->handle != NULL) || (hs->file != NULL)) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("Received data while sending a file\n"));
+    /* already sending a file */
+    /* @todo: abort? */
+    return ERR_USE;
+  }
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+
+  LWIP_DEBUGF(HTTPD_DEBUG, ("Received %"U16_F" bytes\n", p->tot_len));
+
+  /* first check allowed characters in this pbuf? */
+
+  /* enqueue the pbuf */
+  if (hs->req == NULL) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("First pbuf\n"));
+    hs->req = p;
+  } else {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("pbuf enqueued\n"));
+    pbuf_cat(hs->req, p);
+  }
+  /* increase pbuf ref counter as it is freed when we return but we want to
+     keep it on the req list */
+  pbuf_ref(p);
+
+  if (hs->req->next != NULL) {
+    data_len = LWIP_MIN(hs->req->tot_len, LWIP_HTTPD_MAX_REQ_LENGTH);
+    pbuf_copy_partial(hs->req, httpd_req_buf, data_len, 0);
+    data = httpd_req_buf;
+  } else
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+  {
+    data = (char *)p->payload;
+    data_len = p->len;
+    if (p->len != p->tot_len) {
+      LWIP_DEBUGF(HTTPD_DEBUG, ("Warning: incomplete header due to chained pbufs\n"));
+    }
+  }
+
+  /* received enough data for minimal request? */
+  if (data_len >= MIN_REQ_LEN) {
+    /* wait for CRLF before parsing anything */
+    crlf = lwip_strnstr(data, CRLF, data_len);
+    if (crlf != NULL) {
+#if LWIP_HTTPD_SUPPORT_POST
+      int is_post = 0;
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+      int is_09 = 0;
+      char *sp1, *sp2;
+      u16_t left_len, uri_len;
+      LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("CRLF received, parsing request\n"));
+      /* parse method */
+      if (!strncmp(data, "GET ", 4)) {
+        sp1 = data + 3;
+        /* received GET request */
+        LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Received GET request\"\n"));
+#if LWIP_HTTPD_SUPPORT_POST
+      } else if (!strncmp(data, "POST ", 5)) {
+        /* store request type */
+        is_post = 1;
+        sp1 = data + 4;
+        /* received GET request */
+        LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Received POST request\n"));
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+      } else {
+        /* null-terminate the METHOD (pbuf is freed anyway wen returning) */
+        data[4] = 0;
+        /* unsupported method! */
+        LWIP_DEBUGF(HTTPD_DEBUG, ("Unsupported request method (not implemented): \"%s\"\n",
+          data));
+        return http_find_error_file(hs, 501);
+      }
+      /* if we come here, method is OK, parse URI */
+      left_len = (u16_t)(data_len - ((sp1 +1) - data));
+      sp2 = lwip_strnstr(sp1 + 1, " ", left_len);
+#if LWIP_HTTPD_SUPPORT_V09
+      if (sp2 == NULL) {
+        /* HTTP 0.9: respond with correct protocol version */
+        sp2 = lwip_strnstr(sp1 + 1, CRLF, left_len);
+        is_09 = 1;
+#if LWIP_HTTPD_SUPPORT_POST
+        if (is_post) {
+          /* HTTP/0.9 does not support POST */
+          goto badrequest;
+        }
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+      }
+#endif /* LWIP_HTTPD_SUPPORT_V09 */
+      uri_len = (u16_t)(sp2 - (sp1 + 1));
+      if ((sp2 != 0) && (sp2 > sp1)) {
+        /* wait for CRLFCRLF (indicating end of HTTP headers) before parsing anything */
+        if (lwip_strnstr(data, CRLF CRLF, data_len) != NULL) {
+          char *uri = sp1 + 1;
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+          /* This is HTTP/1.0 compatible: for strict 1.1, a connection
+             would always be persistent unless "close" was specified. */
+          if (!is_09 && (lwip_strnstr(data, HTTP11_CONNECTIONKEEPALIVE, data_len) ||
+              lwip_strnstr(data, HTTP11_CONNECTIONKEEPALIVE2, data_len))) {
+            hs->keepalive = 1;
+          } else {
+            hs->keepalive = 0;
+          }
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+          /* null-terminate the METHOD (pbuf is freed anyway wen returning) */
+          *sp1 = 0;
+          uri[uri_len] = 0;
+          LWIP_DEBUGF(HTTPD_DEBUG, ("Received \"%s\" request for URI: \"%s\"\n",
+                      data, uri));
+#if LWIP_HTTPD_SUPPORT_POST
+          if (is_post) {
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+            struct pbuf *q = hs->req;
+#else /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+            struct pbuf *q = inp;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+            err = http_post_request(q, hs, data, data_len, uri, sp2);
+            if (err != ERR_OK) {
+              /* restore header for next try */
+              *sp1 = ' ';
+              *sp2 = ' ';
+              uri[uri_len] = ' ';
+            }
+            if (err == ERR_ARG) {
+              goto badrequest;
+            }
+            return err;
+          } else
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+          {
+            return http_find_file(hs, uri, is_09);
+          }
+        }
+      } else {
+        LWIP_DEBUGF(HTTPD_DEBUG, ("invalid URI\n"));
+      }
+    }
+  }
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+  clen = pbuf_clen(hs->req);
+  if ((hs->req->tot_len <= LWIP_HTTPD_REQ_BUFSIZE) &&
+    (clen <= LWIP_HTTPD_REQ_QUEUELEN)) {
+    /* request not fully received (too short or CRLF is missing) */
+    return ERR_INPROGRESS;
+  } else
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+  {
+#if LWIP_HTTPD_SUPPORT_POST
+badrequest:
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+    LWIP_DEBUGF(HTTPD_DEBUG, ("bad request\n"));
+    /* could not parse request */
+    return http_find_error_file(hs, 400);
+  }
+}
+
+/** Try to find the file specified by uri and, if found, initialize hs
+ * accordingly.
+ *
+ * @param hs the connection state
+ * @param uri the HTTP header URI
+ * @param is_09 1 if the request is HTTP/0.9 (no HTTP headers in response)
+ * @return ERR_OK if file was found and hs has been initialized correctly
+ *         another err_t otherwise
+ */
+static err_t
+http_find_file(struct http_state *hs, const char *uri, int is_09)
+{
+  size_t loop;
+  struct fs_file *file = NULL;
+  char *params = NULL;
+  err_t err;
+#if LWIP_HTTPD_CGI
+  int i;
+#endif /* LWIP_HTTPD_CGI */
+#if !LWIP_HTTPD_SSI
+  const
+#endif /* !LWIP_HTTPD_SSI */
+  /* By default, assume we will not be processing server-side-includes tags */
+  u8_t tag_check = 0;
+
+  /* Have we been asked for the default file (in root or a directory) ? */
+#if LWIP_HTTPD_MAX_REQUEST_URI_LEN
+  size_t uri_len = strlen(uri);
+  if ((uri_len > 0) && (uri[uri_len-1] == '/') &&
+      ((uri != http_uri_buf) || (uri_len == 1))) {
+    size_t copy_len = LWIP_MIN(sizeof(http_uri_buf) - 1, uri_len - 1);
+    if (copy_len > 0) {
+      MEMCPY(http_uri_buf, uri, copy_len);
+      http_uri_buf[copy_len] = 0;
+    }
+#else /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */
+  if ((uri[0] == '/') &&  (uri[1] == 0)) {
+#endif /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */
+    /* Try each of the configured default filenames until we find one
+       that exists. */
+    for (loop = 0; loop < NUM_DEFAULT_FILENAMES; loop++) {
+      const char* file_name;
+#if LWIP_HTTPD_MAX_REQUEST_URI_LEN
+      if (copy_len > 0) {
+        size_t len_left = sizeof(http_uri_buf) - copy_len - 1;
+        if (len_left > 0) {
+          size_t name_len = strlen(g_psDefaultFilenames[loop].name);
+          size_t name_copy_len = LWIP_MIN(len_left, name_len);
+          MEMCPY(&http_uri_buf[copy_len], g_psDefaultFilenames[loop].name, name_copy_len);
+        }
+        file_name = http_uri_buf;
+      } else
+#endif /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */
+      {
+        file_name = g_psDefaultFilenames[loop].name;
+      }
+      LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Looking for %s...\n", file_name));
+      err = fs_open(&hs->file_handle, file_name);
+      if(err == ERR_OK) {
+        uri = file_name;
+        file = &hs->file_handle;
+        LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Opened.\n"));
+#if LWIP_HTTPD_SSI
+        tag_check = g_psDefaultFilenames[loop].shtml;
+#endif /* LWIP_HTTPD_SSI */
+        break;
+      }
+    }
+  }
+  if (file == NULL) {
+    /* No - we've been asked for a specific file. */
+    /* First, isolate the base URI (without any parameters) */
+    params = (char *)strchr(uri, '?');
+    if (params != NULL) {
+      /* URI contains parameters. NULL-terminate the base URI */
+      *params = '\0';
+      params++;
+    }
+
+#if LWIP_HTTPD_CGI
+    http_cgi_paramcount = -1;
+    /* Does the base URI we have isolated correspond to a CGI handler? */
+    if (g_iNumCGIs && g_pCGIs) {
+      for (i = 0; i < g_iNumCGIs; i++) {
+        if (strcmp(uri, g_pCGIs[i].pcCGIName) == 0) {
+          /*
+           * We found a CGI that handles this URI so extract the
+           * parameters and call the handler.
+           */
+           http_cgi_paramcount = extract_uri_parameters(hs, params);
+           uri = g_pCGIs[i].pfnCGIHandler(i, http_cgi_paramcount, hs->params,
+                                          hs->param_vals);
+           break;
+        }
+      }
+    }
+#endif /* LWIP_HTTPD_CGI */
+
+    LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Opening %s\n", uri));
+
+    err = fs_open(&hs->file_handle, uri);
+    if (err == ERR_OK) {
+       file = &hs->file_handle;
+    } else {
+      file = http_get_404_file(hs, &uri);
+    }
+#if LWIP_HTTPD_SSI
+    if (file != NULL) {
+      /* See if we have been asked for an shtml file and, if so,
+         enable tag checking. */
+      const char* ext = NULL, *sub;
+      char* param = (char*)strstr(uri, "?");
+      if (param != NULL) {
+         /* separate uri from parameters for now, set back later */
+         *param = 0;
+      }
+      sub = uri;
+      ext = uri;
+      for (sub = strstr(sub, "."); sub != NULL; sub = strstr(sub, "."))
+      {
+         ext = sub;
+         sub++;
+      }
+      tag_check = 0;
+      for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) {
+        if (!lwip_stricmp(ext, g_pcSSIExtensions[loop])) {
+          tag_check = 1;
+          break;
+        }
+      }
+      if (param != NULL) {
+         *param = '?';
+      }
+    }
+#endif /* LWIP_HTTPD_SSI */
+  }
+  if (file == NULL) {
+    /* None of the default filenames exist so send back a 404 page */
+    file = http_get_404_file(hs, &uri);
+  }
+  return http_init_file(hs, file, is_09, uri, tag_check, params);
+}
+
+/** Initialize a http connection with a file to send (if found).
+ * Called by http_find_file and http_find_error_file.
+ *
+ * @param hs http connection state
+ * @param file file structure to send (or NULL if not found)
+ * @param is_09 1 if the request is HTTP/0.9 (no HTTP headers in response)
+ * @param uri the HTTP header URI
+ * @param tag_check enable SSI tag checking
+ * @param params != NULL if URI has parameters (separated by '?')
+ * @return ERR_OK if file was found and hs has been initialized correctly
+ *         another err_t otherwise
+ */
+static err_t
+http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri,
+               u8_t tag_check, char* params)
+{
+  if (file != NULL) {
+    /* file opened, initialise struct http_state */
+#if LWIP_HTTPD_SSI
+    if (tag_check) {
+      struct http_ssi_state *ssi = http_ssi_state_alloc();
+      if (ssi != NULL) {
+        ssi->tag_index = 0;
+        ssi->tag_state = TAG_NONE;
+        ssi->parsed = file->data;
+        ssi->parse_left = file->len;
+        ssi->tag_end = file->data;
+        hs->ssi = ssi;
+      }
+    }
+#else /* LWIP_HTTPD_SSI */
+    LWIP_UNUSED_ARG(tag_check);
+#endif /* LWIP_HTTPD_SSI */
+    hs->handle = file;
+    hs->file = file->data;
+    LWIP_ASSERT("File length must be positive!", (file->len >= 0));
+#if LWIP_HTTPD_CUSTOM_FILES
+    if (file->is_custom_file && (file->data == NULL)) {
+      /* custom file, need to read data first (via fs_read_custom) */
+      hs->left = 0;
+    } else
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+    {
+      hs->left = file->len;
+    }
+    hs->retries = 0;
+#if LWIP_HTTPD_TIMING
+    hs->time_started = sys_now();
+#endif /* LWIP_HTTPD_TIMING */
+#if !LWIP_HTTPD_DYNAMIC_HEADERS
+    LWIP_ASSERT("HTTP headers not included in file system",
+       (hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0);
+#endif /* !LWIP_HTTPD_DYNAMIC_HEADERS */
+#if LWIP_HTTPD_SUPPORT_V09
+    if (is_09 && ((hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0)) {
+      /* HTTP/0.9 responses are sent without HTTP header,
+         search for the end of the header. */
+      char *file_start = lwip_strnstr(hs->file, CRLF CRLF, hs->left);
+      if (file_start != NULL) {
+        size_t diff = file_start + 4 - hs->file;
+        hs->file += diff;
+        hs->left -= (u32_t)diff;
+      }
+    }
+#endif /* LWIP_HTTPD_SUPPORT_V09*/
+#if LWIP_HTTPD_CGI_SSI
+    if (params != NULL) {
+      /* URI contains parameters, call generic CGI handler */
+      int count;
+#if LWIP_HTTPD_CGI
+      if (http_cgi_paramcount >= 0) {
+        count = http_cgi_paramcount;
+      } else
+#endif
+      {
+        count = extract_uri_parameters(hs, params);
+      }
+      httpd_cgi_handler(uri, count, http_cgi_params, http_cgi_param_vals
+#if defined(LWIP_HTTPD_FILE_STATE) && LWIP_HTTPD_FILE_STATE
+         , hs->handle->state
+#endif /* LWIP_HTTPD_FILE_STATE */
+                        );
+    }
+#else /* LWIP_HTTPD_CGI_SSI */
+    LWIP_UNUSED_ARG(params);
+#endif /* LWIP_HTTPD_CGI_SSI */
+  } else {
+    hs->handle = NULL;
+    hs->file = NULL;
+    hs->left = 0;
+    hs->retries = 0;
+  }
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+  /* Determine the HTTP headers to send based on the file extension of
+   * the requested URI. */
+  if ((hs->handle == NULL) || ((hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) == 0)) {
+    get_http_headers(hs, uri);
+  }
+#else /* LWIP_HTTPD_DYNAMIC_HEADERS */
+  LWIP_UNUSED_ARG(uri);
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+  if (hs->keepalive) {
+#if LWIP_HTTPD_SSI
+     if (hs->ssi != NULL) {
+       hs->keepalive = 0;
+     } else
+#endif /* LWIP_HTTPD_SSI */
+     {
+       if ((hs->handle != NULL) &&
+           ((hs->handle->flags & (FS_FILE_FLAGS_HEADER_INCLUDED|FS_FILE_FLAGS_HEADER_PERSISTENT)) == FS_FILE_FLAGS_HEADER_INCLUDED)) {
+         hs->keepalive = 0;
+       }
+     }
+  }
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+  return ERR_OK;
+}
+
+/**
+ * The pcb had an error and is already deallocated.
+ * The argument might still be valid (if != NULL).
+ */
+static void
+http_err(void *arg, err_t err)
+{
+  struct http_state *hs = (struct http_state *)arg;
+  LWIP_UNUSED_ARG(err);
+
+  LWIP_DEBUGF(HTTPD_DEBUG, ("http_err: %s", lwip_strerr(err)));
+
+  if (hs != NULL) {
+    http_state_free(hs);
+  }
+}
+
+/**
+ * Data has been sent and acknowledged by the remote host.
+ * This means that more data can be sent.
+ */
+static err_t
+http_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
+{
+  struct http_state *hs = (struct http_state *)arg;
+
+  LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_sent %p\n", (void*)pcb));
+
+  LWIP_UNUSED_ARG(len);
+
+  if (hs == NULL) {
+    return ERR_OK;
+  }
+
+  hs->retries = 0;
+
+  http_send(pcb, hs);
+
+  return ERR_OK;
+}
+
+/**
+ * The poll function is called every 2nd second.
+ * If there has been no data sent (which resets the retries) in 8 seconds, close.
+ * If the last portion of a file has not been sent in 2 seconds, close.
+ *
+ * This could be increased, but we don't want to waste resources for bad connections.
+ */
+static err_t
+http_poll(void *arg, struct tcp_pcb *pcb)
+{
+  struct http_state *hs = (struct http_state *)arg;
+  LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_poll: pcb=%p hs=%p pcb_state=%s\n",
+    (void*)pcb, (void*)hs, tcp_debug_state_str(pcb->state)));
+
+  if (hs == NULL) {
+    err_t closed;
+    /* arg is null, close. */
+    LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: arg is NULL, close\n"));
+    closed = http_close_conn(pcb, NULL);
+    LWIP_UNUSED_ARG(closed);
+#if LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR
+    if (closed == ERR_MEM) {
+       tcp_abort(pcb);
+       return ERR_ABRT;
+    }
+#endif /* LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR */
+    return ERR_OK;
+  } else {
+    hs->retries++;
+    if (hs->retries == HTTPD_MAX_RETRIES) {
+      LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: too many retries, close\n"));
+      http_close_conn(pcb, hs);
+      return ERR_OK;
+    }
+
+    /* If this connection has a file open, try to send some more data. If
+     * it has not yet received a GET request, don't do this since it will
+     * cause the connection to close immediately. */
+    if(hs && (hs->handle)) {
+      LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_poll: try to send more data\n"));
+      if(http_send(pcb, hs)) {
+        /* If we wrote anything to be sent, go ahead and send it now. */
+        LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("tcp_output\n"));
+        tcp_output(pcb);
+      }
+    }
+  }
+
+  return ERR_OK;
+}
+
+/**
+ * Data has been received on this pcb.
+ * For HTTP 1.0, this should normally only happen once (if the request fits in one packet).
+ */
+static err_t
+http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+  struct http_state *hs = (struct http_state *)arg;
+  LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: pcb=%p pbuf=%p err=%s\n", (void*)pcb,
+    (void*)p, lwip_strerr(err)));
+
+  if ((err != ERR_OK) || (p == NULL) || (hs == NULL)) {
+    /* error or closed by other side? */
+    if (p != NULL) {
+      /* Inform TCP that we have taken the data. */
+      tcp_recved(pcb, p->tot_len);
+      pbuf_free(p);
+    }
+    if (hs == NULL) {
+      /* this should not happen, only to be robust */
+      LWIP_DEBUGF(HTTPD_DEBUG, ("Error, http_recv: hs is NULL, close\n"));
+    }
+    http_close_conn(pcb, hs);
+    return ERR_OK;
+  }
+
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+  if (hs->no_auto_wnd) {
+     hs->unrecved_bytes += p->tot_len;
+  } else
+#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */
+  {
+    /* Inform TCP that we have taken the data. */
+    tcp_recved(pcb, p->tot_len);
+  }
+
+#if LWIP_HTTPD_SUPPORT_POST
+  if (hs->post_content_len_left > 0) {
+    /* reset idle counter when POST data is received */
+    hs->retries = 0;
+    /* this is data for a POST, pass the complete pbuf to the application */
+    http_post_rxpbuf(hs, p);
+    /* pbuf is passed to the application, don't free it! */
+    if (hs->post_content_len_left == 0) {
+      /* all data received, send response or close connection */
+      http_send(pcb, hs);
+    }
+    return ERR_OK;
+  } else
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+  {
+    if (hs->handle == NULL) {
+      err_t parsed = http_parse_request(p, hs, pcb);
+      LWIP_ASSERT("http_parse_request: unexpected return value", parsed == ERR_OK
+        || parsed == ERR_INPROGRESS ||parsed == ERR_ARG || parsed == ERR_USE);
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+      if (parsed != ERR_INPROGRESS) {
+        /* request fully parsed or error */
+        if (hs->req != NULL) {
+          pbuf_free(hs->req);
+          hs->req = NULL;
+        }
+      }
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+      pbuf_free(p);
+      if (parsed == ERR_OK) {
+#if LWIP_HTTPD_SUPPORT_POST
+       if (hs->post_content_len_left == 0)
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+        {
+          LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: data %p len %"S32_F"\n", (const void*)hs->file, hs->left));
+          http_send(pcb, hs);
+        }
+      } else if (parsed == ERR_ARG) {
+        /* @todo: close on ERR_USE? */
+        http_close_conn(pcb, hs);
+      }
+    } else {
+      LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: already sending data\n"));
+      /* already sending but still receiving data, we might want to RST here? */
+      pbuf_free(p);
+    }
+  }
+  return ERR_OK;
+}
+
+/**
+ * A new incoming connection has been accepted.
+ */
+static err_t
+http_accept(void *arg, struct tcp_pcb *pcb, err_t err)
+{
+  struct http_state *hs;
+  LWIP_UNUSED_ARG(err);
+  LWIP_UNUSED_ARG(arg);
+  LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept %p / %p\n", (void*)pcb, arg));
+
+  if ((err != ERR_OK) || (pcb == NULL)) {
+    return ERR_VAL;
+  }
+
+  /* Set priority */
+  tcp_setprio(pcb, HTTPD_TCP_PRIO);
+
+  /* Allocate memory for the structure that holds the state of the
+     connection - initialized by that function. */
+  hs = http_state_alloc();
+  if (hs == NULL) {
+    LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept: Out of memory, RST\n"));
+    return ERR_MEM;
+  }
+  hs->pcb = pcb;
+
+  /* Tell TCP that this is the structure we wish to be passed for our
+     callbacks. */
+  tcp_arg(pcb, hs);
+
+  /* Set up the various callback functions */
+  tcp_recv(pcb, http_recv);
+  tcp_err(pcb, http_err);
+  tcp_poll(pcb, http_poll, HTTPD_POLL_INTERVAL);
+  tcp_sent(pcb, http_sent);
+
+  return ERR_OK;
+}
+
+/**
+ * @ingroup httpd
+ * Initialize the httpd: set up a listening PCB and bind it to the defined port
+ */
+void
+httpd_init(void)
+{
+  struct tcp_pcb *pcb;
+  err_t err;
+
+#if HTTPD_USE_MEM_POOL
+  LWIP_MEMPOOL_INIT(HTTPD_STATE);
+#if LWIP_HTTPD_SSI
+  LWIP_MEMPOOL_INIT(HTTPD_SSI_STATE);
+#endif
+#endif
+  LWIP_DEBUGF(HTTPD_DEBUG, ("httpd_init\n"));
+
+  pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
+  LWIP_ASSERT("httpd_init: tcp_new failed", pcb != NULL);
+  tcp_setprio(pcb, HTTPD_TCP_PRIO);
+  /* set SOF_REUSEADDR here to explicitly bind httpd to multiple interfaces */
+  err = tcp_bind(pcb, IP_ANY_TYPE, HTTPD_SERVER_PORT);
+  LWIP_UNUSED_ARG(err); /* in case of LWIP_NOASSERT */
+  LWIP_ASSERT("httpd_init: tcp_bind failed", err == ERR_OK);
+  pcb = tcp_listen(pcb);
+  LWIP_ASSERT("httpd_init: tcp_listen failed", pcb != NULL);
+  tcp_accept(pcb, http_accept);
+}
+
+#if LWIP_HTTPD_SSI
+/**
+ * Set the SSI handler function.
+ *
+ * @param ssi_handler the SSI handler function
+ * @param tags an array of SSI tag strings to search for in SSI-enabled files
+ * @param num_tags number of tags in the 'tags' array
+ */
+void
+http_set_ssi_handler(tSSIHandler ssi_handler, const char **tags, int num_tags)
+{
+  LWIP_DEBUGF(HTTPD_DEBUG, ("http_set_ssi_handler\n"));
+
+  LWIP_ASSERT("no ssi_handler given", ssi_handler != NULL);
+  g_pfnSSIHandler = ssi_handler;
+
+#if LWIP_HTTPD_SSI_RAW
+  LWIP_UNUSED_ARG(tags);
+  LWIP_UNUSED_ARG(num_tags);
+#else /* LWIP_HTTPD_SSI_RAW */
+  LWIP_ASSERT("no tags given", tags != NULL);
+  LWIP_ASSERT("invalid number of tags", num_tags > 0);
+
+  g_ppcTags = tags;
+  g_iNumTags = num_tags;
+#endif /* !LWIP_HTTPD_SSI_RAW */
+}
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_CGI
+/**
+ * Set an array of CGI filenames/handler functions
+ *
+ * @param cgis an array of CGI filenames/handler functions
+ * @param num_handlers number of elements in the 'cgis' array
+ */
+void
+http_set_cgi_handlers(const tCGI *cgis, int num_handlers)
+{
+  LWIP_ASSERT("no cgis given", cgis != NULL);
+  LWIP_ASSERT("invalid number of handlers", num_handlers > 0);
+
+  g_pCGIs = cgis;
+  g_iNumCGIs = num_handlers;
+}
+#endif /* LWIP_HTTPD_CGI */
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/httpd/httpd_structs.h b/src/apps/httpd/httpd_structs.h
new file mode 100644
index 0000000..fbd135a
--- /dev/null
+++ b/src/apps/httpd/httpd_structs.h
@@ -0,0 +1,114 @@
+#ifndef LWIP_HTTPD_STRUCTS_H
+#define LWIP_HTTPD_STRUCTS_H
+
+#include "lwip/apps/httpd.h"
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+/** This struct is used for a list of HTTP header strings for various
+ * filename extensions. */
+typedef struct
+{
+  const char *extension;
+  const char *content_type;
+} tHTTPHeader;
+
+/** A list of strings used in HTTP headers (see RFC 1945 HTTP/1.0 and
+ * RFC 2616 HTTP/1.1 for header field definitions) */
+static const char * const g_psHTTPHeaderStrings[] =
+{
+ "HTTP/1.0 200 OK\r\n",
+ "HTTP/1.0 404 File not found\r\n",
+ "HTTP/1.0 400 Bad Request\r\n",
+ "HTTP/1.0 501 Not Implemented\r\n",
+ "HTTP/1.1 200 OK\r\n",
+ "HTTP/1.1 404 File not found\r\n",
+ "HTTP/1.1 400 Bad Request\r\n",
+ "HTTP/1.1 501 Not Implemented\r\n",
+ "Content-Length: ",
+ "Connection: Close\r\n",
+ "Connection: keep-alive\r\n",
+ "Connection: keep-alive\r\nContent-Length: ",
+ "Server: "HTTPD_SERVER_AGENT"\r\n",
+ "\r\n<html><body><h2>404: The requested file cannot be found.</h2></body></html>\r\n"
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ ,"Connection: keep-alive\r\nContent-Length: 77\r\n\r\n<html><body><h2>404: The requested file cannot be found.</h2></body></html>\r\n"
+#endif
+};
+
+/* Indexes into the g_psHTTPHeaderStrings array */
+#define HTTP_HDR_OK             0 /* 200 OK */
+#define HTTP_HDR_NOT_FOUND      1 /* 404 File not found */
+#define HTTP_HDR_BAD_REQUEST    2 /* 400 Bad request */
+#define HTTP_HDR_NOT_IMPL       3 /* 501 Not Implemented */
+#define HTTP_HDR_OK_11          4 /* 200 OK */
+#define HTTP_HDR_NOT_FOUND_11   5 /* 404 File not found */
+#define HTTP_HDR_BAD_REQUEST_11 6 /* 400 Bad request */
+#define HTTP_HDR_NOT_IMPL_11    7 /* 501 Not Implemented */
+#define HTTP_HDR_CONTENT_LENGTH 8 /* Content-Length: (HTTP 1.0)*/
+#define HTTP_HDR_CONN_CLOSE     9 /* Connection: Close (HTTP 1.1) */
+#define HTTP_HDR_CONN_KEEPALIVE 10 /* Connection: keep-alive (HTTP 1.1) */
+#define HTTP_HDR_KEEPALIVE_LEN  11 /* Connection: keep-alive + Content-Length: (HTTP 1.1)*/
+#define HTTP_HDR_SERVER         12 /* Server: HTTPD_SERVER_AGENT */
+#define DEFAULT_404_HTML        13 /* default 404 body */
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+#define DEFAULT_404_HTML_PERSISTENT 14 /* default 404 body, but including Connection: keep-alive */
+#endif
+
+
+#define HTTP_HDR_HTML           "Content-type: text/html\r\n\r\n"
+#define HTTP_HDR_SSI            "Content-type: text/html\r\nExpires: Fri, 10 Apr 2008 14:00:00 GMT\r\nPragma: no-cache\r\n\r\n"
+#define HTTP_HDR_GIF            "Content-type: image/gif\r\n\r\n"
+#define HTTP_HDR_PNG            "Content-type: image/png\r\n\r\n"
+#define HTTP_HDR_JPG            "Content-type: image/jpeg\r\n\r\n"
+#define HTTP_HDR_BMP            "Content-type: image/bmp\r\n\r\n"
+#define HTTP_HDR_ICO            "Content-type: image/x-icon\r\n\r\n"
+#define HTTP_HDR_APP            "Content-type: application/octet-stream\r\n\r\n"
+#define HTTP_HDR_JS             "Content-type: application/javascript\r\n\r\n"
+#define HTTP_HDR_RA             "Content-type: application/javascript\r\n\r\n"
+#define HTTP_HDR_CSS            "Content-type: text/css\r\n\r\n"
+#define HTTP_HDR_SWF            "Content-type: application/x-shockwave-flash\r\n\r\n"
+#define HTTP_HDR_XML            "Content-type: text/xml\r\n\r\n"
+#define HTTP_HDR_PDF            "Content-type: application/pdf\r\n\r\n"
+#define HTTP_HDR_JSON           "Content-type: application/json\r\n\r\n"
+
+#define HTTP_HDR_DEFAULT_TYPE   "Content-type: text/plain\r\n\r\n"
+
+/** A list of extension-to-HTTP header strings (see outdated RFC 1700 MEDIA TYPES
+ * and http://www.iana.org/assignments/media-types for registered content types
+ * and subtypes) */
+static const tHTTPHeader g_psHTTPHeaders[] =
+{
+ { "html", HTTP_HDR_HTML},
+ { "htm",  HTTP_HDR_HTML},
+ { "shtml",HTTP_HDR_SSI},
+ { "shtm", HTTP_HDR_SSI},
+ { "ssi",  HTTP_HDR_SSI},
+ { "gif",  HTTP_HDR_GIF},
+ { "png",  HTTP_HDR_PNG},
+ { "jpg",  HTTP_HDR_JPG},
+ { "bmp",  HTTP_HDR_BMP},
+ { "ico",  HTTP_HDR_ICO},
+ { "class",HTTP_HDR_APP},
+ { "cls",  HTTP_HDR_APP},
+ { "js",   HTTP_HDR_JS},
+ { "ram",  HTTP_HDR_RA},
+ { "css",  HTTP_HDR_CSS},
+ { "swf",  HTTP_HDR_SWF},
+ { "xml",  HTTP_HDR_XML},
+ { "xsl",  HTTP_HDR_XML},
+ { "pdf",  HTTP_HDR_PDF},
+ { "json", HTTP_HDR_JSON}
+};
+
+#define NUM_HTTP_HEADERS (sizeof(g_psHTTPHeaders) / sizeof(tHTTPHeader))
+
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+#if LWIP_HTTPD_SSI
+static const char * const g_pcSSIExtensions[] = {
+  ".shtml", ".shtm", ".ssi", ".xml"
+};
+#define NUM_SHTML_EXTENSIONS (sizeof(g_pcSSIExtensions) / sizeof(const char *))
+#endif /* LWIP_HTTPD_SSI */
+
+#endif /* LWIP_HTTPD_STRUCTS_H */
diff --git a/src/apps/httpd/makefsdata/makefsdata b/src/apps/httpd/makefsdata/makefsdata
new file mode 100644
index 0000000..37b4203
--- /dev/null
+++ b/src/apps/httpd/makefsdata/makefsdata
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+open(OUTPUT, "> fsdata.c");
+
+chdir("fs");
+open(FILES, "find . -type f |");
+
+while($file = <FILES>) {
+
+    # Do not include files in CVS directories nor backup files.
+    if($file =~ /(CVS|~)/) {
+    	next;
+    }
+    
+    chop($file);
+    
+    open(HEADER, "> /tmp/header") || die $!;
+    if($file =~ /404/) {
+	print(HEADER "HTTP/1.0 404 File not found\r\n");
+    } else {
+	print(HEADER "HTTP/1.0 200 OK\r\n");
+    }
+    print(HEADER "Server: lwIP/pre-0.6 (http://www.sics.se/~adam/lwip/)\r\n");
+    if($file =~ /\.html$/) {
+	print(HEADER "Content-type: text/html\r\n");
+    } elsif($file =~ /\.gif$/) {
+	print(HEADER "Content-type: image/gif\r\n");
+    } elsif($file =~ /\.png$/) {
+	print(HEADER "Content-type: image/png\r\n");
+    } elsif($file =~ /\.jpg$/) {
+	print(HEADER "Content-type: image/jpeg\r\n");
+    } elsif($file =~ /\.class$/) {
+	print(HEADER "Content-type: application/octet-stream\r\n");
+    } elsif($file =~ /\.ram$/) {
+	print(HEADER "Content-type: audio/x-pn-realaudio\r\n");    
+    } else {
+	print(HEADER "Content-type: text/plain\r\n");
+    }
+    print(HEADER "\r\n");
+    close(HEADER);
+
+    unless($file =~ /\.plain$/ || $file =~ /cgi/) {
+	system("cat /tmp/header $file > /tmp/file");
+    } else {
+	system("cp $file /tmp/file");
+    }
+    
+    open(FILE, "/tmp/file");
+    unlink("/tmp/file");
+    unlink("/tmp/header");
+
+    $file =~ s/\.//;
+    $fvar = $file;
+    $fvar =~ s-/-_-g;
+    $fvar =~ s-\.-_-g;
+    print(OUTPUT "static const unsigned char data".$fvar."[] = {\n");
+    print(OUTPUT "\t/* $file */\n\t");
+    for($j = 0; $j < length($file); $j++) {
+	printf(OUTPUT "%#02x, ", unpack("C", substr($file, $j, 1)));
+    }
+    printf(OUTPUT "0,\n");
+    
+    
+    $i = 0;
+    while(read(FILE, $data, 1)) {
+        if($i == 0) {
+            print(OUTPUT "\t");
+        }
+        printf(OUTPUT "%#02x, ", unpack("C", $data));
+        $i++;
+        if($i == 10) {
+            print(OUTPUT "\n");
+            $i = 0;
+        }
+    }
+    print(OUTPUT "};\n\n");
+    close(FILE);
+    push(@fvars, $fvar);
+    push(@files, $file);
+}
+
+for($i = 0; $i < @fvars; $i++) {
+    $file = $files[$i];
+    $fvar = $fvars[$i];
+
+    if($i == 0) {
+        $prevfile = "NULL";
+    } else {
+        $prevfile = "file" . $fvars[$i - 1];
+    }
+    print(OUTPUT "const struct fsdata_file file".$fvar."[] = {{$prevfile, data$fvar, ");
+    print(OUTPUT "data$fvar + ". (length($file) + 1) .", ");
+    print(OUTPUT "sizeof(data$fvar) - ". (length($file) + 1) ."}};\n\n");
+}
+
+print(OUTPUT "#define FS_ROOT file$fvars[$i - 1]\n\n");
+print(OUTPUT "#define FS_NUMFILES $i\n");
diff --git a/src/apps/httpd/makefsdata/makefsdata.c b/src/apps/httpd/makefsdata/makefsdata.c
new file mode 100644
index 0000000..934e721
--- /dev/null
+++ b/src/apps/httpd/makefsdata/makefsdata.c
@@ -0,0 +1,1033 @@
+/**
+ * makefsdata: Converts a directory structure for use with the lwIP httpd.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Jim Pettinato
+ *         Simon Goldschmidt
+ *
+ * @todo:
+ * - take TCP_MSS, LWIP_TCP_TIMESTAMPS and
+ *   PAYLOAD_ALIGN_TYPE/PAYLOAD_ALIGNMENT as arguments
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include "windows.h"
+#else
+#include <dir.h>
+#endif
+#include <dos.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+
+/** Makefsdata can generate *all* files deflate-compressed (where file size shrinks).
+ * Since nearly all browsers support this, this is a good way to reduce ROM size.
+ * To compress the files, "miniz.c" must be downloaded seperately.
+ */
+#ifndef MAKEFS_SUPPORT_DEFLATE
+#define MAKEFS_SUPPORT_DEFLATE 0
+#endif
+
+#define COPY_BUFSIZE (1024*1024) /* 1 MByte */
+
+#if MAKEFS_SUPPORT_DEFLATE
+#include "../miniz.c"
+
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef unsigned int uint;
+
+#define my_max(a,b) (((a) > (b)) ? (a) : (b))
+#define my_min(a,b) (((a) < (b)) ? (a) : (b))
+
+/* COMP_OUT_BUF_SIZE is the size of the output buffer used during compression.
+   COMP_OUT_BUF_SIZE must be >= 1 and <= OUT_BUF_SIZE */
+#define COMP_OUT_BUF_SIZE COPY_BUFSIZE
+
+/* OUT_BUF_SIZE is the size of the output buffer used during decompression.
+   OUT_BUF_SIZE must be a power of 2 >= TINFL_LZ_DICT_SIZE (because the low-level decompressor not only writes, but reads from the output buffer as it decompresses) */
+#define OUT_BUF_SIZE COPY_BUFSIZE
+static uint8 s_outbuf[OUT_BUF_SIZE];
+static uint8 s_checkbuf[OUT_BUF_SIZE];
+
+/* tdefl_compressor contains all the state needed by the low-level compressor so it's a pretty big struct (~300k).
+   This example makes it a global vs. putting it on the stack, of course in real-world usage you'll probably malloc() or new it. */
+tdefl_compressor g_deflator;
+tinfl_decompressor g_inflator;
+
+int deflate_level = 10; /* default compression level, can be changed via command line */
+#define USAGE_ARG_DEFLATE " [-defl<:compr_level>]"
+#else /* MAKEFS_SUPPORT_DEFLATE */
+#define USAGE_ARG_DEFLATE ""
+#endif /* MAKEFS_SUPPORT_DEFLATE */
+
+/* Compatibility defines Win32 vs. DOS */
+#ifdef WIN32
+
+#define FIND_T                        WIN32_FIND_DATAA
+#define FIND_T_FILENAME(fInfo)        (fInfo.cFileName)
+#define FIND_T_IS_DIR(fInfo)          ((fInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
+#define FIND_T_IS_FILE(fInfo)         ((fInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
+#define FIND_RET_T                    HANDLE
+#define FINDFIRST_FILE(path, result)  FindFirstFileA(path, result)
+#define FINDFIRST_DIR(path, result)   FindFirstFileA(path, result)
+#define FINDNEXT(ff_res, result)      FindNextFileA(ff_res, result)
+#define FINDFIRST_SUCCEEDED(ret)      (ret != INVALID_HANDLE_VALUE)
+#define FINDNEXT_SUCCEEDED(ret)       (ret == TRUE)
+
+#define GETCWD(path, len)             GetCurrentDirectoryA(len, path)
+#define CHDIR(path)                   SetCurrentDirectoryA(path)
+#define CHDIR_SUCCEEDED(ret)          (ret == TRUE)
+
+#else
+
+#define FIND_T                        struct ffblk
+#define FIND_T_FILENAME(fInfo)        (fInfo.ff_name)
+#define FIND_T_IS_DIR(fInfo)          ((fInfo.ff_attrib & FA_DIREC) == FA_DIREC)
+#define FIND_T_IS_FILE(fInfo)         (1)
+#define FIND_RET_T                    int
+#define FINDFIRST_FILE(path, result)  findfirst(path, result, FA_ARCH)
+#define FINDFIRST_DIR(path, result)   findfirst(path, result, FA_DIREC)
+#define FINDNEXT(ff_res, result)      FindNextFileA(ff_res, result)
+#define FINDFIRST_SUCCEEDED(ret)      (ret == 0)
+#define FINDNEXT_SUCCEEDED(ret)       (ret == 0)
+
+#define GETCWD(path, len)             getcwd(path, len)
+#define CHDIR(path)                   chdir(path)
+#define CHDIR_SUCCEEDED(ret)          (ret == 0)
+
+#endif
+
+#define NEWLINE     "\r\n"
+#define NEWLINE_LEN 2
+
+/* define this to get the header variables we use to build HTTP headers */
+#define LWIP_HTTPD_DYNAMIC_HEADERS 1
+#define LWIP_HTTPD_SSI             1
+#include "lwip/init.h"
+#include "../httpd_structs.h"
+#include "lwip/apps/fs.h"
+
+#include "../core/inet_chksum.c"
+#include "../core/def.c"
+
+/** (Your server name here) */
+const char *serverID = "Server: "HTTPD_SERVER_AGENT"\r\n";
+char serverIDBuffer[1024];
+
+/* change this to suit your MEM_ALIGNMENT */
+#define PAYLOAD_ALIGNMENT 4
+/* set this to 0 to prevent aligning payload */
+#define ALIGN_PAYLOAD 1
+/* define this to a type that has the required alignment */
+#define PAYLOAD_ALIGN_TYPE "unsigned int"
+static int payload_alingment_dummy_counter = 0;
+
+#define HEX_BYTES_PER_LINE 16
+
+#define MAX_PATH_LEN 256
+
+struct file_entry
+{
+   struct file_entry* next;
+   const char* filename_c;
+};
+
+int process_sub(FILE *data_file, FILE *struct_file);
+int process_file(FILE *data_file, FILE *struct_file, const char *filename);
+int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len,
+                           u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed);
+int file_put_ascii(FILE *file, const char *ascii_string, int len, int *i);
+int s_put_ascii(char *buf, const char *ascii_string, int len, int *i);
+void concat_files(const char *file1, const char *file2, const char *targetfile);
+int check_path(char* path, size_t size);
+
+/* 5 bytes per char + 3 bytes per line */
+static char file_buffer_c[COPY_BUFSIZE * 5 + ((COPY_BUFSIZE / HEX_BYTES_PER_LINE) * 3)];
+
+char curSubdir[MAX_PATH_LEN];
+char lastFileVar[MAX_PATH_LEN];
+char hdr_buf[4096];
+
+unsigned char processSubs = 1;
+unsigned char includeHttpHeader = 1;
+unsigned char useHttp11 = 0;
+unsigned char supportSsi = 1;
+unsigned char precalcChksum = 0;
+unsigned char includeLastModified = 0;
+#if MAKEFS_SUPPORT_DEFLATE
+unsigned char deflateNonSsiFiles = 0;
+size_t deflatedBytesReduced = 0;
+size_t overallDataBytes = 0;
+#endif
+
+struct file_entry* first_file = NULL;
+struct file_entry* last_file = NULL;
+
+static void print_usage(void)
+{
+  printf(" Usage: htmlgen [targetdir] [-s] [-e] [-i] [-11] [-nossi] [-c] [-f:<filename>] [-m] [-svr:<name>]" USAGE_ARG_DEFLATE NEWLINE NEWLINE);
+  printf("   targetdir: relative or absolute path to files to convert" NEWLINE);
+  printf("   switch -s: toggle processing of subdirectories (default is on)" NEWLINE);
+  printf("   switch -e: exclude HTTP header from file (header is created at runtime, default is off)" NEWLINE);
+  printf("   switch -11: include HTTP 1.1 header (1.0 is default)" NEWLINE);
+  printf("   switch -nossi: no support for SSI (cannot calculate Content-Length for SSI)" NEWLINE);
+  printf("   switch -c: precalculate checksums for all pages (default is off)" NEWLINE);
+  printf("   switch -f: target filename (default is \"fsdata.c\")" NEWLINE);
+  printf("   switch -m: include \"Last-Modified\" header based on file time" NEWLINE);
+  printf("   switch -svr: server identifier sent in HTTP response header ('Server' field)" NEWLINE);
+#if MAKEFS_SUPPORT_DEFLATE
+  printf("   switch -defl: deflate-compress all non-SSI files (with opt. compr.-level, default=10)" NEWLINE);
+  printf("                 ATTENTION: browser has to support \"Content-Encoding: deflate\"!" NEWLINE);
+#endif
+  printf("   if targetdir not specified, htmlgen will attempt to" NEWLINE);
+  printf("   process files in subdirectory 'fs'" NEWLINE);
+}
+
+int main(int argc, char *argv[])
+{
+  char path[MAX_PATH_LEN];
+  char appPath[MAX_PATH_LEN];
+  FILE *data_file;
+  FILE *struct_file;
+  int filesProcessed;
+  int i;
+  char targetfile[MAX_PATH_LEN];
+  strcpy(targetfile, "fsdata.c");
+
+  memset(path, 0, sizeof(path));
+  memset(appPath, 0, sizeof(appPath));
+
+  printf(NEWLINE " makefsdata - HTML to C source converter" NEWLINE);
+  printf("     by Jim Pettinato               - circa 2003 " NEWLINE);
+  printf("     extended by Simon Goldschmidt  - 2009 " NEWLINE NEWLINE);
+
+  strcpy(path, "fs");
+  for (i = 1; i < argc; i++) {
+    if (argv[i] == NULL) {
+      continue;
+    }
+    if (argv[i][0] == '-') {
+      if (strstr(argv[i], "-svr:") == argv[i]) {
+        snprintf(serverIDBuffer, sizeof(serverIDBuffer), "Server: %s\r\n", &argv[i][5]);
+        serverID = serverIDBuffer;
+        printf("Using Server-ID: \"%s\"\n", serverID);
+      } else if (strstr(argv[i], "-s") == argv[i]) {
+        processSubs = 0;
+      } else if (strstr(argv[i], "-e") == argv[i]) {
+        includeHttpHeader = 0;
+      } else if (strstr(argv[i], "-11") == argv[i]) {
+        useHttp11 = 1;
+      } else if (strstr(argv[i], "-nossi") == argv[i]) {
+        supportSsi = 0;
+      } else if (strstr(argv[i], "-c") == argv[i]) {
+        precalcChksum = 1;
+      } else if (strstr(argv[i], "-f:") == argv[i]) {
+        strncpy(targetfile, &argv[i][3], sizeof(targetfile) - 1);
+        targetfile[sizeof(targetfile) - 1] = 0;
+        printf("Writing to file \"%s\"\n", targetfile);
+      } else if (strstr(argv[i], "-m") == argv[i]) {
+        includeLastModified = 1;
+      } else if (strstr(argv[i], "-defl") == argv[i]) {
+#if MAKEFS_SUPPORT_DEFLATE
+        char* colon = strstr(argv[i], ":");
+        if (colon) {
+          if (colon[1] != 0) {
+            int defl_level = atoi(&colon[1]);
+            if ((defl_level >= 0) && (defl_level <= 10)) {
+              deflate_level = defl_level;
+            } else {
+              printf("ERROR: deflate level must be [0..10]" NEWLINE);
+              exit(0);
+            }
+          }
+        }
+        deflateNonSsiFiles = 1;
+        printf("Deflating all non-SSI files with level %d (but only if size is reduced)" NEWLINE, deflate_level);
+#else
+        printf("WARNING: Deflate support is disabled\n");
+#endif
+      } else if ((strstr(argv[i], "-?")) || (strstr(argv[i], "-h"))) {
+        print_usage();
+        exit(0);
+      }
+    } else if ((argv[i][0] == '/') && (argv[i][1] == '?') && (argv[i][2] == 0)) {
+      print_usage();
+      exit(0);
+    } else {
+      strncpy(path, argv[i], sizeof(path)-1);
+      path[sizeof(path)-1] = 0;
+    }
+  }
+
+  if (!check_path(path, sizeof(path))) {
+    printf("Invalid path: \"%s\"." NEWLINE, path);
+    exit(-1);
+  }
+
+  GETCWD(appPath, MAX_PATH_LEN);
+  /* if command line param or subdir named 'fs' not found spout usage verbiage */
+  if (!CHDIR_SUCCEEDED(CHDIR(path))) {
+    /* if no subdir named 'fs' (or the one which was given) exists, spout usage verbiage */
+    printf(" Failed to open directory \"%s\"." NEWLINE NEWLINE, path);
+    print_usage();
+    exit(-1);
+  }
+  CHDIR(appPath);
+
+  printf("HTTP %sheader will %s statically included." NEWLINE,
+    (includeHttpHeader ? (useHttp11 ? "1.1 " : "1.0 ") : ""),
+    (includeHttpHeader ? "be" : "not be"));
+
+  sprintf(curSubdir, "");  /* start off in web page's root directory - relative paths */
+  printf("  Processing all files in directory %s", path);
+  if (processSubs) {
+    printf(" and subdirectories..." NEWLINE NEWLINE);
+  } else {
+    printf("..." NEWLINE NEWLINE);
+  }
+
+  data_file = fopen("fsdata.tmp", "wb");
+  if (data_file == NULL) {
+    printf("Failed to create file \"fsdata.tmp\"\n");
+    exit(-1);
+  }
+  struct_file = fopen("fshdr.tmp", "wb");
+  if (struct_file == NULL) {
+    printf("Failed to create file \"fshdr.tmp\"\n");
+    fclose(data_file);
+    exit(-1);
+  }
+
+  CHDIR(path);
+
+  fprintf(data_file, "#include \"lwip/apps/fs.h\"" NEWLINE);
+  fprintf(data_file, "#include \"lwip/def.h\"" NEWLINE);
+  fprintf(data_file, "#include \"fsdata.h\"" NEWLINE NEWLINE NEWLINE);
+
+  fprintf(data_file, "#define file_NULL (struct fsdata_file *) NULL" NEWLINE NEWLINE NEWLINE);
+  /* define FS_FILE_FLAGS_HEADER_INCLUDED to 1 if not defined (compatibility with older httpd/fs) */
+  fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_INCLUDED" NEWLINE "#define FS_FILE_FLAGS_HEADER_INCLUDED 1" NEWLINE "#endif" NEWLINE);
+  /* define FS_FILE_FLAGS_HEADER_PERSISTENT to 0 if not defined (compatibility with older httpd/fs: wasn't supported back then) */
+  fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_PERSISTENT" NEWLINE "#define FS_FILE_FLAGS_HEADER_PERSISTENT 0" NEWLINE "#endif" NEWLINE);
+
+  /* define alignment defines */
+#if ALIGN_PAYLOAD
+  fprintf(data_file, "/* FSDATA_FILE_ALIGNMENT: 0=off, 1=by variable, 2=by include */" NEWLINE "#ifndef FSDATA_FILE_ALIGNMENT" NEWLINE "#define FSDATA_FILE_ALIGNMENT 0" NEWLINE "#endif" NEWLINE);
+#endif
+  fprintf(data_file, "#ifndef FSDATA_ALIGN_PRE"  NEWLINE "#define FSDATA_ALIGN_PRE"  NEWLINE "#endif" NEWLINE);
+  fprintf(data_file, "#ifndef FSDATA_ALIGN_POST" NEWLINE "#define FSDATA_ALIGN_POST" NEWLINE "#endif" NEWLINE);
+#if ALIGN_PAYLOAD
+  fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==2" NEWLINE "#include \"fsdata_alignment.h\"" NEWLINE "#endif" NEWLINE);
+#endif
+
+  sprintf(lastFileVar, "NULL");
+
+  filesProcessed = process_sub(data_file, struct_file);
+
+  /* data_file now contains all of the raw data.. now append linked list of
+   * file header structs to allow embedded app to search for a file name */
+  fprintf(data_file, NEWLINE NEWLINE);
+  fprintf(struct_file, "#define FS_ROOT file_%s" NEWLINE, lastFileVar);
+  fprintf(struct_file, "#define FS_NUMFILES %d" NEWLINE NEWLINE, filesProcessed);
+
+  fclose(data_file);
+  fclose(struct_file);
+
+  CHDIR(appPath);
+  /* append struct_file to data_file */
+  printf(NEWLINE "Creating target file..." NEWLINE NEWLINE);
+  concat_files("fsdata.tmp", "fshdr.tmp", targetfile);
+
+  /* if succeeded, delete the temporary files */
+  if (remove("fsdata.tmp") != 0) {
+    printf("Warning: failed to delete fsdata.tmp\n");
+  }
+  if (remove("fshdr.tmp") != 0) {
+    printf("Warning: failed to delete fshdr.tmp\n");
+  }
+
+  printf(NEWLINE "Processed %d files - done." NEWLINE, filesProcessed);
+#if MAKEFS_SUPPORT_DEFLATE
+  if (deflateNonSsiFiles) {
+    printf("(Deflated total byte reduction: %d bytes -> %d bytes (%.02f%%)" NEWLINE,
+      (int)overallDataBytes, (int)deflatedBytesReduced, (float)((deflatedBytesReduced*100.0)/overallDataBytes));
+  }
+#endif
+  printf(NEWLINE);
+
+  while (first_file != NULL) {
+     struct file_entry* fe = first_file;
+     first_file = fe->next;
+     free(fe);
+  }
+
+  return 0;
+}
+
+int check_path(char* path, size_t size)
+{
+  size_t slen;
+  if (path[0] == 0) {
+    /* empty */
+    return 0;
+  }
+  slen = strlen(path);
+  if (slen >= size) {
+    /* not NULL-terminated */
+    return 0;
+  }
+  while ((slen > 0) && ((path[slen] == '\\') || (path[slen] == '/'))) {
+    /* path should not end with trailing backslash */
+    path[slen] = 0;
+    slen--;
+  }
+  if (slen == 0) {
+    return 0;
+  }
+  return 1;
+}
+
+static void copy_file(const char *filename_in, FILE *fout)
+{
+  FILE *fin;
+  size_t len;
+  void* buf;
+  fin = fopen(filename_in, "rb");
+  if (fin == NULL) {
+    printf("Failed to open file \"%s\"\n", filename_in);
+    exit(-1);
+  }
+  buf = malloc(COPY_BUFSIZE);
+  while ((len = fread(buf, 1, COPY_BUFSIZE, fin)) > 0) {
+    fwrite(buf, 1, len, fout);
+  }
+  free(buf);
+  fclose(fin);
+}
+
+void concat_files(const char *file1, const char *file2, const char *targetfile)
+{
+  FILE *fout;
+  fout = fopen(targetfile, "wb");
+  if (fout == NULL) {
+    printf("Failed to open file \"%s\"\n", targetfile);
+    exit(-1);
+  }
+  copy_file(file1, fout);
+  copy_file(file2, fout);
+  fclose(fout);
+}
+
+int process_sub(FILE *data_file, FILE *struct_file)
+{
+  FIND_T fInfo;
+  FIND_RET_T fret;
+  int filesProcessed = 0;
+
+  if (processSubs) {
+    /* process subs recursively */
+    size_t sublen = strlen(curSubdir);
+    size_t freelen = sizeof(curSubdir) - sublen - 1;
+    LWIP_ASSERT("sublen < sizeof(curSubdir)", sublen < sizeof(curSubdir));
+    fret = FINDFIRST_DIR("*", &fInfo);
+    if (FINDFIRST_SUCCEEDED(fret)) {
+      do {
+        const char *curName = FIND_T_FILENAME(fInfo);
+        if ((curName[0] == '.') || (strcmp(curName, "CVS") == 0)) {
+          continue;
+        }
+        if (!FIND_T_IS_DIR(fInfo)) {
+          continue;
+        }
+        if (freelen > 0) {
+           CHDIR(curName);
+           strncat(curSubdir, "/", freelen);
+           strncat(curSubdir, curName, freelen - 1);
+           curSubdir[sizeof(curSubdir) - 1] = 0;
+           printf("processing subdirectory %s/..." NEWLINE, curSubdir);
+           filesProcessed += process_sub(data_file, struct_file);
+           CHDIR("..");
+           curSubdir[sublen] = 0;
+        } else {
+           printf("WARNING: cannot process sub due to path length restrictions: \"%s/%s\"\n", curSubdir, curName);
+        }
+      } while (FINDNEXT_SUCCEEDED(FINDNEXT(fret, &fInfo)));
+    }
+  }
+
+  fret = FINDFIRST_FILE("*.*", &fInfo);
+  if (FINDFIRST_SUCCEEDED(fret)) {
+    /* at least one file in directory */
+    do {
+      if (FIND_T_IS_FILE(fInfo)) {
+        const char *curName = FIND_T_FILENAME(fInfo);
+        printf("processing %s/%s..." NEWLINE, curSubdir, curName);
+        if (process_file(data_file, struct_file, curName) < 0) {
+          printf(NEWLINE "Error... aborting" NEWLINE);
+          return -1;
+        }
+        filesProcessed++;
+      }
+    } while (FINDNEXT_SUCCEEDED(FINDNEXT(fret, &fInfo)));
+  }
+  return filesProcessed;
+}
+
+u8_t* get_file_data(const char* filename, int* file_size, int can_be_compressed, int* is_compressed)
+{
+  FILE *inFile;
+  size_t fsize = 0;
+  u8_t* buf;
+  size_t r;
+  int rs;
+  inFile = fopen(filename, "rb");
+  if (inFile == NULL) {
+    printf("Failed to open file \"%s\"\n", filename);
+    exit(-1);
+  }
+  fseek(inFile, 0, SEEK_END);
+  rs = ftell(inFile);
+  if (rs < 0) {
+     printf("ftell failed with %d\n", errno);
+     exit(-1);
+  }
+  fsize = (size_t)rs;
+  fseek(inFile, 0, SEEK_SET);
+  buf = (u8_t*)malloc(fsize);
+  LWIP_ASSERT("buf != NULL", buf != NULL);
+  r = fread(buf, 1, fsize, inFile);
+  *file_size = fsize;
+  *is_compressed = 0;
+#if MAKEFS_SUPPORT_DEFLATE
+  overallDataBytes += fsize;
+  if (deflateNonSsiFiles) {
+    if (can_be_compressed) {
+      if (fsize < OUT_BUF_SIZE) {
+        u8_t* ret_buf;
+        tdefl_status status;
+        size_t in_bytes = fsize;
+        size_t out_bytes = OUT_BUF_SIZE;
+        const void *next_in = buf;
+        void *next_out = s_outbuf;
+        /* create tdefl() compatible flags (we have to compose the low-level flags ourselves, or use tdefl_create_comp_flags_from_zip_params() but that means MINIZ_NO_ZLIB_APIS can't be defined). */
+        mz_uint comp_flags = s_tdefl_num_probes[MZ_MIN(10, deflate_level)] | ((deflate_level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
+        if (!deflate_level) {
+          comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
+        }
+        status = tdefl_init(&g_deflator, NULL, NULL, comp_flags);
+        if (status != TDEFL_STATUS_OKAY) {
+          printf("tdefl_init() failed!\n");
+          exit(-1);
+        }
+        memset(s_outbuf, 0, sizeof(s_outbuf));
+        status = tdefl_compress(&g_deflator, next_in, &in_bytes, next_out, &out_bytes, TDEFL_FINISH);
+        if (status != TDEFL_STATUS_DONE) {
+          printf("deflate failed: %d\n", status);
+          exit(-1);
+        }
+        LWIP_ASSERT("out_bytes <= COPY_BUFSIZE", out_bytes <= OUT_BUF_SIZE);
+        if (out_bytes < fsize) {
+          ret_buf = (u8_t*)malloc(out_bytes);
+          LWIP_ASSERT("ret_buf != NULL", ret_buf != NULL);
+          memcpy(ret_buf, s_outbuf, out_bytes);
+          {
+            /* sanity-check compression be inflating and comparing to the original */
+            tinfl_status dec_status;
+            tinfl_decompressor inflator;
+            size_t dec_in_bytes = out_bytes;
+            size_t dec_out_bytes = OUT_BUF_SIZE;
+            next_out = s_checkbuf;
+
+            tinfl_init(&inflator);
+            memset(s_checkbuf, 0, sizeof(s_checkbuf));
+            dec_status = tinfl_decompress(&inflator, (const mz_uint8 *)ret_buf, &dec_in_bytes, s_checkbuf, (mz_uint8 *)next_out, &dec_out_bytes, 0);
+            LWIP_ASSERT("tinfl_decompress failed", dec_status == TINFL_STATUS_DONE);
+            LWIP_ASSERT("tinfl_decompress size mismatch", fsize == dec_out_bytes);
+            LWIP_ASSERT("decompressed memcmp failed", !memcmp(s_checkbuf, buf, fsize));
+          }
+          /* free original buffer, use compressed data + size */
+          free(buf);
+          buf = ret_buf;
+          *file_size = out_bytes;
+          printf(" - deflate: %d bytes -> %d bytes (%.02f%%)" NEWLINE, (int)fsize, (int)out_bytes, (float)((out_bytes*100.0)/fsize));
+          deflatedBytesReduced += (size_t)(fsize - out_bytes);
+          *is_compressed = 1;
+        } else {
+          printf(" - uncompressed: (would be %d bytes larger using deflate)" NEWLINE, (int)(out_bytes - fsize));
+        }
+      } else {
+        printf(" - uncompressed: (file is larger than deflate bufer)" NEWLINE);
+      }
+    } else {
+      printf(" - SSI file, cannot be compressed" NEWLINE);
+    }
+  }
+#else
+  LWIP_UNUSED_ARG(can_be_compressed);
+#endif
+  fclose(inFile);
+  return buf;
+}
+
+void process_file_data(FILE* data_file, u8_t* file_data, size_t file_size)
+{
+  size_t written, i, src_off=0;
+
+  size_t off = 0;
+  for (i = 0; i < file_size; i++) {
+    LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - 5);
+    sprintf(&file_buffer_c[off], "0x%02.2x,", file_data[i]);
+    off += 5;
+    if ((++src_off % HEX_BYTES_PER_LINE) == 0) {
+      LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - NEWLINE_LEN);
+      memcpy(&file_buffer_c[off], NEWLINE, NEWLINE_LEN);
+      off += NEWLINE_LEN;
+    }
+    if (off + 20 >= sizeof(file_buffer_c)) {
+      written = fwrite(file_buffer_c, 1, off, data_file);
+      LWIP_ASSERT("written == off", written == off);
+      off = 0;
+    }
+  }
+  written = fwrite(file_buffer_c, 1, off, data_file);
+  LWIP_ASSERT("written == off", written == off);
+}
+
+int write_checksums(FILE *struct_file, const char *varname,
+                    u16_t hdr_len, u16_t hdr_chksum, const u8_t* file_data, size_t file_size)
+{
+  int chunk_size = TCP_MSS;
+  int offset, src_offset;
+  size_t len;
+  int i = 0;
+#if LWIP_TCP_TIMESTAMPS
+  /* when timestamps are used, usable space is 12 bytes less per segment */
+  chunk_size -= 12;
+#endif
+
+  fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE);
+  fprintf(struct_file, "const struct fsdata_chksum chksums_%s[] = {" NEWLINE, varname);
+
+  if (hdr_len > 0) {
+    /* add checksum for HTTP header */
+    fprintf(struct_file, "{%d, 0x%04x, %d}," NEWLINE, 0, hdr_chksum, hdr_len);
+    i++;
+  }
+  src_offset = 0;
+  for (offset = hdr_len; ; offset += len) {
+    unsigned short chksum;
+    void* data = (void*)&file_data[src_offset];
+    len = LWIP_MIN(chunk_size, (int)file_size - src_offset);
+    if (len == 0) {
+      break;
+    }
+    chksum = ~inet_chksum(data, (u16_t)len);
+    /* add checksum for data */
+    fprintf(struct_file, "{%d, 0x%04x, %d}," NEWLINE, offset, chksum, len);
+    i++;
+  }
+  fprintf(struct_file, "};" NEWLINE);
+  fprintf(struct_file, "#endif /* HTTPD_PRECALCULATED_CHECKSUM */" NEWLINE);
+  return i;
+}
+
+static int is_valid_char_for_c_var(char x)
+{
+   if (((x >= 'A') && (x <= 'Z')) ||
+       ((x >= 'a') && (x <= 'z')) ||
+       ((x >= '0') && (x <= '9')) ||
+        (x == '_')) {
+      return 1;
+   }
+   return 0;
+}
+
+static void fix_filename_for_c(char* qualifiedName, size_t max_len)
+{
+   struct file_entry* f;
+   size_t len = strlen(qualifiedName);
+   char *new_name = (char*)malloc(len + 2);
+   int filename_ok;
+   int cnt = 0;
+   size_t i;
+   if (len + 3 == max_len) {
+      printf("File name too long: \"%s\"\n", qualifiedName);
+      exit(-1);
+   }
+   strcpy(new_name, qualifiedName);
+   for (i = 0; i < len; i++) {
+      if (!is_valid_char_for_c_var(new_name[i])) {
+         new_name[i] = '_';
+      }
+   }
+   do {
+      filename_ok = 1;
+      for (f = first_file; f != NULL; f = f->next) {
+         if (!strcmp(f->filename_c, new_name)) {
+            filename_ok = 0;
+            cnt++;
+            /* try next unique file name */
+            sprintf(&new_name[len], "%d", cnt);
+            break;
+         }
+      }
+   } while (!filename_ok && (cnt < 999));
+   if (!filename_ok) {
+      printf("Failed to get unique file name: \"%s\"\n", qualifiedName);
+      exit(-1);
+   }
+   strcpy(qualifiedName, new_name);
+   free(new_name);
+}
+
+static void register_filename(const char* qualifiedName)
+{
+   struct file_entry* fe = (struct file_entry*)malloc(sizeof(struct file_entry));
+   fe->filename_c = strdup(qualifiedName);
+   fe->next = NULL;
+   if (first_file == NULL) {
+      first_file = last_file = fe;
+   } else {
+      last_file->next = fe;
+      last_file = fe;
+   }
+}
+
+int is_ssi_file(const char* filename)
+{
+  size_t loop;
+  for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) {
+    if (strstr(filename, g_pcSSIExtensions[loop])) {
+      return 1;
+    }
+  }
+  return 0;
+}
+
+int process_file(FILE *data_file, FILE *struct_file, const char *filename)
+{
+  char varname[MAX_PATH_LEN];
+  int i = 0;
+  char qualifiedName[MAX_PATH_LEN];
+  int file_size;
+  u16_t http_hdr_chksum = 0;
+  u16_t http_hdr_len = 0;
+  int chksum_count = 0;
+  u8_t flags = 0;
+  const char* flags_str;
+  u8_t has_content_len;
+  u8_t* file_data;
+  int is_compressed = 0;
+
+  /* create qualified name (@todo: prepend slash or not?) */
+  sprintf(qualifiedName,"%s/%s", curSubdir, filename);
+  /* create C variable name */
+  strcpy(varname, qualifiedName);
+  /* convert slashes & dots to underscores */
+  fix_filename_for_c(varname, MAX_PATH_LEN);
+  register_filename(varname);
+#if ALIGN_PAYLOAD
+  /* to force even alignment of array, type 1 */
+  fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==1" NEWLINE);
+  fprintf(data_file, "static const " PAYLOAD_ALIGN_TYPE " dummy_align_%s = %d;" NEWLINE, varname, payload_alingment_dummy_counter++);
+  fprintf(data_file, "#endif" NEWLINE);
+#endif /* ALIGN_PAYLOAD */
+  fprintf(data_file, "static const unsigned char FSDATA_ALIGN_PRE data_%s[] FSDATA_ALIGN_POST = {" NEWLINE, varname);
+  /* encode source file name (used by file system, not returned to browser) */
+  fprintf(data_file, "/* %s (%d chars) */" NEWLINE, qualifiedName, strlen(qualifiedName)+1);
+  file_put_ascii(data_file, qualifiedName, strlen(qualifiedName)+1, &i);
+#if ALIGN_PAYLOAD
+  /* pad to even number of bytes to assure payload is on aligned boundary */
+  while(i % PAYLOAD_ALIGNMENT != 0) {
+    fprintf(data_file, "0x%02.2x,", 0);
+    i++;
+  }
+#endif /* ALIGN_PAYLOAD */
+  fprintf(data_file, NEWLINE);
+
+  has_content_len = !is_ssi_file(filename);
+  file_data = get_file_data(filename, &file_size, includeHttpHeader && has_content_len, &is_compressed);
+  if (includeHttpHeader) {
+    file_write_http_header(data_file, filename, file_size, &http_hdr_len, &http_hdr_chksum, has_content_len, is_compressed);
+    flags = FS_FILE_FLAGS_HEADER_INCLUDED;
+    if (has_content_len) {
+      flags |= FS_FILE_FLAGS_HEADER_PERSISTENT;
+    }
+  }
+  if (precalcChksum) {
+    chksum_count = write_checksums(struct_file, varname, http_hdr_len, http_hdr_chksum, file_data, file_size);
+  }
+
+  /* build declaration of struct fsdata_file in temp file */
+  fprintf(struct_file, "const struct fsdata_file file_%s[] = { {" NEWLINE, varname);
+  fprintf(struct_file, "file_%s," NEWLINE, lastFileVar);
+  fprintf(struct_file, "data_%s," NEWLINE, varname);
+  fprintf(struct_file, "data_%s + %d," NEWLINE, varname, i);
+  fprintf(struct_file, "sizeof(data_%s) - %d," NEWLINE, varname, i);
+  switch(flags)
+  {
+  case(FS_FILE_FLAGS_HEADER_INCLUDED):
+     flags_str = "FS_FILE_FLAGS_HEADER_INCLUDED";
+     break;
+  case(FS_FILE_FLAGS_HEADER_PERSISTENT):
+     flags_str = "FS_FILE_FLAGS_HEADER_PERSISTENT";
+     break;
+  case(FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT):
+     flags_str = "FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT";
+     break;
+  default:
+     flags_str = "0";
+     break;
+  }
+  fprintf(struct_file, "%s," NEWLINE, flags_str);
+  if (precalcChksum) {
+    fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE);
+    fprintf(struct_file, "%d, chksums_%s," NEWLINE, chksum_count, varname);
+    fprintf(struct_file, "#endif /* HTTPD_PRECALCULATED_CHECKSUM */" NEWLINE);
+  }
+  fprintf(struct_file, "}};" NEWLINE NEWLINE);
+  strcpy(lastFileVar, varname);
+
+  /* write actual file contents */
+  i = 0;
+  fprintf(data_file, NEWLINE "/* raw file data (%d bytes) */" NEWLINE, file_size);
+  process_file_data(data_file, file_data, file_size);
+  fprintf(data_file, "};" NEWLINE NEWLINE);
+  free(file_data);
+  return 0;
+}
+
+int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len,
+                           u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed)
+{
+  int i = 0;
+  int response_type = HTTP_HDR_OK;
+  const char* file_type;
+  const char *cur_string;
+  size_t cur_len;
+  int written = 0;
+  size_t hdr_len = 0;
+  u16_t acc;
+  const char *file_ext;
+  int j;
+  u8_t provide_last_modified = includeLastModified;
+
+  memset(hdr_buf, 0, sizeof(hdr_buf));
+
+  if (useHttp11) {
+    response_type = HTTP_HDR_OK_11;
+  }
+
+  fprintf(data_file, NEWLINE "/* HTTP header */");
+  if (strstr(filename, "404") == filename) {
+    response_type = HTTP_HDR_NOT_FOUND;
+    if (useHttp11) {
+      response_type = HTTP_HDR_NOT_FOUND_11;
+    }
+  } else if (strstr(filename, "400") == filename) {
+    response_type = HTTP_HDR_BAD_REQUEST;
+    if (useHttp11) {
+      response_type = HTTP_HDR_BAD_REQUEST_11;
+    }
+  } else if (strstr(filename, "501") == filename) {
+    response_type = HTTP_HDR_NOT_IMPL;
+    if (useHttp11) {
+      response_type = HTTP_HDR_NOT_IMPL_11;
+    }
+  }
+  cur_string = g_psHTTPHeaderStrings[response_type];
+  cur_len = strlen(cur_string);
+  fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len);
+  written += file_put_ascii(data_file, cur_string, cur_len, &i);
+  i = 0;
+  if (precalcChksum) {
+    memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+    hdr_len += cur_len;
+  }
+
+  cur_string = serverID;
+  cur_len = strlen(cur_string);
+  fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len);
+  written += file_put_ascii(data_file, cur_string, cur_len, &i);
+  i = 0;
+  if (precalcChksum) {
+    memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+    hdr_len += cur_len;
+  }
+
+  file_ext = filename;
+  if (file_ext != NULL) {
+    while(strstr(file_ext, ".") != NULL) {
+      file_ext = strstr(file_ext, ".");
+      file_ext++;
+    }
+  }
+  if ((file_ext == NULL) || (*file_ext == 0)) {
+    printf("failed to get extension for file \"%s\", using default.\n", filename);
+    file_type = HTTP_HDR_DEFAULT_TYPE;
+  } else {
+    file_type = NULL;
+    for (j = 0; j < NUM_HTTP_HEADERS; j++) {
+      if (!strcmp(file_ext, g_psHTTPHeaders[j].extension)) {
+        file_type = g_psHTTPHeaders[j].content_type;
+        break;
+      }
+    }
+    if (file_type == NULL) {
+      printf("failed to get file type for extension \"%s\", using default.\n", file_ext);
+      file_type = HTTP_HDR_DEFAULT_TYPE;
+    }
+  }
+
+  /* Content-Length is used for persistent connections in HTTP/1.1 but also for
+     download progress in older versions
+     @todo: just use a big-enough buffer and let the HTTPD send spaces? */
+  if (provide_content_len) {
+    char intbuf[MAX_PATH_LEN];
+    int content_len = file_size;
+    memset(intbuf, 0, sizeof(intbuf));
+    cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH];
+    cur_len = strlen(cur_string);
+    fprintf(data_file, NEWLINE "/* \"%s%d\r\n\" (%d+ bytes) */" NEWLINE, cur_string, content_len, cur_len+2);
+    written += file_put_ascii(data_file, cur_string, cur_len, &i);
+    if (precalcChksum) {
+      memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+      hdr_len += cur_len;
+    }
+
+    _itoa(content_len, intbuf, 10);
+    strcat(intbuf, "\r\n");
+    cur_len = strlen(intbuf);
+    written += file_put_ascii(data_file, intbuf, cur_len, &i);
+    i = 0;
+    if (precalcChksum) {
+      memcpy(&hdr_buf[hdr_len], intbuf, cur_len);
+      hdr_len += cur_len;
+    }
+  }
+  if (provide_last_modified) {
+    char modbuf[256];
+    struct stat stat_data;
+    struct tm* t;
+    memset(modbuf, 0, sizeof(modbuf));
+    memset(&stat_data, 0, sizeof(stat_data));
+    cur_string = modbuf;
+    strcpy(modbuf, "Last-Modified: ");
+    if (stat(filename, &stat_data) != 0) {
+       printf("stat(%s) failed with error %d\n", filename, errno);
+       exit(-1);
+    }
+    t = gmtime(&stat_data.st_mtime);
+    if (t == NULL) {
+       printf("gmtime() failed with error %d\n", errno);
+       exit(-1);
+    }
+    strftime(&modbuf[15], sizeof(modbuf)-15, "%a, %d %b %Y %H:%M:%S GMT", t);
+    cur_len = strlen(cur_string);
+    fprintf(data_file, NEWLINE "/* \"%s\"\r\n\" (%d+ bytes) */" NEWLINE, cur_string, cur_len+2);
+    written += file_put_ascii(data_file, cur_string, cur_len, &i);
+    if (precalcChksum) {
+      memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+      hdr_len += cur_len;
+    }
+
+    modbuf[0] = 0;
+    strcat(modbuf, "\r\n");
+    cur_len = strlen(modbuf);
+    written += file_put_ascii(data_file, modbuf, cur_len, &i);
+    i = 0;
+    if (precalcChksum) {
+      memcpy(&hdr_buf[hdr_len], modbuf, cur_len);
+      hdr_len += cur_len;
+    }
+  }
+
+  /* HTTP/1.1 implements persistent connections */
+  if (useHttp11) {
+    if (provide_content_len) {
+      cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_KEEPALIVE];
+    } else {
+      /* no Content-Length available, so a persistent connection is no possible
+         because the client does not know the data length */
+      cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_CLOSE];
+    }
+    cur_len = strlen(cur_string);
+    fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len);
+    written += file_put_ascii(data_file, cur_string, cur_len, &i);
+    i = 0;
+    if (precalcChksum) {
+      memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+      hdr_len += cur_len;
+    }
+  }
+
+#if MAKEFS_SUPPORT_DEFLATE
+  if (is_compressed) {
+    /* tell the client about the deflate encoding */
+    LWIP_ASSERT("error", deflateNonSsiFiles);
+    cur_string = "Content-Encoding: deflate\r\n";
+    cur_len = strlen(cur_string);
+    fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len);
+    written += file_put_ascii(data_file, cur_string, cur_len, &i);
+    i = 0;
+  }
+#else
+  LWIP_UNUSED_ARG(is_compressed);
+#endif
+
+  /* write content-type, ATTENTION: this includes the double-CRLF! */
+  cur_string = file_type;
+  cur_len = strlen(cur_string);
+  fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len);
+  written += file_put_ascii(data_file, cur_string, cur_len, &i);
+  i = 0;
+
+  /* ATTENTION: headers are done now (double-CRLF has been written!) */
+
+  if (precalcChksum) {
+    memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+    hdr_len += cur_len;
+
+    LWIP_ASSERT("hdr_len <= 0xffff", hdr_len <= 0xffff);
+    LWIP_ASSERT("strlen(hdr_buf) == hdr_len", strlen(hdr_buf) == hdr_len);
+    acc = ~inet_chksum(hdr_buf, (u16_t)hdr_len);
+    *http_hdr_len = (u16_t)hdr_len;
+    *http_hdr_chksum = acc;
+  }
+
+  return written;
+}
+
+int file_put_ascii(FILE *file, const char* ascii_string, int len, int *i)
+{
+  int x;
+  for (x = 0; x < len; x++) {
+    unsigned char cur = ascii_string[x];
+    fprintf(file, "0x%02.2x,", cur);
+    if ((++(*i) % HEX_BYTES_PER_LINE) == 0) {
+      fprintf(file, NEWLINE);
+    }
+  }
+  return len;
+}
+
+int s_put_ascii(char *buf, const char *ascii_string, int len, int *i)
+{
+  int x;
+  int idx = 0;
+  for (x = 0; x < len; x++) {
+    unsigned char cur = ascii_string[x];
+    sprintf(&buf[idx], "0x%02.2x,", cur);
+    idx += 5;
+    if ((++(*i) % HEX_BYTES_PER_LINE) == 0) {
+      sprintf(&buf[idx], NEWLINE);
+      idx += NEWLINE_LEN;
+    }
+  }
+  return len;
+}
diff --git a/src/apps/httpd/makefsdata/readme.txt b/src/apps/httpd/makefsdata/readme.txt
new file mode 100644
index 0000000..3768585
--- /dev/null
+++ b/src/apps/httpd/makefsdata/readme.txt
@@ -0,0 +1,13 @@
+This directory contains a script ('makefsdata') to create C code suitable for
+httpd for given html pages (or other files) in a directory.
+
+There is also a plain C console application doing the same and extended a bit.
+
+Usage: htmlgen [targetdir] [-s] [-i]s
+   targetdir: relative or absolute path to files to convert
+   switch -s: toggle processing of subdirectories (default is on)
+   switch -e: exclude HTTP header from file (header is created at runtime, default is on)
+   switch -11: include HTTP 1.1 header (1.0 is default)
+
+  if targetdir not specified, makefsdata will attempt to
+  process files in subdirectory 'fs'.
diff --git a/src/apps/lwiperf/lwiperf.c b/src/apps/lwiperf/lwiperf.c
new file mode 100644
index 0000000..efabe47
--- /dev/null
+++ b/src/apps/lwiperf/lwiperf.c
@@ -0,0 +1,661 @@
+/**
+ * @file
+ * lwIP iPerf server implementation
+ */
+
+/**
+ * @defgroup iperf Iperf server
+ * @ingroup apps
+ *
+ * This is a simple performance measuring server to check your bandwith using
+ * iPerf2 on a PC as client.
+ * It is currently a minimal implementation providing an IPv4 TCP server only.
+ *
+ * @todo: implement UDP mode and IPv6
+ */
+
+/*
+ * Copyright (c) 2014 Simon Goldschmidt
+ * 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/apps/lwiperf.h"
+
+#include "lwip/tcp.h"
+#include "lwip/sys.h"
+
+#include <string.h>
+
+/* Currently, only TCP-over-IPv4 is implemented (does iperf support IPv6 anyway?) */
+#if LWIP_IPV4 && LWIP_TCP && LWIP_CALLBACK_API
+
+/** Specify the idle timeout (in seconds) after that the test fails */
+#ifndef LWIPERF_TCP_MAX_IDLE_SEC
+#define LWIPERF_TCP_MAX_IDLE_SEC    10U
+#endif
+#if LWIPERF_TCP_MAX_IDLE_SEC > 255
+#error LWIPERF_TCP_MAX_IDLE_SEC must fit into an u8_t
+#endif
+
+/* File internal memory allocation (struct lwiperf_*): this defaults to
+   the heap */
+#ifndef LWIPERF_ALLOC
+#define LWIPERF_ALLOC(type)         mem_malloc(sizeof(type))
+#define LWIPERF_FREE(type, item)    mem_free(item)
+#endif
+
+/** If this is 1, check that received data has the correct format */
+#ifndef LWIPERF_CHECK_RX_DATA
+#define LWIPERF_CHECK_RX_DATA       0
+#endif
+
+/** This is the Iperf settings struct sent from the client */
+typedef struct _lwiperf_settings {
+#define LWIPERF_FLAGS_ANSWER_TEST 0x80000000
+#define LWIPERF_FLAGS_ANSWER_NOW  0x00000001
+  u32_t flags;
+  u32_t num_threads; /* unused for now */
+  u32_t remote_port;
+  u32_t buffer_len; /* unused for now */
+  u32_t win_band; /* TCP window / UDP rate: unused for now */
+  u32_t amount; /* pos. value: bytes?; neg. values: time (unit is 10ms: 1/100 second) */
+} lwiperf_settings_t;
+
+/** Basic connection handle */
+struct _lwiperf_state_base;
+typedef struct _lwiperf_state_base lwiperf_state_base_t;
+struct _lwiperf_state_base {
+  /* 1=tcp, 0=udp */
+  u8_t tcp;
+  /* 1=server, 0=client */
+  u8_t server;
+  lwiperf_state_base_t* next;
+  lwiperf_state_base_t* related_server_state;
+};
+
+/** Connection handle for a TCP iperf session */
+typedef struct _lwiperf_state_tcp {
+  lwiperf_state_base_t base;
+  struct tcp_pcb* server_pcb;
+  struct tcp_pcb* conn_pcb;
+  u32_t time_started;
+  lwiperf_report_fn report_fn;
+  void* report_arg;
+  u8_t poll_count;
+  u8_t next_num;
+  u32_t bytes_transferred;
+  lwiperf_settings_t settings;
+  u8_t have_settings_buf;
+} lwiperf_state_tcp_t;
+
+/** List of active iperf sessions */
+static lwiperf_state_base_t* lwiperf_all_connections;
+/** A const buffer to send from: we want to measure sending, not copying! */
+static const u8_t lwiperf_txbuf_const[1600] = {
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+  '0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9',
+};
+
+static err_t lwiperf_tcp_poll(void *arg, struct tcp_pcb *tpcb);
+static void lwiperf_tcp_err(void *arg, err_t err);
+
+/** Add an iperf session to the 'active' list */
+static void
+lwiperf_list_add(lwiperf_state_base_t* item)
+{
+  if (lwiperf_all_connections == NULL) {
+    lwiperf_all_connections = item;
+  } else {
+    item = lwiperf_all_connections;
+  }
+}
+
+/** Remove an iperf session from the 'active' list */
+static void
+lwiperf_list_remove(lwiperf_state_base_t* item)
+{
+  lwiperf_state_base_t* prev = NULL;
+  lwiperf_state_base_t* iter;
+  for (iter = lwiperf_all_connections; iter != NULL; prev = iter, iter = iter->next) {
+    if (iter == item) {
+      if (prev == NULL) {
+        lwiperf_all_connections = iter->next;
+      } else {
+        prev->next = item;
+      }
+      /* @debug: ensure this item is listed only once */
+      for (iter = iter->next; iter != NULL; iter = iter->next) {
+        LWIP_ASSERT("duplicate entry", iter != item);
+      }
+      break;
+    }
+  }
+}
+
+/** Call the report function of an iperf tcp session */
+static void
+lwip_tcp_conn_report(lwiperf_state_tcp_t* conn, enum lwiperf_report_type report_type)
+{
+  if ((conn != NULL) && (conn->report_fn != NULL)) {
+    u32_t now, duration_ms, bandwidth_kbitpsec;
+    now = sys_now();
+    duration_ms = now - conn->time_started;
+    if (duration_ms == 0) {
+      bandwidth_kbitpsec = 0;
+    } else {
+      bandwidth_kbitpsec = (conn->bytes_transferred / duration_ms) * 8U;
+    }
+    conn->report_fn(conn->report_arg, report_type,
+      &conn->conn_pcb->local_ip, conn->conn_pcb->local_port,
+      &conn->conn_pcb->remote_ip, conn->conn_pcb->remote_port,
+      conn->bytes_transferred, duration_ms, bandwidth_kbitpsec);
+  }
+}
+
+/** Close an iperf tcp session */
+static void
+lwiperf_tcp_close(lwiperf_state_tcp_t* conn, enum lwiperf_report_type report_type)
+{
+  err_t err;
+
+  lwip_tcp_conn_report(conn, report_type);
+  lwiperf_list_remove(&conn->base);
+  if (conn->conn_pcb != NULL) {
+    tcp_arg(conn->conn_pcb, NULL);
+    tcp_poll(conn->conn_pcb, NULL, 0);
+    tcp_sent(conn->conn_pcb, NULL);
+    tcp_recv(conn->conn_pcb, NULL);
+    tcp_err(conn->conn_pcb, NULL);
+    err = tcp_close(conn->conn_pcb);
+    if (err != ERR_OK) {
+      /* don't want to wait for free memory here... */
+      tcp_abort(conn->conn_pcb);
+    }
+  } else {
+    /* no conn pcb, this is the server pcb */
+    err = tcp_close(conn->server_pcb);
+    LWIP_ASSERT("error", err != ERR_OK);
+  }
+  LWIPERF_FREE(lwiperf_state_tcp_t, conn);
+}
+
+/** Try to send more data on an iperf tcp session */
+static err_t
+lwiperf_tcp_client_send_more(lwiperf_state_tcp_t* conn)
+{
+  int send_more;
+  err_t err;
+  u16_t txlen;
+  u16_t txlen_max;
+  void* txptr;
+  u8_t apiflags;
+
+  LWIP_ASSERT("conn invalid", (conn != NULL) && conn->base.tcp && (conn->base.server == 0));
+
+  do {
+    send_more = 0;
+    if (conn->settings.amount & PP_HTONL(0x80000000)) {
+      /* this session is time-limited */
+      u32_t now = sys_now();
+      u32_t diff_ms = now - conn->time_started;
+      u32_t time = (u32_t)-(s32_t)lwip_htonl(conn->settings.amount);
+      u32_t time_ms = time * 10;
+      if (diff_ms >= time_ms) {
+        /* time specified by the client is over -> close the connection */
+        lwiperf_tcp_close(conn, LWIPERF_TCP_DONE_CLIENT);
+        return ERR_OK;
+      }
+    } else {
+      /* this session is byte-limited */
+      u32_t amount_bytes = lwip_htonl(conn->settings.amount);
+      /* @todo: this can send up to 1*MSS more than requested... */
+      if (amount_bytes >= conn->bytes_transferred) {
+        /* all requested bytes transferred -> close the connection */
+        lwiperf_tcp_close(conn, LWIPERF_TCP_DONE_CLIENT);
+        return ERR_OK;
+      }
+    }
+
+    if (conn->bytes_transferred < 24) {
+      /* transmit the settings a first time */
+      txptr = &((u8_t*)&conn->settings)[conn->bytes_transferred];
+      txlen_max = (u16_t)(24 - conn->bytes_transferred);
+      apiflags = TCP_WRITE_FLAG_COPY;
+    } else if (conn->bytes_transferred < 48) {
+      /* transmit the settings a second time */
+      txptr = &((u8_t*)&conn->settings)[conn->bytes_transferred - 24];
+      txlen_max = (u16_t)(48 - conn->bytes_transferred);
+      apiflags = TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE;
+      send_more = 1;
+    } else {
+      /* transmit data */
+      /* @todo: every x bytes, transmit the settings again */
+      txptr = LWIP_CONST_CAST(void*, &lwiperf_txbuf_const[conn->bytes_transferred % 10]);
+      txlen_max = TCP_MSS;
+      if (conn->bytes_transferred == 48) { /* @todo: fix this for intermediate settings, too */
+        txlen_max = TCP_MSS - 24;
+      }
+      apiflags = 0; /* no copying needed */
+      send_more = 1;
+    }
+    txlen = txlen_max;
+    do {
+      err = tcp_write(conn->conn_pcb, txptr, txlen, apiflags);
+      if (err ==  ERR_MEM) {
+        txlen /= 2;
+      }
+    } while ((err == ERR_MEM) && (txlen >= (TCP_MSS/2)));
+
+    if (err == ERR_OK) {
+      conn->bytes_transferred += txlen;
+    } else {
+      send_more = 0;
+    }
+  } while(send_more);
+
+  tcp_output(conn->conn_pcb);
+  return ERR_OK;
+}
+
+/** TCP sent callback, try to send more data */
+static err_t
+lwiperf_tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
+{
+  lwiperf_state_tcp_t* conn = (lwiperf_state_tcp_t*)arg;
+  /* @todo: check 'len' (e.g. to time ACK of all data)? for now, we just send more... */
+  LWIP_ASSERT("invalid conn", conn->conn_pcb == tpcb);
+  LWIP_UNUSED_ARG(tpcb);
+  LWIP_UNUSED_ARG(len);
+
+  conn->poll_count = 0;
+
+  return lwiperf_tcp_client_send_more(conn);
+}
+
+/** TCP connected callback (active connection), send data now */
+static err_t
+lwiperf_tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
+{
+  lwiperf_state_tcp_t* conn = (lwiperf_state_tcp_t*)arg;
+  LWIP_ASSERT("invalid conn", conn->conn_pcb == tpcb);
+  LWIP_UNUSED_ARG(tpcb);
+  if (err != ERR_OK) {
+    lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_REMOTE);
+    return ERR_OK;
+  }
+  conn->poll_count = 0;
+  conn->time_started = sys_now();
+  return lwiperf_tcp_client_send_more(conn);
+}
+
+/** Start TCP connection back to the client (either parallel or after the
+ * receive test has finished.
+ */
+static err_t
+lwiperf_tx_start(lwiperf_state_tcp_t* conn)
+{
+  err_t err;
+  lwiperf_state_tcp_t* client_conn;
+  struct tcp_pcb* newpcb;
+  ip_addr_t remote_addr;
+  u16_t remote_port;
+
+  client_conn = (lwiperf_state_tcp_t*)LWIPERF_ALLOC(lwiperf_state_tcp_t);
+  if (client_conn == NULL) {
+    return ERR_MEM;
+  }
+  newpcb = tcp_new();
+  if (newpcb == NULL) {
+    LWIPERF_FREE(lwiperf_state_tcp_t, client_conn);
+    return ERR_MEM;
+  }
+
+  MEMCPY(client_conn, conn, sizeof(lwiperf_state_tcp_t));
+  client_conn->base.server = 0;
+  client_conn->server_pcb = NULL;
+  client_conn->conn_pcb = newpcb;
+  client_conn->time_started = sys_now(); /* @todo: set this again on 'connected' */
+  client_conn->poll_count = 0;
+  client_conn->next_num = 4; /* initial nr is '4' since the header has 24 byte */
+  client_conn->bytes_transferred = 0;
+  client_conn->settings.flags = 0; /* prevent the remote side starting back as client again */
+
+  tcp_arg(newpcb, client_conn);
+  tcp_sent(newpcb, lwiperf_tcp_client_sent);
+  tcp_poll(newpcb, lwiperf_tcp_poll, 2U);
+  tcp_err(newpcb, lwiperf_tcp_err);
+
+  ip_addr_copy(remote_addr, conn->conn_pcb->remote_ip);
+  remote_port = (u16_t)lwip_htonl(client_conn->settings.remote_port);
+
+  err = tcp_connect(newpcb, &remote_addr, remote_port, lwiperf_tcp_client_connected);
+  if (err != ERR_OK) {
+    lwiperf_tcp_close(client_conn, LWIPERF_TCP_ABORTED_LOCAL);
+    return err;
+  }
+  lwiperf_list_add(&client_conn->base);
+  return ERR_OK;
+}
+
+/** Receive data on an iperf tcp session */
+static err_t
+lwiperf_tcp_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
+{
+  u8_t tmp;
+  u16_t tot_len;
+  u32_t packet_idx;
+  struct pbuf* q;
+  lwiperf_state_tcp_t* conn = (lwiperf_state_tcp_t*)arg;
+
+  LWIP_ASSERT("pcb mismatch", conn->conn_pcb == tpcb);
+  LWIP_UNUSED_ARG(tpcb);
+
+  if (err != ERR_OK) {
+    lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_REMOTE);
+    return ERR_OK;
+  }
+  if (p == NULL) {
+    /* connection closed -> test done */
+    if ((conn->settings.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST|LWIPERF_FLAGS_ANSWER_NOW)) ==
+        PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST)) {
+      /* client requested transmission after end of test */
+      lwiperf_tx_start(conn);
+    }
+    lwiperf_tcp_close(conn, LWIPERF_TCP_DONE_SERVER);
+    return ERR_OK;
+  }
+  tot_len = p->tot_len;
+
+  conn->poll_count = 0;
+
+  if ((!conn->have_settings_buf) || ((conn->bytes_transferred -24) % (1024*128) == 0)) {
+    /* wait for 24-byte header */
+    if (p->tot_len < sizeof(lwiperf_settings_t)) {
+      lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL_DATAERROR);
+      pbuf_free(p);
+      return ERR_VAL;
+    }
+    if (!conn->have_settings_buf) {
+      if (pbuf_copy_partial(p, &conn->settings, sizeof(lwiperf_settings_t), 0) != sizeof(lwiperf_settings_t)) {
+        lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL);
+        pbuf_free(p);
+        return ERR_VAL;
+      }
+      conn->have_settings_buf = 1;
+      if ((conn->settings.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST|LWIPERF_FLAGS_ANSWER_NOW)) ==
+        PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST|LWIPERF_FLAGS_ANSWER_NOW)) {
+          /* client requested parallel transmission test */
+          err_t err2 = lwiperf_tx_start(conn);
+          if (err2 != ERR_OK) {
+            lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL_TXERROR);
+            pbuf_free(p);
+            return err2;
+          }
+      }
+    } else {
+      if (pbuf_memcmp(p, 0, &conn->settings, sizeof(lwiperf_settings_t)) != 0) {
+        lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL_DATAERROR);
+        pbuf_free(p);
+        return ERR_VAL;
+      }
+    }
+    conn->bytes_transferred += sizeof(lwiperf_settings_t);
+    if (conn->bytes_transferred <= 24) {
+      conn->time_started = sys_now();
+      tcp_recved(tpcb, p->tot_len);
+      pbuf_free(p);
+      return ERR_OK;
+    }
+    conn->next_num = 4; /* 24 bytes received... */
+    tmp = pbuf_header(p, -24);
+    LWIP_ASSERT("pbuf_header failed", tmp == 0);
+  }
+
+  packet_idx = 0;
+  for (q = p; q != NULL; q = q->next) {
+#if LWIPERF_CHECK_RX_DATA
+    const u8_t* payload = (const u8_t*)q->payload;
+    u16_t i;
+    for (i = 0; i < q->len; i++) {
+      u8_t val = payload[i];
+      u8_t num = val - '0';
+      if (num == conn->next_num) {
+        conn->next_num++;
+        if (conn->next_num == 10) {
+          conn->next_num = 0;
+        }
+      } else {
+        lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL_DATAERROR);
+        pbuf_free(p);
+        return ERR_VAL;
+      }
+    }
+#endif
+    packet_idx += q->len;
+  }
+  LWIP_ASSERT("count mismatch", packet_idx == p->tot_len);
+  conn->bytes_transferred += packet_idx;
+  tcp_recved(tpcb, tot_len);
+  pbuf_free(p);
+  return ERR_OK;
+}
+
+/** Error callback, iperf tcp session aborted */
+static void
+lwiperf_tcp_err(void *arg, err_t err)
+{
+  lwiperf_state_tcp_t* conn = (lwiperf_state_tcp_t*)arg;
+  LWIP_UNUSED_ARG(err);
+  lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_REMOTE);
+}
+
+/** TCP poll callback, try to send more data */
+static err_t
+lwiperf_tcp_poll(void *arg, struct tcp_pcb *tpcb)
+{
+  lwiperf_state_tcp_t* conn = (lwiperf_state_tcp_t*)arg;
+  LWIP_ASSERT("pcb mismatch", conn->conn_pcb == tpcb);
+  LWIP_UNUSED_ARG(tpcb);
+  if (++conn->poll_count >= LWIPERF_TCP_MAX_IDLE_SEC) {
+    lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL);
+    return ERR_OK; /* lwiperf_tcp_close frees conn */
+  }
+
+  if (!conn->base.server) {
+    lwiperf_tcp_client_send_more(conn);
+  }
+
+  return ERR_OK;
+}
+
+/** This is called when a new client connects for an iperf tcp session */
+static err_t
+lwiperf_tcp_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
+{
+  lwiperf_state_tcp_t *s, *conn;
+  if ((err != ERR_OK) || (newpcb == NULL) || (arg == NULL)) {
+    return ERR_VAL;
+  }
+
+  s = (lwiperf_state_tcp_t*)arg;
+  conn = (lwiperf_state_tcp_t*)LWIPERF_ALLOC(lwiperf_state_tcp_t);
+  if (conn == NULL) {
+    return ERR_MEM;
+  }
+  memset(conn, 0, sizeof(lwiperf_state_tcp_t));
+  conn->base.tcp = 1;
+  conn->base.server = 1;
+  conn->base.related_server_state = &s->base;
+  conn->server_pcb = s->server_pcb;
+  conn->conn_pcb = newpcb;
+  conn->time_started = sys_now();
+  conn->report_fn = s->report_fn;
+  conn->report_arg = s->report_arg;
+
+  /* setup the tcp rx connection */
+  tcp_arg(newpcb, conn);
+  tcp_recv(newpcb, lwiperf_tcp_recv);
+  tcp_poll(newpcb, lwiperf_tcp_poll, 2U);
+  tcp_err(conn->conn_pcb, lwiperf_tcp_err);
+
+  lwiperf_list_add(&conn->base);
+  return ERR_OK;
+}
+
+/** 
+ * @ingroup iperf
+ * Start a TCP iperf server on the default TCP port (5001) and listen for
+ * incoming connections from iperf clients.
+ *
+ * @returns a connection handle that can be used to abort the server
+ *          by calling @ref lwiperf_abort()
+ */
+void*
+lwiperf_start_tcp_server_default(lwiperf_report_fn report_fn, void* report_arg)
+{
+  return lwiperf_start_tcp_server(IP_ADDR_ANY, LWIPERF_TCP_PORT_DEFAULT,
+    report_fn, report_arg);
+}
+
+/**
+ * @ingroup iperf
+ * Start a TCP iperf server on a specific IP address and port and listen for
+ * incoming connections from iperf clients.
+ *
+ * @returns a connection handle that can be used to abort the server
+ *          by calling @ref lwiperf_abort()
+ */
+void*
+lwiperf_start_tcp_server(const ip_addr_t* local_addr, u16_t local_port,
+  lwiperf_report_fn report_fn, void* report_arg)
+{
+  err_t err;
+  struct tcp_pcb* pcb;
+  lwiperf_state_tcp_t* s;
+
+  if (local_addr == NULL) {
+    return NULL;
+  }
+
+  s = (lwiperf_state_tcp_t*)LWIPERF_ALLOC(lwiperf_state_tcp_t);
+  if (s == NULL) {
+    return NULL;
+  }
+  memset(s, 0, sizeof(lwiperf_state_tcp_t));
+  s->base.tcp = 1;
+  s->base.server = 1;
+  s->report_fn = report_fn;
+  s->report_arg = report_arg;
+
+  pcb = tcp_new();
+  if (pcb != NULL) {
+    err = tcp_bind(pcb, local_addr, local_port);
+    if (err == ERR_OK) {
+      s->server_pcb = tcp_listen_with_backlog(pcb, 1);
+    }
+  }
+  if (s->server_pcb == NULL) {
+    if (pcb != NULL) {
+      tcp_close(pcb);
+    }
+    LWIPERF_FREE(lwiperf_state_tcp_t, s);
+    return NULL;
+  }
+  pcb = NULL;
+
+  tcp_arg(s->server_pcb, s);
+  tcp_accept(s->server_pcb, lwiperf_tcp_accept);
+
+  lwiperf_list_add(&s->base);
+  return s;
+}
+
+/**
+ * @ingroup iperf
+ * Abort an iperf session (handle returned by lwiperf_start_tcp_server*())
+ */
+void
+lwiperf_abort(void* lwiperf_session)
+{
+  lwiperf_state_base_t* i, *dealloc, *last = NULL;
+
+  for (i = lwiperf_all_connections; i != NULL; ) {
+    if ((i == lwiperf_session) || (i->related_server_state == lwiperf_session)) {
+      dealloc = i;
+      i = i->next;
+      if (last != NULL) {
+        last->next = i;
+      }
+      LWIPERF_FREE(lwiperf_state_tcp_t, dealloc); /* @todo: type? */
+    } else {
+      last = i;
+      i = i->next;
+    }
+  }
+}
+
+#endif /* LWIP_IPV4 && LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/mdns/mdns.c b/src/apps/mdns/mdns.c
new file mode 100644
index 0000000..14334fc
--- /dev/null
+++ b/src/apps/mdns/mdns.c
@@ -0,0 +1,2028 @@
+/**
+ * @file
+ * MDNS responder implementation
+ *
+ * @defgroup mdns MDNS
+ * @ingroup apps
+ *
+ * RFC 6762 - Multicast DNS\n
+ * RFC 6763 - DNS-Based Service Discovery\n
+ * 
+ * @verbinclude mdns.txt
+ * 
+ * Things left to implement:
+ * -------------------------
+ *
+ * - Probing/conflict resolution
+ * - Sending goodbye messages (zero ttl) - shutdown, DHCP lease about to expire, DHCP turned off...
+ * - Checking that source address of unicast requests are on the same network
+ * - Limiting multicast responses to 1 per second per resource record
+ * - Fragmenting replies if required
+ * - Subscribe to netif address/link change events and act on them (currently needs to be done manually)
+ * - Handling multi-packet known answers
+ * - Individual known answer detection for all local IPv6 addresses
+ * - Dynamic size of outgoing packet
+ */
+
+/*
+ * Copyright (c) 2015 Verisure Innovation AB
+ * 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: Erik Ekman <erik@kryo.se>
+ *
+ */
+
+#include "lwip/apps/mdns.h"
+#include "lwip/apps/mdns_priv.h"
+#include "lwip/netif.h"
+#include "lwip/udp.h"
+#include "lwip/ip_addr.h"
+#include "lwip/mem.h"
+#include "lwip/prot/dns.h"
+
+#include <string.h>
+
+#if LWIP_MDNS_RESPONDER
+
+#if (LWIP_IPV4 && !LWIP_IGMP)
+  #error "If you want to use MDNS with IPv4, you have to define LWIP_IGMP=1 in your lwipopts.h"
+#endif
+#if (LWIP_IPV6 && !LWIP_IPV6_MLD)
+#error "If you want to use MDNS with IPv6, you have to define LWIP_IPV6_MLD=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP)
+  #error "If you want to use MDNS, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+
+#if LWIP_IPV4
+#include "lwip/igmp.h"
+/* IPv4 multicast group 224.0.0.251 */
+static const ip_addr_t v4group = DNS_MQUERY_IPV4_GROUP_INIT;
+#endif
+
+#if LWIP_IPV6
+#include "lwip/mld6.h"
+/* IPv6 multicast group FF02::FB */
+static const ip_addr_t v6group = DNS_MQUERY_IPV6_GROUP_INIT;
+#endif
+
+#define MDNS_PORT 5353
+#define MDNS_TTL  255
+
+/* Stored offsets to beginning of domain names
+ * Used for compression.
+ */
+#define NUM_DOMAIN_OFFSETS 10
+#define DOMAIN_JUMP_SIZE 2
+#define DOMAIN_JUMP 0xc000
+
+static u8_t mdns_netif_client_id;
+static struct udp_pcb *mdns_pcb;
+
+#define NETIF_TO_HOST(netif) (struct mdns_host*)(netif_get_client_data(netif, mdns_netif_client_id))
+
+#define TOPDOMAIN_LOCAL "local"
+
+#define REVERSE_PTR_TOPDOMAIN "arpa"
+#define REVERSE_PTR_V4_DOMAIN "in-addr"
+#define REVERSE_PTR_V6_DOMAIN "ip6"
+
+#define SRV_PRIORITY 0
+#define SRV_WEIGHT   0
+
+/* Payload size allocated for each outgoing UDP packet */
+#define OUTPACKET_SIZE 500
+
+/* Lookup from hostname -> IPv4 */
+#define REPLY_HOST_A            0x01
+/* Lookup from IPv4/v6 -> hostname */
+#define REPLY_HOST_PTR_V4       0x02
+/* Lookup from hostname -> IPv6 */
+#define REPLY_HOST_AAAA         0x04
+/* Lookup from hostname -> IPv6 */
+#define REPLY_HOST_PTR_V6       0x08
+
+/* Lookup for service types */
+#define REPLY_SERVICE_TYPE_PTR  0x10
+/* Lookup for instances of service */
+#define REPLY_SERVICE_NAME_PTR  0x20
+/* Lookup for location of service instance */
+#define REPLY_SERVICE_SRV       0x40
+/* Lookup for text info on service instance */
+#define REPLY_SERVICE_TXT       0x80
+
+static const char *dnssd_protos[] = {
+    "_udp", /* DNSSD_PROTO_UDP */
+    "_tcp", /* DNSSD_PROTO_TCP */
+};
+
+/** Description of a service */
+struct mdns_service {
+  /** TXT record to answer with */
+  struct mdns_domain txtdata;
+  /** Name of service, like 'myweb' */
+  char name[MDNS_LABEL_MAXLEN + 1];
+  /** Type of service, like '_http' */
+  char service[MDNS_LABEL_MAXLEN + 1];
+  /** Callback function and userdata
+   * to update txtdata buffer */
+  service_get_txt_fn_t txt_fn;
+  void *txt_userdata;
+  /** TTL in seconds of SRV/TXT replies */
+  u32_t dns_ttl;
+  /** Protocol, TCP or UDP */
+  u16_t proto;
+  /** Port of the service */
+  u16_t port;
+};
+
+/** Description of a host/netif */
+struct mdns_host {
+  /** Hostname */
+  char name[MDNS_LABEL_MAXLEN + 1];
+  /** Pointer to services */
+  struct mdns_service *services[MDNS_MAX_SERVICES];
+  /** TTL in seconds of A/AAAA/PTR replies */
+  u32_t dns_ttl;
+};
+
+/** Information about received packet */
+struct mdns_packet {
+  /** Sender IP/port */
+  ip_addr_t source_addr;
+  u16_t source_port;
+  /** If packet was received unicast */
+  u16_t recv_unicast;
+  /** Netif that received the packet */
+  struct netif *netif;
+  /** Packet data */
+  struct pbuf *pbuf;
+  /** Current parsing offset in packet */
+  u16_t parse_offset;
+  /** Identifier. Used in legacy queries */
+  u16_t tx_id;
+  /** Number of questions in packet,
+   *  read from packet header */
+  u16_t questions;
+  /** Number of unparsed questions */
+  u16_t questions_left;
+  /** Number of answers in packet,
+   *  (sum of normal, authorative and additional answers)
+   *  read from packet header */
+  u16_t answers;
+  /** Number of unparsed answers */
+  u16_t answers_left;
+};
+
+/** Information about outgoing packet */
+struct mdns_outpacket {
+  /** Netif to send the packet on */
+  struct netif *netif;
+  /** Packet data */
+  struct pbuf *pbuf;
+  /** Current write offset in packet */
+  u16_t write_offset;
+  /** Identifier. Used in legacy queries */
+  u16_t tx_id;
+  /** Destination IP/port if sent unicast */
+  ip_addr_t dest_addr;
+  u16_t dest_port;
+  /** Number of questions written */
+  u16_t questions;
+  /** Number of normal answers written */
+  u16_t answers;
+  /** Number of additional answers written */
+  u16_t additional;
+  /** Offsets for written domain names in packet.
+   *  Used for compression */
+  u16_t domain_offsets[NUM_DOMAIN_OFFSETS];
+  /** If all answers in packet should set cache_flush bit */
+  u8_t cache_flush;
+  /** If reply should be sent unicast */
+  u8_t unicast_reply;
+  /** If legacy query. (tx_id needed, and write
+   *  question again in reply before answer) */
+  u8_t legacy_query;
+  /* Reply bitmask for host information */
+  u8_t host_replies;
+  /* Bitmask for which reverse IPv6 hosts to answer */
+  u8_t host_reverse_v6_replies;
+  /* Reply bitmask per service */
+  u8_t serv_replies[MDNS_MAX_SERVICES];
+};
+
+/** Domain, type and class.
+ *  Shared between questions and answers */
+struct mdns_rr_info {
+  struct mdns_domain domain;
+  u16_t type;
+  u16_t klass;
+};
+
+struct mdns_question {
+  struct mdns_rr_info info;
+  /** unicast reply requested */
+  u16_t unicast;
+};
+
+struct mdns_answer {
+  struct mdns_rr_info info;
+  /** cache flush command bit */
+  u16_t cache_flush;
+  /* Validity time in seconds */
+  u32_t ttl;
+  /** Length of variable answer */
+  u16_t rd_length;
+  /** Offset of start of variable answer in packet */
+  u16_t rd_offset;
+};
+
+/**
+ * Add a label part to a domain
+ * @param domain The domain to add a label to
+ * @param label The label to add, like &lt;hostname&gt;, 'local', 'com' or ''
+ * @param len The length of the label
+ * @return ERR_OK on success, an err_t otherwise if label too long
+ */
+err_t
+mdns_domain_add_label(struct mdns_domain *domain, const char *label, u8_t len)
+{
+  if (len > MDNS_LABEL_MAXLEN) {
+    return ERR_VAL;
+  }
+  if (len > 0 && (1 + len + domain->length >= MDNS_DOMAIN_MAXLEN)) {
+    return ERR_VAL;
+  }
+  /* Allow only zero marker on last byte */
+  if (len == 0 && (1 + domain->length > MDNS_DOMAIN_MAXLEN)) {
+    return ERR_VAL;
+  }
+  domain->name[domain->length] = len;
+  domain->length++;
+  if (len) {
+    MEMCPY(&domain->name[domain->length], label, len);
+    domain->length += len;
+  }
+  return ERR_OK;
+}
+
+/**
+ * Internal readname function with max 6 levels of recursion following jumps
+ * while decompressing name
+ */
+static u16_t
+mdns_readname_loop(struct pbuf *p, u16_t offset, struct mdns_domain *domain, unsigned depth)
+{
+  u8_t c;
+
+  do {
+    if (depth > 5) {
+      /* Too many jumps */
+      return MDNS_READNAME_ERROR;
+    }
+
+    c = pbuf_get_at(p, offset);
+    offset++;
+
+    /* is this a compressed label? */
+    if((c & 0xc0) == 0xc0) {
+      u16_t jumpaddr;
+      if (offset >= p->tot_len) {
+        /* Make sure both jump bytes fit in the packet */
+        return MDNS_READNAME_ERROR;
+      }
+      jumpaddr = (((c & 0x3f) << 8) | (pbuf_get_at(p, offset) & 0xff));
+      offset++;
+      if (jumpaddr >= SIZEOF_DNS_HDR && jumpaddr < p->tot_len) {
+        u16_t res;
+      /* Recursive call, maximum depth will be checked */
+        res = mdns_readname_loop(p, jumpaddr, domain, depth + 1);
+        /* Dont return offset since new bytes were not read (jumped to somewhere in packet) */
+        if (res == MDNS_READNAME_ERROR) {
+          return res;
+        }
+      } else {
+        return MDNS_READNAME_ERROR;
+      }
+      break;
+    }
+
+    /* normal label */
+    if (c <= MDNS_LABEL_MAXLEN) {
+      u8_t label[MDNS_LABEL_MAXLEN];
+      err_t res;
+
+      if (c + domain->length >= MDNS_DOMAIN_MAXLEN) {
+        return MDNS_READNAME_ERROR;
+      }
+      if (c != 0) {
+        if (pbuf_copy_partial(p, label, c, offset) != c) {
+          return MDNS_READNAME_ERROR;
+        }
+        offset += c;
+      }
+      res = mdns_domain_add_label(domain, (char *) label, c);
+      if (res != ERR_OK) {
+        return MDNS_READNAME_ERROR;
+      }
+    } else {
+      /* bad length byte */
+      return MDNS_READNAME_ERROR;
+    }
+  } while (c != 0);
+
+  return offset;
+}
+
+/**
+ * Read possibly compressed domain name from packet buffer
+ * @param p The packet
+ * @param offset start position of domain name in packet
+ * @param domain The domain name destination
+ * @return The new offset after the domain, or MDNS_READNAME_ERROR
+ *         if reading failed
+ */
+u16_t
+mdns_readname(struct pbuf *p, u16_t offset, struct mdns_domain *domain)
+{
+  memset(domain, 0, sizeof(struct mdns_domain));
+  return mdns_readname_loop(p, offset, domain, 0);
+}
+
+/**
+ * Print domain name to debug output
+ * @param domain The domain name
+ */
+static void
+mdns_domain_debug_print(struct mdns_domain *domain)
+{
+  u8_t *src = domain->name;
+  u8_t i;
+
+  while (*src) {
+    u8_t label_len = *src;
+    src++;
+    for (i = 0; i < label_len; i++) {
+      LWIP_DEBUGF(MDNS_DEBUG, ("%c", src[i]));
+    }
+    src += label_len;
+    LWIP_DEBUGF(MDNS_DEBUG, ("."));
+  }
+}
+
+/**
+ * Return 1 if contents of domains match (case-insensitive)
+ * @param a Domain name to compare 1
+ * @param b Domain name to compare 2
+ * @return 1 if domains are equal ignoring case, 0 otherwise
+ */
+int
+mdns_domain_eq(struct mdns_domain *a, struct mdns_domain *b)
+{
+  u8_t *ptra, *ptrb;
+  u8_t len;
+  int res;
+
+  if (a->length != b->length) {
+    return 0;
+  }
+
+  ptra = a->name;
+  ptrb = b->name;
+  while (*ptra && *ptrb && ptra < &a->name[a->length]) {
+    if (*ptra != *ptrb) {
+      return 0;
+    }
+    len = *ptra;
+    ptra++;
+    ptrb++;
+    res = lwip_strnicmp((char *) ptra, (char *) ptrb, len);
+    if (res != 0) {
+      return 0;
+    }
+    ptra += len;
+    ptrb += len;
+  }
+  if (*ptra != *ptrb && ptra < &a->name[a->length]) {
+    return 0;
+  }
+  return 1;
+}
+
+/**
+ * Call user supplied function to setup TXT data
+ * @param service The service to build TXT record for
+ */
+static void
+mdns_prepare_txtdata(struct mdns_service *service)
+{
+  memset(&service->txtdata, 0, sizeof(struct mdns_domain));
+  if (service->txt_fn) {
+    service->txt_fn(service, service->txt_userdata);
+  }
+}
+
+#if LWIP_IPV4
+/**
+ * Build domain for reverse lookup of IPv4 address
+ * like 12.0.168.192.in-addr.arpa. for 192.168.0.12
+ * @param domain Where to write the domain name
+ * @param addr Pointer to an IPv4 address to encode
+ * @return ERR_OK if domain was written, an err_t otherwise
+ */
+static err_t
+mdns_build_reverse_v4_domain(struct mdns_domain *domain, const ip4_addr_t *addr)
+{
+  int i;
+  err_t res;
+  const u8_t *ptr;
+  if (!domain || !addr) {
+    return ERR_ARG;
+  }
+  memset(domain, 0, sizeof(struct mdns_domain));
+  ptr = (const u8_t *) addr;
+  for (i = sizeof(ip4_addr_t) - 1; i >= 0; i--) {
+    char buf[4];
+    u8_t val = ptr[i];
+
+    lwip_itoa(buf, sizeof(buf), val);
+    res = mdns_domain_add_label(domain, buf, (u8_t)strlen(buf));
+    LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res);
+  }
+  res = mdns_domain_add_label(domain, REVERSE_PTR_V4_DOMAIN, (u8_t)(sizeof(REVERSE_PTR_V4_DOMAIN)-1));
+  LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res);
+  res = mdns_domain_add_label(domain, REVERSE_PTR_TOPDOMAIN, (u8_t)(sizeof(REVERSE_PTR_TOPDOMAIN)-1));
+  LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res);
+  res = mdns_domain_add_label(domain, NULL, 0);
+  LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res);
+
+  return ERR_OK;
+}
+#endif
+
+#if LWIP_IPV6
+/**
+ * Build domain for reverse lookup of IP address
+ * like b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. for 2001:db8::567:89ab
+ * @param domain Where to write the domain name
+ * @param addr Pointer to an IPv6 address to encode
+ * @return ERR_OK if domain was written, an err_t otherwise
+ */
+static err_t
+mdns_build_reverse_v6_domain(struct mdns_domain *domain, const ip6_addr_t *addr)
+{
+  int i;
+  err_t res;
+  const u8_t *ptr;
+  if (!domain || !addr) {
+    return ERR_ARG;
+  }
+  memset(domain, 0, sizeof(struct mdns_domain));
+  ptr = (const u8_t *) addr;
+  for (i = sizeof(ip6_addr_t) - 1; i >= 0; i--) {
+    char buf;
+    u8_t byte = ptr[i];
+    int j;
+    for (j = 0; j < 2; j++) {
+      if ((byte & 0x0F) < 0xA) {
+        buf = '0' + (byte & 0x0F);
+      } else {
+        buf = 'a' + (byte & 0x0F) - 0xA;
+      }
+      res = mdns_domain_add_label(domain, &buf, sizeof(buf));
+      LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res);
+      byte >>= 4;
+    }
+  }
+  res = mdns_domain_add_label(domain, REVERSE_PTR_V6_DOMAIN, (u8_t)(sizeof(REVERSE_PTR_V6_DOMAIN)-1));
+  LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res);
+  res = mdns_domain_add_label(domain, REVERSE_PTR_TOPDOMAIN, (u8_t)(sizeof(REVERSE_PTR_TOPDOMAIN)-1));
+  LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res);
+  res = mdns_domain_add_label(domain, NULL, 0);
+  LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res);
+
+  return ERR_OK;
+}
+#endif
+
+/* Add .local. to domain */
+static err_t
+mdns_add_dotlocal(struct mdns_domain *domain)
+{
+  err_t res = mdns_domain_add_label(domain, TOPDOMAIN_LOCAL, (u8_t)(sizeof(TOPDOMAIN_LOCAL)-1));
+  LWIP_ERROR("mdns_add_dotlocal: Failed to add label", (res == ERR_OK), return res);
+  return mdns_domain_add_label(domain, NULL, 0);
+}
+
+/**
+ * Build the <hostname>.local. domain name
+ * @param domain Where to write the domain name
+ * @param mdns TMDNS netif descriptor.
+ * @return ERR_OK if domain <hostname>.local. was written, an err_t otherwise
+ */
+static err_t
+mdns_build_host_domain(struct mdns_domain *domain, struct mdns_host *mdns)
+{
+  err_t res;
+  memset(domain, 0, sizeof(struct mdns_domain));
+  LWIP_ERROR("mdns_build_host_domain: mdns != NULL", (mdns != NULL), return ERR_VAL);
+  res = mdns_domain_add_label(domain, mdns->name, (u8_t)strlen(mdns->name));
+  LWIP_ERROR("mdns_build_host_domain: Failed to add label", (res == ERR_OK), return res);
+  return mdns_add_dotlocal(domain);
+}
+
+/**
+ * Build the lookup-all-services special DNS-SD domain name
+ * @param domain Where to write the domain name
+ * @return ERR_OK if domain _services._dns-sd._udp.local. was written, an err_t otherwise
+ */
+static err_t
+mdns_build_dnssd_domain(struct mdns_domain *domain)
+{
+  err_t res;
+  memset(domain, 0, sizeof(struct mdns_domain));
+  res = mdns_domain_add_label(domain, "_services", (u8_t)(sizeof("_services")-1));
+  LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res);
+  res = mdns_domain_add_label(domain, "_dns-sd", (u8_t)(sizeof("_dns-sd")-1));
+  LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res);
+  res = mdns_domain_add_label(domain, dnssd_protos[DNSSD_PROTO_UDP], (u8_t)strlen(dnssd_protos[DNSSD_PROTO_UDP]));
+  LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res);
+  return mdns_add_dotlocal(domain);
+}
+
+/**
+ * Build domain name for a service
+ * @param domain Where to write the domain name
+ * @param service The service struct, containing service name, type and protocol
+ * @param include_name Whether to include the service name in the domain
+ * @return ERR_OK if domain was written. If service name is included,
+ *         <name>.<type>.<proto>.local. will be written, otherwise <type>.<proto>.local.
+ *         An err_t is returned on error.
+ */
+static err_t
+mdns_build_service_domain(struct mdns_domain *domain, struct mdns_service *service, int include_name)
+{
+  err_t res;
+  memset(domain, 0, sizeof(struct mdns_domain));
+  if (include_name) {
+    res = mdns_domain_add_label(domain, service->name, (u8_t)strlen(service->name));
+    LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res);
+  }
+  res = mdns_domain_add_label(domain, service->service, (u8_t)strlen(service->service));
+  LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res);
+  res = mdns_domain_add_label(domain, dnssd_protos[service->proto], (u8_t)strlen(dnssd_protos[service->proto]));
+  LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res);
+  return mdns_add_dotlocal(domain);
+}
+
+/**
+ * Check which replies we should send for a host/netif based on question
+ * @param netif The network interface that received the question
+ * @param rr Domain/type/class from a question
+ * @param reverse_v6_reply Bitmask of which IPv6 addresses to send reverse PTRs for
+ *                         if reply bit has REPLY_HOST_PTR_V6 set
+ * @return Bitmask of which replies to send
+ */
+static int
+check_host(struct netif *netif, struct mdns_rr_info *rr, u8_t *reverse_v6_reply)
+{
+  err_t res;
+  int replies = 0;
+  struct mdns_domain mydomain;
+
+  LWIP_UNUSED_ARG(reverse_v6_reply); /* if ipv6 is disabled */
+
+  if (rr->klass != DNS_RRCLASS_IN && rr->klass != DNS_RRCLASS_ANY) {
+    /* Invalid class */
+    return replies;
+  }
+
+  /* Handle PTR for our addresses */
+  if (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY) {
+#if LWIP_IPV6
+    int i;
+    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) {
+        res = mdns_build_reverse_v6_domain(&mydomain, netif_ip6_addr(netif, i));
+        if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
+          replies |= REPLY_HOST_PTR_V6;
+          /* Mark which addresses where requested */
+          if (reverse_v6_reply) {
+            *reverse_v6_reply |= (1 << i);
+          }
+        }
+      }
+    }
+#endif
+#if LWIP_IPV4
+    if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+      res = mdns_build_reverse_v4_domain(&mydomain, netif_ip4_addr(netif));
+      if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
+        replies |= REPLY_HOST_PTR_V4;
+      }
+    }
+#endif
+  }
+
+  res = mdns_build_host_domain(&mydomain, NETIF_TO_HOST(netif));
+  /* Handle requests for our hostname */
+  if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
+    /* TODO return NSEC if unsupported protocol requested */
+#if LWIP_IPV4
+    if (!ip4_addr_isany_val(*netif_ip4_addr(netif))
+        && (rr->type == DNS_RRTYPE_A || rr->type == DNS_RRTYPE_ANY)) {
+      replies |= REPLY_HOST_A;
+    }
+#endif
+#if LWIP_IPV6
+    if (rr->type == DNS_RRTYPE_AAAA || rr->type == DNS_RRTYPE_ANY) {
+      replies |= REPLY_HOST_AAAA;
+    }
+#endif
+  }
+
+  return replies;
+}
+
+/**
+ * Check which replies we should send for a service based on question
+ * @param service A registered MDNS service
+ * @param rr Domain/type/class from a question
+ * @return Bitmask of which replies to send
+ */
+static int
+check_service(struct mdns_service *service, struct mdns_rr_info *rr)
+{
+  err_t res;
+  int replies = 0;
+  struct mdns_domain mydomain;
+
+  if (rr->klass != DNS_RRCLASS_IN && rr->klass != DNS_RRCLASS_ANY) {
+    /* Invalid class */
+    return 0;
+  }
+
+  res = mdns_build_dnssd_domain(&mydomain);
+  if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain) &&
+      (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY)) {
+    /* Request for all service types */
+    replies |= REPLY_SERVICE_TYPE_PTR;
+  }
+
+  res = mdns_build_service_domain(&mydomain, service, 0);
+  if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain) &&
+      (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY)) {
+    /* Request for the instance of my service */
+    replies |= REPLY_SERVICE_NAME_PTR;
+  }
+
+  res = mdns_build_service_domain(&mydomain, service, 1);
+  if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
+    /* Request for info about my service */
+    if (rr->type == DNS_RRTYPE_SRV || rr->type == DNS_RRTYPE_ANY) {
+      replies |= REPLY_SERVICE_SRV;
+    }
+    if (rr->type == DNS_RRTYPE_TXT || rr->type == DNS_RRTYPE_ANY) {
+      replies |= REPLY_SERVICE_TXT;
+    }
+  }
+
+  return replies;
+}
+
+/**
+ * Return bytes needed to write before jump for best result of compressing supplied domain
+ * against domain in outpacket starting at specified offset.
+ * If a match is found, offset is updated to where to jump to
+ * @param pbuf Pointer to pbuf with the partially constructed DNS packet
+ * @param offset Start position of a domain written earlier. If this location is suitable
+ *               for compression, the pointer is updated to where in the domain to jump to.
+ * @param domain The domain to write
+ * @return Number of bytes to write of the new domain before writing a jump to the offset.
+ *         If compression can not be done against this previous domain name, the full new
+ *         domain length is returned.
+ */
+u16_t
+mdns_compress_domain(struct pbuf *pbuf, u16_t *offset, struct mdns_domain *domain)
+{
+  struct mdns_domain target;
+  u16_t target_end;
+  u8_t target_len;
+  u8_t writelen = 0;
+  u8_t *ptr;
+  if (pbuf == NULL) {
+    return domain->length;
+  }
+  target_end = mdns_readname(pbuf, *offset, &target);
+  if (target_end == MDNS_READNAME_ERROR) {
+    return domain->length;
+  }
+  target_len = (u8_t)(target_end - *offset);
+  ptr = domain->name;
+  while (writelen < domain->length) {
+    u8_t domainlen = (u8_t)(domain->length - writelen);
+    u8_t labellen;
+    if (domainlen <= target.length && domainlen > DOMAIN_JUMP_SIZE) {
+      /* Compare domains if target is long enough, and we have enough left of the domain */
+      u8_t targetpos = (u8_t)(target.length - domainlen);
+      if ((targetpos + DOMAIN_JUMP_SIZE) >= target_len) {
+        /* We are checking at or beyond a jump in the original, stop looking */
+        break;
+      }
+      if (target.length >= domainlen &&
+          memcmp(&domain->name[writelen], &target.name[targetpos], domainlen) == 0) {
+        *offset += targetpos;
+        return writelen;
+      }
+    }
+    /* Skip to next label in domain */
+    labellen = *ptr;
+    writelen += 1 + labellen;
+    ptr += 1 + labellen;
+  }
+  /* Nothing found */
+  return domain->length;
+}
+
+/**
+ * Write domain to outpacket. Compression will be attempted,
+ * unless domain->skip_compression is set.
+ * @param outpkt The outpacket to write to
+ * @param domain The domain name to write
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_write_domain(struct mdns_outpacket *outpkt, struct mdns_domain *domain)
+{
+  int i;
+  err_t res;
+  u16_t writelen = domain->length;
+  u16_t jump_offset = 0;
+  u16_t jump;
+
+  if (!domain->skip_compression) {
+    for (i = 0; i < NUM_DOMAIN_OFFSETS; ++i) {
+      u16_t offset = outpkt->domain_offsets[i];
+      if (offset) {
+        u16_t len = mdns_compress_domain(outpkt->pbuf, &offset, domain);
+        if (len < writelen) {
+          writelen = len;
+          jump_offset = offset;
+        }
+      }
+    }
+  }
+
+  if (writelen) {
+    /* Write uncompressed part of name */
+    res = pbuf_take_at(outpkt->pbuf, domain->name, writelen, outpkt->write_offset);
+    if (res != ERR_OK) {
+      return res;
+    }
+
+    /* Store offset of this new domain */
+    for (i = 0; i < NUM_DOMAIN_OFFSETS; ++i) {
+      if (outpkt->domain_offsets[i] == 0) {
+        outpkt->domain_offsets[i] = outpkt->write_offset;
+        break;
+      }
+    }
+
+    outpkt->write_offset += writelen;
+  }
+  if (jump_offset) {
+    /* Write jump */
+    jump = lwip_htons(DOMAIN_JUMP | jump_offset);
+    res = pbuf_take_at(outpkt->pbuf, &jump, DOMAIN_JUMP_SIZE, outpkt->write_offset);
+    if (res != ERR_OK) {
+      return res;
+    }
+    outpkt->write_offset += DOMAIN_JUMP_SIZE;
+  }
+  return ERR_OK;
+}
+
+/**
+ * Write a question to an outpacket
+ * A question contains domain, type and class. Since an answer also starts with these fields this function is also
+ * called from mdns_add_answer().
+ * @param outpkt The outpacket to write to
+ * @param domain The domain name the answer is for
+ * @param type The DNS type of the answer (like 'AAAA', 'SRV')
+ * @param klass The DNS type of the answer (like 'IN')
+ * @param unicast If highest bit in class should be set, to instruct the responder to
+ *                reply with a unicast packet
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_add_question(struct mdns_outpacket *outpkt, struct mdns_domain *domain, u16_t type, u16_t klass, u16_t unicast)
+{
+  u16_t question_len;
+  u16_t field16;
+  err_t res;
+
+  if (!outpkt->pbuf) {
+    /* If no pbuf is active, allocate one */
+    outpkt->pbuf = pbuf_alloc(PBUF_TRANSPORT, OUTPACKET_SIZE, PBUF_RAM);
+    if (!outpkt->pbuf) {
+      return ERR_MEM;
+    }
+    outpkt->write_offset = SIZEOF_DNS_HDR;
+  }
+
+  /* Worst case calculation. Domain string might be compressed */
+  question_len = domain->length + sizeof(type) + sizeof(klass);
+  if (outpkt->write_offset + question_len > outpkt->pbuf->tot_len) {
+    /* No space */
+    return ERR_MEM;
+  }
+
+  /* Write name */
+  res = mdns_write_domain(outpkt, domain);
+  if (res != ERR_OK) {
+    return res;
+  }
+
+  /* Write type */
+  field16 = lwip_htons(type);
+  res = pbuf_take_at(outpkt->pbuf, &field16, sizeof(field16), outpkt->write_offset);
+  if (res != ERR_OK) {
+    return res;
+  }
+  outpkt->write_offset += sizeof(field16);
+
+  /* Write class */
+  if (unicast) {
+    klass |= 0x8000;
+  }
+  field16 = lwip_htons(klass);
+  res = pbuf_take_at(outpkt->pbuf, &field16, sizeof(field16), outpkt->write_offset);
+  if (res != ERR_OK) {
+    return res;
+  }
+  outpkt->write_offset += sizeof(field16);
+
+  return ERR_OK;
+}
+
+/**
+ * Write answer to reply packet.
+ * buf or answer_domain can be null. The rd_length written will be buf_length +
+ * size of (compressed) domain. Most uses will need either buf or answer_domain,
+ * special case is SRV that starts with 3 u16 and then a domain name.
+ * @param reply The outpacket to write to
+ * @param domain The domain name the answer is for
+ * @param type The DNS type of the answer (like 'AAAA', 'SRV')
+ * @param klass The DNS type of the answer (like 'IN')
+ * @param cache_flush If highest bit in class should be set, to instruct receiver that
+ *                    this reply replaces any earlier answer for this domain/type/class
+ * @param ttl Validity time in seconds to send out for IP address data in DNS replies
+ * @param buf Pointer to buffer of answer data
+ * @param buf_length Length of variable data
+ * @param answer_domain A domain to write after any buffer data as answer
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_add_answer(struct mdns_outpacket *reply, struct mdns_domain *domain, u16_t type, u16_t klass, u16_t cache_flush,
+                u32_t ttl, const u8_t *buf, size_t buf_length, struct mdns_domain *answer_domain)
+{
+  u16_t answer_len;
+  u16_t field16;
+  u16_t rdlen_offset;
+  u16_t answer_offset;
+  u32_t field32;
+  err_t res;
+
+  if (!reply->pbuf) {
+    /* If no pbuf is active, allocate one */
+    reply->pbuf = pbuf_alloc(PBUF_TRANSPORT, OUTPACKET_SIZE, PBUF_RAM);
+    if (!reply->pbuf) {
+      return ERR_MEM;
+    }
+    reply->write_offset = SIZEOF_DNS_HDR;
+  }
+
+  /* Worst case calculation. Domain strings might be compressed */
+  answer_len = domain->length + sizeof(type) + sizeof(klass) + sizeof(ttl) + sizeof(field16)/*rd_length*/;
+  if (buf) {
+    answer_len += (u16_t)buf_length;
+  }
+  if (answer_domain) {
+    answer_len += answer_domain->length;
+  }
+  if (reply->write_offset + answer_len > reply->pbuf->tot_len) {
+    /* No space */
+    return ERR_MEM;
+  }
+
+  /* Answer starts with same data as question, then more fields */
+  mdns_add_question(reply, domain, type, klass, cache_flush);
+
+  /* Write TTL */
+  field32 = lwip_htonl(ttl);
+  res = pbuf_take_at(reply->pbuf, &field32, sizeof(field32), reply->write_offset);
+  if (res != ERR_OK) {
+    return res;
+  }
+  reply->write_offset += sizeof(field32);
+
+  /* Store offsets and skip forward to the data */
+  rdlen_offset = reply->write_offset;
+  reply->write_offset += sizeof(field16);
+  answer_offset = reply->write_offset;
+
+  if (buf) {
+    /* Write static data */
+    res = pbuf_take_at(reply->pbuf, buf, (u16_t)buf_length, reply->write_offset);
+    if (res != ERR_OK) {
+      return res;
+    }
+    reply->write_offset += (u16_t)buf_length;
+  }
+
+  if (answer_domain) {
+    /* Write name answer (compressed if possible) */
+    res = mdns_write_domain(reply, answer_domain);
+    if (res != ERR_OK) {
+      return res;
+    }
+  }
+
+  /* Write rd_length after when we know the answer size */
+  field16 = lwip_htons(reply->write_offset - answer_offset);
+  res = pbuf_take_at(reply->pbuf, &field16, sizeof(field16), rdlen_offset);
+
+  return res;
+}
+
+/**
+ * Helper function for mdns_read_question/mdns_read_answer
+ * Reads a domain, type and class from the packet
+ * @param pkt The MDNS packet to read from. The parse_offset field will be
+ *            incremented to point to the next unparsed byte.
+ * @param info The struct to fill with domain, type and class
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_read_rr_info(struct mdns_packet *pkt, struct mdns_rr_info *info)
+{
+  u16_t field16, copied;
+  pkt->parse_offset = mdns_readname(pkt->pbuf, pkt->parse_offset, &info->domain);
+  if (pkt->parse_offset == MDNS_READNAME_ERROR) {
+    return ERR_VAL;
+  }
+
+  copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset);
+  if (copied != sizeof(field16)) {
+    return ERR_VAL;
+  }
+  pkt->parse_offset += copied;
+  info->type = lwip_ntohs(field16);
+
+  copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset);
+  if (copied != sizeof(field16)) {
+    return ERR_VAL;
+  }
+  pkt->parse_offset += copied;
+  info->klass = lwip_ntohs(field16);
+
+  return ERR_OK;
+}
+
+/**
+ * Read a question from the packet.
+ * All questions have to be read before the answers.
+ * @param pkt The MDNS packet to read from. The questions_left field will be decremented
+ *            and the parse_offset will be updated.
+ * @param question The struct to fill with question data
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_read_question(struct mdns_packet *pkt, struct mdns_question *question)
+{
+  /* Safety check */
+  if (pkt->pbuf->tot_len < pkt->parse_offset) {
+    return ERR_VAL;
+  }
+
+  if (pkt->questions_left) {
+    err_t res;
+    pkt->questions_left--;
+
+    memset(question, 0, sizeof(struct mdns_question));
+    res = mdns_read_rr_info(pkt, &question->info);
+    if (res != ERR_OK) {
+      return res;
+    }
+
+    /* Extract unicast flag from class field */
+    question->unicast = question->info.klass & 0x8000;
+    question->info.klass &= 0x7FFF;
+
+    return ERR_OK;
+  }
+  return ERR_VAL;
+}
+
+/**
+ * Read an answer from the packet
+ * The variable length reply is not copied, its pbuf offset and length is stored instead.
+ * @param pkt The MDNS packet to read. The answers_left field will be decremented and
+ *            the parse_offset will be updated.
+ * @param answer The struct to fill with answer data
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer)
+{
+  /* Read questions first */
+  if (pkt->questions_left) {
+    return ERR_VAL;
+  }
+
+  /* Safety check */
+  if (pkt->pbuf->tot_len < pkt->parse_offset) {
+    return ERR_VAL;
+  }
+
+  if (pkt->answers_left) {
+    u16_t copied, field16;
+    u32_t ttl;
+    err_t res;
+    pkt->answers_left--;
+
+    memset(answer, 0, sizeof(struct mdns_answer));
+    res = mdns_read_rr_info(pkt, &answer->info);
+    if (res != ERR_OK) {
+      return res;
+    }
+
+    /* Extract cache_flush flag from class field */
+    answer->cache_flush = answer->info.klass & 0x8000;
+    answer->info.klass &= 0x7FFF;
+
+    copied = pbuf_copy_partial(pkt->pbuf, &ttl, sizeof(ttl), pkt->parse_offset);
+    if (copied != sizeof(ttl)) {
+      return ERR_VAL;
+    }
+    pkt->parse_offset += copied;
+    answer->ttl = lwip_ntohl(ttl);
+
+    copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset);
+    if (copied != sizeof(field16)) {
+      return ERR_VAL;
+    }
+    pkt->parse_offset += copied;
+    answer->rd_length = lwip_ntohs(field16);
+
+    answer->rd_offset = pkt->parse_offset;
+    pkt->parse_offset += answer->rd_length;
+
+    return ERR_OK;
+  }
+  return ERR_VAL;
+}
+
+#if LWIP_IPV4
+/** Write an IPv4 address (A) RR to outpacket */
+static err_t
+mdns_add_a_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct netif *netif)
+{
+  struct mdns_domain host;
+  mdns_build_host_domain(&host, NETIF_TO_HOST(netif));
+  LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with A record\n"));
+  return mdns_add_answer(reply, &host, DNS_RRTYPE_A, DNS_RRCLASS_IN, cache_flush, (NETIF_TO_HOST(netif))->dns_ttl, (const u8_t *) netif_ip4_addr(netif), sizeof(ip4_addr_t), NULL);
+}
+
+/** Write a 4.3.2.1.in-addr.arpa -> hostname.local PTR RR to outpacket */
+static err_t
+mdns_add_hostv4_ptr_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct netif *netif)
+{
+  struct mdns_domain host, revhost;
+  mdns_build_host_domain(&host, NETIF_TO_HOST(netif));
+  mdns_build_reverse_v4_domain(&revhost, netif_ip4_addr(netif));
+  LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v4 PTR record\n"));
+  return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, cache_flush, (NETIF_TO_HOST(netif))->dns_ttl, NULL, 0, &host);
+}
+#endif
+
+#if LWIP_IPV6
+/** Write an IPv6 address (AAAA) RR to outpacket */
+static err_t
+mdns_add_aaaa_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct netif *netif, int addrindex)
+{
+  struct mdns_domain host;
+  mdns_build_host_domain(&host, NETIF_TO_HOST(netif));
+  LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with AAAA record\n"));
+  return mdns_add_answer(reply, &host, DNS_RRTYPE_AAAA, DNS_RRCLASS_IN, cache_flush, (NETIF_TO_HOST(netif))->dns_ttl, (const u8_t *) netif_ip6_addr(netif, addrindex), sizeof(ip6_addr_t), NULL);
+}
+
+/** Write a x.y.z.ip6.arpa -> hostname.local PTR RR to outpacket */
+static err_t
+mdns_add_hostv6_ptr_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct netif *netif, int addrindex)
+{
+  struct mdns_domain host, revhost;
+  mdns_build_host_domain(&host, NETIF_TO_HOST(netif));
+  mdns_build_reverse_v6_domain(&revhost, netif_ip6_addr(netif, addrindex));
+  LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v6 PTR record\n"));
+  return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, cache_flush, (NETIF_TO_HOST(netif))->dns_ttl, NULL, 0, &host);
+}
+#endif
+
+/** Write an all-services -> servicetype PTR RR to outpacket */
+static err_t
+mdns_add_servicetype_ptr_answer(struct mdns_outpacket *reply, struct mdns_service *service)
+{
+  struct mdns_domain service_type, service_dnssd;
+  mdns_build_service_domain(&service_type, service, 0);
+  mdns_build_dnssd_domain(&service_dnssd);
+  LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service type PTR record\n"));
+  return mdns_add_answer(reply, &service_dnssd, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0, service->dns_ttl, NULL, 0, &service_type);
+}
+
+/** Write a servicetype -> servicename PTR RR to outpacket */
+static err_t
+mdns_add_servicename_ptr_answer(struct mdns_outpacket *reply, struct mdns_service *service)
+{
+  struct mdns_domain service_type, service_instance;
+  mdns_build_service_domain(&service_type, service, 0);
+  mdns_build_service_domain(&service_instance, service, 1);
+  LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service name PTR record\n"));
+  return mdns_add_answer(reply, &service_type, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0, service->dns_ttl, NULL, 0, &service_instance);
+}
+
+/** Write a SRV RR to outpacket */
+static err_t
+mdns_add_srv_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct mdns_host *mdns, struct mdns_service *service)
+{
+  struct mdns_domain service_instance, srvhost;
+  u16_t srvdata[3];
+  mdns_build_service_domain(&service_instance, service, 1);
+  mdns_build_host_domain(&srvhost, mdns);
+  if (reply->legacy_query) {
+    /* RFC 6762 section 18.14:
+     * In legacy unicast responses generated to answer legacy queries,
+     * name compression MUST NOT be performed on SRV records.
+     */
+    srvhost.skip_compression = 1;
+  }
+  srvdata[0] = lwip_htons(SRV_PRIORITY);
+  srvdata[1] = lwip_htons(SRV_WEIGHT);
+  srvdata[2] = lwip_htons(service->port);
+  LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with SRV record\n"));
+  return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_SRV, DNS_RRCLASS_IN, cache_flush, service->dns_ttl,
+                         (const u8_t *) &srvdata, sizeof(srvdata), &srvhost);
+}
+
+/** Write a TXT RR to outpacket */
+static err_t
+mdns_add_txt_answer(struct mdns_outpacket *reply, u16_t cache_flush, struct mdns_service *service)
+{
+  struct mdns_domain service_instance;
+  mdns_build_service_domain(&service_instance, service, 1);
+  mdns_prepare_txtdata(service);
+  LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with TXT record\n"));
+  return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_TXT, DNS_RRCLASS_IN, cache_flush, service->dns_ttl,
+                         (u8_t *) &service->txtdata.name, service->txtdata.length, NULL);
+}
+
+/**
+ * Setup outpacket as a reply to the incoming packet
+ */
+static void
+mdns_init_outpacket(struct mdns_outpacket *out, struct mdns_packet *in)
+{
+  memset(out, 0, sizeof(struct mdns_outpacket));
+  out->cache_flush = 1;
+  out->netif = in->netif;
+
+  /* Copy source IP/port to use when responding unicast, or to choose
+   * which pcb to use for multicast (IPv4/IPv6)
+   */
+  SMEMCPY(&out->dest_addr, &in->source_addr, sizeof(ip_addr_t));
+  out->dest_port = in->source_port;
+
+  if (in->source_port != MDNS_PORT) {
+    out->unicast_reply = 1;
+    out->cache_flush = 0;
+    if (in->questions == 1) {
+      out->legacy_query = 1;
+      out->tx_id = in->tx_id;
+    }
+  }
+
+  if (in->recv_unicast) {
+    out->unicast_reply = 1;
+  }
+}
+
+/**
+ * Send chosen answers as a reply
+ *
+ * Add all selected answers (first write will allocate pbuf)
+ * Add additional answers based on the selected answers
+ * Send the packet
+ */
+static void
+mdns_send_outpacket(struct mdns_outpacket *outpkt)
+{
+  struct mdns_service *service;
+  err_t res;
+  int i;
+  struct mdns_host* mdns = NETIF_TO_HOST(outpkt->netif);
+
+  /* Write answers to host questions */
+#if LWIP_IPV4
+  if (outpkt->host_replies & REPLY_HOST_A) {
+    res = mdns_add_a_answer(outpkt, outpkt->cache_flush, outpkt->netif);
+    if (res != ERR_OK) {
+      goto cleanup;
+    }
+    outpkt->answers++;
+  }
+  if (outpkt->host_replies & REPLY_HOST_PTR_V4) {
+    res = mdns_add_hostv4_ptr_answer(outpkt, outpkt->cache_flush, outpkt->netif);
+    if (res != ERR_OK) {
+      goto cleanup;
+    }
+    outpkt->answers++;
+  }
+#endif
+#if LWIP_IPV6
+  if (outpkt->host_replies & REPLY_HOST_AAAA) {
+    int addrindex;
+    for (addrindex = 0; addrindex < LWIP_IPV6_NUM_ADDRESSES; ++addrindex) {
+      if (ip6_addr_isvalid(netif_ip6_addr_state(outpkt->netif, addrindex))) {
+        res = mdns_add_aaaa_answer(outpkt, outpkt->cache_flush, outpkt->netif, addrindex);
+        if (res != ERR_OK) {
+          goto cleanup;
+        }
+        outpkt->answers++;
+      }
+    }
+  }
+  if (outpkt->host_replies & REPLY_HOST_PTR_V6) {
+    u8_t rev_addrs = outpkt->host_reverse_v6_replies;
+    int addrindex = 0;
+    while (rev_addrs) {
+      if (rev_addrs & 1) {
+        res = mdns_add_hostv6_ptr_answer(outpkt, outpkt->cache_flush, outpkt->netif, addrindex);
+        if (res != ERR_OK) {
+          goto cleanup;
+        }
+        outpkt->answers++;
+      }
+      addrindex++;
+      rev_addrs >>= 1;
+    }
+  }
+#endif
+
+  /* Write answers to service questions */
+  for (i = 0; i < MDNS_MAX_SERVICES; ++i) {
+    service = mdns->services[i];
+    if (!service) {
+      continue;
+    }
+
+    if (outpkt->serv_replies[i] & REPLY_SERVICE_TYPE_PTR) {
+      res = mdns_add_servicetype_ptr_answer(outpkt, service);
+      if (res != ERR_OK) {
+        goto cleanup;
+      }
+      outpkt->answers++;
+    }
+
+    if (outpkt->serv_replies[i] & REPLY_SERVICE_NAME_PTR) {
+      res = mdns_add_servicename_ptr_answer(outpkt, service);
+      if (res != ERR_OK) {
+        goto cleanup;
+      }
+      outpkt->answers++;
+    }
+
+    if (outpkt->serv_replies[i] & REPLY_SERVICE_SRV) {
+      res = mdns_add_srv_answer(outpkt, outpkt->cache_flush, mdns, service);
+      if (res != ERR_OK) {
+        goto cleanup;
+      }
+      outpkt->answers++;
+    }
+
+    if (outpkt->serv_replies[i] & REPLY_SERVICE_TXT) {
+      res = mdns_add_txt_answer(outpkt, outpkt->cache_flush, service);
+      if (res != ERR_OK) {
+        goto cleanup;
+      }
+      outpkt->answers++;
+    }
+  }
+
+  /* All answers written, add additional RRs */
+  for (i = 0; i < MDNS_MAX_SERVICES; ++i) {
+    service = mdns->services[i];
+    if (!service) {
+      continue;
+    }
+
+    if (outpkt->serv_replies[i] & REPLY_SERVICE_NAME_PTR) {
+      /* Our service instance requested, include SRV & TXT
+       * if they are already not requested. */
+      if (!(outpkt->serv_replies[i] & REPLY_SERVICE_SRV)) {
+        res = mdns_add_srv_answer(outpkt, outpkt->cache_flush, mdns, service);
+        if (res != ERR_OK) {
+          goto cleanup;
+        }
+        outpkt->additional++;
+      }
+
+      if (!(outpkt->serv_replies[i] & REPLY_SERVICE_TXT)) {
+        res = mdns_add_txt_answer(outpkt, outpkt->cache_flush, service);
+        if (res != ERR_OK) {
+          goto cleanup;
+        }
+        outpkt->additional++;
+      }
+    }
+
+    /* If service instance, SRV, record or an IP address is requested,
+     * supply all addresses for the host
+     */
+    if ((outpkt->serv_replies[i] & (REPLY_SERVICE_NAME_PTR | REPLY_SERVICE_SRV)) ||
+        (outpkt->host_replies & (REPLY_HOST_A | REPLY_HOST_AAAA))) {
+#if LWIP_IPV6
+      if (!(outpkt->host_replies & REPLY_HOST_AAAA)) {
+        int addrindex;
+        for (addrindex = 0; addrindex < LWIP_IPV6_NUM_ADDRESSES; ++addrindex) {
+          if (ip6_addr_isvalid(netif_ip6_addr_state(outpkt->netif, addrindex))) {
+            res = mdns_add_aaaa_answer(outpkt, outpkt->cache_flush, outpkt->netif, addrindex);
+            if (res != ERR_OK) {
+              goto cleanup;
+            }
+            outpkt->additional++;
+          }
+        }
+      }
+#endif
+#if LWIP_IPV4
+      if (!(outpkt->host_replies & REPLY_HOST_A)) {
+        res = mdns_add_a_answer(outpkt, outpkt->cache_flush, outpkt->netif);
+        if (res != ERR_OK) {
+          goto cleanup;
+        }
+        outpkt->additional++;
+      }
+#endif
+    }
+  }
+
+  if (outpkt->pbuf) {
+    const ip_addr_t *mcast_destaddr;
+    struct dns_hdr hdr;
+
+    /* Write header */
+    memset(&hdr, 0, sizeof(hdr));
+    hdr.flags1 = DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE;
+    hdr.numanswers = lwip_htons(outpkt->answers);
+    hdr.numextrarr = lwip_htons(outpkt->additional);
+    if (outpkt->legacy_query) {
+      hdr.numquestions = lwip_htons(1);
+      hdr.id = lwip_htons(outpkt->tx_id);
+    }
+    pbuf_take(outpkt->pbuf, &hdr, sizeof(hdr));
+
+    /* Shrink packet */
+    pbuf_realloc(outpkt->pbuf, outpkt->write_offset);
+
+    if (IP_IS_V6_VAL(outpkt->dest_addr)) {
+#if LWIP_IPV6
+      mcast_destaddr = &v6group;
+#endif
+    } else {
+#if LWIP_IPV4
+      mcast_destaddr = &v4group;
+#endif
+    }
+    /* Send created packet */
+    LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Sending packet, len=%d, unicast=%d\n", outpkt->write_offset, outpkt->unicast_reply));
+    if (outpkt->unicast_reply) {
+      udp_sendto_if(mdns_pcb, outpkt->pbuf, &outpkt->dest_addr, outpkt->dest_port, outpkt->netif);
+    } else {
+      udp_sendto_if(mdns_pcb, outpkt->pbuf, mcast_destaddr, MDNS_PORT, outpkt->netif);
+    }
+  }
+
+cleanup:
+  if (outpkt->pbuf) {
+    pbuf_free(outpkt->pbuf);
+    outpkt->pbuf = NULL;
+  }
+}
+
+/**
+ * Send unsolicited answer containing all our known data
+ * @param netif The network interface to send on
+ * @param destination The target address to send to (usually multicast address)
+ */
+static void
+mdns_announce(struct netif *netif, const ip_addr_t *destination)
+{
+  struct mdns_outpacket announce;
+  int i;
+  struct mdns_host* mdns = NETIF_TO_HOST(netif);
+
+  memset(&announce, 0, sizeof(announce));
+  announce.netif = netif;
+  announce.cache_flush = 1;
+#if LWIP_IPV4
+  if (!ip4_addr_isany_val(*netif_ip4_addr(netif)))
+    announce.host_replies = REPLY_HOST_A | REPLY_HOST_PTR_V4;
+#endif
+#if LWIP_IPV6
+  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
+    if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) {
+      announce.host_replies |= REPLY_HOST_AAAA | REPLY_HOST_PTR_V6;
+      announce.host_reverse_v6_replies |= (1 << i);
+    }
+  }
+#endif
+
+  for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+    struct mdns_service *serv = mdns->services[i];
+    if (serv) {
+      announce.serv_replies[i] = REPLY_SERVICE_TYPE_PTR | REPLY_SERVICE_NAME_PTR |
+          REPLY_SERVICE_SRV | REPLY_SERVICE_TXT;
+    }
+  }
+
+  announce.dest_port = MDNS_PORT;
+  SMEMCPY(&announce.dest_addr, destination, sizeof(announce.dest_addr));
+  mdns_send_outpacket(&announce);
+}
+
+/**
+ * Handle question MDNS packet
+ * 1. Parse all questions and set bits what answers to send
+ * 2. Clear pending answers if known answers are supplied
+ * 3. Put chosen answers in new packet and send as reply
+ */
+static void
+mdns_handle_question(struct mdns_packet *pkt)
+{
+  struct mdns_service *service;
+  struct mdns_outpacket reply;
+  int replies = 0;
+  int i;
+  err_t res;
+  struct mdns_host* mdns = NETIF_TO_HOST(pkt->netif);
+
+  mdns_init_outpacket(&reply, pkt);
+
+  while (pkt->questions_left) {
+    struct mdns_question q;
+
+    res = mdns_read_question(pkt, &q);
+    if (res != ERR_OK) {
+      LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping query packet\n"));
+      return;
+    }
+
+    LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Query for domain "));
+    mdns_domain_debug_print(&q.info.domain);
+    LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", q.info.type, q.info.klass));
+
+    if (q.unicast) {
+      /* Reply unicast if any question is unicast */
+      reply.unicast_reply = 1;
+    }
+
+    reply.host_replies |= check_host(pkt->netif, &q.info, &reply.host_reverse_v6_replies);
+    replies |= reply.host_replies;
+
+    for (i = 0; i < MDNS_MAX_SERVICES; ++i) {
+      service = mdns->services[i];
+      if (!service) {
+        continue;
+      }
+      reply.serv_replies[i] |= check_service(service, &q.info);
+      replies |= reply.serv_replies[i];
+    }
+
+    if (replies && reply.legacy_query) {
+      /* Add question to reply packet (legacy packet only has 1 question) */
+      res = mdns_add_question(&reply, &q.info.domain, q.info.type, q.info.klass, 0);
+      if (res != ERR_OK) {
+        goto cleanup;
+      }
+    }
+  }
+
+  /* Handle known answers */
+  while (pkt->answers_left) {
+    struct mdns_answer ans;
+    u8_t rev_v6;
+    int match;
+
+    res = mdns_read_answer(pkt, &ans);
+    if (res != ERR_OK) {
+      LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping query packet\n"));
+      goto cleanup;
+    }
+
+    LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Known answer for domain "));
+    mdns_domain_debug_print(&ans.info.domain);
+    LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", ans.info.type, ans.info.klass));
+
+
+    if (ans.info.type == DNS_RRTYPE_ANY || ans.info.klass == DNS_RRCLASS_ANY) {
+      /* Skip known answers for ANY type & class */
+      continue;
+    }
+
+    rev_v6 = 0;
+    match = reply.host_replies & check_host(pkt->netif, &ans.info, &rev_v6);
+    if (match && (ans.ttl > (mdns->dns_ttl / 2))) {
+      /* The RR in the known answer matches an RR we are planning to send,
+       * and the TTL is less than half gone.
+       * If the payload matches we should not send that answer.
+       */
+      if (ans.info.type == DNS_RRTYPE_PTR) {
+        /* Read domain and compare */
+        struct mdns_domain known_ans, my_ans;
+        u16_t len;
+        len = mdns_readname(pkt->pbuf, ans.rd_offset, &known_ans);
+        res = mdns_build_host_domain(&my_ans, mdns);
+        if (len != MDNS_READNAME_ERROR && res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) {
+#if LWIP_IPV4
+          if (match & REPLY_HOST_PTR_V4) {
+              LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: v4 PTR\n"));
+              reply.host_replies &= ~REPLY_HOST_PTR_V4;
+          }
+#endif
+#if LWIP_IPV6
+          if (match & REPLY_HOST_PTR_V6) {
+              LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: v6 PTR\n"));
+              reply.host_reverse_v6_replies &= ~rev_v6;
+              if (reply.host_reverse_v6_replies == 0) {
+                reply.host_replies &= ~REPLY_HOST_PTR_V6;
+              }
+          }
+#endif
+        }
+      } else if (match & REPLY_HOST_A) {
+#if LWIP_IPV4
+        if (ans.rd_length == sizeof(ip4_addr_t) &&
+            pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip4_addr(pkt->netif), ans.rd_length) == 0) {
+          LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: A\n"));
+          reply.host_replies &= ~REPLY_HOST_A;
+        }
+#endif
+      } else if (match & REPLY_HOST_AAAA) {
+#if LWIP_IPV6
+        if (ans.rd_length == sizeof(ip6_addr_t) &&
+            /* TODO this clears all AAAA responses if first addr is set as known */
+            pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip6_addr(pkt->netif, 0), ans.rd_length) == 0) {
+          LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: AAAA\n"));
+          reply.host_replies &= ~REPLY_HOST_AAAA;
+        }
+#endif
+      }
+    }
+
+    for (i = 0; i < MDNS_MAX_SERVICES; ++i) {
+      service = mdns->services[i];
+      if (!service) {
+        continue;
+      }
+      match = reply.serv_replies[i] & check_service(service, &ans.info);
+      if (match && (ans.ttl > (service->dns_ttl / 2))) {
+        /* The RR in the known answer matches an RR we are planning to send,
+         * and the TTL is less than half gone.
+         * If the payload matches we should not send that answer.
+         */
+        if (ans.info.type == DNS_RRTYPE_PTR) {
+          /* Read domain and compare */
+          struct mdns_domain known_ans, my_ans;
+          u16_t len;
+          len = mdns_readname(pkt->pbuf, ans.rd_offset, &known_ans);
+          if (len != MDNS_READNAME_ERROR) {
+            if (match & REPLY_SERVICE_TYPE_PTR) {
+              res = mdns_build_service_domain(&my_ans, service, 0);
+              if (res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) {
+                LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: service type PTR\n"));
+                reply.serv_replies[i] &= ~REPLY_SERVICE_TYPE_PTR;
+              }
+            }
+            if (match & REPLY_SERVICE_NAME_PTR) {
+              res = mdns_build_service_domain(&my_ans, service, 1);
+              if (res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) {
+                LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: service name PTR\n"));
+                reply.serv_replies[i] &= ~REPLY_SERVICE_NAME_PTR;
+              }
+            }
+          }
+        } else if (match & REPLY_SERVICE_SRV) {
+          /* Read and compare to my SRV record */
+          u16_t field16, len, read_pos;
+          struct mdns_domain known_ans, my_ans;
+          read_pos = ans.rd_offset;
+          do {
+            /* Check priority field */
+            len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+            if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_PRIORITY) {
+              break;
+            }
+            read_pos += len;
+            /* Check weight field */
+            len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+            if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_WEIGHT) {
+              break;
+            }
+            read_pos += len;
+            /* Check port field */
+            len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+            if (len != sizeof(field16) || lwip_ntohs(field16) != service->port) {
+              break;
+            }
+            read_pos += len;
+            /* Check host field */
+            len = mdns_readname(pkt->pbuf, read_pos, &known_ans);
+            mdns_build_host_domain(&my_ans, mdns);
+            if (len == MDNS_READNAME_ERROR || !mdns_domain_eq(&known_ans, &my_ans)) {
+              break;
+            }
+            LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: SRV\n"));
+            reply.serv_replies[i] &= ~REPLY_SERVICE_SRV;
+          } while (0);
+        } else if (match & REPLY_SERVICE_TXT) {
+          mdns_prepare_txtdata(service);
+          if (service->txtdata.length == ans.rd_length &&
+              pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) {
+            LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: TXT\n"));
+            reply.serv_replies[i] &= ~REPLY_SERVICE_TXT;
+          }
+        }
+      }
+    }
+  }
+
+  mdns_send_outpacket(&reply);
+
+cleanup:
+  if (reply.pbuf) {
+    /* This should only happen if we fail to alloc/write question for legacy query */
+    pbuf_free(reply.pbuf);
+    reply.pbuf = NULL;
+  }
+}
+
+/**
+ * Handle response MDNS packet
+ * Only prints debug for now. Will need more code to do conflict resolution.
+ */
+static void
+mdns_handle_response(struct mdns_packet *pkt)
+{
+  /* Ignore all questions */
+  while (pkt->questions_left) {
+    struct mdns_question q;
+    err_t res;
+
+    res = mdns_read_question(pkt, &q);
+    if (res != ERR_OK) {
+      LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping response packet\n"));
+      return;
+    }
+  }
+
+  while (pkt->answers_left) {
+    struct mdns_answer ans;
+    err_t res;
+
+    res = mdns_read_answer(pkt, &ans);
+    if (res != ERR_OK) {
+      LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping response packet\n"));
+      return;
+    }
+
+    LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Answer for domain "));
+    mdns_domain_debug_print(&ans.info.domain);
+    LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", ans.info.type, ans.info.klass));
+  }
+}
+
+/**
+ * Receive input function for MDNS packets.
+ * Handles both IPv4 and IPv6 UDP pcbs.
+ */
+static void
+mdns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+  struct dns_hdr hdr;
+  struct mdns_packet packet;
+  struct netif *recv_netif = ip_current_input_netif();
+  u16_t offset = 0;
+
+  LWIP_UNUSED_ARG(arg);
+  LWIP_UNUSED_ARG(pcb);
+
+  LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Received IPv%d MDNS packet, len %d\n", IP_IS_V6(addr)? 6 : 4, p->tot_len));
+
+  if (NETIF_TO_HOST(recv_netif) == NULL) {
+    /* From netif not configured for MDNS */
+    goto dealloc;
+  }
+
+  if (pbuf_copy_partial(p, &hdr, SIZEOF_DNS_HDR, offset) < SIZEOF_DNS_HDR) {
+    /* Too small */
+    goto dealloc;
+  }
+  offset += SIZEOF_DNS_HDR;
+
+  if (DNS_HDR_GET_OPCODE(&hdr)) {
+    /* Ignore non-standard queries in multicast packets (RFC 6762, section 18.3) */
+    goto dealloc;
+  }
+
+  memset(&packet, 0, sizeof(packet));
+  SMEMCPY(&packet.source_addr, addr, sizeof(packet.source_addr));
+  packet.source_port = port;
+  packet.netif = recv_netif;
+  packet.pbuf = p;
+  packet.parse_offset = offset;
+  packet.tx_id = lwip_ntohs(hdr.id);
+  packet.questions = packet.questions_left = lwip_ntohs(hdr.numquestions);
+  packet.answers = packet.answers_left = lwip_ntohs(hdr.numanswers) + lwip_ntohs(hdr.numauthrr) + lwip_ntohs(hdr.numextrarr);
+
+#if LWIP_IPV6
+  if (IP_IS_V6(ip_current_dest_addr())) {
+    if (!ip_addr_cmp(ip_current_dest_addr(), &v6group)) {
+      packet.recv_unicast = 1;
+    }
+  }
+#endif
+#if LWIP_IPV4
+  if (!IP_IS_V6(ip_current_dest_addr())) {
+    if (!ip_addr_cmp(ip_current_dest_addr(), &v4group)) {
+      packet.recv_unicast = 1;
+    }
+  }
+#endif
+
+  if (hdr.flags1 & DNS_FLAG1_RESPONSE) {
+    mdns_handle_response(&packet);
+  } else {
+    mdns_handle_question(&packet);
+  }
+
+dealloc:
+  pbuf_free(p);
+}
+
+/**
+ * @ingroup mdns
+ * Initiate MDNS responder. Will open UDP sockets on port 5353
+ */
+void
+mdns_resp_init(void)
+{
+  err_t res;
+
+  mdns_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+  LWIP_ASSERT("Failed to allocate pcb", mdns_pcb != NULL);
+#if LWIP_MULTICAST_TX_OPTIONS
+  udp_set_multicast_ttl(mdns_pcb, MDNS_TTL);
+#else
+  mdns_pcb->ttl = MDNS_TTL;
+#endif
+  res = udp_bind(mdns_pcb, IP_ANY_TYPE, MDNS_PORT);
+  LWIP_UNUSED_ARG(res); /* in case of LWIP_NOASSERT */
+  LWIP_ASSERT("Failed to bind pcb", res == ERR_OK);
+  udp_recv(mdns_pcb, mdns_recv, NULL);
+
+  mdns_netif_client_id = netif_alloc_client_data_id();
+}
+
+/**
+ * @ingroup mdns
+ * Announce IP settings have changed on netif.
+ * Call this in your callback registered by netif_set_status_callback().
+ * This function may go away in the future when netif supports registering
+ * multiple callback functions.
+ * @param netif The network interface where settings have changed.
+ */
+void
+mdns_resp_netif_settings_changed(struct netif *netif)
+{
+  LWIP_ERROR("mdns_resp_netif_ip_changed: netif != NULL", (netif != NULL), return);
+
+  if (NETIF_TO_HOST(netif) == NULL) {
+    return;
+  }
+
+  /* Announce on IPv6 and IPv4 */
+#if LWIP_IPV6
+   mdns_announce(netif, IP6_ADDR_ANY);
+#endif
+#if LWIP_IPV4
+   mdns_announce(netif, IP4_ADDR_ANY);
+#endif
+}
+
+/**
+ * @ingroup mdns
+ * Activate MDNS responder for a network interface and send announce packets.
+ * @param netif The network interface to activate.
+ * @param hostname Name to use. Queries for &lt;hostname&gt;.local will be answered
+ *                 with the IP addresses of the netif. The hostname will be copied, the
+ *                 given pointer can be on the stack.
+ * @param dns_ttl Validity time in seconds to send out for IP address data in DNS replies
+ * @return ERR_OK if netif was added, an err_t otherwise
+ */
+err_t
+mdns_resp_add_netif(struct netif *netif, const char *hostname, u32_t dns_ttl)
+{
+  err_t res;
+  struct mdns_host* mdns;
+
+  LWIP_ERROR("mdns_resp_add_netif: netif != NULL", (netif != NULL), return ERR_VAL);
+  LWIP_ERROR("mdns_resp_add_netif: Hostname too long", (strlen(hostname) <= MDNS_LABEL_MAXLEN), return ERR_VAL);
+
+  LWIP_ASSERT("mdns_resp_add_netif: Double add", NETIF_TO_HOST(netif) == NULL);
+  mdns = (struct mdns_host *) mem_malloc(sizeof(struct mdns_host));
+  LWIP_ERROR("mdns_resp_add_netif: Alloc failed", (mdns != NULL), return ERR_MEM);
+
+  netif_set_client_data(netif, mdns_netif_client_id, mdns);
+
+  memset(mdns, 0, sizeof(struct mdns_host));
+  MEMCPY(&mdns->name, hostname, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(hostname)));
+  mdns->dns_ttl = dns_ttl;
+
+  /* Join multicast groups */
+#if LWIP_IPV4
+  res = igmp_joingroup_netif(netif, ip_2_ip4(&v4group));
+  if (res != ERR_OK) {
+    goto cleanup;
+  }
+#endif
+#if LWIP_IPV6
+  res = mld6_joingroup_netif(netif, ip_2_ip6(&v6group));
+  if (res != ERR_OK) {
+    goto cleanup;
+  }
+#endif
+
+  mdns_resp_netif_settings_changed(netif);
+  return ERR_OK;
+
+cleanup:
+  mem_free(mdns);
+  netif_set_client_data(netif, mdns_netif_client_id, NULL);
+  return res;
+}
+
+/**
+ * @ingroup mdns
+ * Stop responding to MDNS queries on this interface, leave multicast groups,
+ * and free the helper structure and any of its services.
+ * @param netif The network interface to remove.
+ * @return ERR_OK if netif was removed, an err_t otherwise
+ */
+err_t
+mdns_resp_remove_netif(struct netif *netif)
+{
+  int i;
+  struct mdns_host* mdns;
+
+  LWIP_ASSERT("mdns_resp_remove_netif: Null pointer", netif);
+  mdns = NETIF_TO_HOST(netif);
+  LWIP_ERROR("mdns_resp_remove_netif: Not an active netif", (mdns != NULL), return ERR_VAL);
+
+  for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+    struct mdns_service *service = mdns->services[i];
+    if (service) {
+      mem_free(service);
+    }
+  }
+
+  /* Leave multicast groups */
+#if LWIP_IPV4
+  igmp_leavegroup_netif(netif, ip_2_ip4(&v4group));
+#endif
+#if LWIP_IPV6
+  mld6_leavegroup_netif(netif, ip_2_ip6(&v6group));
+#endif
+
+  mem_free(mdns);
+  netif_set_client_data(netif, mdns_netif_client_id, NULL);
+  return ERR_OK;
+}
+
+/**
+ * @ingroup mdns
+ * Add a service to the selected network interface.
+ * @param netif The network interface to publish this service on
+ * @param name The name of the service
+ * @param service The service type, like "_http"
+ * @param proto The service protocol, DNSSD_PROTO_TCP for TCP ("_tcp") and DNSSD_PROTO_UDP
+ *              for others ("_udp")
+ * @param port The port the service listens to
+ * @param dns_ttl Validity time in seconds to send out for service data in DNS replies
+ * @param txt_fn Callback to get TXT data. Will be called each time a TXT reply is created to
+ *               allow dynamic replies.
+ * @param txt_data Userdata pointer for txt_fn
+ * @return ERR_OK if the service was added to the netif, an err_t otherwise
+ */
+err_t
+mdns_resp_add_service(struct netif *netif, const char *name, const char *service, enum mdns_sd_proto proto, u16_t port, u32_t dns_ttl, service_get_txt_fn_t txt_fn, void *txt_data)
+{
+  int i;
+  int slot = -1;
+  struct mdns_service *srv;
+  struct mdns_host* mdns;
+
+  LWIP_ASSERT("mdns_resp_add_service: netif != NULL", netif);
+  mdns = NETIF_TO_HOST(netif);
+  LWIP_ERROR("mdns_resp_add_service: Not an mdns netif", (mdns != NULL), return ERR_VAL);
+
+  LWIP_ERROR("mdns_resp_add_service: Name too long", (strlen(name) <= MDNS_LABEL_MAXLEN), return ERR_VAL);
+  LWIP_ERROR("mdns_resp_add_service: Service too long", (strlen(service) <= MDNS_LABEL_MAXLEN), return ERR_VAL);
+  LWIP_ERROR("mdns_resp_add_service: Bad proto (need TCP or UDP)", (proto == DNSSD_PROTO_TCP || proto == DNSSD_PROTO_UDP), return ERR_VAL);
+
+  for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+    if (mdns->services[i] == NULL) {
+      slot = i;
+      break;
+    }
+  }
+  LWIP_ERROR("mdns_resp_add_service: Service list full (increase MDNS_MAX_SERVICES)", (slot >= 0), return ERR_MEM);
+
+  srv = (struct mdns_service*)mem_malloc(sizeof(struct mdns_service));
+  LWIP_ERROR("mdns_resp_add_service: Alloc failed", (srv != NULL), return ERR_MEM);
+
+  memset(srv, 0, sizeof(struct mdns_service));
+
+  MEMCPY(&srv->name, name, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(name)));
+  MEMCPY(&srv->service, service, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(service)));
+  srv->txt_fn = txt_fn;
+  srv->txt_userdata = txt_data;
+  srv->proto = (u16_t)proto;
+  srv->port = port;
+  srv->dns_ttl = dns_ttl;
+
+  mdns->services[slot] = srv;
+
+  /* Announce on IPv6 and IPv4 */
+#if LWIP_IPV6
+  mdns_announce(netif, IP6_ADDR_ANY);
+#endif
+#if LWIP_IPV4
+  mdns_announce(netif, IP4_ADDR_ANY);
+#endif
+
+  return ERR_OK;
+}
+
+/**
+ * @ingroup mdns
+ * Call this function from inside the service_get_txt_fn_t callback to add text data.
+ * Buffer for TXT data is 256 bytes, and each field is prefixed with a length byte.
+ * @param service The service provided to the get_txt callback
+ * @param txt String to add to the TXT field.
+ * @param txt_len Length of string
+ * @return ERR_OK if the string was added to the reply, an err_t otherwise
+ */
+err_t
+mdns_resp_add_service_txtitem(struct mdns_service *service, const char *txt, u8_t txt_len)
+{
+  LWIP_ASSERT("mdns_resp_add_service_txtitem: service != NULL", service);
+
+  /* Use a mdns_domain struct to store txt chunks since it is the same encoding */
+  return mdns_domain_add_label(&service->txtdata, txt, txt_len);
+}
+
+#endif /* LWIP_MDNS_RESPONDER */
diff --git a/src/apps/mqtt/mqtt.c b/src/apps/mqtt/mqtt.c
new file mode 100644
index 0000000..899e2cb
--- /dev/null
+++ b/src/apps/mqtt/mqtt.c
@@ -0,0 +1,1342 @@
+/**
+ * @file
+ * MQTT client
+ *
+ * @defgroup mqtt MQTT client
+ * @ingroup apps
+ * @verbinclude mqtt_client.txt
+ */
+
+/*
+ * Copyright (c) 2016 Erik Andersson <erian747@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce 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: Erik Andersson <erian747@gmail.com>
+ *
+ *
+ * @todo:
+ * - Handle large outgoing payloads for PUBLISH messages
+ * - Fix restriction of a single topic in each (UN)SUBSCRIBE message (protocol has support for multiple topics)
+ * - Add support for legacy MQTT protocol version
+ *
+ * Please coordinate changes and requests with Erik Andersson
+ * Erik Andersson <erian747@gmail.com>
+ *
+ */
+#include "lwip/apps/mqtt.h"
+#include "lwip/timeouts.h"
+#include "lwip/ip_addr.h"
+#include "lwip/mem.h"
+#include "lwip/err.h"
+#include "lwip/pbuf.h"
+#include "lwip/tcp.h"
+#include <string.h>
+
+#if LWIP_TCP && LWIP_CALLBACK_API
+
+/**
+ * MQTT_DEBUG: Default is off.
+ */
+#if !defined MQTT_DEBUG || defined __DOXYGEN__
+#define MQTT_DEBUG                  LWIP_DBG_OFF
+#endif
+
+#define MQTT_DEBUG_TRACE        (MQTT_DEBUG | LWIP_DBG_TRACE)
+#define MQTT_DEBUG_STATE        (MQTT_DEBUG | LWIP_DBG_STATE)
+#define MQTT_DEBUG_WARN         (MQTT_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define MQTT_DEBUG_WARN_STATE   (MQTT_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
+#define MQTT_DEBUG_SERIOUS      (MQTT_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
+
+static void mqtt_cyclic_timer(void *arg);
+
+/**
+ * MQTT client connection states
+ */
+enum {
+  TCP_DISCONNECTED,
+  TCP_CONNECTING,
+  MQTT_CONNECTING,
+  MQTT_CONNECTED
+};
+
+/**
+ * MQTT control message types
+ */
+enum mqtt_message_type {
+  MQTT_MSG_TYPE_CONNECT = 1,
+  MQTT_MSG_TYPE_CONNACK = 2,
+  MQTT_MSG_TYPE_PUBLISH = 3,
+  MQTT_MSG_TYPE_PUBACK = 4,
+  MQTT_MSG_TYPE_PUBREC = 5,
+  MQTT_MSG_TYPE_PUBREL = 6,
+  MQTT_MSG_TYPE_PUBCOMP = 7,
+  MQTT_MSG_TYPE_SUBSCRIBE = 8,
+  MQTT_MSG_TYPE_SUBACK = 9,
+  MQTT_MSG_TYPE_UNSUBSCRIBE = 10,
+  MQTT_MSG_TYPE_UNSUBACK = 11,
+  MQTT_MSG_TYPE_PINGREQ = 12,
+  MQTT_MSG_TYPE_PINGRESP = 13,
+  MQTT_MSG_TYPE_DISCONNECT = 14
+};
+
+/** Helpers to extract control packet type and qos from first byte in fixed header */
+#define MQTT_CTL_PACKET_TYPE(fixed_hdr_byte0) ((fixed_hdr_byte0 & 0xf0) >> 4)
+#define MQTT_CTL_PACKET_QOS(fixed_hdr_byte0) ((fixed_hdr_byte0 & 0x6) >> 1)
+
+/**
+ * MQTT connect flags, only used in CONNECT message
+ */
+enum mqtt_connect_flag {
+  MQTT_CONNECT_FLAG_USERNAME = 1 << 7,
+  MQTT_CONNECT_FLAG_PASSWORD = 1 << 6,
+  MQTT_CONNECT_FLAG_WILL_RETAIN = 1 << 5,
+  MQTT_CONNECT_FLAG_WILL = 1 << 2,
+  MQTT_CONNECT_FLAG_CLEAN_SESSION = 1 << 1
+};
+
+
+#if defined(LWIP_DEBUG)
+static const char * const mqtt_message_type_str[15] =
+{
+  "UNDEFINED",
+  "CONNECT",
+  "CONNACK",
+  "PUBLISH",
+  "PUBACK",
+  "PUBREC",
+  "PUBREL",
+  "PUBCOMP",
+  "SUBSCRIBE",
+  "SUBACK",
+  "UNSUBSCRIBE",
+  "UNSUBACK",
+  "PINGREQ",
+  "PINGRESP",
+  "DISCONNECT"
+};
+
+/**
+ * Message type value to string
+ * @param msg_type see enum mqtt_message_type
+ * 
+ * @return Control message type text string
+ */
+static const char *
+mqtt_msg_type_to_str(u8_t msg_type)
+{
+  if (msg_type >= LWIP_ARRAYSIZE(mqtt_message_type_str)) {
+    msg_type = 0;
+  }
+  return mqtt_message_type_str[msg_type];
+}
+
+#endif
+
+
+/**
+ * Generate MQTT packet identifier
+ * @param client MQTT client
+ * @return New packet identifier, range 1 to 65535
+ */
+static u16_t
+msg_generate_packet_id(mqtt_client_t *client)
+{
+  client->pkt_id_seq++;
+  if (client->pkt_id_seq == 0) {
+    client->pkt_id_seq++;
+  }
+  return client->pkt_id_seq;
+}
+
+/*--------------------------------------------------------------------------------------------------------------------- */
+/* Output ring buffer */
+
+
+#define MQTT_RINGBUF_IDX_MASK ((MQTT_OUTPUT_RINGBUF_SIZE) - 1)
+
+/** Add single item to ring buffer */
+#define mqtt_ringbuf_put(rb, item) ((rb)->buf)[(rb)->put++ & MQTT_RINGBUF_IDX_MASK] = (item)
+
+/** Return number of bytes in ring buffer */
+#define mqtt_ringbuf_len(rb) ((u16_t)((rb)->put - (rb)->get))
+
+/** Return number of bytes free in ring buffer */
+#define mqtt_ringbuf_free(rb) (MQTT_OUTPUT_RINGBUF_SIZE - mqtt_ringbuf_len(rb))
+
+/** Return number of bytes possible to read without wrapping around */
+#define mqtt_ringbuf_linear_read_length(rb) LWIP_MIN(mqtt_ringbuf_len(rb), (MQTT_OUTPUT_RINGBUF_SIZE - ((rb)->get & MQTT_RINGBUF_IDX_MASK)))
+
+/** Return pointer to ring buffer get position */
+#define mqtt_ringbuf_get_ptr(rb) (&(rb)->buf[(rb)->get & MQTT_RINGBUF_IDX_MASK])
+
+#define mqtt_ringbuf_advance_get_idx(rb, len) ((rb)->get += (len))
+
+
+/**
+ * Try send as many bytes as possible from output ring buffer
+ * @param rb Output ring buffer
+ * @param tpcb TCP connection handle
+ */
+static void
+mqtt_output_send(struct mqtt_ringbuf_t *rb, struct tcp_pcb *tpcb)
+{
+  err_t err;
+  u8_t wrap = 0;
+  u16_t ringbuf_lin_len = mqtt_ringbuf_linear_read_length(rb);
+  u16_t send_len = tcp_sndbuf(tpcb);
+  LWIP_ASSERT("mqtt_output_send: tpcb != NULL", tpcb != NULL);
+
+  if (send_len == 0 || ringbuf_lin_len == 0) {
+    return;
+  }
+
+  LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_output_send: tcp_sndbuf: %d bytes, ringbuf_linear_available: %d, get %d, put %d\n",
+                                send_len, ringbuf_lin_len, ((rb)->get & MQTT_RINGBUF_IDX_MASK), ((rb)->put & MQTT_RINGBUF_IDX_MASK)));
+
+  if (send_len > ringbuf_lin_len) {
+    /* Space in TCP output buffer is larger than available in ring buffer linear portion */
+    send_len = ringbuf_lin_len;
+    /* Wrap around if more data in ring buffer after linear portion */
+    wrap = (mqtt_ringbuf_len(rb) > ringbuf_lin_len);
+  }
+  err = tcp_write(tpcb, mqtt_ringbuf_get_ptr(rb), send_len, TCP_WRITE_FLAG_COPY | (wrap ? TCP_WRITE_FLAG_MORE : 0));
+  if ((err == ERR_OK) && wrap) {
+    mqtt_ringbuf_advance_get_idx(rb, send_len);
+    /* Use the lesser one of ring buffer linear length and TCP send buffer size */
+    send_len = LWIP_MIN(tcp_sndbuf(tpcb), mqtt_ringbuf_linear_read_length(rb));
+    err = tcp_write(tpcb, mqtt_ringbuf_get_ptr(rb), send_len, TCP_WRITE_FLAG_COPY);
+  }
+
+  if (err == ERR_OK) {
+    mqtt_ringbuf_advance_get_idx(rb, send_len);
+    /* Flush */
+    tcp_output(tpcb);
+  } else {
+    LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_output_send: Send failed with err %d (\"%s\")\n", err, lwip_strerr(err)));
+  }
+}
+
+
+
+/*--------------------------------------------------------------------------------------------------------------------- */
+/* Request queue */
+
+/**
+ * Create request item
+ * @param r_objs Pointer to request objects
+ * @param pkt_id Packet identifier of request
+ * @param cb Packet callback to call when requests lifetime ends
+ * @param arg Parameter following callback
+ * @return Request or NULL if failed to create
+ */
+static struct mqtt_request_t *
+mqtt_create_request(struct mqtt_request_t *r_objs, u16_t pkt_id, mqtt_request_cb_t cb, void *arg)
+{
+  struct mqtt_request_t *r = NULL;
+  u8_t n;
+  LWIP_ASSERT("mqtt_create_request: r_objs != NULL", r_objs != NULL);
+  for (n = 0; n < MQTT_REQ_MAX_IN_FLIGHT; n++) {
+    /* Item point to itself if not in use */
+    if (r_objs[n].next == &r_objs[n]) {
+      r = &r_objs[n];
+      r->next = NULL;
+      r->cb = cb;
+      r->arg = arg;
+      r->pkt_id = pkt_id;
+      break;
+    }
+  }
+  return r;
+}
+
+
+/**
+ * Append request to pending request queue
+ * @param tail Pointer to request queue tail pointer
+ * @param r Request to append
+ */
+static void
+mqtt_append_request(struct mqtt_request_t **tail, struct mqtt_request_t *r)
+{
+  struct mqtt_request_t *head = NULL;
+  s16_t time_before = 0;
+  struct mqtt_request_t *iter;
+
+  LWIP_ASSERT("mqtt_append_request: tail != NULL", tail != NULL);
+
+  /* Iterate trough queue to find head, and count total timeout time */
+  for (iter = *tail; iter != NULL; iter = iter->next) {
+    time_before += iter->timeout_diff;
+    head = iter;
+  }
+
+  LWIP_ASSERT("mqtt_append_request: time_before <= MQTT_REQ_TIMEOUT", time_before <= MQTT_REQ_TIMEOUT);
+  r->timeout_diff = MQTT_REQ_TIMEOUT - time_before;
+  if (head == NULL) {
+    *tail = r;
+  } else {
+    head->next = r;
+  }
+}
+
+
+/**
+ * Delete request item
+ * @param r Request item to delete
+ */
+static void
+mqtt_delete_request(struct mqtt_request_t *r)
+{
+  if (r != NULL) {
+    r->next = r;
+  }
+}
+
+/**
+ * Remove a request item with a specific packet identifier from request queue
+ * @param tail Pointer to request queue tail pointer
+ * @param pkt_id Packet identifier of request to take
+ * @return Request item if found, NULL if not
+ */
+static struct mqtt_request_t *
+mqtt_take_request(struct mqtt_request_t **tail, u16_t pkt_id)
+{
+  struct mqtt_request_t *iter = NULL, *prev = NULL;
+  LWIP_ASSERT("mqtt_take_request: tail != NULL", tail != NULL);
+  /* Search all request for pkt_id */
+  for (iter = *tail; iter != NULL; iter = iter->next) {
+    if (iter->pkt_id == pkt_id) {
+      break;
+    }
+    prev = iter;
+  }
+
+  /* If request was found */
+  if (iter != NULL) {
+    /* unchain */
+    if (prev == NULL) {
+      *tail= iter->next;
+    } else {
+      prev->next = iter->next;
+    }
+    /* If exists, add remaining timeout time for the request to next */
+    if (iter->next != NULL) {
+      iter->next->timeout_diff += iter->timeout_diff;
+    }
+    iter->next = NULL;
+  }
+  return iter;
+}
+
+/**
+ * Handle requests timeout
+ * @param tail Pointer to request queue tail pointer
+ * @param t Time since last call in seconds
+ */
+static void
+mqtt_request_time_elapsed(struct mqtt_request_t **tail, u8_t t)
+{
+  struct mqtt_request_t *r;
+  LWIP_ASSERT("mqtt_request_time_elapsed: tail != NULL", tail != NULL);
+  r = *tail;
+  while (t > 0 && r != NULL) {
+    if (t >= r->timeout_diff) {
+      t -= (u8_t)r->timeout_diff;
+      /* Unchain */
+      *tail = r->next;
+      /* Notify upper layer about timeout */
+      if (r->cb != NULL) {
+        r->cb(r->arg, ERR_TIMEOUT);
+      }
+      mqtt_delete_request(r);
+      /* Tail might be be modified in callback, so re-read it in every iteration */
+      r = *(struct mqtt_request_t * const volatile *)tail;
+    } else {
+      r->timeout_diff -= t;
+      t = 0;
+    }
+  }
+}
+
+/**
+ * Free all request items
+ * @param tail Pointer to request queue tail pointer
+ */
+static void
+mqtt_clear_requests(struct mqtt_request_t **tail)
+{
+  struct mqtt_request_t *iter, *next;
+  LWIP_ASSERT("mqtt_clear_requests: tail != NULL", tail != NULL);
+  for (iter = *tail; iter != NULL; iter = next) {
+    next = iter->next;
+    mqtt_delete_request(iter);
+  }
+  *tail = NULL;
+}
+/**
+ * Initialize all request items
+ * @param r_objs Pointer to request objects
+ */
+static void
+mqtt_init_requests(struct mqtt_request_t *r_objs)
+{
+  u8_t n;
+  LWIP_ASSERT("mqtt_init_requests: r_objs != NULL", r_objs != NULL);
+  for (n = 0; n < MQTT_REQ_MAX_IN_FLIGHT; n++) {
+    /* Item pointing to itself indicates unused */
+    r_objs[n].next = &r_objs[n];
+  }
+}
+
+/*--------------------------------------------------------------------------------------------------------------------- */
+/* Output message build helpers */
+
+
+static void
+mqtt_output_append_u8(struct mqtt_ringbuf_t *rb, u8_t value)
+{
+  mqtt_ringbuf_put(rb, value);
+}
+
+static
+void mqtt_output_append_u16(struct mqtt_ringbuf_t *rb, u16_t value)
+{
+  mqtt_ringbuf_put(rb, value >> 8);
+  mqtt_ringbuf_put(rb, value & 0xff);
+}
+
+static void
+mqtt_output_append_buf(struct mqtt_ringbuf_t *rb, const void *data, u16_t length)
+{
+  u16_t n;
+  for (n = 0; n < length; n++) {
+    mqtt_ringbuf_put(rb, ((const u8_t *)data)[n]);
+  }
+}
+
+static void
+mqtt_output_append_string(struct mqtt_ringbuf_t *rb, const char *str, u16_t length)
+{
+  u16_t n;
+  mqtt_ringbuf_put(rb, length >> 8);
+  mqtt_ringbuf_put(rb, length & 0xff);
+  for (n = 0; n < length; n++) {
+    mqtt_ringbuf_put(rb, str[n]);
+  }
+}
+
+/**
+ * Append fixed header
+ * @param rb Output ring buffer
+ * @param msg_type see enum mqtt_message_type
+ * @param dup MQTT DUP flag
+ * @param qos MQTT QoS field
+ * @param retain MQTT retain flag
+ * @param r_length Remaining length after fixed header
+ */
+
+static void
+mqtt_output_append_fixed_header(struct mqtt_ringbuf_t *rb, u8_t msg_type, u8_t dup,
+                 u8_t qos, u8_t retain, u16_t r_length)
+{
+  /* Start with control byte */
+  mqtt_output_append_u8(rb, (((msg_type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1)));
+  /* Encode remaining length field */
+  do {
+    mqtt_output_append_u8(rb, (r_length & 0x7f) | (r_length >= 128 ? 0x80 : 0));
+    r_length >>= 7;
+  } while (r_length > 0);
+}
+
+
+/**
+ * Check output buffer space
+ * @param rb Output ring buffer
+ * @param r_length Remaining length after fixed header
+ * @return 1 if message will fit, 0 if not enough buffer space
+ */
+static u8_t
+mqtt_output_check_space(struct mqtt_ringbuf_t *rb, u16_t r_length)
+{
+  /* Start with length of type byte + remaining length */
+  u16_t total_len = 1 + r_length;
+
+  LWIP_ASSERT("mqtt_output_check_space: rb != NULL", rb != NULL);
+
+ /* Calculate number of required bytes to contain the remaining bytes field and add to total*/
+  do {
+    total_len++;
+    r_length >>= 7;
+  } while (r_length > 0);
+
+  return (total_len <= mqtt_ringbuf_free(rb));
+}
+
+
+/**
+ * Close connection to server
+ * @param client MQTT client
+ * @param reason Reason for disconnection
+ */
+static void
+mqtt_close(mqtt_client_t *client, mqtt_connection_status_t reason)
+{
+  LWIP_ASSERT("mqtt_close: client != NULL", client != NULL);
+
+  /* Bring down TCP connection if not already done */
+  if (client->conn != NULL) {
+    err_t res;
+    tcp_recv(client->conn, NULL);
+    tcp_err(client->conn,  NULL);
+    tcp_sent(client->conn, NULL);
+    res = tcp_close(client->conn);
+    if (res != ERR_OK) {
+      tcp_abort(client->conn);
+      LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_close: Close err=%s\n", lwip_strerr(res)));
+    }
+    client->conn = NULL;
+  }
+
+  /* Remove all pending requests */
+  mqtt_clear_requests(&client->pend_req_queue);
+  /* Stop cyclic timer */
+  sys_untimeout(mqtt_cyclic_timer, client);
+
+  /* Notify upper layer of disconnection if changed state */
+  if (client->conn_state != TCP_DISCONNECTED) {
+
+    client->conn_state = TCP_DISCONNECTED;
+    if (client->connect_cb != NULL) {
+      client->connect_cb(client, client->connect_arg, reason);
+    }
+  }
+}
+
+
+/**
+ * Interval timer, called every MQTT_CYCLIC_TIMER_INTERVAL seconds in MQTT_CONNECTING and MQTT_CONNECTED states
+ * @param arg MQTT client
+ */
+static void
+mqtt_cyclic_timer(void *arg)
+{
+  u8_t restart_timer = 1;
+  mqtt_client_t *client = (mqtt_client_t *)arg;
+  LWIP_ASSERT("mqtt_cyclic_timer: client != NULL", client != NULL);
+
+  if (client->conn_state == MQTT_CONNECTING) {
+    client->cyclic_tick++;
+    if ((client->cyclic_tick * MQTT_CYCLIC_TIMER_INTERVAL) >= MQTT_CONNECT_TIMOUT) {
+      LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_cyclic_timer: CONNECT attempt to server timed out\n"));
+      /* Disconnect TCP */
+      mqtt_close(client, MQTT_CONNECT_TIMEOUT);
+      restart_timer = 0;
+    }
+  } else if (client->conn_state == MQTT_CONNECTED) {
+    /* Handle timeout for pending requests */
+    mqtt_request_time_elapsed(&client->pend_req_queue, MQTT_CYCLIC_TIMER_INTERVAL);
+
+    /* keep_alive > 0 means keep alive functionality shall be used */
+    if (client->keep_alive > 0) {
+
+      client->server_watchdog++;
+      /* If reception from server has been idle for 1.5*keep_alive time, server is considered unresponsive */
+      if ((client->server_watchdog * MQTT_CYCLIC_TIMER_INTERVAL) > (client->keep_alive + client->keep_alive/2)) {
+        LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_cyclic_timer: Server incoming keep-alive timeout\n"));
+        mqtt_close(client, MQTT_CONNECT_TIMEOUT);
+        restart_timer = 0;
+      }
+
+      /* If time for a keep alive message to be sent, transmission has been idle for keep_alive time */
+      if ((client->cyclic_tick * MQTT_CYCLIC_TIMER_INTERVAL) >= client->keep_alive) {
+        LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_cyclic_timer: Sending keep-alive message to server\n"));
+        if (mqtt_output_check_space(&client->output, 0) != 0) {
+          mqtt_output_append_fixed_header(&client->output, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0, 0);
+          client->cyclic_tick = 0;
+        }
+      } else {
+        client->cyclic_tick++;
+      }
+    }
+  } else {
+    LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_cyclic_timer: Timer should not be running in state %d\n", client->conn_state));
+    restart_timer = 0;
+  }
+  if (restart_timer) {
+    sys_timeout(MQTT_CYCLIC_TIMER_INTERVAL*1000, mqtt_cyclic_timer, arg);
+  }
+}
+
+
+/**
+ * Send PUBACK, PUBREC or PUBREL response message
+ * @param client MQTT client
+ * @param msg PUBACK, PUBREC or PUBREL
+ * @param pkt_id Packet identifier
+ * @param qos QoS value
+ * @return ERR_OK if successful, ERR_MEM if out of memory
+ */
+static err_t
+pub_ack_rec_rel_response(mqtt_client_t *client, u8_t msg, u16_t pkt_id, u8_t qos)
+{
+  err_t err = ERR_OK;
+  if (mqtt_output_check_space(&client->output, 2)) {
+    mqtt_output_append_fixed_header(&client->output, msg, 0, qos, 0, 2);
+    mqtt_output_append_u16(&client->output, pkt_id);
+    mqtt_output_send(&client->output, client->conn);
+  } else {
+    LWIP_DEBUGF(MQTT_DEBUG_TRACE,("pub_ack_rec_rel_response: OOM creating response: %s with pkt_id: %d\n",
+                                  mqtt_msg_type_to_str(msg), pkt_id));
+    err = ERR_MEM;
+  }
+  return err;
+}
+
+/**
+ * Subscribe response from server
+ * @param r Matching request
+ * @param result Result code from server
+ */
+static void
+mqtt_incomming_suback(struct mqtt_request_t *r, u8_t result)
+{
+  if (r->cb != NULL) {
+    r->cb(r->arg, result < 3 ? ERR_OK : ERR_ABRT);
+  }
+}
+
+
+/**
+ * Complete MQTT message received or buffer full
+ * @param client MQTT client
+ * @param fixed_hdr_idx header index
+ * @param length length received part
+ * @param remaining_length Remaining length of complete message
+ */
+static mqtt_connection_status_t
+  mqtt_message_received(mqtt_client_t *client, u8_t fixed_hdr_idx, u16_t length, u32_t remaining_length)
+{
+  mqtt_connection_status_t res = MQTT_CONNECT_ACCEPTED;
+
+  u8_t *var_hdr_payload = client->rx_buffer + fixed_hdr_idx;
+
+  /* Control packet type */
+  u8_t pkt_type = MQTT_CTL_PACKET_TYPE(client->rx_buffer[0]);
+  u16_t pkt_id = 0;
+
+  if (pkt_type == MQTT_MSG_TYPE_CONNACK) {
+    if (client->conn_state == MQTT_CONNECTING) {
+      /* Get result code from CONNACK */
+      res = (mqtt_connection_status_t)var_hdr_payload[1];
+      LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_message_received: Connect response code %d\n", res));
+      if (res == MQTT_CONNECT_ACCEPTED) {
+        /* Reset cyclic_tick when changing to connected state */
+        client->cyclic_tick = 0;
+        client->conn_state = MQTT_CONNECTED;
+        /* Notify upper layer */
+        if (client->connect_cb != 0) {
+          client->connect_cb(client, client->connect_arg, res);
+        }
+      }
+    } else {
+      LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_message_received: Received CONNACK in connected state\n"));
+    }
+  } else if (pkt_type == MQTT_MSG_TYPE_PINGRESP) {
+    LWIP_DEBUGF(MQTT_DEBUG_TRACE,( "mqtt_message_received: Received PINGRESP from server\n"));
+
+  } else if (pkt_type == MQTT_MSG_TYPE_PUBLISH) {
+    u16_t payload_offset = 0;
+    u16_t payload_length = length;
+    u8_t qos = MQTT_CTL_PACKET_QOS(client->rx_buffer[0]);
+
+    if (client->msg_idx <= MQTT_VAR_HEADER_BUFFER_LEN) {
+      /* Should have topic and pkt id*/
+      uint8_t *topic;
+      uint16_t after_topic;
+      u8_t bkp;
+      u16_t topic_len = var_hdr_payload[0];
+      topic_len = (topic_len << 8) + (u16_t)(var_hdr_payload[1]);
+
+      topic = var_hdr_payload + 2;
+      after_topic = 2 + topic_len;
+      /* Check length, add one byte even for QoS 0 so that zero termination will fit */
+      if ((after_topic + (qos? 2 : 1)) > length) {
+        LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_message_received: Receive buffer can not fit topic + pkt_id\n"));
+        goto out_disconnect;
+      }
+
+      /* id for QoS 1 and 2 */
+      if (qos > 0) {
+        client->inpub_pkt_id = ((u16_t)var_hdr_payload[after_topic] << 8) + (u16_t)var_hdr_payload[after_topic + 1];
+        after_topic += 2;
+      } else {
+        client->inpub_pkt_id = 0;
+      }
+      /* Take backup of byte after topic */
+      bkp = topic[topic_len];
+      /* Zero terminate string */
+      topic[topic_len] = 0;
+      /* Payload data remaining in receive buffer */
+      payload_length = length - after_topic;
+      payload_offset = after_topic;
+
+      LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_incomming_publish: Received message with QoS %d at topic: %s, payload length %d\n",
+                                    qos, topic, remaining_length + payload_length));
+      if (client->pub_cb != NULL) {
+        client->pub_cb(client->inpub_arg, (const char *)topic, remaining_length + payload_length);
+      }
+      /* Restore byte after topic */
+      topic[topic_len] = bkp;
+    }
+    if (payload_length > 0 || remaining_length == 0) {
+      client->data_cb(client->inpub_arg, var_hdr_payload + payload_offset, payload_length, remaining_length == 0 ? MQTT_DATA_FLAG_LAST : 0);
+      /* Reply if QoS > 0 */
+      if (remaining_length == 0 && qos > 0) {
+        /* Send PUBACK for QoS 1 or PUBREC for QoS 2 */
+        u8_t resp_msg = (qos == 1) ? MQTT_MSG_TYPE_PUBACK : MQTT_MSG_TYPE_PUBREC;
+        LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_incomming_publish: Sending publish response: %s with pkt_id: %d\n",
+                                      mqtt_msg_type_to_str(resp_msg), client->inpub_pkt_id));
+        pub_ack_rec_rel_response(client, resp_msg, client->inpub_pkt_id, 0);
+      }
+    }
+  } else {
+    /* Get packet identifier */
+    pkt_id = (u16_t)var_hdr_payload[0] << 8;
+    pkt_id |= (u16_t)var_hdr_payload[1];
+    if (pkt_id == 0) {
+      LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_message_received: Got message with illegal packet identifier: 0\n"));
+      goto out_disconnect;
+    }
+    if (pkt_type == MQTT_MSG_TYPE_PUBREC) {
+      LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_message_received: PUBREC, sending PUBREL with pkt_id: %d\n",pkt_id));
+      pub_ack_rec_rel_response(client, MQTT_MSG_TYPE_PUBREL, pkt_id, 1);
+
+    } else if (pkt_type == MQTT_MSG_TYPE_PUBREL) {
+      LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_message_received: PUBREL, sending PUBCOMP response with pkt_id: %d\n",pkt_id));
+      pub_ack_rec_rel_response(client, MQTT_MSG_TYPE_PUBCOMP, pkt_id, 0);
+
+    } else if (pkt_type == MQTT_MSG_TYPE_SUBACK || pkt_type == MQTT_MSG_TYPE_UNSUBACK ||
+              pkt_type == MQTT_MSG_TYPE_PUBCOMP || pkt_type == MQTT_MSG_TYPE_PUBACK) {
+      struct mqtt_request_t *r = mqtt_take_request(&client->pend_req_queue, pkt_id);
+      if (r != NULL) {
+        LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_message_received: %s response with id %d\n", mqtt_msg_type_to_str(pkt_type), pkt_id));
+        if (pkt_type == MQTT_MSG_TYPE_SUBACK) {
+          if (length < 3) {
+            LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_message_received: To small SUBACK packet\n"));
+            goto out_disconnect;
+          } else {
+            mqtt_incomming_suback(r, var_hdr_payload[2]);
+          }
+        } else if (r->cb != NULL) {
+          r->cb(r->arg, ERR_OK);
+        }
+        mqtt_delete_request(r);
+      } else {
+        LWIP_DEBUGF(MQTT_DEBUG_WARN,( "mqtt_message_received: Received %s reply, with wrong pkt_id: %d\n", mqtt_msg_type_to_str(pkt_type), pkt_id));
+      }
+    } else {
+      LWIP_DEBUGF(MQTT_DEBUG_WARN,( "mqtt_message_received: Received unknown message type: %d\n", pkt_type));
+      goto out_disconnect;
+    }
+  }
+  return res;
+out_disconnect:
+  return MQTT_CONNECT_DISCONNECTED;
+}
+
+
+/**
+ * MQTT incoming message parser
+ * @param client MQTT client
+ * @param p PBUF chain of received data
+ * @return Connection status
+ */
+static mqtt_connection_status_t
+mqtt_parse_incoming(mqtt_client_t *client, struct pbuf *p)
+{
+  u16_t in_offset = 0;
+  u32_t msg_rem_len = 0;
+  u8_t fixed_hdr_idx = 0;
+  u8_t b = 0;
+
+  while (p->tot_len > in_offset) {
+    if ((fixed_hdr_idx < 2) || ((b & 0x80) != 0)) {
+
+      if (fixed_hdr_idx < client->msg_idx) {
+        b = client->rx_buffer[fixed_hdr_idx];
+      } else {
+        b = pbuf_get_at(p, in_offset++);
+        client->rx_buffer[client->msg_idx++] = b;
+      }
+      fixed_hdr_idx++;
+
+      if (fixed_hdr_idx >= 2) {
+        msg_rem_len |= (u32_t)(b & 0x7f) << ((fixed_hdr_idx - 2) * 7);
+        if ((b & 0x80) == 0) {
+          LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_parse_incoming: Remaining length after fixed header: %d\n", msg_rem_len));
+          if (msg_rem_len == 0) {
+            /* Complete message with no extra headers of payload received */
+            mqtt_message_received(client, fixed_hdr_idx, 0, 0);
+            client->msg_idx = 0;
+            fixed_hdr_idx = 0;
+          } else {
+            /* Bytes remaining in message */
+            msg_rem_len = (msg_rem_len + fixed_hdr_idx) - client->msg_idx;
+          }
+        }
+      }
+    } else {
+      u16_t cpy_len, cpy_start, buffer_space;
+
+      cpy_start = (client->msg_idx - fixed_hdr_idx) % (MQTT_VAR_HEADER_BUFFER_LEN - fixed_hdr_idx) + fixed_hdr_idx;
+
+      /* Allow to copy the lesser one of available length in input data or bytes remaining in message */
+      cpy_len = (u16_t)LWIP_MIN((u16_t)(p->tot_len - in_offset), msg_rem_len);
+
+      /* Limit to available space in buffer */
+      buffer_space = MQTT_VAR_HEADER_BUFFER_LEN - cpy_start;
+      if (cpy_len > buffer_space) {
+        cpy_len = buffer_space;
+      }
+      pbuf_copy_partial(p, client->rx_buffer+cpy_start, cpy_len, in_offset);
+
+      /* Advance get and put indexes  */
+      client->msg_idx += cpy_len;
+      in_offset += cpy_len;
+      msg_rem_len -= cpy_len;
+
+      LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_parse_incoming: msg_idx: %d, cpy_len: %d, remaining %d\n", client->msg_idx, cpy_len, msg_rem_len));
+      if (msg_rem_len == 0 || cpy_len == buffer_space) {
+        /* Whole message received or buffer is full */
+        mqtt_connection_status_t res = mqtt_message_received(client, fixed_hdr_idx, (cpy_start + cpy_len) - fixed_hdr_idx, msg_rem_len);
+        if (res != MQTT_CONNECT_ACCEPTED) {
+          return res;
+        }
+        if (msg_rem_len == 0) {
+          /* Reset parser state */
+          client->msg_idx = 0;
+          /* msg_tot_len = 0; */
+          fixed_hdr_idx = 0;
+        }
+      }
+    }
+  }
+  return MQTT_CONNECT_ACCEPTED;
+}
+
+
+/**
+ * TCP received callback function. @see tcp_recv_fn
+ * @param arg MQTT client
+ * @param p PBUF chain of received data
+ * @param err Passed as return value if not ERR_OK
+ * @return ERR_OK or err passed into callback
+ */
+static err_t
+mqtt_tcp_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+  mqtt_client_t *client = (mqtt_client_t *)arg;
+  LWIP_ASSERT("mqtt_tcp_recv_cb: client != NULL", client != NULL);
+  LWIP_ASSERT("mqtt_tcp_recv_cb: client->conn == pcb", client->conn == pcb);
+
+  if (p == NULL) {
+    LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_tcp_recv_cb: Recv pbuf=NULL, remote has closed connection\n"));
+    mqtt_close(client, MQTT_CONNECT_DISCONNECTED);
+  } else {
+    mqtt_connection_status_t res;
+    if (err != ERR_OK) {
+      LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_tcp_recv_cb: Recv err=%d\n", err));
+      pbuf_free(p);
+      return err;
+    }
+
+    /* Tell remote that data has been received */
+    tcp_recved(pcb, p->tot_len);
+    res = mqtt_parse_incoming(client, p);
+    pbuf_free(p);
+
+    if (res != MQTT_CONNECT_ACCEPTED) {
+      mqtt_close(client, res);
+    }
+    /* If keep alive functionality is used */
+    if (client->keep_alive != 0) {
+      /* Reset server alive watchdog */
+      client->server_watchdog = 0;
+    }
+
+  }
+  return ERR_OK;
+}
+
+
+/**
+ * TCP data sent callback function. @see tcp_sent_fn
+ * @param arg MQTT client
+ * @param tpcb TCP connection handle
+ * @param len Number of bytes sent
+ * @return ERR_OK
+ */
+static err_t
+mqtt_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb, u16_t len)
+{
+  mqtt_client_t *client = (mqtt_client_t *)arg;
+
+  LWIP_UNUSED_ARG(tpcb);
+  LWIP_UNUSED_ARG(len);
+
+  if (client->conn_state == MQTT_CONNECTED) {
+    struct mqtt_request_t *r;
+
+    /* Reset keep-alive send timer and server watchdog */
+    client->cyclic_tick = 0;
+    client->server_watchdog = 0;
+    /* QoS 0 publish has no response from server, so call its callbacks here */
+    while ((r = mqtt_take_request(&client->pend_req_queue, 0)) != NULL) {
+      LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_tcp_sent_cb: Calling QoS 0 publish complete callback\n"));
+      if (r->cb != NULL) {
+        r->cb(r->arg, ERR_OK);
+      }
+      mqtt_delete_request(r);
+    }
+    /* Try send any remaining buffers from output queue */
+    mqtt_output_send(&client->output, client->conn);
+  }
+  return ERR_OK;
+}
+
+/**
+ * TCP error callback function. @see tcp_err_fn
+ * @param arg MQTT client
+ * @param err Error encountered
+ */
+static void
+mqtt_tcp_err_cb(void *arg, err_t err)
+{
+  mqtt_client_t *client = (mqtt_client_t *)arg;
+  LWIP_UNUSED_ARG(err); /* only used for debug output */
+  LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_tcp_err_cb: TCP error callback: error %d, arg: %p\n", err, arg));
+  LWIP_ASSERT("mqtt_tcp_err_cb: client != NULL", client != NULL);
+  /* Set conn to null before calling close as pcb is already deallocated*/
+  client->conn = 0;
+  mqtt_close(client, MQTT_CONNECT_DISCONNECTED);
+}
+
+/**
+ * TCP poll callback function. @see tcp_poll_fn
+ * @param arg MQTT client
+ * @param tpcb TCP connection handle
+ * @return err ERR_OK
+ */
+static err_t
+mqtt_tcp_poll_cb(void *arg, struct tcp_pcb *tpcb)
+{
+  mqtt_client_t *client = (mqtt_client_t *)arg;
+  if (client->conn_state == MQTT_CONNECTED) {
+    /* Try send any remaining buffers from output queue */
+    mqtt_output_send(&client->output, tpcb);
+  }
+  return ERR_OK;
+}
+
+/**
+ * TCP connect callback function. @see tcp_connected_fn
+ * @param arg MQTT client
+ * @param err Always ERR_OK, mqtt_tcp_err_cb is called in case of error
+ * @return ERR_OK
+ */
+static err_t
+mqtt_tcp_connect_cb(void *arg, struct tcp_pcb *tpcb, err_t err)
+{
+  mqtt_client_t* client = (mqtt_client_t *)arg;
+
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_tcp_connect_cb: TCP connect error %d\n", err));
+    return err;
+  }
+
+  /* Initiate receiver state */
+  client->msg_idx = 0;
+
+  /* Setup TCP callbacks */
+  tcp_recv(tpcb, mqtt_tcp_recv_cb);
+  tcp_sent(tpcb, mqtt_tcp_sent_cb);
+  tcp_poll(tpcb, mqtt_tcp_poll_cb, 2);
+
+  LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_tcp_connect_cb: TCP connection established to server\n"));
+  /* Enter MQTT connect state */
+  client->conn_state = MQTT_CONNECTING;
+
+  /* Start cyclic timer */
+  sys_timeout(MQTT_CYCLIC_TIMER_INTERVAL*1000, mqtt_cyclic_timer, client);
+  client->cyclic_tick = 0;
+
+  /* Start transmission from output queue, connect message is the first one out*/
+  mqtt_output_send(&client->output, client->conn);
+
+  return ERR_OK;
+}
+
+
+
+/*---------------------------------------------------------------------------------------------------- */
+/* Public API */
+
+
+/**
+ * @ingroup mqtt
+ * MQTT publish function.
+ * @param client MQTT client
+ * @param topic Publish topic string
+ * @param payload Data to publish (NULL is allowed)
+ * @param payload_length: Length of payload (0 is allowed)
+ * @param qos Quality of service, 0 1 or 2
+ * @param retain MQTT retain flag
+ * @param cb Callback to call when publish is complete or has timed out
+ * @param arg User supplied argument to publish callback
+ * @return ERR_OK if successful
+ *         ERR_CONN if client is disconnected
+ *         ERR_MEM if short on memory
+ */
+err_t
+mqtt_publish(mqtt_client_t *client, const char *topic, const void *payload, u16_t payload_length, u8_t qos, u8_t retain,
+             mqtt_request_cb_t cb, void *arg)
+{
+  struct mqtt_request_t *r;
+  u16_t pkt_id;
+  size_t topic_strlen;
+  size_t total_len;
+  u16_t topic_len;
+  u16_t remaining_length;
+
+  LWIP_ASSERT("mqtt_publish: client != NULL", client);
+  LWIP_ASSERT("mqtt_publish: topic != NULL", topic);
+  LWIP_ERROR("mqtt_publish: TCP disconnected", (client->conn_state != TCP_DISCONNECTED), return ERR_CONN);
+
+  topic_strlen = strlen(topic);
+  LWIP_ERROR("mqtt_publish: topic length overflow", (topic_strlen <= (0xFFFF - 2)), return ERR_ARG);
+  topic_len = (u16_t)topic_strlen;
+  total_len = 2 + topic_len + payload_length;
+  LWIP_ERROR("mqtt_publish: total length overflow", (total_len <= 0xFFFF), return ERR_ARG);
+  remaining_length = (u16_t)total_len;
+
+  LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_publish: Publish with payload length %d to topic \"%s\"\n", payload_length, topic));
+
+  if (qos > 0) {
+    remaining_length += 2;
+    /* Generate pkt_id id for QoS1 and 2 */
+    pkt_id = msg_generate_packet_id(client);
+  } else {
+    /* Use reserved value pkt_id 0 for QoS 0 in request handle */
+    pkt_id = 0;
+  }
+
+  r = mqtt_create_request(client->req_list, pkt_id, cb, arg);
+  if (r == NULL) {
+    return ERR_MEM;
+  }
+
+  if (mqtt_output_check_space(&client->output, remaining_length) == 0) {
+    mqtt_delete_request(r);
+    return ERR_MEM;
+  }
+  /* Append fixed header */
+  mqtt_output_append_fixed_header(&client->output, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain, remaining_length);
+
+  /* Append Topic */
+  mqtt_output_append_string(&client->output, topic, topic_len);
+
+  /* Append packet if for QoS 1 and 2*/
+  if (qos > 0) {
+    mqtt_output_append_u16(&client->output, pkt_id);
+  }
+
+  /* Append optional publish payload */
+  if ((payload != NULL) && (payload_length > 0)) {
+    mqtt_output_append_buf(&client->output, payload, payload_length);
+  }
+
+  mqtt_append_request(&client->pend_req_queue, r);
+  mqtt_output_send(&client->output, client->conn);
+  return ERR_OK;
+}
+
+
+/**
+ * @ingroup mqtt
+ * MQTT subscribe/unsubscribe function.
+ * @param client MQTT client
+ * @param topic topic to subscribe to
+ * @param qos Quality of service, 0 1 or 2 (only used for subscribe)
+ * @param cb Callback to call when subscribe/unsubscribe reponse is received
+ * @param arg User supplied argument to publish callback
+ * @param sub 1 for subscribe, 0 for unsubscribe
+ * @return ERR_OK if successful, @see err_t enum for other results
+ */
+err_t
+mqtt_sub_unsub(mqtt_client_t *client, const char *topic, u8_t qos, mqtt_request_cb_t cb, void *arg, u8_t sub)
+{
+  size_t topic_strlen;
+  size_t total_len;
+  u16_t topic_len;
+  u16_t remaining_length;
+  u16_t pkt_id;
+  struct mqtt_request_t *r;
+
+  LWIP_ASSERT("mqtt_sub_unsub: client != NULL", client);
+  LWIP_ASSERT("mqtt_sub_unsub: topic != NULL", topic);
+
+  topic_strlen = strlen(topic);
+  LWIP_ERROR("mqtt_sub_unsub: topic length overflow", (topic_strlen <= (0xFFFF - 2)), return ERR_ARG);
+  topic_len = (u16_t)topic_strlen;
+  /* Topic string, pkt_id, qos for subscribe */
+  total_len =  topic_len + 2 + 2 + (sub != 0);
+  LWIP_ERROR("mqtt_sub_unsub: total length overflow", (total_len <= 0xFFFF), return ERR_ARG);
+  remaining_length = (u16_t)total_len;
+
+  LWIP_ASSERT("mqtt_sub_unsub: qos < 3", qos < 3);
+  if (client->conn_state == TCP_DISCONNECTED) {
+    LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_sub_unsub: Can not (un)subscribe in disconnected state\n"));
+    return ERR_CONN;
+  }
+
+  pkt_id = msg_generate_packet_id(client);
+  r = mqtt_create_request(client->req_list, pkt_id, cb, arg);
+  if (r == NULL) {
+    return ERR_MEM;
+  }
+
+  if (mqtt_output_check_space(&client->output, remaining_length) == 0) {
+    mqtt_delete_request(r);
+    return ERR_MEM;
+  }
+
+  LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_sub_unsub: Client (un)subscribe to topic \"%s\", id: %d\n", topic, pkt_id));
+
+  mqtt_output_append_fixed_header(&client->output, sub ? MQTT_MSG_TYPE_SUBSCRIBE : MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0, remaining_length);
+  /* Packet id */
+  mqtt_output_append_u16(&client->output, pkt_id);
+  /* Topic */
+  mqtt_output_append_string(&client->output, topic, topic_len);
+  /* QoS */
+  if (sub != 0) {
+    mqtt_output_append_u8(&client->output, LWIP_MIN(qos, 2));
+  }
+
+  mqtt_append_request(&client->pend_req_queue, r);
+  mqtt_output_send(&client->output, client->conn);
+  return ERR_OK;
+}
+
+
+/**
+ * @ingroup mqtt
+ * Set callback to handle incoming publish requests from server
+ * @param client MQTT client
+ * @param pub_cb Callback invoked when publish starts, contain topic and total length of payload
+ * @param data_cb Callback for each fragment of payload that arrives
+ * @param arg User supplied argument to both callbacks
+ */
+void
+mqtt_set_inpub_callback(mqtt_client_t *client, mqtt_incoming_publish_cb_t pub_cb,
+                             mqtt_incoming_data_cb_t data_cb, void *arg)
+{
+  LWIP_ASSERT("mqtt_set_inpub_callback: client != NULL", client != NULL);
+  client->data_cb = data_cb;
+  client->pub_cb = pub_cb;
+  client->inpub_arg = arg;
+}
+
+/**
+ * @ingroup mqtt
+ * Create a new MQTT client instance
+ * @return Pointer to instance on success, NULL otherwise
+ */
+mqtt_client_t *
+mqtt_client_new(void)
+{
+  mqtt_client_t *client = (mqtt_client_t *)mem_malloc(sizeof(mqtt_client_t));
+  if (client != NULL) {
+    memset(client, 0, sizeof(mqtt_client_t));
+  }
+  return client;
+}
+
+
+/**
+ * @ingroup mqtt
+ * Connect to MQTT server
+ * @param client MQTT client
+ * @param ip_addr Server IP
+ * @param port Server port
+ * @param cb Connection state change callback
+ * @param arg User supplied argument to connection callback
+ * @param client_info Client identification and connection options
+ * @return ERR_OK if successful, @see err_t enum for other results
+ */
+err_t
+mqtt_client_connect(mqtt_client_t *client, const ip_addr_t *ip_addr, u16_t port, mqtt_connection_cb_t cb, void *arg,
+                    const struct mqtt_connect_client_info_t *client_info)
+{
+  err_t err;
+  size_t len;
+  u16_t client_id_length;
+  /* Length is the sum of 2+"MQTT", protocol level, flags and keep alive */
+  u16_t remaining_length = 2 + 4 + 1 + 1 + 2;
+  u8_t flags = 0, will_topic_len = 0, will_msg_len = 0;
+
+  LWIP_ASSERT("mqtt_client_connect: client != NULL", client != NULL);
+  LWIP_ASSERT("mqtt_client_connect: ip_addr != NULL", ip_addr != NULL);
+  LWIP_ASSERT("mqtt_client_connect: client_info != NULL", client_info != NULL);
+  LWIP_ASSERT("mqtt_client_connect: client_info->client_id != NULL", client_info->client_id != NULL);
+
+  if (client->conn_state != TCP_DISCONNECTED) {
+    LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_client_connect: Already connected\n"));
+    return ERR_ISCONN;
+  }
+
+  /* Wipe clean */
+  memset(client, 0, sizeof(mqtt_client_t));
+  client->connect_arg = arg;
+  client->connect_cb = cb;
+  client->keep_alive = client_info->keep_alive;
+  mqtt_init_requests(client->req_list);
+
+  /* Build connect message */
+  if (client_info->will_topic != NULL && client_info->will_msg != NULL) {
+    flags |= MQTT_CONNECT_FLAG_WILL;
+    flags |= (client_info->will_qos & 3) << 3;
+    if (client_info->will_retain) {
+      flags |= MQTT_CONNECT_FLAG_WILL_RETAIN;
+    }
+    len = strlen(client_info->will_topic);
+    LWIP_ERROR("mqtt_client_connect: client_info->will_topic length overflow", len <= 0xFF, return ERR_VAL);
+    LWIP_ERROR("mqtt_client_connect: client_info->will_topic length must be > 0", len > 0, return ERR_VAL);
+    will_topic_len = (u8_t)len;
+    len = strlen(client_info->will_msg);
+    LWIP_ERROR("mqtt_client_connect: client_info->will_msg length overflow", len <= 0xFF, return ERR_VAL);
+    will_msg_len = (u8_t)len;
+    len = remaining_length + 2 + will_topic_len + 2 + will_msg_len;
+    LWIP_ERROR("mqtt_client_connect: remaining_length overflow", len <= 0xFFFF, return ERR_VAL);
+    remaining_length = (u16_t)len;
+  }
+
+  /* Don't complicate things, always connect using clean session */
+  flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION;
+
+  len = strlen(client_info->client_id);
+  LWIP_ERROR("mqtt_client_connect: client_info->client_id length overflow", len <= 0xFFFF, return ERR_VAL);
+  client_id_length = (u16_t)len;
+  len = remaining_length + 2 + client_id_length;
+  LWIP_ERROR("mqtt_client_connect: remaining_length overflow", len <= 0xFFFF, return ERR_VAL);
+  remaining_length = (u16_t)len;
+
+  if (mqtt_output_check_space(&client->output, remaining_length) == 0) {
+    return ERR_MEM;
+  }
+
+  client->conn = tcp_new();
+  if (client->conn == NULL) {
+    return ERR_MEM;
+  }
+
+  /* Set arg pointer for callbacks */
+  tcp_arg(client->conn, client);
+  /* Any local address, pick random local port number */
+  err = tcp_bind(client->conn, IP_ADDR_ANY, 0);
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(MQTT_DEBUG_WARN,("mqtt_client_connect: Error binding to local ip/port, %d\n", err));
+    goto tcp_fail;
+  }
+  LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_client_connect: Connecting to host: %s at port:%"U16_F"\n", ipaddr_ntoa(ip_addr), port));
+
+  /* Connect to server */
+  err = tcp_connect(client->conn, ip_addr, port, mqtt_tcp_connect_cb);
+  if (err != ERR_OK) {
+    LWIP_DEBUGF(MQTT_DEBUG_TRACE,("mqtt_client_connect: Error connecting to remote ip/port, %d\n", err));
+    goto tcp_fail;
+  }
+  /* Set error callback */
+  tcp_err(client->conn, mqtt_tcp_err_cb);
+  client->conn_state = TCP_CONNECTING;
+
+  /* Append fixed header */
+  mqtt_output_append_fixed_header(&client->output, MQTT_MSG_TYPE_CONNECT, 0, 0, 0, remaining_length);
+  /* Append Protocol string */
+  mqtt_output_append_string(&client->output, "MQTT", 4);
+  /* Append Protocol level */
+  mqtt_output_append_u8(&client->output, 4);
+  /* Append connect flags */
+  mqtt_output_append_u8(&client->output, flags);
+  /* Append keep-alive */
+  mqtt_output_append_u16(&client->output, client_info->keep_alive);
+  /* Append client id */
+  mqtt_output_append_string(&client->output, client_info->client_id, client_id_length);
+  /* Append will message if used */
+  if ((flags & MQTT_CONNECT_FLAG_WILL) != 0) {
+    mqtt_output_append_string(&client->output, client_info->will_topic, will_topic_len);
+    mqtt_output_append_string(&client->output, client_info->will_msg, will_msg_len);
+  }
+  return ERR_OK;
+
+tcp_fail:
+  tcp_abort(client->conn);
+  client->conn = NULL;
+  return err;
+}
+
+
+/**
+ * @ingroup mqtt
+ * Disconnect from MQTT server
+ * @param client MQTT client
+ */
+void
+mqtt_disconnect(mqtt_client_t *client)
+{
+  LWIP_ASSERT("mqtt_disconnect: client != NULL", client);
+  /* If connection in not already closed */
+  if (client->conn_state != TCP_DISCONNECTED) {
+    /* Set conn_state before calling mqtt_close to prevent callback from being called */
+    client->conn_state = TCP_DISCONNECTED;
+    mqtt_close(client, (mqtt_connection_status_t)0);
+  }
+}
+
+/**
+ * @ingroup mqtt
+ * Check connection with server
+ * @param client MQTT client
+ * @return 1 if connected to server, 0 otherwise
+ */
+u8_t
+mqtt_client_is_connected(mqtt_client_t *client)
+{
+  LWIP_ASSERT("mqtt_client_is_connected: client != NULL", client);
+  return client->conn_state == MQTT_CONNECTED;
+}
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/netbiosns/netbiosns.c b/src/apps/netbiosns/netbiosns.c
new file mode 100644
index 0000000..2dfbe65
--- /dev/null
+++ b/src/apps/netbiosns/netbiosns.c
@@ -0,0 +1,367 @@
+/**
+ * @file
+ * NetBIOS name service responder
+ */
+
+/**
+ * @defgroup netbiosns NETBIOS responder
+ * @ingroup apps
+ *
+ * This is an example implementation of a NetBIOS name server.
+ * It responds to name queries for a configurable name.
+ * Name resolving is not supported.
+ *
+ * Note that the device doesn't broadcast it's own name so can't
+ * detect duplicate names!
+ */
+
+/*
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce 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/apps/netbiosns.h"
+
+#if LWIP_IPV4 && LWIP_UDP  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/udp.h"
+#include "lwip/netif.h"
+
+#include <string.h>
+
+/** default port number for "NetBIOS Name service */
+#define NETBIOS_PORT     137
+
+/** size of a NetBIOS name */
+#define NETBIOS_NAME_LEN 16
+
+/** The Time-To-Live for NetBIOS name responds (in seconds)
+ * Default is 300000 seconds (3 days, 11 hours, 20 minutes) */
+#define NETBIOS_NAME_TTL 300000u
+
+/** NetBIOS header flags */
+#define NETB_HFLAG_RESPONSE           0x8000U
+#define NETB_HFLAG_OPCODE             0x7800U
+#define NETB_HFLAG_OPCODE_NAME_QUERY  0x0000U
+#define NETB_HFLAG_AUTHORATIVE        0x0400U
+#define NETB_HFLAG_TRUNCATED          0x0200U
+#define NETB_HFLAG_RECURS_DESIRED     0x0100U
+#define NETB_HFLAG_RECURS_AVAILABLE   0x0080U
+#define NETB_HFLAG_BROADCAST          0x0010U
+#define NETB_HFLAG_REPLYCODE          0x0008U
+#define NETB_HFLAG_REPLYCODE_NOERROR  0x0000U
+
+/** NetBIOS name flags */
+#define NETB_NFLAG_UNIQUE             0x8000U
+#define NETB_NFLAG_NODETYPE           0x6000U
+#define NETB_NFLAG_NODETYPE_HNODE     0x6000U
+#define NETB_NFLAG_NODETYPE_MNODE     0x4000U
+#define NETB_NFLAG_NODETYPE_PNODE     0x2000U
+#define NETB_NFLAG_NODETYPE_BNODE     0x0000U
+
+/** NetBIOS message header */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct netbios_hdr {
+  PACK_STRUCT_FIELD(u16_t trans_id);
+  PACK_STRUCT_FIELD(u16_t flags);
+  PACK_STRUCT_FIELD(u16_t questions);
+  PACK_STRUCT_FIELD(u16_t answerRRs);
+  PACK_STRUCT_FIELD(u16_t authorityRRs);
+  PACK_STRUCT_FIELD(u16_t additionalRRs);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** NetBIOS message name part */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct netbios_name_hdr {
+  PACK_STRUCT_FLD_8(u8_t  nametype);
+  PACK_STRUCT_FLD_8(u8_t  encname[(NETBIOS_NAME_LEN*2)+1]);
+  PACK_STRUCT_FIELD(u16_t type);
+  PACK_STRUCT_FIELD(u16_t cls);
+  PACK_STRUCT_FIELD(u32_t ttl);
+  PACK_STRUCT_FIELD(u16_t datalen);
+  PACK_STRUCT_FIELD(u16_t flags);
+  PACK_STRUCT_FLD_S(ip4_addr_p_t addr);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/** NetBIOS message */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct netbios_resp
+{
+  struct netbios_hdr      resp_hdr;
+  struct netbios_name_hdr resp_name;
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+#ifdef NETBIOS_LWIP_NAME
+#define NETBIOS_LOCAL_NAME NETBIOS_LWIP_NAME
+#else
+static char netbiosns_local_name[NETBIOS_NAME_LEN];
+#define NETBIOS_LOCAL_NAME netbiosns_local_name
+#endif
+
+struct udp_pcb *netbiosns_pcb;
+
+/** Decode a NetBIOS name (from packet to string) */
+static int
+netbiosns_name_decode(char *name_enc, char *name_dec, int name_dec_len)
+{
+  char *pname;
+  char  cname;
+  char  cnbname;
+  int   idx = 0;
+
+  LWIP_UNUSED_ARG(name_dec_len);
+
+  /* Start decoding netbios name. */
+  pname  = name_enc;
+  for (;;) {
+    /* Every two characters of the first level-encoded name
+     * turn into one character in the decoded name. */
+    cname = *pname;
+    if (cname == '\0')
+      break;    /* no more characters */
+    if (cname == '.')
+      break;    /* scope ID follows */
+    if (cname < 'A' || cname > 'Z') {
+      /* Not legal. */
+      return -1;
+    }
+    cname -= 'A';
+    cnbname = cname << 4;
+    pname++;
+
+    cname = *pname;
+    if (cname == '\0' || cname == '.') {
+      /* No more characters in the name - but we're in
+       * the middle of a pair.  Not legal. */
+      return -1;
+    }
+    if (cname < 'A' || cname > 'Z') {
+      /* Not legal. */
+      return -1;
+    }
+    cname -= 'A';
+    cnbname |= cname;
+    pname++;
+
+    /* Do we have room to store the character? */
+    if (idx < NETBIOS_NAME_LEN) {
+      /* Yes - store the character. */
+      name_dec[idx++] = (cnbname!=' '?cnbname:'\0');
+    }
+  }
+
+  return 0;
+}
+
+#if 0 /* function currently unused */
+/** Encode a NetBIOS name (from string to packet) - currently unused because
+    we don't ask for names. */
+static int
+netbiosns_name_encode(char *name_enc, char *name_dec, int name_dec_len)
+{
+  char         *pname;
+  char          cname;
+  unsigned char ucname;
+  int           idx = 0;
+
+  /* Start encoding netbios name. */
+  pname = name_enc;
+
+  for (;;) {
+    /* Every two characters of the first level-encoded name
+     * turn into one character in the decoded name. */
+    cname = *pname;
+    if (cname == '\0')
+      break;    /* no more characters */
+    if (cname == '.')
+      break;    /* scope ID follows */
+    if ((cname < 'A' || cname > 'Z') && (cname < '0' || cname > '9')) {
+      /* Not legal. */
+      return -1;
+    }
+
+    /* Do we have room to store the character? */
+    if (idx >= name_dec_len) {
+      return -1;
+    }
+
+    /* Yes - store the character. */
+    ucname = cname;
+    name_dec[idx++] = ('A'+((ucname>>4) & 0x0F));
+    name_dec[idx++] = ('A'+( ucname     & 0x0F));
+    pname++;
+  }
+
+  /* Fill with "space" coding */
+  for (;idx < name_dec_len - 1;) {
+    name_dec[idx++] = 'C';
+    name_dec[idx++] = 'A';
+  }
+
+  /* Terminate string */
+  name_dec[idx] = '\0';
+
+  return 0;
+}
+#endif /* 0 */
+
+/** NetBIOS Name service recv callback */
+static void
+netbiosns_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+  LWIP_UNUSED_ARG(arg);
+
+  /* if packet is valid */
+  if (p != NULL) {
+    char   netbios_name[NETBIOS_NAME_LEN+1];
+    struct netbios_hdr*      netbios_hdr      = (struct netbios_hdr*)p->payload;
+    struct netbios_name_hdr* netbios_name_hdr = (struct netbios_name_hdr*)(netbios_hdr+1);
+
+    /* we only answer if we got a default interface */
+    if (netif_default != NULL) {
+      /* @todo: do we need to check answerRRs/authorityRRs/additionalRRs? */
+      /* if the packet is a NetBIOS name query question */
+      if (((netbios_hdr->flags & PP_NTOHS(NETB_HFLAG_OPCODE)) == PP_NTOHS(NETB_HFLAG_OPCODE_NAME_QUERY)) &&
+          ((netbios_hdr->flags & PP_NTOHS(NETB_HFLAG_RESPONSE)) == 0) &&
+           (netbios_hdr->questions == PP_NTOHS(1))) {
+        /* decode the NetBIOS name */
+        netbiosns_name_decode((char*)(netbios_name_hdr->encname), netbios_name, sizeof(netbios_name));
+        /* if the packet is for us */
+        if (lwip_strnicmp(netbios_name, NETBIOS_LOCAL_NAME, sizeof(NETBIOS_LOCAL_NAME)) == 0) {
+          struct pbuf *q;
+          struct netbios_resp *resp;
+
+          q = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct netbios_resp), PBUF_RAM);
+          if (q != NULL) {
+            resp = (struct netbios_resp*)q->payload;
+
+            /* prepare NetBIOS header response */
+            resp->resp_hdr.trans_id      = netbios_hdr->trans_id;
+            resp->resp_hdr.flags         = PP_HTONS(NETB_HFLAG_RESPONSE |
+                                                 NETB_HFLAG_OPCODE_NAME_QUERY |
+                                                 NETB_HFLAG_AUTHORATIVE |
+                                                 NETB_HFLAG_RECURS_DESIRED);
+            resp->resp_hdr.questions     = 0;
+            resp->resp_hdr.answerRRs     = PP_HTONS(1);
+            resp->resp_hdr.authorityRRs  = 0;
+            resp->resp_hdr.additionalRRs = 0;
+
+            /* prepare NetBIOS header datas */
+            MEMCPY( resp->resp_name.encname, netbios_name_hdr->encname, sizeof(netbios_name_hdr->encname));
+            resp->resp_name.nametype     = netbios_name_hdr->nametype;
+            resp->resp_name.type         = netbios_name_hdr->type;
+            resp->resp_name.cls          = netbios_name_hdr->cls;
+            resp->resp_name.ttl          = PP_HTONL(NETBIOS_NAME_TTL);
+            resp->resp_name.datalen      = PP_HTONS(sizeof(resp->resp_name.flags)+sizeof(resp->resp_name.addr));
+            resp->resp_name.flags        = PP_HTONS(NETB_NFLAG_NODETYPE_BNODE);
+            ip4_addr_copy(resp->resp_name.addr, *netif_ip4_addr(netif_default));
+
+            /* send the NetBIOS response */
+            udp_sendto(upcb, q, addr, port);
+
+            /* free the "reference" pbuf */
+            pbuf_free(q);
+          }
+        }
+      }
+    }
+    /* free the pbuf */
+    pbuf_free(p);
+  }
+}
+
+/**
+ * @ingroup netbiosns 
+ * Init netbios responder
+ */
+void
+netbiosns_init(void)
+{
+#ifdef NETBIOS_LWIP_NAME
+  LWIP_ASSERT("NetBIOS name is too long!", strlen(NETBIOS_LWIP_NAME) < NETBIOS_NAME_LEN);
+#endif
+
+  netbiosns_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+  if (netbiosns_pcb != NULL) {
+    /* we have to be allowed to send broadcast packets! */
+    ip_set_option(netbiosns_pcb, SOF_BROADCAST);
+    udp_bind(netbiosns_pcb, IP_ANY_TYPE, NETBIOS_PORT);
+    udp_recv(netbiosns_pcb, netbiosns_recv, netbiosns_pcb);
+  }
+}
+
+#ifndef NETBIOS_LWIP_NAME
+/**
+ * @ingroup netbiosns 
+ * Set netbios name. ATTENTION: the hostname must be less than 15 characters!
+ */
+void
+netbiosns_set_name(const char* hostname)
+{
+  size_t copy_len = strlen(hostname);
+  LWIP_ASSERT("NetBIOS name is too long!", copy_len < NETBIOS_NAME_LEN);
+  if (copy_len >= NETBIOS_NAME_LEN) {
+    copy_len = NETBIOS_NAME_LEN - 1;
+  }
+  MEMCPY(netbiosns_local_name, hostname, copy_len + 1);
+}
+#endif
+
+/**
+ * @ingroup netbiosns 
+ * Stop netbios responder
+ */
+void
+netbiosns_stop(void)
+{
+  if (netbiosns_pcb != NULL) {
+    udp_remove(netbiosns_pcb);
+    netbiosns_pcb = NULL;
+  }
+}
+
+#endif /* LWIP_IPV4 && LWIP_UDP */
diff --git a/src/apps/snmp/snmp_asn1.c b/src/apps/snmp/snmp_asn1.c
new file mode 100644
index 0000000..f35b760
--- /dev/null
+++ b/src/apps/snmp/snmp_asn1.c
@@ -0,0 +1,749 @@
+/**
+ * @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@axon.tv>
+ *         Martin Hentschel <info@cl-soft.de>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "snmp_asn1.h"
+
+#define PBUF_OP_EXEC(code) \
+  if ((code) != ERR_OK) { \
+    return ERR_BUF; \
+  }
+
+/**
+ * Encodes a TLV into a pbuf stream.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param tlv TLV to encode
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_ans1_enc_tlv(struct snmp_pbuf_stream* pbuf_stream, struct snmp_asn1_tlv* tlv)
+{
+  u8_t data;
+  u8_t length_bytes_required;
+
+  /* write type */
+  if ((tlv->type & SNMP_ASN1_DATATYPE_MASK) == SNMP_ASN1_DATATYPE_EXTENDED) {
+    /* extended format is not used by SNMP so we do not accept those values */
+    return ERR_ARG;
+  }
+  if (tlv->type_len != 0) {
+    /* any other value as auto is not accepted for type (we always use one byte because extended syntax is prohibited) */
+    return ERR_ARG;
+  }
+
+  PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, tlv->type));
+  tlv->type_len = 1;
+
+  /* write length */
+  if (tlv->value_len <= 127) {
+    length_bytes_required = 1;
+  } else if (tlv->value_len <= 255) {
+    length_bytes_required = 2;
+  } else  {
+    length_bytes_required = 3;
+  }
+
+  /* check for forced min length */
+  if (tlv->length_len > 0) {
+    if (tlv->length_len < length_bytes_required) {
+      /* unable to code requested length in requested number of bytes */
+      return ERR_ARG;
+    }
+
+    length_bytes_required = tlv->length_len;
+  } else {
+    tlv->length_len = length_bytes_required;
+  }
+
+  if (length_bytes_required > 1) {
+    /* multi byte representation required */
+    length_bytes_required--;
+    data = 0x80 | length_bytes_required; /* extended length definition, 1 length byte follows */
+
+    PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, data));
+
+    while (length_bytes_required > 1) {
+      if (length_bytes_required == 2) {
+        /* append high byte */
+        data = (u8_t)(tlv->value_len >> 8);
+      } else {
+        /* append leading 0x00 */
+        data = 0x00;
+      }
+
+      PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, data));
+      length_bytes_required--;
+    }
+  }
+
+  /* append low byte */
+  data = (u8_t)(tlv->value_len & 0xFF);
+  PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, data));
+
+  return ERR_OK;
+}
+
+/**
+ * Encodes raw data (octet string, opaque) into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param raw_len raw data length
+ * @param raw points raw data
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_asn1_enc_raw(struct snmp_pbuf_stream* pbuf_stream, const u8_t *raw, u16_t raw_len)
+{
+  PBUF_OP_EXEC(snmp_pbuf_stream_writebuf(pbuf_stream, raw, raw_len));
+
+  return ERR_OK;
+}
+
+/**
+ * Encodes u32_t (counter, gauge, timeticks) into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @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 successful, ERR_ARG if we can't (or won't) encode
+ *
+ * @see snmp_asn1_enc_u32t_cnt()
+ */
+err_t
+snmp_asn1_enc_u32t(struct snmp_pbuf_stream* pbuf_stream, u16_t octets_needed, u32_t value)
+{
+  if (octets_needed > 5) {
+    return ERR_ARG;
+  }
+  if (octets_needed == 5) {
+    /* not enough bits in 'value' add leading 0x00 */
+    PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, 0x00));
+    octets_needed--;
+  }
+
+  while (octets_needed > 1) {
+    octets_needed--;
+    PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)(value >> (octets_needed << 3))));
+  }
+
+  /* (only) one least significant octet */
+  PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)value));
+
+  return ERR_OK;
+}
+
+/**
+ * Encodes u64_t (counter64) into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @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 successful, ERR_ARG if we can't (or won't) encode
+ *
+ * @see snmp_asn1_enc_u64t_cnt()
+ */
+err_t
+snmp_asn1_enc_u64t(struct snmp_pbuf_stream* pbuf_stream, u16_t octets_needed, const u32_t* value)
+{
+  if (octets_needed > 9) {
+    return ERR_ARG;
+  }
+  if (octets_needed == 9) {
+    /* not enough bits in 'value' add leading 0x00 */
+    PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, 0x00));
+    octets_needed--;
+  }
+
+  while (octets_needed > 4) {
+    octets_needed--;
+    PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)(*value >> ((octets_needed-4) << 3))));
+  }
+
+  /* skip to low u32 */
+  value++;
+
+  while (octets_needed > 1) {
+    octets_needed--;
+    PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)(*value >> (octets_needed << 3))));
+  }
+
+  /* always write at least one octet (also in case of value == 0) */
+  PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)(*value)));
+
+  return ERR_OK;
+}
+
+/**
+ * Encodes s32_t integer into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @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 successful, ERR_ARG if we can't (or won't) encode
+ *
+ * @see snmp_asn1_enc_s32t_cnt()
+ */
+err_t
+snmp_asn1_enc_s32t(struct snmp_pbuf_stream* pbuf_stream, u16_t octets_needed, s32_t value)
+{
+  while (octets_needed > 1) {
+    octets_needed--;
+
+    PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)(value >> (octets_needed << 3))));
+  }
+
+  /* (only) one least significant octet */
+  PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)value));
+
+  return ERR_OK;
+}
+
+/**
+ * Encodes object identifier into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param oid points to object identifier array
+ * @param oid_len object identifier array length
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_asn1_enc_oid(struct snmp_pbuf_stream* pbuf_stream, const u32_t *oid, u16_t oid_len)
+{
+  if (oid_len > 1) {
+    /* write compressed first two sub id's */
+    u32_t compressed_byte = ((oid[0] * 40) + oid[1]);
+    PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)compressed_byte));
+    oid_len -= 2;
+    oid += 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 (oid_len > 0) {
+    u32_t sub_id;
+    u8_t shift, tail;
+
+    oid_len--;
+    sub_id = *oid;
+    tail = 0;
+    shift = 28;
+    while (shift > 0) {
+      u8_t code;
+
+      code = (u8_t)(sub_id >> shift);
+      if ((code != 0) || (tail != 0)) {
+        tail = 1;
+        PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, code | 0x80));
+      }
+      shift -= 7;
+    }
+    PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)sub_id & 0x7F));
+
+    /* proceed to next sub-identifier */
+    oid++;
+  }
+  return ERR_OK;
+}
+
+/**
+ * Returns octet count for length.
+ *
+ * @param length parameter 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 value to be encoded
+ * @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 u64_t.
+ *
+ * @param value value to be encoded
+ * @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_u64t_cnt(const u32_t *value, u16_t *octets_needed)
+{
+  /* check if high u32 is 0 */
+  if (*value == 0x00) {
+    /* only low u32 is important */
+    value++;
+    snmp_asn1_enc_u32t_cnt(*value, octets_needed);
+  } else {
+    /* low u32 does not matter for length determination */
+    snmp_asn1_enc_u32t_cnt(*value, octets_needed);
+    *octets_needed = *octets_needed + 4; /* add the 4 bytes of low u32 */
+  }
+}
+
+/**
+ * Returns octet count for an s32_t.
+ *
+ * @param value value to be encoded
+ * @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 oid points to object identifier array
+ * @param oid_len object identifier array length
+ * @param octets_needed points to the return value
+ */
+void
+snmp_asn1_enc_oid_cnt(const u32_t *oid, u16_t oid_len, u16_t *octets_needed)
+{
+  u32_t sub_id;
+
+  *octets_needed = 0;
+  if (oid_len > 1) {
+    /* compressed prefix in one octet */
+    (*octets_needed)++;
+    oid_len -= 2;
+    oid += 2;
+  }
+  while (oid_len > 0) {
+    oid_len--;
+    sub_id = *oid;
+
+    sub_id >>= 7;
+    (*octets_needed)++;
+    while (sub_id > 0) {
+      sub_id >>= 7;
+      (*octets_needed)++;
+    }
+    oid++;
+  }
+}
+
+/**
+ * Decodes a TLV from a pbuf stream.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param tlv returns decoded TLV
+ * @return ERR_OK if successful, ERR_VAL if we can't decode
+ */
+err_t
+snmp_asn1_dec_tlv(struct snmp_pbuf_stream* pbuf_stream, struct snmp_asn1_tlv* tlv)
+{
+  u8_t data;
+
+  /* decode type first */
+  PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+  tlv->type = data;
+
+  if ((tlv->type & SNMP_ASN1_DATATYPE_MASK) == SNMP_ASN1_DATATYPE_EXTENDED) {
+    /* extended format is not used by SNMP so we do not accept those values */
+    return ERR_VAL;
+  }
+  tlv->type_len = 1;
+
+  /* now, decode length */
+  PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+
+  if (data < 0x80) { /* short form */
+    tlv->length_len = 1;
+    tlv->value_len  = data;
+  } else if (data > 0x80) { /* long form */
+    u8_t length_bytes = data - 0x80;
+    tlv->length_len = length_bytes + 1; /* this byte + defined number of length bytes following */
+    tlv->value_len = 0;
+
+    while (length_bytes > 0) {
+      /* we only support up to u16.maxvalue-1 (2 bytes) but have to accept leading zero bytes */
+      if (tlv->value_len > 0xFF) {
+        return ERR_VAL;
+      }
+      PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+      tlv->value_len <<= 8;
+      tlv->value_len |= data;
+
+      /* take care for special value used for indefinite length */
+      if (tlv->value_len == 0xFFFF) {
+        return ERR_VAL;
+      }
+
+      length_bytes--;
+    }
+  } else { /* data == 0x80 indefinite length form */
+    /* (not allowed for SNMP; RFC 1157, 3.2.2) */
+    return ERR_VAL;
+  }
+
+  return ERR_OK;
+}
+
+/**
+ * Decodes positive integer (counter, gauge, timeticks) into u32_t.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded integer field
+ * @param value return host order integer
+ * @return ERR_OK if successful, 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 snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t *value)
+{
+  u8_t data;
+
+  if ((len > 0) && (len <= 5)) {
+    PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+
+    /* expecting sign bit to be zero, only unsigned please! */
+    if (((len == 5) && (data == 0x00)) || ((len < 5) && ((data & 0x80) == 0))) {
+      *value = data;
+      len--;
+
+      while (len > 0) {
+        PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+        len--;
+
+        *value <<= 8;
+        *value |= data;
+      }
+
+      return ERR_OK;
+    }
+  }
+
+  return ERR_VAL;
+}
+
+/**
+ * Decodes large positive integer (counter64) into 2x u32_t.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded integer field
+ * @param value return host order integer
+ * @return ERR_OK if successful, 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_u64t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t *value)
+{
+  u8_t data;
+
+  if (len <= 4) {
+    /* high u32 is 0 */
+    *value = 0;
+    /* directly skip to low u32 */
+    value++;
+  }
+
+  if ((len > 0) && (len <= 9)) {
+    PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+
+    /* expecting sign bit to be zero, only unsigned please! */
+    if (((len == 9) && (data == 0x00)) || ((len < 9) && ((data & 0x80) == 0))) {
+      *value = data;
+      len--;
+
+      while (len > 0) {
+        PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+
+        if (len == 4) {
+          /* skip to low u32 */
+          value++;
+          *value = 0;
+        } else {
+          *value <<= 8;
+        }
+
+        *value |= data;
+        len--;
+      }
+
+      return ERR_OK;
+    }
+  }
+
+  return ERR_VAL;
+}
+
+/**
+ * Decodes integer into s32_t.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded integer field
+ * @param value return host order integer
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
+ *
+ * @note ASN coded integers are _always_ signed!
+ */
+err_t
+snmp_asn1_dec_s32t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, s32_t *value)
+{
+#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;
+  u8_t data;
+
+  if ((len > 0) && (len < 5)) {
+    PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+    len--;
+
+    if (data & 0x80) {
+      /* negative, start from -1 */
+      *value = -1;
+      sign = 1;
+      *lsb_ptr &= data;
+    } else {
+      /* positive, start from 0 */
+      *value = 0;
+      sign = 0;
+      *lsb_ptr |= data;
+    }
+
+    /* OR/AND octets with value */
+    while (len > 0) {
+      PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+      len--;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+      *value <<= 8;
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+      *value >>= 8;
+#endif
+
+      if (sign) {
+        *lsb_ptr |= 255;
+        *lsb_ptr &= data;
+      } else {
+        *lsb_ptr |= data;
+      }
+    }
+
+    return ERR_OK;
+  }
+
+  return ERR_VAL;
+}
+
+/**
+ * Decodes object identifier from incoming message into array of u32_t.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded object identifier
+ * @param oid return decoded object identifier
+ * @param oid_len return decoded object identifier length
+ * @param oid_max_len size of oid buffer
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
+ */
+err_t
+snmp_asn1_dec_oid(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t* oid, u8_t* oid_len, u8_t oid_max_len)
+{
+  u32_t *oid_ptr;
+  u8_t data;
+
+  *oid_len = 0;
+  oid_ptr = oid;
+  if (len > 0) {
+    if (oid_max_len < 2) {
+      return ERR_MEM;
+    }
+
+    PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+    len--;
+
+    /* first compressed octet */
+    if (data == 0x2B) {
+      /* (most) common case 1.3 (iso.org) */
+      *oid_ptr = 1;
+      oid_ptr++;
+      *oid_ptr = 3;
+      oid_ptr++;
+    } else if (data < 40) {
+      *oid_ptr = 0;
+      oid_ptr++;
+      *oid_ptr = data;
+      oid_ptr++;
+    } else if (data < 80) {
+      *oid_ptr = 1;
+      oid_ptr++;
+      *oid_ptr = data - 40;
+      oid_ptr++;
+    } else {
+      *oid_ptr = 2;
+      oid_ptr++;
+      *oid_ptr = data - 80;
+      oid_ptr++;
+    }
+    *oid_len = 2;
+  } else {
+    /* accepting zero length identifiers e.g. for getnext operation. uncommon but valid */
+    return ERR_OK;
+  }
+
+  while ((len > 0) && (*oid_len < oid_max_len)) {
+    PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+    len--;
+
+    if ((data & 0x80) == 0x00) {
+      /* sub-identifier uses single octet */
+      *oid_ptr = data;
+    } else {
+      /* sub-identifier uses multiple octets */
+      u32_t sub_id = (data & ~0x80);
+      while ((len > 0) && ((data & 0x80) != 0)) {
+        PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+        len--;
+
+        sub_id = (sub_id << 7) + (data & ~0x80);
+      }
+
+      if ((data & 0x80) != 0) {
+        /* "more bytes following" bit still set at end of len */
+        return ERR_VAL;
+      }
+      *oid_ptr = sub_id;
+    }
+    oid_ptr++;
+    (*oid_len)++;
+  }
+
+  if (len > 0) {
+    /* OID to long to fit in our buffer */
+    return ERR_MEM;
+  }
+
+  return ERR_OK;
+}
+
+/**
+ * Decodes (copies) raw data (ip-addresses, octet strings, opaque encoding)
+ * from incoming message into array.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded raw data (zero is valid, e.g. empty string!)
+ * @param buf return raw bytes
+ * @param buf_len returns length of the raw return value
+ * @param buf_max_len buffer size
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
+ */
+err_t
+snmp_asn1_dec_raw(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u8_t *buf, u16_t* buf_len, u16_t buf_max_len)
+{
+  if (len > buf_max_len) {
+    /* not enough dst space */
+    return ERR_MEM;
+  }
+  *buf_len = len;
+
+  while (len > 0) {
+    PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, buf));
+    buf++;
+    len--;
+  }
+
+  return ERR_OK;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_asn1.h b/src/apps/snmp/snmp_asn1.h
new file mode 100644
index 0000000..ec50d8c
--- /dev/null
+++ b/src/apps/snmp/snmp_asn1.h
@@ -0,0 +1,108 @@
+/**
+ * @file
+ * Abstract Syntax Notation One (ISO 8824, 8825) codec.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * Copyright (c) 2016 Elias Oenal.
+ * 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@axon.tv>
+ *         Martin Hentschel <info@cl-soft.de>
+ *         Elias Oenal <lwip@eliasoenal.com>
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_ASN1_H
+#define LWIP_HDR_APPS_SNMP_ASN1_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP
+
+#include "lwip/err.h"
+#include "lwip/apps/snmp_core.h"
+#include "snmp_pbuf_stream.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SNMP_ASN1_TLV_INDEFINITE_LENGTH 0x80
+
+#define SNMP_ASN1_CLASS_MASK        0xC0
+#define SNMP_ASN1_CONTENTTYPE_MASK  0x20
+#define SNMP_ASN1_DATATYPE_MASK     0x1F
+#define SNMP_ASN1_DATATYPE_EXTENDED 0x1F /* DataType indicating that datatype is encoded in following bytes */
+
+/* context specific (SNMP) tags (from SNMP spec. RFC1157) */
+#define SNMP_ASN1_CONTEXT_PDU_GET_REQ      0
+#define SNMP_ASN1_CONTEXT_PDU_GET_NEXT_REQ 1
+#define SNMP_ASN1_CONTEXT_PDU_GET_RESP     2
+#define SNMP_ASN1_CONTEXT_PDU_SET_REQ      3
+#define SNMP_ASN1_CONTEXT_PDU_TRAP         4
+#define SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ 5
+
+#define SNMP_ASN1_CONTEXT_VARBIND_NO_SUCH_OBJECT      0
+#define SNMP_ASN1_CONTEXT_VARBIND_END_OF_MIB_VIEW     2
+
+struct snmp_asn1_tlv
+{
+  u8_t  type;       /* only U8 because extended types are not specified by SNMP */
+  u8_t  type_len;   /* encoded length of 'type' field (normally 1) */
+  u8_t  length_len; /* indicates how many bytes are required to encode the 'value_len' field */
+  u16_t value_len;  /* encoded length of the value */
+};
+#define SNMP_ASN1_TLV_HDR_LENGTH(tlv) ((tlv).type_len + (tlv).length_len)
+#define SNMP_ASN1_TLV_LENGTH(tlv) ((tlv).type_len + (tlv).length_len + (tlv).value_len)
+#define SNMP_ASN1_SET_TLV_PARAMS(tlv, type_, length_len_, value_len_) do { (tlv).type = (type_); (tlv).type_len = 0; (tlv).length_len = (length_len_); (tlv).value_len = (value_len_); } while (0);
+
+err_t snmp_asn1_dec_tlv(struct snmp_pbuf_stream* pbuf_stream, struct snmp_asn1_tlv* tlv);
+err_t snmp_asn1_dec_u32t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t *value);
+err_t snmp_asn1_dec_u64t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t *value);
+err_t snmp_asn1_dec_s32t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, s32_t *value);
+err_t snmp_asn1_dec_oid(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t* oid, u8_t* oid_len, u8_t oid_max_len);
+err_t snmp_asn1_dec_raw(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u8_t *buf, u16_t* buf_len, u16_t buf_max_len);
+
+err_t snmp_ans1_enc_tlv(struct snmp_pbuf_stream* pbuf_stream, struct snmp_asn1_tlv* tlv);
+
+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_u64t_cnt(const 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(const u32_t *oid, u16_t oid_len, u16_t *octets_needed);
+err_t snmp_asn1_enc_oid(struct snmp_pbuf_stream* pbuf_stream, const u32_t *oid, u16_t oid_len);
+err_t snmp_asn1_enc_s32t(struct snmp_pbuf_stream* pbuf_stream, u16_t octets_needed, s32_t value);
+err_t snmp_asn1_enc_u32t(struct snmp_pbuf_stream* pbuf_stream, u16_t octets_needed, u32_t value);
+err_t snmp_asn1_enc_u64t(struct snmp_pbuf_stream* pbuf_stream, u16_t octets_needed, const u32_t* value);
+err_t snmp_asn1_enc_raw(struct snmp_pbuf_stream* pbuf_stream, const u8_t *raw, u16_t raw_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* LWIP_HDR_APPS_SNMP_ASN1_H */
diff --git a/src/apps/snmp/snmp_core.c b/src/apps/snmp/snmp_core.c
new file mode 100644
index 0000000..c041833
--- /dev/null
+++ b/src/apps/snmp/snmp_core.c
@@ -0,0 +1,1349 @@
+/**
+ * @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@axon.tv>
+ *         Martin Hentschel <info@cl-soft.de>
+*/
+
+/**
+ * @defgroup snmp SNMPv2c agent
+ * @ingroup apps
+ * SNMPv2c compatible agent\n
+ * There is also a MIB compiler and a MIB viewer in lwIP contrib repository
+ * (lwip-contrib/apps/LwipMibCompiler).\n
+ * The agent implements the most important MIB2 MIBs including IPv6 support
+ * (interfaces, UDP, TCP, SNMP, ICMP, SYSTEM). IP MIB is an older version
+ * whithout IPv6 statistics (TODO).\n
+ * Rewritten by Martin Hentschel <info@cl-soft.de> and
+ * Dirk Ziegelmeier <dziegel@gmx.de>\n
+ * Work on SNMPv3 has started, but is not finished.\n
+ *
+ * 0 Agent Capabilities
+ * ====================
+ * 
+ * Features:
+ * ---------
+ * - SNMPv2c support.
+ * - Low RAM usage - no memory pools, stack only.
+ * - MIB2 implementation is separated from SNMP stack.
+ * - Support for multiple MIBs (snmp_set_mibs() call) - e.g. for private MIB.
+ * - Simple and generic API for MIB implementation.
+ * - Comfortable node types and helper functions for scalar arrays and tables.
+ * - Counter64, bit and truthvalue datatype support.
+ * - Callbacks for SNMP writes e.g. to implement persistency.
+ * - Runs on two APIs: RAW and netconn.
+ * - Async API is gone - the stack now supports netconn API instead,
+ *   so blocking operations can be done in MIB calls.
+ *   SNMP runs in a worker thread when netconn API is used.
+ * - Simplified thread sync support for MIBs - useful when MIBs
+ *   need to access variables shared with other threads where no locking is
+ *   possible. Used in MIB2 to access lwIP stats from lwIP thread.
+ * 
+ * MIB compiler (code generator):
+ * ------------------------------
+ * - Provided in lwIP contrib repository.
+ * - Written in C#. MIB viewer used Windows Forms.
+ * - Developed on Windows with Visual Studio 2010.
+ * - Can be compiled and used on all platforms with http://www.monodevelop.com/.
+ * - Based on a heavily modified version of of SharpSnmpLib (a4bd05c6afb4)
+ *   (https://sharpsnmplib.codeplex.com/SourceControl/network/forks/Nemo157/MIBParserUpdate).
+ * - MIB parser, C file generation framework and LWIP code generation are cleanly
+ *   separated, which means the code may be useful as a base for code generation
+ *   of other SNMP agents.
+ * 
+ * Notes:
+ * ------
+ * - Stack and MIB compiler were used to implement a Profinet device.
+ *   Compiled/implemented MIBs: LLDP-MIB, LLDP-EXT-DOT3-MIB, LLDP-EXT-PNO-MIB.
+ * 
+ * SNMPv1 per RFC1157 and SNMPv2c per RFC 3416
+ * -------------------------------------------
+ *   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
+ * ------
+ *   The standard lwIP stack management information base.
+ *   This is a required MIB, so this is always enabled.
+ *   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.
+ *  
+ * 
+ * 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 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
+ * for sysContact, sysLocation, and snmpEnableAuthenTraps.
+ * You can do this by calling
+ * 
+ * - snmp_mib2_set_syscontact()
+ * - snmp_mib2_set_syslocation()
+ * - snmp_set_auth_traps_enabled()
+ * 
+ * You can register a callback which is called on successful write access: 
+ * snmp_set_write_callback().
+ * 
+ * Additionally you may want to set
+ * 
+ * - snmp_mib2_set_sysdescr()
+ * - snmp_set_device_enterprise_oid()
+ * - snmp_mib2_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()
+ * 
+ * If you need more than MIB2, set the MIBs you want to use
+ * by snmp_set_mibs().
+ * 
+ * Finally, enable the agent by calling snmp_init()
+ *
+ * @defgroup snmp_core Core
+ * @ingroup snmp
+ * 
+ * @defgroup snmp_traps Traps
+ * @ingroup snmp
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "snmp_core_priv.h"
+#include "lwip/netif.h"
+#include <string.h>
+
+
+#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_UDP && LWIP_SNMP)
+  #error "If you want to use SNMP, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+
+struct snmp_statistics snmp_stats;
+static const struct snmp_obj_id  snmp_device_enterprise_oid_default = {SNMP_DEVICE_ENTERPRISE_OID_LEN, SNMP_DEVICE_ENTERPRISE_OID};
+static const struct snmp_obj_id* snmp_device_enterprise_oid         = &snmp_device_enterprise_oid_default;
+
+const u32_t snmp_zero_dot_zero_values[] = { 0, 0 };
+const struct snmp_obj_id_const_ref snmp_zero_dot_zero = { LWIP_ARRAYSIZE(snmp_zero_dot_zero_values), snmp_zero_dot_zero_values };
+
+
+#if SNMP_LWIP_MIB2
+#include "lwip/apps/snmp_mib2.h"
+static const struct snmp_mib* const default_mibs[] = { &mib2 };
+static u8_t snmp_num_mibs                          = 1;
+#else
+static const struct snmp_mib* const default_mibs[] = { NULL };
+static u8_t snmp_num_mibs                          = 0;
+#endif
+
+/* List of known mibs */
+static struct snmp_mib const * const *snmp_mibs = default_mibs;
+
+/**
+ * @ingroup snmp_core
+ * Sets the MIBs to use.
+ * Example: call snmp_set_mibs() as follows:
+ * static const struct snmp_mib *my_snmp_mibs[] = {
+ *   &mib2,
+ *   &private_mib
+ * };
+ * snmp_set_mibs(my_snmp_mibs, LWIP_ARRAYSIZE(my_snmp_mibs));
+ */
+void
+snmp_set_mibs(const struct snmp_mib **mibs, u8_t num_mibs)
+{
+  LWIP_ASSERT("mibs pointer must be != NULL", (mibs != NULL));
+  LWIP_ASSERT("num_mibs pointer must be != 0", (num_mibs != 0));
+  snmp_mibs     = mibs;
+  snmp_num_mibs = num_mibs;
+}
+
+/**
+ * @ingroup snmp_core
+ * 'device enterprise oid' is used for 'device OID' field in trap PDU's (for identification of generating device)
+ * as well as for value returned by MIB-2 'sysObjectID' field (if internal MIB2 implementation is used).
+ * The 'device enterprise oid' shall point to an OID located under 'private-enterprises' branch (1.3.6.1.4.1.XXX). If a vendor
+ * wants to provide a custom object there, he has to get its own enterprise oid from IANA (http://www.iana.org). It
+ * is not allowed to use LWIP enterprise ID!
+ * In order to identify a specific device it is recommended to create a dedicated OID for each device type under its own 
+ * enterprise oid.
+ * e.g.
+ * device a > 1.3.6.1.4.1.XXX(ent-oid).1(devices).1(device a)
+ * device b > 1.3.6.1.4.1.XXX(ent-oid).1(devices).2(device b)
+ * for more details see description of 'sysObjectID' field in RFC1213-MIB
+ */
+void snmp_set_device_enterprise_oid(const struct snmp_obj_id* device_enterprise_oid)
+{
+  if (device_enterprise_oid == NULL) {
+    snmp_device_enterprise_oid = &snmp_device_enterprise_oid_default;
+  } else {
+    snmp_device_enterprise_oid = device_enterprise_oid;
+  }
+}
+
+/**
+ * @ingroup snmp_core
+ * Get 'device enterprise oid' 
+ */
+const struct snmp_obj_id* snmp_get_device_enterprise_oid(void)
+{
+  return snmp_device_enterprise_oid;
+}
+
+#if LWIP_IPV4
+/**
+ * Conversion from InetAddressIPv4 oid to lwIP ip4_addr
+ * @param oid points to u32_t ident[4] input
+ * @param ip points to output struct
+ */
+u8_t
+snmp_oid_to_ip4(const u32_t *oid, ip4_addr_t *ip)
+{
+  if ((oid[0] > 0xFF) ||
+      (oid[1] > 0xFF) ||
+      (oid[2] > 0xFF) ||
+      (oid[3] > 0xFF)) {
+    ip4_addr_copy(*ip, *IP4_ADDR_ANY4);
+    return 0;
+  }
+
+  IP4_ADDR(ip, oid[0], oid[1], oid[2], oid[3]);
+  return 1;
+}
+
+/**
+ * Convert ip4_addr to InetAddressIPv4 (no InetAddressType)
+ * @param ip points to input struct
+ * @param oid points to u32_t ident[4] output
+ */
+void
+snmp_ip4_to_oid(const ip4_addr_t *ip, u32_t *oid)
+{
+  oid[0] = ip4_addr1(ip);
+  oid[1] = ip4_addr2(ip);
+  oid[2] = ip4_addr3(ip);
+  oid[3] = ip4_addr4(ip);
+}
+#endif /* LWIP_IPV4 */
+
+#if LWIP_IPV6
+/**
+ * Conversion from InetAddressIPv6 oid to lwIP ip6_addr
+ * @param oid points to u32_t oid[16] input
+ * @param ip points to output struct
+ */
+u8_t
+snmp_oid_to_ip6(const u32_t *oid, ip6_addr_t *ip)
+{
+  if ((oid[0]  > 0xFF) ||
+      (oid[1]  > 0xFF) ||
+      (oid[2]  > 0xFF) ||
+      (oid[3]  > 0xFF) ||
+      (oid[4]  > 0xFF) ||
+      (oid[5]  > 0xFF) ||
+      (oid[6]  > 0xFF) ||
+      (oid[7]  > 0xFF) ||
+      (oid[8]  > 0xFF) ||
+      (oid[9]  > 0xFF) ||
+      (oid[10] > 0xFF) ||
+      (oid[11] > 0xFF) ||
+      (oid[12] > 0xFF) ||
+      (oid[13] > 0xFF) ||
+      (oid[14] > 0xFF) ||
+      (oid[15] > 0xFF)) {
+    ip6_addr_set_any(ip);
+    return 0;
+  }
+
+  ip->addr[0] = (oid[0]  << 24) | (oid[1]  << 16) | (oid[2]  << 8) | (oid[3]  << 0);
+  ip->addr[1] = (oid[4]  << 24) | (oid[5]  << 16) | (oid[6]  << 8) | (oid[7]  << 0);
+  ip->addr[2] = (oid[8]  << 24) | (oid[9]  << 16) | (oid[10] << 8) | (oid[11] << 0);
+  ip->addr[3] = (oid[12] << 24) | (oid[13] << 16) | (oid[14] << 8) | (oid[15] << 0);
+  return 1;
+}
+
+/**
+ * Convert ip6_addr to InetAddressIPv6 (no InetAddressType)
+ * @param ip points to input struct
+ * @param oid points to u32_t ident[16] output
+ */
+void
+snmp_ip6_to_oid(const ip6_addr_t *ip, u32_t *oid)
+{
+  oid[0]  = (ip->addr[0] & 0xFF000000) >> 24;
+  oid[1]  = (ip->addr[0] & 0x00FF0000) >> 16;
+  oid[2]  = (ip->addr[0] & 0x0000FF00) >>  8;
+  oid[3]  = (ip->addr[0] & 0x000000FF) >>  0;
+  oid[4]  = (ip->addr[1] & 0xFF000000) >> 24;
+  oid[5]  = (ip->addr[1] & 0x00FF0000) >> 16;
+  oid[6]  = (ip->addr[1] & 0x0000FF00) >>  8;
+  oid[7]  = (ip->addr[1] & 0x000000FF) >>  0;
+  oid[8]  = (ip->addr[2] & 0xFF000000) >> 24;
+  oid[9]  = (ip->addr[2] & 0x00FF0000) >> 16;
+  oid[10] = (ip->addr[2] & 0x0000FF00) >>  8;
+  oid[11] = (ip->addr[2] & 0x000000FF) >>  0;
+  oid[12] = (ip->addr[3] & 0xFF000000) >> 24;
+  oid[13] = (ip->addr[3] & 0x00FF0000) >> 16;
+  oid[14] = (ip->addr[3] & 0x0000FF00) >>  8;
+  oid[15] = (ip->addr[3] & 0x000000FF) >>  0;
+}
+#endif /* LWIP_IPV6 */
+
+#if LWIP_IPV4 || LWIP_IPV6
+/**
+ * Convert to InetAddressType+InetAddress+InetPortNumber
+ * @param ip IP address
+ * @param port Port
+ * @param oid OID
+ * @return OID length
+ */
+u8_t
+snmp_ip_port_to_oid(const ip_addr_t *ip, u16_t port, u32_t *oid)
+{
+  u8_t idx;
+
+  idx = snmp_ip_to_oid(ip, oid);
+  oid[idx] = port;
+  idx++;
+
+  return idx;
+}
+
+/**
+ * Convert to InetAddressType+InetAddress
+ * @param ip IP address
+ * @param oid OID
+ * @return OID length
+ */
+u8_t
+snmp_ip_to_oid(const ip_addr_t *ip, u32_t *oid)
+{
+  if (IP_IS_ANY_TYPE_VAL(*ip)) {
+    oid[0] = 0; /* any */
+    oid[1] = 0; /* no IP OIDs follow */
+    return 2;
+  } else if (IP_IS_V6(ip)) {
+#if LWIP_IPV6
+    oid[0] = 2; /* ipv6 */
+    oid[1] = 16; /* 16 InetAddressIPv6 OIDs follow */
+    snmp_ip6_to_oid(ip_2_ip6(ip), &oid[2]);
+    return 18;
+#else /* LWIP_IPV6 */
+    return 0;
+#endif /* LWIP_IPV6 */
+  } else {
+#if LWIP_IPV4
+    oid[0] = 1; /* ipv4 */
+    oid[1] = 4; /* 4 InetAddressIPv4 OIDs follow */
+    snmp_ip4_to_oid(ip_2_ip4(ip), &oid[2]);
+    return 6;
+#else /* LWIP_IPV4 */
+    return 0;
+#endif /* LWIP_IPV4 */
+  }
+}
+
+/**
+ * Convert from InetAddressType+InetAddress to ip_addr_t
+ * @param oid OID
+ * @param oid_len OID length
+ * @param ip IP address
+ * @return Parsed OID length
+ */
+u8_t
+snmp_oid_to_ip(const u32_t *oid, u8_t oid_len, ip_addr_t *ip)
+{
+  /* InetAddressType */
+  if (oid_len < 1) {
+    return 0;
+  }
+
+  if (oid[0] == 0) { /* any */
+    /* 1x InetAddressType, 1x OID len */
+    if (oid_len < 2) {
+      return 0;
+    }
+    if (oid[1] != 0) {
+      return 0;
+    }
+
+    memset(ip, 0, sizeof(*ip));
+    IP_SET_TYPE(ip, IPADDR_TYPE_ANY);
+
+    return 2;
+  } else if (oid[0] == 1) { /* ipv4 */
+#if LWIP_IPV4
+    /* 1x InetAddressType, 1x OID len, 4x InetAddressIPv4 */
+    if (oid_len < 6) {
+      return 0;
+    }
+
+    /* 4x ipv4 OID */
+    if (oid[1] != 4) {
+      return 0;
+    }
+
+    IP_SET_TYPE(ip, IPADDR_TYPE_V4);
+    if (!snmp_oid_to_ip4(&oid[2], ip_2_ip4(ip))) {
+      return 0;
+    }
+
+    return 6;
+#else /* LWIP_IPV4 */
+    return 0;
+#endif /* LWIP_IPV4 */
+  } else if (oid[0] == 2) { /* ipv6 */
+#if LWIP_IPV6
+    /* 1x InetAddressType, 1x OID len, 16x InetAddressIPv6 */
+    if (oid_len < 18) {
+      return 0;
+    }
+
+    /* 16x ipv6 OID */
+    if (oid[1] != 16) {
+      return 0;
+    }
+
+    IP_SET_TYPE(ip, IPADDR_TYPE_V6);
+    if (!snmp_oid_to_ip6(&oid[2], ip_2_ip6(ip))) {
+      return 0;
+    }
+
+    return 18;
+#else /* LWIP_IPV6 */
+    return 0;
+#endif /* LWIP_IPV6 */
+  } else { /* unsupported InetAddressType */
+    return 0;
+  }
+}
+
+/**
+ * Convert from InetAddressType+InetAddress+InetPortNumber to ip_addr_t and u16_t
+ * @param oid OID
+ * @param oid_len OID length
+ * @param ip IP address
+ * @param port Port
+ * @return Parsed OID length
+ */
+u8_t
+snmp_oid_to_ip_port(const u32_t *oid, u8_t oid_len, ip_addr_t *ip, u16_t *port)
+{
+  u8_t idx = 0;
+
+  /* InetAddressType + InetAddress */
+  idx += snmp_oid_to_ip(&oid[idx], oid_len-idx, ip);
+  if (idx == 0) {
+    return 0;
+  }
+
+  /* InetPortNumber */
+  if (oid_len < (idx+1)) {
+    return 0;
+  }
+  if (oid[idx] > 0xffff) {
+    return 0;
+  }
+  *port = (u16_t)oid[idx];
+  idx++;
+
+  return idx;
+}
+
+#endif /* LWIP_IPV4 || LWIP_IPV6 */
+
+/**
+ * Assign an OID to struct snmp_obj_id
+ * @param target Assignment target 
+ * @param oid OID
+ * @param oid_len OID length
+ */
+void
+snmp_oid_assign(struct snmp_obj_id* target, const u32_t *oid, u8_t oid_len)
+{
+  LWIP_ASSERT("oid_len <= LWIP_SNMP_OBJ_ID_LEN", oid_len <= SNMP_MAX_OBJ_ID_LEN);
+
+  target->len = oid_len;
+
+  if (oid_len > 0) {
+    MEMCPY(target->id, oid, oid_len * sizeof(u32_t));
+  }
+}
+
+/**
+ * Prefix an OID to OID in struct snmp_obj_id
+ * @param target Assignment target to prefix
+ * @param oid OID
+ * @param oid_len OID length
+ */
+void
+snmp_oid_prefix(struct snmp_obj_id* target, const u32_t *oid, u8_t oid_len)
+{
+  LWIP_ASSERT("target->len + oid_len <= LWIP_SNMP_OBJ_ID_LEN", (target->len + oid_len) <= SNMP_MAX_OBJ_ID_LEN);
+
+  if (oid_len > 0) {
+    /* move existing OID to make room at the beginning for OID to insert */
+    int i;
+    for (i = target->len-1; i>=0; i--) {
+      target->id[i + oid_len] = target->id[i];
+    }
+
+    /* paste oid at the beginning */
+    MEMCPY(target->id, oid, oid_len * sizeof(u32_t));
+  }
+}
+
+/**
+ * Combine two OIDs into struct snmp_obj_id
+ * @param target Assignmet target
+ * @param oid1 OID 1
+ * @param oid1_len OID 1 length
+ * @param oid2 OID 2
+ * @param oid2_len OID 2 length
+ */
+void
+snmp_oid_combine(struct snmp_obj_id* target, const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len)
+{
+  snmp_oid_assign(target, oid1, oid1_len);
+  snmp_oid_append(target, oid2, oid2_len);
+}
+
+/**
+ * Append OIDs to struct snmp_obj_id
+ * @param target Assignment target to append to
+ * @param oid OID
+ * @param oid_len OID length
+ */
+void
+snmp_oid_append(struct snmp_obj_id* target, const u32_t *oid, u8_t oid_len)
+{
+  LWIP_ASSERT("offset + oid_len <= LWIP_SNMP_OBJ_ID_LEN", (target->len + oid_len) <= SNMP_MAX_OBJ_ID_LEN);
+
+  if (oid_len > 0) {
+    MEMCPY(&target->id[target->len], oid, oid_len * sizeof(u32_t));
+    target->len += oid_len;
+  }
+}
+
+/**
+ * Compare two OIDs
+ * @param oid1 OID 1
+ * @param oid1_len OID 1 length
+ * @param oid2 OID 2
+ * @param oid2_len OID 2 length
+ * @return -1: OID1&lt;OID2  1: OID1 &gt;OID2 0: equal
+ */
+s8_t
+snmp_oid_compare(const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len)
+{
+  u8_t level = 0;
+  LWIP_ASSERT("'oid1' param must not be NULL or 'oid1_len' param be 0!", (oid1 != NULL) || (oid1_len == 0));
+  LWIP_ASSERT("'oid2' param must not be NULL or 'oid2_len' param be 0!", (oid2 != NULL) || (oid2_len == 0));
+
+  while ((level < oid1_len) && (level < oid2_len)) {
+    if (*oid1 < *oid2) {
+      return -1;
+    }
+    if (*oid1 > *oid2) {
+      return 1;
+    }
+
+    level++;
+    oid1++;
+    oid2++;
+  }
+
+  /* common part of both OID's is equal, compare length */
+  if (oid1_len < oid2_len) {
+    return -1;
+  }
+  if (oid1_len > oid2_len) {
+    return 1;
+  }
+
+  /* they are equal */
+  return 0;
+}
+
+
+/**
+ * Check of two OIDs are equal
+ * @param oid1 OID 1
+ * @param oid1_len OID 1 length
+ * @param oid2 OID 2
+ * @param oid2_len OID 2 length
+ * @return 1: equal 0: non-equal
+ */
+u8_t
+snmp_oid_equal(const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len)
+{
+  return (snmp_oid_compare(oid1, oid1_len, oid2, oid2_len) == 0)? 1 : 0;
+}
+
+/**
+ * Convert netif to interface index
+ * @param netif netif
+ * @return index
+ */
+u8_t
+netif_to_num(const struct netif *netif)
+{
+  u8_t result = 0;
+  struct netif *netif_iterator = netif_list;
+
+  while (netif_iterator != NULL) {
+    result++;
+
+    if (netif_iterator == netif) {
+      return result;
+    }
+
+    netif_iterator = netif_iterator->next;
+  }
+
+  LWIP_ASSERT("netif not found in netif_list", 0);
+  return 0;
+}
+
+static const struct snmp_mib*
+snmp_get_mib_from_oid(const u32_t *oid, u8_t oid_len)
+{
+  const u32_t* list_oid;
+  const u32_t* searched_oid;
+  u8_t i, l;
+
+  u8_t max_match_len = 0;
+  const struct snmp_mib* matched_mib = NULL;
+
+  LWIP_ASSERT("'oid' param must not be NULL!", (oid != NULL));
+
+  if (oid_len == 0) {
+    return NULL;
+  }
+
+  for (i = 0; i < snmp_num_mibs; i++) {
+    LWIP_ASSERT("MIB array not initialized correctly", (snmp_mibs[i] != NULL));
+    LWIP_ASSERT("MIB array not initialized correctly - base OID is NULL", (snmp_mibs[i]->base_oid != NULL));
+
+    if (oid_len >= snmp_mibs[i]->base_oid_len) {
+      l            = snmp_mibs[i]->base_oid_len;
+      list_oid     = snmp_mibs[i]->base_oid;
+      searched_oid = oid;
+
+      while (l > 0) {
+        if (*list_oid != *searched_oid) {
+          break;
+        }
+
+        l--;
+        list_oid++;
+        searched_oid++;
+      }
+
+      if ((l == 0) && (snmp_mibs[i]->base_oid_len > max_match_len)) {
+        max_match_len = snmp_mibs[i]->base_oid_len;
+        matched_mib = snmp_mibs[i];
+      }
+    }
+  }
+
+  return matched_mib;
+}
+
+static const struct snmp_mib*
+snmp_get_next_mib(const u32_t *oid, u8_t oid_len)
+{
+  u8_t i;
+  const struct snmp_mib* next_mib = NULL;
+
+  LWIP_ASSERT("'oid' param must not be NULL!", (oid != NULL));
+
+  if (oid_len == 0) {
+    return NULL;
+  }
+
+  for (i = 0; i < snmp_num_mibs; i++) {
+    if (snmp_mibs[i]->base_oid != NULL) {
+      /* check if mib is located behind starting point */
+      if (snmp_oid_compare(snmp_mibs[i]->base_oid, snmp_mibs[i]->base_oid_len, oid, oid_len) > 0) {
+        if ((next_mib == NULL) ||
+            (snmp_oid_compare(snmp_mibs[i]->base_oid, snmp_mibs[i]->base_oid_len,
+                              next_mib->base_oid, next_mib->base_oid_len) < 0)) {
+          next_mib = snmp_mibs[i];
+        }
+      }
+    }
+  }
+
+  return next_mib;
+}
+
+static const struct snmp_mib*
+snmp_get_mib_between(const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len)
+{
+  const struct snmp_mib* next_mib = snmp_get_next_mib(oid1, oid1_len);
+
+  LWIP_ASSERT("'oid2' param must not be NULL!", (oid2 != NULL));
+  LWIP_ASSERT("'oid2_len' param must be greater than 0!", (oid2_len > 0));
+
+  if (next_mib != NULL) {
+    if (snmp_oid_compare(next_mib->base_oid, next_mib->base_oid_len, oid2, oid2_len) < 0) {
+      return next_mib;
+    }
+  }
+
+  return NULL;
+}
+
+u8_t
+snmp_get_node_instance_from_oid(const u32_t *oid, u8_t oid_len, struct snmp_node_instance* node_instance)
+{
+  u8_t result = SNMP_ERR_NOSUCHOBJECT;
+  const struct snmp_mib *mib;
+  const struct snmp_node *mn = NULL;
+
+  mib = snmp_get_mib_from_oid(oid, oid_len);
+  if (mib != NULL) {
+    u8_t oid_instance_len;
+
+    mn = snmp_mib_tree_resolve_exact(mib, oid, oid_len, &oid_instance_len);
+    if ((mn != NULL) && (mn->node_type != SNMP_NODE_TREE)) {
+      /* get instance */
+      const struct snmp_leaf_node* leaf_node = (const struct snmp_leaf_node*)(const void*)mn;
+
+      node_instance->node = mn;
+      snmp_oid_assign(&node_instance->instance_oid, oid + (oid_len - oid_instance_len), oid_instance_len);
+
+      result = leaf_node->get_instance(
+        oid,
+        oid_len - oid_instance_len,
+        node_instance);
+
+#ifdef LWIP_DEBUG
+      if (result == SNMP_ERR_NOERROR) {
+        if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_READ) != 0) && (node_instance->get_value == NULL)) {
+          LWIP_DEBUGF(SNMP_DEBUG, ("SNMP inconsistent access: node is readable but no get_value function is specified\n"));
+        }
+        if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_WRITE) != 0) && (node_instance->set_value == NULL)) {
+          LWIP_DEBUGF(SNMP_DEBUG, ("SNMP inconsistent access: node is writable but no set_value and/or set_test function is specified\n"));
+        }
+      }
+#endif
+    }
+  }
+
+  return result;
+}
+
+u8_t
+snmp_get_next_node_instance_from_oid(const u32_t *oid, u8_t oid_len, snmp_validate_node_instance_method validate_node_instance_method, void* validate_node_instance_arg, struct snmp_obj_id* node_oid, struct snmp_node_instance* node_instance)
+{
+  const struct snmp_mib      *mib;
+  const struct snmp_node *mn = NULL;
+  const u32_t* start_oid     = NULL;
+  u8_t         start_oid_len = 0;
+
+  /* resolve target MIB from passed OID */
+  mib = snmp_get_mib_from_oid(oid, oid_len);
+  if (mib == NULL) {
+    /* passed OID does not reference any known MIB, start at the next closest MIB */
+    mib = snmp_get_next_mib(oid, oid_len);
+
+    if (mib != NULL) {
+      start_oid     = mib->base_oid;
+      start_oid_len = mib->base_oid_len;
+    }
+  } else {
+    start_oid     = oid;
+    start_oid_len = oid_len;
+  }
+
+  /* resolve target node from MIB, skip to next MIB if no suitable node is found in current MIB */
+  while ((mib != NULL) && (mn == NULL)) {
+    u8_t oid_instance_len;
+
+    /* check if OID directly references a node inside current MIB, in this case we have to ask this node for the next instance */
+    mn = snmp_mib_tree_resolve_exact(mib, start_oid, start_oid_len, &oid_instance_len);
+    if (mn != NULL) {
+      snmp_oid_assign(node_oid, start_oid, start_oid_len - oid_instance_len); /* set oid to node */
+      snmp_oid_assign(&node_instance->instance_oid, start_oid + (start_oid_len - oid_instance_len), oid_instance_len); /* set (relative) instance oid */
+    } else {
+      /* OID does not reference a node, search for the next closest node inside MIB; set instance_oid.len to zero because we want the first instance of this node */
+      mn = snmp_mib_tree_resolve_next(mib, start_oid, start_oid_len, node_oid);
+      node_instance->instance_oid.len = 0;
+    }
+
+    /* validate the node; if the node has no further instance or the returned instance is invalid, search for the next in MIB and validate again */
+    node_instance->node = mn;
+    while (mn != NULL) {
+       u8_t result;
+
+      /* clear fields which may have values from previous loops */
+      node_instance->asn1_type        = 0;
+      node_instance->access           = SNMP_NODE_INSTANCE_NOT_ACCESSIBLE;
+      node_instance->get_value        = NULL;
+      node_instance->set_test         = NULL;
+      node_instance->set_value        = NULL;
+      node_instance->release_instance = NULL;
+      node_instance->reference.ptr    = NULL;
+      node_instance->reference_len    = 0;
+
+      result = ((const struct snmp_leaf_node*)(const void*)mn)->get_next_instance(
+        node_oid->id,
+        node_oid->len,
+        node_instance);
+
+      if (result == SNMP_ERR_NOERROR) {
+#ifdef LWIP_DEBUG
+        if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_READ) != 0) && (node_instance->get_value == NULL)) {
+          LWIP_DEBUGF(SNMP_DEBUG, ("SNMP inconsistent access: node is readable but no get_value function is specified\n"));
+        }
+        if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_WRITE) != 0) && (node_instance->set_value == NULL)) {
+          LWIP_DEBUGF(SNMP_DEBUG, ("SNMP inconsistent access: node is writable but no set_value function is specified\n"));
+        }
+#endif
+
+        /* validate node because the node may be not accessible for example (but let the caller decide what is valid */
+        if ((validate_node_instance_method == NULL) ||
+            (validate_node_instance_method(node_instance, validate_node_instance_arg) == SNMP_ERR_NOERROR)) {
+          /* node_oid "returns" the full result OID (including the instance part) */
+          snmp_oid_append(node_oid, node_instance->instance_oid.id, node_instance->instance_oid.len);
+          break;
+        }
+
+        if (node_instance->release_instance != NULL) {
+          node_instance->release_instance(node_instance);
+        }
+        /*
+        the instance itself is not valid, ask for next instance from same node.
+        we don't have to change any variables because node_instance->instance_oid is used as input (starting point)
+        as well as output (resulting next OID), so we have to simply call get_next_instance method again
+        */
+      } else {
+        if (node_instance->release_instance != NULL) {
+          node_instance->release_instance(node_instance);
+        }
+
+        /* the node has no further instance, skip to next node */
+        mn = snmp_mib_tree_resolve_next(mib, node_oid->id, node_oid->len, &node_instance->instance_oid); /* misuse node_instance->instance_oid as tmp buffer */
+        if (mn != NULL) {
+          /* prepare for next loop */
+          snmp_oid_assign(node_oid, node_instance->instance_oid.id, node_instance->instance_oid.len);
+          node_instance->instance_oid.len = 0;
+          node_instance->node = mn;
+        }
+      }
+    }
+
+    if (mn != NULL) {
+      /*
+      we found a suitable next node,
+      now we have to check if a inner MIB is located between the searched OID and the resulting OID.
+      this is possible because MIB's may be located anywhere in the global tree, that means also in 
+      the subtree of another MIB (e.g. if searched OID is .2 and resulting OID is .4, then another
+      MIB having .3 as root node may exist)
+      */
+      const struct snmp_mib *intermediate_mib;
+      intermediate_mib = snmp_get_mib_between(start_oid, start_oid_len, node_oid->id, node_oid->len);
+
+      if (intermediate_mib != NULL) {
+        /* search for first node inside intermediate mib in next loop */
+        if (node_instance->release_instance != NULL) {
+          node_instance->release_instance(node_instance);
+        }
+
+        mn            = NULL;
+        mib           = intermediate_mib;
+        start_oid     = mib->base_oid;
+        start_oid_len = mib->base_oid_len;
+      }
+      /* else { we found out target node } */
+    } else {
+      /*
+      there is no further (suitable) node inside this MIB, search for the next MIB with following priority
+      1. search for inner MIB's (whose root is located inside tree of current MIB)
+      2. search for surrouding MIB's (where the current MIB is the inner MIB) and continue there if any
+      3. take the next closest MIB (not being related to the current MIB)
+      */
+      const struct snmp_mib *next_mib;
+      next_mib = snmp_get_next_mib(start_oid, start_oid_len); /* returns MIB's related to point 1 and 3 */
+
+      /* is the found MIB an inner MIB? (point 1) */
+      if ((next_mib != NULL) && (next_mib->base_oid_len > mib->base_oid_len) &&
+          (snmp_oid_compare(next_mib->base_oid, mib->base_oid_len, mib->base_oid, mib->base_oid_len) == 0)) {
+        /* yes it is -> continue at inner MIB */
+        mib = next_mib;
+        start_oid     = mib->base_oid;
+        start_oid_len = mib->base_oid_len;
+      } else {
+        /* check if there is a surrounding mib where to continue (point 2) (only possible if OID length > 1) */
+        if (mib->base_oid_len > 1) {
+          mib = snmp_get_mib_from_oid(mib->base_oid, mib->base_oid_len - 1);
+
+          if (mib == NULL) {
+            /* no surrounding mib, use next mib encountered above (point 3) */
+            mib = next_mib;
+
+            if (mib != NULL) {
+              start_oid     = mib->base_oid;
+              start_oid_len = mib->base_oid_len;
+            }
+          }
+          /* else { start_oid stays the same because we want to continue from current offset in surrounding mib (point 2) } */
+        }
+      }
+    }
+  }
+
+  if (mib == NULL) {
+    /* loop is only left when mib == null (error) or mib_node != NULL (success) */
+    return SNMP_ERR_ENDOFMIBVIEW;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+/**
+ * Searches tree for the supplied object identifier.
+ *
+ */
+const struct snmp_node *
+snmp_mib_tree_resolve_exact(const struct snmp_mib *mib, const u32_t *oid, u8_t oid_len, u8_t* oid_instance_len)
+{
+  const struct snmp_node* const* node = &mib->root_node;
+  u8_t oid_offset = mib->base_oid_len;
+
+  while ((oid_offset < oid_len) && ((*node)->node_type == SNMP_NODE_TREE)) {
+    /* search for matching sub node */
+    u32_t subnode_oid = *(oid + oid_offset);
+
+    u32_t i = (*(const struct snmp_tree_node* const*)node)->subnode_count;
+    node    = (*(const struct snmp_tree_node* const*)node)->subnodes;
+    while ((i > 0) && ((*node)->oid != subnode_oid)) {
+      node++;
+      i--;
+    }
+
+    if (i == 0) {
+      /* no matching subnode found */
+      return NULL;
+    }
+
+    oid_offset++;
+  }
+
+  if ((*node)->node_type != SNMP_NODE_TREE) {
+    /* we found a leaf node */
+    *oid_instance_len = oid_len - oid_offset;
+    return (*node);
+  }
+
+  return NULL;
+}
+
+const struct snmp_node*
+snmp_mib_tree_resolve_next(const struct snmp_mib *mib, const u32_t *oid, u8_t oid_len, struct snmp_obj_id* oidret)
+{
+  u8_t  oid_offset = mib->base_oid_len;
+  const struct snmp_node* const* node;
+  const struct snmp_tree_node* node_stack[SNMP_MAX_OBJ_ID_LEN];
+  s32_t nsi = 0; /* NodeStackIndex */
+  u32_t subnode_oid;
+
+  if (mib->root_node->node_type != SNMP_NODE_TREE) {
+    /* a next operation on a mib with only a leaf node will always return NULL because there is no other node */
+    return NULL;
+  }
+
+  /* first build node stack related to passed oid (as far as possible), then go backwards to determine the next node */
+  node_stack[nsi] = (const struct snmp_tree_node*)(const void*)mib->root_node;
+  while (oid_offset < oid_len) {
+    /* search for matching sub node */
+    u32_t i = node_stack[nsi]->subnode_count;
+    node    = node_stack[nsi]->subnodes;
+
+    subnode_oid = *(oid + oid_offset);
+
+    while ((i > 0) && ((*node)->oid != subnode_oid)) {
+      node++;
+      i--;
+    }
+
+    if ((i == 0) || ((*node)->node_type != SNMP_NODE_TREE)) {
+      /* no (matching) tree-subnode found */
+      break;
+    }
+    nsi++;
+    node_stack[nsi] = (const struct snmp_tree_node*)(const void*)(*node);
+
+    oid_offset++;
+  }
+
+
+  if (oid_offset >= oid_len) {
+    /* passed oid references a tree node -> return first useable sub node of it */
+    subnode_oid = 0;
+  } else {
+    subnode_oid = *(oid + oid_offset) + 1;
+  }
+
+  while (nsi >= 0) {
+    const struct snmp_node* subnode = NULL;
+
+    /* find next node on current level */
+    s32_t i        = node_stack[nsi]->subnode_count;
+    node           = node_stack[nsi]->subnodes;
+    while (i > 0) {
+      if ((*node)->oid == subnode_oid) {
+        subnode = *node;
+        break;
+      } else if (((*node)->oid > subnode_oid) && ((subnode == NULL) || ((*node)->oid < subnode->oid))) {
+        subnode = *node;
+      }
+
+      node++;
+      i--;
+    }
+
+    if (subnode == NULL) {
+      /* no further node found on this level, go one level up and start searching with index of current node*/
+      subnode_oid = node_stack[nsi]->node.oid + 1;
+      nsi--;
+    } else {
+      if (subnode->node_type == SNMP_NODE_TREE) {
+        /* next is a tree node, go into it and start searching */
+        nsi++;
+        node_stack[nsi] = (const struct snmp_tree_node*)(const void*)subnode;
+        subnode_oid = 0;
+      } else {
+        /* we found a leaf node -> fill oidret and return it */
+        snmp_oid_assign(oidret, mib->base_oid, mib->base_oid_len);
+        i = 1;
+        while (i <= nsi) {
+          oidret->id[oidret->len] = node_stack[i]->node.oid;
+          oidret->len++;
+          i++;
+        }
+
+        oidret->id[oidret->len] = subnode->oid;
+        oidret->len++;
+
+        return subnode;
+      }
+    }
+  }
+
+  return NULL;
+}
+
+/** initialize struct next_oid_state using this function before passing it to next_oid_check */
+void
+snmp_next_oid_init(struct snmp_next_oid_state *state,
+  const u32_t *start_oid, u8_t start_oid_len,
+  u32_t *next_oid_buf, u8_t next_oid_max_len)
+{
+  state->start_oid        = start_oid;
+  state->start_oid_len    = start_oid_len;
+  state->next_oid         = next_oid_buf;
+  state->next_oid_len     = 0;
+  state->next_oid_max_len = next_oid_max_len;
+  state->status           = SNMP_NEXT_OID_STATUS_NO_MATCH;
+}
+
+/** checks if the passed incomplete OID may be a possible candidate for snmp_next_oid_check();
+this methid is intended if the complete OID is not yet known but it is very expensive to build it up,
+so it is possible to test the starting part before building up the complete oid and pass it to snmp_next_oid_check()*/
+u8_t
+snmp_next_oid_precheck(struct snmp_next_oid_state *state, const u32_t *oid, const u8_t oid_len)
+{
+  if (state->status != SNMP_NEXT_OID_STATUS_BUF_TO_SMALL) {
+    u8_t start_oid_len = (oid_len < state->start_oid_len) ? oid_len : state->start_oid_len;
+
+    /* check passed OID is located behind start offset */
+    if (snmp_oid_compare(oid, oid_len, state->start_oid, start_oid_len) >= 0) {
+      /* check if new oid is located closer to start oid than current closest oid */
+      if ((state->status == SNMP_NEXT_OID_STATUS_NO_MATCH) ||
+        (snmp_oid_compare(oid, oid_len, state->next_oid, state->next_oid_len) < 0)) {
+        return 1;
+      }
+    }
+  }
+
+  return 0;
+}
+
+/** checks the passed OID if it is a candidate to be the next one (get_next); returns !=0 if passed oid is currently closest, otherwise 0 */
+u8_t
+snmp_next_oid_check(struct snmp_next_oid_state *state, const u32_t *oid, const u8_t oid_len, void* reference)
+{
+  /* do not overwrite a fail result */
+  if (state->status != SNMP_NEXT_OID_STATUS_BUF_TO_SMALL) {
+    /* check passed OID is located behind start offset */
+    if (snmp_oid_compare(oid, oid_len, state->start_oid, state->start_oid_len) > 0) {
+      /* check if new oid is located closer to start oid than current closest oid */
+      if ((state->status == SNMP_NEXT_OID_STATUS_NO_MATCH) ||
+        (snmp_oid_compare(oid, oid_len, state->next_oid, state->next_oid_len) < 0)) {
+        if (oid_len <= state->next_oid_max_len) {
+          MEMCPY(state->next_oid, oid, oid_len * sizeof(u32_t));
+          state->next_oid_len = oid_len;
+          state->status       = SNMP_NEXT_OID_STATUS_SUCCESS;
+          state->reference    = reference;
+          return 1;
+        } else {
+          state->status = SNMP_NEXT_OID_STATUS_BUF_TO_SMALL;
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+
+u8_t
+snmp_oid_in_range(const u32_t *oid_in, u8_t oid_len, const struct snmp_oid_range *oid_ranges, u8_t oid_ranges_len)
+{
+  u8_t i;
+
+  if (oid_len != oid_ranges_len) {
+    return 0;
+  }
+
+  for (i = 0; i < oid_ranges_len; i++) {
+    if ((oid_in[i] < oid_ranges[i].min) || (oid_in[i] > oid_ranges[i].max)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+snmp_err_t
+snmp_set_test_ok(struct snmp_node_instance* instance, u16_t value_len, void* value)
+{
+  LWIP_UNUSED_ARG(instance);
+  LWIP_UNUSED_ARG(value_len);
+  LWIP_UNUSED_ARG(value);
+
+  return SNMP_ERR_NOERROR;
+}
+
+/**
+ * Decodes BITS pseudotype value from ASN.1 OctetString.
+ *
+ * @note Because BITS pseudo type is encoded as OCTET STRING, it cannot directly
+ * be encoded/decoded by the agent. Instead call this function as required from
+ * get/test/set methods.
+ *
+ * @param buf points to a buffer holding the ASN1 octet string
+ * @param buf_len length of octet string
+ * @param bit_value decoded Bit value with Bit0 == LSB
+ * @return ERR_OK if successful, ERR_ARG if bit value contains more than 32 bit
+ */
+err_t
+snmp_decode_bits(const u8_t *buf, u32_t buf_len, u32_t *bit_value)
+{
+  u8_t b;
+  u8_t bits_processed = 0;
+  *bit_value = 0;
+
+  while (buf_len > 0) {
+    /* any bit set in this byte? */
+    if (*buf != 0x00) {
+      if (bits_processed >= 32) {
+        /* accept more than 4 bytes, but only when no bits are set */
+        return ERR_VAL;
+      }
+
+      b = *buf;
+      do {
+        if (b & 0x80) {
+          *bit_value |= (1 << bits_processed);
+        }
+        bits_processed++;
+        b <<= 1;
+      }
+      while ((bits_processed & 0x07) != 0); /* &0x07 -> % 8 */
+    } else {
+      bits_processed += 8;
+    }
+
+    buf_len--;
+    buf++;
+  }
+
+  return ERR_OK;
+}
+
+err_t
+snmp_decode_truthvalue(const s32_t *asn1_value, u8_t *bool_value)
+{
+  /* defined by RFC1443:
+   TruthValue ::= TEXTUAL-CONVENTION
+    STATUS       current
+    DESCRIPTION
+     "Represents a boolean value."
+    SYNTAX       INTEGER { true(1), false(2) }
+  */
+
+  if ((asn1_value == NULL) || (bool_value == NULL)) {
+    return ERR_ARG;
+  }
+
+  if (*asn1_value == 1) {
+    *bool_value = 1;
+  } else if (*asn1_value == 2) {
+    *bool_value = 0;
+  } else {
+    return ERR_VAL;
+  }
+
+  return ERR_OK;
+}
+
+/**
+ * Encodes BITS pseudotype value into ASN.1 OctetString.
+ *
+ * @note Because BITS pseudo type is encoded as OCTET STRING, it cannot directly
+ * be encoded/decoded by the agent. Instead call this function as required from
+ * get/test/set methods.
+ *
+ * @param buf points to a buffer where the resulting ASN1 octet string is stored to
+ * @param buf_len max length of the bufffer
+ * @param bit_value Bit value to encode with Bit0 == LSB
+ * @param bit_count Number of possible bits for the bit value (according to rfc we have to send all bits independant from their truth value)
+ * @return number of bytes used from buffer to store the resulting OctetString
+ */
+u8_t
+snmp_encode_bits(u8_t *buf, u32_t buf_len, u32_t bit_value, u8_t bit_count)
+{
+  u8_t len = 0;
+  u8_t min_bytes = (bit_count + 7) >> 3; /* >>3 -> / 8 */
+
+  while ((buf_len > 0) && (bit_value != 0x00)) {
+    s8_t i = 7;
+    *buf = 0x00;
+    while (i >= 0) {
+      if (bit_value & 0x01) {
+        *buf |= 0x01;
+      }
+
+      if (i > 0) {
+        *buf <<= 1;
+      }
+
+      bit_value >>= 1;
+      i--;
+    }
+
+    buf++;
+    buf_len--;
+    len++;
+  }
+
+  if (len < min_bytes) {
+    buf     += len;
+    buf_len -= len;
+
+    while ((len < min_bytes) && (buf_len > 0)) {
+      *buf = 0x00;
+      buf++;
+      buf_len--;
+      len++;
+    }
+  }
+
+  return len;
+}
+
+u8_t
+snmp_encode_truthvalue(s32_t *asn1_value, u32_t bool_value)
+{
+  /* defined by RFC1443:
+   TruthValue ::= TEXTUAL-CONVENTION
+    STATUS       current
+    DESCRIPTION
+     "Represents a boolean value."
+    SYNTAX       INTEGER { true(1), false(2) }
+  */
+
+  if (asn1_value == NULL) {
+    return 0;
+  }
+
+  if (bool_value) {
+    *asn1_value = 1; /* defined by RFC1443 */
+  } else {
+    *asn1_value = 2; /* defined by RFC1443 */
+  }
+
+  return sizeof(s32_t);
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_core_priv.h b/src/apps/snmp/snmp_core_priv.h
new file mode 100644
index 0000000..5552177
--- /dev/null
+++ b/src/apps/snmp/snmp_core_priv.h
@@ -0,0 +1,76 @@
+/*
+ * 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: Martin Hentschel <info@cl-soft.de>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_CORE_PRIV_H
+#define LWIP_HDR_APPS_SNMP_CORE_PRIV_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_core.h"
+#include "snmp_asn1.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* (outdated) SNMPv1 error codes
+ * shall not be used by MIBS anymore, nevertheless required from core for properly answering a v1 request
+ */
+#define SNMP_ERR_NOSUCHNAME 2
+#define SNMP_ERR_BADVALUE   3
+#define SNMP_ERR_READONLY   4
+/* error codes which are internal and shall not be used by MIBS
+ * shall not be used by MIBS anymore, nevertheless required from core for properly answering a v1 request
+ */
+#define SNMP_ERR_TOOBIG               1
+#define SNMP_ERR_AUTHORIZATIONERROR   16
+#define SNMP_ERR_NOSUCHOBJECT         SNMP_VARBIND_EXCEPTION_OFFSET + SNMP_ASN1_CONTEXT_VARBIND_NO_SUCH_OBJECT
+#define SNMP_ERR_ENDOFMIBVIEW         SNMP_VARBIND_EXCEPTION_OFFSET + SNMP_ASN1_CONTEXT_VARBIND_END_OF_MIB_VIEW
+
+
+const struct snmp_node* snmp_mib_tree_resolve_exact(const struct snmp_mib *mib, const u32_t *oid, u8_t oid_len, u8_t* oid_instance_len);
+const struct snmp_node* snmp_mib_tree_resolve_next(const struct snmp_mib *mib, const u32_t *oid, u8_t oid_len, struct snmp_obj_id* oidret);
+
+typedef u8_t (*snmp_validate_node_instance_method)(struct snmp_node_instance*, void*);
+
+u8_t snmp_get_node_instance_from_oid(const u32_t *oid, u8_t oid_len, struct snmp_node_instance* node_instance);
+u8_t snmp_get_next_node_instance_from_oid(const u32_t *oid, u8_t oid_len, snmp_validate_node_instance_method validate_node_instance_method, void* validate_node_instance_arg, struct snmp_obj_id* node_oid, struct snmp_node_instance* node_instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* LWIP_HDR_APPS_SNMP_CORE_PRIV_H */
diff --git a/src/apps/snmp/snmp_mib2.c b/src/apps/snmp/snmp_mib2.c
new file mode 100644
index 0000000..9d8c43c
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2.c
@@ -0,0 +1,116 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) objects and 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *         Christiaan Simons <christiaan.simons@axon.tv>
+ */
+
+/**
+ * @defgroup snmp_mib2 MIB2
+ * @ingroup snmp
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2 /* don't build if not configured for use in lwipopts.h */
+
+#if !LWIP_STATS
+#error LWIP_SNMP MIB2 needs LWIP_STATS (for MIB2)
+#endif
+#if !MIB2_STATS
+#error LWIP_SNMP MIB2 needs MIB2_STATS (for MIB2)
+#endif
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_scalar.h"
+
+#if SNMP_USE_NETCONN
+#include "lwip/tcpip.h"
+#include "lwip/priv/tcpip_priv.h"
+void
+snmp_mib2_lwip_synchronizer(snmp_threadsync_called_fn fn, void* arg)
+{
+#if LWIP_TCPIP_CORE_LOCKING
+  LOCK_TCPIP_CORE();
+  fn(arg);
+  UNLOCK_TCPIP_CORE();
+#else
+  tcpip_callback(fn, arg);
+#endif
+}
+
+struct snmp_threadsync_instance snmp_mib2_lwip_locks;
+#endif
+
+/* 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) */
+
+/* --- mib-2 .1.3.6.1.2.1 ----------------------------------------------------- */
+extern const struct snmp_scalar_array_node snmp_mib2_snmp_root;
+extern const struct snmp_tree_node snmp_mib2_udp_root;
+extern const struct snmp_tree_node snmp_mib2_tcp_root;
+extern const struct snmp_scalar_array_node snmp_mib2_icmp_root;
+extern const struct snmp_tree_node snmp_mib2_interface_root;
+extern const struct snmp_scalar_array_node snmp_mib2_system_node;
+extern const struct snmp_tree_node snmp_mib2_at_root;
+extern const struct snmp_tree_node snmp_mib2_ip_root;
+
+static const struct snmp_node* const mib2_nodes[] = {
+  &snmp_mib2_system_node.node.node,
+  &snmp_mib2_interface_root.node,
+#if LWIP_ARP && LWIP_IPV4
+  &snmp_mib2_at_root.node,
+#endif /* LWIP_ARP && LWIP_IPV4 */
+#if LWIP_IPV4
+  &snmp_mib2_ip_root.node,
+#endif /* LWIP_IPV4 */
+#if LWIP_ICMP
+  &snmp_mib2_icmp_root.node.node,
+#endif /* LWIP_ICMP */
+#if LWIP_TCP
+  &snmp_mib2_tcp_root.node,
+#endif /* LWIP_TCP */
+#if LWIP_UDP
+  &snmp_mib2_udp_root.node,
+#endif /* LWIP_UDP */
+  &snmp_mib2_snmp_root.node.node
+};
+
+static const struct snmp_tree_node mib2_root = SNMP_CREATE_TREE_NODE(1, mib2_nodes);
+
+static const u32_t  mib2_base_oid_arr[] = { 1,3,6,1,2,1 };
+const struct snmp_mib mib2 = SNMP_MIB_CREATE(mib2_base_oid_arr, &mib2_root.node);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_icmp.c b/src/apps/snmp/snmp_mib2_icmp.c
new file mode 100644
index 0000000..995bd32
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_icmp.c
@@ -0,0 +1,182 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) ICMP objects and 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *         Christiaan Simons <christiaan.simons@axon.tv>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/icmp.h"
+#include "lwip/stats.h"
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_ICMP
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+   static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+/* --- icmp .1.3.6.1.2.1.5 ----------------------------------------------------- */
+
+static s16_t
+icmp_get_value(const struct snmp_scalar_array_node_def *node, void *value)
+{
+  u32_t *uint_ptr = (u32_t*)value;
+
+  switch (node->oid) {
+  case 1: /* icmpInMsgs */
+    *uint_ptr = STATS_GET(mib2.icmpinmsgs);
+    return sizeof(*uint_ptr);
+  case 2: /* icmpInErrors */
+    *uint_ptr = STATS_GET(mib2.icmpinerrors);
+    return sizeof(*uint_ptr);
+  case 3: /* icmpInDestUnreachs */
+    *uint_ptr = STATS_GET(mib2.icmpindestunreachs);
+    return sizeof(*uint_ptr);
+  case 4: /* icmpInTimeExcds */
+    *uint_ptr = STATS_GET(mib2.icmpintimeexcds);
+    return sizeof(*uint_ptr);
+  case 5: /* icmpInParmProbs */
+    *uint_ptr = STATS_GET(mib2.icmpinparmprobs);
+    return sizeof(*uint_ptr);
+  case 6: /* icmpInSrcQuenchs */
+    *uint_ptr = STATS_GET(mib2.icmpinsrcquenchs);
+    return sizeof(*uint_ptr);
+  case 7: /* icmpInRedirects */
+    *uint_ptr = STATS_GET(mib2.icmpinredirects);
+    return sizeof(*uint_ptr);
+  case 8: /* icmpInEchos */
+    *uint_ptr = STATS_GET(mib2.icmpinechos);
+    return sizeof(*uint_ptr);
+  case 9: /* icmpInEchoReps */
+    *uint_ptr = STATS_GET(mib2.icmpinechoreps);
+    return sizeof(*uint_ptr);
+  case 10: /* icmpInTimestamps */
+    *uint_ptr = STATS_GET(mib2.icmpintimestamps);
+    return sizeof(*uint_ptr);
+  case 11: /* icmpInTimestampReps */
+    *uint_ptr = STATS_GET(mib2.icmpintimestampreps);
+    return sizeof(*uint_ptr);
+  case 12: /* icmpInAddrMasks */
+    *uint_ptr = STATS_GET(mib2.icmpinaddrmasks);
+    return sizeof(*uint_ptr);
+  case 13: /* icmpInAddrMaskReps */
+    *uint_ptr = STATS_GET(mib2.icmpinaddrmaskreps);
+    return sizeof(*uint_ptr);
+  case 14: /* icmpOutMsgs */
+    *uint_ptr = STATS_GET(mib2.icmpoutmsgs);
+    return sizeof(*uint_ptr);
+  case 15: /* icmpOutErrors */
+    *uint_ptr = STATS_GET(mib2.icmpouterrors);
+    return sizeof(*uint_ptr);
+  case 16: /* icmpOutDestUnreachs */
+    *uint_ptr = STATS_GET(mib2.icmpoutdestunreachs);
+    return sizeof(*uint_ptr);
+  case 17: /* icmpOutTimeExcds */
+    *uint_ptr = STATS_GET(mib2.icmpouttimeexcds);
+    return sizeof(*uint_ptr);
+  case 18: /* icmpOutParmProbs: not supported -> always 0 */
+    *uint_ptr = 0;
+    return sizeof(*uint_ptr);
+  case 19: /* icmpOutSrcQuenchs: not supported -> always 0 */
+    *uint_ptr = 0;
+    return sizeof(*uint_ptr);
+  case 20: /* icmpOutRedirects: not supported -> always 0 */
+    *uint_ptr = 0;
+    return sizeof(*uint_ptr);
+  case 21: /* icmpOutEchos */
+    *uint_ptr = STATS_GET(mib2.icmpoutechos);
+    return sizeof(*uint_ptr);
+  case 22: /* icmpOutEchoReps */
+    *uint_ptr = STATS_GET(mib2.icmpoutechoreps);
+    return sizeof(*uint_ptr);
+  case 23: /* icmpOutTimestamps: not supported -> always 0 */
+    *uint_ptr = 0;
+    return sizeof(*uint_ptr);
+  case 24: /* icmpOutTimestampReps: not supported -> always 0 */
+    *uint_ptr = 0;
+    return sizeof(*uint_ptr);
+  case 25: /* icmpOutAddrMasks: not supported -> always 0 */
+    *uint_ptr = 0;
+    return sizeof(*uint_ptr);
+  case 26: /* icmpOutAddrMaskReps: not supported -> always 0 */
+    *uint_ptr = 0;
+    return sizeof(*uint_ptr);
+  default:
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("icmp_get_value(): unknown id: %"S32_F"\n", node->oid));
+    break;
+  }
+
+  return 0;
+}
+
+
+static const struct snmp_scalar_array_node_def icmp_nodes[] = {
+  { 1, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  { 2, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  { 3, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  { 4, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  { 5, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  { 6, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  { 7, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  { 8, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  { 9, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {10, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {11, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {12, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {13, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {14, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {15, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {16, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {17, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {18, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {19, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {20, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {21, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {22, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {23, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {24, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {25, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+  {26, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}
+};
+
+const struct snmp_scalar_array_node snmp_mib2_icmp_root = SNMP_SCALAR_CREATE_ARRAY_NODE(5, icmp_nodes, icmp_get_value, NULL, NULL);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_ICMP */
diff --git a/src/apps/snmp/snmp_mib2_interfaces.c b/src/apps/snmp/snmp_mib2_interfaces.c
new file mode 100644
index 0000000..979b507
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_interfaces.c
@@ -0,0 +1,375 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) INTERFACES objects and 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *         Christiaan Simons <christiaan.simons@axon.tv>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/netif.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+   static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+
+/* --- interfaces .1.3.6.1.2.1.2 ----------------------------------------------------- */
+
+static s16_t
+interfaces_get_value(struct snmp_node_instance* instance, void* value)
+{
+  if (instance->node->oid == 1) {
+    s32_t *sint_ptr = (s32_t*)value;
+    s32_t num_netifs = 0;
+
+    struct netif *netif = netif_list;
+    while (netif != NULL) {
+      num_netifs++;
+      netif = netif->next;
+    }
+
+    *sint_ptr = num_netifs;
+    return sizeof(*sint_ptr);
+  }
+
+  return 0;
+}
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range interfaces_Table_oid_ranges[] = {
+  { 1, 0xff } /* netif->num is u8_t */
+};
+
+static const u8_t iftable_ifOutQLen         = 0;
+
+static const u8_t iftable_ifOperStatus_up   = 1;
+static const u8_t iftable_ifOperStatus_down = 2;
+
+static const u8_t iftable_ifAdminStatus_up             = 1;
+static const u8_t iftable_ifAdminStatus_lowerLayerDown = 7;
+static const u8_t iftable_ifAdminStatus_down           = 2;
+
+static snmp_err_t
+interfaces_Table_get_cell_instance(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, struct snmp_node_instance* cell_instance)
+{
+  u32_t ifIndex;
+  struct netif *netif;
+
+  LWIP_UNUSED_ARG(column);
+
+  /* check if incoming OID length and if values are in plausible range */
+  if (!snmp_oid_in_range(row_oid, row_oid_len, interfaces_Table_oid_ranges, LWIP_ARRAYSIZE(interfaces_Table_oid_ranges))) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* get netif index from incoming OID */
+  ifIndex = row_oid[0];
+
+  /* find netif with index */
+  netif = netif_list;
+  while (netif != NULL) {
+    if (netif_to_num(netif) == ifIndex) {
+      /* store netif pointer for subsequent operations (get/test/set) */
+      cell_instance->reference.ptr = netif;
+      return SNMP_ERR_NOERROR;
+    }
+    netif = netif->next;
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+interfaces_Table_get_next_cell_instance(const u32_t* column, struct snmp_obj_id* row_oid, struct snmp_node_instance* cell_instance)
+{
+  struct netif *netif;
+  struct snmp_next_oid_state state;
+  u32_t result_temp[LWIP_ARRAYSIZE(interfaces_Table_oid_ranges)];
+
+  LWIP_UNUSED_ARG(column);
+
+  /* init struct to search next oid */
+  snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(interfaces_Table_oid_ranges));
+
+  /* iterate over all possible OIDs to find the next one */
+  netif = netif_list;
+  while (netif != NULL) {
+    u32_t test_oid[LWIP_ARRAYSIZE(interfaces_Table_oid_ranges)];
+    test_oid[0] = netif_to_num(netif);
+
+    /* check generated OID: is it a candidate for the next one? */
+    snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(interfaces_Table_oid_ranges), netif);
+
+    netif = netif->next;
+  }
+
+  /* did we find a next one? */
+  if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+    snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+    /* store netif pointer for subsequent operations (get/test/set) */
+    cell_instance->reference.ptr = /* (struct netif*) */state.reference;
+    return SNMP_ERR_NOERROR;
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static s16_t
+interfaces_Table_get_value(struct snmp_node_instance* instance, void* value)
+{
+  struct netif *netif = (struct netif*)instance->reference.ptr;
+  u32_t* value_u32 = (u32_t*)value;
+  s32_t* value_s32 = (s32_t*)value;
+  u16_t value_len;
+
+  switch (SNMP_TABLE_GET_COLUMN_FROM_OID(instance->instance_oid.id))
+  {
+  case 1: /* ifIndex */
+    *value_s32 = netif_to_num(netif);
+    value_len = sizeof(*value_s32);
+    break;
+  case 2: /* ifDescr */
+    value_len = sizeof(netif->name);
+    MEMCPY(value, netif->name, value_len);
+    break;
+  case 3: /* ifType */
+    *value_s32 = netif->link_type;
+    value_len = sizeof(*value_s32);
+    break;
+  case 4: /* ifMtu */
+    *value_s32 = netif->mtu;
+    value_len = sizeof(*value_s32);
+    break;
+  case 5: /* ifSpeed */
+    *value_u32 = netif->link_speed;
+    value_len = sizeof(*value_u32);
+    break;
+  case 6: /* ifPhysAddress */
+    value_len = sizeof(netif->hwaddr);
+    MEMCPY(value, &netif->hwaddr, value_len);
+    break;
+  case 7: /* ifAdminStatus */
+    if (netif_is_up(netif)) {
+      *value_s32 = iftable_ifOperStatus_up;
+    } else {
+      *value_s32 = iftable_ifOperStatus_down;
+    }
+    value_len = sizeof(*value_s32);
+    break;
+  case 8: /* ifOperStatus */
+    if (netif_is_up(netif)) {
+      if (netif_is_link_up(netif)) {
+        *value_s32 = iftable_ifAdminStatus_up;
+      } else {
+        *value_s32 = iftable_ifAdminStatus_lowerLayerDown;
+      }
+    } else {
+      *value_s32 = iftable_ifAdminStatus_down;
+    }
+    value_len = sizeof(*value_s32);
+    break;
+  case 9: /* ifLastChange */
+    *value_u32 = netif->ts;
+    value_len = sizeof(*value_u32);
+    break;
+  case 10: /* ifInOctets */
+    *value_u32 = netif->mib2_counters.ifinoctets;
+    value_len = sizeof(*value_u32);
+    break;
+  case 11: /* ifInUcastPkts */
+    *value_u32 = netif->mib2_counters.ifinucastpkts;
+    value_len = sizeof(*value_u32);
+    break;
+  case 12: /* ifInNUcastPkts */
+    *value_u32 = netif->mib2_counters.ifinnucastpkts;
+    value_len = sizeof(*value_u32);
+    break;
+  case 13: /* ifInDiscards */
+    *value_u32 = netif->mib2_counters.ifindiscards;
+    value_len = sizeof(*value_u32);
+    break;
+  case 14: /* ifInErrors */
+    *value_u32 = netif->mib2_counters.ifinerrors;
+    value_len = sizeof(*value_u32);
+    break;
+  case 15: /* ifInUnkownProtos */
+    *value_u32 = netif->mib2_counters.ifinunknownprotos;
+    value_len = sizeof(*value_u32);
+    break;
+  case 16: /* ifOutOctets */
+    *value_u32 = netif->mib2_counters.ifoutoctets;
+    value_len = sizeof(*value_u32);
+    break;
+  case 17: /* ifOutUcastPkts */
+    *value_u32 = netif->mib2_counters.ifoutucastpkts;
+    value_len = sizeof(*value_u32);
+    break;
+  case 18: /* ifOutNUcastPkts */
+    *value_u32 = netif->mib2_counters.ifoutnucastpkts;
+    value_len = sizeof(*value_u32);
+    break;
+  case 19: /* ifOutDiscarts */
+    *value_u32 = netif->mib2_counters.ifoutdiscards;
+    value_len = sizeof(*value_u32);
+    break;
+  case 20: /* ifOutErrors */
+    *value_u32 = netif->mib2_counters.ifouterrors;
+    value_len = sizeof(*value_u32);
+    break;
+  case 21: /* ifOutQLen */
+    *value_u32 = iftable_ifOutQLen;
+    value_len = sizeof(*value_u32);
+    break;
+  /** @note returning zeroDotZero (0.0) no media specific MIB support */
+  case 22: /* ifSpecific */
+    value_len = snmp_zero_dot_zero.len * sizeof(u32_t);
+    MEMCPY(value, snmp_zero_dot_zero.id, value_len);
+    break;
+  default:
+    return 0;
+  }
+
+  return value_len;
+}
+
+#if !SNMP_SAFE_REQUESTS
+
+static snmp_err_t
+interfaces_Table_set_test(struct snmp_node_instance* instance, u16_t len, void *value)
+{
+  s32_t *sint_ptr = (s32_t*)value;
+
+  /* stack should never call this method for another column,
+  because all other columns are set to readonly */
+  LWIP_ASSERT("Invalid column", (SNMP_TABLE_GET_COLUMN_FROM_OID(instance->instance_oid.id) == 7));
+  LWIP_UNUSED_ARG(len);
+
+  if (*sint_ptr == 1 || *sint_ptr == 2) {
+    return SNMP_ERR_NOERROR;
+  }
+
+  return SNMP_ERR_WRONGVALUE;
+}
+
+static snmp_err_t
+interfaces_Table_set_value(struct snmp_node_instance* instance, u16_t len, void *value)
+{
+  struct netif *netif = (struct netif*)instance->reference.ptr;
+  s32_t *sint_ptr = (s32_t*)value;
+
+  /* stack should never call this method for another column,
+  because all other columns are set to readonly */
+  LWIP_ASSERT("Invalid column", (SNMP_TABLE_GET_COLUMN_FROM_OID(instance->instance_oid.id) == 7));
+  LWIP_UNUSED_ARG(len);
+
+  if (*sint_ptr == 1) {
+    netif_set_up(netif);
+  } else if (*sint_ptr == 2) {
+    netif_set_down(netif);
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+#endif /* SNMP_SAFE_REQUESTS */
+
+static const struct snmp_scalar_node interfaces_Number = SNMP_SCALAR_CREATE_NODE_READONLY(1, SNMP_ASN1_TYPE_INTEGER, interfaces_get_value);
+
+static const struct snmp_table_col_def interfaces_Table_columns[] = {
+  {  1, SNMP_ASN1_TYPE_INTEGER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifIndex */
+  {  2, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifDescr */
+  {  3, SNMP_ASN1_TYPE_INTEGER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifType */
+  {  4, SNMP_ASN1_TYPE_INTEGER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifMtu */
+  {  5, SNMP_ASN1_TYPE_GAUGE,        SNMP_NODE_INSTANCE_READ_ONLY }, /* ifSpeed */
+  {  6, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifPhysAddress */
+#if !SNMP_SAFE_REQUESTS
+  {  7, SNMP_ASN1_TYPE_INTEGER,      SNMP_NODE_INSTANCE_READ_WRITE }, /* ifAdminStatus */
+#else
+  {  7, SNMP_ASN1_TYPE_INTEGER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifAdminStatus */
+#endif
+  {  8, SNMP_ASN1_TYPE_INTEGER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOperStatus */
+  {  9, SNMP_ASN1_TYPE_TIMETICKS,    SNMP_NODE_INSTANCE_READ_ONLY }, /* ifLastChange */
+  { 10, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInOctets */
+  { 11, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInUcastPkts */
+  { 12, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInNUcastPkts */
+  { 13, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInDiscarts */
+  { 14, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInErrors */
+  { 15, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInUnkownProtos */
+  { 16, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutOctets */
+  { 17, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutUcastPkts */
+  { 18, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutNUcastPkts */
+  { 19, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutDiscarts */
+  { 20, SNMP_ASN1_TYPE_COUNTER,      SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutErrors */
+  { 21, SNMP_ASN1_TYPE_GAUGE,        SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutQLen */
+  { 22, SNMP_ASN1_TYPE_OBJECT_ID,    SNMP_NODE_INSTANCE_READ_ONLY }  /* ifSpecific */
+};
+
+#if !SNMP_SAFE_REQUESTS
+static const struct snmp_table_node interfaces_Table = SNMP_TABLE_CREATE(
+  2, interfaces_Table_columns,
+  interfaces_Table_get_cell_instance, interfaces_Table_get_next_cell_instance,
+  interfaces_Table_get_value, interfaces_Table_set_test, interfaces_Table_set_value);
+#else
+static const struct snmp_table_node interfaces_Table = SNMP_TABLE_CREATE(
+  2, interfaces_Table_columns,
+  interfaces_Table_get_cell_instance, interfaces_Table_get_next_cell_instance,
+  interfaces_Table_get_value, NULL, NULL);
+#endif
+
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */
+CREATE_LWIP_SYNC_NODE(1, interfaces_Number)
+CREATE_LWIP_SYNC_NODE(2, interfaces_Table)
+
+static const struct snmp_node* const interface_nodes[] = {
+  &SYNC_NODE_NAME(interfaces_Number).node.node,
+  &SYNC_NODE_NAME(interfaces_Table).node.node
+};
+
+const struct snmp_tree_node snmp_mib2_interface_root = SNMP_CREATE_TREE_NODE(2, interface_nodes);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_ip.c b/src/apps/snmp/snmp_mib2_ip.c
new file mode 100644
index 0000000..4f05180
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_ip.c
@@ -0,0 +1,743 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) IP objects and 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *         Christiaan Simons <christiaan.simons@axon.tv>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/stats.h"
+#include "lwip/netif.h"
+#include "lwip/ip.h"
+#include "lwip/etharp.h"
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+   static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+#if LWIP_IPV4
+/* --- ip .1.3.6.1.2.1.4 ----------------------------------------------------- */
+
+static s16_t
+ip_get_value(struct snmp_node_instance* instance, void* value)
+{
+  s32_t* sint_ptr = (s32_t*)value;
+  u32_t* uint_ptr = (u32_t*)value;
+
+  switch (instance->node->oid) {
+  case 1: /* ipForwarding */
+#if IP_FORWARD
+    /* forwarding */
+    *sint_ptr = 1;
+#else
+    /* not-forwarding */
+    *sint_ptr = 2;
+#endif
+    return sizeof(*sint_ptr);
+  case 2: /* ipDefaultTTL */
+    *sint_ptr = IP_DEFAULT_TTL;
+    return sizeof(*sint_ptr);
+  case 3: /* ipInReceives */
+    *uint_ptr = STATS_GET(mib2.ipinreceives);
+    return sizeof(*uint_ptr);
+  case 4: /* ipInHdrErrors */
+    *uint_ptr = STATS_GET(mib2.ipinhdrerrors);
+    return sizeof(*uint_ptr);
+  case 5: /* ipInAddrErrors */
+    *uint_ptr = STATS_GET(mib2.ipinaddrerrors);
+    return sizeof(*uint_ptr);
+  case 6: /* ipForwDatagrams */
+    *uint_ptr = STATS_GET(mib2.ipforwdatagrams);
+    return sizeof(*uint_ptr);
+  case 7: /* ipInUnknownProtos */
+    *uint_ptr = STATS_GET(mib2.ipinunknownprotos);
+    return sizeof(*uint_ptr);
+  case 8: /* ipInDiscards */
+    *uint_ptr = STATS_GET(mib2.ipindiscards);
+    return sizeof(*uint_ptr);
+  case 9: /* ipInDelivers */
+    *uint_ptr = STATS_GET(mib2.ipindelivers);
+    return sizeof(*uint_ptr);
+  case 10: /* ipOutRequests */
+    *uint_ptr = STATS_GET(mib2.ipoutrequests);
+    return sizeof(*uint_ptr);
+  case 11: /* ipOutDiscards */
+    *uint_ptr = STATS_GET(mib2.ipoutdiscards);
+    return sizeof(*uint_ptr);
+  case 12: /* ipOutNoRoutes */
+    *uint_ptr = STATS_GET(mib2.ipoutnoroutes);
+    return sizeof(*uint_ptr);
+  case 13: /* ipReasmTimeout */
+#if IP_REASSEMBLY
+    *sint_ptr = IP_REASS_MAXAGE;
+#else
+    *sint_ptr = 0;
+#endif
+    return sizeof(*sint_ptr);
+  case 14: /* ipReasmReqds */
+    *uint_ptr = STATS_GET(mib2.ipreasmreqds);
+    return sizeof(*uint_ptr);
+  case 15: /* ipReasmOKs */
+    *uint_ptr = STATS_GET(mib2.ipreasmoks);
+    return sizeof(*uint_ptr);
+  case 16: /* ipReasmFails */
+    *uint_ptr = STATS_GET(mib2.ipreasmfails);
+    return sizeof(*uint_ptr);
+  case 17: /* ipFragOKs */
+    *uint_ptr = STATS_GET(mib2.ipfragoks);
+    return sizeof(*uint_ptr);
+  case 18: /* ipFragFails */
+    *uint_ptr = STATS_GET(mib2.ipfragfails);
+    return sizeof(*uint_ptr);
+  case 19: /* ipFragCreates */
+    *uint_ptr = STATS_GET(mib2.ipfragcreates);
+    return sizeof(*uint_ptr);
+  case 23: /* ipRoutingDiscards: not supported -> always 0 */
+    *uint_ptr = 0;
+    return sizeof(*uint_ptr);
+  default:
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_get_value(): unknown id: %"S32_F"\n", instance->node->oid));
+    break;
+  }
+
+  return 0;
+}
+
+/**
+ * Test ip object value before setting.
+ *
+ * @param instance node instance
+ * @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 snmp_err_t
+ip_set_test(struct snmp_node_instance* instance, u16_t len, void *value)
+{
+  snmp_err_t ret = SNMP_ERR_WRONGVALUE;
+  s32_t *sint_ptr = (s32_t*)value;
+
+  LWIP_UNUSED_ARG(len);
+  switch (instance->node->oid) {
+  case 1: /* ipForwarding */
+#if IP_FORWARD
+    /* forwarding */
+    if (*sint_ptr == 1)
+#else
+    /* not-forwarding */
+    if (*sint_ptr == 2)
+#endif
+    {
+      ret = SNMP_ERR_NOERROR;
+    }
+    break;
+  case 2: /* ipDefaultTTL */
+    if (*sint_ptr == IP_DEFAULT_TTL) {
+      ret = SNMP_ERR_NOERROR;
+    }
+    break;
+  default:
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("ip_set_test(): unknown id: %"S32_F"\n", instance->node->oid));
+    break;
+  }
+
+  return ret;
+}
+
+static snmp_err_t
+ip_set_value(struct snmp_node_instance* instance, u16_t len, void *value)
+{
+  LWIP_UNUSED_ARG(instance);
+  LWIP_UNUSED_ARG(len);
+  LWIP_UNUSED_ARG(value);
+  /* nothing to do here because in set_test we only accept values being the same as our own stored value -> no need to store anything */
+  return SNMP_ERR_NOERROR;
+}
+
+/* --- ipAddrTable --- */
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range ip_AddrTable_oid_ranges[] = {
+  { 0, 0xff }, /* IP A */
+  { 0, 0xff }, /* IP B */
+  { 0, 0xff }, /* IP C */
+  { 0, 0xff }  /* IP D */
+};
+
+static snmp_err_t
+ip_AddrTable_get_cell_value_core(struct netif *netif, const u32_t* column, union snmp_variant_value* value, u32_t* value_len)
+{
+  LWIP_UNUSED_ARG(value_len);
+
+  switch (*column) {
+  case 1: /* ipAdEntAddr */
+    value->u32 = netif_ip4_addr(netif)->addr;
+    break;
+  case 2: /* ipAdEntIfIndex */
+    value->u32 = netif_to_num(netif);
+    break;
+  case 3: /* ipAdEntNetMask */
+    value->u32 = netif_ip4_netmask(netif)->addr;
+    break;
+  case 4: /* ipAdEntBcastAddr */
+    /* lwIP oddity, there's no broadcast
+       address in the netif we can rely on */
+    value->u32 = IPADDR_BROADCAST & 1;
+    break;
+  case 5: /* ipAdEntReasmMaxSize */
+#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...
+     */
+    value->u32 = (IP_HLEN + ((IP_REASS_MAX_PBUFS/2) *
+        (PBUF_POOL_BUFSIZE - PBUF_LINK_ENCAPSULATION_HLEN - 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 */
+    value->u32 = 0;
+#endif
+    break;
+  default:
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+ip_AddrTable_get_cell_value(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, union snmp_variant_value* value, u32_t* value_len)
+{
+  ip4_addr_t ip;
+  struct netif *netif;
+
+  /* check if incoming OID length and if values are in plausible range */
+  if (!snmp_oid_in_range(row_oid, row_oid_len, ip_AddrTable_oid_ranges, LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges))) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* get IP from incoming OID */
+  snmp_oid_to_ip4(&row_oid[0], &ip); /* we know it succeeds because of oid_in_range check above */
+
+  /* find netif with requested ip */
+  netif = netif_list;
+  while (netif != NULL) {
+    if (ip4_addr_cmp(&ip, netif_ip4_addr(netif))) {
+      /* fill in object properties */
+      return ip_AddrTable_get_cell_value_core(netif, column, value, value_len);
+    }
+
+    netif = netif->next;
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+ip_AddrTable_get_next_cell_instance_and_value(const u32_t* column, struct snmp_obj_id* row_oid, union snmp_variant_value* value, u32_t* value_len)
+{
+  struct netif *netif;
+  struct snmp_next_oid_state state;
+  u32_t result_temp[LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges)];
+
+  /* init struct to search next oid */
+  snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges));
+
+  /* iterate over all possible OIDs to find the next one */
+  netif = netif_list;
+  while (netif != NULL) {
+    u32_t test_oid[LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges)];
+    snmp_ip4_to_oid(netif_ip4_addr(netif), &test_oid[0]);
+
+    /* check generated OID: is it a candidate for the next one? */
+    snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges), netif);
+
+    netif = netif->next;
+  }
+
+  /* did we find a next one? */
+  if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+    snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+    /* fill in object properties */
+    return ip_AddrTable_get_cell_value_core((struct netif*)state.reference, column, value, value_len);
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+/* --- ipRouteTable --- */
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range ip_RouteTable_oid_ranges[] = {
+  { 0, 0xff }, /* IP A */
+  { 0, 0xff }, /* IP B */
+  { 0, 0xff }, /* IP C */
+  { 0, 0xff }, /* IP D */
+};
+
+static snmp_err_t
+ip_RouteTable_get_cell_value_core(struct netif *netif, u8_t default_route, const u32_t* column, union snmp_variant_value* value, u32_t* value_len)
+{
+  switch (*column) {
+  case 1: /* ipRouteDest */
+    if (default_route) {
+       /* default rte has 0.0.0.0 dest */
+      value->u32 = IP4_ADDR_ANY4->addr;
+    } else {
+      /* netifs have netaddress dest */
+      ip4_addr_t tmp;
+      ip4_addr_get_network(&tmp, netif_ip4_addr(netif), netif_ip4_netmask(netif));
+      value->u32 = tmp.addr;
+    }
+    break;
+  case 2: /* ipRouteIfIndex */
+    value->u32 = netif_to_num(netif);
+    break;
+  case 3: /* ipRouteMetric1 */
+    if (default_route) {
+      value->s32 = 1; /* default */
+    } else {
+      value->s32 = 0; /* normal */
+    }
+    break;
+  case 4: /* ipRouteMetric2 */
+  case 5: /* ipRouteMetric3 */
+  case 6: /* ipRouteMetric4 */
+    value->s32 = -1; /* none */
+    break;
+  case 7: /* ipRouteNextHop */
+    if (default_route) {
+      /* default rte: gateway */
+      value->u32 = netif_ip4_gw(netif)->addr;
+    } else {
+      /* other rtes: netif ip_addr  */
+      value->u32 = netif_ip4_addr(netif)->addr;
+    }
+    break;
+  case 8: /* ipRouteType */
+    if (default_route) {
+      /* default rte is indirect */
+      value->u32 = 4; /* indirect */
+    } else {
+      /* other rtes are direct */
+      value->u32 = 3; /* direct */
+    }
+    break;
+  case 9: /* ipRouteProto */
+    /* locally defined routes */
+    value->u32 = 2; /* local */
+    break;
+  case 10: /* ipRouteAge */
+    /* @todo (sysuptime - timestamp last change) / 100 */
+    value->u32 = 0;
+    break;
+  case 11: /* ipRouteMask */
+    if (default_route) {
+      /* default rte use 0.0.0.0 mask */
+      value->u32 = IP4_ADDR_ANY4->addr;
+    } else {
+      /* other rtes use netmask */
+      value->u32 = netif_ip4_netmask(netif)->addr;
+    }
+    break;
+  case 12: /* ipRouteMetric5 */
+    value->s32 = -1; /* none */
+    break;
+  case 13: /* ipRouteInfo */
+    value->const_ptr = snmp_zero_dot_zero.id;
+    *value_len = snmp_zero_dot_zero.len * sizeof(u32_t);
+    break;
+  default:
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+ip_RouteTable_get_cell_value(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, union snmp_variant_value* value, u32_t* value_len)
+{
+  ip4_addr_t test_ip;
+  struct netif *netif;
+
+  /* check if incoming OID length and if values are in plausible range */
+  if (!snmp_oid_in_range(row_oid, row_oid_len, ip_RouteTable_oid_ranges, LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges))) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* get IP and port from incoming OID */
+  snmp_oid_to_ip4(&row_oid[0], &test_ip); /* we know it succeeds because of oid_in_range check above */
+
+  /* default route is on default netif */
+  if (ip4_addr_isany_val(test_ip) && (netif_default != NULL)) {
+    /* fill in object properties */
+    return ip_RouteTable_get_cell_value_core(netif_default, 1, column, value, value_len);
+  }
+
+  /* find netif with requested route */
+  netif = netif_list;
+  while (netif != NULL) {
+    ip4_addr_t dst;
+    ip4_addr_get_network(&dst, netif_ip4_addr(netif), netif_ip4_netmask(netif));
+
+    if (ip4_addr_cmp(&dst, &test_ip)) {
+      /* fill in object properties */
+      return ip_RouteTable_get_cell_value_core(netif, 0, column, value, value_len);
+    }
+
+    netif = netif->next;
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+ip_RouteTable_get_next_cell_instance_and_value(const u32_t* column, struct snmp_obj_id* row_oid, union snmp_variant_value* value, u32_t* value_len)
+{
+  struct netif *netif;
+  struct snmp_next_oid_state state;
+  u32_t result_temp[LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges)];
+  u32_t test_oid[LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges)];
+
+  /* init struct to search next oid */
+  snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges));
+
+  /* check default route */
+  if (netif_default != NULL) {
+    snmp_ip4_to_oid(IP4_ADDR_ANY4, &test_oid[0]);
+    snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges), netif_default);
+  }
+
+  /* iterate over all possible OIDs to find the next one */
+  netif = netif_list;
+  while (netif != NULL) {
+    ip4_addr_t dst;
+    ip4_addr_get_network(&dst, netif_ip4_addr(netif), netif_ip4_netmask(netif));
+
+    /* check generated OID: is it a candidate for the next one? */
+    if (!ip4_addr_isany_val(dst)) {
+      snmp_ip4_to_oid(&dst, &test_oid[0]);
+      snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges), netif);
+    }
+
+    netif = netif->next;
+  }
+
+  /* did we find a next one? */
+  if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+    ip4_addr_t dst;
+    snmp_oid_to_ip4(&result_temp[0], &dst);
+    snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+    /* fill in object properties */
+    return ip_RouteTable_get_cell_value_core((struct netif*)state.reference, ip4_addr_isany_val(dst), column, value, value_len);
+  } else {
+    /* not found */
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+}
+
+#if LWIP_ARP && LWIP_IPV4
+/* --- ipNetToMediaTable --- */
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range ip_NetToMediaTable_oid_ranges[] = {
+  { 1, 0xff }, /* IfIndex */
+  { 0, 0xff }, /* IP A    */
+  { 0, 0xff }, /* IP B    */
+  { 0, 0xff }, /* IP C    */
+  { 0, 0xff }  /* IP D    */
+};
+
+static snmp_err_t
+ip_NetToMediaTable_get_cell_value_core(u8_t arp_table_index, const u32_t* column, union snmp_variant_value* value, u32_t* value_len)
+{
+  ip4_addr_t *ip;
+  struct netif *netif;
+  struct eth_addr *ethaddr;
+
+  etharp_get_entry(arp_table_index, &ip, &netif, &ethaddr);
+
+  /* value */
+  switch (*column) {
+  case 1: /* atIfIndex / ipNetToMediaIfIndex */
+    value->u32 = netif_to_num(netif);
+    break;
+  case 2: /* atPhysAddress / ipNetToMediaPhysAddress */
+    value->ptr = ethaddr;
+    *value_len = sizeof(*ethaddr);
+    break;
+  case 3: /* atNetAddress / ipNetToMediaNetAddress */
+    value->u32 = ip->addr;
+    break;
+  case 4: /* ipNetToMediaType */
+    value->u32 = 3; /* dynamic*/
+    break;
+  default:
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+ip_NetToMediaTable_get_cell_value(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, union snmp_variant_value* value, u32_t* value_len)
+{
+  ip4_addr_t ip_in;
+  u8_t netif_index;
+  u8_t i;
+
+  /* check if incoming OID length and if values are in plausible range */
+  if (!snmp_oid_in_range(row_oid, row_oid_len, ip_NetToMediaTable_oid_ranges, LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges))) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* get IP from incoming OID */
+  netif_index = (u8_t)row_oid[0];
+  snmp_oid_to_ip4(&row_oid[1], &ip_in); /* we know it succeeds because of oid_in_range check above */
+
+  /* find requested entry */
+  for (i=0; i<ARP_TABLE_SIZE; i++) {
+    ip4_addr_t *ip;
+    struct netif *netif;
+    struct eth_addr *ethaddr;
+
+    if (etharp_get_entry(i, &ip, &netif, &ethaddr)) {
+      if ((netif_index == netif_to_num(netif)) && ip4_addr_cmp(&ip_in, ip)) {
+        /* fill in object properties */
+        return ip_NetToMediaTable_get_cell_value_core(i, column, value, value_len);
+      }
+    }
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+ip_NetToMediaTable_get_next_cell_instance_and_value(const u32_t* column, struct snmp_obj_id* row_oid, union snmp_variant_value* value, u32_t* value_len)
+{
+  u8_t i;
+  struct snmp_next_oid_state state;
+  u32_t result_temp[LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges)];
+
+  /* init struct to search next oid */
+  snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges));
+
+  /* iterate over all possible OIDs to find the next one */
+  for (i=0; i<ARP_TABLE_SIZE; i++) {
+    ip4_addr_t *ip;
+    struct netif *netif;
+    struct eth_addr *ethaddr;
+
+    if (etharp_get_entry(i, &ip, &netif, &ethaddr)) {
+      u32_t test_oid[LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges)];
+
+      test_oid[0] = netif_to_num(netif);
+      snmp_ip4_to_oid(ip, &test_oid[1]);
+
+      /* check generated OID: is it a candidate for the next one? */
+      snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges), LWIP_PTR_NUMERIC_CAST(void*, i));
+    }
+  }
+
+  /* did we find a next one? */
+  if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+    snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+    /* fill in object properties */
+    return ip_NetToMediaTable_get_cell_value_core(LWIP_PTR_NUMERIC_CAST(u8_t, state.reference), column, value, value_len);
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+#endif /* LWIP_ARP && LWIP_IPV4 */
+
+static const struct snmp_scalar_node ip_Forwarding      = SNMP_SCALAR_CREATE_NODE(1, SNMP_NODE_INSTANCE_READ_WRITE, SNMP_ASN1_TYPE_INTEGER, ip_get_value, ip_set_test, ip_set_value);
+static const struct snmp_scalar_node ip_DefaultTTL      = SNMP_SCALAR_CREATE_NODE(2, SNMP_NODE_INSTANCE_READ_WRITE, SNMP_ASN1_TYPE_INTEGER, ip_get_value, ip_set_test, ip_set_value);
+static const struct snmp_scalar_node ip_InReceives      = SNMP_SCALAR_CREATE_NODE_READONLY(3, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InHdrErrors     = SNMP_SCALAR_CREATE_NODE_READONLY(4, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InAddrErrors    = SNMP_SCALAR_CREATE_NODE_READONLY(5, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_ForwDatagrams   = SNMP_SCALAR_CREATE_NODE_READONLY(6, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InUnknownProtos = SNMP_SCALAR_CREATE_NODE_READONLY(7, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InDiscards      = SNMP_SCALAR_CREATE_NODE_READONLY(8, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InDelivers      = SNMP_SCALAR_CREATE_NODE_READONLY(9, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_OutRequests     = SNMP_SCALAR_CREATE_NODE_READONLY(10, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_OutDiscards     = SNMP_SCALAR_CREATE_NODE_READONLY(11, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_OutNoRoutes     = SNMP_SCALAR_CREATE_NODE_READONLY(12, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_ReasmTimeout    = SNMP_SCALAR_CREATE_NODE_READONLY(13, SNMP_ASN1_TYPE_INTEGER, ip_get_value);
+static const struct snmp_scalar_node ip_ReasmReqds      = SNMP_SCALAR_CREATE_NODE_READONLY(14, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_ReasmOKs        = SNMP_SCALAR_CREATE_NODE_READONLY(15, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_ReasmFails      = SNMP_SCALAR_CREATE_NODE_READONLY(16, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_FragOKs         = SNMP_SCALAR_CREATE_NODE_READONLY(17, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_FragFails       = SNMP_SCALAR_CREATE_NODE_READONLY(18, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_FragCreates     = SNMP_SCALAR_CREATE_NODE_READONLY(19, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_RoutingDiscards = SNMP_SCALAR_CREATE_NODE_READONLY(23, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+
+static const struct snmp_table_simple_col_def ip_AddrTable_columns[] = {
+  { 1, SNMP_ASN1_TYPE_IPADDR,  SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipAdEntAddr */
+  { 2, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipAdEntIfIndex */
+  { 3, SNMP_ASN1_TYPE_IPADDR,  SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipAdEntNetMask */
+  { 4, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipAdEntBcastAddr */
+  { 5, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }  /* ipAdEntReasmMaxSize */
+};
+
+static const struct snmp_table_simple_node ip_AddrTable = SNMP_TABLE_CREATE_SIMPLE(20, ip_AddrTable_columns, ip_AddrTable_get_cell_value, ip_AddrTable_get_next_cell_instance_and_value);
+
+static const struct snmp_table_simple_col_def ip_RouteTable_columns[] = {
+  {  1, SNMP_ASN1_TYPE_IPADDR,    SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteDest */
+  {  2, SNMP_ASN1_TYPE_INTEGER,   SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteIfIndex */
+  {  3, SNMP_ASN1_TYPE_INTEGER,   SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric1 */
+  {  4, SNMP_ASN1_TYPE_INTEGER,   SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric2 */
+  {  5, SNMP_ASN1_TYPE_INTEGER,   SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric3 */
+  {  6, SNMP_ASN1_TYPE_INTEGER,   SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric4 */
+  {  7, SNMP_ASN1_TYPE_IPADDR,    SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteNextHop */
+  {  8, SNMP_ASN1_TYPE_INTEGER,   SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteType */
+  {  9, SNMP_ASN1_TYPE_INTEGER,   SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteProto */
+  { 10, SNMP_ASN1_TYPE_INTEGER,   SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteAge */
+  { 11, SNMP_ASN1_TYPE_IPADDR,    SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteMask */
+  { 12, SNMP_ASN1_TYPE_INTEGER,   SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric5 */
+  { 13, SNMP_ASN1_TYPE_OBJECT_ID, SNMP_VARIANT_VALUE_TYPE_PTR }  /* ipRouteInfo */
+};
+
+static const struct snmp_table_simple_node ip_RouteTable = SNMP_TABLE_CREATE_SIMPLE(21, ip_RouteTable_columns, ip_RouteTable_get_cell_value, ip_RouteTable_get_next_cell_instance_and_value);
+#endif /* LWIP_IPV4 */
+
+#if LWIP_ARP && LWIP_IPV4
+static const struct snmp_table_simple_col_def ip_NetToMediaTable_columns[] = {
+  {  1, SNMP_ASN1_TYPE_INTEGER,      SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipNetToMediaIfIndex */
+  {  2, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_VARIANT_VALUE_TYPE_PTR }, /* ipNetToMediaPhysAddress */
+  {  3, SNMP_ASN1_TYPE_IPADDR,       SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipNetToMediaNetAddress */
+  {  4, SNMP_ASN1_TYPE_INTEGER,      SNMP_VARIANT_VALUE_TYPE_U32 }  /* ipNetToMediaType */
+};
+
+static const struct snmp_table_simple_node ip_NetToMediaTable = SNMP_TABLE_CREATE_SIMPLE(22, ip_NetToMediaTable_columns, ip_NetToMediaTable_get_cell_value, ip_NetToMediaTable_get_next_cell_instance_and_value);
+#endif /* LWIP_ARP && LWIP_IPV4 */
+
+#if LWIP_IPV4
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */
+CREATE_LWIP_SYNC_NODE( 1, ip_Forwarding)
+CREATE_LWIP_SYNC_NODE( 2, ip_DefaultTTL)
+CREATE_LWIP_SYNC_NODE( 3, ip_InReceives)
+CREATE_LWIP_SYNC_NODE( 4, ip_InHdrErrors)
+CREATE_LWIP_SYNC_NODE( 5, ip_InAddrErrors)
+CREATE_LWIP_SYNC_NODE( 6, ip_ForwDatagrams)
+CREATE_LWIP_SYNC_NODE( 7, ip_InUnknownProtos)
+CREATE_LWIP_SYNC_NODE( 8, ip_InDiscards)
+CREATE_LWIP_SYNC_NODE( 9, ip_InDelivers)
+CREATE_LWIP_SYNC_NODE(10, ip_OutRequests)
+CREATE_LWIP_SYNC_NODE(11, ip_OutDiscards)
+CREATE_LWIP_SYNC_NODE(12, ip_OutNoRoutes)
+CREATE_LWIP_SYNC_NODE(13, ip_ReasmTimeout)
+CREATE_LWIP_SYNC_NODE(14, ip_ReasmReqds)
+CREATE_LWIP_SYNC_NODE(15, ip_ReasmOKs)
+CREATE_LWIP_SYNC_NODE(15, ip_ReasmFails)
+CREATE_LWIP_SYNC_NODE(17, ip_FragOKs)
+CREATE_LWIP_SYNC_NODE(18, ip_FragFails)
+CREATE_LWIP_SYNC_NODE(19, ip_FragCreates)
+CREATE_LWIP_SYNC_NODE(20, ip_AddrTable)
+CREATE_LWIP_SYNC_NODE(21, ip_RouteTable)
+#if LWIP_ARP
+CREATE_LWIP_SYNC_NODE(22, ip_NetToMediaTable)
+#endif /* LWIP_ARP */
+CREATE_LWIP_SYNC_NODE(23, ip_RoutingDiscards)
+
+static const struct snmp_node* const ip_nodes[] = {
+  &SYNC_NODE_NAME(ip_Forwarding).node.node,
+  &SYNC_NODE_NAME(ip_DefaultTTL).node.node,
+  &SYNC_NODE_NAME(ip_InReceives).node.node,
+  &SYNC_NODE_NAME(ip_InHdrErrors).node.node,
+  &SYNC_NODE_NAME(ip_InAddrErrors).node.node,
+  &SYNC_NODE_NAME(ip_ForwDatagrams).node.node,
+  &SYNC_NODE_NAME(ip_InUnknownProtos).node.node,
+  &SYNC_NODE_NAME(ip_InDiscards).node.node,
+  &SYNC_NODE_NAME(ip_InDelivers).node.node,
+  &SYNC_NODE_NAME(ip_OutRequests).node.node,
+  &SYNC_NODE_NAME(ip_OutDiscards).node.node,
+  &SYNC_NODE_NAME(ip_OutNoRoutes).node.node,
+  &SYNC_NODE_NAME(ip_ReasmTimeout).node.node,
+  &SYNC_NODE_NAME(ip_ReasmReqds).node.node,
+  &SYNC_NODE_NAME(ip_ReasmOKs).node.node,
+  &SYNC_NODE_NAME(ip_ReasmFails).node.node,
+  &SYNC_NODE_NAME(ip_FragOKs).node.node,
+  &SYNC_NODE_NAME(ip_FragFails).node.node,
+  &SYNC_NODE_NAME(ip_FragCreates).node.node,
+  &SYNC_NODE_NAME(ip_AddrTable).node.node,
+  &SYNC_NODE_NAME(ip_RouteTable).node.node,
+#if LWIP_ARP
+  &SYNC_NODE_NAME(ip_NetToMediaTable).node.node,
+#endif /* LWIP_ARP */
+  &SYNC_NODE_NAME(ip_RoutingDiscards).node.node
+};
+
+const struct snmp_tree_node snmp_mib2_ip_root = SNMP_CREATE_TREE_NODE(4, ip_nodes);
+#endif /* LWIP_IPV4 */
+
+/* --- at .1.3.6.1.2.1.3 ----------------------------------------------------- */
+
+#if LWIP_ARP && LWIP_IPV4
+/* at node table is a subset of ip_nettomedia table (same rows but less columns) */
+static const struct snmp_table_simple_col_def at_Table_columns[] = {
+  { 1, SNMP_ASN1_TYPE_INTEGER,      SNMP_VARIANT_VALUE_TYPE_U32 }, /* atIfIndex */
+  { 2, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_VARIANT_VALUE_TYPE_PTR }, /* atPhysAddress */
+  { 3, SNMP_ASN1_TYPE_IPADDR,       SNMP_VARIANT_VALUE_TYPE_U32 }  /* atNetAddress */
+};
+
+static const struct snmp_table_simple_node at_Table = SNMP_TABLE_CREATE_SIMPLE(1, at_Table_columns, ip_NetToMediaTable_get_cell_value, ip_NetToMediaTable_get_next_cell_instance_and_value);
+
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */
+CREATE_LWIP_SYNC_NODE(1, at_Table)
+
+static const struct snmp_node* const at_nodes[] = {
+  &SYNC_NODE_NAME(at_Table).node.node
+};
+
+const struct snmp_tree_node snmp_mib2_at_root = SNMP_CREATE_TREE_NODE(3, at_nodes);
+#endif /* LWIP_ARP && LWIP_IPV4 */
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_snmp.c b/src/apps/snmp/snmp_mib2_snmp.c
new file mode 100644
index 0000000..8a36d61
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_snmp.c
@@ -0,0 +1,227 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) SNMP objects and 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *         Christiaan Simons <christiaan.simons@axon.tv>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_scalar.h"
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2
+
+#define MIB2_AUTH_TRAPS_ENABLED  1
+#define MIB2_AUTH_TRAPS_DISABLED 2
+
+/* --- snmp .1.3.6.1.2.1.11 ----------------------------------------------------- */
+static s16_t
+snmp_get_value(const struct snmp_scalar_array_node_def *node, void *value)
+{
+  u32_t *uint_ptr = (u32_t*)value;
+  switch (node->oid) {
+  case 1: /* snmpInPkts */
+    *uint_ptr = snmp_stats.inpkts;
+    break;
+  case 2: /* snmpOutPkts */
+    *uint_ptr = snmp_stats.outpkts;
+    break;
+  case 3: /* snmpInBadVersions */
+    *uint_ptr = snmp_stats.inbadversions;
+    break;
+  case 4: /* snmpInBadCommunityNames */
+    *uint_ptr = snmp_stats.inbadcommunitynames;
+    break;
+  case 5: /* snmpInBadCommunityUses */
+    *uint_ptr = snmp_stats.inbadcommunityuses;
+    break;
+  case 6: /* snmpInASNParseErrs */
+    *uint_ptr = snmp_stats.inasnparseerrs;
+    break;
+  case 8: /* snmpInTooBigs */
+    *uint_ptr = snmp_stats.intoobigs;
+    break;
+  case 9: /* snmpInNoSuchNames */
+    *uint_ptr = snmp_stats.innosuchnames;
+    break;
+  case 10: /* snmpInBadValues */
+    *uint_ptr = snmp_stats.inbadvalues;
+    break;
+  case 11: /* snmpInReadOnlys */
+    *uint_ptr = snmp_stats.inreadonlys;
+    break;
+  case 12: /* snmpInGenErrs */
+    *uint_ptr = snmp_stats.ingenerrs;
+    break;
+  case 13: /* snmpInTotalReqVars */
+    *uint_ptr = snmp_stats.intotalreqvars;
+    break;
+  case 14: /* snmpInTotalSetVars */
+    *uint_ptr = snmp_stats.intotalsetvars;
+    break;
+  case 15: /* snmpInGetRequests */
+    *uint_ptr = snmp_stats.ingetrequests;
+    break;
+  case 16: /* snmpInGetNexts */
+    *uint_ptr = snmp_stats.ingetnexts;
+    break;
+  case 17: /* snmpInSetRequests */
+    *uint_ptr = snmp_stats.insetrequests;
+    break;
+  case 18: /* snmpInGetResponses */
+    *uint_ptr = snmp_stats.ingetresponses;
+    break;
+  case 19: /* snmpInTraps */
+    *uint_ptr = snmp_stats.intraps;
+    break;
+  case 20: /* snmpOutTooBigs */
+    *uint_ptr = snmp_stats.outtoobigs;
+    break;
+  case 21: /* snmpOutNoSuchNames */
+    *uint_ptr = snmp_stats.outnosuchnames;
+    break;
+  case 22: /* snmpOutBadValues */
+    *uint_ptr = snmp_stats.outbadvalues;
+    break;
+  case 24: /* snmpOutGenErrs */
+    *uint_ptr = snmp_stats.outgenerrs;
+    break;
+  case 25: /* snmpOutGetRequests */
+    *uint_ptr = snmp_stats.outgetrequests;
+    break;
+  case 26: /* snmpOutGetNexts */
+    *uint_ptr = snmp_stats.outgetnexts;
+    break;
+  case 27: /* snmpOutSetRequests */
+    *uint_ptr = snmp_stats.outsetrequests;
+    break;
+  case 28: /* snmpOutGetResponses */
+    *uint_ptr = snmp_stats.outgetresponses;
+    break;
+  case 29: /* snmpOutTraps */
+    *uint_ptr = snmp_stats.outtraps;
+    break;
+  case 30: /* snmpEnableAuthenTraps */
+    if (snmp_get_auth_traps_enabled() == SNMP_AUTH_TRAPS_DISABLED) {
+      *uint_ptr = MIB2_AUTH_TRAPS_DISABLED;
+    } else {
+      *uint_ptr = MIB2_AUTH_TRAPS_ENABLED;
+    }
+    break;
+  case 31: /* snmpSilentDrops */
+    *uint_ptr = 0; /* not supported */
+    break;
+  case 32: /* snmpProxyDrops */
+    *uint_ptr = 0; /* not supported */
+    break;
+  default:
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("snmp_get_value(): unknown id: %"S32_F"\n", node->oid));
+    return 0;
+  }
+
+  return sizeof(*uint_ptr);
+}
+
+static snmp_err_t
+snmp_set_test(const struct snmp_scalar_array_node_def *node, u16_t len, void *value)
+{
+  snmp_err_t ret = SNMP_ERR_WRONGVALUE;
+  LWIP_UNUSED_ARG(len);
+
+  if (node->oid == 30) {
+    /* snmpEnableAuthenTraps */
+    s32_t *sint_ptr = (s32_t*)value;
+
+    /* we should have writable non-volatile mem here */
+    if ((*sint_ptr == MIB2_AUTH_TRAPS_DISABLED) || (*sint_ptr == MIB2_AUTH_TRAPS_ENABLED)) {
+      ret = SNMP_ERR_NOERROR;
+    }
+  }
+  return ret;
+}
+
+static snmp_err_t
+snmp_set_value(const struct snmp_scalar_array_node_def *node, u16_t len, void *value)
+{
+  LWIP_UNUSED_ARG(len);
+
+  if (node->oid == 30) {
+    /* snmpEnableAuthenTraps */
+    s32_t *sint_ptr = (s32_t*)value;
+    if (*sint_ptr == MIB2_AUTH_TRAPS_DISABLED) {
+      snmp_set_auth_traps_enabled(SNMP_AUTH_TRAPS_DISABLED);
+    } else {
+      snmp_set_auth_traps_enabled(SNMP_AUTH_TRAPS_ENABLED);
+    }
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+/* the following nodes access variables in SNMP stack (snmp_stats) from SNMP worker thread -> OK, no sync needed */
+static const struct snmp_scalar_array_node_def snmp_nodes[] = {
+  { 1, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInPkts */
+  { 2, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutPkts */
+  { 3, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInBadVersions */
+  { 4, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInBadCommunityNames */
+  { 5, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInBadCommunityUses */
+  { 6, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInASNParseErrs */
+  { 8, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInTooBigs */
+  { 9, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInNoSuchNames */
+  {10, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInBadValues */
+  {11, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInReadOnlys */
+  {12, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInGenErrs */
+  {13, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInTotalReqVars */
+  {14, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInTotalSetVars */
+  {15, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInGetRequests */
+  {16, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInGetNexts */
+  {17, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInSetRequests */
+  {18, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInGetResponses */
+  {19, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpInTraps */
+  {20, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutTooBigs */
+  {21, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutNoSuchNames */
+  {22, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutBadValues */
+  {24, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutGenErrs */
+  {25, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutGetRequests */
+  {26, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutGetNexts */
+  {27, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutSetRequests */
+  {28, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutGetResponses */
+  {29, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpOutTraps */
+  {30, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_WRITE}, /* snmpEnableAuthenTraps */
+  {31, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},  /* snmpSilentDrops */
+  {32, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}   /* snmpProxyDrops */
+};
+
+const struct snmp_scalar_array_node snmp_mib2_snmp_root = SNMP_SCALAR_CREATE_ARRAY_NODE(11, snmp_nodes, snmp_get_value, snmp_set_test, snmp_set_value);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_system.c b/src/apps/snmp/snmp_mib2_system.c
new file mode 100644
index 0000000..90e5780
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_system.c
@@ -0,0 +1,377 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) SYSTEM objects and 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *         Christiaan Simons <christiaan.simons@axon.tv>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/sys.h"
+
+#include <string.h>
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+   static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+/* --- system .1.3.6.1.2.1.1 ----------------------------------------------------- */
+
+/** mib-2.system.sysDescr */
+static const u8_t   sysdescr_default[] = SNMP_LWIP_MIB2_SYSDESC;
+static const u8_t*  sysdescr           = sysdescr_default;
+static const u16_t* sysdescr_len       = NULL; /* use strlen for determining len */
+
+/** mib-2.system.sysContact */
+static const u8_t   syscontact_default[]     = SNMP_LWIP_MIB2_SYSCONTACT;
+static const u8_t*  syscontact               = syscontact_default;
+static const u16_t* syscontact_len           = NULL; /* use strlen for determining len */
+static u8_t*        syscontact_wr            = NULL; /* if writable, points to the same buffer as syscontact (required for correct constness) */
+static u16_t*       syscontact_wr_len        = NULL; /* if writable, points to the same buffer as syscontact_len (required for correct constness) */
+static u16_t        syscontact_bufsize       = 0;    /* 0=not writable */
+
+/** mib-2.system.sysName */
+static const u8_t   sysname_default[]        = SNMP_LWIP_MIB2_SYSNAME;
+static const u8_t*  sysname                  = sysname_default;
+static const u16_t* sysname_len              = NULL; /* use strlen for determining len */
+static u8_t*        sysname_wr               = NULL; /* if writable, points to the same buffer as sysname (required for correct constness) */
+static u16_t*       sysname_wr_len           = NULL; /* if writable, points to the same buffer as sysname_len (required for correct constness) */
+static u16_t        sysname_bufsize          = 0;    /* 0=not writable */
+
+/** mib-2.system.sysLocation */
+static const u8_t   syslocation_default[]    = SNMP_LWIP_MIB2_SYSLOCATION;
+static const u8_t*  syslocation              = syslocation_default;
+static const u16_t* syslocation_len           = NULL; /* use strlen for determining len */
+static u8_t*        syslocation_wr            = NULL; /* if writable, points to the same buffer as syslocation (required for correct constness) */
+static u16_t*       syslocation_wr_len        = NULL; /* if writable, points to the same buffer as syslocation_len (required for correct constness) */
+static u16_t        syslocation_bufsize       = 0;    /* 0=not writable */
+
+/**
+ * @ingroup snmp_mib2
+ * Initializes sysDescr pointers.
+ *
+ * @param str if non-NULL then copy str pointer
+ * @param len points to string length, excluding zero terminator
+ */
+void
+snmp_mib2_set_sysdescr(const u8_t *str, const u16_t *len)
+{
+  if (str != NULL) {
+    sysdescr     = str;
+    sysdescr_len = len;
+  }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * Initializes sysContact pointers
+ *
+ * @param ocstr if non-NULL then copy str pointer
+ * @param ocstrlen points to string length, excluding zero terminator.
+ *        if set to NULL it is assumed that ocstr is NULL-terminated.
+ * @param bufsize size of the buffer in bytes.
+ *        (this is required because the buffer can be overwritten by snmp-set)
+ *        if ocstrlen is NULL buffer needs space for terminating 0 byte.
+ *        otherwise complete buffer is used for string.
+ *        if bufsize is set to 0, the value is regarded as read-only.
+ */
+void
+snmp_mib2_set_syscontact(u8_t *ocstr, u16_t *ocstrlen, u16_t bufsize)
+{
+  if (ocstr != NULL) {
+    syscontact         = ocstr;
+    syscontact_wr      = ocstr;
+    syscontact_len     = ocstrlen;
+    syscontact_wr_len  = ocstrlen;
+    syscontact_bufsize = bufsize;
+  }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * see \ref snmp_mib2_set_syscontact but set pointer to readonly memory
+ */
+void
+snmp_mib2_set_syscontact_readonly(const u8_t *ocstr, const u16_t *ocstrlen)
+{
+  if (ocstr != NULL) {
+    syscontact         = ocstr;
+    syscontact_len     = ocstrlen;
+    syscontact_wr      = NULL;
+    syscontact_wr_len  = NULL;
+    syscontact_bufsize = 0;
+  }
+}
+
+
+/**
+ * @ingroup snmp_mib2
+ * Initializes sysName pointers
+ *
+ * @param ocstr if non-NULL then copy str pointer
+ * @param ocstrlen points to string length, excluding zero terminator.
+ *        if set to NULL it is assumed that ocstr is NULL-terminated.
+ * @param bufsize size of the buffer in bytes.
+ *        (this is required because the buffer can be overwritten by snmp-set)
+ *        if ocstrlen is NULL buffer needs space for terminating 0 byte.
+ *        otherwise complete buffer is used for string.
+ *        if bufsize is set to 0, the value is regarded as read-only.
+ */
+void
+snmp_mib2_set_sysname(u8_t *ocstr, u16_t *ocstrlen, u16_t bufsize)
+{
+  if (ocstr != NULL) {
+    sysname         = ocstr;
+    sysname_wr      = ocstr;
+    sysname_len     = ocstrlen;
+    sysname_wr_len  = ocstrlen;
+    sysname_bufsize = bufsize;
+  }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * see \ref snmp_mib2_set_sysname but set pointer to readonly memory
+ */
+void
+snmp_mib2_set_sysname_readonly(const u8_t *ocstr, const u16_t *ocstrlen)
+{
+  if (ocstr != NULL) {
+    sysname         = ocstr;
+    sysname_len     = ocstrlen;
+    sysname_wr      = NULL;
+    sysname_wr_len  = NULL;
+    sysname_bufsize = 0;
+  }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * Initializes sysLocation pointers
+ *
+ * @param ocstr if non-NULL then copy str pointer
+ * @param ocstrlen points to string length, excluding zero terminator.
+ *        if set to NULL it is assumed that ocstr is NULL-terminated.
+ * @param bufsize size of the buffer in bytes.
+ *        (this is required because the buffer can be overwritten by snmp-set)
+ *        if ocstrlen is NULL buffer needs space for terminating 0 byte.
+ *        otherwise complete buffer is used for string.
+ *        if bufsize is set to 0, the value is regarded as read-only.
+ */
+void
+snmp_mib2_set_syslocation(u8_t *ocstr, u16_t *ocstrlen, u16_t bufsize)
+{
+  if (ocstr != NULL) {
+    syslocation         = ocstr;
+    syslocation_wr      = ocstr;
+    syslocation_len     = ocstrlen;
+    syslocation_wr_len  = ocstrlen;
+    syslocation_bufsize = bufsize;
+  }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * see \ref snmp_mib2_set_syslocation but set pointer to readonly memory
+ */
+void
+snmp_mib2_set_syslocation_readonly(const u8_t *ocstr, const u16_t *ocstrlen)
+{
+  if (ocstr != NULL) {
+    syslocation         = ocstr;
+    syslocation_len     = ocstrlen;
+    syslocation_wr      = NULL;
+    syslocation_wr_len  = NULL;
+    syslocation_bufsize = 0;
+  }
+}
+
+
+static s16_t
+system_get_value(const struct snmp_scalar_array_node_def *node, void *value)
+{
+  const u8_t*  var = NULL;
+  const s16_t* var_len;
+  u16_t result;
+
+  switch (node->oid) {
+  case 1: /* sysDescr */
+    var     = sysdescr;
+    var_len = (const s16_t*)sysdescr_len;
+    break;
+  case 2: /* sysObjectID */
+    {
+      const struct snmp_obj_id* dev_enterprise_oid = snmp_get_device_enterprise_oid();
+      MEMCPY(value, dev_enterprise_oid->id, dev_enterprise_oid->len * sizeof(u32_t));
+      return dev_enterprise_oid->len * sizeof(u32_t);
+    }
+  case 3: /* sysUpTime */
+    MIB2_COPY_SYSUPTIME_TO((u32_t*)value);
+    return sizeof(u32_t);
+  case 4: /* sysContact */
+    var     = syscontact;
+    var_len = (const s16_t*)syscontact_len;
+    break;
+  case 5: /* sysName */
+    var     = sysname;
+    var_len = (const s16_t*)sysname_len;
+    break;
+  case 6: /* sysLocation */
+    var     = syslocation;
+    var_len = (const s16_t*)syslocation_len;
+    break;
+  case 7: /* sysServices */
+    *(s32_t*)value = SNMP_SYSSERVICES;
+    return sizeof(s32_t);
+  default:
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("system_get_value(): unknown id: %"S32_F"\n", node->oid));
+    return 0;
+  }
+
+  /* handle string values (OID 1,4,5 and 6) */
+  LWIP_ASSERT("", (value != NULL));
+  if (var_len == NULL) {
+    result = (s16_t)strlen((const char*)var);
+  } else {
+    result = *var_len;
+  }
+  MEMCPY(value, var, result);
+  return result;
+}
+
+static snmp_err_t
+system_set_test(const struct snmp_scalar_array_node_def *node, u16_t len, void *value)
+{
+  snmp_err_t ret = SNMP_ERR_WRONGVALUE;
+  const u16_t* var_bufsize  = NULL;
+  const u16_t* var_wr_len;
+
+  LWIP_UNUSED_ARG(value);
+
+  switch (node->oid) {
+  case 4: /* sysContact */
+    var_bufsize  = &syscontact_bufsize;
+    var_wr_len   = syscontact_wr_len;
+    break;
+  case 5: /* sysName */
+    var_bufsize  = &sysname_bufsize;
+    var_wr_len   = sysname_wr_len;
+    break;
+  case 6: /* sysLocation */
+    var_bufsize  = &syslocation_bufsize;
+    var_wr_len   = syslocation_wr_len;
+    break;
+  default:
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("system_set_test(): unknown id: %"S32_F"\n", node->oid));
+    return ret;
+  }
+
+  /* check if value is writable at all */
+  if (*var_bufsize > 0) {
+    if (var_wr_len == NULL) {
+      /* we have to take the terminating 0 into account */
+      if (len < *var_bufsize) {
+        ret = SNMP_ERR_NOERROR;
+      }
+    } else {
+      if (len <= *var_bufsize) {
+        ret = SNMP_ERR_NOERROR;
+      }
+    }
+  } else {
+    ret = SNMP_ERR_NOTWRITABLE;
+  }
+
+  return ret;
+}
+
+static snmp_err_t
+system_set_value(const struct snmp_scalar_array_node_def *node, u16_t len, void *value)
+{
+  u8_t*  var_wr = NULL;
+  u16_t* var_wr_len;
+
+  switch (node->oid) {
+  case 4: /* sysContact */
+    var_wr     = syscontact_wr;
+    var_wr_len = syscontact_wr_len;
+    break;
+  case 5: /* sysName */
+    var_wr     = sysname_wr;
+    var_wr_len = sysname_wr_len;
+    break;
+  case 6: /* sysLocation */
+    var_wr     = syslocation_wr;
+    var_wr_len = syslocation_wr_len;
+    break;
+  default:
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("system_set_value(): unknown id: %"S32_F"\n", node->oid));
+    return SNMP_ERR_GENERROR;
+  }
+
+  /* no need to check size of target buffer, this was already done in set_test method */
+  LWIP_ASSERT("", var_wr != NULL);
+  MEMCPY(var_wr, value, len);
+  
+  if (var_wr_len == NULL) {
+    /* add terminating 0 */
+    var_wr[len] = 0;
+  } else {
+    *var_wr_len = len;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static const struct snmp_scalar_array_node_def system_nodes[] = {
+  {1, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY},  /* sysDescr */
+  {2, SNMP_ASN1_TYPE_OBJECT_ID,    SNMP_NODE_INSTANCE_READ_ONLY},  /* sysObjectID */
+  {3, SNMP_ASN1_TYPE_TIMETICKS,    SNMP_NODE_INSTANCE_READ_ONLY},  /* sysUpTime */
+  {4, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_WRITE}, /* sysContact */
+  {5, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_WRITE}, /* sysName */
+  {6, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_WRITE}, /* sysLocation */
+  {7, SNMP_ASN1_TYPE_INTEGER,      SNMP_NODE_INSTANCE_READ_ONLY}   /* sysServices */
+};
+
+const struct snmp_scalar_array_node snmp_mib2_system_node = SNMP_SCALAR_CREATE_ARRAY_NODE(1, system_nodes, system_get_value, system_set_test, system_set_value);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_tcp.c b/src/apps/snmp/snmp_mib2_tcp.c
new file mode 100644
index 0000000..21f6965
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_tcp.c
@@ -0,0 +1,594 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) TCP objects and 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *         Christiaan Simons <christiaan.simons@axon.tv>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/tcp.h"
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_TCP
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+   static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+/* --- tcp .1.3.6.1.2.1.6 ----------------------------------------------------- */
+
+static s16_t
+tcp_get_value(struct snmp_node_instance* instance, void* value)
+{
+  u32_t *uint_ptr = (u32_t*)value;
+  s32_t *sint_ptr = (s32_t*)value;
+
+  switch (instance->node->oid) {
+  case 1: /* tcpRtoAlgorithm, vanj(4) */
+    *sint_ptr = 4;
+    return sizeof(*sint_ptr);
+  case 2: /* tcpRtoMin */
+    /* @todo not the actual value, a guess,
+        needs to be calculated */
+    *sint_ptr = 1000;
+    return sizeof(*sint_ptr);
+  case 3: /* tcpRtoMax */
+    /* @todo not the actual value, a guess,
+        needs to be calculated */
+    *sint_ptr = 60000;
+    return sizeof(*sint_ptr);
+  case 4: /* tcpMaxConn */
+    *sint_ptr = MEMP_NUM_TCP_PCB;
+    return sizeof(*sint_ptr);
+  case 5: /* tcpActiveOpens */
+    *uint_ptr = STATS_GET(mib2.tcpactiveopens);
+    return sizeof(*uint_ptr);
+  case 6: /* tcpPassiveOpens */
+    *uint_ptr = STATS_GET(mib2.tcppassiveopens);
+    return sizeof(*uint_ptr);
+  case 7: /* tcpAttemptFails */
+    *uint_ptr = STATS_GET(mib2.tcpattemptfails);
+    return sizeof(*uint_ptr);
+  case 8: /* tcpEstabResets */
+    *uint_ptr = STATS_GET(mib2.tcpestabresets);
+    return sizeof(*uint_ptr);
+  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;
+    }
+    return sizeof(*uint_ptr);
+  case 10: /* tcpInSegs */
+    *uint_ptr = STATS_GET(mib2.tcpinsegs);
+    return sizeof(*uint_ptr);
+  case 11: /* tcpOutSegs */
+    *uint_ptr = STATS_GET(mib2.tcpoutsegs);
+    return sizeof(*uint_ptr);
+  case 12: /* tcpRetransSegs */
+    *uint_ptr = STATS_GET(mib2.tcpretranssegs);
+    return sizeof(*uint_ptr);
+  case 14: /* tcpInErrs */
+    *uint_ptr = STATS_GET(mib2.tcpinerrs);
+    return sizeof(*uint_ptr);
+  case 15: /* tcpOutRsts */
+    *uint_ptr = STATS_GET(mib2.tcpoutrsts);
+    return sizeof(*uint_ptr);
+  case 17: /* tcpHCInSegs */
+    memset(value, 0, 2*sizeof(u32_t)); /* not supported */
+    return 2*sizeof(u32_t);
+  case 18: /* tcpHCOutSegs */
+    memset(value, 0, 2*sizeof(u32_t)); /* not supported */
+    return 2*sizeof(u32_t);
+  default:
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("tcp_get_value(): unknown id: %"S32_F"\n", instance->node->oid));
+    break;
+  }
+
+  return 0;
+}
+
+/* --- tcpConnTable --- */
+
+#if LWIP_IPV4
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range tcp_ConnTable_oid_ranges[] = {
+  { 0, 0xff   }, /* IP A */
+  { 0, 0xff   }, /* IP B */
+  { 0, 0xff   }, /* IP C */
+  { 0, 0xff   }, /* IP D */
+  { 0, 0xffff }, /* Port */
+  { 0, 0xff   }, /* IP A */
+  { 0, 0xff   }, /* IP B */
+  { 0, 0xff   }, /* IP C */
+  { 0, 0xff   }, /* IP D */
+  { 0, 0xffff }  /* Port */
+};
+
+static snmp_err_t
+tcp_ConnTable_get_cell_value_core(struct tcp_pcb *pcb, const u32_t* column, union snmp_variant_value* value, u32_t* value_len)
+{
+  LWIP_UNUSED_ARG(value_len);
+
+  /* value */
+  switch (*column) {
+  case 1: /* tcpConnState */
+    value->u32 = pcb->state + 1;
+    break;
+  case 2: /* tcpConnLocalAddress */
+    value->u32 = ip_2_ip4(&pcb->local_ip)->addr;
+    break;
+  case 3: /* tcpConnLocalPort */
+    value->u32 = pcb->local_port;
+    break;
+  case 4: /* tcpConnRemAddress */
+    if (pcb->state == LISTEN) {
+      value->u32 = IP4_ADDR_ANY4->addr;
+    } else {
+      value->u32 = ip_2_ip4(&pcb->remote_ip)->addr;
+    }
+    break;
+  case 5: /* tcpConnRemPort */
+    if (pcb->state == LISTEN) {
+      value->u32 = 0;
+    } else {
+      value->u32 = pcb->remote_port;
+    }
+    break;
+  default:
+    LWIP_ASSERT("invalid id", 0);
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+tcp_ConnTable_get_cell_value(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, union snmp_variant_value* value, u32_t* value_len)
+{
+  u8_t i;
+  ip4_addr_t local_ip;
+  ip4_addr_t remote_ip;
+  u16_t local_port;
+  u16_t remote_port;
+  struct tcp_pcb *pcb;
+
+  /* check if incoming OID length and if values are in plausible range */
+  if (!snmp_oid_in_range(row_oid, row_oid_len, tcp_ConnTable_oid_ranges, LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges))) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* get IPs and ports from incoming OID */
+  snmp_oid_to_ip4(&row_oid[0], &local_ip); /* we know it succeeds because of oid_in_range check above */
+  local_port = (u16_t)row_oid[4];
+  snmp_oid_to_ip4(&row_oid[5], &remote_ip); /* we know it succeeds because of oid_in_range check above */
+  remote_port = (u16_t)row_oid[9];
+
+  /* find tcp_pcb with requested ips and ports */
+  for (i = 0; i < LWIP_ARRAYSIZE(tcp_pcb_lists); i++) {
+    pcb = *tcp_pcb_lists[i];
+
+    while (pcb != NULL) {
+      /* do local IP and local port match? */
+      if (IP_IS_V4_VAL(pcb->local_ip) &&
+         ip4_addr_cmp(&local_ip, ip_2_ip4(&pcb->local_ip)) && (local_port == pcb->local_port)) {
+
+        /* PCBs in state LISTEN are not connected and have no remote_ip or remote_port */
+        if (pcb->state == LISTEN) {
+          if (ip4_addr_cmp(&remote_ip, IP4_ADDR_ANY4) && (remote_port == 0)) {
+            /* fill in object properties */
+            return tcp_ConnTable_get_cell_value_core(pcb, column, value, value_len);
+          }
+        } else {
+          if (IP_IS_V4_VAL(pcb->remote_ip) &&
+             ip4_addr_cmp(&remote_ip, ip_2_ip4(&pcb->remote_ip)) && (remote_port == pcb->remote_port)) {
+            /* fill in object properties */
+            return tcp_ConnTable_get_cell_value_core(pcb, column, value, value_len);
+          }
+        }
+      }
+
+      pcb = pcb->next;
+    }
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+tcp_ConnTable_get_next_cell_instance_and_value(const u32_t* column, struct snmp_obj_id* row_oid, union snmp_variant_value* value, u32_t* value_len)
+{
+  u8_t i;
+  struct tcp_pcb *pcb;
+  struct snmp_next_oid_state state;
+  u32_t result_temp[LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges)];
+
+  /* init struct to search next oid */
+  snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges));
+
+  /* iterate over all possible OIDs to find the next one */
+  for (i = 0; i < LWIP_ARRAYSIZE(tcp_pcb_lists); i++) {
+    pcb = *tcp_pcb_lists[i];
+    while (pcb != NULL) {
+      u32_t test_oid[LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges)];
+
+      if (IP_IS_V4_VAL(pcb->local_ip)) {
+        snmp_ip4_to_oid(ip_2_ip4(&pcb->local_ip), &test_oid[0]);
+        test_oid[4] = pcb->local_port;
+
+        /* PCBs in state LISTEN are not connected and have no remote_ip or remote_port */
+        if (pcb->state == LISTEN) {
+          snmp_ip4_to_oid(IP4_ADDR_ANY4, &test_oid[5]);
+          test_oid[9] = 0;
+        } else {
+          if (IP_IS_V6_VAL(pcb->remote_ip)) { /* should never happen */
+            continue;
+          }
+          snmp_ip4_to_oid(ip_2_ip4(&pcb->remote_ip), &test_oid[5]);
+          test_oid[9] = pcb->remote_port;
+        }
+
+        /* check generated OID: is it a candidate for the next one? */
+        snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges), pcb);
+      }
+
+      pcb = pcb->next;
+    }
+  }
+
+  /* did we find a next one? */
+  if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+    snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+    /* fill in object properties */
+    return tcp_ConnTable_get_cell_value_core((struct tcp_pcb*)state.reference, column, value, value_len);
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+#endif /* LWIP_IPV4 */
+
+/* --- tcpConnectionTable --- */
+
+static snmp_err_t
+tcp_ConnectionTable_get_cell_value_core(const u32_t* column, struct tcp_pcb *pcb, union snmp_variant_value* value)
+{
+  /* all items except tcpConnectionState and tcpConnectionProcess are declared as not-accessible */
+  switch (*column) {
+  case 7: /* tcpConnectionState */
+    value->u32 = pcb->state + 1;
+    break;
+  case 8: /* tcpConnectionProcess */
+    value->u32 = 0; /* not supported */
+    break;
+  default:
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+tcp_ConnectionTable_get_cell_value(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, union snmp_variant_value* value, u32_t* value_len)
+{
+  ip_addr_t local_ip, remote_ip;
+  u16_t local_port, remote_port;
+  struct tcp_pcb *pcb;
+  u8_t idx = 0;
+  u8_t i;
+  struct tcp_pcb ** const tcp_pcb_nonlisten_lists[] = {&tcp_bound_pcbs, &tcp_active_pcbs, &tcp_tw_pcbs};
+
+  LWIP_UNUSED_ARG(value_len);
+
+  /* tcpConnectionLocalAddressType + tcpConnectionLocalAddress + tcpConnectionLocalPort */
+  idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len-idx, &local_ip, &local_port);
+  if (idx == 0) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* tcpConnectionRemAddressType + tcpConnectionRemAddress + tcpConnectionRemPort */
+  idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len-idx, &remote_ip, &remote_port);
+  if (idx == 0) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* find tcp_pcb with requested ip and port*/
+  for (i = 0; i < LWIP_ARRAYSIZE(tcp_pcb_nonlisten_lists); i++) {
+    pcb = *tcp_pcb_nonlisten_lists[i];
+
+    while (pcb != NULL) {
+      if (ip_addr_cmp(&local_ip, &pcb->local_ip) &&
+         (local_port == pcb->local_port) &&
+         ip_addr_cmp(&remote_ip, &pcb->remote_ip) &&
+         (remote_port == pcb->remote_port)) {
+        /* fill in object properties */
+        return tcp_ConnectionTable_get_cell_value_core(column, pcb, value);
+      }
+      pcb = pcb->next;
+    }
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+tcp_ConnectionTable_get_next_cell_instance_and_value(const u32_t* column, struct snmp_obj_id* row_oid, union snmp_variant_value* value, u32_t* value_len)
+{
+  struct tcp_pcb *pcb;
+  struct snmp_next_oid_state state;
+  /* 1x tcpConnectionLocalAddressType + 1x OID len + 16x tcpConnectionLocalAddress  + 1x tcpConnectionLocalPort
+   * 1x tcpConnectionRemAddressType   + 1x OID len + 16x tcpConnectionRemAddress    + 1x tcpConnectionRemPort */
+  u32_t  result_temp[38];
+  u8_t i;
+  struct tcp_pcb ** const tcp_pcb_nonlisten_lists[] = {&tcp_bound_pcbs, &tcp_active_pcbs, &tcp_tw_pcbs};
+
+  LWIP_UNUSED_ARG(value_len);
+
+  /* init struct to search next oid */
+  snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(result_temp));
+
+  /* iterate over all possible OIDs to find the next one */
+  for (i = 0; i < LWIP_ARRAYSIZE(tcp_pcb_nonlisten_lists); i++) {
+    pcb = *tcp_pcb_nonlisten_lists[i];
+
+    while (pcb != NULL) {
+      u8_t idx = 0;
+      u32_t test_oid[LWIP_ARRAYSIZE(result_temp)];
+
+      /* tcpConnectionLocalAddressType + tcpConnectionLocalAddress + tcpConnectionLocalPort */
+      idx += snmp_ip_port_to_oid(&pcb->local_ip, pcb->local_port, &test_oid[idx]);
+
+      /* tcpConnectionRemAddressType + tcpConnectionRemAddress + tcpConnectionRemPort */
+      idx += snmp_ip_port_to_oid(&pcb->remote_ip, pcb->remote_port, &test_oid[idx]);
+
+      /* check generated OID: is it a candidate for the next one? */
+      snmp_next_oid_check(&state, test_oid, idx, pcb);
+
+      pcb = pcb->next;
+    }
+  }
+
+  /* did we find a next one? */
+  if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+    snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+    /* fill in object properties */
+    return tcp_ConnectionTable_get_cell_value_core(column, (struct tcp_pcb*)state.reference, value);
+  } else {
+    /* not found */
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+}
+
+/* --- tcpListenerTable --- */
+
+static snmp_err_t
+tcp_ListenerTable_get_cell_value_core(const u32_t* column, union snmp_variant_value* value)
+{
+  /* all items except tcpListenerProcess are declared as not-accessible */
+  switch (*column) {
+  case 4: /* tcpListenerProcess */
+    value->u32 = 0; /* not supported */
+    break;
+  default:
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+tcp_ListenerTable_get_cell_value(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, union snmp_variant_value* value, u32_t* value_len)
+{
+  ip_addr_t local_ip;
+  u16_t local_port;
+  struct tcp_pcb_listen *pcb;
+  u8_t idx = 0;
+
+  LWIP_UNUSED_ARG(value_len);
+
+  /* tcpListenerLocalAddressType + tcpListenerLocalAddress + tcpListenerLocalPort */
+  idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len-idx, &local_ip, &local_port);
+  if (idx == 0) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* find tcp_pcb with requested ip and port*/
+  pcb = tcp_listen_pcbs.listen_pcbs;
+  while (pcb != NULL) {
+    if (ip_addr_cmp(&local_ip, &pcb->local_ip) &&
+       (local_port == pcb->local_port)) {
+      /* fill in object properties */
+      return tcp_ListenerTable_get_cell_value_core(column, value);
+    }
+    pcb = pcb->next;
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+tcp_ListenerTable_get_next_cell_instance_and_value(const u32_t* column, struct snmp_obj_id* row_oid, union snmp_variant_value* value, u32_t* value_len)
+{
+  struct tcp_pcb_listen *pcb;
+  struct snmp_next_oid_state state;
+  /* 1x tcpListenerLocalAddressType + 1x OID len + 16x tcpListenerLocalAddress  + 1x tcpListenerLocalPort */
+  u32_t  result_temp[19];
+
+  LWIP_UNUSED_ARG(value_len);
+
+  /* init struct to search next oid */
+  snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(result_temp));
+
+  /* iterate over all possible OIDs to find the next one */
+  pcb = tcp_listen_pcbs.listen_pcbs;
+  while (pcb != NULL) {
+    u8_t idx = 0;
+    u32_t test_oid[LWIP_ARRAYSIZE(result_temp)];
+
+    /* tcpListenerLocalAddressType + tcpListenerLocalAddress + tcpListenerLocalPort */
+    idx += snmp_ip_port_to_oid(&pcb->local_ip, pcb->local_port, &test_oid[idx]);
+
+    /* check generated OID: is it a candidate for the next one? */
+    snmp_next_oid_check(&state, test_oid, idx, NULL);
+
+    pcb = pcb->next;
+  }
+
+  /* did we find a next one? */
+  if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+    snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+    /* fill in object properties */
+    return tcp_ListenerTable_get_cell_value_core(column, value);
+  } else {
+    /* not found */
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+}
+
+static const struct snmp_scalar_node tcp_RtoAlgorithm  = SNMP_SCALAR_CREATE_NODE_READONLY(1, SNMP_ASN1_TYPE_INTEGER, tcp_get_value);
+static const struct snmp_scalar_node tcp_RtoMin        = SNMP_SCALAR_CREATE_NODE_READONLY(2, SNMP_ASN1_TYPE_INTEGER, tcp_get_value);
+static const struct snmp_scalar_node tcp_RtoMax        = SNMP_SCALAR_CREATE_NODE_READONLY(3, SNMP_ASN1_TYPE_INTEGER, tcp_get_value);
+static const struct snmp_scalar_node tcp_MaxConn       = SNMP_SCALAR_CREATE_NODE_READONLY(4, SNMP_ASN1_TYPE_INTEGER, tcp_get_value);
+static const struct snmp_scalar_node tcp_ActiveOpens   = SNMP_SCALAR_CREATE_NODE_READONLY(5, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_PassiveOpens  = SNMP_SCALAR_CREATE_NODE_READONLY(6, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_AttemptFails  = SNMP_SCALAR_CREATE_NODE_READONLY(7, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_EstabResets   = SNMP_SCALAR_CREATE_NODE_READONLY(8, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_CurrEstab     = SNMP_SCALAR_CREATE_NODE_READONLY(9, SNMP_ASN1_TYPE_GAUGE, tcp_get_value);
+static const struct snmp_scalar_node tcp_InSegs        = SNMP_SCALAR_CREATE_NODE_READONLY(10, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_OutSegs       = SNMP_SCALAR_CREATE_NODE_READONLY(11, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_RetransSegs   = SNMP_SCALAR_CREATE_NODE_READONLY(12, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_InErrs        = SNMP_SCALAR_CREATE_NODE_READONLY(14, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_OutRsts       = SNMP_SCALAR_CREATE_NODE_READONLY(15, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_HCInSegs      = SNMP_SCALAR_CREATE_NODE_READONLY(17, SNMP_ASN1_TYPE_COUNTER64, tcp_get_value);
+static const struct snmp_scalar_node tcp_HCOutSegs     = SNMP_SCALAR_CREATE_NODE_READONLY(18, SNMP_ASN1_TYPE_COUNTER64, tcp_get_value);
+
+#if LWIP_IPV4
+static const struct snmp_table_simple_col_def tcp_ConnTable_columns[] = {
+  {  1, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnState */
+  {  2, SNMP_ASN1_TYPE_IPADDR,  SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnLocalAddress */
+  {  3, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnLocalPort */
+  {  4, SNMP_ASN1_TYPE_IPADDR,  SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnRemAddress */
+  {  5, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }  /* tcpConnRemPort */
+};
+
+static const struct snmp_table_simple_node tcp_ConnTable = SNMP_TABLE_CREATE_SIMPLE(13, tcp_ConnTable_columns, tcp_ConnTable_get_cell_value, tcp_ConnTable_get_next_cell_instance_and_value);
+#endif /* LWIP_IPV4 */
+
+static const struct snmp_table_simple_col_def tcp_ConnectionTable_columns[] = {
+  /* all items except tcpConnectionState and tcpConnectionProcess are declared as not-accessible */
+  { 7, SNMP_ASN1_TYPE_INTEGER,    SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnectionState */
+  { 8, SNMP_ASN1_TYPE_UNSIGNED32, SNMP_VARIANT_VALUE_TYPE_U32 }  /* tcpConnectionProcess */
+};
+
+static const struct snmp_table_simple_node tcp_ConnectionTable = SNMP_TABLE_CREATE_SIMPLE(19, tcp_ConnectionTable_columns, tcp_ConnectionTable_get_cell_value, tcp_ConnectionTable_get_next_cell_instance_and_value);
+
+
+static const struct snmp_table_simple_col_def tcp_ListenerTable_columns[] = {
+  /* all items except tcpListenerProcess are declared as not-accessible */
+  { 4, SNMP_ASN1_TYPE_UNSIGNED32, SNMP_VARIANT_VALUE_TYPE_U32 }  /* tcpListenerProcess */
+};
+
+static const struct snmp_table_simple_node tcp_ListenerTable = SNMP_TABLE_CREATE_SIMPLE(20, tcp_ListenerTable_columns, tcp_ListenerTable_get_cell_value, tcp_ListenerTable_get_next_cell_instance_and_value);
+
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */
+CREATE_LWIP_SYNC_NODE( 1, tcp_RtoAlgorithm)
+CREATE_LWIP_SYNC_NODE( 2, tcp_RtoMin)
+CREATE_LWIP_SYNC_NODE( 3, tcp_RtoMax)
+CREATE_LWIP_SYNC_NODE( 4, tcp_MaxConn)
+CREATE_LWIP_SYNC_NODE( 5, tcp_ActiveOpens)
+CREATE_LWIP_SYNC_NODE( 6, tcp_PassiveOpens)
+CREATE_LWIP_SYNC_NODE( 7, tcp_AttemptFails)
+CREATE_LWIP_SYNC_NODE( 8, tcp_EstabResets)
+CREATE_LWIP_SYNC_NODE( 9, tcp_CurrEstab)
+CREATE_LWIP_SYNC_NODE(10, tcp_InSegs)
+CREATE_LWIP_SYNC_NODE(11, tcp_OutSegs)
+CREATE_LWIP_SYNC_NODE(12, tcp_RetransSegs)
+#if LWIP_IPV4
+CREATE_LWIP_SYNC_NODE(13, tcp_ConnTable)
+#endif /* LWIP_IPV4 */
+CREATE_LWIP_SYNC_NODE(14, tcp_InErrs)
+CREATE_LWIP_SYNC_NODE(15, tcp_OutRsts)
+CREATE_LWIP_SYNC_NODE(17, tcp_HCInSegs)
+CREATE_LWIP_SYNC_NODE(18, tcp_HCOutSegs)
+CREATE_LWIP_SYNC_NODE(19, tcp_ConnectionTable)
+CREATE_LWIP_SYNC_NODE(20, tcp_ListenerTable)
+
+static const struct snmp_node* const tcp_nodes[] = {
+  &SYNC_NODE_NAME(tcp_RtoAlgorithm).node.node,
+  &SYNC_NODE_NAME(tcp_RtoMin).node.node,
+  &SYNC_NODE_NAME(tcp_RtoMax).node.node,
+  &SYNC_NODE_NAME(tcp_MaxConn).node.node,
+  &SYNC_NODE_NAME(tcp_ActiveOpens).node.node,
+  &SYNC_NODE_NAME(tcp_PassiveOpens).node.node,
+  &SYNC_NODE_NAME(tcp_AttemptFails).node.node,
+  &SYNC_NODE_NAME(tcp_EstabResets).node.node,
+  &SYNC_NODE_NAME(tcp_CurrEstab).node.node,
+  &SYNC_NODE_NAME(tcp_InSegs).node.node,
+  &SYNC_NODE_NAME(tcp_OutSegs).node.node,
+  &SYNC_NODE_NAME(tcp_RetransSegs).node.node,
+#if LWIP_IPV4
+  &SYNC_NODE_NAME(tcp_ConnTable).node.node,
+#endif /* LWIP_IPV4 */
+  &SYNC_NODE_NAME(tcp_InErrs).node.node,
+  &SYNC_NODE_NAME(tcp_OutRsts).node.node,
+  &SYNC_NODE_NAME(tcp_HCInSegs).node.node,
+  &SYNC_NODE_NAME(tcp_HCOutSegs).node.node,
+  &SYNC_NODE_NAME(tcp_ConnectionTable).node.node,
+  &SYNC_NODE_NAME(tcp_ListenerTable).node.node
+};
+
+const struct snmp_tree_node snmp_mib2_tcp_root = SNMP_CREATE_TREE_NODE(6, tcp_nodes);
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_TCP */
diff --git a/src/apps/snmp/snmp_mib2_udp.c b/src/apps/snmp/snmp_mib2_udp.c
new file mode 100644
index 0000000..6a983df
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_udp.c
@@ -0,0 +1,357 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) UDP objects and 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *         Christiaan Simons <christiaan.simons@axon.tv>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/udp.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_UDP
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+   static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+/* --- udp .1.3.6.1.2.1.7 ----------------------------------------------------- */
+
+static s16_t
+udp_get_value(struct snmp_node_instance* instance, void* value)
+{
+  u32_t *uint_ptr = (u32_t*)value;
+
+  switch (instance->node->oid) {
+  case 1: /* udpInDatagrams */
+    *uint_ptr = STATS_GET(mib2.udpindatagrams);
+    return sizeof(*uint_ptr);
+  case 2: /* udpNoPorts */
+    *uint_ptr = STATS_GET(mib2.udpnoports);
+    return sizeof(*uint_ptr);
+  case 3: /* udpInErrors */
+    *uint_ptr = STATS_GET(mib2.udpinerrors);
+    return sizeof(*uint_ptr);
+  case 4: /* udpOutDatagrams */
+    *uint_ptr = STATS_GET(mib2.udpoutdatagrams);
+    return sizeof(*uint_ptr);
+  case 8: /* udpHCInDatagrams */
+    memset(value, 0, 2*sizeof(u32_t)); /* not supported */
+    return 2*sizeof(u32_t);
+  case 9: /* udpHCOutDatagrams */
+    memset(value, 0, 2*sizeof(u32_t)); /* not supported */
+    return 2*sizeof(u32_t);
+  default:
+    LWIP_DEBUGF(SNMP_MIB_DEBUG,("udp_get_value(): unknown id: %"S32_F"\n", instance->node->oid));
+    break;
+  }
+
+  return 0;
+}
+
+/* --- udpEndpointTable --- */
+
+static snmp_err_t
+udp_endpointTable_get_cell_value_core(const u32_t* column, union snmp_variant_value* value)
+{
+  /* all items except udpEndpointProcess are declared as not-accessible */
+  switch (*column) {
+  case 8: /* udpEndpointProcess */
+    value->u32 = 0; /* not supported */
+    break;
+  default:
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+udp_endpointTable_get_cell_value(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, union snmp_variant_value* value, u32_t* value_len)
+{
+  ip_addr_t local_ip, remote_ip;
+  u16_t local_port, remote_port;
+  struct udp_pcb *pcb;
+  u8_t idx = 0;
+
+  LWIP_UNUSED_ARG(value_len);
+
+  /* udpEndpointLocalAddressType + udpEndpointLocalAddress + udpEndpointLocalPort */
+  idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len-idx, &local_ip, &local_port);
+  if (idx == 0) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* udpEndpointRemoteAddressType + udpEndpointRemoteAddress + udpEndpointRemotePort */
+  idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len-idx, &remote_ip, &remote_port);
+  if (idx == 0) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* udpEndpointInstance */
+  if (row_oid_len < (idx+1)) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+  if (row_oid[idx] != 0) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+  
+  /* find udp_pcb with requested ip and port*/
+  pcb = udp_pcbs;
+  while (pcb != NULL) {
+    if (ip_addr_cmp(&local_ip, &pcb->local_ip) &&
+       (local_port == pcb->local_port) &&
+       ip_addr_cmp(&remote_ip, &pcb->remote_ip) &&
+       (remote_port == pcb->remote_port)) {
+      /* fill in object properties */
+      return udp_endpointTable_get_cell_value_core(column, value);
+    }
+    pcb = pcb->next;
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t 
+udp_endpointTable_get_next_cell_instance_and_value(const u32_t* column, struct snmp_obj_id* row_oid, union snmp_variant_value* value, u32_t* value_len)
+{
+  struct udp_pcb *pcb;
+  struct snmp_next_oid_state state;
+  /* 1x udpEndpointLocalAddressType  + 1x OID len + 16x udpEndpointLocalAddress  + 1x udpEndpointLocalPort  +
+   * 1x udpEndpointRemoteAddressType + 1x OID len + 16x udpEndpointRemoteAddress + 1x udpEndpointRemotePort +
+   * 1x udpEndpointInstance = 39
+   */
+  u32_t  result_temp[39];
+
+  LWIP_UNUSED_ARG(value_len);
+
+  /* init struct to search next oid */
+  snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(result_temp));
+
+  /* iterate over all possible OIDs to find the next one */
+  pcb = udp_pcbs;
+  while (pcb != NULL) {
+    u32_t test_oid[LWIP_ARRAYSIZE(result_temp)];
+    u8_t idx = 0;
+
+    /* udpEndpointLocalAddressType + udpEndpointLocalAddress + udpEndpointLocalPort */
+    idx += snmp_ip_port_to_oid(&pcb->local_ip, pcb->local_port, &test_oid[idx]);
+
+    /* udpEndpointRemoteAddressType + udpEndpointRemoteAddress + udpEndpointRemotePort */
+    idx += snmp_ip_port_to_oid(&pcb->remote_ip, pcb->remote_port, &test_oid[idx]);
+
+    test_oid[idx] = 0; /* udpEndpointInstance */    
+    idx++;
+    
+    /* check generated OID: is it a candidate for the next one? */
+    snmp_next_oid_check(&state, test_oid, idx, NULL);
+    
+    pcb = pcb->next;
+  }
+
+  /* did we find a next one? */
+  if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+    snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+    /* fill in object properties */
+    return udp_endpointTable_get_cell_value_core(column, value);
+  } else {
+    /* not found */
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+}
+
+/* --- udpTable --- */
+
+#if LWIP_IPV4
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range udp_Table_oid_ranges[] = {
+  { 0, 0xff   }, /* IP A        */
+  { 0, 0xff   }, /* IP B        */
+  { 0, 0xff   }, /* IP C        */
+  { 0, 0xff   }, /* IP D        */
+  { 1, 0xffff }  /* Port        */
+};
+
+static snmp_err_t 
+udp_Table_get_cell_value_core(struct udp_pcb *pcb, const u32_t* column, union snmp_variant_value* value, u32_t* value_len)
+{
+  LWIP_UNUSED_ARG(value_len);
+
+  switch (*column) {
+  case 1: /* udpLocalAddress */
+    /* set reference to PCB local IP and return a generic node that copies IP4 addresses */
+    value->u32 = ip_2_ip4(&pcb->local_ip)->addr;
+    break;
+  case 2: /* udpLocalPort */
+    /* set reference to PCB local port and return a generic node that copies u16_t values */
+    value->u32 = pcb->local_port;
+    break;
+  default:
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t 
+udp_Table_get_cell_value(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, union snmp_variant_value* value, u32_t* value_len)
+{
+  ip4_addr_t ip;
+  u16_t port;
+  struct udp_pcb *pcb;
+
+  /* check if incoming OID length and if values are in plausible range */
+  if (!snmp_oid_in_range(row_oid, row_oid_len, udp_Table_oid_ranges, LWIP_ARRAYSIZE(udp_Table_oid_ranges))) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  /* get IP and port from incoming OID */
+  snmp_oid_to_ip4(&row_oid[0], &ip); /* we know it succeeds because of oid_in_range check above */
+  port = (u16_t)row_oid[4];
+
+  /* find udp_pcb with requested ip and port*/
+  pcb = udp_pcbs;
+  while (pcb != NULL) {
+    if (IP_IS_V4_VAL(pcb->local_ip)) {
+      if (ip4_addr_cmp(&ip, ip_2_ip4(&pcb->local_ip)) && (port == pcb->local_port)) {
+        /* fill in object properties */
+        return udp_Table_get_cell_value_core(pcb, column, value, value_len);
+      }
+    }
+    pcb = pcb->next;
+  }
+
+  /* not found */
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t 
+udp_Table_get_next_cell_instance_and_value(const u32_t* column, struct snmp_obj_id* row_oid, union snmp_variant_value* value, u32_t* value_len)
+{
+  struct udp_pcb *pcb;
+  struct snmp_next_oid_state state;
+  u32_t  result_temp[LWIP_ARRAYSIZE(udp_Table_oid_ranges)];
+
+  /* init struct to search next oid */
+  snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(udp_Table_oid_ranges));
+
+  /* iterate over all possible OIDs to find the next one */
+  pcb = udp_pcbs;
+  while (pcb != NULL) {
+    u32_t test_oid[LWIP_ARRAYSIZE(udp_Table_oid_ranges)];
+
+    if (IP_IS_V4_VAL(pcb->local_ip)) {
+      snmp_ip4_to_oid(ip_2_ip4(&pcb->local_ip), &test_oid[0]);
+      test_oid[4] = pcb->local_port;
+
+      /* check generated OID: is it a candidate for the next one? */
+      snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(udp_Table_oid_ranges), pcb);
+    }
+    
+    pcb = pcb->next;
+  }
+
+  /* did we find a next one? */
+  if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+    snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+    /* fill in object properties */
+    return udp_Table_get_cell_value_core((struct udp_pcb*)state.reference, column, value, value_len);
+  } else {
+    /* not found */
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+}
+
+#endif /* LWIP_IPV4 */
+
+static const struct snmp_scalar_node udp_inDatagrams    = SNMP_SCALAR_CREATE_NODE_READONLY(1, SNMP_ASN1_TYPE_COUNTER,   udp_get_value);
+static const struct snmp_scalar_node udp_noPorts        = SNMP_SCALAR_CREATE_NODE_READONLY(2, SNMP_ASN1_TYPE_COUNTER,   udp_get_value);
+static const struct snmp_scalar_node udp_inErrors       = SNMP_SCALAR_CREATE_NODE_READONLY(3, SNMP_ASN1_TYPE_COUNTER,   udp_get_value);
+static const struct snmp_scalar_node udp_outDatagrams   = SNMP_SCALAR_CREATE_NODE_READONLY(4, SNMP_ASN1_TYPE_COUNTER,   udp_get_value);
+static const struct snmp_scalar_node udp_HCInDatagrams  = SNMP_SCALAR_CREATE_NODE_READONLY(8, SNMP_ASN1_TYPE_COUNTER64, udp_get_value);
+static const struct snmp_scalar_node udp_HCOutDatagrams = SNMP_SCALAR_CREATE_NODE_READONLY(9, SNMP_ASN1_TYPE_COUNTER64, udp_get_value);
+
+#if LWIP_IPV4
+static const struct snmp_table_simple_col_def udp_Table_columns[] = {
+  { 1, SNMP_ASN1_TYPE_IPADDR,  SNMP_VARIANT_VALUE_TYPE_U32 }, /* udpLocalAddress */
+  { 2, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }  /* udpLocalPort */
+};
+static const struct snmp_table_simple_node udp_Table = SNMP_TABLE_CREATE_SIMPLE(5, udp_Table_columns, udp_Table_get_cell_value, udp_Table_get_next_cell_instance_and_value);
+#endif /* LWIP_IPV4 */
+
+static const struct snmp_table_simple_col_def udp_endpointTable_columns[] = {
+  /* all items except udpEndpointProcess are declared as not-accessible */   
+  { 8, SNMP_ASN1_TYPE_UNSIGNED32, SNMP_VARIANT_VALUE_TYPE_U32 }  /* udpEndpointProcess */
+};
+
+static const struct snmp_table_simple_node udp_endpointTable = SNMP_TABLE_CREATE_SIMPLE(7, udp_endpointTable_columns, udp_endpointTable_get_cell_value, udp_endpointTable_get_next_cell_instance_and_value);
+
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */ 
+CREATE_LWIP_SYNC_NODE(1, udp_inDatagrams)
+CREATE_LWIP_SYNC_NODE(2, udp_noPorts)
+CREATE_LWIP_SYNC_NODE(3, udp_inErrors)
+CREATE_LWIP_SYNC_NODE(4, udp_outDatagrams)
+#if LWIP_IPV4
+CREATE_LWIP_SYNC_NODE(5, udp_Table)
+#endif /* LWIP_IPV4 */
+CREATE_LWIP_SYNC_NODE(7, udp_endpointTable)
+CREATE_LWIP_SYNC_NODE(8, udp_HCInDatagrams)
+CREATE_LWIP_SYNC_NODE(9, udp_HCOutDatagrams)
+
+static const struct snmp_node* const udp_nodes[] = {
+  &SYNC_NODE_NAME(udp_inDatagrams).node.node,
+  &SYNC_NODE_NAME(udp_noPorts).node.node,
+  &SYNC_NODE_NAME(udp_inErrors).node.node,
+  &SYNC_NODE_NAME(udp_outDatagrams).node.node,
+#if LWIP_IPV4
+  &SYNC_NODE_NAME(udp_Table).node.node,
+#endif /* LWIP_IPV4 */
+  &SYNC_NODE_NAME(udp_endpointTable).node.node,
+  &SYNC_NODE_NAME(udp_HCInDatagrams).node.node,
+  &SYNC_NODE_NAME(udp_HCOutDatagrams).node.node
+};
+
+const struct snmp_tree_node snmp_mib2_udp_root = SNMP_CREATE_TREE_NODE(7, udp_nodes);
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_UDP */
diff --git a/src/apps/snmp/snmp_msg.c b/src/apps/snmp/snmp_msg.c
new file mode 100644
index 0000000..0cb7ca9
--- /dev/null
+++ b/src/apps/snmp/snmp_msg.c
@@ -0,0 +1,1668 @@
+/**
+ * @file
+ * SNMP message processing (RFC1157).
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * Copyright (c) 2016 Elias Oenal.
+ * 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@axon.tv>
+ *         Martin Hentschel <info@cl-soft.de>
+ *         Elias Oenal <lwip@eliasoenal.com>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "snmp_msg.h"
+#include "snmp_asn1.h"
+#include "snmp_core_priv.h"
+#include "lwip/ip_addr.h"
+#include "lwip/stats.h"
+
+#if LWIP_SNMP_V3
+#include "lwip/apps/snmpv3.h"
+#include "snmpv3_priv.h"
+#ifdef LWIP_SNMPV3_INCLUDE_ENGINE
+#include LWIP_SNMPV3_INCLUDE_ENGINE
+#endif
+#endif
+
+#include <string.h>
+
+/* public (non-static) constants */
+/** SNMP community string */
+const char *snmp_community = SNMP_COMMUNITY;
+/** SNMP community string for write access */
+const char *snmp_community_write = SNMP_COMMUNITY_WRITE;
+/** SNMP community string for sending traps */
+const char *snmp_community_trap = SNMP_COMMUNITY_TRAP;
+
+snmp_write_callback_fct snmp_write_callback     = NULL;
+void*                   snmp_write_callback_arg = NULL;
+
+/**
+ * @ingroup snmp_core
+ * Returns current SNMP community string.
+ * @return current SNMP community string
+ */
+const char *
+snmp_get_community(void)
+{
+  return snmp_community;
+}
+
+/**
+ * @ingroup snmp_core
+ * Sets SNMP community string.
+ * The string itself (its storage) must be valid throughout the whole life of
+ * program (or until it is changed to sth else).
+ *
+ * @param community is a pointer to new community string
+ */
+void
+snmp_set_community(const char * const community)
+{
+  LWIP_ASSERT("community string is too long!", strlen(community) <= SNMP_MAX_COMMUNITY_STR_LEN);
+  snmp_community = community;
+}
+
+/**
+ * @ingroup snmp_core
+ * Returns current SNMP write-access community string.
+ * @return current SNMP write-access community string
+ */
+const char *
+snmp_get_community_write(void)
+{
+  return snmp_community_write;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Returns current SNMP community string used for sending traps.
+ * @return current SNMP community string used for sending traps
+ */
+const char *
+snmp_get_community_trap(void)
+{
+  return snmp_community_trap;
+}
+
+/**
+ * @ingroup snmp_core
+ * Sets SNMP community string for write-access.
+ * The string itself (its storage) must be valid throughout the whole life of
+ * program (or until it is changed to sth else).
+ *
+ * @param community is a pointer to new write-access community string
+ */
+void
+snmp_set_community_write(const char * const community)
+{
+  LWIP_ASSERT("community string must not be NULL", community != NULL);
+  LWIP_ASSERT("community string is too long!", strlen(community) <= SNMP_MAX_COMMUNITY_STR_LEN);
+  snmp_community_write = community;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Sets SNMP community string used for sending traps.
+ * The string itself (its storage) must be valid throughout the whole life of
+ * program (or until it is changed to sth else).
+ *
+ * @param community is a pointer to new trap community string
+ */
+void
+snmp_set_community_trap(const char * const community)
+{
+  LWIP_ASSERT("community string is too long!", strlen(community) <= SNMP_MAX_COMMUNITY_STR_LEN);
+  snmp_community_trap = community;
+}
+
+/**
+ * @ingroup snmp_core
+ * Callback fired on every successful write access
+ */
+void 
+snmp_set_write_callback(snmp_write_callback_fct write_callback, void* callback_arg)
+{
+  snmp_write_callback     = write_callback;
+  snmp_write_callback_arg = callback_arg;
+}
+
+/* ----------------------------------------------------------------------- */
+/* forward declarations */
+/* ----------------------------------------------------------------------- */
+
+static err_t snmp_process_get_request(struct snmp_request *request);
+static err_t snmp_process_getnext_request(struct snmp_request *request);
+static err_t snmp_process_getbulk_request(struct snmp_request *request);
+static err_t snmp_process_set_request(struct snmp_request *request);
+
+static err_t snmp_parse_inbound_frame(struct snmp_request *request);
+static err_t snmp_prepare_outbound_frame(struct snmp_request *request);
+static err_t snmp_complete_outbound_frame(struct snmp_request *request);
+static void snmp_execute_write_callbacks(struct snmp_request *request);
+
+
+/* ----------------------------------------------------------------------- */
+/* implementation */
+/* ----------------------------------------------------------------------- */
+
+void
+snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t port)
+{
+  err_t err;
+  struct snmp_request request;
+   
+  memset(&request, 0, sizeof(request));
+  request.handle       = handle;
+  request.source_ip    = source_ip;
+  request.source_port  = port;
+  request.inbound_pbuf = p;
+
+  snmp_stats.inpkts++;
+
+  err = snmp_parse_inbound_frame(&request);
+  if (err == ERR_OK) {
+    err = snmp_prepare_outbound_frame(&request);
+    if (err == ERR_OK) {
+
+      if (request.error_status == SNMP_ERR_NOERROR) {
+        /* only process frame if we do not already have an error to return (e.g. all readonly) */
+        if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_REQ) {
+          err = snmp_process_get_request(&request);
+        } else if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_NEXT_REQ) {
+          err = snmp_process_getnext_request(&request);
+        } else if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ) {
+          err = snmp_process_getbulk_request(&request);
+        } else if (request.request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+          err = snmp_process_set_request(&request);
+        }
+      }
+
+      if (err == ERR_OK) {
+        err = snmp_complete_outbound_frame(&request);
+      
+        if (err == ERR_OK) {
+          err = snmp_sendto(request.handle, request.outbound_pbuf, request.source_ip, request.source_port);
+
+          if ((request.request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) 
+            && (request.error_status == SNMP_ERR_NOERROR) 
+            && (snmp_write_callback != NULL)) {
+            /* raise write notification for all written objects */
+            snmp_execute_write_callbacks(&request);
+          }
+        }
+      }
+    }
+  
+    if (request.outbound_pbuf != NULL) {
+      pbuf_free(request.outbound_pbuf);
+    }
+  }
+}
+
+static u8_t
+snmp_msg_getnext_validate_node_inst(struct snmp_node_instance* node_instance, void* validate_arg)
+{
+  if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_READ) != SNMP_NODE_INSTANCE_ACCESS_READ) || (node_instance->get_value == NULL)) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  if ((node_instance->asn1_type == SNMP_ASN1_TYPE_COUNTER64) && (((struct snmp_request*)validate_arg)->version == SNMP_VERSION_1)) {
+    /* according to RFC 2089 skip Counter64 objects in GetNext requests from v1 clients */
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  return SNMP_ERR_NOERROR;
+}
+
+static void 
+snmp_process_varbind(struct snmp_request *request, struct snmp_varbind *vb, u8_t get_next)
+{
+  err_t err;
+  struct snmp_node_instance node_instance;
+  memset(&node_instance, 0, sizeof(node_instance));
+
+  if (get_next) {
+    struct snmp_obj_id result_oid;
+    request->error_status = snmp_get_next_node_instance_from_oid(vb->oid.id, vb->oid.len, snmp_msg_getnext_validate_node_inst, request,  &result_oid, &node_instance);
+
+    if (request->error_status == SNMP_ERR_NOERROR) {
+      snmp_oid_assign(&vb->oid, result_oid.id, result_oid.len);
+    }
+  } else {
+    request->error_status = snmp_get_node_instance_from_oid(vb->oid.id, vb->oid.len, &node_instance);
+
+    if (request->error_status == SNMP_ERR_NOERROR) {
+      /* use 'getnext_validate' method for validation to avoid code duplication (some checks have to be executed here) */
+      request->error_status = snmp_msg_getnext_validate_node_inst(&node_instance, request);
+
+      if (request->error_status != SNMP_ERR_NOERROR) {
+        if (node_instance.release_instance != NULL) {
+          node_instance.release_instance(&node_instance);
+        }
+      }
+    }
+  }
+
+  if (request->error_status != SNMP_ERR_NOERROR)  {
+    if (request->error_status >= SNMP_VARBIND_EXCEPTION_OFFSET) {
+      if ((request->version == SNMP_VERSION_2c) || request->version == SNMP_VERSION_3) {
+        /* in SNMP v2c a varbind related exception is stored in varbind and not in frame header */
+        vb->type = (SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_CLASS_CONTEXT | (request->error_status & SNMP_VARBIND_EXCEPTION_MASK));
+        vb->value_len = 0;
+
+        err = snmp_append_outbound_varbind(&(request->outbound_pbuf_stream), vb);
+        if (err == ERR_OK) {
+          /* we stored the exception in varbind -> go on */
+          request->error_status = SNMP_ERR_NOERROR;
+        } else if (err == ERR_BUF) {
+          request->error_status = SNMP_ERR_TOOBIG;
+        } else {
+          request->error_status = SNMP_ERR_GENERROR;
+        }
+      }
+    } else {
+      /* according to RFC 1157/1905, all other errors only return genError */
+      request->error_status = SNMP_ERR_GENERROR;
+    }
+  } else {
+    s16_t len = node_instance.get_value(&node_instance, vb->value);
+    vb->type = node_instance.asn1_type;
+
+    if(len >= 0) {
+      vb->value_len = (u16_t)len; /* cast is OK because we checked >= 0 above */
+
+      LWIP_ASSERT("SNMP_MAX_VALUE_SIZE is configured too low", (vb->value_len & ~SNMP_GET_VALUE_RAW_DATA) <= SNMP_MAX_VALUE_SIZE);
+      err = snmp_append_outbound_varbind(&request->outbound_pbuf_stream, vb);
+
+      if (err == ERR_BUF) {
+        request->error_status = SNMP_ERR_TOOBIG;
+      } else if (err != ERR_OK) {
+        request->error_status = SNMP_ERR_GENERROR;
+      }
+    } else {
+      request->error_status = SNMP_ERR_GENERROR;
+    }
+
+    if (node_instance.release_instance != NULL) {
+      node_instance.release_instance(&node_instance);
+    }
+  }
+}
+
+
+/**
+ * Service an internal or external event for SNMP GET.
+ *
+ * @param request points to the associated message process state
+ */
+static err_t
+snmp_process_get_request(struct snmp_request *request)
+{
+  snmp_vb_enumerator_err_t err;
+  struct snmp_varbind vb;
+  vb.value = request->value_buffer;
+
+  LWIP_DEBUGF(SNMP_DEBUG, ("SNMP get request\n"));
+
+  while (request->error_status == SNMP_ERR_NOERROR) {
+    err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+    if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+      if ((vb.type == SNMP_ASN1_TYPE_NULL) && (vb.value_len == 0)) {
+        snmp_process_varbind(request, &vb, 0);
+      } else {
+        request->error_status = SNMP_ERR_GENERROR;
+      }
+    } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+      /* no more varbinds in request */
+      break;
+    } else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
+      /* malformed ASN.1, don't answer */
+      return ERR_ARG;
+    } else {
+      request->error_status = SNMP_ERR_GENERROR;
+    }
+  }
+
+  return ERR_OK;
+}
+
+/**
+ * Service an internal or external event for SNMP GET.
+ *
+ * @param request points to the associated message process state
+ */
+static err_t
+snmp_process_getnext_request(struct snmp_request *request)
+{
+  snmp_vb_enumerator_err_t err;
+  struct snmp_varbind vb;
+  vb.value = request->value_buffer;
+
+  LWIP_DEBUGF(SNMP_DEBUG, ("SNMP get-next request\n"));
+
+  while (request->error_status == SNMP_ERR_NOERROR) {
+    err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+    if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+      if ((vb.type == SNMP_ASN1_TYPE_NULL) && (vb.value_len == 0)) {
+        snmp_process_varbind(request, &vb, 1);
+      } else {
+        request->error_status = SNMP_ERR_GENERROR;
+      }
+    } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+      /* no more varbinds in request */
+      break;
+    } else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
+      /* malformed ASN.1, don't answer */
+      return ERR_ARG;
+    } else {
+      request->error_status = SNMP_ERR_GENERROR;
+    }
+  }
+  
+  return ERR_OK;
+}
+
+/**
+ * Service an internal or external event for SNMP GETBULKT.
+ *
+ * @param request points to the associated message process state
+ */
+static err_t
+snmp_process_getbulk_request(struct snmp_request *request)
+{
+  snmp_vb_enumerator_err_t err;
+  s32_t non_repeaters     = request->non_repeaters;
+  s32_t repetitions;
+  u16_t repetition_offset = 0;
+  struct snmp_varbind_enumerator repetition_varbind_enumerator;
+  struct snmp_varbind vb;
+  vb.value = request->value_buffer;
+
+  if (SNMP_LWIP_GETBULK_MAX_REPETITIONS > 0) {
+    repetitions = LWIP_MIN(request->max_repetitions, SNMP_LWIP_GETBULK_MAX_REPETITIONS);
+  } else {
+    repetitions = request->max_repetitions;
+  }
+
+  LWIP_DEBUGF(SNMP_DEBUG, ("SNMP get-bulk request\n"));
+
+  /* process non repeaters and first repetition */
+  while (request->error_status == SNMP_ERR_NOERROR) {
+    if (non_repeaters == 0) {
+      repetition_offset = request->outbound_pbuf_stream.offset;
+
+      if (repetitions == 0) {
+        /* do not resolve repeaters when repetitions is set to 0 */
+        break;
+      }
+      repetitions--;
+    }
+
+    err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+    if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+      /* no more varbinds in request */
+      break;
+    } else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
+      /* malformed ASN.1, don't answer */
+      return ERR_ARG;
+    } else if ((err != SNMP_VB_ENUMERATOR_ERR_OK) || (vb.type != SNMP_ASN1_TYPE_NULL) || (vb.value_len != 0)) {
+      request->error_status = SNMP_ERR_GENERROR;
+    } else {
+      snmp_process_varbind(request, &vb, 1);
+      non_repeaters--;
+    }
+  }
+
+  /* process repetitions > 1 */
+  while ((request->error_status == SNMP_ERR_NOERROR) && (repetitions > 0) && (request->outbound_pbuf_stream.offset != repetition_offset)) {
+
+    u8_t all_endofmibview = 1;
+    
+    snmp_vb_enumerator_init(&repetition_varbind_enumerator, request->outbound_pbuf, repetition_offset, request->outbound_pbuf_stream.offset - repetition_offset);
+    repetition_offset = request->outbound_pbuf_stream.offset; /* for next loop */
+
+    while (request->error_status == SNMP_ERR_NOERROR) {
+      vb.value = NULL; /* do NOT decode value (we enumerate outbound buffer here, so all varbinds have values assigned) */
+      err = snmp_vb_enumerator_get_next(&repetition_varbind_enumerator, &vb);
+      if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+        vb.value = request->value_buffer;
+        snmp_process_varbind(request, &vb, 1);
+
+        if (request->error_status != SNMP_ERR_NOERROR) {
+          /* already set correct error-index (here it cannot be taken from inbound varbind enumerator) */
+          request->error_index = request->non_repeaters + repetition_varbind_enumerator.varbind_count;
+        } else if (vb.type != (SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTEXT_VARBIND_END_OF_MIB_VIEW)) {
+          all_endofmibview = 0;
+        }
+      } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+        /* no more varbinds in request */
+        break;
+      } else {
+        LWIP_DEBUGF(SNMP_DEBUG, ("Very strange, we cannot parse the varbind output that we created just before!"));
+        request->error_status = SNMP_ERR_GENERROR;
+        request->error_index  = request->non_repeaters + repetition_varbind_enumerator.varbind_count;
+      }
+    }
+
+    if ((request->error_status == SNMP_ERR_NOERROR) && all_endofmibview) {
+      /* stop when all varbinds in a loop return EndOfMibView */
+      break;
+    }
+    
+    repetitions--;
+  }
+
+  if (request->error_status == SNMP_ERR_TOOBIG) {
+    /* for GetBulk it is ok, if not all requested variables fit into the response -> just return the varbinds added so far */
+    request->error_status = SNMP_ERR_NOERROR;
+  }
+
+  return ERR_OK;
+}
+
+/**
+ * Service an internal or external event for SNMP SET.
+ *
+ * @param request points to the associated message process state
+ */
+static err_t
+snmp_process_set_request(struct snmp_request *request)
+{
+  snmp_vb_enumerator_err_t err;
+  struct snmp_varbind vb;
+  vb.value = request->value_buffer;
+
+  LWIP_DEBUGF(SNMP_DEBUG, ("SNMP set request\n"));
+
+  /* perform set test on all objects */
+  while (request->error_status == SNMP_ERR_NOERROR) {
+    err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+    if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+      struct snmp_node_instance node_instance;
+      memset(&node_instance, 0, sizeof(node_instance));
+      
+      request->error_status = snmp_get_node_instance_from_oid(vb.oid.id, vb.oid.len, &node_instance);
+      if (request->error_status == SNMP_ERR_NOERROR) {
+        if (node_instance.asn1_type != vb.type) {
+          request->error_status = SNMP_ERR_WRONGTYPE;
+        } else if (((node_instance.access & SNMP_NODE_INSTANCE_ACCESS_WRITE) != SNMP_NODE_INSTANCE_ACCESS_WRITE) || (node_instance.set_value == NULL)) {
+          request->error_status = SNMP_ERR_NOTWRITABLE;
+        } else {
+          if (node_instance.set_test != NULL) {
+            request->error_status = node_instance.set_test(&node_instance, vb.value_len, vb.value);
+          }
+        }
+
+        if (node_instance.release_instance != NULL) {
+          node_instance.release_instance(&node_instance);
+        }
+      }
+    } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+      /* no more varbinds in request */
+      break;
+    } else if (err == SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH) {
+      request->error_status = SNMP_ERR_WRONGLENGTH;
+    } else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
+      /* malformed ASN.1, don't answer */
+      return ERR_ARG;
+    } else {
+      request->error_status = SNMP_ERR_GENERROR;
+    }
+  }
+
+  /* perform real set operation on all objects */
+  if (request->error_status == SNMP_ERR_NOERROR) {
+    snmp_vb_enumerator_init(&request->inbound_varbind_enumerator, request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len);
+    while (request->error_status == SNMP_ERR_NOERROR) {
+      err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+      if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+        struct snmp_node_instance node_instance;
+        memset(&node_instance, 0, sizeof(node_instance));
+        request->error_status = snmp_get_node_instance_from_oid(vb.oid.id, vb.oid.len, &node_instance);
+        if (request->error_status == SNMP_ERR_NOERROR) {
+          if (node_instance.set_value(&node_instance, vb.value_len, vb.value) != SNMP_ERR_NOERROR) {
+            if (request->inbound_varbind_enumerator.varbind_count == 1) {
+              request->error_status = SNMP_ERR_COMMITFAILED;
+            } else {
+              /* we cannot undo the set operations done so far */
+              request->error_status = SNMP_ERR_UNDOFAILED;
+            }
+          }
+
+          if (node_instance.release_instance != NULL) {
+            node_instance.release_instance(&node_instance);
+          }
+        }
+      } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+        /* no more varbinds in request */
+        break;
+      } else {
+        /* first time enumerating varbinds work but second time not, although nothing should have changed in between ??? */
+        request->error_status = SNMP_ERR_GENERROR;
+      }
+    }
+  }
+
+  return ERR_OK;
+}
+
+#define PARSE_EXEC(code, retValue) \
+  if ((code) != ERR_OK) { \
+    LWIP_DEBUGF(SNMP_DEBUG, ("Malformed ASN.1 detected.\n")); \
+    snmp_stats.inasnparseerrs++; \
+    return retValue; \
+  }
+
+#define PARSE_ASSERT(cond, retValue) \
+  if (!(cond)) { \
+    LWIP_DEBUGF(SNMP_DEBUG, ("SNMP parse assertion failed!: " # cond)); \
+    snmp_stats.inasnparseerrs++; \
+    return retValue; \
+  }
+
+#define BUILD_EXEC(code, retValue) \
+  if ((code) != ERR_OK) { \
+    LWIP_DEBUGF(SNMP_DEBUG, ("SNMP error during creation of outbound frame!: " # code)); \
+    return retValue; \
+  }
+
+#define IF_PARSE_EXEC(code)   PARSE_EXEC(code, ERR_ARG)
+#define IF_PARSE_ASSERT(code) PARSE_ASSERT(code, ERR_ARG)
+
+/**
+ * Checks and decodes incoming SNMP message header, logs header errors.
+ *
+ * @param request points to the current message request state return
+ * @return
+ * - ERR_OK SNMP header is sane and accepted
+ * - ERR_VAL SNMP header is either malformed or rejected
+ */
+static err_t
+snmp_parse_inbound_frame(struct snmp_request *request)
+{
+  struct snmp_pbuf_stream pbuf_stream;
+  struct snmp_asn1_tlv tlv;
+  s32_t parent_tlv_value_len;
+  s32_t s32_value;
+  err_t err;
+
+  IF_PARSE_EXEC(snmp_pbuf_stream_init(&pbuf_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
+  
+  /* decode main container consisting of version, community and PDU */
+  IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+  IF_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_SEQUENCE) && (tlv.value_len == pbuf_stream.length));
+  parent_tlv_value_len = tlv.value_len;
+
+  /* decode version */
+  IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+  IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+  parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+  IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+  
+  IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+  if ((s32_value != SNMP_VERSION_1) &&
+      (s32_value != SNMP_VERSION_2c)
+#if LWIP_SNMP_V3
+      && (s32_value != SNMP_VERSION_3)
+#endif
+     )
+  {
+    /* unsupported SNMP version */
+    snmp_stats.inbadversions++;
+    return ERR_ARG;
+  }
+  request->version = (u8_t)s32_value;
+
+#if LWIP_SNMP_V3
+  if (request->version == SNMP_VERSION_3) {
+    u16_t u16_value;
+    u16_t inbound_msgAuthenticationParameters_offset;
+
+    /* SNMPv3 doesn't use communities */
+    /* @todo: Differentiate read/write access */
+    strcpy((char*)request->community, snmp_community);
+    request->community_strlen = (u16_t)strlen(snmp_community);
+
+    /* RFC3414 globalData */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    /* decode msgID */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+    request->msg_id = s32_value;
+
+    /* decode msgMaxSize */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+    request->msg_max_size = s32_value;
+
+    /* decode msgFlags */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+    request->msg_flags = (u8_t)s32_value;
+
+    /* decode msgSecurityModel */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+    request->msg_security_model = s32_value;
+
+    /* RFC3414 msgSecurityParameters
+     * The User-based Security Model defines the contents of the OCTET
+     * STRING as a SEQUENCE.
+     *
+     * We skip the protective dummy OCTET STRING header
+     * to access the SEQUENCE header.
+     */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    /* msgSecurityParameters SEQUENCE header */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    /* decode msgAuthoritativeEngineID */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_authoritative_engine_id,
+        &u16_value, SNMP_V3_MAX_ENGINE_ID_LENGTH));
+    request->msg_authoritative_engine_id_len = (u8_t)u16_value;
+
+    /* msgAuthoritativeEngineBoots */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->msg_authoritative_engine_boots));
+
+    /* msgAuthoritativeEngineTime */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->msg_authoritative_engine_time));
+    /* @todo: Implement time window checking */
+
+    /* msgUserName */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_user_name,
+        &u16_value, SNMP_V3_MAX_USER_LENGTH));
+    request->msg_user_name_len = (u8_t)u16_value;
+    /* @todo: Implement unknown user error response */
+    IF_PARSE_EXEC(snmpv3_get_user((char*)request->msg_user_name, NULL, NULL, NULL, NULL));
+
+    /* msgAuthenticationParameters */
+    memset(request->msg_authentication_parameters, 0, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+    /* Remember position */
+    inbound_msgAuthenticationParameters_offset = pbuf_stream.offset;
+    LWIP_UNUSED_ARG(inbound_msgAuthenticationParameters_offset);
+    /* Read auth parameters */
+    IF_PARSE_ASSERT(tlv.value_len <= SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+    IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_authentication_parameters,
+        &u16_value, tlv.value_len));
+
+#if LWIP_SNMP_V3_CRYPTO
+    if (request->msg_flags & SNMP_V3_AUTH_FLAG) {
+      const u8_t zero_arr[SNMP_V3_MAX_AUTH_PARAM_LENGTH] = { 0 };
+      u8_t key[20];
+      u8_t algo;
+      u8_t hmac[LWIP_MAX(SNMP_V3_SHA_LEN, SNMP_V3_MD5_LEN)];
+      struct snmp_pbuf_stream auth_stream;
+
+      /* Rewind stream */
+      IF_PARSE_EXEC(snmp_pbuf_stream_init(&pbuf_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
+      IF_PARSE_EXEC(snmp_pbuf_stream_seek_abs(&pbuf_stream, inbound_msgAuthenticationParameters_offset));
+      /* Set auth parameters to zero for verification */
+      IF_PARSE_EXEC(snmp_asn1_enc_raw(&pbuf_stream, zero_arr, tlv.value_len));
+
+      /* Verify authentication */
+      IF_PARSE_EXEC(snmp_pbuf_stream_init(&auth_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
+
+      IF_PARSE_EXEC(snmpv3_get_user((char*)request->msg_user_name, &algo, key, NULL, NULL));
+      IF_PARSE_EXEC(snmpv3_auth(&auth_stream, request->inbound_pbuf->tot_len, key, algo, hmac));
+      /* @todo: Implement error response */
+      IF_PARSE_EXEC(memcmp(request->msg_authentication_parameters, hmac, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
+    }
+#else
+    /* Ungraceful exit if we encounter cryptography and don't support it.
+     * @todo: Implement error response
+     */
+    IF_PARSE_ASSERT(!(request->msg_flags & (SNMP_V3_AUTH_FLAG | SNMP_V3_PRIV_FLAG)));
+#endif
+
+    /* msgPrivacyParameters */
+    memset(request->msg_privacy_parameters, 0, SNMP_V3_MAX_PRIV_PARAM_LENGTH);
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_privacy_parameters,
+        &u16_value, SNMP_V3_MAX_PRIV_PARAM_LENGTH));
+
+#if LWIP_SNMP_V3_CRYPTO
+    /* Decrypt message */
+    if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
+      u8_t key[20];
+      u8_t algo;
+
+      IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+      IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+      parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+      IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+      IF_PARSE_EXEC(snmpv3_get_user((char*)request->msg_user_name, NULL, NULL, &algo, key));
+      IF_PARSE_EXEC(snmpv3_crypt(&pbuf_stream, tlv.value_len, key,
+          request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
+          request->msg_authoritative_engine_time, algo, SNMP_V3_PRIV_MODE_DECRYPT));
+    }
+#endif
+
+    /* Scoped PDU
+     * Encryption context
+     */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    /* contextEngineID */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->context_engine_id,
+        &u16_value, SNMP_V3_MAX_ENGINE_ID_LENGTH));
+    request->context_engine_id_len = (u8_t)u16_value;
+
+    /* contextName */
+    IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+    IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+    parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+    IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+    IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->context_name,
+        &u16_value, SNMP_V3_MAX_ENGINE_ID_LENGTH));
+    request->context_name_len = (u8_t)u16_value;
+  } else
+#endif
+  {
+  /* decode community */
+  IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+  IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+  parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+  IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+  err = snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->community, &request->community_strlen, SNMP_MAX_COMMUNITY_STR_LEN);
+  if (err == ERR_MEM) {
+    /* community string does not fit in our buffer -> its too long -> its invalid */
+    request->community_strlen = 0;
+    snmp_pbuf_stream_seek(&pbuf_stream, tlv.value_len);
+  } else {
+    IF_PARSE_ASSERT(err == ERR_OK);
+  }
+  /* add zero terminator */
+  request->community[request->community_strlen] = 0;
+  }
+
+  /* decode PDU type (next container level) */
+  IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+  IF_PARSE_ASSERT(tlv.value_len <= pbuf_stream.length);
+  request->inbound_padding_len = pbuf_stream.length - tlv.value_len;
+  parent_tlv_value_len = tlv.value_len;
+
+  /* validate PDU type */
+  switch(tlv.type) {
+    case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_REQ):
+      /* GetRequest PDU */
+      snmp_stats.ingetrequests++;
+      break;
+    case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_NEXT_REQ):
+      /* GetNextRequest PDU */
+      snmp_stats.ingetnexts++;
+      break;
+    case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ):
+      /* GetBulkRequest PDU */
+      if (request->version < SNMP_VERSION_2c) {
+        /* RFC2089: invalid, drop packet */
+        return ERR_ARG;
+      }
+      break;
+    case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_SET_REQ):
+      /* SetRequest PDU */
+      snmp_stats.insetrequests++;
+      break;
+    default:
+      /* unsupported input PDU for this agent (no parse error) */
+      LWIP_DEBUGF(SNMP_DEBUG, ("Unknown/Invalid SNMP PDU type received: %d", tlv.type)); \
+      return ERR_ARG;
+      break;
+  }
+  request->request_type = tlv.type & SNMP_ASN1_DATATYPE_MASK;
+
+  /* validate community (do this after decoding PDU type because we don't want to increase 'inbadcommunitynames' for wrong frame types */
+  if (request->community_strlen == 0) {
+    /* community string was too long or really empty*/
+    snmp_stats.inbadcommunitynames++;
+    snmp_authfail_trap();
+    return ERR_ARG;
+  } else if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+    if (snmp_community_write[0] == 0) {
+      /* our write community is empty, that means all our objects are readonly */
+      request->error_status = SNMP_ERR_NOTWRITABLE;
+      request->error_index  = 1;
+    } else if (strncmp(snmp_community_write, (const char*)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0) {
+      /* community name does not match */
+      snmp_stats.inbadcommunitynames++;
+      snmp_authfail_trap();
+      return ERR_ARG;
+    }
+  } else { 
+    if (strncmp(snmp_community, (const char*)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0) {
+      /* community name does not match */
+      snmp_stats.inbadcommunitynames++;
+      snmp_authfail_trap();
+      return ERR_ARG;
+    }
+  }
+  
+  /* decode request ID */
+  IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+  IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+  parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+  IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+  
+  IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->request_id));
+
+  /* decode error status / non-repeaters */
+  IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+  IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+  parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+  IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+  if (request->request_type == SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ) {
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->non_repeaters));
+    if (request->non_repeaters < 0) {
+      /* RFC 1905, 4.2.3 */
+      request->non_repeaters = 0;
+    }
+  } else {
+    /* only check valid value, don't touch 'request->error_status', maybe a response error status was already set to above; */
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+    IF_PARSE_ASSERT(s32_value == SNMP_ERR_NOERROR);
+  }
+
+  /* decode error index / max-repetitions */
+  IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+  IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+  parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+  IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+  if (request->request_type == SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ) {
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->max_repetitions));
+    if (request->max_repetitions < 0) {
+      /* RFC 1905, 4.2.3 */
+      request->max_repetitions = 0;
+    }
+  } else {
+    IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->error_index));
+    IF_PARSE_ASSERT(s32_value == 0);
+  }
+
+  /* decode varbind-list type (next container level) */
+  IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+  IF_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_SEQUENCE) && (tlv.value_len <= pbuf_stream.length));
+  
+  request->inbound_varbind_offset = pbuf_stream.offset;
+  request->inbound_varbind_len    = pbuf_stream.length - request->inbound_padding_len;
+  snmp_vb_enumerator_init(&(request->inbound_varbind_enumerator), request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len);
+
+  return ERR_OK;
+}
+
+#define OF_BUILD_EXEC(code) BUILD_EXEC(code, ERR_ARG)
+
+static err_t
+snmp_prepare_outbound_frame(struct snmp_request *request)
+{
+  struct snmp_asn1_tlv tlv;
+  struct snmp_pbuf_stream* pbuf_stream = &(request->outbound_pbuf_stream);
+
+  /* try allocating pbuf(s) for maximum response size */
+  request->outbound_pbuf = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);
+  if (request->outbound_pbuf == NULL) {
+    return ERR_MEM;
+  }
+
+  snmp_pbuf_stream_init(pbuf_stream, request->outbound_pbuf, 0, request->outbound_pbuf->tot_len);
+
+  /* 'Message' sequence */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+  /* version */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+  snmp_asn1_enc_s32t_cnt(request->version, &tlv.value_len);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+  OF_BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->version) );
+
+#if LWIP_SNMP_V3
+  if (request->version < SNMP_VERSION_3) {
+#endif
+  /* community */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->community_strlen);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+  OF_BUILD_EXEC( snmp_asn1_enc_raw(pbuf_stream, request->community, request->community_strlen) );
+#if LWIP_SNMP_V3
+  } else {
+    const char* id;
+
+    /* globalData */
+    request->outbound_msg_global_data_offset = pbuf_stream->offset;
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, 0);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+    /* msgID */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+    snmp_asn1_enc_s32t_cnt(request->msg_id, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_id));
+
+    /* msgMaxSize */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+    snmp_asn1_enc_s32t_cnt(request->msg_max_size, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_max_size));
+
+    /* msgFlags */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 1);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, &request->msg_flags, 1));
+
+    /* msgSecurityModel */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+    snmp_asn1_enc_s32t_cnt(request->msg_security_model, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_security_model));
+
+    /* end of msgGlobalData */
+    request->outbound_msg_global_data_end = pbuf_stream->offset;
+
+    /* msgSecurityParameters */
+    request->outbound_msg_security_parameters_str_offset = pbuf_stream->offset;
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, 0);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+    request->outbound_msg_security_parameters_seq_offset = pbuf_stream->offset;
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, 0);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+    /* msgAuthoritativeEngineID */
+    snmpv3_get_engine_id(&id, &request->msg_authoritative_engine_id_len);
+    MEMCPY(request->msg_authoritative_engine_id, id, request->msg_authoritative_engine_id_len);
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->msg_authoritative_engine_id_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_authoritative_engine_id, request->msg_authoritative_engine_id_len));
+
+    request->msg_authoritative_engine_time = snmpv3_get_engine_time();
+    request->msg_authoritative_engine_boots = snmpv3_get_engine_boots();
+
+    /* msgAuthoritativeEngineBoots */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+    snmp_asn1_enc_s32t_cnt(request->msg_authoritative_engine_boots, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_authoritative_engine_boots));
+
+    /* msgAuthoritativeEngineTime */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+    snmp_asn1_enc_s32t_cnt(request->msg_authoritative_engine_time, &tlv.value_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_authoritative_engine_time));
+
+    /* msgUserName */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->msg_user_name_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_user_name, request->msg_user_name_len));
+
+#if LWIP_SNMP_V3_CRYPTO
+    /* msgAuthenticationParameters */
+    if (request->msg_flags & SNMP_V3_AUTH_FLAG) {
+      memset(request->msg_authentication_parameters, 0, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+      request->outbound_msg_authentication_parameters_offset = pbuf_stream->offset;
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+      OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_authentication_parameters, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
+    } else
+#endif
+    {
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 0);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    }
+
+#if LWIP_SNMP_V3_CRYPTO
+    /* msgPrivacyParameters */
+    if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
+      snmpv3_build_priv_param(request->msg_privacy_parameters);
+
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, SNMP_V3_MAX_PRIV_PARAM_LENGTH);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+      OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_privacy_parameters, SNMP_V3_MAX_PRIV_PARAM_LENGTH));
+    } else
+#endif
+    {
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 0);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+    }
+
+    /* End of msgSecurityParameters, so we can calculate the length of this sequence later */
+    request->outbound_msg_security_parameters_end = pbuf_stream->offset;
+
+#if LWIP_SNMP_V3_CRYPTO
+    /* For encryption we have to encapsulate the payload in an octet string */
+    if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
+      request->outbound_scoped_pdu_string_offset = pbuf_stream->offset;
+      SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 3, 0);
+      OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    }
+#endif
+    /* Scoped PDU
+     * Encryption context
+     */
+    request->outbound_scoped_pdu_seq_offset = pbuf_stream->offset;
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+    /* contextEngineID */
+    snmpv3_get_engine_id(&id, &request->context_engine_id_len);
+    MEMCPY(request->context_engine_id, id, request->context_engine_id_len);
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->context_engine_id_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->context_engine_id, request->context_engine_id_len));
+
+    /* contextName */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->context_name_len);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->context_name, request->context_name_len));
+  }
+#endif
+
+  /* 'PDU' sequence */
+  request->outbound_pdu_offset = pbuf_stream->offset;
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_RESP), 3, 0);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+  /* request ID */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+  snmp_asn1_enc_s32t_cnt(request->request_id, &tlv.value_len);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+  OF_BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->request_id) );
+
+  /* error status */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+  request->outbound_error_status_offset = pbuf_stream->offset;
+  OF_BUILD_EXEC( snmp_pbuf_stream_write(pbuf_stream, 0) );
+
+  /* error index */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+  request->outbound_error_index_offset = pbuf_stream->offset;
+  OF_BUILD_EXEC( snmp_pbuf_stream_write(pbuf_stream, 0) );
+
+  /* 'VarBindList' sequence */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+  request->outbound_varbind_offset = pbuf_stream->offset;
+
+  return ERR_OK;
+}
+
+/** Calculate the length of a varbind list */
+err_t
+snmp_varbind_length(struct snmp_varbind *varbind, struct snmp_varbind_len *len)
+{
+  /* calculate required lengths */
+  snmp_asn1_enc_oid_cnt(varbind->oid.id, varbind->oid.len, &len->oid_value_len);
+  snmp_asn1_enc_length_cnt(len->oid_value_len, &len->oid_len_len);
+
+  if (varbind->value_len == 0) {
+    len->value_value_len = 0;
+  } else if (varbind->value_len & SNMP_GET_VALUE_RAW_DATA) {
+    len->value_value_len = varbind->value_len & (~SNMP_GET_VALUE_RAW_DATA);
+  } else {
+    switch (varbind->type) {
+      case SNMP_ASN1_TYPE_INTEGER:
+        if (varbind->value_len != sizeof (s32_t)) {
+          return ERR_VAL;
+        }
+        snmp_asn1_enc_s32t_cnt(*((s32_t*) varbind->value), &len->value_value_len);
+        break;
+      case SNMP_ASN1_TYPE_COUNTER:
+      case SNMP_ASN1_TYPE_GAUGE:
+      case SNMP_ASN1_TYPE_TIMETICKS:
+        if (varbind->value_len != sizeof (u32_t)) {
+          return ERR_VAL;
+        }
+        snmp_asn1_enc_u32t_cnt(*((u32_t*) varbind->value), &len->value_value_len);
+        break;
+      case SNMP_ASN1_TYPE_OCTET_STRING:
+      case SNMP_ASN1_TYPE_IPADDR:
+      case SNMP_ASN1_TYPE_OPAQUE:
+        len->value_value_len = varbind->value_len;
+        break;
+      case SNMP_ASN1_TYPE_NULL:
+        if (varbind->value_len != 0) {
+          return ERR_VAL;
+        }
+        len->value_value_len = 0;
+        break;
+      case SNMP_ASN1_TYPE_OBJECT_ID:
+        if ((varbind->value_len & 0x03) != 0) {
+          return ERR_VAL;
+        }
+        snmp_asn1_enc_oid_cnt((u32_t*) varbind->value, varbind->value_len >> 2, &len->value_value_len);
+        break;
+      case SNMP_ASN1_TYPE_COUNTER64:
+        if (varbind->value_len != (2 * sizeof (u32_t))) {
+          return ERR_VAL;
+        }
+        snmp_asn1_enc_u64t_cnt((u32_t*) varbind->value, &len->value_value_len);
+        break;
+      default:
+        /* unsupported type */
+        return ERR_VAL;
+    }
+  }
+  snmp_asn1_enc_length_cnt(len->value_value_len, &len->value_len_len);
+
+  len->vb_value_len = 1 + len->oid_len_len + len->oid_value_len + 1 + len->value_len_len + len->value_value_len;
+  snmp_asn1_enc_length_cnt(len->vb_value_len, &len->vb_len_len);
+
+  return ERR_OK;
+}
+
+#define OVB_BUILD_EXEC(code) BUILD_EXEC(code, ERR_ARG)
+
+err_t
+snmp_append_outbound_varbind(struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind* varbind)
+{
+  struct snmp_asn1_tlv tlv;
+  struct snmp_varbind_len len;
+  err_t err;
+
+  err = snmp_varbind_length(varbind, &len);
+
+  if (err != ERR_OK) {
+    return err;
+  }
+
+  /* check length already before adding first data because in case of GetBulk,
+   *  data added so far is returned and therefore no partial data shall be added
+   */
+  if ((1 + len.vb_len_len + len.vb_value_len) > pbuf_stream->length) {
+    return ERR_BUF;
+  }
+
+  /* 'VarBind' sequence */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, len.vb_len_len, len.vb_value_len);
+  OVB_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+  /* VarBind OID */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OBJECT_ID, len.oid_len_len, len.oid_value_len);
+  OVB_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+  OVB_BUILD_EXEC(snmp_asn1_enc_oid(pbuf_stream, varbind->oid.id, varbind->oid.len));
+
+  /* VarBind value */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, varbind->type, len.value_len_len, len.value_value_len);
+  OVB_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+  if (len.value_value_len > 0) {
+    if (varbind->value_len & SNMP_GET_VALUE_RAW_DATA) {
+      OVB_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, (u8_t*) varbind->value, len.value_value_len));
+    } else {
+      switch (varbind->type) {
+        case SNMP_ASN1_TYPE_INTEGER:
+          OVB_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, len.value_value_len, *((s32_t*) varbind->value)));
+          break;
+        case SNMP_ASN1_TYPE_COUNTER:
+        case SNMP_ASN1_TYPE_GAUGE:
+        case SNMP_ASN1_TYPE_TIMETICKS:
+          OVB_BUILD_EXEC(snmp_asn1_enc_u32t(pbuf_stream, len.value_value_len, *((u32_t*) varbind->value)));
+          break;
+        case SNMP_ASN1_TYPE_OCTET_STRING:
+        case SNMP_ASN1_TYPE_IPADDR:
+        case SNMP_ASN1_TYPE_OPAQUE:
+          OVB_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, (u8_t*) varbind->value, len.value_value_len));
+          len.value_value_len = varbind->value_len;
+          break;
+        case SNMP_ASN1_TYPE_OBJECT_ID:
+          OVB_BUILD_EXEC(snmp_asn1_enc_oid(pbuf_stream, (u32_t*) varbind->value, varbind->value_len / sizeof (u32_t)));
+          break;
+        case SNMP_ASN1_TYPE_COUNTER64:
+          OVB_BUILD_EXEC(snmp_asn1_enc_u64t(pbuf_stream, len.value_value_len, (u32_t*) varbind->value));
+          break;
+        default:
+          LWIP_ASSERT("Unknown variable type", 0);
+          break;
+      }
+    }
+  }
+
+  return ERR_OK;
+}
+
+static err_t
+snmp_complete_outbound_frame(struct snmp_request *request)
+{
+  struct snmp_asn1_tlv tlv;
+  u16_t frame_size;
+  u8_t outbound_padding = 0;
+
+  if (request->version == SNMP_VERSION_1) {
+    if (request->error_status != SNMP_ERR_NOERROR) {
+      /* map v2c error codes to v1 compliant error code (according to RFC 2089) */
+      switch (request->error_status) {
+        /* mapping of implementation specific "virtual" error codes 
+         * (during processing of frame we already stored them in error_status field, 
+         * so no need to check all varbinds here for those exceptions as suggested by RFC) */
+        case SNMP_ERR_NOSUCHINSTANCE:
+        case SNMP_ERR_NOSUCHOBJECT:
+        case SNMP_ERR_ENDOFMIBVIEW:
+          request->error_status = SNMP_ERR_NOSUCHNAME;
+          break;
+        /* mapping according to RFC */
+        case SNMP_ERR_WRONGVALUE:
+        case SNMP_ERR_WRONGENCODING:
+        case SNMP_ERR_WRONGTYPE:
+        case SNMP_ERR_WRONGLENGTH:
+        case SNMP_ERR_INCONSISTENTVALUE:
+          request->error_status = SNMP_ERR_BADVALUE;
+          break;
+        case SNMP_ERR_NOACCESS:
+        case SNMP_ERR_NOTWRITABLE:
+        case SNMP_ERR_NOCREATION:
+        case SNMP_ERR_INCONSISTENTNAME:
+        case SNMP_ERR_AUTHORIZATIONERROR:
+          request->error_status = SNMP_ERR_NOSUCHNAME;
+          break;
+        case SNMP_ERR_RESOURCEUNAVAILABLE:
+        case SNMP_ERR_COMMITFAILED:
+        case SNMP_ERR_UNDOFAILED:
+        default:
+          request->error_status = SNMP_ERR_GENERROR;
+          break;
+       }
+    }
+  } else {
+    if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+      /* map error codes to according to RFC 1905 (4.2.5.  The SetRequest-PDU) return 'NotWritable' for unknown OIDs) */
+      switch (request->error_status) {
+        case SNMP_ERR_NOSUCHINSTANCE:
+        case SNMP_ERR_NOSUCHOBJECT:
+        case SNMP_ERR_ENDOFMIBVIEW:
+          request->error_status = SNMP_ERR_NOTWRITABLE;
+          break;
+        default:
+          break;
+      }
+    }
+
+    if (request->error_status >= SNMP_VARBIND_EXCEPTION_OFFSET) {
+      /* should never occur because v2 frames store exceptions directly inside varbinds and not as frame error_status */
+      LWIP_DEBUGF(SNMP_DEBUG, ("snmp_complete_outbound_frame() > Found v2 request with varbind exception code stored as error status!\n"));
+      return ERR_ARG;
+    }
+  }
+
+  if ((request->error_status != SNMP_ERR_NOERROR) || (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ)) {
+    /* all inbound vars are returned in response without any modification for error responses and successful set requests*/
+    struct snmp_pbuf_stream inbound_stream;
+    OF_BUILD_EXEC( snmp_pbuf_stream_init(&inbound_stream, request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len) );
+    OF_BUILD_EXEC( snmp_pbuf_stream_init(&(request->outbound_pbuf_stream), request->outbound_pbuf, request->outbound_varbind_offset, request->outbound_pbuf->tot_len - request->outbound_varbind_offset) );
+    snmp_pbuf_stream_writeto(&inbound_stream, &(request->outbound_pbuf_stream), 0);
+  }
+
+  frame_size = request->outbound_pbuf_stream.offset;
+
+#if LWIP_SNMP_V3 && LWIP_SNMP_V3_CRYPTO
+  /* Calculate padding for encryption */
+  if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_PRIV_FLAG)) {
+    u8_t i;
+    outbound_padding = (8 - (u8_t)((frame_size - request->outbound_scoped_pdu_seq_offset) & 0x07)) & 0x07;
+    for (i = 0; i < outbound_padding; i++) {
+      snmp_pbuf_stream_write(&request->outbound_pbuf_stream, 0);
+    }
+  }
+#endif
+
+  /* complete missing length in 'Message' sequence ; 'Message' tlv is located at the beginning (offset 0) */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size + outbound_padding - 1 - 3); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+  OF_BUILD_EXEC( snmp_pbuf_stream_init(&(request->outbound_pbuf_stream), request->outbound_pbuf, 0, request->outbound_pbuf->tot_len) );
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv) );
+
+#if LWIP_SNMP_V3
+  if (request->version == SNMP_VERSION_3) {
+    /* complete missing length in 'globalData' sequence */
+    /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, request->outbound_msg_global_data_end
+        - request->outbound_msg_global_data_offset - 1 - 1);
+    OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_msg_global_data_offset));
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+
+    /* complete missing length in 'msgSecurityParameters' sequence */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, request->outbound_msg_security_parameters_end
+        - request->outbound_msg_security_parameters_str_offset - 1 - 1);
+    OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_msg_security_parameters_str_offset));
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, request->outbound_msg_security_parameters_end
+        - request->outbound_msg_security_parameters_seq_offset - 1 - 1);
+    OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_msg_security_parameters_seq_offset));
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+
+    /* complete missing length in scoped PDU sequence */
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size - request->outbound_scoped_pdu_seq_offset - 1 - 3);
+    OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_scoped_pdu_seq_offset));
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+  }
+#endif
+
+  /* complete missing length in 'PDU' sequence */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_RESP), 3,
+      frame_size - request->outbound_pdu_offset - 1 - 3); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+  OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_pdu_offset) );
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv) );
+
+  /* process and encode final error status */
+  if (request->error_status != 0) {
+    u16_t len;
+    snmp_asn1_enc_s32t_cnt(request->error_status, &len);
+    if (len != 1) {
+      /* error, we only reserved one byte for it */
+      return ERR_ARG;
+    }
+    OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_error_status_offset) );
+    OF_BUILD_EXEC( snmp_asn1_enc_s32t(&(request->outbound_pbuf_stream), len, request->error_status) );
+
+    /* for compatibility to v1, log statistics; in v2 (RFC 1907) these statistics are obsoleted */
+    switch (request->error_status) {
+      case SNMP_ERR_TOOBIG:
+        snmp_stats.outtoobigs++;
+        break;
+      case SNMP_ERR_NOSUCHNAME:
+        snmp_stats.outnosuchnames++;
+        break;
+      case SNMP_ERR_BADVALUE:
+        snmp_stats.outbadvalues++;
+        break;
+      case SNMP_ERR_GENERROR:
+      default:
+        snmp_stats.outgenerrs++;
+        break;
+    }
+
+    if (request->error_status == SNMP_ERR_TOOBIG) {
+      request->error_index = 0; /* defined by RFC 1157 */
+    } else if (request->error_index == 0) {
+      /* set index to varbind where error occured (if not already set before, e.g. during GetBulk processing) */
+      request->error_index = request->inbound_varbind_enumerator.varbind_count;
+    }
+  } else {
+    if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+      snmp_stats.intotalsetvars += request->inbound_varbind_enumerator.varbind_count;
+    } else {
+      snmp_stats.intotalreqvars += request->inbound_varbind_enumerator.varbind_count;
+    }
+  }
+
+  /* encode final error index*/
+  if (request->error_index != 0) {
+    u16_t len;
+    snmp_asn1_enc_s32t_cnt(request->error_index, &len);
+    if (len != 1) {
+      /* error, we only reserved one byte for it */
+      return ERR_VAL;
+    }
+    OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_error_index_offset) );
+    OF_BUILD_EXEC( snmp_asn1_enc_s32t(&(request->outbound_pbuf_stream), len, request->error_index) );
+  }
+
+  /* complete missing length in 'VarBindList' sequence ; 'VarBindList' tlv is located directly before varbind offset */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size - request->outbound_varbind_offset);
+  OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_varbind_offset - 1 - 3) ); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+  OF_BUILD_EXEC( snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv) );
+
+  /* Authenticate response */
+#if LWIP_SNMP_V3 && LWIP_SNMP_V3_CRYPTO
+  /* Encrypt response */
+  if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_PRIV_FLAG)) {
+    u8_t key[20];
+    u8_t algo;
+
+    /* complete missing length in PDU sequence */
+    OF_BUILD_EXEC(snmp_pbuf_stream_init(&request->outbound_pbuf_stream, request->outbound_pbuf, 0, request->outbound_pbuf->tot_len));
+    OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_scoped_pdu_string_offset));
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 3, frame_size + outbound_padding
+        - request->outbound_scoped_pdu_string_offset - 1 - 3);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+
+    OF_BUILD_EXEC(snmpv3_get_user((char*)request->msg_user_name, NULL, NULL, &algo, key));
+
+    OF_BUILD_EXEC(snmpv3_crypt(&request->outbound_pbuf_stream, tlv.value_len, key,
+        request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
+        request->msg_authoritative_engine_time, algo, SNMP_V3_PRIV_MODE_ENCRYPT));
+  }
+
+  if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_AUTH_FLAG)) {
+    u8_t key[20];
+    u8_t algo;
+    u8_t hmac[20];
+
+    OF_BUILD_EXEC(snmpv3_get_user((char*)request->msg_user_name, &algo, key, NULL, NULL));
+    OF_BUILD_EXEC(snmp_pbuf_stream_init(&(request->outbound_pbuf_stream),
+        request->outbound_pbuf, 0, request->outbound_pbuf->tot_len));
+    OF_BUILD_EXEC(snmpv3_auth(&request->outbound_pbuf_stream, frame_size + outbound_padding, key, algo, hmac));
+
+    MEMCPY(request->msg_authentication_parameters, hmac, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+    OF_BUILD_EXEC(snmp_pbuf_stream_init(&request->outbound_pbuf_stream,
+                  request->outbound_pbuf, 0, request->outbound_pbuf->tot_len));
+    OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&request->outbound_pbuf_stream,
+                  request->outbound_msg_authentication_parameters_offset));
+
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+    OF_BUILD_EXEC(snmp_ans1_enc_tlv(&request->outbound_pbuf_stream, &tlv));
+    OF_BUILD_EXEC(snmp_asn1_enc_raw(&request->outbound_pbuf_stream,
+                  request->msg_authentication_parameters, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
+  }
+#endif
+
+  pbuf_realloc(request->outbound_pbuf, frame_size + outbound_padding);
+
+  snmp_stats.outgetresponses++;
+  snmp_stats.outpkts++;
+
+  return ERR_OK;
+}
+
+static void 
+snmp_execute_write_callbacks(struct snmp_request *request)
+{
+  struct snmp_varbind_enumerator inbound_varbind_enumerator;
+  struct snmp_varbind vb;
+
+  snmp_vb_enumerator_init(&inbound_varbind_enumerator, request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len);
+  vb.value = NULL; /* do NOT decode value (we enumerate outbound buffer here, so all varbinds have values assigned, which we don't need here) */
+
+  while (snmp_vb_enumerator_get_next(&inbound_varbind_enumerator, &vb) == SNMP_VB_ENUMERATOR_ERR_OK) {
+    snmp_write_callback(vb.oid.id, vb.oid.len, snmp_write_callback_arg);
+  }
+}
+
+
+/* ----------------------------------------------------------------------- */
+/* VarBind enumerator methods */
+/* ----------------------------------------------------------------------- */
+
+void
+snmp_vb_enumerator_init(struct snmp_varbind_enumerator* enumerator, struct pbuf* p, u16_t offset, u16_t length)
+{
+  snmp_pbuf_stream_init(&(enumerator->pbuf_stream), p, offset, length);
+  enumerator->varbind_count = 0;
+}
+
+#define VB_PARSE_EXEC(code)   PARSE_EXEC(code, SNMP_VB_ENUMERATOR_ERR_ASN1ERROR)
+#define VB_PARSE_ASSERT(code) PARSE_ASSERT(code, SNMP_VB_ENUMERATOR_ERR_ASN1ERROR)
+
+snmp_vb_enumerator_err_t
+snmp_vb_enumerator_get_next(struct snmp_varbind_enumerator* enumerator, struct snmp_varbind* varbind)
+{
+  struct snmp_asn1_tlv tlv;
+  u16_t  varbind_len;
+  err_t  err;
+  
+  if (enumerator->pbuf_stream.length == 0)
+  {
+    return SNMP_VB_ENUMERATOR_ERR_EOVB;
+  }
+  enumerator->varbind_count++;
+
+  /* decode varbind itself (parent container of a varbind) */
+  VB_PARSE_EXEC(snmp_asn1_dec_tlv(&(enumerator->pbuf_stream), &tlv));
+  VB_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_SEQUENCE) && (tlv.value_len <= enumerator->pbuf_stream.length));
+  varbind_len = tlv.value_len;
+
+  /* decode varbind name (object id) */
+  VB_PARSE_EXEC(snmp_asn1_dec_tlv(&(enumerator->pbuf_stream), &tlv));
+  VB_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_OBJECT_ID) && (SNMP_ASN1_TLV_LENGTH(tlv) < varbind_len) && (tlv.value_len < enumerator->pbuf_stream.length));
+   
+  VB_PARSE_EXEC(snmp_asn1_dec_oid(&(enumerator->pbuf_stream), tlv.value_len, varbind->oid.id, &(varbind->oid.len), SNMP_MAX_OBJ_ID_LEN));
+  varbind_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+
+  /* decode varbind value (object id) */
+  VB_PARSE_EXEC(snmp_asn1_dec_tlv(&(enumerator->pbuf_stream), &tlv));
+  VB_PARSE_ASSERT((SNMP_ASN1_TLV_LENGTH(tlv) == varbind_len) && (tlv.value_len <= enumerator->pbuf_stream.length));
+  varbind->type = tlv.type;
+
+  /* shall the value be decoded ? */
+  if (varbind->value != NULL) {
+    switch (varbind->type) {
+      case SNMP_ASN1_TYPE_INTEGER:
+        VB_PARSE_EXEC(snmp_asn1_dec_s32t(&(enumerator->pbuf_stream), tlv.value_len, (s32_t*)varbind->value));
+        varbind->value_len = sizeof(s32_t*);
+        break;
+      case SNMP_ASN1_TYPE_COUNTER:
+      case SNMP_ASN1_TYPE_GAUGE:
+      case SNMP_ASN1_TYPE_TIMETICKS:
+        VB_PARSE_EXEC(snmp_asn1_dec_u32t(&(enumerator->pbuf_stream), tlv.value_len, (u32_t*)varbind->value));
+        varbind->value_len = sizeof(u32_t*);
+        break;
+      case SNMP_ASN1_TYPE_OCTET_STRING:
+      case SNMP_ASN1_TYPE_OPAQUE:
+        err = snmp_asn1_dec_raw(&(enumerator->pbuf_stream), tlv.value_len, (u8_t*)varbind->value, &varbind->value_len, SNMP_MAX_VALUE_SIZE);
+        if (err == ERR_MEM) {
+          return SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH;
+        }
+        VB_PARSE_ASSERT(err == ERR_OK);
+        break;
+      case SNMP_ASN1_TYPE_NULL:
+        varbind->value_len = 0;
+        break;
+      case SNMP_ASN1_TYPE_OBJECT_ID:
+        /* misuse tlv.length_len as OID_length transporter */
+        err = snmp_asn1_dec_oid(&(enumerator->pbuf_stream), tlv.value_len, (u32_t*)varbind->value, &tlv.length_len, SNMP_MAX_OBJ_ID_LEN);
+        if (err == ERR_MEM) {
+          return SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH;
+        }
+        VB_PARSE_ASSERT(err == ERR_OK);
+        varbind->value_len = tlv.length_len * sizeof(u32_t);
+        break;
+      case SNMP_ASN1_TYPE_IPADDR:
+        if (tlv.value_len == 4) {
+          /* must be exactly 4 octets! */
+          VB_PARSE_EXEC(snmp_asn1_dec_raw(&(enumerator->pbuf_stream), tlv.value_len, (u8_t*)varbind->value, &varbind->value_len, SNMP_MAX_VALUE_SIZE));
+        } else {
+          VB_PARSE_ASSERT(0);
+        }
+        break;
+      case SNMP_ASN1_TYPE_COUNTER64:
+        VB_PARSE_EXEC(snmp_asn1_dec_u64t(&(enumerator->pbuf_stream), tlv.value_len, (u32_t*)varbind->value));
+        varbind->value_len = 2 * sizeof(u32_t*);
+        break;
+      default:
+        VB_PARSE_ASSERT(0);
+        break;
+    }
+  } else {
+    snmp_pbuf_stream_seek(&(enumerator->pbuf_stream), tlv.value_len);
+    varbind->value_len = tlv.value_len;
+  }
+
+  return SNMP_VB_ENUMERATOR_ERR_OK;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_msg.h b/src/apps/snmp/snmp_msg.h
new file mode 100644
index 0000000..2d01ef3
--- /dev/null
+++ b/src/apps/snmp/snmp_msg.h
@@ -0,0 +1,194 @@
+/**
+ * @file
+ * SNMP Agent message handling structures (internal API, do not use in client code).
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * Copyright (c) 2016 Elias Oenal.
+ * 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@axon.tv>
+ *         Martin Hentschel <info@cl-soft.de>
+ *         Elias Oenal <lwip@eliasoenal.com>
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_MSG_H
+#define LWIP_HDR_APPS_SNMP_MSG_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP
+
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "snmp_pbuf_stream.h"
+#include "lwip/ip_addr.h"
+#include "lwip/err.h"
+
+#if LWIP_SNMP_V3
+#include "snmpv3_priv.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
+
+/* version defines used in PDU */
+#define SNMP_VERSION_1  0
+#define SNMP_VERSION_2c 1
+#define SNMP_VERSION_3  3
+
+struct snmp_varbind_enumerator
+{
+  struct snmp_pbuf_stream pbuf_stream;
+  u16_t varbind_count;
+};
+
+typedef enum {
+  SNMP_VB_ENUMERATOR_ERR_OK            = 0,
+  SNMP_VB_ENUMERATOR_ERR_EOVB          = 1,
+  SNMP_VB_ENUMERATOR_ERR_ASN1ERROR     = 2,
+  SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH = 3
+} snmp_vb_enumerator_err_t;
+
+void snmp_vb_enumerator_init(struct snmp_varbind_enumerator* enumerator, struct pbuf* p, u16_t offset, u16_t length);
+snmp_vb_enumerator_err_t snmp_vb_enumerator_get_next(struct snmp_varbind_enumerator* enumerator, struct snmp_varbind* varbind);
+
+struct snmp_request
+{
+  /* Communication handle */
+  void *handle;
+  /* source IP address */
+  const ip_addr_t *source_ip;
+  /* source UDP port */
+  u16_t source_port;
+  /* incoming snmp version */
+  u8_t version;
+  /* community name (zero terminated) */
+  u8_t community[SNMP_MAX_COMMUNITY_STR_LEN + 1];
+  /* community string length (exclusive zero term) */
+  u16_t community_strlen;
+  /* request type */
+  u8_t request_type;
+  /* request ID */
+  s32_t request_id;
+  /* error status */
+  s32_t error_status;
+  /* error index */
+  s32_t error_index;
+  /* non-repeaters (getBulkRequest (SNMPv2c)) */
+  s32_t non_repeaters;
+  /* max-repetitions (getBulkRequest (SNMPv2c)) */
+  s32_t max_repetitions;
+  
+#if LWIP_SNMP_V3
+  s32_t msg_id;
+  s32_t msg_max_size;
+  u8_t  msg_flags;
+  s32_t msg_security_model;
+  u8_t  msg_authoritative_engine_id[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+  u8_t  msg_authoritative_engine_id_len;
+  s32_t msg_authoritative_engine_boots;
+  s32_t msg_authoritative_engine_time;
+  u8_t  msg_user_name[SNMP_V3_MAX_USER_LENGTH];
+  u8_t  msg_user_name_len;
+  u8_t  msg_authentication_parameters[SNMP_V3_MAX_AUTH_PARAM_LENGTH];
+  u8_t  msg_privacy_parameters[SNMP_V3_MAX_PRIV_PARAM_LENGTH];
+  u8_t  context_engine_id[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+  u8_t  context_engine_id_len;
+  u8_t  context_name[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+  u8_t  context_name_len;
+#endif
+
+  struct pbuf *inbound_pbuf;
+  struct snmp_varbind_enumerator inbound_varbind_enumerator;
+  u16_t inbound_varbind_offset;
+  u16_t inbound_varbind_len;
+  u16_t inbound_padding_len;
+
+  struct pbuf *outbound_pbuf;
+  struct snmp_pbuf_stream outbound_pbuf_stream;
+  u16_t outbound_pdu_offset;
+  u16_t outbound_error_status_offset;
+  u16_t outbound_error_index_offset;
+  u16_t outbound_varbind_offset;
+#if LWIP_SNMP_V3
+  u16_t outbound_msg_global_data_offset;
+  u16_t outbound_msg_global_data_end;
+  u16_t outbound_msg_security_parameters_str_offset;
+  u16_t outbound_msg_security_parameters_seq_offset;
+  u16_t outbound_msg_security_parameters_end;
+  u16_t outbound_msg_authentication_parameters_offset;
+  u16_t outbound_scoped_pdu_seq_offset;
+  u16_t outbound_scoped_pdu_string_offset;
+#endif
+
+  u8_t value_buffer[SNMP_MAX_VALUE_SIZE];
+};
+
+/** A helper struct keeping length information about varbinds */
+struct snmp_varbind_len
+{
+  u8_t  vb_len_len;
+  u16_t vb_value_len;
+  u8_t  oid_len_len;
+  u16_t oid_value_len;
+  u8_t  value_len_len;
+  u16_t value_value_len;
+};
+
+/** Agent community string */
+extern const char *snmp_community;
+/** Agent community string for write access */
+extern const char *snmp_community_write;
+/** handle for sending traps */
+extern void* snmp_traps_handle;
+
+void snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t port);
+err_t snmp_sendto(void *handle, struct pbuf *p, const ip_addr_t *dst, u16_t port);
+u8_t snmp_get_local_ip_for_dst(void* handle, const ip_addr_t *dst, ip_addr_t *result);
+err_t snmp_varbind_length(struct snmp_varbind *varbind, struct snmp_varbind_len *len);
+err_t snmp_append_outbound_varbind(struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind* varbind);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* LWIP_HDR_APPS_SNMP_MSG_H */
diff --git a/src/apps/snmp/snmp_netconn.c b/src/apps/snmp/snmp_netconn.c
new file mode 100644
index 0000000..24c3e26
--- /dev/null
+++ b/src/apps/snmp/snmp_netconn.c
@@ -0,0 +1,121 @@
+/**
+ * @file
+ * SNMP netconn frontend.
+ */
+
+/*
+ * 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.
+ *
+ * Author: Dirk Ziegelmeier <dziegel@gmx.de>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && SNMP_USE_NETCONN
+
+#include <string.h>
+#include "lwip/api.h"
+#include "lwip/ip.h"
+#include "lwip/udp.h"
+#include "snmp_msg.h"
+#include "lwip/sys.h"
+
+/** SNMP netconn API worker thread */
+static void
+snmp_netconn_thread(void *arg)
+{
+  struct netconn *conn;
+  struct netbuf *buf;
+  err_t err;
+  LWIP_UNUSED_ARG(arg);
+  
+  /* Bind to SNMP port with default IP address */
+#if LWIP_IPV6
+  conn = netconn_new(NETCONN_UDP_IPV6);
+  netconn_bind(conn, IP6_ADDR_ANY, SNMP_IN_PORT);
+#else /* LWIP_IPV6 */
+  conn = netconn_new(NETCONN_UDP);
+  netconn_bind(conn, IP4_ADDR_ANY, SNMP_IN_PORT);
+#endif /* LWIP_IPV6 */
+  LWIP_ERROR("snmp_netconn: invalid conn", (conn != NULL), return;);
+  
+  snmp_traps_handle = conn;
+
+  do {
+    err = netconn_recv(conn, &buf);
+
+    if (err == ERR_OK) {
+      snmp_receive(conn, buf->p, &buf->addr, buf->port);
+    }
+
+    if (buf != NULL) {
+      netbuf_delete(buf);
+    }
+  } while(1);
+}
+
+err_t 
+snmp_sendto(void *handle, struct pbuf *p, const ip_addr_t *dst, u16_t port)
+{
+  err_t result;
+  struct netbuf buf;
+  
+  memset(&buf, 0, sizeof(buf));
+  buf.p = p;
+  result = netconn_sendto((struct netconn*)handle, &buf, dst, port);
+  
+  return result;
+}
+
+u8_t
+snmp_get_local_ip_for_dst(void* handle, const ip_addr_t *dst, ip_addr_t *result)
+{
+  struct netconn* conn = (struct netconn*)handle;
+  struct netif *dst_if;
+  const ip_addr_t* dst_ip;
+
+  LWIP_UNUSED_ARG(conn); /* unused in case of IPV4 only configuration */
+
+  ip_route_get_local_ip(&conn->pcb.udp->local_ip, dst, dst_if, dst_ip);
+
+  if ((dst_if != NULL) && (dst_ip != NULL)) {
+    ip_addr_copy(*result, *dst_ip);
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/**
+ * Starts SNMP Agent.
+ */
+void
+snmp_init(void)
+{
+  sys_thread_new("snmp_netconn", snmp_netconn_thread, NULL, SNMP_STACK_SIZE, SNMP_THREAD_PRIO);
+}
+
+#endif /* LWIP_SNMP && SNMP_USE_NETCONN */
diff --git a/src/apps/snmp/snmp_pbuf_stream.c b/src/apps/snmp/snmp_pbuf_stream.c
new file mode 100644
index 0000000..3c1217d
--- /dev/null
+++ b/src/apps/snmp/snmp_pbuf_stream.c
@@ -0,0 +1,156 @@
+/**
+ * @file
+ * SNMP pbuf stream wrapper implementation (internal API, do not use in client code).
+ */
+
+/*
+ * 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: Martin Hentschel <info@cl-soft.de>
+ *
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "snmp_pbuf_stream.h"
+#include "lwip/def.h"
+#include <string.h>
+
+err_t
+snmp_pbuf_stream_init(struct snmp_pbuf_stream* pbuf_stream, struct pbuf* p, u16_t offset, u16_t length)
+{
+  pbuf_stream->offset = offset;
+  pbuf_stream->length = length;
+  pbuf_stream->pbuf   = p;
+
+  return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_read(struct snmp_pbuf_stream* pbuf_stream, u8_t* data)
+{
+  if (pbuf_stream->length == 0) {
+    return ERR_BUF;
+  }
+
+  if (pbuf_copy_partial(pbuf_stream->pbuf, data, 1, pbuf_stream->offset) == 0) {
+    return ERR_BUF;
+  }
+
+  pbuf_stream->offset++;
+  pbuf_stream->length--;
+
+  return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_write(struct snmp_pbuf_stream* pbuf_stream, u8_t data)
+{
+  return snmp_pbuf_stream_writebuf(pbuf_stream, &data, 1);
+}
+
+err_t
+snmp_pbuf_stream_writebuf(struct snmp_pbuf_stream* pbuf_stream, const void* buf, u16_t buf_len)
+{
+  if (pbuf_stream->length < buf_len) {
+    return ERR_BUF;
+  }
+
+  if (pbuf_take_at(pbuf_stream->pbuf, buf, buf_len, pbuf_stream->offset) != ERR_OK) {
+    return ERR_BUF;
+  }
+
+  pbuf_stream->offset += buf_len;
+  pbuf_stream->length -= buf_len;
+
+  return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_writeto(struct snmp_pbuf_stream* pbuf_stream, struct snmp_pbuf_stream* target_pbuf_stream, u16_t len)
+{
+
+  if ((pbuf_stream == NULL) || (target_pbuf_stream == NULL)) {
+    return ERR_ARG;
+  }
+  if ((len > pbuf_stream->length) || (len > target_pbuf_stream->length)) {
+    return ERR_ARG;
+  }
+
+  if (len == 0) {
+    len = LWIP_MIN(pbuf_stream->length, target_pbuf_stream->length);
+  }
+
+  while (len > 0) {
+    u16_t chunk_len;
+    err_t err;
+    u16_t target_offset;
+    struct pbuf* pbuf = pbuf_skip(pbuf_stream->pbuf, pbuf_stream->offset, &target_offset);
+
+    if ((pbuf == NULL) || (pbuf->len == 0)) {
+      return ERR_BUF;
+    }
+
+    chunk_len = LWIP_MIN(len, pbuf->len);
+    err = snmp_pbuf_stream_writebuf(target_pbuf_stream, &((u8_t*)pbuf->payload)[target_offset], chunk_len);
+    if (err != ERR_OK) {
+      return err;
+    }
+
+    pbuf_stream->offset   += chunk_len;
+    pbuf_stream->length   -= chunk_len;
+    len -= chunk_len;
+  }
+
+  return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_seek(struct snmp_pbuf_stream* pbuf_stream, s32_t offset)
+{
+  if ((offset < 0) || (offset > pbuf_stream->length)) {
+    /* we cannot seek backwards or forward behind stream end */
+    return ERR_ARG;
+  }
+
+  pbuf_stream->offset += (u16_t)offset;
+  pbuf_stream->length -= (u16_t)offset;
+
+  return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_seek_abs(struct snmp_pbuf_stream* pbuf_stream, u32_t offset)
+{
+  s32_t rel_offset = offset - pbuf_stream->offset;
+  return snmp_pbuf_stream_seek(pbuf_stream, rel_offset);
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_pbuf_stream.h b/src/apps/snmp/snmp_pbuf_stream.h
new file mode 100644
index 0000000..9778de7
--- /dev/null
+++ b/src/apps/snmp/snmp_pbuf_stream.h
@@ -0,0 +1,73 @@
+/**
+ * @file
+ * SNMP pbuf stream wrapper (internal API, do not use in client code).
+ */
+
+/*
+ * 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: Martin Hentschel <info@cl-soft.de>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_PBUF_STREAM_H
+#define LWIP_HDR_APPS_SNMP_PBUF_STREAM_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP
+
+#include "lwip/err.h"
+#include "lwip/pbuf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct snmp_pbuf_stream
+{
+  struct pbuf* pbuf;
+  u16_t offset;
+  u16_t length;
+};
+
+err_t snmp_pbuf_stream_init(struct snmp_pbuf_stream* pbuf_stream, struct pbuf* p, u16_t offset, u16_t length);
+err_t snmp_pbuf_stream_read(struct snmp_pbuf_stream* pbuf_stream, u8_t* data);
+err_t snmp_pbuf_stream_write(struct snmp_pbuf_stream* pbuf_stream, u8_t data);
+err_t snmp_pbuf_stream_writebuf(struct snmp_pbuf_stream* pbuf_stream, const void* buf, u16_t buf_len);
+err_t snmp_pbuf_stream_writeto(struct snmp_pbuf_stream* pbuf_stream, struct snmp_pbuf_stream* target_pbuf_stream, u16_t len);
+err_t snmp_pbuf_stream_seek(struct snmp_pbuf_stream* pbuf_stream, s32_t offset);
+err_t snmp_pbuf_stream_seek_abs(struct snmp_pbuf_stream* pbuf_stream, u32_t offset);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* LWIP_HDR_APPS_SNMP_PBUF_STREAM_H */
diff --git a/src/apps/snmp/snmp_raw.c b/src/apps/snmp/snmp_raw.c
new file mode 100644
index 0000000..4a40864
--- /dev/null
+++ b/src/apps/snmp/snmp_raw.c
@@ -0,0 +1,100 @@
+/**
+ * @file
+ * SNMP RAW API frontend.
+ */
+
+/*
+ * 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.
+ *
+ * Author: Dirk Ziegelmeier <dziegel@gmx.de>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+#include "lwip/ip_addr.h"
+
+#if LWIP_SNMP && SNMP_USE_RAW
+
+#include "lwip/udp.h"
+#include "lwip/ip.h"
+#include "snmp_msg.h"
+
+/* lwIP UDP receive callback function */
+static void
+snmp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+  LWIP_UNUSED_ARG(arg);
+
+  snmp_receive(pcb, p, addr, port);
+
+  pbuf_free(p);
+}
+
+err_t 
+snmp_sendto(void *handle, struct pbuf *p, const ip_addr_t *dst, u16_t port)
+{
+  return udp_sendto((struct udp_pcb*)handle, p, dst, port);
+}
+
+u8_t
+snmp_get_local_ip_for_dst(void* handle, const ip_addr_t *dst, ip_addr_t *result)
+{
+  struct udp_pcb* udp_pcb = (struct udp_pcb*)handle;
+  struct netif *dst_if;
+  const ip_addr_t* dst_ip;
+
+  LWIP_UNUSED_ARG(udp_pcb); /* unused in case of IPV4 only configuration */
+
+  ip_route_get_local_ip(&udp_pcb->local_ip, dst, dst_if, dst_ip);
+
+  if ((dst_if != NULL) && (dst_ip != NULL)) {
+    ip_addr_copy(*result, *dst_ip);
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/**
+ * @ingroup snmp_core
+ * Starts SNMP Agent.
+ * Allocates UDP pcb and binds it to IP_ANY_TYPE port 161.
+ */
+void
+snmp_init(void)
+{
+  err_t err;
+  
+  struct udp_pcb *snmp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+  LWIP_ERROR("snmp_raw: no PCB", (snmp_pcb != NULL), return;);
+
+  snmp_traps_handle = snmp_pcb;
+
+  udp_recv(snmp_pcb, snmp_recv, (void *)SNMP_IN_PORT);
+  err = udp_bind(snmp_pcb, IP_ANY_TYPE, SNMP_IN_PORT);
+  LWIP_ERROR("snmp_raw: Unable to bind PCB", (err == ERR_OK), return;);
+}
+
+#endif /* LWIP_SNMP && SNMP_USE_RAW */
diff --git a/src/apps/snmp/snmp_scalar.c b/src/apps/snmp/snmp_scalar.c
new file mode 100644
index 0000000..136c9ec
--- /dev/null
+++ b/src/apps/snmp/snmp_scalar.c
@@ -0,0 +1,220 @@
+/**
+ * @file
+ * SNMP scalar node support 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: Martin Hentschel <info@cl-soft.de>
+ *
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/apps/snmp_core.h"
+
+static s16_t snmp_scalar_array_get_value(struct snmp_node_instance* instance, void* value);
+static snmp_err_t  snmp_scalar_array_set_test(struct snmp_node_instance* instance, u16_t value_len, void* value);
+static snmp_err_t  snmp_scalar_array_set_value(struct snmp_node_instance* instance, u16_t value_len, void* value);
+
+snmp_err_t 
+snmp_scalar_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  const struct snmp_scalar_node* scalar_node = (const struct snmp_scalar_node*)(const void*)instance->node;
+
+  LWIP_UNUSED_ARG(root_oid);
+  LWIP_UNUSED_ARG(root_oid_len);
+
+  /* scalar only has one dedicated instance: .0 */
+  if ((instance->instance_oid.len != 1) || (instance->instance_oid.id[0] != 0)) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  instance->access    = scalar_node->access;
+  instance->asn1_type = scalar_node->asn1_type;
+  instance->get_value = scalar_node->get_value;
+  instance->set_test  = scalar_node->set_test;
+  instance->set_value = scalar_node->set_value;
+  return SNMP_ERR_NOERROR;
+}
+
+snmp_err_t 
+snmp_scalar_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  /* because our only instance is .0 we can only return a next instance if no instance oid is passed */
+  if (instance->instance_oid.len == 0) {
+    instance->instance_oid.len   = 1;
+    instance->instance_oid.id[0] = 0;
+
+    return snmp_scalar_get_instance(root_oid, root_oid_len, instance);
+  }
+
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+
+snmp_err_t
+snmp_scalar_array_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  LWIP_UNUSED_ARG(root_oid);
+  LWIP_UNUSED_ARG(root_oid_len);
+
+  if ((instance->instance_oid.len == 2) && (instance->instance_oid.id[1] == 0)) {
+    const struct snmp_scalar_array_node* array_node = (const struct snmp_scalar_array_node*)(const void*)instance->node;
+    const struct snmp_scalar_array_node_def* array_node_def = array_node->array_nodes;
+    u32_t i = 0;
+
+    while (i < array_node->array_node_count) {
+      if (array_node_def->oid == instance->instance_oid.id[0]) {
+        break;
+      }
+
+      array_node_def++;
+      i++;
+    }
+
+    if (i < array_node->array_node_count) {
+      instance->access              = array_node_def->access;
+      instance->asn1_type           = array_node_def->asn1_type;
+      instance->get_value           = snmp_scalar_array_get_value;
+      instance->set_test            = snmp_scalar_array_set_test;
+      instance->set_value           = snmp_scalar_array_set_value;
+      instance->reference.const_ptr = array_node_def;
+
+      return SNMP_ERR_NOERROR;
+    }
+  }
+
+  return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+snmp_err_t
+snmp_scalar_array_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  const struct snmp_scalar_array_node* array_node = (const struct snmp_scalar_array_node*)(const void*)instance->node;
+  const struct snmp_scalar_array_node_def* array_node_def = array_node->array_nodes;
+  const struct snmp_scalar_array_node_def* result = NULL;
+
+  LWIP_UNUSED_ARG(root_oid);
+  LWIP_UNUSED_ARG(root_oid_len);
+
+  if ((instance->instance_oid.len == 0) && (array_node->array_node_count > 0)) {
+    /* return node with lowest OID */
+    u16_t i = 0;
+    
+    result = array_node_def;
+    array_node_def++;
+
+    for (i = 1; i < array_node->array_node_count; i++) {
+      if (array_node_def->oid < result->oid) {
+        result = array_node_def;
+      }
+      array_node_def++;
+    }
+  } else if (instance->instance_oid.len >= 1) {
+    if (instance->instance_oid.len == 1) {
+      /* if we have the requested OID we return its instance, otherwise we search for the next available */    
+      u16_t i = 0;
+      while (i < array_node->array_node_count) {
+        if (array_node_def->oid == instance->instance_oid.id[0]) {
+          result = array_node_def;
+          break;
+        }
+
+        array_node_def++;
+        i++;
+      }
+    }
+    if (result == NULL) {
+      u32_t oid_dist = 0xFFFFFFFFUL;
+      u16_t i        = 0;
+      array_node_def = array_node->array_nodes; /* may be already at the end when if case before was executed without result -> reinitialize to start */
+      while (i < array_node->array_node_count) {
+        if ((array_node_def->oid > instance->instance_oid.id[0]) &&
+            ((u32_t)(array_node_def->oid - instance->instance_oid.id[0]) < oid_dist)) {
+          result   = array_node_def;
+          oid_dist = array_node_def->oid - instance->instance_oid.id[0];
+        }
+
+        array_node_def++;
+        i++;
+      }
+    }
+  }
+
+  if (result == NULL) {
+    /* nothing to return */
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  instance->instance_oid.len   = 2;
+  instance->instance_oid.id[0] = result->oid;
+  instance->instance_oid.id[1] = 0;
+  
+  instance->access              = result->access;
+  instance->asn1_type           = result->asn1_type;
+  instance->get_value           = snmp_scalar_array_get_value;
+  instance->set_test            = snmp_scalar_array_set_test;
+  instance->set_value           = snmp_scalar_array_set_value;
+  instance->reference.const_ptr = result;
+
+  return SNMP_ERR_NOERROR;
+}
+
+static s16_t
+snmp_scalar_array_get_value(struct snmp_node_instance* instance, void* value)
+{
+  const struct snmp_scalar_array_node* array_node = (const struct snmp_scalar_array_node*)(const void*)instance->node;
+  const struct snmp_scalar_array_node_def* array_node_def = (const struct snmp_scalar_array_node_def*)instance->reference.const_ptr;
+
+  return array_node->get_value(array_node_def, value);
+}
+
+static snmp_err_t
+snmp_scalar_array_set_test(struct snmp_node_instance* instance, u16_t value_len, void* value)
+{
+  const struct snmp_scalar_array_node* array_node = (const struct snmp_scalar_array_node*)(const void*)instance->node;
+  const struct snmp_scalar_array_node_def* array_node_def = (const struct snmp_scalar_array_node_def*)instance->reference.const_ptr;
+
+  return array_node->set_test(array_node_def, value_len, value);
+}
+
+static snmp_err_t
+snmp_scalar_array_set_value(struct snmp_node_instance* instance, u16_t value_len, void* value)
+{
+  const struct snmp_scalar_array_node* array_node = (const struct snmp_scalar_array_node*)(const void*)instance->node;
+  const struct snmp_scalar_array_node_def* array_node_def = (const struct snmp_scalar_array_node_def*)instance->reference.const_ptr;
+
+  return array_node->set_value(array_node_def, value_len, value);
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_table.c b/src/apps/snmp/snmp_table.c
new file mode 100644
index 0000000..63ca595
--- /dev/null
+++ b/src/apps/snmp/snmp_table.c
@@ -0,0 +1,343 @@
+/**
+ * @file
+ * SNMP table support 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: Martin Hentschel <info@cl-soft.de>
+ *
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_table.h"
+#include <string.h>
+
+snmp_err_t snmp_table_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  snmp_err_t ret = SNMP_ERR_NOSUCHINSTANCE;
+  const struct snmp_table_node* table_node = (const struct snmp_table_node*)(const void*)instance->node;
+
+  LWIP_UNUSED_ARG(root_oid);
+  LWIP_UNUSED_ARG(root_oid_len);
+
+  /* check min. length (fixed row entry definition, column, row instance oid with at least one entry */
+  /* fixed row entry always has oid 1 */
+  if ((instance->instance_oid.len >= 3) && (instance->instance_oid.id[0] == 1)) {
+    /* search column */
+    const struct snmp_table_col_def* col_def = table_node->columns;
+    u16_t i = table_node->column_count;
+    while (i > 0) {
+      if (col_def->index == instance->instance_oid.id[1]) {
+        break;
+      }
+      
+      col_def++;
+      i--;
+    }
+
+    if (i > 0) {
+      /* everything may be overwritten by get_cell_instance_method() in order to implement special handling for single columns/cells */
+      instance->asn1_type = col_def->asn1_type;
+      instance->access    = col_def->access;
+      instance->get_value = table_node->get_value;
+      instance->set_test  = table_node->set_test;
+      instance->set_value = table_node->set_value;
+
+      ret = table_node->get_cell_instance(
+        &(instance->instance_oid.id[1]),
+        &(instance->instance_oid.id[2]),
+        instance->instance_oid.len-2,
+        instance);
+    }
+  }
+
+  return ret;
+}
+
+snmp_err_t snmp_table_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  const struct snmp_table_node* table_node = (const struct snmp_table_node*)(const void*)instance->node;
+  const struct snmp_table_col_def* col_def;
+  struct snmp_obj_id row_oid;
+  u32_t column = 0;
+  snmp_err_t result;
+
+  LWIP_UNUSED_ARG(root_oid);
+  LWIP_UNUSED_ARG(root_oid_len);
+
+  /* check that first part of id is 0 or 1, referencing fixed row entry */
+  if ((instance->instance_oid.len > 0) && (instance->instance_oid.id[0] > 1)) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+  if (instance->instance_oid.len > 1) {
+    column = instance->instance_oid.id[1];
+  }
+  if (instance->instance_oid.len > 2) {
+    snmp_oid_assign(&row_oid, &(instance->instance_oid.id[2]), instance->instance_oid.len - 2);
+  } else {
+    row_oid.len = 0;
+  }
+
+  instance->get_value    = table_node->get_value;
+  instance->set_test     = table_node->set_test;
+  instance->set_value    = table_node->set_value;
+
+  /* resolve column and value */
+  do {
+    u16_t i;
+    const struct snmp_table_col_def* next_col_def = NULL;
+    col_def = table_node->columns;
+
+    for (i = 0; i < table_node->column_count; i++) {
+      if (col_def->index == column) {
+        next_col_def = col_def;
+        break;
+      } else if ((col_def->index > column) && ((next_col_def == NULL) || (col_def->index < next_col_def->index))) {
+        next_col_def = col_def;
+      }
+      col_def++;
+    }
+
+    if (next_col_def == NULL) {
+      /* no further column found */
+      return SNMP_ERR_NOSUCHINSTANCE;
+    }
+
+    instance->asn1_type          = next_col_def->asn1_type;
+    instance->access             = next_col_def->access;
+
+    result = table_node->get_next_cell_instance(
+      &next_col_def->index,
+      &row_oid,
+      instance);
+
+    if (result == SNMP_ERR_NOERROR) {
+      col_def = next_col_def;
+      break;
+    }
+
+    row_oid.len = 0; /* reset row_oid because we switch to next column and start with the first entry there */
+    column = next_col_def->index + 1;
+  } while (1);
+
+  /* build resulting oid */
+  instance->instance_oid.len   = 2;
+  instance->instance_oid.id[0] = 1;
+  instance->instance_oid.id[1] = col_def->index;
+  snmp_oid_append(&instance->instance_oid, row_oid.id, row_oid.len);
+
+  return SNMP_ERR_NOERROR;
+}
+
+
+snmp_err_t snmp_table_simple_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  snmp_err_t ret = SNMP_ERR_NOSUCHINSTANCE;
+  const struct snmp_table_simple_node* table_node = (const struct snmp_table_simple_node*)(const void*)instance->node;
+
+  LWIP_UNUSED_ARG(root_oid);
+  LWIP_UNUSED_ARG(root_oid_len);
+
+  /* check min. length (fixed row entry definition, column, row instance oid with at least one entry */
+  /* fixed row entry always has oid 1 */
+  if ((instance->instance_oid.len >= 3) && (instance->instance_oid.id[0] == 1)) {
+    ret = table_node->get_cell_value(
+      &(instance->instance_oid.id[1]),
+      &(instance->instance_oid.id[2]),
+      instance->instance_oid.len-2,
+      &instance->reference,
+      &instance->reference_len);
+
+    if (ret == SNMP_ERR_NOERROR) {
+      /* search column */
+      const struct snmp_table_simple_col_def* col_def = table_node->columns;
+      u32_t i = table_node->column_count;
+      while (i > 0) {
+        if (col_def->index == instance->instance_oid.id[1]) {
+          break;
+        }
+
+        col_def++;
+        i--;
+      }
+
+      if (i > 0) {
+        instance->asn1_type = col_def->asn1_type;
+        instance->access    = SNMP_NODE_INSTANCE_READ_ONLY;
+        instance->set_test  = NULL;
+        instance->set_value = NULL;
+
+        switch (col_def->data_type) {
+          case SNMP_VARIANT_VALUE_TYPE_U32:
+            instance->get_value = snmp_table_extract_value_from_u32ref;
+            break;
+          case SNMP_VARIANT_VALUE_TYPE_S32:
+            instance->get_value = snmp_table_extract_value_from_s32ref;
+            break;
+          case SNMP_VARIANT_VALUE_TYPE_PTR: /* fall through */
+          case SNMP_VARIANT_VALUE_TYPE_CONST_PTR:
+            instance->get_value = snmp_table_extract_value_from_refconstptr;
+            break;
+          default:
+            LWIP_DEBUGF(SNMP_DEBUG, ("snmp_table_simple_get_instance(): unknown column data_type: %d\n", col_def->data_type));
+            return SNMP_ERR_GENERROR;
+        }        
+
+        ret = SNMP_ERR_NOERROR;
+      } else {
+        ret = SNMP_ERR_NOSUCHINSTANCE;
+      }
+    } 
+  }
+
+  return ret;
+}
+
+snmp_err_t snmp_table_simple_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  const struct snmp_table_simple_node* table_node = (const struct snmp_table_simple_node*)(const void*)instance->node;
+  const struct snmp_table_simple_col_def* col_def;
+  struct snmp_obj_id row_oid;
+  u32_t column = 0;
+  snmp_err_t result;
+
+  LWIP_UNUSED_ARG(root_oid);
+  LWIP_UNUSED_ARG(root_oid_len);
+
+  /* check that first part of id is 0 or 1, referencing fixed row entry */
+  if ((instance->instance_oid.len > 0) && (instance->instance_oid.id[0] > 1)) {
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+  if (instance->instance_oid.len > 1) {
+    column = instance->instance_oid.id[1];
+  }
+  if (instance->instance_oid.len > 2) {
+    snmp_oid_assign(&row_oid, &(instance->instance_oid.id[2]), instance->instance_oid.len - 2);
+  } else {
+    row_oid.len = 0;
+  }
+
+  /* resolve column and value */
+  do {
+    u32_t i;
+    const struct snmp_table_simple_col_def* next_col_def = NULL;
+    col_def = table_node->columns;
+
+    for (i = 0; i < table_node->column_count; i++) {
+      if (col_def->index == column) {
+        next_col_def = col_def;
+        break;
+      } else if ((col_def->index > column) && ((next_col_def == NULL) ||
+                 (col_def->index < next_col_def->index))) {
+        next_col_def = col_def;
+      }
+      col_def++;
+    }
+
+    if (next_col_def == NULL) {
+      /* no further column found */
+      return SNMP_ERR_NOSUCHINSTANCE;
+    }
+
+    result = table_node->get_next_cell_instance_and_value(
+      &next_col_def->index,
+      &row_oid,
+      &instance->reference,
+      &instance->reference_len);
+
+    if (result == SNMP_ERR_NOERROR) {
+      col_def = next_col_def;
+      break;
+    }
+
+    row_oid.len = 0; /* reset row_oid because we switch to next column and start with the first entry there */
+    column = next_col_def->index + 1;
+  }
+  while (1);
+
+  instance->asn1_type = col_def->asn1_type;
+  instance->access    = SNMP_NODE_INSTANCE_READ_ONLY;
+  instance->set_test  = NULL;
+  instance->set_value = NULL;
+
+  switch (col_def->data_type) {
+    case SNMP_VARIANT_VALUE_TYPE_U32:
+      instance->get_value = snmp_table_extract_value_from_u32ref;
+      break;
+    case SNMP_VARIANT_VALUE_TYPE_S32:
+      instance->get_value = snmp_table_extract_value_from_s32ref;
+      break;
+    case SNMP_VARIANT_VALUE_TYPE_PTR: /* fall through */
+    case SNMP_VARIANT_VALUE_TYPE_CONST_PTR:
+      instance->get_value = snmp_table_extract_value_from_refconstptr;
+      break;
+    default:
+      LWIP_DEBUGF(SNMP_DEBUG, ("snmp_table_simple_get_instance(): unknown column data_type: %d\n", col_def->data_type));
+      return SNMP_ERR_GENERROR;
+  }
+
+  /* build resulting oid */
+  instance->instance_oid.len   = 2;
+  instance->instance_oid.id[0] = 1;
+  instance->instance_oid.id[1] = col_def->index;
+  snmp_oid_append(&instance->instance_oid, row_oid.id, row_oid.len);
+
+  return SNMP_ERR_NOERROR;
+}
+
+
+s16_t
+snmp_table_extract_value_from_s32ref(struct snmp_node_instance* instance, void* value)
+{
+  s32_t *dst = (s32_t*)value;
+  *dst = instance->reference.s32;
+  return sizeof(*dst);
+}
+
+s16_t
+snmp_table_extract_value_from_u32ref(struct snmp_node_instance* instance, void* value)
+{
+  u32_t *dst = (u32_t*)value;
+  *dst = instance->reference.u32;
+  return sizeof(*dst);
+}
+
+s16_t
+snmp_table_extract_value_from_refconstptr(struct snmp_node_instance* instance, void* value)
+{
+  MEMCPY(value, instance->reference.const_ptr, instance->reference_len);
+  return (u16_t)instance->reference_len;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_threadsync.c b/src/apps/snmp/snmp_threadsync.c
new file mode 100644
index 0000000..204f265
--- /dev/null
+++ b/src/apps/snmp/snmp_threadsync.c
@@ -0,0 +1,219 @@
+/**
+ * @file
+ * SNMP thread synchronization 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.
+ *
+ * Author: Dirk Ziegelmeier <dziegel@gmx.de>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && (NO_SYS == 0) /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_threadsync.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/sys.h"
+#include <string.h>
+        
+static void
+call_synced_function(struct threadsync_data *call_data, snmp_threadsync_called_fn fn)
+{
+  sys_mutex_lock(&call_data->threadsync_node->instance->sem_usage_mutex);
+  call_data->threadsync_node->instance->sync_fn(fn, call_data);
+  sys_sem_wait(&call_data->threadsync_node->instance->sem);
+  sys_mutex_unlock(&call_data->threadsync_node->instance->sem_usage_mutex);
+}
+
+static void
+threadsync_get_value_synced(void *ctx)
+{
+  struct threadsync_data *call_data = (struct threadsync_data*)ctx;
+
+  call_data->retval.s16 = call_data->proxy_instance.get_value(&call_data->proxy_instance, call_data->arg1.value);
+
+  sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static s16_t
+threadsync_get_value(struct snmp_node_instance* instance, void* value)
+{
+  struct threadsync_data *call_data = (struct threadsync_data*)instance->reference.ptr;
+
+  call_data->arg1.value = value;
+  call_synced_function(call_data, threadsync_get_value_synced);
+
+  return call_data->retval.s16;
+}
+
+static void
+threadsync_set_test_synced(void *ctx)
+{
+  struct threadsync_data *call_data = (struct threadsync_data*)ctx;
+
+  call_data->retval.err = call_data->proxy_instance.set_test(&call_data->proxy_instance, call_data->arg2.len, call_data->arg1.value);
+
+  sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static snmp_err_t
+threadsync_set_test(struct snmp_node_instance* instance, u16_t len, void *value)
+{
+  struct threadsync_data *call_data = (struct threadsync_data*)instance->reference.ptr;
+
+  call_data->arg1.value = value;
+  call_data->arg2.len = len;
+  call_synced_function(call_data, threadsync_set_test_synced);
+
+  return call_data->retval.err;
+}
+
+static void
+threadsync_set_value_synced(void *ctx)
+{
+  struct threadsync_data *call_data = (struct threadsync_data*)ctx;
+
+  call_data->retval.err = call_data->proxy_instance.set_value(&call_data->proxy_instance, call_data->arg2.len, call_data->arg1.value);
+
+  sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static snmp_err_t
+threadsync_set_value(struct snmp_node_instance* instance, u16_t len, void *value)
+{
+  struct threadsync_data *call_data = (struct threadsync_data*)instance->reference.ptr;
+
+  call_data->arg1.value = value;
+  call_data->arg2.len = len;
+  call_synced_function(call_data, threadsync_set_value_synced);
+  
+  return call_data->retval.err;
+}
+
+static void
+threadsync_release_instance_synced(void* ctx)
+{
+  struct threadsync_data *call_data = (struct threadsync_data*)ctx;
+  
+  call_data->proxy_instance.release_instance(&call_data->proxy_instance);
+
+  sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static void
+threadsync_release_instance(struct snmp_node_instance *instance)
+{
+  struct threadsync_data *call_data = (struct threadsync_data*)instance->reference.ptr;
+  
+  if (call_data->proxy_instance.release_instance != NULL) {
+    call_synced_function(call_data, threadsync_release_instance_synced);
+  }
+}
+
+static void
+get_instance_synced(void* ctx)
+{
+  struct threadsync_data *call_data   = (struct threadsync_data*)ctx;
+  const struct snmp_leaf_node *leaf   = (const struct snmp_leaf_node*)(const void*)call_data->proxy_instance.node;
+
+  call_data->retval.err = leaf->get_instance(call_data->arg1.root_oid, call_data->arg2.root_oid_len, &call_data->proxy_instance);
+
+  sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static void
+get_next_instance_synced(void* ctx)
+{
+  struct threadsync_data *call_data   = (struct threadsync_data*)ctx;
+  const struct snmp_leaf_node *leaf   = (const struct snmp_leaf_node*)(const void*)call_data->proxy_instance.node;
+
+  call_data->retval.err = leaf->get_next_instance(call_data->arg1.root_oid, call_data->arg2.root_oid_len, &call_data->proxy_instance);
+
+  sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static snmp_err_t
+do_sync(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance, snmp_threadsync_called_fn fn)
+{
+  const struct snmp_threadsync_node *threadsync_node = (const struct snmp_threadsync_node*)(const void*)instance->node;
+  struct threadsync_data *call_data = &threadsync_node->instance->data;
+
+  if (threadsync_node->node.node.oid != threadsync_node->target->node.oid) {
+    LWIP_DEBUGF(SNMP_DEBUG, ("Sync node OID does not match target node OID"));
+    return SNMP_ERR_NOSUCHINSTANCE;
+  }
+
+  memset(&call_data->proxy_instance, 0, sizeof(call_data->proxy_instance));
+
+  instance->reference.ptr = call_data;
+  snmp_oid_assign(&call_data->proxy_instance.instance_oid, instance->instance_oid.id, instance->instance_oid.len);
+
+  call_data->proxy_instance.node = &threadsync_node->target->node;
+  call_data->threadsync_node     = threadsync_node;
+
+  call_data->arg1.root_oid       = root_oid;
+  call_data->arg2.root_oid_len   = root_oid_len;
+  call_synced_function(call_data, fn);
+
+  if (call_data->retval.err == SNMP_ERR_NOERROR) {
+    instance->access           = call_data->proxy_instance.access;
+    instance->asn1_type        = call_data->proxy_instance.asn1_type;
+    instance->release_instance = threadsync_release_instance;
+    instance->get_value        = (call_data->proxy_instance.get_value != NULL)? threadsync_get_value : NULL;
+    instance->set_value        = (call_data->proxy_instance.set_value != NULL)? threadsync_set_value : NULL;
+    instance->set_test         = (call_data->proxy_instance.set_test != NULL)?  threadsync_set_test  : NULL;
+    snmp_oid_assign(&instance->instance_oid, call_data->proxy_instance.instance_oid.id, call_data->proxy_instance.instance_oid.len);
+  }
+
+  return call_data->retval.err;
+}
+
+snmp_err_t
+snmp_threadsync_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  return do_sync(root_oid, root_oid_len, instance, get_instance_synced);
+}
+
+snmp_err_t
+snmp_threadsync_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance)
+{
+  return do_sync(root_oid, root_oid_len, instance, get_next_instance_synced);
+}
+
+/** Initializes thread synchronization instance */
+void snmp_threadsync_init(struct snmp_threadsync_instance *instance, snmp_threadsync_synchronizer_fn sync_fn)
+{
+  err_t err = sys_mutex_new(&instance->sem_usage_mutex);
+  LWIP_ASSERT("Failed to set up mutex", err == ERR_OK);
+  err = sys_sem_new(&instance->sem, 0);
+  LWIP_UNUSED_ARG(err); /* in case of LWIP_NOASSERT */
+  LWIP_ASSERT("Failed to set up semaphore", err == ERR_OK);
+  instance->sync_fn = sync_fn;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_traps.c b/src/apps/snmp/snmp_traps.c
new file mode 100644
index 0000000..0d2df64
--- /dev/null
+++ b/src/apps/snmp/snmp_traps.c
@@ -0,0 +1,445 @@
+/**
+ * @file
+ * SNMPv1 traps 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: Martin Hentschel
+ *         Christiaan Simons <christiaan.simons@axon.tv>
+ *
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include <string.h>
+
+#include "lwip/snmp.h"
+#include "lwip/sys.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "snmp_msg.h"
+#include "snmp_asn1.h"
+#include "snmp_core_priv.h"
+
+struct snmp_msg_trap
+{
+  /* source enterprise ID (sysObjectID) */
+  const struct snmp_obj_id *enterprise;
+  /* source IP address, raw network order format */
+  ip_addr_t sip;
+  /* generic trap code */
+  u32_t gen_trap;
+  /* specific trap code */
+  u32_t spc_trap;
+  /* timestamp */
+  u32_t ts;
+  /* snmp_version */
+  u32_t snmp_version;
+
+  /* output trap lengths used in ASN encoding */
+  /* encoding pdu length */
+  u16_t pdulen;
+  /* encoding community length */
+  u16_t comlen;
+  /* encoding sequence length */
+  u16_t seqlen;
+  /* encoding varbinds sequence length */
+  u16_t vbseqlen;
+};
+
+static u16_t snmp_trap_varbind_sum(struct snmp_msg_trap *trap, struct snmp_varbind *varbinds);
+static u16_t snmp_trap_header_sum(struct snmp_msg_trap *trap, u16_t vb_len);
+static void snmp_trap_header_enc(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream);
+static void snmp_trap_varbind_enc(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbinds);
+
+/** Agent community string for sending traps */
+extern const char *snmp_community_trap;
+
+void* snmp_traps_handle;
+
+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;
+};
+static struct snmp_trap_dst trap_dst[SNMP_TRAP_DESTINATIONS];
+
+static u8_t snmp_auth_traps_enabled = 0;
+
+/**
+ * @ingroup snmp_traps
+ * 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;
+  }
+}
+
+/**
+ * @ingroup snmp_traps
+ * 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, const ip_addr_t *dst)
+{
+  if (dst_idx < SNMP_TRAP_DESTINATIONS) {
+    ip_addr_set(&trap_dst[dst_idx].dip, dst);
+  }
+}
+
+/**
+ * @ingroup snmp_traps
+ * Enable/disable authentication traps
+ */
+void
+snmp_set_auth_traps_enabled(u8_t enable)
+{
+  snmp_auth_traps_enabled = enable;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Get authentication traps enabled state
+ */
+u8_t
+snmp_get_auth_traps_enabled(void)
+{
+  return snmp_auth_traps_enabled;
+}
+
+
+/**
+ * @ingroup snmp_traps
+ * Sends a generic or enterprise specific trap message.
+ *
+ * @param eoid points to enterprise object identifier
+ * @param generic_trap is the trap code
+ * @param specific_trap used for enterprise traps when generic_trap == 6
+ * @param varbinds linked list of varbinds to be sent
+ * @return ERR_OK when success, ERR_MEM if we're out of memory
+ *
+ * @note the use of the enterprise 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(const struct snmp_obj_id* eoid, s32_t generic_trap, s32_t specific_trap, struct snmp_varbind *varbinds)
+{
+  struct snmp_msg_trap trap_msg;
+  struct snmp_trap_dst *td;
+  struct pbuf *p;
+  u16_t i, tot_len;
+  err_t err = ERR_OK;
+
+  trap_msg.snmp_version = 0;
+
+  for (i = 0, td = &trap_dst[0]; i < SNMP_TRAP_DESTINATIONS; i++, td++) {
+    if ((td->enable != 0) && !ip_addr_isany(&td->dip)) {
+      /* lookup current source address for this dst */
+      if (snmp_get_local_ip_for_dst(snmp_traps_handle, &td->dip, &trap_msg.sip)) {
+        if (eoid == NULL) {
+          trap_msg.enterprise = snmp_get_device_enterprise_oid();
+        } else {
+          trap_msg.enterprise = eoid;
+        }
+
+        trap_msg.gen_trap = generic_trap;
+        if (generic_trap == SNMP_GENTRAP_ENTERPRISE_SPECIFIC) {
+          trap_msg.spc_trap = specific_trap;
+        } else {
+          trap_msg.spc_trap = 0;
+        }
+
+        MIB2_COPY_SYSUPTIME_TO(&trap_msg.ts);
+
+        /* pass 0, calculate length fields */
+        tot_len = snmp_trap_varbind_sum(&trap_msg, varbinds);
+        tot_len = snmp_trap_header_sum(&trap_msg, tot_len);
+
+        /* allocate pbuf(s) */
+        p = pbuf_alloc(PBUF_TRANSPORT, tot_len, PBUF_RAM);
+        if (p != NULL) {
+          struct snmp_pbuf_stream pbuf_stream;
+          snmp_pbuf_stream_init(&pbuf_stream, p, 0, tot_len);
+
+          /* pass 1, encode packet ino the pbuf(s) */
+          snmp_trap_header_enc(&trap_msg, &pbuf_stream);
+          snmp_trap_varbind_enc(&trap_msg, &pbuf_stream, varbinds);
+
+          snmp_stats.outtraps++;
+          snmp_stats.outpkts++;
+
+          /** send to the TRAP destination */
+          snmp_sendto(snmp_traps_handle, p, &td->dip, SNMP_TRAP_PORT);
+          pbuf_free(p);
+        } else {
+          err = ERR_MEM;
+        }
+      } else {
+        /* routing error */
+        err = ERR_RTE;
+      }
+    }
+  }
+  return err;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Send generic SNMP trap
+ */
+err_t 
+snmp_send_trap_generic(s32_t generic_trap)
+{
+  static const struct snmp_obj_id oid = { 7, { 1, 3, 6, 1, 2, 1, 11 } };
+  return snmp_send_trap(&oid, generic_trap, 0, NULL);
+}
+
+/**
+ * @ingroup snmp_traps
+ * Send specific SNMP trap with variable bindings
+ */
+err_t
+snmp_send_trap_specific(s32_t specific_trap, struct snmp_varbind *varbinds)
+{
+  return snmp_send_trap(NULL, SNMP_GENTRAP_ENTERPRISE_SPECIFIC, specific_trap, varbinds);
+}
+
+/**
+ * @ingroup snmp_traps
+ * Send coldstart trap
+ */
+void
+snmp_coldstart_trap(void)
+{
+  snmp_send_trap_generic(SNMP_GENTRAP_COLDSTART);
+}
+
+/**
+ * @ingroup snmp_traps
+ * Send authentication failure trap (used internally by agent) 
+ */
+void
+snmp_authfail_trap(void)
+{
+  if (snmp_auth_traps_enabled != 0) {
+    snmp_send_trap_generic(SNMP_GENTRAP_AUTH_FAILURE);
+  }
+}
+
+static u16_t
+snmp_trap_varbind_sum(struct snmp_msg_trap *trap, struct snmp_varbind *varbinds)
+{
+  struct snmp_varbind *varbind;
+  u16_t tot_len;
+  u8_t tot_len_len;
+
+  tot_len = 0;
+  varbind = varbinds;
+  while (varbind != NULL) {
+    struct snmp_varbind_len len;
+
+    if (snmp_varbind_length(varbind, &len) == ERR_OK) {
+      tot_len += 1 + len.vb_len_len + len.vb_value_len;
+    }
+
+    varbind = varbind->next;
+  }
+
+  trap->vbseqlen = tot_len;
+  snmp_asn1_enc_length_cnt(trap->vbseqlen, &tot_len_len);
+  tot_len += 1 + tot_len_len;
+
+  return tot_len;
+}
+
+/**
+ * Sums trap header field lengths from tail to head and
+ * returns trap_header_lengths for second encoding pass.
+ *
+ * @param trap Trap message
+ * @param vb_len varbind-list length
+ * @return the required length for encoding the trap header
+ */
+static u16_t
+snmp_trap_header_sum(struct snmp_msg_trap *trap, u16_t vb_len)
+{
+  u16_t tot_len;
+  u16_t len;
+  u8_t lenlen;
+
+  tot_len = vb_len;
+
+  snmp_asn1_enc_u32t_cnt(trap->ts, &len);
+  snmp_asn1_enc_length_cnt(len, &lenlen);
+  tot_len += 1 + len + lenlen;
+
+  snmp_asn1_enc_s32t_cnt(trap->spc_trap, &len);
+  snmp_asn1_enc_length_cnt(len, &lenlen);
+  tot_len += 1 + len + lenlen;
+
+  snmp_asn1_enc_s32t_cnt(trap->gen_trap, &len);
+  snmp_asn1_enc_length_cnt(len, &lenlen);
+  tot_len += 1 + len + lenlen;
+
+  if (IP_IS_V6_VAL(trap->sip)) {
+#if LWIP_IPV6
+    len = sizeof(ip_2_ip6(&trap->sip)->addr);
+#endif
+  } else {
+#if LWIP_IPV4
+    len = sizeof(ip_2_ip4(&trap->sip)->addr);
+#endif
+  }
+  snmp_asn1_enc_length_cnt(len, &lenlen);
+  tot_len += 1 + len + lenlen;
+
+  snmp_asn1_enc_oid_cnt(trap->enterprise->id, trap->enterprise->len, &len);
+  snmp_asn1_enc_length_cnt(len, &lenlen);
+  tot_len += 1 + len + lenlen;
+
+  trap->pdulen = tot_len;
+  snmp_asn1_enc_length_cnt(trap->pdulen, &lenlen);
+  tot_len += 1 + lenlen;
+
+  trap->comlen = (u16_t)LWIP_MIN(strlen(snmp_community_trap), 0xFFFF);
+  snmp_asn1_enc_length_cnt(trap->comlen, &lenlen);
+  tot_len += 1 + lenlen + trap->comlen;
+
+  snmp_asn1_enc_s32t_cnt(trap->snmp_version, &len);
+  snmp_asn1_enc_length_cnt(len, &lenlen);
+  tot_len += 1 + len + lenlen;
+
+  trap->seqlen = tot_len;
+  snmp_asn1_enc_length_cnt(trap->seqlen, &lenlen);
+  tot_len += 1 + lenlen;
+
+  return tot_len;
+}
+
+static void
+snmp_trap_varbind_enc(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbinds)
+{
+  struct snmp_asn1_tlv tlv;
+  struct snmp_varbind *varbind;
+
+  varbind = varbinds;
+
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 0, trap->vbseqlen);
+  snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+
+  while (varbind != NULL) {
+    snmp_append_outbound_varbind(pbuf_stream, varbind);
+
+    varbind = varbind->next;
+  }
+}
+
+/**
+ * Encodes trap header from head to tail.
+ */
+static void
+snmp_trap_header_enc(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream)
+{
+  struct snmp_asn1_tlv tlv;
+
+  /* 'Message' sequence */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 0, trap->seqlen);
+  snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+
+  /* version */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+  snmp_asn1_enc_s32t_cnt(trap->snmp_version, &tlv.value_len);
+  snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+  snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->snmp_version);
+
+  /* community */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, trap->comlen);
+  snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+  snmp_asn1_enc_raw(pbuf_stream,  (const u8_t *)snmp_community_trap, trap->comlen);
+
+  /* 'PDU' sequence */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_TRAP), 0, trap->pdulen);
+  snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+
+  /* object ID */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OBJECT_ID, 0, 0);
+  snmp_asn1_enc_oid_cnt(trap->enterprise->id, trap->enterprise->len, &tlv.value_len);
+  snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+  snmp_asn1_enc_oid(pbuf_stream, trap->enterprise->id, trap->enterprise->len);
+
+  /* IP addr */
+  if (IP_IS_V6_VAL(trap->sip)) {
+#if LWIP_IPV6
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_IPADDR, 0, sizeof(ip_2_ip6(&trap->sip)->addr));
+    snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+    snmp_asn1_enc_raw(pbuf_stream, (const u8_t *)&ip_2_ip6(&trap->sip)->addr, sizeof(ip_2_ip6(&trap->sip)->addr));
+#endif
+  } else {
+#if LWIP_IPV4
+    SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_IPADDR, 0, sizeof(ip_2_ip4(&trap->sip)->addr));
+    snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+    snmp_asn1_enc_raw(pbuf_stream, (const u8_t *)&ip_2_ip4(&trap->sip)->addr, sizeof(ip_2_ip4(&trap->sip)->addr));
+#endif
+  }
+
+  /* trap length */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+  snmp_asn1_enc_s32t_cnt(trap->gen_trap, &tlv.value_len);
+  snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+  snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->gen_trap);
+
+  /* specific trap */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+  snmp_asn1_enc_s32t_cnt(trap->spc_trap, &tlv.value_len);
+  snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+  snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->spc_trap);
+
+  /* timestamp */
+  SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_TIMETICKS, 0, 0);
+  snmp_asn1_enc_s32t_cnt(trap->ts, &tlv.value_len);
+  snmp_ans1_enc_tlv(pbuf_stream, &tlv);
+  snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->ts);
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmpv3.c b/src/apps/snmp/snmpv3.c
new file mode 100644
index 0000000..69fb3a0
--- /dev/null
+++ b/src/apps/snmp/snmpv3.c
@@ -0,0 +1,136 @@
+/**
+ * @file
+ * Additional SNMPv3 functionality RFC3414 and RFC3826.
+ */
+
+/*
+ * Copyright (c) 2016 Elias Oenal.
+ * 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: Elias Oenal <lwip@eliasoenal.com>
+ */
+
+#include "snmpv3_priv.h"
+#include "lwip/apps/snmpv3.h"
+#include "lwip/sys.h"
+#include <string.h>
+
+#if LWIP_SNMP && LWIP_SNMP_V3
+
+#ifdef LWIP_SNMPV3_INCLUDE_ENGINE
+#include LWIP_SNMPV3_INCLUDE_ENGINE
+#endif
+
+#define SNMP_MAX_TIME_BOOT 2147483647UL
+
+/** Call this if engine has been changed. Has to reset boots, see below */
+void
+snmpv3_engine_id_changed(void)
+{
+  snmpv3_set_engine_boots(0);
+}
+
+/** According to RFC3414 2.2.2.
+ *
+ * The number of times that the SNMP engine has
+ * (re-)initialized itself since snmpEngineID
+ * was last configured.
+ */
+u32_t
+snmpv3_get_engine_boots_internal(void)
+{
+  if (snmpv3_get_engine_boots() == 0 ||
+      snmpv3_get_engine_boots() < SNMP_MAX_TIME_BOOT) {
+    return snmpv3_get_engine_boots();
+  }
+
+  snmpv3_set_engine_boots(SNMP_MAX_TIME_BOOT);
+  return snmpv3_get_engine_boots();
+}
+
+/** RFC3414 2.2.2.
+ *
+ * Once the timer reaches 2147483647 it gets reset to zero and the
+ * engine boot ups get incremented.
+ */
+u32_t
+snmpv3_get_engine_time_internal(void)
+{
+  if (snmpv3_get_engine_time() >= SNMP_MAX_TIME_BOOT) {
+    snmpv3_reset_engine_time();
+
+    if (snmpv3_get_engine_boots() < SNMP_MAX_TIME_BOOT - 1) {
+      snmpv3_set_engine_boots(snmpv3_get_engine_boots() + 1);
+    } else {
+      snmpv3_set_engine_boots(SNMP_MAX_TIME_BOOT);
+    }
+  }
+
+  return snmpv3_get_engine_time();
+}
+
+#if LWIP_SNMP_V3_CRYPTO
+
+/* This function ignores the byte order suggestion in RFC3414
+ * since it simply doesn't influence the effectiveness of an IV.
+ *
+ * Implementing RFC3826 priv param algorithm if LWIP_RAND is available.
+ *
+ * @todo: This is a potential thread safety issue.
+ */
+err_t
+snmpv3_build_priv_param(u8_t* priv_param)
+{
+#ifdef LWIP_RAND /* Based on RFC3826 */
+  static u8_t init;
+  static u32_t priv1, priv2;
+
+  /* Lazy initialisation */
+  if (init == 0) {
+    init = 1;
+    priv1 = LWIP_RAND();
+    priv2 = LWIP_RAND();
+  }
+
+  SMEMCPY(&priv_param[0], &priv1, sizeof(priv1));
+  SMEMCPY(&priv_param[4], &priv2, sizeof(priv2));
+
+  /* Emulate 64bit increment */
+  priv1++;
+  if (!priv1) { /* Overflow */
+    priv2++;
+  }
+#else /* Based on RFC3414 */
+  static u32_t ctr;
+  u32_t boots = LWIP_SNMPV3_GET_ENGINE_BOOTS();
+  SMEMCPY(&priv_param[0], &boots, 4);
+  SMEMCPY(&priv_param[4], &ctr, 4);
+  ctr++;
+#endif
+  return ERR_OK;
+}
+#endif /* LWIP_SNMP_V3_CRYPTO */
+
+#endif
diff --git a/src/apps/snmp/snmpv3_dummy.c b/src/apps/snmp/snmpv3_dummy.c
new file mode 100644
index 0000000..bdfe844
--- /dev/null
+++ b/src/apps/snmp/snmpv3_dummy.c
@@ -0,0 +1,145 @@
+/**
+ * @file
+ * Dummy SNMPv3 functions.
+ */
+
+/*
+ * Copyright (c) 2016 Elias Oenal.
+ * 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: Elias Oenal <lwip@eliasoenal.com>
+ *         Dirk Ziegelmeier <dirk@ziegelmeier.net>
+ */
+
+#include "lwip/apps/snmpv3.h"
+#include "snmpv3_priv.h"
+#include <string.h>
+#include "lwip/err.h"
+
+#if LWIP_SNMP && LWIP_SNMP_V3
+
+/**
+ *  @param username is a pointer to a string.
+ * @param auth_algo is a pointer to u8_t. The implementation has to set this if user was found.
+ * @param auth_key is a pointer to a pointer to a string. Implementation has to set this if user was found.
+ * @param priv_algo is a pointer to u8_t. The implementation has to set this if user was found.
+ * @param priv_key is a pointer to a pointer to a string. Implementation has to set this if user was found.
+ */
+err_t
+snmpv3_get_user(const char* username, u8_t *auth_algo, u8_t *auth_key, u8_t *priv_algo, u8_t *priv_key)
+{
+  const char* engine_id;
+  u8_t engine_id_len;
+  
+  if(strlen(username) == 0) {
+    return ERR_OK;
+  }
+  
+  if(memcmp(username, "lwip", 4) != 0) {
+    return ERR_VAL;
+  }
+  
+  snmpv3_get_engine_id(&engine_id, &engine_id_len);
+  
+  if(auth_key != NULL) {
+    snmpv3_password_to_key_sha((const u8_t*)"maplesyrup", 10,
+      (const u8_t*)engine_id, engine_id_len,
+      auth_key);
+    *auth_algo = SNMP_V3_AUTH_ALGO_SHA;
+  }
+  if(priv_key != NULL) {
+    snmpv3_password_to_key_sha((const u8_t*)"maplesyrup", 10,
+      (const u8_t*)engine_id, engine_id_len,
+      priv_key);
+    *priv_algo = SNMP_V3_PRIV_ALGO_DES;
+  }
+  return ERR_OK;
+}
+
+/**
+ * Get engine ID from persistence
+ * @param id
+ * @param len
+ */
+void
+snmpv3_get_engine_id(const char **id, u8_t *len)
+{
+  *id = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02";
+  *len = 12;
+}
+
+/**
+ * Store engine ID in persistence
+ * @param id
+ * @param len
+ */
+err_t
+snmpv3_set_engine_id(const char *id, u8_t len)
+{
+  LWIP_UNUSED_ARG(id);
+  LWIP_UNUSED_ARG(len);
+  return ERR_OK;
+}
+
+/**
+ * Get engine boots from persistence. Must be increased on each boot.
+ * @return 
+ */
+u32_t
+snmpv3_get_engine_boots(void)
+{
+  return 0;
+}
+
+/**
+ * Store engine boots in persistence
+ * @param boots
+ */
+void 
+snmpv3_set_engine_boots(u32_t boots)
+{
+  LWIP_UNUSED_ARG(boots);
+}
+
+/**
+ * RFC3414 2.2.2.
+ * Once the timer reaches 2147483647 it gets reset to zero and the
+ * engine boot ups get incremented.
+ */
+u32_t
+snmpv3_get_engine_time(void)
+{
+  return 0;
+}
+
+/**
+ * Reset current engine time to 0
+ */
+void
+snmpv3_reset_engine_time(void)
+{
+}
+
+#endif /* LWIP_SNMP && LWIP_SNMP_V3 */
diff --git a/src/apps/snmp/snmpv3_mbedtls.c b/src/apps/snmp/snmpv3_mbedtls.c
new file mode 100644
index 0000000..0b1eefb
--- /dev/null
+++ b/src/apps/snmp/snmpv3_mbedtls.c
@@ -0,0 +1,331 @@
+/**
+ * @file
+ * SNMPv3 crypto/auth functions implemented for ARM mbedtls.
+ */
+
+/*
+ * Copyright (c) 2016 Elias Oenal and Dirk Ziegelmeier.
+ * 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: Elias Oenal <lwip@eliasoenal.com>
+ *         Dirk Ziegelmeier <dirk@ziegelmeier.net>
+ */
+
+#include "lwip/apps/snmpv3.h"
+#include "snmpv3_priv.h"
+#include "lwip/arch.h"
+#include "snmp_msg.h"
+#include "lwip/sys.h"
+#include <string.h>
+
+#if LWIP_SNMP && LWIP_SNMP_V3 && LWIP_SNMP_V3_MBEDTLS
+
+#include "mbedtls/md.h"
+#include "mbedtls/cipher.h"
+
+#include "mbedtls/md5.h"
+#include "mbedtls/sha1.h"
+
+err_t
+snmpv3_auth(struct snmp_pbuf_stream* stream, u16_t length,
+    const u8_t* key, u8_t algo, u8_t* hmac_out)
+{
+  u32_t i;
+  u8_t key_len;
+  const mbedtls_md_info_t *md_info;
+  mbedtls_md_context_t ctx;
+  struct snmp_pbuf_stream read_stream;
+  snmp_pbuf_stream_init(&read_stream, stream->pbuf, stream->offset, stream->length);
+
+  if (algo == SNMP_V3_AUTH_ALGO_MD5) {
+    md_info = mbedtls_md_info_from_type(MBEDTLS_MD_MD5);
+    key_len = SNMP_V3_MD5_LEN;
+  } else if (algo == SNMP_V3_AUTH_ALGO_SHA) {
+    md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);
+    key_len = SNMP_V3_SHA_LEN;
+  } else {
+    return ERR_ARG;
+  }
+
+  mbedtls_md_init(&ctx);
+  if(mbedtls_md_setup(&ctx, md_info, 1) != 0) {
+    return ERR_ARG;
+  }
+          
+  if (mbedtls_md_hmac_starts(&ctx, key, key_len) != 0) {
+    goto free_md;
+  }
+
+  for (i = 0; i < length; i++) {
+    u8_t byte;
+
+    if (snmp_pbuf_stream_read(&read_stream, &byte)) {
+      goto free_md;
+    }
+
+    if (mbedtls_md_hmac_update(&ctx, &byte, 1) != 0) {
+      goto free_md;
+    }
+  }
+
+  if (mbedtls_md_hmac_finish(&ctx, hmac_out) != 0) {
+    goto free_md;
+  }
+
+  mbedtls_md_free(&ctx);
+  return ERR_OK;
+  
+free_md:
+  mbedtls_md_free(&ctx);
+  return ERR_ARG;
+}
+
+#if LWIP_SNMP_V3_CRYPTO
+
+err_t
+snmpv3_crypt(struct snmp_pbuf_stream* stream, u16_t length,
+    const u8_t* key, const u8_t* priv_param, const u32_t engine_boots,
+    const u32_t engine_time, u8_t algo, u8_t mode)
+{
+  size_t i;
+  mbedtls_cipher_context_t ctx;
+  const mbedtls_cipher_info_t *cipher_info;
+
+  struct snmp_pbuf_stream read_stream;
+  struct snmp_pbuf_stream write_stream;
+  snmp_pbuf_stream_init(&read_stream, stream->pbuf, stream->offset, stream->length);
+  snmp_pbuf_stream_init(&write_stream, stream->pbuf, stream->offset, stream->length);
+  mbedtls_cipher_init(&ctx);
+
+  if (algo == SNMP_V3_PRIV_ALGO_DES) {
+    u8_t iv_local[8];
+    u8_t out_bytes[8];
+    size_t out_len;
+
+    /* RFC 3414 mandates padding for DES */
+    if ((length & 0x07) != 0) {
+      return ERR_ARG;
+    }
+
+    cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_DES_CBC);
+    if(mbedtls_cipher_setup(&ctx, cipher_info) != 0) {
+      return ERR_ARG;
+    }
+    if(mbedtls_cipher_set_padding_mode(&ctx, MBEDTLS_PADDING_NONE) != 0) {
+      return ERR_ARG;
+    }
+    if(mbedtls_cipher_setkey(&ctx, key, 8*8, (mode == SNMP_V3_PRIV_MODE_ENCRYPT)? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT) != 0) {
+      goto error;
+    }
+
+    /* Prepare IV */    
+    for (i = 0; i < LWIP_ARRAYSIZE(iv_local); i++) {
+      iv_local[i] = priv_param[i] ^ key[i + 8];
+    }
+    if(mbedtls_cipher_set_iv(&ctx, iv_local, LWIP_ARRAYSIZE(iv_local)) != 0) {
+      goto error;
+    }
+
+    for (i = 0; i < length; i += 8) {
+      size_t j;
+      u8_t in_bytes[8];
+      out_len = LWIP_ARRAYSIZE(out_bytes) ;
+      
+      for (j = 0; j < LWIP_ARRAYSIZE(in_bytes); j++) {
+        snmp_pbuf_stream_read(&read_stream, &in_bytes[j]);
+      }
+
+      if(mbedtls_cipher_update(&ctx, in_bytes, LWIP_ARRAYSIZE(in_bytes), out_bytes, &out_len) != 0) {
+        goto error;
+      }
+
+      snmp_pbuf_stream_writebuf(&write_stream, out_bytes, out_len);
+    }
+    
+    out_len = LWIP_ARRAYSIZE(out_bytes);
+    if(mbedtls_cipher_finish(&ctx, out_bytes, &out_len) != 0) {
+      goto error;
+    }
+    snmp_pbuf_stream_writebuf(&write_stream, out_bytes, out_len);
+  } else if (algo == SNMP_V3_PRIV_ALGO_AES) {
+    u8_t iv_local[16];
+
+    cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_CFB128);
+    if(mbedtls_cipher_setup(&ctx, cipher_info) != 0) {
+      return ERR_ARG;
+    }
+    if(mbedtls_cipher_setkey(&ctx, key, 16*8, (mode == SNMP_V3_PRIV_MODE_ENCRYPT)? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT) != 0) {
+      goto error;
+    }
+
+    /*
+     * IV is the big endian concatenation of boots,
+     * uptime and priv param - see RFC3826.
+     */
+    iv_local[0 + 0] = (engine_boots >> 24) & 0xFF;
+    iv_local[0 + 1] = (engine_boots >> 16) & 0xFF;
+    iv_local[0 + 2] = (engine_boots >>  8) & 0xFF;
+    iv_local[0 + 3] = (engine_boots >>  0) & 0xFF;
+    iv_local[4 + 0] = (engine_time  >> 24) & 0xFF;
+    iv_local[4 + 1] = (engine_time  >> 16) & 0xFF;
+    iv_local[4 + 2] = (engine_time  >>  8) & 0xFF;
+    iv_local[4 + 3] = (engine_time  >>  0) & 0xFF;
+    SMEMCPY(iv_local + 8, priv_param, 8);
+    if(mbedtls_cipher_set_iv(&ctx, iv_local, LWIP_ARRAYSIZE(iv_local)) != 0) {
+      goto error;
+    }
+
+    for (i = 0; i < length; i++) {
+      u8_t in_byte;
+      u8_t out_byte;
+      size_t out_len = sizeof(out_byte);
+      
+      snmp_pbuf_stream_read(&read_stream, &in_byte);
+      if(mbedtls_cipher_update(&ctx, &in_byte, sizeof(in_byte), &out_byte, &out_len) != 0) {
+        goto error;
+      }
+      snmp_pbuf_stream_write(&write_stream, out_byte);
+    }
+  } else {
+    return ERR_ARG;
+  }
+
+  mbedtls_cipher_free(&ctx);
+  return ERR_OK;
+
+error:
+  mbedtls_cipher_free(&ctx);
+  return ERR_OK;
+}
+
+#endif /* LWIP_SNMP_V3_CRYPTO */
+
+/* A.2.1. Password to Key Sample Code for MD5 */
+void 
+snmpv3_password_to_key_md5(
+    const u8_t *password,    /* IN */
+    u8_t        passwordlen, /* IN */
+    const u8_t *engineID,    /* IN  - pointer to snmpEngineID  */
+    u8_t        engineLength,/* IN  - length of snmpEngineID */
+    u8_t       *key)         /* OUT - pointer to caller 16-octet buffer */
+{
+  mbedtls_md5_context MD;
+  u8_t *cp, password_buf[64];
+  u32_t password_index = 0;
+  u8_t i;
+  u32_t count = 0;
+
+  mbedtls_md5_init(&MD); /* initialize MD5 */
+  mbedtls_md5_starts(&MD);
+
+  /**********************************************/
+  /* Use while loop until we've done 1 Megabyte */
+  /**********************************************/
+  while (count < 1048576) {
+    cp = password_buf;
+    for (i = 0; i < 64; i++) {
+      /*************************************************/
+      /* Take the next octet of the password, wrapping */
+      /* to the beginning of the password as necessary.*/
+      /*************************************************/
+      *cp++ = password[password_index++ % passwordlen];
+    }
+    mbedtls_md5_update(&MD, password_buf, 64);
+    count += 64;
+  }
+  mbedtls_md5_finish(&MD, key); /* tell MD5 we're done */
+
+  /*****************************************************/
+  /* Now localize the key with the engineID and pass   */
+  /* through MD5 to produce final key                  */
+  /* May want to ensure that engineLength <= 32,       */
+  /* otherwise need to use a buffer larger than 64     */
+  /*****************************************************/
+  SMEMCPY(password_buf, key, 16);
+  MEMCPY(password_buf + 16, engineID, engineLength);
+  SMEMCPY(password_buf + 16 + engineLength, key, 16);
+
+  mbedtls_md5_starts(&MD);
+  mbedtls_md5_update(&MD, password_buf, 32 + engineLength);
+  mbedtls_md5_finish(&MD, key);
+
+  mbedtls_md5_free(&MD);
+  return;
+}
+
+/* A.2.2. Password to Key Sample Code for SHA */
+void 
+snmpv3_password_to_key_sha(
+    const u8_t *password,    /* IN */
+    u8_t        passwordlen, /* IN */
+    const u8_t *engineID,    /* IN  - pointer to snmpEngineID  */
+    u8_t        engineLength,/* IN  - length of snmpEngineID */
+    u8_t       *key)         /* OUT - pointer to caller 20-octet buffer */
+{
+  mbedtls_sha1_context SH;
+  u8_t *cp, password_buf[72];
+  u32_t password_index = 0;
+  u8_t i;
+  u32_t count = 0;
+
+  mbedtls_sha1_init(&SH); /* initialize SHA */
+  mbedtls_sha1_starts(&SH);
+
+  /**********************************************/
+  /* Use while loop until we've done 1 Megabyte */
+  /**********************************************/
+  while (count < 1048576) {
+    cp = password_buf;
+    for (i = 0; i < 64; i++) {
+      /*************************************************/
+      /* Take the next octet of the password, wrapping */
+      /* to the beginning of the password as necessary.*/
+      /*************************************************/
+      *cp++ = password[password_index++ % passwordlen];
+    }
+    mbedtls_sha1_update(&SH, password_buf, 64);
+    count += 64;
+  }
+  mbedtls_sha1_finish(&SH, key); /* tell SHA we're done */
+
+  /*****************************************************/
+  /* Now localize the key with the engineID and pass   */
+  /* through SHA to produce final key                  */
+  /* May want to ensure that engineLength <= 32,       */
+  /* otherwise need to use a buffer larger than 72     */
+  /*****************************************************/
+  SMEMCPY(password_buf, key, 20);
+  MEMCPY(password_buf + 20, engineID, engineLength);
+  SMEMCPY(password_buf + 20 + engineLength, key, 20);
+
+  mbedtls_sha1_starts(&SH);
+  mbedtls_sha1_update(&SH, password_buf, 40 + engineLength);
+  mbedtls_sha1_finish(&SH, key);
+  
+  mbedtls_sha1_free(&SH);
+  return;
+}
+
+#endif /* LWIP_SNMP && LWIP_SNMP_V3 && LWIP_SNMP_V3_MBEDTLS */
diff --git a/src/apps/snmp/snmpv3_priv.h b/src/apps/snmp/snmpv3_priv.h
new file mode 100644
index 0000000..b87666d
--- /dev/null
+++ b/src/apps/snmp/snmpv3_priv.h
@@ -0,0 +1,66 @@
+/**
+ * @file
+ * Additional SNMPv3 functionality RFC3414 and RFC3826 (internal API, do not use in client code).
+ */
+
+/*
+ * Copyright (c) 2016 Elias Oenal.
+ * 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: Elias Oenal <lwip@eliasoenal.com>
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_V3_PRIV_H
+#define LWIP_HDR_APPS_SNMP_V3_PRIV_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && LWIP_SNMP_V3
+
+#include "snmp_pbuf_stream.h"
+
+/* According to RFC 3411 */
+#define SNMP_V3_MAX_ENGINE_ID_LENGTH  32
+#define SNMP_V3_MAX_USER_LENGTH       32
+
+#define SNMP_V3_MAX_AUTH_PARAM_LENGTH  12
+#define SNMP_V3_MAX_PRIV_PARAM_LENGTH  8
+
+#define SNMP_V3_AUTH_FLAG      0x01
+#define SNMP_V3_PRIV_FLAG      0x02
+
+#define SNMP_V3_MD5_LEN        16
+#define SNMP_V3_SHA_LEN        20
+
+u32_t snmpv3_get_engine_boots_internal(void);
+u32_t snmpv3_get_engine_time_internal(void);
+err_t snmpv3_auth(struct snmp_pbuf_stream* stream, u16_t length, const u8_t* key, u8_t algo, u8_t* hmac_out);
+err_t snmpv3_crypt(struct snmp_pbuf_stream* stream, u16_t length, const u8_t* key,
+    const u8_t* priv_param, const u32_t engine_boots, const u32_t engine_time, u8_t algo, u8_t mode);
+err_t snmpv3_build_priv_param(u8_t* priv_param);
+
+#endif
+
+#endif /* LWIP_HDR_APPS_SNMP_V3_PRIV_H */
diff --git a/src/apps/sntp/sntp.c b/src/apps/sntp/sntp.c
new file mode 100644
index 0000000..71b2abe
--- /dev/null
+++ b/src/apps/sntp/sntp.c
@@ -0,0 +1,727 @@
+/**
+ * @file
+ * SNTP client module
+ */
+
+/*
+ * Copyright (c) 2007-2009 Frédéric Bernon, Simon Goldschmidt
+ * 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: Frédéric Bernon, Simon Goldschmidt
+ */
+
+
+/**
+ * @defgroup sntp SNTP
+ * @ingroup apps
+ *
+ * This is simple "SNTP" client for the lwIP raw API.
+ * It is a minimal implementation of SNTPv4 as specified in RFC 4330.
+ *
+ * For a list of some public NTP servers, see this link :
+ * http://support.ntp.org/bin/view/Servers/NTPPoolServers
+ *
+ * @todo:
+ * - set/change servers at runtime
+ * - complete SNTP_CHECK_RESPONSE checks 3 and 4
+ */
+
+#include "lwip/apps/sntp.h"
+
+#include "lwip/opt.h"
+#include "lwip/timeouts.h"
+#include "lwip/udp.h"
+#include "lwip/dns.h"
+#include "lwip/ip_addr.h"
+#include "lwip/pbuf.h"
+#include "lwip/dhcp.h"
+
+#include <string.h>
+#include <time.h>
+
+#if LWIP_UDP
+
+/* Handle support for more than one server via SNTP_MAX_SERVERS */
+#if SNTP_MAX_SERVERS > 1
+#define SNTP_SUPPORT_MULTIPLE_SERVERS 1
+#else /* NTP_MAX_SERVERS > 1 */
+#define SNTP_SUPPORT_MULTIPLE_SERVERS 0
+#endif /* NTP_MAX_SERVERS > 1 */
+
+#if (SNTP_UPDATE_DELAY < 15000) && !defined(SNTP_SUPPRESS_DELAY_CHECK)
+#error "SNTPv4 RFC 4330 enforces a minimum update time of 15 seconds (define SNTP_SUPPRESS_DELAY_CHECK to disable this error)!"
+#endif
+
+/* Configure behaviour depending on microsecond or second precision */
+#ifdef SNTP_SET_SYSTEM_TIME_US
+#define SNTP_CALC_TIME_US           1
+#define SNTP_RECEIVE_TIME_SIZE      2
+#else
+#define SNTP_SET_SYSTEM_TIME_US(sec, us)
+#define SNTP_CALC_TIME_US           0
+#define SNTP_RECEIVE_TIME_SIZE      1
+#endif
+
+
+/* the various debug levels for this file */
+#define SNTP_DEBUG_TRACE        (SNTP_DEBUG | LWIP_DBG_TRACE)
+#define SNTP_DEBUG_STATE        (SNTP_DEBUG | LWIP_DBG_STATE)
+#define SNTP_DEBUG_WARN         (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define SNTP_DEBUG_WARN_STATE   (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
+#define SNTP_DEBUG_SERIOUS      (SNTP_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
+
+#define SNTP_ERR_KOD                1
+
+/* SNTP protocol defines */
+#define SNTP_MSG_LEN                48
+
+#define SNTP_OFFSET_LI_VN_MODE      0
+#define SNTP_LI_MASK                0xC0
+#define SNTP_LI_NO_WARNING          0x00
+#define SNTP_LI_LAST_MINUTE_61_SEC  0x01
+#define SNTP_LI_LAST_MINUTE_59_SEC  0x02
+#define SNTP_LI_ALARM_CONDITION     0x03 /* (clock not synchronized) */
+
+#define SNTP_VERSION_MASK           0x38
+#define SNTP_VERSION                (4/* NTP Version 4*/<<3)
+
+#define SNTP_MODE_MASK              0x07
+#define SNTP_MODE_CLIENT            0x03
+#define SNTP_MODE_SERVER            0x04
+#define SNTP_MODE_BROADCAST         0x05
+
+#define SNTP_OFFSET_STRATUM         1
+#define SNTP_STRATUM_KOD            0x00
+
+#define SNTP_OFFSET_ORIGINATE_TIME  24
+#define SNTP_OFFSET_RECEIVE_TIME    32
+#define SNTP_OFFSET_TRANSMIT_TIME   40
+
+/* number of seconds between 1900 and 1970 (MSB=1)*/
+#define DIFF_SEC_1900_1970         (2208988800UL)
+/* number of seconds between 1970 and Feb 7, 2036 (6:28:16 UTC) (MSB=0) */
+#define DIFF_SEC_1970_2036         (2085978496UL)
+
+/**
+ * SNTP packet format (without optional fields)
+ * Timestamps are coded as 64 bits:
+ * - 32 bits seconds since Jan 01, 1970, 00:00
+ * - 32 bits seconds fraction (0-padded)
+ * For future use, if the MSB in the seconds part is set, seconds are based
+ * on Feb 07, 2036, 06:28:16.
+ */
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct sntp_msg {
+  PACK_STRUCT_FLD_8(u8_t  li_vn_mode);
+  PACK_STRUCT_FLD_8(u8_t  stratum);
+  PACK_STRUCT_FLD_8(u8_t  poll);
+  PACK_STRUCT_FLD_8(u8_t  precision);
+  PACK_STRUCT_FIELD(u32_t root_delay);
+  PACK_STRUCT_FIELD(u32_t root_dispersion);
+  PACK_STRUCT_FIELD(u32_t reference_identifier);
+  PACK_STRUCT_FIELD(u32_t reference_timestamp[2]);
+  PACK_STRUCT_FIELD(u32_t originate_timestamp[2]);
+  PACK_STRUCT_FIELD(u32_t receive_timestamp[2]);
+  PACK_STRUCT_FIELD(u32_t transmit_timestamp[2]);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+
+/* function prototypes */
+static void sntp_request(void *arg);
+
+/** The operating mode */
+static u8_t sntp_opmode;
+
+/** The UDP pcb used by the SNTP client */
+static struct udp_pcb* sntp_pcb;
+/** Names/Addresses of servers */
+struct sntp_server {
+#if SNTP_SERVER_DNS
+  char* name;
+#endif /* SNTP_SERVER_DNS */
+  ip_addr_t addr;
+};
+static struct sntp_server sntp_servers[SNTP_MAX_SERVERS];
+
+#if SNTP_GET_SERVERS_FROM_DHCP
+static u8_t sntp_set_servers_from_dhcp;
+#endif /* SNTP_GET_SERVERS_FROM_DHCP */
+#if SNTP_SUPPORT_MULTIPLE_SERVERS
+/** The currently used server (initialized to 0) */
+static u8_t sntp_current_server;
+#else /* SNTP_SUPPORT_MULTIPLE_SERVERS */
+#define sntp_current_server 0
+#endif /* SNTP_SUPPORT_MULTIPLE_SERVERS */
+
+#if SNTP_RETRY_TIMEOUT_EXP
+#define SNTP_RESET_RETRY_TIMEOUT() sntp_retry_timeout = SNTP_RETRY_TIMEOUT
+/** Retry time, initialized with SNTP_RETRY_TIMEOUT and doubled with each retry. */
+static u32_t sntp_retry_timeout;
+#else /* SNTP_RETRY_TIMEOUT_EXP */
+#define SNTP_RESET_RETRY_TIMEOUT()
+#define sntp_retry_timeout SNTP_RETRY_TIMEOUT
+#endif /* SNTP_RETRY_TIMEOUT_EXP */
+
+#if SNTP_CHECK_RESPONSE >= 1
+/** Saves the last server address to compare with response */
+static ip_addr_t sntp_last_server_address;
+#endif /* SNTP_CHECK_RESPONSE >= 1 */
+
+#if SNTP_CHECK_RESPONSE >= 2
+/** Saves the last timestamp sent (which is sent back by the server)
+ * to compare against in response */
+static u32_t sntp_last_timestamp_sent[2];
+#endif /* SNTP_CHECK_RESPONSE >= 2 */
+
+/**
+ * SNTP processing of received timestamp
+ */
+static void
+sntp_process(u32_t *receive_timestamp)
+{
+  /* convert SNTP time (1900-based) to unix GMT time (1970-based)
+   * if MSB is 0, SNTP time is 2036-based!
+   */
+  u32_t rx_secs = lwip_ntohl(receive_timestamp[0]);
+  int is_1900_based = ((rx_secs & 0x80000000) != 0);
+  u32_t t = is_1900_based ? (rx_secs - DIFF_SEC_1900_1970) : (rx_secs + DIFF_SEC_1970_2036);
+  time_t tim = t;
+
+#if SNTP_CALC_TIME_US
+  u32_t us = lwip_ntohl(receive_timestamp[1]) / 4295;
+  SNTP_SET_SYSTEM_TIME_US(t, us);
+  /* display local time from GMT time */
+  LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s, %"U32_F" us", ctime(&tim), us));
+
+#else /* SNTP_CALC_TIME_US */
+
+  /* change system time and/or the update the RTC clock */
+  SNTP_SET_SYSTEM_TIME(t);
+  /* display local time from GMT time */
+  LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s", ctime(&tim)));
+#endif /* SNTP_CALC_TIME_US */
+  LWIP_UNUSED_ARG(tim);
+}
+
+/**
+ * Initialize request struct to be sent to server.
+ */
+static void
+sntp_initialize_request(struct sntp_msg *req)
+{
+  memset(req, 0, SNTP_MSG_LEN);
+  req->li_vn_mode = SNTP_LI_NO_WARNING | SNTP_VERSION | SNTP_MODE_CLIENT;
+
+#if SNTP_CHECK_RESPONSE >= 2
+  {
+    u32_t sntp_time_sec, sntp_time_us;
+    /* fill in transmit timestamp and save it in 'sntp_last_timestamp_sent' */
+    SNTP_GET_SYSTEM_TIME(sntp_time_sec, sntp_time_us);
+    sntp_last_timestamp_sent[0] = lwip_htonl(sntp_time_sec + DIFF_SEC_1900_1970);
+    req->transmit_timestamp[0] = sntp_last_timestamp_sent[0];
+    /* we send/save us instead of fraction to be faster... */
+    sntp_last_timestamp_sent[1] = lwip_htonl(sntp_time_us);
+    req->transmit_timestamp[1] = sntp_last_timestamp_sent[1];
+  }
+#endif /* SNTP_CHECK_RESPONSE >= 2 */
+}
+
+/**
+ * Retry: send a new request (and increase retry timeout).
+ *
+ * @param arg is unused (only necessary to conform to sys_timeout)
+ */
+static void
+sntp_retry(void* arg)
+{
+  LWIP_UNUSED_ARG(arg);
+
+  LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_retry: Next request will be sent in %"U32_F" ms\n",
+    sntp_retry_timeout));
+
+  /* set up a timer to send a retry and increase the retry delay */
+  sys_timeout(sntp_retry_timeout, sntp_request, NULL);
+
+#if SNTP_RETRY_TIMEOUT_EXP
+  {
+    u32_t new_retry_timeout;
+    /* increase the timeout for next retry */
+    new_retry_timeout = sntp_retry_timeout << 1;
+    /* limit to maximum timeout and prevent overflow */
+    if ((new_retry_timeout <= SNTP_RETRY_TIMEOUT_MAX) &&
+        (new_retry_timeout > sntp_retry_timeout)) {
+      sntp_retry_timeout = new_retry_timeout;
+    }
+  }
+#endif /* SNTP_RETRY_TIMEOUT_EXP */
+}
+
+#if SNTP_SUPPORT_MULTIPLE_SERVERS
+/**
+ * If Kiss-of-Death is received (or another packet parsing error),
+ * try the next server or retry the current server and increase the retry
+ * timeout if only one server is available.
+ * (implicitly, SNTP_MAX_SERVERS > 1)
+ *
+ * @param arg is unused (only necessary to conform to sys_timeout)
+ */
+static void
+sntp_try_next_server(void* arg)
+{
+  u8_t old_server, i;
+  LWIP_UNUSED_ARG(arg);
+
+  old_server = sntp_current_server;
+  for (i = 0; i < SNTP_MAX_SERVERS - 1; i++) {
+    sntp_current_server++;
+    if (sntp_current_server >= SNTP_MAX_SERVERS) {
+      sntp_current_server = 0;
+    }
+    if (!ip_addr_isany(&sntp_servers[sntp_current_server].addr)
+#if SNTP_SERVER_DNS
+        || (sntp_servers[sntp_current_server].name != NULL)
+#endif
+        ) {
+      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_try_next_server: Sending request to server %"U16_F"\n",
+        (u16_t)sntp_current_server));
+      /* new server: reset retry timeout */
+      SNTP_RESET_RETRY_TIMEOUT();
+      /* instantly send a request to the next server */
+      sntp_request(NULL);
+      return;
+    }
+  }
+  /* no other valid server found */
+  sntp_current_server = old_server;
+  sntp_retry(NULL);
+}
+#else /* SNTP_SUPPORT_MULTIPLE_SERVERS */
+/* Always retry on error if only one server is supported */
+#define sntp_try_next_server    sntp_retry
+#endif /* SNTP_SUPPORT_MULTIPLE_SERVERS */
+
+/** UDP recv callback for the sntp pcb */
+static void
+sntp_recv(void *arg, struct udp_pcb* pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+  u8_t mode;
+  u8_t stratum;
+  u32_t receive_timestamp[SNTP_RECEIVE_TIME_SIZE];
+  err_t err;
+
+  LWIP_UNUSED_ARG(arg);
+  LWIP_UNUSED_ARG(pcb);
+
+  /* packet received: stop retry timeout  */
+  sys_untimeout(sntp_try_next_server, NULL);
+  sys_untimeout(sntp_request, NULL);
+
+  err = ERR_ARG;
+#if SNTP_CHECK_RESPONSE >= 1
+  /* check server address and port */
+  if (((sntp_opmode != SNTP_OPMODE_POLL) || ip_addr_cmp(addr, &sntp_last_server_address)) &&
+    (port == SNTP_PORT))
+#else /* SNTP_CHECK_RESPONSE >= 1 */
+  LWIP_UNUSED_ARG(addr);
+  LWIP_UNUSED_ARG(port);
+#endif /* SNTP_CHECK_RESPONSE >= 1 */
+  {
+    /* process the response */
+    if (p->tot_len == SNTP_MSG_LEN) {
+      pbuf_copy_partial(p, &mode, 1, SNTP_OFFSET_LI_VN_MODE);
+      mode &= SNTP_MODE_MASK;
+      /* if this is a SNTP response... */
+      if (((sntp_opmode == SNTP_OPMODE_POLL) && (mode == SNTP_MODE_SERVER)) ||
+          ((sntp_opmode == SNTP_OPMODE_LISTENONLY) && (mode == SNTP_MODE_BROADCAST))) {
+        pbuf_copy_partial(p, &stratum, 1, SNTP_OFFSET_STRATUM);
+        if (stratum == SNTP_STRATUM_KOD) {
+          /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */
+          err = SNTP_ERR_KOD;
+          LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_recv: Received Kiss-of-Death\n"));
+        } else {
+#if SNTP_CHECK_RESPONSE >= 2
+          /* check originate_timetamp against sntp_last_timestamp_sent */
+          u32_t originate_timestamp[2];
+          pbuf_copy_partial(p, &originate_timestamp, 8, SNTP_OFFSET_ORIGINATE_TIME);
+          if ((originate_timestamp[0] != sntp_last_timestamp_sent[0]) ||
+              (originate_timestamp[1] != sntp_last_timestamp_sent[1]))
+          {
+            LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid originate timestamp in response\n"));
+          } else
+#endif /* SNTP_CHECK_RESPONSE >= 2 */
+          /* @todo: add code for SNTP_CHECK_RESPONSE >= 3 and >= 4 here */
+          {
+            /* correct answer */
+            err = ERR_OK;
+            pbuf_copy_partial(p, &receive_timestamp, SNTP_RECEIVE_TIME_SIZE * 4, SNTP_OFFSET_TRANSMIT_TIME);
+          }
+        }
+      } else {
+        LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid mode in response: %"U16_F"\n", (u16_t)mode));
+        /* wait for correct response */
+        err = ERR_TIMEOUT;
+      }
+    } else {
+      LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid packet length: %"U16_F"\n", p->tot_len));
+    }
+  }
+#if SNTP_CHECK_RESPONSE >= 1
+  else {
+    /* packet from wrong remote address or port, wait for correct response */
+    err = ERR_TIMEOUT;
+  }
+#endif /* SNTP_CHECK_RESPONSE >= 1 */
+  pbuf_free(p);
+  if (err == ERR_OK) {
+    sntp_process(receive_timestamp);
+
+    /* Set up timeout for next request (only if poll response was received)*/
+    if (sntp_opmode == SNTP_OPMODE_POLL) {
+      /* Correct response, reset retry timeout */
+      SNTP_RESET_RETRY_TIMEOUT();
+
+      sys_timeout((u32_t)SNTP_UPDATE_DELAY, sntp_request, NULL);
+      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_recv: Scheduled next time request: %"U32_F" ms\n",
+        (u32_t)SNTP_UPDATE_DELAY));
+    }
+  } else if (err != ERR_TIMEOUT) {
+    /* Errors are only processed in case of an explicit poll response */
+    if (sntp_opmode == SNTP_OPMODE_POLL) {
+      if (err == SNTP_ERR_KOD) {
+        /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */
+        sntp_try_next_server(NULL);
+      } else {
+        /* another error, try the same server again */
+        sntp_retry(NULL);
+      }
+    }
+  }
+}
+
+/** Actually send an sntp request to a server.
+ *
+ * @param server_addr resolved IP address of the SNTP server
+ */
+static void
+sntp_send_request(const ip_addr_t *server_addr)
+{
+  struct pbuf* p;
+  p = pbuf_alloc(PBUF_TRANSPORT, SNTP_MSG_LEN, PBUF_RAM);
+  if (p != NULL) {
+    struct sntp_msg *sntpmsg = (struct sntp_msg *)p->payload;
+    LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_send_request: Sending request to server\n"));
+    /* initialize request message */
+    sntp_initialize_request(sntpmsg);
+    /* send request */
+    udp_sendto(sntp_pcb, p, server_addr, SNTP_PORT);
+    /* free the pbuf after sending it */
+    pbuf_free(p);
+    /* set up receive timeout: try next server or retry on timeout */
+    sys_timeout((u32_t)SNTP_RECV_TIMEOUT, sntp_try_next_server, NULL);
+#if SNTP_CHECK_RESPONSE >= 1
+    /* save server address to verify it in sntp_recv */
+    ip_addr_set(&sntp_last_server_address, server_addr);
+#endif /* SNTP_CHECK_RESPONSE >= 1 */
+  } else {
+    LWIP_DEBUGF(SNTP_DEBUG_SERIOUS, ("sntp_send_request: Out of memory, trying again in %"U32_F" ms\n",
+      (u32_t)SNTP_RETRY_TIMEOUT));
+    /* out of memory: set up a timer to send a retry */
+    sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_request, NULL);
+  }
+}
+
+#if SNTP_SERVER_DNS
+/**
+ * DNS found callback when using DNS names as server address.
+ */
+static void
+sntp_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg)
+{
+  LWIP_UNUSED_ARG(hostname);
+  LWIP_UNUSED_ARG(arg);
+
+  if (ipaddr != NULL) {
+    /* Address resolved, send request */
+    LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_dns_found: Server address resolved, sending request\n"));
+    sntp_send_request(ipaddr);
+  } else {
+    /* DNS resolving failed -> try another server */
+    LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_dns_found: Failed to resolve server address resolved, trying next server\n"));
+    sntp_try_next_server(NULL);
+  }
+}
+#endif /* SNTP_SERVER_DNS */
+
+/**
+ * Send out an sntp request.
+ *
+ * @param arg is unused (only necessary to conform to sys_timeout)
+ */
+static void
+sntp_request(void *arg)
+{
+  ip_addr_t sntp_server_address;
+  err_t err;
+
+  LWIP_UNUSED_ARG(arg);
+
+  /* initialize SNTP server address */
+#if SNTP_SERVER_DNS
+  if (sntp_servers[sntp_current_server].name) {
+    /* always resolve the name and rely on dns-internal caching & timeout */
+    ip_addr_set_zero(&sntp_servers[sntp_current_server].addr);
+    err = dns_gethostbyname(sntp_servers[sntp_current_server].name, &sntp_server_address,
+      sntp_dns_found, NULL);
+    if (err == ERR_INPROGRESS) {
+      /* DNS request sent, wait for sntp_dns_found being called */
+      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_request: Waiting for server address to be resolved.\n"));
+      return;
+    } else if (err == ERR_OK) {
+      sntp_servers[sntp_current_server].addr = sntp_server_address;
+    }
+  } else
+#endif /* SNTP_SERVER_DNS */
+  {
+    sntp_server_address = sntp_servers[sntp_current_server].addr;
+    err = (ip_addr_isany_val(sntp_server_address)) ? ERR_ARG : ERR_OK;
+  }
+
+  if (err == ERR_OK) {
+    LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_request: current server address is %s\n",
+      ipaddr_ntoa(&sntp_server_address)));
+    sntp_send_request(&sntp_server_address);
+  } else {
+    /* address conversion failed, try another server */
+    LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_request: Invalid server address, trying next server.\n"));
+    sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_try_next_server, NULL);
+  }
+}
+
+/**
+ * @ingroup sntp
+ * Initialize this module.
+ * Send out request instantly or after SNTP_STARTUP_DELAY(_FUNC).
+ */
+void
+sntp_init(void)
+{
+#ifdef SNTP_SERVER_ADDRESS
+#if SNTP_SERVER_DNS
+  sntp_setservername(0, SNTP_SERVER_ADDRESS);
+#else
+#error SNTP_SERVER_ADDRESS string not supported SNTP_SERVER_DNS==0
+#endif
+#endif /* SNTP_SERVER_ADDRESS */
+
+  if (sntp_pcb == NULL) {
+    sntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+    LWIP_ASSERT("Failed to allocate udp pcb for sntp client", sntp_pcb != NULL);
+    if (sntp_pcb != NULL) {
+      udp_recv(sntp_pcb, sntp_recv, NULL);
+
+      if (sntp_opmode == SNTP_OPMODE_POLL) {
+        SNTP_RESET_RETRY_TIMEOUT();
+#if SNTP_STARTUP_DELAY
+        sys_timeout((u32_t)SNTP_STARTUP_DELAY_FUNC, sntp_request, NULL);
+#else
+        sntp_request(NULL);
+#endif
+      } else if (sntp_opmode == SNTP_OPMODE_LISTENONLY) {
+        ip_set_option(sntp_pcb, SOF_BROADCAST);
+        udp_bind(sntp_pcb, IP_ANY_TYPE, SNTP_PORT);
+      }
+    }
+  }
+}
+
+/**
+ * @ingroup sntp
+ * Stop this module.
+ */
+void
+sntp_stop(void)
+{
+  if (sntp_pcb != NULL) {
+    sys_untimeout(sntp_request, NULL);
+    sys_untimeout(sntp_try_next_server, NULL);
+    udp_remove(sntp_pcb);
+    sntp_pcb = NULL;
+  }
+}
+
+/**
+ * @ingroup sntp
+ * Get enabled state.
+ */
+u8_t sntp_enabled(void)
+{
+  return (sntp_pcb != NULL)? 1 : 0;
+}
+
+/**
+ * @ingroup sntp
+ * Sets the operating mode.
+ * @param operating_mode one of the available operating modes
+ */
+void
+sntp_setoperatingmode(u8_t operating_mode)
+{
+  LWIP_ASSERT("Invalid operating mode", operating_mode <= SNTP_OPMODE_LISTENONLY);
+  LWIP_ASSERT("Operating mode must not be set while SNTP client is running", sntp_pcb == NULL);
+  sntp_opmode = operating_mode;
+}
+
+/**
+ * @ingroup sntp
+ * Gets the operating mode.
+ */
+u8_t
+sntp_getoperatingmode(void)
+{
+  return sntp_opmode;
+}
+
+#if SNTP_GET_SERVERS_FROM_DHCP
+/**
+ * Config SNTP server handling by IP address, name, or DHCP; clear table
+ * @param set_servers_from_dhcp enable or disable getting server addresses from dhcp
+ */
+void
+sntp_servermode_dhcp(int set_servers_from_dhcp)
+{
+  u8_t new_mode = set_servers_from_dhcp ? 1 : 0;
+  if (sntp_set_servers_from_dhcp != new_mode) {
+    sntp_set_servers_from_dhcp = new_mode;
+  }
+}
+#endif /* SNTP_GET_SERVERS_FROM_DHCP */
+
+/**
+ * @ingroup sntp
+ * Initialize one of the NTP servers by IP address
+ *
+ * @param idx the index of the NTP server to set must be < SNTP_MAX_SERVERS
+ * @param server IP address of the NTP server to set
+ */
+void
+sntp_setserver(u8_t idx, const ip_addr_t *server)
+{
+  if (idx < SNTP_MAX_SERVERS) {
+    if (server != NULL) {
+      sntp_servers[idx].addr = (*server);
+    } else {
+      ip_addr_set_zero(&sntp_servers[idx].addr);
+    }
+#if SNTP_SERVER_DNS
+    sntp_servers[idx].name = NULL;
+#endif
+  }
+}
+
+#if LWIP_DHCP && SNTP_GET_SERVERS_FROM_DHCP
+/**
+ * Initialize one of the NTP servers by IP address, required by DHCP
+ *
+ * @param numdns the index of the NTP server to set must be < SNTP_MAX_SERVERS
+ * @param dnsserver IP address of the NTP server to set
+ */
+void
+dhcp_set_ntp_servers(u8_t num, const ip4_addr_t *server)
+{
+  LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp: %s %u.%u.%u.%u as NTP server #%u via DHCP\n",
+    (sntp_set_servers_from_dhcp ? "Got" : "Rejected"),
+    ip4_addr1(server), ip4_addr2(server), ip4_addr3(server), ip4_addr4(server), num));
+  if (sntp_set_servers_from_dhcp && num) {
+    u8_t i;
+    for (i = 0; (i < num) && (i < SNTP_MAX_SERVERS); i++) {
+      ip_addr_t addr;
+      ip_addr_copy_from_ip4(addr, server[i]);
+      sntp_setserver(i, &addr);
+    }
+    for (i = num; i < SNTP_MAX_SERVERS; i++) {
+      sntp_setserver(i, NULL);
+    }
+  }
+}
+#endif /* LWIP_DHCP && SNTP_GET_SERVERS_FROM_DHCP */
+
+/**
+ * @ingroup sntp
+ * Obtain one of the currently configured by IP address (or DHCP) NTP servers
+ *
+ * @param idx the index of the NTP server
+ * @return IP address of the indexed NTP server or "ip_addr_any" if the NTP
+ *         server has not been configured by address (or at all).
+ */
+const ip_addr_t*
+sntp_getserver(u8_t idx)
+{
+  if (idx < SNTP_MAX_SERVERS) {
+    return &sntp_servers[idx].addr;
+  }
+  return IP_ADDR_ANY;
+}
+
+#if SNTP_SERVER_DNS
+/**
+ * Initialize one of the NTP servers by name
+ *
+ * @param numdns the index of the NTP server to set must be < SNTP_MAX_SERVERS
+ * @param dnsserver DNS name of the NTP server to set, to be resolved at contact time
+ */
+void
+sntp_setservername(u8_t idx, char *server)
+{
+  if (idx < SNTP_MAX_SERVERS) {
+    sntp_servers[idx].name = server;
+  }
+}
+
+/**
+ * Obtain one of the currently configured by name NTP servers.
+ *
+ * @param numdns the index of the NTP server
+ * @return IP address of the indexed NTP server or NULL if the NTP
+ *         server has not been configured by name (or at all)
+ */
+char *
+sntp_getservername(u8_t idx)
+{
+  if (idx < SNTP_MAX_SERVERS) {
+    return sntp_servers[idx].name;
+  }
+  return NULL;
+}
+#endif /* SNTP_SERVER_DNS */
+
+#endif /* LWIP_UDP */
diff --git a/src/apps/tftp/tftp_server.c b/src/apps/tftp/tftp_server.c
new file mode 100644
index 0000000..243b092
--- /dev/null
+++ b/src/apps/tftp/tftp_server.c
@@ -0,0 +1,417 @@
+/****************************************************************//**
+ *
+ * @file tftp_server.c
+ *
+ * @author   Logan Gunthorpe <logang@deltatee.com>
+ *           Dirk Ziegelmeier <dziegel@gmx.de>
+ *
+ * @brief    Trivial File Transfer Protocol (RFC 1350)
+ *
+ * Copyright (c) Deltatee Enterprises Ltd. 2013
+ * 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: Logan Gunthorpe <logang@deltatee.com>
+ *         Dirk Ziegelmeier <dziegel@gmx.de>
+ *
+ */
+
+/**
+ * @defgroup tftp TFTP server
+ * @ingroup apps
+ *
+ * This is simple TFTP server for the lwIP raw API.
+ */
+
+#include "lwip/apps/tftp_server.h"
+
+#if LWIP_UDP
+
+#include "lwip/udp.h"
+#include "lwip/timeouts.h"
+#include "lwip/debug.h"
+
+#define TFTP_MAX_PAYLOAD_SIZE 512
+#define TFTP_HEADER_LENGTH    4
+
+#define TFTP_RRQ   1
+#define TFTP_WRQ   2
+#define TFTP_DATA  3
+#define TFTP_ACK   4
+#define TFTP_ERROR 5
+
+enum tftp_error {
+  TFTP_ERROR_FILE_NOT_FOUND    = 1,
+  TFTP_ERROR_ACCESS_VIOLATION  = 2,
+  TFTP_ERROR_DISK_FULL         = 3,
+  TFTP_ERROR_ILLEGAL_OPERATION = 4,
+  TFTP_ERROR_UNKNOWN_TRFR_ID   = 5,
+  TFTP_ERROR_FILE_EXISTS       = 6,
+  TFTP_ERROR_NO_SUCH_USER      = 7
+};
+
+#include <string.h>
+
+struct tftp_state {
+  const struct tftp_context *ctx;
+  void *handle;
+  struct pbuf *last_data;
+  struct udp_pcb *upcb;
+  ip_addr_t addr;
+  u16_t port;
+  int timer;
+  int last_pkt;
+  u16_t blknum;
+  u8_t retries;
+  u8_t mode_write;
+};
+
+static struct tftp_state tftp_state;
+
+static void tftp_tmr(void* arg);
+
+static void
+close_handle(void)
+{
+  tftp_state.port = 0;
+  ip_addr_set_any(0, &tftp_state.addr);
+
+  if(tftp_state.last_data != NULL) {
+    pbuf_free(tftp_state.last_data);
+    tftp_state.last_data = NULL;
+  }
+
+  sys_untimeout(tftp_tmr, NULL);
+  
+  if (tftp_state.handle) {
+    tftp_state.ctx->close(tftp_state.handle);
+    tftp_state.handle = NULL;
+    LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: closing\n"));
+  }
+}
+
+static void
+send_error(const ip_addr_t *addr, u16_t port, enum tftp_error code, const char *str)
+{
+  int str_length = strlen(str);
+  struct pbuf* p;
+  u16_t* payload;
+  
+  p = pbuf_alloc(PBUF_TRANSPORT, (u16_t)(TFTP_HEADER_LENGTH + str_length + 1), PBUF_RAM);
+  if(p == NULL) {
+    return;
+  }
+
+  payload = (u16_t*) p->payload;
+  payload[0] = PP_HTONS(TFTP_ERROR);
+  payload[1] = lwip_htons(code);
+  MEMCPY(&payload[2], str, str_length + 1);
+
+  udp_sendto(tftp_state.upcb, p, addr, port);
+  pbuf_free(p);
+}
+
+static void
+send_ack(u16_t blknum)
+{
+  struct pbuf* p;
+  u16_t* payload;
+  
+  p = pbuf_alloc(PBUF_TRANSPORT, TFTP_HEADER_LENGTH, PBUF_RAM);
+  if(p == NULL) {
+    return;
+  }
+  payload = (u16_t*) p->payload;
+  
+  payload[0] = PP_HTONS(TFTP_ACK);
+  payload[1] = lwip_htons(blknum);
+  udp_sendto(tftp_state.upcb, p, &tftp_state.addr, tftp_state.port);
+  pbuf_free(p);
+}
+
+static void
+resend_data(void)
+{
+  struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, tftp_state.last_data->len, PBUF_RAM);
+  if(p == NULL) {
+    return;
+  }
+
+  if(pbuf_copy(p, tftp_state.last_data) != ERR_OK) {
+    pbuf_free(p);
+    return;
+  }
+    
+  udp_sendto(tftp_state.upcb, p, &tftp_state.addr, tftp_state.port);
+  pbuf_free(p);
+}
+
+static void
+send_data(void)
+{
+  u16_t *payload;
+  int ret;
+
+  if(tftp_state.last_data != NULL) {
+    pbuf_free(tftp_state.last_data);
+  }
+  
+  tftp_state.last_data = pbuf_alloc(PBUF_TRANSPORT, TFTP_HEADER_LENGTH + TFTP_MAX_PAYLOAD_SIZE, PBUF_RAM);
+  if(tftp_state.last_data == NULL) {
+    return;
+  }
+
+  payload = (u16_t *) tftp_state.last_data->payload;
+  payload[0] = PP_HTONS(TFTP_DATA);
+  payload[1] = lwip_htons(tftp_state.blknum);
+
+  ret = tftp_state.ctx->read(tftp_state.handle, &payload[2], TFTP_MAX_PAYLOAD_SIZE);
+  if (ret < 0) {
+    send_error(&tftp_state.addr, tftp_state.port, TFTP_ERROR_ACCESS_VIOLATION, "Error occured while reading the file.");
+    close_handle();
+    return;
+  }
+
+  pbuf_realloc(tftp_state.last_data, (u16_t)(TFTP_HEADER_LENGTH + ret));
+  resend_data();
+}
+
+static void
+recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+  u16_t *sbuf = (u16_t *) p->payload;
+  int opcode;
+
+  LWIP_UNUSED_ARG(arg);
+  LWIP_UNUSED_ARG(upcb);
+  
+  if (((tftp_state.port != 0) && (port != tftp_state.port)) ||
+      (!ip_addr_isany_val(tftp_state.addr) && !ip_addr_cmp(&tftp_state.addr, addr))) {
+    send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Only one connection at a time is supported");
+    pbuf_free(p);
+    return;
+  }
+
+  opcode = sbuf[0];
+
+  tftp_state.last_pkt = tftp_state.timer;
+  tftp_state.retries = 0;
+
+  switch (opcode) {
+    case PP_HTONS(TFTP_RRQ): /* fall through */
+    case PP_HTONS(TFTP_WRQ):
+    {
+      const char tftp_null = 0;
+      char filename[TFTP_MAX_FILENAME_LEN];
+      char mode[TFTP_MAX_MODE_LEN];
+      u16_t filename_end_offset;
+      u16_t mode_end_offset;
+
+      if(tftp_state.handle != NULL) {
+        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Only one connection at a time is supported");
+        break;
+      }
+      
+      sys_timeout(TFTP_TIMER_MSECS, tftp_tmr, NULL);
+
+      /* find \0 in pbuf -> end of filename string */
+      filename_end_offset = pbuf_memfind(p, &tftp_null, sizeof(tftp_null), 2);
+      if((u16_t)(filename_end_offset-2) > sizeof(filename)) {
+        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Filename too long/not NULL terminated");
+        break;
+      }
+      pbuf_copy_partial(p, filename, filename_end_offset-2, 2);
+
+      /* find \0 in pbuf -> end of mode string */
+      mode_end_offset = pbuf_memfind(p, &tftp_null, sizeof(tftp_null), filename_end_offset+1);
+      if((u16_t)(mode_end_offset-filename_end_offset) > sizeof(mode)) {
+        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Mode too long/not NULL terminated");
+        break;
+      }
+      pbuf_copy_partial(p, mode, mode_end_offset-filename_end_offset, filename_end_offset+1);
+ 
+      tftp_state.handle = tftp_state.ctx->open(filename, mode, opcode == PP_HTONS(TFTP_WRQ));
+      tftp_state.blknum = 1;
+
+      if (!tftp_state.handle) {
+        send_error(addr, port, TFTP_ERROR_FILE_NOT_FOUND, "Unable to open requested file.");
+        break;
+      }
+
+      LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: %s request from ", (opcode == PP_HTONS(TFTP_WRQ)) ? "write" : "read"));
+      ip_addr_debug_print(TFTP_DEBUG | LWIP_DBG_STATE, addr);
+      LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, (" for '%s' mode '%s'\n", filename, mode));
+
+      ip_addr_copy(tftp_state.addr, *addr);
+      tftp_state.port = port;
+
+      if (opcode == PP_HTONS(TFTP_WRQ)) {
+        tftp_state.mode_write = 1;
+        send_ack(0);
+      } else {
+        tftp_state.mode_write = 0;
+        send_data();
+      }
+
+      break;
+    }
+    
+    case PP_HTONS(TFTP_DATA):
+    {
+      int ret;
+      u16_t blknum;
+      
+      if (tftp_state.handle == NULL) {
+        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "No connection");
+        break;
+      }
+
+      if (tftp_state.mode_write != 1) {
+        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Not a write connection");
+        break;
+      }
+
+      blknum = lwip_ntohs(sbuf[1]);
+      pbuf_header(p, -TFTP_HEADER_LENGTH);
+
+      ret = tftp_state.ctx->write(tftp_state.handle, p);
+      if (ret < 0) {
+        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "error writing file");
+        close_handle();
+      } else {
+        send_ack(blknum);
+      }
+
+      if (p->tot_len < TFTP_MAX_PAYLOAD_SIZE) {
+        close_handle();
+      }
+      break;
+    }
+
+    case PP_HTONS(TFTP_ACK):
+    {
+      u16_t blknum;
+      int lastpkt;
+
+      if (tftp_state.handle == NULL) {
+        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "No connection");
+        break;
+      }
+
+      if (tftp_state.mode_write != 0) {
+        send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Not a read connection");
+        break;
+      }
+
+      blknum = lwip_ntohs(sbuf[1]);
+      if (blknum != tftp_state.blknum) {
+        send_error(addr, port, TFTP_ERROR_UNKNOWN_TRFR_ID, "Wrong block number");
+        break;
+      }
+
+      lastpkt = 0;
+
+      if (tftp_state.last_data != NULL) {
+        lastpkt = tftp_state.last_data->tot_len != (TFTP_MAX_PAYLOAD_SIZE + TFTP_HEADER_LENGTH);
+      }
+
+      if (!lastpkt) {
+        tftp_state.blknum++;
+        send_data();
+      } else {
+        close_handle();
+      }
+
+      break;
+    }
+    
+    default:
+      send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "Unknown operation");
+      break;
+  }
+
+  pbuf_free(p);
+}
+
+static void
+tftp_tmr(void* arg)
+{
+  LWIP_UNUSED_ARG(arg);
+  
+  tftp_state.timer++;
+
+  if (tftp_state.handle == NULL) {
+    return;
+  }
+
+  sys_timeout(TFTP_TIMER_MSECS, tftp_tmr, NULL);
+
+  if ((tftp_state.timer - tftp_state.last_pkt) > (TFTP_TIMEOUT_MSECS / TFTP_TIMER_MSECS)) {
+    if ((tftp_state.last_data != NULL) && (tftp_state.retries < TFTP_MAX_RETRIES)) {
+      LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: timeout, retrying\n"));
+      resend_data();
+      tftp_state.retries++;
+    } else {
+      LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: timeout\n"));
+      close_handle();
+    }
+  }
+}
+
+/** @ingroup tftp
+ * Initialize TFTP server.
+ * @param ctx TFTP callback struct
+ */
+err_t 
+tftp_init(const struct tftp_context *ctx)
+{
+  err_t ret;
+
+  struct udp_pcb *pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+  if (pcb == NULL) {
+    return ERR_MEM;
+  }
+
+  ret = udp_bind(pcb, IP_ANY_TYPE, TFTP_PORT);
+  if (ret != ERR_OK) {
+    udp_remove(pcb);
+    return ret;
+  }
+
+  tftp_state.handle    = NULL;
+  tftp_state.port      = 0;
+  tftp_state.ctx       = ctx;
+  tftp_state.timer     = 0;
+  tftp_state.last_data = NULL;
+  tftp_state.upcb      = pcb;
+
+  udp_recv(pcb, recv, NULL);
+
+  return ERR_OK;
+}
+
+#endif /* LWIP_UDP */
diff --git a/src/core/def.c b/src/core/def.c
new file mode 100644
index 0000000..8125313
--- /dev/null
+++ b/src/core/def.c
@@ -0,0 +1,222 @@
+/**
+ * @file
+ * Common functions used throughout the stack.
+ *
+ * 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_htons(x) your_htons
+ * \#define lwip_htonl(x) your_htonl
+ *
+ * Note lwip_ntohs() and lwip_ntohl() are merely references to the htonx counterparts.
+ * 
+ * If you \#define them to htons() and htonl(), you should
+ * \#define LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS to prevent lwIP from
+ * defining htonx/ntohx compatibility macros.
+
+ * @defgroup sys_nonstandard Non-standard functions
+ * @ingroup sys_layer
+ * lwIP provides default implementations for non-standard functions.
+ * These can be mapped to OS functions to reduce code footprint if desired.
+ * All defines related to this section must not be placed in lwipopts.h,
+ * but in arch/cc.h!
+ * These options cannot be \#defined in lwipopts.h since they are not options
+ * of lwIP itself, but options of the lwIP port to your system.
+ */
+
+/*
+ * 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"
+
+#include <string.h>
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+
+#if !defined(lwip_htons)
+/**
+ * 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 (u16_t)PP_HTONS(n);
+}
+#endif /* lwip_htons */
+
+#if !defined(lwip_htonl)
+/**
+ * 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 (u32_t)PP_HTONL(n);
+}
+#endif /* lwip_htonl */
+
+#endif /* BYTE_ORDER == LITTLE_ENDIAN */
+
+#ifndef lwip_strnstr
+/**
+ * @ingroup sys_nonstandard
+ * lwIP default implementation for strnstr() non-standard function.
+ * This can be \#defined to strnstr() depending on your platform port.
+ */
+char*
+lwip_strnstr(const char* buffer, const char* token, size_t n)
+{
+  const char* p;
+  size_t tokenlen = strlen(token);
+  if (tokenlen == 0) {
+    return LWIP_CONST_CAST(char *, buffer);
+  }
+  for (p = buffer; *p && (p + tokenlen <= buffer + n); p++) {
+    if ((*p == *token) && (strncmp(p, token, tokenlen) == 0)) {
+      return LWIP_CONST_CAST(char *, p);
+    }
+  }
+  return NULL;
+}
+#endif
+
+#ifndef lwip_stricmp
+/**
+ * @ingroup sys_nonstandard
+ * lwIP default implementation for stricmp() non-standard function.
+ * This can be \#defined to stricmp() depending on your platform port.
+ */
+int
+lwip_stricmp(const char* str1, const char* str2)
+{
+  char c1, c2;
+
+  do {
+    c1 = *str1++;
+    c2 = *str2++;
+    if (c1 != c2) {
+      char c1_upc = c1 | 0x20;
+      if ((c1_upc >= 'a') && (c1_upc <= 'z')) {
+        /* characters are not equal an one is in the alphabet range:
+        downcase both chars and check again */
+        char c2_upc = c2 | 0x20;
+        if (c1_upc != c2_upc) {
+          /* still not equal */
+          /* don't care for < or > */
+          return 1;
+        }
+      } else {
+        /* characters are not equal but none is in the alphabet range */
+        return 1;
+      }
+    }
+  } while (c1 != 0);
+  return 0;
+}
+#endif
+
+#ifndef lwip_strnicmp
+/**
+ * @ingroup sys_nonstandard
+ * lwIP default implementation for strnicmp() non-standard function.
+ * This can be \#defined to strnicmp() depending on your platform port.
+ */
+int
+lwip_strnicmp(const char* str1, const char* str2, size_t len)
+{
+  char c1, c2;
+
+  do {
+    c1 = *str1++;
+    c2 = *str2++;
+    if (c1 != c2) {
+      char c1_upc = c1 | 0x20;
+      if ((c1_upc >= 'a') && (c1_upc <= 'z')) {
+        /* characters are not equal an one is in the alphabet range:
+        downcase both chars and check again */
+        char c2_upc = c2 | 0x20;
+        if (c1_upc != c2_upc) {
+          /* still not equal */
+          /* don't care for < or > */
+          return 1;
+        }
+      } else {
+        /* characters are not equal but none is in the alphabet range */
+        return 1;
+      }
+    }
+  } while (len-- && c1 != 0);
+  return 0;
+}
+#endif
+
+#ifndef lwip_itoa
+/**
+ * @ingroup sys_nonstandard
+ * lwIP default implementation for itoa() non-standard function.
+ * This can be \#defined to itoa() or snprintf(result, bufsize, "%d", number) depending on your platform port.
+ */
+void
+lwip_itoa(char* result, size_t bufsize, int number)
+{
+  const int base = 10;
+  char* ptr = result, *ptr1 = result, tmp_char;
+  int tmp_value;
+  LWIP_UNUSED_ARG(bufsize);
+
+  do {
+    tmp_value = number;
+    number /= base;
+    *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - number * base)];
+  } while(number);
+
+   /* Apply negative sign */
+  if (tmp_value < 0) {
+     *ptr++ = '-';
+  }
+  *ptr-- = '\0';
+  while(ptr1 < ptr) {
+    tmp_char = *ptr;
+    *ptr--= *ptr1;
+    *ptr1++ = tmp_char;
+  }
+}
+#endif
diff --git a/src/core/dns.c b/src/core/dns.c
new file mode 100644
index 0000000..12c6f16
--- /dev/null
+++ b/src/core/dns.c
@@ -0,0 +1,1573 @@
+/**
+ * @file
+ * DNS - host name to IP address resolver.
+ *
+ * @defgroup dns DNS
+ * @ingroup callbackstyle_api
+ *
+ * Implements a DNS host name to IP address resolver.
+ *
+ * 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).
+ * 
+ * Multicast DNS queries are supported for names ending on ".local".
+ * However, only "One-Shot Multicast DNS Queries" are supported (RFC 6762
+ * chapter 5.1), this is not a fully compliant implementation of continuous
+ * mDNS querying!
+ *
+ * All functions must be called from TCPIP thread.
+ * 
+ * @see @ref netconn_common for thread-safe access.
+ */
+
+/*
+ * Port to lwIP from uIP
+ * by Jim Pettinato April 2007
+ *
+ * security fixes and more by Simon Goldschmidt
+ *
+ * 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.
+ */
+
+/*-----------------------------------------------------------------------------
+ * 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 */
+/** @todo: one-shot mDNS: dual-stack fallback to another IP version */
+
+/*-----------------------------------------------------------------------------
+ * Includes
+ *----------------------------------------------------------------------------*/
+
+#include "lwip/opt.h"
+
+#if LWIP_DNS /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/udp.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/dns.h"
+#include "lwip/prot/dns.h"
+
+#include <string.h>
+
+/** Random generator function to create random TXIDs and source ports for queries */
+#ifndef DNS_RAND_TXID
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_XID) != 0)
+#define DNS_RAND_TXID LWIP_RAND
+#else
+static u16_t dns_txid;
+#define DNS_RAND_TXID() (++dns_txid)
+#endif
+#endif
+
+/** Limits the source port to be >= 1024 by default */
+#ifndef DNS_PORT_ALLOWED
+#define DNS_PORT_ALLOWED(port) ((port) >= 1024)
+#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
+#elif DNS_MAX_TTL > 0x7FFFFFFF
+#error DNS_MAX_TTL must be a positive 32-bit value
+#endif
+
+#if DNS_TABLE_SIZE > 255
+#error DNS_TABLE_SIZE must fit into an u8_t
+#endif
+#if DNS_MAX_SERVERS > 255
+#error DNS_MAX_SERVERS must fit into an u8_t
+#endif
+
+/* The number of parallel requests (i.e. calls to dns_gethostbyname
+ * that cannot be answered from the DNS table.
+ * This is set to the table size by default.
+ */
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
+#ifndef DNS_MAX_REQUESTS
+#define DNS_MAX_REQUESTS          DNS_TABLE_SIZE
+#else
+#if DNS_MAX_REQUESTS > 255
+#error DNS_MAX_REQUESTS must fit into an u8_t
+#endif
+#endif
+#else
+/* In this configuration, both arrays have to have the same size and are used
+ * like one entry (used/free) */
+#define DNS_MAX_REQUESTS          DNS_TABLE_SIZE
+#endif
+
+/* The number of UDP source ports used in parallel */
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0)
+#ifndef DNS_MAX_SOURCE_PORTS
+#define DNS_MAX_SOURCE_PORTS      DNS_MAX_REQUESTS
+#else
+#if DNS_MAX_SOURCE_PORTS > 255
+#error DNS_MAX_SOURCE_PORTS must fit into an u8_t
+#endif
+#endif
+#else
+#ifdef DNS_MAX_SOURCE_PORTS
+#undef DNS_MAX_SOURCE_PORTS
+#endif
+#define DNS_MAX_SOURCE_PORTS      1
+#endif
+
+#if LWIP_IPV4 && LWIP_IPV6
+#define LWIP_DNS_ADDRTYPE_IS_IPV6(t) (((t) == LWIP_DNS_ADDRTYPE_IPV6_IPV4) || ((t) == LWIP_DNS_ADDRTYPE_IPV6))
+#define LWIP_DNS_ADDRTYPE_MATCH_IP(t, ip) (IP_IS_V6_VAL(ip) ? LWIP_DNS_ADDRTYPE_IS_IPV6(t) : (!LWIP_DNS_ADDRTYPE_IS_IPV6(t)))
+#define LWIP_DNS_ADDRTYPE_ARG(x) , x
+#define LWIP_DNS_ADDRTYPE_ARG_OR_ZERO(x) x
+#define LWIP_DNS_SET_ADDRTYPE(x, y) do { x = y; } while(0)
+#else
+#if LWIP_IPV6
+#define LWIP_DNS_ADDRTYPE_IS_IPV6(t) 1
+#else
+#define LWIP_DNS_ADDRTYPE_IS_IPV6(t) 0
+#endif
+#define LWIP_DNS_ADDRTYPE_MATCH_IP(t, ip) 1
+#define LWIP_DNS_ADDRTYPE_ARG(x)
+#define LWIP_DNS_ADDRTYPE_ARG_OR_ZERO(x) 0
+#define LWIP_DNS_SET_ADDRTYPE(x, y)
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+#define LWIP_DNS_ISMDNS_ARG(x) , x
+#else
+#define LWIP_DNS_ISMDNS_ARG(x)
+#endif
+
+/** 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
+/* maximum allowed size for the struct due to non-packed */
+#define SIZEOF_DNS_ANSWER_ASSERT 12
+
+/* DNS table entry states */
+typedef enum {
+  DNS_STATE_UNUSED           = 0,
+  DNS_STATE_NEW              = 1,
+  DNS_STATE_ASKING           = 2,
+  DNS_STATE_DONE             = 3
+} dns_state_enum_t;
+
+/** DNS table entry */
+struct dns_table_entry {
+  u32_t ttl;
+  ip_addr_t ipaddr;
+  u16_t txid;
+  u8_t  state;
+  u8_t  server_idx;
+  u8_t  tmr;
+  u8_t  retries;
+  u8_t  seqno;
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0)
+  u8_t pcb_idx;
+#endif
+  char name[DNS_MAX_NAME_LENGTH];
+#if LWIP_IPV4 && LWIP_IPV6
+  u8_t reqaddrtype;
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+  u8_t is_mdns;
+#endif
+};
+
+/** DNS request table entry: used when dns_gehostbyname cannot answer the
+ * request from the DNS table */
+struct dns_req_entry {
+  /* pointer to callback on DNS query done */
+  dns_found_callback found;
+  /* argument passed to the callback function */
+  void *arg;
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
+  u8_t dns_table_idx;
+#endif
+#if LWIP_IPV4 && LWIP_IPV6
+  u8_t reqaddrtype;
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+};
+
+#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(void);
+static err_t dns_lookup_local(const char *hostname, ip_addr_t *addr LWIP_DNS_ADDRTYPE_ARG(u8_t dns_addrtype));
+#endif /* DNS_LOCAL_HOSTLIST */
+
+
+/* forward declarations */
+static void dns_recv(void *s, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);
+static void dns_check_entries(void);
+static void dns_call_found(u8_t idx, ip_addr_t* addr);
+
+/*-----------------------------------------------------------------------------
+ * Globals
+ *----------------------------------------------------------------------------*/
+
+/* DNS variables */
+static struct udp_pcb        *dns_pcbs[DNS_MAX_SOURCE_PORTS];
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0)
+static u8_t                   dns_last_pcb_idx;
+#endif
+static u8_t                   dns_seqno;
+static struct dns_table_entry dns_table[DNS_TABLE_SIZE];
+static struct dns_req_entry   dns_requests[DNS_MAX_REQUESTS];
+static ip_addr_t              dns_servers[DNS_MAX_SERVERS];
+
+#if LWIP_IPV4
+const ip_addr_t dns_mquery_v4group = DNS_MQUERY_IPV4_GROUP_INIT;
+#endif /* LWIP_IPV4 */
+#if LWIP_IPV6
+const ip_addr_t dns_mquery_v6group = DNS_MQUERY_IPV6_GROUP_INIT;
+#endif /* LWIP_IPV6 */
+
+/**
+ * Initialize the resolver: set up the UDP pcb and configure the default server
+ * (if DNS_SERVER_ADDRESS is set).
+ */
+void
+dns_init(void)
+{
+#ifdef DNS_SERVER_ADDRESS
+  /* initialize default DNS server address */
+  ip_addr_t dnsserver;
+  DNS_SERVER_ADDRESS(&dnsserver);
+  dns_setserver(0, &dnsserver);
+#endif /* DNS_SERVER_ADDRESS */
+
+  LWIP_ASSERT("sanity check SIZEOF_DNS_QUERY",
+    sizeof(struct dns_query) == SIZEOF_DNS_QUERY);
+  LWIP_ASSERT("sanity check SIZEOF_DNS_ANSWER",
+    sizeof(struct dns_answer) <= SIZEOF_DNS_ANSWER_ASSERT);
+
+  LWIP_DEBUGF(DNS_DEBUG, ("dns_init: initializing\n"));
+
+  /* if dns client not yet initialized... */
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) == 0)
+  if (dns_pcbs[0] == NULL) {
+    dns_pcbs[0] = udp_new_ip_type(IPADDR_TYPE_ANY);
+    LWIP_ASSERT("dns_pcbs[0] != NULL", dns_pcbs[0] != 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_pcbs[0], IP_ANY_TYPE, 0);
+    udp_recv(dns_pcbs[0], dns_recv, NULL);
+  }
+#endif
+
+#if DNS_LOCAL_HOSTLIST
+  dns_init_local();
+#endif
+}
+
+/**
+ * @ingroup dns
+ * 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, const ip_addr_t *dnsserver)
+{
+  if (numdns < DNS_MAX_SERVERS) {
+    if (dnsserver != NULL) {
+      dns_servers[numdns] = (*dnsserver);
+    } else {
+      dns_servers[numdns] = *IP_ADDR_ANY;
+    }
+  }
+}
+
+/**
+ * @ingroup dns
+ * 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.
+ */
+const 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)
+{
+  LWIP_DEBUGF(DNS_DEBUG, ("dns_tmr: dns_check_entries\n"));
+  dns_check_entries();
+}
+
+#if DNS_LOCAL_HOSTLIST
+static void
+dns_init_local(void)
+{
+#if DNS_LOCAL_HOSTLIST_IS_DYNAMIC && defined(DNS_LOCAL_HOSTLIST_INIT)
+  size_t 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 < LWIP_ARRAYSIZE(local_hostlist_init); 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) {
+      char* entry_name = (char*)entry + sizeof(struct local_hostlist_entry);
+      MEMCPY(entry_name, init_entry->name, namelen);
+      entry_name[namelen] = 0;
+      entry->name = entry_name;
+      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) */
+}
+
+/**
+ * @ingroup dns
+ * Iterate the local host-list for a hostname.
+ *
+ * @param iterator_fn a function that is called for every entry in the local host-list
+ * @param iterator_arg 3rd argument passed to iterator_fn
+ * @return the number of entries in the local host-list
+ */
+size_t
+dns_local_iterate(dns_found_callback iterator_fn, void *iterator_arg)
+{
+  size_t i;
+#if DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+  struct local_hostlist_entry *entry = local_hostlist_dynamic;
+  i = 0;
+  while (entry != NULL) {
+    if (iterator_fn != NULL) {
+      iterator_fn(entry->name, &entry->addr, iterator_arg);
+    }
+    i++;
+    entry = entry->next;
+  }
+#else /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+  for (i = 0; i < LWIP_ARRAYSIZE(local_hostlist_static); i++) {
+    if (iterator_fn != NULL) {
+      iterator_fn(local_hostlist_static[i].name, &local_hostlist_static[i].addr, iterator_arg);
+    }
+  }
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+  return i;
+}
+
+/**
+ * @ingroup dns
+ * Scans the local host-list for a hostname.
+ *
+ * @param hostname Hostname to look for in the local host-list
+ * @param addr the first IP address for the hostname in the local host-list or
+ *         IPADDR_NONE if not found.
+ * @param dns_addrtype - LWIP_DNS_ADDRTYPE_IPV4_IPV6: try to resolve IPv4 (ATTENTION: no fallback here!)
+ *                     - LWIP_DNS_ADDRTYPE_IPV6_IPV4: try to resolve IPv6 (ATTENTION: no fallback here!)
+ *                     - LWIP_DNS_ADDRTYPE_IPV4: try to resolve IPv4 only
+ *                     - LWIP_DNS_ADDRTYPE_IPV6: try to resolve IPv6 only
+ * @return ERR_OK if found, ERR_ARG if not found
+ */
+err_t
+dns_local_lookup(const char *hostname, ip_addr_t *addr, u8_t dns_addrtype)
+{
+  LWIP_UNUSED_ARG(dns_addrtype);
+  return dns_lookup_local(hostname, addr LWIP_DNS_ADDRTYPE_ARG(dns_addrtype));
+}
+
+/* Internal implementation for dns_local_lookup and dns_lookup */
+static err_t
+dns_lookup_local(const char *hostname, ip_addr_t *addr LWIP_DNS_ADDRTYPE_ARG(u8_t dns_addrtype))
+{
+#if DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+  struct local_hostlist_entry *entry = local_hostlist_dynamic;
+  while (entry != NULL) {
+    if ((lwip_stricmp(entry->name, hostname) == 0) &&
+        LWIP_DNS_ADDRTYPE_MATCH_IP(dns_addrtype, entry->addr)) {
+      if (addr) {
+        ip_addr_copy(*addr, entry->addr);
+      }
+      return ERR_OK;
+    }
+    entry = entry->next;
+  }
+#else /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+  size_t i;
+  for (i = 0; i < LWIP_ARRAYSIZE(local_hostlist_static); i++) {
+    if ((lwip_stricmp(local_hostlist_static[i].name, hostname) == 0) &&
+        LWIP_DNS_ADDRTYPE_MATCH_IP(dns_addrtype, local_hostlist_static[i].addr)) {
+      if (addr) {
+        ip_addr_copy(*addr, local_hostlist_static[i].addr);
+      }
+      return ERR_OK;
+    }
+  }
+#endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
+  return ERR_ARG;
+}
+
+#if DNS_LOCAL_HOSTLIST_IS_DYNAMIC
+/**
+ * @ingroup dns
+ * Remove all entries from the local host-list for a specific hostname
+ * and/or IP address
+ *
+ * @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) || !lwip_stricmp(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;
+}
+
+/**
+ * @ingroup dns
+ * 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;
+  char* entry_name;
+  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(entry_name, hostname, namelen);
+  entry_name[namelen] = 0;
+  entry->name = entry_name;
+  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 */
+
+/**
+ * @ingroup dns
+ * 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
+ * @param addr 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.
+ * @return ERR_OK if found, ERR_ARG if not found
+ */
+static err_t
+dns_lookup(const char *name, ip_addr_t *addr LWIP_DNS_ADDRTYPE_ARG(u8_t dns_addrtype))
+{
+  u8_t i;
+#if DNS_LOCAL_HOSTLIST || defined(DNS_LOOKUP_LOCAL_EXTERN)
+#endif /* DNS_LOCAL_HOSTLIST || defined(DNS_LOOKUP_LOCAL_EXTERN) */
+#if DNS_LOCAL_HOSTLIST
+  if (dns_lookup_local(name, addr LWIP_DNS_ADDRTYPE_ARG(dns_addrtype)) == ERR_OK) {
+    return ERR_OK;
+  }
+#endif /* DNS_LOCAL_HOSTLIST */
+#ifdef DNS_LOOKUP_LOCAL_EXTERN
+  if (DNS_LOOKUP_LOCAL_EXTERN(name, addr, LWIP_DNS_ADDRTYPE_ARG_OR_ZERO(dns_addrtype)) == ERR_OK) {
+    return ERR_OK;
+  }
+#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) &&
+        (lwip_strnicmp(name, dns_table[i].name, sizeof(dns_table[i].name)) == 0) &&
+        LWIP_DNS_ADDRTYPE_MATCH_IP(dns_addrtype, dns_table[i].ipaddr)) {
+      LWIP_DEBUGF(DNS_DEBUG, ("dns_lookup: \"%s\": found = ", name));
+      ip_addr_debug_print(DNS_DEBUG, &(dns_table[i].ipaddr));
+      LWIP_DEBUGF(DNS_DEBUG, ("\n"));
+      if (addr) {
+        ip_addr_copy(*addr, dns_table[i].ipaddr);
+      }
+      return ERR_OK;
+    }
+  }
+
+  return ERR_ARG;
+}
+
+/**
+ * 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 p pbuf containing the encoded hostname in the DNS response
+ * @param start_offset offset into p where the name starts
+ * @return 0xFFFF: names differ, other: names equal -> offset behind name
+ */
+static u16_t
+dns_compare_name(const char *query, struct pbuf* p, u16_t start_offset)
+{
+  int n;
+  u16_t response_offset = start_offset;
+
+  do {
+    n = pbuf_try_get_at(p, response_offset++);
+    if (n < 0) {
+      return 0xFFFF;
+    }
+    /** @see RFC 1035 - 4.1.4. Message compression */
+    if ((n & 0xc0) == 0xc0) {
+      /* Compressed name: cannot be equal since we don't send them */
+      return 0xFFFF;
+    } else {
+      /* Not compressed name */
+      while (n > 0) {
+        int c = pbuf_try_get_at(p, response_offset);
+        if (c < 0) {
+          return 0xFFFF;
+        }
+        if ((*query) != (u8_t)c) {
+          return 0xFFFF;
+        }
+        ++response_offset;
+        ++query;
+        --n;
+      }
+      ++query;
+    }
+    n = pbuf_try_get_at(p, response_offset);
+    if (n < 0) {
+      return 0xFFFF;
+    }
+  } while (n != 0);
+
+  return response_offset + 1;
+}
+
+/**
+ * Walk through a compact encoded DNS name and return the end of the name.
+ *
+ * @param p pbuf containing the name
+ * @param query_idx start index into p pointing to encoded DNS name in the DNS server response
+ * @return index to end of the name
+ */
+static u16_t
+dns_skip_name(struct pbuf* p, u16_t query_idx)
+{
+  int n;
+  u16_t offset = query_idx;
+
+  do {
+    n = pbuf_try_get_at(p, offset++);
+    if (n < 0) {
+      return 0xFFFF;
+    }
+    /** @see RFC 1035 - 4.1.4. Message compression */
+    if ((n & 0xc0) == 0xc0) {
+      /* Compressed name: since we only want to skip it (not check it), stop here */
+      break;
+    } else {
+      /* Not compressed name */
+      if (offset + n >= p->tot_len) {
+        return 0xFFFF;
+      }
+      offset = (u16_t)(offset + n);
+    }
+    n = pbuf_try_get_at(p, offset);
+    if (n < 0) {
+      return 0xFFFF;
+    }
+  } while (n != 0);
+
+  return offset + 1;
+}
+
+/**
+ * Send a DNS query packet.
+ *
+ * @param idx the DNS table entry index for which to send a request
+ * @return ERR_OK if packet is sent; an err_t indicating the problem otherwise
+ */
+static err_t
+dns_send(u8_t idx)
+{
+  err_t err;
+  struct dns_hdr hdr;
+  struct dns_query qry;
+  struct pbuf *p;
+  u16_t query_idx, copy_len;
+  const char *hostname, *hostname_part;
+  u8_t n;
+  u8_t pcb_idx;
+  struct dns_table_entry* entry = &dns_table[idx];
+
+  LWIP_DEBUGF(DNS_DEBUG, ("dns_send: dns_servers[%"U16_F"] \"%s\": request\n",
+              (u16_t)(entry->server_idx), entry->name));
+  LWIP_ASSERT("dns server out of array", entry->server_idx < DNS_MAX_SERVERS);
+  if (ip_addr_isany_val(dns_servers[entry->server_idx])
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+      && !entry->is_mdns
+#endif
+    ) {
+    /* DNS server not valid anymore, e.g. PPP netif has been shut down */
+    /* call specified callback function if provided */
+    dns_call_found(idx, NULL);
+    /* flush this entry */
+    entry->state = DNS_STATE_UNUSED;
+    return ERR_OK;
+  }
+
+  /* if here, we have either a new query or a retry on a previous query to process */
+  p = pbuf_alloc(PBUF_TRANSPORT, (u16_t)(SIZEOF_DNS_HDR + strlen(entry->name) + 2 +
+                 SIZEOF_DNS_QUERY), PBUF_RAM);
+  if (p != NULL) {
+    const ip_addr_t* dst;
+    u16_t dst_port;
+    /* fill dns header */
+    memset(&hdr, 0, SIZEOF_DNS_HDR);
+    hdr.id = lwip_htons(entry->txid);
+    hdr.flags1 = DNS_FLAG1_RD;
+    hdr.numquestions = PP_HTONS(1);
+    pbuf_take(p, &hdr, SIZEOF_DNS_HDR);
+    hostname = entry->name;
+    --hostname;
+
+    /* convert hostname into suitable query format. */
+    query_idx = SIZEOF_DNS_HDR;
+    do {
+      ++hostname;
+      hostname_part = hostname;
+      for (n = 0; *hostname != '.' && *hostname != 0; ++hostname) {
+        ++n;
+      }
+      copy_len = (u16_t)(hostname - hostname_part);
+      pbuf_put_at(p, query_idx, n);
+      pbuf_take_at(p, hostname_part, copy_len, query_idx + 1);
+      query_idx += n + 1;
+    } while (*hostname != 0);
+    pbuf_put_at(p, query_idx, 0);
+    query_idx++;
+
+    /* fill dns query */
+    if (LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype)) {
+      qry.type = PP_HTONS(DNS_RRTYPE_AAAA);
+    } else {
+      qry.type = PP_HTONS(DNS_RRTYPE_A);
+    }
+    qry.cls = PP_HTONS(DNS_RRCLASS_IN);
+    pbuf_take_at(p, &qry, SIZEOF_DNS_QUERY, query_idx);
+
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0)
+    pcb_idx = entry->pcb_idx;
+#else
+    pcb_idx = 0;
+#endif
+    /* send dns packet */
+    LWIP_DEBUGF(DNS_DEBUG, ("sending DNS request ID %d for name \"%s\" to server %d\r\n",
+      entry->txid, entry->name, entry->server_idx));
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+    if (entry->is_mdns) {
+      dst_port = DNS_MQUERY_PORT;
+#if LWIP_IPV6
+      if (LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype))
+      {
+        dst = &dns_mquery_v6group;
+      }
+#endif
+#if LWIP_IPV4 && LWIP_IPV6
+      else
+#endif
+#if LWIP_IPV4
+      {
+        dst = &dns_mquery_v4group;
+      }
+#endif
+    } else
+#endif /* LWIP_DNS_SUPPORT_MDNS_QUERIES */
+    {
+      dst_port = DNS_SERVER_PORT;
+      dst = &dns_servers[entry->server_idx];
+    }
+    err = udp_sendto(dns_pcbs[pcb_idx], p, dst, dst_port);
+
+    /* free pbuf */
+    pbuf_free(p);
+  } else {
+    err = ERR_MEM;
+  }
+
+  return err;
+}
+
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0)
+static struct udp_pcb*
+dns_alloc_random_port(void)
+{
+  err_t err;
+  struct udp_pcb* ret;
+
+  ret = udp_new_ip_type(IPADDR_TYPE_ANY);
+  if (ret == NULL) {
+    /* out of memory, have to reuse an existing pcb */
+    return NULL;
+  }
+  do {
+    u16_t port = (u16_t)DNS_RAND_TXID();
+    if (!DNS_PORT_ALLOWED(port)) {
+      /* this port is not allowed, try again */
+      err = ERR_USE;
+      continue;
+    }
+    err = udp_bind(ret, IP_ANY_TYPE, port);
+  } while (err == ERR_USE);
+  if (err != ERR_OK) {
+    udp_remove(ret);
+    return NULL;
+  }
+  udp_recv(ret, dns_recv, NULL);
+  return ret;
+}
+
+/**
+ * dns_alloc_pcb() - allocates a new pcb (or reuses an existing one) to be used
+ * for sending a request
+ *
+ * @return an index into dns_pcbs
+ */
+static u8_t
+dns_alloc_pcb(void)
+{
+  u8_t i;
+  u8_t idx;
+
+  for (i = 0; i < DNS_MAX_SOURCE_PORTS; i++) {
+    if (dns_pcbs[i] == NULL) {
+      break;
+    }
+  }
+  if (i < DNS_MAX_SOURCE_PORTS) {
+    dns_pcbs[i] = dns_alloc_random_port();
+    if (dns_pcbs[i] != NULL) {
+      /* succeeded */
+      dns_last_pcb_idx = i;
+      return i;
+    }
+  }
+  /* if we come here, creating a new UDP pcb failed, so we have to use
+     an already existing one */
+  for (i = 0, idx = dns_last_pcb_idx + 1; i < DNS_MAX_SOURCE_PORTS; i++, idx++) {
+    if (idx >= DNS_MAX_SOURCE_PORTS) {
+      idx = 0;
+    }
+    if (dns_pcbs[idx] != NULL) {
+      dns_last_pcb_idx = idx;
+      return idx;
+    }
+  }
+  return DNS_MAX_SOURCE_PORTS;
+}
+#endif /* ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0) */
+
+/**
+ * dns_call_found() - call the found callback and check if there are duplicate
+ * entries for the given hostname. If there are any, their found callback will
+ * be called and they will be removed.
+ *
+ * @param idx dns table index of the entry that is resolved or removed
+ * @param addr IP address for the hostname (or NULL on error or memory shortage)
+ */
+static void
+dns_call_found(u8_t idx, ip_addr_t* addr)
+{
+#if ((LWIP_DNS_SECURE & (LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT)) != 0)
+  u8_t i;
+#endif
+
+#if LWIP_IPV4 && LWIP_IPV6
+  if (addr != NULL) {
+    /* check that address type matches the request and adapt the table entry */
+    if (IP_IS_V6_VAL(*addr)) {
+      LWIP_ASSERT("invalid response", LWIP_DNS_ADDRTYPE_IS_IPV6(dns_table[idx].reqaddrtype));
+      dns_table[idx].reqaddrtype = LWIP_DNS_ADDRTYPE_IPV6;
+    } else {
+      LWIP_ASSERT("invalid response", !LWIP_DNS_ADDRTYPE_IS_IPV6(dns_table[idx].reqaddrtype));
+      dns_table[idx].reqaddrtype = LWIP_DNS_ADDRTYPE_IPV4;
+    }
+  }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
+  for (i = 0; i < DNS_MAX_REQUESTS; i++) {
+    if (dns_requests[i].found && (dns_requests[i].dns_table_idx == idx)) {
+      (*dns_requests[i].found)(dns_table[idx].name, addr, dns_requests[i].arg);
+      /* flush this entry */
+      dns_requests[i].found = NULL;
+    }
+  }
+#else
+  if (dns_requests[idx].found) {
+    (*dns_requests[idx].found)(dns_table[idx].name, addr, dns_requests[idx].arg);
+  }
+  dns_requests[idx].found = NULL;
+#endif
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0)
+  /* close the pcb used unless other request are using it */
+  for (i = 0; i < DNS_MAX_REQUESTS; i++) {
+    if (i == idx) {
+      continue; /* only check other requests */
+    }
+    if (dns_table[i].state == DNS_STATE_ASKING) {
+      if (dns_table[i].pcb_idx == dns_table[idx].pcb_idx) {
+        /* another request is still using the same pcb */
+        dns_table[idx].pcb_idx = DNS_MAX_SOURCE_PORTS;
+        break;
+      }
+    }
+  }
+  if (dns_table[idx].pcb_idx < DNS_MAX_SOURCE_PORTS) {
+    /* if we come here, the pcb is not used any more and can be removed */
+    udp_remove(dns_pcbs[dns_table[idx].pcb_idx]);
+    dns_pcbs[dns_table[idx].pcb_idx] = NULL;
+    dns_table[idx].pcb_idx = DNS_MAX_SOURCE_PORTS;
+  }
+#endif
+}
+
+/* Create a query transmission ID that is unique for all outstanding queries */
+static u16_t
+dns_create_txid(void)
+{
+  u16_t txid;
+  u8_t i;
+
+again:
+  txid = (u16_t)DNS_RAND_TXID();
+
+  /* check whether the ID is unique */
+  for (i = 0; i < DNS_TABLE_SIZE; i++) {
+    if ((dns_table[i].state == DNS_STATE_ASKING) &&
+        (dns_table[i].txid == txid)) {
+      /* ID already used by another pending query */
+      goto again;
+    }
+  }
+
+  return txid;
+}
+
+/**
+ * dns_check_entry() - see if entry 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 *entry = &dns_table[i];
+
+  LWIP_ASSERT("array index out of bounds", i < DNS_TABLE_SIZE);
+
+  switch (entry->state) {
+    case DNS_STATE_NEW:
+      /* initialize new entry */
+      entry->txid = dns_create_txid();
+      entry->state = DNS_STATE_ASKING;
+      entry->server_idx = 0;
+      entry->tmr = 1;
+      entry->retries = 0;
+
+      /* send DNS packet for this entry */
+      err = dns_send(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 (--entry->tmr == 0) {
+        if (++entry->retries == DNS_MAX_RETRIES) {
+          if ((entry->server_idx + 1 < DNS_MAX_SERVERS) && !ip_addr_isany_val(dns_servers[entry->server_idx + 1])
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+            && !entry->is_mdns
+#endif /* LWIP_DNS_SUPPORT_MDNS_QUERIES */
+            ) {
+            /* change of server */
+            entry->server_idx++;
+            entry->tmr = 1;
+            entry->retries = 0;
+          } else {
+            LWIP_DEBUGF(DNS_DEBUG, ("dns_check_entry: \"%s\": timeout\n", entry->name));
+            /* call specified callback function if provided */
+            dns_call_found(i, NULL);
+            /* flush this entry */
+            entry->state = DNS_STATE_UNUSED;
+            break;
+          }
+        } else {
+          /* wait longer for the next retry */
+          entry->tmr = entry->retries;
+        }
+
+        /* send DNS packet for this entry */
+        err = dns_send(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 ((entry->ttl == 0) || (--entry->ttl == 0)) {
+        LWIP_DEBUGF(DNS_DEBUG, ("dns_check_entry: \"%s\": flush\n", entry->name));
+        /* flush this entry, there cannot be any related pending entries in this state */
+        entry->state = DNS_STATE_UNUSED;
+      }
+      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);
+  }
+}
+
+/**
+ * Save TTL and call dns_call_found for correct response.
+ */
+static void
+dns_correct_response(u8_t idx, u32_t ttl)
+{
+  struct dns_table_entry *entry = &dns_table[idx];
+
+  entry->state = DNS_STATE_DONE;
+
+  LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response = ", entry->name));
+  ip_addr_debug_print(DNS_DEBUG, (&(entry->ipaddr)));
+  LWIP_DEBUGF(DNS_DEBUG, ("\n"));
+
+  /* read the answer resource record's TTL, and maximize it if needed */
+  entry->ttl = ttl;
+  if (entry->ttl > DNS_MAX_TTL) {
+    entry->ttl = DNS_MAX_TTL;
+  }
+  dns_call_found(idx, &entry->ipaddr);
+
+  if (entry->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 */
+    /* entry reused during callback? */
+    if (entry->state == DNS_STATE_DONE) {
+      entry->state = DNS_STATE_UNUSED;
+    }
+  }
+}
+/**
+ * Receive input function for DNS response packets arriving for the dns UDP pcb.
+ */
+static void
+dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+  u8_t i;
+  u16_t txid;
+  u16_t res_idx;
+  struct dns_hdr hdr;
+  struct dns_answer ans;
+  struct dns_query qry;
+  u16_t nquestions, nanswers;
+
+  LWIP_UNUSED_ARG(arg);
+  LWIP_UNUSED_ARG(pcb);
+  LWIP_UNUSED_ARG(port);
+
+  /* is the dns message big enough ? */
+  if (p->tot_len < (SIZEOF_DNS_HDR + SIZEOF_DNS_QUERY)) {
+    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, &hdr, SIZEOF_DNS_HDR, 0) == SIZEOF_DNS_HDR) {
+    /* Match the ID in the DNS header with the name table. */
+    txid = lwip_htons(hdr.id);
+    for (i = 0; i < DNS_TABLE_SIZE; i++) {
+      const struct dns_table_entry *entry = &dns_table[i];
+      if ((entry->state == DNS_STATE_ASKING) &&
+          (entry->txid == txid)) {
+
+        /* We only care about the question(s) and the answers. The authrr
+           and the extrarr are simply discarded. */
+        nquestions = lwip_htons(hdr.numquestions);
+        nanswers   = lwip_htons(hdr.numanswers);
+
+        /* Check for correct response. */
+        if ((hdr.flags1 & DNS_FLAG1_RESPONSE) == 0) {
+          LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": not a response\n", entry->name));
+          goto memerr; /* ignore this packet */
+        }
+        if (nquestions != 1) {
+          LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response not match to query\n", entry->name));
+          goto memerr; /* ignore this packet */
+        }
+
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+        if (!entry->is_mdns)
+#endif /* LWIP_DNS_SUPPORT_MDNS_QUERIES */
+        {
+          /* Check whether response comes from the same network address to which the
+             question was sent. (RFC 5452) */
+          if (!ip_addr_cmp(addr, &dns_servers[entry->server_idx])) {
+            goto memerr; /* ignore this packet */
+          }
+        }
+
+        /* Check if the name in the "question" part match with the name in the entry and
+           skip it if equal. */
+        res_idx = dns_compare_name(entry->name, p, SIZEOF_DNS_HDR);
+        if (res_idx == 0xFFFF) {
+          LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response not match to query\n", entry->name));
+          goto memerr; /* ignore this packet */
+        }
+
+        /* check if "question" part matches the request */
+        if (pbuf_copy_partial(p, &qry, SIZEOF_DNS_QUERY, res_idx) != SIZEOF_DNS_QUERY) {
+          goto memerr; /* ignore this packet */
+        }
+        if ((qry.cls != PP_HTONS(DNS_RRCLASS_IN)) ||
+          (LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype) && (qry.type != PP_HTONS(DNS_RRTYPE_AAAA))) ||
+          (!LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype) && (qry.type != PP_HTONS(DNS_RRTYPE_A)))) {
+          LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response not match to query\n", entry->name));
+          goto memerr; /* ignore this packet */
+        }
+        /* skip the rest of the "question" part */
+        res_idx += SIZEOF_DNS_QUERY;
+
+        /* Check for error. If so, call callback to inform. */
+        if (hdr.flags2 & DNS_FLAG2_ERR_MASK) {
+          LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": error in flags\n", entry->name));
+        } else {
+          while ((nanswers > 0) && (res_idx < p->tot_len)) {
+            /* skip answer resource record's host name */
+            res_idx = dns_skip_name(p, res_idx);
+            if (res_idx == 0xFFFF) {
+              goto memerr; /* ignore this packet */
+            }
+
+            /* Check for IP address type and Internet class. Others are discarded. */
+            if (pbuf_copy_partial(p, &ans, SIZEOF_DNS_ANSWER, res_idx) != SIZEOF_DNS_ANSWER) {
+              goto memerr; /* ignore this packet */
+            }
+            res_idx += SIZEOF_DNS_ANSWER;
+
+            if (ans.cls == PP_HTONS(DNS_RRCLASS_IN)) {
+#if LWIP_IPV4
+              if ((ans.type == PP_HTONS(DNS_RRTYPE_A)) && (ans.len == PP_HTONS(sizeof(ip4_addr_t)))) {
+#if LWIP_IPV4 && LWIP_IPV6
+                if (!LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype))
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+                {
+                  ip4_addr_t ip4addr;
+                  /* read the IP address after answer resource record's header */
+                  if (pbuf_copy_partial(p, &ip4addr, sizeof(ip4_addr_t), res_idx) != sizeof(ip4_addr_t)) {
+                    goto memerr; /* ignore this packet */
+                  }
+                  ip_addr_copy_from_ip4(dns_table[i].ipaddr, ip4addr);
+                  pbuf_free(p);
+                  /* handle correct response */
+                  dns_correct_response(i, lwip_ntohl(ans.ttl));
+                  return;
+                }
+              }
+#endif /* LWIP_IPV4 */
+#if LWIP_IPV6
+              if ((ans.type == PP_HTONS(DNS_RRTYPE_AAAA)) && (ans.len == PP_HTONS(sizeof(ip6_addr_t)))) {
+#if LWIP_IPV4 && LWIP_IPV6
+                if (LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype))
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+                {
+                  ip6_addr_t ip6addr;
+                  /* read the IP address after answer resource record's header */
+                  if (pbuf_copy_partial(p, &ip6addr, sizeof(ip6_addr_t), res_idx) != sizeof(ip6_addr_t)) {
+                    goto memerr; /* ignore this packet */
+                  }
+                  ip_addr_copy_from_ip6(dns_table[i].ipaddr, ip6addr);
+                  pbuf_free(p);
+                  /* handle correct response */
+                  dns_correct_response(i, lwip_ntohl(ans.ttl));
+                  return;
+                }
+              }
+#endif /* LWIP_IPV6 */
+            }
+            /* skip this answer */
+            if ((int)(res_idx + lwip_htons(ans.len)) > 0xFFFF) {
+              goto memerr; /* ignore this packet */
+            }
+            res_idx += lwip_htons(ans.len);
+            --nanswers;
+          }
+#if LWIP_IPV4 && LWIP_IPV6
+          if ((entry->reqaddrtype == LWIP_DNS_ADDRTYPE_IPV4_IPV6) ||
+              (entry->reqaddrtype == LWIP_DNS_ADDRTYPE_IPV6_IPV4)) {
+            if (entry->reqaddrtype == LWIP_DNS_ADDRTYPE_IPV4_IPV6) {
+              /* IPv4 failed, try IPv6 */
+              dns_table[i].reqaddrtype = LWIP_DNS_ADDRTYPE_IPV6;
+            } else {
+              /* IPv6 failed, try IPv4 */
+              dns_table[i].reqaddrtype = LWIP_DNS_ADDRTYPE_IPV4;
+            }
+            pbuf_free(p);
+            dns_table[i].state = DNS_STATE_NEW;
+            dns_check_entry(i);
+            return;
+          }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+          LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": error in response\n", entry->name));
+        }
+        /* call callback to indicate error, clean up memory and return */
+        pbuf_free(p);
+        dns_call_found(i, NULL);
+        dns_table[i].state = DNS_STATE_UNUSED;
+        return;
+      }
+    }
+  }
+
+memerr:
+  /* deallocate memory and return */
+  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 function to be called on success, failure or timeout
+ * @param callback_arg argument to pass to the callback function
+ * @return err_t return code.
+ */
+static err_t
+dns_enqueue(const char *name, size_t hostnamelen, dns_found_callback found,
+            void *callback_arg LWIP_DNS_ADDRTYPE_ARG(u8_t dns_addrtype) LWIP_DNS_ISMDNS_ARG(u8_t is_mdns))
+{
+  u8_t i;
+  u8_t lseq, lseqi;
+  struct dns_table_entry *entry = NULL;
+  size_t namelen;
+  struct dns_req_entry* req;
+
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
+  u8_t r;
+  /* check for duplicate entries */
+  for (i = 0; i < DNS_TABLE_SIZE; i++) {
+    if ((dns_table[i].state == DNS_STATE_ASKING) &&
+        (lwip_strnicmp(name, dns_table[i].name, sizeof(dns_table[i].name)) == 0)) {
+#if LWIP_IPV4 && LWIP_IPV6
+      if (dns_table[i].reqaddrtype != dns_addrtype) {
+        /* requested address types don't match
+           this can lead to 2 concurrent requests, but mixing the address types
+           for the same host should not be that common */
+        continue;
+      }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+      /* this is a duplicate entry, find a free request entry */
+      for (r = 0; r < DNS_MAX_REQUESTS; r++) {
+        if (dns_requests[r].found == 0) {
+          dns_requests[r].found = found;
+          dns_requests[r].arg = callback_arg;
+          dns_requests[r].dns_table_idx = i;
+          LWIP_DNS_SET_ADDRTYPE(dns_requests[r].reqaddrtype, dns_addrtype);
+          LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": duplicate request\n", name));
+          return ERR_INPROGRESS;
+        }
+      }
+    }
+  }
+  /* no duplicate entries found */
+#endif
+
+  /* search an unused entry, or the oldest one */
+  lseq = 0;
+  lseqi = DNS_TABLE_SIZE;
+  for (i = 0; i < DNS_TABLE_SIZE; ++i) {
+    entry = &dns_table[i];
+    /* is it an unused entry ? */
+    if (entry->state == DNS_STATE_UNUSED) {
+      break;
+    }
+    /* check if this is the oldest completed entry */
+    if (entry->state == DNS_STATE_DONE) {
+      u8_t age = dns_seqno - entry->seqno;
+      if (age > lseq) {
+        lseq = age;
+        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 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;
+      entry = &dns_table[i];
+    }
+  }
+
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
+  /* find a free request entry */
+  req = NULL;
+  for (r = 0; r < DNS_MAX_REQUESTS; r++) {
+    if (dns_requests[r].found == NULL) {
+      req = &dns_requests[r];
+      break;
+    }
+  }
+  if (req == NULL) {
+    /* no request entry can be used now, table is full */
+    LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": DNS request entries table is full\n", name));
+    return ERR_MEM;
+  }
+  req->dns_table_idx = i;
+#else
+  /* in this configuration, the entry index is the same as the request index */
+  req = &dns_requests[i];
+#endif
+
+  /* use this entry */
+  LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": use DNS entry %"U16_F"\n", name, (u16_t)(i)));
+
+  /* fill the entry */
+  entry->state = DNS_STATE_NEW;
+  entry->seqno = dns_seqno;
+  LWIP_DNS_SET_ADDRTYPE(entry->reqaddrtype, dns_addrtype);
+  LWIP_DNS_SET_ADDRTYPE(req->reqaddrtype, dns_addrtype);
+  req->found = found;
+  req->arg   = callback_arg;
+  namelen = LWIP_MIN(hostnamelen, DNS_MAX_NAME_LENGTH-1);
+  MEMCPY(entry->name, name, namelen);
+  entry->name[namelen] = 0;
+
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0)
+  entry->pcb_idx = dns_alloc_pcb();
+  if (entry->pcb_idx >= DNS_MAX_SOURCE_PORTS) {
+    /* failed to get a UDP pcb */
+    LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": failed to allocate a pcb\n", name));
+    entry->state = DNS_STATE_UNUSED;
+    req->found = NULL;
+    return ERR_MEM;
+  }
+  LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": use DNS pcb %"U16_F"\n", name, (u16_t)(entry->pcb_idx)));
+#endif
+
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+  entry->is_mdns = is_mdns;
+#endif
+
+  dns_seqno++;
+
+  /* force to send query without waiting timer */
+  dns_check_entry(i);
+
+  /* dns query is enqueued */
+  return ERR_INPROGRESS;
+}
+
+/**
+ * @ingroup dns
+ * 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)
+{
+  return dns_gethostbyname_addrtype(hostname, addr, found, callback_arg, LWIP_DNS_ADDRTYPE_DEFAULT);
+}
+
+/**
+ * @ingroup dns
+ * Like dns_gethostbyname, but returned address type can be controlled:
+ * @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
+ * @param dns_addrtype - LWIP_DNS_ADDRTYPE_IPV4_IPV6: try to resolve IPv4 first, try IPv6 if IPv4 fails only
+ *                     - LWIP_DNS_ADDRTYPE_IPV6_IPV4: try to resolve IPv6 first, try IPv4 if IPv6 fails only
+ *                     - LWIP_DNS_ADDRTYPE_IPV4: try to resolve IPv4 only
+ *                     - LWIP_DNS_ADDRTYPE_IPV6: try to resolve IPv6 only
+ */
+err_t
+dns_gethostbyname_addrtype(const char *hostname, ip_addr_t *addr, dns_found_callback found,
+                           void *callback_arg, u8_t dns_addrtype)
+{
+  size_t hostnamelen;
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+  u8_t is_mdns;
+#endif
+  /* not initialized or no valid server yet, or invalid addr pointer
+   * or invalid hostname or invalid hostname length */
+  if ((addr == NULL) ||
+      (!hostname) || (!hostname[0])) {
+    return ERR_ARG;
+  }
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) == 0)
+  if (dns_pcbs[0] == NULL) {
+    return ERR_ARG;
+  }
+#endif
+  hostnamelen = strlen(hostname);
+  if (hostnamelen >= DNS_MAX_NAME_LENGTH) {
+    LWIP_DEBUGF(DNS_DEBUG, ("dns_gethostbyname: name too long to resolve"));
+    return ERR_ARG;
+  }
+
+
+#if LWIP_HAVE_LOOPIF
+  if (strcmp(hostname, "localhost") == 0) {
+    ip_addr_set_loopback(LWIP_DNS_ADDRTYPE_IS_IPV6(dns_addrtype), addr);
+    return ERR_OK;
+  }
+#endif /* LWIP_HAVE_LOOPIF */
+
+  /* host name already in octet notation? set ip addr and return ERR_OK */
+  if (ipaddr_aton(hostname, addr)) {
+#if LWIP_IPV4 && LWIP_IPV6
+    if ((IP_IS_V6(addr) && (dns_addrtype != LWIP_DNS_ADDRTYPE_IPV4)) ||
+        (IP_IS_V4(addr) && (dns_addrtype != LWIP_DNS_ADDRTYPE_IPV6)))
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+    {
+      return ERR_OK;
+    }
+  }
+  /* already have this address cached? */
+  if (dns_lookup(hostname, addr LWIP_DNS_ADDRTYPE_ARG(dns_addrtype)) == ERR_OK) {
+    return ERR_OK;
+  }
+#if LWIP_IPV4 && LWIP_IPV6
+  if ((dns_addrtype == LWIP_DNS_ADDRTYPE_IPV4_IPV6) || (dns_addrtype == LWIP_DNS_ADDRTYPE_IPV6_IPV4)) {
+    /* fallback to 2nd IP type and try again to lookup */
+    u8_t fallback;
+    if (dns_addrtype == LWIP_DNS_ADDRTYPE_IPV4_IPV6) {
+      fallback = LWIP_DNS_ADDRTYPE_IPV6;
+    } else {
+      fallback = LWIP_DNS_ADDRTYPE_IPV4;
+    }
+    if (dns_lookup(hostname, addr LWIP_DNS_ADDRTYPE_ARG(fallback)) == ERR_OK) {
+      return ERR_OK;
+    }
+  }
+#else /* LWIP_IPV4 && LWIP_IPV6 */
+  LWIP_UNUSED_ARG(dns_addrtype);
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+  if (strstr(hostname, ".local") == &hostname[hostnamelen] - 6) {
+    is_mdns = 1;
+  } else {
+    is_mdns = 0;
+  }
+
+  if (!is_mdns)
+#endif /* LWIP_DNS_SUPPORT_MDNS_QUERIES */
+  {
+    /* prevent calling found callback if no server is set, return error instead */
+    if (ip_addr_isany_val(dns_servers[0])) {
+      return ERR_VAL;
+    }
+  }
+
+  /* queue query with specified callback */
+  return dns_enqueue(hostname, hostnamelen, found, callback_arg LWIP_DNS_ADDRTYPE_ARG(dns_addrtype)
+     LWIP_DNS_ISMDNS_ARG(is_mdns));
+}
+
+#endif /* LWIP_DNS */
diff --git a/src/core/inet_chksum.c b/src/core/inet_chksum.c
new file mode 100644
index 0000000..917f3e4
--- /dev/null
+++ b/src/core/inet_chksum.c
@@ -0,0 +1,609 @@
+/**
+ * @file
+ * Incluse internet checksum functions.\n
+ *
+ * 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.
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/inet_chksum.h"
+#include "lwip/def.h"
+#include "lwip/ip_addr.h"
+
+#include <string.h>
+
+#ifndef LWIP_CHKSUM
+# define LWIP_CHKSUM lwip_standard_chksum
+# ifndef LWIP_CHKSUM_ALGORITHM
+#  define LWIP_CHKSUM_ALGORITHM 2
+# endif
+u16_t lwip_standard_chksum(const 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(const void *dataptr, int len)
+{
+  u32_t acc;
+  u16_t src;
+  const u8_t *octetptr;
+
+  acc = 0;
+  /* dataptr may be at odd or even addresses */
+  octetptr = (const 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 lwip_htons()
+     instead of lwip_ntohs() since it has a little less call overhead.
+     The caller must invert bits for Internet sum ! */
+  return lwip_htons((u16_t)acc);
+}
+#endif
+
+#if (LWIP_CHKSUM_ALGORITHM == 2) /* Alternative version #2 */
+/*
+ * Curt McDowell
+ * Broadcom Corp.
+ * csm@broadcom.com
+ *
+ * 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(const void *dataptr, int len)
+{
+  const u8_t *pb = (const u8_t *)dataptr;
+  const u16_t *ps;
+  u16_t 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 = (const u16_t *)(const void *)pb;
+  while (len > 1) {
+    sum += *ps++;
+    len -= 2;
+  }
+
+  /* Consume left-over byte, if any */
+  if (len > 0) {
+    ((u8_t *)&t)[0] = *(const u8_t *)ps;
+  }
+
+  /* Add end bytes */
+  sum += t;
+
+  /* Fold 32-bit sum to 16 bits
+     calling this twice is probably 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(const void *dataptr, int len)
+{
+  const u8_t *pb = (const u8_t *)dataptr;
+  const u16_t *ps;
+  u16_t t = 0;
+  const 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 = (const u16_t *)(const void*)pb;
+
+  if (((mem_ptr_t)ps & 3) && len > 1) {
+    sum += *ps++;
+    len -= 2;
+  }
+
+  pl = (const u32_t *)(const void*)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 = (const 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] = *(const u8_t *)ps;
+  }
+
+  sum += t;                     /* add end bytes */
+
+  /* Fold 32-bit sum to 16 bits
+     calling this twice is probably 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)lwip_htons((u16_t)proto);
+  acc += (u32_t)lwip_htons(proto_len);
+
+  /* Fold 32-bit sum to 16 bits
+     calling this twice is probably 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);
+}
+
+#if LWIP_IPV4
+/* inet_chksum_pseudo:
+ *
+ * Calculates the IPv4 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,
+       const ip4_addr_t *src, const ip4_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);
+}
+#endif /* LWIP_IPV4 */
+
+#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 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 src source ipv6 address (used for checksum of pseudo header)
+ * @param dest destination ipv6 address (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,
+       const ip6_addr_t *src, const 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 */
+
+/* ip_chksum_pseudo:
+ *
+ * Calculates the IPv4 or IPv6 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
+ip_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len,
+       const ip_addr_t *src, const ip_addr_t *dest)
+{
+#if LWIP_IPV6
+  if (IP_IS_V6(dest)) {
+    return ip6_chksum_pseudo(p, proto, proto_len, ip_2_ip6(src), ip_2_ip6(dest));
+  }
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4 && LWIP_IPV6
+  else
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+#if LWIP_IPV4
+  {
+    return inet_chksum_pseudo(p, proto, proto_len, ip_2_ip4(src), ip_2_ip4(dest));
+  }
+#endif /* LWIP_IPV4 */
+}
+
+/** 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)lwip_htons((u16_t)proto);
+  acc += (u32_t)lwip_htons(proto_len);
+
+  /* Fold 32-bit sum to 16 bits
+     calling this twice is probably 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);
+}
+
+#if LWIP_IPV4
+/* inet_chksum_pseudo_partial:
+ *
+ * Calculates the IPv4 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, const ip4_addr_t *src, const ip4_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);
+}
+#endif /* LWIP_IPV4 */
+
+#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 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
+ * @param src source ipv6 address (used for checksum of pseudo header)
+ * @param dest destination ipv6 address (used for checksum of pseudo header)
+ * @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, const ip6_addr_t *src, const 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 */
+
+/* ip_chksum_pseudo_partial:
+ *
+ * Calculates the IPv4 or IPv6 pseudo Internet checksum used by TCP and UDP for a pbuf chain.
+ *
+ * @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
+ip_chksum_pseudo_partial(struct pbuf *p, u8_t proto, u16_t proto_len,
+       u16_t chksum_len, const ip_addr_t *src, const ip_addr_t *dest)
+{
+#if LWIP_IPV6
+  if (IP_IS_V6(dest)) {
+    return ip6_chksum_pseudo_partial(p, proto, proto_len, chksum_len, ip_2_ip6(src), ip_2_ip6(dest));
+  }
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4 && LWIP_IPV6
+  else
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+#if LWIP_IPV4
+  {
+    return inet_chksum_pseudo_partial(p, proto, proto_len, chksum_len, ip_2_ip4(src), ip_2_ip4(dest));
+  }
+#endif /* LWIP_IPV4 */
+}
+
+/* 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(const void *dataptr, u16_t len)
+{
+  return (u16_t)~(unsigned int)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/src/core/init.c b/src/core/init.c
new file mode 100644
index 0000000..ea3b5eb
--- /dev/null
+++ b/src/core/init.c
@@ -0,0 +1,385 @@
+/**
+ * @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@sics.se>
+ */
+
+#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/priv/tcp_priv.h"
+#include "lwip/igmp.h"
+#include "lwip/dns.h"
+#include "lwip/timeouts.h"
+#include "lwip/etharp.h"
+#include "lwip/ip6.h"
+#include "lwip/nd6.h"
+#include "lwip/mld6.h"
+#include "lwip/api.h"
+
+#include "netif/ppp/ppp_opts.h"
+#include "netif/ppp/ppp_impl.h"
+
+#ifndef LWIP_SKIP_PACKING_CHECK
+
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct packed_struct_test
+{
+  PACK_STRUCT_FLD_8(u8_t  dummy1);
+  PACK_STRUCT_FIELD(u32_t dummy2);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+#  include "arch/epstruct.h"
+#endif
+#define PACKED_STRUCT_TEST_EXPECTED_SIZE 5
+
+#endif
+
+/* 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_DHCP)
+  #error "If you want to use DHCP, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP && LWIP_MULTICAST_TX_OPTIONS)
+  #error "If you want to use IGMP/LWIP_MULTICAST_TX_OPTIONS, 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_IGMP && !LWIP_MULTICAST_TX_OPTIONS)
+  #error "If you want to use IGMP, you have to define LWIP_MULTICAST_TX_OPTIONS==1 in your lwipopts.h"
+#endif
+#if (LWIP_IGMP && !LWIP_IPV4)
+  #error "IGMP needs LWIP_IPV4 enabled in your lwipopts.h"
+#endif
+#if (LWIP_MULTICAST_TX_OPTIONS && !LWIP_IPV4)
+  #error "LWIP_MULTICAST_TX_OPTIONS needs LWIP_IPV4 enabled 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_WND_SCALE
+#if (LWIP_TCP && (TCP_WND > 0xffffffff))
+  #error "If you want to use TCP, TCP_WND must fit in an u32_t, so, you have to reduce it in your lwipopts.h"
+#endif
+#if (LWIP_TCP && (TCP_RCV_SCALE > 14))
+  #error "The maximum valid window scale value is 14!"
+#endif
+#if (LWIP_TCP && (TCP_WND > (0xFFFFU << TCP_RCV_SCALE)))
+  #error "TCP_WND is bigger than the configured LWIP_WND_SCALE allows!"
+#endif
+#if (LWIP_TCP && ((TCP_WND >> TCP_RCV_SCALE) == 0))
+  #error "TCP_WND is too small for the configured LWIP_WND_SCALE (results in zero window)!"
+#endif
+#else /* LWIP_WND_SCALE */
+#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 (or enable window scaling)"
+#endif
+#endif /* LWIP_WND_SCALE */
+#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_PPP_API && (NO_SYS==1))
+  #error "If you want to use PPP API, you have to define NO_SYS=0 in your lwipopts.h"
+#endif
+#if (LWIP_PPP_API && (PPP_SUPPORT==0))
+  #error "If you want to use PPP API, you have to enable PPP_SUPPORT 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_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 && !PPPOL2TP_SUPPORT
+  #error "PPP_SUPPORT needs at least one of PPPOS_SUPPORT, PPPOE_SUPPORT or PPPOL2TP_SUPPORT turned on"
+#endif
+#if PPP_SUPPORT && !PPP_IPV4_SUPPORT && !PPP_IPV6_SUPPORT
+  #error "PPP_SUPPORT needs PPP_IPV4_SUPPORT and/or PPP_IPV6_SUPPORT turned on"
+#endif
+#if PPP_SUPPORT && PPP_IPV4_SUPPORT && !LWIP_IPV4
+  #error "PPP_IPV4_SUPPORT needs LWIP_IPV4 turned on"
+#endif
+#if PPP_SUPPORT && PPP_IPV6_SUPPORT && !LWIP_IPV6
+  #error "PPP_IPV6_SUPPORT needs LWIP_IPV6 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_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 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_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
+#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 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
+#if !NO_SYS && LWIP_TCPIP_CORE_LOCKING && LWIP_COMPAT_MUTEX && !defined(LWIP_COMPAT_MUTEX_ALLOWED)
+  #error "LWIP_COMPAT_MUTEX cannot prevent priority inversion. It is recommended to implement priority-aware mutexes. (Define LWIP_COMPAT_MUTEX_ALLOWED to disable this error.)"
+#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 MEMP_MEM_MALLOC
+#if !LWIP_DISABLE_MEMP_SANITY_CHECKS
+#if LWIP_NETCONN || LWIP_SOCKET
+#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 /* LWIP_NETCONN || LWIP_SOCKET */
+#endif /* !LWIP_DISABLE_MEMP_SANITY_CHECKS */
+#if MEM_USE_POOLS
+#error "MEMP_MEM_MALLOC and MEM_USE_POOLS cannot be enabled at the same time"
+#endif
+#ifdef LWIP_HOOK_MEMP_AVAILABLE
+#error "LWIP_HOOK_MEMP_AVAILABLE doesn't make sense with MEMP_MEM_MALLOC"
+#endif
+#endif /* MEMP_MEM_MALLOC */
+
+/* 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_SNDLOWAT >= (0xFFFF - (4 * TCP_MSS))
+  #error "lwip_sanity_check: WARNING: TCP_SNDLOWAT must at least be 4*MSS below u16_t overflow!"
+#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_SIZE && (PBUF_POOL_BUFSIZE <= (PBUF_LINK_ENCAPSULATION_HLEN + 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 && PBUF_POOL_SIZE && (TCP_WND > (PBUF_POOL_SIZE * (PBUF_POOL_BUFSIZE - (PBUF_LINK_ENCAPSULATION_HLEN + 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 */
+
+/**
+ * @ingroup lwip_nosys
+ * Initialize all modules.
+ * Use this in NO_SYS mode. Use tcpip_init() otherwise.
+ */
+void
+lwip_init(void)
+{
+#ifndef LWIP_SKIP_CONST_CHECK
+  int a = 0;
+  LWIP_UNUSED_ARG(a);
+  LWIP_ASSERT("LWIP_CONST_CAST not implemented correctly. Check your lwIP port.", LWIP_CONST_CAST(void*, &a) == &a);
+#endif
+#ifndef LWIP_SKIP_PACKING_CHECK
+  LWIP_ASSERT("Struct packing not implemented correctly. Check your lwIP port.", sizeof(struct packed_struct_test) == PACKED_STRUCT_TEST_EXPECTED_SIZE);
+#endif
+
+  /* Modules initialization */
+  stats_init();
+#if !NO_SYS
+  sys_init();
+#endif /* !NO_SYS */
+  mem_init();
+  memp_init();
+  pbuf_init();
+  netif_init();
+#if LWIP_IPV4
+  ip_init();
+#if LWIP_ARP
+  etharp_init();
+#endif /* LWIP_ARP */
+#endif /* LWIP_IPV4 */
+#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_IGMP
+  igmp_init();
+#endif /* LWIP_IGMP */
+#if LWIP_DNS
+  dns_init();
+#endif /* LWIP_DNS */
+#if PPP_SUPPORT
+  ppp_init();
+#endif
+ 
+#if LWIP_TIMERS
+  sys_timeouts_init();
+#endif /* LWIP_TIMERS */
+}
diff --git a/src/core/ip.c b/src/core/ip.c
new file mode 100644
index 0000000..2e02408
--- /dev/null
+++ b/src/core/ip.c
@@ -0,0 +1,124 @@
+/**
+ * @file
+ * Common IPv4 and IPv6 code
+ *
+ * @defgroup ip IP
+ * @ingroup callbackstyle_api
+ * 
+ * @defgroup ip4 IPv4
+ * @ingroup ip
+ *
+ * @defgroup ip6 IPv6
+ * @ingroup ip
+ * 
+ * @defgroup ipaddr IP address handling
+ * @ingroup infrastructure
+ * 
+ * @defgroup ip4addr IPv4 only
+ * @ingroup ipaddr
+ * 
+ * @defgroup ip6addr IPv6 only
+ * @ingroup ipaddr
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV4 || LWIP_IPV6
+
+#include "lwip/ip_addr.h"
+#include "lwip/ip.h"
+
+/** Global data for both IPv4 and IPv6 */
+struct ip_globals ip_data;
+
+#if LWIP_IPV4 && LWIP_IPV6
+
+const ip_addr_t ip_addr_any_type = IPADDR_ANY_TYPE_INIT;
+
+/**
+ * @ingroup ipaddr
+ * Convert IP address string (both versions) to numeric.
+ * The version is auto-detected from the string.
+ *
+ * @param cp IP address string to convert
+ * @param addr conversion result is stored here
+ * @return 1 on success, 0 on error
+ */
+int
+ipaddr_aton(const char *cp, ip_addr_t *addr)
+{
+  if (cp != NULL) {
+    const char* c;
+    for (c = cp; *c != 0; c++) {
+      if (*c == ':') {
+        /* contains a colon: IPv6 address */
+        if (addr) {
+          IP_SET_TYPE_VAL(*addr, IPADDR_TYPE_V6);
+        }
+        return ip6addr_aton(cp, ip_2_ip6(addr));
+      } else if (*c == '.') {
+        /* contains a dot: IPv4 address */
+        break;
+      }
+    }
+    /* call ip4addr_aton as fallback or if IPv4 was found */
+    if (addr) {
+      IP_SET_TYPE_VAL(*addr, IPADDR_TYPE_V4);
+    }
+    return ip4addr_aton(cp, ip_2_ip4(addr));
+  }
+  return 0;
+}
+
+/**
+ * @ingroup lwip_nosys
+ * If both IP versions are enabled, this function can dispatch packets to the correct one.
+ * Don't call directly, pass to netif_add() and call netif->input().
+ */
+err_t
+ip_input(struct pbuf *p, struct netif *inp)
+{
+  if (p != NULL) {
+    if (IP_HDR_GET_VERSION(p->payload) == 6) {
+      return ip6_input(p, inp);
+    }
+    return ip4_input(p, inp);
+  }
+  return ERR_VAL;
+}
+
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+#endif /* LWIP_IPV4 || LWIP_IPV6 */
diff --git a/src/core/ipv4/autoip.c b/src/core/ipv4/autoip.c
new file mode 100644
index 0000000..10db8a3
--- /dev/null
+++ b/src/core/ipv4/autoip.c
@@ -0,0 +1,527 @@
+/**
+ * @file
+ * AutoIP Automatic LinkLocal IP Configuration
+ *
+ * This is a AutoIP implementation for the lwIP TCP/IP stack. It aims to conform
+ * with RFC 3927.
+ *
+ * @defgroup autoip AUTOIP
+ * @ingroup ip4
+ * AUTOIP related functions
+ * USAGE:
+ *
+ * define @ref LWIP_AUTOIP 1 in your lwipopts.h
+ * Options:
+ * AUTOIP_TMR_INTERVAL msecs,
+ *   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 @ref LWIP_DHCP_AUTOIP_COOP 1 in your lwipopts.h.
+ * - Configure your DHCP Client.
+ * 
+ * @see netifapi_autoip
+ */
+
+/*
+ *
+ * Copyright (c) 2007 Dominik Spies <kontakt@dspies.de>
+ * 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@dspies.de>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV4 && 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 "lwip/etharp.h"
+#include "lwip/prot/autoip.h"
+
+#include <string.h>
+
+/** 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_data(netif)? netif_autoip_data(netif)->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) \
+  lwip_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 err_t autoip_arp_announce(struct netif *netif);
+static void autoip_start_probing(struct netif *netif);
+
+/**
+ * @ingroup autoip 
+ * 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 autoip (uninitialised) autoip 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_data(netif) == NULL);
+
+  /* clear data structure */
+  memset(autoip, 0, sizeof(struct autoip));
+  /* autoip->state = AUTOIP_STATE_OFF; */
+  netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_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)
+{
+  struct autoip* autoip = netif_autoip_data(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)
+{
+  struct autoip* autoip = netif_autoip_data(netif);
+
+  /* RFC3927, 2.5 "Conflict Detection and Defense" allows two options where
+     a) means retreat on the first conflict and
+     b) allows to keep an already configured address when having only one
+        conflict in 10 seconds
+     We use option b) since it helps to improve the chance that one of the two
+     conflicting hosts may be able to retain its address. */
+
+  if (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"));
+
+    /* Active TCP sessions are aborted when removing the ip addresss */
+    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);
+    autoip->lastconflict = DEFEND_INTERVAL * AUTOIP_TICKS_PER_SECOND;
+  }
+}
+
+/**
+ * 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, ip4_addr_t *ipaddr)
+{
+  struct autoip* autoip = netif_autoip_data(netif);
+
+  /* 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 = lwip_ntohl(LWIP_AUTOIP_CREATE_SEED_ADDR(netif));
+  addr += 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, lwip_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)(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)
+{
+  struct autoip* autoip = netif_autoip_data(netif);
+  /* this works because netif->ip_addr is ANY */
+  return etharp_request(netif, &autoip->llipaddr);
+}
+
+/**
+ * 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_gratuitous(netif);
+}
+
+/**
+ * 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_data(netif);
+  ip4_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_addr(netif, &autoip->llipaddr, &sn_mask, &gw_addr);
+  /* interface is used by routing now that an address is set */
+
+  return ERR_OK;
+}
+
+/**
+ * @ingroup autoip 
+ * 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_data(netif);
+  err_t result = ERR_OK;
+
+  LWIP_ERROR("netif is not up, old style port?", netif_is_up(netif), return ERR_ARG;);
+
+  /* Set IP-Address, Netmask and Gateway to 0 to make sure that
+   * ARP Packets are formed correctly
+   */
+  netif_set_addr(netif, IP4_ADDR_ANY4, IP4_ADDR_ANY4, IP4_ADDR_ANY4);
+
+  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_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_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;
+    ip4_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_data(netif);
+
+  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(&autoip->llipaddr), ip4_addr2_16(&autoip->llipaddr),
+      ip4_addr3_16(&autoip->llipaddr), ip4_addr4_16(&autoip->llipaddr)));
+
+  /* time to wait to first probe, this is randomly
+   * chosen 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
+   * acquiring 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)
+{
+  struct autoip* autoip = netif_autoip_data(netif);
+
+  if (autoip && (autoip->state != AUTOIP_STATE_OFF)) {
+    autoip_start_probing(netif);
+  }
+}
+
+/**
+ * @ingroup autoip 
+ * Stop AutoIP client
+ *
+ * @param netif network interface on which stop the AutoIP client
+ */
+err_t
+autoip_stop(struct netif *netif)
+{
+  struct autoip* autoip = netif_autoip_data(netif);
+
+  if (autoip != NULL) {
+    autoip->state = AUTOIP_STATE_OFF;
+    if (ip4_addr_islinklocal(netif_ip4_addr(netif))) {
+      netif_set_addr(netif, IP4_ADDR_ANY4, IP4_ADDR_ANY4, IP4_ADDR_ANY4);
+    }
+  }
+  return ERR_OK;
+}
+
+/**
+ * Has to be called in loop every AUTOIP_TMR_INTERVAL milliseconds
+ */
+void
+autoip_tmr(void)
+{
+  struct netif *netif = netif_list;
+  /* loop through netif's */
+  while (netif != NULL) {
+    struct autoip* autoip = netif_autoip_data(netif);
+    /* only act on AutoIP configured interfaces */
+    if (autoip != NULL) {
+      if (autoip->lastconflict > 0) {
+        autoip->lastconflict--;
+      }
+
+      LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE,
+        ("autoip_tmr() AutoIP-State: %"U16_F", ttw=%"U16_F"\n",
+        (u16_t)(autoip->state), autoip->ttw));
+
+      if (autoip->ttw > 0) {
+        autoip->ttw--;
+      }
+
+      switch(autoip->state) {
+        case AUTOIP_STATE_PROBING:
+          if (autoip->ttw == 0) {
+            if (autoip->sent_num >= PROBE_NUM) {
+              /* Switch to ANNOUNCING: now we can bind to an IP address and use it */
+              autoip->state = AUTOIP_STATE_ANNOUNCING;
+              autoip_bind(netif);
+              /* autoip_bind() calls netif_set_addr(): this triggers a gratuitous ARP
+                 which counts as an announcement */
+              autoip->sent_num = 1;
+              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(&autoip->llipaddr), ip4_addr2_16(&autoip->llipaddr),
+                  ip4_addr3_16(&autoip->llipaddr), ip4_addr4_16(&autoip->llipaddr)));
+            } else {
+              autoip_arp_probe(netif);
+              LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE, ("autoip_tmr() PROBING Sent Probe\n"));
+              autoip->sent_num++;
+              if (autoip->sent_num == PROBE_NUM) {
+                /* calculate time to wait to for announce */
+                autoip->ttw = ANNOUNCE_WAIT * AUTOIP_TICKS_PER_SECOND;
+              } else {
+                /* calculate time to wait to next probe */
+                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 (autoip->ttw == 0) {
+            autoip_arp_announce(netif);
+            LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE, ("autoip_tmr() ANNOUNCING Sent Announce\n"));
+            autoip->ttw = ANNOUNCE_INTERVAL * AUTOIP_TICKS_PER_SECOND;
+            autoip->sent_num++;
+
+            if (autoip->sent_num >= ANNOUNCE_NUM) {
+                autoip->state = AUTOIP_STATE_BOUND;
+                autoip->sent_num = 0;
+                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(&autoip->llipaddr), ip4_addr2_16(&autoip->llipaddr),
+                     ip4_addr3_16(&autoip->llipaddr), ip4_addr4_16(&autoip->llipaddr)));
+            }
+          }
+          break;
+
+        default:
+          /* nothing to do in other states */
+          break;
+      }
+    }
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+}
+
+/**
+ * Handles every incoming ARP Packet, called by etharp_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)
+{
+  struct autoip* autoip = netif_autoip_data(netif);
+
+  LWIP_DEBUGF(AUTOIP_DEBUG | LWIP_DBG_TRACE, ("autoip_arp_reply()\n"));
+  if ((autoip != NULL) && (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
+    */
+    ip4_addr_t sipaddr, dipaddr;
+    struct eth_addr netifaddr;
+    ETHADDR16_COPY(netifaddr.addr, netif->hwaddr);
+
+    /* Copy struct ip4_addr2 to aligned ip4_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 (autoip->state == AUTOIP_STATE_PROBING) {
+     /* 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 ((ip4_addr_cmp(&sipaddr, &autoip->llipaddr)) ||
+          (ip4_addr_isany_val(sipaddr) &&
+           ip4_addr_cmp(&dipaddr, &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 (ip4_addr_cmp(&sipaddr, &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);
+      }
+    }
+  }
+}
+
+/** check if AutoIP supplied netif->ip_addr
+ *
+ * @param netif the netif to check
+ * @return 1 if AutoIP supplied netif->ip_addr (state BOUND or ANNOUNCING),
+ *         0 otherwise
+ */
+u8_t
+autoip_supplied_address(const struct netif *netif)
+{
+  if ((netif != NULL) && (netif_autoip_data(netif) != NULL)) {
+    struct autoip* autoip = netif_autoip_data(netif);
+    return (autoip->state == AUTOIP_STATE_BOUND) || (autoip->state == AUTOIP_STATE_ANNOUNCING);
+  }
+  return 0;
+}
+
+u8_t
+autoip_accept_packet(struct netif *netif, const ip4_addr_t *addr)
+{
+  struct autoip* autoip = netif_autoip_data(netif);
+  return (autoip != NULL) && ip4_addr_cmp(addr, &(autoip->llipaddr));
+}
+
+#endif /* LWIP_IPV4 && LWIP_AUTOIP */
diff --git a/src/core/ipv4/dhcp.c b/src/core/ipv4/dhcp.c
new file mode 100644
index 0000000..0cf97b0
--- /dev/null
+++ b/src/core/ipv4/dhcp.c
@@ -0,0 +1,1951 @@
+/**
+ * @file
+ * Dynamic Host Configuration Protocol client
+ *
+ * @defgroup dhcp4 DHCPv4
+ * @ingroup ip4
+ * DHCP (IPv4) related functions
+ * 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, ...)
+ *
+ * Options:
+ * @ref DHCP_COARSE_TIMER_SECS (recommended 60 which is a minute)
+ * @ref DHCP_FINE_TIMER_MSECS (recommended 500 which equals TCP coarse timer)
+ *
+ * dhcp_start() starts a DHCP client instance which
+ * configures the interface by obtaining an IP address lease and maintaining it.
+ *
+ * Use dhcp_release() to end the lease and use dhcp_stop()
+ * to remove the DHCP client.
+ *
+ * @see netifapi_dhcp4
+ */
+
+/*
+ * Copyright (c) 2001-2004 Leon Woestenberg <leon.woestenberg@gmx.net>
+ * 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 part of 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@gmx.net>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV4 && 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 "lwip/etharp.h"
+#include "lwip/prot/dhcp.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
+
+#if LWIP_DNS && LWIP_DHCP_MAX_DNS_SERVERS
+#if DNS_MAX_SERVERS > LWIP_DHCP_MAX_DNS_SERVERS
+#define LWIP_DHCP_PROVIDE_DNS_SERVERS LWIP_DHCP_MAX_DNS_SERVERS
+#else
+#define LWIP_DHCP_PROVIDE_DNS_SERVERS DNS_MAX_SERVERS
+#endif
+#else
+#define LWIP_DHCP_PROVIDE_DNS_SERVERS 0
+#endif
+
+/** 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). */
+enum dhcp_option_idx {
+  DHCP_OPTION_IDX_OVERLOAD = 0,
+  DHCP_OPTION_IDX_MSG_TYPE,
+  DHCP_OPTION_IDX_SERVER_ID,
+  DHCP_OPTION_IDX_LEASE_TIME,
+  DHCP_OPTION_IDX_T1,
+  DHCP_OPTION_IDX_T2,
+  DHCP_OPTION_IDX_SUBNET_MASK,
+  DHCP_OPTION_IDX_ROUTER,
+#if LWIP_DHCP_PROVIDE_DNS_SERVERS
+  DHCP_OPTION_IDX_DNS_SERVER,
+  DHCP_OPTION_IDX_DNS_SERVER_LAST = DHCP_OPTION_IDX_DNS_SERVER + LWIP_DHCP_PROVIDE_DNS_SERVERS - 1,
+#endif /* LWIP_DHCP_PROVIDE_DNS_SERVERS */
+#if LWIP_DHCP_GET_NTP_SRV
+  DHCP_OPTION_IDX_NTP_SERVER,
+  DHCP_OPTION_IDX_NTP_SERVER_LAST = DHCP_OPTION_IDX_NTP_SERVER + LWIP_DHCP_MAX_NTP_SERVERS - 1,
+#endif /* LWIP_DHCP_GET_NTP_SRV */
+  DHCP_OPTION_IDX_MAX
+};
+
+/** 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];
+
+static u8_t dhcp_discover_request_options[] = {
+  DHCP_OPTION_SUBNET_MASK,
+  DHCP_OPTION_ROUTER,
+  DHCP_OPTION_BROADCAST
+#if LWIP_DHCP_PROVIDE_DNS_SERVERS
+  , DHCP_OPTION_DNS_SERVER
+#endif /* LWIP_DHCP_PROVIDE_DNS_SERVERS */
+#if LWIP_DHCP_GET_NTP_SRV
+  , DHCP_OPTION_NTP
+#endif /* LWIP_DHCP_GET_NTP_SRV */
+  };
+
+#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))
+
+static struct udp_pcb *dhcp_pcb;
+static u8_t dhcp_pcb_refcount;
+
+/* 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, const 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);
+
+/** Ensure DHCP PCB is allocated and bound */
+static err_t
+dhcp_inc_pcb_refcount(void)
+{
+  if (dhcp_pcb_refcount == 0) {
+    LWIP_ASSERT("dhcp_inc_pcb_refcount(): memory leak", dhcp_pcb == NULL);
+
+    /* allocate UDP PCB */
+    dhcp_pcb = udp_new();
+
+    if (dhcp_pcb == NULL) {
+      return ERR_MEM;
+    }
+
+    ip_set_option(dhcp_pcb, SOF_BROADCAST);
+
+    /* set up local and remote port for the pcb -> listen on all interfaces on all src/dest IPs */
+    udp_bind(dhcp_pcb, IP4_ADDR_ANY, DHCP_CLIENT_PORT);
+    udp_connect(dhcp_pcb, IP4_ADDR_ANY, DHCP_SERVER_PORT);
+    udp_recv(dhcp_pcb, dhcp_recv, NULL);
+  }
+
+  dhcp_pcb_refcount++;
+
+  return ERR_OK;
+}
+
+/** Free DHCP PCB if the last netif stops using it */
+static void
+dhcp_dec_pcb_refcount(void)
+{
+  LWIP_ASSERT("dhcp_pcb_refcount(): refcount error", (dhcp_pcb_refcount > 0));
+  dhcp_pcb_refcount--;
+
+  if (dhcp_pcb_refcount == 0) {
+    udp_remove(dhcp_pcb);
+    dhcp_pcb = NULL;
+  }
+}
+
+/**
+ * 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_data(netif);
+
+  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));
+  /* Change to a defined state - set this before assigning the address
+     to ensure the callback can use dhcp_supplied_address() */
+  dhcp_set_state(dhcp, DHCP_STATE_BACKING_OFF);
+  /* remove IP address from interface (must no longer be used, as per RFC2131) */
+  netif_set_addr(netif, IP4_ADDR_ANY4, IP4_ADDR_ANY4, IP4_ADDR_ANY4);
+  /* 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_data(netif);
+  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_STATE_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"));
+  }
+  if (dhcp->tries < 255) {
+    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_data(netif);
+
+  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)) {
+    ip_addr_set_ip4_u32(&dhcp->server_ip_addr, lwip_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(ip_2_ip4(&dhcp->server_ip_addr))));
+    /* remember offered address */
+    ip4_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_data(netif);
+  err_t result;
+  u16_t msecs;
+  u8_t i;
+
+  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_STATE_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, lwip_ntohl(ip4_addr_get_u32(&dhcp->offered_ip_addr)));
+
+    dhcp_option(dhcp, DHCP_OPTION_SERVER_ID, 4);
+    dhcp_option_long(dhcp, lwip_ntohl(ip4_addr_get_u32(ip_2_ip4(&dhcp->server_ip_addr))));
+
+    dhcp_option(dhcp, DHCP_OPTION_PARAMETER_REQUEST_LIST, LWIP_ARRAYSIZE(dhcp_discover_request_options));
+    for (i = 0; i < LWIP_ARRAYSIZE(dhcp_discover_request_options); i++) {
+      dhcp_option_byte(dhcp, dhcp_discover_request_options[i]);
+    }
+
+#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_src(dhcp_pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif, IP4_ADDR_ANY);
+    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"));
+  }
+  if (dhcp->tries < 255) {
+    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.
+ * Must be called once a minute (see @ref DHCP_COARSE_TIMER_SECS).
+ */
+void
+dhcp_coarse_tmr(void)
+{
+  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 */
+    struct dhcp *dhcp = netif_dhcp_data(netif);
+    if ((dhcp != NULL) && (dhcp->state != DHCP_STATE_OFF)) {
+      /* compare lease time to expire timeout */
+      if (dhcp->t0_timeout && (++dhcp->lease_used == dhcp->t0_timeout)) {
+        LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_coarse_tmr(): t0 timeout\n"));
+        /* this clients' lease time has expired */
+        dhcp_release(netif);
+        dhcp_discover(netif);
+      /* timer is active (non zero), and triggers (zeroes) now? */
+      } else if (dhcp->t2_rebind_time && (dhcp->t2_rebind_time-- == 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 (dhcp->t1_renew_time && (dhcp->t1_renew_time-- == 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 (this function must be called every 500ms,
+ * see @ref DHCP_FINE_TIMER_MSECS).
+ *
+ * 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(void)
+{
+  struct netif *netif = netif_list;
+  /* loop through netif's */
+  while (netif != NULL) {
+    struct dhcp *dhcp = netif_dhcp_data(netif);
+    /* only act on DHCP configured interfaces */
+    if (dhcp != NULL) {
+      /* timer is active (non zero), and is about to trigger now */
+      if (dhcp->request_timeout > 1) {
+        dhcp->request_timeout--;
+      }
+      else if (dhcp->request_timeout == 1) {
+        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_data(netif);
+
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_timeout()\n"));
+  /* back-off period has passed, or server selection timed out */
+  if ((dhcp->state == DHCP_STATE_BACKING_OFF) || (dhcp->state == DHCP_STATE_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_STATE_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_STATE_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 */
+  } else if (dhcp->state == DHCP_STATE_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_data(netif);
+
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_STATE, ("dhcp_t1_timeout()\n"));
+  if ((dhcp->state == DHCP_STATE_REQUESTING) || (dhcp->state == DHCP_STATE_BOUND) ||
+      (dhcp->state == DHCP_STATE_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_STATE_RENEWING, not DHCP_STATE_BOUND */
+    dhcp_renew(netif);
+    /* Calculate next timeout */
+    if (((dhcp->t2_timeout - dhcp->lease_used) / 2) >= ((60 + DHCP_COARSE_TIMER_SECS / 2) / DHCP_COARSE_TIMER_SECS))
+    {
+       dhcp->t1_renew_time = ((dhcp->t2_timeout - dhcp->lease_used) / 2);
+    }
+  }
+}
+
+/**
+ * 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_data(netif);
+
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_t2_timeout()\n"));
+  if ((dhcp->state == DHCP_STATE_REQUESTING) || (dhcp->state == DHCP_STATE_BOUND) ||
+      (dhcp->state == DHCP_STATE_RENEWING) || (dhcp->state == DHCP_STATE_REBINDING)) {
+    /* 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_STATE_REBINDING, not DHCP_STATE_BOUND */
+    dhcp_rebind(netif);
+    /* Calculate next timeout */
+    if (((dhcp->t0_timeout - dhcp->lease_used) / 2) >= ((60 + DHCP_COARSE_TIMER_SECS / 2) / DHCP_COARSE_TIMER_SECS))
+    {
+       dhcp->t2_rebind_time = ((dhcp->t0_timeout - dhcp->lease_used) / 2);
+    }
+  }
+}
+
+/**
+ * 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_data(netif);
+
+#if LWIP_DHCP_PROVIDE_DNS_SERVERS || LWIP_DHCP_GET_NTP_SRV
+  u8_t n;
+#endif /* LWIP_DHCP_PROVIDE_DNS_SERVERS || LWIP_DHCP_GET_NTP_SRV */
+#if LWIP_DHCP_GET_NTP_SRV
+  ip4_addr_t ntp_server_addrs[LWIP_DHCP_MAX_NTP_SERVERS];
+#endif
+
+  /* clear options we might not get from the ACK */
+  ip4_addr_set_zero(&dhcp->offered_sn_mask);
+  ip4_addr_set_zero(&dhcp->offered_gw_addr);
+#if LWIP_DHCP_BOOTP_FILE
+  ip4_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 (offered_t0_lease * 0.875 -> 87.5%)*/
+    dhcp->offered_t2_rebind = (dhcp->offered_t0_lease * 7U) / 8U;
+  }
+
+  /* (y)our internet address */
+  ip4_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 */
+  ip4_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, lwip_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, lwip_htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_ROUTER)));
+  }
+
+#if LWIP_DHCP_GET_NTP_SRV
+  /* NTP servers */
+  for (n = 0; (n < LWIP_DHCP_MAX_NTP_SERVERS) && dhcp_option_given(dhcp, DHCP_OPTION_IDX_NTP_SERVER + n); n++) {
+    ip4_addr_set_u32(&ntp_server_addrs[n], lwip_htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_NTP_SERVER + n)));
+  }
+  dhcp_set_ntp_servers(n, ntp_server_addrs);
+#endif /* LWIP_DHCP_GET_NTP_SRV */
+
+#if LWIP_DHCP_PROVIDE_DNS_SERVERS
+  /* DNS servers */
+  for (n = 0; (n < LWIP_DHCP_PROVIDE_DNS_SERVERS) && dhcp_option_given(dhcp, DHCP_OPTION_IDX_DNS_SERVER + n); n++) {
+    ip_addr_t dns_addr;
+    ip_addr_set_ip4_u32(&dns_addr, lwip_htonl(dhcp_get_option_value(dhcp, DHCP_OPTION_IDX_DNS_SERVER + n)));
+    dns_setserver(n, &dns_addr);
+  }
+#endif /* LWIP_DHCP_PROVIDE_DNS_SERVERS */
+}
+
+/**
+ * @ingroup dhcp4
+ * 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_data(netif) == NULL);
+
+  /* clear data structure */
+  memset(dhcp, 0, sizeof(struct dhcp));
+  /* dhcp_set_state(&dhcp, DHCP_STATE_OFF); */
+  netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP, dhcp);
+}
+
+/**
+ * @ingroup dhcp4
+ * 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_data(netif) != NULL) {
+    mem_free(netif_dhcp_data(netif));
+    netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP, NULL);
+  }
+}
+
+/**
+ * @ingroup dhcp4
+ * 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;
+
+  LWIP_ERROR("netif != NULL", (netif != NULL), return ERR_ARG;);
+  LWIP_ERROR("netif is not up, old style port?", netif_is_up(netif), return ERR_ARG;);
+  dhcp = netif_dhcp_data(netif);
+  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));
+
+  /* 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(): mallocing 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_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_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"));
+    LWIP_ASSERT("pbuf p_out wasn't freed", dhcp->p_out == NULL);
+    LWIP_ASSERT("reply wasn't freed", dhcp->msg_in == NULL );
+
+    if (dhcp->pcb_allocated != 0) {
+      dhcp_dec_pcb_refcount(); /* free DHCP PCB if not needed any more */
+    }
+    /* dhcp is cleared below, no need to reset flag*/
+  }
+
+  /* clear data structure */
+  memset(dhcp, 0, sizeof(struct dhcp));
+  /* dhcp_set_state(&dhcp, DHCP_STATE_OFF); */
+
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_start(): starting DHCP configuration\n"));
+
+  if (dhcp_inc_pcb_refcount() != ERR_OK) { /* ensure DHCP PCB is allocated */
+    return ERR_MEM;
+  }
+  dhcp->pcb_allocated = 1;
+
+#if LWIP_DHCP_CHECK_LINK_UP
+  if (!netif_is_link_up(netif)) {
+    /* set state INIT and wait for dhcp_network_changed() to call dhcp_discover() */
+    dhcp_set_state(dhcp, DHCP_STATE_INIT);
+    return ERR_OK;
+  }
+#endif /* LWIP_DHCP_CHECK_LINK_UP */
+
+
+  /* (re)start the DHCP negotiation */
+  result = dhcp_discover(netif);
+  if (result != ERR_OK) {
+    /* free resources allocated above */
+    dhcp_stop(netif);
+    return ERR_MEM;
+  }
+  return result;
+}
+
+/**
+ * @ingroup dhcp4
+ * 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;
+
+  LWIP_ERROR("netif != NULL", (netif != NULL), return;);
+
+  if (dhcp_inc_pcb_refcount() != ERR_OK) { /* ensure DHCP PCB is allocated */
+    return;
+  }
+
+  memset(&dhcp, 0, sizeof(struct dhcp));
+  dhcp_set_state(&dhcp, DHCP_STATE_INFORMING);
+
+  /* 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(dhcp_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"));
+  }
+
+  dhcp_dec_pcb_refcount(); /* delete DHCP PCB if not needed any more */
+}
+
+/** 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_data(netif);
+
+  if (!dhcp)
+    return;
+  switch (dhcp->state) {
+  case DHCP_STATE_REBINDING:
+  case DHCP_STATE_RENEWING:
+  case DHCP_STATE_BOUND:
+  case DHCP_STATE_REBOOTING:
+    dhcp->tries = 0;
+    dhcp_reboot(netif);
+    break;
+  case DHCP_STATE_OFF:
+    /* stay off */
+    break;
+  default:
+    /* INIT/REQUESTING/CHECKING/BACKING_OFF restart with new 'rid' because the
+       state changes, SELECTING: continue with current 'rid' as we stay in the
+       same state */
+#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 */
+    /* ensure we start with short timeouts, even if already discovering */
+    dhcp->tries = 0;
+    dhcp_discover(netif);
+    break;
+  }
+}
+
+#if DHCP_DOES_ARP_CHECK
+/**
+ * Match an ARP reply with the offered IP address:
+ * check whether the offered IP address is not in use using ARP
+ *
+ * @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, const ip4_addr_t *addr)
+{
+  struct dhcp *dhcp;
+
+  LWIP_ERROR("netif != NULL", (netif != NULL), return;);
+  dhcp = netif_dhcp_data(netif);
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_arp_reply()\n"));
+  /* is a DHCP client doing an ARP check? */
+  if ((dhcp != NULL) && (dhcp->state == DHCP_STATE_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 (ip4_addr_cmp(addr, &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_data(netif);
+  err_t result = ERR_OK;
+  u16_t msecs;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_decline()\n"));
+  dhcp_set_state(dhcp, DHCP_STATE_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, lwip_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_src(dhcp_pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif, IP4_ADDR_ANY);
+    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"));
+  }
+  if (dhcp->tries < 255) {
+    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_data(netif);
+  err_t result = ERR_OK;
+  u16_t msecs;
+  u8_t i;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_discover()\n"));
+  ip4_addr_set_any(&dhcp->offered_ip_addr);
+  dhcp_set_state(dhcp, DHCP_STATE_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, LWIP_ARRAYSIZE(dhcp_discover_request_options));
+    for (i = 0; i < LWIP_ARRAYSIZE(dhcp_discover_request_options); i++) {
+      dhcp_option_byte(dhcp, dhcp_discover_request_options[i]);
+    }
+    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_src(dhcp_pcb, dhcp->p_out, IP_ADDR_BROADCAST, DHCP_SERVER_PORT, netif, IP4_ADDR_ANY);
+    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"));
+  }
+  if (dhcp->tries < 255) {
+    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;
+  ip4_addr_t sn_mask, gw_addr;
+  LWIP_ERROR("dhcp_bind: netif != NULL", (netif != NULL), return;);
+  dhcp = netif_dhcp_data(netif);
+  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));
+
+  /* reset time used of lease */
+  dhcp->lease_used = 0;
+
+  if (dhcp->offered_t0_lease != 0xffffffffUL) {
+     /* set renewal period timer */
+     LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_bind(): t0 renewal timer %"U32_F" secs\n", dhcp->offered_t0_lease));
+     timeout = (dhcp->offered_t0_lease + DHCP_COARSE_TIMER_SECS / 2) / DHCP_COARSE_TIMER_SECS;
+     if (timeout > 0xffff) {
+       timeout = 0xffff;
+     }
+     dhcp->t0_timeout = (u16_t)timeout;
+     if (dhcp->t0_timeout == 0) {
+       dhcp->t0_timeout = 1;
+     }
+     LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_bind(): set request timeout %"U32_F" msecs\n", dhcp->offered_t0_lease*1000));
+  }
+
+  /* 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));
+    dhcp->t1_renew_time = dhcp->t1_timeout;
+  }
+  /* 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));
+    dhcp->t2_rebind_time = dhcp->t2_timeout;
+  }
+
+  /* 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 */
+    ip4_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));
+    }
+  }
+
+  ip4_addr_copy(gw_addr, dhcp->offered_gw_addr);
+  /* gateway address not given? */
+  if (ip4_addr_isany_val(gw_addr)) {
+    /* copy network address */
+    ip4_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" SN: 0x%08"X32_F" GW: 0x%08"X32_F"\n",
+    ip4_addr_get_u32(&dhcp->offered_ip_addr), ip4_addr_get_u32(&sn_mask), ip4_addr_get_u32(&gw_addr)));
+  /* netif is now bound to DHCP leased address - set this before assigning the address
+     to ensure the callback can use dhcp_supplied_address() */
+  dhcp_set_state(dhcp, DHCP_STATE_BOUND);
+
+  netif_set_addr(netif, &dhcp->offered_ip_addr, &sn_mask, &gw_addr);
+  /* interface is used by routing now that an address is set */
+}
+
+/**
+ * @ingroup dhcp4
+ * 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_data(netif);
+  err_t result;
+  u16_t msecs;
+  u8_t i;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_renew()\n"));
+  dhcp_set_state(dhcp, DHCP_STATE_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));
+
+    dhcp_option(dhcp, DHCP_OPTION_PARAMETER_REQUEST_LIST, LWIP_ARRAYSIZE(dhcp_discover_request_options));
+    for (i = 0; i < LWIP_ARRAYSIZE(dhcp_discover_request_options); i++) {
+      dhcp_option_byte(dhcp, dhcp_discover_request_options[i]);
+    }
+
+#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"));
+  }
+  if (dhcp->tries < 255) {
+    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_data(netif);
+  err_t result;
+  u16_t msecs;
+  u8_t i;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_rebind()\n"));
+  dhcp_set_state(dhcp, DHCP_STATE_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));
+
+    dhcp_option(dhcp, DHCP_OPTION_PARAMETER_REQUEST_LIST, LWIP_ARRAYSIZE(dhcp_discover_request_options));
+    for (i = 0; i < LWIP_ARRAYSIZE(dhcp_discover_request_options); i++) {
+      dhcp_option_byte(dhcp, dhcp_discover_request_options[i]);
+    }
+
+#if LWIP_NETIF_HOSTNAME
+    dhcp_option_hostname(dhcp, netif);
+#endif /* LWIP_NETIF_HOSTNAME */
+
+    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"));
+  }
+  if (dhcp->tries < 255) {
+    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_data(netif);
+  err_t result;
+  u16_t msecs;
+  u8_t i;
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("dhcp_reboot()\n"));
+  dhcp_set_state(dhcp, DHCP_STATE_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, DHCP_MAX_MSG_LEN_MIN_REQUIRED);
+
+    dhcp_option(dhcp, DHCP_OPTION_REQUESTED_IP, 4);
+    dhcp_option_long(dhcp, lwip_ntohl(ip4_addr_get_u32(&dhcp->offered_ip_addr)));
+
+    dhcp_option(dhcp, DHCP_OPTION_PARAMETER_REQUEST_LIST, LWIP_ARRAYSIZE(dhcp_discover_request_options));
+    for (i = 0; i < LWIP_ARRAYSIZE(dhcp_discover_request_options); i++) {
+      dhcp_option_byte(dhcp, dhcp_discover_request_options[i]);
+    }
+
+    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"));
+  }
+  if (dhcp->tries < 255) {
+    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;
+}
+
+
+/**
+ * @ingroup dhcp4
+ * Release a DHCP lease (usually called before @ref dhcp_stop).
+ *
+ * @param netif network interface which must release its lease
+ */
+err_t
+dhcp_release(struct netif *netif)
+{
+  struct dhcp *dhcp = netif_dhcp_data(netif);
+  err_t result;
+  ip_addr_t server_ip_addr;
+  u8_t is_dhcp_supplied_address;
+
+  LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_release()\n"));
+  if (dhcp == NULL) {
+    return ERR_ARG;
+  }
+  ip_addr_copy(server_ip_addr, dhcp->server_ip_addr);
+
+  is_dhcp_supplied_address = dhcp_supplied_address(netif);
+
+  /* idle DHCP client */
+  dhcp_set_state(dhcp, DHCP_STATE_OFF);
+  /* clean old DHCP offer */
+  ip_addr_set_zero_ip4(&dhcp->server_ip_addr);
+  ip4_addr_set_zero(&dhcp->offered_ip_addr);
+  ip4_addr_set_zero(&dhcp->offered_sn_mask);
+  ip4_addr_set_zero(&dhcp->offered_gw_addr);
+#if LWIP_DHCP_BOOTP_FILE
+  ip4_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;
+  dhcp->t1_renew_time = dhcp->t2_rebind_time = dhcp->lease_used = dhcp->t0_timeout = 0;
+
+  if (!is_dhcp_supplied_address) {
+    /* don't issue release message when address is not dhcp-assigned */
+    return ERR_OK;
+  }
+
+  /* create and initialize the DHCP message header */
+  result = dhcp_create_msg(netif, dhcp, DHCP_RELEASE);
+  if (result == ERR_OK) {
+    dhcp_option(dhcp, DHCP_OPTION_SERVER_ID, 4);
+    dhcp_option_long(dhcp, lwip_ntohl(ip4_addr_get_u32(ip_2_ip4(&server_ip_addr))));
+
+    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, &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_STATE_OFF\n"));
+  } else {
+    /* sending release failed, but that's not a problem since the correct behaviour of dhcp does not rely on release */
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("dhcp_release: could not allocate DHCP request\n"));
+  }
+  /* remove IP address from interface (prevents routing from selecting this interface) */
+  netif_set_addr(netif, IP4_ADDR_ANY4, IP4_ADDR_ANY4, IP4_ADDR_ANY4);
+
+  return result;
+}
+
+/**
+ * @ingroup dhcp4
+ * 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_data(netif);
+
+  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 */
+
+    LWIP_ASSERT("reply wasn't freed", dhcp->msg_in == NULL);
+    dhcp_set_state(dhcp, DHCP_STATE_OFF);
+
+    if (dhcp->pcb_allocated != 0) {
+      dhcp_dec_pcb_refcount(); /* free DHCP PCB if not needed any more */
+      dhcp->pcb_allocated = 0;
+    }
+  }
+}
+
+/*
+ * 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) {
+      size_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);
+      LWIP_ASSERT("DHCP: hostname is too long!", len <= 0xFF);
+      dhcp_option(dhcp, DHCP_OPTION_HOSTNAME, (u8_t)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 contiguous 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) && (offset < offset_max) && (options[offset] != DHCP_OPTION_END)) {
+    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;
+#if LWIP_DHCP_PROVIDE_DNS_SERVERS
+      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;
+#endif /* LWIP_DHCP_PROVIDE_DNS_SERVERS */
+      case(DHCP_OPTION_LEASE_TIME):
+        LWIP_ERROR("len == 4", len == 4, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_LEASE_TIME;
+        break;
+#if LWIP_DHCP_GET_NTP_SRV
+      case(DHCP_OPTION_NTP):
+        /* special case: there might be more than one server */
+        LWIP_ERROR("len %% 4 == 0", len % 4 == 0, return ERR_VAL;);
+        /* limit number of NTP servers */
+        decode_len = LWIP_MIN(len, 4 * LWIP_DHCP_MAX_NTP_SERVERS);
+        LWIP_ERROR("len >= decode_len", len >= decode_len, return ERR_VAL;);
+        decode_idx = DHCP_OPTION_IDX_NTP_SERVER;
+        break;
+#endif /* LWIP_DHCP_GET_NTP_SRV*/
+      case(DHCP_OPTION_OVERLOAD):
+        LWIP_ERROR("len == 1", len == 1, return ERR_VAL;);
+        /* decode overload only in options, not in file/sname: invalid packet */
+        LWIP_ERROR("overload in file/sname", options_idx == DHCP_OPTIONS_OFS, 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", (u16_t)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);
+        if (pbuf_copy_partial(q, &value, copy_len, val_offset) != copy_len) {
+          return ERR_BUF;
+        }
+        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, lwip_htonl(value));
+          decode_len -= 4;
+          val_offset += 4;
+          decode_idx++;
+          goto decode_next;
+        } else if (decode_len == 4) {
+          value = lwip_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_ERROR("next pbuf was null", q != NULL, return ERR_VAL;);
+        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) */
+      if (pbuf_copy_partial(p, dhcp->boot_file_name, DHCP_FILE_LEN-1, DHCP_FILE_OFS) != (DHCP_FILE_LEN-1)) {
+        return ERR_BUF;
+      }
+      /* 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, const ip_addr_t *addr, u16_t port)
+{
+  struct netif *netif = ip_current_input_netif();
+  struct dhcp *dhcp = netif_dhcp_data(netif);
+  struct dhcp_msg *reply_msg = (struct dhcp_msg *)p->payload;
+  u8_t msg_type;
+  u8_t i;
+
+  LWIP_UNUSED_ARG(arg);
+
+  /* Caught DHCP message from netif that does not have DHCP enabled? -> not interested */
+  if ((dhcp == NULL) || (dhcp->pcb_allocated == 0)) {
+    goto free_pbuf_and_return;
+  }
+
+  LWIP_ASSERT("invalid server address type", IP_IS_V4(addr));
+
+  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(ip_2_ip4(addr)), ip4_addr2_16(ip_2_ip4(addr)), ip4_addr3_16(ip_2_ip4(addr)), ip4_addr4_16(ip_2_ip4(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 < NETIF_MAX_HWADDR_LEN && i < DHCP_CHADDR_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 (lwip_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",lwip_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_STATE_REQUESTING) {
+      dhcp_handle_ack(netif);
+#if DHCP_DOES_ARP_CHECK
+      if ((netif->flags & NETIF_FLAG_ETHARP) != 0) {
+        /* check if the acknowledged lease address is already in use */
+        dhcp_check(netif);
+      } else {
+        /* bind interface to the acknowledged lease address */
+        dhcp_bind(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_STATE_REBOOTING) || (dhcp->state == DHCP_STATE_REBINDING) ||
+             (dhcp->state == DHCP_STATE_RENEWING)) {
+      dhcp_handle_ack(netif);
+      dhcp_bind(netif);
+    }
+  }
+  /* received a DHCP_NAK in appropriate state? */
+  else if ((msg_type == DHCP_NAK) &&
+    ((dhcp->state == DHCP_STATE_REBOOTING) || (dhcp->state == DHCP_STATE_REQUESTING) ||
+     (dhcp->state == DHCP_STATE_REBINDING) || (dhcp->state == DHCP_STATE_RENEWING  ))) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("DHCP_NAK received\n"));
+    dhcp_handle_nak(netif);
+  }
+  /* received a DHCP_OFFER in DHCP_STATE_SELECTING state? */
+  else if ((msg_type == DHCP_OFFER) && (dhcp->state == DHCP_STATE_SELECTING)) {
+    LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("DHCP_OFFER received in DHCP_STATE_SELECTING state\n"));
+    dhcp->request_timeout = 0;
+    /* remember offered lease */
+    dhcp_handle_offer(netif);
+  }
+
+free_pbuf_and_return:
+  if (dhcp != NULL) {
+    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)));
+
+  /* DHCP_REQUEST should reuse 'xid' from DHCPOFFER */
+  if ((message_type != DHCP_REQUEST) || (dhcp->state == DHCP_STATE_REBOOTING)) {
+    /* 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 = lwip_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;
+  ip4_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_RELEASE) ||
+      ((message_type == DHCP_REQUEST) && /* DHCP_STATE_BOUND not used for sending! */
+       ((dhcp->state== DHCP_STATE_RENEWING) || dhcp->state== DHCP_STATE_REBINDING))) {
+    ip4_addr_copy(dhcp->msg_out->ciaddr, *netif_ip4_addr(netif));
+  }
+  ip4_addr_set_zero(&dhcp->msg_out->yiaddr);
+  ip4_addr_set_zero(&dhcp->msg_out->siaddr);
+  ip4_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;
+  }
+}
+
+/** check if DHCP supplied netif->ip_addr
+ *
+ * @param netif the netif to check
+ * @return 1 if DHCP supplied netif->ip_addr (states BOUND or RENEWING),
+ *         0 otherwise
+ */
+u8_t
+dhcp_supplied_address(const struct netif *netif)
+{
+  if ((netif != NULL) && (netif_dhcp_data(netif) != NULL)) {
+    struct dhcp* dhcp = netif_dhcp_data(netif);
+    return (dhcp->state == DHCP_STATE_BOUND) || (dhcp->state == DHCP_STATE_RENEWING) ||
+           (dhcp->state == DHCP_STATE_REBINDING);
+  }
+  return 0;
+}
+
+#endif /* LWIP_IPV4 && LWIP_DHCP */
diff --git a/src/core/ipv4/etharp.c b/src/core/ipv4/etharp.c
new file mode 100644
index 0000000..3f48a99
--- /dev/null
+++ b/src/core/ipv4/etharp.c
@@ -0,0 +1,1206 @@
+/**
+ * @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@axon.tv>
+ * 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/etharp.h"
+#include "lwip/stats.h"
+#include "lwip/snmp.h"
+#include "lwip/dhcp.h"
+#include "lwip/autoip.h"
+#include "netif/ethernet.h"
+
+#include <string.h>
+
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+
+#if LWIP_IPV4 && LWIP_ARP /* don't build if not configured for use in lwipopts.h */
+
+/** 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_UNICAST   (ARP_MAXAGE - 30)
+#define ARP_AGE_REREQUEST_USED_BROADCAST (ARP_MAXAGE - 15)
+
+/** the time an ARP entry stays pending after first request,
+ *  for ARP_TMR_INTERVAL = 1000, this is
+ *  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 5
+
+/** ARP states */
+enum etharp_state {
+  ETHARP_STATE_EMPTY = 0,
+  ETHARP_STATE_PENDING,
+  ETHARP_STATE_STABLE,
+  ETHARP_STATE_STABLE_REREQUESTING_1,
+  ETHARP_STATE_STABLE_REREQUESTING_2
+#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 */
+  ip4_addr_t ipaddr;
+  struct netif *netif;
+  struct eth_addr ethaddr;
+  u16_t ctime;
+  u8_t state;
+};
+
+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
+
+
+static err_t etharp_request_dst(struct netif *netif, const ip4_addr_t *ipaddr, const struct eth_addr* hw_dst_addr);
+static 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 ip4_addr_t *ipsrc_addr,
+                        const struct eth_addr *hwdst_addr, const ip4_addr_t *ipdst_addr,
+                        const u16_t opcode);
+
+#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 */
+  mib2_remove_arp_entry(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;
+  ip4_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 ARP_TMR_INTERVAL milliseconds (1 second),
+ * 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_1) {
+        /* Don't send more than one request every 2 seconds. */
+        arp_table[i].state = ETHARP_STATE_STABLE_REREQUESTING_2;
+      } else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_2) {
+        /* Reset state to stable, so that the next transmitted packet will
+           re-send an ARP request. */
+        arp_table[i].state = ETHARP_STATE_STABLE;
+      } else if (arp_table[i].state == ETHARP_STATE_PENDING) {
+        /* still pending, resend an ARP query */
+        etharp_request(arp_table[i].netif, &arp_table[i].ipaddr);
+      }
+    }
+  }
+}
+
+/**
+ * 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 @ref etharp_state
+ * @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(const ip4_addr_t *ipaddr, u8_t flags, struct netif* netif)
+{
+  s8_t old_pending = ARP_TABLE_SIZE, old_stable = ARP_TABLE_SIZE;
+  s8_t empty = ARP_TABLE_SIZE;
+  u8_t i = 0;
+  /* oldest entry with packets on queue */
+  s8_t old_queue = ARP_TABLE_SIZE;
+  /* its age */
+  u16_t age_queue = 0, age_pending = 0, age_stable = 0;
+
+  LWIP_UNUSED_ARG(netif);
+
+  /**
+   * 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 && ip4_addr_cmp(ipaddr, &arp_table[i].ipaddr)
+#if ETHARP_TABLE_MATCH_NETIF
+          && ((netif == NULL) || (netif == arp_table[i].netif))
+#endif /* ETHARP_TABLE_MATCH_NETIF */
+        ) {
+        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 */
+    ip4_addr_copy(arp_table[i].ipaddr, *ipaddr);
+  }
+  arp_table[i].ctime = 0;
+#if ETHARP_TABLE_MATCH_NETIF
+  arp_table[i].netif = netif;
+#endif /* ETHARP_TABLE_MATCH_NETIF*/
+  return (err_t)i;
+}
+
+/**
+ * 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 @ref etharp_state
+ *
+ * @return
+ * - ERR_OK Successfully 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, const ip4_addr_t *ipaddr, struct eth_addr *ethaddr, u8_t flags)
+{
+  s8_t i;
+  LWIP_ASSERT("netif->hwaddr_len == ETH_HWADDR_LEN", netif->hwaddr_len == ETH_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),
+    (u16_t)ethaddr->addr[0], (u16_t)ethaddr->addr[1], (u16_t)ethaddr->addr[2],
+    (u16_t)ethaddr->addr[3], (u16_t)ethaddr->addr[4], (u16_t)ethaddr->addr[5]));
+  /* non-unicast address? */
+  if (ip4_addr_isany(ipaddr) ||
+      ip4_addr_isbroadcast(ipaddr, netif) ||
+      ip4_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, netif);
+  /* 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 if (arp_table[i].state == ETHARP_STATE_STATIC) {
+    /* found entry is a static type, don't overwrite it */
+    return ERR_VAL;
+  } 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 */
+  mib2_add_arp_entry(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 */
+    ethernet_output(netif, p, (struct eth_addr*)(netif->hwaddr), ethaddr, ETHTYPE_IP);
+    /* 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(const ip4_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),
+    (u16_t)ethaddr->addr[0], (u16_t)ethaddr->addr[1], (u16_t)ethaddr->addr[2],
+    (u16_t)ethaddr->addr[3], (u16_t)ethaddr->addr[4], (u16_t)ethaddr->addr[5]));
+
+  netif = ip4_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(const ip4_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, NULL);
+  /* 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, const ip4_addr_t *ipaddr,
+         struct eth_addr **eth_ret, const ip4_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, netif);
+  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;
+}
+
+/**
+ * Possibility to iterate over stable ARP table entries
+ *
+ * @param i entry number, 0 to ARP_TABLE_SIZE
+ * @param ipaddr return value: IP address
+ * @param netif return value: points to interface
+ * @param eth_ret return value: ETH address
+ * @return 1 on valid index, 0 otherwise
+ */
+u8_t
+etharp_get_entry(u8_t i, ip4_addr_t **ipaddr, struct netif **netif, struct eth_addr **eth_ret)
+{
+  LWIP_ASSERT("ipaddr != NULL", ipaddr != NULL);
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+  LWIP_ASSERT("eth_ret != NULL", eth_ret != NULL);
+
+  if((i < ARP_TABLE_SIZE) && (arp_table[i].state >= ETHARP_STATE_STABLE)) {
+    *ipaddr  = &arp_table[i].ipaddr;
+    *netif   = arp_table[i].netif;
+    *eth_ret = &arp_table[i].ethaddr;
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+/**
+ * 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 p The ARP packet that arrived on netif. Is freed by this function.
+ * @param netif The lwIP network interface on which the ARP packet pbuf arrived.
+ *
+ * @see pbuf_free()
+ */
+void
+etharp_input(struct pbuf *p, struct netif *netif)
+{
+  struct etharp_hdr *hdr;
+  /* these are aligned properly, whereas the ARP header fields might not be */
+  ip4_addr_t sipaddr, dipaddr;
+  u8_t for_us;
+
+  LWIP_ERROR("netif != NULL", (netif != NULL), return;);
+
+  hdr = (struct etharp_hdr *)p->payload;
+
+  /* RFC 826 "Packet Reception": */
+  if ((hdr->hwtype != PP_HTONS(HWTYPE_ETHERNET)) ||
+      (hdr->hwlen != ETH_HWADDR_LEN) ||
+      (hdr->protolen != sizeof(ip4_addr_t)) ||
+      (hdr->proto != PP_HTONS(ETHTYPE_IP)))  {
+    LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING,
+      ("etharp_input: packet dropped, wrong hw type, hwlen, proto, protolen or ethernet type (%"U16_F"/%"U16_F"/%"U16_F"/%"U16_F")\n",
+      hdr->hwtype, (u16_t)hdr->hwlen, hdr->proto, (u16_t)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 continuously 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 ip4_addr2 to aligned ip4_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 (ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+    for_us = 0;
+  } else {
+    /* ARP packet directed to us? */
+    for_us = (u8_t)ip4_addr_cmp(&dipaddr, netif_ip4_addr(netif));
+  }
+
+  /* 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 possibly send out an IP packet that was queued on it. */
+
+    LWIP_DEBUGF (ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: incoming ARP request\n"));
+    /* ARP request for our address? */
+    if (for_us) {
+      /* send ARP response */
+      etharp_raw(netif,
+                 (struct eth_addr *)netif->hwaddr, &hdr->shwaddr,
+                 (struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif),
+                 &hdr->shwaddr, &sipaddr,
+                 ARP_REPLY);
+    /* we are not configured? */
+    } else if (ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+      /* { for_us == 0 and netif->ip_addr.addr == 0 } */
+      LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_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_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_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_input: ARP unknown opcode type %"S16_F"\n", lwip_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) {
+    if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_BROADCAST) {
+      /* issue a standard request using broadcast */
+      if (etharp_request(netif, &arp_table[arp_idx].ipaddr) == ERR_OK) {
+        arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1;
+      }
+    } else if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_UNICAST) {
+      /* issue a unicast request (for 15 seconds) to prevent unnecessary broadcast */
+      if (etharp_request_dst(netif, &arp_table[arp_idx].ipaddr, &arp_table[arp_idx].ethaddr) == ERR_OK) {
+        arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1;
+      }
+    }
+  }
+
+  return ethernet_output(netif, q, (struct eth_addr*)(netif->hwaddr), &arp_table[arp_idx].ethaddr, ETHTYPE_IP);
+}
+
+/**
+ * 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 ethernet_output().
+ */
+err_t
+etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr)
+{
+  const struct eth_addr *dest;
+  struct eth_addr mcastaddr;
+  const ip4_addr_t *dst_addr = ipaddr;
+
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+  LWIP_ASSERT("q != NULL", q != NULL);
+  LWIP_ASSERT("ipaddr != NULL", ipaddr != NULL);
+
+  /* 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 (ip4_addr_isbroadcast(ipaddr, netif)) {
+    /* broadcast on Ethernet also */
+    dest = (const struct eth_addr *)&ethbroadcast;
+  /* multicast destination IP address? */
+  } else if (ip4_addr_ismulticast(ipaddr)) {
+    /* Hash IP multicast address to MAC address.*/
+    mcastaddr.addr[0] = LL_IP4_MULTICAST_ADDR_0;
+    mcastaddr.addr[1] = LL_IP4_MULTICAST_ADDR_1;
+    mcastaddr.addr[2] = LL_IP4_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 (!ip4_addr_netcmp(ipaddr, netif_ip4_addr(netif), netif_ip4_netmask(netif)) &&
+        !ip4_addr_islinklocal(ipaddr)) {
+#if LWIP_AUTOIP
+      struct ip_hdr *iphdr = LWIP_ALIGNMENT_CAST(struct ip_hdr*, q->payload);
+      /* 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 (!ip4_addr_islinklocal(&iphdr->src))
+#endif /* LWIP_AUTOIP */
+      {
+#ifdef LWIP_HOOK_ETHARP_GET_GW
+        /* For advanced routing, a single default gateway might not be enough, so get
+           the IP address of the gateway to handle the current destination address. */
+        dst_addr = LWIP_HOOK_ETHARP_GET_GW(netif, ipaddr);
+        if (dst_addr == NULL)
+#endif /* LWIP_HOOK_ETHARP_GET_GW */
+        {
+          /* interface has default gateway? */
+          if (!ip4_addr_isany_val(*netif_ip4_gw(netif))) {
+            /* send to hardware address of default gateway IP address */
+            dst_addr = netif_ip4_gw(netif);
+          /* 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) &&
+#if ETHARP_TABLE_MATCH_NETIF
+            (arp_table[etharp_cached_entry].netif == netif) &&
+#endif
+            (ip4_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) &&
+#if ETHARP_TABLE_MATCH_NETIF
+          (arp_table[i].netif == netif) &&
+#endif
+          (ip4_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 ethernet_output(netif, q, (struct eth_addr*)(netif->hwaddr), dest, ETHTYPE_IP);
+}
+
+/**
+ * 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, const ip4_addr_t *ipaddr, struct pbuf *q)
+{
+  struct eth_addr * srcaddr = (struct eth_addr *)netif->hwaddr;
+  err_t result = ERR_MEM;
+  int is_new_entry = 0;
+  s8_t i; /* ARP entry index */
+
+  /* non-unicast address? */
+  if (ip4_addr_isbroadcast(ipaddr, netif) ||
+      ip4_addr_ismulticast(ipaddr) ||
+      ip4_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, netif);
+
+  /* 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) {
+    is_new_entry = 1;
+    arp_table[i].state = ETHARP_STATE_PENDING;
+    /* record network interface for re-sending arp request in etharp_tmr */
+    arp_table[i].netif = netif;
+  }
+
+  /* { 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 new entry? or an implicit query request? */
+  if (is_new_entry || (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 = ethernet_output(netif, q, srcaddr, &(arp_table[i].ethaddr), ETHTYPE_IP);
+  /* 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_LINK, 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) {
+        unsigned int qlen = 0;
+        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;
+          qlen++;
+          while (r->next != NULL) {
+            r = r->next;
+            qlen++;
+          }
+          r->next = new_entry;
+        } else {
+          /* queue did not exist, first item in queue */
+          arp_table[i].q = new_entry;
+        }
+#if ARP_QUEUE_LEN
+        if (qlen >= ARP_QUEUE_LEN) {
+          struct etharp_q_entry *old;
+          old = arp_table[i].q;
+          arp_table[i].q = arp_table[i].q->next;
+          pbuf_free(old->p);
+          memp_free(MEMP_ARP_QUEUE, old);
+        }
+#endif
+        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
+ */
+static 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 ip4_addr_t *ipsrc_addr,
+           const struct eth_addr *hwdst_addr, const ip4_addr_t *ipdst_addr,
+           const u16_t opcode)
+{
+  struct pbuf *p;
+  err_t result = ERR_OK;
+  struct etharp_hdr *hdr;
+
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+
+  /* allocate a pbuf for the outgoing ARP request packet */
+  p = pbuf_alloc(PBUF_LINK, SIZEOF_ETHARP_HDR, 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_HDR));
+
+  hdr = (struct etharp_hdr *)p->payload;
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_raw: sending raw ARP packet.\n"));
+  hdr->opcode = lwip_htons(opcode);
+
+  LWIP_ASSERT("netif->hwaddr_len must be the same as ETH_HWADDR_LEN for etharp!",
+              (netif->hwaddr_len == ETH_HWADDR_LEN));
+
+  /* Write the ARP MAC-Addresses */
+  ETHADDR16_COPY(&hdr->shwaddr, hwsrc_addr);
+  ETHADDR16_COPY(&hdr->dhwaddr, hwdst_addr);
+  /* Copy struct ip4_addr2 to aligned ip4_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 = ETH_HWADDR_LEN;
+  hdr->protolen = sizeof(ip4_addr_t);
+
+  /* send ARP query */
+#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) */
+  if(ip4_addr_islinklocal(ipsrc_addr)) {
+    ethernet_output(netif, p, ethsrc_addr, &ethbroadcast, ETHTYPE_ARP);
+  } else
+#endif /* LWIP_AUTOIP */
+  {
+    ethernet_output(netif, p, ethsrc_addr, ethdst_addr, ETHTYPE_ARP);
+  }
+
+  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 to a specific eth address.
+ * Used to send unicast request to refresh the ARP table just before an entry
+ * times out
+ *
+ * @param netif the lwip network interface on which to send the request
+ * @param ipaddr the IP address for which to ask
+ * @param hw_dst_addr the ethernet address to send this packet to
+ * @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
+ */
+static err_t
+etharp_request_dst(struct netif *netif, const ip4_addr_t *ipaddr, const struct eth_addr* hw_dst_addr)
+{
+  return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, hw_dst_addr,
+                    (struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif), &ethzero,
+                    ipaddr, ARP_REQUEST);
+}
+
+/**
+ * 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, const ip4_addr_t *ipaddr)
+{
+  LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_request: sending ARP request.\n"));
+  return etharp_request_dst(netif, ipaddr, &ethbroadcast);
+}
+#endif /* LWIP_IPV4 && LWIP_ARP */
+
+#endif /* LWIP_ARP || LWIP_ETHERNET */
diff --git a/src/core/ipv4/icmp.c b/src/core/ipv4/icmp.c
new file mode 100644
index 0000000..5ee24ee
--- /dev/null
+++ b/src/core/ipv4/icmp.c
@@ -0,0 +1,397 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+/* Some ICMP messages should be passed to the transport protocols. This
+   is not implemented. */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV4 && 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 <string.h>
+
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+
+/** 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;
+  const struct ip_hdr *iphdr_in;
+  u16_t hlen;
+  const ip4_addr_t* src;
+
+  ICMP_STATS_INC(icmp.recv);
+  MIB2_STATS_INC(mib2.icmpinmsgs);
+
+  iphdr_in = ip4_current_header();
+  hlen = IPH_HL(iphdr_in) * 4;
+  if (hlen < IP_HLEN) {
+    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: short IP header (%"S16_F" bytes) received\n", hlen));
+    goto lenerr;
+  }
+  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). */
+    MIB2_STATS_INC(mib2.icmpinechoreps);
+    break;
+  case ICMP_ECHO:
+    MIB2_STATS_INC(mib2.icmpinechos);
+    src = ip4_current_dest_addr();
+    /* multicast destination address? */
+    if (ip4_addr_ismulticast(ip4_current_dest_addr())) {
+#if LWIP_MULTICAST_PING
+      /* For multicast, use address of receiving interface as source address */
+      src = netif_ip4_addr(inp);
+#else /* LWIP_MULTICAST_PING */
+      LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: Not echoing to multicast pings\n"));
+      goto icmperr;
+#endif /* LWIP_MULTICAST_PING */
+    }
+    /* broadcast destination address? */
+    if (ip4_addr_isbroadcast(ip4_current_dest_addr(), ip_current_netif())) {
+#if LWIP_BROADCAST_PING
+      /* For broadcast, use address of receiving interface as source address */
+      src = netif_ip4_addr(inp);
+#else /* LWIP_BROADCAST_PING */
+      LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: Not echoing to broadcast pings\n"));
+      goto icmperr;
+#endif /* 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 CHECKSUM_CHECK_ICMP
+    IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_ICMP) {
+      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);
+        MIB2_STATS_INC(mib2.icmpinerrors);
+        return;
+      }
+    }
+#endif
+#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
+    if (pbuf_header(p, (s16_t)(hlen + PBUF_LINK_HLEN + PBUF_LINK_ENCAPSULATION_HLEN))) {
+      /* p is not big enough to contain link headers
+       * allocate a new one and copy p into it
+       */
+      struct pbuf *r;
+      /* allocate new packet buffer with space for link headers */
+      r = pbuf_alloc(PBUF_LINK, p->tot_len + hlen, PBUF_RAM);
+      if (r == NULL) {
+        LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: allocating new pbuf failed\n"));
+        goto icmperr;
+      }
+      if (r->len < hlen + sizeof(struct icmp_echo_hdr)) {
+        LWIP_DEBUGF(ICMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("first pbuf cannot hold the ICMP header"));
+        pbuf_free(r);
+        goto icmperr;
+      }
+      /* copy the ip header */
+      MEMCPY(r->payload, iphdr_in, hlen);
+      /* switch r->payload back to icmp header (cannot fail) */
+      if (pbuf_header(r, (s16_t)-hlen)) {
+        LWIP_ASSERT("icmp_input: moving r->payload to icmp header failed\n", 0);
+        pbuf_free(r);
+        goto icmperr;
+      }
+      /* copy the rest of the packet without ip header */
+      if (pbuf_copy(r, p) != ERR_OK) {
+        LWIP_DEBUGF(ICMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("icmp_input: copying to new pbuf failed"));
+        pbuf_free(r);
+        goto icmperr;
+      }
+      /* 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 (cannot fail) */
+      if (pbuf_header(p, -(s16_t)(hlen + PBUF_LINK_HLEN + PBUF_LINK_ENCAPSULATION_HLEN))) {
+        LWIP_ASSERT("icmp_input: restoring original p->payload failed\n", 0);
+        goto icmperr;
+      }
+    }
+#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;
+    if (pbuf_header(p, (s16_t)hlen)) {
+      LWIP_DEBUGF(ICMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("Can't move over header in packet"));
+    } else {
+      err_t ret;
+      struct ip_hdr *iphdr = (struct ip_hdr*)p->payload;
+      ip4_addr_copy(iphdr->src, *src);
+      ip4_addr_copy(iphdr->dest, *ip4_current_src_addr());
+      ICMPH_TYPE_SET(iecho, ICMP_ER);
+#if CHECKSUM_GEN_ICMP
+      IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_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);
+        }
+      }
+#if LWIP_CHECKSUM_CTRL_PER_NETIF
+      else {
+        iecho->chksum = 0;
+      }
+#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF */
+#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
+      IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_IP) {
+        IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, hlen));
+      }
+#endif /* CHECKSUM_GEN_IP */
+
+      ICMP_STATS_INC(icmp.xmit);
+      /* increase number of messages attempted to send */
+      MIB2_STATS_INC(mib2.icmpoutmsgs);
+      /* increase number of echo replies attempted to send */
+      MIB2_STATS_INC(mib2.icmpoutechoreps);
+
+      /* send an ICMP packet */
+      ret = ip4_output_if(p, src, LWIP_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: %s\n", lwip_strerr(ret)));
+      }
+    }
+    break;
+  default:
+    if (type == ICMP_DUR) {
+      MIB2_STATS_INC(mib2.icmpindestunreachs);
+    } else if (type == ICMP_TE) {
+      MIB2_STATS_INC(mib2.icmpintimeexcds);
+    } else if (type == ICMP_PP) {
+      MIB2_STATS_INC(mib2.icmpinparmprobs);
+    } else if (type == ICMP_SQ) {
+      MIB2_STATS_INC(mib2.icmpinsrcquenchs);
+    } else if (type == ICMP_RD) {
+      MIB2_STATS_INC(mib2.icmpinredirects);
+    } else if (type == ICMP_TS) {
+      MIB2_STATS_INC(mib2.icmpintimestamps);
+    } else if (type == ICMP_TSR) {
+      MIB2_STATS_INC(mib2.icmpintimestampreps);
+    } else if (type == ICMP_AM) {
+      MIB2_STATS_INC(mib2.icmpinaddrmasks);
+    } else if (type == ICMP_AMR) {
+      MIB2_STATS_INC(mib2.icmpinaddrmaskreps);
+    }
+    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);
+  MIB2_STATS_INC(mib2.icmpinerrors);
+  return;
+#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN || !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING
+icmperr:
+  pbuf_free(p);
+  ICMP_STATS_INC(icmp.err);
+  MIB2_STATS_INC(mib2.icmpinerrors);
+  return;
+#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN || !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING */
+}
+
+/**
+ * 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)
+{
+  MIB2_STATS_INC(mib2.icmpoutdestunreachs);
+  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)
+{
+  MIB2_STATS_INC(mib2.icmpouttimeexcds);
+  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;
+  ip4_addr_t iphdr_src;
+  struct netif *netif;
+
+  /* increase number of messages attempted to send */
+  MIB2_STATS_INC(mib2.icmpoutmsgs);
+
+  /* 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"));
+    MIB2_STATS_INC(mib2.icmpouterrors);
+    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 "));
+  ip4_addr_debug_print_val(ICMP_DEBUG, iphdr->src);
+  LWIP_DEBUGF(ICMP_DEBUG, (" to "));
+  ip4_addr_debug_print_val(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);
+
+  ip4_addr_copy(iphdr_src, iphdr->src);
+#ifdef LWIP_HOOK_IP4_ROUTE_SRC
+  {
+    ip4_addr_t iphdr_dst;
+    ip4_addr_copy(iphdr_dst, iphdr->dest);
+    netif = ip4_route_src(&iphdr_src, &iphdr_dst);
+  }
+#else
+  netif = ip4_route(&iphdr_src);
+#endif
+  if (netif != NULL) {
+    /* calculate checksum */
+    icmphdr->chksum = 0;
+#if CHECKSUM_GEN_ICMP
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP) {
+      icmphdr->chksum = inet_chksum(icmphdr, q->len);
+    }
+#endif
+    ICMP_STATS_INC(icmp.xmit);
+    ip4_output_if(q, NULL, &iphdr_src, ICMP_TTL, 0, IP_PROTO_ICMP, netif);
+  }
+  pbuf_free(q);
+}
+
+#endif /* LWIP_IPV4 && LWIP_ICMP */
diff --git a/src/core/ipv4/igmp.c b/src/core/ipv4/igmp.c
new file mode 100644
index 0000000..74a6c37
--- /dev/null
+++ b/src/core/ipv4/igmp.c
@@ -0,0 +1,800 @@
+/**
+ * @file
+ * IGMP - Internet Group Management Protocol
+ *
+ * @defgroup igmp IGMP
+ * @ingroup ip4
+ * To be called from TCPIP thread
+ */
+
+/*
+ * 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_IPV4 && 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/stats.h"
+#include "lwip/prot/igmp.h"
+
+#include "string.h"
+
+static struct igmp_group *igmp_lookup_group(struct netif *ifp, const ip4_addr_t *addr);
+static err_t  igmp_remove_group(struct netif* netif, struct igmp_group *group);
+static void   igmp_timeout(struct netif *netif, 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, const ip4_addr_t *src, const ip4_addr_t *dest, struct netif *netif);
+static void   igmp_send(struct netif *netif, struct igmp_group *group, u8_t type);
+
+static ip4_addr_t     allsystems;
+static ip4_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);
+}
+
+/**
+ * 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", (void*)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 "));
+      ip4_addr_debug_print_val(IGMP_DEBUG, allsystems);
+      LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", (void*)netif));
+      netif->igmp_mac_filter(netif, &allsystems, NETIF_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 = netif_igmp_data(netif);
+
+  netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_IGMP, NULL);
+
+  while (group != NULL) {
+    struct igmp_group *next = group->next; /* avoid use-after-free below */
+
+    /* disable the group at the MAC level */
+    if (netif->igmp_mac_filter != NULL) {
+      LWIP_DEBUGF(IGMP_DEBUG, ("igmp_stop: igmp_mac_filter(DEL "));
+      ip4_addr_debug_print(IGMP_DEBUG, &group->group_address);
+      LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", (void*)netif));
+      netif->igmp_mac_filter(netif, &(group->group_address), NETIF_DEL_MAC_FILTER);
+    }
+
+    /* free group */
+    memp_free(MEMP_IGMP_GROUP, 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 = netif_igmp_data(netif);
+
+  LWIP_DEBUGF(IGMP_DEBUG, ("igmp_report_groups: sending IGMP reports on if %p\n", (void*)netif));
+
+  /* Skip the first group in the list, it is always the allsystems group added in igmp_start() */
+  if(group != NULL) {
+    group = group->next;
+  }
+  
+  while (group != NULL) {
+    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, const ip4_addr_t *addr)
+{
+  struct igmp_group *group = netif_igmp_data(ifp);
+
+  while (group != NULL) {
+    if (ip4_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.
+ */
+static struct igmp_group *
+igmp_lookup_group(struct netif *ifp, const ip4_addr_t *addr)
+{
+  struct igmp_group *group;
+  struct igmp_group *list_head = netif_igmp_data(ifp);
+
+  /* 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) {
+    ip4_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;
+
+    /* Ensure allsystems group is always first in list */    
+    if (list_head == NULL) {
+      /* this is the first entry in linked list */
+      LWIP_ASSERT("igmp_lookup_group: first group must be allsystems",
+        (ip4_addr_cmp(addr, &allsystems) != 0));
+      group->next = NULL;
+      netif_set_client_data(ifp, LWIP_NETIF_CLIENT_DATA_INDEX_IGMP, group);
+    } else {
+      /* append _after_ first entry */
+      LWIP_ASSERT("igmp_lookup_group: all except first group must not be allsystems",
+        (ip4_addr_cmp(addr, &allsystems) == 0));
+      group->next = list_head->next;
+      list_head->next = group;
+    }
+  }
+
+  LWIP_DEBUGF(IGMP_DEBUG, ("igmp_lookup_group: %sallocated a new group with address ", (group?"":"impossible to ")));
+  ip4_addr_debug_print(IGMP_DEBUG, addr);
+  LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", (void*)ifp));
+
+  return group;
+}
+
+/**
+ * Remove a group in the global igmp_group_list, but don't free it yet
+ *
+ * @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 netif* netif, struct igmp_group *group)
+{
+  err_t err = ERR_OK;
+  struct igmp_group *tmp_group;
+
+  /* Skip the first group in the list, it is always the allsystems group added in igmp_start() */
+  for (tmp_group = netif_igmp_data(netif); tmp_group != NULL; tmp_group = tmp_group->next) {
+    if (tmp_group->next == group) {
+      tmp_group->next = group->next;
+      break;
+    }
+  }
+  /* Group not found in the global igmp_group_list */
+  if (tmp_group == NULL) {
+    err = ERR_ARG;
+  }
+
+  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, const ip4_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 "));
+  ip4_addr_debug_print(IGMP_DEBUG, &(ip4_current_header()->src));
+  LWIP_DEBUGF(IGMP_DEBUG, (" to address "));
+  ip4_addr_debug_print(IGMP_DEBUG, &(ip4_current_header()->dest));
+  LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", (void*)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 ((ip4_addr_cmp(dest, &allsystems)) && ip4_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 = netif_igmp_data(inp);
+      
+      /* Do not send messages on the all systems group address! */
+      /* Skip the first group in the list, it is always the allsystems group added in igmp_start() */
+      if(groupref != NULL) {
+        groupref = groupref->next;
+      }
+
+      while (groupref) {
+        igmp_delaying_member(groupref, igmp->igmp_maxresp);
+        groupref = groupref->next;
+      }
+    } else {
+      /* IGMP_MEMB_QUERY to a specific group ? */
+      if (!ip4_addr_isany(&igmp->igmp_group_address)) {
+        LWIP_DEBUGF(IGMP_DEBUG, ("igmp_input: IGMP_MEMB_QUERY to a specific group "));
+        ip4_addr_debug_print(IGMP_DEBUG, &igmp->igmp_group_address);
+        if (ip4_addr_cmp(dest, &allsystems)) {
+          ip4_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 */
+          ip4_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, (void*)&group, (void*)inp));
+    IGMP_STATS_INC(igmp.proterr);
+    break;
+  }
+
+  pbuf_free(p);
+  return;
+}
+
+/**
+ * @ingroup igmp
+ * 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(const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr)
+{
+  err_t err = ERR_VAL; /* no matching interface */
+  struct netif *netif;
+
+  /* make sure it is multicast address */
+  LWIP_ERROR("igmp_joingroup: attempt to join non-multicast address", ip4_addr_ismulticast(groupaddr), return ERR_VAL;);
+  LWIP_ERROR("igmp_joingroup: attempt to join allsystems address", (!ip4_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) && ((ip4_addr_isany(ifaddr) || ip4_addr_cmp(netif_ip4_addr(netif), ifaddr)))) {
+      err = igmp_joingroup_netif(netif, groupaddr);
+      if (err != ERR_OK) {
+        /* Return an error even if some network interfaces are joined */
+        /** @todo undo any other netif already joined */
+        return err;
+      }
+    }
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+
+  return err;
+}
+
+/**
+ * @ingroup igmp
+ * Join a group on one network interface.
+ *
+ * @param netif 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, an err_t otherwise
+ */
+err_t
+igmp_joingroup_netif(struct netif *netif, const ip4_addr_t *groupaddr)
+{
+  struct igmp_group *group;
+
+  /* make sure it is multicast address */
+  LWIP_ERROR("igmp_joingroup_netif: attempt to join non-multicast address", ip4_addr_ismulticast(groupaddr), return ERR_VAL;);
+  LWIP_ERROR("igmp_joingroup_netif: attempt to join allsystems address", (!ip4_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;);
+
+  /* make sure it is an igmp-enabled netif */
+  LWIP_ERROR("igmp_joingroup_netif: attempt to join on non-IGMP netif", netif->flags & NETIF_FLAG_IGMP, return ERR_VAL;);
+
+  /* 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_netif: join to group not in state IGMP_GROUP_NON_MEMBER\n"));
+    } else {
+      /* OK - it was new group */
+      LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup_netif: join to new group: "));
+      ip4_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_netif: igmp_mac_filter(ADD "));
+        ip4_addr_debug_print(IGMP_DEBUG, groupaddr);
+        LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", (void*)netif));
+        netif->igmp_mac_filter(netif, groupaddr, NETIF_ADD_MAC_FILTER);
+      }
+
+      IGMP_STATS_INC(igmp.tx_join);
+      igmp_send(netif, 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 */
+    return ERR_OK;
+  } else {
+    LWIP_DEBUGF(IGMP_DEBUG, ("igmp_joingroup_netif: Not enough memory to join to group\n"));
+    return ERR_MEM;
+  }
+}
+
+/**
+ * @ingroup igmp
+ * 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(const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr)
+{
+  err_t err = ERR_VAL; /* no matching interface */
+  struct netif *netif;
+
+  /* make sure it is multicast address */
+  LWIP_ERROR("igmp_leavegroup: attempt to leave non-multicast address", ip4_addr_ismulticast(groupaddr), return ERR_VAL;);
+  LWIP_ERROR("igmp_leavegroup: attempt to leave allsystems address", (!ip4_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) && ((ip4_addr_isany(ifaddr) || ip4_addr_cmp(netif_ip4_addr(netif), ifaddr)))) {
+      err_t res = igmp_leavegroup_netif(netif, groupaddr);
+      if (err != ERR_OK) {
+        /* Store this result if we have not yet gotten a success */
+        err = res;
+      }
+    }
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+
+  return err;
+}
+
+/**
+ * @ingroup igmp
+ * Leave a group on one network interface.
+ *
+ * @param netif 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, an err_t otherwise
+ */
+err_t
+igmp_leavegroup_netif(struct netif *netif, const ip4_addr_t *groupaddr)
+{
+  struct igmp_group *group;
+
+  /* make sure it is multicast address */
+  LWIP_ERROR("igmp_leavegroup_netif: attempt to leave non-multicast address", ip4_addr_ismulticast(groupaddr), return ERR_VAL;);
+  LWIP_ERROR("igmp_leavegroup_netif: attempt to leave allsystems address", (!ip4_addr_cmp(groupaddr, &allsystems)), return ERR_VAL;);
+
+  /* make sure it is an igmp-enabled netif */
+  LWIP_ERROR("igmp_leavegroup_netif: attempt to leave on non-IGMP netif", netif->flags & NETIF_FLAG_IGMP, return ERR_VAL;);
+
+  /* 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_netif: Leaving group: "));
+    ip4_addr_debug_print(IGMP_DEBUG, groupaddr);
+    LWIP_DEBUGF(IGMP_DEBUG, ("\n"));
+
+    /* If there is no other use of the group */
+    if (group->use <= 1) {
+      /* Remove the group from the list */
+      igmp_remove_group(netif, group);
+
+      /* If we are the last reporter for this group */
+      if (group->last_reporter_flag) {
+        LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup_netif: sending leaving group\n"));
+        IGMP_STATS_INC(igmp.tx_leave);
+        igmp_send(netif, group, IGMP_LEAVE_GROUP);
+      }
+
+      /* Disable the group at the MAC level */
+      if (netif->igmp_mac_filter != NULL) {
+        LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup_netif: igmp_mac_filter(DEL "));
+        ip4_addr_debug_print(IGMP_DEBUG, groupaddr);
+        LWIP_DEBUGF(IGMP_DEBUG, (") on if %p\n", (void*)netif));
+        netif->igmp_mac_filter(netif, groupaddr, NETIF_DEL_MAC_FILTER);
+      }
+
+      /* Free group struct */
+      memp_free(MEMP_IGMP_GROUP, group);
+    } else {
+      /* Decrement group use */
+      group->use--;
+    }
+    return ERR_OK;
+  } else {
+    LWIP_DEBUGF(IGMP_DEBUG, ("igmp_leavegroup_netif: not member of group\n"));
+    return ERR_VAL;
+  }
+}
+
+/**
+ * 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 netif *netif = netif_list;
+
+  while (netif != NULL) {
+    struct igmp_group *group = netif_igmp_data(netif);
+
+    while (group != NULL) {
+      if (group->timer > 0) {
+        group->timer--;
+        if (group->timer == 0) {
+          igmp_timeout(netif, group);
+        }
+      }
+      group = group->next;
+    }
+    netif = netif->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 netif *netif, struct igmp_group *group)
+{
+  /* If the state is IGMP_GROUP_DELAYING_MEMBER then we send a report for this group
+     (unless it is the allsystems group) */
+  if ((group->group_state == IGMP_GROUP_DELAYING_MEMBER) &&
+      (!(ip4_addr_cmp(&(group->group_address), &allsystems)))) {
+    LWIP_DEBUGF(IGMP_DEBUG, ("igmp_timeout: report membership for group with address "));
+    ip4_addr_debug_print(IGMP_DEBUG, &(group->group_address));
+    LWIP_DEBUGF(IGMP_DEBUG, (" on if %p\n", (void*)netif));
+
+    group->group_state = IGMP_GROUP_IDLE_MEMBER;
+    
+    IGMP_STATS_INC(igmp.tx_report);
+    igmp_send(netif, 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)
+{
+#ifdef LWIP_RAND
+  group->timer = max_time > 2 ? (LWIP_RAND() % max_time) : 1;
+#else /* LWIP_RAND */
+  /* ATTENTION: use this only if absolutely necessary! */
+  group->timer = max_time / 2;
+#endif /* LWIP_RAND */
+
+  if (group->timer == 0) {
+    group->timer = 1;
+  }
+}
+
+/**
+ * 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 == LWIP_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 == IP4_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 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, const ip4_addr_t *src, const ip4_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 ip4_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 netif *netif, struct igmp_group *group, u8_t type)
+{
+  struct pbuf*     p    = NULL;
+  struct igmp_msg* igmp = NULL;
+  ip4_addr_t   src  = *IP4_ADDR_ANY4;
+  ip4_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)));
+    ip4_addr_copy(src, *netif_ip4_addr(netif));
+
+    if (type == IGMP_V2_MEMB_REPORT) {
+      dest = &(group->group_address);
+      ip4_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;
+        ip4_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, 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_IPV4 && LWIP_IGMP */
diff --git a/src/core/ipv4/ip4.c b/src/core/ipv4/ip4.c
new file mode 100644
index 0000000..4e4eb61
--- /dev/null
+++ b/src/core/ipv4/ip4.c
@@ -0,0 +1,1086 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV4
+
+#include "lwip/ip.h"
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/ip4_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/priv/tcp_priv.h"
+#include "lwip/autoip.h"
+#include "lwip/stats.h"
+#include "lwip/prot/dhcp.h"
+
+#include <string.h>
+
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+
+/** 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
+#if LWIP_CHECKSUM_CTRL_PER_NETIF
+#define LWIP_INLINE_IP_CHKSUM   0
+#else /* LWIP_CHECKSUM_CTRL_PER_NETIF */
+#define LWIP_INLINE_IP_CHKSUM   1
+#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF */
+#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(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 */
+
+/** The IP header ID of the next outgoing IP packet */
+static u16_t ip_id;
+
+#if LWIP_MULTICAST_TX_OPTIONS
+/** The default netif used for multicast */
+static struct netif* ip4_default_multicast_netif;
+
+/**
+ * @ingroup ip4
+ * Set a default netif for IPv4 multicast. */
+void
+ip4_set_default_multicast_netif(struct netif* default_multicast_netif)
+{
+  ip4_default_multicast_netif = default_multicast_netif;
+}
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
+
+#ifdef LWIP_HOOK_IP4_ROUTE_SRC
+/**
+ * Source based IPv4 routing must be fully implemented in
+ * LWIP_HOOK_IP4_ROUTE_SRC(). This function only provides he parameters.
+ */
+struct netif *
+ip4_route_src(const ip4_addr_t *dest, const ip4_addr_t *src)
+{
+  if (src != NULL) {
+    /* when src==NULL, the hook is called from ip4_route(dest) */
+    struct netif *netif = LWIP_HOOK_IP4_ROUTE_SRC(dest, src);
+    if (netif != NULL) {
+      return netif;
+    }
+  }
+  return ip4_route(dest);
+}
+#endif /* LWIP_HOOK_IP4_ROUTE_SRC */
+
+/**
+ * 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 *
+ip4_route(const ip4_addr_t *dest)
+{
+  struct netif *netif;
+
+#if LWIP_MULTICAST_TX_OPTIONS
+  /* Use administratively selected interface for multicast by default */
+  if (ip4_addr_ismulticast(dest) && ip4_default_multicast_netif) {
+    return ip4_default_multicast_netif;
+  }
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
+
+  /* iterate through netifs */
+  for (netif = netif_list; netif != NULL; netif = netif->next) {
+    /* is the netif up, does it have a link and a valid address? */
+    if (netif_is_up(netif) && netif_is_link_up(netif) && !ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+      /* network mask matches? */
+      if (ip4_addr_netcmp(dest, netif_ip4_addr(netif), netif_ip4_netmask(netif))) {
+        /* return netif on which to forward IP packet */
+        return netif;
+      }
+      /* gateway matches on a non broadcast interface? (i.e. peer in a point to point interface) */
+      if (((netif->flags & NETIF_FLAG_BROADCAST) == 0) && ip4_addr_cmp(dest, netif_ip4_gw(netif))) {
+        /* return netif on which to forward IP packet */
+        return netif;
+      }
+    }
+  }
+
+#if LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF
+  /* loopif is disabled, looopback traffic is passed through any netif */
+  if (ip4_addr_isloopback(dest)) {
+    /* don't check for link on loopback traffic */
+    if (netif_default != NULL && netif_is_up(netif_default)) {
+      return netif_default;
+    }
+    /* default netif is not up, just use any netif for loopback traffic */
+    for (netif = netif_list; netif != NULL; netif = netif->next) {
+      if (netif_is_up(netif)) {
+        return netif;
+      }
+    }
+    return NULL;
+  }
+#endif /* LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF */
+
+#ifdef LWIP_HOOK_IP4_ROUTE_SRC
+  netif = LWIP_HOOK_IP4_ROUTE_SRC(dest, NULL);
+  if (netif != NULL) {
+    return netif;
+  }
+#elif defined(LWIP_HOOK_IP4_ROUTE)
+  netif = LWIP_HOOK_IP4_ROUTE(dest);
+  if (netif != NULL) {
+    return netif;
+  }
+#endif
+
+  if ((netif_default == NULL) || !netif_is_up(netif_default) || !netif_is_link_up(netif_default) ||
+      ip4_addr_isany_val(*netif_ip4_addr(netif_default))) {
+    /* No matching netif found and default netif is not usable.
+       If this is not good enough for you, use LWIP_HOOK_IP4_ROUTE() */
+    LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ip4_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);
+    MIB2_STATS_INC(mib2.ipoutnoroutes);
+    return NULL;
+  }
+
+  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
+ * @return 1: can forward 0: discard
+ */
+static int
+ip4_canforward(struct pbuf *p)
+{
+  u32_t addr = lwip_htonl(ip4_addr_get_u32(ip4_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
+ip4_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp)
+{
+  struct netif *netif;
+
+  PERF_START;
+  LWIP_UNUSED_ARG(inp);
+
+  if (!ip4_canforward(p)) {
+    goto return_noroute;
+  }
+
+  /* RFC3927 2.7: do not forward link-local addresses */
+  if (ip4_addr_islinklocal(ip4_current_dest_addr())) {
+    LWIP_DEBUGF(IP_DEBUG, ("ip4_forward: not forwarding LLA %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+      ip4_addr1_16(ip4_current_dest_addr()), ip4_addr2_16(ip4_current_dest_addr()),
+      ip4_addr3_16(ip4_current_dest_addr()), ip4_addr4_16(ip4_current_dest_addr())));
+    goto return_noroute;
+  }
+
+  /* Find network interface where to forward this IP packet to. */
+  netif = ip4_route_src(ip4_current_dest_addr(), ip4_current_src_addr());
+  if (netif == NULL) {
+    LWIP_DEBUGF(IP_DEBUG, ("ip4_forward: no forwarding route for %"U16_F".%"U16_F".%"U16_F".%"U16_F" found\n",
+      ip4_addr1_16(ip4_current_dest_addr()), ip4_addr2_16(ip4_current_dest_addr()),
+      ip4_addr3_16(ip4_current_dest_addr()), ip4_addr4_16(ip4_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, ("ip4_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) {
+    MIB2_STATS_INC(mib2.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, ("ip4_forward: forwarding packet to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\n",
+    ip4_addr1_16(ip4_current_dest_addr()), ip4_addr2_16(ip4_current_dest_addr()),
+    ip4_addr3_16(ip4_current_dest_addr()), ip4_addr4_16(ip4_current_dest_addr())));
+
+  IP_STATS_INC(ip.fw);
+  MIB2_STATS_INC(mib2.ipforwdatagrams);
+  IP_STATS_INC(ip.xmit);
+
+  PERF_STOP("ip4_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
+      ip4_frag(p, netif, ip4_current_dest_addr());
+#else /* IP_FRAG */
+      /* @todo: send ICMP Destination Unreachable code 13 "Communication administratively prohibited"? */
+#endif /* IP_FRAG */
+    } else {
+#if LWIP_ICMP
+      /* send ICMP Destination Unreachable code 4: "Fragmentation Needed and DF Set" */
+      icmp_dest_unreach(p, ICMP_DUR_FRAG);
+#endif /* LWIP_ICMP */
+    }
+    return;
+  }
+  /* transmit pbuf on chosen interface */
+  netif->output(netif, p, ip4_current_dest_addr());
+  return;
+return_noroute:
+  MIB2_STATS_INC(mib2.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
+ip4_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 || LWIP_IGMP
+  int check_ip_src = 1;
+#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING || LWIP_IGMP */
+
+  IP_STATS_INC(ip.recv);
+  MIB2_STATS_INC(mib2.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", (u16_t)IPH_V(iphdr)));
+    ip4_debug_print(p);
+    pbuf_free(p);
+    IP_STATS_INC(ip.err);
+    IP_STATS_INC(ip.drop);
+    MIB2_STATS_INC(mib2.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 = lwip_ntohs(IPH_LEN(iphdr));
+
+  /* Trim pbuf. This is especially required for packets < 60 bytes. */
+  if (iphdr_len < p->tot_len) {
+    pbuf_realloc(p, iphdr_len);
+  }
+
+  /* header length exceeds first pbuf length, or ip length exceeds total pbuf length? */
+  if ((iphdr_hlen > p->len) || (iphdr_len > p->tot_len) || (iphdr_hlen < IP_HLEN)) {
+    if (iphdr_hlen < IP_HLEN) {
+      LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+        ("ip4_input: short IP header (%"U16_F" bytes) received, IP packet dropped\n", iphdr_hlen));
+    }
+    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);
+    MIB2_STATS_INC(mib2.ipindiscards);
+    return ERR_OK;
+  }
+
+  /* verify checksum */
+#if CHECKSUM_CHECK_IP
+  IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_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)));
+      ip4_debug_print(p);
+      pbuf_free(p);
+      IP_STATS_INC(ip.chkerr);
+      IP_STATS_INC(ip.drop);
+      MIB2_STATS_INC(mib2.ipinhdrerrors);
+      return ERR_OK;
+    }
+  }
+#endif
+
+  /* copy IP addresses to aligned ip_addr_t */
+  ip_addr_copy_from_ip4(ip_data.current_iphdr_dest, iphdr->dest);
+  ip_addr_copy_from_ip4(ip_data.current_iphdr_src, iphdr->src);
+
+  /* match packet against an interface, i.e. is this packet for us? */
+  if (ip4_addr_ismulticast(ip4_current_dest_addr())) {
+#if LWIP_IGMP
+    if ((inp->flags & NETIF_FLAG_IGMP) && (igmp_lookfor_group(inp, ip4_current_dest_addr()))) {
+      /* IGMP snooping switches need 0.0.0.0 to be allowed as source address (RFC 4541) */
+      ip4_addr_t allsystems;
+      IP4_ADDR(&allsystems, 224, 0, 0, 1);
+      if (ip4_addr_cmp(ip4_current_dest_addr(), &allsystems) &&
+          ip4_addr_isany(ip4_current_src_addr())) {
+        check_ip_src = 0;
+      }
+      netif = inp;
+    } else {
+      netif = NULL;
+    }
+#else /* LWIP_IGMP */
+    if ((netif_is_up(inp)) && (!ip4_addr_isany_val(*netif_ip4_addr(inp)))) {
+      netif = inp;
+    } else {
+      netif = NULL;
+    }
+#endif /* LWIP_IGMP */
+  } 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 {
+      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_ip4_addr(netif)),
+          ip4_addr_get_u32(&iphdr->dest) & ip4_addr_get_u32(netif_ip4_netmask(netif)),
+          ip4_addr_get_u32(netif_ip4_addr(netif)) & ip4_addr_get_u32(netif_ip4_netmask(netif)),
+          ip4_addr_get_u32(&iphdr->dest) & ~ip4_addr_get_u32(netif_ip4_netmask(netif))));
+
+      /* interface is up and configured? */
+      if ((netif_is_up(netif)) && (!ip4_addr_isany_val(*netif_ip4_addr(netif)))) {
+        /* unicast to this interface address? */
+        if (ip4_addr_cmp(ip4_current_dest_addr(), netif_ip4_addr(netif)) ||
+            /* or broadcast on this interface network address? */
+            ip4_addr_isbroadcast(ip4_current_dest_addr(), netif)
+#if LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF
+            || (ip4_addr_get_u32(ip4_current_dest_addr()) == PP_HTONL(IPADDR_LOOPBACK))
+#endif /* LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF */
+            ) {
+          LWIP_DEBUGF(IP_DEBUG, ("ip4_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 (autoip_accept_packet(netif, ip4_current_dest_addr())) {
+          LWIP_DEBUGF(IP_DEBUG, ("ip4_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) {
+#if !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF
+        /* Packets sent to the loopback address must not be accepted on an
+         * interface that does not have the loopback address assigned to it,
+         * unless a non-loopback interface is used for loopback traffic. */
+        if (ip4_addr_isloopback(ip4_current_dest_addr())) {
+          netif = NULL;
+          break;
+        }
+#endif /* !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF */
+        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, ("ip4_input: UDP packet to DHCP client port %"U16_F"\n",
+        lwip_ntohs(udphdr->dest)));
+      if (IP_ACCEPT_LINK_LAYER_ADDRESSED_PORT(udphdr->dest)) {
+        LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip4_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 LWIP_IGMP || IP_ACCEPT_LINK_LAYER_ADDRESSING
+  if (check_ip_src
+#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) */
+      && !ip4_addr_isany_val(*ip4_current_src_addr())
+#endif /* IP_ACCEPT_LINK_LAYER_ADDRESSING */
+     )
+#endif /* LWIP_IGMP || IP_ACCEPT_LINK_LAYER_ADDRESSING */
+  {
+    if ((ip4_addr_isbroadcast(ip4_current_src_addr(), inp)) ||
+        (ip4_addr_ismulticast(ip4_current_src_addr()))) {
+      /* packet source is not valid */
+      LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_WARNING, ("ip4_input: packet source is not valid.\n"));
+      /* free (drop) packet pbufs */
+      pbuf_free(p);
+      IP_STATS_INC(ip.drop);
+      MIB2_STATS_INC(mib2.ipinaddrerrors);
+      MIB2_STATS_INC(mib2.ipindiscards);
+      return ERR_OK;
+    }
+  }
+
+  /* packet not for us? */
+  if (netif == NULL) {
+    /* packet not for us, route or discard */
+    LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip4_input: packet not for us.\n"));
+#if IP_FORWARD
+    /* non-broadcast packet? */
+    if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), inp)) {
+      /* try to forward IP packet on (other) interfaces */
+      ip4_forward(p, iphdr, inp);
+    } else
+#endif /* IP_FORWARD */
+    {
+      IP_STATS_INC(ip.drop);
+      MIB2_STATS_INC(mib2.ipinaddrerrors);
+      MIB2_STATS_INC(mib2.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 ip4_reass()\n",
+      lwip_ntohs(IPH_ID(iphdr)), p->tot_len, lwip_ntohs(IPH_LEN(iphdr)), (u16_t)!!(IPH_OFFSET(iphdr) & PP_HTONS(IP_MF)), (u16_t)((lwip_ntohs(IPH_OFFSET(iphdr)) & IP_OFFMASK)*8)));
+    /* reassemble the packet*/
+    p = ip4_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",
+      lwip_ntohs(IPH_OFFSET(iphdr))));
+    IP_STATS_INC(ip.opterr);
+    IP_STATS_INC(ip.drop);
+    /* unsupported protocol feature */
+    MIB2_STATS_INC(mib2.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 */
+    MIB2_STATS_INC(mib2.ipinunknownprotos);
+    return ERR_OK;
+  }
+#endif /* IP_OPTIONS_ALLOWED == 0 */
+
+  /* send to upper layers */
+  LWIP_DEBUGF(IP_DEBUG, ("ip4_input: \n"));
+  ip4_debug_print(p);
+  LWIP_DEBUGF(IP_DEBUG, ("ip4_input: p->len %"U16_F" p->tot_len %"U16_F"\n", p->len, p->tot_len));
+
+  ip_data.current_netif = netif;
+  ip_data.current_input_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, -(s16_t)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 */
+      MIB2_STATS_INC(mib2.ipindelivers);
+      udp_input(p, inp);
+      break;
+#endif /* LWIP_UDP */
+#if LWIP_TCP
+    case IP_PROTO_TCP:
+      MIB2_STATS_INC(mib2.ipindelivers);
+      tcp_input(p, inp);
+      break;
+#endif /* LWIP_TCP */
+#if LWIP_ICMP
+    case IP_PROTO_ICMP:
+      MIB2_STATS_INC(mib2.ipindelivers);
+      icmp_input(p, inp);
+      break;
+#endif /* LWIP_ICMP */
+#if LWIP_IGMP
+    case IP_PROTO_IGMP:
+      igmp_input(p, inp, ip4_current_dest_addr());
+      break;
+#endif /* LWIP_IGMP */
+    default:
+#if LWIP_ICMP
+      /* send ICMP destination protocol unreachable unless is was a broadcast */
+      if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), netif) &&
+          !ip4_addr_ismulticast(ip4_current_dest_addr())) {
+        pbuf_header_force(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", (u16_t)IPH_PROTO(iphdr)));
+
+      IP_STATS_INC(ip.proterr);
+      IP_STATS_INC(ip.drop);
+      MIB2_STATS_INC(mib2.ipinunknownprotos);
+    }
+  }
+
+  /* @todo: this is not really necessary... */
+  ip_data.current_netif = NULL;
+  ip_data.current_input_netif = NULL;
+  ip_data.current_ip4_header = NULL;
+  ip_data.current_ip_header_tot_len = 0;
+  ip4_addr_set_any(ip4_current_src_addr());
+  ip4_addr_set_any(ip4_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 LWIP_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 == LWIP_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 == IP4_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
+ip4_output_if(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
+             u8_t ttl, u8_t tos,
+             u8_t proto, struct netif *netif)
+{
+#if IP_OPTIONS_SEND
+  return ip4_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
+ip4_output_if_opt(struct pbuf *p, const ip4_addr_t *src, const ip4_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 */
+  const ip4_addr_t *src_used = src;
+  if (dest != LWIP_IP_HDRINCL) {
+    if (ip4_addr_isany(src)) {
+      src_used = netif_ip4_addr(netif);
+    }
+  }
+
+#if IP_OPTIONS_SEND
+  return ip4_output_if_opt_src(p, src_used, dest, ttl, tos, proto, netif,
+    ip_options, optlen);
+#else /* IP_OPTIONS_SEND */
+  return ip4_output_if_src(p, src_used, dest, ttl, tos, proto, netif);
+#endif /* IP_OPTIONS_SEND */
+}
+
+/**
+ * Same as ip_output_if() but 'src' address is not replaced by netif address
+ * when it is 'any'.
+ */
+err_t
+ip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
+             u8_t ttl, u8_t tos,
+             u8_t proto, struct netif *netif)
+{
+#if IP_OPTIONS_SEND
+  return ip4_output_if_opt_src(p, src, dest, ttl, tos, proto, netif, NULL, 0);
+}
+
+/**
+ * Same as ip_output_if_opt() but 'src' address is not replaced by netif address
+ * when it is 'any'.
+ */
+err_t
+ip4_output_if_opt_src(struct pbuf *p, const ip4_addr_t *src, const ip4_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;
+  ip4_addr_t dest_addr;
+#if CHECKSUM_GEN_IP_INLINE
+  u32_t chk_sum = 0;
+#endif /* CHECKSUM_GEN_IP_INLINE */
+
+  LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);
+
+  MIB2_STATS_INC(mib2.ipoutrequests);
+
+  /* Should the IP header be generated or is it already included in p? */
+  if (dest != LWIP_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, ("ip4_output_if_opt: not enough room for IP options in pbuf\n"));
+        IP_STATS_INC(ip.err);
+        MIB2_STATS_INC(mib2.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, ("ip4_output: not enough room for IP header in pbuf\n"));
+
+      IP_STATS_INC(ip.err);
+      MIB2_STATS_INC(mib2.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 += PP_NTOHS(proto | (ttl << 8));
+#endif /* CHECKSUM_GEN_IP_INLINE */
+
+    /* dest cannot be NULL here */
+    ip4_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 += PP_NTOHS(tos | (iphdr->_v_hl << 8));
+#endif /* CHECKSUM_GEN_IP_INLINE */
+    IPH_LEN_SET(iphdr, lwip_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, lwip_htons(ip_id));
+#if CHECKSUM_GEN_IP_INLINE
+    chk_sum += iphdr->_id;
+#endif /* CHECKSUM_GEN_IP_INLINE */
+    ++ip_id;
+
+    if (src == NULL) {
+      ip4_addr_copy(iphdr->src, *IP4_ADDR_ANY4);
+    } else {
+      /* src cannot be NULL here */
+      ip4_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;
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
+      iphdr->_chksum = (u16_t)chk_sum; /* network order */
+    }
+#if LWIP_CHECKSUM_CTRL_PER_NETIF
+    else {
+      IPH_CHKSUM_SET(iphdr, 0);
+    }
+#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
+#else /* CHECKSUM_GEN_IP_INLINE */
+    IPH_CHKSUM_SET(iphdr, 0);
+#if CHECKSUM_GEN_IP
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
+      IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen));
+    }
+#endif /* CHECKSUM_GEN_IP */
+#endif /* CHECKSUM_GEN_IP_INLINE */
+  } else {
+    /* IP header already included in p */
+    iphdr = (struct ip_hdr *)p->payload;
+    ip4_addr_copy(dest_addr, iphdr->dest);
+    dest = &dest_addr;
+  }
+
+  IP_STATS_INC(ip.xmit);
+
+  LWIP_DEBUGF(IP_DEBUG, ("ip4_output_if: %c%c%"U16_F"\n", netif->name[0], netif->name[1], (u16_t)netif->num));
+  ip4_debug_print(p);
+
+#if ENABLE_LOOPBACK
+  if (ip4_addr_cmp(dest, netif_ip4_addr(netif))
+#if !LWIP_HAVE_LOOPIF
+      || ip4_addr_isloopback(dest)
+#endif /* !LWIP_HAVE_LOOPIF */
+      ) {
+    /* Packet to self, enqueue it for loopback */
+    LWIP_DEBUGF(IP_DEBUG, ("netif_loop_output()"));
+    return netif_loop_output(netif, p);
+  }
+#if LWIP_MULTICAST_TX_OPTIONS
+  if ((p->flags & PBUF_FLAG_MCASTLOOP) != 0) {
+    netif_loop_output(netif, p);
+  }
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
+#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 ip4_frag(p, netif, dest);
+  }
+#endif /* IP_FRAG */
+
+  LWIP_DEBUGF(IP_DEBUG, ("ip4_output_if: call netif->output()\n"));
+  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 == LWIP_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 == IP4_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
+ip4_output(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
+          u8_t ttl, u8_t tos, u8_t proto)
+{
+  struct netif *netif;
+
+  LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);
+
+  if ((netif = ip4_route_src(dest, src)) == NULL) {
+    LWIP_DEBUGF(IP_DEBUG, ("ip4_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 ip4_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 == LWIP_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 == IP4_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
+ip4_output_hinted(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
+          u8_t ttl, u8_t tos, u8_t proto, u8_t *addr_hint)
+{
+  struct netif *netif;
+  err_t err;
+
+  LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);
+
+  if ((netif = ip4_route_src(dest, src)) == NULL) {
+    LWIP_DEBUGF(IP_DEBUG, ("ip4_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 = ip4_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
+ip4_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",
+                    (u16_t)IPH_V(iphdr),
+                    (u16_t)IPH_HL(iphdr),
+                    (u16_t)IPH_TOS(iphdr),
+                    lwip_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",
+                    lwip_ntohs(IPH_ID(iphdr)),
+                    (u16_t)(lwip_ntohs(IPH_OFFSET(iphdr)) >> 15 & 1),
+                    (u16_t)(lwip_ntohs(IPH_OFFSET(iphdr)) >> 14 & 1),
+                    (u16_t)(lwip_ntohs(IPH_OFFSET(iphdr)) >> 13 & 1),
+                    (u16_t)(lwip_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",
+                    (u16_t)IPH_TTL(iphdr),
+                    (u16_t)IPH_PROTO(iphdr),
+                    lwip_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 */
+
+#endif /* LWIP_IPV4 */
diff --git a/src/core/ipv4/ip4_addr.c b/src/core/ipv4/ip4_addr.c
new file mode 100644
index 0000000..2d47992
--- /dev/null
+++ b/src/core/ipv4/ip4_addr.c
@@ -0,0 +1,331 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV4
+
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+
+/* used by IP4_ADDR_ANY and IP_ADDR_BROADCAST in ip_addr.h */
+const ip_addr_t ip_addr_any = IPADDR4_INIT(IPADDR_ANY);
+const ip_addr_t ip_addr_broadcast = IPADDR4_INIT(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(u32_t addr, const struct netif *netif)
+{
+  ip4_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_ip4_addr(netif))) {
+    return 0;
+  /*  on the same (sub) network... */
+  } else if (ip4_addr_netcmp(&ipaddr, netif_ip4_addr(netif), netif_ip4_netmask(netif))
+         /* ...and host identifier bits are all ones? =>... */
+          && ((addr & ~ip4_addr_get_u32(netif_ip4_netmask(netif))) ==
+           (IPADDR_BROADCAST & ~ip4_addr_get_u32(netif_ip4_netmask(netif))))) {
+    /* => 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 representation (e.g. "127.0.0.1")
+ * @return ip address in network order
+ */
+u32_t
+ipaddr_addr(const char *cp)
+{
+  ip4_addr_t val;
+
+  if (ip4addr_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 representation (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
+ip4addr_aton(const char *cp, ip4_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) + (u32_t)(c - '0');
+        c = *++cp;
+      } else if (base == 16 && isxdigit(c)) {
+        val = (val << 4) | (u32_t)(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;
+    }
+    if (parts[0] > 0xff) {
+      return 0;
+    }
+    val |= parts[0] << 24;
+    break;
+
+  case 3:             /* a.b.c -- 8.8.16 bits */
+    if (val > 0xffff) {
+      return 0;
+    }
+    if ((parts[0] > 0xff) || (parts[1] > 0xff)) {
+      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;
+    }
+    if ((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 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, lwip_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
+ *         representation of addr
+ */
+char*
+ip4addr_ntoa(const ip4_addr_t *addr)
+{
+  static char str[IP4ADDR_STRLEN_MAX];
+  return ip4addr_ntoa_r(addr, str, IP4ADDR_STRLEN_MAX);
+}
+
+/**
+ * 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*
+ip4addr_ntoa_r(const ip4_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++] = (char)('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;
+}
+
+#endif /* LWIP_IPV4 */
diff --git a/src/core/ipv4/ip4_frag.c b/src/core/ipv4/ip4_frag.c
new file mode 100644
index 0000000..fdb2088
--- /dev/null
+++ b/src/core/ipv4/ip4_frag.c
@@ -0,0 +1,864 @@
+/**
+ * @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@iv.ro>
+ *         Simon Goldschmidt
+ * original reassembly code by Adam Dunkels <adam@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV4
+
+#include "lwip/ip4_frag.h"
+#include "lwip/def.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/netif.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
+
+#define IP_REASS_VALIDATE_TELEGRAM_FINISHED  1
+#define IP_REASS_VALIDATE_PBUF_QUEUED        0
+#define IP_REASS_VALIDATE_PBUF_DROPPED       -1
+
+/** 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)  \
+  (ip4_addr_cmp(&(iphdrA)->src, &(iphdrB)->src) && \
+   ip4_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;
+  u16_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);
+  }
+
+  MIB2_STATS_INC(mib2.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, *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;
+    oldest_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;
+          oldest_prev = prev;
+        } else if (r->timer <= oldest->timer) {
+          /* older than the previous oldest */
+          oldest = r;
+          oldest_prev = prev;
+        }
+      }
+      if (r->next != NULL) {
+        prev = r;
+      }
+      r = r->next;
+    }
+    if (oldest != NULL) {
+      pbufs_freed_current = ip_reass_free_complete_datagram(oldest, 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;
+#if ! IP_REASS_FREE_OLDEST
+  LWIP_UNUSED_ARG(clen);
+#endif
+
+  /* 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_reassdata 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 ipr points to the reassembly state
+ * @param new_p points to the pbuf for the current fragment
+ * @param is_last is 1 if this pbuf has MF==0 (ipr->flags not updated yet)
+ * @return see IP_REASS_VALIDATE_* defines
+ */
+static int
+ip_reass_chain_frag_into_datagram_and_validate(struct ip_reassdata *ipr, struct pbuf *new_p, int is_last)
+{
+  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 = lwip_ntohs(IPH_LEN(fraghdr)) - IPH_HL(fraghdr) * 4;
+  offset = (lwip_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 one 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;
+        if (iprh_prev->end != iprh->start) {
+          /* There is a fragment missing between the current
+           * and the previous fragment */
+          valid = 0;
+        }
+      } else {
+#if IP_REASS_CHECK_OVERLAP
+        if (iprh->end > iprh_tmp->start) {
+          /* fragment overlaps with following, throw away */
+          goto freepbuf;
+        }
+#endif /* IP_REASS_CHECK_OVERLAP */
+        /* 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 holes. */
+      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 (is_last || ((ipr->flags & IP_REASS_FLAG_LASTFRAG) != 0)) {
+    /* and had no holes so far */
+    if (valid) {
+      /* then check if the rest of the fragments is here */
+      /* Check if the queue starts with the first datagram */
+      if ((ipr->p == NULL) || (((struct ip_reass_helper*)ipr->p->payload)->start != 0)) {
+        valid = 0;
+      } else {
+        /* and check that there are no holes 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);
+        }
+      }
+    }
+    /* 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 ? IP_REASS_VALIDATE_TELEGRAM_FINISHED : IP_REASS_VALIDATE_PBUF_QUEUED;
+  }
+  /* If we come here, not all fragments were received, yet! */
+  return IP_REASS_VALIDATE_PBUF_QUEUED; /* not yet valid! */
+#if IP_REASS_CHECK_OVERLAP
+freepbuf:
+  ip_reass_pbufcount -= pbuf_clen(new_p);
+  pbuf_free(new_p);
+  return IP_REASS_VALIDATE_PBUF_DROPPED;
+#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 *
+ip4_reass(struct pbuf *p)
+{
+  struct pbuf *r;
+  struct ip_hdr *fraghdr;
+  struct ip_reassdata *ipr;
+  struct ip_reass_helper *iprh;
+  u16_t offset, len, clen;
+  int valid;
+  int is_last;
+
+  IPFRAG_STATS_INC(ip_frag.recv);
+  MIB2_STATS_INC(mib2.ipreasmreqds);
+
+  fraghdr = (struct ip_hdr*)p->payload;
+
+  if ((IPH_HL(fraghdr) * 4) != IP_HLEN) {
+    LWIP_DEBUGF(IP_REASS_DEBUG,("ip4_reass: IP options currently not supported!\n"));
+    IPFRAG_STATS_INC(ip_frag.err);
+    goto nullreturn;
+  }
+
+  offset = (lwip_ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) * 8;
+  len = lwip_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,("ip4_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, ("ip4_reass: matching previous fragment ID=%"X16_F"\n",
+        lwip_ntohs(IPH_ID(fraghdr))));
+      IPFRAG_STATS_INC(ip_frag.cachehit);
+      break;
+    }
+  }
+
+  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 (((lwip_ntohs(IPH_OFFSET(fraghdr)) & IP_OFFMASK) == 0) &&
+      ((lwip_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);
+    }
+  }
+
+  /* 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*/
+  is_last = (IPH_OFFSET(fraghdr) & PP_NTOHS(IP_MF)) == 0;
+  if (is_last) {
+    u16_t datagram_len = (u16_t)(offset + len);
+    if ((datagram_len < offset) || (datagram_len > (0xFFFF - IP_HLEN))) {
+      /* u16_t overflow, cannot handle this */
+      goto nullreturn;
+    }
+  }
+  /* find the right place to insert this pbuf */
+  /* @todo: trim pbufs if fragments are overlapping */
+  valid = ip_reass_chain_frag_into_datagram_and_validate(ipr, p, is_last);
+  if (valid == IP_REASS_VALIDATE_PBUF_DROPPED) {
+    goto nullreturn;
+  }
+  /* if we come here, the pbuf has been enqueued */
+
+  /* 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
+     (overflow checked by testing against IP_REASS_MAX_PBUFS) */
+  ip_reass_pbufcount = (u16_t)(ip_reass_pbufcount + clen);
+  if (is_last) {
+    u16_t datagram_len = (u16_t)(offset + len);
+    ipr->datagram_len = datagram_len;
+    ipr->flags |= IP_REASS_FLAG_LASTFRAG;
+    LWIP_DEBUGF(IP_REASS_DEBUG,
+     ("ip4_reass: last fragment seen, total len %"S16_F"\n",
+      ipr->datagram_len));
+  }
+
+  if (valid == IP_REASS_VALIDATE_TELEGRAM_FINISHED) {
+    struct ip_reassdata *ipr_prev;
+    /* 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, lwip_htons(ipr->datagram_len));
+    IPH_OFFSET_SET(fraghdr, 0);
+    IPH_CHKSUM_SET(fraghdr, 0);
+    /* @todo: do we need to set/calculate the correct checksum? */
+#if CHECKSUM_GEN_IP
+    IF__NETIF_CHECKSUM_ENABLED(ip_current_input_netif(), NETIF_CHECKSUM_GEN_IP) {
+      IPH_CHKSUM_SET(fraghdr, inet_chksum(fraghdr, IP_HLEN));
+    }
+#endif /* CHECKSUM_GEN_IP */
+
+    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 succeeding fragment */
+      pbuf_header(r, -IP_HLEN);
+      pbuf_cat(p, r);
+      r = iprh->next_pbuf;
+    }
+
+    /* find the previous entry in the linked list */
+    if (ipr == reassdatagrams) {
+      ipr_prev = NULL;
+    } else {
+      for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
+        if (ipr_prev->next == ipr) {
+          break;
+        }
+      }
+    }
+
+    /* 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);
+
+    MIB2_STATS_INC(mib2.ipreasmoks);
+
+    /* 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,("ip4_reass: nullreturn\n"));
+  IPFRAG_STATS_INC(ip_frag.drop);
+  pbuf_free(p);
+  return NULL;
+}
+#endif /* IP_REASSEMBLY */
+
+#if IP_FRAG
+#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 */
+
+/**
+ * Fragment an IP datagram if too large for the netif.
+ *
+ * Chop the datagram in MTU sized chunks and send them in order
+ * by pointing PBUF_REFs into p.
+ *
+ * @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
+ip4_frag(struct pbuf *p, struct netif *netif, const ip4_addr_t *dest)
+{
+  struct pbuf *rambuf;
+#if !LWIP_NETIF_TX_SINGLE_PBUF
+  struct pbuf *newpbuf;
+  u16_t newpbuflen = 0;
+  u16_t left_to_copy;
+#endif
+  struct ip_hdr *original_iphdr;
+  struct ip_hdr *iphdr;
+  const u16_t nfb = (netif->mtu - IP_HLEN) / 8;
+  u16_t left, fragsize;
+  u16_t ofo;
+  int last;
+  u16_t poff = IP_HLEN;
+  u16_t tmp;
+
+  original_iphdr = (struct ip_hdr *)p->payload;
+  iphdr = original_iphdr;
+  LWIP_ERROR("ip4_frag() does not support IP options", IPH_HL(iphdr) * 4 == IP_HLEN, return ERR_VAL);
+
+  /* Save original offset */
+  tmp = lwip_ntohs(IPH_OFFSET(iphdr));
+  ofo = tmp & IP_OFFMASK;
+  LWIP_ERROR("ip_frag(): MF already set", (tmp & IP_MF) == 0, return ERR_VAL);
+
+  left = p->tot_len - IP_HLEN;
+
+  while (left) {
+    /* Fill this fragment */
+    fragsize = LWIP_MIN(left, nfb * 8);
+
+#if LWIP_NETIF_TX_SINGLE_PBUF
+    rambuf = pbuf_alloc(PBUF_IP, fragsize, PBUF_RAM);
+    if (rambuf == NULL) {
+      goto memerr;
+    }
+    LWIP_ASSERT("this needs a pbuf in one piece!",
+      (rambuf->len == rambuf->tot_len) && (rambuf->next == NULL));
+    poff += pbuf_copy_partial(p, rambuf->payload, fragsize, poff);
+    /* make room for the IP header */
+    if (pbuf_header(rambuf, IP_HLEN)) {
+      pbuf_free(rambuf);
+      goto memerr;
+    }
+    /* fill in the IP header */
+    SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN);
+    iphdr = (struct ip_hdr*)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) {
+      goto memerr;
+    }
+    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;
+
+    left_to_copy = fragsize;
+    while (left_to_copy) {
+      struct pbuf_custom_ref *pcr;
+      u16_t plen = p->len - poff;
+      newpbuflen = LWIP_MIN(left_to_copy, plen);
+      /* Is this pbuf already empty? */
+      if (!newpbuflen) {
+        poff = 0;
+        p = p->next;
+        continue;
+      }
+      pcr = ip_frag_alloc_pbuf_custom_ref();
+      if (pcr == NULL) {
+        pbuf_free(rambuf);
+        goto memerr;
+      }
+      /* Mirror this pbuf, although we might not need all of it. */
+      newpbuf = pbuf_alloced_custom(PBUF_RAW, newpbuflen, PBUF_REF, &pcr->pc,
+        (u8_t*)p->payload + poff, newpbuflen);
+      if (newpbuf == NULL) {
+        ip_frag_free_pbuf_custom_ref(pcr);
+        pbuf_free(rambuf);
+        goto memerr;
+      }
+      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) {
+        poff = 0;
+        p = p->next;
+      }
+    }
+    poff += newpbuflen;
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+
+    /* Correct header */
+    last = (left <= netif->mtu - IP_HLEN);
+
+    /* Set new offset and MF flag */
+    tmp = (IP_OFFMASK & (ofo));
+    if (!last) {
+      tmp = tmp | IP_MF;
+    }
+    IPH_OFFSET_SET(iphdr, lwip_htons(tmp));
+    IPH_LEN_SET(iphdr, lwip_htons(fragsize + IP_HLEN));
+    IPH_CHKSUM_SET(iphdr, 0);
+#if CHECKSUM_GEN_IP
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {
+      IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));
+    }
+#endif /* CHECKSUM_GEN_IP */
+
+    /* 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);
+    left -= fragsize;
+    ofo += nfb;
+  }
+  MIB2_STATS_INC(mib2.ipfragoks);
+  return ERR_OK;
+memerr:
+  MIB2_STATS_INC(mib2.ipfragfails);
+  return ERR_MEM;
+}
+#endif /* IP_FRAG */
+
+#endif /* LWIP_IPV4 */
diff --git a/src/core/ipv6/dhcp6.c b/src/core/ipv6/dhcp6.c
new file mode 100644
index 0000000..f27a725
--- /dev/null
+++ b/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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6 && 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 && LWIP_IPV6_DHCP6 */
diff --git a/src/core/ipv6/ethip6.c b/src/core/ipv6/ethip6.c
new file mode 100644
index 0000000..8f9a91b
--- /dev/null
+++ b/src/core/ipv6/ethip6.c
@@ -0,0 +1,118 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#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 "lwip/prot/ethernet.h"
+#include "netif/ethernet.h"
+
+#include <string.h>
+
+/**
+ * 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, ask the ND6 module what to do. It will either let us
+ * send the the packet right away, or queue the packet for later itself, unless
+ * an error occurs.
+ *
+ * @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_OK or the return value of @ref nd6_get_next_hop_addr_or_queue.
+ */
+err_t
+ethip6_output(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr)
+{
+  struct eth_addr dest;
+  const u8_t *hwaddr;
+  err_t result;
+
+  /* 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] = ((const u8_t *)(&(ip6addr->addr[3])))[0];
+    dest.addr[3] = ((const u8_t *)(&(ip6addr->addr[3])))[1];
+    dest.addr[4] = ((const u8_t *)(&(ip6addr->addr[3])))[2];
+    dest.addr[5] = ((const u8_t *)(&(ip6addr->addr[3])))[3];
+
+    /* Send out. */
+    return ethernet_output(netif, q, (const struct eth_addr*)(netif->hwaddr), &dest, ETHTYPE_IPV6);
+  }
+
+  /* We have a unicast destination IP address */
+  /* @todo anycast? */
+
+  /* Ask ND6 what to do with the packet. */
+  result = nd6_get_next_hop_addr_or_queue(netif, q, ip6addr, &hwaddr);
+  if (result != ERR_OK) {
+    return result;
+  }
+
+  /* If no hardware address is returned, nd6 has queued the packet for later. */
+  if (hwaddr == NULL) {
+    return ERR_OK;
+  }
+
+  /* Send out the packet using the returned hardware address. */
+  SMEMCPY(dest.addr, hwaddr, 6);
+  return ethernet_output(netif, q, (const struct eth_addr*)(netif->hwaddr), &dest, ETHTYPE_IPV6);
+}
+
+#endif /* LWIP_IPV6 && LWIP_ETHERNET */
diff --git a/src/core/ipv6/icmp6.c b/src/core/ipv6/icmp6.c
new file mode 100644
index 0000000..323b69a
--- /dev/null
+++ b/src/core/ipv6/icmp6.c
@@ -0,0 +1,350 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#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/prot/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/ip.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;
+  const 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 CHECKSUM_CHECK_ICMP6
+  IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_ICMP6) {
+    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 /* CHECKSUM_CHECK_ICMP6 */
+
+  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 = ip_2_ip6(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;
+#if CHECKSUM_GEN_ICMP6
+    IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_ICMP6) {
+      ((struct icmp6_echo_hdr *)(r->payload))->chksum = ip6_chksum_pseudo(r,
+          IP6_NEXTH_ICMP6, r->tot_len, reply_src, ip6_current_src_addr());
+    }
+#endif /* CHECKSUM_GEN_ICMP6 */
+
+    /* 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;
+  const ip6_addr_t *reply_src;
+  ip6_addr_t *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 = ip_2_ip6(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;
+#if CHECKSUM_GEN_ICMP6
+  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) {
+    icmp6hdr->chksum = ip6_chksum_pseudo(q, IP6_NEXTH_ICMP6, q->tot_len,
+      reply_src, reply_dest);
+  }
+#endif /* CHECKSUM_GEN_ICMP6 */
+
+  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/src/core/ipv6/inet6.c b/src/core/ipv6/inet6.c
new file mode 100644
index 0000000..d9a992c
--- /dev/null
+++ b/src/core/ipv6/inet6.c
@@ -0,0 +1,53 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#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/inet.h"
+
+/** This variable is initialized by the system to contain the wildcard IPv6 address.
+ */
+const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+
+#endif /* LWIP_IPV6 */
diff --git a/src/core/ipv6/ip6.c b/src/core/ipv6/ip6.c
new file mode 100644
index 0000000..f14e334
--- /dev/null
+++ b/src/core/ipv6/ip6.c
@@ -0,0 +1,1122 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#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/ip.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/priv/tcp_priv.h"
+#include "lwip/dhcp6.h"
+#include "lwip/nd6.h"
+#include "lwip/mld6.h"
+#include "lwip/debug.h"
+#include "lwip/stats.h"
+
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+
+/**
+ * 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(const ip6_addr_t *src, const ip6_addr_t *dest)
+{
+  struct netif *netif;
+  s8_t i;
+
+  /* If single netif configuration, fast return. */
+  if ((netif_list != NULL) && (netif_list->next == NULL)) {
+    if (!netif_is_up(netif_list) || !netif_is_link_up(netif_list)) {
+      return NULL;
+    }
+    return netif_list;
+  }
+
+  /* Special processing for link-local addresses. */
+  if (ip6_addr_islinklocal(dest)) {
+    if (ip6_addr_isany(src)) {
+      /* Use default netif, if Up. */
+      if (netif_default == NULL || !netif_is_up(netif_default) ||
+          !netif_is_link_up(netif_default)) {
+        return NULL;
+      }
+      return netif_default;
+    }
+
+    /* Try to find the netif for the source address, checking that link is up. */
+    for (netif = netif_list; netif != NULL; netif = netif->next) {
+      if (!netif_is_up(netif) || !netif_is_link_up(netif)) {
+        continue;
+      }
+      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, if up */
+    if (netif_default == NULL || !netif_is_up(netif_default) ||
+        !netif_is_link_up(netif_default)) {
+      return NULL;
+    }
+    return netif_default;
+  }
+
+  /* we come here for non-link-local addresses */
+#ifdef LWIP_HOOK_IP6_ROUTE
+  netif = LWIP_HOOK_IP6_ROUTE(src, dest);
+  if (netif != NULL) {
+    return netif;
+  }
+#endif
+
+  /* See if the destination subnet matches a configured address. */
+  for (netif = netif_list; netif != NULL; netif = netif->next) {
+    if (!netif_is_up(netif) || !netif_is_link_up(netif)) {
+      continue;
+    }
+    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. */
+  netif = nd6_find_route(dest);
+  if ((netif != NULL) && netif_is_up(netif) && netif_is_link_up(netif)) {
+    return netif;
+  }
+
+  /* try with the netif that matches the source address. */
+  if (!ip6_addr_isany(src)) {
+    for (netif = netif_list; netif != NULL; netif = netif->next) {
+      if (!netif_is_up(netif) || !netif_is_link_up(netif)) {
+        continue;
+      }
+      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;
+        }
+      }
+    }
+  }
+
+#if LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF
+  /* loopif is disabled, loopback traffic is passed through any netif */
+  if (ip6_addr_isloopback(dest)) {
+    /* don't check for link on loopback traffic */
+    if (netif_default != NULL && netif_is_up(netif_default)) {
+      return netif_default;
+    }
+    /* default netif is not up, just use any netif for loopback traffic */
+    for (netif = netif_list; netif != NULL; netif = netif->next) {
+      if (netif_is_up(netif)) {
+        return netif;
+      }
+    }
+    return NULL;
+  }
+#endif /* LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF */
+
+  /* no matching netif found, use default netif, if up */
+  if ((netif_default == NULL) || !netif_is_up(netif_default) || !netif_is_link_up(netif_default)) {
+    return NULL;
+  }
+  return netif_default;
+}
+
+/**
+ * @ingroup ip6
+ * 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
+ */
+const ip_addr_t *
+ip6_select_source_address(struct netif *netif, const ip6_addr_t *dest)
+{
+  const ip_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_ip_addr6(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_ip_addr6(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_ip_addr6(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_ip_addr6(netif, i);
+        }
+        else {
+          /* Replace src only if we find a prefix match. */
+          /* @todo find longest matching prefix. */
+          if ((!(ip6_addr_netcmp(ip_2_ip6(src), dest))) &&
+              ip6_addr_netcmp(netif_ip6_addr(netif, i), dest)) {
+            src = netif_ip_addr6(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_ip_addr6(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 or loopback addresses */
+  if (ip6_addr_islinklocal(ip6_current_dest_addr()) ||
+      ip6_addr_isloopback(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_ANY6, 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;
+  }
+
+#ifdef LWIP_HOOK_IP6_INPUT
+  if (LWIP_HOOK_IP6_INPUT(p, inp)) {
+    /* the packet has been eaten */
+    return ERR_OK;
+  }
+#endif
+
+  /* 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",
+            (u16_t)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",
+            (u16_t)(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 */
+  ip_addr_copy_from_ip6(ip_data.current_iphdr_dest, ip6hdr->dest);
+  ip_addr_copy_from_ip6(ip_data.current_iphdr_src, ip6hdr->src);
+
+  /* Don't accept virtual IPv4 mapped IPv6 addresses.
+   * Don't accept multicast source addresses. */
+  if (ip6_addr_isipv4mappedipv6(ip_2_ip6(&ip_data.current_iphdr_dest)) ||
+     ip6_addr_isipv4mappedipv6(ip_2_ip6(&ip_data.current_iphdr_src)) ||
+     ip6_addr_ismulticast(ip_2_ip6(&ip_data.current_iphdr_src))) {
+    IP6_STATS_INC(ip6.err);
+    IP6_STATS_INC(ip6.drop);
+    return ERR_OK;
+  }
+
+  /* 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;
+  ip_data.current_input_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 (first) {
+        if (ip6_addr_islinklocal(ip6_current_dest_addr())
+#if !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF
+            || ip6_addr_isloopback(ip6_current_dest_addr())
+#endif /* !LWIP_NETIF_LOOPBACK || LWIP_HAVE_LOOPIF */
+        ) {
+          /* Do not match link-local addresses to other netifs. The loopback
+           * address is to be considered link-local and packets to it should be
+           * dropped on other interfaces, as per RFC 4291 Sec. 2.5.3. This
+           * requirement cannot be implemented in the case that loopback
+           * traffic is sent across a non-loopback interface, however.
+           */
+          netif = NULL;
+          break;
+        }
+        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;
+  }
+
+  /* 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, -(s16_t)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, -(s16_t)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, -(s16_t)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 &
+           PP_HTONS(IP6_FRAG_OFFSET_MASK | 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, -(s16_t)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_force(p, (s16_t)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, -(s16_t)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, -(s16_t)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, -(s16_t)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", (u16_t)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_input_netif = NULL;
+  ip_data.current_ip6_header = NULL;
+  ip_data.current_ip_header_tot_len = 0;
+  ip6_addr_set_zero(ip6_current_src_addr());
+  ip6_addr_set_zero(ip6_current_dest_addr());
+
+  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
+ * LWIP_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 == LWIP_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, const ip6_addr_t *src, const ip6_addr_t *dest,
+             u8_t hl, u8_t tc,
+             u8_t nexth, struct netif *netif)
+{
+  const ip6_addr_t *src_used = src;
+  if (dest != LWIP_IP_HDRINCL) {
+    if (src != NULL && ip6_addr_isany(src)) {
+      src_used = ip_2_ip6(ip6_select_source_address(netif, dest));
+      if ((src_used == NULL) || ip6_addr_isany(src_used)) {
+        /* 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;
+      }
+    }
+  }
+  return ip6_output_if_src(p, src_used, dest, hl, tc, nexth, netif);
+}
+
+/**
+ * Same as ip6_output_if() but 'src' address is not replaced by netif address
+ * when it is 'any'.
+ */
+err_t
+ip6_output_if_src(struct pbuf *p, const ip6_addr_t *src, const 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;
+
+  LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);
+
+  /* Should the IPv6 header be generated or is it already included in p? */
+  if (dest != LWIP_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_ANY6;
+    }
+    /* 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], (u16_t)netif->num));
+  ip6_debug_print(p);
+
+#if ENABLE_LOOPBACK
+  {
+    int i;
+#if !LWIP_HAVE_LOOPIF
+    if (ip6_addr_isloopback(dest)) {
+      return netif_loop_output(netif, p);
+    }
+#endif /* !LWIP_HAVE_LOOPIF */
+    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
+          ip6_addr_cmp(dest, netif_ip6_addr(netif, i))) {
+        /* Packet to self, enqueue it for loopback */
+        LWIP_DEBUGF(IP6_DEBUG, ("netif_loop_output()\n"));
+        return netif_loop_output(netif, p);
+      }
+    }
+  }
+#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()\n"));
+  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 == LWIP_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, const ip6_addr_t *src, const 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;
+
+  LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);
+
+  if (dest != LWIP_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 == LWIP_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, const ip6_addr_t *src, const 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;
+
+  LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);
+
+  if (dest != LWIP_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/src/core/ipv6/ip6_addr.c b/src/core/ipv6/ip6_addr.c
new file mode 100644
index 0000000..aa06659
--- /dev/null
+++ b/src/core/ipv6/ip6_addr.c
@@ -0,0 +1,292 @@
+/**
+ * @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@inicotech.com>
+ *
+ * Functions for handling IPv6 addresses.
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/ip_addr.h"
+#include "lwip/def.h"
+
+/* used by IP6_ADDR_ANY(6) in ip6_addr.h */
+const ip_addr_t ip6_addr_any = IPADDR6_INIT(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 representation (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] == ':') {
+        if (s[2] == ':') {
+          /* invalid format: three successive colons */
+          return 0;
+        }
+        s++;
+        /* "::" found, set zeros */
+        while (zero_blocks > 0) {
+          zero_blocks--;
+          if (current_block_index & 0x1) {
+            addr_index++;
+          } else {
+            if (addr) {
+              addr->addr[addr_index] = 0;
+            }
+          }
+          current_block_index++;
+          if (current_block_index > 7) {
+            /* address too long! */
+            return 0;
+          }
+        }
+      }
+    } else if (isxdigit(*s)) {
+      /* add current digit */
+      current_block_value = (current_block_value << 4) +
+          (isdigit(*s) ? (u32_t)(*s - '0') :
+          (u32_t)(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] = lwip_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
+ *         representation 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, next_block_value;
+  s32_t i;
+  u8_t zero_flag, empty_block_flag;
+
+  i = 0;
+  empty_block_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 = lwip_htonl(addr->addr[current_block_index >> 1]);
+    if ((current_block_index & 0x1) == 0) {
+      current_block_value = current_block_value >> 16;
+    }
+    current_block_value &= 0xffff;
+
+    /* Check for empty block. */
+    if (current_block_value == 0) {
+      if (current_block_index == 7 && empty_block_flag == 1) {
+        /* special case, we must render a ':' for the last block. */
+        buf[i++] = ':';
+        if (i >= buflen) {
+          return NULL;
+        }
+        break;
+      }
+      if (empty_block_flag == 0) {
+        /* generate empty block "::", but only if more than one contiguous zero block,
+         * according to current formatting suggestions RFC 5952. */
+        next_block_value = lwip_htonl(addr->addr[(current_block_index + 1) >> 1]);
+        if ((current_block_index & 0x1) == 0x01) {
+            next_block_value = next_block_value >> 16;
+        }
+        next_block_value &= 0xffff;
+        if (next_block_value == 0) {
+          empty_block_flag = 1;
+          buf[i++] = ':';
+          if (i >= buflen) {
+            return NULL;
+          }
+          continue; /* move on to next block. */
+        }
+      } else if (empty_block_flag == 1) {
+        /* move on to next block. */
+        continue;
+      }
+    } else if (empty_block_flag == 1) {
+      /* Set this flag value so we don't produce multiple empty blocks. */
+      empty_block_flag = 2;
+    }
+
+    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;
+    }
+  }
+
+  buf[i] = 0;
+
+  return buf;
+}
+
+#endif /* LWIP_IPV6 */
diff --git a/src/core/ipv6/ip6_frag.c b/src/core/ipv6/ip6_frag.c
new file mode 100644
index 0000000..ff07f71
--- /dev/null
+++ b/src/core/ipv6/ip6_frag.c
@@ -0,0 +1,805 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#include "lwip/opt.h"
+#include "lwip/ip6_frag.h"
+#include "lwip/ip6.h"
+#include "lwip/icmp6.h"
+#include "lwip/nd6.h"
+#include "lwip/ip.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 */
+
+#if IPV6_FRAG_COPYHEADER
+#define IPV6_FRAG_REQROOM ((s16_t)(sizeof(struct ip6_reass_helper) - IP6_FRAG_HLEN))
+#endif
+
+#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;
+
+#if !IPV6_FRAG_COPYHEADER
+  LWIP_ASSERT("sizeof(struct ip6_reass_helper) <= IP6_FRAG_HLEN, set IPV6_FRAG_COPYHEADER to 1",
+    sizeof(struct ip6_reass_helper) <= IP6_FRAG_HLEN);
+#endif /* !IPV6_FRAG_COPYHEADER */
+
+  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;
+  u16_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 ipv6 header (we are now pointing to Fragment header).
+       This cannot fail since we already checked when receiving this fragment. */
+    if (pbuf_header_force(p, (s16_t)((u8_t*)p->payload - (u8_t*)IPV6_FRAG_HDRREF(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 == ipr) {
+      /* nothing to free, ipr is the only element on the list */
+      return;
+    }
+    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
+ * @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;
+  u16_t clen;
+  u8_t valid = 1;
+  struct pbuf *q;
+
+  IP6_FRAG_STATS_INC(ip6_frag.recv);
+
+  if ((const void*)ip6_current_header() != ((u8_t*)p->payload) - IP6_HLEN) {
+    /* ip6_frag_hdr must be in the first pbuf, not chained */
+    IP6_FRAG_STATS_INC(ip6_frag.proterr);
+    IP6_FRAG_STATS_INC(ip6_frag.drop);
+    goto nullreturn;
+  }
+
+  frag_hdr = (struct ip6_frag_hdr *) p->payload;
+
+  clen = pbuf_clen(p);
+
+  offset = lwip_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 = lwip_ntohs(ip6_current_header()->_plen);
+  len -= (u16_t)(((u8_t*)p->payload - (const 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(), &(IPV6_FRAG_HDRREF(ipr->iphdr)->src)) &&
+        ip6_addr_cmp(ip6_current_dest_addr(), &(IPV6_FRAG_HDRREF(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) {
+        /* re-search ipr_prev since it might have been removed */
+        for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
+          if (ipr_prev->next == ipr) {
+            break;
+          }
+        }
+      } else
+#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). */
+#if IPV6_FRAG_COPYHEADER
+    MEMCPY(&ipr->iphdr, ip6_current_header(), IP6_HLEN);
+#else /* IPV6_FRAG_COPYHEADER */
+    /* need to use the none-const pointer here: */
+    ipr->iphdr = ip_data.current_ip6_header;
+#endif /* IPV6_FRAG_COPYHEADER */
+
+    /* 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) {
+      /* re-search ipr_prev since it might have been removed */
+      for (ipr_prev = reassdatagrams; ipr_prev != NULL; ipr_prev = ipr_prev->next) {
+        if (ipr_prev->next == ipr) {
+          break;
+        }
+      }
+    } else
+#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. */
+#if IPV6_FRAG_COPYHEADER
+  if (IPV6_FRAG_REQROOM > 0) {
+    /* Make room for struct ip6_reass_helper (only required if sizeof(void*) > 4).
+       This cannot fail since we already checked when receiving this fragment. */
+    u8_t hdrerr = pbuf_header_force(p, IPV6_FRAG_REQROOM);
+    LWIP_UNUSED_ARG(hdrerr); /* in case of LWIP_NOASSERT */
+    LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
+  }
+#else /* IPV6_FRAG_COPYHEADER */
+  LWIP_ASSERT("sizeof(struct ip6_reass_helper) <= IP6_FRAG_HLEN, set IPV6_FRAG_COPYHEADER to 1",
+    sizeof(struct ip6_reass_helper) <= IP6_FRAG_HLEN);
+#endif /* IPV6_FRAG_COPYHEADER */
+  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) {
+#if IPV6_FRAG_COPYHEADER
+    if (iprh->next_pbuf != NULL) {
+      MEMCPY(&ipr->iphdr, ip6_current_header(), IP6_HLEN);
+    }
+#else /* IPV6_FRAG_COPYHEADER */
+    /* need to use the none-const pointer here: */
+    ipr->iphdr = ip_data.current_ip6_header;
+#endif /* IPV6_FRAG_COPYHEADER */
+  }
+
+  /* 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 */
+    struct ip6_hdr* iphdr_ptr;
+
+    /* chain together the pbufs contained within the ip6_reassdata list. */
+    iprh = (struct ip6_reass_helper*) ipr->p->payload;
+    while (iprh != NULL) {
+      struct pbuf* next_pbuf = iprh->next_pbuf;
+      if (next_pbuf != NULL) {
+        /* Save next helper struct (will be hidden in next step). */
+        iprh_tmp = (struct ip6_reass_helper*)next_pbuf->payload;
+
+        /* hide the fragment header for every succeeding fragment */
+        pbuf_header(next_pbuf, -IP6_FRAG_HLEN);
+#if IPV6_FRAG_COPYHEADER
+        if (IPV6_FRAG_REQROOM > 0) {
+          /* hide the extra bytes borrowed from ip6_hdr for struct ip6_reass_helper */
+          u8_t hdrerr = pbuf_header(next_pbuf, -(s16_t)(IPV6_FRAG_REQROOM));
+          LWIP_UNUSED_ARG(hdrerr); /* in case of LWIP_NOASSERT */
+          LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
+        }
+#endif
+        pbuf_cat(ipr->p, next_pbuf);
+      }
+      else {
+        iprh_tmp = NULL;
+      }
+
+      iprh = iprh_tmp;
+    }
+
+#if IPV6_FRAG_COPYHEADER
+    if (IPV6_FRAG_REQROOM > 0) {
+      /* get back room for struct ip6_reass_helper (only required if sizeof(void*) > 4) */
+      u8_t hdrerr = pbuf_header(ipr->p, -(s16_t)(IPV6_FRAG_REQROOM));
+      LWIP_UNUSED_ARG(hdrerr); /* in case of LWIP_NOASSERT */
+      LWIP_ASSERT("no room for struct ip6_reass_helper", hdrerr == 0);
+    }
+    iphdr_ptr = (struct ip6_hdr*)((u8_t*)ipr->p->payload - IP6_HLEN);
+    MEMCPY(iphdr_ptr, &ipr->iphdr, IP6_HLEN);
+#else
+    iphdr_ptr = ipr->iphdr;
+#endif
+
+    /* Adjust datagram length by adding header lengths. */
+    ipr->datagram_len += (u16_t)(((u8_t*)ipr->p->payload - (u8_t*)iphdr_ptr)
+                         + IP6_FRAG_HLEN
+                         - IP6_HLEN);
+
+    /* Set payload length in ip header. */
+    iphdr_ptr->_plen = lwip_htons(ipr->datagram_len);
+
+    /* Get the first 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.
+       This cannot fail since we already checked when receiving this fragment. */
+    if (pbuf_header_force(p, (s16_t)((u8_t*)p->payload - (u8_t*)iphdr_ptr))) {
+      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
+
+#if !LWIP_NETIF_TX_SINGLE_PBUF
+/** 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);
+}
+#endif /* !LWIP_NETIF_TX_SINGLE_PBUF */
+
+/**
+ * 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, const ip6_addr_t *dest)
+{
+  struct ip6_hdr *original_ip6hdr;
+  struct ip6_hdr *ip6hdr;
+  struct ip6_frag_hdr *frag_hdr;
+  struct pbuf *rambuf;
+#if !LWIP_NETIF_TX_SINGLE_PBUF
+  struct pbuf *newpbuf;
+  u16_t newpbuflen = 0;
+  u16_t left_to_copy;
+#endif
+  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;
+
+  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;
+
+#if LWIP_NETIF_TX_SINGLE_PBUF
+    rambuf = pbuf_alloc(PBUF_IP, cop + 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!",
+      (rambuf->len == rambuf->tot_len) && (rambuf->next == NULL));
+    poff += pbuf_copy_partial(p, (u8_t*)rambuf->payload + IP6_FRAG_HLEN, cop, poff);
+    /* make room for the IP header */
+    if (pbuf_header(rambuf, IP6_HLEN)) {
+      pbuf_free(rambuf);
+      IP6_FRAG_STATS_INC(ip6_frag.memerr);
+      return ERR_MEM;
+    }
+    /* fill in the IP header */
+    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);
+#else
+    /* 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)));
+    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;
+#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
+
+    /* Set headers */
+    frag_hdr->_nexth = original_ip6hdr->_nexth;
+    frag_hdr->reserved = 0;
+    frag_hdr->_fragment_offset = lwip_htons((fragment_offset & IP6_FRAG_OFFSET_MASK) | (last ? 0 : IP6_FRAG_MORE_FLAG));
+    frag_hdr->_identification = lwip_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/src/core/ipv6/mld6.c b/src/core/ipv6/mld6.c
new file mode 100644
index 0000000..9acb82f
--- /dev/null
+++ b/src/core/ipv6/mld6.c
@@ -0,0 +1,588 @@
+/**
+ * @file
+ * Multicast listener discovery
+ *
+ * @defgroup mld6 MLD6
+ * @ingroup ip6
+ * Multicast listener discovery for IPv6. Aims to be compliant with RFC 2710.
+ * No support for MLDv2.\n
+ * To be called from TCPIP thread
+ */
+
+/*
+ * 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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+/* 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/prot/mld6.h"
+#include "lwip/icmp6.h"
+#include "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/ip.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
+
+/* Forward declarations. */
+static struct mld_group *mld6_new_group(struct netif *ifp, const ip6_addr_t *addr);
+static err_t mld6_remove_group(struct netif *netif, struct mld_group *group);
+static void mld6_delayed_report(struct mld_group *group, u16_t maxresp);
+static void mld6_send(struct netif *netif, 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 = netif_mld6_data(netif);
+
+  netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_MLD6, NULL);
+
+  while (group != NULL) {
+    struct mld_group *next = group->next; /* avoid use-after-free below */
+
+    /* disable the group at the MAC level */
+    if (netif->mld_mac_filter != NULL) {
+      netif->mld_mac_filter(netif, &(group->group_address), NETIF_DEL_MAC_FILTER);
+    }
+
+    /* free group */
+    memp_free(MEMP_MLD6_GROUP, 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 = netif_mld6_data(netif);
+
+  while (group != NULL) {
+    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, const ip6_addr_t *addr)
+{
+  struct mld_group *group = netif_mld6_data(ifp);
+
+  while (group != NULL) {
+    if (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, const ip6_addr_t *addr)
+{
+  struct mld_group *group;
+
+  group = (struct mld_group *)memp_malloc(MEMP_MLD6_GROUP);
+  if (group != NULL) {
+    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               = netif_mld6_data(ifp);
+
+    netif_set_client_data(ifp, LWIP_NETIF_CLIENT_DATA_INDEX_MLD6, group);
+  }
+
+  return group;
+}
+
+/**
+ * Remove a group from the mld_group_list, but do not free it yet
+ *
+ * @param group the group to remove
+ * @return ERR_OK if group was removed from the list, an err_t otherwise
+ */
+static err_t
+mld6_remove_group(struct netif *netif, struct mld_group *group)
+{
+  err_t err = ERR_OK;
+
+  /* Is it the first group? */
+  if (netif_mld6_data(netif) == group) {
+    netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_MLD6, group->next);
+  } else {
+    /* look for group further down the list */
+    struct mld_group *tmpGroup;
+    for (tmpGroup = netif_mld6_data(netif); tmpGroup != NULL; tmpGroup = tmpGroup->next) {
+      if (tmpGroup->next == group) {
+        tmpGroup->next = group->next;
+        break;
+      }
+    }
+    /* Group not find group */
+    if (tmpGroup == NULL) {
+      err = ERR_ARG;
+    }
+  }
+
+  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 = netif_mld6_data(inp);
+      while (group != NULL) {
+        if ((!(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);
+}
+
+/**
+ * @ingroup mld6
+ * 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(const ip6_addr_t *srcaddr, const ip6_addr_t *groupaddr)
+{
+  err_t         err = ERR_VAL; /* no matching interface */
+  struct netif *netif;
+
+  /* loop through netif's */
+  netif = netif_list;
+  while (netif != NULL) {
+    /* Should we join this interface ? */
+    if (ip6_addr_isany(srcaddr) ||
+        netif_get_ip6_addr_match(netif, srcaddr) >= 0) {
+      err = mld6_joingroup_netif(netif, groupaddr);
+      if (err != ERR_OK) {
+        return err;
+      }
+    }
+
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+
+  return err;
+}
+
+/**
+ * @ingroup mld6
+ * Join a group on a network interface.
+ *
+ * @param netif the network interface which should join a new group.
+ * @param groupaddr the ipv6 address of the group to join
+ * @return ERR_OK if group was joined on the netif, an err_t otherwise
+ */
+err_t
+mld6_joingroup_netif(struct netif *netif, const ip6_addr_t *groupaddr)
+{
+  struct mld_group *group;
+
+  /* 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, NETIF_ADD_MAC_FILTER);
+    }
+
+    /* Report our membership. */
+    MLD6_STATS_INC(mld6.tx_report);
+    mld6_send(netif, group, ICMP6_TYPE_MLR);
+    mld6_delayed_report(group, MLD6_JOIN_DELAYING_MEMBER_TMR_MS);
+  }
+
+  /* Increment group use */
+  group->use++;
+  return ERR_OK;
+}
+
+/**
+ * @ingroup mld6
+ * 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(const ip6_addr_t *srcaddr, const ip6_addr_t *groupaddr)
+{
+  err_t         err = ERR_VAL; /* no matching interface */
+  struct netif *netif;
+
+  /* loop through netif's */
+  netif = netif_list;
+  while (netif != NULL) {
+    /* Should we leave this interface ? */
+    if (ip6_addr_isany(srcaddr) ||
+        netif_get_ip6_addr_match(netif, srcaddr) >= 0) {
+      err_t res = mld6_leavegroup_netif(netif, groupaddr);
+      if (err != ERR_OK) {
+        /* Store this result if we have not yet gotten a success */
+        err = res;
+      }
+    }
+    /* proceed to next network interface */
+    netif = netif->next;
+  }
+
+  return err;
+}
+
+/**
+ * @ingroup mld6
+ * Leave a group on a network interface.
+ *
+ * @param netif the network interface which should leave the group.
+ * @param groupaddr the ipv6 address of the group to leave
+ * @return ERR_OK if group was left on the netif, an err_t otherwise
+ */
+err_t
+mld6_leavegroup_netif(struct netif *netif, const ip6_addr_t *groupaddr)
+{
+  struct mld_group *group;
+
+  /* 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) {
+      /* Remove the group from the list */
+      mld6_remove_group(netif, group);
+
+      /* If we are the last reporter for this group */
+      if (group->last_reporter_flag) {
+        MLD6_STATS_INC(mld6.tx_leave);
+        mld6_send(netif, group, ICMP6_TYPE_MLD);
+      }
+
+      /* Disable the group at the MAC level */
+      if (netif->mld_mac_filter != NULL) {
+        netif->mld_mac_filter(netif, groupaddr, NETIF_DEL_MAC_FILTER);
+      }
+
+      /* free group struct */
+      memp_free(MEMP_MLD6_GROUP, group);
+    } else {
+      /* Decrement group use */
+      group->use--;
+    }
+
+    /* Left group */
+    return ERR_OK;
+  }
+
+  /* Group not found */
+  return ERR_VAL;
+}
+
+
+/**
+ * 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 netif *netif = netif_list;
+
+  while (netif != NULL) {
+    struct mld_group *group = netif_mld6_data(netif);
+
+    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(netif, group, ICMP6_TYPE_MLR);
+            group->group_state = MLD6_GROUP_IDLE_MEMBER;
+          }
+        }
+      }
+      group = group->next;
+    }
+    netif = netif->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;
+  if (maxresp == 0) {
+    maxresp = 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 netif *netif, struct mld_group *group, u8_t type)
+{
+  struct mld_header *mld_hdr;
+  struct pbuf *p;
+  const 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) {
+    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(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_ANY6;
+  } else {
+    /* Use link-local address as source address. */
+    src_addr = netif_ip6_addr(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));
+
+#if CHECKSUM_GEN_ICMP6
+  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) {
+    mld_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len,
+      src_addr, &(group->group_address));
+  }
+#endif /* CHECKSUM_GEN_ICMP6 */
+
+  /* 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);
+
+  if (type == ICMP6_TYPE_MLR) {
+    /* Remember we were the last to report */
+    group->last_reporter_flag = 1;
+  }
+
+  /* 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, netif);
+  pbuf_free(p);
+}
+
+#endif /* LWIP_IPV6 */
diff --git a/src/core/ipv6/nd6.c b/src/core/ipv6/nd6.c
new file mode 100644
index 0000000..0b36718
--- /dev/null
+++ b/src/core/ipv6/nd6.c
@@ -0,0 +1,2102 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/nd6.h"
+#include "lwip/priv/nd6_priv.h"
+#include "lwip/prot/nd6.h"
+#include "lwip/prot/icmp6.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/ip.h"
+#include "lwip/stats.h"
+#include "lwip/dns.h"
+
+#include <string.h>
+
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+
+#if LWIP_IPV6_DUP_DETECT_ATTEMPTS > IP6_ADDR_TENTATIVE_COUNT_MASK
+#error LWIP_IPV6_DUP_DETECT_ATTEMPTS > IP6_ADDR_TENTATIVE_COUNT_MASK
+#endif
+
+/* 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(const 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(const ip6_addr_t *ip6addr);
+static s8_t nd6_new_destination_cache_entry(void);
+static s8_t nd6_is_prefix_in_netif(const ip6_addr_t *ip6addr, struct netif *netif);
+static s8_t nd6_select_router(const ip6_addr_t *ip6addr, struct netif *netif);
+static s8_t nd6_get_router(const ip6_addr_t *router_addr, struct netif *netif);
+static s8_t nd6_new_router(const 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);
+static s8_t nd6_get_next_hop_entry(const ip6_addr_t *ip6addr, struct netif *netif);
+static err_t nd6_queue_packet(s8_t neighbor_index, struct pbuf *q);
+
+#define ND6_SEND_FLAG_MULTICAST_DEST 0x01
+#define ND6_SEND_FLAG_ALLNODES_DEST 0x02
+static void nd6_send_ns(struct netif *netif, const ip6_addr_t *target_addr, u8_t flags);
+static void nd6_send_na(struct netif *netif, const ip6_addr_t *target_addr, u8_t flags);
+static void nd6_send_neighbor_cache_probe(struct nd6_neighbor_cache_entry *entry, u8_t flags);
+#if LWIP_IPV6_SEND_ROUTER_SOLICIT
+static err_t 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())) {
+      ip6_addr_t target_address;
+      
+      /* This is an unsolicited NA.
+       * link-layer changed?
+       * part of DAD mechanism? */
+
+      /* Create an aligned copy. */
+      ip6_addr_set(&target_address, &(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_isinvalid(netif_ip6_addr_state(inp, i)) &&
+            ip6_addr_cmp(&target_address, netif_ip6_addr(inp, i))) {
+          /* We are using a duplicate address. */
+          netif_ip6_addr_set_state(inp, i, IP6_ADDR_INVALID);
+
+#if LWIP_IPV6_AUTOCONFIG
+          /* Check to see if this address was autoconfigured. */
+          if (!ip6_addr_islinklocal(&target_address)) {
+            i = nd6_get_onlink_prefix(&target_address, 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 */
+
+      /* Check that link-layer address option also fits in packet. */
+      if (p->len < (sizeof(struct na_header) + 2)) {
+        /* @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));
+
+      if (p->len < (sizeof(struct na_header) + (lladdr_opt->length << 3))) {
+        /* @todo debug message */
+        pbuf_free(p);
+        ND6_STATS_INC(nd6.lenerr);
+        ND6_STATS_INC(nd6.drop);
+        return;
+      }
+
+      /* This is an unsolicited NA, most likely there was a LLADDR change. */
+      i = nd6_find_neighbor_cache_entry(&target_address);
+      if (i >= 0) {
+        if (na_hdr->flags & ND6_FLAG_OVERRIDE) {
+          MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
+        }
+      }
+    } else {
+      ip6_addr_t target_address;
+
+      /* This is a solicited NA.
+       * neighbor address resolution response?
+       * neighbor unreachability detection response? */
+
+      /* Create an aligned copy. */
+      ip6_addr_set(&target_address, &(na_hdr->target_address));
+
+      /* Find the cache entry corresponding to this na. */
+      i = nd6_find_neighbor_cache_entry(&target_address);
+      if (i < 0) {
+        /* We no longer care about this target address. drop it. */
+        pbuf_free(p);
+        return;
+      }
+
+      /* Update cache entry. */
+      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) + 2)) {
+          /* @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));
+
+        if (p->len < (sizeof(struct na_header) + (lladdr_opt->length << 3))) {
+          /* @todo debug message */
+          pbuf_free(p);
+          ND6_STATS_INC(nd6.lenerr);
+          ND6_STATS_INC(nd6.drop);
+          return;
+        }
+
+        MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
+      }
+
+      neighbor_cache[i].netif = inp;
+      neighbor_cache[i].state = ND6_REACHABLE;
+      neighbor_cache[i].counter.reachable_time = reachable_time;
+
+      /* 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. */
+    if (p->len >= (sizeof(struct ns_header) + 2)) {
+      lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct ns_header));
+      if (p->len < (sizeof(struct ns_header) + (lladdr_opt->length << 3))) {
+        lladdr_opt = NULL;
+      }
+    } else {
+      lladdr_opt = NULL;
+    }
+
+    /* 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_isinvalid(netif_ip6_addr_state(inp, i)) &&
+            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 {
+      ip6_addr_t target_address;
+      
+      /* 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 / ND6_TMR_INTERVAL;
+        }
+      } 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 / ND6_TMR_INTERVAL;
+      }
+
+      /* Create an aligned copy. */
+      ip6_addr_set(&target_address, &(ns_hdr->target_address));
+
+      /* Send back a NA for us. Allocate the reply pbuf. */
+      nd6_send_na(inp, &target_address, 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;
+#if LWIP_ND6_RDNSS_MAX_DNS_SERVERS
+    /* There can by multiple RDNSS options per RA */
+    u8_t rdnss_server_idx = 0;
+#endif /* LWIP_ND6_RDNSS_MAX_DNS_SERVERS */
+
+    /* 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
+    /* ensure at least one solicitation is sent */
+    if ((inp->rs_count < LWIP_ND6_MAX_MULTICAST_SOLICIT) ||
+        (nd6_send_rs(inp) == ERR_OK)) {
+      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 = lwip_htons(ra_hdr->router_lifetime);
+
+    /* Re-set default timer values. */
+#if LWIP_ND6_ALLOW_RA_UPDATES
+    if (ra_hdr->retrans_timer > 0) {
+      retrans_timer = lwip_htonl(ra_hdr->retrans_timer);
+    }
+    if (ra_hdr->reachable_time > 0) {
+      reachable_time = lwip_htonl(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;
+        if (pbuf_copy_partial(p, buffer, sizeof(struct prefix_option), offset) != sizeof(struct prefix_option)) {
+          pbuf_free(p);
+          ND6_STATS_INC(nd6.lenerr);
+          ND6_STATS_INC(nd6.drop);
+          return;
+        }
+      }
+      if (buffer[1] == 0) {
+        /* zero-length extension. drop packet */
+        pbuf_free(p);
+        ND6_STATS_INC(nd6.lenerr);
+        ND6_STATS_INC(nd6.drop);
+        return;
+      }
+      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 (lwip_htonl(mtu_opt->mtu) >= 1280) {
+#if LWIP_ND6_ALLOW_RA_UPDATES
+          inp->mtu = (u16_t)lwip_htonl(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) &&
+            (prefix_opt->prefix_length == 64)  &&
+            !ip6_addr_islinklocal(&(prefix_opt->prefix))) {
+          /* Add to on-link prefix list. */
+          s8_t prefix;
+          ip6_addr_t prefix_addr;
+
+          /* Get a memory-aligned copy of the prefix. */
+          ip6_addr_set(&prefix_addr, &(prefix_opt->prefix));
+
+          /* find cache entry for this prefix. */
+          prefix = nd6_get_onlink_prefix(&prefix_addr, inp);
+          if (prefix < 0) {
+            /* Create a new cache entry. */
+            prefix = nd6_new_onlink_prefix(&prefix_addr, inp);
+          }
+          if (prefix >= 0) {
+            prefix_list[prefix].invalidation_timer = lwip_htonl(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[prefix].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;
+#if LWIP_ND6_RDNSS_MAX_DNS_SERVERS
+      case ND6_OPTION_TYPE_RDNSS:
+      {
+        u8_t num, n;
+        struct rdnss_option * rdnss_opt;
+
+        rdnss_opt = (struct rdnss_option *)buffer;
+        num = (rdnss_opt->length - 1) / 2;
+        for (n = 0; (rdnss_server_idx < DNS_MAX_SERVERS) && (n < num); n++) {
+          ip_addr_t rdnss_address;
+
+          /* Get a memory-aligned copy of the prefix. */
+          ip_addr_copy_from_ip6(rdnss_address, rdnss_opt->rdnss_address[n]);
+
+          if (htonl(rdnss_opt->lifetime) > 0) {
+            /* TODO implement Lifetime > 0 */
+            dns_setserver(rdnss_server_idx++, &rdnss_address);
+          } else {
+            /* TODO implement DNS removal in dns.c */
+            u8_t s;
+            for (s = 0; s < DNS_MAX_SERVERS; s++) {
+              const ip_addr_t *addr = dns_getserver(s);
+              if(ip_addr_cmp(addr, &rdnss_address)) {
+                dns_setserver(s, NULL);
+              }
+            }
+          }
+        }
+        break;
+      }
+#endif /* LWIP_ND6_RDNSS_MAX_DNS_SERVERS */
+      default:
+        /* Unrecognized option, abort. */
+        ND6_STATS_INC(nd6.proterr);
+        break;
+      }
+      /* option length is checked earlier to be non-zero to make sure loop ends */
+      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;
+    ip6_addr_t tmp;
+
+    /* 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;
+
+    if (p->len >= (sizeof(struct redirect_header) + 2)) {
+      lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct redirect_header));
+      if (p->len < (sizeof(struct redirect_header) + (lladdr_opt->length << 3))) {
+        lladdr_opt = NULL;
+      }
+    } else {
+      lladdr_opt = NULL;
+    }
+
+    /* Copy original destination address to current source address, to have an aligned copy. */
+    ip6_addr_set(&tmp, &(redir_hdr->destination_address));
+
+    /* Find dest address in cache */
+    i = nd6_find_destination_cache_entry(&tmp);
+    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(&tmp, &(redir_hdr->target_address));
+
+        i = nd6_find_neighbor_cache_entry(&tmp);
+        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), &tmp);
+
+            /* 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 / ND6_TMR_INTERVAL;
+          }
+        }
+        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 / ND6_TMR_INTERVAL;
+          }
+        }
+      }
+    }
+    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 */
+    u32_t pmtu;
+    ip6_addr_t tmp;
+
+    /* 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(&tmp, &(ip6hdr->dest));
+
+    /* Look for entry in destination cache. */
+    i = nd6_find_destination_cache_entry(&tmp);
+    if (i < 0) {
+      /* Destination not in cache, drop packet. */
+      pbuf_free(p);
+      return;
+    }
+
+    /* Change the Path MTU. */
+    pmtu = lwip_htonl(icmp6hdr->data);
+    destination_cache[i].pmtu = (u16_t)LWIP_MIN(pmtu, 0xFFFF);
+
+    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;
+  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) &&
+          (!neighbor_cache[i].isrouter)) {
+        /* Retries exceeded. */
+        nd6_free_neighbor_cache_entry(i);
+      } else {
+        /* Send a NS for this entry. */
+        neighbor_cache[i].counter.probes_sent++;
+        nd6_send_neighbor_cache_probe(&neighbor_cache[i], 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++;
+      break;
+    case ND6_DELAY:
+      if (neighbor_cache[i].counter.delay_time <= 1) {
+        /* Change to PROBE state. */
+        neighbor_cache[i].state = ND6_PROBE;
+        neighbor_cache[i].counter.probes_sent = 0;
+      } else {
+        neighbor_cache[i].counter.delay_time--;
+      }
+      break;
+    case ND6_PROBE:
+      if ((neighbor_cache[i].counter.probes_sent >= LWIP_ND6_MAX_MULTICAST_SOLICIT) &&
+          (!neighbor_cache[i].isrouter)) {
+        /* Retries exceeded. */
+        nd6_free_neighbor_cache_entry(i);
+      } else {
+        /* Send a NS for this entry. */
+        neighbor_cache[i].counter.probes_sent++;
+        nd6_send_neighbor_cache_probe(&neighbor_cache[i], 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 remaining. 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].netif != NULL) {
+      if (prefix_list[i].invalidation_timer < ND6_TMR_INTERVAL / 1000) {
+        /* Entry timed out, remove it */
+        prefix_list[i].invalidation_timer = 0;
+
+#if LWIP_IPV6_AUTOCONFIG
+        /* If any addresses were configured with this prefix, remove them */
+        if (prefix_list[i].flags & ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED) {
+          s8_t j;
+
+          for (j = 1; j < LWIP_IPV6_NUM_ADDRESSES; j++) {
+            if ((netif_ip6_addr_state(prefix_list[i].netif, j) != IP6_ADDR_INVALID) &&
+                ip6_addr_netcmp(&prefix_list[i].prefix, netif_ip6_addr(prefix_list[i].netif, j))) {
+              netif_ip6_addr_set_state(prefix_list[i].netif, j, IP6_ADDR_INVALID);
+              prefix_list[i].flags = 0;
+
+              /* Exit loop. */
+              break;
+            }
+          }
+        }
+#endif /* LWIP_IPV6_AUTOCONFIG */
+
+        prefix_list[i].netif = NULL;
+        prefix_list[i].flags = 0;
+      } else {
+        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)) {
+          s8_t j;
+          /* 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_ADDR_INVALID) {
+              /* Generate an address using this prefix and interface ID from link-local address. */
+              netif_ip6_addr_set_parts(prefix_list[i].netif, j,
+                prefix_list[i].prefix.addr[0], prefix_list[i].prefix.addr[1],
+                netif_ip6_addr(prefix_list[i].netif, 0)->addr[2], netif_ip6_addr(prefix_list[i].netif, 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) {
+      u8_t addr_state = netif_ip6_addr_state(netif, i);
+      if (ip6_addr_istentative(addr_state)) {
+        if ((addr_state & IP6_ADDR_TENTATIVE_COUNT_MASK) >= LWIP_IPV6_DUP_DETECT_ATTEMPTS) {
+          /* No NA received in response. Mark address as valid. */
+          netif_ip6_addr_set_state(netif, i, IP6_ADDR_PREFERRED);
+          /* @todo implement preferred and valid lifetimes. */
+        } else if (netif->flags & NETIF_FLAG_UP) {
+          /* Send a NS for this address. */
+          nd6_send_ns(netif, netif_ip6_addr(netif, i), ND6_SEND_FLAG_MULTICAST_DEST);
+          /* tentative: set next state by increasing by one */
+          netif_ip6_addr_set_state(netif, i, addr_state + 1);
+          /* @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) &&
+        (!ip6_addr_isinvalid(netif_ip6_addr_state(netif, 0)))) {
+      if (nd6_send_rs(netif) == ERR_OK) {
+        netif->rs_count--;
+      }
+    }
+  }
+#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
+
+}
+
+/** Send a neighbor solicitation message for a specific neighbor cache entry
+ *
+ * @param entry the neightbor cache entry for wich to send the message
+ * @param flags one of ND6_SEND_FLAG_*
+ */
+static void
+nd6_send_neighbor_cache_probe(struct nd6_neighbor_cache_entry *entry, u8_t flags)
+{
+  nd6_send_ns(entry->netif, &entry->next_hop_address, flags);
+}
+
+/**
+ * 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, const ip6_addr_t *target_addr, u8_t flags)
+{
+  struct ns_header *ns_hdr;
+  struct pbuf *p;
+  const ip6_addr_t *src_addr;
+  u16_t lladdr_opt_len;
+
+  if (ip6_addr_isvalid(netif_ip6_addr_state(netif,0))) {
+    /* Use link-local address as source address. */
+    src_addr = netif_ip6_addr(netif, 0);
+    /* calculate option length (in 8-byte-blocks) */
+    lladdr_opt_len = ((netif->hwaddr_len + 2) + 7) >> 3;
+  } else {
+    src_addr = IP6_ADDR_ANY6;
+    /* Option "MUST NOT be included when the source IP address is the unspecified address." */
+    lladdr_opt_len = 0;
+  }
+
+  /* Allocate a packet. */
+  p = pbuf_alloc(PBUF_IP, sizeof(struct ns_header) + (lladdr_opt_len << 3), PBUF_RAM);
+  if (p == NULL) {
+    ND6_STATS_INC(nd6.memerr);
+    return;
+  }
+
+  /* Set fields. */
+  ns_hdr = (struct ns_header *)p->payload;
+
+  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);
+
+  if (lladdr_opt_len != 0) {
+    struct lladdr_option *lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct ns_header));
+    lladdr_opt->type = ND6_OPTION_TYPE_SOURCE_LLADDR;
+    lladdr_opt->length = (u8_t)lladdr_opt_len;
+    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;
+  }
+
+#if CHECKSUM_GEN_ICMP6
+  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) {
+    ns_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len, src_addr,
+      target_addr);
+  }
+#endif /* CHECKSUM_GEN_ICMP6 */
+
+  /* Send the packet out. */
+  ND6_STATS_INC(nd6.xmit);
+  ip6_output_if(p, (src_addr == IP6_ADDR_ANY6) ? 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, const ip6_addr_t *target_addr, u8_t flags)
+{
+  struct na_header *na_hdr;
+  struct lladdr_option *lladdr_opt;
+  struct pbuf *p;
+  const ip6_addr_t *src_addr;
+  const ip6_addr_t *dest_addr;
+  u16_t lladdr_opt_len;
+
+  /* Use link-local address as source address. */
+  /* src_addr = netif_ip6_addr(netif, 0); */
+  /* Use target address as source address. */
+  src_addr = target_addr;
+
+  /* Allocate a packet. */
+  lladdr_opt_len = ((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0);
+  p = pbuf_alloc(PBUF_IP, sizeof(struct na_header) + (lladdr_opt_len << 3), PBUF_RAM);
+  if (p == NULL) {
+    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 = (u8_t)lladdr_opt_len;
+  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();
+  }
+
+#if CHECKSUM_GEN_ICMP6
+  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) {
+    na_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len, src_addr,
+      dest_addr);
+  }
+#endif /* CHECKSUM_GEN_ICMP6 */
+
+  /* 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 err_t
+nd6_send_rs(struct netif *netif)
+{
+  struct rs_header *rs_hdr;
+  struct lladdr_option *lladdr_opt;
+  struct pbuf *p;
+  const ip6_addr_t *src_addr;
+  err_t err;
+  u16_t lladdr_opt_len = 0;
+
+  /* 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_ANY6;
+  }
+
+  /* Generate the all routers target address. */
+  ip6_addr_set_allrouters_linklocal(&multicast_address);
+
+  /* Allocate a packet. */
+  if (src_addr != IP6_ADDR_ANY6) {
+    lladdr_opt_len = ((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0);
+  }
+  p = pbuf_alloc(PBUF_IP, sizeof(struct rs_header) + (lladdr_opt_len << 3), PBUF_RAM);
+  if (p == NULL) {
+    ND6_STATS_INC(nd6.memerr);
+    return ERR_BUF;
+  }
+
+  /* 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_ANY6) {
+    /* 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 = (u8_t)lladdr_opt_len;
+    SMEMCPY(lladdr_opt->addr, netif->hwaddr, netif->hwaddr_len);
+  }
+
+#if CHECKSUM_GEN_ICMP6
+  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) {
+    rs_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len, src_addr,
+      &multicast_address);
+  }
+#endif /* CHECKSUM_GEN_ICMP6 */
+
+  /* Send the packet out. */
+  ND6_STATS_INC(nd6.xmit);
+
+  err = ip6_output_if(p, (src_addr == IP6_ADDR_ANY6) ? NULL : src_addr, &multicast_address,
+      LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, netif);
+  pbuf_free(p);
+
+  return err;
+}
+#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(const 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;
+  }
+  if (neighbor_cache[i].isrouter) {
+    /* isrouter needs to be cleared before deleting a neighbor cache entry */
+    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(const 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;
+}
+
+/**
+ * Clear the destination cache.
+ *
+ * This operation may be necessary for consistency in the light of changing
+ * local addresses and/or use of the gateway hook.
+ */
+void
+nd6_clear_destination_cache(void)
+{
+  int i;
+
+  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
+    ip6_addr_set_any(&destination_cache[i].destination_addr);
+  }
+}
+
+/**
+ * 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(const 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
+ */
+static s8_t
+nd6_select_router(const 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 a router-announced route to the given destination.
+ *
+ * The caller is responsible for checking whether the returned netif, if any,
+ * is in a suitable state (up, link up) to be used for packet transmission.
+ *
+ * @param ip6addr the destination IPv6 address
+ * @return the netif to use for the destination, or NULL if none found
+ */
+struct netif *
+nd6_find_route(const ip6_addr_t *ip6addr)
+{
+  s8_t i;
+
+  i = nd6_select_router(ip6addr, NULL);
+  if (i >= 0) {
+    if (default_router_list[i].neighbor_entry != NULL) {
+      return default_router_list[i].neighbor_entry->netif; /* may be NULL */
+    }
+  }
+
+  return NULL;
+}
+
+/**
+ * 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(const 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(const ip6_addr_t *router_addr, struct netif *netif)
+{
+  s8_t router_index;
+  s8_t free_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 = 1;
+    nd6_send_neighbor_cache_probe(&neighbor_cache[neighbor_index], ND6_SEND_FLAG_MULTICAST_DEST);
+  }
+
+  /* Mark neighbor as router. */
+  neighbor_cache[neighbor_index].isrouter = 1;
+
+  /* Look for empty entry. */
+  free_router_index = LWIP_ND6_NUM_ROUTERS;
+  for (router_index = LWIP_ND6_NUM_ROUTERS - 1; router_index >= 0; router_index--) {
+    /* check if router already exists (this is a special case for 2 netifs on the same subnet
+       - e.g. wifi and cable) */
+    if(default_router_list[router_index].neighbor_entry == &(neighbor_cache[neighbor_index])){ 
+      return router_index; 
+    } 
+    if (default_router_list[router_index].neighbor_entry == NULL) {
+      /* remember lowest free index to create a new entry */
+      free_router_index = router_index;
+    }
+  }
+  if (free_router_index < LWIP_ND6_NUM_ROUTERS) {
+    default_router_list[free_router_index].neighbor_entry = &(neighbor_cache[neighbor_index]);
+    return free_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 /* LWIP_IPV6_AUTOCONFIG */
+      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
+ */
+static s8_t
+nd6_get_next_hop_entry(const ip6_addr_t *ip6addr, struct netif *netif)
+{
+#ifdef LWIP_HOOK_ND6_GET_GW
+  const ip6_addr_t *next_hop_addr;
+#endif /* LWIP_HOOK_ND6_GET_GW */
+  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);
+#ifdef LWIP_HOOK_ND6_GET_GW
+      } else if ((next_hop_addr = LWIP_HOOK_ND6_GET_GW(netif, ip6addr)) != NULL) {
+        /* Next hop for destination provided by hook function. */
+        destination_cache[nd6_cached_destination_index].pmtu = netif->mtu;
+        ip6_addr_set(&destination_cache[nd6_cached_destination_index].next_hop_addr, next_hop_addr);
+#endif /* LWIP_HOOK_ND6_GET_GW */
+      } 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 = 1;
+      nd6_send_neighbor_cache_probe(&neighbor_cache[i], ND6_SEND_FLAG_MULTICAST_DEST);
+    }
+  }
+
+  /* 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
+ */
+static 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;
+  ip6_addr_t dest;
+#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);
+    /* Create an aligned copy. */
+    ip6_addr_set(&dest, &(ip6hdr->dest));
+    /* send the queued IPv6 packet */
+    (neighbor_cache[i].netif)->output_ip6(neighbor_cache[i].netif, q->p, &dest);
+    /* 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);
+    /* Create an aligned copy. */
+    ip6_addr_set(&dest, &(ip6hdr->dest));
+    /* send the queued IPv6 packet */
+    (neighbor_cache[i].netif)->output_ip6(neighbor_cache[i].netif, neighbor_cache[i].q, &dest);
+    /* free the queued IP packet */
+    pbuf_free(neighbor_cache[i].q);
+    neighbor_cache[i].q = NULL;
+  }
+#endif /* LWIP_ND6_QUEUEING */
+}
+
+/**
+ * A packet is to be transmitted to a specific IPv6 destination on a specific
+ * interface. Check if we can find the hardware address of the next hop to use
+ * for the packet. If so, give the hardware address to the caller, which should
+ * use it to send the packet right away. Otherwise, enqueue the packet for
+ * later transmission while looking up the hardware address, if possible.
+ *
+ * As such, this function returns one of three different possible results:
+ *
+ * - ERR_OK with a non-NULL 'hwaddrp': the caller should send the packet now.
+ * - ERR_OK with a NULL 'hwaddrp': the packet has been enqueued for later.
+ * - not ERR_OK: something went wrong; forward the error upward in the stack.
+ *
+ * @param netif The lwIP network interface on which the IP packet will be sent.
+ * @param q The pbuf(s) containing the IP packet to be sent.
+ * @param ip6addr The destination IPv6 address of the packet.
+ * @param hwaddrp On success, filled with a pointer to a HW address or NULL (meaning
+ *        the packet has been queued).
+ * @return
+ * - ERR_OK on success, ERR_RTE if no route was found for the packet,
+ * or ERR_MEM if low memory conditions prohibit sending the packet at all.
+ */
+err_t
+nd6_get_next_hop_addr_or_queue(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr, const u8_t **hwaddrp)
+{
+  s8_t i;
+
+  /* Get next hop record. */
+  i = nd6_get_next_hop_entry(ip6addr, netif);
+  if (i < 0) {
+    /* failed to get a next hop neighbor record. */
+    return i;
+  }
+
+  /* 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 / ND6_TMR_INTERVAL;
+  }
+  /* @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)) {
+
+    /* Tell the caller to send out the packet now. */
+    *hwaddrp = neighbor_cache[i].lladdr;
+    return ERR_OK;
+  }
+
+  /* We should queue packet on this interface. */
+  *hwaddrp = NULL;
+  return nd6_queue_packet(i, q);
+}
+
+
+/**
+ * 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(const 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(const 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;
+  }
+
+  /* For safety: don't set as reachable if we don't have a LL address yet. Misuse protection. */
+  if (neighbor_cache[i].state == ND6_INCOMPLETE || neighbor_cache[i].state == ND6_NO_ENTRY) {
+    return;
+  }
+
+  /* Set reachability state. */
+  neighbor_cache[i].state = ND6_REACHABLE;
+  neighbor_cache[i].counter.reachable_time = reachable_time;
+}
+#endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */
+
+/**
+ * Remove all prefix, neighbor_cache and router entries of the specified netif.
+ *
+ * @param netif points to a network interface
+ */
+void
+nd6_cleanup_netif(struct netif *netif)
+{
+  u8_t i;
+  s8_t router_index;
+  for (i = 0; i < LWIP_ND6_NUM_PREFIXES; i++) {
+    if (prefix_list[i].netif == netif) {
+      prefix_list[i].netif = NULL;
+      prefix_list[i].flags = 0;
+    }
+  }
+  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
+    if (neighbor_cache[i].netif == netif) {
+      for (router_index = 0; router_index < LWIP_ND6_NUM_ROUTERS; router_index++) {
+        if (default_router_list[router_index].neighbor_entry == &neighbor_cache[i]) {
+          default_router_list[router_index].neighbor_entry = NULL;
+          default_router_list[router_index].flags = 0;
+        }
+      }
+      neighbor_cache[i].isrouter = 0;
+      nd6_free_neighbor_cache_entry(i);
+    }
+  }
+}
+
+#if LWIP_IPV6_MLD
+/**
+ * The state of a local IPv6 address entry is about to change. If needed, join
+ * or leave the solicited-node multicast group for the address.
+ *
+ * @param netif The netif that owns the address.
+ * @param addr_idx The index of the address.
+ * @param new_state The new (IP6_ADDR_) state for the address.
+ */
+void
+nd6_adjust_mld_membership(struct netif *netif, s8_t addr_idx, u8_t new_state)
+{
+  u8_t old_state, old_member, new_member;
+
+  old_state = netif_ip6_addr_state(netif, addr_idx);
+
+  /* Determine whether we were, and should be, a member of the solicited-node
+   * multicast group for this address. For tentative addresses, the group is
+   * not joined until the address enters the TENTATIVE_1 (or VALID) state. */
+  old_member = (old_state != IP6_ADDR_INVALID && old_state != IP6_ADDR_TENTATIVE);
+  new_member = (new_state != IP6_ADDR_INVALID && new_state != IP6_ADDR_TENTATIVE);
+
+  if (old_member != new_member) {
+    ip6_addr_set_solicitednode(&multicast_address, netif_ip6_addr(netif, addr_idx)->addr[3]);
+
+    if (new_member) {
+      mld6_joingroup_netif(netif, &multicast_address);
+    } else {
+      mld6_leavegroup_netif(netif, &multicast_address);
+    }
+  }
+}
+#endif /* LWIP_IPV6_MLD */
+
+#endif /* LWIP_IPV6 */
diff --git a/src/core/mem.c b/src/core/mem.c
new file mode 100644
index 0000000..db3b7cc
--- /dev/null
+++ b/src/core/mem.c
@@ -0,0 +1,777 @@
+/**
+ * @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
+ * MEMP_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@sics.se>
+ *         Simon Goldschmidt
+ *
+ */
+
+#include "lwip/opt.h"
+#include "lwip/mem.h"
+#include "lwip/def.h"
+#include "lwip/sys.h"
+#include "lwip/stats.h"
+#include "lwip/err.h"
+
+#include <string.h>
+
+#if MEM_LIBC_MALLOC
+#include <stdlib.h> /* for malloc()/free() */
+#endif
+
+#if MEM_LIBC_MALLOC || MEM_USE_POOLS
+
+/** mem_init is not used when using pools instead of a heap or using
+ * C library malloc().
+ */
+void
+mem_init(void)
+{
+}
+
+/** mem_trim is not used when using pools instead of a heap or using
+ * C library malloc(): we can't free part of a pool element and the stack
+ * support mem_trim() to return a different pointer
+ */
+void*
+mem_trim(void *mem, mem_size_t size)
+{
+  LWIP_UNUSED_ARG(size);
+  return mem;
+}
+#endif /* MEM_LIBC_MALLOC || MEM_USE_POOLS */
+
+#if MEM_LIBC_MALLOC
+/* lwIP heap implemented using C library malloc() */
+
+/* in case C library malloc() needs extra protection,
+ * allow these defines to be overridden.
+ */
+#ifndef mem_clib_free
+#define mem_clib_free free
+#endif
+#ifndef mem_clib_malloc
+#define mem_clib_malloc malloc
+#endif
+#ifndef mem_clib_calloc
+#define mem_clib_calloc calloc
+#endif
+
+#if LWIP_STATS && MEM_STATS
+#define MEM_LIBC_STATSHELPER_SIZE LWIP_MEM_ALIGN_SIZE(sizeof(mem_size_t))
+#else
+#define MEM_LIBC_STATSHELPER_SIZE 0
+#endif
+
+/**
+ * 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 must always be aligned (as defined by MEM_ALIGNMENT).
+ */
+void *
+mem_malloc(mem_size_t size)
+{
+  void* ret = mem_clib_malloc(size + MEM_LIBC_STATSHELPER_SIZE);
+  if (ret == NULL) {
+    MEM_STATS_INC(err);
+  } else {
+    LWIP_ASSERT("malloc() must return aligned memory", LWIP_MEM_ALIGN(ret) == ret);
+#if LWIP_STATS && MEM_STATS
+    *(mem_size_t*)ret = size;
+    ret = (u8_t*)ret + MEM_LIBC_STATSHELPER_SIZE;
+    MEM_STATS_INC_USED(used, size);
+#endif
+  }
+  return ret;
+}
+
+/** Put memory back on the heap
+ *
+ * @param rmem is the pointer as returned by a previous call to mem_malloc()
+ */
+void
+mem_free(void *rmem)
+{
+  LWIP_ASSERT("rmem != NULL", (rmem != NULL));
+  LWIP_ASSERT("rmem == MEM_ALIGN(rmem)", (rmem == LWIP_MEM_ALIGN(rmem)));
+#if LWIP_STATS && MEM_STATS
+  rmem = (u8_t*)rmem - MEM_LIBC_STATSHELPER_SIZE;
+  MEM_STATS_DEC_USED(used, *(mem_size_t*)rmem);
+#endif
+  mem_clib_free(rmem);
+}
+
+#elif MEM_USE_POOLS
+
+/* lwIP heap 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 = NULL;
+  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)) {
+    /* 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_pools[poolnr]->size) {
+      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) {
+          continue;
+        }
+#endif /* MEM_USE_POOLS_TRY_BIGGER_POOL */
+        MEM_STATS_INC(err);
+        return NULL;
+      }
+      break;
+    }
+  }
+  if (poolnr > MEMP_POOL_LAST) {
+    LWIP_ASSERT("mem_malloc(): no pool is that big!", 0);
+    MEM_STATS_INC(err);
+    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));
+
+#if MEMP_OVERFLOW_CHECK || (LWIP_STATS && MEM_STATS)
+  /* truncating to u16_t is safe because struct memp_desc::size is u16_t */
+  element->size = (u16_t)size;
+  MEM_STATS_INC_USED(used, element->size);
+#endif /* MEMP_OVERFLOW_CHECK || (LWIP_STATS && MEM_STATS) */
+#if MEMP_OVERFLOW_CHECK
+  /* initialize unused memory (diff between requested size and selected pool's size) */
+  memset((u8_t*)ret + size, 0xcd, memp_pools[poolnr]->size - size);
+#endif /* MEMP_OVERFLOW_CHECK */
+  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 */
+  /* cast through void* to get rid of alignment warnings */
+  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));
+
+  MEM_STATS_DEC_USED(used, hmem->size);
+#if MEMP_OVERFLOW_CHECK
+  {
+     u16_t i;
+     LWIP_ASSERT("MEM_USE_POOLS: invalid chunk size",
+        hmem->size <= memp_pools[hmem->poolnr]->size);
+     /* check that unused memory remained untouched (diff between requested size and selected pool's size) */
+     for (i = hmem->size; i < memp_pools[hmem->poolnr]->size; i++) {
+        u8_t data = *((u8_t*)rmem + i);
+        LWIP_ASSERT("MEM_USE_POOLS: mem overflow detected", data == 0xcd);
+     }
+  }
+#endif /* MEMP_OVERFLOW_CHECK */
+
+  /* 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 aligns.
+ */
+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 */
+LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U*SIZEOF_STRUCT_MEM));
+#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 ... */
+  /* cast through void* to get rid of alignment warnings */
+  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 ... */
+  /* cast through void* to get rid of alignment warnings */
+  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;
+}
+
+/**
+ * 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 exact 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 */
+
+#if MEM_LIBC_MALLOC && (!LWIP_STATS || !MEM_STATS)
+void *
+mem_calloc(mem_size_t count, mem_size_t size)
+{
+  return mem_clib_calloc(count, size);
+}
+
+#else /* MEM_LIBC_MALLOC && (!LWIP_STATS || !MEM_STATS) */
+/**
+ * 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, (size_t)count * (size_t)size);
+  }
+  return p;
+}
+#endif /* MEM_LIBC_MALLOC && (!LWIP_STATS || !MEM_STATS) */
diff --git a/src/core/memp.c b/src/core/memp.c
new file mode 100644
index 0000000..58fab1a
--- /dev/null
+++ b/src/core/memp.c
@@ -0,0 +1,496 @@
+/**
+ * @file
+ * Dynamic pool memory manager
+ *
+ * lwIP has dedicated pools for many structures (netconn, protocol control blocks,
+ * packet buffers, ...). All these pools are managed here.
+ *
+ * @defgroup mempool Memory pools
+ * @ingroup infrastructure
+ * Custom memory pools
+
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/memp.h"
+#include "lwip/sys.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+/* Make sure we include everything we need for size calculation required by memp_std.h */
+#include "lwip/pbuf.h"
+#include "lwip/raw.h"
+#include "lwip/udp.h"
+#include "lwip/tcp.h"
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/ip4_frag.h"
+#include "lwip/netbuf.h"
+#include "lwip/api.h"
+#include "lwip/priv/tcpip_priv.h"
+#include "lwip/priv/api_msg.h"
+#include "lwip/sockets.h"
+#include "lwip/netifapi.h"
+#include "lwip/etharp.h"
+#include "lwip/igmp.h"
+#include "lwip/timeouts.h"
+/* needed by default MEMP_NUM_SYS_TIMEOUT */
+#include "netif/ppp/ppp_opts.h"
+#include "lwip/netdb.h"
+#include "lwip/dns.h"
+#include "lwip/priv/nd6_priv.h"
+#include "lwip/ip6_frag.h"
+#include "lwip/mld6.h"
+
+#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc)
+#include "lwip/priv/memp_std.h"
+
+const struct memp_desc* const memp_pools[MEMP_MAX] = {
+#define LWIP_MEMPOOL(name,num,size,desc) &memp_ ## name,
+#include "lwip/priv/memp_std.h"
+};
+
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+
+#if MEMP_MEM_MALLOC && MEMP_OVERFLOW_CHECK >= 2
+#undef MEMP_OVERFLOW_CHECK
+/* MEMP_OVERFLOW_CHECK >= 2 does not work with MEMP_MEM_MALLOC, use 1 instead */
+#define MEMP_OVERFLOW_CHECK 1
+#endif
+
+#if MEMP_SANITY_CHECK && !MEMP_MEM_MALLOC
+/**
+ * Check that memp-lists don't form a circle, using "Floyd's cycle-finding algorithm".
+ */
+static int
+memp_sanity(const struct memp_desc *desc)
+{
+  struct memp *t, *h;
+
+  t = *desc->tab;
+  if (t != NULL) {
+    for (h = t->next; (t != NULL) && (h != NULL); t = t->next,
+      h = ((h->next != NULL) ? h->next->next : NULL)) {
+      if (t == h) {
+        return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+#endif /* MEMP_SANITY_CHECK && !MEMP_MEM_MALLOC */
+
+#if MEMP_OVERFLOW_CHECK
+/**
+ * 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 desc the pool p comes from
+ */
+static void
+memp_overflow_check_element_overflow(struct memp *p, const struct memp_desc *desc)
+{
+#if MEMP_SANITY_REGION_AFTER_ALIGNED > 0
+  u16_t k;
+  u8_t *m;
+  m = (u8_t*)p + MEMP_SIZE + desc->size;
+  for (k = 0; k < MEMP_SANITY_REGION_AFTER_ALIGNED; k++) {
+    if (m[k] != 0xcd) {
+      char errstr[128] = "detected memp overflow in pool ";
+      strcat(errstr, desc->desc);
+      LWIP_ASSERT(errstr, 0);
+    }
+  }
+#else /* MEMP_SANITY_REGION_AFTER_ALIGNED > 0 */
+  LWIP_UNUSED_ARG(p);
+  LWIP_UNUSED_ARG(desc);
+#endif /* MEMP_SANITY_REGION_AFTER_ALIGNED > 0 */
+}
+
+/**
+ * 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 desc the pool p comes from
+ */
+static void
+memp_overflow_check_element_underflow(struct memp *p, const struct memp_desc *desc)
+{
+#if MEMP_SANITY_REGION_BEFORE_ALIGNED > 0
+  u16_t k;
+  u8_t *m;
+  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 ";
+      strcat(errstr, desc->desc);
+      LWIP_ASSERT(errstr, 0);
+    }
+  }
+#else /* MEMP_SANITY_REGION_BEFORE_ALIGNED > 0 */
+  LWIP_UNUSED_ARG(p);
+  LWIP_UNUSED_ARG(desc);
+#endif /* MEMP_SANITY_REGION_BEFORE_ALIGNED > 0 */
+}
+
+/**
+ * Initialize the restricted area of on memp element.
+ */
+static void
+memp_overflow_init_element(struct memp *p, const struct memp_desc *desc)
+{
+#if MEMP_SANITY_REGION_BEFORE_ALIGNED > 0 || MEMP_SANITY_REGION_AFTER_ALIGNED > 0
+  u8_t *m;
+#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 + desc->size;
+  memset(m, 0xcd, MEMP_SANITY_REGION_AFTER_ALIGNED);
+#endif
+#else /* MEMP_SANITY_REGION_BEFORE_ALIGNED > 0 || MEMP_SANITY_REGION_AFTER_ALIGNED > 0 */
+  LWIP_UNUSED_ARG(p);
+  LWIP_UNUSED_ARG(desc);
+#endif /* MEMP_SANITY_REGION_BEFORE_ALIGNED > 0 || MEMP_SANITY_REGION_AFTER_ALIGNED > 0 */
+}
+
+#if MEMP_OVERFLOW_CHECK >= 2
+/**
+ * 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;
+  SYS_ARCH_DECL_PROTECT(old_level);
+  SYS_ARCH_PROTECT(old_level);
+
+  for (i = 0; i < MEMP_MAX; ++i) {
+    p = (struct memp*)LWIP_MEM_ALIGN(memp_pools[i]->base);
+    for (j = 0; j < memp_pools[i]->num; ++j) {
+      memp_overflow_check_element_overflow(p, memp_pools[i]);
+      memp_overflow_check_element_underflow(p, memp_pools[i]);
+      p = LWIP_ALIGNMENT_CAST(struct memp*, ((u8_t*)p + MEMP_SIZE + memp_pools[i]->size + MEMP_SANITY_REGION_AFTER_ALIGNED));
+    }
+  }
+  SYS_ARCH_UNPROTECT(old_level);
+}
+#endif /* MEMP_OVERFLOW_CHECK >= 2 */
+#endif /* MEMP_OVERFLOW_CHECK */
+
+/**
+ * Initialize custom memory pool.
+ * Related functions: memp_malloc_pool, memp_free_pool
+ *
+ * @param desc pool to initialize
+ */
+void
+memp_init_pool(const struct memp_desc *desc)
+{
+#if MEMP_MEM_MALLOC
+  LWIP_UNUSED_ARG(desc);
+#else
+  int i;
+  struct memp *memp;
+
+  *desc->tab = NULL;
+  memp = (struct memp*)LWIP_MEM_ALIGN(desc->base);
+  /* create a linked list of memp elements */
+  for (i = 0; i < desc->num; ++i) {
+    memp->next = *desc->tab;
+    *desc->tab = memp;
+#if MEMP_OVERFLOW_CHECK
+    memp_overflow_init_element(memp, desc);
+#endif /* MEMP_OVERFLOW_CHECK */
+   /* cast through void* to get rid of alignment warnings */
+   memp = (struct memp *)(void *)((u8_t *)memp + MEMP_SIZE + desc->size
+#if MEMP_OVERFLOW_CHECK
+      + MEMP_SANITY_REGION_AFTER_ALIGNED
+#endif
+    );
+  }
+#if MEMP_STATS
+  desc->stats->avail = desc->num;
+#endif /* MEMP_STATS */
+#endif /* !MEMP_MEM_MALLOC */
+
+#if MEMP_STATS && (defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY)
+  desc->stats->name  = desc->desc;
+#endif /* MEMP_STATS && (defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY) */
+}
+
+/**
+ * Initializes lwIP built-in pools.
+ * Related functions: memp_malloc, memp_free
+ *
+ * Carves out memp_memory into linked lists for each pool-type.
+ */
+void
+memp_init(void)
+{
+  u16_t i;
+
+  /* for every pool: */
+  for (i = 0; i < LWIP_ARRAYSIZE(memp_pools); i++) {
+    memp_init_pool(memp_pools[i]);
+
+#if LWIP_STATS && MEMP_STATS
+    lwip_stats.memp[i] = memp_pools[i]->stats;
+#endif
+  }
+
+#if MEMP_OVERFLOW_CHECK >= 2
+  /* check everything a first time to see if it worked */
+  memp_overflow_check_all();
+#endif /* MEMP_OVERFLOW_CHECK >= 2 */
+}
+
+static void*
+#if !MEMP_OVERFLOW_CHECK
+do_memp_malloc_pool(const struct memp_desc *desc)
+#else
+do_memp_malloc_pool_fn(const struct memp_desc *desc, const char* file, const int line)
+#endif
+{
+  struct memp *memp;
+  SYS_ARCH_DECL_PROTECT(old_level);
+
+#if MEMP_MEM_MALLOC
+  memp = (struct memp *)mem_malloc(MEMP_SIZE + MEMP_ALIGN_SIZE(desc->size));
+  SYS_ARCH_PROTECT(old_level);
+#else /* MEMP_MEM_MALLOC */
+  SYS_ARCH_PROTECT(old_level);
+
+  memp = *desc->tab;
+#endif /* MEMP_MEM_MALLOC */
+
+  if (memp != NULL) {
+#if !MEMP_MEM_MALLOC
+#if MEMP_OVERFLOW_CHECK == 1
+    memp_overflow_check_element_overflow(memp, desc);
+    memp_overflow_check_element_underflow(memp, desc);
+#endif /* MEMP_OVERFLOW_CHECK */
+
+    *desc->tab = memp->next;
+#if MEMP_OVERFLOW_CHECK
+    memp->next = NULL;
+#endif /* MEMP_OVERFLOW_CHECK */
+#endif /* !MEMP_MEM_MALLOC */
+#if MEMP_OVERFLOW_CHECK
+    memp->file = file;
+    memp->line = line;
+#if MEMP_MEM_MALLOC
+    memp_overflow_init_element(memp, desc);
+#endif /* MEMP_MEM_MALLOC */
+#endif /* MEMP_OVERFLOW_CHECK */
+    LWIP_ASSERT("memp_malloc: memp properly aligned",
+                ((mem_ptr_t)memp % MEM_ALIGNMENT) == 0);
+#if MEMP_STATS
+    desc->stats->used++;
+    if (desc->stats->used > desc->stats->max) {
+      desc->stats->max = desc->stats->used;
+    }
+#endif
+    SYS_ARCH_UNPROTECT(old_level);
+    /* cast through u8_t* to get rid of alignment warnings */
+    return ((u8_t*)memp + MEMP_SIZE);
+  } else {
+    LWIP_DEBUGF(MEMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("memp_malloc: out of memory in pool %s\n", desc->desc));
+#if MEMP_STATS
+    desc->stats->err++;
+#endif
+  }
+
+  SYS_ARCH_UNPROTECT(old_level);
+  return NULL;
+}
+
+/**
+ * Get an element from a custom pool.
+ *
+ * @param desc the pool to get an element from
+ *
+ * @return a pointer to the allocated memory or a NULL pointer on error
+ */
+void *
+#if !MEMP_OVERFLOW_CHECK
+memp_malloc_pool(const struct memp_desc *desc)
+#else
+memp_malloc_pool_fn(const struct memp_desc *desc, const char* file, const int line)
+#endif
+{
+  LWIP_ASSERT("invalid pool desc", desc != NULL);
+  if (desc == NULL) {
+    return NULL;
+  }
+
+#if !MEMP_OVERFLOW_CHECK
+  return do_memp_malloc_pool(desc);
+#else
+  return do_memp_malloc_pool_fn(desc, file, line);
+#endif
+}
+
+/**
+ * Get an element from a specific pool.
+ *
+ * @param type the pool to get an element from
+ *
+ * @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
+{
+  void *memp;
+  LWIP_ERROR("memp_malloc: type < MEMP_MAX", (type < MEMP_MAX), return NULL;);
+
+#if MEMP_OVERFLOW_CHECK >= 2
+  memp_overflow_check_all();
+#endif /* MEMP_OVERFLOW_CHECK >= 2 */
+
+#if !MEMP_OVERFLOW_CHECK
+  memp = do_memp_malloc_pool(memp_pools[type]);
+#else
+  memp = do_memp_malloc_pool_fn(memp_pools[type], file, line);
+#endif
+
+  return memp;
+}
+
+static void
+do_memp_free_pool(const struct memp_desc* desc, void *mem)
+{
+  struct memp *memp;
+  SYS_ARCH_DECL_PROTECT(old_level);
+
+  LWIP_ASSERT("memp_free: mem properly aligned",
+                ((mem_ptr_t)mem % MEM_ALIGNMENT) == 0);
+
+  /* cast through void* to get rid of alignment warnings */
+  memp = (struct memp *)(void *)((u8_t*)mem - MEMP_SIZE);
+
+  SYS_ARCH_PROTECT(old_level);
+
+#if MEMP_OVERFLOW_CHECK == 1
+  memp_overflow_check_element_overflow(memp, desc);
+  memp_overflow_check_element_underflow(memp, desc);
+#endif /* MEMP_OVERFLOW_CHECK */
+
+#if MEMP_STATS
+  desc->stats->used--;
+#endif
+
+#if MEMP_MEM_MALLOC
+  LWIP_UNUSED_ARG(desc);
+  SYS_ARCH_UNPROTECT(old_level);
+  mem_free(memp);
+#else /* MEMP_MEM_MALLOC */
+  memp->next = *desc->tab;
+  *desc->tab = memp;
+
+#if MEMP_SANITY_CHECK
+  LWIP_ASSERT("memp sanity", memp_sanity(desc));
+#endif /* MEMP_SANITY_CHECK */
+
+  SYS_ARCH_UNPROTECT(old_level);
+#endif /* !MEMP_MEM_MALLOC */
+}
+
+/**
+ * Put a custom pool element back into its pool.
+ *
+ * @param desc the pool where to put mem
+ * @param mem the memp element to free
+ */
+void
+memp_free_pool(const struct memp_desc* desc, void *mem)
+{
+  LWIP_ASSERT("invalid pool desc", desc != NULL);
+  if ((desc == NULL) || (mem == NULL)) {
+    return;
+  }
+
+  do_memp_free_pool(desc, mem);
+}
+
+/**
+ * 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)
+{
+#ifdef LWIP_HOOK_MEMP_AVAILABLE
+  struct memp *old_first;
+#endif
+
+  LWIP_ERROR("memp_free: type < MEMP_MAX", (type < MEMP_MAX), return;);
+
+  if (mem == NULL) {
+    return;
+  }
+
+#if MEMP_OVERFLOW_CHECK >= 2
+  memp_overflow_check_all();
+#endif /* MEMP_OVERFLOW_CHECK >= 2 */
+
+#ifdef LWIP_HOOK_MEMP_AVAILABLE
+  old_first = *memp_pools[type]->tab;
+#endif
+
+  do_memp_free_pool(memp_pools[type], mem);
+
+#ifdef LWIP_HOOK_MEMP_AVAILABLE
+  if (old_first == NULL) {
+    LWIP_HOOK_MEMP_AVAILABLE(type);
+  }
+#endif
+}
diff --git a/src/core/netif.c b/src/core/netif.c
new file mode 100644
index 0000000..6444654
--- /dev/null
+++ b/src/core/netif.c
@@ -0,0 +1,1267 @@
+/**
+ * @file
+ * lwIP network interface abstraction
+ * 
+ * @defgroup netif Network interface (NETIF)
+ * @ingroup callbackstyle_api
+ * 
+ * @defgroup netif_ip4 IPv4 address handling
+ * @ingroup netif
+ * 
+ * @defgroup netif_ip6 IPv6 address handling
+ * @ingroup netif
+ * 
+ * @defgroup netif_cd Client data handling
+ * Store data (void*) on a netif for application usage.
+ * @see @ref LWIP_NUM_NETIF_CLIENT_DATA
+ * @ingroup netif
+ */
+
+/*
+ * 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@sics.se>
+ */
+
+#include "lwip/opt.h"
+
+#include <string.h>
+
+#include "lwip/def.h"
+#include "lwip/ip_addr.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/netif.h"
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/udp.h"
+#include "lwip/raw.h"
+#include "lwip/snmp.h"
+#include "lwip/igmp.h"
+#include "lwip/etharp.h"
+#include "lwip/stats.h"
+#include "lwip/sys.h"
+#include "lwip/ip.h"
+#if ENABLE_LOOPBACK
+#if LWIP_NETIF_LOOPBACK_MULTITHREADING
+#include "lwip/tcpip.h"
+#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
+#endif /* ENABLE_LOOPBACK */
+
+#include "netif/ethernet.h"
+
+#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_IPV6
+#include "lwip/nd6.h"
+#endif
+
+#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_NUM_NETIF_CLIENT_DATA > 0
+static u8_t netif_client_id;
+#endif
+
+#define NETIF_REPORT_TYPE_IPV4  0x01
+#define NETIF_REPORT_TYPE_IPV6  0x02
+static void netif_issue_reports(struct netif* netif, u8_t report_type);
+
+#if LWIP_IPV6
+static err_t netif_null_output_ip6(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr);
+#endif /* LWIP_IPV6 */
+
+#if LWIP_HAVE_LOOPIF
+#if LWIP_IPV4
+static err_t netif_loop_output_ipv4(struct netif *netif, struct pbuf *p, const ip4_addr_t* addr);
+#endif
+#if LWIP_IPV6
+static err_t netif_loop_output_ipv6(struct netif *netif, struct pbuf *p, const ip6_addr_t* addr);
+#endif
+
+
+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!
+   */
+  MIB2_INIT_NETIF(netif, snmp_ifType_softwareLoopback, 0);
+
+  netif->name[0] = 'l';
+  netif->name[1] = 'o';
+#if LWIP_IPV4
+  netif->output = netif_loop_output_ipv4;
+#endif
+#if LWIP_IPV6
+  netif->output_ip6 = netif_loop_output_ipv6;
+#endif
+#if LWIP_LOOPIF_MULTICAST
+  netif->flags |= NETIF_FLAG_IGMP;
+#endif
+  return ERR_OK;
+}
+#endif /* LWIP_HAVE_LOOPIF */
+
+void
+netif_init(void)
+{
+#if LWIP_HAVE_LOOPIF
+#if LWIP_IPV4
+#define LOOPIF_ADDRINIT &loop_ipaddr, &loop_netmask, &loop_gw,
+  ip4_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);
+#else /* LWIP_IPV4 */
+#define LOOPIF_ADDRINIT
+#endif /* LWIP_IPV4 */
+
+#if NO_SYS
+  netif_add(&loop_netif, LOOPIF_ADDRINIT NULL, netif_loopif_init, ip_input);
+#else  /* NO_SYS */
+  netif_add(&loop_netif, LOOPIF_ADDRINIT NULL, netif_loopif_init, tcpip_input);
+#endif /* NO_SYS */
+
+#if LWIP_IPV6
+  IP_ADDR6_HOST(loop_netif.ip6_addr, 0, 0, 0, 0x00000001UL);
+  loop_netif.ip6_addr_state[0] = IP6_ADDR_VALID;
+#endif /* LWIP_IPV6 */
+
+  netif_set_link_up(&loop_netif);
+  netif_set_up(&loop_netif);
+
+#endif /* LWIP_HAVE_LOOPIF */
+}
+
+/**
+ * @ingroup lwip_nosys
+ * Forwards a received packet for input processing with
+ * ethernet_input() or ip_input() depending on netif flags.
+ * Don't call directly, pass to netif_add() and call
+ * netif->input().
+ * Only works if the netif driver correctly sets 
+ * NETIF_FLAG_ETHARP and/or NETIF_FLAG_ETHERNET flag!
+ */
+err_t
+netif_input(struct pbuf *p, struct netif *inp)
+{
+#if LWIP_ETHERNET
+  if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
+    return ethernet_input(p, inp);
+  } else
+#endif /* LWIP_ETHERNET */
+  return ip_input(p, inp);
+}
+
+/**
+ * @ingroup netif
+ * 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.\n
+ * It is recommended to use a function that passes the input directly
+ * to the stack (netif_input(), NO_SYS=1 mode) or via sending a
+ * message to TCPIP thread (tcpip_input(), NO_SYS=0 mode).\n
+ * These functions use netif flags NETIF_FLAG_ETHARP and NETIF_FLAG_ETHERNET
+ * to decide whether to forward to ethernet_input() or ip_input().
+ * In other words, the functions only work when the netif
+ * driver is implemented correctly!\n
+ * Most members of struct netif should be be initialized by the 
+ * netif init function = netif driver (init parameter of this function).\n
+ * IPv6: Don't forget to call netif_create_ip6_linklocal_address() after
+ * setting the MAC address in struct netif.hwaddr
+ * (IPv6 requires a link-local address).
+ * 
+ * @return netif, or NULL if failed.
+ */
+struct netif *
+netif_add(struct netif *netif,
+#if LWIP_IPV4
+          const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
+#endif /* LWIP_IPV4 */
+          void *state, netif_init_fn init, netif_input_fn input)
+{
+#if LWIP_IPV6
+  s8_t i;
+#endif
+
+  LWIP_ASSERT("No init function given", init != NULL);
+
+  /* reset new interface configuration state */
+#if LWIP_IPV4
+  ip_addr_set_zero_ip4(&netif->ip_addr);
+  ip_addr_set_zero_ip4(&netif->netmask);
+  ip_addr_set_zero_ip4(&netif->gw);
+#endif /* LWIP_IPV4 */
+#if LWIP_IPV6
+  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+    ip_addr_set_zero_ip6(&netif->ip6_addr[i]);
+    netif->ip6_addr_state[i] = IP6_ADDR_INVALID;
+  }
+  netif->output_ip6 = netif_null_output_ip6;
+#endif /* LWIP_IPV6 */
+  NETIF_SET_CHECKSUM_CTRL(netif, NETIF_CHECKSUM_ENABLE_ALL);
+  netif->flags = 0;
+#ifdef netif_get_client_data
+  memset(netif->client_data, 0, sizeof(netif->client_data));
+#endif /* LWIP_NUM_NETIF_CLIENT_DATA */
+#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_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;
+  netif->num = netif_num++;
+  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 */
+
+#if LWIP_IPV4
+  netif_set_addr(netif, ipaddr, netmask, gw);
+#endif /* LWIP_IPV4 */
+
+  /* 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;
+  mib2_netif_added(netif);
+
+#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",
+    netif->name[0], netif->name[1]));
+#if LWIP_IPV4
+  LWIP_DEBUGF(NETIF_DEBUG, (" addr "));
+  ip4_addr_debug_print(NETIF_DEBUG, ipaddr);
+  LWIP_DEBUGF(NETIF_DEBUG, (" netmask "));
+  ip4_addr_debug_print(NETIF_DEBUG, netmask);
+  LWIP_DEBUGF(NETIF_DEBUG, (" gw "));
+  ip4_addr_debug_print(NETIF_DEBUG, gw);
+#endif /* LWIP_IPV4 */
+  LWIP_DEBUGF(NETIF_DEBUG, ("\n"));
+  return netif;
+}
+
+#if LWIP_IPV4
+/**
+ * @ingroup netif_ip4
+ * 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, const ip4_addr_t *ipaddr, const ip4_addr_t *netmask,
+    const ip4_addr_t *gw)
+{
+  if (ip4_addr_isany(ipaddr)) {
+    /* when removing an address, we have to remove it *before* changing netmask/gw
+       to ensure that tcp RST segment can be sent correctly */
+    netif_set_ipaddr(netif, ipaddr);
+    netif_set_netmask(netif, netmask);
+    netif_set_gw(netif, gw);
+  } else {
+    netif_set_netmask(netif, netmask);
+    netif_set_gw(netif, gw);
+    /* set ipaddr last to ensure netmask/gw have been set when status callback is called */
+    netif_set_ipaddr(netif, ipaddr);
+  }
+}
+#endif /* LWIP_IPV4*/
+
+/**
+ * @ingroup netif
+ * Remove a network interface from the list of lwIP netifs.
+ *
+ * @param netif the network interface to remove
+ */
+void
+netif_remove(struct netif *netif)
+{
+#if LWIP_IPV6
+  int i;
+#endif
+
+  if (netif == NULL) {
+    return;
+  }
+
+#if LWIP_IPV4
+  if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+#if LWIP_TCP
+    tcp_netif_ip_addr_changed(netif_ip_addr4(netif), NULL);
+#endif /* LWIP_TCP */
+#if LWIP_UDP
+    udp_netif_ip_addr_changed(netif_ip_addr4(netif), NULL);
+#endif /* LWIP_UDP */
+#if LWIP_RAW
+    raw_netif_ip_addr_changed(netif_ip_addr4(netif), NULL);
+#endif /* LWIP_RAW */
+  }
+
+#if LWIP_IGMP
+  /* stop IGMP processing */
+  if (netif->flags & NETIF_FLAG_IGMP) {
+    igmp_stop(netif);
+  }
+#endif /* LWIP_IGMP */
+#endif /* LWIP_IPV4*/
+
+#if LWIP_IPV6
+  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+    if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) {
+#if LWIP_TCP
+      tcp_netif_ip_addr_changed(netif_ip_addr6(netif, i), NULL);
+#endif /* LWIP_TCP */
+#if LWIP_UDP
+      udp_netif_ip_addr_changed(netif_ip_addr6(netif, i), NULL);
+#endif /* LWIP_UDP */
+#if LWIP_RAW
+      raw_netif_ip_addr_changed(netif_ip_addr6(netif, i), NULL);
+#endif /* LWIP_RAW */
+    }
+  }
+#if LWIP_IPV6_MLD
+  /* stop MLD processing */
+  mld6_stop(netif);
+#endif /* LWIP_IPV6_MLD */
+#endif /* LWIP_IPV6 */
+  if (netif_is_up(netif)) {
+    /* set netif down before removing (call callback function) */
+    netif_set_down(netif);
+  }
+
+  mib2_remove_ip4(netif);
+
+  /* this netif is default? */
+  if (netif_default == netif) {
+    /* reset default netif */
+    netif_set_default(NULL);
+  }
+  /*  is it the first netif? */
+  if (netif_list == netif) {
+    netif_list = netif->next;
+  } else {
+    /*  look for netif further down the list */
+    struct netif * tmp_netif;
+    for (tmp_netif = netif_list; tmp_netif != NULL; tmp_netif = tmp_netif->next) {
+      if (tmp_netif->next == netif) {
+        tmp_netif->next = netif->next;
+        break;
+      }
+    }
+    if (tmp_netif == NULL) {
+      return; /* netif is not on the list */
+    }
+  }
+  mib2_netif_removed(netif);
+#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") );
+}
+
+/**
+ * @ingroup netif
+ * 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(const char *name)
+{
+  struct netif *netif;
+  u8_t num;
+
+  if (name == NULL) {
+    return NULL;
+  }
+
+  num = (u8_t)(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;
+}
+
+#if LWIP_IPV4
+/**
+ * @ingroup netif_ip4
+ * 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, const ip4_addr_t *ipaddr)
+{
+  ip_addr_t new_addr;
+  *ip_2_ip4(&new_addr) = (ipaddr ? *ipaddr : *IP4_ADDR_ANY4);
+  IP_SET_TYPE_VAL(new_addr, IPADDR_TYPE_V4);
+
+  /* address is actually being changed? */
+  if (ip4_addr_cmp(ip_2_ip4(&new_addr), netif_ip4_addr(netif)) == 0) {
+    LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_STATE, ("netif_set_ipaddr: netif address being changed\n"));
+#if LWIP_TCP
+    tcp_netif_ip_addr_changed(netif_ip_addr4(netif), &new_addr);
+#endif /* LWIP_TCP */
+#if LWIP_UDP
+    udp_netif_ip_addr_changed(netif_ip_addr4(netif), &new_addr);
+#endif /* LWIP_UDP */
+#if LWIP_RAW
+    raw_netif_ip_addr_changed(netif_ip_addr4(netif), &new_addr);
+#endif /* LWIP_RAW */
+
+    mib2_remove_ip4(netif);
+    mib2_remove_route_ip4(0, netif);
+    /* set new IP address to netif */
+    ip4_addr_set(ip_2_ip4(&netif->ip_addr), ipaddr);
+    IP_SET_TYPE_VAL(netif->ip_addr, IPADDR_TYPE_V4);
+    mib2_add_ip4(netif);
+    mib2_add_route_ip4(0, netif);
+
+    netif_issue_reports(netif, NETIF_REPORT_TYPE_IPV4);
+
+    NETIF_STATUS_CALLBACK(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_ip4_addr(netif)),
+    ip4_addr2_16(netif_ip4_addr(netif)),
+    ip4_addr3_16(netif_ip4_addr(netif)),
+    ip4_addr4_16(netif_ip4_addr(netif))));
+}
+
+/**
+ * @ingroup netif_ip4
+ * 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, const ip4_addr_t *gw)
+{
+  ip4_addr_set(ip_2_ip4(&netif->gw), gw);
+  IP_SET_TYPE_VAL(netif->gw, IPADDR_TYPE_V4);
+  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_ip4_gw(netif)),
+    ip4_addr2_16(netif_ip4_gw(netif)),
+    ip4_addr3_16(netif_ip4_gw(netif)),
+    ip4_addr4_16(netif_ip4_gw(netif))));
+}
+
+/**
+ * @ingroup netif_ip4
+ * 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, const ip4_addr_t *netmask)
+{
+  mib2_remove_route_ip4(0, netif);
+  /* set new netmask to netif */
+  ip4_addr_set(ip_2_ip4(&netif->netmask), netmask);
+  IP_SET_TYPE_VAL(netif->netmask, IPADDR_TYPE_V4);
+  mib2_add_route_ip4(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_ip4_netmask(netif)),
+    ip4_addr2_16(netif_ip4_netmask(netif)),
+    ip4_addr3_16(netif_ip4_netmask(netif)),
+    ip4_addr4_16(netif_ip4_netmask(netif))));
+}
+#endif /* LWIP_IPV4 */
+
+/**
+ * @ingroup netif
+ * 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 */
+    mib2_remove_route_ip4(1, netif);
+  } else {
+    /* install default route */
+    mib2_add_route_ip4(1, netif);
+  }
+  netif_default = netif;
+  LWIP_DEBUGF(NETIF_DEBUG, ("netif: setting default interface %c%c\n",
+           netif ? netif->name[0] : '\'', netif ? netif->name[1] : '\''));
+}
+
+/**
+ * @ingroup netif
+ * Bring an interface up, available for processing
+ * traffic.
+ */
+void
+netif_set_up(struct netif *netif)
+{
+  if (!(netif->flags & NETIF_FLAG_UP)) {
+    netif->flags |= NETIF_FLAG_UP;
+
+    MIB2_COPY_SYSUPTIME_TO(&netif->ts);
+
+    NETIF_STATUS_CALLBACK(netif);
+
+    if (netif->flags & NETIF_FLAG_LINK_UP) {
+      netif_issue_reports(netif, NETIF_REPORT_TYPE_IPV4|NETIF_REPORT_TYPE_IPV6);
+    }
+  }
+}
+
+/** Send ARP/IGMP/MLD/RS events, e.g. on link-up/netif-up or addr-change
+ */
+static void
+netif_issue_reports(struct netif* netif, u8_t report_type)
+{
+#if LWIP_IPV4
+  if ((report_type & NETIF_REPORT_TYPE_IPV4) &&
+      !ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+#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 */
+  }
+#endif /* LWIP_IPV4 */
+
+#if LWIP_IPV6
+  if (report_type & NETIF_REPORT_TYPE_IPV6) {
+#if LWIP_IPV6_MLD
+    /* send mld memberships */
+    mld6_report_groups(netif);
+#endif /* 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 */
+  }
+#endif /* LWIP_IPV6 */
+}
+
+/**
+ * @ingroup netif
+ * Bring an interface down, disabling any traffic processing.
+ */
+void
+netif_set_down(struct netif *netif)
+{
+  if (netif->flags & NETIF_FLAG_UP) {
+    netif->flags &= ~NETIF_FLAG_UP;
+    MIB2_COPY_SYSUPTIME_TO(&netif->ts);
+
+#if LWIP_IPV4 && LWIP_ARP
+    if (netif->flags & NETIF_FLAG_ETHARP) {
+      etharp_cleanup_netif(netif);
+    }
+#endif /* LWIP_IPV4 && LWIP_ARP */
+
+#if LWIP_IPV6
+    nd6_cleanup_netif(netif);
+#endif /* LWIP_IPV6 */
+
+    NETIF_STATUS_CALLBACK(netif);
+  }
+}
+
+#if LWIP_NETIF_STATUS_CALLBACK
+/**
+ * @ingroup netif
+ * Set callback to be called when interface is brought up/down or address is changed while up
+ */
+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
+/**
+ * @ingroup netif
+ * 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 */
+
+/**
+ * @ingroup netif
+ * 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
+    dhcp_network_changed(netif);
+#endif /* LWIP_DHCP */
+
+#if LWIP_AUTOIP
+    autoip_network_changed(netif);
+#endif /* LWIP_AUTOIP */
+
+    if (netif->flags & NETIF_FLAG_UP) {
+      netif_issue_reports(netif, NETIF_REPORT_TYPE_IPV4|NETIF_REPORT_TYPE_IPV6);
+    }
+    NETIF_LINK_CALLBACK(netif);
+  }
+}
+
+/**
+ * @ingroup 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
+/**
+ * @ingroup netif
+ * 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
+/**
+ * @ingroup netif
+ * 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'
+ * @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)
+{
+  struct pbuf *r;
+  err_t err;
+  struct pbuf *last;
+#if LWIP_LOOPBACK_MAX_PBUFS
+  u16_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 MIB2_STATS
+#if LWIP_HAVE_LOOPIF
+  struct netif *stats_if = &loop_netif;
+#else /* LWIP_HAVE_LOOPIF */
+  struct netif *stats_if = netif;
+#endif /* LWIP_HAVE_LOOPIF */
+#endif /* MIB2_STATS */
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  /* 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);
+    MIB2_STATS_NETIF_INC(stats_if, ifoutdiscards);
+    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);
+    MIB2_STATS_NETIF_INC(stats_if, ifoutdiscards);
+    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);
+    MIB2_STATS_NETIF_INC(stats_if, ifoutdiscards);
+    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) {
+    /* nothing to do here, just get to the last pbuf */
+  }
+
+  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);
+  MIB2_STATS_NETIF_ADD(stats_if, ifoutoctets, p->tot_len);
+  MIB2_STATS_NETIF_INC(stats_if, ifoutucastpkts);
+
+#if LWIP_NETIF_LOOPBACK_MULTITHREADING
+  /* For multithreading environment, schedule a call to netif_poll */
+  tcpip_callback_with_block((tcpip_callback_fn)netif_poll, netif, 0);
+#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
+
+  return ERR_OK;
+}
+
+#if LWIP_HAVE_LOOPIF
+#if LWIP_IPV4
+static err_t
+netif_loop_output_ipv4(struct netif *netif, struct pbuf *p, const ip4_addr_t* addr)
+{
+  LWIP_UNUSED_ARG(addr);
+  return netif_loop_output(netif, p);
+}
+#endif /* LWIP_IPV4 */
+
+#if LWIP_IPV6
+static err_t
+netif_loop_output_ipv6(struct netif *netif, struct pbuf *p, const ip6_addr_t* addr)
+{
+  LWIP_UNUSED_ARG(addr);
+  return netif_loop_output(netif, p);
+}
+#endif /* LWIP_IPV6 */
+#endif /* LWIP_HAVE_LOOPIF */
+
+
+/**
+ * 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)
+{
+  /* If we have a loopif, SNMP counters are adjusted for it,
+   * if not they are adjusted for 'netif'. */
+#if MIB2_STATS
+#if LWIP_HAVE_LOOPIF
+  struct netif *stats_if = &loop_netif;
+#else /* LWIP_HAVE_LOOPIF */
+  struct netif *stats_if = netif;
+#endif /* LWIP_HAVE_LOOPIF */
+#endif /* MIB2_STATS */
+  SYS_ARCH_DECL_PROTECT(lev);
+
+  /* Get a packet from the list. With SYS_LIGHTWEIGHT_PROT=1, this is protected */
+  SYS_ARCH_PROTECT(lev);
+  while (netif->loop_first != NULL) {
+    struct pbuf *in, *in_end;
+#if LWIP_LOOPBACK_MAX_PBUFS
+    u8_t clen = 1;
+#endif /* LWIP_LOOPBACK_MAX_PBUFS */
+
+    in = in_end = netif->loop_first;
+    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;
+#if LWIP_LOOPBACK_MAX_PBUFS
+      clen++;
+#endif /* LWIP_LOOPBACK_MAX_PBUFS */
+    }
+#if LWIP_LOOPBACK_MAX_PBUFS
+    /* 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 */
+
+    /* '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);
+
+    LINK_STATS_INC(link.recv);
+    MIB2_STATS_NETIF_ADD(stats_if, ifinoctets, in->tot_len);
+    MIB2_STATS_NETIF_INC(stats_if, ifinucastpkts);
+    /* loopback packets are always IP packets! */
+    if (ip_input(in, netif) != ERR_OK) {
+      pbuf_free(in);
+    }
+    SYS_ARCH_PROTECT(lev);
+  }
+  SYS_ARCH_UNPROTECT(lev);
+}
+
+#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_NUM_NETIF_CLIENT_DATA > 0
+/**
+ * @ingroup netif_cd
+ * Allocate an index to store data in client_data member of struct netif.
+ * Returned value is an index in mentioned array.
+ * @see LWIP_NUM_NETIF_CLIENT_DATA
+ */
+u8_t
+netif_alloc_client_data_id(void)
+{
+  u8_t result = netif_client_id;
+  netif_client_id++;
+
+  LWIP_ASSERT("Increase LWIP_NUM_NETIF_CLIENT_DATA in lwipopts.h", result < LWIP_NUM_NETIF_CLIENT_DATA);
+  return result + LWIP_NETIF_CLIENT_DATA_INDEX_MAX;
+}
+#endif
+
+#if LWIP_IPV6
+/**
+ * @ingroup netif_ip6
+ * Change an IPv6 address of a network interface
+ *
+ * @param netif the network interface to change
+ * @param addr_idx index of the IPv6 address
+ * @param addr6 the new IPv6 address
+ *
+ * @note call netif_ip6_addr_set_state() to set the address valid/temptative
+ */
+void
+netif_ip6_addr_set(struct netif *netif, s8_t addr_idx, const ip6_addr_t *addr6)
+{
+  LWIP_ASSERT("addr6 != NULL", addr6 != NULL);
+  netif_ip6_addr_set_parts(netif, addr_idx, addr6->addr[0], addr6->addr[1],
+    addr6->addr[2], addr6->addr[3]);
+}
+
+/*
+ * Change an IPv6 address of a network interface (internal version taking 4 * u32_t)
+ *
+ * @param netif the network interface to change
+ * @param addr_idx index of the IPv6 address
+ * @param i0 word0 of the new IPv6 address
+ * @param i1 word1 of the new IPv6 address
+ * @param i2 word2 of the new IPv6 address
+ * @param i3 word3 of the new IPv6 address
+ */
+void
+netif_ip6_addr_set_parts(struct netif *netif, s8_t addr_idx, u32_t i0, u32_t i1, u32_t i2, u32_t i3)
+{
+  const ip6_addr_t *old_addr;
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+  LWIP_ASSERT("invalid index", addr_idx < LWIP_IPV6_NUM_ADDRESSES);
+
+  old_addr = netif_ip6_addr(netif, addr_idx);
+  /* address is actually being changed? */
+  if ((old_addr->addr[0] != i0) || (old_addr->addr[1] != i1) ||
+      (old_addr->addr[2] != i2) || (old_addr->addr[3] != i3)) {
+    LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_STATE, ("netif_ip6_addr_set: netif address being changed\n"));
+
+    if (netif_ip6_addr_state(netif, addr_idx) & IP6_ADDR_VALID) {
+#if LWIP_TCP || LWIP_UDP
+      ip_addr_t new_ipaddr;
+      IP_ADDR6(&new_ipaddr, i0, i1, i2, i3);
+#endif /* LWIP_TCP || LWIP_UDP */
+#if LWIP_TCP
+      tcp_netif_ip_addr_changed(netif_ip_addr6(netif, addr_idx), &new_ipaddr);
+#endif /* LWIP_TCP */
+#if LWIP_UDP
+      udp_netif_ip_addr_changed(netif_ip_addr6(netif, addr_idx), &new_ipaddr);
+#endif /* LWIP_UDP */
+#if LWIP_RAW
+      raw_netif_ip_addr_changed(netif_ip_addr6(netif, addr_idx), &new_ipaddr);
+#endif /* LWIP_RAW */
+    }
+    /* @todo: remove/readd mib2 ip6 entries? */
+
+    IP6_ADDR(ip_2_ip6(&(netif->ip6_addr[addr_idx])), i0, i1, i2, i3);
+    IP_SET_TYPE_VAL(netif->ip6_addr[addr_idx], IPADDR_TYPE_V6);
+
+    if (netif_ip6_addr_state(netif, addr_idx) & IP6_ADDR_VALID) {
+      netif_issue_reports(netif, NETIF_REPORT_TYPE_IPV6);
+      NETIF_STATUS_CALLBACK(netif);
+    }
+  }
+
+  LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("netif: IPv6 address %d of interface %c%c set to %s/0x%"X8_F"\n",
+    addr_idx, netif->name[0], netif->name[1], ip6addr_ntoa(netif_ip6_addr(netif, addr_idx)),
+    netif_ip6_addr_state(netif, addr_idx)));
+}
+
+/**
+ * @ingroup netif_ip6
+ * Change the state of an IPv6 address of a network interface
+ * (INVALID, TEMPTATIVE, PREFERRED, DEPRECATED, where TEMPTATIVE
+ * includes the number of checks done, see ip6_addr.h)
+ *
+ * @param netif the network interface to change
+ * @param addr_idx index of the IPv6 address
+ * @param state the new IPv6 address state
+ */
+void
+netif_ip6_addr_set_state(struct netif* netif, s8_t addr_idx, u8_t state)
+{
+  u8_t old_state;
+  LWIP_ASSERT("netif != NULL", netif != NULL);
+  LWIP_ASSERT("invalid index", addr_idx < LWIP_IPV6_NUM_ADDRESSES);
+
+  old_state = netif_ip6_addr_state(netif, addr_idx);
+  /* state is actually being changed? */
+  if (old_state != state) {
+    u8_t old_valid = old_state & IP6_ADDR_VALID;
+    u8_t new_valid = state & IP6_ADDR_VALID;
+    LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_STATE, ("netif_ip6_addr_set_state: netif address state being changed\n"));
+
+#if LWIP_IPV6_MLD
+    /* Reevaluate solicited-node multicast group membership. */
+    if (netif->flags & NETIF_FLAG_MLD6) {
+      nd6_adjust_mld_membership(netif, addr_idx, state);
+    }
+#endif /* LWIP_IPV6_MLD */
+
+    if (old_valid && !new_valid) {
+      /* address about to be removed by setting invalid */
+#if LWIP_TCP
+      tcp_netif_ip_addr_changed(netif_ip_addr6(netif, addr_idx), NULL);
+#endif /* LWIP_TCP */
+#if LWIP_UDP
+      udp_netif_ip_addr_changed(netif_ip_addr6(netif, addr_idx), NULL);
+#endif /* LWIP_UDP */
+#if LWIP_RAW
+      raw_netif_ip_addr_changed(netif_ip_addr6(netif, addr_idx), NULL);
+#endif /* LWIP_RAW */
+      /* @todo: remove mib2 ip6 entries? */
+    }
+    netif->ip6_addr_state[addr_idx] = state;
+
+    if (!old_valid && new_valid) {
+      /* address added by setting valid */
+      /* @todo: add mib2 ip6 entries? */
+      netif_issue_reports(netif, NETIF_REPORT_TYPE_IPV6);
+    }
+    if ((old_state & IP6_ADDR_PREFERRED) != (state & IP6_ADDR_PREFERRED)) {
+      /* address state has changed (valid flag changed or switched between
+         preferred and deprecated) -> call the callback function */
+      NETIF_STATUS_CALLBACK(netif);
+    }
+  }
+
+  LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("netif: IPv6 address %d of interface %c%c set to %s/0x%"X8_F"\n",
+    addr_idx, netif->name[0], netif->name[1], ip6addr_ntoa(netif_ip6_addr(netif, addr_idx)),
+    netif_ip6_addr_state(netif, addr_idx)));
+}
+
+/**
+ * Checks if a specific address is assigned to the netif and returns its
+ * index.
+ *
+ * @param netif the netif to check
+ * @param ip6addr the IPv6 address to find
+ * @return >= 0: address found, this is its index
+ *         -1: address not found on this netif
+ */
+s8_t
+netif_get_ip6_addr_match(struct netif *netif, const ip6_addr_t *ip6addr)
+{
+  s8_t i;
+  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+    if (!ip6_addr_isinvalid(netif_ip6_addr_state(netif, i)) &&
+        ip6_addr_cmp(netif_ip6_addr(netif, i), ip6addr)) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+/**
+ * @ingroup netif_ip6
+ * Create a link-local IPv6 address on a netif (stored in slot 0)
+ *
+ * @param netif the netif to create the address on
+ * @param from_mac_48bit if != 0, assume hwadr is a 48-bit MAC address (std conversion)
+ *                       if == 0, use hwaddr directly as interface ID
+ */
+void
+netif_create_ip6_linklocal_address(struct netif *netif, u8_t from_mac_48bit)
+{
+  u8_t i, addr_index;
+
+  /* Link-local prefix. */
+  ip_2_ip6(&netif->ip6_addr[0])->addr[0] = PP_HTONL(0xfe800000ul);
+  ip_2_ip6(&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. */
+    ip_2_ip6(&netif->ip6_addr[0])->addr[2] = lwip_htonl((((u32_t)(netif->hwaddr[0] ^ 0x02)) << 24) |
+        ((u32_t)(netif->hwaddr[1]) << 16) |
+        ((u32_t)(netif->hwaddr[2]) << 8) |
+        (0xff));
+    ip_2_ip6(&netif->ip6_addr[0])->addr[3] = lwip_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. */
+    ip_2_ip6(&netif->ip6_addr[0])->addr[2] = 0;
+    ip_2_ip6(&netif->ip6_addr[0])->addr[3] = 0;
+
+    addr_index = 3;
+    for (i = 0; (i < 8) && (i < netif->hwaddr_len); i++) {
+      if (i == 4) {
+        addr_index--;
+      }
+      ip_2_ip6(&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_set_state(netif, 0, IP6_ADDR_TENTATIVE);
+#else
+  /* Consider address valid. */
+  netif_ip6_addr_set_state(netif, 0, IP6_ADDR_PREFERRED);
+#endif /* LWIP_IPV6_AUTOCONFIG */
+}
+
+/**
+ * @ingroup netif_ip6
+ * This function allows for the easy addition of a new IPv6 address to an interface.
+ * It takes care of finding an empty slot and then sets the address tentative
+ * (to make sure that all the subsequent processing happens).
+ *
+ * @param netif netif to add the address on
+ * @param ip6addr address to add
+ * @param chosen_idx if != NULL, the chosen IPv6 address index will be stored here
+ */
+err_t
+netif_add_ip6_address(struct netif *netif, const ip6_addr_t *ip6addr, s8_t *chosen_idx)
+{
+  s8_t i;
+
+  i = netif_get_ip6_addr_match(netif, ip6addr);
+  if (i >= 0) {
+    /* Address already added */
+    if (chosen_idx != NULL) {
+      *chosen_idx = i;
+    }
+    return ERR_OK;
+  }
+
+  /* Find a free slot -- musn't be the first one (reserved for link local) */
+  for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+    if (ip6_addr_isinvalid(netif_ip6_addr_state(netif, i))) {
+      ip_addr_copy_from_ip6(netif->ip6_addr[i], *ip6addr);
+      netif_ip6_addr_set_state(netif, i, IP6_ADDR_TENTATIVE);
+      if (chosen_idx != NULL) {
+        *chosen_idx = i;
+      }
+      return ERR_OK;
+    }
+  }
+
+  if (chosen_idx != NULL) {
+    *chosen_idx = -1;
+  }
+  return ERR_VAL;
+}
+
+/** Dummy IPv6 output function for netifs not supporting IPv6
+ */
+static err_t
+netif_null_output_ip6(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr)
+{
+  LWIP_UNUSED_ARG(netif);
+  LWIP_UNUSED_ARG(p);
+  LWIP_UNUSED_ARG(ipaddr);
+
+  return ERR_IF;
+}
+#endif /* LWIP_IPV6 */
diff --git a/src/core/pbuf.c b/src/core/pbuf.c
new file mode 100644
index 0000000..059f83a
--- /dev/null
+++ b/src/core/pbuf.c
@@ -0,0 +1,1442 @@
+/**
+ * @file
+ * Packet buffer management
+ */
+
+/**
+ * @defgroup pbuf Packet buffers (PBUF)
+ * @ingroup infrastructure
+ *
+ * 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).
+ *
+ * Example of custom pbuf usage for zero-copy RX:
+  @code{.c}
+typedef struct my_custom_pbuf
+{
+   struct pbuf_custom p;
+   void* dma_descriptor;
+} my_custom_pbuf_t;
+
+LWIP_MEMPOOL_DECLARE(RX_POOL, 10, sizeof(my_custom_pbuf_t), "Zero-copy RX PBUF pool");
+
+void my_pbuf_free_custom(void* p)
+{
+  my_custom_pbuf_t* my_puf = (my_custom_pbuf_t*)p;
+
+  LOCK_INTERRUPTS();
+  free_rx_dma_descriptor(my_pbuf->dma_descriptor);
+  LWIP_MEMPOOL_FREE(RX_POOL, my_pbuf);
+  UNLOCK_INTERRUPTS();
+}
+
+void eth_rx_irq()
+{
+  dma_descriptor*   dma_desc = get_RX_DMA_descriptor_from_ethernet();
+  my_custom_pbuf_t* my_pbuf  = (my_custom_pbuf_t*)LWIP_MEMPOOL_ALLOC(RX_POOL);
+
+  my_pbuf->p.custom_free_function = my_pbuf_free_custom;
+  my_pbuf->dma_descriptor         = dma_desc;
+
+  invalidate_cpu_cache(dma_desc->rx_data, dma_desc->rx_length);
+  
+  struct pbuf* p = pbuf_alloced_custom(PBUF_RAW,
+     dma_desc->rx_length,
+     PBUF_REF,
+     &my_pbuf->p,
+     dma_desc->rx_data,
+     dma_desc->max_buffer_size);
+
+  if(netif->input(p, netif) != ERR_OK) {
+    pbuf_free(p);
+  }
+}
+  @endcode
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+
+#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"
+#if LWIP_TCP && TCP_QUEUE_OOSEQ
+#include "lwip/priv/tcp_priv.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_SET(pbuf_free_ooseq_pending, 0);
+
+  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_callback() 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_SET(pbuf_free_ooseq_pending, 1);
+#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 */
+
+/**
+ * @ingroup pbuf
+ * 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_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN;
+    break;
+  case PBUF_IP:
+    /* add room for IP layer header */
+    offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN;
+    break;
+  case PBUF_LINK:
+    /* add room for link layer header */
+    offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN;
+    break;
+  case PBUF_RAW_TX:
+    /* add room for encapsulating link layer headers (e.g. 802.11) */
+    offset = PBUF_LINK_ENCAPSULATION_HLEN;
+    break;
+  case PBUF_RAW:
+    /* no offset (e.g. RX buffers or chain successors) */
+    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 unsuccessfully */
+        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:
+    {
+      mem_size_t alloc_len = LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length);
+      
+      /* bug #50040: Check for integer overflow when calculating alloc_len */
+      if (alloc_len < LWIP_MEM_ALIGN_SIZE(length)) {
+        return NULL;
+      }
+    
+      /* If pbuf is to be allocated in RAM, allocate memory for it. */
+      p = (struct pbuf*)mem_malloc(alloc_len);
+    }
+
+    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
+/**
+ * @ingroup pbuf
+ * Initialize a custom pbuf (already allocated).
+ *
+ * @param l 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_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN;
+    break;
+  case PBUF_IP:
+    /* add room for IP layer header */
+    offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN;
+    break;
+  case PBUF_LINK:
+    /* add room for link layer header */
+    offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN;
+    break;
+  case PBUF_RAW_TX:
+    /* add room for encapsulating link layer headers (e.g. 802.11) */
+    offset = PBUF_LINK_ENCAPSULATION_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 */
+
+/**
+ * @ingroup 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)
+#if LWIP_SUPPORT_CUSTOM_PBUF
+      && ((q->flags & PBUF_FLAG_IS_CUSTOM) == 0)
+#endif /* LWIP_SUPPORT_CUSTOM_PBUF */
+     ) {
+    /* 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.
+ * @see pbuf_header.
+ *
+ * @param p pbuf to change the header size.
+ * @param header_size_increment Number of bytes to increment header size.
+ * @param force Allow 'header_size_increment > 0' for PBUF_REF/PBUF_ROM types
+ *
+ * @return non-zero on failure, zero on success.
+ *
+ */
+static u8_t
+pbuf_header_impl(struct pbuf *p, s16_t header_size_increment, u8_t force)
+{
+  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 = (u16_t)-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 = (u16_t)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_TRACE,
+        ("pbuf_header: failed as %p < %p (not enough space for new header size)\n",
+        (void *)p->payload, (void *)((u8_t *)p + SIZEOF_STRUCT_PBUF)));
+      /* restore old payload pointer */
+      p->payload = payload;
+      /* bail out unsuccessfully */
+      return 1;
+    }
+  /* pbuf types referring 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 if ((header_size_increment > 0) && force) {
+      p->payload = (u8_t *)p->payload - header_size_increment;
+    } else {
+      /* cannot expand payload to front (yet!)
+       * bail out unsuccessfully */
+      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;
+}
+
+/**
+ * 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 successful.
+ *
+ * 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)
+{
+   return pbuf_header_impl(p, header_size_increment, 0);
+}
+
+/**
+ * Same as pbuf_header but does not check if 'header_size > 0' is allowed.
+ * This is used internally only, to allow PBUF_REF for RX.
+ */
+u8_t
+pbuf_header_force(struct pbuf *p, s16_t header_size_increment)
+{
+   return pbuf_header_impl(p, header_size_increment, 1);
+}
+
+/**
+ * @ingroup pbuf
+ * 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
+ */
+u16_t
+pbuf_clen(const struct pbuf *p)
+{
+  u16_t len;
+
+  len = 0;
+  while (p != NULL) {
+    ++len;
+    p = p->next;
+  }
+  return len;
+}
+
+/**
+ * @ingroup pbuf
+ * Increment the reference count of the pbuf.
+ *
+ * @param p pbuf to increase reference counter of
+ *
+ */
+void
+pbuf_ref(struct pbuf *p)
+{
+  /* pbuf given? */
+  if (p != NULL) {
+    SYS_ARCH_INC(p->ref, 1);
+    LWIP_ASSERT("pbuf ref overflow", p->ref > 0);
+  }
+}
+
+/**
+ * @ingroup pbuf
+ * 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.
+   */
+}
+
+/**
+ * @ingroup pbuf
+ * 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);
+}
+
+/**
+ * @ingroup pbuf
+ * 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, const 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",
+    (const void*)p_to, (const 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!",
+                 (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!",
+                  (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;
+}
+
+/**
+ * @ingroup pbuf
+ * 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(const struct pbuf *buf, void *dataptr, u16_t len, u16_t offset)
+{
+  const 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;
+}
+
+#if LWIP_TCP && TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+/**
+ * This method modifies a 'pbuf chain', so that its total length is
+ * smaller than 64K. The remainder of the original pbuf chain is stored
+ * in *rest.
+ * This function never creates new pbufs, but splits an existing chain
+ * in two parts. The tot_len of the modified packet queue will likely be
+ * smaller than 64K.
+ * 'packet queues' are not supported by this function.
+ *
+ * @param p the pbuf queue to be split
+ * @param rest pointer to store the remainder (after the first 64K)
+ */
+void pbuf_split_64k(struct pbuf *p, struct pbuf **rest)
+{
+  *rest = NULL;
+  if ((p != NULL) && (p->next != NULL)) {
+    u16_t tot_len_front = p->len;
+    struct pbuf *i = p;
+    struct pbuf *r = p->next;
+
+    /* continue until the total length (summed up as u16_t) overflows */
+    while ((r != NULL) && ((u16_t)(tot_len_front + r->len) > tot_len_front)) {
+      tot_len_front += r->len;
+      i = r;
+      r = r->next;
+    }
+    /* i now points to last packet of the first segment. Set next
+       pointer to NULL */
+    i->next = NULL;
+
+    if (r != NULL) {
+      /* Update the tot_len field in the first part */
+      for (i = p; i != NULL; i = i->next) {
+        i->tot_len -= r->tot_len;
+        LWIP_ASSERT("tot_len/len mismatch in last pbuf",
+                    (i->next != NULL) || (i->tot_len == i->len));
+      }
+      if (p->flags & PBUF_FLAG_TCP_FIN) {
+        r->flags |= PBUF_FLAG_TCP_FIN;
+      }
+
+      /* tot_len field in rest does not need modifications */
+      /* reference counters do not need modifications */
+      *rest = r;
+    }
+  }
+}
+#endif /* LWIP_TCP && TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+
+/* Actual implementation of pbuf_skip() but returning const pointer... */
+static const struct pbuf*
+pbuf_skip_const(const struct pbuf* in, u16_t in_offset, u16_t* out_offset)
+{
+  u16_t offset_left = in_offset;
+  const struct pbuf* q = in;
+
+  /* get the correct pbuf */
+  while ((q != NULL) && (q->len <= offset_left)) {
+    offset_left -= q->len;
+    q = q->next;
+  }
+  if (out_offset != NULL) {
+    *out_offset = offset_left;
+  }
+  return q;
+}
+
+/**
+ * @ingroup pbuf
+ * Skip a number of bytes at the start of a pbuf
+ *
+ * @param in input pbuf
+ * @param in_offset offset to skip
+ * @param out_offset resulting offset in the returned pbuf
+ * @return the pbuf in the queue where the offset is
+ */
+struct pbuf*
+pbuf_skip(struct pbuf* in, u16_t in_offset, u16_t* out_offset)
+{
+  const struct pbuf* out = pbuf_skip_const(in, in_offset, out_offset);
+  return LWIP_CONST_CAST(struct pbuf*, out);
+}
+
+/**
+ * @ingroup pbuf
+ * 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 ERR_ARG;);
+  LWIP_ERROR("pbuf_take: invalid dataptr", (dataptr != NULL), return ERR_ARG;);
+  LWIP_ERROR("pbuf_take: buf not large enough", (buf->tot_len >= len), return ERR_MEM;);
+
+  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, &((const 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;
+}
+
+/**
+ * @ingroup pbuf
+ * Same as pbuf_take() but puts data at an offset
+ *
+ * @param buf pbuf to fill with data
+ * @param dataptr application supplied data buffer
+ * @param len length of the application supplied data buffer
+ * @param offset offset in pbuf where to copy dataptr to
+ *
+ * @return ERR_OK if successful, ERR_MEM if the pbuf is not big enough
+ */
+err_t
+pbuf_take_at(struct pbuf *buf, const void *dataptr, u16_t len, u16_t offset)
+{
+  u16_t target_offset;
+  struct pbuf* q = pbuf_skip(buf, offset, &target_offset);
+
+  /* return requested data if pbuf is OK */
+  if ((q != NULL) && (q->tot_len >= target_offset + len)) {
+    u16_t remaining_len = len;
+    const u8_t* src_ptr = (const u8_t*)dataptr;
+    /* copy the part that goes into the first pbuf */
+    u16_t first_copy_len = LWIP_MIN(q->len - target_offset, len);
+    MEMCPY(((u8_t*)q->payload) + target_offset, dataptr, first_copy_len);
+    remaining_len -= first_copy_len;
+    src_ptr += first_copy_len;
+    if (remaining_len > 0) {
+      return pbuf_take(q->next, src_ptr, remaining_len);
+    }
+    return ERR_OK;
+  }
+  return ERR_MEM;
+}
+
+/**
+ * @ingroup pbuf
+ * 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_UNUSED_ARG(err); /* in case of LWIP_NOASSERT */
+  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 */
+
+/**
+ * @ingroup pbuf
+ * 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(const struct pbuf* p, u16_t offset)
+{
+  int ret = pbuf_try_get_at(p, offset);
+  if (ret >= 0) {
+    return (u8_t)ret;
+  }
+  return 0;
+}
+
+/**
+ * @ingroup pbuf
+ * Get one byte from the specified position in a pbuf
+ *
+ * @param p pbuf to parse
+ * @param offset offset into p of the byte to return
+ * @return byte at an offset into p [0..0xFF] OR negative if 'offset' >= p->tot_len
+ */
+int
+pbuf_try_get_at(const struct pbuf* p, u16_t offset)
+{
+  u16_t q_idx;
+  const struct pbuf* q = pbuf_skip_const(p, offset, &q_idx);
+
+  /* return requested data if pbuf is OK */
+  if ((q != NULL) && (q->len > q_idx)) {
+    return ((u8_t*)q->payload)[q_idx];
+  }
+  return -1;
+}
+
+/**
+ * @ingroup pbuf
+ * Put one byte to the specified position in a pbuf
+ * WARNING: silently ignores offset >= p->tot_len
+ *
+ * @param p pbuf to fill
+ * @param offset offset into p of the byte to write
+ * @param data byte to write at an offset into p
+ */
+void
+pbuf_put_at(struct pbuf* p, u16_t offset, u8_t data)
+{
+  u16_t q_idx;
+  struct pbuf* q = pbuf_skip(p, offset, &q_idx);
+
+  /* write requested data if pbuf is OK */
+  if ((q != NULL) && (q->len > q_idx)) {
+    ((u8_t*)q->payload)[q_idx] = data;
+  }
+}
+
+/**
+ * @ingroup pbuf
+ * Compare pbuf contents at specified offset with memory s2, both of length n
+ *
+ * @param p pbuf to compare
+ * @param offset offset into p at which 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(const struct pbuf* p, u16_t offset, const void* s2, u16_t n)
+{
+  u16_t start = offset;
+  const struct pbuf* q = p;
+  u16_t i;
+ 
+  /* pbuf long enough to perform check? */
+  if(p->tot_len < (offset + n)) {
+    return 0xffff;
+  }
+ 
+  /* get the correct pbuf from chain. We know it succeeds because of p->tot_len check above. */
+  while ((q != NULL) && (q->len <= start)) {
+    start -= q->len;
+    q = q->next;
+  }
+ 
+  /* return requested data if pbuf is OK */
+  for (i = 0; i < n; i++) {
+    /* We know pbuf_get_at() succeeds because of p->tot_len check above. */
+    u8_t a = pbuf_get_at(q, start + i);
+    u8_t b = ((const u8_t*)s2)[i];
+    if (a != b) {
+      return i+1;
+    }
+  }
+  return 0;
+}
+
+/**
+ * @ingroup pbuf
+ * 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(const 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; i++) {
+      u16_t plus = pbuf_memcmp(p, i, mem, mem_len);
+      if (plus == 0) {
+        return i;
+      }
+    }
+  }
+  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(const 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/src/core/raw.c b/src/core/raw.c
new file mode 100644
index 0000000..80cf9ec
--- /dev/null
+++ b/src/core/raw.c
@@ -0,0 +1,521 @@
+/**
+ * @file
+ * Implementation of raw protocol PCBs for low-level handling of
+ * different types of protocols besides (or overriding) those
+ * already available in lwIP.\n
+ * See also @ref raw_raw
+ * 
+ * @defgroup raw_raw RAW
+ * @ingroup callbackstyle_api
+ * Implementation of raw protocol PCBs for low-level handling of
+ * different types of protocols besides (or overriding) those
+ * already available in lwIP.\n
+ * @see @ref raw_api
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+
+#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 "lwip/ip6.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/inet_chksum.h"
+
+#include <string.h>
+
+/** The list of RAW PCBs */
+static struct raw_pcb *raw_pcbs;
+
+static u8_t
+raw_input_match(struct raw_pcb *pcb, u8_t broadcast)
+{
+  LWIP_UNUSED_ARG(broadcast); /* in IPv6 only case */
+
+#if LWIP_IPV4 && LWIP_IPV6
+  /* Dual-stack: PCBs listening to any IP type also listen to any IP address */
+  if (IP_IS_ANY_TYPE_VAL(pcb->local_ip)) {
+#if IP_SOF_BROADCAST_RECV
+    if ((broadcast != 0) && !ip_get_option(pcb, SOF_BROADCAST)) {
+      return 0;
+    }
+#endif /* IP_SOF_BROADCAST_RECV */
+    return 1;
+  }
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+  /* Only need to check PCB if incoming IP version matches PCB IP version */
+  if (IP_ADDR_PCB_VERSION_MATCH_EXACT(pcb, ip_current_dest_addr())) {
+#if LWIP_IPV4
+    /* Special case: IPv4 broadcast: receive all broadcasts
+     * Note: broadcast variable can only be 1 if it is an IPv4 broadcast */
+    if (broadcast != 0) {
+#if IP_SOF_BROADCAST_RECV
+      if (ip_get_option(pcb, SOF_BROADCAST))
+#endif /* IP_SOF_BROADCAST_RECV */
+      {
+        if (ip4_addr_isany(ip_2_ip4(&pcb->local_ip))) {
+          return 1;
+        }
+      }
+    } else
+#endif /* LWIP_IPV4 */
+    /* Handle IPv4 and IPv6: catch all or exact match */
+    if (ip_addr_isany(&pcb->local_ip) ||
+       ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+/**
+ * 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;
+  s16_t proto;
+  u8_t eaten = 0;
+  u8_t broadcast = ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif());
+
+  LWIP_UNUSED_ARG(inp);
+
+#if LWIP_IPV6
+#if LWIP_IPV4
+  if (IP_HDR_GET_VERSION(p->payload) == 6)
+#endif /* LWIP_IPV4 */
+  {
+    struct ip6_hdr *ip6hdr = (struct ip6_hdr *)p->payload;
+    proto = IP6H_NEXTH(ip6hdr);
+  }
+#if LWIP_IPV4
+  else
+#endif /* LWIP_IPV4 */
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4
+  {
+    proto = IPH_PROTO((struct ip_hdr *)p->payload);
+  }
+#endif /* LWIP_IPV4 */
+
+  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) && raw_input_match(pcb, broadcast)) {
+      /* receive callback function available? */
+      if (pcb->recv != NULL) {
+#ifndef LWIP_NOASSERT
+        void* old_payload = p->payload;
+#endif
+        /* the receive callback function did not eat the packet? */
+        eaten = pcb->recv(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;
+}
+
+/**
+ * @ingroup raw_raw
+ * 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 IP4_ADDR_ANY to
+ * bind to all local interfaces.
+ *
+ * @return lwIP error code.
+ * - ERR_OK. Successful. No error occurred.
+ * - 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, const ip_addr_t *ipaddr)
+{
+  if ((pcb == NULL) || (ipaddr == NULL)) {
+    return ERR_VAL;
+  }
+  ip_addr_set_ipaddr(&pcb->local_ip, ipaddr);
+  return ERR_OK;
+}
+
+/**
+ * @ingroup raw_raw
+ * 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, const ip_addr_t *ipaddr)
+{
+  if ((pcb == NULL) || (ipaddr == NULL)) {
+    return ERR_VAL;
+  }
+  ip_addr_set_ipaddr(&pcb->remote_ip, ipaddr);
+  return ERR_OK;
+}
+
+/**
+ * @ingroup raw_raw
+ * 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.
+ */
+void
+raw_recv(struct raw_pcb *pcb, raw_recv_fn recv, void *recv_arg)
+{
+  /* remember recv() callback and user data */
+  pcb->recv = recv;
+  pcb->recv_arg = recv_arg;
+}
+
+/**
+ * @ingroup raw_raw
+ * 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, const ip_addr_t *ipaddr)
+{
+  err_t err;
+  struct netif *netif;
+  const ip_addr_t *src_ip;
+  struct pbuf *q; /* q will be sent down the stack */
+  s16_t header_size;
+
+  if ((pcb == NULL) || (ipaddr == NULL) || !IP_ADDR_PCB_VERSION_MATCH(pcb, ipaddr)) {
+    return ERR_VAL;
+  }
+
+  LWIP_DEBUGF(RAW_DEBUG | LWIP_DBG_TRACE, ("raw_sendto\n"));
+
+  header_size = (
+#if LWIP_IPV4 && LWIP_IPV6
+    IP_IS_V6(ipaddr) ? IP6_HLEN : IP_HLEN);
+#elif LWIP_IPV4
+    IP_HLEN);
+#else
+    IP6_HLEN);
+#endif
+
+  /* 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;
+    }
+  }
+
+  if(IP_IS_ANY_TYPE_VAL(pcb->local_ip)) {
+    /* Don't call ip_route() with IP_ANY_TYPE */
+    netif = ip_route(IP46_ADDR_ANY(IP_GET_TYPE(ipaddr)), ipaddr);
+  } else {
+    netif = ip_route(&pcb->local_ip, ipaddr);
+  }
+
+  if (netif == NULL) {
+    LWIP_DEBUGF(RAW_DEBUG | LWIP_DBG_LEVEL_WARNING, ("raw_sendto: No route to "));
+    ip_addr_debug_print(RAW_DEBUG | LWIP_DBG_LEVEL_WARNING, ipaddr);
+    /* free any temporary header pbuf allocated by pbuf_header() */
+    if (q != p) {
+      pbuf_free(q);
+    }
+    return ERR_RTE;
+  }
+
+#if IP_SOF_BROADCAST
+  if (IP_IS_V4(ipaddr))
+  {
+    /* 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 (ip_addr_isany(&pcb->local_ip)) {
+    /* use outgoing network interface IP address as source address */
+    src_ip = ip_netif_get_local_ip(netif, ipaddr);
+#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;
+  }
+
+#if LWIP_IPV6
+  /* If requested, based on the IPV6_CHECKSUM socket option per RFC3542,
+     compute the checksum and update the checksum in the payload. */
+  if (IP_IS_V6(ipaddr) && pcb->chksum_reqd) {
+    u16_t chksum = ip6_chksum_pseudo(p, pcb->protocol, p->tot_len, ip_2_ip6(src_ip), ip_2_ip6(ipaddr));
+    LWIP_ASSERT("Checksum must fit into first pbuf", p->len >= (pcb->chksum_offset + 2));
+    SMEMCPY(((u8_t *)p->payload) + pcb->chksum_offset, &chksum, sizeof(u16_t));
+  }
+#endif
+
+  NETIF_SET_HWADDRHINT(netif, &pcb->addr_hint);
+  err = ip_output_if(q, src_ip, ipaddr, 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;
+}
+
+/**
+ * @ingroup raw_raw
+ * 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, &pcb->remote_ip);
+}
+
+/**
+ * @ingroup raw_raw
+ * 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;
+        break;
+      }
+    }
+  }
+  memp_free(MEMP_RAW_PCB, pcb);
+}
+
+/**
+ * @ingroup raw_raw
+ * 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;
+}
+
+/**
+ * @ingroup raw_raw
+ * Create a RAW PCB for specific IP type.
+ *
+ * @return The RAW PCB which was created. NULL if the PCB data structure
+ * could not be allocated.
+ *
+ * @param type IP address type, see @ref lwip_ip_addr_type definitions.
+ * If you want to listen to IPv4 and IPv6 (dual-stack) packets,
+ * supply @ref IPADDR_TYPE_ANY as argument and bind to @ref IP_ANY_TYPE.
+ * @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_ip_type(u8_t type, u8_t proto)
+{
+  struct raw_pcb *pcb;
+  pcb = raw_new(proto);
+#if LWIP_IPV4 && LWIP_IPV6
+  if (pcb != NULL) {
+    IP_SET_TYPE_VAL(pcb->local_ip,  type);
+    IP_SET_TYPE_VAL(pcb->remote_ip, type);
+  }
+#else /* LWIP_IPV4 && LWIP_IPV6 */
+  LWIP_UNUSED_ARG(type);
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+  return pcb;
+}
+
+/** This function is called from netif.c when address is changed
+ *
+ * @param old_addr IP address of the netif before change
+ * @param new_addr IP address of the netif after change
+ */
+void raw_netif_ip_addr_changed(const ip_addr_t* old_addr, const ip_addr_t* new_addr)
+{
+  struct raw_pcb* rpcb;
+
+  if (!ip_addr_isany(old_addr) && !ip_addr_isany(new_addr)) {
+    for (rpcb = raw_pcbs; rpcb != NULL; rpcb = rpcb->next) {
+      /* PCB bound to current local interface address? */
+      if (ip_addr_cmp(&rpcb->local_ip, old_addr)) {
+        /* The PCB is bound to the old ipaddr and
+         * is set to bound to the new one instead */
+        ip_addr_copy(rpcb->local_ip, *new_addr);
+      }
+    }
+  }
+}
+
+#endif /* LWIP_RAW */
diff --git a/src/core/stats.c b/src/core/stats.c
new file mode 100644
index 0000000..893d199
--- /dev/null
+++ b/src/core/stats.c
@@ -0,0 +1,169 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+#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 "lwip/debug.h"
+
+#include <string.h>
+
+struct stats_ lwip_stats;
+
+void
+stats_init(void)
+{
+#ifdef LWIP_DEBUG
+#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 || MLD6_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", igmp->tx_report));
+}
+#endif /* IGMP_STATS || MLD6_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)
+{
+  if (index < MEMP_MAX) {
+    stats_display_mem(mem, mem->name);
+  }
+}
+#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", (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/src/core/sys.c b/src/core/sys.c
new file mode 100644
index 0000000..7059b4d
--- /dev/null
+++ b/src/core/sys.c
@@ -0,0 +1,106 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+/**
+ * @defgroup sys_layer Porting (system abstraction layer)
+ * @ingroup lwip
+ * @verbinclude "sys_arch.txt"
+ *
+ * @defgroup sys_os OS abstraction layer
+ * @ingroup sys_layer
+ * No need to implement functions in this section in NO_SYS mode.
+ *
+ * @defgroup sys_sem Semaphores
+ * @ingroup sys_os
+ *
+ * @defgroup sys_mutex Mutexes
+ * @ingroup sys_os
+ * Mutexes are recommended to correctly handle priority inversion,
+ * especially if you use LWIP_CORE_LOCKING .
+ *
+ * @defgroup sys_mbox Mailboxes
+ * @ingroup sys_os
+ *
+ * @defgroup sys_time Time
+ * @ingroup sys_layer
+ *
+ * @defgroup sys_prot Critical sections
+ * @ingroup sys_layer
+ * Used to protect short regions of code against concurrent access.
+ * - Your system is a bare-metal system (probably with an RTOS)
+ *   and interrupts are under your control:
+ *   Implement this as LockInterrupts() / UnlockInterrupts()
+ * - Your system uses an RTOS with deferred interrupt handling from a
+ *   worker thread: Implement as a global mutex or lock/unlock scheduler
+ * - Your system uses a high-level OS with e.g. POSIX signals:
+ *   Implement as a global mutex
+ *
+ * @defgroup sys_misc Misc
+ * @ingroup sys_os
+ */
+
+#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/src/core/tcp.c b/src/core/tcp.c
new file mode 100644
index 0000000..b5144d2
--- /dev/null
+++ b/src/core/tcp.c
@@ -0,0 +1,2169 @@
+/**
+ * @file 
+ * Transmission Control Protocol for IP
+ * See also @ref tcp_raw
+ *
+ * @defgroup tcp_raw TCP
+ * @ingroup callbackstyle_api
+ * Transmission Control Protocol for IP\n
+ * @see @ref raw_api and @ref netconn
+ *
+ * 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.\n
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+
+#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/tcp.h"
+#include "lwip/priv/tcp_priv.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>
+
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+
+#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) ((u16_t)(((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 */
+
+/* As initial send MSS, we use TCP_MSS but limit it to 536. */
+#if TCP_MSS > 536
+#define INITIAL_MSS 536
+#else
+#define INITIAL_MSS TCP_MSS
+#endif
+
+static 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;
+static const u8_t tcp_backoff[13] =
+    { 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7};
+ /* Times per slowtmr hits */
+static 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;
+
+/** 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};
+
+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);
+
+static err_t tcp_close_shutdown_fin(struct tcp_pcb *pcb);
+
+/**
+ * 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_slowtmr() every 500 ms, i.e., every other timer
+       tcp_tmr() is called. */
+    tcp_slowtmr();
+  }
+}
+
+#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
+/** Called when a listen pcb is closed. Iterates one pcb list and removes the
+ * closed listener pcb from pcb->listener if matching.
+ */
+static void
+tcp_remove_listener(struct tcp_pcb *list, struct tcp_pcb_listen *lpcb)
+{
+   struct tcp_pcb *pcb;
+   for (pcb = list; pcb != NULL; pcb = pcb->next) {
+      if (pcb->listener == lpcb) {
+         pcb->listener = NULL;
+      }
+   }
+}
+#endif
+
+/** Called when a listen pcb is closed. Iterates all pcb lists and removes the
+ * closed listener pcb from pcb->listener if matching.
+ */
+static void
+tcp_listen_closed(struct tcp_pcb *pcb)
+{
+#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
+  size_t i;
+  LWIP_ASSERT("pcb != NULL", pcb != NULL);
+  LWIP_ASSERT("pcb->state == LISTEN", pcb->state == LISTEN);
+  for (i = 1; i < LWIP_ARRAYSIZE(tcp_pcb_lists); i++) {
+    tcp_remove_listener(*tcp_pcb_lists[i], (struct tcp_pcb_listen*)pcb);
+  }
+#endif
+  LWIP_UNUSED_ARG(pcb);
+}
+
+#if TCP_LISTEN_BACKLOG
+/** @ingroup tcp_raw
+ * Delay accepting a connection in respect to the listen backlog:
+ * the number of outstanding connections is increased until
+ * tcp_backlog_accepted() is called.
+ *
+ * ATTENTION: the caller is responsible for calling tcp_backlog_accepted()
+ * or else the backlog feature will get out of sync!
+ *
+ * @param pcb the connection pcb which is not fully accepted yet
+ */
+void
+tcp_backlog_delayed(struct tcp_pcb* pcb)
+{
+  LWIP_ASSERT("pcb != NULL", pcb != NULL);
+  if ((pcb->flags & TF_BACKLOGPEND) == 0) {
+    if (pcb->listener != NULL) {
+      pcb->listener->accepts_pending++;
+      LWIP_ASSERT("accepts_pending != 0", pcb->listener->accepts_pending != 0);
+      pcb->flags |= TF_BACKLOGPEND;
+    }
+  }
+}
+
+/** @ingroup tcp_raw
+ * A delayed-accept a connection is accepted (or closed/aborted): decreases
+ * the number of outstanding connections after calling tcp_backlog_delayed().
+ *
+ * ATTENTION: the caller is responsible for calling tcp_backlog_accepted()
+ * or else the backlog feature will get out of sync!
+ *
+ * @param pcb the connection pcb which is now fully accepted (or closed/aborted)
+ */
+void
+tcp_backlog_accepted(struct tcp_pcb* pcb)
+{
+  LWIP_ASSERT("pcb != NULL", pcb != NULL);
+  if ((pcb->flags & TF_BACKLOGPEND) != 0) {
+    if (pcb->listener != NULL) {
+      LWIP_ASSERT("accepts_pending != 0", pcb->listener->accepts_pending != 0);
+      pcb->listener->accepts_pending--;
+      pcb->flags &= ~TF_BACKLOGPEND;
+    }
+  }
+}
+#endif /* TCP_LISTEN_BACKLOG */
+
+/**
+ * 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)
+{
+  if (rst_on_unacked_data && ((pcb->state == ESTABLISHED) || (pcb->state == CLOSE_WAIT))) {
+    if ((pcb->refused_data != NULL) || (pcb->rcv_wnd != TCP_WND_MAX(pcb))) {
+      /* 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);
+
+      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 */
+        if (tcp_input_pcb == pcb) {
+          /* prevent using a deallocated pcb: free it from tcp_input later */
+          tcp_trigger_input_pcb_close();
+        } else {
+          memp_free(MEMP_TCP_PCB, pcb);
+        }
+      }
+      return ERR_OK;
+    }
+  }
+
+  /* - states which free the pcb are handled here,
+     - states which send FIN and change state are handled in tcp_close_shutdown_fin() */
+  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. */
+    if (pcb->local_port != 0) {
+      TCP_RMV(&tcp_bound_pcbs, pcb);
+    }
+    memp_free(MEMP_TCP_PCB, pcb);
+    break;
+  case LISTEN:
+    tcp_listen_closed(pcb);
+    tcp_pcb_remove(&tcp_listen_pcbs.pcbs, pcb);
+    memp_free(MEMP_TCP_PCB_LISTEN, pcb);
+    break;
+  case SYN_SENT:
+    TCP_PCB_REMOVE_ACTIVE(pcb);
+    memp_free(MEMP_TCP_PCB, pcb);
+    MIB2_STATS_INC(mib2.tcpattemptfails);
+    break;
+  default:
+    return tcp_close_shutdown_fin(pcb);
+  }
+  return ERR_OK;
+}
+
+static err_t
+tcp_close_shutdown_fin(struct tcp_pcb *pcb)
+{
+  err_t err;
+  LWIP_ASSERT("pcb != NULL", pcb != NULL);
+
+  switch (pcb->state) {
+  case SYN_RCVD:
+    err = tcp_send_fin(pcb);
+    if (err == ERR_OK) {
+      tcp_backlog_accepted(pcb);
+      MIB2_STATS_INC(mib2.tcpattemptfails);
+      pcb->state = FIN_WAIT_1;
+    }
+    break;
+  case ESTABLISHED:
+    err = tcp_send_fin(pcb);
+    if (err == ERR_OK) {
+      MIB2_STATS_INC(mib2.tcpestabresets);
+      pcb->state = FIN_WAIT_1;
+    }
+    break;
+  case CLOSE_WAIT:
+    err = tcp_send_fin(pcb);
+    if (err == ERR_OK) {
+      MIB2_STATS_INC(mib2.tcpestabresets);
+      pcb->state = LAST_ACK;
+    }
+    break;
+  default:
+    /* Has already been closed, do nothing. */
+    return ERR_OK;
+  }
+
+  if (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. */
+    tcp_output(pcb);
+  } else if (err == ERR_MEM) {
+    /* Mark this pcb for closing. Closing is retried from tcp_tmr. */
+    pcb->flags |= TF_CLOSEPEND;
+    /* We have to return ERR_OK from here to indicate to the callers that this
+       pcb should not be used any more as it will be freed soon via tcp_tmr.
+       This is OK here since sending FIN does not guarantee a time frime for
+       actually freeing the pcb, either (it is left in closure states for
+       remote ACK or timeout) */
+    return ERR_OK;
+  }
+  return err;
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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)
+{
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_close: closing in "));
+  tcp_debug_print_state(pcb->state);
+
+  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);
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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
+ * (i.e. returns ER_OK), the PCB must 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, (u8_t)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 = 0;
+    u16_t local_port = 0;
+    enum tcp_state last_state;
+    seqno = pcb->snd_nxt;
+    ackno = pcb->rcv_nxt;
+#if LWIP_CALLBACK_API
+    errf = pcb->errf;
+#endif /* LWIP_CALLBACK_API */
+    errf_arg = pcb->callback_arg;
+    if (pcb->state == CLOSED) {
+      if (pcb->local_port != 0) {
+        /* bound, not yet opened */
+        TCP_RMV(&tcp_bound_pcbs, pcb);
+      }
+    } else {
+      send_rst = reset;
+      local_port = pcb->local_port;
+      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 */
+    tcp_backlog_accepted(pcb);
+    if (send_rst) {
+      LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_abandon: sending RST\n"));
+      tcp_rst(seqno, ackno, &pcb->local_ip, &pcb->remote_ip, local_port, pcb->remote_port);
+    }
+    last_state = pcb->state;
+    memp_free(MEMP_TCP_PCB, pcb);
+    TCP_EVENT_ERR(last_state, errf, errf_arg, ERR_ABRT);
+  }
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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);
+}
+
+/**
+ * @ingroup tcp_raw
+ * Binds the connection to a local port number 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 IP4_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, const ip_addr_t *ipaddr, u16_t port)
+{
+  int i;
+  int max_pcb_list = NUM_TCP_PCB_LISTS;
+  struct tcp_pcb *cpcb;
+
+#if LWIP_IPV4
+  /* Don't propagate NULL pointer (IPv4 ANY) to subsequent functions */
+  if (ipaddr == NULL) {
+    ipaddr = IP4_ADDR_ANY;
+  }
+#endif /* LWIP_IPV4 */
+
+  /* still need to check for ipaddr == NULL in IPv6 only case */
+  if ((pcb == NULL) || (ipaddr == NULL)) {
+    return ERR_VAL;
+  }
+
+  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;
+    }
+  } else {
+    /* 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_IS_V6(ipaddr) == IP_IS_V6_VAL(cpcb->local_ip)) &&
+                (ip_addr_isany(&cpcb->local_ip) ||
+                ip_addr_isany(ipaddr) ||
+                ip_addr_cmp(&cpcb->local_ip, ipaddr))) {
+              return ERR_USE;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  if (!ip_addr_isany(ipaddr)) {
+    ip_addr_set(&pcb->local_ip, 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;
+}
+#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(err);
+
+  tcp_abort(pcb);
+
+  return ERR_ABRT;
+}
+#endif /* LWIP_CALLBACK_API */
+
+/**
+ * @ingroup tcp_raw
+ * 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_with_backlog(tpcb, backlog);
+ */
+struct tcp_pcb *
+tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog)
+{
+  return tcp_listen_with_backlog_and_err(pcb, backlog, NULL);
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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
+ * @param err when NULL is returned, this contains the error reason
+ * @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_with_backlog_and_err(tpcb, backlog, &err);
+ */
+struct tcp_pcb *
+tcp_listen_with_backlog_and_err(struct tcp_pcb *pcb, u8_t backlog, err_t *err)
+{
+  struct tcp_pcb_listen *lpcb = NULL;
+  err_t res;
+
+  LWIP_UNUSED_ARG(backlog);
+  LWIP_ERROR("tcp_listen: pcb already connected", pcb->state == CLOSED, res = ERR_CLSD; goto done);
+
+  /* already listening? */
+  if (pcb->state == LISTEN) {
+    lpcb = (struct tcp_pcb_listen*)pcb;
+    res = ERR_ALREADY;
+    goto done;
+  }
+#if SO_REUSE
+  if (ip_get_option(pcb, SOF_REUSEADDR)) {
+    /* 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_addr_cmp(&lpcb->local_ip, &pcb->local_ip)) {
+        /* this address/port is already used */
+        lpcb = NULL;
+        res = ERR_USE;
+        goto done;
+      }
+    }
+  }
+#endif /* SO_REUSE */
+  lpcb = (struct tcp_pcb_listen *)memp_malloc(MEMP_TCP_PCB_LISTEN);
+  if (lpcb == NULL) {
+    res = ERR_MEM;
+    goto done;
+  }
+  lpcb->callback_arg = pcb->callback_arg;
+  lpcb->local_port = pcb->local_port;
+  lpcb->state = LISTEN;
+  lpcb->prio = pcb->prio;
+  lpcb->so_options = pcb->so_options;
+  lpcb->ttl = pcb->ttl;
+  lpcb->tos = pcb->tos;
+#if LWIP_IPV4 && LWIP_IPV6
+  IP_SET_TYPE_VAL(lpcb->remote_ip, pcb->local_ip.type);
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+  ip_addr_copy(lpcb->local_ip, pcb->local_ip);
+  if (pcb->local_port != 0) {
+    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;
+  tcp_backlog_set(lpcb, backlog);
+#endif /* TCP_LISTEN_BACKLOG */
+  TCP_REG(&tcp_listen_pcbs.pcbs, (struct tcp_pcb *)lpcb);
+  res = ERR_OK;
+done:
+  if (err != NULL) {
+    *err = res;
+  }
+  return (struct tcp_pcb *)lpcb;
+}
+
+/**
+ * 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;
+#if !LWIP_WND_SCALE
+      LWIP_ASSERT("new_rcv_ann_wnd <= 0xffff", new_rcv_ann_wnd <= 0xffff);
+#endif
+      pcb->rcv_ann_wnd = (tcpwnd_size_t)new_rcv_ann_wnd;
+    }
+    return 0;
+  }
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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);
+
+  pcb->rcv_wnd += len;
+  if (pcb->rcv_wnd > TCP_WND_MAX(pcb)) {
+    pcb->rcv_wnd = TCP_WND_MAX(pcb);
+  } else if (pcb->rcv_wnd == 0) {
+    /* rcv_wnd overflowed */
+    if ((pcb->state == CLOSE_WAIT) || (pcb->state == LAST_ACK)) {
+      /* In passive close, we allow this, since the FIN bit is added to rcv_wnd
+         by the stack itself, since it is not mandatory for an application
+         to call tcp_recved() for the FIN bit, but e.g. the netconn API does so. */
+      pcb->rcv_wnd = TCP_WND_MAX(pcb);
+    } else {
+      LWIP_ASSERT("tcp_recved: len wrapped rcv_wnd\n", 0);
+    }
+  }
+
+  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: received %"U16_F" bytes, wnd %"TCPWNDSIZE_F" (%"TCPWNDSIZE_F").\n",
+         len, pcb->rcv_wnd, (u16_t)(TCP_WND_MAX(pcb) - 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;
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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 (on error,
+                    the err calback will be called)
+ * @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, const ip_addr_t *ipaddr, u16_t port,
+      tcp_connected_fn connected)
+{
+  err_t ret;
+  u32_t iss;
+  u16_t old_local_port;
+
+  if ((pcb == NULL) || (ipaddr == NULL)) {
+    return ERR_VAL;
+  }
+
+  LWIP_ERROR("tcp_connect: can only connect from state CLOSED", pcb->state == CLOSED, return ERR_ISCONN);
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_connect to port %"U16_F"\n", port));
+  ip_addr_set(&pcb->remote_ip, ipaddr);
+  pcb->remote_port = port;
+
+  /* check if we have a route to the remote host */
+  if (ip_addr_isany(&pcb->local_ip)) {
+    /* no local IP address set, yet. */
+    struct netif *netif;
+    const ip_addr_t *local_ip;
+    ip_route_get_local_ip(&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. */
+    ip_addr_copy(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;
+    }
+  } else {
+#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_addr_cmp(&cpcb->local_ip, &pcb->local_ip) &&
+              ip_addr_cmp(&cpcb->remote_ip, ipaddr)) {
+            /* linux returns EISCONN here, but ERR_USE should be OK for us */
+            return ERR_USE;
+          }
+        }
+      }
+    }
+#endif /* SO_REUSE */
+  }
+
+  iss = tcp_next_iss(pcb);
+  pcb->rcv_nxt = 0;
+  pcb->snd_nxt = iss;
+  pcb->lastack = iss - 1;
+  pcb->snd_wl2 = iss - 1;
+  pcb->snd_lbb = iss - 1;
+  /* Start with a window that does not need scaling. When window scaling is
+     enabled and used, the window is enlarged when both sides agree on scaling. */
+  pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND_MIN16(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 = INITIAL_MSS;
+#if TCP_CALCULATE_EFF_SEND_MSS
+  pcb->mss = tcp_eff_send_mss(pcb->mss, &pcb->local_ip, &pcb->remote_ip);
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+  pcb->cwnd = 1;
+#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);
+    MIB2_STATS_INC(mib2.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;
+  tcpwnd_size_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. */
+        u8_t backoff_cnt = tcp_persist_backoff[pcb->persist_backoff-1];
+        if (pcb->persist_cnt < backoff_cnt) {
+          pcb->persist_cnt++;
+        }
+        if (pcb->persist_cnt >= backoff_cnt) {
+          if (tcp_zero_window_probe(pcb) == ERR_OK) {
+            pcb->persist_cnt = 0;
+            if (pcb->persist_backoff < sizeof(tcp_persist_backoff)) {
+              pcb->persist_backoff++;
+            }
+          }
+        }
+      } 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) {
+            u8_t backoff_idx = LWIP_MIN(pcb->nrtx, sizeof(tcp_backoff)-1);
+            pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[backoff_idx];
+          }
+
+          /* 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 < (tcpwnd_size_t)(pcb->mss << 1)) {
+            pcb->ssthresh = (pcb->mss << 1);
+          }
+          pcb->cwnd = pcb->mss;
+          LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %"TCPWNDSIZE_F
+                                       " ssthresh %"TCPWNDSIZE_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 "));
+        ip_addr_debug_print(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)
+      {
+        err = tcp_keepalive(pcb);
+        if (err == ERR_OK) {
+          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;
+#if LWIP_CALLBACK_API
+      tcp_err_fn err_fn = pcb->errf;
+#endif /* LWIP_CALLBACK_API */
+      void *err_arg;
+      enum tcp_state last_state;
+      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);
+      }
+
+      err_arg = pcb->callback_arg;
+      last_state = pcb->state;
+      pcb2 = pcb;
+      pcb = pcb->next;
+      memp_free(MEMP_TCP_PCB, pcb2);
+
+      tcp_active_pcbs_changed = 0;
+      TCP_EVENT_ERR(last_state, 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);
+      }
+      /* send pending FIN */
+      if (pcb->flags & TF_CLOSEPEND) {
+        LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: pending FIN\n"));
+        pcb->flags &= ~(TF_CLOSEPEND);
+        tcp_close_shutdown_fin(pcb);
+      }
+
+      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;
+    } else {
+      pcb = pcb->next;
+    }
+  }
+}
+
+/** Call tcp_output for all active pcbs that have TF_NAGLEMEMERR set */
+void
+tcp_txnow(void)
+{
+  struct tcp_pcb *pcb;
+
+  for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
+    if (pcb->flags & TF_NAGLEMEMERR) {
+      tcp_output(pcb);
+    }
+  }
+}
+
+/** Pass pcb->refused_data to the recv callback */
+err_t
+tcp_process_refused_data(struct tcp_pcb *pcb)
+{
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+  struct pbuf *rest;
+  while (pcb->refused_data != NULL)
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+  {
+    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;
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+    pbuf_split_64k(refused_data, &rest);
+    pcb->refused_data = rest;
+#else /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+    pcb->refused_data = NULL;
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+    /* 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
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+          && (rest == NULL)
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+         ) {
+        /* correct rcv_wnd as the application won't call tcp_recved()
+           for the FIN's seqno */
+        if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) {
+          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) */
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+      if (rest != NULL) {
+        pbuf_cat(refused_data, rest);
+      }
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+      pcb->refused_data = refused_data;
+      return ERR_INPROGRESS;
+    }
+  }
+  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 = LWIP_MIN(TCP_PRIO_MAX, prio);
+
+  /* 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 <= 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 specific state.
+ * Called from tcp_alloc() for LAST_ACK and CLOSING if no more connections are available.
+ */
+static void
+tcp_kill_state(enum tcp_state state)
+{
+  struct tcp_pcb *pcb, *inactive;
+  u32_t inactivity;
+
+  LWIP_ASSERT("invalid state", (state == CLOSING) || (state == LAST_ACK));
+
+  inactivity = 0;
+  inactive = NULL;
+  /* Go through the list of active pcbs and get the oldest pcb that is in state
+     CLOSING/LAST_ACK. */
+  for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
+    if (pcb->state == state) {
+      if ((u32_t)(tcp_ticks - pcb->tmr) >= inactivity) {
+        inactivity = tcp_ticks - pcb->tmr;
+        inactive = pcb;
+      }
+    }
+  }
+  if (inactive != NULL) {
+    LWIP_DEBUGF(TCP_DEBUG, ("tcp_kill_closing: killing oldest %s PCB %p (%"S32_F")\n",
+           tcp_state_str[state], (void *)inactive, inactivity));
+    /* Don't send a RST, since no data is lost. */
+    tcp_abandon(inactive, 0);
+  }
+}
+
+/**
+ * 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;
+
+  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 oldest connection in LAST-ACK (these wouldn't go to TIME-WAIT). */
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing off oldest LAST-ACK connection\n"));
+      tcp_kill_state(LAST_ACK);
+      /* Try to allocate a tcp_pcb again. */
+      pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);
+      if (pcb == NULL) {
+        /* Try killing oldest connection in CLOSING. */
+        LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing off oldest CLOSING connection\n"));
+        tcp_kill_state(CLOSING);
+        /* 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 multiple times before */
+            MEMP_STATS_DEC(err, MEMP_TCP_PCB);
+          }
+        }
+        if (pcb != NULL) {
+          /* adjust err stats: memp_malloc failed multiple times before */
+          MEMP_STATS_DEC(err, MEMP_TCP_PCB);
+        }
+      }
+      if (pcb != NULL) {
+        /* adjust err stats: memp_malloc failed multiple times before */
+        MEMP_STATS_DEC(err, MEMP_TCP_PCB);
+      }
+    }
+    if (pcb != NULL) {
+      /* adjust err stats: memp_malloc failed above */
+      MEMP_STATS_DEC(err, MEMP_TCP_PCB);
+    }
+  }
+  if (pcb != NULL) {
+    /* zero out the whole pcb, so there is no need to initialize members to zero */
+    memset(pcb, 0, sizeof(struct tcp_pcb));
+    pcb->prio = prio;
+    pcb->snd_buf = TCP_SND_BUF;
+    /* Start with a window that does not need scaling. When window scaling is
+       enabled and used, the window is enlarged when both sides agree on scaling. */
+    pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND_MIN16(TCP_WND);
+    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 = INITIAL_MSS;
+    pcb->rto = 3000 / TCP_SLOW_INTERVAL;
+    pcb->sv = 3000 / TCP_SLOW_INTERVAL;
+    pcb->rtime = -1;
+    pcb->cwnd = 1;
+    pcb->tmr = tcp_ticks;
+    pcb->last_timer = tcp_timer_ctr;
+
+    /* RFC 5681 recommends setting ssthresh abritrarily high and gives an example
+    of using the largest advertised receive window.  We've seen complications with
+    receiving TCPs that use window scaling and/or window auto-tuning where the
+    initial advertised window is very small and then grows rapidly once the
+    connection is established. To avoid these complications, we set ssthresh to the
+    largest effective cwnd (amount of in-flight data) that the sender can have. */
+    pcb->ssthresh = TCP_SND_BUF;
+
+#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 */
+  }
+  return pcb;
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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);
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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().
+ *
+ * @param type IP address type, see @ref lwip_ip_addr_type definitions.
+ * If you want to listen to IPv4 and IPv6 (dual-stack) connections,
+ * supply @ref IPADDR_TYPE_ANY as argument and bind to @ref IP_ANY_TYPE.
+ * @return a new tcp_pcb that initially is in state CLOSED
+ */
+struct tcp_pcb *
+tcp_new_ip_type(u8_t type)
+{
+  struct tcp_pcb * pcb;
+  pcb = tcp_alloc(TCP_PRIO_NORMAL);
+#if LWIP_IPV4 && LWIP_IPV6
+  if (pcb != NULL) {
+    IP_SET_TYPE_VAL(pcb->local_ip, type);
+    IP_SET_TYPE_VAL(pcb->remote_ip, type);
+  }
+#else
+  LWIP_UNUSED_ARG(type);
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+  return pcb;
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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. */
+  if (pcb != NULL) {
+    pcb->callback_arg = arg;
+  }
+}
+#if LWIP_CALLBACK_API
+
+/**
+ * @ingroup tcp_raw
+ * 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)
+{
+  if (pcb != NULL) {
+    LWIP_ASSERT("invalid socket state for recv callback", pcb->state != LISTEN);
+    pcb->recv = recv;
+  }
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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)
+{
+  if (pcb != NULL) {
+    LWIP_ASSERT("invalid socket state for sent callback", pcb->state != LISTEN);
+    pcb->sent = sent;
+  }
+}
+
+/**
+ * @ingroup tcp_raw
+ * Used to specify the function that should be called when a fatal error
+ * has occurred on the connection.
+ *
+ * @note The corresponding pcb is already freed when this callback is called!
+ * 
+ * @param pcb tcp_pcb to set the err callback
+ * @param err callback function to call for this pcb when a fatal error
+ *        has occurred on the connection
+ */
+void
+tcp_err(struct tcp_pcb *pcb, tcp_err_fn err)
+{
+  if (pcb != NULL) {
+    LWIP_ASSERT("invalid socket state for err callback", pcb->state != LISTEN);
+    pcb->errf = err;
+  }
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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)
+{
+  if ((pcb != NULL) && (pcb->state == LISTEN)) {
+    struct tcp_pcb_listen *lpcb = (struct tcp_pcb_listen*)pcb;
+    lpcb->accept = accept;
+  }
+}
+#endif /* LWIP_CALLBACK_API */
+
+
+/**
+ * @ingroup tcp_raw
+ * 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"));
+
+    tcp_backlog_accepted(pcb);
+
+    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;
+  /* reset the local port to prevent the pcb from being 'bound' */
+  pcb->local_port = 0;
+
+  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(struct tcp_pcb *pcb)
+{
+#ifdef LWIP_HOOK_TCP_ISN
+  return LWIP_HOOK_TCP_ISN(&pcb->local_ip, pcb->local_port, &pcb->remote_ip, pcb->remote_port);
+#else /* LWIP_HOOK_TCP_ISN */
+  static u32_t iss = 6510;
+
+  LWIP_UNUSED_ARG(pcb);
+
+  iss += tcp_ticks;       /* XXX */
+  return iss;
+#endif /* LWIP_HOOK_TCP_ISN */
+}
+
+#if TCP_CALCULATE_EFF_SEND_MSS
+/**
+ * Calculates the effective send mss that can be used for a specific IP address
+ * by using ip_route to determine 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, const ip_addr_t *dest
+#if LWIP_IPV6 || LWIP_IPV4_SRC_ROUTING
+                     , const ip_addr_t *src
+#endif /* LWIP_IPV6 || LWIP_IPV4_SRC_ROUTING */
+                     )
+{
+  u16_t mss_s;
+  struct netif *outif;
+  s16_t mtu;
+
+  outif = ip_route(src, dest);
+#if LWIP_IPV6
+#if LWIP_IPV4
+  if (IP_IS_V6(dest))
+#endif /* LWIP_IPV4 */
+  {
+    /* First look in destination cache, to see if there is a Path MTU. */
+    mtu = nd6_get_destination_mtu(ip_2_ip6(dest), outif);
+  }
+#if LWIP_IPV4
+  else
+#endif /* LWIP_IPV4 */
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4
+  {
+    if (outif == NULL) {
+      return sendmss;
+    }
+    mtu = outif->mtu;
+  }
+#endif /* LWIP_IPV4 */
+
+  if (mtu != 0) {
+#if LWIP_IPV6
+#if LWIP_IPV4
+    if (IP_IS_V6(dest))
+#endif /* LWIP_IPV4 */
+    {
+      mss_s = mtu - IP6_HLEN - TCP_HLEN;
+    }
+#if LWIP_IPV4
+    else
+#endif /* LWIP_IPV4 */
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4
+    {
+      mss_s = mtu - IP_HLEN - TCP_HLEN;
+    }
+#endif /* LWIP_IPV4 */
+    /* 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 */
+
+/** Helper function for tcp_netif_ip_addr_changed() that iterates a pcb list */
+static void
+tcp_netif_ip_addr_changed_pcblist(const ip_addr_t* old_addr, struct tcp_pcb* pcb_list)
+{
+  struct tcp_pcb *pcb;
+  pcb = pcb_list;
+  while (pcb != NULL) {
+    /* PCB bound to current local interface address? */
+    if (ip_addr_cmp(&pcb->local_ip, old_addr)
+#if LWIP_AUTOIP
+      /* connections to link-local addresses must persist (RFC3927 ch. 1.9) */
+      && (!IP_IS_V4_VAL(pcb->local_ip) || !ip4_addr_islinklocal(ip_2_ip4(&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;
+    }
+  }
+}
+
+/** This function is called from netif.c when address is changed or netif is removed
+ *
+ * @param old_addr IP address of the netif before change
+ * @param new_addr IP address of the netif after change or NULL if netif has been removed
+ */
+void
+tcp_netif_ip_addr_changed(const ip_addr_t* old_addr, const ip_addr_t* new_addr)
+{
+  struct tcp_pcb_listen *lpcb, *next;
+
+  if (!ip_addr_isany(old_addr)) {
+    tcp_netif_ip_addr_changed_pcblist(old_addr, tcp_active_pcbs);
+    tcp_netif_ip_addr_changed_pcblist(old_addr, tcp_bound_pcbs);
+
+    if (!ip_addr_isany(new_addr)) {
+      /* PCB bound to current local interface address? */
+      for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = next) {
+        next = lpcb->next;
+        /* PCB bound to current local interface address? */
+        if (ip_addr_cmp(&lpcb->local_ip, old_addr)) {
+          /* The PCB is listening to the old ipaddr and
+            * is set to listen to the new one instead */
+          ip_addr_copy(lpcb->local_ip, *new_addr);
+        }
+      }
+    }
+  }
+}
+
+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",
+         lwip_ntohs(tcphdr->src), lwip_ntohs(tcphdr->dest)));
+  LWIP_DEBUGF(TCP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(TCP_DEBUG, ("|           %010"U32_F"          | (seq no)\n",
+          lwip_ntohl(tcphdr->seqno)));
+  LWIP_DEBUGF(TCP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(TCP_DEBUG, ("|           %010"U32_F"          | (ack no)\n",
+         lwip_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),
+         (u16_t)(TCPH_FLAGS(tcphdr) >> 5 & 1),
+         (u16_t)(TCPH_FLAGS(tcphdr) >> 4 & 1),
+         (u16_t)(TCPH_FLAGS(tcphdr) >> 3 & 1),
+         (u16_t)(TCPH_FLAGS(tcphdr) >> 2 & 1),
+         (u16_t)(TCPH_FLAGS(tcphdr) >> 1 & 1),
+         (u16_t)(TCPH_FLAGS(tcphdr)      & 1),
+         lwip_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",
+         lwip_ntohs(tcphdr->chksum), lwip_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;
+  struct tcp_pcb_listen *pcbl;
+
+  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 (pcbl = tcp_listen_pcbs.listen_pcbs; pcbl != NULL; pcbl = pcbl->next) {
+    LWIP_DEBUGF(TCP_DEBUG, ("Local port %"U16_F" ", pcbl->local_port));
+    tcp_debug_print_state(pcbl->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/src/core/tcp_in.c b/src/core/tcp_in.c
new file mode 100644
index 0000000..5e839ff
--- /dev/null
+++ b/src/core/tcp_in.c
@@ -0,0 +1,1837 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/priv/tcp_priv.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/ip6.h"
+#include "lwip/ip6_addr.h"
+#if LWIP_ND6_TCP_REACHABILITY_HINTS
+#include "lwip/nd6.h"
+#endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */
+
+/** Initial CWND calculation as defined RFC 2581 */
+#define LWIP_TCP_CALC_INITIAL_CWND(mss) LWIP_MIN((4U * (mss)), LWIP_MAX((2U * (mss)), 4380U));
+
+/* 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 u16_t tcphdr_optlen;
+static u16_t tcphdr_opt1len;
+static u8_t* tcphdr_opt2;
+static u16_t tcp_optidx;
+static u32_t seqno, ackno;
+static tcpwnd_size_t recv_acked;
+static u16_t tcplen;
+static u8_t flags;
+
+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 void tcp_listen_input(struct tcp_pcb_listen *pcb);
+static void tcp_timewait_input(struct tcp_pcb *pcb);
+
+static int tcp_input_delayed_close(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_bytes;
+  err_t err;
+
+  LWIP_UNUSED_ARG(inp);
+
+  PERF_START;
+
+  TCP_STATS_INC(tcp.recv);
+  MIB2_STATS_INC(mib2.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 < TCP_HLEN) {
+    /* 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_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif()) ||
+      ip_addr_ismulticast(ip_current_dest_addr())) {
+    TCP_STATS_INC(tcp.proterr);
+    goto dropped;
+  }
+
+#if CHECKSUM_CHECK_TCP
+  IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_TCP) {
+    /* Verify TCP checksum. */
+    u16_t chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len,
+                               ip_current_src_addr(), ip_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 */
+
+  /* sanity-check header length */
+  hdrlen_bytes = TCPH_HDRLEN(tcphdr) * 4;
+  if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) {
+    LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: invalid header length (%"U16_F")\n", (u16_t)hdrlen_bytes));
+    TCP_STATS_INC(tcp.lenerr);
+    goto dropped;
+  }
+
+  /* Move the payload pointer in the pbuf so that it points to the
+     TCP data instead of the TCP header. */
+  tcphdr_optlen = hdrlen_bytes - TCP_HLEN;
+  tcphdr_opt2 = NULL;
+  if (p->len >= hdrlen_bytes) {
+    /* all options are in the first pbuf */
+    tcphdr_opt1len = tcphdr_optlen;
+    pbuf_header(p, -(s16_t)hdrlen_bytes); /* cannot fail */
+  } else {
+    u16_t opt2len;
+    /* TCP header fits into first pbuf, options don't - data is in the next pbuf */
+    /* there must be a next pbuf, due to hdrlen_bytes sanity check above */
+    LWIP_ASSERT("p->next != NULL", p->next != NULL);
+
+    /* advance over the TCP header (cannot fail) */
+    pbuf_header(p, -TCP_HLEN);
+
+    /* determine how long the first and second parts of the options are */
+    tcphdr_opt1len = p->len;
+    opt2len = tcphdr_optlen - tcphdr_opt1len;
+
+    /* options continue in the next pbuf: set p to zero length and hide the
+        options in the next pbuf (adjusting p->tot_len) */
+    pbuf_header(p, -(s16_t)tcphdr_opt1len);
+
+    /* check that the options fit in the second pbuf */
+    if (opt2len > p->next->len) {
+      /* drop short packets */
+      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: options overflow second pbuf (%"U16_F" bytes)\n", p->next->len));
+      TCP_STATS_INC(tcp.lenerr);
+      goto dropped;
+    }
+
+    /* remember the pointer to the second part of the options */
+    tcphdr_opt2 = (u8_t*)p->next->payload;
+
+    /* advance p->next to point after the options, and manually
+        adjust p->tot_len to keep it consistent with the changed p->next */
+    pbuf_header(p->next, -(s16_t)opt2len);
+    p->tot_len -= opt2len;
+
+    LWIP_ASSERT("p->len == 0", p->len == 0);
+    LWIP_ASSERT("p->tot_len == p->next->tot_len", p->tot_len == p->next->tot_len);
+  }
+
+  /* Convert fields in TCP header to host byte order. */
+  tcphdr->src = lwip_ntohs(tcphdr->src);
+  tcphdr->dest = lwip_ntohs(tcphdr->dest);
+  seqno = tcphdr->seqno = lwip_ntohl(tcphdr->seqno);
+  ackno = tcphdr->ackno = lwip_ntohl(tcphdr->ackno);
+  tcphdr->wnd = lwip_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_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) &&
+        ip_addr_cmp(&pcb->local_ip, ip_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;
+      } else {
+        TCP_STATS_INC(tcp.cachehit);
+      }
+      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_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) &&
+          ip_addr_cmp(&pcb->local_ip, ip_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;
+    for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
+      if (lpcb->local_port == tcphdr->dest) {
+        if (IP_IS_ANY_TYPE_VAL(lpcb->local_ip)) {
+          /* found an ANY TYPE (IPv4/IPv6) match */
+#if SO_REUSE
+          lpcb_any = lpcb;
+          lpcb_prev = prev;
+#else /* SO_REUSE */
+          break;
+#endif /* SO_REUSE */
+        } else if (IP_ADDR_PCB_VERSION_MATCH_EXACT(lpcb, ip_current_dest_addr())) {
+          if (ip_addr_cmp(&lpcb->local_ip, ip_current_dest_addr())) {
+            /* found an exact match */
+            break;
+          } else if (ip_addr_isany(&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) {
+      /* 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;
+      } else {
+        TCP_STATS_INC(tcp.cachehit);
+      }
+
+      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
+    tcp_debug_print_state(pcb->state);
+#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;
+    recv_acked = 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 */
+        if (pcb->rcv_ann_wnd == 0) {
+          /* this is a zero-window probe, we respond to it with current RCV.NXT
+          and drop the data segment */
+          tcp_send_empty_ack(pcb);
+        }
+        TCP_STATS_INC(tcp.drop);
+        MIB2_STATS_INC(mib2.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->state, pcb->errf, pcb->callback_arg, ERR_RST);
+        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 (recv_acked > 0) {
+          u16_t acked16;
+#if LWIP_WND_SCALE
+          /* recv_acked is u32_t but the sent callback only takes a u16_t,
+             so we might have to call it multiple times. */
+          u32_t acked = recv_acked;
+          while (acked > 0) {
+            acked16 = (u16_t)LWIP_MIN(acked, 0xffffu);
+            acked -= acked16;
+#else
+          {
+            acked16 = recv_acked;
+#endif
+            TCP_EVENT_SENT(pcb, (u16_t)acked16, err);
+            if (err == ERR_ABRT) {
+              goto aborted;
+            }
+          }
+          recv_acked = 0;
+        }
+        if (tcp_input_delayed_close(pcb)) {
+          goto aborted;
+        }
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+        while (recv_data != NULL) {
+          struct pbuf *rest = NULL;
+          pbuf_split_64k(recv_data, &rest);
+#else /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+        if (recv_data != NULL) {
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+
+          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);
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+            if (rest != NULL) {
+              pbuf_free(rest);
+            }
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+            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) {
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+            if (rest != NULL) {
+              pbuf_free(rest);
+            }
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+            goto aborted;
+          }
+
+          /* If the upper layer can't receive this data, store it */
+          if (err != ERR_OK) {
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+            if (rest != NULL) {
+              pbuf_cat(recv_data, rest);
+            }
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+            pcb->refused_data = recv_data;
+            LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n"));
+#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
+            break;
+          } else {
+            /* Upper layer received the data, go on with the rest if > 64K */
+            recv_data = rest;
+#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
+          }
+        }
+
+        /* 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_MAX(pcb)) {
+              pcb->rcv_wnd++;
+            }
+            TCP_EVENT_CLOSED(pcb, err);
+            if (err == ERR_ABRT) {
+              goto aborted;
+            }
+          }
+        }
+
+        tcp_input_pcb = NULL;
+        if (tcp_input_delayed_close(pcb)) {
+          goto aborted;
+        }
+        /* 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, ip_current_dest_addr(),
+        ip_current_src_addr(), tcphdr->dest, tcphdr->src);
+    }
+    pbuf_free(p);
+  }
+
+  LWIP_ASSERT("tcp_input: tcp_pcbs_sane()", tcp_pcbs_sane());
+  PERF_STOP("tcp_input");
+  return;
+dropped:
+  TCP_STATS_INC(tcp.drop);
+  MIB2_STATS_INC(mib2.tcpinerrs);
+  pbuf_free(p);
+}
+
+/** Called from tcp_input to check for TF_CLOSED flag. This results in closing
+ * and deallocating a pcb at the correct place to ensure noone references it
+ * any more.
+ * @returns 1 if the pcb has been closed and deallocated, 0 otherwise
+ */
+static int
+tcp_input_delayed_close(struct tcp_pcb *pcb)
+{
+  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->state, pcb->errf, pcb->callback_arg, ERR_CLSD);
+    }
+    tcp_pcb_remove(&tcp_active_pcbs, pcb);
+    memp_free(MEMP_TCP_PCB, pcb);
+    return 1;
+  }
+  return 0;
+}
+
+/**
+ * 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
+ *
+ * @note the segment which arrived is saved in global variables, therefore only the pcb
+ *       involved is passed as a parameter to this function
+ */
+static void
+tcp_listen_input(struct tcp_pcb_listen *pcb)
+{
+  struct tcp_pcb *npcb;
+  u32_t iss;
+  err_t rc;
+
+  if (flags & TCP_RST) {
+    /* An incoming RST should be ignored. Return. */
+    return;
+  }
+
+  /* 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, ip_current_dest_addr(),
+      ip_current_src_addr(), tcphdr->dest, tcphdr->src);
+  } 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;
+    }
+#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) {
+      err_t err;
+      LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\n"));
+      TCP_STATS_INC(tcp.memerr);
+      TCP_EVENT_ACCEPT(pcb, NULL, pcb->callback_arg, ERR_MEM, err);
+      LWIP_UNUSED_ARG(err); /* err not useful here */
+      return;
+    }
+#if TCP_LISTEN_BACKLOG
+    pcb->accepts_pending++;
+    npcb->flags |= TF_BACKLOGPEND;
+#endif /* TCP_LISTEN_BACKLOG */
+    /* Set up the new PCB. */
+    ip_addr_copy(npcb->local_ip, *ip_current_dest_addr());
+    ip_addr_copy(npcb->remote_ip, *ip_current_src_addr());
+    npcb->local_port = pcb->local_port;
+    npcb->remote_port = tcphdr->src;
+    npcb->state = SYN_RCVD;
+    npcb->rcv_nxt = seqno + 1;
+    npcb->rcv_ann_right_edge = npcb->rcv_nxt;
+    iss = tcp_next_iss(npcb);
+    npcb->snd_wl2 = iss;
+    npcb->snd_nxt = iss;
+    npcb->lastack = iss;
+    npcb->snd_lbb = iss;
+    npcb->snd_wl1 = seqno - 1;/* initialise to seqno-1 to force window update */
+    npcb->callback_arg = pcb->callback_arg;
+#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
+    npcb->listener = pcb;
+#endif /* LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG */
+    /* 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);
+    npcb->snd_wnd = tcphdr->wnd;
+    npcb->snd_wnd_max = npcb->snd_wnd;
+
+#if TCP_CALCULATE_EFF_SEND_MSS
+    npcb->mss = tcp_eff_send_mss(npcb->mss, &npcb->local_ip, &npcb->remote_ip);
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+
+    MIB2_STATS_INC(mib2.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;
+    }
+    tcp_output(npcb);
+  }
+  return;
+}
+
+/**
+ * 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 void
+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;
+  }
+  /* - 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, ip_current_dest_addr(),
+        ip_current_src_addr(), tcphdr->dest, tcphdr->src);
+      return;
+    }
+  } 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;
+    tcp_output(pcb);
+  }
+  return;
+}
+
+/**
+ * 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) {
+      /* "In the SYN-SENT state (a RST received in response to an initial SYN),
+          the RST is acceptable if the ACK field acknowledges the SYN." */
+      if (ackno == pcb->snd_nxt) {
+        acceptable = 1;
+      }
+    } else {
+      /* "In all states except SYN-SENT, all reset (RST) segments are validated
+          by checking their SEQ-fields." */
+      if (seqno == pcb->rcv_nxt) {
+        acceptable = 1;
+      } else  if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
+                                  pcb->rcv_nxt + pcb->rcv_wnd)) {
+        /* If the sequence number is inside the window, we only send an ACK
+           and wait for a re-send with matching sequence number.
+           This violates RFC 793, but is required to protection against
+           CVE-2004-0230 (RST spoofing attack). */
+        tcp_ack_now(pcb);
+      }
+    }
+
+    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, lwip_ntohl(pcb->unacked->tcphdr->seqno)));
+    /* received SYN ACK with expected sequence number? */
+    if ((flags & TCP_ACK) && (flags & TCP_SYN)
+        && (ackno == pcb->lastack + 1)) {
+      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 = pcb->snd_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);
+#endif /* TCP_CALCULATE_EFF_SEND_MSS */
+
+      pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss);
+      LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SENT): cwnd %"TCPWNDSIZE_F
+                                   " ssthresh %"TCPWNDSIZE_F"\n",
+                                   pcb->cwnd, pcb->ssthresh));
+      LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0));
+      --pcb->snd_queuelen;
+      LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"TCPWNDSIZE_F"\n", (tcpwnd_size_t)pcb->snd_queuelen));
+      rseg = pcb->unacked;
+      if (rseg == NULL) {
+        /* might happen if tcp_output fails in tcp_rexmit_rto()
+           in which case the segment is on the unsent list */
+        rseg = pcb->unsent;
+        LWIP_ASSERT("no segment to free", rseg != NULL);
+        pcb->unsent = rseg->next;
+      } else {
+        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 successfully
+       * 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, ip_current_dest_addr(),
+        ip_current_src_addr(), tcphdr->dest, tcphdr->src);
+      /* Resend SYN immediately (don't wait for rto timeout) to establish
+        connection faster, but do not send more SYNs than we otherwise would
+        have, or we might get caught in a loop on loopback interfaces. */
+      if (pcb->nrtx < TCP_SYNMAXRTX) {
+        pcb->rtime = 0;
+        tcp_rexmit_rto(pcb);
+      }
+    }
+    break;
+  case SYN_RCVD:
+    if (flags & TCP_ACK) {
+      /* expected ACK number? */
+      if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {
+        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 || TCP_LISTEN_BACKLOG
+#if LWIP_CALLBACK_API
+        LWIP_ASSERT("pcb->listener->accept != NULL",
+          (pcb->listener == NULL) || (pcb->listener->accept != NULL));
+#endif
+        if (pcb->listener == NULL) {
+          /* listen pcb might be closed by now */
+          err = ERR_VAL;
+        } else
+#endif /* LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG */
+        {
+          tcp_backlog_accepted(pcb);
+          /* Call the accept function. */
+          TCP_EVENT_ACCEPT(pcb->listener, pcb, pcb->callback_arg, 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;
+        }
+        /* 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 (recv_acked != 0) {
+          recv_acked--;
+        }
+
+        pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss);
+        LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SYN_RCVD): cwnd %"TCPWNDSIZE_F
+                                     " ssthresh %"TCPWNDSIZE_F"\n",
+                                     pcb->cwnd, pcb->ssthresh));
+
+        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, ip_current_dest_addr(),
+          ip_current_src_addr(), tcphdr->dest, tcphdr->src);
+      }
+    } 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) &&
+          pcb->unsent == NULL) {
+        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->unsent == NULL) {
+      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 && pcb->unsent == NULL) {
+      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 && pcb->unsent == NULL) {
+      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, it 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 */
+  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 && (u32_t)SND_WND_SCALE(pcb, tcphdr->wnd) > pcb->snd_wnd)) {
+      pcb->snd_wnd = SND_WND_SCALE(pcb, tcphdr->wnd);
+      /* keep track of the biggest window announced by the remote host to calculate
+         the maximum segment size */
+      if (pcb->snd_wnd_max < pcb->snd_wnd) {
+        pcb->snd_wnd_max = pcb->snd_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 %"TCPWNDSIZE_F"\n", pcb->snd_wnd));
+#if TCP_WND_DEBUG
+    } else {
+      if (pcb->snd_wnd != (tcpwnd_size_t)SND_WND_SCALE(pcb, 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)) {
+      /* 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 ((tcpwnd_size_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;
+
+      /* 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 ((tcpwnd_size_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
+            pcb->cwnd += pcb->mss;
+          }
+          LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"TCPWNDSIZE_F"\n", pcb->cwnd));
+        } else {
+          tcpwnd_size_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 %"TCPWNDSIZE_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?
+                                    lwip_ntohl(pcb->unacked->tcphdr->seqno): 0,
+                                    pcb->unacked != NULL?
+                                    lwip_ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked): 0));
+
+      /* Remove segment from the unacknowledged list if the incoming
+         ACK acknowledges them. */
+      while (pcb->unacked != NULL &&
+             TCP_SEQ_LEQ(lwip_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",
+                                      lwip_ntohl(pcb->unacked->tcphdr->seqno),
+                                      lwip_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 %"TCPWNDSIZE_F" ... ", (tcpwnd_size_t)pcb->snd_queuelen));
+        LWIP_ASSERT("pcb->snd_queuelen >= pbuf_clen(next->p)", (pcb->snd_queuelen >= pbuf_clen(next->p)));
+
+        pcb->snd_queuelen -= pbuf_clen(next->p);
+        recv_acked += next->len;
+        tcp_seg_free(next);
+
+        LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"TCPWNDSIZE_F" (after freeing unacked)\n", (tcpwnd_size_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 (ip_current_is_v6()) {
+        /* Inform neighbor reachability of forward progress. */
+        nd6_reachability_hint(ip6_current_src_addr());
+      }
+#endif /* LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS*/
+    } else {
+      /* Out of sequence ACK, didn't really ack anything */
+      tcp_send_empty_ack(pcb);
+    }
+
+    /* 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, lwip_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",
+                                    lwip_ntohl(pcb->unsent->tcphdr->seqno), lwip_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 %"TCPWNDSIZE_F" ... ", (tcpwnd_size_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 */
+      pcb->snd_queuelen -= pbuf_clen(next->p);
+      recv_acked += next->len;
+      tcp_seg_free(next);
+      LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"TCPWNDSIZE_F" (after freeing unsent)\n", (tcpwnd_size_t)pcb->snd_queuelen));
+      if (pcb->snd_queuelen != 0) {
+        LWIP_ASSERT("tcp_receive: valid queue length",
+          pcb->unacked != NULL || pcb->unsent != NULL);
+      }
+    }
+    pcb->snd_buf += recv_acked;
+    /* 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, (u16_t)(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, (u16_t)(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, chapter 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.*/
+
+      struct pbuf *p = inseg.p;
+      off = pcb->rcv_nxt - seqno;
+      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) & ~(unsigned int)TCP_FIN);
+          }
+          /* Adjust length of segment to fit in the window. */
+          TCPWND_CHECK16(pcb->rcv_wnd);
+          inseg.len = (u16_t)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) != 0 &&
+                  (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. */
+            /* With window scaling, this can overflow recv_data->tot_len, but
+               that's not a problem since we explicitly fix that before passing
+               recv_data 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 (ip_current_is_v6()) {
+          /* 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 lengths 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 (TCP_SEQ_GT((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 = (u16_t)(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 within 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_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd - 1)) {
+      tcp_ack_now(pcb);
+    }
+  }
+}
+
+static u8_t
+tcp_getoptbyte(void)
+{
+  if ((tcphdr_opt2 == NULL) || (tcp_optidx < tcphdr_opt1len)) {
+    u8_t* opts = (u8_t *)tcphdr + TCP_HLEN;
+    return opts[tcp_optidx++];
+  } else {
+    u8_t idx = (u8_t)(tcp_optidx++ - tcphdr_opt1len);
+    return tcphdr_opt2[idx];
+  }
+}
+
+/**
+ * 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)
+{
+  u8_t data;
+  u16_t mss;
+#if LWIP_TCP_TIMESTAMPS
+  u32_t tsval;
+#endif
+
+  /* Parse the TCP MSS option, if present. */
+  if (tcphdr_optlen != 0) {
+    for (tcp_optidx = 0; tcp_optidx < tcphdr_optlen; ) {
+      u8_t opt = tcp_getoptbyte();
+      switch (opt) {
+      case LWIP_TCP_OPT_EOL:
+        /* End of options. */
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: EOL\n"));
+        return;
+      case LWIP_TCP_OPT_NOP:
+        /* NOP option. */
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: NOP\n"));
+        break;
+      case LWIP_TCP_OPT_MSS:
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: MSS\n"));
+        if (tcp_getoptbyte() != LWIP_TCP_OPT_LEN_MSS || (tcp_optidx - 2 + LWIP_TCP_OPT_LEN_MSS) > tcphdr_optlen) {
+          /* Bad length */
+          LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
+          return;
+        }
+        /* An MSS option with the right option length. */
+        mss = (tcp_getoptbyte() << 8);
+        mss |= tcp_getoptbyte();
+        /* Limit the mss to the configured TCP_MSS and prevent division by zero */
+        pcb->mss = ((mss > TCP_MSS) || (mss == 0)) ? TCP_MSS : mss;
+        break;
+#if LWIP_WND_SCALE
+      case LWIP_TCP_OPT_WS:
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: WND_SCALE\n"));
+        if (tcp_getoptbyte() != LWIP_TCP_OPT_LEN_WS || (tcp_optidx - 2 + LWIP_TCP_OPT_LEN_WS) > tcphdr_optlen) {
+          /* Bad length */
+          LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
+          return;
+        }
+        /* An WND_SCALE option with the right option length. */
+        data = tcp_getoptbyte();
+        /* If syn was received with wnd scale option,
+           activate wnd scale opt, but only if this is not a retransmission */
+        if ((flags & TCP_SYN) && !(pcb->flags & TF_WND_SCALE)) {
+          pcb->snd_scale = data;
+          if (pcb->snd_scale > 14U) {
+            pcb->snd_scale = 14U;
+          }
+          pcb->rcv_scale = TCP_RCV_SCALE;
+          pcb->flags |= TF_WND_SCALE;
+          /* window scaling is enabled, we can use the full receive window */
+          LWIP_ASSERT("window not at default value", pcb->rcv_wnd == TCPWND_MIN16(TCP_WND));
+          LWIP_ASSERT("window not at default value", pcb->rcv_ann_wnd == TCPWND_MIN16(TCP_WND));
+          pcb->rcv_wnd = pcb->rcv_ann_wnd = TCP_WND;
+        }
+        break;
+#endif
+#if LWIP_TCP_TIMESTAMPS
+      case LWIP_TCP_OPT_TS:
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: TS\n"));
+        if (tcp_getoptbyte() != LWIP_TCP_OPT_LEN_TS || (tcp_optidx - 2 + LWIP_TCP_OPT_LEN_TS) > tcphdr_optlen) {
+          /* Bad length */
+          LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n"));
+          return;
+        }
+        /* TCP timestamp option with valid length */
+        tsval = tcp_getoptbyte();
+        tsval |= (tcp_getoptbyte() << 8);
+        tsval |= (tcp_getoptbyte() << 16);
+        tsval |= (tcp_getoptbyte() << 24);
+        if (flags & TCP_SYN) {
+          pcb->ts_recent = lwip_ntohl(tsval);
+          /* Enable sending timestamps in every segment now that we know
+             the remote host supports it. */
+          pcb->flags |= TF_TIMESTAMP;
+        } else if (TCP_SEQ_BETWEEN(pcb->ts_lastacksent, seqno, seqno+tcplen)) {
+          pcb->ts_recent = lwip_ntohl(tsval);
+        }
+        /* Advance to next option (6 bytes already read) */
+        tcp_optidx += LWIP_TCP_OPT_LEN_TS - 6;
+        break;
+#endif
+      default:
+        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: other\n"));
+        data = tcp_getoptbyte();
+        if (data < 2) {
+          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. */
+        tcp_optidx += data - 2;
+      }
+    }
+  }
+}
+
+void
+tcp_trigger_input_pcb_close(void)
+{
+  recv_flags |= TF_CLOSED;
+}
+
+#endif /* LWIP_TCP */
diff --git a/src/core/tcp_out.c b/src/core/tcp_out.c
new file mode 100644
index 0000000..2435408
--- /dev/null
+++ b/src/core/tcp_out.c
@@ -0,0 +1,1671 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_TCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/priv/tcp_priv.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/ip6.h"
+#include "lwip/ip6_addr.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
+/* Allow to override the failure of sanity check from warning to e.g. hard failure */
+#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
+#ifndef TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL
+#define TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL(msg) LWIP_DEBUGF(TCP_DEBUG | LWIP_DBG_LEVEL_WARNING, msg)
+#endif
+#endif
+
+#if TCP_OVERSIZE
+/** The size of segment pbufs created when TCP_OVERSIZE is enabled */
+#ifndef TCP_OVERSIZE_CALC_LENGTH
+#define TCP_OVERSIZE_CALC_LENGTH(length) ((length) + TCP_OVERSIZE)
+#endif
+#endif
+
+/* Forward declarations.*/
+static err_t tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb, struct netif *netif);
+
+/** 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 = lwip_htons(pcb->local_port);
+    tcphdr->dest = lwip_htons(pcb->remote_port);
+    tcphdr->seqno = seqno_be;
+    tcphdr->ackno = lwip_htonl(pcb->rcv_nxt);
+    TCPH_HDRLEN_FLAGS_SET(tcphdr, (5 + optlen / 4), TCP_ACK);
+    tcphdr->wnd = lwip_htons(TCPWND_MIN16(RCV_WND_SCALE(pcb, 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 | LWIP_DBG_LEVEL_SERIOUS, ("tcp_create_segment: no memory.\n"));
+    pbuf_free(p);
+    return NULL;
+  }
+  seg->flags = optflags;
+  seg->next = NULL;
+  seg->p = p;
+  LWIP_ASSERT("p->tot_len >= optlen", p->tot_len >= optlen);
+  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 | LWIP_DBG_LEVEL_SERIOUS, ("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 = lwip_htons(pcb->local_port);
+  seg->tcphdr->dest = lwip_htons(pcb->remote_port);
+  seg->tcphdr->seqno = lwip_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 will 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.
+ */
+#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);
+  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(TCP_OVERSIZE_CALC_LENGTH(length)));
+    }
+  }
+#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 | LWIP_DBG_LEVEL_SEVERE, ("tcp_write: too much data (len=%"U16_F" > snd_buf=%"TCPWNDSIZE_F")\n",
+      len, pcb->snd_buf));
+    pcb->flags |= TF_NAGLEMEMERR;
+    return ERR_MEM;
+  }
+
+  LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: queuelen: %"TCPWNDSIZE_F"\n", (tcpwnd_size_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 | LWIP_DBG_LEVEL_SEVERE, ("tcp_write: too long queue %"U16_F" (max %"U16_F")\n",
+      pcb->snd_queuelen, (u16_t)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;
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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 not 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;
+#if TCP_OVERSIZE_DBGCHECK
+  u16_t oversize_add = 0;
+#endif /* TCP_OVERSIZE_DBGCHECK*/
+#endif /* TCP_OVERSIZE */
+  u16_t extendlen = 0;
+#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, TCPWND_MIN16(pcb->snd_wnd_max/2));
+  mss_local = mss_local ? mss_local : pcb->mss;
+
+#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)) {
+    /* Make sure the timestamp option is only included in data segments if we
+       agreed about it with the remote host. */
+    optflags = TF_SEG_OPTS_TS;
+    optlen = LWIP_TCP_OPT_LENGTH(TF_SEG_OPTS_TS);
+    /* ensure that segments can hold at least one data byte... */
+    mss_local = LWIP_MAX(mss_local, LWIP_TCP_OPT_LEN_TS + 1);
+  }
+#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);
+    LWIP_ASSERT("mss_local is too small", mss_local >= last_unsent->len + unsent_optlen);
+    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->oversize_left */
+    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 <= space);
+      seg = last_unsent;
+      oversize_used = LWIP_MIN(space, LWIP_MIN(oversize, len));
+      pos += oversize_used;
+      oversize -= oversize_used;
+      space -= oversize_used;
+    }
+    /* now we are either finished or oversize is zero */
+    LWIP_ASSERT("inconsistent oversize vs. len", (oversize == 0) || (pos == len));
+#endif /* TCP_OVERSIZE */
+
+    /*
+     * Phase 2: Chain a new pbuf to the end of pcb->unsent.
+     *
+     * As an exception when NOT copying the data, if the given data buffer
+     * directly follows the last unsent data buffer in memory, extend the last
+     * ROM pbuf reference to the buffer, thus saving a ROM pbuf allocation.
+     *
+     * 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 = LWIP_MIN(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 | LWIP_DBG_LEVEL_SERIOUS,
+                      ("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n",
+                       seglen));
+          goto memerr;
+        }
+#if TCP_OVERSIZE_DBGCHECK
+        oversize_add = oversize;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+        TCP_DATA_COPY2(concat_p->payload, (const u8_t*)arg + pos, seglen, &concat_chksum, &concat_chksum_swapped);
+#if TCP_CHECKSUM_ON_COPY
+        concat_chksummed += seglen;
+#endif /* TCP_CHECKSUM_ON_COPY */
+        queuelen += pbuf_clen(concat_p);
+      } else {
+        /* Data is not copied */
+        /* If the last unsent pbuf is of type PBUF_ROM, try to extend it. */
+        struct pbuf *p;
+        for (p = last_unsent->p; p->next != NULL; p = p->next);
+        if (p->type == PBUF_ROM && (const u8_t *)p->payload + p->len == (const u8_t *)arg) {
+          LWIP_ASSERT("tcp_write: ROM pbufs cannot be oversized", pos == 0);
+          extendlen = seglen;
+        } else {
+          if ((concat_p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) {
+            LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
+                        ("tcp_write: could not allocate memory for zero-copy pbuf\n"));
+            goto memerr;
+          }
+          /* reference the non-volatile payload data */
+          ((struct pbuf_rom*)concat_p)->payload = (const u8_t*)arg + pos;
+          queuelen += pbuf_clen(concat_p);
+        }
+#if TCP_CHECKSUM_ON_COPY
+        /* calculate the checksum of nocopy-data */
+        tcp_seg_add_chksum(~inet_chksum((const u8_t*)arg + pos, seglen), seglen,
+          &concat_chksum, &concat_chksum_swapped);
+        concat_chksummed += seglen;
+#endif /* TCP_CHECKSUM_ON_COPY */
+      }
+
+      pos += seglen;
+    }
+  } 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 = LWIP_MIN(left, max_len);
+#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 | LWIP_DBG_LEVEL_SERIOUS, ("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, (const 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 | LWIP_DBG_LEVEL_SERIOUS, ("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((const u8_t*)arg + pos, seglen);
+      if (seglen & 1) {
+        chksum_swapped = 1;
+        chksum = SWAP_BYTES_IN_WORD(chksum);
+      }
+#endif /* TCP_CHECKSUM_ON_COPY */
+      /* reference the non-volatile payload data */
+      ((struct pbuf_rom*)p2)->payload = (const 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 | LWIP_DBG_LEVEL_SERIOUS, ("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 | LWIP_DBG_LEVEL_SERIOUS, ("tcp_write: queue too long %"U16_F" (%d)\n",
+        queuelen, (int)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",
+      lwip_ntohl(seg->tcphdr->seqno),
+      lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg)));
+
+    pos += seglen;
+  }
+
+  /*
+   * All three segmentation phases were successful. We can commit the
+   * transaction.
+   */
+#if TCP_OVERSIZE_DBGCHECK
+  if ((last_unsent != NULL) && (oversize_add != 0)) {
+    last_unsent->oversize_left += oversize_add;
+  }
+#endif /* TCP_OVERSIZE_DBGCHECK */
+
+  /*
+   * 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, unless we
+   * determined that the last ROM pbuf can be extended to include the new data.
+   */
+  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;
+  } else if (extendlen > 0) {
+    struct pbuf *p;
+    LWIP_ASSERT("tcp_write: extension of reference requires reference",
+      last_unsent != NULL && last_unsent->p != NULL);
+    for (p = last_unsent->p; p->next != NULL; p = p->next) {
+      p->tot_len += extendlen;
+    }
+    p->tot_len += extendlen;
+    p->len += extendlen;
+    last_unsent->len += extendlen;
+  }
+
+#if TCP_CHECKSUM_ON_COPY
+  if (concat_chksummed) {
+    LWIP_ASSERT("tcp_write: concat checksum needs concatenated data",
+        concat_p != NULL || extendlen > 0);
+    /*if concat checksumm swapped - swap it back */
+    if (concat_chksum_swapped) {
+      concat_chksum = SWAP_BYTES_IN_WORD(concat_chksum);
+    }
+    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.
+ */
+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 (FIN flag should always come through!) */
+  if (((pcb->snd_queuelen >= TCP_SND_QUEUELEN) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) &&
+      ((flags & TCP_FIN) == 0)) {
+    LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("tcp_enqueue_flags: too long queue %"U16_F" (max %"U16_F")\n",
+                                       pcb->snd_queuelen, (u16_t)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_WND_SCALE
+    if ((pcb->state != SYN_RCVD) || (pcb->flags & TF_WND_SCALE)) {
+      /* In a <SYN,ACK> (sent in state SYN_RCVD), the window scale option may only
+         be sent if we received a window scale option from the remote host. */
+      optflags |= TF_SEG_OPTS_WND_SCALE;
+    }
+#endif /* LWIP_WND_SCALE */
+  }
+#if LWIP_TCP_TIMESTAMPS
+  if ((pcb->flags & TF_TIMESTAMP)) {
+    /* Make sure the timestamp option is only included in data segments if we
+       agreed about it with the remote host. */
+    optflags |= TF_SEG_OPTS_TS;
+  }
+#endif /* LWIP_TCP_TIMESTAMPS */
+  optlen = LWIP_TCP_OPT_LENGTH(optflags);
+
+  /* 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 % LWIP_MIN(MEM_ALIGNMENT, 4)) == 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",
+               lwip_ntohl(seg->tcphdr->seqno),
+               lwip_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 */
+  }
+  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] = lwip_htonl(sys_now());
+  opts[2] = lwip_htonl(pcb->ts_recent);
+}
+#endif
+
+#if LWIP_WND_SCALE
+/** Build a window scale option (3 bytes long) at the specified options pointer)
+ *
+ * @param opts option pointer where to store the window scale option
+ */
+static void
+tcp_build_wnd_scale_option(u32_t *opts)
+{
+  /* Pad with one NOP option to make everything nicely aligned */
+  opts[0] = PP_HTONL(0x01030300 | TCP_RCV_SCALE);
+}
+#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)
+{
+  err_t err;
+  struct pbuf *p;
+  u8_t optlen = 0;
+  struct netif *netif;
+#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, lwip_htonl(pcb->snd_nxt));
+  if (p == NULL) {
+    /* let tcp_fasttmr retry sending this ACK */
+    pcb->flags |= (TF_ACK_DELAY | TF_ACK_NOW);
+    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));
+
+  /* NB. MSS and window scale options are only sent on SYNs, so ignore them 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
+
+  netif = ip_route(&pcb->local_ip, &pcb->remote_ip);
+  if (netif == NULL) {
+    err = ERR_RTE;
+  } else {
+#if CHECKSUM_GEN_TCP
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_TCP) {
+      tcphdr->chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len,
+        &pcb->local_ip, &pcb->remote_ip);
+    }
+#endif
+    NETIF_SET_HWADDRHINT(netif, &(pcb->addr_hint));
+    err = ip_output_if(p, &pcb->local_ip, &pcb->remote_ip,
+      pcb->ttl, pcb->tos, IP_PROTO_TCP, netif);
+    NETIF_SET_HWADDRHINT(netif, NULL);
+  }
+  pbuf_free(p);
+
+  if (err != ERR_OK) {
+    /* let tcp_fasttmr retry sending this ACK */
+    pcb->flags |= (TF_ACK_DELAY | TF_ACK_NOW);
+  } else {
+    /* remove ACK flags from the PCB, as we sent an empty ACK now */
+    pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
+  }
+
+  return err;
+}
+
+/**
+ * @ingroup tcp_raw
+ * 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;
+  err_t err;
+  struct netif *netif;
+#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 ||
+      lwip_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);
+  }
+
+  netif = ip_route(&pcb->local_ip, &pcb->remote_ip);
+  if (netif == NULL) {
+    return ERR_RTE;
+  }
+
+  /* If we don't have a local IP address, we get one from netif */
+  if (ip_addr_isany(&pcb->local_ip)) {
+    const ip_addr_t *local_ip = ip_netif_get_local_ip(netif, &pcb->remote_ip);
+    if (local_ip == NULL) {
+      return ERR_RTE;
+    }
+    ip_addr_copy(pcb->local_ip, *local_ip);
+  }
+
+#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 %"TCPWNDSIZE_F
+                                 ", cwnd %"TCPWNDSIZE_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 %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F", wnd %"U32_F
+                 ", effwnd %"U32_F", seq %"U32_F", ack %"U32_F"\n",
+                 pcb->snd_wnd, pcb->cwnd, wnd,
+                 lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len,
+                 lwip_ntohl(seg->tcphdr->seqno), pcb->lastack));
+  }
+#endif /* TCP_CWND_DEBUG */
+  /* Check if we need to start the persistent timer when the next unsent segment
+   * does not fit within the remaining send window and RTO timer is not running (we
+   * have no in-flight data). A traditional approach would fill the remaining window
+   * with part of the unsent segment (which will engage zero-window probing upon
+   * reception of the zero window update from the receiver). This ensures the
+   * subsequent window update is reliably received. With the goal of being lightweight,
+   * we avoid splitting the unsent segment and treat the window as already zero.
+   */
+  if (seg != NULL &&
+      lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd &&
+      wnd > 0 && wnd == pcb->snd_wnd && pcb->unacked == NULL) {
+    /* Start the persist timer */
+    if (pcb->persist_backoff == 0) {
+      pcb->persist_cnt = 0;
+      pcb->persist_backoff = 1;
+    }
+    goto output_done;
+  }
+  /* data available and window allows it to be sent? */
+  while (seg != NULL &&
+         lwip_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 %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F", wnd %"U32_F", effwnd %"U32_F", seq %"U32_F", ack %"U32_F", i %"S16_F"\n",
+                            pcb->snd_wnd, pcb->cwnd, wnd,
+                            lwip_ntohl(seg->tcphdr->seqno) + seg->len -
+                            pcb->lastack,
+                            lwip_ntohl(seg->tcphdr->seqno), pcb->lastack, i));
+    ++i;
+#endif /* TCP_CWND_DEBUG */
+
+    if (pcb->state != SYN_SENT) {
+      TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);
+    }
+
+#if TCP_OVERSIZE_DBGCHECK
+    seg->oversize_left = 0;
+#endif /* TCP_OVERSIZE_DBGCHECK */
+    err = tcp_output_segment(seg, pcb, netif);
+    if (err != ERR_OK) {
+      /* segment could not be sent, for whatever reason */
+      pcb->flags |= TF_NAGLEMEMERR;
+      return err;
+    }
+    pcb->unsent = seg->next;
+    if (pcb->state != SYN_SENT) {
+      pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
+    }
+    snd_nxt = lwip_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(lwip_ntohl(seg->tcphdr->seqno), lwip_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(lwip_ntohl((*cur_seg)->tcphdr->seqno), lwip_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;
+  }
+output_done:
+#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
+ * @param netif the netif used to send the segment
+ */
+static err_t
+tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb, struct netif *netif)
+{
+  err_t err;
+  u16_t len;
+  u32_t *opts;
+
+  if (seg->p->ref != 1) {
+    /* This can happen if the pbuf of this segment is still referenced by the
+       netif driver due to deferred transmission. Since this function modifies
+       p->len, we must not continue in this case. */
+    return ERR_OK;
+  }
+
+  /* The TCP header has already been constructed, but the ackno and
+   wnd fields remain. */
+  seg->tcphdr->ackno = lwip_htonl(pcb->rcv_nxt);
+
+  /* advertise our receive window size in this TCP segment */
+#if LWIP_WND_SCALE
+  if (seg->flags & TF_SEG_OPTS_WND_SCALE) {
+    /* The Window field in a SYN segment itself (the only type where we send
+       the window scale option) is never scaled. */
+    seg->tcphdr->wnd = lwip_htons(TCPWND_MIN16(pcb->rcv_ann_wnd));
+  } else
+#endif /* LWIP_WND_SCALE */
+  {
+    seg->tcphdr->wnd = lwip_htons(TCPWND_MIN16(RCV_WND_SCALE(pcb, 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 */
+  /* cast through void* to get rid of alignment warnings */
+  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);
+#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
+#if LWIP_WND_SCALE
+  if (seg->flags & TF_SEG_OPTS_WND_SCALE) {
+    tcp_build_wnd_scale_option(opts);
+    opts += 1;
+  }
+#endif
+
+  /* Set retransmission timer running if it is not currently enabled
+     This must be set before checking the route. */
+  if (pcb->rtime < 0) {
+    pcb->rtime = 0;
+  }
+
+  if (pcb->rttest == 0) {
+    pcb->rttest = tcp_ticks;
+    pcb->rtseq = lwip_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",
+          lwip_htonl(seg->tcphdr->seqno), lwip_htonl(seg->tcphdr->seqno) +
+          seg->len));
+
+  len = (u16_t)((u8_t *)seg->tcphdr - (u8_t *)seg->p->payload);
+  if (len == 0) {
+    /** Exclude retransmitted segments from this count. */
+    MIB2_STATS_INC(mib2.tcpoutsegs);
+  }
+
+  seg->p->len -= len;
+  seg->p->tot_len -= len;
+
+  seg->p->payload = seg->tcphdr;
+
+  seg->tcphdr->chksum = 0;
+#if CHECKSUM_GEN_TCP
+  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_TCP) {
+#if TCP_CHECKSUM_ON_COPY
+    u32_t acc;
+#if TCP_CHECKSUM_ON_COPY_SANITY_CHECK
+    u16_t chksum_slow = ip_chksum_pseudo(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 = ip_chksum_pseudo_partial(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) {
+      TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL(
+                  ("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 */
+    seg->tcphdr->chksum = ip_chksum_pseudo(seg->p, IP_PROTO_TCP,
+      seg->p->tot_len, &pcb->local_ip, &pcb->remote_ip);
+#endif /* TCP_CHECKSUM_ON_COPY */
+  }
+#endif /* CHECKSUM_GEN_TCP */
+  TCP_STATS_INC(tcp.xmit);
+
+  NETIF_SET_HWADDRHINT(netif, &(pcb->addr_hint));
+  err = ip_output_if(seg->p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl,
+    pcb->tos, IP_PROTO_TCP, netif);
+  NETIF_SET_HWADDRHINT(netif, NULL);
+  return err;
+}
+
+/**
+ * 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(u32_t seqno, u32_t ackno,
+  const ip_addr_t *local_ip, const ip_addr_t *remote_ip,
+  u16_t local_port, u16_t remote_port)
+{
+  struct pbuf *p;
+  struct tcp_hdr *tcphdr;
+  struct netif *netif;
+  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 = lwip_htons(local_port);
+  tcphdr->dest = lwip_htons(remote_port);
+  tcphdr->seqno = lwip_htonl(seqno);
+  tcphdr->ackno = lwip_htonl(ackno);
+  TCPH_HDRLEN_FLAGS_SET(tcphdr, TCP_HLEN/4, TCP_RST | TCP_ACK);
+#if LWIP_WND_SCALE
+  tcphdr->wnd = PP_HTONS(((TCP_WND >> TCP_RCV_SCALE) & 0xFFFF));
+#else
+  tcphdr->wnd = PP_HTONS(TCP_WND);
+#endif
+  tcphdr->chksum = 0;
+  tcphdr->urgp = 0;
+
+  TCP_STATS_INC(tcp.xmit);
+  MIB2_STATS_INC(mib2.tcpoutrsts);
+
+  netif = ip_route(local_ip, remote_ip);
+  if (netif != NULL) {
+#if CHECKSUM_GEN_TCP
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_TCP) {
+      tcphdr->chksum = ip_chksum_pseudo(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 */
+    ip_output_if(p, local_ip, remote_ip, TCP_TTL, 0, IP_PROTO_TCP, netif);
+  }
+  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;
+#if TCP_OVERSIZE_DBGCHECK
+  /* if last unsent changed, we need to update unsent_oversize */
+  if (pcb->unsent == NULL) {
+    pcb->unsent_oversize = seg->oversize_left;
+  }
+#endif /* TCP_OVERSIZE_DBGCHECK */
+  /* unsent queue is the concatenated queue (of unacked, unsent) */
+  pcb->unsent = pcb->unacked;
+  /* unacked queue is now empty */
+  pcb->unacked = NULL;
+
+  /* increment number of retransmissions */
+  if (pcb->nrtx < 0xFF) {
+    ++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 retransmit.
+ *
+ * @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(lwip_ntohl((*cur_seg)->tcphdr->seqno), lwip_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 */
+
+  if (pcb->nrtx < 0xFF) {
+    ++pcb->nrtx;
+  }
+
+  /* Don't take any rtt measurements after retransmitting. */
+  pcb->rttest = 0;
+
+  /* Do the actual retransmission. */
+  MIB2_STATS_INC(mib2.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,
+                 lwip_ntohl(pcb->unacked->tcphdr->seqno)));
+    tcp_rexmit(pcb);
+
+    /* Set ssthresh to half of the minimum of the current
+     * cwnd and the advertised window */
+    pcb->ssthresh = LWIP_MIN(pcb->cwnd, pcb->snd_wnd) / 2;
+
+    /* The minimum value for ssthresh should be 2 MSS */
+    if (pcb->ssthresh < (2U * pcb->mss)) {
+      LWIP_DEBUGF(TCP_FR_DEBUG,
+                  ("tcp_receive: The minimum value for ssthresh %"TCPWNDSIZE_F
+                   " should be min 2 mss %"U16_F"...\n",
+                   pcb->ssthresh, (u16_t)(2*pcb->mss)));
+      pcb->ssthresh = 2*pcb->mss;
+    }
+
+    pcb->cwnd = pcb->ssthresh + 3 * pcb->mss;
+    pcb->flags |= TF_INFR;
+
+    /* Reset the retransmission timer to prevent immediate rto retransmissions */
+    pcb->rtime = 0;
+  }
+}
+
+
+/**
+ * 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
+ */
+err_t
+tcp_keepalive(struct tcp_pcb *pcb)
+{
+  err_t err;
+  struct pbuf *p;
+  struct netif *netif;
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: sending KEEPALIVE probe to "));
+  ip_addr_debug_print(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, (u16_t)pcb->keep_cnt_sent));
+
+  p = tcp_output_alloc_header(pcb, 0, 0, lwip_htonl(pcb->snd_nxt - 1));
+  if (p == NULL) {
+    LWIP_DEBUGF(TCP_DEBUG,
+                ("tcp_keepalive: could not allocate memory for pbuf\n"));
+    return ERR_MEM;
+  }
+  netif = ip_route(&pcb->local_ip, &pcb->remote_ip);
+  if (netif == NULL) {
+    err = ERR_RTE;
+  } else {
+#if CHECKSUM_GEN_TCP
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_TCP) {
+      struct tcp_hdr *tcphdr = (struct tcp_hdr *)p->payload;
+      tcphdr->chksum = ip_chksum_pseudo(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 */
+    NETIF_SET_HWADDRHINT(netif, &(pcb->addr_hint));
+    err = ip_output_if(p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, 0, IP_PROTO_TCP, netif);
+    NETIF_SET_HWADDRHINT(netif, NULL);
+  }
+  pbuf_free(p);
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: seqno %"U32_F" ackno %"U32_F" err %d.\n",
+                          pcb->snd_nxt - 1, pcb->rcv_nxt, (int)err));
+  return err;
+}
+
+
+/**
+ * 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
+ */
+err_t
+tcp_zero_window_probe(struct tcp_pcb *pcb)
+{
+  err_t err;
+  struct pbuf *p;
+  struct tcp_hdr *tcphdr;
+  struct tcp_seg *seg;
+  u16_t len;
+  u8_t is_fin;
+  u32_t snd_nxt;
+  struct netif *netif;
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_zero_window_probe: sending ZERO WINDOW probe to "));
+  ip_addr_debug_print(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, (u16_t)pcb->keep_cnt_sent));
+
+  seg = pcb->unacked;
+
+  if (seg == NULL) {
+    seg = pcb->unsent;
+  }
+  if (seg == NULL) {
+    /* nothing to send, zero window probe not needed */
+    return ERR_OK;
+  }
+
+  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 ERR_MEM;
+  }
+  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);
+  }
+
+  /* The byte may be acknowledged without the window being opened. */
+  snd_nxt = lwip_ntohl(seg->tcphdr->seqno) + 1;
+  if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) {
+    pcb->snd_nxt = snd_nxt;
+  }
+
+  netif = ip_route(&pcb->local_ip, &pcb->remote_ip);
+  if (netif == NULL) {
+    err = ERR_RTE;
+  } else {
+#if CHECKSUM_GEN_TCP
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_TCP) {
+      tcphdr->chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len,
+        &pcb->local_ip, &pcb->remote_ip);
+    }
+#endif
+    TCP_STATS_INC(tcp.xmit);
+
+    /* Send output to IP */
+    NETIF_SET_HWADDRHINT(netif, &(pcb->addr_hint));
+    err = ip_output_if(p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl,
+      0, IP_PROTO_TCP, netif);
+    NETIF_SET_HWADDRHINT(netif, NULL);
+  }
+
+  pbuf_free(p);
+
+  LWIP_DEBUGF(TCP_DEBUG, ("tcp_zero_window_probe: seqno %"U32_F
+                          " ackno %"U32_F" err %d.\n",
+                          pcb->snd_nxt - 1, pcb->rcv_nxt, (int)err));
+  return err;
+}
+#endif /* LWIP_TCP */
diff --git a/src/core/timeouts.c b/src/core/timeouts.c
new file mode 100644
index 0000000..8bf209a
--- /dev/null
+++ b/src/core/timeouts.c
@@ -0,0 +1,433 @@
+/**
+ * @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@sics.se>
+ *         Simon Goldschmidt
+ *
+ */
+
+#include "lwip/opt.h"
+
+#include "lwip/timeouts.h"
+#include "lwip/priv/tcp_priv.h"
+
+#include "lwip/def.h"
+#include "lwip/memp.h"
+#include "lwip/priv/tcpip_priv.h"
+
+#include "lwip/ip4_frag.h"
+#include "lwip/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"
+
+#if LWIP_DEBUG_TIMERNAMES
+#define HANDLER(x) x, #x
+#else /* LWIP_DEBUG_TIMERNAMES */
+#define HANDLER(x) x
+#endif /* LWIP_DEBUG_TIMERNAMES */
+
+/** This array contains all stack-internal cyclic timers. To get the number of
+ * timers, use LWIP_ARRAYSIZE() */
+const struct lwip_cyclic_timer lwip_cyclic_timers[] = {
+#if LWIP_TCP
+  /* The TCP timer is a special case: it does not have to run always and
+     is triggered to start from TCP using tcp_timer_needed() */
+  {TCP_TMR_INTERVAL, HANDLER(tcp_tmr)},
+#endif /* LWIP_TCP */
+#if LWIP_IPV4
+#if IP_REASSEMBLY
+  {IP_TMR_INTERVAL, HANDLER(ip_reass_tmr)},
+#endif /* IP_REASSEMBLY */
+#if LWIP_ARP
+  {ARP_TMR_INTERVAL, HANDLER(etharp_tmr)},
+#endif /* LWIP_ARP */
+#if LWIP_DHCP
+  {DHCP_COARSE_TIMER_MSECS, HANDLER(dhcp_coarse_tmr)},
+  {DHCP_FINE_TIMER_MSECS, HANDLER(dhcp_fine_tmr)},
+#endif /* LWIP_DHCP */
+#if LWIP_AUTOIP
+  {AUTOIP_TMR_INTERVAL, HANDLER(autoip_tmr)},
+#endif /* LWIP_AUTOIP */
+#if LWIP_IGMP
+  {IGMP_TMR_INTERVAL, HANDLER(igmp_tmr)},
+#endif /* LWIP_IGMP */
+#endif /* LWIP_IPV4 */
+#if LWIP_DNS
+  {DNS_TMR_INTERVAL, HANDLER(dns_tmr)},
+#endif /* LWIP_DNS */
+#if LWIP_IPV6
+  {ND6_TMR_INTERVAL, HANDLER(nd6_tmr)},
+#if LWIP_IPV6_REASS
+  {IP6_REASS_TMR_INTERVAL, HANDLER(ip6_reass_tmr)},
+#endif /* LWIP_IPV6_REASS */
+#if LWIP_IPV6_MLD
+  {MLD6_TMR_INTERVAL, HANDLER(mld6_tmr)},
+#endif /* LWIP_IPV6_MLD */
+#endif /* LWIP_IPV6 */
+};
+
+#if LWIP_TIMERS && !LWIP_TIMERS_CUSTOM
+
+/** The one and only timeout list */
+static struct sys_timeo *next_timeout;
+static u32_t timeouts_last_time;
+
+#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 */
+
+/**
+ * Timer callback function that calls mld6_tmr() and reschedules itself.
+ *
+ * @param arg unused argument
+ */
+static void
+cyclic_timer(void *arg)
+{
+  const struct lwip_cyclic_timer* cyclic = (const struct lwip_cyclic_timer*)arg;
+#if LWIP_DEBUG_TIMERNAMES
+  LWIP_DEBUGF(TIMERS_DEBUG, ("tcpip: %s()\n", cyclic->handler_name));
+#endif
+  cyclic->handler();
+  sys_timeout(cyclic->interval_ms, cyclic_timer, arg);
+}
+
+/** Initialize this module */
+void sys_timeouts_init(void)
+{
+  size_t i;
+  /* tcp_tmr() at index 0 is started on demand */
+  for (i = (LWIP_TCP ? 1 : 0); i < LWIP_ARRAYSIZE(lwip_cyclic_timers); i++) {
+    /* we have to cast via size_t to get rid of const warning
+      (this is OK as cyclic_timer() casts back to const* */
+    sys_timeout(lwip_cyclic_timers[i].interval_ms, cyclic_timer, LWIP_CONST_CAST(void*, &lwip_cyclic_timers[i]));
+  }
+
+  /* Initialise timestamp for sys_check_timeouts */
+  timeouts_last_time = sys_now();
+}
+
+/**
+ * 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;
+  u32_t now, diff;
+
+  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;
+  }
+
+  now = sys_now();
+  if (next_timeout == NULL) {
+    diff = 0;
+    timeouts_last_time = now;
+  } else {
+    diff = now - timeouts_last_time;
+  }
+
+  timeout->next = NULL;
+  timeout->h = handler;
+  timeout->arg = arg;
+  timeout->time = msecs + diff;
+#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;
+        } else if (timeout->time > msecs) {
+          /* If this is the case, 'timeouts_last_time' and 'now' differs too much.
+             This can be due to sys_check_timeouts() not being called at the right
+             times, but also when stopping in a breakpoint. Anyway, let's assume
+             this is not wanted, so add the first timer's time instead of 'diff' */
+          timeout->time = msecs + next_timeout->time;
+        }
+        timeout->next = t->next;
+        t->next = timeout;
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * Go through timeout list (for this task only) and remove the first matching
+ * entry (subsequent entries remain untouched), even though the timeout has not
+ * triggered yet.
+ *
+ * @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;
+}
+
+/**
+ * @ingroup lwip_nosys
+ * 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.
+ */
+#if !NO_SYS && !defined __DOXYGEN__
+static
+#endif /* !NO_SYS */
+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 {
+      PBUF_CHECK_FREE_OOSEQ();
+      had_one = 0;
+      tmptimeout = next_timeout;
+      if (tmptimeout && (tmptimeout->time <= diff)) {
+        /* timeout has expired */
+        had_one = 1;
+        timeouts_last_time += tmptimeout->time;
+        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) {
+#if !NO_SYS
+          /* For LWIP_TCPIP_CORE_LOCKING, lock the core before calling the
+             timeout handler function. */
+          LOCK_TCPIP_CORE();
+#endif /* !NO_SYS */
+          handler(arg);
+#if !NO_SYS
+          UNLOCK_TCPIP_CORE();
+#endif /* !NO_SYS */
+        }
+        LWIP_TCPIP_THREAD_ALIVE();
+      }
+    /* 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();
+}
+
+/** Return the time left before the next timeout is due. If no timeouts are
+ * enqueued, returns 0xffffffff
+ */
+#if !NO_SYS
+static
+#endif /* !NO_SYS */
+u32_t
+sys_timeouts_sleeptime(void)
+{
+  u32_t diff;
+  if (next_timeout == NULL) {
+    return 0xffffffff;
+  }
+  diff = sys_now() - timeouts_last_time;
+  if (diff > next_timeout->time) {
+    return 0;
+  } else {
+    return next_timeout->time - diff;
+  }
+}
+
+#if !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 sleeptime;
+
+again:
+  if (!next_timeout) {
+    sys_arch_mbox_fetch(mbox, msg, 0);
+    return;
+  }
+
+  sleeptime = sys_timeouts_sleeptime();
+  if (sleeptime == 0 || sys_arch_mbox_fetch(mbox, msg, sleeptime) == SYS_ARCH_TIMEOUT) {
+    /* If a SYS_ARCH_TIMEOUT value is returned, a timeout occurred
+       before a message could be fetched. */
+    sys_check_timeouts();
+    /* We try again to fetch a message from the mbox. */
+    goto again;
+  }
+}
+
+#endif /* NO_SYS */
+
+#else /* LWIP_TIMERS && !LWIP_TIMERS_CUSTOM */
+/* Satisfy the TCP code which calls this function */
+void
+tcp_timer_needed(void)
+{
+}
+#endif /* LWIP_TIMERS && !LWIP_TIMERS_CUSTOM */
diff --git a/src/core/udp.c b/src/core/udp.c
new file mode 100644
index 0000000..ce2e3d2
--- /dev/null
+++ b/src/core/udp.c
@@ -0,0 +1,1191 @@
+/**
+ * @file
+ * User Datagram Protocol module\n
+ * The code for the User Datagram Protocol UDP & UDPLite (RFC 3828).\n
+ * See also @ref udp_raw
+ * 
+ * @defgroup udp_raw UDP
+ * @ingroup callbackstyle_api
+ * User Datagram Protocol module\n
+ * @see @ref raw_api and @ref netconn
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+
+/* @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/netif.h"
+#include "lwip/icmp.h"
+#include "lwip/icmp6.h"
+#include "lwip/stats.h"
+#include "lwip/snmp.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) ((u16_t)(((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;
+}
+
+/** Common code to see if the current input packet matches the pcb
+ * (current input packet is accessed via ip(4/6)_current_* macros)
+ *
+ * @param pcb pcb to check
+ * @param inp network interface on which the datagram was received (only used for IPv4)
+ * @param broadcast 1 if his is an IPv4 broadcast (global or subnet-only), 0 otherwise (only used for IPv4)
+ * @return 1 on match, 0 otherwise
+ */
+static u8_t
+udp_input_local_match(struct udp_pcb *pcb, struct netif *inp, u8_t broadcast)
+{
+  LWIP_UNUSED_ARG(inp);       /* in IPv6 only case */
+  LWIP_UNUSED_ARG(broadcast); /* in IPv6 only case */
+
+  /* Dual-stack: PCBs listening to any IP type also listen to any IP address */
+  if (IP_IS_ANY_TYPE_VAL(pcb->local_ip)) {
+#if LWIP_IPV4 && IP_SOF_BROADCAST_RECV
+    if ((broadcast != 0) && !ip_get_option(pcb, SOF_BROADCAST)) {
+      return 0;
+    }
+#endif /* LWIP_IPV4 && IP_SOF_BROADCAST_RECV */
+    return 1;
+  }
+
+  /* Only need to check PCB if incoming IP version matches PCB IP version */
+  if (IP_ADDR_PCB_VERSION_MATCH_EXACT(pcb, ip_current_dest_addr())) {
+#if LWIP_IPV4
+    /* Special case: IPv4 broadcast: all or broadcasts in my subnet
+     * Note: broadcast variable can only be 1 if it is an IPv4 broadcast */
+    if (broadcast != 0) {
+#if IP_SOF_BROADCAST_RECV
+      if (ip_get_option(pcb, SOF_BROADCAST))
+#endif /* IP_SOF_BROADCAST_RECV */
+      {
+        if (ip4_addr_isany(ip_2_ip4(&pcb->local_ip)) ||
+          ((ip4_current_dest_addr()->addr == IPADDR_BROADCAST)) ||
+           ip4_addr_netcmp(ip_2_ip4(&pcb->local_ip), ip4_current_dest_addr(), netif_ip4_netmask(inp))) {
+          return 1;
+        }
+      }
+    } else
+#endif /* LWIP_IPV4 */
+    /* Handle IPv4 and IPv6: all or exact match */
+    if (ip_addr_isany(&pcb->local_ip) || ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+/**
+ * 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 broadcast;
+  u8_t for_us = 0;
+
+  LWIP_UNUSED_ARG(inp);
+
+  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);
+    MIB2_STATS_INC(mib2.udpinerrors);
+    pbuf_free(p);
+    goto end;
+  }
+
+  udphdr = (struct udp_hdr *)p->payload;
+
+  /* is broadcast packet ? */
+  broadcast = ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif());
+
+  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 = lwip_ntohs(udphdr->src);
+  dest = lwip_ntohs(udphdr->dest);
+
+  udp_debug_print(udphdr);
+
+  /* print the UDP source and destination */
+  LWIP_DEBUGF(UDP_DEBUG, ("udp ("));
+  ip_addr_debug_print(UDP_DEBUG, ip_current_dest_addr());
+  LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F") <-- (", lwip_ntohs(udphdr->dest)));
+  ip_addr_debug_print(UDP_DEBUG, ip_current_src_addr());
+  LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F")\n", lwip_ntohs(udphdr->src)));
+
+  pcb = NULL;
+  prev = NULL;
+  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) {
+    /* print the PCB local and remote address */
+    LWIP_DEBUGF(UDP_DEBUG, ("pcb ("));
+    ip_addr_debug_print(UDP_DEBUG, &pcb->local_ip);
+    LWIP_DEBUGF(UDP_DEBUG, (", %"U16_F") <-- (", pcb->local_port));
+    ip_addr_debug_print(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) &&
+        (udp_input_local_match(pcb, inp, broadcast) != 0)) {
+      if (((pcb->flags & UDP_FLAGS_CONNECTED) == 0) &&
+          ((uncon_pcb == NULL)
+#if SO_REUSE
+          /* prefer specific IPs over cath-all */
+          || !ip_addr_isany(&pcb->local_ip)
+#endif /* SO_REUSE */
+          )) {
+        /* the first unconnected matching PCB */
+        uncon_pcb = pcb;
+      }
+
+      /* compare PCB remote addr+port to UDP source addr+port */
+      if ((pcb->remote_port == src) &&
+          (ip_addr_isany_val(pcb->remote_ip) ||
+          ip_addr_cmp(&pcb->remote_ip, ip_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_get_ip6_addr_match(inp, ip6_current_dest_addr()) >= 0;
+    }
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4
+    if (!ip_current_is_v6()) {
+      for_us = ip4_addr_cmp(netif_ip4_addr(inp), ip4_current_dest_addr());
+    }
+#endif /* LWIP_IPV4 */
+  }
+
+  if (for_us) {
+    LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_input: calculating checksum\n"));
+#if CHECKSUM_CHECK_UDP
+    IF__NETIF_CHECKSUM_ENABLED(inp, CHECKSUM_CHECK_UDP) {
+#if LWIP_UDPLITE
+      if (ip_current_header_proto() == IP_PROTO_UDPLITE) {
+        /* Do the UDP Lite checksum */
+        u16_t chklen = lwip_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 (ip_chksum_pseudo_partial(p, IP_PROTO_UDPLITE,
+                     p->tot_len, chklen,
+                     ip_current_src_addr(), ip_current_dest_addr()) != 0) {
+          goto chkerr;
+        }
+      } else
+#endif /* LWIP_UDPLITE */
+      {
+        if (udphdr->chksum != 0) {
+          if (ip_chksum_pseudo(p, IP_PROTO_UDP, p->tot_len,
+                               ip_current_src_addr(),
+                               ip_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);
+      MIB2_STATS_INC(mib2.udpinerrors);
+      pbuf_free(p);
+      goto end;
+    }
+
+    if (pcb != NULL) {
+      MIB2_STATS_INC(mib2.udpindatagrams);
+#if SO_REUSE && SO_REUSE_RXTOALL
+      if (ip_get_option(pcb, SOF_REUSEADDR) &&
+          (broadcast || ip_addr_ismulticast(ip_current_dest_addr()))) {
+        /* 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) &&
+                (udp_input_local_match(mpcb, inp, broadcast) != 0)) {
+              /* pass a copy of the packet to all local matches */
+              if (mpcb->recv != NULL) {
+                struct pbuf *q;
+                /* for that, move payload to IP header again */
+                if (p_header_changed == 0) {
+                  pbuf_header_force(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);
+                    mpcb->recv(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 != NULL) {
+        /* now the recv function is responsible for freeing p */
+        pcb->recv(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 && !ip_addr_ismulticast(ip_current_dest_addr())) {
+        /* move payload pointer back to ip header */
+        pbuf_header_force(p, (s16_t)(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);
+      MIB2_STATS_INC(mib2.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);
+  MIB2_STATS_INC(mib2.udpinerrors);
+  pbuf_free(p);
+  PERF_STOP("udp_input");
+#endif /* CHECKSUM_CHECK_UDP */
+}
+
+/**
+ * @ingroup udp_raw
+ * 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 occurred.
+ * - ERR_MEM. Out of memory.
+ * - ERR_RTE. Could not find route to destination address.
+ * - ERR_VAL. No PCB or PCB is dual-stack
+ * - 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)
+{
+  if ((pcb == NULL) || IP_IS_ANY_TYPE_VAL(pcb->remote_ip)) {
+    return ERR_VAL;
+  }
+
+  /* send to the packet using remote ip and port stored in the pcb */
+  return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);
+}
+
+#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
+/** @ingroup udp_raw
+ * 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)
+{
+  if ((pcb == NULL) || IP_IS_ANY_TYPE_VAL(pcb->remote_ip)) {
+    return ERR_VAL;
+  }
+
+  /* send to the packet using remote ip and port stored in the pcb */
+  return udp_sendto_chksum(pcb, p, &pcb->remote_ip, pcb->remote_port,
+    have_chksum, chksum);
+}
+#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+
+/**
+ * @ingroup udp_raw
+ * 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,
+  const 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);
+}
+
+/** @ingroup udp_raw
+ * Same as udp_sendto(), but with checksum */
+err_t
+udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p, const 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;
+  const ip_addr_t *dst_ip_route = dst_ip;
+
+  if ((pcb == NULL) || (dst_ip == NULL) || !IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
+    return ERR_VAL;
+  }
+
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send\n"));
+
+#if LWIP_IPV6 || (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS)
+  if (ip_addr_ismulticast(dst_ip_route)) {
+#if LWIP_IPV6
+    if (IP_IS_V6(dst_ip)) {
+      /* For multicast, find a netif based on source address. */
+      dst_ip_route = &pcb->local_ip;
+    } else
+#endif /* LWIP_IPV6 */
+    {
+#if LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS
+      /* IPv4 does not use source-based routing by default, so we use an
+         administratively selected interface for multicast by default.
+         However, this can be overridden by setting an interface address
+         in pcb->multicast_ip that is used for routing. */
+      if (!ip_addr_isany_val(pcb->multicast_ip) &&
+          !ip4_addr_cmp(ip_2_ip4(&pcb->multicast_ip), IP4_ADDR_BROADCAST)) {
+        dst_ip_route = &pcb->multicast_ip;
+      }
+#endif /* LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS */
+    }
+  }
+#endif /* LWIP_IPV6 || (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS) */
+
+  /* find the outgoing network interface for this packet */
+  if(IP_IS_ANY_TYPE_VAL(pcb->local_ip)) {
+    /* Don't call ip_route() with IP_ANY_TYPE */
+    netif = ip_route(IP46_ADDR_ANY(IP_GET_TYPE(dst_ip_route)), dst_ip_route);
+  } else {
+    netif = ip_route(&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 "));
+    ip_addr_debug_print(UDP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, 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 */
+}
+
+/**
+ * @ingroup udp_raw
+ * 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,
+  const 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, const 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 */
+  const ip_addr_t *src_ip;
+
+  if ((pcb == NULL) || (dst_ip == NULL) || !IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
+    return ERR_VAL;
+  }
+
+  /* PCB local address is IP_ANY_ADDR? */
+#if LWIP_IPV6
+  if (IP_IS_V6(dst_ip)) {
+    if (ip6_addr_isany(ip_2_ip6(&pcb->local_ip))) {
+      src_ip = ip6_select_source_address(netif, ip_2_ip6(dst_ip));
+      if (src_ip == NULL) {
+        /* No suitable source address was found. */
+        return ERR_RTE;
+      }
+    } else {
+      /* use UDP PCB local IPv6 address as source address, if still valid. */
+      if (netif_get_ip6_addr_match(netif, ip_2_ip6(&pcb->local_ip)) < 0) {
+        /* Address isn't valid anymore. */
+        return ERR_RTE;
+      }
+      src_ip = &pcb->local_ip;
+    }
+  }
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4 && LWIP_IPV6
+  else
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+#if LWIP_IPV4
+  if (ip4_addr_isany(ip_2_ip4(&pcb->local_ip)) ||
+      ip4_addr_ismulticast(ip_2_ip4(&pcb->local_ip))) {
+    /* if the local_ip is any or multicast
+     * use the outgoing network interface IP address as source address */
+    src_ip = netif_ip_addr4(netif);
+  } else {
+    /* check if UDP PCB local IP address is correct
+     * this could be an old address if netif->ip_addr has changed */
+    if (!ip4_addr_cmp(ip_2_ip4(&(pcb->local_ip)), netif_ip4_addr(netif))) {
+      /* local_ip doesn't match, drop the packet */
+      return ERR_RTE;
+    }
+    /* use UDP PCB local IP address as source address */
+    src_ip = &pcb->local_ip;
+  }
+#endif /* LWIP_IPV4 */
+#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
+  return udp_sendto_if_src_chksum(pcb, p, dst_ip, dst_port, netif, have_chksum, chksum, src_ip);
+#else /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+  return udp_sendto_if_src(pcb, p, dst_ip, dst_port, netif, src_ip);
+#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+}
+
+/** @ingroup udp_raw
+ * Same as @ref udp_sendto_if, but with source address */
+err_t
+udp_sendto_if_src(struct udp_pcb *pcb, struct pbuf *p,
+  const ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif, const ip_addr_t *src_ip)
+{
+#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP
+  return udp_sendto_if_src_chksum(pcb, p, dst_ip, dst_port, netif, 0, 0, src_ip);
+}
+
+/** Same as udp_sendto_if_src(), but with checksum */
+err_t
+udp_sendto_if_src_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip,
+                     u16_t dst_port, struct netif *netif, u8_t have_chksum,
+                     u16_t chksum, const ip_addr_t *src_ip)
+{
+#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UDP */
+  struct udp_hdr *udphdr;
+  err_t err;
+  struct pbuf *q; /* q will be sent down the stack */
+  u8_t ip_proto;
+  u8_t ttl;
+
+  if ((pcb == NULL) || (dst_ip == NULL) || !IP_ADDR_PCB_VERSION_MATCH(pcb, src_ip) ||
+      !IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
+    return ERR_VAL;
+  }
+
+#if LWIP_IPV4 && IP_SOF_BROADCAST
+  /* broadcast filter? */
+  if (!ip_get_option(pcb, SOF_BROADCAST) &&
+#if LWIP_IPV6
+      IP_IS_V4(dst_ip) &&
+#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 /* LWIP_IPV4 && 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, &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 = lwip_htons(pcb->local_port);
+  udphdr->dest = lwip_htons(dst_port);
+  /* in UDP, 0 checksum means 'no checksum' */
+  udphdr->chksum = 0x0000;
+
+  /* Multicast Loop? */
+#if (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS) || (LWIP_IPV6 && LWIP_IPV6_MLD)
+  if (((pcb->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) && ip_addr_ismulticast(dst_ip)) {
+    q->flags |= PBUF_FLAG_MCASTLOOP;
+  }
+#endif /* (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS) || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+
+  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 = lwip_htons(chklen_hdr);
+    /* calculate checksum */
+#if CHECKSUM_GEN_UDP
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP) {
+#if LWIP_CHECKSUM_ON_COPY
+      if (have_chksum) {
+        chklen = UDP_HLEN;
+      }
+#endif /* LWIP_CHECKSUM_ON_COPY */
+      udphdr->chksum = ip_chksum_pseudo_partial(q, IP_PROTO_UDPLITE,
+        q->tot_len, chklen, src_ip, 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 = lwip_htons(q->tot_len);
+    /* calculate checksum */
+#if CHECKSUM_GEN_UDP
+    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP) {
+      /* Checksum is mandatory over IPv6. */
+      if (IP_IS_V6(dst_ip) || (pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
+        u16_t udpchksum;
+#if LWIP_CHECKSUM_ON_COPY
+        if (have_chksum) {
+          u32_t acc;
+          udpchksum = ip_chksum_pseudo_partial(q, IP_PROTO_UDP,
+            q->tot_len, UDP_HLEN, src_ip, dst_ip);
+          acc = udpchksum + (u16_t)~(chksum);
+          udpchksum = FOLD_U32T(acc);
+        } else
+#endif /* LWIP_CHECKSUM_ON_COPY */
+        {
+          udpchksum = ip_chksum_pseudo(q, IP_PROTO_UDP, q->tot_len,
+            src_ip, 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;
+  }
+
+  /* Determine TTL to use */
+#if LWIP_MULTICAST_TX_OPTIONS
+  ttl = (ip_addr_ismulticast(dst_ip) ? udp_get_multicast_ttl(pcb) : pcb->ttl);
+#else /* LWIP_MULTICAST_TX_OPTIONS */
+  ttl = pcb->ttl;
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
+
+  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 = ip_output_if_src(q, src_ip, dst_ip, ttl, pcb->tos, ip_proto, netif);
+  NETIF_SET_HWADDRHINT(netif, NULL);
+
+  /* @todo: must this be increased even if error occurred? */
+  MIB2_STATS_INC(mib2.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;
+}
+
+/**
+ * @ingroup udp_raw
+ * 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 IP4_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 occurred.
+ * - 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, const ip_addr_t *ipaddr, u16_t port)
+{
+  struct udp_pcb *ipcb;
+  u8_t rebind;
+
+#if LWIP_IPV4
+  /* Don't propagate NULL pointer (IPv4 ANY) to subsequent functions */
+  if (ipaddr == NULL) {
+    ipaddr = IP4_ADDR_ANY;
+  }
+#endif /* LWIP_IPV4 */
+
+  /* still need to check for ipaddr == NULL in IPv6 only case */
+  if ((pcb == NULL) || (ipaddr == NULL)) {
+    return ERR_VAL;
+  }
+
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_bind(ipaddr = "));
+  ip_addr_debug_print(UDP_DEBUG | LWIP_DBG_TRACE, 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) {
+      rebind = 1;
+      break;
+    }
+  }
+
+  /* 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;
+    }
+  } else {
+    for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
+      if (pcb != ipcb) {
+      /* By default, we don't allow to bind to a port that any other udp
+         PCB is already bound to, unless *all* PCBs with that port have tha
+         REUSEADDR flag set. */
+#if SO_REUSE
+        if (!ip_get_option(pcb, SOF_REUSEADDR) ||
+            !ip_get_option(ipcb, SOF_REUSEADDR))
+#endif /* SO_REUSE */
+        {
+          /* port matches that of PCB in list and REUSEADDR not set -> reject */
+          if ((ipcb->local_port == port) &&
+              /* IP address matches? */
+              ip_addr_cmp(&ipcb->local_ip, 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;
+          }
+        }
+      }
+    }
+  }
+
+  ip_addr_set_ipaddr(&pcb->local_ip, ipaddr);
+
+  pcb->local_port = port;
+  mib2_udp_bind(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 "));
+  ip_addr_debug_print(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;
+}
+
+/**
+ * @ingroup udp_raw
+ * 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, const ip_addr_t *ipaddr, u16_t port)
+{
+  struct udp_pcb *ipcb;
+
+  if ((pcb == NULL) || (ipaddr == NULL)) {
+    return ERR_VAL;
+  }
+
+  if (pcb->local_port == 0) {
+    err_t err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
+    if (err != ERR_OK) {
+      return err;
+    }
+  }
+
+  ip_addr_set_ipaddr(&pcb->remote_ip, ipaddr);
+  pcb->remote_port = port;
+  pcb->flags |= UDP_FLAGS_CONNECTED;
+
+  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_STATE, ("udp_connect: connected to "));
+  ip_addr_debug_print(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;
+}
+
+/**
+ * @ingroup udp_raw
+ * Disconnect a UDP PCB
+ *
+ * @param pcb the udp pcb to disconnect.
+ */
+void
+udp_disconnect(struct udp_pcb *pcb)
+{
+  /* reset remote address association */
+#if LWIP_IPV4 && LWIP_IPV6
+  if (IP_IS_ANY_TYPE_VAL(pcb->local_ip)) {
+    ip_addr_copy(pcb->remote_ip, *IP_ANY_TYPE);
+  } else {
+#endif
+    ip_addr_set_any(IP_IS_V6_VAL(pcb->remote_ip), &pcb->remote_ip);
+#if LWIP_IPV4 && LWIP_IPV6
+  }
+#endif
+  pcb->remote_port = 0;
+  /* mark PCB as unconnected */
+  pcb->flags &= ~UDP_FLAGS_CONNECTED;
+}
+
+/**
+ * @ingroup udp_raw
+ * 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 which 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 = recv;
+  pcb->recv_arg = recv_arg;
+}
+
+/**
+ * @ingroup udp_raw
+ * 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;
+
+  mib2_udp_unbind(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;
+        break;
+      }
+    }
+  }
+  memp_free(MEMP_UDP_PCB, pcb);
+}
+
+/**
+ * @ingroup udp_raw
+ * 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;
+#if LWIP_MULTICAST_TX_OPTIONS
+    udp_set_multicast_ttl(pcb, UDP_TTL);
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
+  }
+  return pcb;
+}
+
+/**
+ * @ingroup udp_raw
+ * Create a UDP PCB for specific IP type.
+ *
+ * @param type IP address type, see @ref lwip_ip_addr_type definitions.
+ * If you want to listen to IPv4 and IPv6 (dual-stack) packets,
+ * supply @ref IPADDR_TYPE_ANY as argument and bind to @ref IP_ANY_TYPE.
+ * @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_ip_type(u8_t type)
+{
+  struct udp_pcb *pcb;
+  pcb = udp_new();
+#if LWIP_IPV4 && LWIP_IPV6
+  if (pcb != NULL) {
+    IP_SET_TYPE_VAL(pcb->local_ip,  type);
+    IP_SET_TYPE_VAL(pcb->remote_ip, type);
+  }
+#else
+  LWIP_UNUSED_ARG(type);
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+  return pcb;
+}
+
+/** This function is called from netif.c when address is changed
+ *
+ * @param old_addr IP address of the netif before change
+ * @param new_addr IP address of the netif after change
+ */
+void udp_netif_ip_addr_changed(const ip_addr_t* old_addr, const ip_addr_t* new_addr)
+{
+  struct udp_pcb* upcb;
+
+  if (!ip_addr_isany(old_addr) && !ip_addr_isany(new_addr)) {
+    for (upcb = udp_pcbs; upcb != NULL; upcb = upcb->next) {
+      /* PCB bound to current local interface address? */
+      if (ip_addr_cmp(&upcb->local_ip, old_addr)) {
+        /* The PCB is bound to the old ipaddr and
+         * is set to bound to the new one instead */
+        ip_addr_copy(upcb->local_ip, *new_addr);
+      }
+    }
+  }
+}
+
+#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",
+                          lwip_ntohs(udphdr->src), lwip_ntohs(udphdr->dest)));
+  LWIP_DEBUGF(UDP_DEBUG, ("+-------------------------------+\n"));
+  LWIP_DEBUGF(UDP_DEBUG, ("|     %5"U16_F"     |     0x%04"X16_F"    | (len, chksum)\n",
+                          lwip_ntohs(udphdr->len), lwip_ntohs(udphdr->chksum)));
+  LWIP_DEBUGF(UDP_DEBUG, ("+-------------------------------+\n"));
+}
+#endif /* UDP_DEBUG */
+
+#endif /* LWIP_UDP */
diff --git a/src/include/lwip/api.h b/src/include/lwip/api.h
new file mode 100644
index 0000000..516bd16
--- /dev/null
+++ b/src/include/lwip/api.h
@@ -0,0 +1,400 @@
+/**
+ * @file
+ * netconn API (to be used from non-TCPIP threads)
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_API_H
+#define LWIP_HDR_API_H
+
+#include "lwip/opt.h"
+
+#if LWIP_NETCONN || LWIP_SOCKET /* don't build if not configured for use in lwipopts.h */
+/* Note: Netconn API is always available when sockets are enabled -
+ * sockets are implemented on top of them */
+
+#include "lwip/arch.h"
+#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) */
+/** 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 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)&NETCONN_TYPE_IPV6) != 0)
+#define NETCONNTYPE_ISUDPLITE(t)     (((t)&0xF3) == NETCONN_UDPLITE)
+#define NETCONNTYPE_ISUDPNOCHKSUM(t) (((t)&0xF3) == NETCONN_UDPNOCHKSUM)
+#else /* LWIP_IPV6 */
+#define NETCONNTYPE_ISIPV6(t)        (0)
+#define NETCONNTYPE_ISUDPLITE(t)     ((t) == NETCONN_UDPLITE)
+#define NETCONNTYPE_ISUDPNOCHKSUM(t) ((t) == NETCONN_UDPNOCHKSUM)
+#endif /* LWIP_IPV6 */
+
+/** @ingroup netconn_common
+ * Protocol family and type of the netconn
+ */
+enum netconn_type {
+  NETCONN_INVALID     = 0,
+  /** TCP IPv4 */
+  NETCONN_TCP         = 0x10,
+#if LWIP_IPV6
+  /** TCP IPv6 */
+  NETCONN_TCP_IPV6    = NETCONN_TCP | NETCONN_TYPE_IPV6 /* 0x18 */,
+#endif /* LWIP_IPV6 */
+  /** UDP IPv4 */
+  NETCONN_UDP         = 0x20,
+  /** UDP IPv4 lite */
+  NETCONN_UDPLITE     = 0x21,
+  /** UDP IPv4 no checksum */
+  NETCONN_UDPNOCHKSUM = 0x22,
+
+#if LWIP_IPV6
+  /** UDP IPv6 (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
+  NETCONN_UDP_IPV6         = NETCONN_UDP | NETCONN_TYPE_IPV6 /* 0x28 */,
+  /** UDP IPv6 lite (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
+  NETCONN_UDPLITE_IPV6     = NETCONN_UDPLITE | NETCONN_TYPE_IPV6 /* 0x29 */,
+  /** UDP IPv6 no checksum (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
+  NETCONN_UDPNOCHKSUM_IPV6 = NETCONN_UDPNOCHKSUM | NETCONN_TYPE_IPV6 /* 0x2a */,
+#endif /* LWIP_IPV6 */
+
+  /** Raw connection IPv4 */
+  NETCONN_RAW         = 0x40
+#if LWIP_IPV6
+  /** Raw connection IPv6 (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
+  , 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
+};
+
+/** Used to inform the callback function about changes
+ * 
+ * Event explanation:
+ * 
+ * In the netconn implementation, there are three ways to block a client:
+ * 
+ * - accept mbox (sys_arch_mbox_fetch(&conn->acceptmbox, &accept_ptr, 0); in netconn_accept())
+ * - receive mbox (sys_arch_mbox_fetch(&conn->recvmbox, &buf, 0); in netconn_recv_data())
+ * - send queue is full (sys_arch_sem_wait(LWIP_API_MSG_SEM(msg), 0); in lwip_netconn_do_write())
+ * 
+ * The events have to be seen as events signaling the state of these mboxes/semaphores. For non-blocking
+ * connections, you need to know in advance whether a call to a netconn function call would block or not,
+ * and these events tell you about that.
+ * 
+ * RCVPLUS events say: Safe to perform a potentially blocking call call once more. 
+ * They are counted in sockets - three RCVPLUS events for accept mbox means you are safe
+ * to call netconn_accept 3 times without being blocked.
+ * Same thing for receive mbox.
+ * 
+ * RCVMINUS events say: Your call to to a possibly blocking function is "acknowledged".
+ * Socket implementation decrements the counter.
+ * 
+ * For TX, there is no need to count, its merely a flag. SENDPLUS means you may send something.
+ * SENDPLUS occurs when enough data was delivered to peer so netconn_send() can be called again.
+ * A SENDMINUS event occurs when the next call to a netconn_send() would be blocking.
+ */
+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) */
+
+#if LWIP_DNS
+/* Used for netconn_gethostbyname_addrtype(), these should match the DNS_ADDRTYPE defines in dns.h */
+#define NETCONN_DNS_DEFAULT   NETCONN_DNS_IPV4_IPV6
+#define NETCONN_DNS_IPV4      0
+#define NETCONN_DNS_IPV6      1
+#define NETCONN_DNS_IPV4_IPV6 2 /* try to resolve IPv4 first, try IPv6 if IPv4 fails only */
+#define NETCONN_DNS_IPV6_IPV4 3 /* try to resolve IPv6 first, try IPv4 if IPv6 fails only */
+#endif /* LWIP_DNS */
+
+/* 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;
+
+/** 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;
+#if !LWIP_NETCONN_SEM_PER_THREAD
+  /** sem that is used to synchronously execute functions in the core context */
+  sys_sem_t op_completed;
+#endif
+  /** 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) in milliseconds */
+  s32_t send_timeout;
+#endif /* LWIP_SO_RCVTIMEO */
+#if LWIP_SO_RCVTIMEO
+  /** timeout in milliseconds 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 */
+  int recv_avail;
+#endif /* LWIP_SO_RCVBUF */
+#if LWIP_SO_LINGER
+   /** values <0 mean linger is disabled, values > 0 are seconds to linger */
+  s16_t linger;
+#endif /* LWIP_SO_LINGER */
+  /** 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 *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 { if ((conn) != NULL) { \
+  SYS_ARCH_DECL_PROTECT(netconn_set_safe_err_lev); \
+  SYS_ARCH_PROTECT(netconn_set_safe_err_lev); \
+  if (!ERR_IS_FATAL((conn)->last_err)) { \
+    (conn)->last_err = err; \
+  } \
+  SYS_ARCH_UNPROTECT(netconn_set_safe_err_lev); \
+}} while(0);
+
+/* Network connection functions: */
+
+/** @ingroup netconn_common
+ * Create new netconn connection
+ * @param t @ref netconn_type */
+#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);
+/** @ingroup netconn_common */
+#define netconn_peer(c,i,p) netconn_getaddr(c,i,p,0)
+/** @ingroup netconn_common */
+#define netconn_addr(c,i,p) netconn_getaddr(c,i,p,1)
+
+err_t   netconn_bind(struct netconn *conn, const ip_addr_t *addr, u16_t port);
+err_t   netconn_connect(struct netconn *conn, const 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);
+/** @ingroup netconn_tcp */
+#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);
+err_t   netconn_sendto(struct netconn *conn, struct netbuf *buf,
+                             const 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);
+/** @ingroup netconn_tcp */
+#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, const ip_addr_t *multiaddr,
+                             const ip_addr_t *netif_addr, enum netconn_igmp join_or_leave);
+#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
+#if LWIP_DNS
+#if LWIP_IPV4 && LWIP_IPV6
+err_t   netconn_gethostbyname_addrtype(const char *name, ip_addr_t *addr, u8_t dns_addrtype);
+#define netconn_gethostbyname(name, addr) netconn_gethostbyname_addrtype(name, addr, NETCONN_DNS_DEFAULT)
+#else /* LWIP_IPV4 && LWIP_IPV6 */
+err_t   netconn_gethostbyname(const char *name, ip_addr_t *addr);
+#define netconn_gethostbyname_addrtype(name, addr, dns_addrtype) netconn_gethostbyname(name, addr)
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+#endif /* LWIP_DNS */
+
+#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)
+
+#if LWIP_IPV6
+/** @ingroup netconn_common
+ * TCP: Set the IPv6 ONLY status of netconn calls (see NETCONN_FLAG_IPV6_V6ONLY)
+ */
+#define netconn_set_ipv6only(conn, val)  do { if(val) { \
+  (conn)->flags |= NETCONN_FLAG_IPV6_V6ONLY; \
+} else { \
+  (conn)->flags &= ~ NETCONN_FLAG_IPV6_V6ONLY; }} while(0)
+/** @ingroup netconn_common
+ * TCP: Get the IPv6 ONLY status of netconn calls (see NETCONN_FLAG_IPV6_V6ONLY)
+ */
+#define netconn_get_ipv6only(conn)        (((conn)->flags & NETCONN_FLAG_IPV6_V6ONLY) != 0)
+#endif /* LWIP_IPV6 */
+
+#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*/
+
+#if LWIP_NETCONN_SEM_PER_THREAD
+void netconn_thread_init(void);
+void netconn_thread_cleanup(void);
+#else /* LWIP_NETCONN_SEM_PER_THREAD */
+#define netconn_thread_init()
+#define netconn_thread_cleanup()
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_NETCONN || LWIP_SOCKET */
+
+#endif /* LWIP_HDR_API_H */
diff --git a/src/include/lwip/apps/FILES b/src/include/lwip/apps/FILES
new file mode 100644
index 0000000..adfc0f3
--- /dev/null
+++ b/src/include/lwip/apps/FILES
@@ -0,0 +1,2 @@
+This directory contains application headers.
+Every application shall provide one api file APP.h and optionally one options file APP_opts.h
diff --git a/src/include/lwip/apps/fs.h b/src/include/lwip/apps/fs.h
new file mode 100644
index 0000000..bb176fa
--- /dev/null
+++ b/src/include/lwip/apps/fs.h
@@ -0,0 +1,103 @@
+/*
+ * 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: Adam Dunkels <adam@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_APPS_FS_H
+#define LWIP_HDR_APPS_FS_H
+
+#include "httpd_opts.h"
+#include "lwip/err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define FS_READ_EOF     -1
+#define FS_READ_DELAYED -2
+
+#if HTTPD_PRECALCULATED_CHECKSUM
+struct fsdata_chksum {
+  u32_t offset;
+  u16_t chksum;
+  u16_t len;
+};
+#endif /* HTTPD_PRECALCULATED_CHECKSUM */
+
+#define FS_FILE_FLAGS_HEADER_INCLUDED     0x01
+#define FS_FILE_FLAGS_HEADER_PERSISTENT   0x02
+
+struct fs_file {
+  const char *data;
+  int len;
+  int index;
+  void *pextension;
+#if HTTPD_PRECALCULATED_CHECKSUM
+  const struct fsdata_chksum *chksum;
+  u16_t chksum_count;
+#endif /* HTTPD_PRECALCULATED_CHECKSUM */
+  u8_t flags;
+#if LWIP_HTTPD_CUSTOM_FILES
+  u8_t is_custom_file;
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+#if LWIP_HTTPD_FILE_STATE
+  void *state;
+#endif /* LWIP_HTTPD_FILE_STATE */
+};
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+typedef void (*fs_wait_cb)(void *arg);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+err_t fs_open(struct fs_file *file, const char *name);
+void fs_close(struct fs_file *file);
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+#if LWIP_HTTPD_FS_ASYNC_READ
+int fs_read_async(struct fs_file *file, char *buffer, int count, fs_wait_cb callback_fn, void *callback_arg);
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+int fs_read(struct fs_file *file, char *buffer, int count);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+#if LWIP_HTTPD_FS_ASYNC_READ
+int fs_is_file_ready(struct fs_file *file, fs_wait_cb callback_fn, void *callback_arg);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+int fs_bytes_left(struct fs_file *file);
+
+#if LWIP_HTTPD_FILE_STATE
+/** This user-defined function is called when a file is opened. */
+void *fs_state_init(struct fs_file *file, const char *name);
+/** This user-defined function is called when a file is closed. */
+void fs_state_free(struct fs_file *file, void *state);
+#endif /* #if LWIP_HTTPD_FILE_STATE */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_FS_H */
diff --git a/src/include/lwip/apps/httpd.h b/src/include/lwip/apps/httpd.h
new file mode 100644
index 0000000..40f1811
--- /dev/null
+++ b/src/include/lwip/apps/httpd.h
@@ -0,0 +1,236 @@
+/**
+ * @file
+ * HTTP server
+ */
+
+/*
+ * 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: Adam Dunkels <adam@sics.se>
+ *
+ * This version of the file has been modified by Texas Instruments to offer
+ * simple server-side-include (SSI) and Common Gateway Interface (CGI)
+ * capability.
+ */
+
+#ifndef LWIP_HDR_APPS_HTTPD_H
+#define LWIP_HDR_APPS_HTTPD_H
+
+#include "httpd_opts.h"
+#include "lwip/err.h"
+#include "lwip/pbuf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_HTTPD_CGI
+
+/*
+ * Function pointer for a CGI script handler.
+ *
+ * This function is called each time the HTTPD server is asked for a file
+ * whose name was previously registered as a CGI function using a call to
+ * http_set_cgi_handler. The iIndex parameter provides the index of the
+ * CGI within the ppcURLs array passed to http_set_cgi_handler. Parameters
+ * pcParam and pcValue provide access to the parameters provided along with
+ * the URI. iNumParams provides a count of the entries in the pcParam and
+ * pcValue arrays. Each entry in the pcParam array contains the name of a
+ * parameter with the corresponding entry in the pcValue array containing the
+ * value for that parameter. Note that pcParam may contain multiple elements
+ * with the same name if, for example, a multi-selection list control is used
+ * in the form generating the data.
+ *
+ * The function should return a pointer to a character string which is the
+ * path and filename of the response that is to be sent to the connected
+ * browser, for example "/thanks.htm" or "/response/error.ssi".
+ *
+ * The maximum number of parameters that will be passed to this function via
+ * iNumParams is defined by LWIP_HTTPD_MAX_CGI_PARAMETERS. Any parameters in the incoming
+ * HTTP request above this number will be discarded.
+ *
+ * Requests intended for use by this CGI mechanism must be sent using the GET
+ * method (which encodes all parameters within the URI rather than in a block
+ * later in the request). Attempts to use the POST method will result in the
+ * request being ignored.
+ *
+ */
+typedef const char *(*tCGIHandler)(int iIndex, int iNumParams, char *pcParam[],
+                             char *pcValue[]);
+
+/*
+ * Structure defining the base filename (URL) of a CGI and the associated
+ * function which is to be called when that URL is requested.
+ */
+typedef struct
+{
+    const char *pcCGIName;
+    tCGIHandler pfnCGIHandler;
+} tCGI;
+
+void http_set_cgi_handlers(const tCGI *pCGIs, int iNumHandlers);
+
+#endif /* LWIP_HTTPD_CGI */
+
+#if LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI
+
+#if LWIP_HTTPD_CGI_SSI
+/** Define this generic CGI handler in your application.
+ * It is called once for every URI with parameters.
+ * The parameters can be stored to 
+ */
+extern void httpd_cgi_handler(const char* uri, int iNumParams, char **pcParam, char **pcValue
+#if defined(LWIP_HTTPD_FILE_STATE) && LWIP_HTTPD_FILE_STATE
+                                     , void *connection_state
+#endif /* LWIP_HTTPD_FILE_STATE */
+                                     );
+#endif /* LWIP_HTTPD_CGI_SSI */
+
+#endif /* LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI */
+
+#if LWIP_HTTPD_SSI
+
+/*
+ * Function pointer for the SSI tag handler callback.
+ *
+ * This function will be called each time the HTTPD server detects a tag of the
+ * form <!--#name--> in a .shtml, .ssi or .shtm file where "name" appears as
+ * one of the tags supplied to http_set_ssi_handler in the ppcTags array.  The
+ * returned insert string, which will be appended after the the string
+ * "<!--#name-->" in file sent back to the client,should be written to pointer
+ * pcInsert.  iInsertLen contains the size of the buffer pointed to by
+ * pcInsert.  The iIndex parameter provides the zero-based index of the tag as
+ * found in the ppcTags array and identifies the tag that is to be processed.
+ *
+ * The handler returns the number of characters written to pcInsert excluding
+ * any terminating NULL or a negative number to indicate a failure (tag not
+ * recognized, for example).
+ *
+ * Note that the behavior of this SSI mechanism is somewhat different from the
+ * "normal" SSI processing as found in, for example, the Apache web server.  In
+ * this case, the inserted text is appended following the SSI tag rather than
+ * replacing the tag entirely.  This allows for an implementation that does not
+ * require significant additional buffering of output data yet which will still
+ * offer usable SSI functionality.  One downside to this approach is when
+ * attempting to use SSI within JavaScript.  The SSI tag is structured to
+ * resemble an HTML comment but this syntax does not constitute a comment
+ * within JavaScript and, hence, leaving the tag in place will result in
+ * problems in these cases.  To work around this, any SSI tag which needs to
+ * output JavaScript code must do so in an encapsulated way, sending the whole
+ * HTML <script>...</script> section as a single include.
+ */
+typedef u16_t (*tSSIHandler)(
+#if LWIP_HTTPD_SSI_RAW
+                             const char* ssi_tag_name,
+#else /* LWIP_HTTPD_SSI_RAW */
+                             int iIndex,
+#endif /* LWIP_HTTPD_SSI_RAW */
+                             char *pcInsert, int iInsertLen
+#if LWIP_HTTPD_SSI_MULTIPART
+                             , u16_t current_tag_part, u16_t *next_tag_part
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+#if defined(LWIP_HTTPD_FILE_STATE) && LWIP_HTTPD_FILE_STATE
+                             , void *connection_state
+#endif /* LWIP_HTTPD_FILE_STATE */
+                             );
+
+/** Set the SSI handler function
+ * (if LWIP_HTTPD_SSI_RAW==1, only the first argument is used)
+ */
+void http_set_ssi_handler(tSSIHandler pfnSSIHandler,
+                          const char **ppcTags, int iNumTags);
+
+/** For LWIP_HTTPD_SSI_RAW==1, return this to indicate the tag is unknown.
+ * In this case, the webserver writes a warning into the page.
+ * You can also just return 0 to write nothing for unknown tags.
+ */
+#define HTTPD_SSI_TAG_UNKNOWN 0xFFFF
+
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_SUPPORT_POST
+
+/* These functions must be implemented by the application */
+
+/** Called when a POST request has been received. The application can decide
+ * whether to accept it or not.
+ *
+ * @param connection Unique connection identifier, valid until httpd_post_end
+ *        is called.
+ * @param uri The HTTP header URI receiving the POST request.
+ * @param http_request The raw HTTP request (the first packet, normally).
+ * @param http_request_len Size of 'http_request'.
+ * @param content_len Content-Length from HTTP header.
+ * @param response_uri Filename of response file, to be filled when denying the
+ *        request
+ * @param response_uri_len Size of the 'response_uri' buffer.
+ * @param post_auto_wnd Set this to 0 to let the callback code handle window
+ *        updates by calling 'httpd_post_data_recved' (to throttle rx speed)
+ *        default is 1 (httpd handles window updates automatically)
+ * @return ERR_OK: Accept the POST request, data may be passed in
+ *         another err_t: Deny the POST request, send back 'bad request'.
+ */
+err_t httpd_post_begin(void *connection, const char *uri, const char *http_request,
+                       u16_t http_request_len, int content_len, char *response_uri,
+                       u16_t response_uri_len, u8_t *post_auto_wnd);
+
+/** Called for each pbuf of data that has been received for a POST.
+ * ATTENTION: The application is responsible for freeing the pbufs passed in!
+ *
+ * @param connection Unique connection identifier.
+ * @param p Received data.
+ * @return ERR_OK: Data accepted.
+ *         another err_t: Data denied, http_post_get_response_uri will be called.
+ */
+err_t httpd_post_receive_data(void *connection, struct pbuf *p);
+
+/** Called when all data is received or when the connection is closed.
+ * The application must return the filename/URI of a file to send in response
+ * to this POST request. If the response_uri buffer is untouched, a 404
+ * response is returned.
+ *
+ * @param connection Unique connection identifier.
+ * @param response_uri Filename of response file, to be filled when denying the request
+ * @param response_uri_len Size of the 'response_uri' buffer.
+ */
+void httpd_post_finished(void *connection, char *response_uri, u16_t response_uri_len);
+
+#if LWIP_HTTPD_POST_MANUAL_WND
+void httpd_post_data_recved(void *connection, u16_t recved_len);
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+
+void httpd_init(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HTTPD_H */
diff --git a/src/include/lwip/apps/httpd_opts.h b/src/include/lwip/apps/httpd_opts.h
new file mode 100644
index 0000000..2506080
--- /dev/null
+++ b/src/include/lwip/apps/httpd_opts.h
@@ -0,0 +1,322 @@
+/**
+ * @file
+ * HTTP server options list
+ */
+
+/*
+ * 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: Adam Dunkels <adam@sics.se>
+ *
+ * This version of the file has been modified by Texas Instruments to offer
+ * simple server-side-include (SSI) and Common Gateway Interface (CGI)
+ * capability.
+ */
+
+#ifndef LWIP_HDR_APPS_HTTPD_OPTS_H
+#define LWIP_HDR_APPS_HTTPD_OPTS_H
+
+#include "lwip/opt.h"
+
+/**
+ * @defgroup httpd_opts Options
+ * @ingroup httpd
+ * @{
+ */
+
+/** Set this to 1 to support CGI (old style) */
+#if !defined LWIP_HTTPD_CGI || defined __DOXYGEN__
+#define LWIP_HTTPD_CGI            0
+#endif
+
+/** Set this to 1 to support CGI (new style) */
+#if !defined LWIP_HTTPD_CGI_SSI || defined __DOXYGEN__
+#define LWIP_HTTPD_CGI_SSI        0
+#endif
+
+/** Set this to 1 to support SSI (Server-Side-Includes) */
+#if !defined LWIP_HTTPD_SSI || defined __DOXYGEN__
+#define LWIP_HTTPD_SSI            0
+#endif
+
+/** Set this to 1 to implement an SSI tag handler callback that gets a const char*
+ * to the tag (instead of an index into a pre-registered array of known tags) */
+#if !defined LWIP_HTTPD_SSI_RAW || defined __DOXYGEN__
+#define LWIP_HTTPD_SSI_RAW        0
+#endif
+
+/** Set this to 1 to support HTTP POST */
+#if !defined LWIP_HTTPD_SUPPORT_POST || defined __DOXYGEN__
+#define LWIP_HTTPD_SUPPORT_POST   0
+#endif
+
+/* The maximum number of parameters that the CGI handler can be sent. */
+#if !defined LWIP_HTTPD_MAX_CGI_PARAMETERS || defined __DOXYGEN__
+#define LWIP_HTTPD_MAX_CGI_PARAMETERS 16
+#endif
+
+/** LWIP_HTTPD_SSI_MULTIPART==1: SSI handler function is called with 2 more
+ * arguments indicating a counter for insert string that are too long to be
+ * inserted at once: the SSI handler function must then set 'next_tag_part'
+ * which will be passed back to it in the next call. */
+#if !defined LWIP_HTTPD_SSI_MULTIPART || defined __DOXYGEN__
+#define LWIP_HTTPD_SSI_MULTIPART    0
+#endif
+
+/* The maximum length of the string comprising the tag name */
+#if !defined LWIP_HTTPD_MAX_TAG_NAME_LEN || defined __DOXYGEN__
+#define LWIP_HTTPD_MAX_TAG_NAME_LEN 8
+#endif
+
+/* The maximum length of string that can be returned to replace any given tag */
+#if !defined LWIP_HTTPD_MAX_TAG_INSERT_LEN || defined __DOXYGEN__
+#define LWIP_HTTPD_MAX_TAG_INSERT_LEN 192
+#endif
+
+#if !defined LWIP_HTTPD_POST_MANUAL_WND || defined __DOXYGEN__
+#define LWIP_HTTPD_POST_MANUAL_WND  0
+#endif
+
+/** This string is passed in the HTTP header as "Server: " */
+#if !defined HTTPD_SERVER_AGENT || defined __DOXYGEN__
+#define HTTPD_SERVER_AGENT "lwIP/" LWIP_VERSION_STRING " (http://savannah.nongnu.org/projects/lwip)"
+#endif
+
+/** Set this to 1 if you want to include code that creates HTTP headers
+ * at runtime. Default is off: HTTP headers are then created statically
+ * by the makefsdata tool. Static headers mean smaller code size, but
+ * the (readonly) fsdata will grow a bit as every file includes the HTTP
+ * header. */
+#if !defined LWIP_HTTPD_DYNAMIC_HEADERS || defined __DOXYGEN__
+#define LWIP_HTTPD_DYNAMIC_HEADERS 0
+#endif
+
+#if !defined HTTPD_DEBUG || defined __DOXYGEN__
+#define HTTPD_DEBUG         LWIP_DBG_OFF
+#endif
+
+/** Set this to 1 to use a memp pool for allocating 
+ * struct http_state instead of the heap.
+ */
+#if !defined HTTPD_USE_MEM_POOL || defined __DOXYGEN__
+#define HTTPD_USE_MEM_POOL  0
+#endif
+
+/** The server port for HTTPD to use */
+#if !defined HTTPD_SERVER_PORT || defined __DOXYGEN__
+#define HTTPD_SERVER_PORT                   80
+#endif
+
+/** Maximum retries before the connection is aborted/closed.
+ * - number of times pcb->poll is called -> default is 4*500ms = 2s;
+ * - reset when pcb->sent is called
+ */
+#if !defined HTTPD_MAX_RETRIES || defined __DOXYGEN__
+#define HTTPD_MAX_RETRIES                   4
+#endif
+
+/** The poll delay is X*500ms */
+#if !defined HTTPD_POLL_INTERVAL || defined __DOXYGEN__
+#define HTTPD_POLL_INTERVAL                 4
+#endif
+
+/** Priority for tcp pcbs created by HTTPD (very low by default).
+ *  Lower priorities get killed first when running out of memory.
+ */
+#if !defined HTTPD_TCP_PRIO || defined __DOXYGEN__
+#define HTTPD_TCP_PRIO                      TCP_PRIO_MIN
+#endif
+
+/** Set this to 1 to enable timing each file sent */
+#if !defined LWIP_HTTPD_TIMING || defined __DOXYGEN__
+#define LWIP_HTTPD_TIMING                   0
+#endif
+/** Set this to 1 to enable timing each file sent */
+#if !defined HTTPD_DEBUG_TIMING || defined __DOXYGEN__
+#define HTTPD_DEBUG_TIMING                  LWIP_DBG_OFF
+#endif
+
+/** Set this to one to show error pages when parsing a request fails instead
+    of simply closing the connection. */
+#if !defined LWIP_HTTPD_SUPPORT_EXTSTATUS || defined __DOXYGEN__
+#define LWIP_HTTPD_SUPPORT_EXTSTATUS        0
+#endif
+
+/** Set this to 0 to drop support for HTTP/0.9 clients (to save some bytes) */
+#if !defined LWIP_HTTPD_SUPPORT_V09 || defined __DOXYGEN__
+#define LWIP_HTTPD_SUPPORT_V09              1
+#endif
+
+/** Set this to 1 to enable HTTP/1.1 persistent connections.
+ * ATTENTION: If the generated file system includes HTTP headers, these must
+ * include the "Connection: keep-alive" header (pass argument "-11" to makefsdata).
+ */
+#if !defined LWIP_HTTPD_SUPPORT_11_KEEPALIVE || defined __DOXYGEN__
+#define LWIP_HTTPD_SUPPORT_11_KEEPALIVE     0
+#endif
+
+/** Set this to 1 to support HTTP request coming in in multiple packets/pbufs */
+#if !defined LWIP_HTTPD_SUPPORT_REQUESTLIST || defined __DOXYGEN__
+#define LWIP_HTTPD_SUPPORT_REQUESTLIST      1
+#endif
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+/** Number of rx pbufs to enqueue to parse an incoming request (up to the first
+    newline) */
+#if !defined LWIP_HTTPD_REQ_QUEUELEN || defined __DOXYGEN__
+#define LWIP_HTTPD_REQ_QUEUELEN             5
+#endif
+
+/** Number of (TCP payload-) bytes (in pbufs) to enqueue to parse and incoming
+    request (up to the first double-newline) */
+#if !defined LWIP_HTTPD_REQ_BUFSIZE || defined __DOXYGEN__
+#define LWIP_HTTPD_REQ_BUFSIZE              LWIP_HTTPD_MAX_REQ_LENGTH
+#endif
+
+/** Defines the maximum length of a HTTP request line (up to the first CRLF,
+    copied from pbuf into this a global buffer when pbuf- or packet-queues
+    are received - otherwise the input pbuf is used directly) */
+#if !defined LWIP_HTTPD_MAX_REQ_LENGTH || defined __DOXYGEN__
+#define LWIP_HTTPD_MAX_REQ_LENGTH           LWIP_MIN(1023, (LWIP_HTTPD_REQ_QUEUELEN * PBUF_POOL_BUFSIZE))
+#endif
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+
+/** This is the size of a static buffer used when URIs end with '/'.
+ * In this buffer, the directory requested is concatenated with all the
+ * configured default file names.
+ * Set to 0 to disable checking default filenames on non-root directories.
+ */
+#if !defined LWIP_HTTPD_MAX_REQUEST_URI_LEN || defined __DOXYGEN__
+#define LWIP_HTTPD_MAX_REQUEST_URI_LEN      63
+#endif
+
+/** Maximum length of the filename to send as response to a POST request,
+ * filled in by the application when a POST is finished.
+ */
+#if !defined LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN || defined __DOXYGEN__
+#define LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN 63
+#endif
+
+/** Set this to 0 to not send the SSI tag (default is on, so the tag will
+ * be sent in the HTML page */
+#if !defined LWIP_HTTPD_SSI_INCLUDE_TAG || defined __DOXYGEN__
+#define LWIP_HTTPD_SSI_INCLUDE_TAG           1
+#endif
+
+/** Set this to 1 to call tcp_abort when tcp_close fails with memory error.
+ * This can be used to prevent consuming all memory in situations where the
+ * HTTP server has low priority compared to other communication. */
+#if !defined LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR || defined __DOXYGEN__
+#define LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR  0
+#endif
+
+/** Set this to 1 to kill the oldest connection when running out of
+ * memory for 'struct http_state' or 'struct http_ssi_state'.
+ * ATTENTION: This puts all connections on a linked list, so may be kind of slow.
+ */
+#if !defined LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED || defined __DOXYGEN__
+#define LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED 0
+#endif
+
+/** Set this to 1 to send URIs without extension without headers */
+#if !defined LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI || defined __DOXYGEN__
+#define LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI 0
+#endif
+
+/** Default: Tags are sent from struct http_state and are therefore volatile */
+#if !defined HTTP_IS_TAG_VOLATILE || defined __DOXYGEN__
+#define HTTP_IS_TAG_VOLATILE(ptr) TCP_WRITE_FLAG_COPY
+#endif
+
+/* By default, the httpd is limited to send 2*pcb->mss to keep resource usage low
+   when http is not an important protocol in the device. */
+#if !defined HTTPD_LIMIT_SENDING_TO_2MSS || defined __DOXYGEN__
+#define HTTPD_LIMIT_SENDING_TO_2MSS 1
+#endif
+
+/* Define this to a function that returns the maximum amount of data to enqueue.
+   The function have this signature: u16_t fn(struct tcp_pcb* pcb); */
+#if !defined HTTPD_MAX_WRITE_LEN || defined __DOXYGEN__
+#if HTTPD_LIMIT_SENDING_TO_2MSS
+#define HTTPD_MAX_WRITE_LEN(pcb)    (2 * tcp_mss(pcb))
+#endif
+#endif
+
+/*------------------- FS OPTIONS -------------------*/
+
+/** Set this to 1 and provide the functions:
+ * - "int fs_open_custom(struct fs_file *file, const char *name)"
+ *    Called first for every opened file to allow opening files
+ *    that are not included in fsdata(_custom).c
+ * - "void fs_close_custom(struct fs_file *file)"
+ *    Called to free resources allocated by fs_open_custom().
+ */
+#if !defined LWIP_HTTPD_CUSTOM_FILES || defined __DOXYGEN__
+#define LWIP_HTTPD_CUSTOM_FILES       0
+#endif
+
+/** Set this to 1 to support fs_read() to dynamically read file data.
+ * Without this (default=off), only one-block files are supported,
+ * and the contents must be ready after fs_open().
+ */
+#if !defined LWIP_HTTPD_DYNAMIC_FILE_READ || defined __DOXYGEN__
+#define LWIP_HTTPD_DYNAMIC_FILE_READ  0
+#endif
+
+/** Set this to 1 to include an application state argument per file
+ * that is opened. This allows to keep a state per connection/file.
+ */
+#if !defined LWIP_HTTPD_FILE_STATE || defined __DOXYGEN__
+#define LWIP_HTTPD_FILE_STATE         0
+#endif
+
+/** HTTPD_PRECALCULATED_CHECKSUM==1: include precompiled checksums for
+ * predefined (MSS-sized) chunks of the files to prevent having to calculate
+ * the checksums at runtime. */
+#if !defined HTTPD_PRECALCULATED_CHECKSUM || defined __DOXYGEN__
+#define HTTPD_PRECALCULATED_CHECKSUM  0
+#endif
+
+/** LWIP_HTTPD_FS_ASYNC_READ==1: support asynchronous read operations
+ * (fs_read_async returns FS_READ_DELAYED and calls a callback when finished).
+ */
+#if !defined LWIP_HTTPD_FS_ASYNC_READ || defined __DOXYGEN__
+#define LWIP_HTTPD_FS_ASYNC_READ      0
+#endif
+
+/** Set this to 1 to include "fsdata_custom.c" instead of "fsdata.c" for the
+ * file system (to prevent changing the file included in CVS) */
+#if !defined HTTPD_USE_CUSTOM_FSDATA || defined __DOXYGEN__
+#define HTTPD_USE_CUSTOM_FSDATA 0
+#endif
+
+/**
+ * @}
+ */
+
+#endif /* LWIP_HDR_APPS_HTTPD_OPTS_H */
diff --git a/src/include/lwip/apps/lwiperf.h b/src/include/lwip/apps/lwiperf.h
new file mode 100644
index 0000000..7dbebb0
--- /dev/null
+++ b/src/include/lwip/apps/lwiperf.h
@@ -0,0 +1,84 @@
+/**
+ * @file
+ * lwIP iPerf server implementation
+ */
+
+/*
+ * Copyright (c) 2014 Simon Goldschmidt
+ * 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 LWIP_HDR_APPS_LWIPERF_H
+#define LWIP_HDR_APPS_LWIPERF_H
+
+#include "lwip/opt.h"
+#include "lwip/ip_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LWIPERF_TCP_PORT_DEFAULT  5001
+
+/** lwIPerf test results */
+enum lwiperf_report_type
+{
+  /** The server side test is done */
+  LWIPERF_TCP_DONE_SERVER,
+  /** The client side test is done */
+  LWIPERF_TCP_DONE_CLIENT,
+  /** Local error lead to test abort */
+  LWIPERF_TCP_ABORTED_LOCAL,
+  /** Data check error lead to test abort */
+  LWIPERF_TCP_ABORTED_LOCAL_DATAERROR,
+  /** Transmit error lead to test abort */
+  LWIPERF_TCP_ABORTED_LOCAL_TXERROR,
+  /** Remote side aborted the test */
+  LWIPERF_TCP_ABORTED_REMOTE
+};
+
+/** Prototype of a report function that is called when a session is finished.
+    This report function can show the test results.
+    @param report_type contains the test result */
+typedef void (*lwiperf_report_fn)(void *arg, enum lwiperf_report_type report_type,
+  const ip_addr_t* local_addr, u16_t local_port, const ip_addr_t* remote_addr, u16_t remote_port,
+  u32_t bytes_transferred, u32_t ms_duration, u32_t bandwidth_kbitpsec);
+
+
+void* lwiperf_start_tcp_server(const ip_addr_t* local_addr, u16_t local_port,
+                               lwiperf_report_fn report_fn, void* report_arg);
+void* lwiperf_start_tcp_server_default(lwiperf_report_fn report_fn, void* report_arg);
+void  lwiperf_abort(void* lwiperf_session);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_LWIPERF_H */
diff --git a/src/include/lwip/apps/mdns.h b/src/include/lwip/apps/mdns.h
new file mode 100644
index 0000000..d036816
--- /dev/null
+++ b/src/include/lwip/apps/mdns.h
@@ -0,0 +1,69 @@
+/**
+ * @file
+ * MDNS responder
+ */
+
+ /*
+ * Copyright (c) 2015 Verisure Innovation AB
+ * 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: Erik Ekman <erik@kryo.se>
+ *
+ */
+#ifndef LWIP_HDR_MDNS_H
+#define LWIP_HDR_MDNS_H
+
+#include "lwip/apps/mdns_opts.h"
+#include "lwip/netif.h"
+
+#if LWIP_MDNS_RESPONDER
+
+enum mdns_sd_proto {
+  DNSSD_PROTO_UDP = 0,
+  DNSSD_PROTO_TCP = 1
+};
+
+#define MDNS_LABEL_MAXLEN  63
+
+struct mdns_host;
+struct mdns_service;
+
+/** Callback function to add text to a reply, called when generating the reply */
+typedef void (*service_get_txt_fn_t)(struct mdns_service *service, void *txt_userdata);
+
+void mdns_resp_init(void);
+
+err_t mdns_resp_add_netif(struct netif *netif, const char *hostname, u32_t dns_ttl);
+err_t mdns_resp_remove_netif(struct netif *netif);
+
+err_t mdns_resp_add_service(struct netif *netif, const char *name, const char *service, enum mdns_sd_proto proto, u16_t port, u32_t dns_ttl, service_get_txt_fn_t txt_fn, void *txt_userdata);
+err_t mdns_resp_add_service_txtitem(struct mdns_service *service, const char *txt, u8_t txt_len);
+void mdns_resp_netif_settings_changed(struct netif *netif);
+
+#endif /* LWIP_MDNS_RESPONDER */
+
+#endif /* LWIP_HDR_MDNS_H */
diff --git a/src/include/lwip/apps/mdns_opts.h b/src/include/lwip/apps/mdns_opts.h
new file mode 100644
index 0000000..bf186bc
--- /dev/null
+++ b/src/include/lwip/apps/mdns_opts.h
@@ -0,0 +1,74 @@
+/**
+ * @file
+ * MDNS responder
+ */
+
+ /*
+ * Copyright (c) 2015 Verisure Innovation AB
+ * 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: Erik Ekman <erik@kryo.se>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_MDNS_OPTS_H
+#define LWIP_HDR_APPS_MDNS_OPTS_H
+
+#include "lwip/opt.h"
+
+/**
+ * @defgroup mdns_opts Options
+ * @ingroup mdns
+ * @{
+ */
+
+/**
+ * LWIP_MDNS_RESPONDER==1: Turn on multicast DNS module. UDP must be available for MDNS
+ * transport. IGMP is needed for IPv4 multicast.
+ */
+#ifndef LWIP_MDNS_RESPONDER
+#define LWIP_MDNS_RESPONDER             0
+#endif /* LWIP_MDNS_RESPONDER */
+
+/** The maximum number of services per netif */
+#ifndef MDNS_MAX_SERVICES
+#define MDNS_MAX_SERVICES               1
+#endif
+
+/**
+ * MDNS_DEBUG: Enable debugging for multicast DNS.
+ */
+#ifndef MDNS_DEBUG
+#define MDNS_DEBUG                       LWIP_DBG_OFF
+#endif
+
+/**
+ * @}
+ */
+
+#endif /* LWIP_HDR_APPS_MDNS_OPTS_H */
+
diff --git a/src/include/lwip/apps/mdns_priv.h b/src/include/lwip/apps/mdns_priv.h
new file mode 100644
index 0000000..8ee6db8
--- /dev/null
+++ b/src/include/lwip/apps/mdns_priv.h
@@ -0,0 +1,66 @@
+/**
+ * @file
+ * MDNS responder private definitions
+ */
+
+ /*
+ * Copyright (c) 2015 Verisure Innovation AB
+ * 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: Erik Ekman <erik@kryo.se>
+ *
+ */
+#ifndef LWIP_HDR_MDNS_PRIV_H
+#define LWIP_HDR_MDNS_PRIV_H
+
+#include "lwip/apps/mdns_opts.h"
+#include "lwip/pbuf.h"
+
+#if LWIP_MDNS_RESPONDER
+
+/* Domain struct and methods - visible for unit tests */
+
+#define MDNS_DOMAIN_MAXLEN 256
+#define MDNS_READNAME_ERROR 0xFFFF
+
+struct mdns_domain {
+  /* Encoded domain name */
+  u8_t name[MDNS_DOMAIN_MAXLEN];
+  /* Total length of domain name, including zero */
+  u16_t length;
+  /* Set if compression of this domain is not allowed */
+  u8_t skip_compression;
+};
+
+err_t mdns_domain_add_label(struct mdns_domain *domain, const char *label, u8_t len);
+u16_t mdns_readname(struct pbuf *p, u16_t offset, struct mdns_domain *domain);
+int mdns_domain_eq(struct mdns_domain *a, struct mdns_domain *b);
+u16_t mdns_compress_domain(struct pbuf *pbuf, u16_t *offset, struct mdns_domain *domain);
+
+#endif /* LWIP_MDNS_RESPONDER */
+
+#endif /* LWIP_HDR_MDNS_PRIV_H */
diff --git a/src/include/lwip/apps/mqtt.h b/src/include/lwip/apps/mqtt.h
new file mode 100644
index 0000000..34b230b
--- /dev/null
+++ b/src/include/lwip/apps/mqtt.h
@@ -0,0 +1,244 @@
+/**
+ * @file
+ * MQTT client
+ */
+
+/*
+ * Copyright (c) 2016 Erik Andersson
+ * 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: Erik Andersson
+ *
+ */
+#ifndef LWIP_HDR_APPS_MQTT_CLIENT_H
+#define LWIP_HDR_APPS_MQTT_CLIENT_H
+
+#include "lwip/apps/mqtt_opts.h"
+#include "lwip/err.h"
+#include "lwip/ip_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct mqtt_client_t mqtt_client_t;
+
+/** @ingroup mqtt
+ * Default MQTT port */
+#define MQTT_PORT 1883
+
+/*---------------------------------------------------------------------------------------------- */
+/* Connection with server */
+
+/**
+ * @ingroup mqtt
+ * Client information and connection parameters */
+struct mqtt_connect_client_info_t {
+  /** Client identifier, must be set by caller */
+  const char *client_id;
+  /** User name and password, set to NULL if not used */
+  const char* client_user;
+  const char* client_pass;
+  /** keep alive time in seconds, 0 to disable keep alive functionality*/
+  u16_t keep_alive;
+  /** will topic, set to NULL if will is not to be used,
+      will_msg, will_qos and will retain are then ignored */
+  const char* will_topic;
+  const char* will_msg;
+  u8_t will_qos;
+  u8_t will_retain;
+};
+
+/**
+ * @ingroup mqtt
+ * Connection status codes */
+typedef enum
+{
+  MQTT_CONNECT_ACCEPTED                 = 0,
+  MQTT_CONNECT_REFUSED_PROTOCOL_VERSION = 1,
+  MQTT_CONNECT_REFUSED_IDENTIFIER       = 2,
+  MQTT_CONNECT_REFUSED_SERVER           = 3,
+  MQTT_CONNECT_REFUSED_USERNAME_PASS    = 4,
+  MQTT_CONNECT_REFUSED_NOT_AUTHORIZED_  = 5,
+  MQTT_CONNECT_DISCONNECTED             = 256,
+  MQTT_CONNECT_TIMEOUT                  = 257
+} mqtt_connection_status_t;
+
+/**
+ * @ingroup mqtt
+ * Function prototype for mqtt connection status callback. Called when
+ * client has connected to the server after initiating a mqtt connection attempt by
+ * calling mqtt_connect() or when connection is closed by server or an error
+ *
+ * @param client MQTT client itself
+ * @param arg Additional argument to pass to the callback function
+ * @param status Connect result code or disconnection notification @see mqtt_connection_status_t
+ *
+ */
+typedef void (*mqtt_connection_cb_t)(mqtt_client_t *client, void *arg, mqtt_connection_status_t status);
+
+
+/**
+ * @ingroup mqtt
+ * Data callback flags */
+enum {
+  /** Flag set when last fragment of data arrives in data callback */
+  MQTT_DATA_FLAG_LAST = 1
+};
+
+/** 
+ * @ingroup mqtt
+ * Function prototype for MQTT incoming publish data callback function. Called when data
+ * arrives to a subscribed topic @see mqtt_subscribe
+ *
+ * @param arg Additional argument to pass to the callback function
+ * @param data User data, pointed object, data may not be referenced after callback return,
+          NULL is passed when all publish data are delivered
+ * @param len Length of publish data fragment
+ * @param flags MQTT_DATA_FLAG_LAST set when this call contains the last part of data from publish message
+ *
+ */
+typedef void (*mqtt_incoming_data_cb_t)(void *arg, const u8_t *data, u16_t len, u8_t flags);
+
+
+/** 
+ * @ingroup mqtt
+ * Function prototype for MQTT incoming publish function. Called when an incoming publish
+ * arrives to a subscribed topic @see mqtt_subscribe
+ *
+ * @param arg Additional argument to pass to the callback function
+ * @param topic Zero terminated Topic text string, topic may not be referenced after callback return
+ * @param tot_len Total length of publish data, if set to 0 (no publish payload) data callback will not be invoked
+ */
+typedef void (*mqtt_incoming_publish_cb_t)(void *arg, const char *topic, u32_t tot_len);
+
+
+/**
+ * @ingroup mqtt
+ * Function prototype for mqtt request callback. Called when a subscribe, unsubscribe
+ * or publish request has completed
+ * @param arg Pointer to user data supplied when invoking request
+ * @param err ERR_OK on success
+ *            ERR_TIMEOUT if no response was received within timeout,
+ *            ERR_ABRT if (un)subscribe was denied
+ */
+typedef void (*mqtt_request_cb_t)(void *arg, err_t err);
+
+
+/**
+ * Pending request item, binds application callback to pending server requests
+ */
+struct mqtt_request_t
+{
+  /** Next item in list, NULL means this is the last in chain,
+      next pointing at itself means request is unallocated */
+  struct mqtt_request_t *next;
+  /** Callback to upper layer */
+  mqtt_request_cb_t cb;
+  void *arg;
+  /** MQTT packet identifier */
+  u16_t pkt_id;
+  /** Expire time relative to element before this  */
+  u16_t timeout_diff;
+};
+
+/** Ring buffer */
+struct mqtt_ringbuf_t {
+  u16_t put;
+  u16_t get;
+  u8_t buf[MQTT_OUTPUT_RINGBUF_SIZE];
+};
+
+/** MQTT client */
+struct mqtt_client_t
+{
+  /** Timers and timeouts */
+  u16_t cyclic_tick;
+  u16_t keep_alive;
+  u16_t server_watchdog;
+  /** Packet identifier generator*/
+  u16_t pkt_id_seq;
+  /** Packet identifier of pending incoming publish */
+  u16_t inpub_pkt_id;
+  /** Connection state */
+  u8_t conn_state;
+  struct tcp_pcb *conn;
+  /** Connection callback */
+  void *connect_arg;
+  mqtt_connection_cb_t connect_cb;
+  /** Pending requests to server */
+  struct mqtt_request_t *pend_req_queue;
+  struct mqtt_request_t req_list[MQTT_REQ_MAX_IN_FLIGHT];
+  void *inpub_arg;
+  /** Incoming data callback */
+  mqtt_incoming_data_cb_t data_cb;
+  mqtt_incoming_publish_cb_t pub_cb;
+  /** Input */
+  u32_t msg_idx;
+  u8_t rx_buffer[MQTT_VAR_HEADER_BUFFER_LEN];
+  /** Output ring-buffer */
+  struct mqtt_ringbuf_t output;
+};
+
+
+/** Connect to server */
+err_t mqtt_client_connect(mqtt_client_t *client, const ip_addr_t *ipaddr, u16_t port, mqtt_connection_cb_t cb, void *arg,
+                   const struct mqtt_connect_client_info_t *client_info);
+
+/** Disconnect from server */
+void mqtt_disconnect(mqtt_client_t *client);
+
+/** Create new client */
+mqtt_client_t *mqtt_client_new(void);
+
+/** Check connection status */
+u8_t mqtt_client_is_connected(mqtt_client_t *client);
+
+/** Set callback to call for incoming publish */
+void mqtt_set_inpub_callback(mqtt_client_t *client, mqtt_incoming_publish_cb_t,
+                             mqtt_incoming_data_cb_t data_cb, void *arg);
+
+/** Common function for subscribe and unsubscribe */
+err_t mqtt_sub_unsub(mqtt_client_t *client, const char *topic, u8_t qos, mqtt_request_cb_t cb, void *arg, u8_t sub);
+
+/** @ingroup mqtt
+ *Subscribe to topic */
+#define mqtt_subscribe(client, topic, qos, cb, arg) mqtt_sub_unsub(client, topic, qos, cb, arg, 1)
+/** @ingroup mqtt
+ *  Unsubscribe to topic */
+#define mqtt_unsubscribe(client, topic, cb, arg) mqtt_sub_unsub(client, topic, 0, cb, arg, 0)
+
+
+/** Publish data to topic */
+err_t mqtt_publish(mqtt_client_t *client, const char *topic, const void *payload, u16_t payload_length, u8_t qos, u8_t retain,
+                                    mqtt_request_cb_t cb, void *arg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_MQTT_CLIENT_H */
diff --git a/src/include/lwip/apps/mqtt_opts.h b/src/include/lwip/apps/mqtt_opts.h
new file mode 100644
index 0000000..ffefacd
--- /dev/null
+++ b/src/include/lwip/apps/mqtt_opts.h
@@ -0,0 +1,103 @@
+/**
+ * @file
+ * MQTT client options
+ */
+
+/*
+ * Copyright (c) 2016 Erik Andersson
+ * 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: Erik Andersson
+ *
+ */
+#ifndef LWIP_HDR_APPS_MQTT_OPTS_H
+#define LWIP_HDR_APPS_MQTT_OPTS_H
+
+#include "lwip/opt.h"
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup mqtt_opts Options
+ * @ingroup mqtt
+ * @{
+ */
+
+/**
+ * Output ring-buffer size, must be able to fit largest outgoing publish message topic+payloads
+ */
+#ifndef MQTT_OUTPUT_RINGBUF_SIZE
+#define MQTT_OUTPUT_RINGBUF_SIZE 256
+#endif
+
+/**
+ * Number of bytes in receive buffer, must be at least the size of the longest incoming topic + 8
+ * If one wants to avoid fragmented incoming publish, set length to max incoming topic length + max payload length + 8
+ */
+#ifndef MQTT_VAR_HEADER_BUFFER_LEN
+#define MQTT_VAR_HEADER_BUFFER_LEN 128
+#endif
+
+/**
+ * Maximum number of pending subscribe, unsubscribe and publish requests to server .
+ */
+#ifndef MQTT_REQ_MAX_IN_FLIGHT
+#define MQTT_REQ_MAX_IN_FLIGHT 4
+#endif
+
+/**
+ * Seconds between each cyclic timer call.
+ */
+#ifndef MQTT_CYCLIC_TIMER_INTERVAL
+#define MQTT_CYCLIC_TIMER_INTERVAL 5
+#endif
+
+/**
+ * Publish, subscribe and unsubscribe request timeout in seconds.
+ */
+#ifndef MQTT_REQ_TIMEOUT
+#define MQTT_REQ_TIMEOUT 30
+#endif
+
+/**
+ * Seconds for MQTT connect response timeout after sending connect request
+ */
+#ifndef MQTT_CONNECT_TIMOUT
+#define MQTT_CONNECT_TIMOUT 100
+#endif
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_MQTT_OPTS_H */
diff --git a/src/include/lwip/apps/netbiosns.h b/src/include/lwip/apps/netbiosns.h
new file mode 100644
index 0000000..c9f68d8
--- /dev/null
+++ b/src/include/lwip/apps/netbiosns.h
@@ -0,0 +1,43 @@
+/**
+ * @file
+ * NETBIOS name service responder
+ */
+
+/*
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce 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_HDR_APPS_NETBIOS_H
+#define LWIP_HDR_APPS_NETBIOS_H
+
+#include "lwip/apps/netbiosns_opts.h"
+
+void netbiosns_init(void);
+#ifndef NETBIOS_LWIP_NAME
+void netbiosns_set_name(const char* hostname);
+#endif
+void netbiosns_stop(void);
+
+#endif /* LWIP_HDR_APPS_NETBIOS_H */
diff --git a/src/include/lwip/apps/netbiosns_opts.h b/src/include/lwip/apps/netbiosns_opts.h
new file mode 100644
index 0000000..0909ef7
--- /dev/null
+++ b/src/include/lwip/apps/netbiosns_opts.h
@@ -0,0 +1,59 @@
+/**
+ * @file
+ * NETBIOS name service responder options
+ */
+
+/*
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce 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_HDR_APPS_NETBIOS_OPTS_H
+#define LWIP_HDR_APPS_NETBIOS_OPTS_H
+
+#include "lwip/opt.h"
+
+/**
+ * @defgroup netbiosns_opts Options
+ * @ingroup netbiosns
+ * @{
+ */
+
+/** NetBIOS name of lwip device
+ * This must be uppercase until NETBIOS_STRCMP() is defined to a string
+ * comparision function that is case insensitive.
+ * If you want to use the netif's hostname, use this (with LWIP_NETIF_HOSTNAME):
+ * (ip_current_netif() != NULL ? ip_current_netif()->hostname != NULL ? ip_current_netif()->hostname : "" : "")
+ *
+ * If this is not defined, netbiosns_set_name() can be called at runtime to change the name.
+ */
+#ifdef __DOXYGEN__
+#define NETBIOS_LWIP_NAME "NETBIOSLWIPDEV"
+#endif
+
+/**
+ * @}
+ */
+
+#endif /* LWIP_HDR_APPS_NETBIOS_OPTS_H */
diff --git a/src/include/lwip/apps/snmp.h b/src/include/lwip/apps/snmp.h
new file mode 100644
index 0000000..10e8ff4
--- /dev/null
+++ b/src/include/lwip/apps/snmp.h
@@ -0,0 +1,128 @@
+/**
+ * @file
+ * SNMP server main API - start and basic configuration
+ */
+
+/*
+ * Copyright (c) 2001, 2002 Leon Woestenberg <leon.woestenberg@axon.tv>
+ * 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@axon.tv>
+ *         Martin Hentschel <info@cl-soft.de>
+ *
+ */
+#ifndef LWIP_HDR_APPS_SNMP_H
+#define LWIP_HDR_APPS_SNMP_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/err.h"
+#include "lwip/apps/snmp_core.h"
+
+/** SNMP variable binding descriptor (publically needed for traps) */
+struct snmp_varbind
+{
+  /** pointer to next varbind, NULL for last in list */
+  struct snmp_varbind *next;
+  /** pointer to previous varbind, NULL for first in list */
+  struct snmp_varbind *prev;
+
+  /** object identifier */
+  struct snmp_obj_id oid;
+
+  /** value ASN1 type */
+  u8_t type;
+  /** object value length */
+  u16_t value_len;
+  /** object value */
+  void *value;
+};
+
+/**
+ * @ingroup snmp_core
+ * Agent setup, start listening to port 161.
+ */
+void snmp_init(void);
+void snmp_set_mibs(const struct snmp_mib **mibs, u8_t num_mibs);
+
+void snmp_set_device_enterprise_oid(const struct snmp_obj_id* device_enterprise_oid);
+const struct snmp_obj_id* snmp_get_device_enterprise_oid(void);
+
+void snmp_trap_dst_enable(u8_t dst_idx, u8_t enable);
+void snmp_trap_dst_ip_set(u8_t dst_idx, const ip_addr_t *dst);
+
+/** Generic trap: cold start */
+#define SNMP_GENTRAP_COLDSTART 0
+/** Generic trap: warm start */
+#define SNMP_GENTRAP_WARMSTART 1
+/** Generic trap: link down */
+#define SNMP_GENTRAP_LINKDOWN 2
+/** Generic trap: link up */
+#define SNMP_GENTRAP_LINKUP 3
+/** Generic trap: authentication failure */
+#define SNMP_GENTRAP_AUTH_FAILURE 4
+/** Generic trap: EGP neighbor lost */
+#define SNMP_GENTRAP_EGP_NEIGHBOR_LOSS 5
+/** Generic trap: enterprise specific */
+#define SNMP_GENTRAP_ENTERPRISE_SPECIFIC 6
+
+err_t snmp_send_trap_generic(s32_t generic_trap);
+err_t snmp_send_trap_specific(s32_t specific_trap, struct snmp_varbind *varbinds);
+err_t snmp_send_trap(const struct snmp_obj_id* oid, s32_t generic_trap, s32_t specific_trap, struct snmp_varbind *varbinds);
+
+#define SNMP_AUTH_TRAPS_DISABLED 0
+#define SNMP_AUTH_TRAPS_ENABLED  1
+void snmp_set_auth_traps_enabled(u8_t enable);
+u8_t snmp_get_auth_traps_enabled(void);
+
+const char * snmp_get_community(void);
+const char * snmp_get_community_write(void);
+const char * snmp_get_community_trap(void);
+void snmp_set_community(const char * const community);
+void snmp_set_community_write(const char * const community);
+void snmp_set_community_trap(const char * const community);
+
+void snmp_coldstart_trap(void);
+void snmp_authfail_trap(void);
+
+typedef void (*snmp_write_callback_fct)(const u32_t* oid, u8_t oid_len, void* callback_arg);
+void snmp_set_write_callback(snmp_write_callback_fct write_callback, void* callback_arg);
+
+#endif /* LWIP_SNMP */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_SNMP_H */
diff --git a/src/include/lwip/apps/snmp_core.h b/src/include/lwip/apps/snmp_core.h
new file mode 100644
index 0000000..e781c53
--- /dev/null
+++ b/src/include/lwip/apps/snmp_core.h
@@ -0,0 +1,364 @@
+/**
+ * @file
+ * SNMP core API for implementing MIBs
+ */
+
+/*
+ * 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@axon.tv>
+ *         Martin Hentschel <info@cl-soft.de>
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_CORE_H
+#define LWIP_HDR_APPS_SNMP_CORE_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/ip_addr.h"
+#include "lwip/err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* basic ASN1 defines */
+#define SNMP_ASN1_CLASS_UNIVERSAL   0x00
+#define SNMP_ASN1_CLASS_APPLICATION 0x40
+#define SNMP_ASN1_CLASS_CONTEXT     0x80
+#define SNMP_ASN1_CLASS_PRIVATE     0xC0
+
+#define SNMP_ASN1_CONTENTTYPE_PRIMITIVE   0x00
+#define SNMP_ASN1_CONTENTTYPE_CONSTRUCTED 0x20
+
+/* universal tags (from ASN.1 spec.) */
+#define SNMP_ASN1_UNIVERSAL_END_OF_CONTENT  0
+#define SNMP_ASN1_UNIVERSAL_INTEGER         2
+#define SNMP_ASN1_UNIVERSAL_OCTET_STRING    4
+#define SNMP_ASN1_UNIVERSAL_NULL            5
+#define SNMP_ASN1_UNIVERSAL_OBJECT_ID       6
+#define SNMP_ASN1_UNIVERSAL_SEQUENCE_OF    16
+
+/* application specific (SNMP) tags (from SNMPv2-SMI) */
+#define SNMP_ASN1_APPLICATION_IPADDR    0  /* [APPLICATION 0] IMPLICIT OCTET STRING (SIZE (4)) */
+#define SNMP_ASN1_APPLICATION_COUNTER   1  /* [APPLICATION 1] IMPLICIT INTEGER (0..4294967295) => u32_t */
+#define SNMP_ASN1_APPLICATION_GAUGE     2  /* [APPLICATION 2] IMPLICIT INTEGER (0..4294967295) => u32_t */
+#define SNMP_ASN1_APPLICATION_TIMETICKS 3  /* [APPLICATION 3] IMPLICIT INTEGER (0..4294967295) => u32_t */
+#define SNMP_ASN1_APPLICATION_OPAQUE    4  /* [APPLICATION 4] IMPLICIT OCTET STRING */
+#define SNMP_ASN1_APPLICATION_COUNTER64 6  /* [APPLICATION 6] IMPLICIT INTEGER (0..18446744073709551615) */
+
+/* context specific (SNMP) tags (from RFC 1905) */
+#define SNMP_ASN1_CONTEXT_VARBIND_NO_SUCH_INSTANCE 1
+
+/* full ASN1 type defines */
+#define SNMP_ASN1_TYPE_END_OF_CONTENT (SNMP_ASN1_CLASS_UNIVERSAL | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_UNIVERSAL_END_OF_CONTENT)
+#define SNMP_ASN1_TYPE_INTEGER        (SNMP_ASN1_CLASS_UNIVERSAL | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_UNIVERSAL_INTEGER)
+#define SNMP_ASN1_TYPE_OCTET_STRING   (SNMP_ASN1_CLASS_UNIVERSAL | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_UNIVERSAL_OCTET_STRING)
+#define SNMP_ASN1_TYPE_NULL           (SNMP_ASN1_CLASS_UNIVERSAL | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_UNIVERSAL_NULL)
+#define SNMP_ASN1_TYPE_OBJECT_ID      (SNMP_ASN1_CLASS_UNIVERSAL | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_UNIVERSAL_OBJECT_ID)
+#define SNMP_ASN1_TYPE_SEQUENCE       (SNMP_ASN1_CLASS_UNIVERSAL | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_UNIVERSAL_SEQUENCE_OF)
+#define SNMP_ASN1_TYPE_IPADDR         (SNMP_ASN1_CLASS_APPLICATION | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_APPLICATION_IPADDR)
+#define SNMP_ASN1_TYPE_IPADDRESS      SNMP_ASN1_TYPE_IPADDR
+#define SNMP_ASN1_TYPE_COUNTER        (SNMP_ASN1_CLASS_APPLICATION | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_APPLICATION_COUNTER)
+#define SNMP_ASN1_TYPE_COUNTER32      SNMP_ASN1_TYPE_COUNTER
+#define SNMP_ASN1_TYPE_GAUGE          (SNMP_ASN1_CLASS_APPLICATION | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_APPLICATION_GAUGE)
+#define SNMP_ASN1_TYPE_GAUGE32        SNMP_ASN1_TYPE_GAUGE
+#define SNMP_ASN1_TYPE_UNSIGNED32     SNMP_ASN1_TYPE_GAUGE
+#define SNMP_ASN1_TYPE_TIMETICKS      (SNMP_ASN1_CLASS_APPLICATION | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_APPLICATION_TIMETICKS)
+#define SNMP_ASN1_TYPE_OPAQUE         (SNMP_ASN1_CLASS_APPLICATION | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_APPLICATION_OPAQUE)
+#define SNMP_ASN1_TYPE_COUNTER64      (SNMP_ASN1_CLASS_APPLICATION | SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_APPLICATION_COUNTER64)
+
+#define SNMP_VARBIND_EXCEPTION_OFFSET 0xF0
+#define SNMP_VARBIND_EXCEPTION_MASK   0x0F
+
+/** error codes predefined by SNMP prot. */
+typedef enum {
+  SNMP_ERR_NOERROR             = 0,
+/* 
+outdated v1 error codes. do not use anmore!
+#define SNMP_ERR_NOSUCHNAME 2  use SNMP_ERR_NOSUCHINSTANCE instead
+#define SNMP_ERR_BADVALUE   3  use SNMP_ERR_WRONGTYPE,SNMP_ERR_WRONGLENGTH,SNMP_ERR_WRONGENCODING or SNMP_ERR_WRONGVALUE instead
+#define SNMP_ERR_READONLY   4  use SNMP_ERR_NOTWRITABLE instead
+*/
+  SNMP_ERR_GENERROR            = 5,
+  SNMP_ERR_NOACCESS            = 6,
+  SNMP_ERR_WRONGTYPE           = 7,
+  SNMP_ERR_WRONGLENGTH         = 8,
+  SNMP_ERR_WRONGENCODING       = 9,
+  SNMP_ERR_WRONGVALUE          = 10,
+  SNMP_ERR_NOCREATION          = 11,
+  SNMP_ERR_INCONSISTENTVALUE   = 12,
+  SNMP_ERR_RESOURCEUNAVAILABLE = 13,
+  SNMP_ERR_COMMITFAILED        = 14,
+  SNMP_ERR_UNDOFAILED          = 15,
+  SNMP_ERR_NOTWRITABLE         = 17,
+  SNMP_ERR_INCONSISTENTNAME    = 18,
+
+  SNMP_ERR_NOSUCHINSTANCE      = SNMP_VARBIND_EXCEPTION_OFFSET + SNMP_ASN1_CONTEXT_VARBIND_NO_SUCH_INSTANCE
+} snmp_err_t;
+
+/** internal object identifier representation */
+struct snmp_obj_id
+{
+  u8_t len;
+  u32_t id[SNMP_MAX_OBJ_ID_LEN];
+};
+
+struct snmp_obj_id_const_ref
+{
+  u8_t len;
+  const u32_t* id;
+};
+
+extern const struct snmp_obj_id_const_ref snmp_zero_dot_zero; /* administrative identifier from SNMPv2-SMI */
+
+/** SNMP variant value, used as reference in struct snmp_node_instance and table implementation */
+union snmp_variant_value
+{
+  void* ptr;
+  const void* const_ptr;
+  u32_t u32;
+  s32_t s32;
+};
+
+
+/**
+SNMP MIB node types
+ tree node is the only node the stack can process in order to walk the tree,
+ all other nodes are assumed to be leaf nodes.
+ This cannot be an enum because users may want to define their own node types.
+*/
+#define SNMP_NODE_TREE         0x00
+/* predefined leaf node types */
+#define SNMP_NODE_SCALAR       0x01
+#define SNMP_NODE_SCALAR_ARRAY 0x02
+#define SNMP_NODE_TABLE        0x03
+#define SNMP_NODE_THREADSYNC   0x04
+
+/** node "base class" layout, the mandatory fields for a node  */
+struct snmp_node
+{
+  /** one out of SNMP_NODE_TREE or any leaf node type (like SNMP_NODE_SCALAR) */
+  u8_t node_type;
+  /** the number assigned to this node which used as part of the full OID */
+  u32_t oid;
+};
+
+/** SNMP node instance access types */
+typedef enum {
+  SNMP_NODE_INSTANCE_ACCESS_READ    = 1,
+  SNMP_NODE_INSTANCE_ACCESS_WRITE   = 2,
+  SNMP_NODE_INSTANCE_READ_ONLY      = SNMP_NODE_INSTANCE_ACCESS_READ,
+  SNMP_NODE_INSTANCE_READ_WRITE     = (SNMP_NODE_INSTANCE_ACCESS_READ | SNMP_NODE_INSTANCE_ACCESS_WRITE),
+  SNMP_NODE_INSTANCE_WRITE_ONLY     = SNMP_NODE_INSTANCE_ACCESS_WRITE,
+  SNMP_NODE_INSTANCE_NOT_ACCESSIBLE = 0
+} snmp_access_t;
+
+struct snmp_node_instance;
+
+typedef s16_t (*node_instance_get_value_method)(struct snmp_node_instance*, void*);
+typedef snmp_err_t (*node_instance_set_test_method)(struct snmp_node_instance*, u16_t, void*);
+typedef snmp_err_t (*node_instance_set_value_method)(struct snmp_node_instance*, u16_t, void*);
+typedef void (*node_instance_release_method)(struct snmp_node_instance*);
+
+#define SNMP_GET_VALUE_RAW_DATA 0x8000
+
+/** SNMP node instance */
+struct snmp_node_instance
+{
+  /** prefilled with the node, get_instance() is called on; may be changed by user to any value to pass an arbitrary node between calls to get_instance() and get_value/test_value/set_value */
+  const struct snmp_node* node;
+  /** prefilled with the instance id requested; for get_instance() this is the exact oid requested; for get_next_instance() this is the relative starting point, stack expects relative oid of next node here */
+  struct snmp_obj_id instance_oid;
+
+  /** ASN type for this object (see snmp_asn1.h for definitions) */
+  u8_t asn1_type;
+  /** one out of instance access types defined above (SNMP_NODE_INSTANCE_READ_ONLY,...) */
+  snmp_access_t access;
+
+  /** returns object value for the given object identifier. Return values <0 to indicate an error */
+  node_instance_get_value_method get_value;
+  /** tests length and/or range BEFORE setting */
+  node_instance_set_test_method set_test;
+  /** sets object value, only called when set_test() was successful */
+  node_instance_set_value_method set_value;
+  /** called in any case when the instance is not required anymore by stack (useful for freeing memory allocated in get_instance/get_next_instance methods) */
+  node_instance_release_method release_instance;
+
+  /** reference to pass arbitrary value between calls to get_instance() and get_value/test_value/set_value */
+  union snmp_variant_value reference;
+  /** see reference (if reference is a pointer, the length of underlying data may be stored here or anything else) */
+  u32_t reference_len;
+};
+
+
+/** SNMP tree node */
+struct snmp_tree_node
+{
+  /** inherited "base class" members */
+  struct snmp_node node;
+  u16_t subnode_count;
+  const struct snmp_node* const *subnodes;
+};
+
+#define SNMP_CREATE_TREE_NODE(oid, subnodes) \
+  {{ SNMP_NODE_TREE, (oid) }, \
+  (u16_t)LWIP_ARRAYSIZE(subnodes), (subnodes) }
+
+#define SNMP_CREATE_EMPTY_TREE_NODE(oid) \
+  {{ SNMP_NODE_TREE, (oid) }, \
+  0, NULL }
+
+/** SNMP leaf node */
+struct snmp_leaf_node
+{
+  /** inherited "base class" members */
+  struct snmp_node node;
+  snmp_err_t (*get_instance)(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+  snmp_err_t (*get_next_instance)(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+};
+
+/** represents a single mib with its base oid and root node */
+struct snmp_mib
+{
+  const u32_t *base_oid;
+  u8_t base_oid_len;
+  const struct snmp_node *root_node;
+};
+
+#define SNMP_MIB_CREATE(oid_list, root_node) { (oid_list), (u8_t)LWIP_ARRAYSIZE(oid_list), root_node }
+
+/** OID range structure */
+struct snmp_oid_range
+{
+  u32_t min;
+  u32_t max;
+};
+
+/** checks if incoming OID length and values are in allowed ranges */
+u8_t snmp_oid_in_range(const u32_t *oid_in, u8_t oid_len, const struct snmp_oid_range *oid_ranges, u8_t oid_ranges_len);
+
+typedef enum {
+  SNMP_NEXT_OID_STATUS_SUCCESS,
+  SNMP_NEXT_OID_STATUS_NO_MATCH,
+  SNMP_NEXT_OID_STATUS_BUF_TO_SMALL
+} snmp_next_oid_status_t;
+
+/** state for next_oid_init / next_oid_check functions */
+struct snmp_next_oid_state
+{
+  const u32_t* start_oid;
+  u8_t start_oid_len;
+
+  u32_t* next_oid;
+  u8_t next_oid_len;
+  u8_t next_oid_max_len;
+
+  snmp_next_oid_status_t status;
+  void* reference;
+};
+
+void snmp_next_oid_init(struct snmp_next_oid_state *state,
+  const u32_t *start_oid, u8_t start_oid_len,
+  u32_t *next_oid_buf, u8_t next_oid_max_len);
+u8_t snmp_next_oid_precheck(struct snmp_next_oid_state *state, const u32_t *oid, const u8_t oid_len);
+u8_t snmp_next_oid_check(struct snmp_next_oid_state *state, const u32_t *oid, const u8_t oid_len, void* reference);
+
+void snmp_oid_assign(struct snmp_obj_id* target, const u32_t *oid, u8_t oid_len);
+void snmp_oid_combine(struct snmp_obj_id* target, const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len);
+void snmp_oid_prefix(struct snmp_obj_id* target, const u32_t *oid, u8_t oid_len);
+void snmp_oid_append(struct snmp_obj_id* target, const u32_t *oid, u8_t oid_len);
+u8_t snmp_oid_equal(const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len);
+s8_t snmp_oid_compare(const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len);
+
+#if LWIP_IPV4
+u8_t snmp_oid_to_ip4(const u32_t *oid, ip4_addr_t *ip);
+void snmp_ip4_to_oid(const ip4_addr_t *ip, u32_t *oid);
+#endif /* LWIP_IPV4 */
+#if LWIP_IPV6
+u8_t snmp_oid_to_ip6(const u32_t *oid, ip6_addr_t *ip);
+void snmp_ip6_to_oid(const ip6_addr_t *ip, u32_t *oid);
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4 || LWIP_IPV6
+u8_t snmp_ip_to_oid(const ip_addr_t *ip, u32_t *oid);
+u8_t snmp_ip_port_to_oid(const ip_addr_t *ip, u16_t port, u32_t *oid);
+
+u8_t snmp_oid_to_ip(const u32_t *oid, u8_t oid_len, ip_addr_t *ip);
+u8_t snmp_oid_to_ip_port(const u32_t *oid, u8_t oid_len, ip_addr_t *ip, u16_t *port);
+#endif /* LWIP_IPV4 || LWIP_IPV6 */
+
+struct netif;
+u8_t netif_to_num(const struct netif *netif);
+
+snmp_err_t snmp_set_test_ok(struct snmp_node_instance* instance, u16_t value_len, void* value); /* generic function which can be used if test is always successful */
+
+err_t snmp_decode_bits(const u8_t *buf, u32_t buf_len, u32_t *bit_value);
+err_t snmp_decode_truthvalue(const s32_t *asn1_value, u8_t *bool_value);
+u8_t  snmp_encode_bits(u8_t *buf, u32_t buf_len, u32_t bit_value, u8_t bit_count);
+u8_t  snmp_encode_truthvalue(s32_t *asn1_value, u32_t bool_value);
+
+struct snmp_statistics
+{
+  u32_t inpkts;
+  u32_t outpkts;
+  u32_t inbadversions;
+  u32_t inbadcommunitynames;
+  u32_t inbadcommunityuses;
+  u32_t inasnparseerrs;
+  u32_t intoobigs;
+  u32_t innosuchnames;
+  u32_t inbadvalues;
+  u32_t inreadonlys;
+  u32_t ingenerrs;
+  u32_t intotalreqvars;
+  u32_t intotalsetvars;
+  u32_t ingetrequests;
+  u32_t ingetnexts;
+  u32_t insetrequests;
+  u32_t ingetresponses;
+  u32_t intraps;
+  u32_t outtoobigs;
+  u32_t outnosuchnames;
+  u32_t outbadvalues;
+  u32_t outgenerrs;
+  u32_t outgetrequests;
+  u32_t outgetnexts;
+  u32_t outsetrequests;
+  u32_t outgetresponses;
+  u32_t outtraps;
+};
+
+extern struct snmp_statistics snmp_stats;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* LWIP_HDR_APPS_SNMP_CORE_H */
diff --git a/src/include/lwip/apps/snmp_mib2.h b/src/include/lwip/apps/snmp_mib2.h
new file mode 100644
index 0000000..2f4a689
--- /dev/null
+++ b/src/include/lwip/apps/snmp_mib2.h
@@ -0,0 +1,78 @@
+/**
+ * @file
+ * SNMP MIB2 API
+ */
+
+/*
+ * 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *
+ */
+#ifndef LWIP_HDR_APPS_SNMP_MIB2_H
+#define LWIP_HDR_APPS_SNMP_MIB2_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+#if SNMP_LWIP_MIB2
+
+#include "lwip/apps/snmp_core.h"
+
+extern const struct snmp_mib mib2;
+
+#if SNMP_USE_NETCONN
+#include "lwip/apps/snmp_threadsync.h"
+void snmp_mib2_lwip_synchronizer(snmp_threadsync_called_fn fn, void* arg);
+extern struct snmp_threadsync_instance snmp_mib2_lwip_locks;
+#endif
+
+#ifndef SNMP_SYSSERVICES
+#define SNMP_SYSSERVICES ((1 << 6) | (1 << 3) | ((IP_FORWARD) << 2))
+#endif
+
+void snmp_mib2_set_sysdescr(const u8_t* str, const u16_t* len); /* read-only be defintion */
+void snmp_mib2_set_syscontact(u8_t *ocstr, u16_t *ocstrlen, u16_t bufsize);
+void snmp_mib2_set_syscontact_readonly(const u8_t *ocstr, const u16_t *ocstrlen);
+void snmp_mib2_set_sysname(u8_t *ocstr, u16_t *ocstrlen, u16_t bufsize);
+void snmp_mib2_set_sysname_readonly(const u8_t *ocstr, const u16_t *ocstrlen);
+void snmp_mib2_set_syslocation(u8_t *ocstr, u16_t *ocstrlen, u16_t bufsize);
+void snmp_mib2_set_syslocation_readonly(const u8_t *ocstr, const u16_t *ocstrlen);
+
+#endif /* SNMP_LWIP_MIB2 */
+#endif /* LWIP_SNMP */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_SNMP_MIB2_H */
diff --git a/src/include/lwip/apps/snmp_opts.h b/src/include/lwip/apps/snmp_opts.h
new file mode 100644
index 0000000..6c9ba7b
--- /dev/null
+++ b/src/include/lwip/apps/snmp_opts.h
@@ -0,0 +1,293 @@
+/**
+ * @file
+ * SNMP server options list
+ */
+
+/*
+ * Copyright (c) 2015 Dirk Ziegelmeier
+ * 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: Dirk Ziegelmeier
+ *
+ */
+#ifndef LWIP_HDR_SNMP_OPTS_H
+#define LWIP_HDR_SNMP_OPTS_H
+
+#include "lwip/opt.h"
+
+/**
+ * @defgroup snmp_opts Options
+ * @ingroup snmp
+ * @{
+ */
+
+/**
+ * LWIP_SNMP==1: This enables the lwIP SNMP agent. UDP must be available
+ * for SNMP transport.
+ * If you want to use your own SNMP agent, leave this disabled.
+ * To integrate MIB2 of an external agent, you need to enable
+ * LWIP_MIB2_CALLBACKS and MIB2_STATS. This will give you the callbacks
+ * and statistics counters you need to get MIB2 working.
+ */
+#if !defined LWIP_SNMP || defined __DOXYGEN__
+#define LWIP_SNMP                       0
+#endif
+
+/**
+ * SNMP_USE_NETCONN: Use netconn API instead of raw API.
+ * Makes SNMP agent run in a worker thread, so blocking operations
+ * can be done in MIB calls.
+ */
+#if !defined SNMP_USE_NETCONN || defined __DOXYGEN__
+#define SNMP_USE_NETCONN           0
+#endif
+
+/**
+ * SNMP_USE_RAW: Use raw API.
+ * SNMP agent does not run in a worker thread, so blocking operations
+ * should not be done in MIB calls.
+ */
+#if !defined SNMP_USE_RAW || defined __DOXYGEN__
+#define SNMP_USE_RAW               1
+#endif
+
+#if SNMP_USE_NETCONN && SNMP_USE_RAW
+#error SNMP stack can use only one of the APIs {raw, netconn}
+#endif
+
+#if LWIP_SNMP && !SNMP_USE_NETCONN && !SNMP_USE_RAW
+#error SNMP stack needs a receive API and UDP {raw, netconn}
+#endif
+
+#if SNMP_USE_NETCONN
+/**
+ * SNMP_STACK_SIZE: Stack size of SNMP netconn worker thread
+ */
+#if !defined SNMP_STACK_SIZE || defined __DOXYGEN__
+#define SNMP_STACK_SIZE            DEFAULT_THREAD_STACKSIZE
+#endif
+
+/**
+ * SNMP_THREAD_PRIO: SNMP netconn worker thread priority
+ */
+#if !defined SNMP_THREAD_PRIO || defined __DOXYGEN__
+#define SNMP_THREAD_PRIO           DEFAULT_THREAD_PRIO
+#endif
+#endif /* SNMP_USE_NETCONN */
+
+/**
+ * SNMP_TRAP_DESTINATIONS: Number of trap destinations. At least one trap
+ * destination is required
+ */
+#if !defined SNMP_TRAP_DESTINATIONS || defined __DOXYGEN__
+#define SNMP_TRAP_DESTINATIONS          1
+#endif
+
+/**
+ * Only allow SNMP write actions that are 'safe' (e.g. disabling netifs is not
+ * a safe action and disabled when SNMP_SAFE_REQUESTS = 1).
+ * Unsafe requests are disabled by default!
+ */
+#if !defined SNMP_SAFE_REQUESTS || defined __DOXYGEN__
+#define SNMP_SAFE_REQUESTS              1
+#endif
+
+/**
+ * The maximum length of strings used.
+ */
+#if !defined SNMP_MAX_OCTET_STRING_LEN || defined __DOXYGEN__
+#define SNMP_MAX_OCTET_STRING_LEN       127
+#endif
+
+/**
+ * The maximum number of Sub ID's inside an object identifier.
+ * Indirectly this also limits the maximum depth of SNMP tree.
+ */
+#if !defined SNMP_MAX_OBJ_ID_LEN || defined __DOXYGEN__
+#define SNMP_MAX_OBJ_ID_LEN             50
+#endif
+
+#if !defined SNMP_MAX_VALUE_SIZE || defined __DOXYGEN__
+/**
+ * The maximum size of a value.
+ */
+#define SNMP_MIN_VALUE_SIZE             (2 * sizeof(u32_t*)) /* size required to store the basic types (8 bytes for counter64) */
+/**
+ * The minimum size of a value.
+ */
+#define SNMP_MAX_VALUE_SIZE             LWIP_MAX(LWIP_MAX((SNMP_MAX_OCTET_STRING_LEN), sizeof(u32_t)*(SNMP_MAX_OBJ_ID_LEN)), SNMP_MIN_VALUE_SIZE)
+#endif
+
+/**
+ * The snmp read-access community. Used for write-access and traps, too
+ * unless SNMP_COMMUNITY_WRITE or SNMP_COMMUNITY_TRAP are enabled, respectively.
+ */
+#if !defined SNMP_COMMUNITY || defined __DOXYGEN__
+#define SNMP_COMMUNITY                  "public"
+#endif
+
+/**
+ * The snmp write-access community.
+ * Set this community to "" in order to disallow any write access.
+ */
+#if !defined SNMP_COMMUNITY_WRITE || defined __DOXYGEN__
+#define SNMP_COMMUNITY_WRITE            "private"
+#endif
+
+/**
+ * The snmp community used for sending traps.
+ */
+#if !defined SNMP_COMMUNITY_TRAP || defined __DOXYGEN__
+#define SNMP_COMMUNITY_TRAP             "public"
+#endif
+
+/**
+ * The maximum length of community string.
+ * If community names shall be adjusted at runtime via snmp_set_community() calls,
+ * enter here the possible maximum length (+1 for terminating null character).
+ */
+#if !defined SNMP_MAX_COMMUNITY_STR_LEN || defined __DOXYGEN__
+#define SNMP_MAX_COMMUNITY_STR_LEN LWIP_MAX(LWIP_MAX(sizeof(SNMP_COMMUNITY), sizeof(SNMP_COMMUNITY_WRITE)), sizeof(SNMP_COMMUNITY_TRAP))
+#endif
+
+/**
+ * The OID identifiying the device. This may be the enterprise OID itself or any OID located below it in tree.
+ */
+#if !defined SNMP_DEVICE_ENTERPRISE_OID || defined __DOXYGEN__
+#define SNMP_LWIP_ENTERPRISE_OID 26381
+/**
+ * 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!
+ * @note don't change this define, use snmp_set_device_enterprise_oid()
+ *
+ * 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_DEVICE_ENTERPRISE_OID {1, 3, 6, 1, 4, 1, SNMP_LWIP_ENTERPRISE_OID}
+/**
+ * Length of SNMP_DEVICE_ENTERPRISE_OID
+ */
+#define SNMP_DEVICE_ENTERPRISE_OID_LEN 7
+#endif
+
+/**
+ * SNMP_DEBUG: Enable debugging for SNMP messages.
+ */
+#if !defined SNMP_DEBUG || defined __DOXYGEN__
+#define SNMP_DEBUG                      LWIP_DBG_OFF
+#endif
+
+/**
+ * SNMP_MIB_DEBUG: Enable debugging for SNMP MIBs.
+ */
+#if !defined SNMP_MIB_DEBUG || defined __DOXYGEN__
+#define SNMP_MIB_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/**
+ * Indicates if the MIB2 implementation of LWIP SNMP stack is used.
+ */
+#if !defined SNMP_LWIP_MIB2 || defined __DOXYGEN__
+#define SNMP_LWIP_MIB2                      LWIP_SNMP
+#endif
+
+/**
+ * Value return for sysDesc field of MIB2.
+ */
+#if !defined SNMP_LWIP_MIB2_SYSDESC || defined __DOXYGEN__
+#define SNMP_LWIP_MIB2_SYSDESC              "lwIP"
+#endif
+
+/**
+ * Value return for sysName field of MIB2.
+ * To make sysName field settable, call snmp_mib2_set_sysname() to provide the necessary buffers.
+ */
+#if !defined SNMP_LWIP_MIB2_SYSNAME || defined __DOXYGEN__
+#define SNMP_LWIP_MIB2_SYSNAME              "FQDN-unk"
+#endif
+
+/**
+ * Value return for sysContact field of MIB2.
+ * To make sysContact field settable, call snmp_mib2_set_syscontact() to provide the necessary buffers.
+ */
+#if !defined SNMP_LWIP_MIB2_SYSCONTACT || defined __DOXYGEN__
+#define SNMP_LWIP_MIB2_SYSCONTACT           ""
+#endif
+
+/**
+ * Value return for sysLocation field of MIB2.
+ * To make sysLocation field settable, call snmp_mib2_set_syslocation() to provide the necessary buffers.
+ */
+#if !defined SNMP_LWIP_MIB2_SYSLOCATION || defined __DOXYGEN__
+#define SNMP_LWIP_MIB2_SYSLOCATION          ""
+#endif
+
+/**
+ * This value is used to limit the repetitions processed in GetBulk requests (value == 0 means no limitation).
+ * This may be useful to limit the load for a single request.
+ * According to SNMP RFC 1905 it is allowed to not return all requested variables from a GetBulk request if system load would be too high.
+ * so the effect is that the client will do more requests to gather all data.
+ * For the stack this could be useful in case that SNMP processing is done in TCP/IP thread. In this situation a request with many
+ * repetitions could block the thread for a longer time. Setting limit here will keep the stack more responsive.
+ */
+#if !defined SNMP_LWIP_GETBULK_MAX_REPETITIONS || defined __DOXYGEN__
+#define SNMP_LWIP_GETBULK_MAX_REPETITIONS 0
+#endif
+
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- SNMPv3 options ----------
+   ------------------------------------
+*/
+
+/**
+ * LWIP_SNMP_V3==1: This enables EXPERIMENTAL SNMPv3 support. LWIP_SNMP must
+ * also be enabled.
+ * THIS IS UNDER DEVELOPMENT AND SHOULD NOT BE ENABLED IN PRODUCTS.
+ */
+#ifndef LWIP_SNMP_V3
+#define LWIP_SNMP_V3               0
+#endif
+
+#ifndef LWIP_SNMP_V3_CRYPTO
+#define LWIP_SNMP_V3_CRYPTO        LWIP_SNMP_V3
+#endif
+
+#ifndef LWIP_SNMP_V3_MBEDTLS
+#define LWIP_SNMP_V3_MBEDTLS       LWIP_SNMP_V3
+#endif
+
+#endif /* LWIP_HDR_SNMP_OPTS_H */
diff --git a/src/include/lwip/apps/snmp_scalar.h b/src/include/lwip/apps/snmp_scalar.h
new file mode 100644
index 0000000..40a060c
--- /dev/null
+++ b/src/include/lwip/apps/snmp_scalar.h
@@ -0,0 +1,113 @@
+/**
+ * @file
+ * SNMP server MIB API to implement scalar nodes
+ */
+
+/*
+ * 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: Martin Hentschel <info@cl-soft.de>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_SCALAR_H
+#define LWIP_HDR_APPS_SNMP_SCALAR_H
+
+#include "lwip/apps/snmp_opts.h"
+#include "lwip/apps/snmp_core.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+/** basic scalar node */
+struct snmp_scalar_node
+{
+  /** inherited "base class" members */
+  struct snmp_leaf_node node;
+  u8_t asn1_type;
+  snmp_access_t access;
+  node_instance_get_value_method get_value;
+  node_instance_set_test_method set_test;
+  node_instance_set_value_method set_value;
+};
+
+
+snmp_err_t snmp_scalar_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+snmp_err_t snmp_scalar_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+
+#define SNMP_SCALAR_CREATE_NODE(oid, access, asn1_type, get_value_method, set_test_method, set_value_method) \
+  {{{ SNMP_NODE_SCALAR, (oid) }, \
+    snmp_scalar_get_instance, \
+    snmp_scalar_get_next_instance }, \
+    (asn1_type), (access), (get_value_method), (set_test_method), (set_value_method) }
+
+#define SNMP_SCALAR_CREATE_NODE_READONLY(oid, asn1_type, get_value_method) SNMP_SCALAR_CREATE_NODE(oid, SNMP_NODE_INSTANCE_READ_ONLY, asn1_type, get_value_method, NULL, NULL)
+
+/** scalar array node - a tree node which contains scalars only as children */
+struct snmp_scalar_array_node_def
+{
+  u32_t         oid;
+  u8_t          asn1_type;
+  snmp_access_t access;
+};
+
+typedef s16_t (*snmp_scalar_array_get_value_method)(const struct snmp_scalar_array_node_def*, void*);
+typedef snmp_err_t (*snmp_scalar_array_set_test_method)(const struct snmp_scalar_array_node_def*, u16_t, void*);
+typedef snmp_err_t (*snmp_scalar_array_set_value_method)(const struct snmp_scalar_array_node_def*, u16_t, void*);
+
+/** basic scalar array node */
+struct snmp_scalar_array_node
+{
+  /** inherited "base class" members */
+  struct snmp_leaf_node node;
+  u16_t array_node_count;
+  const struct snmp_scalar_array_node_def* array_nodes;
+  snmp_scalar_array_get_value_method get_value;
+  snmp_scalar_array_set_test_method set_test;
+  snmp_scalar_array_set_value_method set_value;
+};
+
+snmp_err_t snmp_scalar_array_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+snmp_err_t snmp_scalar_array_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+
+#define SNMP_SCALAR_CREATE_ARRAY_NODE(oid, array_nodes, get_value_method, set_test_method, set_value_method) \
+  {{{ SNMP_NODE_SCALAR_ARRAY, (oid) }, \
+    snmp_scalar_array_get_instance, \
+    snmp_scalar_array_get_next_instance }, \
+    (u16_t)LWIP_ARRAYSIZE(array_nodes), (array_nodes), (get_value_method), (set_test_method), (set_value_method) }
+
+#endif /* LWIP_SNMP */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_SNMP_SCALAR_H */
diff --git a/src/include/lwip/apps/snmp_table.h b/src/include/lwip/apps/snmp_table.h
new file mode 100644
index 0000000..4988b51
--- /dev/null
+++ b/src/include/lwip/apps/snmp_table.h
@@ -0,0 +1,134 @@
+/**
+ * @file
+ * SNMP server MIB API to implement table nodes
+ */
+
+/*
+ * 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: Martin Hentschel <info@cl-soft.de>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_TABLE_H
+#define LWIP_HDR_APPS_SNMP_TABLE_H
+
+#include "lwip/apps/snmp_opts.h"
+#include "lwip/apps/snmp_core.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+/** default (customizable) read/write table */
+struct snmp_table_col_def
+{
+  u32_t index;
+  u8_t asn1_type;
+  snmp_access_t access;
+};
+
+/** table node */
+struct snmp_table_node
+{
+  /** inherited "base class" members */
+  struct snmp_leaf_node node;
+  u16_t column_count;
+  const struct snmp_table_col_def* columns;
+  snmp_err_t (*get_cell_instance)(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, struct snmp_node_instance* cell_instance);
+  snmp_err_t (*get_next_cell_instance)(const u32_t* column, struct snmp_obj_id* row_oid, struct snmp_node_instance* cell_instance);
+  /** returns object value for the given object identifier */
+  node_instance_get_value_method get_value;
+  /** tests length and/or range BEFORE setting */
+  node_instance_set_test_method set_test;
+  /** sets object value, only called when set_test() was successful */
+  node_instance_set_value_method set_value;
+};
+
+snmp_err_t snmp_table_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+snmp_err_t snmp_table_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+
+#define SNMP_TABLE_CREATE(oid, columns, get_cell_instance_method, get_next_cell_instance_method, get_value_method, set_test_method, set_value_method) \
+  {{{ SNMP_NODE_TABLE, (oid) }, \
+  snmp_table_get_instance, \
+  snmp_table_get_next_instance }, \
+  (u16_t)LWIP_ARRAYSIZE(columns), (columns), \
+  (get_cell_instance_method), (get_next_cell_instance_method), \
+  (get_value_method), (set_test_method), (set_value_method)}
+
+#define SNMP_TABLE_GET_COLUMN_FROM_OID(oid) ((oid)[1]) /* first array value is (fixed) row entry (fixed to 1) and 2nd value is column, follow3ed by instance */
+
+
+/** simple read-only table */
+typedef enum {
+  SNMP_VARIANT_VALUE_TYPE_U32,
+  SNMP_VARIANT_VALUE_TYPE_S32,
+  SNMP_VARIANT_VALUE_TYPE_PTR,
+  SNMP_VARIANT_VALUE_TYPE_CONST_PTR
+} snmp_table_column_data_type_t;
+
+struct snmp_table_simple_col_def
+{
+  u32_t index;
+  u8_t asn1_type;
+  snmp_table_column_data_type_t data_type; /* depending of what union member is used to store the value*/
+};
+
+/** simple read-only table node */
+struct snmp_table_simple_node
+{
+  /* inherited "base class" members */
+  struct snmp_leaf_node node;
+  u16_t column_count;
+  const struct snmp_table_simple_col_def* columns;
+  snmp_err_t (*get_cell_value)(const u32_t* column, const u32_t* row_oid, u8_t row_oid_len, union snmp_variant_value* value, u32_t* value_len);
+  snmp_err_t (*get_next_cell_instance_and_value)(const u32_t* column, struct snmp_obj_id* row_oid, union snmp_variant_value* value, u32_t* value_len);
+};
+
+snmp_err_t snmp_table_simple_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+snmp_err_t snmp_table_simple_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+
+#define SNMP_TABLE_CREATE_SIMPLE(oid, columns, get_cell_value_method, get_next_cell_instance_and_value_method) \
+  {{{ SNMP_NODE_TABLE, (oid) }, \
+  snmp_table_simple_get_instance, \
+  snmp_table_simple_get_next_instance }, \
+  (u16_t)LWIP_ARRAYSIZE(columns), (columns), (get_cell_value_method), (get_next_cell_instance_and_value_method) }
+
+s16_t snmp_table_extract_value_from_s32ref(struct snmp_node_instance* instance, void* value);
+s16_t snmp_table_extract_value_from_u32ref(struct snmp_node_instance* instance, void* value);
+s16_t snmp_table_extract_value_from_refconstptr(struct snmp_node_instance* instance, void* value);
+
+#endif /* LWIP_SNMP */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_SNMP_TABLE_H */
diff --git a/src/include/lwip/apps/snmp_threadsync.h b/src/include/lwip/apps/snmp_threadsync.h
new file mode 100644
index 0000000..a25dbf2
--- /dev/null
+++ b/src/include/lwip/apps/snmp_threadsync.h
@@ -0,0 +1,114 @@
+/**
+ * @file
+ * SNMP server MIB API to implement thread synchronization
+ */
+
+/*
+ * 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: Dirk Ziegelmeier <dziegel@gmx.de>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_THREADSYNC_H
+#define LWIP_HDR_APPS_SNMP_THREADSYNC_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_core.h"
+#include "lwip/sys.h"
+
+typedef void (*snmp_threadsync_called_fn)(void* arg);
+typedef void (*snmp_threadsync_synchronizer_fn)(snmp_threadsync_called_fn fn, void* arg);
+
+
+/** Thread sync runtime data. For internal usage only. */
+struct threadsync_data
+{
+  union {
+    snmp_err_t err;
+    s16_t s16;
+  } retval;
+  union {
+    const u32_t *root_oid;
+    void *value;
+  } arg1;
+  union {
+    u8_t root_oid_len;
+    u16_t len;
+  } arg2;
+  const struct snmp_threadsync_node *threadsync_node;
+  struct snmp_node_instance proxy_instance;
+};
+
+/** Thread sync instance. Needed EXCATLY once for every thread to be synced into. */
+struct snmp_threadsync_instance
+{
+  sys_sem_t                       sem;
+  sys_mutex_t                     sem_usage_mutex;
+  snmp_threadsync_synchronizer_fn sync_fn;
+  struct threadsync_data          data;
+};
+
+/** SNMP thread sync proxy leaf node */
+struct snmp_threadsync_node
+{
+  /* inherited "base class" members */
+  struct snmp_leaf_node           node;
+
+  const struct snmp_leaf_node     *target;
+  struct snmp_threadsync_instance *instance;
+};
+
+snmp_err_t snmp_threadsync_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+snmp_err_t snmp_threadsync_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance* instance);
+
+/** Create thread sync proxy node */
+#define SNMP_CREATE_THREAD_SYNC_NODE(oid, target_leaf_node, threadsync_instance) \
+  {{{ SNMP_NODE_THREADSYNC, (oid) }, \
+    snmp_threadsync_get_instance, \
+    snmp_threadsync_get_next_instance }, \
+    (target_leaf_node), \
+    (threadsync_instance) }
+
+/** Create thread sync instance data */
+void snmp_threadsync_init(struct snmp_threadsync_instance *instance, snmp_threadsync_synchronizer_fn sync_fn);
+
+#endif /* LWIP_SNMP */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_SNMP_THREADSYNC_H */
diff --git a/src/include/lwip/apps/snmpv3.h b/src/include/lwip/apps/snmpv3.h
new file mode 100644
index 0000000..c99fed4
--- /dev/null
+++ b/src/include/lwip/apps/snmpv3.h
@@ -0,0 +1,90 @@
+/**
+ * @file
+ * Additional SNMPv3 functionality RFC3414 and RFC3826.
+ */
+
+/*
+ * Copyright (c) 2016 Elias Oenal.
+ * 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: Elias Oenal <lwip@eliasoenal.com>
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_V3_H
+#define LWIP_HDR_APPS_SNMP_V3_H
+
+#include "lwip/apps/snmp_opts.h"
+#include "lwip/err.h"
+
+#if LWIP_SNMP && LWIP_SNMP_V3
+
+#define SNMP_V3_AUTH_ALGO_INVAL  0
+#define SNMP_V3_AUTH_ALGO_MD5    1
+#define SNMP_V3_AUTH_ALGO_SHA    2
+
+#define SNMP_V3_PRIV_ALGO_INVAL  0
+#define SNMP_V3_PRIV_ALGO_DES    1
+#define SNMP_V3_PRIV_ALGO_AES    2
+
+#define SNMP_V3_PRIV_MODE_DECRYPT  0
+#define SNMP_V3_PRIV_MODE_ENCRYPT  1
+
+/*
+ * The following callback functions must be implemented by the application.
+ * There is a dummy implementation in snmpv3_dummy.c.
+ */
+
+void snmpv3_get_engine_id(const char **id, u8_t *len);
+err_t snmpv3_set_engine_id(const char* id, u8_t len);
+
+u32_t snmpv3_get_engine_boots(void);
+void snmpv3_set_engine_boots(u32_t boots);
+
+u32_t snmpv3_get_engine_time(void);
+void snmpv3_reset_engine_time(void);
+
+err_t snmpv3_get_user(const char* username, u8_t *auth_algo, u8_t *auth_key, u8_t *priv_algo, u8_t *priv_key);
+
+/* The following functions are provided by the SNMPv3 agent */
+
+void snmpv3_engine_id_changed(void);
+
+void snmpv3_password_to_key_md5(
+    const u8_t *password,     /* IN */
+    u8_t        passwordlen,  /* IN */
+    const u8_t *engineID,     /* IN  - pointer to snmpEngineID  */
+    u8_t        engineLength, /* IN  - length of snmpEngineID */
+    u8_t       *key);         /* OUT - pointer to caller 16-octet buffer */
+
+void snmpv3_password_to_key_sha(
+    const u8_t *password,     /* IN */
+    u8_t        passwordlen,  /* IN */
+    const u8_t *engineID,     /* IN  - pointer to snmpEngineID  */
+    u8_t        engineLength, /* IN  - length of snmpEngineID */
+    u8_t       *key);         /* OUT - pointer to caller 20-octet buffer */
+
+#endif
+
+#endif /* LWIP_HDR_APPS_SNMP_V3_H */
diff --git a/src/include/lwip/apps/sntp.h b/src/include/lwip/apps/sntp.h
new file mode 100644
index 0000000..40df9cc
--- /dev/null
+++ b/src/include/lwip/apps/sntp.h
@@ -0,0 +1,76 @@
+/**
+ * @file
+ * SNTP client API
+ */
+
+/*
+ * Copyright (c) 2007-2009 Frédéric Bernon, Simon Goldschmidt
+ * 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: Frédéric Bernon, Simon Goldschmidt
+ *
+ */
+#ifndef LWIP_HDR_APPS_SNTP_H
+#define LWIP_HDR_APPS_SNTP_H
+
+#include "lwip/apps/sntp_opts.h"
+#include "lwip/ip_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* SNTP operating modes: default is to poll using unicast.
+   The mode has to be set before calling sntp_init(). */
+#define SNTP_OPMODE_POLL            0
+#define SNTP_OPMODE_LISTENONLY      1
+void sntp_setoperatingmode(u8_t operating_mode);
+u8_t sntp_getoperatingmode(void);
+
+void sntp_init(void);
+void sntp_stop(void);
+u8_t sntp_enabled(void);
+
+void sntp_setserver(u8_t idx, const ip_addr_t *addr);
+const ip_addr_t* sntp_getserver(u8_t idx);
+
+#if SNTP_SERVER_DNS
+void sntp_setservername(u8_t idx, char *server);
+char *sntp_getservername(u8_t idx);
+#endif /* SNTP_SERVER_DNS */
+
+#if SNTP_GET_SERVERS_FROM_DHCP
+void sntp_servermode_dhcp(int set_servers_from_dhcp);
+#else /* SNTP_GET_SERVERS_FROM_DHCP */
+#define sntp_servermode_dhcp(x)
+#endif /* SNTP_GET_SERVERS_FROM_DHCP */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_SNTP_H */
diff --git a/src/include/lwip/apps/sntp_opts.h b/src/include/lwip/apps/sntp_opts.h
new file mode 100644
index 0000000..f3651f9
--- /dev/null
+++ b/src/include/lwip/apps/sntp_opts.h
@@ -0,0 +1,173 @@
+/**
+ * @file
+ * SNTP client options list
+ */
+
+/*
+ * Copyright (c) 2007-2009 Frédéric Bernon, Simon Goldschmidt
+ * 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: Frédéric Bernon, Simon Goldschmidt
+ *
+ */
+#ifndef LWIP_HDR_APPS_SNTP_OPTS_H
+#define LWIP_HDR_APPS_SNTP_OPTS_H
+
+#include "lwip/opt.h"
+
+/**
+ * @defgroup sntp_opts Options
+ * @ingroup sntp
+ * @{
+ */
+
+/** SNTP macro to change system time in seconds
+ * Define SNTP_SET_SYSTEM_TIME_US(sec, us) to set the time in microseconds instead of this one
+ * if you need the additional precision.
+ */
+#if !defined SNTP_SET_SYSTEM_TIME || defined __DOXYGEN__
+#define SNTP_SET_SYSTEM_TIME(sec)   LWIP_UNUSED_ARG(sec)
+#endif
+
+/** The maximum number of SNTP servers that can be set */
+#if !defined SNTP_MAX_SERVERS || defined __DOXYGEN__
+#define SNTP_MAX_SERVERS           LWIP_DHCP_MAX_NTP_SERVERS
+#endif
+
+/** Set this to 1 to implement the callback function called by dhcp when
+ * NTP servers are received. */
+#if !defined SNTP_GET_SERVERS_FROM_DHCP || defined __DOXYGEN__
+#define SNTP_GET_SERVERS_FROM_DHCP LWIP_DHCP_GET_NTP_SRV
+#endif
+
+/** Set this to 1 to support DNS names (or IP address strings) to set sntp servers
+ * One server address/name can be defined as default if SNTP_SERVER_DNS == 1:
+ * \#define SNTP_SERVER_ADDRESS "pool.ntp.org"
+ */
+#if !defined SNTP_SERVER_DNS || defined __DOXYGEN__
+#define SNTP_SERVER_DNS            0
+#endif
+
+/**
+ * SNTP_DEBUG: Enable debugging for SNTP.
+ */
+#if !defined SNTP_DEBUG || defined __DOXYGEN__
+#define SNTP_DEBUG                  LWIP_DBG_OFF
+#endif
+
+/** SNTP server port */
+#if !defined SNTP_PORT || defined __DOXYGEN__
+#define SNTP_PORT                   123
+#endif
+
+/** Set this to 1 to allow config of SNTP server(s) by DNS name */
+#if !defined SNTP_SERVER_DNS || defined __DOXYGEN__
+#define SNTP_SERVER_DNS             0
+#endif
+
+/** Sanity check:
+ * Define this to
+ * - 0 to turn off sanity checks (default; smaller code)
+ * - >= 1 to check address and port of the response packet to ensure the
+ *        response comes from the server we sent the request to.
+ * - >= 2 to check returned Originate Timestamp against Transmit Timestamp
+ *        sent to the server (to ensure response to older request).
+ * - >= 3 @todo: discard reply if any of the LI, Stratum, or Transmit Timestamp
+ *        fields is 0 or the Mode field is not 4 (unicast) or 5 (broadcast).
+ * - >= 4 @todo: to check that the Root Delay and Root Dispersion fields are each
+ *        greater than or equal to 0 and less than infinity, where infinity is
+ *        currently a cozy number like one second. This check avoids using a
+ *        server whose synchronization source has expired for a very long time.
+ */
+#if !defined SNTP_CHECK_RESPONSE || defined __DOXYGEN__
+#define SNTP_CHECK_RESPONSE         0
+#endif
+
+/** According to the RFC, this shall be a random delay
+ * between 1 and 5 minutes (in milliseconds) to prevent load peaks.
+ * This can be defined to a random generation function,
+ * which must return the delay in milliseconds as u32_t.
+ * Turned off by default.
+ */
+#if !defined SNTP_STARTUP_DELAY || defined __DOXYGEN__
+#define SNTP_STARTUP_DELAY          0
+#endif
+
+/** If you want the startup delay to be a function, define this
+ * to a function (including the brackets) and define SNTP_STARTUP_DELAY to 1.
+ */
+#if !defined SNTP_STARTUP_DELAY_FUNC || defined __DOXYGEN__
+#define SNTP_STARTUP_DELAY_FUNC     SNTP_STARTUP_DELAY
+#endif
+
+/** SNTP receive timeout - in milliseconds
+ * Also used as retry timeout - this shouldn't be too low.
+ * Default is 3 seconds.
+ */
+#if !defined SNTP_RECV_TIMEOUT || defined __DOXYGEN__
+#define SNTP_RECV_TIMEOUT           3000
+#endif
+
+/** SNTP update delay - in milliseconds
+ * Default is 1 hour. Must not be beolw 15 seconds by specification (i.e. 15000)
+ */
+#if !defined SNTP_UPDATE_DELAY || defined __DOXYGEN__
+#define SNTP_UPDATE_DELAY           3600000
+#endif
+
+/** SNTP macro to get system time, used with SNTP_CHECK_RESPONSE >= 2
+ * to send in request and compare in response.
+ */
+#if !defined SNTP_GET_SYSTEM_TIME || defined __DOXYGEN__
+#define SNTP_GET_SYSTEM_TIME(sec, us)     do { (sec) = 0; (us) = 0; } while(0)
+#endif
+
+/** Default retry timeout (in milliseconds) if the response
+ * received is invalid.
+ * This is doubled with each retry until SNTP_RETRY_TIMEOUT_MAX is reached.
+ */
+#if !defined SNTP_RETRY_TIMEOUT || defined __DOXYGEN__
+#define SNTP_RETRY_TIMEOUT          SNTP_RECV_TIMEOUT
+#endif
+
+/** Maximum retry timeout (in milliseconds). */
+#if !defined SNTP_RETRY_TIMEOUT_MAX || defined __DOXYGEN__
+#define SNTP_RETRY_TIMEOUT_MAX      (SNTP_RETRY_TIMEOUT * 10)
+#endif
+
+/** Increase retry timeout with every retry sent
+ * Default is on to conform to RFC.
+ */
+#if !defined SNTP_RETRY_TIMEOUT_EXP || defined __DOXYGEN__
+#define SNTP_RETRY_TIMEOUT_EXP      1
+#endif
+
+/**
+ * @}
+ */
+
+#endif /* LWIP_HDR_APPS_SNTP_OPTS_H */
diff --git a/src/include/lwip/apps/tftp_opts.h b/src/include/lwip/apps/tftp_opts.h
new file mode 100644
index 0000000..6968a80
--- /dev/null
+++ b/src/include/lwip/apps/tftp_opts.h
@@ -0,0 +1,105 @@
+/****************************************************************//**
+ *
+ * @file tftp_opts.h
+ *
+ * @author   Logan Gunthorpe <logang@deltatee.com>
+ *
+ * @brief    Trivial File Transfer Protocol (RFC 1350) implementation options
+ *
+ * Copyright (c) Deltatee Enterprises Ltd. 2013
+ * 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: Logan Gunthorpe <logang@deltatee.com>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_TFTP_OPTS_H
+#define LWIP_HDR_APPS_TFTP_OPTS_H
+
+#include "lwip/opt.h"
+
+/**
+ * @defgroup tftp_opts Options
+ * @ingroup tftp
+ * @{
+ */
+
+/**
+ * Enable TFTP debug messages
+ */
+#if !defined TFTP_DEBUG || defined __DOXYGEN__
+#define TFTP_DEBUG            LWIP_DBG_ON
+#endif
+
+/**
+ * TFTP server port
+ */
+#if !defined TFTP_PORT || defined __DOXYGEN__
+#define TFTP_PORT             69
+#endif
+
+/**
+ * TFTP timeout
+ */
+#if !defined TFTP_TIMEOUT_MSECS || defined __DOXYGEN__
+#define TFTP_TIMEOUT_MSECS    10000
+#endif
+
+/**
+ * Max. number of retries when a file is read from server
+ */
+#if !defined TFTP_MAX_RETRIES || defined __DOXYGEN__
+#define TFTP_MAX_RETRIES      5
+#endif
+
+/**
+ * TFTP timer cyclic interval
+ */
+#if !defined TFTP_TIMER_MSECS || defined __DOXYGEN__
+#define TFTP_TIMER_MSECS      50
+#endif
+
+/**
+ * Max. length of TFTP filename
+ */
+#if !defined TFTP_MAX_FILENAME_LEN || defined __DOXYGEN__
+#define TFTP_MAX_FILENAME_LEN 20
+#endif
+
+/**
+ * Max. length of TFTP mode
+ */
+#if !defined TFTP_MAX_MODE_LEN || defined __DOXYGEN__
+#define TFTP_MAX_MODE_LEN     7
+#endif
+
+/**
+ * @}
+ */
+
+#endif /* LWIP_HDR_APPS_TFTP_OPTS_H */
diff --git a/src/include/lwip/apps/tftp_server.h b/src/include/lwip/apps/tftp_server.h
new file mode 100644
index 0000000..3fbe701
--- /dev/null
+++ b/src/include/lwip/apps/tftp_server.h
@@ -0,0 +1,94 @@
+/****************************************************************//**
+ *
+ * @file tftp_server.h
+ *
+ * @author   Logan Gunthorpe <logang@deltatee.com>
+ *
+ * @brief    Trivial File Transfer Protocol (RFC 1350)
+ *
+ * Copyright (c) Deltatee Enterprises Ltd. 2013
+ * 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: Logan Gunthorpe <logang@deltatee.com>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_TFTP_SERVER_H
+#define LWIP_HDR_APPS_TFTP_SERVER_H
+
+#include "lwip/apps/tftp_opts.h"
+#include "lwip/err.h"
+#include "lwip/pbuf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @ingroup tftp
+ * TFTP context containing callback functions for TFTP transfers
+ */
+struct tftp_context {
+  /**
+   * Open file for read/write.
+   * @param fname Filename
+   * @param mode Mode string from TFTP RFC 1350 (netascii, octet, mail)
+   * @param write Flag indicating read (0) or write (!= 0) access
+   * @returns File handle supplied to other functions
+   */
+  void* (*open)(const char* fname, const char* mode, u8_t write);
+  /**
+   * Close file handle
+   * @param handle File handle returned by open()
+   */
+  void (*close)(void* handle);
+  /**
+   * Read from file 
+   * @param handle File handle returned by open()
+   * @param buf Target buffer to copy read data to
+   * @param bytes Number of bytes to copy to buf
+   * @returns &gt;= 0: Success; &lt; 0: Error
+   */
+  int (*read)(void* handle, void* buf, int bytes);
+  /**
+   * Write to file
+   * @param handle File handle returned by open()
+   * @param pbuf PBUF adjusted such that payload pointer points
+   *             to the beginning of write data. In other words,
+   *             TFTP headers are stripped off.
+   * @returns &gt;= 0: Success; &lt; 0: Error
+   */
+  int (*write)(void* handle, struct pbuf* p);
+};
+
+err_t tftp_init(const struct tftp_context* ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_APPS_TFTP_SERVER_H */
diff --git a/src/include/lwip/arch.h b/src/include/lwip/arch.h
new file mode 100644
index 0000000..55714e1
--- /dev/null
+++ b/src/include/lwip/arch.h
@@ -0,0 +1,319 @@
+/**
+ * @file
+ * Support for different processor and compiler architectures
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_ARCH_H
+#define LWIP_HDR_ARCH_H
+
+#ifndef LITTLE_ENDIAN
+#define LITTLE_ENDIAN 1234
+#endif
+
+#ifndef BIG_ENDIAN
+#define BIG_ENDIAN 4321
+#endif
+
+#include "arch/cc.h"
+
+/**
+ * @defgroup compiler_abstraction Compiler/platform abstraction
+ * @ingroup sys_layer
+ * All defines related to this section must not be placed in lwipopts.h,
+ * but in arch/cc.h!
+ * These options cannot be \#defined in lwipopts.h since they are not options
+ * of lwIP itself, but options of the lwIP port to your system.
+ * @{
+ */
+
+/** Define the byte order of the system.
+ * Needed for conversion of network data to host byte order.
+ * Allowed values: LITTLE_ENDIAN and BIG_ENDIAN
+ */
+#ifndef BYTE_ORDER
+#define BYTE_ORDER LITTLE_ENDIAN
+#endif
+
+/** Define random number generator function of your system */
+#ifdef __DOXYGEN__
+#define LWIP_RAND() ((u32_t)rand())
+#endif
+
+/** Platform specific diagnostic output.\n
+ * Note the default implementation pulls in printf, which may
+ * in turn pull in a lot of standard libary code. In resource-constrained 
+ * systems, this should be defined to something less resource-consuming.
+ */
+#ifndef LWIP_PLATFORM_DIAG
+#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
+#include <stdio.h>
+#include <stdlib.h>
+#endif
+
+/** Platform specific assertion handling.\n
+ * Note the default implementation pulls in printf, fflush and abort, which may
+ * in turn pull in a lot of standard libary code. In resource-constrained 
+ * systems, this should be defined to something less resource-consuming.
+ */
+#ifndef LWIP_PLATFORM_ASSERT
+#define LWIP_PLATFORM_ASSERT(x) do {printf("Assertion \"%s\" failed at line %d in %s\n", \
+                                     x, __LINE__, __FILE__); fflush(NULL); abort();} while(0)
+#include <stdio.h>
+#include <stdlib.h>
+#endif
+
+/** Define this to 1 in arch/cc.h of your port if you do not want to
+ * include stddef.h header to get size_t. You need to typedef size_t
+ * by yourself in this case.
+ */
+#ifndef LWIP_NO_STDDEF_H
+#define LWIP_NO_STDDEF_H 0
+#endif
+
+#if !LWIP_NO_STDDEF_H
+#include <stddef.h> /* for size_t */
+#endif
+
+/** Define this to 1 in arch/cc.h of your port if your compiler does not provide
+ * the stdint.h header. You need to typedef the generic types listed in
+ * lwip/arch.h yourself in this case (u8_t, u16_t...).
+ */
+#ifndef LWIP_NO_STDINT_H
+#define LWIP_NO_STDINT_H 0
+#endif
+
+/* Define generic types used in lwIP */
+#if !LWIP_NO_STDINT_H
+#include <stdint.h>
+typedef uint8_t   u8_t;
+typedef int8_t    s8_t;
+typedef uint16_t  u16_t;
+typedef int16_t   s16_t;
+typedef uint32_t  u32_t;
+typedef int32_t   s32_t;
+typedef uintptr_t mem_ptr_t;
+#endif
+
+/** Define this to 1 in arch/cc.h of your port if your compiler does not provide
+ * the inttypes.h header. You need to define the format strings listed in
+ * lwip/arch.h yourself in this case (X8_F, U16_F...).
+ */
+#ifndef LWIP_NO_INTTYPES_H
+#define LWIP_NO_INTTYPES_H 0
+#endif
+
+/* Define (sn)printf formatters for these lwIP types */
+#if !LWIP_NO_INTTYPES_H
+#include <inttypes.h>
+#ifndef X8_F
+#define X8_F  "02" PRIx8
+#endif
+#ifndef U16_F
+#define U16_F PRIu16
+#endif
+#ifndef S16_F
+#define S16_F PRId16
+#endif
+#ifndef X16_F
+#define X16_F PRIx16
+#endif
+#ifndef U32_F
+#define U32_F PRIu32
+#endif
+#ifndef S32_F
+#define S32_F PRId32
+#endif
+#ifndef X32_F
+#define X32_F PRIx32
+#endif
+#ifndef SZT_F
+#define SZT_F PRIuPTR
+#endif
+#endif
+
+/** Define this to 1 in arch/cc.h of your port if your compiler does not provide
+ * the limits.h header. You need to define the type limits yourself in this case
+ * (e.g. INT_MAX).
+ */
+#ifndef LWIP_NO_LIMITS_H
+#define LWIP_NO_LIMITS_H 0
+#endif
+
+/* Include limits.h? */
+#if !LWIP_NO_LIMITS_H
+#include <limits.h>
+#endif
+
+/** C++ const_cast<target_type>(val) equivalent to remove constness from a value (GCC -Wcast-qual) */
+#ifndef LWIP_CONST_CAST
+#define LWIP_CONST_CAST(target_type, val) ((target_type)((ptrdiff_t)val))
+#endif
+
+/** Get rid of alignment cast warnings (GCC -Wcast-align) */
+#ifndef LWIP_ALIGNMENT_CAST
+#define LWIP_ALIGNMENT_CAST(target_type, val) LWIP_CONST_CAST(target_type, val)
+#endif
+
+/** Get rid of warnings related to pointer-to-numeric and vice-versa casts,
+ * e.g. "conversion from 'u8_t' to 'void *' of greater size"
+ */
+#ifndef LWIP_PTR_NUMERIC_CAST
+#define LWIP_PTR_NUMERIC_CAST(target_type, val) LWIP_CONST_CAST(target_type, val)
+#endif
+
+/** Allocates a memory buffer of specified size that is of sufficient size to align
+ * its start address using LWIP_MEM_ALIGN.
+ * You can declare your own version here e.g. to enforce alignment without adding
+ * trailing padding bytes (see LWIP_MEM_ALIGN_BUFFER) or your own section placement
+ * requirements.\n
+ * e.g. if you use gcc and need 32 bit alignment:\n
+ * \#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[size] \_\_attribute\_\_((aligned(4)))\n
+ * or more portable:\n
+ * \#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u32_t variable_name[(size + sizeof(u32_t) - 1) / sizeof(u32_t)]
+ */
+#ifndef LWIP_DECLARE_MEMORY_ALIGNED
+#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]
+#endif
+
+/** 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 - 1U) & ~(MEM_ALIGNMENT-1U))
+#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 - 1U))
+#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
+extern "C" {
+#endif
+
+/** Packed structs support.
+  * Placed BEFORE declaration of a packed struct.\n
+  * For examples of packed struct declarations, see include/lwip/prot/ subfolder.\n
+  * A port to GCC/clang is included in lwIP, if you use these compilers there is nothing to do here.
+  */
+#ifndef PACK_STRUCT_BEGIN
+#define PACK_STRUCT_BEGIN
+#endif /* PACK_STRUCT_BEGIN */
+
+/** Packed structs support.
+  * Placed AFTER declaration of a packed struct.\n
+  * For examples of packed struct declarations, see include/lwip/prot/ subfolder.\n
+  * A port to GCC/clang is included in lwIP, if you use these compilers there is nothing to do here.
+  */
+#ifndef PACK_STRUCT_END
+#define PACK_STRUCT_END
+#endif /* PACK_STRUCT_END */
+
+/** Packed structs support.
+  * Placed between end of declaration of a packed struct and trailing semicolon.\n
+  * For examples of packed struct declarations, see include/lwip/prot/ subfolder.\n
+  * A port to GCC/clang is included in lwIP, if you use these compilers there is nothing to do here.
+  */
+#ifndef PACK_STRUCT_STRUCT
+#if defined(__GNUC__) || defined(__clang__)
+#define PACK_STRUCT_STRUCT __attribute__((packed))
+#else
+#define PACK_STRUCT_STRUCT
+#endif
+#endif /* PACK_STRUCT_STRUCT */
+
+/** Packed structs support.
+  * Wraps u32_t and u16_t members.\n
+  * For examples of packed struct declarations, see include/lwip/prot/ subfolder.\n
+  * A port to GCC/clang is included in lwIP, if you use these compilers there is nothing to do here.
+  */
+#ifndef PACK_STRUCT_FIELD
+#define PACK_STRUCT_FIELD(x) x
+#endif /* PACK_STRUCT_FIELD */
+
+/** Packed structs support.
+  * Wraps u8_t members, where some compilers warn that packing is not necessary.\n
+  * For examples of packed struct declarations, see include/lwip/prot/ subfolder.\n
+  * A port to GCC/clang is included in lwIP, if you use these compilers there is nothing to do here.
+  */
+#ifndef PACK_STRUCT_FLD_8
+#define PACK_STRUCT_FLD_8(x) PACK_STRUCT_FIELD(x)
+#endif /* PACK_STRUCT_FLD_8 */
+
+/** Packed structs support.
+  * Wraps members that are packed structs themselves, where some compilers warn that packing is not necessary.\n
+  * For examples of packed struct declarations, see include/lwip/prot/ subfolder.\n
+  * A port to GCC/clang is included in lwIP, if you use these compilers there is nothing to do here.
+  */
+#ifndef PACK_STRUCT_FLD_S
+#define PACK_STRUCT_FLD_S(x) PACK_STRUCT_FIELD(x)
+#endif /* PACK_STRUCT_FLD_S */
+
+/** Packed structs support using \#include files before and after struct to be packed.\n
+ * The file included BEFORE the struct is "arch/bpstruct.h".\n
+ * The file included AFTER the struct is "arch/epstruct.h".\n
+ * This can be used to implement struct packing on MS Visual C compilers, see
+ * the Win32 port in the lwIP contrib repository for reference.
+ * For examples of packed struct declarations, see include/lwip/prot/ subfolder.\n
+ * A port to GCC/clang is included in lwIP, if you use these compilers there is nothing to do here.
+ */
+#ifdef __DOXYGEN__
+#define PACK_STRUCT_USE_INCLUDES
+#endif
+
+/** Eliminates compiler warning about unused arguments (GCC -Wextra -Wunused). */
+#ifndef LWIP_UNUSED_ARG
+#define LWIP_UNUSED_ARG(x) (void)x
+#endif /* LWIP_UNUSED_ARG */
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_ARCH_H */
diff --git a/src/include/lwip/autoip.h b/src/include/lwip/autoip.h
new file mode 100644
index 0000000..1d85bcc
--- /dev/null
+++ b/src/include/lwip/autoip.h
@@ -0,0 +1,99 @@
+/**
+ * @file
+ *
+ * AutoIP Automatic LinkLocal IP Configuration
+ */
+
+/*
+ *
+ * Copyright (c) 2007 Dominik Spies <kontakt@dspies.de>
+ * 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@dspies.de>
+ *
+ * This is a AutoIP implementation for the lwIP TCP/IP stack. It aims to conform
+ * with RFC 3927.
+ *
+ */
+
+#ifndef LWIP_HDR_AUTOIP_H
+#define LWIP_HDR_AUTOIP_H
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV4 && LWIP_AUTOIP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/netif.h"
+/* #include "lwip/udp.h" */
+#include "lwip/etharp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** AutoIP Timing */
+#define AUTOIP_TMR_INTERVAL      100
+#define AUTOIP_TICKS_PER_SECOND (1000 / AUTOIP_TMR_INTERVAL)
+
+/** AutoIP state information per netif */
+struct autoip
+{
+  /** the currently selected, probed, announced or used LL IP-Address */
+  ip4_addr_t llipaddr;
+  /** current AutoIP state machine state */
+  u8_t state;
+  /** sent number of probes or announces, dependent on state */
+  u8_t sent_num;
+  /** ticks to wait, tick is AUTOIP_TMR_INTERVAL long */
+  u16_t ttw;
+  /** ticks until a conflict can be solved by defending */
+  u8_t lastconflict;
+  /** total number of probed/used Link Local IP-Addresses */
+  u8_t tried_llipaddr;
+};
+
+
+void autoip_set_struct(struct netif *netif, struct autoip *autoip);
+/** Remove a struct autoip previously set to the netif using autoip_set_struct() */
+#define autoip_remove_struct(netif) do { (netif)->autoip = NULL; } while (0)
+err_t autoip_start(struct netif *netif);
+err_t autoip_stop(struct netif *netif);
+void autoip_arp_reply(struct netif *netif, struct etharp_hdr *hdr);
+void autoip_tmr(void);
+void autoip_network_changed(struct netif *netif);
+u8_t autoip_supplied_address(const struct netif *netif);
+
+/* for lwIP internal use by ip4.c */
+u8_t autoip_accept_packet(struct netif *netif, const ip4_addr_t *addr);
+
+#define netif_autoip_data(netif) ((struct autoip*)netif_get_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_AUTOIP))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV4 && LWIP_AUTOIP */
+
+#endif /* LWIP_HDR_AUTOIP_H */
diff --git a/src/include/lwip/debug.h b/src/include/lwip/debug.h
new file mode 100644
index 0000000..a142f1c
--- /dev/null
+++ b/src/include/lwip/debug.h
@@ -0,0 +1,167 @@
+/**
+ * @file
+ * Debug messages infrastructure
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_DEBUG_H
+#define LWIP_HDR_DEBUG_H
+
+#include "lwip/arch.h"
+#include "lwip/opt.h"
+
+/**
+ * @defgroup debugging_levels LWIP_DBG_MIN_LEVEL and LWIP_DBG_TYPES_ON values
+ * @ingroup lwip_opts_debugmsg
+ * @{
+ */
+
+/** @name Debug level (LWIP_DBG_MIN_LEVEL)
+ * @{
+ */
+/** Debug level: ALL messages*/
+#define LWIP_DBG_LEVEL_ALL     0x00
+/** Debug level: Warnings. bad checksums, dropped packets, ... */
+#define LWIP_DBG_LEVEL_WARNING 0x01
+/** Debug level: Serious. memory allocation failures, ... */
+#define LWIP_DBG_LEVEL_SERIOUS 0x02
+/** Debug level: Severe */
+#define LWIP_DBG_LEVEL_SEVERE  0x03
+/**
+ * @}
+ */
+
+#define LWIP_DBG_MASK_LEVEL    0x03
+/* compatibility define only */
+#define LWIP_DBG_LEVEL_OFF     LWIP_DBG_LEVEL_ALL
+
+/** @name Enable/disable debug messages completely (LWIP_DBG_TYPES_ON)
+ * @{
+ */
+/** 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
+/**
+ * @}
+ */
+
+/** @name Debug message types (LWIP_DBG_TYPES_ON)
+ * @{
+ */
+/** 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
+/**
+ * @}
+ */
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_assertions Assertion handling
+ * @ingroup lwip_opts_debug
+ * @{
+ */
+/**
+ * LWIP_NOASSERT: Disable LWIP_ASSERT checks:
+ * To disable assertions define LWIP_NOASSERT in arch/cc.h.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_NOASSERT
+#undef LWIP_NOASSERT
+#endif
+/**
+ * @}
+ */
+
+#ifndef LWIP_NOASSERT
+#define LWIP_ASSERT(message, assertion) do { if (!(assertion)) { \
+  LWIP_PLATFORM_ASSERT(message); }} while(0)
+#ifndef LWIP_PLATFORM_ASSERT
+#error "If you want to use LWIP_ASSERT, LWIP_PLATFORM_ASSERT(message) needs to be defined in your arch/cc.h"
+#endif
+#else  /* LWIP_NOASSERT */
+#define LWIP_ASSERT(message, assertion)
+#endif /* LWIP_NOASSERT */
+
+#ifndef LWIP_ERROR
+#ifndef LWIP_NOASSERT
+#define LWIP_PLATFORM_ERROR(message) LWIP_PLATFORM_ASSERT(message)
+#elif defined LWIP_DEBUG
+#define LWIP_PLATFORM_ERROR(message) LWIP_PLATFORM_DIAG((message))
+#else
+#define LWIP_PLATFORM_ERROR(message)
+#endif
+
+/* if "expression" isn't true, then print "message" and execute "handler" expression */
+#define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \
+  LWIP_PLATFORM_ERROR(message); handler;}} while(0)
+#endif /* LWIP_ERROR */
+
+/** Enable debug message printing, but only if debug message type is enabled
+ *  AND is of correct type AND is at least LWIP_DBG_LEVEL.
+ */
+#ifdef __DOXYGEN__
+#define LWIP_DEBUG
+#undef LWIP_DEBUG
+#endif
+
+#ifdef LWIP_DEBUG
+#ifndef LWIP_PLATFORM_DIAG
+#error "If you want to use LWIP_DEBUG, LWIP_PLATFORM_DIAG(message) needs to be defined in your arch/cc.h"
+#endif
+#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_HDR_DEBUG_H */
diff --git a/src/include/lwip/def.h b/src/include/lwip/def.h
new file mode 100644
index 0000000..82a9d89
--- /dev/null
+++ b/src/include/lwip/def.h
@@ -0,0 +1,141 @@
+/**
+ * @file
+ * various utility macros
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_DEF_H
+#define LWIP_HDR_DEF_H
+
+/* arch.h might define NULL already */
+#include "lwip/arch.h"
+#include "lwip/opt.h"
+#if LWIP_PERF
+#include "arch/perf.h"
+#else /* LWIP_PERF */
+#define PERF_START    /* null definition */
+#define PERF_STOP(x)  /* null definition */
+#endif /* LWIP_PERF */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LWIP_MAX(x , y)  (((x) > (y)) ? (x) : (y))
+#define LWIP_MIN(x , y)  (((x) < (y)) ? (x) : (y))
+
+/* Get the number of entries in an array ('x' must NOT be a pointer!) */
+#define LWIP_ARRAYSIZE(x) (sizeof(x)/sizeof((x)[0]))
+
+/** Create u32_t value from bytes */
+#define LWIP_MAKEU32(a,b,c,d) (((u32_t)((a) & 0xff) << 24) | \
+                               ((u32_t)((b) & 0xff) << 16) | \
+                               ((u32_t)((c) & 0xff) << 8)  | \
+                                (u32_t)((d) & 0xff))
+
+#ifndef NULL
+#ifdef __cplusplus
+#define NULL 0
+#else
+#define NULL ((void *)0)
+#endif
+#endif
+
+#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 */
+#ifndef lwip_htons
+u16_t lwip_htons(u16_t x);
+#endif
+#define lwip_ntohs(x) lwip_htons(x)
+
+#ifndef lwip_htonl
+u32_t lwip_htonl(u32_t x);
+#endif
+#define lwip_ntohl(x) lwip_htonl(x)
+
+/* 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) & 0x00ffUL) << 8) | (((x) & 0xff00UL) >> 8))
+#define PP_NTOHS(x) PP_HTONS(x)
+#define PP_HTONL(x) ((((x) & 0x000000ffUL) << 24) | \
+                     (((x) & 0x0000ff00UL) <<  8) | \
+                     (((x) & 0x00ff0000UL) >>  8) | \
+                     (((x) & 0xff000000UL) >> 24))
+#define PP_NTOHL(x) PP_HTONL(x)
+#endif /* BYTE_ORDER == BIG_ENDIAN */
+
+/* Provide usual function names as macros for users, but this can be turned off */
+#ifndef LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS
+#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
+
+/* Functions that are not available as standard implementations.
+ * In cc.h, you can #define these to implementations available on
+ * your platform to save some code bytes if you use these functions
+ * in your application, too.
+ */
+
+#ifndef lwip_itoa
+/* This can be #defined to itoa() or snprintf(result, bufsize, "%d", number) depending on your platform */
+void  lwip_itoa(char* result, size_t bufsize, int number);
+#endif
+#ifndef lwip_strnicmp
+/* This can be #defined to strnicmp() or strncasecmp() depending on your platform */
+int   lwip_strnicmp(const char* str1, const char* str2, size_t len);
+#endif
+#ifndef lwip_stricmp
+/* This can be #defined to stricmp() or strcasecmp() depending on your platform */
+int   lwip_stricmp(const char* str1, const char* str2);
+#endif
+#ifndef lwip_strnstr
+/* This can be #defined to strnstr() depending on your platform */
+char* lwip_strnstr(const char* buffer, const char* token, size_t n);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_DEF_H */
diff --git a/src/include/lwip/dhcp.h b/src/include/lwip/dhcp.h
new file mode 100644
index 0000000..df932af
--- /dev/null
+++ b/src/include/lwip/dhcp.h
@@ -0,0 +1,143 @@
+/**
+ * @file
+ * DHCP client API
+ */
+
+/*
+ * Copyright (c) 2001-2004 Leon Woestenberg <leon.woestenberg@gmx.net>
+ * 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 part of the lwIP TCP/IP stack.
+ *
+ * Author: Leon Woestenberg <leon.woestenberg@gmx.net>
+ *
+ */
+#ifndef LWIP_HDR_DHCP_H
+#define LWIP_HDR_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_BOOT_FILE_LEN      128U
+
+/* AutoIP cooperation flags (struct dhcp.autoip_coop_state) */
+typedef enum {
+  DHCP_AUTOIP_COOP_STATE_OFF  = 0,
+  DHCP_AUTOIP_COOP_STATE_ON   = 1
+} dhcp_autoip_coop_state_enum_t;
+
+struct dhcp
+{
+  /** transaction identifier of last sent request */
+  u32_t xid;
+  /** incoming msg */
+  struct dhcp_msg *msg_in;
+  /** track PCB allocation state */
+  u8_t pcb_allocated;
+  /** 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 */
+  u16_t t1_renew_time;  /* #ticks with period DHCP_COARSE_TIMER_SECS until next renew try */
+  u16_t t2_rebind_time; /* #ticks with period DHCP_COARSE_TIMER_SECS until next rebind try */
+  u16_t lease_used; /* #ticks with period DHCP_COARSE_TIMER_SECS since last received DHCP ack */
+  u16_t t0_timeout; /* #ticks with period DHCP_COARSE_TIMER_SECS for lease time */
+  ip_addr_t server_ip_addr; /* dhcp server address that offered this lease (ip_addr_t because passed to UDP) */
+  ip4_addr_t offered_ip_addr;
+  ip4_addr_t offered_sn_mask;
+  ip4_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 87.5 of lease period)  */
+#if LWIP_DHCP_BOOTP_FILE
+  ip4_addr_t offered_si_addr;
+  char boot_file_name[DHCP_BOOT_FILE_LEN];
+#endif /* LWIP_DHCP_BOOTPFILE */
+};
+
+
+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) netif_set_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP, NULL)
+void dhcp_cleanup(struct netif *netif);
+err_t dhcp_start(struct netif *netif);
+err_t dhcp_renew(struct netif *netif);
+err_t dhcp_release(struct netif *netif);
+void dhcp_stop(struct netif *netif);
+void dhcp_inform(struct netif *netif);
+void dhcp_network_changed(struct netif *netif);
+#if DHCP_DOES_ARP_CHECK
+void dhcp_arp_reply(struct netif *netif, const ip4_addr_t *addr);
+#endif
+u8_t dhcp_supplied_address(const struct netif *netif);
+/* to be called every minute */
+void dhcp_coarse_tmr(void);
+/* to be called every half second */
+void dhcp_fine_tmr(void);
+
+#if LWIP_DHCP_GET_NTP_SRV
+/** This function must exist, in other to add offered NTP servers to
+ * the NTP (or SNTP) engine.
+ * See LWIP_DHCP_MAX_NTP_SERVERS */
+extern void dhcp_set_ntp_servers(u8_t num_ntp_servers, const ip4_addr_t* ntp_server_addrs);
+#endif /* LWIP_DHCP_GET_NTP_SRV */
+
+#define netif_dhcp_data(netif) ((struct dhcp*)netif_get_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_DHCP */
+
+#endif /*LWIP_HDR_DHCP_H*/
diff --git a/src/include/lwip/dhcp6.h b/src/include/lwip/dhcp6.h
new file mode 100644
index 0000000..455336d
--- /dev/null
+++ b/src/include/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@inicotech.com>
+ *
+ * IPv6 address autoconfiguration as per RFC 4862.
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#ifndef LWIP_HDR_IP6_DHCP6_H
+#define LWIP_HDR_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_HDR_IP6_DHCP6_H */
diff --git a/src/include/lwip/dns.h b/src/include/lwip/dns.h
new file mode 100644
index 0000000..1453d72
--- /dev/null
+++ b/src/include/lwip/dns.h
@@ -0,0 +1,130 @@
+/**
+ * @file
+ * DNS API
+ */
+
+/**
+ * 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_HDR_DNS_H
+#define LWIP_HDR_DNS_H
+
+#include "lwip/opt.h"
+
+#if LWIP_DNS
+
+#include "lwip/ip_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** DNS timer period */
+#define DNS_TMR_INTERVAL          1000
+
+/* DNS resolve types: */
+#define LWIP_DNS_ADDRTYPE_IPV4      0
+#define LWIP_DNS_ADDRTYPE_IPV6      1
+#define LWIP_DNS_ADDRTYPE_IPV4_IPV6 2 /* try to resolve IPv4 first, try IPv6 if IPv4 fails only */
+#define LWIP_DNS_ADDRTYPE_IPV6_IPV4 3 /* try to resolve IPv6 first, try IPv4 if IPv6 fails only */
+#if LWIP_IPV4 && LWIP_IPV6
+#ifndef LWIP_DNS_ADDRTYPE_DEFAULT
+#define LWIP_DNS_ADDRTYPE_DEFAULT   LWIP_DNS_ADDRTYPE_IPV4_IPV6
+#endif
+#elif LWIP_IPV4
+#define LWIP_DNS_ADDRTYPE_DEFAULT   LWIP_DNS_ADDRTYPE_IPV4
+#else
+#define LWIP_DNS_ADDRTYPE_DEFAULT   LWIP_DNS_ADDRTYPE_IPV6
+#endif
+
+#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;
+};
+#define DNS_LOCAL_HOSTLIST_ELEM(name, addr_init) {name, addr_init, NULL}
+#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 */
+
+#if LWIP_IPV4
+extern const ip_addr_t dns_mquery_v4group;
+#endif /* LWIP_IPV4 */
+#if LWIP_IPV6
+extern const ip_addr_t dns_mquery_v6group;
+#endif /* LWIP_IPV6 */
+
+/** 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, const ip_addr_t *ipaddr, void *callback_arg);
+
+void             dns_init(void);
+void             dns_tmr(void);
+void             dns_setserver(u8_t numdns, const ip_addr_t *dnsserver);
+const 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);
+err_t            dns_gethostbyname_addrtype(const char *hostname, ip_addr_t *addr,
+                                   dns_found_callback found, void *callback_arg,
+                                   u8_t dns_addrtype);
+
+
+#if DNS_LOCAL_HOSTLIST
+size_t         dns_local_iterate(dns_found_callback iterator_fn, void *iterator_arg);
+err_t          dns_local_lookup(const char *hostname, ip_addr_t *addr, u8_t dns_addrtype);
+#if 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_IS_DYNAMIC */
+#endif /* DNS_LOCAL_HOSTLIST */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_DNS */
+
+#endif /* LWIP_HDR_DNS_H */
diff --git a/src/include/lwip/err.h b/src/include/lwip/err.h
new file mode 100644
index 0000000..84e528d
--- /dev/null
+++ b/src/include/lwip/err.h
@@ -0,0 +1,119 @@
+/**
+ * @file
+ * lwIP Error codes
+ */
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_ERR_H
+#define LWIP_HDR_ERR_H
+
+#include "lwip/opt.h"
+#include "lwip/arch.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup infrastructure_errors Error codes
+ * @ingroup infrastructure
+ * @{
+ */
+
+/** 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. */
+typedef enum {
+/** No error, everything 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 connecting.      */
+  ERR_ALREADY    = -9,
+/** Conn already established.*/
+  ERR_ISCONN     = -10,
+/** Not connected.           */
+  ERR_CONN       = -11,
+/** Low-level netif error    */
+  ERR_IF         = -12,
+
+/** Connection aborted.      */
+  ERR_ABRT       = -13,
+/** Connection reset.        */
+  ERR_RST        = -14,
+/** Connection closed.       */
+  ERR_CLSD       = -15,
+/** Illegal argument.        */
+  ERR_ARG        = -16
+} err_enum_t;
+
+#define ERR_IS_FATAL(e) ((e) <= ERR_ABRT)
+
+/**
+ * @}
+ */
+
+#ifdef LWIP_DEBUG
+extern const char *lwip_strerr(err_t err);
+#else
+#define lwip_strerr(x) ""
+#endif /* LWIP_DEBUG */
+
+#if !NO_SYS
+int err_to_errno(err_t err);
+#endif /* !NO_SYS */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_ERR_H */
diff --git a/src/include/lwip/errno.h b/src/include/lwip/errno.h
new file mode 100644
index 0000000..641cffb
--- /dev/null
+++ b/src/include/lwip/errno.h
@@ -0,0 +1,193 @@
+/**
+ * @file
+ * Posix Errno defines
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_ERRNO_H
+#define LWIP_HDR_ERRNO_H
+
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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
+
+#else /* LWIP_PROVIDE_ERRNO */
+
+/* Define LWIP_ERRNO_INCLUDE to <errno.h> to include the error defines here */
+#ifdef LWIP_ERRNO_INCLUDE
+#include LWIP_ERRNO_INCLUDE
+#endif /* LWIP_ERRNO_INCLUDE */
+
+#endif /* LWIP_PROVIDE_ERRNO */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_ERRNO_H */
diff --git a/src/include/lwip/etharp.h b/src/include/lwip/etharp.h
new file mode 100644
index 0000000..7080a19
--- /dev/null
+++ b/src/include/lwip/etharp.h
@@ -0,0 +1,106 @@
+/**
+ * @file
+ * Ethernet output function - handles OUTGOING ethernet level traffic, implements
+ * ARP resolving.
+ * To be used in most low-level netif implementations
+ */
+
+/*
+ * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
+ * Copyright (c) 2003-2004 Leon Woestenberg <leon.woestenberg@axon.tv>
+ * 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@sics.se>
+ *
+ */
+
+#ifndef LWIP_HDR_NETIF_ETHARP_H
+#define LWIP_HDR_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/ip4_addr.h"
+#include "lwip/netif.h"
+#include "lwip/ip4.h"
+#include "lwip/prot/ethernet.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_IPV4 && LWIP_ARP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/prot/etharp.h"
+
+/** 1 seconds period */
+#define ARP_TMR_INTERVAL 1000
+
+#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, no init needed. */
+void etharp_tmr(void);
+s8_t etharp_find_addr(struct netif *netif, const ip4_addr_t *ipaddr,
+         struct eth_addr **eth_ret, const ip4_addr_t **ip_ret);
+u8_t etharp_get_entry(u8_t i, ip4_addr_t **ipaddr, struct netif **netif, struct eth_addr **eth_ret);
+err_t etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr);
+err_t etharp_query(struct netif *netif, const ip4_addr_t *ipaddr, struct pbuf *q);
+err_t etharp_request(struct netif *netif, const ip4_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_ip4_addr(netif))
+void etharp_cleanup_netif(struct netif *netif);
+
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+err_t etharp_add_static_entry(const ip4_addr_t *ipaddr, struct eth_addr *ethaddr);
+err_t etharp_remove_static_entry(const ip4_addr_t *ipaddr);
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+#endif /* LWIP_IPV4 && LWIP_ARP */
+
+void etharp_input(struct pbuf *p, struct netif *netif);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_ARP || LWIP_ETHERNET */
+
+#endif /* LWIP_HDR_NETIF_ETHARP_H */
diff --git a/src/include/lwip/ethip6.h b/src/include/lwip/ethip6.h
new file mode 100644
index 0000000..5e88dff
--- /dev/null
+++ b/src/include/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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#ifndef LWIP_HDR_ETHIP6_H
+#define LWIP_HDR_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, const ip6_addr_t *ip6addr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV6 && LWIP_ETHERNET */
+
+#endif /* LWIP_HDR_ETHIP6_H */
diff --git a/src/include/lwip/icmp.h b/src/include/lwip/icmp.h
new file mode 100644
index 0000000..f5a31fd
--- /dev/null
+++ b/src/include/lwip/icmp.h
@@ -0,0 +1,110 @@
+/**
+ * @file
+ * ICMP API
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_ICMP_H
+#define LWIP_HDR_ICMP_H
+
+#include "lwip/opt.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+#include "lwip/prot/icmp.h"
+
+#if LWIP_IPV6 && LWIP_ICMP6
+#include "lwip/icmp6.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** ICMP destination unreachable codes */
+enum icmp_dur_type {
+  /** net unreachable */
+  ICMP_DUR_NET   = 0,
+  /** host unreachable */
+  ICMP_DUR_HOST  = 1,
+  /** protocol unreachable */
+  ICMP_DUR_PROTO = 2,
+  /** port unreachable */
+  ICMP_DUR_PORT  = 3,
+  /** fragmentation needed and DF set */
+  ICMP_DUR_FRAG  = 4,
+  /** source route failed */
+  ICMP_DUR_SR    = 5
+};
+
+/** ICMP time exceeded codes */
+enum icmp_te_type {
+  /** time to live exceeded in transit */
+  ICMP_TE_TTL  = 0,
+  /** fragment reassembly time exceeded */
+  ICMP_TE_FRAG = 1
+};
+
+#if LWIP_IPV4 && 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_IPV4 && LWIP_ICMP */
+
+#if LWIP_IPV4 && LWIP_IPV6
+#if LWIP_ICMP && 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) do{ if(!(isipv6)) { icmp_dest_unreach(pbuf, ICMP_DUR_PORT);}}while(0)
+#elif LWIP_ICMP6
+#define icmp_port_unreach(isipv6, pbuf) do{ if(isipv6) { icmp6_dest_unreach(pbuf, ICMP6_DUR_PORT);}}while(0)
+#else
+#define icmp_port_unreach(isipv6, pbuf)
+#endif
+#elif LWIP_IPV6 && LWIP_ICMP6
+#define icmp_port_unreach(isipv6, pbuf) icmp6_dest_unreach(pbuf, ICMP6_DUR_PORT)
+#elif LWIP_IPV4 && LWIP_ICMP
+#define icmp_port_unreach(isipv6, pbuf) icmp_dest_unreach(pbuf, ICMP_DUR_PORT)
+#else /* (LWIP_IPV6 && LWIP_ICMP6) || (LWIP_IPV4 && LWIP_ICMP) */
+#define icmp_port_unreach(isipv6, pbuf)
+#endif /* (LWIP_IPV6 && LWIP_ICMP6) || (LWIP_IPV4 && LWIP_ICMP) LWIP_IPV4*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_ICMP_H */
diff --git a/src/include/lwip/icmp6.h b/src/include/lwip/icmp6.h
new file mode 100644
index 0000000..a29dc8c
--- /dev/null
+++ b/src/include/lwip/icmp6.h
@@ -0,0 +1,70 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+#ifndef LWIP_HDR_ICMP6_H
+#define LWIP_HDR_ICMP6_H
+
+#include "lwip/opt.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/netif.h"
+#include "lwip/prot/icmp6.h"
+
+#ifdef __cplusplus
+extern "C" {
+#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_HDR_ICMP6_H */
diff --git a/src/include/lwip/igmp.h b/src/include/lwip/igmp.h
new file mode 100644
index 0000000..ffd80e6
--- /dev/null
+++ b/src/include/lwip/igmp.h
@@ -0,0 +1,115 @@
+/**
+ * @file
+ * IGMP API
+ */
+
+/*
+ * 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_HDR_IGMP_H
+#define LWIP_HDR_IGMP_H
+
+#include "lwip/opt.h"
+#include "lwip/ip_addr.h"
+#include "lwip/netif.h"
+#include "lwip/pbuf.h"
+
+#if LWIP_IPV4 && 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)
+
+/* Compatibility defines (don't use for new code) */
+#define IGMP_DEL_MAC_FILTER            NETIF_DEL_MAC_FILTER
+#define IGMP_ADD_MAC_FILTER            NETIF_ADD_MAC_FILTER
+
+/**
+ * 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;
+  /** multicast address */
+  ip4_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, const ip4_addr_t *addr);
+void   igmp_input(struct pbuf *p, struct netif *inp, const ip4_addr_t *dest);
+err_t  igmp_joingroup(const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr);
+err_t  igmp_joingroup_netif(struct netif *netif, const ip4_addr_t *groupaddr);
+err_t  igmp_leavegroup(const ip4_addr_t *ifaddr, const ip4_addr_t *groupaddr);
+err_t  igmp_leavegroup_netif(struct netif *netif, const ip4_addr_t *groupaddr);
+void   igmp_tmr(void);
+
+/** @ingroup igmp 
+ * Get list head of IGMP groups for netif.
+ * Note: The allsystems group IP is contained in the list as first entry.
+ * @see @ref netif_set_igmp_mac_filter()
+ */
+#define netif_igmp_data(netif) ((struct igmp_group *)netif_get_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_IGMP))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV4 && LWIP_IGMP */
+
+#endif /* LWIP_HDR_IGMP_H */
diff --git a/src/include/lwip/inet.h b/src/include/lwip/inet.h
new file mode 100644
index 0000000..4a34f02
--- /dev/null
+++ b/src/include/lwip/inet.h
@@ -0,0 +1,172 @@
+/**
+ * @file
+ * This file (together with sockets.h) aims to provide structs and functions from
+ * - arpa/inet.h
+ * - netinet/in.h
+ *
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_INET_H
+#define LWIP_HDR_INET_H
+
+#include "lwip/opt.h"
+#include "lwip/def.h"
+#include "lwip/ip_addr.h"
+#include "lwip/ip6_addr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* If your port already typedef's in_addr_t, define IN_ADDR_T_DEFINED
+   to prevent this code from redefining it. */
+#if !defined(in_addr_t) && !defined(IN_ADDR_T_DEFINED)
+typedef u32_t in_addr_t;
+#endif
+
+struct in_addr {
+  in_addr_t s_addr;
+};
+
+struct in6_addr {
+  union {
+    u32_t u32_addr[4];
+    u8_t  u8_addr[16];
+  } un;
+#define s6_addr  un.u8_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
+
+/** This macro can be used to initialize a variable of type struct in6_addr
+    to the IPv6 wildcard address. */
+#define IN6ADDR_ANY_INIT {{{0,0,0,0}}}
+/** This macro can be used to initialize a variable of type struct in6_addr
+    to the IPv6 loopback address. */
+#define IN6ADDR_LOOPBACK_INIT {{{0,0,0,PP_HTONL(1)}}}
+/** This variable is initialized by the system to contain the wildcard IPv6 address. */
+extern const struct in6_addr in6addr_any;
+
+/* Definitions of the bits in an (IPv4) 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
+
+
+#ifndef INET_ADDRSTRLEN
+#define INET_ADDRSTRLEN     IP4ADDR_STRLEN_MAX
+#endif
+#if LWIP_IPV6
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN    IP6ADDR_STRLEN_MAX
+#endif
+#endif
+
+#if LWIP_IPV4
+
+#define inet_addr_from_ip4addr(target_inaddr, source_ipaddr) ((target_inaddr)->s_addr = ip4_addr_get_u32(source_ipaddr))
+#define inet_addr_to_ip4addr(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 ip4_addr_t are an u32_t effectively! */
+#define inet_addr_to_ip4addr_p(target_ip4addr_p, source_inaddr)   ((target_ip4addr_p) = (ip4_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)             ip4addr_aton(cp, (ip4_addr_t*)addr)
+#define inet_ntoa(addr)                 ip4addr_ntoa((const ip4_addr_t*)&(addr))
+#define inet_ntoa_r(addr, buf, buflen)  ip4addr_ntoa_r((const ip4_addr_t*)&(addr), buf, buflen)
+
+#endif /* LWIP_IPV4 */
+
+#if LWIP_IPV6
+#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((const ip6_addr_t*)&(addr))
+#define inet6_ntoa_r(addr, buf, buflen) ip6addr_ntoa_r((const ip6_addr_t*)&(addr), buf, buflen)
+
+#endif /* LWIP_IPV6 */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_INET_H */
diff --git a/src/include/lwip/inet_chksum.h b/src/include/lwip/inet_chksum.h
new file mode 100644
index 0000000..4e23d7f
--- /dev/null
+++ b/src/include/lwip/inet_chksum.h
@@ -0,0 +1,105 @@
+/**
+ * @file
+ * IP checksum calculation 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_INET_CHKSUM_H
+#define LWIP_HDR_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 lwip_htons() for little-endian */
+#ifndef SWAP_BYTES_IN_WORD
+#define SWAP_BYTES_IN_WORD(w) (((w) & 0xff) << 8) | (((w) & 0xff00) >> 8)
+#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 */
+# else /* LWIP_CHKSUM_COPY */
+#  define LWIP_CHKSUM_COPY_ALGORITHM 0
+# 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(const void *dataptr, u16_t len);
+u16_t inet_chksum_pbuf(struct pbuf *p);
+#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_IPV4
+u16_t inet_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len,
+       const ip4_addr_t *src, const ip4_addr_t *dest);
+u16_t inet_chksum_pseudo_partial(struct pbuf *p, u8_t proto,
+       u16_t proto_len, u16_t chksum_len, const ip4_addr_t *src, const ip4_addr_t *dest);
+#endif /* LWIP_IPV4 */
+
+#if LWIP_IPV6
+u16_t ip6_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len,
+       const ip6_addr_t *src, const ip6_addr_t *dest);
+u16_t ip6_chksum_pseudo_partial(struct pbuf *p, u8_t proto, u16_t proto_len,
+       u16_t chksum_len, const ip6_addr_t *src, const ip6_addr_t *dest);
+#endif /* LWIP_IPV6 */
+
+
+u16_t ip_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len,
+       const ip_addr_t *src, const ip_addr_t *dest);
+u16_t ip_chksum_pseudo_partial(struct pbuf *p, u8_t proto, u16_t proto_len,
+       u16_t chksum_len, const ip_addr_t *src, const ip_addr_t *dest);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_INET_H */
+
diff --git a/src/include/lwip/init.h b/src/include/lwip/init.h
new file mode 100644
index 0000000..3c234cb
--- /dev/null
+++ b/src/include/lwip/init.h
@@ -0,0 +1,100 @@
+/**
+ * @file
+ * lwIP initialization API
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_INIT_H
+#define LWIP_HDR_INIT_H
+
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup lwip_version Version
+ * @ingroup lwip
+ * @{
+ */
+
+/** X.x.x: Major version of the stack */
+#define LWIP_VERSION_MAJOR      2
+/** x.X.x: Minor version of the stack */
+#define LWIP_VERSION_MINOR      0
+/** x.x.X: Revision of the stack */
+#define LWIP_VERSION_REVISION   3
+/** For release candidates, this is set to 1..254
+  * For official releases, this is set to 255 (LWIP_RC_RELEASE)
+  * For development versions (Git), this is set to 0 (LWIP_RC_DEVELOPMENT) */
+#define LWIP_VERSION_RC         LWIP_RC_RELEASE
+
+/** LWIP_VERSION_RC is set to LWIP_RC_RELEASE for official releases */
+#define LWIP_RC_RELEASE         255
+/** LWIP_VERSION_RC is set to LWIP_RC_DEVELOPMENT for Git versions */
+#define LWIP_RC_DEVELOPMENT     0
+
+#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))
+
+/* Some helper defines to get a version string */
+#define LWIP_VERSTR2(x) #x
+#define LWIP_VERSTR(x) LWIP_VERSTR2(x)
+#if LWIP_VERSION_IS_RELEASE
+#define LWIP_VERSION_STRING_SUFFIX ""
+#elif LWIP_VERSION_IS_DEVELOPMENT
+#define LWIP_VERSION_STRING_SUFFIX "d"
+#else
+#define LWIP_VERSION_STRING_SUFFIX "rc" LWIP_VERSTR(LWIP_VERSION_RC)
+#endif
+
+/** Provides the version of the stack */
+#define LWIP_VERSION   (((u32_t)LWIP_VERSION_MAJOR) << 24   | ((u32_t)LWIP_VERSION_MINOR) << 16 | \
+                        ((u32_t)LWIP_VERSION_REVISION) << 8 | ((u32_t)LWIP_VERSION_RC))
+/** Provides the version of the stack as string */
+#define LWIP_VERSION_STRING     LWIP_VERSTR(LWIP_VERSION_MAJOR) "." LWIP_VERSTR(LWIP_VERSION_MINOR) "." LWIP_VERSTR(LWIP_VERSION_REVISION) LWIP_VERSION_STRING_SUFFIX
+
+/**
+ * @}
+ */
+
+/* Modules initialization */
+void lwip_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_INIT_H */
diff --git a/src/include/lwip/ip.h b/src/include/lwip/ip.h
new file mode 100644
index 0000000..0673be9
--- /dev/null
+++ b/src/include/lwip/ip.h
@@ -0,0 +1,319 @@
+/**
+ * @file
+ * IP API
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_IP_H
+#define LWIP_HDR_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"
+#include "lwip/prot/ip.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. */
+#define LWIP_IP_HDRINCL  NULL
+
+/** 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 */
+#ifndef LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX
+#define LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p) LWIP_ASSERT("p->ref == 1", (p)->ref == 1)
+#endif
+
+#if LWIP_NETIF_HWADDRHINT
+#define IP_PCB_ADDRHINT ;u8_t addr_hint
+#else
+#define IP_PCB_ADDRHINT
+#endif /* LWIP_NETIF_HWADDRHINT */
+
+/** 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 addresses in network byte order */ \
+  ip_addr_t local_ip; \
+  ip_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 in sockets.h
+ */
+#define SOF_REUSEADDR     0x04U  /* allow local address reuse */
+#define SOF_KEEPALIVE     0x08U  /* keep connections alive */
+#define SOF_BROADCAST     0x20U  /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */
+
+/* These flags are inherited (e.g. from a listen-pcb to a connection-pcb): */
+#define SOF_INHERITED   (SOF_REUSEADDR|SOF_KEEPALIVE)
+
+/** Global variables of this module, kept in a struct for efficient access using base+index. */
+struct ip_globals
+{
+  /** The interface that accepted the packet for the current callback invocation. */
+  struct netif *current_netif;
+  /** The interface that received the packet for the current callback invocation. */
+  struct netif *current_input_netif;
+#if LWIP_IPV4
+  /** Header of the input packet currently being processed. */
+  struct ip_hdr *current_ip4_header;
+#endif /* LWIP_IPV4 */
+#if LWIP_IPV6
+  /** Header of the input IPv6 packet currently being processed. */
+  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 */
+  ip_addr_t current_iphdr_src;
+  /** Destination IP address of current_header */
+  ip_addr_t current_iphdr_dest;
+};
+extern struct ip_globals ip_data;
+
+
+/** Get the interface that accepted the current packet.
+ * This may or may not be the receiving netif, depending on your netif/network setup.
+ * 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 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_input_netif() (ip_data.current_input_netif)
+/** 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 ip_current_src_addr()   (&ip_data.current_iphdr_src)
+/** Destination IP address of current_header */
+#define ip_current_dest_addr()  (&ip_data.current_iphdr_dest)
+
+#if LWIP_IPV4 && LWIP_IPV6
+/** Get the IPv4 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 ip4_current_header()     ((const struct ip_hdr*)(ip_data.current_ip4_header))
+/** 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()      ((const struct ip6_hdr*)(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()    (ip_2_ip6(&ip_data.current_iphdr_src))
+/** Destination IPv6 address of current_header */
+#define ip6_current_dest_addr()   (ip_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(ip4_current_header()))
+/** Get the transport layer header */
+#define ip_next_header_ptr()     ((const void*)((ip_current_is_v6() ? \
+  (const u8_t*)ip6_current_header() : (const u8_t*)ip4_current_header())  + ip_current_header_tot_len()))
+
+/** Source IP4 address of current_header */
+#define ip4_current_src_addr()     (ip_2_ip4(&ip_data.current_iphdr_src))
+/** Destination IP4 address of current_header */
+#define ip4_current_dest_addr()    (ip_2_ip4(&ip_data.current_iphdr_dest))
+
+#elif LWIP_IPV4 /* LWIP_IPV4 && LWIP_IPV6 */
+
+/** Get the IPv4 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 ip4_current_header()     ((const struct ip_hdr*)(ip_data.current_ip4_header))
+/** Always returns FALSE when only supporting IPv4 only */
+#define ip_current_is_v6()        0
+/** Get the transport layer protocol */
+#define ip_current_header_proto() IPH_PROTO(ip4_current_header())
+/** Get the transport layer header */
+#define ip_next_header_ptr()     ((const void*)((const u8_t*)ip4_current_header() + ip_current_header_tot_len()))
+/** Source IP4 address of current_header */
+#define ip4_current_src_addr()     (&ip_data.current_iphdr_src)
+/** Destination IP4 address of current_header */
+#define ip4_current_dest_addr()    (&ip_data.current_iphdr_dest)
+
+#elif LWIP_IPV6 /* LWIP_IPV4 && 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()      ((const struct ip6_hdr*)(ip_data.current_ip6_header))
+/** Always returns TRUE when only supporting IPv6 only */
+#define ip_current_is_v6()        1
+/** Get the transport layer protocol */
+#define ip_current_header_proto() IP6H_NEXTH(ip6_current_header())
+/** Get the transport layer header */
+#define ip_next_header_ptr()     ((const void*)((const u8_t*)ip6_current_header()))
+/** Source IP6 address of current_header */
+#define ip6_current_src_addr()    (&ip_data.current_iphdr_src)
+/** Destination IP6 address of current_header */
+#define ip6_current_dest_addr()   (&ip_data.current_iphdr_dest)
+
+#endif /* LWIP_IPV6 */
+
+/** Union source address of current_header */
+#define ip_current_src_addr()    (&ip_data.current_iphdr_src)
+/** Union destination address of current_header */
+#define ip_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_IPV4 && LWIP_IPV6
+/**
+ * @ingroup ip
+ * Output IP packet, netif is selected by source address
+ */
+#define ip_output(p, src, dest, ttl, tos, proto) \
+        (IP_IS_V6(dest) ? \
+        ip6_output(p, ip_2_ip6(src), ip_2_ip6(dest), ttl, tos, proto) : \
+        ip4_output(p, ip_2_ip4(src), ip_2_ip4(dest), ttl, tos, proto))
+/**
+ * @ingroup ip
+ * Output IP packet to specified interface
+ */
+#define ip_output_if(p, src, dest, ttl, tos, proto, netif) \
+        (IP_IS_V6(dest) ? \
+        ip6_output_if(p, ip_2_ip6(src), ip_2_ip6(dest), ttl, tos, proto, netif) : \
+        ip4_output_if(p, ip_2_ip4(src), ip_2_ip4(dest), ttl, tos, proto, netif))
+/**
+ * @ingroup ip
+ * Output IP packet to interface specifying source address
+ */
+#define ip_output_if_src(p, src, dest, ttl, tos, proto, netif) \
+        (IP_IS_V6(dest) ? \
+        ip6_output_if_src(p, ip_2_ip6(src), ip_2_ip6(dest), ttl, tos, proto, netif) : \
+        ip4_output_if_src(p, ip_2_ip4(src), ip_2_ip4(dest), ttl, tos, proto, netif))
+/** Output IP packet with addr_hint */
+#define ip_output_hinted(p, src, dest, ttl, tos, proto, addr_hint) \
+        (IP_IS_V6(dest) ? \
+        ip6_output_hinted(p, ip_2_ip6(src), ip_2_ip6(dest), ttl, tos, proto, addr_hint) : \
+        ip4_output_hinted(p, ip_2_ip4(src), ip_2_ip4(dest), ttl, tos, proto, addr_hint))
+/**
+ * @ingroup ip
+ * Get netif for address combination. See \ref ip6_route and \ref ip4_route
+ */
+#define ip_route(src, dest) \
+        (IP_IS_V6(dest) ? \
+        ip6_route(ip_2_ip6(src), ip_2_ip6(dest)) : \
+        ip4_route_src(ip_2_ip4(dest), ip_2_ip4(src)))
+/**
+ * @ingroup ip
+ * Get netif for IP.
+ */
+#define ip_netif_get_local_ip(netif, dest) (IP_IS_V6(dest) ? \
+        ip6_netif_get_local_ip(netif, ip_2_ip6(dest)) : \
+        ip4_netif_get_local_ip(netif))
+#define ip_debug_print(is_ipv6, p) ((is_ipv6) ? ip6_debug_print(p) : ip4_debug_print(p))
+
+err_t ip_input(struct pbuf *p, struct netif *inp);
+
+#elif LWIP_IPV4 /* LWIP_IPV4 && LWIP_IPV6 */
+
+#define ip_output(p, src, dest, ttl, tos, proto) \
+        ip4_output(p, src, dest, ttl, tos, proto)
+#define ip_output_if(p, src, dest, ttl, tos, proto, netif) \
+        ip4_output_if(p, src, dest, ttl, tos, proto, netif)
+#define ip_output_if_src(p, src, dest, ttl, tos, proto, netif) \
+        ip4_output_if_src(p, src, dest, ttl, tos, proto, netif)
+#define ip_output_hinted(p, src, dest, ttl, tos, proto, addr_hint) \
+        ip4_output_hinted(p, src, dest, ttl, tos, proto, addr_hint)
+#define ip_route(src, dest) \
+        ip4_route_src(dest, src)
+#define ip_netif_get_local_ip(netif, dest) \
+        ip4_netif_get_local_ip(netif)
+#define ip_debug_print(is_ipv6, p) ip4_debug_print(p)
+
+#define ip_input ip4_input
+
+#elif LWIP_IPV6 /* LWIP_IPV4 && LWIP_IPV6 */
+
+#define ip_output(p, src, dest, ttl, tos, proto) \
+        ip6_output(p, src, dest, ttl, tos, proto)
+#define ip_output_if(p, src, dest, ttl, tos, proto, netif) \
+        ip6_output_if(p, src, dest, ttl, tos, proto, netif)
+#define ip_output_if_src(p, src, dest, ttl, tos, proto, netif) \
+        ip6_output_if_src(p, src, dest, ttl, tos, proto, netif)
+#define ip_output_hinted(p, src, dest, ttl, tos, proto, addr_hint) \
+        ip6_output_hinted(p, src, dest, ttl, tos, proto, addr_hint)
+#define ip_route(src, dest) \
+        ip6_route(src, dest)
+#define ip_netif_get_local_ip(netif, dest) \
+        ip6_netif_get_local_ip(netif, dest)
+#define ip_debug_print(is_ipv6, p) ip6_debug_print(p)
+
+#define ip_input ip6_input
+
+#endif /* LWIP_IPV6 */
+
+#define ip_route_get_local_ip(src, dest, netif, ipaddr) do { \
+  (netif) = ip_route(src, dest); \
+  (ipaddr) = ip_netif_get_local_ip(netif, dest); \
+}while(0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_IP_H */
+
+
diff --git a/src/include/lwip/ip4.h b/src/include/lwip/ip4.h
new file mode 100644
index 0000000..48246ec
--- /dev/null
+++ b/src/include/lwip/ip4.h
@@ -0,0 +1,111 @@
+/**
+ * @file
+ * IPv4 API
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_IP4_H
+#define LWIP_HDR_IP4_H
+
+#include "lwip/opt.h"
+
+#if LWIP_IPV4
+
+#include "lwip/def.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip4_addr.h"
+#include "lwip/err.h"
+#include "lwip/netif.h"
+#include "lwip/prot/ip4.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef LWIP_HOOK_IP4_ROUTE_SRC
+#define LWIP_IPV4_SRC_ROUTING   1
+#else
+#define LWIP_IPV4_SRC_ROUTING   0
+#endif
+
+/** Currently, the function ip_output_if_opt() is only used with IGMP */
+#define IP_OPTIONS_SEND   (LWIP_IPV4 && LWIP_IGMP)
+
+#define ip_init() /* Compatibility define, no init needed. */
+struct netif *ip4_route(const ip4_addr_t *dest);
+#if LWIP_IPV4_SRC_ROUTING
+struct netif *ip4_route_src(const ip4_addr_t *dest, const ip4_addr_t *src);
+#else /* LWIP_IPV4_SRC_ROUTING */
+#define ip4_route_src(dest, src) ip4_route(dest)
+#endif /* LWIP_IPV4_SRC_ROUTING */
+err_t ip4_input(struct pbuf *p, struct netif *inp);
+err_t ip4_output(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
+       u8_t ttl, u8_t tos, u8_t proto);
+err_t ip4_output_if(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
+       u8_t ttl, u8_t tos, u8_t proto, struct netif *netif);
+err_t ip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
+       u8_t ttl, u8_t tos, u8_t proto, struct netif *netif);
+#if LWIP_NETIF_HWADDRHINT
+err_t ip4_output_hinted(struct pbuf *p, const ip4_addr_t *src, const ip4_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 ip4_output_if_opt(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
+       u8_t ttl, u8_t tos, u8_t proto, struct netif *netif, void *ip_options,
+       u16_t optlen);
+err_t ip4_output_if_opt_src(struct pbuf *p, const ip4_addr_t *src, const ip4_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 */
+
+#if LWIP_MULTICAST_TX_OPTIONS
+void  ip4_set_default_multicast_netif(struct netif* default_multicast_netif);
+#endif /* LWIP_MULTICAST_TX_OPTIONS */
+
+#define ip4_netif_get_local_ip(netif) (((netif) != NULL) ? netif_ip_addr4(netif) : NULL)
+
+#if IP_DEBUG
+void ip4_debug_print(struct pbuf *p);
+#else
+#define ip4_debug_print(p)
+#endif /* IP_DEBUG */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV4 */
+
+#endif /* LWIP_HDR_IP_H */
+
+
diff --git a/src/include/lwip/ip4_addr.h b/src/include/lwip/ip4_addr.h
new file mode 100644
index 0000000..51b46b8
--- /dev/null
+++ b/src/include/lwip/ip4_addr.h
@@ -0,0 +1,227 @@
+/**
+ * @file
+ * IPv4 address API
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_IP4_ADDR_H
+#define LWIP_HDR_IP4_ADDR_H
+
+#include "lwip/opt.h"
+#include "lwip/def.h"
+
+#if LWIP_IPV4
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** This is the aligned version of ip4_addr_t,
+   used as local variable, on the stack, etc. */
+struct ip4_addr {
+  u32_t addr;
+};
+
+/** ip4_addr_t uses a struct for convenience only, so that the same defines can
+ * operate both on ip4_addr_t as well as on ip4_addr_p_t. */
+typedef struct ip4_addr ip4_addr_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 ip4_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;
+
+/** 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! */
+
+/** Set an IP address given by the four byte-parts */
+#define IP4_ADDR(ipaddr, a,b,c,d)  (ipaddr)->addr = PP_HTONL(LWIP_MAKEU32(a,b,c,d))
+
+/** 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(ip4_addr_t))
+#endif
+
+/** Copy IP address - faster than ip4_addr_set: no NULL check */
+#define ip4_addr_copy(dest, src) ((dest).addr = (src).addr)
+/** Safely copy one IP address to another (src may be NULL) */
+#define ip4_addr_set(dest, src) ((dest)->addr = \
+                                    ((src) == NULL ? 0 : \
+                                    (src)->addr))
+/** Set complete address to zero */
+#define ip4_addr_set_zero(ipaddr)     ((ipaddr)->addr = 0)
+/** Set address to IPADDR_ANY (no need for lwip_htonl()) */
+#define ip4_addr_set_any(ipaddr)      ((ipaddr)->addr = IPADDR_ANY)
+/** Set address to loopback address */
+#define ip4_addr_set_loopback(ipaddr) ((ipaddr)->addr = PP_HTONL(IPADDR_LOOPBACK))
+/** Check if an address is in the loopback region */
+#define ip4_addr_isloopback(ipaddr)    (((ipaddr)->addr & PP_HTONL(IP_CLASSA_NET)) == PP_HTONL(((u32_t)IP_LOOPBACKNET) << 24))
+/** Safely copy one IP address to another and change byte order
+ * from host- to network-order. */
+#define ip4_addr_set_hton(dest, src) ((dest)->addr = \
+                               ((src) == NULL ? 0:\
+                               lwip_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 ip4_addr_get_network(target, host, netmask) do { ((target)->addr = ((host)->addr) & ((netmask)->addr)); } while(0)
+
+/**
+ * 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 ip4_addr_netcmp(addr1, addr2, mask) (((addr1)->addr & \
+                                              (mask)->addr) == \
+                                             ((addr2)->addr & \
+                                              (mask)->addr))
+#define ip4_addr_cmp(addr1, addr2) ((addr1)->addr == (addr2)->addr)
+
+#define ip4_addr_isany_val(addr1)   ((addr1).addr == IPADDR_ANY)
+#define ip4_addr_isany(addr1) ((addr1) == NULL || ip4_addr_isany_val(*(addr1)))
+
+#define ip4_addr_isbroadcast(addr1, netif) ip4_addr_isbroadcast_u32((addr1)->addr, netif)
+u8_t ip4_addr_isbroadcast_u32(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 ip4_addr_ismulticast(addr1) (((addr1)->addr & PP_HTONL(0xf0000000UL)) == PP_HTONL(0xe0000000UL))
+
+#define ip4_addr_islinklocal(addr1) (((addr1)->addr & PP_HTONL(0xffff0000UL)) == PP_HTONL(0xa9fe0000UL))
+
+#define ip4_addr_debug_print_parts(debug, a, b, c, d) \
+  LWIP_DEBUGF(debug, ("%" U16_F ".%" U16_F ".%" U16_F ".%" U16_F, a, b, c, d))
+#define ip4_addr_debug_print(debug, ipaddr) \
+  ip4_addr_debug_print_parts(debug, \
+                      (u16_t)((ipaddr) != NULL ? ip4_addr1_16(ipaddr) : 0),       \
+                      (u16_t)((ipaddr) != NULL ? ip4_addr2_16(ipaddr) : 0),       \
+                      (u16_t)((ipaddr) != NULL ? ip4_addr3_16(ipaddr) : 0),       \
+                      (u16_t)((ipaddr) != NULL ? ip4_addr4_16(ipaddr) : 0))
+#define ip4_addr_debug_print_val(debug, ipaddr) \
+  ip4_addr_debug_print_parts(debug, \
+                      ip4_addr1_16(&(ipaddr)),       \
+                      ip4_addr2_16(&(ipaddr)),       \
+                      ip4_addr3_16(&(ipaddr)),       \
+                      ip4_addr4_16(&(ipaddr)))
+
+/* Get one byte from the 4-byte address */
+#define ip4_addr1(ipaddr) (((const u8_t*)(&(ipaddr)->addr))[0])
+#define ip4_addr2(ipaddr) (((const u8_t*)(&(ipaddr)->addr))[1])
+#define ip4_addr3(ipaddr) (((const u8_t*)(&(ipaddr)->addr))[2])
+#define ip4_addr4(ipaddr) (((const u8_t*)(&(ipaddr)->addr))[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))
+
+#define IP4ADDR_STRLEN_MAX  16
+
+/** For backwards compatibility */
+#define ip_ntoa(ipaddr)  ipaddr_ntoa(ipaddr)
+
+u32_t ipaddr_addr(const char *cp);
+int ip4addr_aton(const char *cp, ip4_addr_t *addr);
+/** returns ptr to static buffer; not reentrant! */
+char *ip4addr_ntoa(const ip4_addr_t *addr);
+char *ip4addr_ntoa_r(const ip4_addr_t *addr, char *buf, int buflen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV4 */
+
+#endif /* LWIP_HDR_IP_ADDR_H */
diff --git a/src/include/lwip/ip4_frag.h b/src/include/lwip/ip4_frag.h
new file mode 100644
index 0000000..ed5bf14
--- /dev/null
+++ b/src/include/lwip/ip4_frag.h
@@ -0,0 +1,100 @@
+/**
+ * @file
+ * IP fragmentation/reassembly
+ */
+
+/*
+ * 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@iv.ro>
+ *
+ */
+
+#ifndef LWIP_HDR_IP4_FRAG_H
+#define LWIP_HDR_IP4_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"
+
+#if LWIP_IPV4
+
+#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 * ip4_reass(struct pbuf *p);
+#endif /* IP_REASSEMBLY */
+
+#if IP_FRAG
+#if !LWIP_NETIF_TX_SINGLE_PBUF
+#ifndef LWIP_PBUF_CUSTOM_REF_DEFINED
+#define LWIP_PBUF_CUSTOM_REF_DEFINED
+/** 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. */
+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_DEFINED */
+#endif /* !LWIP_NETIF_TX_SINGLE_PBUF */
+
+err_t ip4_frag(struct pbuf *p, struct netif *netif, const ip4_addr_t *dest);
+#endif /* IP_FRAG */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV4 */
+
+#endif /* LWIP_HDR_IP4_FRAG_H */
diff --git a/src/include/lwip/ip6.h b/src/include/lwip/ip6.h
new file mode 100644
index 0000000..099b94f
--- /dev/null
+++ b/src/include/lwip/ip6.h
@@ -0,0 +1,93 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+#ifndef LWIP_HDR_IP6_H
+#define LWIP_HDR_IP6_H
+
+#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/prot/ip6.h"
+#include "lwip/def.h"
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+
+#include "lwip/err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct netif *ip6_route(const ip6_addr_t *src, const ip6_addr_t *dest);
+const ip_addr_t *ip6_select_source_address(struct netif *netif, const ip6_addr_t * dest);
+err_t         ip6_input(struct pbuf *p, struct netif *inp);
+err_t         ip6_output(struct pbuf *p, const ip6_addr_t *src, const ip6_addr_t *dest,
+                         u8_t hl, u8_t tc, u8_t nexth);
+err_t         ip6_output_if(struct pbuf *p, const ip6_addr_t *src, const ip6_addr_t *dest,
+                            u8_t hl, u8_t tc, u8_t nexth, struct netif *netif);
+err_t         ip6_output_if_src(struct pbuf *p, const ip6_addr_t *src, const ip6_addr_t *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, const ip6_addr_t *src, const 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_ip(netif, dest) (((netif) != NULL) ? \
+  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_HDR_IP6_H */
diff --git a/src/include/lwip/ip6_addr.h b/src/include/lwip/ip6_addr.h
new file mode 100644
index 0000000..ee381ae
--- /dev/null
+++ b/src/include/lwip/ip6_addr.h
@@ -0,0 +1,285 @@
+/**
+ * @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@inicotech.com>
+ *
+ * Structs and macros for handling IPv6 addresses.
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+#ifndef LWIP_HDR_IP6_ADDR_H
+#define LWIP_HDR_IP6_ADDR_H
+
+#include "lwip/opt.h"
+#include "def.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];
+};
+
+/** IPv6 address */
+typedef struct ip6_addr ip6_addr_t;
+
+/** Set an IPv6 partial address given by byte-parts */
+#define IP6_ADDR_PART(ip6addr, index, a,b,c,d) \
+  (ip6addr)->addr[index] = PP_HTONL(LWIP_MAKEU32(a,b,c,d))
+
+/** Set a full IPv6 address by passing the 4 u32_t indices in network byte order
+    (use PP_HTONL() for constants) */
+#define IP6_ADDR(ip6addr, idx0, idx1, idx2, idx3) do { \
+  (ip6addr)->addr[0] = idx0; \
+  (ip6addr)->addr[1] = idx1; \
+  (ip6addr)->addr[2] = idx2; \
+  (ip6addr)->addr[3] = idx3; } while(0)
+
+/** Access address in 16-bit block */
+#define IP6_ADDR_BLOCK1(ip6addr) ((u16_t)((lwip_htonl((ip6addr)->addr[0]) >> 16) & 0xffff))
+/** Access address in 16-bit block */
+#define IP6_ADDR_BLOCK2(ip6addr) ((u16_t)((lwip_htonl((ip6addr)->addr[0])) & 0xffff))
+/** Access address in 16-bit block */
+#define IP6_ADDR_BLOCK3(ip6addr) ((u16_t)((lwip_htonl((ip6addr)->addr[1]) >> 16) & 0xffff))
+/** Access address in 16-bit block */
+#define IP6_ADDR_BLOCK4(ip6addr) ((u16_t)((lwip_htonl((ip6addr)->addr[1])) & 0xffff))
+/** Access address in 16-bit block */
+#define IP6_ADDR_BLOCK5(ip6addr) ((u16_t)((lwip_htonl((ip6addr)->addr[2]) >> 16) & 0xffff))
+/** Access address in 16-bit block */
+#define IP6_ADDR_BLOCK6(ip6addr) ((u16_t)((lwip_htonl((ip6addr)->addr[2])) & 0xffff))
+/** Access address in 16-bit block */
+#define IP6_ADDR_BLOCK7(ip6addr) ((u16_t)((lwip_htonl((ip6addr)->addr[3]) >> 16) & 0xffff))
+/** Access address in 16-bit block */
+#define IP6_ADDR_BLOCK8(ip6addr) ((u16_t)((lwip_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 lwip_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 : lwip_htonl((src)->addr[0]); \
+                                        (dest)->addr[1] = (src) == NULL ? 0 : lwip_htonl((src)->addr[1]); \
+                                        (dest)->addr[2] = (src) == NULL ? 0 : lwip_htonl((src)->addr[2]); \
+                                        (dest)->addr[3] = (src) == NULL ? 0 : lwip_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)   (lwip_htonl((ip6addr)->addr[2]) & 0x0000ffffUL)
+
+#define ip6_addr_isany_val(ip6addr) (((ip6addr).addr[0] == 0) && \
+                                     ((ip6addr).addr[1] == 0) && \
+                                     ((ip6addr).addr[2] == 0) && \
+                                     ((ip6addr).addr[3] == 0))
+#define ip6_addr_isany(ip6addr) (((ip6addr) == NULL) || ip6_addr_isany_val(*(ip6addr)))
+
+#define ip6_addr_isloopback(ip6addr) (((ip6addr)->addr[0] == 0UL) && \
+                                      ((ip6addr)->addr[1] == 0UL) && \
+                                      ((ip6addr)->addr[2] == 0UL) && \
+                                      ((ip6addr)->addr[3] == PP_HTONL(0x00000001UL)))
+
+#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_isipv4mappedipv6(ip6addr) (((ip6addr)->addr[0] == 0) && ((ip6addr)->addr[1] == 0) && (((ip6addr)->addr[2]) == PP_HTONL(0x0000FFFFUL)))
+
+#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) ((lwip_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(0xff8f0000UL)) == PP_HTONL(0xff010000UL))
+#define ip6_addr_ismulticast_linklocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xff8f0000UL)) == PP_HTONL(0xff020000UL))
+#define ip6_addr_ismulticast_adminlocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xff8f0000UL)) == PP_HTONL(0xff040000UL))
+#define ip6_addr_ismulticast_sitelocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xff8f0000UL)) == PP_HTONL(0xff050000UL))
+#define ip6_addr_ismulticast_orglocal(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xff8f0000UL)) == PP_HTONL(0xff080000UL))
+#define ip6_addr_ismulticast_global(ip6addr) (((ip6addr)->addr[0] & PP_HTONL(0xff8f0000UL)) == 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] = (PP_HTONL(0xff000000UL) | (if_id));}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] == (PP_HTONL(0xff000000UL) | (sn_addr)->addr[3])))
+
+/* 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 /* This bit marks an address as valid (preferred or deprecated) */
+#define IP6_ADDR_PREFERRED    0x30
+#define IP6_ADDR_DEPRECATED   0x10 /* Same as VALID (valid but not preferred) */
+
+#define IP6_ADDR_TENTATIVE_COUNT_MASK 0x07 /* 1-7 probes sent */
+
+#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_parts(debug, a, b, c, d, e, f, g, h) \
+  LWIP_DEBUGF(debug, ("%" X16_F ":%" X16_F ":%" X16_F ":%" X16_F ":%" X16_F ":%" X16_F ":%" X16_F ":%" X16_F, \
+                      a, b, c, d, e, f, g, h))
+#define ip6_addr_debug_print(debug, ipaddr) \
+  ip6_addr_debug_print_parts(debug, \
+                      (u16_t)((ipaddr) != NULL ? IP6_ADDR_BLOCK1(ipaddr) : 0),    \
+                      (u16_t)((ipaddr) != NULL ? IP6_ADDR_BLOCK2(ipaddr) : 0),    \
+                      (u16_t)((ipaddr) != NULL ? IP6_ADDR_BLOCK3(ipaddr) : 0),    \
+                      (u16_t)((ipaddr) != NULL ? IP6_ADDR_BLOCK4(ipaddr) : 0),    \
+                      (u16_t)((ipaddr) != NULL ? IP6_ADDR_BLOCK5(ipaddr) : 0),    \
+                      (u16_t)((ipaddr) != NULL ? IP6_ADDR_BLOCK6(ipaddr) : 0),    \
+                      (u16_t)((ipaddr) != NULL ? IP6_ADDR_BLOCK7(ipaddr) : 0),    \
+                      (u16_t)((ipaddr) != NULL ? IP6_ADDR_BLOCK8(ipaddr) : 0))
+#define ip6_addr_debug_print_val(debug, ipaddr) \
+  ip6_addr_debug_print_parts(debug, \
+                      IP6_ADDR_BLOCK1(&(ipaddr)),    \
+                      IP6_ADDR_BLOCK2(&(ipaddr)),    \
+                      IP6_ADDR_BLOCK3(&(ipaddr)),    \
+                      IP6_ADDR_BLOCK4(&(ipaddr)),    \
+                      IP6_ADDR_BLOCK5(&(ipaddr)),    \
+                      IP6_ADDR_BLOCK6(&(ipaddr)),    \
+                      IP6_ADDR_BLOCK7(&(ipaddr)),    \
+                      IP6_ADDR_BLOCK8(&(ipaddr)))
+
+#define IP6ADDR_STRLEN_MAX    46
+
+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_HDR_IP6_ADDR_H */
diff --git a/src/include/lwip/ip6_frag.h b/src/include/lwip/ip6_frag.h
new file mode 100644
index 0000000..6be2747
--- /dev/null
+++ b/src/include/lwip/ip6_frag.h
@@ -0,0 +1,120 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+#ifndef LWIP_HDR_IP6_FRAG_H
+#define LWIP_HDR_IP6_FRAG_H
+
+#include "lwip/opt.h"
+#include "lwip/pbuf.h"
+#include "lwip/ip6_addr.h"
+#include "lwip/ip6.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 */
+
+/** IP6_FRAG_COPYHEADER==1: for platforms where sizeof(void*) > 4, this needs to
+ * be enabled (to not overwrite part of the data). When enabled, the IPv6 header
+ * is copied instead of referencing it, which gives more room for struct ip6_reass_helper */
+#ifndef IPV6_FRAG_COPYHEADER
+#define IPV6_FRAG_COPYHEADER   0
+#endif
+
+/** The IPv6 reassembly timer interval in milliseconds. */
+#define IP6_REASS_TMR_INTERVAL 1000
+
+/* Copy the complete header of the first fragment to struct ip6_reassdata
+   or just point to its original location in the first pbuf? */
+#if IPV6_FRAG_COPYHEADER
+#define IPV6_FRAG_HDRPTR
+#define IPV6_FRAG_HDRREF(hdr) (&(hdr))
+#else /* IPV6_FRAG_COPYHEADER */
+#define IPV6_FRAG_HDRPTR *
+#define IPV6_FRAG_HDRREF(hdr) (hdr)
+#endif /* IPV6_FRAG_COPYHEADER */
+
+/** 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 IPV6_FRAG_HDRPTR 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 */
+
+#ifndef LWIP_PBUF_CUSTOM_REF_DEFINED
+#define LWIP_PBUF_CUSTOM_REF_DEFINED
+/** 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. */
+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_DEFINED */
+
+err_t ip6_frag(struct pbuf *p, struct netif *netif, const ip6_addr_t *dest);
+
+#endif /* LWIP_IPV6 && LWIP_IPV6_FRAG */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_IP6_FRAG_H */
diff --git a/src/include/lwip/ip_addr.h b/src/include/lwip/ip_addr.h
new file mode 100644
index 0000000..11f65d2
--- /dev/null
+++ b/src/include/lwip/ip_addr.h
@@ -0,0 +1,407 @@
+/**
+ * @file
+ * IP address API (common IPv4 and IPv6)
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_IP_ADDR_H
+#define LWIP_HDR_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
+
+/** @ingroup ipaddr
+ * IP address types for use in ip_addr_t.type member.
+ * @see tcp_new_ip_type(), udp_new_ip_type(), raw_new_ip_type().
+ */
+enum lwip_ip_addr_type {
+  /** IPv4 */
+  IPADDR_TYPE_V4 =   0U,
+  /** IPv6 */
+  IPADDR_TYPE_V6 =   6U,
+  /** IPv4+IPv6 ("dual-stack") */
+  IPADDR_TYPE_ANY = 46U
+};
+
+#if LWIP_IPV4 && LWIP_IPV6
+/**
+ * @ingroup ipaddr
+ * A union struct for both IP version's addresses.
+ * ATTENTION: watch out for its size when adding IPv6 address scope!
+ */
+typedef struct ip_addr {
+  union {
+    ip6_addr_t ip6;
+    ip4_addr_t ip4;
+  } u_addr;
+  /** @ref lwip_ip_addr_type */
+  u8_t type;
+} ip_addr_t;
+
+extern const ip_addr_t ip_addr_any_type;
+
+/** @ingroup ip4addr */
+#define IPADDR4_INIT(u32val)          { { { { u32val, 0ul, 0ul, 0ul } } }, IPADDR_TYPE_V4 }
+/** @ingroup ip4addr */
+#define IPADDR4_INIT_BYTES(a,b,c,d)   IPADDR4_INIT(PP_HTONL(LWIP_MAKEU32(a,b,c,d)))
+/** @ingroup ip6addr */
+#define IPADDR6_INIT(a, b, c, d)      { { { { a, b, c, d } } }, IPADDR_TYPE_V6 }
+/** @ingroup ip6addr */
+#define IPADDR6_INIT_HOST(a, b, c, d) { { { { PP_HTONL(a), PP_HTONL(b), PP_HTONL(c), PP_HTONL(d) } } }, IPADDR_TYPE_V6 }
+
+/** @ingroup ipaddr */
+#define IP_IS_ANY_TYPE_VAL(ipaddr)    (IP_GET_TYPE(&ipaddr) == IPADDR_TYPE_ANY)
+/** @ingroup ipaddr */
+#define IPADDR_ANY_TYPE_INIT          { { { { 0ul, 0ul, 0ul, 0ul } } }, IPADDR_TYPE_ANY }
+
+/** @ingroup ip4addr */
+#define IP_IS_V4_VAL(ipaddr)          (IP_GET_TYPE(&ipaddr) == IPADDR_TYPE_V4)
+/** @ingroup ip6addr */
+#define IP_IS_V6_VAL(ipaddr)          (IP_GET_TYPE(&ipaddr) == IPADDR_TYPE_V6)
+/** @ingroup ip4addr */
+#define IP_IS_V4(ipaddr)              (((ipaddr) == NULL) || IP_IS_V4_VAL(*(ipaddr)))
+/** @ingroup ip6addr */
+#define IP_IS_V6(ipaddr)              (((ipaddr) != NULL) && IP_IS_V6_VAL(*(ipaddr)))
+
+#define IP_SET_TYPE_VAL(ipaddr, iptype) do { (ipaddr).type = (iptype); }while(0)
+#define IP_SET_TYPE(ipaddr, iptype)     do { if((ipaddr) != NULL) { IP_SET_TYPE_VAL(*(ipaddr), iptype); }}while(0)
+#define IP_GET_TYPE(ipaddr)           ((ipaddr)->type)
+
+#define IP_ADDR_PCB_VERSION_MATCH_EXACT(pcb, ipaddr) (IP_GET_TYPE(&pcb->local_ip) == IP_GET_TYPE(ipaddr))
+#define IP_ADDR_PCB_VERSION_MATCH(pcb, ipaddr) (IP_IS_ANY_TYPE_VAL(pcb->local_ip) || IP_ADDR_PCB_VERSION_MATCH_EXACT(pcb, ipaddr))
+
+/** @ingroup ip6addr
+ * Convert generic ip address to specific protocol version
+ */
+#define ip_2_ip6(ipaddr)   (&((ipaddr)->u_addr.ip6))
+/** @ingroup ip4addr
+ * Convert generic ip address to specific protocol version
+ */
+#define ip_2_ip4(ipaddr)   (&((ipaddr)->u_addr.ip4))
+
+/** @ingroup ip4addr */
+#define IP_ADDR4(ipaddr,a,b,c,d)      do { IP4_ADDR(ip_2_ip4(ipaddr),a,b,c,d); \
+                                           IP_SET_TYPE_VAL(*(ipaddr), IPADDR_TYPE_V4); } while(0)
+/** @ingroup ip6addr */
+#define IP_ADDR6(ipaddr,i0,i1,i2,i3)  do { IP6_ADDR(ip_2_ip6(ipaddr),i0,i1,i2,i3); \
+                                           IP_SET_TYPE_VAL(*(ipaddr), IPADDR_TYPE_V6); } while(0)
+/** @ingroup ip6addr */
+#define IP_ADDR6_HOST(ipaddr,i0,i1,i2,i3)  IP_ADDR6(ipaddr,PP_HTONL(i0),PP_HTONL(i1),PP_HTONL(i2),PP_HTONL(i3))
+
+/** @ingroup ipaddr */
+#define ip_addr_copy(dest, src)      do{ IP_SET_TYPE_VAL(dest, IP_GET_TYPE(&src)); if(IP_IS_V6_VAL(src)){ \
+  ip6_addr_copy(*ip_2_ip6(&(dest)), *ip_2_ip6(&(src))); }else{ \
+  ip4_addr_copy(*ip_2_ip4(&(dest)), *ip_2_ip4(&(src))); }}while(0)
+/** @ingroup ip6addr */
+#define ip_addr_copy_from_ip6(dest, src)      do{ \
+  ip6_addr_copy(*ip_2_ip6(&(dest)), src); IP_SET_TYPE_VAL(dest, IPADDR_TYPE_V6); }while(0)
+/** @ingroup ip4addr */
+#define ip_addr_copy_from_ip4(dest, src)      do{ \
+  ip4_addr_copy(*ip_2_ip4(&(dest)), src); IP_SET_TYPE_VAL(dest, IPADDR_TYPE_V4); }while(0)
+/** @ingroup ip4addr */
+#define ip_addr_set_ip4_u32(ipaddr, val)  do{if(ipaddr){ip4_addr_set_u32(ip_2_ip4(ipaddr), val); \
+  IP_SET_TYPE(ipaddr, IPADDR_TYPE_V4); }}while(0)
+/** @ingroup ip4addr */
+#define ip_addr_get_ip4_u32(ipaddr)  (((ipaddr) && IP_IS_V4(ipaddr)) ? \
+  ip4_addr_get_u32(ip_2_ip4(ipaddr)) : 0)
+/** @ingroup ipaddr */
+#define ip_addr_set(dest, src) do{ IP_SET_TYPE(dest, IP_GET_TYPE(src)); if(IP_IS_V6(src)){ \
+  ip6_addr_set(ip_2_ip6(dest), ip_2_ip6(src)); }else{ \
+  ip4_addr_set(ip_2_ip4(dest), ip_2_ip4(src)); }}while(0)
+/** @ingroup ipaddr */
+#define ip_addr_set_ipaddr(dest, src) ip_addr_set(dest, src)
+/** @ingroup ipaddr */
+#define ip_addr_set_zero(ipaddr)     do{ \
+  ip6_addr_set_zero(ip_2_ip6(ipaddr)); IP_SET_TYPE(ipaddr, 0); }while(0)
+/** @ingroup ip5addr */
+#define ip_addr_set_zero_ip4(ipaddr)     do{ \
+  ip6_addr_set_zero(ip_2_ip6(ipaddr)); IP_SET_TYPE(ipaddr, IPADDR_TYPE_V4); }while(0)
+/** @ingroup ip6addr */
+#define ip_addr_set_zero_ip6(ipaddr)     do{ \
+  ip6_addr_set_zero(ip_2_ip6(ipaddr)); IP_SET_TYPE(ipaddr, IPADDR_TYPE_V6); }while(0)
+/** @ingroup ipaddr */
+#define ip_addr_set_any(is_ipv6, ipaddr)      do{if(is_ipv6){ \
+  ip6_addr_set_any(ip_2_ip6(ipaddr)); IP_SET_TYPE(ipaddr, IPADDR_TYPE_V6); }else{ \
+  ip4_addr_set_any(ip_2_ip4(ipaddr)); IP_SET_TYPE(ipaddr, IPADDR_TYPE_V4); }}while(0)
+/** @ingroup ipaddr */
+#define ip_addr_set_loopback(is_ipv6, ipaddr) do{if(is_ipv6){ \
+  ip6_addr_set_loopback(ip_2_ip6(ipaddr)); IP_SET_TYPE(ipaddr, IPADDR_TYPE_V6); }else{ \
+  ip4_addr_set_loopback(ip_2_ip4(ipaddr)); IP_SET_TYPE(ipaddr, IPADDR_TYPE_V4); }}while(0)
+/** @ingroup ipaddr */
+#define ip_addr_set_hton(dest, src)  do{if(IP_IS_V6(src)){ \
+  ip6_addr_set_hton(ip_2_ip6(ipaddr), (src)); IP_SET_TYPE(dest, IPADDR_TYPE_V6); }else{ \
+  ip4_addr_set_hton(ip_2_ip4(ipaddr), (src)); IP_SET_TYPE(dest, IPADDR_TYPE_V4); }}while(0)
+/** @ingroup ipaddr */
+#define ip_addr_get_network(target, host, netmask) do{if(IP_IS_V6(host)){ \
+  ip4_addr_set_zero(ip_2_ip4(target)); IP_SET_TYPE(target, IPADDR_TYPE_V6); } else { \
+  ip4_addr_get_network(ip_2_ip4(target), ip_2_ip4(host), ip_2_ip4(netmask)); IP_SET_TYPE(target, IPADDR_TYPE_V4); }}while(0)
+/** @ingroup ipaddr */
+#define ip_addr_netcmp(addr1, addr2, mask) ((IP_IS_V6(addr1) && IP_IS_V6(addr2)) ? \
+  0 : \
+  ip4_addr_netcmp(ip_2_ip4(addr1), ip_2_ip4(addr2), mask))
+/** @ingroup ipaddr */
+#define ip_addr_cmp(addr1, addr2)    ((IP_GET_TYPE(addr1) != IP_GET_TYPE(addr2)) ? 0 : (IP_IS_V6_VAL(*(addr1)) ? \
+  ip6_addr_cmp(ip_2_ip6(addr1), ip_2_ip6(addr2)) : \
+  ip4_addr_cmp(ip_2_ip4(addr1), ip_2_ip4(addr2))))
+/** @ingroup ipaddr */
+#define ip_addr_isany(ipaddr)        ((IP_IS_V6(ipaddr)) ? \
+  ip6_addr_isany(ip_2_ip6(ipaddr)) : \
+  ip4_addr_isany(ip_2_ip4(ipaddr)))
+/** @ingroup ipaddr */
+#define ip_addr_isany_val(ipaddr)        ((IP_IS_V6_VAL(ipaddr)) ? \
+  ip6_addr_isany_val(*ip_2_ip6(&(ipaddr))) : \
+  ip4_addr_isany_val(*ip_2_ip4(&(ipaddr))))
+/** @ingroup ipaddr */
+#define ip_addr_isbroadcast(ipaddr, netif) ((IP_IS_V6(ipaddr)) ? \
+  0 : \
+  ip4_addr_isbroadcast(ip_2_ip4(ipaddr), netif))
+/** @ingroup ipaddr */
+#define ip_addr_ismulticast(ipaddr)  ((IP_IS_V6(ipaddr)) ? \
+  ip6_addr_ismulticast(ip_2_ip6(ipaddr)) : \
+  ip4_addr_ismulticast(ip_2_ip4(ipaddr)))
+/** @ingroup ipaddr */
+#define ip_addr_isloopback(ipaddr)  ((IP_IS_V6(ipaddr)) ? \
+  ip6_addr_isloopback(ip_2_ip6(ipaddr)) : \
+  ip4_addr_isloopback(ip_2_ip4(ipaddr)))
+/** @ingroup ipaddr */
+#define ip_addr_islinklocal(ipaddr)  ((IP_IS_V6(ipaddr)) ? \
+  ip6_addr_islinklocal(ip_2_ip6(ipaddr)) : \
+  ip4_addr_islinklocal(ip_2_ip4(ipaddr)))
+#define ip_addr_debug_print(debug, ipaddr) do { if(IP_IS_V6(ipaddr)) { \
+  ip6_addr_debug_print(debug, ip_2_ip6(ipaddr)); } else { \
+  ip4_addr_debug_print(debug, ip_2_ip4(ipaddr)); }}while(0)
+#define ip_addr_debug_print_val(debug, ipaddr) do { if(IP_IS_V6_VAL(ipaddr)) { \
+  ip6_addr_debug_print_val(debug, *ip_2_ip6(&(ipaddr))); } else { \
+  ip4_addr_debug_print_val(debug, *ip_2_ip4(&(ipaddr))); }}while(0)
+/** @ingroup ipaddr */
+#define ipaddr_ntoa(addr)   (((addr) == NULL) ? "NULL" : \
+  ((IP_IS_V6(addr)) ? ip6addr_ntoa(ip_2_ip6(addr)) : ip4addr_ntoa(ip_2_ip4(addr))))
+/** @ingroup ipaddr */
+#define ipaddr_ntoa_r(addr, buf, buflen)   (((addr) == NULL) ? "NULL" : \
+  ((IP_IS_V6(addr)) ? ip6addr_ntoa_r(ip_2_ip6(addr), buf, buflen) : ip4addr_ntoa_r(ip_2_ip4(addr), buf, buflen)))
+int ipaddr_aton(const char *cp, ip_addr_t *addr);
+
+/** @ingroup ipaddr */
+#define IPADDR_STRLEN_MAX   IP6ADDR_STRLEN_MAX
+
+/** @ingroup ipaddr */
+#define ip4_2_ipv4_mapped_ipv6(ip6addr, ip4addr) do { \
+  (ip6addr)->addr[3] = (ip4addr)->addr; \
+  (ip6addr)->addr[2] = PP_HTONL(0x0000FFFFUL); \
+  (ip6addr)->addr[1] = 0; \
+  (ip6addr)->addr[0] = 0; } while(0);
+
+/** @ingroup ipaddr */
+#define unmap_ipv4_mapped_ipv6(ip4addr, ip6addr) \
+  (ip4addr)->addr = (ip6addr)->addr[3];
+
+#define IP46_ADDR_ANY(type) (((type) == IPADDR_TYPE_V6)? IP6_ADDR_ANY : IP4_ADDR_ANY)
+
+#else /* LWIP_IPV4 && LWIP_IPV6 */
+
+#define IP_ADDR_PCB_VERSION_MATCH(addr, pcb)         1
+#define IP_ADDR_PCB_VERSION_MATCH_EXACT(pcb, ipaddr) 1
+
+#if LWIP_IPV4
+
+typedef ip4_addr_t ip_addr_t;
+#define IPADDR4_INIT(u32val)                    { u32val }
+#define IPADDR4_INIT_BYTES(a,b,c,d)             IPADDR4_INIT(PP_HTONL(LWIP_MAKEU32(a,b,c,d)))
+#define IP_IS_V4_VAL(ipaddr)                    1
+#define IP_IS_V6_VAL(ipaddr)                    0
+#define IP_IS_V4(ipaddr)                        1
+#define IP_IS_V6(ipaddr)                        0
+#define IP_IS_ANY_TYPE_VAL(ipaddr)              0
+#define IP_SET_TYPE_VAL(ipaddr, iptype)
+#define IP_SET_TYPE(ipaddr, iptype)
+#define IP_GET_TYPE(ipaddr)                     IPADDR_TYPE_V4
+#define ip_2_ip4(ipaddr)                        (ipaddr)
+#define IP_ADDR4(ipaddr,a,b,c,d)                IP4_ADDR(ipaddr,a,b,c,d)
+
+#define ip_addr_copy(dest, src)                 ip4_addr_copy(dest, src)
+#define ip_addr_copy_from_ip4(dest, src)        ip4_addr_copy(dest, src)
+#define ip_addr_set_ip4_u32(ipaddr, val)        ip4_addr_set_u32(ip_2_ip4(ipaddr), val)
+#define ip_addr_get_ip4_u32(ipaddr)             ip4_addr_get_u32(ip_2_ip4(ipaddr))
+#define ip_addr_set(dest, src)                  ip4_addr_set(dest, src)
+#define ip_addr_set_ipaddr(dest, src)           ip4_addr_set(dest, src)
+#define ip_addr_set_zero(ipaddr)                ip4_addr_set_zero(ipaddr)
+#define ip_addr_set_zero_ip4(ipaddr)            ip4_addr_set_zero(ipaddr)
+#define ip_addr_set_any(is_ipv6, ipaddr)        ip4_addr_set_any(ipaddr)
+#define ip_addr_set_loopback(is_ipv6, ipaddr)   ip4_addr_set_loopback(ipaddr)
+#define ip_addr_set_hton(dest, src)             ip4_addr_set_hton(dest, src)
+#define ip_addr_get_network(target, host, mask) ip4_addr_get_network(target, host, mask)
+#define ip_addr_netcmp(addr1, addr2, mask)      ip4_addr_netcmp(addr1, addr2, mask)
+#define ip_addr_cmp(addr1, addr2)               ip4_addr_cmp(addr1, addr2)
+#define ip_addr_isany(ipaddr)                   ip4_addr_isany(ipaddr)
+#define ip_addr_isany_val(ipaddr)               ip4_addr_isany_val(ipaddr)
+#define ip_addr_isloopback(ipaddr)              ip4_addr_isloopback(ipaddr)
+#define ip_addr_islinklocal(ipaddr)             ip4_addr_islinklocal(ipaddr)
+#define ip_addr_isbroadcast(addr, netif)        ip4_addr_isbroadcast(addr, netif)
+#define ip_addr_ismulticast(ipaddr)             ip4_addr_ismulticast(ipaddr)
+#define ip_addr_debug_print(debug, ipaddr)      ip4_addr_debug_print(debug, ipaddr)
+#define ip_addr_debug_print_val(debug, ipaddr)  ip4_addr_debug_print_val(debug, ipaddr)
+#define ipaddr_ntoa(ipaddr)                     ip4addr_ntoa(ipaddr)
+#define ipaddr_ntoa_r(ipaddr, buf, buflen)      ip4addr_ntoa_r(ipaddr, buf, buflen)
+#define ipaddr_aton(cp, addr)                   ip4addr_aton(cp, addr)
+
+#define IPADDR_STRLEN_MAX   IP4ADDR_STRLEN_MAX
+
+#define IP46_ADDR_ANY(type) (IP4_ADDR_ANY)
+
+#else /* LWIP_IPV4 */
+
+typedef ip6_addr_t ip_addr_t;
+#define IPADDR6_INIT(a, b, c, d)                { { a, b, c, d } }
+#define IPADDR6_INIT_HOST(a, b, c, d)           { { PP_HTONL(a), PP_HTONL(b), PP_HTONL(c), PP_HTONL(d) } }
+#define IP_IS_V4_VAL(ipaddr)                    0
+#define IP_IS_V6_VAL(ipaddr)                    1
+#define IP_IS_V4(ipaddr)                        0
+#define IP_IS_V6(ipaddr)                        1
+#define IP_IS_ANY_TYPE_VAL(ipaddr)              0
+#define IP_SET_TYPE_VAL(ipaddr, iptype)
+#define IP_SET_TYPE(ipaddr, iptype)
+#define IP_GET_TYPE(ipaddr)                     IPADDR_TYPE_V6
+#define ip_2_ip6(ipaddr)                        (ipaddr)
+#define IP_ADDR6(ipaddr,i0,i1,i2,i3)            IP6_ADDR(ipaddr,i0,i1,i2,i3)
+#define IP_ADDR6_HOST(ipaddr,i0,i1,i2,i3)       IP_ADDR6(ipaddr,PP_HTONL(i0),PP_HTONL(i1),PP_HTONL(i2),PP_HTONL(i3))
+
+#define ip_addr_copy(dest, src)                 ip6_addr_copy(dest, src)
+#define ip_addr_copy_from_ip6(dest, src)        ip6_addr_copy(dest, src)
+#define ip_addr_set(dest, src)                  ip6_addr_set(dest, src)
+#define ip_addr_set_ipaddr(dest, src)           ip6_addr_set(dest, src)
+#define ip_addr_set_zero(ipaddr)                ip6_addr_set_zero(ipaddr)
+#define ip_addr_set_zero_ip6(ipaddr)            ip6_addr_set_zero(ipaddr)
+#define ip_addr_set_any(is_ipv6, ipaddr)        ip6_addr_set_any(ipaddr)
+#define ip_addr_set_loopback(is_ipv6, ipaddr)   ip6_addr_set_loopback(ipaddr)
+#define ip_addr_set_hton(dest, src)             ip6_addr_set_hton(dest, src)
+#define ip_addr_get_network(target, host, mask) ip6_addr_set_zero(target)
+#define ip_addr_netcmp(addr1, addr2, mask)      0
+#define ip_addr_cmp(addr1, addr2)               ip6_addr_cmp(addr1, addr2)
+#define ip_addr_isany(ipaddr)                   ip6_addr_isany(ipaddr)
+#define ip_addr_isany_val(ipaddr)               ip6_addr_isany_val(ipaddr)
+#define ip_addr_isloopback(ipaddr)              ip6_addr_isloopback(ipaddr)
+#define ip_addr_islinklocal(ipaddr)             ip6_addr_islinklocal(ipaddr)
+#define ip_addr_isbroadcast(addr, netif)        0
+#define ip_addr_ismulticast(ipaddr)             ip6_addr_ismulticast(ipaddr)
+#define ip_addr_debug_print(debug, ipaddr)      ip6_addr_debug_print(debug, ipaddr)
+#define ip_addr_debug_print_val(debug, ipaddr)  ip6_addr_debug_print_val(debug, ipaddr)
+#define ipaddr_ntoa(ipaddr)                     ip6addr_ntoa(ipaddr)
+#define ipaddr_ntoa_r(ipaddr, buf, buflen)      ip6addr_ntoa_r(ipaddr, buf, buflen)
+#define ipaddr_aton(cp, addr)                   ip6addr_aton(cp, addr)
+
+#define IPADDR_STRLEN_MAX   IP6ADDR_STRLEN_MAX
+
+#define IP46_ADDR_ANY(type) (IP6_ADDR_ANY)
+
+#endif /* LWIP_IPV4 */
+#endif /* LWIP_IPV4 && LWIP_IPV6 */
+
+#if LWIP_IPV4
+
+extern const ip_addr_t ip_addr_any;
+extern const ip_addr_t ip_addr_broadcast;
+
+/**
+ * @ingroup ip4addr
+ * Can be used as a fixed/const ip_addr_t
+ * for the IP wildcard.
+ * Defined to @ref IP4_ADDR_ANY when IPv4 is enabled.
+ * Defined to @ref IP6_ADDR_ANY in IPv6 only systems.
+ * Use this if you can handle IPv4 _AND_ IPv6 addresses.
+ * Use @ref IP4_ADDR_ANY or @ref IP6_ADDR_ANY when the IP
+ * type matters.
+ */
+#define IP_ADDR_ANY         IP4_ADDR_ANY
+/**
+ * @ingroup ip4addr
+ * Can be used as a fixed/const ip_addr_t
+ * for the IPv4 wildcard and the broadcast address
+ */
+#define IP4_ADDR_ANY        (&ip_addr_any)
+/**
+ * @ingroup ip4addr
+ * Can be used as a fixed/const ip4_addr_t
+ * for the wildcard and the broadcast address
+ */
+#define IP4_ADDR_ANY4       (ip_2_ip4(&ip_addr_any))
+
+/** @ingroup ip4addr */
+#define IP_ADDR_BROADCAST   (&ip_addr_broadcast)
+/** @ingroup ip4addr */
+#define IP4_ADDR_BROADCAST  (ip_2_ip4(&ip_addr_broadcast))
+
+#endif /* LWIP_IPV4*/
+
+#if LWIP_IPV6
+
+extern const ip_addr_t ip6_addr_any;
+
+/** 
+ * @ingroup ip6addr
+ * IP6_ADDR_ANY can be used as a fixed ip_addr_t
+ * for the IPv6 wildcard address
+ */
+#define IP6_ADDR_ANY   (&ip6_addr_any)
+/**
+ * @ingroup ip6addr
+ * IP6_ADDR_ANY6 can be used as a fixed ip6_addr_t
+ * for the IPv6 wildcard address
+ */
+#define IP6_ADDR_ANY6  (ip_2_ip6(&ip6_addr_any))
+
+#if !LWIP_IPV4
+/** IPv6-only configurations */
+#define IP_ADDR_ANY IP6_ADDR_ANY
+#endif /* !LWIP_IPV4 */
+
+#endif
+
+#if LWIP_IPV4 && LWIP_IPV6
+/** @ingroup ipaddr */
+#define IP_ANY_TYPE    (&ip_addr_any_type)
+#else
+#define IP_ANY_TYPE    IP_ADDR_ANY
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_IP_ADDR_H */
diff --git a/src/include/lwip/mem.h b/src/include/lwip/mem.h
new file mode 100644
index 0000000..ff208d2
--- /dev/null
+++ b/src/include/lwip/mem.h
@@ -0,0 +1,82 @@
+/**
+ * @file
+ * Heap API
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_MEM_H
+#define LWIP_HDR_MEM_H
+
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if MEM_LIBC_MALLOC
+
+#include "lwip/arch.h"
+
+typedef size_t mem_size_t;
+#define MEM_SIZE_F SZT_F
+
+#elif MEM_USE_POOLS
+
+typedef u16_t mem_size_t;
+#define MEM_SIZE_F U16_F
+
+#else
+
+/* 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 */
+#endif
+
+void  mem_init(void);
+void *mem_trim(void *mem, mem_size_t size);
+void *mem_malloc(mem_size_t size);
+void *mem_calloc(mem_size_t count, mem_size_t size);
+void  mem_free(void *mem);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_MEM_H */
diff --git a/src/include/lwip/memp.h b/src/include/lwip/memp.h
new file mode 100644
index 0000000..562cd05
--- /dev/null
+++ b/src/include/lwip/memp.h
@@ -0,0 +1,155 @@
+/**
+ * @file
+ * Memory pool API
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+
+#ifndef LWIP_HDR_MEMP_H
+#define LWIP_HDR_MEMP_H
+
+#include "lwip/opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* run once with empty definition to handle all custom includes in lwippools.h */
+#define LWIP_MEMPOOL(name,num,size,desc)
+#include "lwip/priv/memp_std.h"
+
+/** 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/priv/memp_std.h"
+  MEMP_MAX
+} memp_t;
+
+#include "lwip/priv/memp_priv.h"
+#include "lwip/stats.h"
+
+extern const struct memp_desc* const memp_pools[MEMP_MAX];
+
+/**
+ * @ingroup mempool
+ * Declare prototype for private memory pool if it is used in multiple files
+ */
+#define LWIP_MEMPOOL_PROTOTYPE(name) extern const struct memp_desc memp_ ## name
+
+#if MEMP_MEM_MALLOC
+
+#define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \
+  LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \
+  const struct memp_desc memp_ ## name = { \
+    DECLARE_LWIP_MEMPOOL_DESC(desc) \
+    LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \
+    LWIP_MEM_ALIGN_SIZE(size) \
+  };
+
+#else /* MEMP_MEM_MALLOC */
+
+/**
+ * @ingroup mempool
+ * Declare a private memory pool
+ * Private mempools example:
+ * .h: only when pool is used in multiple .c files: LWIP_MEMPOOL_PROTOTYPE(my_private_pool);
+ * .c:
+ *   - in global variables section: LWIP_MEMPOOL_DECLARE(my_private_pool, 10, sizeof(foo), "Some description")
+ *   - call ONCE before using pool (e.g. in some init() function): LWIP_MEMPOOL_INIT(my_private_pool);
+ *   - allocate: void* my_new_mem = LWIP_MEMPOOL_ALLOC(my_private_pool);
+ *   - free: LWIP_MEMPOOL_FREE(my_private_pool, my_new_mem);
+ *
+ * To relocate a pool, declare it as extern in cc.h. Example for GCC:
+ *   extern u8_t __attribute__((section(".onchip_mem"))) memp_memory_my_private_pool[];
+ */
+#define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \
+  LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_ ## name ## _base, ((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))); \
+    \
+  LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \
+    \
+  static struct memp *memp_tab_ ## name; \
+    \
+  const struct memp_desc memp_ ## name = { \
+    DECLARE_LWIP_MEMPOOL_DESC(desc) \
+    LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \
+    LWIP_MEM_ALIGN_SIZE(size), \
+    (num), \
+    memp_memory_ ## name ## _base, \
+    &memp_tab_ ## name \
+  };
+
+#endif /* MEMP_MEM_MALLOC */
+
+/**
+ * @ingroup mempool
+ * Initialize a private memory pool
+ */
+#define LWIP_MEMPOOL_INIT(name)    memp_init_pool(&memp_ ## name)
+/**
+ * @ingroup mempool
+ * Allocate from a private memory pool
+ */
+#define LWIP_MEMPOOL_ALLOC(name)   memp_malloc_pool(&memp_ ## name)
+/**
+ * @ingroup mempool
+ * Free element from a private memory pool
+ */
+#define LWIP_MEMPOOL_FREE(name, x) memp_free_pool(&memp_ ## name, (x))
+
+#if MEM_USE_POOLS
+/** This structure is used to save the pool one element came from.
+ * This has to be defined here as it is required for pool size calculation. */
+struct memp_malloc_helper
+{
+   memp_t poolnr;
+#if MEMP_OVERFLOW_CHECK || (LWIP_STATS && MEM_STATS)
+   u16_t size;
+#endif /* MEMP_OVERFLOW_CHECK || (LWIP_STATS && MEM_STATS) */
+};
+#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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_HDR_MEMP_H */
diff --git a/src/include/lwip/mld6.h b/src/include/lwip/mld6.h
new file mode 100644
index 0000000..7fa0797
--- /dev/null
+++ b/src/include/lwip/mld6.h
@@ -0,0 +1,99 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#ifndef LWIP_HDR_MLD6_H
+#define LWIP_HDR_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
+
+/** MLD group */
+struct mld_group {
+  /** next link */
+  struct mld_group *next;
+  /** 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;
+};
+
+#define MLD6_TMR_INTERVAL              100 /* Milliseconds */
+
+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, const ip6_addr_t *addr);
+void   mld6_input(struct pbuf *p, struct netif *inp);
+err_t  mld6_joingroup(const ip6_addr_t *srcaddr, const ip6_addr_t *groupaddr);
+err_t  mld6_joingroup_netif(struct netif *netif, const ip6_addr_t *groupaddr);
+err_t  mld6_leavegroup(const ip6_addr_t *srcaddr, const ip6_addr_t *groupaddr);
+err_t  mld6_leavegroup_netif(struct netif *netif, const ip6_addr_t *groupaddr);
+
+/** @ingroup mld6
+ * Get list head of MLD6 groups for netif.
+ * Note: The allnodes group IP is NOT in the list, since it must always 
+ * be received for correct IPv6 operation.
+ * @see @ref netif_set_mld_mac_filter()
+ */
+#define netif_mld6_data(netif) ((struct mld_group *)netif_get_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_MLD6))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV6_MLD && LWIP_IPV6 */
+
+#endif /* LWIP_HDR_MLD6_H */
diff --git a/src/include/lwip/nd6.h b/src/include/lwip/nd6.h
new file mode 100644
index 0000000..8204fa4
--- /dev/null
+++ b/src/include/lwip/nd6.h
@@ -0,0 +1,84 @@
+/**
+ * @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@inicotech.com>
+ *
+ *
+ * Please coordinate changes and requests with Ivan Delamer
+ * <delamer@inicotech.com>
+ */
+
+#ifndef LWIP_HDR_ND6_H
+#define LWIP_HDR_ND6_H
+
+#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/err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** 1 second period */
+#define ND6_TMR_INTERVAL 1000
+
+struct pbuf;
+struct netif;
+
+void nd6_tmr(void);
+void nd6_input(struct pbuf *p, struct netif *inp);
+void nd6_clear_destination_cache(void);
+struct netif *nd6_find_route(const ip6_addr_t *ip6addr);
+err_t nd6_get_next_hop_addr_or_queue(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr, const u8_t **hwaddrp);
+u16_t nd6_get_destination_mtu(const ip6_addr_t *ip6addr, struct netif *netif);
+#if LWIP_ND6_TCP_REACHABILITY_HINTS
+void nd6_reachability_hint(const ip6_addr_t *ip6addr);
+#endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */
+void nd6_cleanup_netif(struct netif *netif);
+#if LWIP_IPV6_MLD
+void nd6_adjust_mld_membership(struct netif *netif, s8_t addr_idx, u8_t new_state);
+#endif /* LWIP_IPV6_MLD */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_IPV6 */
+
+#endif /* LWIP_HDR_ND6_H */
diff --git a/src/include/lwip/netbuf.h b/src/include/lwip/netbuf.h
new file mode 100644
index 0000000..e6865f8
--- /dev/null
+++ b/src/include/lwip/netbuf.h
@@ -0,0 +1,118 @@
+/**
+ * @file
+ * netbuf API (for netconn API)
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_NETBUF_H
+#define LWIP_HDR_NETBUF_H
+
+#include "lwip/opt.h"
+
+#if LWIP_NETCONN || LWIP_SOCKET /* don't build if not configured for use in lwipopts.h */
+/* Note: Netconn API is always available when sockets are enabled -
+ * sockets are implemented on top of them */
+
+#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
+
+/** "Network buffer" - contains data and addressing info */
+struct netbuf {
+  struct pbuf *p, *ptr;
+  ip_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
+  ip_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)         (&((buf)->addr))
+#define netbuf_set_fromaddr(buf, fromaddr) ip_addr_set(&((buf)->addr), fromaddr)
+#define netbuf_fromport(buf)         ((buf)->port)
+#if LWIP_NETBUF_RECVINFO
+#define netbuf_destaddr(buf)         (&((buf)->toaddr))
+#define netbuf_set_destaddr(buf, destaddr) ip_addr_set(&((buf)->toaddr), destaddr)
+#if LWIP_CHECKSUM_ON_COPY
+#define netbuf_destport(buf)         (((buf)->flags & NETBUF_FLAG_DESTADDR) ? (buf)->toport_chksum : 0)
+#else /* LWIP_CHECKSUM_ON_COPY */
+#define netbuf_destport(buf)         ((buf)->toport_chksum)
+#endif /* LWIP_CHECKSUM_ON_COPY */
+#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 */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_NETCONN || LWIP_SOCKET */
+
+#endif /* LWIP_HDR_NETBUF_H */
diff --git a/src/include/lwip/netdb.h b/src/include/lwip/netdb.h
new file mode 100644
index 0000000..d3d15df
--- /dev/null
+++ b/src/include/lwip/netdb.h
@@ -0,0 +1,150 @@
+/**
+ * @file
+ * NETDB API (sockets)
+ */
+
+/*
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce 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_HDR_NETDB_H
+#define LWIP_HDR_NETDB_H
+
+#include "lwip/opt.h"
+
+#if LWIP_DNS && LWIP_SOCKET
+
+#include "lwip/arch.h"
+#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_DEFINE_FLAGS
+#define LWIP_DNS_API_DEFINE_FLAGS     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 EAI_FAMILY      204
+
+#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_DEFINE_FLAGS
+/* input flags for struct addrinfo */
+#define AI_PASSIVE      0x01
+#define AI_CANONNAME    0x02
+#define AI_NUMERICHOST  0x04
+#define AI_NUMERICSERV  0x08
+#define AI_V4MAPPED     0x10
+#define AI_ALL          0x20
+#define AI_ADDRCONFIG   0x40
+#endif /* LWIP_DNS_API_DEFINE_FLAGS */
+
+#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 */
+
+#define NETDB_ELEM_SIZE           (sizeof(struct addrinfo) + sizeof(struct sockaddr_storage) + DNS_MAX_NAME_LENGTH + 1)
+
+#if LWIP_DNS_API_DECLARE_H_ERRNO
+/* application accessible 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
+/** @ingroup netdbapi */
+#define gethostbyname(name) lwip_gethostbyname(name)
+/** @ingroup netdbapi */
+#define gethostbyname_r(name, ret, buf, buflen, result, h_errnop) \
+       lwip_gethostbyname_r(name, ret, buf, buflen, result, h_errnop)
+/** @ingroup netdbapi */
+#define freeaddrinfo(addrinfo) lwip_freeaddrinfo(addrinfo)
+/** @ingroup netdbapi */
+#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_HDR_NETDB_H */
diff --git a/src/include/lwip/netif.h b/src/include/lwip/netif.h
new file mode 100644
index 0000000..67a2d24
--- /dev/null
+++ b/src/include/lwip/netif.h
@@ -0,0 +1,474 @@
+/**
+ * @file
+ * netif API (to be used from TCPIP thread)
+ */
+
+/*
+ * 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@sics.se>
+ *
+ */
+#ifndef LWIP_HDR_NETIF_H
+#define LWIP_HDR_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/def.h"
+#include "lwip/pbuf.h"
+#include "lwip/stats.h"
+
+#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.
+    This does not have to be changed, normally. */
+#ifndef NETIF_MAX_HWADDR_LEN
+#define NETIF_MAX_HWADDR_LEN 6U
+#endif
+
+/**
+ * @defgroup netif_flags Flags
+ * @ingroup netif
+ * @{
+ */
+
+/** Whether the network interface is 'up'. This is
+ * a software flag used to control whether this network
+ * interface is enabled and processes traffic.
+ * It must be set by the startup code before this netif can be used
+ * (also for dhcp/autoip).
+ */
+#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 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      0x04U
+/** 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       0x08U
+/** 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     0x10U
+/** If set, the netif has IGMP capability.
+ * Set by the netif driver in its init function. */
+#define NETIF_FLAG_IGMP         0x20U
+/** If set, the netif has MLD6 capability.
+ * Set by the netif driver in its init function. */
+#define NETIF_FLAG_MLD6         0x40U
+
+/**
+ * @}
+ */
+
+enum lwip_internal_netif_client_data_index
+{
+#if LWIP_DHCP
+   LWIP_NETIF_CLIENT_DATA_INDEX_DHCP,
+#endif
+#if LWIP_AUTOIP
+   LWIP_NETIF_CLIENT_DATA_INDEX_AUTOIP,
+#endif
+#if LWIP_IGMP
+   LWIP_NETIF_CLIENT_DATA_INDEX_IGMP,
+#endif
+#if LWIP_IPV6_MLD
+   LWIP_NETIF_CLIENT_DATA_INDEX_MLD6,
+#endif
+   LWIP_NETIF_CLIENT_DATA_INDEX_MAX
+};
+
+#if LWIP_CHECKSUM_CTRL_PER_NETIF
+#define NETIF_CHECKSUM_GEN_IP       0x0001
+#define NETIF_CHECKSUM_GEN_UDP      0x0002
+#define NETIF_CHECKSUM_GEN_TCP      0x0004
+#define NETIF_CHECKSUM_GEN_ICMP     0x0008
+#define NETIF_CHECKSUM_GEN_ICMP6    0x0010
+#define NETIF_CHECKSUM_CHECK_IP     0x0100
+#define NETIF_CHECKSUM_CHECK_UDP    0x0200
+#define NETIF_CHECKSUM_CHECK_TCP    0x0400
+#define NETIF_CHECKSUM_CHECK_ICMP   0x0800
+#define NETIF_CHECKSUM_CHECK_ICMP6  0x1000
+#define NETIF_CHECKSUM_ENABLE_ALL   0xFFFF
+#define NETIF_CHECKSUM_DISABLE_ALL  0x0000
+#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF */
+
+struct netif;
+
+/** MAC Filter Actions, these are passed to a netif's igmp_mac_filter or
+ * mld_mac_filter callback function. */
+enum netif_mac_filter_action {
+  /** Delete a filter entry */
+  NETIF_DEL_MAC_FILTER = 0,
+  /** Add a filter entry */
+  NETIF_ADD_MAC_FILTER = 1
+};
+
+/** 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);
+
+#if LWIP_IPV4
+/** 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,
+       const ip4_addr_t *ipaddr);
+#endif /* LWIP_IPV4*/
+
+#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 'ethip6_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,
+       const 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);
+#if LWIP_IPV4 && LWIP_IGMP
+/** Function prototype for netif igmp_mac_filter functions */
+typedef err_t (*netif_igmp_mac_filter_fn)(struct netif *netif,
+       const ip4_addr_t *group, enum netif_mac_filter_action action);
+#endif /* LWIP_IPV4 && LWIP_IGMP */
+#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,
+       const ip6_addr_t *group, enum netif_mac_filter_action action);
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+
+#if LWIP_DHCP || LWIP_AUTOIP || LWIP_IGMP || LWIP_IPV6_MLD || (LWIP_NUM_NETIF_CLIENT_DATA > 0)
+u8_t netif_alloc_client_data_id(void);
+/** @ingroup netif_cd
+ * Set client data. Obtain ID from netif_alloc_client_data_id().
+ */
+#define netif_set_client_data(netif, id, data) netif_get_client_data(netif, id) = (data)
+/** @ingroup netif_cd
+ * Get client data. Obtain ID from netif_alloc_client_data_id().
+ */
+#define netif_get_client_data(netif, id)       (netif)->client_data[(id)]
+#endif /* LWIP_DHCP || LWIP_AUTOIP || (LWIP_NUM_NETIF_CLIENT_DATA > 0) */
+
+/** 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;
+
+#if LWIP_IPV4
+  /** IP address configuration in network byte order */
+  ip_addr_t ip_addr;
+  ip_addr_t netmask;
+  ip_addr_t gw;
+#endif /* LWIP_IPV4 */
+#if LWIP_IPV6
+  /** Array of IPv6 addresses for this netif. */
+  ip_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;
+#if LWIP_IPV4
+  /** 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.
+   *  For ethernet physical layer, this is usually etharp_output() */
+  netif_output_fn output;
+#endif /* LWIP_IPV4 */
+  /** This function is called by ethernet_output() 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.
+   *  For ethernet physical layer, this is usually ethip6_output() */
+  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;
+#ifdef netif_get_client_data
+  void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX + LWIP_NUM_NETIF_CLIENT_DATA];
+#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_NETIF_HOSTNAME
+  /* the hostname for this netif, NULL is a valid value */
+  const char*  hostname;
+#endif /* LWIP_NETIF_HOSTNAME */
+#if LWIP_CHECKSUM_CTRL_PER_NETIF
+  u16_t chksum_flags;
+#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
+  /** 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 @ref netif_flags) */
+  u8_t flags;
+  /** descriptive abbreviation */
+  char name[2];
+  /** number of this interface */
+  u8_t num;
+#if MIB2_STATS
+  /** link type (from "snmp_ifType" enum from snmp_mib2.h) */
+  u8_t link_type;
+  /** (estimate) link speed */
+  u32_t link_speed;
+  /** timestamp at last change made (up/down) */
+  u32_t ts;
+  /** counters */
+  struct stats_mib2_netif_ctrs mib2_counters;
+#endif /* MIB2_STATS */
+#if LWIP_IPV4 && 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_IPV4 && 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_CHECKSUM_CTRL_PER_NETIF
+#define NETIF_SET_CHECKSUM_CTRL(netif, chksumflags) do { \
+  (netif)->chksum_flags = chksumflags; } while(0)
+#define IF__NETIF_CHECKSUM_ENABLED(netif, chksumflag) if (((netif) == NULL) || (((netif)->chksum_flags & (chksumflag)) != 0))
+#else /* LWIP_CHECKSUM_CTRL_PER_NETIF */
+#define NETIF_SET_CHECKSUM_CTRL(netif, chksumflags)
+#define IF__NETIF_CHECKSUM_ENABLED(netif, chksumflag)
+#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF */
+
+/** 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,
+#if LWIP_IPV4
+                        const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
+#endif /* LWIP_IPV4 */
+                        void *state, netif_init_fn init, netif_input_fn input);
+#if LWIP_IPV4
+void netif_set_addr(struct netif *netif, const ip4_addr_t *ipaddr, const ip4_addr_t *netmask,
+                    const ip4_addr_t *gw);
+#endif /* LWIP_IPV4 */
+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(const char *name);
+
+void netif_set_default(struct netif *netif);
+
+#if LWIP_IPV4
+void netif_set_ipaddr(struct netif *netif, const ip4_addr_t *ipaddr);
+void netif_set_netmask(struct netif *netif, const ip4_addr_t *netmask);
+void netif_set_gw(struct netif *netif, const ip4_addr_t *gw);
+/** @ingroup netif_ip4 */
+#define netif_ip4_addr(netif)    ((const ip4_addr_t*)ip_2_ip4(&((netif)->ip_addr)))
+/** @ingroup netif_ip4 */
+#define netif_ip4_netmask(netif) ((const ip4_addr_t*)ip_2_ip4(&((netif)->netmask)))
+/** @ingroup netif_ip4 */
+#define netif_ip4_gw(netif)      ((const ip4_addr_t*)ip_2_ip4(&((netif)->gw)))
+/** @ingroup netif_ip4 */
+#define netif_ip_addr4(netif)    ((const ip_addr_t*)&((netif)->ip_addr))
+/** @ingroup netif_ip4 */
+#define netif_ip_netmask4(netif) ((const ip_addr_t*)&((netif)->netmask))
+/** @ingroup netif_ip4 */
+#define netif_ip_gw4(netif)      ((const ip_addr_t*)&((netif)->gw))
+#endif /* LWIP_IPV4 */
+
+void netif_set_up(struct netif *netif);
+void netif_set_down(struct netif *netif);
+/** @ingroup 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
+/** @ingroup netif */
+#define netif_set_hostname(netif, name) do { if((netif) != NULL) { (netif)->hostname = name; }}while(0)
+/** @ingroup netif */
+#define netif_get_hostname(netif) (((netif) != NULL) ? ((netif)->hostname) : NULL)
+#endif /* LWIP_NETIF_HOSTNAME */
+
+#if LWIP_IGMP
+/** @ingroup netif */
+#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 LWIP_IPV6 && LWIP_IPV6_MLD
+/** @ingroup netif */
+#define netif_set_mld_mac_filter(netif, function) do { if((netif) != NULL) { (netif)->mld_mac_filter = function; }}while(0)
+#define netif_get_mld_mac_filter(netif) (((netif) != NULL) ? ((netif)->mld_mac_filter) : NULL)
+#define netif_mld_mac_filter(netif, addr, action) do { if((netif) && (netif)->mld_mac_filter) { (netif)->mld_mac_filter((netif), (addr), (action)); }}while(0)
+#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
+
+#if ENABLE_LOOPBACK
+err_t netif_loop_output(struct netif *netif, struct pbuf *p);
+void netif_poll(struct netif *netif);
+#if !LWIP_NETIF_LOOPBACK_MULTITHREADING
+void netif_poll_all(void);
+#endif /* !LWIP_NETIF_LOOPBACK_MULTITHREADING */
+#endif /* ENABLE_LOOPBACK */
+
+err_t netif_input(struct pbuf *p, struct netif *inp);
+
+#if LWIP_IPV6
+/** @ingroup netif_ip6 */
+#define netif_ip_addr6(netif, i)  ((const ip_addr_t*)(&((netif)->ip6_addr[i])))
+/** @ingroup netif_ip6 */
+#define netif_ip6_addr(netif, i)  ((const ip6_addr_t*)ip_2_ip6(&((netif)->ip6_addr[i])))
+void netif_ip6_addr_set(struct netif *netif, s8_t addr_idx, const ip6_addr_t *addr6);
+void netif_ip6_addr_set_parts(struct netif *netif, s8_t addr_idx, u32_t i0, u32_t i1, u32_t i2, u32_t i3);
+#define netif_ip6_addr_state(netif, i)  ((netif)->ip6_addr_state[i])
+void netif_ip6_addr_set_state(struct netif* netif, s8_t addr_idx, u8_t state);
+s8_t netif_get_ip6_addr_match(struct netif *netif, const ip6_addr_t *ip6addr);
+void netif_create_ip6_linklocal_address(struct netif *netif, u8_t from_mac_48bit);
+err_t netif_add_ip6_address(struct netif *netif, const ip6_addr_t *ip6addr, s8_t *chosen_idx);
+#define netif_set_ip6_autoconfig_enabled(netif, action) do { if(netif) { (netif)->ip6_autoconfig_enabled = (action); }}while(0)
+#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_HDR_NETIF_H */
diff --git a/src/include/lwip/netifapi.h b/src/include/lwip/netifapi.h
new file mode 100644
index 0000000..8bd2b4f
--- /dev/null
+++ b/src/include/lwip/netifapi.h
@@ -0,0 +1,140 @@
+/**
+ * @file
+ * netif API (to be used from non-TCPIP threads)
+ */
+
+/*
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce 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_HDR_NETIFAPI_H
+#define LWIP_HDR_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"
+#include "lwip/priv/tcpip_priv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if LWIP_MPU_COMPATIBLE
+#define NETIFAPI_IPADDR_DEF(type, m)  type m
+#else /* LWIP_MPU_COMPATIBLE */
+#define NETIFAPI_IPADDR_DEF(type, m)  const type * m
+#endif /* LWIP_MPU_COMPATIBLE */
+
+typedef void (*netifapi_void_fn)(struct netif *netif);
+typedef err_t (*netifapi_errt_fn)(struct netif *netif);
+
+struct netifapi_msg {
+  struct tcpip_api_call_data call;
+  struct netif *netif;
+  union {
+    struct {
+#if LWIP_IPV4
+      NETIFAPI_IPADDR_DEF(ip4_addr_t, ipaddr);
+      NETIFAPI_IPADDR_DEF(ip4_addr_t, netmask);
+      NETIFAPI_IPADDR_DEF(ip4_addr_t, gw);
+#endif /* LWIP_IPV4 */
+      void *state;
+      netif_init_fn init;
+      netif_input_fn input;
+    } add;
+    struct {
+      netifapi_void_fn voidfunc;
+      netifapi_errt_fn errtfunc;
+    } common;
+  } msg;
+};
+
+
+/* API for application */
+err_t netifapi_netif_add(struct netif *netif,
+#if LWIP_IPV4
+                         const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
+#endif /* LWIP_IPV4 */
+                         void *state, netif_init_fn init, netif_input_fn input);
+
+#if LWIP_IPV4
+err_t netifapi_netif_set_addr(struct netif *netif, const ip4_addr_t *ipaddr,
+                              const ip4_addr_t *netmask, const ip4_addr_t *gw);
+#endif /* LWIP_IPV4*/
+
+err_t netifapi_netif_common(struct netif *netif, netifapi_void_fn voidfunc,
+                            netifapi_errt_fn errtfunc);
+
+/** @ingroup netifapi_netif */
+#define netifapi_netif_remove(n)        netifapi_netif_common(n, netif_remove, NULL)
+/** @ingroup netifapi_netif */
+#define netifapi_netif_set_up(n)        netifapi_netif_common(n, netif_set_up, NULL)
+/** @ingroup netifapi_netif */
+#define netifapi_netif_set_down(n)      netifapi_netif_common(n, netif_set_down, NULL)
+/** @ingroup netifapi_netif */
+#define netifapi_netif_set_default(n)   netifapi_netif_common(n, netif_set_default, NULL)
+/** @ingroup netifapi_netif */
+#define netifapi_netif_set_link_up(n)   netifapi_netif_common(n, netif_set_link_up, NULL)
+/** @ingroup netifapi_netif */
+#define netifapi_netif_set_link_down(n) netifapi_netif_common(n, netif_set_link_down, NULL)
+
+/**
+ * @defgroup netifapi_dhcp4 DHCPv4
+ * @ingroup netifapi
+ * To be called from non-TCPIP threads
+ */
+/** @ingroup netifapi_dhcp4 */
+#define netifapi_dhcp_start(n)        netifapi_netif_common(n, NULL, dhcp_start)
+/** @ingroup netifapi_dhcp4 */
+#define netifapi_dhcp_stop(n)         netifapi_netif_common(n, dhcp_stop, NULL)
+/** @ingroup netifapi_dhcp4 */
+#define netifapi_dhcp_inform(n)       netifapi_netif_common(n, dhcp_inform, NULL)
+/** @ingroup netifapi_dhcp4 */
+#define netifapi_dhcp_renew(n)        netifapi_netif_common(n, NULL, dhcp_renew)
+/** @ingroup netifapi_dhcp4 */
+#define netifapi_dhcp_release(n)      netifapi_netif_common(n, NULL, dhcp_release)
+
+/**
+ * @defgroup netifapi_autoip AUTOIP
+ * @ingroup netifapi
+ * To be called from non-TCPIP threads
+ */
+/** @ingroup netifapi_autoip */
+#define netifapi_autoip_start(n)      netifapi_netif_common(n, NULL, autoip_start)
+/** @ingroup netifapi_autoip */
+#define netifapi_autoip_stop(n)       netifapi_netif_common(n, NULL, autoip_stop)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_NETIF_API */
+
+#endif /* LWIP_HDR_NETIFAPI_H */
diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h
new file mode 100644
index 0000000..fd459af
--- /dev/null
+++ b/src/include/lwip/opt.h
@@ -0,0 +1,2876 @@
+/**
+ * @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@sics.se>
+ *
+ */
+
+/*
+ * NOTE: || defined __DOXYGEN__ is a workaround for doxygen bug -
+ * without this, doxygen does not see the actual #define
+ */
+
+#if !defined LWIP_HDR_OPT_H
+#define LWIP_HDR_OPT_H
+
+/*
+ * Include user defined options first. Anything not defined in these files
+ * will be set to standard values. Override anything you don't like!
+ */
+#include "lwipopts.h"
+#include "lwip/debug.h"
+
+/**
+ * @defgroup lwip_opts Options (lwipopts.h)
+ * @ingroup lwip
+ *
+ * @defgroup lwip_opts_debug Debugging
+ * @ingroup lwip_opts
+ *
+ * @defgroup lwip_opts_infrastructure Infrastructure
+ * @ingroup lwip_opts
+ *
+ * @defgroup lwip_opts_callback Callback-style APIs
+ * @ingroup lwip_opts
+ *
+ * @defgroup lwip_opts_threadsafe_apis Thread-safe APIs
+ * @ingroup lwip_opts
+ */
+
+ /*
+   ------------------------------------
+   -------------- NO SYS --------------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_nosys NO_SYS
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * NO_SYS==1: Use lwIP without OS-awareness (no thread, semaphores, mutexes or
+ * mboxes). This means threaded APIs cannot be used (socket, netconn,
+ * i.e. everything in the 'api' folder), only the callback-style raw API is
+ * available (and you have to watch out for yourself that you don't access
+ * lwIP functions/structures from more than one context at a time!)
+ */
+#if !defined NO_SYS || defined __DOXYGEN__
+#define NO_SYS                          0
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_timers Timers
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_TIMERS==0: Drop support for sys_timeout and lwip-internal cyclic timers.
+ * (the array of lwip-internal cyclic timers is still provided)
+ * (check NO_SYS_NO_TIMERS for compatibility to old versions)
+ */
+#if !defined LWIP_TIMERS || defined __DOXYGEN__
+#ifdef NO_SYS_NO_TIMERS
+#define LWIP_TIMERS                     (!NO_SYS || (NO_SYS && !NO_SYS_NO_TIMERS))
+#else
+#define LWIP_TIMERS                     1
+#endif
+#endif
+
+/**
+ * LWIP_TIMERS_CUSTOM==1: Provide your own timer implementation.
+ * Function prototypes in timeouts.h and the array of lwip-internal cyclic timers
+ * are still included, but the implementation is not. The following functions
+ * will be required: sys_timeouts_init(), sys_timeout(), sys_untimeout(),
+ *                   sys_timeouts_mbox_fetch()
+ */
+#if !defined LWIP_TIMERS_CUSTOM || defined __DOXYGEN__
+#define LWIP_TIMERS_CUSTOM              0
+#endif
+/**
+ * @}
+ */
+
+/**
+ * @defgroup lwip_opts_memcpy memcpy
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * MEMCPY: override this if you have a faster implementation at hand than the
+ * one included in your C library
+ */
+#if !defined MEMCPY || defined __DOXYGEN__
+#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.
+ */
+#if !defined SMEMCPY || defined __DOXYGEN__
+#define SMEMCPY(dst,src,len)            memcpy(dst,src,len)
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ----------- Core locking -----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_lock Core locking and MPU
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * LWIP_MPU_COMPATIBLE: enables special memory management mechanism
+ * which makes lwip able to work on MPU (Memory Protection Unit) system
+ * by not passing stack-pointers to other threads
+ * (this decreases performance as memory is allocated from pools instead
+ * of keeping it on the stack)
+ */
+#if !defined LWIP_MPU_COMPATIBLE || defined __DOXYGEN__
+#define LWIP_MPU_COMPATIBLE             0
+#endif
+
+/**
+ * LWIP_TCPIP_CORE_LOCKING
+ * Creates a global mutex that is held during TCPIP thread operations.
+ * Can be locked by client code to perform lwIP operations without changing
+ * into TCPIP thread using callbacks. See LOCK_TCPIP_CORE() and
+ * UNLOCK_TCPIP_CORE().
+ * Your system should provide mutexes supporting priority inversion to use this.
+ */
+#if !defined LWIP_TCPIP_CORE_LOCKING || defined __DOXYGEN__
+#define LWIP_TCPIP_CORE_LOCKING         1
+#endif
+
+/**
+ * LWIP_TCPIP_CORE_LOCKING_INPUT: when LWIP_TCPIP_CORE_LOCKING is enabled,
+ * this lets tcpip_input() grab the mutex for input packets as well,
+ * instead of allocating a message and passing it to tcpip_thread.
+ *
+ * ATTENTION: this does not work when tcpip_input() is called from
+ * interrupt context!
+ */
+#if !defined LWIP_TCPIP_CORE_LOCKING_INPUT || defined __DOXYGEN__
+#define LWIP_TCPIP_CORE_LOCKING_INPUT   0
+#endif
+
+/**
+ * SYS_LIGHTWEIGHT_PROT==1: enable inter-task protection (and task-vs-interrupt
+ * protection) for certain critical regions during buffer allocation, deallocation
+ * and memory allocation and deallocation.
+ * ATTENTION: This is required when using lwIP from more than one context! If
+ * you disable this, you must be sure what you are doing!
+ */
+#if !defined SYS_LIGHTWEIGHT_PROT || defined __DOXYGEN__
+#define SYS_LIGHTWEIGHT_PROT            1
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------
+   ---------- Memory options ----------
+   ------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_mem Heap and memory pools
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * 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.
+ */
+#if !defined MEM_LIBC_MALLOC || defined __DOXYGEN__
+#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 (heap alloc can be much slower than pool alloc) and usage from interrupts
+ * (especially if your netif driver allocates PBUF_POOL pbufs for received frames
+ * from interrupt)!
+ * ATTENTION: Currently, this uses the heap for ALL pools (also for private pools,
+ * not only for internal pools defined in memp_std.h)!
+ */
+#if !defined MEMP_MEM_MALLOC || defined __DOXYGEN__
+#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
+ */
+#if !defined MEM_ALIGNMENT || defined __DOXYGEN__
+#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.
+ */
+#if !defined MEM_SIZE || defined __DOXYGEN__
+#define MEM_SIZE                        1600
+#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!)
+ */
+#if !defined MEMP_OVERFLOW_CHECK || defined __DOXYGEN__
+#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.
+ */
+#if !defined MEMP_SANITY_CHECK || defined __DOXYGEN__
+#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.
+ */
+#if !defined MEM_USE_POOLS || defined __DOXYGEN__
+#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. */
+#if !defined MEM_USE_POOLS_TRY_BIGGER_POOL || defined __DOXYGEN__
+#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
+ * include path somewhere.
+ */
+#if !defined MEMP_USE_CUSTOM_POOLS || defined __DOXYGEN__
+#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);
+ */
+#if !defined LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT || defined __DOXYGEN__
+#define LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT 0
+#endif
+/**
+ * @}
+ */
+
+/*
+   ------------------------------------------------
+   ---------- Internal Memory Pool Sizes ----------
+   ------------------------------------------------
+*/
+/**
+ * @defgroup lwip_opts_memp Internal memory pools
+ * @ingroup lwip_opts_infrastructure
+ * @{
+ */
+/**
+ * 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.
+ */
+#if !defined MEMP_NUM_PBUF || defined __DOXYGEN__
+#define MEMP_NUM_PBUF                   16
+#endif
+
+/**
+ * MEMP_NUM_RAW_PCB: Number of raw connection PCBs
+ * (requires the LWIP_RAW option)
+ */
+#if !defined MEMP_NUM_RAW_PCB || defined __DOXYGEN__
+#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)
+ */
+#if !defined MEMP_NUM_UDP_PCB || defined __DOXYGEN__
+#define MEMP_NUM_UDP_PCB                4
+#endif
+
+/**
+ * MEMP_NUM_TCP_PCB: the number of simultaneously active TCP connections.
+ * (requires the LWIP_TCP option)
+ */
+#if !defined MEMP_NUM_TCP_PCB || defined __DOXYGEN__
+#define MEMP_NUM_TCP_PCB                5
+#endif
+
+/**
+ * MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP connections.
+ * (requires the LWIP_TCP option)
+ */
+#if !defined MEMP_NUM_TCP_PCB_LISTEN || defined __DOXYGEN__
+#define MEMP_NUM_TCP_PCB_LISTEN         8
+#endif
+
+/**
+ * MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP segments.
+ * (requires the LWIP_TCP option)
+ */
+#if !defined MEMP_NUM_TCP_SEG || defined __DOXYGEN__
+#define MEMP_NUM_TCP_SEG                16
+#endif
+
+/**
+ * MEMP_NUM_REASSDATA: the number of IP packets simultaneously queued for
+ * reassembly (whole packets, not fragments!)
+ */
+#if !defined MEMP_NUM_REASSDATA || defined __DOXYGEN__
+#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 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.
+ */
+#if !defined MEMP_NUM_FRAG_PBUF || defined __DOXYGEN__
+#define MEMP_NUM_FRAG_PBUF              15
+#endif
+
+/**
+ * MEMP_NUM_ARP_QUEUE: the number of simultaneously queued outgoing
+ * packets (pbufs) that are waiting for an ARP request (to resolve
+ * their destination address) to finish.
+ * (requires the ARP_QUEUEING option)
+ */
+#if !defined MEMP_NUM_ARP_QUEUE || defined __DOXYGEN__
+#define MEMP_NUM_ARP_QUEUE              30
+#endif
+
+/**
+ * MEMP_NUM_IGMP_GROUP: The number of multicast groups whose network interfaces
+ * can be members at the same time (one per netif - allsystems group -, plus one
+ * per netif membership).
+ * (requires the LWIP_IGMP option)
+ */
+#if !defined MEMP_NUM_IGMP_GROUP || defined __DOXYGEN__
+#define MEMP_NUM_IGMP_GROUP             8
+#endif
+
+/**
+ * MEMP_NUM_SYS_TIMEOUT: the number of simultaneously active timeouts.
+ * The default number of timeouts is calculated here for all enabled modules.
+ * The formula expects settings to be either '0' or '1'.
+ */
+#if !defined MEMP_NUM_SYS_TIMEOUT || defined __DOXYGEN__
+#define MEMP_NUM_SYS_TIMEOUT            (LWIP_TCP + IP_REASSEMBLY + LWIP_ARP + (2*LWIP_DHCP) + LWIP_AUTOIP + LWIP_IGMP + LWIP_DNS + (PPP_SUPPORT*6*MEMP_NUM_PPP_PCB) + (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)
+ */
+#if !defined MEMP_NUM_NETBUF || defined __DOXYGEN__
+#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)
+ */
+#if !defined MEMP_NUM_NETCONN || defined __DOXYGEN__
+#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)
+ */
+#if !defined MEMP_NUM_TCPIP_MSG_API || defined __DOXYGEN__
+#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)
+ */
+#if !defined MEMP_NUM_TCPIP_MSG_INPKT || defined __DOXYGEN__
+#define MEMP_NUM_TCPIP_MSG_INPKT        8
+#endif
+
+/**
+ * MEMP_NUM_NETDB: the number of concurrently running lwip_addrinfo() calls
+ * (before freeing the corresponding memory using lwip_freeaddrinfo()).
+ */
+#if !defined MEMP_NUM_NETDB || defined __DOXYGEN__
+#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.
+ */
+#if !defined MEMP_NUM_LOCALHOSTLIST || defined __DOXYGEN__
+#define MEMP_NUM_LOCALHOSTLIST          1
+#endif
+
+/**
+ * PBUF_POOL_SIZE: the number of buffers in the pbuf pool.
+ */
+#if !defined PBUF_POOL_SIZE || defined __DOXYGEN__
+#define PBUF_POOL_SIZE                  16
+#endif
+
+/** MEMP_NUM_API_MSG: the number of concurrently active calls to various
+ * socket, netconn, and tcpip functions
+ */
+#if !defined MEMP_NUM_API_MSG || defined __DOXYGEN__
+#define MEMP_NUM_API_MSG                MEMP_NUM_TCPIP_MSG_API
+#endif
+
+/** MEMP_NUM_DNS_API_MSG: the number of concurrently active calls to netconn_gethostbyname
+ */
+#if !defined MEMP_NUM_DNS_API_MSG || defined __DOXYGEN__
+#define MEMP_NUM_DNS_API_MSG            MEMP_NUM_TCPIP_MSG_API
+#endif
+
+/** MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA: the number of concurrently active calls
+ * to getsockopt/setsockopt
+ */
+#if !defined MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA || defined __DOXYGEN__
+#define MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA MEMP_NUM_TCPIP_MSG_API
+#endif
+
+/** MEMP_NUM_NETIFAPI_MSG: the number of concurrently active calls to the
+ * netifapi functions
+ */
+#if !defined MEMP_NUM_NETIFAPI_MSG || defined __DOXYGEN__
+#define MEMP_NUM_NETIFAPI_MSG           MEMP_NUM_TCPIP_MSG_API
+#endif
+/**
+ * @}
+ */
+
+/*
+   ---------------------------------
+   ---------- ARP options ----------
+   ---------------------------------
+*/
+/**
+ * @defgroup lwip_opts_arp ARP
+ * @ingroup lwip_opts_ipv4
+ * @{
+ */
+/**
+ * LWIP_ARP==1: Enable ARP functionality.
+ */
+#if !defined LWIP_ARP || defined __DOXYGEN__
+#define LWIP_ARP                        1
+#endif
+
+/**
+ * ARP_TABLE_SIZE: Number of active MAC-IP address pairs cached.
+ */
+#if !defined ARP_TABLE_SIZE || defined __DOXYGEN__
+#define ARP_TABLE_SIZE                  10
+#endif
+
+/** the time an ARP entry stays valid after its last update,
+ *  for ARP_TMR_INTERVAL = 1000, this is
+ *  (60 * 5) seconds = 5 minutes.
+ */
+#if !defined ARP_MAXAGE || defined __DOXYGEN__
+#define ARP_MAXAGE                      300