{"id":25343961,"url":"https://github.com/ssilverman/qnethernet","last_synced_at":"2025-10-29T13:31:26.233Z","repository":{"id":39901128,"uuid":"400918755","full_name":"ssilverman/QNEthernet","owner":"ssilverman","description":"An lwIP-based Ethernet library for Teensy 4.1 and possibly some other platforms","archived":false,"fork":false,"pushed_at":"2024-04-12T16:31:49.000Z","size":4977,"stargazers_count":69,"open_issues_count":2,"forks_count":17,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-04-13T00:03:19.686Z","etag":null,"topics":["ethernet","lwip","teensy","w5500"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ssilverman.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null},"funding":{"github":"ssilverman"}},"created_at":"2021-08-29T00:37:57.000Z","updated_at":"2024-04-14T21:36:14.498Z","dependencies_parsed_at":"2023-09-25T05:58:12.856Z","dependency_job_id":"5e8e2e8b-85eb-4781-ba9c-5eba990ffaef","html_url":"https://github.com/ssilverman/QNEthernet","commit_stats":null,"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssilverman%2FQNEthernet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssilverman%2FQNEthernet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssilverman%2FQNEthernet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssilverman%2FQNEthernet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssilverman","download_url":"https://codeload.github.com/ssilverman/QNEthernet/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238829558,"owners_count":19537719,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ethernet","lwip","teensy","w5500"],"created_at":"2025-02-14T11:00:09.450Z","updated_at":"2025-10-29T13:31:26.224Z","avatar_url":"https://github.com/ssilverman.png","language":"C","funding_links":["https://github.com/sponsors/ssilverman","https://www.buymeacoffee.com/ssilverman"],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"https://github.com/sponsors/ssilverman\" title=\"Sponsor @ssilverman on GitHub\"\u003e\u003cimg src=\"https://img.shields.io/badge/Sponsor-30363d.svg?logo=githubsponsors\u0026logoColor=EA4AAA\" alt=\"Sponsor @ssilverman button\"\u003e\u003c/a\u003e\nor\n\u003ca href=\"https://www.buymeacoffee.com/ssilverman\" title=\"Donate to this project using Buy Me a Coffee\"\u003e\u003cimg src=\"https://img.shields.io/badge/buy%20me%20a%20coffee-donate-orange.svg?logo=buy-me-a-coffee\u0026logoColor=FFDD00\" alt=\"Buy Me a Coffee donate button\"\u003e\u003c/a\u003e\n\n# _QNEthernet_, an lwIP-Based Ethernet Library For Teensy 4.1 and possibly some other platforms\n\n_Version: 0.33.0-snapshot_\n\nThe _QNEthernet_ library provides Ethernet functionality for the Teensy 4.1 and\npossibly some other platforms. It's designed to be compatible with the\nArduino-style Ethernet API.\n\nThis library is distributed under the \"AGPL-3.0-or-later\" license. Please\ncontact the author if you wish to inquire about other license options.\n\nPlease see the _lwip-info/_ directory for the info files provided with the\nlwIP release.\n\n## Table of contents\n\n1. [Introduction](#introduction)\n   1. [Two notes](#two-notes)\n   2. [Other differences and notes](#other-differences-and-notes)\n2. [Additional functions and features not in the Arduino-style API](#additional-functions-and-features-not-in-the-arduino-style-api)\n   1. [`Ethernet`](#ethernet)\n   2. [`EthernetClient`](#ethernetclient)\n      1. [TCP socket options](#tcp-socket-options)\n      2. [IP header values](#ip-header-values)\n   3. [`EthernetServer`](#ethernetserver)\n   4. [`EthernetUDP`](#ethernetudp)\n      1. [IP header values](#ip-header-values-1)\n      2. [`parsePacket()` return values](#parsepacket-return-values)\n   5. [`EthernetFrame`](#ethernetframe)\n   6. [`MDNS`](#mdns)\n   7. [`DNSClient`](#dnsclient)\n   8. [Print utilities](#print-utilities)\n   9. [`operator bool()` and `explicit`](#operator-bool-and-explicit)\n   10. [Use of `errno`](#use-of-errno)\n3. [How to run](#how-to-run)\n   1. [Concurrent use is not supported](#concurrent-use-is-not-supported)\n   2. [How to move the stack forward and receive data](#how-to-move-the-stack-forward-and-receive-data)\n   3. [Link detection](#link-detection)\n   4. [Notes on `yield()`](#notes-on-yield)\n4. [How to write data to connections](#how-to-write-data-to-connections)\n   1. [`writeFully()` with more break conditions](#writefully-with-more-break-conditions)\n   2. [Write immediacy](#write-immediacy)\n5. [A note on the examples](#a-note-on-the-examples)\n6. [A survey of how connections (aka `EthernetClient`) work](#a-survey-of-how-connections-aka-ethernetclient-work)\n   1. [Connections and link/interface detection](#connections-and-linkinterface-detection)\n   2. [`connect()` behaviour and its return values](#connect-behaviour-and-its-return-values)\n   3. [Non-blocking connection functions](#non-blocking-connection-functions)\n   4. [Getting the TCP state](#getting-the-tcp-state)\n7. [How to use multicast](#how-to-use-multicast)\n8. [How to use listeners](#how-to-use-listeners)\n9. [How to change the number of sockets](#how-to-change-the-number-of-sockets)\n10. [UDP receive buffering](#udp-receive-buffering)\n11. [mDNS services](#mdns-services)\n12. [DNS](#dns)\n13. [stdio](#stdio)\n    1. [Adapt stdio files to the Print interface](#adapt-stdio-files-to-the-print-interface)\n14. [Raw Ethernet frames](#raw-ethernet-frames)\n    1. [Promiscuous mode](#promiscuous-mode)\n    2. [Raw frame receive buffering](#raw-frame-receive-buffering)\n    3. [Raw frame loopback](#raw-frame-loopback)\n15. [How to implement VLAN tagging](#how-to-implement-vlan-tagging)\n16. [Application layered TCP: TLS, proxies, etc.](#application-layered-tcp-tls-proxies-etc)\n    1. [About the allocator functions](#about-the-allocator-functions)\n    2. [About the TLS adapter functions](#about-the-tls-adapter-functions)\n    3. [How to enable Mbed TLS](#how-to-enable-mbed-tls)\n       1. [Installing the Mbed TLS library](#installing-the-mbed-tls-library)\n          1. [Mbed TLS library install for Arduino IDE](#mbed-tls-library-install-for-arduino-ide)\n          2. [Mbed TLS library install for PlatformIO](#mbed-tls-library-install-for-platformio)\n       2. [Implementing the _altcp_tls_adapter_ functions](#implementing-the-altcp_tls_adapter-functions)\n       3. [Implementing the Mbed TLS entropy function](#implementing-the-mbed-tls-entropy-function)\n17. [On connections that hang around after cable disconnect](#on-connections-that-hang-around-after-cable-disconnect)\n18. [Notes on ordering and timing](#notes-on-ordering-and-timing)\n19. [Notes on RAM1 usage](#notes-on-ram1-usage)\n20. [Heap memory use](#heap-memory-use)\n21. [Entropy generation](#entropy-generation)\n    1. [The `RandomDevice` _UniformRandomBitGenerator_](#the-randomdevice-uniformrandombitgenerator)\n22. [Security features](#security-features)\n    1. [Secure TCP initial sequence numbers (ISNs)](#secure-tcp-initial-sequence-numbers-isns)\n23. [Configuration macros](#configuration-macros)\n    1. [Configuring macros using the Arduino IDE](#configuring-macros-using-the-arduino-ide)\n    2. [Configuring macros using PlatformIO](#configuring-macros-using-platformio)\n    3. [Changing lwIP configuration macros in `lwipopts.h`](#changing-lwip-configuration-macros-in-lwipoptsh)\n24. [Complete list of features](#complete-list-of-features)\n25. [Compatibility with other APIs](#compatibility-with-other-apis)\n26. [Other notes](#other-notes)\n27. [To do](#to-do)\n28. [Code style](#code-style)\n29. [References](#references)\n\n## Introduction\n\nThe _QNEthernet_ library is designed to be a drop-in replacement for code using\nthe Arduino-style Ethernet API.\n\n**Note: Please read the function docs in the relevant header files for\nmore information.**\n\n### Three notes\n\nThere are three notes, as follows:\n\n1. The `QNEthernet.h` header must be included instead of `Ethernet.h`.\n2. Everything is inside the `qindesign::network` namespace. In many cases,\n   adding the following at the top of your program will obviate the need to\n   qualify any object uses or make any other changes:\n   ```c++\n   using namespace qindesign::network;\n   ```\n3. On non-Teensy platforms: `Ethernet.loop()` must be called regularly, either\n   at the end of the main `loop()` function, or by hooking into `yield()`.\n   (This is already done for you on the Teensy platform by hooking into\n   `yield()` via `EventResponder`.)\\\n   See: [How to move the stack forward and receive data](#how-to-move-the-stack-forward-and-receive-data)\n\nFor API additions beyond what the Arduino-style API provides, see:\\\n[Additional functions and features not in the Arduino-style API](#additional-functions-and-features-not-in-the-arduino-style-api)\n\n### Other differences and notes\n\n* UDP support is already included in _QNEthernet.h_. There's no need to also\n  include _QNEthernetUDP.h_.\n* Ethernet `loop()` is called from `yield()` (automatically on the Teensy\n  platform). The functions that wait for timeouts rely on this. This also means\n  that you must use `delay(ms)` (assuming it internally calls `yield()`),\n  `yield()`, or `Ethernet.loop()` when waiting on conditions; waiting without\n  calling these functions will cause the TCP/IP stack to never refresh. Note\n  that many of the I/O functions call `loop()` so that there's less burden on\n  the calling code.\n* All but one of the `Ethernet.begin(...)` functions don't block.\n  `Ethernet.begin(mac[, timeout])` blocks, while waiting for an IP address, to\n  match the Arduino-style API. It uses a default timeout of 60 seconds. This\n  behaviour can be emulated by following a call to `begin()` with a loop that\n  checks `Ethernet.localIP()` for a valid IP. See also the new\n  `Ethernet.waitForLocalIP(timeout)` or `Ethernet.onAddressChanged(cb)`.\n* The Arduino-style `Ethernet.begin(mac, ...)` functions all accept a NULL MAC\n  address. If the address is NULL then the internal or system default MAC\n  address will be used. As well, if Ethernet fails to start then the MAC address\n  will not be changed.\n* `EthernetServer::write(...)` functions always return the write size requested.\n  This is because different clients may behave differently.\n* The examples at\n  https://docs.arduino.cc/libraries/ethernet/#Server%20Class (server.accept())\n  and https://docs.arduino.cc/libraries/ethernet/#Client%20Class\n  (if\u0026nbsp;(EthernetClient)) directly contradict each other with regard to what\n  `operator bool()` means in `EthernetClient`. The first example uses it as\n  \"already connected\", while the second uses it as \"available to connect\".\n  \"Connected\" is the chosen concept, but different from `connected()` in that it\n  doesn't check for unread data.\n* The three Arduino-defined `Ethernet.begin(...)` functions that use the MAC\n  address and that don't specify a subnet are deprecated because they make some\n  incorrect assumptions about the subnet and gateway.\n* `Ethernet.hardwareStatus()`: Adds `EthernetOtherHardware` and\n  `EthernetTeensy41` to the list of possible return values. Note that these\n  values are not defined in the Arduino-style API.\n* The following `Ethernet` functions are deprecated and do nothing or return\n  some default value:\n  * `maintain()`: Returns zero.\n  * `setRetransmissionCount(uint8_t number)`: Does nothing.\n  * `setRetransmissionTimeout(uint16_t milliseconds)`: Does nothing.\n* The `EthernetUDP::flush()` function does nothing because it is ill-defined.\n  Note that this is actually defined in the \"Arduino WiFi\" and Teensy \"UDP\" APIs\n  and not in the main \"Arduino Ethernet\" API.\\\n  See: https://docs.arduino.cc/libraries/wifi/#UDP%20class (WiFiUDP.flush())\n* The system starts with the Teensy's actual MAC address or some default MAC\n  address on other platforms. If you want to use that address with the\n  MAC-taking API, you can collect it with `Ethernet.macAddress(mac)` and then\n  pass it to one of the MAC-taking `begin(...)` functions.\n* All classes and objects are in the `qindesign::network` namespace. This means\n  you'll need to fully qualify any types. To avoid this, you could utilize a\n  `using` directive:\n  ```c++\n  using namespace qindesign::network;\n\n  EthernetUDP udp;\n\n  void setup() {\n    Ethernet.begin();\n  }\n  ```\n  However, this pollutes the current namespace. An alternative is to choose\n  something shorter. For example:\n  ```c++\n  namespace qn = qindesign::network;\n\n  qn::EthernetUDP udp;\n\n  void setup() {\n    qn::Ethernet.begin();\n  }\n  ```\n* Files that configure lwIP for our system:\n  * _src/sys_arch.cpp_\n  * _src/lwipopts.h_ \u0026larr; use this one for tuning (see _src/lwip/opt.h_ for\n    more details)\n  * _src/arch/cc.h_\n* File that configures _QNEthernet_ options: _src/qnethernet_opts.h_\n* The main include file, `QNEthernet.h`, in addition to including the\n  `Ethernet`, `EthernetFrame`, and `MDNS` instances, also includes the headers\n  for `EthernetClient`, `EthernetServer`, and `EthernetUDP`.\n* Most of the `Ethernet` functions do nothing or return some form of\n  empty/nothing/false unless the system has been initialized.\n\n## Additional functions and features not in the Arduino-style API\n\n_QNEthernet_ defines functions that don't exist in the Arduino-style API as it's\ncurrently defined. (See:\n[Arduino Ethernet Reference](https://docs.arduino.cc/libraries/ethernet/))\nThis section documents those functions.\n\nFeatures:\n* The `read(buf, len)` functions allow a NULL buffer so that the caller can skip\n  data without having to read into a buffer.\n\n### `Ethernet`\n\nThe `Ethernet` object is the main Ethernet interface.\n\n* `begin()`: Initializes the library, uses the Teensy's internal MAC address or\n  some default MAC address, and starts the DHCP client. This returns whether\n  startup was successful. This does not wait for an IP address.\n* `begin(ipaddr, netmask, gw)`: Initializes the library, uses the Teensy's\n  internal MAC address or some default MAC address, and uses the given\n  parameters for the network configuration. This returns whether startup was\n  successful. The DNS server is not set. This starts the DHCP client if the IP\n  address is `INADDR_NONE`.\n* `begin(ipaddr, netmask, gw, dns)`: Initializes the library, uses the Teensy's\n  internal MAC address or some default MAC address, and uses the given\n  parameters for the network configuration. This returns whether startup was\n  successful. The DNS server is only set if `dns` is not `INADDR_NONE`; it\n  remains the same if `dns` is `INADDR_NONE`. This starts the DHCP client if the\n  IP address is `INADDR_NONE`.\n\n  Passing `dns`, if not `INADDR_NONE`, ensures that the DNS server IP is set\n  before the _address-changed_ callback is called. The alternative approach to\n  ensure that the callback has all the information is to call\n  `setDNSServerIP(ip)` before the three-parameter version.\n\n* `broadcastIP()`: Returns the broadcast IP address associated with the current\n  local IP and subnet mask. If Ethernet is not initialized then this will return\n  255.255.255.255.\n* `dnsServerIP(index)`: Gets a specific DNS server IP address. This returns\n  `INADDR_NONE` if the index not in the exclusive range,\n  [0, `DNSClient::maxServers()`).\n* `driverCapabilities()`: Returns the driver's set of capabilities.\\\n  Notes:\n  * If the link state is not detectable then it must be managed\n    with `setLinkState(flag)`.\n* `end()`: Shuts down the library, including the Ethernet clocks.\n* `hostByName()`: Convenience function that tries to resolve a hostname into an\n  IP address. This returns whether successful.\n* `hostname()`: Gets the DHCP client hostname. An empty string means that no\n  hostname is set. The default is \"qnethernet-lwip\".\n* `interfaceName()`: Returns the interface name, or, if Ethernet has not been\n  initialized, an empty string.\n* `interfaceStatus()`: Returns the network interface status, `true` for UP and\n  `false` for DOWN.\n* `isDHCPActive()`: Returns whether DHCP is active.\n* `isDHCPEnabled()`: Returns whether the DHCP client is enabled. This is valid\n  whether Ethernet has been started or not.\n* `linkState()`: Returns a `bool` indicating the link state. This returns `true`\n  if the link is on and `false` otherwise. This may be managed manually\n  with `setLinkState(flag)`.\n* `linkSpeed()`: Returns the link speed in Mbps.\n* `linkIsCrossover()`: Returns whether a crossover cable is detected.\n* `linkIsFullDuplex()`: Returns whether the link is full duplex (`true`) or half\n  duplex (`false`).\n* `joinGroup(ip)`: Joins a multicast group.\n* `leaveGroup(ip)`: Leaves a multicast group.\n* `macAddress()`: Convenience function that returns a pointer to the current\n  MAC address.\n* `macAddress(mac)`: Fills the 6-byte `mac` array with the current MAC address.\n  Note that the equivalent Arduino function is `MACAddress(mac)`.\n* `renewDHCP()`: Renews any active DHCP lease and returns whether the request\n  was sent successfully.\n* `setDHCPEnabled(flag)`: Enables or disables the DHCP client. This may be\n  called either before or after Ethernet has started. If DHCP is desired and\n  Ethernet is up, but DHCP is not active, an attempt will be made to start the\n  DHCP client if the flag is true. This returns whether that attempt was\n  successful or if no restart attempt is required.\n* `setDNSServerIP(dnsServerIP)`: Sets the DNS server IP address. Note that the\n  equivalent Arduino function is `setDnsServerIP(dnsServerIP)`.\n* `setDNSServerIP(index, ip)`: Sets a specific DNS server IP address. This does\n  nothing if the index is not in the exclusive range,\n  [0, `DNSClient::maxServers()`).\n* `setHostname(hostname)`: Sets the DHCP client hostname. The empty string will\n  set the hostname to nothing. To use something other than the default at system\n  start, call this before calling `begin()`.\n* `setLinkState(flag)`: Manually sets the link state. This is useful when using\n  the loopback feature. Network operations will usually fail unless there's\n  a link.\n* `setMACAddressAllowed(mac, flag)`: Allows or disallows Ethernet frames\n  addressed to the specified MAC address. This is useful when processing raw\n  Ethernet frames.\n* `waitForLink(timeout)`: Waits for the specified timeout (milliseconds) for\n  a link to be detected. This is useful when setting a static IP and making\n  connections as a client. Returns whether a link was detected within the\n  given timeout.\n* `waitForLocalIP(timeout)`: Waits for the specified timeout (milliseconds) for\n  the system to have a local IP address. This is useful when waiting for a\n  DHCP-assigned address. Returns whether the system obtained an address within\n  the given timeout. Note that this also works for a static-assigned address.\n* `operator bool()`: Tests if Ethernet is initialized.\n* Callback functions: Note that callbacks should be registered before any other\n  Ethernet functions are called. This ensures that all events are captured. This\n  includes `Ethernet.begin(...)`.\n  * `onLinkState(cb)`: The callback is called when the link changes state, for\n    example when the Ethernet cable is unplugged.\n  * `onAddressChanged(cb)`: The callback is called when any IP settings have\n    changed. This might be called before the link or network interface is up if\n    a static IP is set.\n  * `onInterfaceStatus(cb)`: The callback is called when the network interface\n    status changes. It is called _after_ the interface is up but _before_ the\n    interface goes down.\n* `static constexpr bool isPromiscuousMode()`: Returns whether promiscuous mode\n  is enabled.\n* `static const char* libraryVersion()`: Returns the library version.\n* `static constexpr int maxMulticastGroups()`: Returns the maximum number of\n  available multicast groups, not including the \"all systems\" group.\n* `static constexpr size_t mtu()`: Returns the MTU.\n\n### `EthernetClient`\n\n* `abort()`: Aborts a connection without going through the TCP close process.\n* `close()`: Closes a connection, but without waiting. It's similar to `stop()`.\n  This is superseded by `setConnectionTimeoutEnabled(flag)` with `stop()`.\n* `closeOutput()`: Shuts down the transmit side of the socket. This is a\n  half-close operation.\n* `connectNoWait(ip, port)`: Similar to `connect(ip, port)`, but it doesn't\n  wait for a connection. This is superseded by\n  `setConnectionTimeoutEnabled(flag)` with `connect(ip, port)`.\n* `connectNoWait(host, port)`: Similar to `connect(host, port)`, but it doesn't\n  wait for a connection. Note that the DNS lookup will still wait. This is\n  superseded by `setConnectionTimeoutEnabled(flag)` with `connect(host, port)`.\n* `connecting()`: Returns whether the client is in the middle of connecting.\n  This is used when doing a non-blocking connect.\n* `connectionId()`: Returns an ID for the connection to which the client refers.\n  It will return non-zero if connected and zero if not connected. Note that it's\n  possible for new connections to reuse previously-used IDs.\n* `connectionTimeout()`: Returns the current timeout value.\n* `isConnectionTimeoutEnabled()`: Returns whether connection timeout is enabled.\n* `localIP()`: Returns the local IP of the network interface used for the\n  client. Currently, This returns the same value as `Ethernet.localIP()`.\n* `setConnectionTimeout(timeout)`: The parameter is a `uint32_t` and not a\n  `uint16_t`. The spec, as of this writing, specifies a `uint16_t` parameter.\n* `setConnectionTimeoutEnabled(flag)`: Enables or disables use of a connection\n  timeout. If disabled, then calls to `connect(...)` and `stop()` won't block.\n  This supersedes the `connectNoWait(...)` and `close()` calls.\n* `status()`: Returns the current TCP connection state. This returns one of\n  lwIP's `tcp_state` enum values. To use with _altcp_, define the\n  `LWIP_DEBUG` macro.\n* `writeFully(b)`: Writes a single byte.\n* `writeFully(s)`: Writes a string (`const char *`).\n* `writeFully(buf, size)`: Writes a data buffer (`const void *`).\n* `static constexpr int maxSockets()`: Returns the maximum number of\n  TCP connections.\n\n#### TCP socket options\n\n * `setNoDelay(flag)`: Sets or clears the TCP_NODELAY flag in order to disable\n   or enable Nagle's algorithm, respectively. This must be changed for each\n   new connection. Returns `true` if connected and the option was set, and\n   `false` otherwise.\n * `isNoDelay()`: Returns whether the TCP_NODELAY flag is set for the current\n   connection. Returns `false` if not connected.\n\n#### IP header values\n\n* `outgoingDiffServ()`: Returns the current value of the differentiated services\n  (DiffServ) field from the outgoing IP header, or zero if not connected.\n* `setOutgoingDiffServ(ds)`: Sets the differentiated services (DiffServ) field\n  in the outgoing IP header, if connected. Returns `true` if connected and the\n  option was set, and `false` otherwise.\n* `outgoingTTL()`: Returns the current value of the TTL field from the outgoing\n  IP header, or zero if not connected.\n* `setOutgoingTTL(ttl)`: Sets the TTL field in the outgoing IP header, if\n  connected. Returns `true` if connected and the option was set, and\n  `false` otherwise.\n\n### `EthernetServer`\n\n* `begin(port)`: Starts the server on the given port, first disconnecting any\n  existing server if it was listening on a different port. This returns whether\n  the server was successfully started.\n* `beginWithReuse()`: Similar to `begin()`, but also sets the SO_REUSEADDR\n  socket option. This returns whether the server was successfully started.\n* `beginWithReuse(port)`: Similar to `begin(port)`, but also sets the\n  SO_REUSEADDR socket option. This returns whether the server was\n  successfully started.\n* `end()`: Shuts down the server.\n* `port()`: Returns the server's port, a signed 32-bit value, where -1 means the\n  port is not set and a non-negative value is a 16-bit quantity.\n* `static constexpr int maxListeners()`: Returns the maximum number of\n  TCP listeners.\n* `EthernetServer()`: Creates a placeholder server without a port. This form is\n  useful when you don't know the port in advance.\n\nAll the `begin` functions call `end()` first only if the server is currently\nlistening and the port or _reuse_ options have changed.\n\n### `EthernetUDP`\n\n* `beginWithReuse(localPort)`: Similar to `begin(localPort)`, but also sets the\n  SO_REUSEADDR socket option.\n* `beginMulticastWithReuse(ip, localPort)`: Similar to\n  `beginMulticast(ip, localPort)`, but also sets the SO_REUSEADDR socket option.\n* `data()`: Returns a pointer to the received packet data.\n* `droppedReceiveCount()`: Returns the total number of dropped received packets\n  since reception was started. Note that this is the count of dropped packets at\n  the layer above the driver.\n* `localPort()`: Returns the port to which the socket is bound, or zero if it is\n  not bound.\n* `receiveQueueCapacity()`: Returns the receive queue capacity.\n* `receiveQueueSize()`: Returns the number of packets currently in the\n  receive queue.\n* `receivedTimestamp()`: Returns the approximate packet arrival time, measured\n  with `millis()`. This is useful in the case where packets have been queued and\n  the caller needs the approximate arrival time. Packets are timestamped when\n  the UDP receive callback is called.\n* `send(host, port, data, len)`: Sends a packet without having to use\n  `beginPacket()`, `write()`, and `endPacket()`. It causes less overhead. The\n  host can be either an IP address or a hostname.\n* `setReceiveQueueCapacity(capacity)`: Changes the receive queue capacity. The\n  minimum possible value is 1 and the default is 1. If a value of zero is used,\n  it will default to 1. If the new capacity is smaller than the number of items\n  in the queue then all the oldest packets will get dropped.\n* `size()`: Returns the total size of the received packet data.\n* `totalReceiveCount()`: Returns the total number of received packets, including\n  dropped packets, since reception was started. Note that this is the count at\n  the layer above the driver.\n* `operator bool()`: Tests if the socket is listening.\n* `static constexpr int maxSockets()`: Returns the maximum number of\n  UDP sockets.\n* `EthernetUDP(capacity)`: Creates a new UDP socket having the specified receive\n  packet queue capacity. The minimum possible value is 1 and the default is 1.\n  If a value of zero is used, it will default to 1.\n\nAll the `begin` functions call `stop()` first only if the socket is currently\nlistening and the local port or _reuse_ options have changed.\n\n#### IP header values\n\n* `outgoingDiffServ()`: Returns the current value of the differentiated services\n  (DiffServ) field from the outgoing IP header, or zero if the object hasn't yet\n  been set up.\n* `receivedDiffServ()`: Returns the DiffServ value of the last received packet.\n* `setOutgoingDiffServ(ds)`: Sets the differentiated services (DiffServ) field\n  in the outgoing IP header, setting up any necessary internal state. Returns\n  whether successful.\n* `outgoingTTL()`: Returns the current value of the TTL field from the outgoing\n  IP header, or zero if the object hasn't yet been set up.\n* `receivedTTL()`: Returns the TTL value of the last received packet.\n* `setOutgoingTTL(ttl)`: Sets the TTL field in the outgoing IP header, setting\n  up any necessary internal state. Returns whether successful.\n\n#### `parsePacket()` return values\n\nThe `EthernetUDP::parsePacket()` function in _QNEthernet_ is able to detect\nzero-length UDP packets. This means that a zero return value indicates a\nvalid packet.\n\nMany Arduino examples do the following to test whether there's packet data:\n\n```c++\nint packetSize = udp.parsePacket();\nif (packetSize) {  // \u003c-- THIS IS NOT CORRECT\n  // ...do something...\n}\n```\n\nThis is not correct because negative values mean that there's no packet\navailable, and negative values are implicitly converted to a Boolean _true_.\nInstead, the code should look like this:\n\n```c++\nint packetSize = udp.parsePacket();\nif (packetSize \u003e= non_negative_value) {  // non_negative_value \u003e= 0\n  // ...do something...\n}\n```\n\nNote that `if (packetSize \u003e 0)` would also be correct, or even something like\n`if (packetSize \u003e= 4)`, just as long as the `if (packetSize)` form is not used.\n\n### `EthernetFrame`\n\nThe `EthernetFrame` object adds the ability to send and receive raw Ethernet\nframes. It provides an API that is similar in feel to `EthernetUDP`. Because,\nlike `EthernetUDP`, it derives from `Stream`, the `Stream` API can be used to\nread from a frame and the `Print` API can be used to write to the frame.\n\n* `beginFrame()`: Starts a new frame. New data can be added using the\n  `Print` API. This is similar to `EthernetUDP::beginPacket()`.\n* `beginFrame(dstAddr, srcAddr, typeOrLen)`: Starts a new frame and writes the\n  given addresses and EtherType/length.\n* `beginVLANFrame(dstAddr, srcAddr, vlanInfo, typeOrLen)`: Starts a new\n  VLAN-tagged frame and writes the given addresses, VLAN info, and\n  EtherType/length.\n* `clear()`: Clears the outgoing and incoming buffers.\n* `data()`: Returns a pointer to the frame data.\n* `destinationMAC()`: Returns a pointer to the destination MAC.\n* `droppedReceiveCount()`: Returns the total number of dropped received frames\n  since reception was started. Note that this is the count of dropped frames at\n  the layer above the driver.\n* `endFrame()`: Sends the frame. This returns whether the send was successful. A\n  frame must have been started, its data length must be in the range 14-1514 for\n  non-VLAN frames or 18-1518 for VLAN frames, and Ethernet must have been\n  initialized. This is similar to `EthernetUDP::endPacket()`.\n* `etherTypeOrLength()`: Returns the EtherType/length value immediately\n  following the source MAC. Note that VLAN frames are handled specially.\n* `parseFrame()`: Checks if a new frame is available. This is similar\n  to `EthernetUDP::parseFrame()`.\n* `payload()`: Returns a pointer to the payload immediately following the\n  EtherType/length field. Note that VLAN frames are handled specially.\n* `receiveQueueCapacity()`: Returns the receive queue capacity.\n* `receiveQueueSize()`: Returns the number of frames currently in the\n  receive queue.\n* `receivedTimestamp()`: Returns the approximate frame arrival time, measured\n  with `millis()`. This is useful in the case where frames have been queued and\n  the caller needs the approximate arrival time. Frames are timestamped when\n  the unknown ethernet protocol receive callback is called.\n* `send(frame, len)`: Sends a raw Ethernet frame without the overhead of\n  `beginFrame()`/`write()`/`endFrame()`. See the description of `endFrame()` for\n  size limits. This is similar to `EthernetUDP::send(data, len)`.\n* `setReceiveQueueCapacity(capacity)`: Sets the receive queue capacity. The\n  minimum possible value is 1 and the default is 1. If a value of zero is used,\n  it will default to 1. If the new capacity is smaller than the number of items\n  in the queue then all the oldest frames will get dropped.\n* `size()`: Returns the total size of the frame data.\n* `sourceMAC()`: Returns a pointer to the source MAC.\n* `totalReceiveCount()`: Returns the total number of received frames, including\n  dropped frames, since reception was started. Note that this is the count at\n  the layer above the driver.\n* `static constexpr int maxFrameLen()`: Returns the maximum frame length\n  including the 4-byte FCS. Subtract 4 to get the maximum length that can be\n  sent or received using this API. Note that this size includes VLAN frames,\n  which are 4 bytes larger.\n* `static constexpr int minFrameLen()`: Returns the minimum frame length\n  including the 4-byte FCS. Subtract 4 to get the minimum length that can be\n  sent or received using this API. Note that padding does not need to be managed\n  by the caller, meaning frames smaller than this size are allowed; the system\n  will insert padding as needed.\n\n### `MDNS`\n\nThe `MDNS` object provides an mDNS API.\n\n* `begin(hostname)`: Starts the mDNS responder and uses the given hostname as\n  the name. If the responder is already running and the hostname is different\n  then the current state is renamed.\n* `end()`: Stops the mDNS responder.\n* `addService(type, protocol, port)`: Adds a service. The protocol will be set\n  to `\"_udp\"` for anything other than `\"_tcp\"`. The strings should have a `\"_\"`\n  prefix. Uses the hostname as the service name.\n* `addService(type, protocol, port, getTXTFunc)`: Adds a service and associated\n  TXT records.\n* `hostname()`: Returns the hostname if the responder is running and an empty\n  string otherwise.\n* `removeService(type, protocol, port)`: Removes a service.\n* `restart()`: Restarts the responder, for use when the cable has been\n  disconnected for a while and then reconnected. This isn't normally needed\n  because the responder already watches for link reconnect.\n* `operator bool()`: Tests if the mDNS responder is operating.\n* `static constexpr int maxServices()`: Returns the maximum number of\n  supported services.\n\n### `DNSClient`\n\nThe `DNSClient` class provides an interface to the DNS client.\n\n* `setServer(index, ip)`: Sets a DNS server address.\n* `getServer(index)`: Gets a DNS server address.\n* `getHostByName(hostname, callback, timeout)`: Looks up a host by name and\n  calls the callback when there's a result. The callback is not called once the\n  timeout has been reached. The timeout is ignored if it's set to zero.\n* `getHostByName(hostname, ip, timeout)`: Looks up a host by name.\n* `static constexpr int maxServers()`: Returns the maximum number of\n  DNS servers.\n\n### Print utilities\n\nThe `qnethernet/util/PrintUtils.h` file declares some useful output functions\nand classes. Note that this file is included when `QNEthernet.h` is included;\nthere's no need to include it separately.\n\nThe functions and classes are in the `qindesign::network::util` namespace, so if\nyou've already added `using namespace qindesign::network;` to your code, they\ncan be called with `util::writeMagic()` syntax. Otherwise, they need to be fully\nqualified: `qindesign::network::util::writeMagic()`.\n\nFunctions:\n\n1. `writeFully(Print\u0026, buf, size, breakf = nullptr)`: Attempts to completely\n   write bytes to the given `Print` object; the optional `breakf` function is\n   used as the stopping condition. It returns the number of bytes actually\n   written. The return value will be less than the requested size only if\n   `breakf` returned `true` before all bytes were written. Note that a NULL\n   `breakf` function is assumed to return `false`.\n\n   For example, the `EthernetClient::writeFully(...)` functions use this and\n   pass the \"am I disconnected\" condition as the `breakf` function.\n\n2. `writeMagic(Print\u0026, mac, breakf = nullptr)`: Writes the payload for a\n   [Magic packet](https://en.wikipedia.org/wiki/Wake-on-LAN#Magic_packet) to\n   the given `Print` object. This uses `writeFully(...)` under the covers and\n   passes along the `breakf` function as the stopping condition.\n\nClasses:\n\n1. `NullPrint`: A `Print` object that sends all data nowhere.\n\n2. `PrintDecorator`: A `Print` decorator meant to be used as a base class.\n\n3. `StdioPrint`: A `Print` decorator for `stdio` output files. It provides a\n   `Print` interface so that it is easy to print `Printable` objects to `stdout`\n   or `stderr` without having to worry about buffering and the need to flush any\n   output before printing a `Printable` directly to, say, `Serial`.\n\n### `operator bool()` and `explicit`\n\nAll the `operator bool()` functions in the API are marked as `explicit`. This\nmeans that you might get compiler errors in some circumstances when trying to\nuse a Boolean-convertible object.\n\nYou can use the object as a Boolean expression. For example in an `if` statement\nor ternary conditional.\n\nYou can't return the object as a `bool` from a function. For example, the\nfollowing code should give a compiler error:\n\n```c++\nEthernetClient client_;\n\nbool isConnected() {\n  return client_;\n}\n```\n\nInstead, use the following code; it fixes the problem:\n\n```c++\nEthernetClient client_;\n\nbool isConnected() {\n  return client_ ? true : false;\n  // Or this:\n  // if (client_) {\n  //   return true;\n  // } else {\n  //   return false;\n  // }\n}\n```\n\nThis will also work:\n\n```c++\nbool isConnected() {\n  return static_cast\u003cbool\u003e(client_);\n}\n```\n\nSee also:\n1. [The safe bool problem](https://en.cppreference.com/w/cpp/language/implicit_conversion#The_safe_bool_problem)\n2. [`explicit` specifier](https://en.cppreference.com/w/cpp/language/explicit)\n\n### Use of `errno`\n\nWhen a function call fails, it is often the case that `errno` will be set to\nsomething appropriate. See the function documentation of interest in the\nrelevant header for more information.\n\n## How to run\n\nThis library works with both PlatformIO and Arduino. To use it with Arduino,\nhere are a few steps to follow:\n\n1. Add `#include \u003cQNEthernet.h\u003e`. Note that this include already includes the\n   header for `EthernetUDP`. Some external examples also include `SPI.h`. This\n   is not needed unless you're actually using SPI in your program.\n2. Below that, add: `using namespace qindesign::network;`\n3. You likely don't want or need to set/choose your own MAC address, so just\n   call `Ethernet.begin()` with no arguments to use DHCP, and the three- or\n   four-argument version (IP, subnet mask, gateway[, DNS]) to set your own\n   address. If you really want to set your own MAC address, see\n   `setMACAddress(mac)` or one of the `begin(...)` functions that takes a MAC\n   address parameter.\n4. There is an `Ethernet.waitForLocalIP(timeout)` convenience function that can\n   be used to wait for DHCP to supply an address because `Ethernet.begin()`\n   doesn't wait. Try 15 seconds (15,000 ms) and see if that works for you.\n\n   Alternatively, you can use [listeners](#how-to-use-listeners) to watch for\n   address, link, and network interface activity changes. This obviates the need\n   for waiting and is the preferred approach.\n\n5. `Ethernet.hardwareStatus()` can detect both the Ethernet and no-Ethernet\n   versions of the hardware.\n6. Most other things should be the same.\n\nPlease see the examples for more things you can do with the API, including but\nnot limited to:\n* Using listeners to watch for network changes,\n* Monitoring and sending raw Ethernet frames, and\n* Setting up an mDNS service.\n\n### How to move the stack forward and receive data\n\nAll reception is processed in `Ethernet.loop()`. There's no thread or ISR that\nregularly processes the input. This means that this function must be called\nregularly. For example, it could be called at the end of the main `loop()`\nfunction. Another good place is to hook into `yield()` because the Arduino\nframework calls that every time `loop()` finishes and likely during a call\nto `delay()`.\n\nOn the Teensy platform, the call is already hooked into `yield()` via the\nbuilt-in `EventResponder` approach, so there's nothing more to add.\n\n### Concurrent use is not supported\n\nConcurrent use of _QNEthernet_ is not currently supported. This includes ISR\napproaches and multi-threading approaches.\n\nFirst, the underlying lwIP stack must be configured and used a certain way in\norder to provide concurrent support. _QNEthernet_ does not configure lwIP for\nthis. Second, the _QNEthernet_ API, the layer on top of lwIP, isn't designed for\nconcurrent use.\n\n### Link detection\n\nNormally, a link is detected by the driver at some polling rate. (For the\ncurious, see `driver_poll()` and its uses.) However, it's possible for a driver\nto not be able to detect a link. For example, the W5100 chip is unable to read\nthis state.\n\nSince the underlying lwIP stack depends on the link being up in order to operate\nproperly, a project will need to manage the link state itself for those drivers.\nThe suggestion is this:\n1. Start Ethernet as you normally would.\n2. If successful, check `Ethernet.driverCapabilities().hasLinkState`.\n3. If `false`, then call `Ethernet.setLinkState(true)`.\n\nCode example:\n\n```c++\nif (ethernet_is_started \u0026\u0026 !Ethernet.driverCapabilities().hasLinkState) {\n  Ethernet.setLinkState(true);\n}\n```\n\n### Notes on `yield()`\n\nIt may be the case that the `yield()` function is overridden, say to implement\ncooperative multitasking. If that implementation doesn't call `Ethernet.loop()`\nthen there are two things to be aware of:\n1. `Ethernet.loop()` needs to be called regularly somewhere. One good place is\n   at the end of the main program loop.\n2. Any library functions that use `yield()` while waiting for an event, say in\n  `Ethernet.waitForLocalIP()` or `EthernetClient::connect()`, need to call\n  `Ethernet.loop()` during the wait, otherwise the stack won't move forward and\n  the event will never occur. A good place to do this is after the\n  `yield()` call.\n\nTo accomplish #2, there is a configuration macro,\n`QNETHERNET_DO_LOOP_IN_YIELD`, that enables a call to `Ethernet.loop()` after\neach `yield()` call, guaranteeing that the stack moves forward. See the\n[Configuration macros](#configuration-macros) section.\n\nThe complete list of functions, as of this writing, that use `yield()` is:\n1. `DNSClient::getHostByName()`\n2. `EthernetClass::waitForLink()`\n3. `EthernetClass::waitForLocalIP()`\n4. `EthernetClient::connect()`\n5. `EthernetClient::stop()`\n\n## How to write data to connections\n\nI'll start with these statements:\n1. **Don't use the \u003ccode\u003eprint\u003cem\u003eX\u003c/em\u003e(...)\u003c/code\u003e functions when writing data\n   to connections.**\n2. **Always check the `write(...)` and \u003ccode\u003eprint\u003cem\u003eX\u003c/em\u003e(...)\u003c/code\u003e return\n   values, retrying if necessary.**\n3. Data isn't necessarily sent immediately.\n\nThe `write(...)` and \u003ccode\u003eprint\u003cem\u003eX\u003c/em\u003e(...)\u003c/code\u003e functions in the `Print`\nAPI all return the number of bytes actually written. This means that you _must\nalways check the return value, retrying any missing bytes_ if you want all your\ndata to get sent.\n\nFor example, the following code won't necessarily send all 250\u0026times;102 bytes.\nBuffers might get full. There might be retries. Etcetera.\n\n```c++\nvoid sendTestData(EthernetClient\u0026 client) {\n  for (int i = 0; i \u003c 250; i++) {\n    // 102-byte string (println appends CRLF)\n    client.println(\"1234567890\"\n                   \"1234567890\"\n                   \"1234567890\"\n                   \"1234567890\"\n                   \"1234567890\"\n                   \"1234567890\"\n                   \"1234567890\"\n                   \"1234567890\"\n                   \"1234567890\"\n                   \"1234567890\");\n  }\n}\n```\n\nThe following modification will print a message every time the number of bytes\nactually written does not match the number of bytes sent to the function. You\nmight find that the message prints one or more times.\n\n```c++\nvoid sendTestData(EthernetClient\u0026 client) {\n  for (int i = 0; i \u003c 250; i++) {\n    // 102-byte string (println appends CRLF)\n    size_t written = client.println(\"1234567890\"\n                                    \"1234567890\"\n                                    \"1234567890\"\n                                    \"1234567890\"\n                                    \"1234567890\"\n                                    \"1234567890\"\n                                    \"1234567890\"\n                                    \"1234567890\"\n                                    \"1234567890\"\n                                    \"1234567890\");\n    if (written != 102) {\n      // This is not an error!\n      Serial.println(\"Didn't write fully\");\n    }\n  }\n}\n```\n\nThe solution is to utilize the raw `write(...)` functions and retry any bytes\nthat aren't sent. Let's create a `writeFully(...)` function and use that to send\nthe data:\n\n```c++\n// Keep writing until all the bytes are sent or the connection\n// is closed.\nvoid writeFully(EthernetClient\u0026 client, const char* data, int len) {\n  // Don't use client.connected() as the \"connected\" check because\n  // that will return true if there's data available, and this loop\n  // does not check for data available or remove it if it's there.\n  while (len \u003e 0 \u0026\u0026 client) {\n    size_t written = client.write(data, len);\n    len -= written;\n    data += written;\n  }\n}\n\nvoid sendTestData(EthernetClient\u0026 client) {\n  for (int i = 0; i \u003c 250; i++) {\n    // 102-byte string\n    size_t written = writeFully(client, \"1234567890\"\n                                        \"1234567890\"\n                                        \"1234567890\"\n                                        \"1234567890\"\n                                        \"1234567890\"\n                                        \"1234567890\"\n                                        \"1234567890\"\n                                        \"1234567890\"\n                                        \"1234567890\"\n                                        \"1234567890\"\n                                        \"\\r\\n\");\n  }\n}\n```\n\n_Note that the library implements `writeFully(...)`; you don't have to roll\nyour own._\n\nRewriting this to use the library function:\n\n```c++\nvoid sendTestData(EthernetClient\u0026 client) {\n  for (int i = 0; i \u003c 250; i++) {\n    // 102-byte string\n    size_t written = client.writeFully(\"1234567890\"\n                                       \"1234567890\"\n                                       \"1234567890\"\n                                       \"1234567890\"\n                                       \"1234567890\"\n                                       \"1234567890\"\n                                       \"1234567890\"\n                                       \"1234567890\"\n                                       \"1234567890\"\n                                       \"1234567890\"\n                                       \"\\r\\n\");\n  }\n}\n```\n\nLet's go back to our original statement about not using the\n\u003ccode\u003eprint\u003cem\u003eX\u003c/em\u003e(...)\u003c/code\u003e functions. Their implementation is opaque and\nthey sometimes make assumptions that the data will be \"written fully\". For\nexample, Teensyduino's current `print(const String\u0026)` implementation attempts\nto send all the bytes and returns the number of bytes sent, but it doesn't tell\nyou _which_ bytes were sent. For the string `\"12345\"`, `print(s)` might send\n`\"12\"`, fail to send `\"3\"`, and successfully send `\"45\"`, returning the value 4.\n\nSimilarly, we have no idea what `print(const Printable\u0026 obj)` does because the\n`Printable` implementation passed to it is beyond our control. For example,\nTeensyduino's `IPAddress::printTo(Print\u0026)` implementation prints the address\nwithout checking the return value of the `print(...)` calls.\n\nAlso, most examples I've seen that use any of the\n\u003ccode\u003eprint\u003cem\u003eX\u003c/em\u003e(...)\u003c/code\u003e functions never check the return values.\nCommon practice seems to stem from this style of usage. Network applications\nwork a little differently, and there's no guarantee all the data gets sent.\n\nThe `write(...)` functions don't have this problem (unless, of course, there's a\nfaulty implementation). They attempt to send bytes and return the number of\nbytes actually sent.\n\nIn summary, my ***strong*** suggestion is to use the `write(...)` functions when\nsending network data, checking the return values and acting on them. Or you can\nuse the library's `writeFully(...)` functions.\n\nSee the discussion at:\nhttps://forum.pjrc.com/index.php?threads/nativeethernet-stalling-with-dropped-packets.68389/\n\n### `writeFully()` with more break conditions\n\nBy default, the `EthernetClient` versions of `writeFully()` wait until the\nconnection is closed. However, TCP connections can sometimes persist for a long\ntime after a cable/link disconnect, and a user might wish to only wait so long\nfor a connection to return. This can be solved by adding additional checks to\nthe stopping condition function (the `breakf` parameter).\n\nFor example, to break on connection close or link down:\n\n```c++\nsize_t writeFully(EthernetClient\u0026 c, const uint8_t* buf, size_t size) {\n  return qindesign::network::util::writeFully(c, buf, size, [\u0026c]() {\n    return !static_cast\u003cbool\u003e(c) || !Ethernet.linkState();\n  });\n}\n```\n\nTo break on connection close or timeout:\n\n```c++\nsize_t writeFully(EthernetClient\u0026 c, const uint8_t* buf, size_t size,\n                  uint32_t timeout) {\n  uint32_t startT = millis();\n  return qindesign::network::util::writeFully(\n      c, buf, size, [\u0026c, startT, timeout]() {\n        return !static_cast\u003cbool\u003e(c) || (millis() - startT) \u003e= timeout;\n      });\n}\n```\n\nSee also:\n* [writeFully causes program to freeze · Issue #46 · ssilverman/QNEthernet](https://github.com/ssilverman/QNEthernet/issues/46)\n* [On connections that hang around after cable disconnect](#on-connections-that-hang-around-after-cable-disconnect)\n* [Print utilities](#print-utilities)\n\n### Write immediacy\n\nData isn't necessarily completely sent across the wire after `write(...)` or\n`writeFully(...)` calls. Instead, data is merely enqueued until the internal\nbuffer is full or a timer expires. Now, if the data to send is larger than the\ninternal TCP buffer then data will be sent and the extra data will be enqueued.\nIn other words, data is only sent when either the buffer is full or an internal\ntimer has expired.\n\nTo send any buffered data, call `flush()`.\n\nTo quote lwIP's `tcp_write()` docs:\n\u003e Write data for sending (but does not send it immediately).\n\u003e\n\u003e It waits in the expectation of more data being sent soon (as\n\u003e it can send them more efficiently by combining them together).\n\u003e To prompt the system to send data now, call tcp_output() after\n\u003e calling tcp_write().\n\n`flush()` is what always calls `tcp_output()` internally. The `write(...)` and\n`writeFully(...)` functions only call this when the buffer is full. The\nsuggestion is to call `flush()` when done sending a \"packet\" of data, for some\ndefinition of \"packet\" specific to your application. For example, after sending\na web page to a client or after a chunk of data is ready for the server\nto process.\n\nThere is a configuration option, `QNETHERNET_FLUSH_AFTER_WRITE`, that causes an\nautomatic flush after data is written. However, this may reduce TCP efficiency.\nThis option is for use with hard-to-modify code or libraries that assume data\nwill get sent immediately. The preferred approach is to call flush() in the code\nor library.\n\n## A note on the examples\n\nThe examples aren't meant to be simple. They're meant to be functional. There\nare plenty of Arduino-style Ethernet library examples out there. This library\ndoes not aim to be just like the others, it aims to provide some more powerful\ntools to enable more powerful programs. The Teensy 4.1 is a serious device; it\ndemands serious examples.\n\nAn attempt was made to use a more robust programming style and modern C++ tools.\nFor those not as experienced with C++ or larger projects, these may have a\nsteeper learning curve. Hopefully this brings up lots of questions and an\nexposure to new concepts. For example, state machines, lambdas, callbacks,\ndata structures, vector::emplace_back, and std::move.\n\nFor those that do have more C++ and larger project experience, I invite you to\nimprove or add to the examples so that the set of examples here becomes what\nyou've always hoped great library examples could be.\n\n## A survey of how connections (aka `EthernetClient`) work\n\nHopefully this section disambiguates some details about what each function does:\n1. `connected()`: Returns whether connected OR data is still available\n   (or both).\n2. `operator bool()`: Returns whether connected (at least in _QNEthernet_).\n3. `available()`: Returns the amount of data available, whether the connection\n   is closed or not.\n4. `read(...)`: Reads data if there's data available, whether the connection is\n   closed or not.\n\nConnections will be closed automatically if the client shuts down a connection,\nand _QNEthernet_ will properly handle the state such that the API behaves as\nexpected. In addition, if a client closes a connection, any buffered data will\nstill be available via the client API. If it were up to me, I'd have swapped the\nmeaning of `operator bool()` and `connected()`, but see the above list as\na guide.\n\nSome options:\n\n1. Keep checking `connected()` (or `operator bool()`) and\n   `available()`/`read(...)` to keep reading data. The data will run out when\n   the connection is closed and after all the buffers are empty. The calls to\n   `connected()` (or `operator bool()`) will indicate connection status (plus\n   data available in the case of `connected()` or just connection state in the\n   case of `operator bool()`).\n2. Same as the above, but without one of the two connection-status calls\n   (`connected()` or `operator bool()`). The data will just run out after\n   connection-closed and after the buffers are empty.\n\n### Connections and link/interface detection\n\nA link and active network interface must be present before a connection can be\nmade. Either call `Ethernet.waitForLink(timeout)` or check the link state or\nnetwork interface status before attempting to connect. Which approach you use\nwill depend on how your code is structured or intended to be used.\n\nBe aware when using a listener approach to start or stop services that it's\npossible, when setting a static IP, for the _address-changed_ callback to be\ncalled before the link or network interface is up.\n\nNote that this section also applies to the DNS client.\n\n### `connect()` behaviour and its return values\n\nFirstly, `connect()` blocks. See the [next section](#connectnowait-doesnt-wait)\nfor a non-blocking way to connect.\n\nThe Arduino-style API,\n[here](https://docs.arduino.cc/libraries/ethernet/#Client%20Class\n(client.connect())), used to define a set of possible `int` return values for\nthis function, but now it returns a Boolean value indicating success. Note that\nthe function signatures still return an `int`.\n\n### Non-blocking connection functions\n\nThe `connectNoWait()` functions implement non-blocking TCP connections. These\nfunctions behave similarly to `connect()`, however they do not wait for the\nconnection to be established.\n\nTo check for connection establishment, simply call `connecting()` to determine\nif the client is still in the process of connecting, and then either\n`connected()` or the Boolean operator. If a connection can't be established then\n`close()` must be called on the object.\n\nNote that DNS lookups for hostnames will still wait.\n\n### Getting the TCP state\n\nTCP connections can be in one of\n[several states](https://www.rfc-editor.org/rfc/rfc9293#name-state-machine-overview).\nKnowing a connection's underlying TCP state can be useful, for example, to avoid\nwaiting for a timeout via reading data.\n\nThis state can be retrieved using an `EthernetClient`'s `status()` function.\nNote that, to avoid modiyfing the lwIP code too much, the `LWIP_DEBUG` macro\nmust be defined when using this function with _altcp_. If not using it with\n_altcp_, there's no such concern.\n\nThe states, as defined by lwIP's `tcp_state` enum:\n1. CLOSED\n2. LISTEN\n3. SYN_SENT\n4. SYN_RCVD\n5. ESTABLISHED\n6. FIN_WAIT_1\n7. FIN_WAIT_2\n8. CLOSE_WAIT\n9. CLOSING\n10. LAST_ACK\n11. TIME_WAIT\n\nThis enum isn't a C++ \"enum class\", so its values can be used directly. The\ndefinition is already included by _qnethernet/QNEthernetClient.h_, which is, in\nturn, included by _QNEthernet.h_.\n\nReferences:\n1. [TCP states](https://www.rfc-editor.org/rfc/rfc9293#name-state-machine-overview)\n2. [consider adding `status()` in EthernetClient · Issue #52 · ssilverman/QNEthernet](https://github.com/ssilverman/QNEthernet/issues/52#issuecomment-1737950354)\n3. [WiFiNINA - client.status() - Arduino Reference](https://docs.arduino.cc/libraries/wifinina/#Client%20Class)\n   (client.status())\n\n## How to use multicast\n\nThere are a few ways in the API to utilize multicast to send or receive packets.\nFor reception, you must join a multicast group.\n\nThe first is by using the `EthernetUDP::beginMulticast(ip, port)` function. This\nboth binds to a specific port, for only receiving traffic on that port, and\njoins the specified group address. It's similar to\n`EthernetUDP::begin(localPort)` in that the socket will \"own\" the port.\n\nSince only one socket at a time can be bound to a specific port, you will need\nto use the same socket if you want to receive traffic sent to multiple groups on\nthe same port. You can accomplish this by either calling\n`beginMulticast(ip, port)` multiple times, or by using `begin(localPort)` once,\nand then calling `Ethernet.joinGroup(ip)` for each group you want to join.\n\nTo send multicast traffic, simply send to the appropriate IP address. There's no\nneed to join a group.\n\nThe lwIP stack keeps track of a group \"use count\". This means:\n1. That `joinGroup(ip)` can be called multiple times, it just needs to be paired\n   with a matching number of calls to `leaveGroup(ip)`. Each call increments an\n   internal count.\n2. Each call to `leaveGroup(ip)` decrements a count, and when that count reaches\n   zero, the stack actually leaves the group.\n\n## How to use listeners\n\nInstead of waiting for certain states at system start, for example _link-up_ or\n_address-changed_, it's possible to watch for state changes using listeners, and\nthen act on those state changes. This will make your application more robust and\nresponsive to state changes during program operation.\n\nNote that callbacks should be registered before any other Ethernet functions are\ncalled. This ensures that all events are captured. This includes\n`Ethernet.begin(...)`.\n\nThe relevant functions are (see the [`Ethernet`](#ethernet) section for further\ndescriptions):\n1. `Ethernet.onLinkState(cb)`\n2. `Ethernet.onAddressChanged(cb)`\n3. `Ethernet.onInterfaceStatus(cb)`\n\n_Link-state_ events occur when an Ethernet link is detected or lost.\n\n_Address-changed_ events occur when the IP address changes, but its effects are\na little more subtle. When setting an address via DHCP, the link and network\ninterface must already be up in order to receive the information. However, when\nsetting a static IP address, the event may occur before the link or network\ninterface is up. This means that if a connection or DNS lookup is attempted when\nit is detected that the address is valid, the attempt will fail.\n\n_Interface-status_ events happen when the network interface comes up or goes\ndown. No network operations can happen before the network interface is up. For\nexample, when setting a static IP address, the _address-changed_ event may occur\nbefore the network interface has come up. This means that, for example, any\nconnection attempts or DNS lookup attempts will fail.\n\nIt is suggested, therefore, that when taking an action based on an\n_address-changed_ event, the link state and network interface status are checked\nin addition.\n\nServers, on the other hand, can be brought up even when there's no link or\nactive network interface.\n\nIn summary, no network operations can be done before all three of _link-up_,\n_address_changed-to-valid_, and _interface-up_ occur.\n\nLast, no network tasks should be performed inside the listeners. Instead, set a\nflag and then process that flag somewhere in the main loop.\n\nSee: [Connections and link/interface detection](#connections-and-linkinterface-detection)\n\n## How to change the number of sockets\n\nlwIP preallocates almost everything. This means that the number of sockets (UDP,\nTCP, etc.) the stack can handle is set at compile time. The following table\nshows how to change the number for each socket type.\n\n| Socket Type     | Macro in `lwipopts.h`     | Notes                 |\n| --------------- | ------------------------- | --------------------- |\n| UDP             | `MEMP_NUM_UDP_PCB`        |                       |\n| TCP             | `MEMP_NUM_TCP_PCB`        | Simultaneously active |\n| TCP (listening) | `MEMP_NUM_TCP_PCB_LISTEN` | Listening             |\n\n## UDP receive buffering\n\nIf UDP packets come in at a faster rate than they are consumed, some may get\ndropped. To help mitigate this, the `EthernetUDP(capacity)` constructor can be\ncalled with a value \u003e 1. The minimum capacity is 1, meaning any new packets will\ncause any existing packet to get dropped. If it's set to 2 then there will be\nspace for one additional packet for a total of 2 packets, and so on. Setting a\nvalue of zero will use the default of 1.\n\n## mDNS services\n\nIt's possible to register mDNS services. Some notes:\n* Similar to `Ethernet`, there is a global `MDNS` object. It too is in the\n  `qindesign::network` namespace.\n* It's possible to add TXT items when adding a service. For example, the\n  following code adds \"path=/\" to the TXT of an HTTP service:\n  ```c++\n    MDNS.begin(\"Device Name\");\n    MDNS.addService(\"_http\", \"_tcp\", 80, []() {\n      return std::vector\u003cString\u003e{\"path=/\"};\n    });\n  ```\n  You can add more than one item to the TXT record by adding to the vector.\n* When adding a service, the function that returns TXT items defaults to NULL,\n  so it's not necessary to specify that parameter. For example:\n  ```c++\n    MDNS.begin(\"Device Name\");\n    MDNS.addService(\"_http\", \"_tcp\", 80);\n  ```\n* The host name is normally used as the service name, but there are also\n  functions that let you specify the service name. For example:\n  ```c++\n  MDNS.begin(\"Host Name\");\n  MDNS.addService(\"my-http-service\", \"_http\", \"_tcp\", 80);\n  ```\n\n## DNS\n\nThe library interfaces with DNS using the `DNSClient` class. Note that all the\nfunctions are static.\n\nThings you can do:\n1. Look up an IP address by name, and\n2. Set multiple DNS servers.\n\nThe `Ethernet.setDNSServerIP(ip)` function sets the zeroth DNS server address\nand the `Ethernet.setDNSServerIP(index, ip)` function sets the nth DNS server\naddress. Corresponding `dnsServerIP()` functions get the DNS server addresses.\n\n## stdio\n\nInternally, lwIP uses `printf` for debug output and assertions. _QNEthernet_\ndefines its own `_write()` function so that it has more control over `stdout`\nand `stderr` than that provided by the internal output support.\n\nNote: As of Teensyduino 1.58-beta4, there's internal support for `printf`,\npartially obviating the need for _QNEthernet_ to define its own. However, it\nalways maps all of `stdin`, `stdout`, and `stderr` specifically to `Serial`.\n\nCompared to the internal `printf` support, the _QNEthernet_ version:\n1. Can map output to any `Print` interface, not just `Serial`,\n2. Can separate `stdout` and `stderr` outputs, and\n3. Disallows `stdin` as a valid output.\n\nTo enable the _QNEthernet_ version, set the `QNETHERNET_CUSTOM_WRITE` macro to\n`1` and set the `stdoutPrint` or `stderrPrint` variables to point to a valid\n`Print` implementation. Note that if the feature is disabled, then neither\n`stdoutPrint` nor `stderrPrint` will be defined.\n\nBoth variables default to NULL.\n\nFor example:\n```c++\n// Define QNETHERNET_CUSTOM_WRITE somewhere\n\nvoid setup() {\n  Serial.begin(115200);\n  while (!Serial \u0026\u0026 millis() \u003c 4000) {\n    // Wait for Serial\n  }\n  qindesign::network::stdoutPrint = \u0026Serial;\n}\n```\n\nIf your application wants to define its own `_write()` implementation or to use\nthe system default, then leave the `QNETHERNET_CUSTOM_WRITE` macro undefined.\n\n### Adapt stdio files to the Print interface\n\nThere is a utility class for decorating stdio `FILE*` objects with the `Print`\ninterface. See the `StdioPrint` class in _src/qnethernet/util/PrintUtils.h_.\n\nThis is useful when:\n1. A `FILE*` object does its own buffering and you also need to write to the\n   underlying `Print` object directly, `Serial` for example. It avoids having to\n   remember to call `fflush()` on the `FILE*` before writing to the underlying\n   `Print` object.\n2. There's a need to easily print `Printable` objects to the `FILE*`.\n\n## Raw Ethernet frames\n\nThere is support for sending and receiving raw Ethernet frames. See the\n[`EthernetFrame`](#ethernetframe) API, above.\n\nThis API doesn't receive any known Ethernet frame types. These include:\n1. IPv4 (0x0800)\n2. ARP  (0x0806)\n3. IPv6 (0x86DD) (if enabled)\n\nIf frames are addressed to a MAC address that doesn't belong to the device and\nisn't a multicast group MAC address then it is necessary to tell the Ethernet\nstack about it. See the `Ethernet.setMACAddressAllowed(mac, flag)` function.\n\nAn example that uses such MAC addresses is the Precision Time Protocol (PTP)\nover Ethernet. It uses 01-1B-19-00-00-00 for forwardable frames and\n01-80-C2-00-00-0E for non-forwardable frames. See\n[PTP Message Transport](https://en.wikipedia.org/wiki/Precision_Time_Protocol#Message_transport)\n\nTo enable raw frame support, set the `QNETHERNET_ENABLE_RAW_FRAME_SUPPORT` macro\nto `1`. This will use some space.\n\n### Promiscuous mode\n\nIt's possible to enable promiscuous mode so that all frames are received, even\nones whose destination MAC address would normally be filtered out by the\nEthernet hardware. To do this, set the `QNETHERNET_ENABLE_PROMISCUOUS_MODE`\nmacro to `1`.\n\n### Raw frame receive buffering\n\nSimilar to [UDP buffering](#udp-receive-buffering), if raw frames come in at a\nfaster rate than they are consumed, some may get dropped. To help mitigate this,\nthe receive queue capacity can be adjusted with the\n`EthernetFrame.setReceiveQueueCapacity(capacity)` function. The default queue\ncapacity is 1 and the minimum is also 1 (if a zero is passed in then 1 will be\nused instead).\n\nFor a size of 1, any new frames will cause any existing frame to get dropped. If\nthe size is 2 then there will be space for one additional frame for a total of 2\nframes, and so on.\n\n### Raw frame loopback\n\nRaw frames having a destination MAC address that matches the local MAC address\nor the broadcast MAC address can optionally be looped back up the stack. To\nenable this feature, set the `QNETHERNET_ENABLE_RAW_FRAME_LOOPBACK` macro\nto `1`.\n\n## How to implement VLAN tagging\n\nThe lwIP stack supports VLAN tagging. Here are the steps for how to implement\nit. Note that all defines should go inside `lwipopts.h`. Documentation for these\ndefines can be found in _src/lwip/opt.h_.\n\n1. Define `ETHARP_SUPPORT_VLAN` as `1`.\n2. To set VLAN tags, define `LWIP_HOOK_VLAN_SET`.\n3. To validate VLAN tags on input, define one of:\n   1. `LWIP_HOOK_VLAN_CHECK`, (see `LWIP_HOOK_VLAN_CHECK`)\n   2. `ETHARP_VLAN_CHECK_FN`, (see `ETHARP_SUPPORT_VLAN`)\n   3. `ETHARP_VLAN_CHECK`. (see `ETHARP_SUPPORT_VLAN`)\n\n## Application layered TCP: TLS, proxies, etc.\n\nlwIP provides a way to decorate the TCP layer. It's called \"Application Layered\nTCP.\" It enables an application to add things like TLS and proxies without\nchanging the source code.\n\nHere are the steps to add decorated TCP:\n1. Set `LWIP_ALTCP` and optionally `LWIP_ALTCP_TLS` to `1` in `lwipopts.h`.\n2. Implement two functions somewhere in your code, having these names and\n   signatures:\n   ```c++\n   std::function\u003cbool(const ip_addr_t* ipaddr, uint16_t port,\n                      altcp_allocator_t\u0026 allocator)\u003e qnethernet_altcp_get_allocator;\n   std::function\u003cvoid(const altcp_allocator_t\u0026 allocator)\u003e qnethernet_altcp_free_allocator;\n   ```\n3. Implement all the functions necessary for the wrapping implementation. For\n   example, for TLS, this means all the functions declared in\n   _src/lwip/altcp_tls.h_.\n\nSee _src/lwip/altcp.c_ and the _AltcpTemplate_ example for more information.\n\nNote that if the `QNETHERNET_ENABLE_ALTCP_DEFAULT_FUNCTIONS` macro is enabled,\ndefault, simple, implementations of these functions will be provided. Only the\nregular TCP allocator will be used.\n\n### About the allocator functions\n\nThe functions from step 2 create and destroy any resources used by the altcp\nwrapper. The first function fills in the allocator function (`allocator-\u003ealloc`)\nand an argument appropriate to that allocator function (`allocator-\u003earg`). For\nexample, the `altcp_tcp_alloc()` allocator function doesn't need an argument (it\ncan be set to NULL), but `altcp_tls_alloc()` needs a pointer to a\n`struct altcp_tls_config`.\n\nThe connection will fail if the allocator function is set to NULL.\n\nThe second function frees any resources that haven't already been freed. It's up\nto the application and TCP wrapper implementation to properly manage resources\nand to provide a way to determine whether a resource needs to be freed. It is\nonly called if `qnethernet_altcp_get_allocator` returns `true` and a socket\ncould not be created.\n\nThe `ipaddr` and `port` parameters indicate what the calling code is trying\nto do:\n\n1. If `ipaddr` is NULL then the application is trying to listen.\n2. If `ipaddr` is not NULL then the application is trying to connect.\n\n### About the TLS adapter functions\n\nThe _src/altcp_tls_adapter.cpp_ file implements the allocator functions for\naltcp TLS integration. It specifies new functions that make it a little easier\nto integrate a library. These are as follows:\n\n1. Type: `std::function\u003cbool(const ip_addr_t* ip, uint16_t port)\u003e`\\\n   Name: `qnethernet_altcp_is_tls`\\\n   Description: Determines if the connection should use TLS. The IP address will\n                be NULL for a server connection.\n2. Type: `std::function\u003cvoid(const ip_addr_t\u0026 ipaddr, uint16_t port,\n                             const uint8_t*\u0026 cert, size_t\u0026 cert_len)\u003e`\\\n   Name: `qnethernet_altcp_tls_client_cert`\\\n   Note: All the arguments are references.\\\n   Description: Retrieves the certificate for a client connection. The values\n                are initialized to NULL and zero, respectively, before this\n                function is called. The IP address and port can be used to\n                determine the certificate data, if needed.\n3. Type: `std::function\u003cuint8_t(uint16_t port)\u003e`\\\n   Name: `qnethernet_altcp_tls_server_cert_count`\\\n   Description: Returns the certificate count for a server connection.\n4. Type: `std::function\u003cvoid(uint16_t port, uint8_t index,\n                             const uint8_t*\u0026 privkey,      size_t\u0026 privkey_len,\n                             const uint8_t*\u0026 privkey_pass, size_t\u0026 privkey_pass_len,\n                             const uint8_t*\u0026 cert,         size_t\u0026 cert_len)\u003e`\\\n   Name: `qnethernet_altcp_tls_server_cert`\\\n   Description: Retrieves the certificate and private key for a server\n                connection. The values are initialized to NULL and zero before\n                this function is called. It will be called for each server\n                certificate, a total of N times, where N is the value returned\n                by `qnethernet_altcp_tls_server_cert_count`. The `index`\n                argument will be in the range zero to N-1. The port and\n                certificate index can be used to determine the certificate data,\n                if needed.\n\nCurrently, this file is only built if the `LWIP_ALTCP`, `LWIP_ALTCP_TLS`, and\n`QNETHERNET_ALTCP_TLS_ADAPTER` macros are enabled by setting them to `1`.\n\n### How to enable Mbed TLS\n\nThe lwIP distribution comes with a way to use the Mbed TLS library as an ALTCP\nTLS layer. It currently only supports the 2.x.x versions; as of this writing,\nthe latest version is 2.28.9.\n\nMore detailed information are in the subsections below, but here is an outline\nof how to use this feature:\n1. Set the following macros to `1` in _lwipopts.h_:\n   1. `LWIP_ALTCP` \u0026mdash; Enables the ALTCP layer\n   2. `LWIP_ALTCP_TLS` \u0026mdash; Enables the TLS features of ALTCP\n   3. `LWIP_ALTCP_TLS_MBEDTLS` \u0026mdash; Enables the Mbed TLS code for ALTCP TLS\n2. Set the `QNETHERNET_ALTCP_TLS_ADAPTER` macro to `1` in _qnethernet_opts.h_.\n   This enables the _altcp_tls_adapter_ functions that help ease integration.\n3. Install the latest Mbed TLS v2.x.x.\n4. Implement the functions required by _src/altcp_tls_adapter.cpp_. This file\n   implements the above allocator functions and simplifies the integration.\n5. Implement an entropy function for internal Mbed TLS use.\n\n#### Installing the Mbed TLS library\n\nCurrently, there doesn't seem to be an Arduino-friendly version of this library.\nSo, first download or clone a snapshot of the latest 2.x.x version (current as\nof this writing is 2.28.9): http://github.com/Mbed-TLS/mbedtls\n\nSee the `v2.28.9` or `mbedtls-2.28.9` tags for the 2.28.9 version, or the\n`mbedtls-2.28` branch for the latest 2.28.x version. The `development` and\n`master` branches currently point to version 3.6.x.\n\n##### Mbed TLS library install for Arduino IDE\n\nIn your preferred \"Libraries\" folder, create a folder named _mbedtls_.\nUnderneath that, create a _src_ folder. Copy, recursively, all files and folders\nfrom the distribution as follows:\n1. _distro_/library/* -\u003e \"Libraries\"/mbedtls/src\n2. _distro_/include/* -\u003e \"Libraries\"/mbedtls/src\n\nThe \"Libraries\" folder can is the same thing as \"Sketchbook location\" in the\napplication's Preferences. There should be a _libraries/_ folder inside\nthat location.\n\nNext, create an empty _mbedtls.h_ file inside _\"Libraries\"/mbedtls/src/_.\n\nNext, create a _library.properties_ file inside _\"Libraries\"/mbedtls/_:\n```properties\nname=Mbed TLS\nversion=2.28.9\nsentence=Mbed TLS is a C library that implements cryptographic primitives, X.509 certificate manipulation and the SSL/TLS and DTLS protocols.\nparagraph=Its small code footprint makes it suitable for embedded systems.\ncategory=Communication\nurl=https://github.com/Mbed-TLS/mbedtls\nincludes=mbedtls.h\n```\n(Ref: https://arduino.github.io/arduino-cli/latest/library-specification/)\n\nLast, modify the _mbedtls/src/mbedtls/config.h_ file by replacing it with the\ncontents of _examples/MbedTLSDemo/sample_mbedtls_config.h_. Note that Mbed TLS\nuses a slightly different configuration mechanism than lwIP: it uses macro\npresence rather than macro values.\n\nFor posterity, the following changes are the minimum possible set just to be\nable to get the library to compile:\n1. Define:\n   1. `MBEDTLS_NO_PLATFORM_ENTROPY`\n   2. `MBEDTLS_ENTROPY_HARDWARE_ALT` \u0026mdash; Requires `mbedtls_hardware_poll()`\n      function implementation\n2. Undefine:\n   1. `MBEDTLS_NET_C`\n   2. `MBEDTLS_TIMING_C`\n   3. `MBEDTLS_FS_IO`\n   4. `MBEDTLS_PSA_ITS_FILE_C`\n   5. `MBEDTLS_PSA_CRYPTO_STORAGE_C`\n\nThere are also example configuration headers in Mbed TLS under _configs/_.\n\nIt's likely that, if you're using the Arduino IDE, you'll need to restart it\nafter installing the library.\n\n##### Mbed TLS library install for PlatformIO\n\nIn your preferred \"Libraries\" folder, create a folder named _mbedtls_. Copy all\nfiles and folders, recursively, from the Mbed TLS distribution into that folder.\n\nThe \"Libraries\" folder is either PlatformIO's global libraries location or the\napplication's local _lib/_ folder.\n\nNext, create a _library.json_ file inside _\"Libraries\"/mbedtls/_:\n```json\n{\n  \"name\": \"Mbed TLS\",\n  \"version\": \"2.28.9\",\n  \"description\": \"Mbed TLS is a C library that implements cryptographic primitives, X.509 certificate manipulation and the SSL/TLS and DTLS protocols. Its small code footprint makes it suitable for embedded systems.\",\n  \"keywords\": [\n    \"tls\",\n    \"networking\"\n  ],\n  \"homepage\": \"https://www.trustedfirmware.org/projects/mbed-tls\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Mbed-TLS/mbedtls.git\"\n  },\n  \"license\": \"Apache-2.0 OR GPL-2.0-or-later\",\n  \"build\": {\n    \"srcDir\": \"library\",\n    \"includeDir\": \"include\"\n  }\n}\n```\n(Ref: https://docs.platformio.org/en/latest/manifests/library-json/index.html)\n\nLast, modify the _mbedtls/include/mbedtls/config.h_ file per the instructions in\nthe previous, Arduino IDE install, section.\n\n#### Implementing the _altcp_tls_adapter_ functions\n\nThe _MbedTLSDemo_ example illustrates how to implement these.\n\n#### Implementing the Mbed TLS entropy function\n\nSee how the _MbedTLSDemo_ example does it. Look for the\n`mbedtls_hardware_poll()` function. The example uses _QNEthernet_'s internal\nentropy function, `qnethernet_hal_fill_entropy()`. You can, of course, use your\nown entropy source if you like.\n\nIf you add the function to a C++ file, then it must be declared `extern \"C\"`.\n\n## On connections that hang around after cable disconnect\n\nRef: [EthernetServer accept no longer connects clients after unplugging/plugging ethernet cable ~7 times · Issue #15 · ssilverman/QNEthernet](https://github.com/ssilverman/QNEthernet/issues/15)\n\nTCP tries its best to maintain reliable communication between two endpoints,\neven when the physical link is unreliable. It uses techniques such as timeouts,\nretries, and exponential backoff. For example, if a cable is disconnected and\nthen reconnected, there may be some packet loss during the disconnect time, so\nTCP will try to resend any lost packets by retrying at successively larger\nintervals.\n\nThe TCP close process uses some two-way communication to properly shut down a\nconnection, and therefore is also subject to physical link reliability. If the\nphysical link is interrupted or the other side doesn't participate in the close\nprocess then the connection may appear to become \"stuck\", even when told to\nclose. The TCP stack won't consider the connection closed until all timeouts and\nretries have elapsed.\n\nIt turns out that some systems drop and forget a connection when the physical\nlink is disconnected. This means that the other side may still be waiting to\ncontinue or close the connection, timing out and retrying until all attempts\nhave failed. This can be as long as a half hour, or maybe more, depending on how\nthe stack is configured.\n\nThe above link contains a discussion where a user of this library couldn't\naccept any new connections, even when all the connections had been closed, until\nall the existing connections timed out after about a half hour. What happened\nwas this: connections were being made, the Ethernet cable was disconnected and\nreconnected, and then more connections were made. When the cable was\ndisconnected, all connections were closed using the `close()` function. The\nTeensy side still maintained connection state for all the connections, choosing\nto do what TCP does: make a best effort to maintain or properly close those\nconnections. Once all the available sockets had been exhausted, no more\nconnections could be accepted.\n\nThose connections couldn't be cleared and sockets made available until all the\nTCP retries had elapsed. The main problem was that the other side simply dropped\nthe connections when it detected a link disconnect. If the other system had\nmaintained those connections, it would have continued the close processes as\nnormal when the Ethernet cable was reconnected. That's why tests on my system\ncouldn't reproduce the issue: the IP stack on the Mac maintained state across\ncable disconnects/reconnects. The issue reporter was using Windows, and the IP\nstack there apparently drops a connection if the link disconnects. This left the\nTeensy side waiting for replies and retrying, and the Windows side no longer\nsending traffic.\n\nTo mitigate this problem, there are a few possible solutions, including:\n1. Reduce the number of retransmission attempts by changing the `TCP_MAXRTX`\n   setting in `lwipopts.h`, or\n2. Abort connections upon link disconnect.\n\nTo accomplish #2, there's an `EthernetClient::abort()` function that simply\ndrops a TCP connection without going though the normal TCP close process. This\ncould be called on connections when the link has been disconnected. (See also\n`Ethernet.onLinkState(cb)` or `Ethernet.linkState()`.)\n\nFun links:\n* [Removing Exponential Backoff from TCP](http://ccr.sigcomm.org/online/files/p19-mondal.pdf)\n  * Ref: [[PDF] Removing exponential backoff from TCP](https://www.semanticscholar.org/paper/Removing-exponential-backoff-from-TCP-Mondal-Kuzmanovic/2ab0df78bb0aa95c0ed8f3dc687937ccc64f6785)\n  * Older link: [Removing Exponential Backoff from TCP | acm sigcomm](http://www.sigcomm.org/node/2736)\n* [Exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff)\n\n## Notes on ordering and timing\n\n* Link status is polled about 8 times a second.\n* For static IP addresses, the _address-changed_ callback is called before\n  lwIP's `netif_default` is set. `MDNS.begin()` relies on `netif_default`, so\n  that function and anything else that relies on `netif_default` should be\n  called after `Ethernet.begin(...)`, and not from the listener.\n* The DNS lookup timeout is `DNS_MAX_RETRIES * DNS_TMR_INTERVAL`, where\n  `DNS_TMR_INTERVAL` is 1000.\n\n## Notes on RAM1 usage\n\nBy default, the Ethernet RX and TX buffers will go into RAM2. If, for whatever\nreason, you'd prefer to put them into RAM1, set the\n`QNETHERNET_BUFFERS_IN_RAM1` macro to `1`. _[As of this writing, no speed\ncomparison tests have been done.]_\n\nThere's a second configuration macro, `QNETHERNET_LWIP_MEMORY_IN_RAM1`, for\nindicating that lwIP-declared memory should go into RAM1 instead of RAM2.\n\nThese options are useful in the case where a program needs more dynamic memory,\nsay. Putting more things in RAM1 will free up more space for things like `new`\nand STL allocation.\n\n## Heap memory use\n\nThe library is configured, by default, to use the system-defined malloc\nfunctions. These include _malloc_, _free_, and _calloc_. The `MEM_LIBC_MALLOC`\noption controls this. Setting `MEM_LIBC_MALLOC` to zero will change any internal\nmalloc calls to use the lwIP-supplied malloc functions with a preallocated heap.\n\nWhen `MEM_LIBC_MALLOC` is enabled, the `MEM_SIZE` option is not used, and when\ndisabled, `MEM_SIZE` _is_ used and the heap is preallocated. One of the reasons\nthis option was enabled by default is that it saves memory if the whole heap\nisn't used. Plus, it saves some program memory because it doesn't need to\ninclude the code for the lwIP-defined functions. However, there's no cap on the\namount of memory a program can use which may be a concern for some software.\n\nA second reason this option was enabled is that the current system-supplied\nfunctions are likely optimized for the current platform. Yet a third reason is\nit's hard to anticipate what programs will actually need; this way obviates the\nneed to assume what a good heap size is.\n\nThere's a few macros that can be used if you want to use your own malloc\nfunctions and override the defaults. These are: `mem_clib_free`,\n`mem_clib_malloc`, and `mem_clib_calloc`. By default, if not set, these point to\nthe system-provided functions.\n\nFor example, if you want to use EXTMEM for the heap, then you can define these\nas `extmem_free`, `extmem_malloc`, and `extmem_calloc`, respectively. This could\neither be done in _lwipopts.h_ or wherever your build system provides\nbuild flags.\n\nExample that defines these in _lwipopts.h_:\n\n```c++\n#define mem_clib_free extmem_free\n#define mem_clib_malloc extmem_malloc\n#define mem_clib_calloc extmem_calloc\n```\n\n## Entropy generation\n\nFor the Teensy 4.0 and 4.1, this library defines functions for accessing the\nprocessor's internal \"true random number generator\" (TRNG) for entropy. If your\nproject needs to use the _Entropy_ library instead, set the\n`QNETHERNET_USE_ENTROPY_LIB` macro to `1` so that any internal entropy\ncollection doesn't interfere with your project's entropy collection.\n\nThe _Entropy_ library does essentially the same things as the internal TRNG\nfunctions, it just requires an additional dependency. This is the reason these\nfunctions are provided: to remove that dependency.\n\nSee the function declarations in _src/qnethernet/security/entropy.h_ if you want\nto use them yourself.\n\nIf the target device isn't a Teensy 4 then the _Entropy_ library will be used,\nunless it's not accessible or doesn't exist for the device, in which case an\ninstance of `std::minstd_rand` will be used.\n\n### The `RandomDevice` _UniformRandomBitGenerator_\n\nAlso provided is a class called `RandomDevice` that implements the\n[_UniformRandomBitGenerator_](https://en.cppreference.com/w/cpp/named_req/UniformRandomBitGenerator)\nC++ named requirement. It's in the `qindesign::security` namespace. An instance\ncan be accessed by calling its `instance()` static function.\n\nThis object works with both the internal entropy functions and with the\n_Entropy_ library.\n\nThis is the preferred way to acquire entropy. It is meant to be used with a\n[Random number distribution](https://en.cppreference.com/w/cpp/numeric/random#Random_number_distributions).\n\n## Security features\n\nThis section discusses the security features of this library.\n(Caveat: This library was not written by a security expert, so there are no\nsecurity guarantees.)\n\n### Secure TCP initial sequence numbers (ISNs)\n\nSecure TCP ISNs are generated using the suggested algorithm from\n[RFC 6528 Defending against Sequence Number Attacks](https://datatracker.ietf.org/doc/html/rfc6528).\nThe SipHash-2-4-64 algorithm is used as the pseudorandom function (PRF), and the\nmicrosecond clock is used as the timer.\n\nThe 128-bit key is generated on first use with a call to\n`qnethernet_hal_fill_entropy()`.\n\nThe ISN is calculated as follows:\n\u003e micros() + SipHash-2-4-64(local_port, remote_port, remote_ip, local_ip)\n\nThis feature can be enabled or disabled with the\n[`QNETHERNET_ENABLE_SECURE_TCP_ISN` macro](#configuration-macros).\n\n## Configuration macros\n\nThere are two sets of configuration macros:\n1. _QNEthernet_-specific options, and\n2. lwIP stack options.\n\nAny of them can either be configured as project build macros or by changing them\nin the relevant configuration file:\n1. _qnethernet_opts.h_ for the _QNEthernet_-specific options, and\n2. _lwipopts.h_ for the lwIP options.\n\nThe _QNEthernet_-specific macros are as follows:\n\n| Macro                                       | Default  | Description                                                                                    | Link                                                                                    |\n| ------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |\n| `QNETHERNET_ALTCP_TLS_ADAPTER`              | Disabled | Enables the _altcp_tls_adapter_ functions for easier TLS library integration                   | [About the TLS adapter functions](#about-the-tls-adapter-functions)                     |\n| `QNETHERNET_BUFFERS_IN_RAM1`                | Disabled | Puts the RX and TX buffers into RAM1                                                           | [Notes on RAM1 usage](#notes-on-ram1-usage)                                             |\n| `QNETHERNET_CUSTOM_WRITE`                   | Disabled | Uses expanded `stdio` output behaviour                                                         | [stdio](#stdio)                                                                         |\n| `QNETHERNET_DO_LOOP_IN_YIELD`               | Enabled  | The library should try to hook into or override yield() to call Ethernet.loop()                | [Notes on `yield()`](#notes-on-yield)                                                   |\n| `QNETHERNET_ENABLE_ALTCP_DEFAULT_FUNCTIONS` | Disabled | Enables default implementations of the altcp interface functions                               | [Application layered TCP: TLS, proxies, etc.](#application-layered-tcp-tls-proxies-etc) |\n| `QNETHERNET_ENABLE_PROMISCUOUS_MODE`        | Disabled | Enables promiscuous mode                                                                       | [Promiscuous mode](#promiscuous-mode)                                                   |\n| `QNETHERNET_ENABLE_RAW_FRAME_LOOPBACK`      | Disabled | Enables raw frame loopback when the destination MAC matches the local MAC or the broadcast MAC | [Raw frame loopback](#raw-frame-loopback)                                               |\n| `QNETHERNET_ENABLE_RAW_FRAME_SUPPORT`       | Disabled | Enables raw frame support                                                                      | [Raw Ethernet Frames](#raw-ethernet-frames)                                             |\n| `QNETHERNET_ENABLE_SECURE_TCP_ISN`          | Enabled  | Enables secure TCP initial sequence numbers (ISNs)                                             | [Secure TCP initial sequence numbers (ISNs)](#secure-tcp-initial-sequence-numbers-isns) |\n| `QNETHERNET_FLUSH_AFTER_WRITE`              | Disabled | Follows every `EthernetClient::write()` call with a flush; may reduce efficiency               | [Write immediacy](#write-immediacy)                                                     |\n| `QNETHERNET_LWIP_MEMORY_IN_RAM1`            | Disabled | Puts lwIP-declared memory into RAM1                                                            | [Notes on RAM1 usage](#notes-on-ram1-usage)                                             |\n| `QNETHERNET_USE_ENTROPY_LIB`                | Disabled | Uses _Entropy_ library instead of internal functions                                           | [Entropy collection](#entropy-collection)                                               |\n\nTo enable a feature, set the associated macro to `1` or just define it. To\ndisable a feature, either set the same macro to `0` or leave it undefined.\n\nSee\n[Changing lwIP configuration macros in `lwipopts.h`](#changing-lwip-configuration-macros-in-lwipoptsh)\nfor changing the IP stack configuration.\n\nNote that disabling features means that the build will not include those\nfeatures, thus saving space.\n\n### Configuring macros using the Arduino IDE\n\n_[Current as of this writing: Arduino IDE 2.3.5, Teensyduino 1.60-beta3]_\n\nThe Arduino IDE provides a facility to override the build options specified in a\nplatform's build configuration file, _platform.txt_. It does this by looking for\na file named _platform.local.txt_ in the same place. Any options in that \"local\"\nfile override equivalent options in the main file.\n\nNote that the IDE might need to be restarted when the file changes.\n\nThe suggested way to override compiler options is with the `*.extra_flags`\nproperties, for example, `compiler.cpp.extra_flags`, `compiler.c.extra_flags`,\nand `build.extra_flags`. However, this only works if _platform.txt_ uses those\noptions. If it does not then there's nothing to override. The current\nTeensyduino installation's _platform.txt_ file does not use these options.\n\nHere's how to implement the behaviour:\n1. Insert these sections somewhere in _platform.txt_, before the first location\n   where these properties will be used:\n   ```\n   # This can be overridden in boards.txt\n   build.extra_flags=\n\n   # These can be overridden in platform.local.txt\n   compiler.c.extra_flags=\n   compiler.cpp.extra_flags=\n   compiler.S.extra_flags=\n   ```\n2. Insert `{compiler.cpp.extra_flags}` and `{build.extra_flags}` before\n   `{includes}` in:\n   1. `recipe.preproc.includes`\n   2. `recipe.preproc.macros`\n   3. `recipe.cpp.o.pattern`\n3. Insert `{compiler.c.extra_flags}` and `{build.extra_flags}` before\n   `{includes}` in:\n   1. `recipe.c.o.pattern`\n4. Insert `{compiler.S.extra_flags}` and `{build.extra_flags}` before\n   `{includes}` in:\n   1. `recipe.S.o.pattern`\n\nNext, create a _platform.local.txt_ file in the same directory as the\n_platform.txt_ file and add the options you need. The contents of the properties\nare exactly the same as if adding them to the command line. For example, to\nenable raw frame support and disable DNS using the macros (the '-D' option\ndefines a macro):\n\n```\ncompiler.cpp.extra_flags=-DQNETHERNET_ENABLE_RAW_FRAME_SUPPORT=1 -DLWIP_DNS=0\ncompiler.c.extra_flags=-DQNETHERNET_ENABLE_RAW_FRAME_SUPPORT=1 -DLWIP_DNS=0\n```\n\nEach additional option is simply appended. No commas or quotes are required\nunless they would be used for those same options on the command line.\nSee\n[this issue comment](https://github.com/ssilverman/QNEthernet/issues/54#issuecomment-1788100978)\nfor some incorrect variants.\n\nNote that both properties are needed because _QNEthernet_ contains a mixture of\nC and C++ sources. If the extra flags are exactly the same for both properties,\nand this is likely the case, one could refer to the other. For example:\n\n```\ncompiler.cpp.extra_flags=-DQNETHERNET_ENABLE_RAW_FRAME_SUPPORT=1 -DLWIP_DNS=0\ncompiler.c.extra_flags={compiler.cpp.extra_flags}\n```\n\nThe properties of most interest are probably the ones in this example. There are\nother ones defined in the Arduino AVR version, but those aren't really\nneeded here.\n\nLest you think I've forgotten to add it, here're the locations of the files for\nthe current latest version of the IDE (for Teensy, specifically; the locations\nwill be different, but should be similar, for other platforms):\n* Mac: _~/Library/Arduino15/packages/teensy/hardware/avr/{version}_\n* Linux: _~/.arduino15/packages/teensy/hardware/avr/{version}_\n* Windows: _%userprofile%\\\\AppData\\\\Local\\\\Arduino15\\\\packages\\\\teensy\\\\hardware\\\\avr\\\\{version}_\n\nReferences:\n1. [Additional compiler options - Programming - Arduino Forum](https://forum.arduino.cc/t/additional-compiler-options/631297)\n2. [Arduino IDE: Where can I pass defines to the compiler? - IDE 1.x - Arduino Forum](https://forum.arduino.cc/t/arduino-ide-where-can-i-pass-defines-to-the-compiler/680845)\n3. [Request for Arduino IDE \u0026quot;extra_flags\u0026quot; support | Teensy Forum](https://forum.pjrc.com/index.php?threads/request-for-arduino-ide-extra_flags-support.72556/)\n4. [Platform specification - Arduino CLI](https://arduino.github.io/arduino-cli/latest/platform-specification/)\n5. This one started it all \u0026rarr; [RawFrameMonitor example seems to be missing something... · Issue #33 · ssilverman/QNEthernet](https://github.com/ssilverman/QNEthernet/issues/33)\n6. [Open the Arduino15 folder \u0026ndash; Arduino Help Center](https://support.arduino.cc/hc/en-us/articles/360018448279-Open-the-Arduino15-folder)\n7. [Enabling Raw Frame Support and Promiscuous · Issue #54 · ssilverman/QNEthernet](https://github.com/ssilverman/QNEthernet/issues/54)\n\n### Configuring macros using PlatformIO\n\nSimply add compiler flags to the `build_flags` build option in _platformio.ini_.\n\nFor example:\n\n```\nbuild_flags = -DQNETHERNET_ENABLE_RAW_FRAME_SUPPORT=1\n```\n\n### Changing lwIP configuration macros in `lwipopts.h`\n\nThe `lwipopts.h` file defines certain macros needed by the system. It is\nappropriate for the user to alter some of those macros if needed, for example,\nto change the number of UDP sockets or IGMP groups.\n\nThese macros can either be modified directly in the file (`lwipopts.h`) or from\nthe command line with a `-D` directive. The ones that can be modified from the\ncommand line are either wrapped in an `#ifndef` block or not defined at all.\n\nUseful macro list; please see further descriptions in `opt.h` and\nin `mdns_opts.h`:\n\n| Macro                     | Description                                                |\n| ------------------------- | ---------------------------------------------------------- |\n| `DNS_MAX_RETRIES`         | Maximum number of DNS retries                              |\n| `LWIP_ALTCP`              | `1` to enable application layered TCP (eg. TLS, proxies)   |\n| `LWIP_ALTCP_TLS`          | `1` to enable TLS support for ALTCP                        |\n| `LWIP_ALTCP_TLS_MBEDTLS`  | `1` to enable the Mbed TLS implementation for ALTCP TLS    |\n| `LWIP_DHCP`               | Zero to disable DHCP                                       |\n| `LWIP_DNS`                | Zero to disable DNS                                        |\n| `LWIP_IGMP`               | Zero to disable IGMP; also disables mDNS by default        |\n| `LWIP_LOOPBACK_MAX_PBUFS` | Non-zero to specify loopback queue size                    |\n| `LWIP_MDNS_RESPONDER`     | Zero to disable mDNS capabilities                          |\n| `LWIP_NETIF_LOOPBACK`     | `1` to enable loopback capabilities                        |\n| `LWIP_STATS`              | `1` to enable lwIP stats collection                        |\n| `LWIP_STATS_LARGE`        | `1` to use 32-bit stats counters instead of 16-bit         |\n| `LWIP_TCP`                | Zero to disable TCP                                        |\n| `LWIP_UDP`                | Zero to disable UDP; also disables DHCP and DNS by default |\n| `MDNS_MAX_SERVICES`       | Maximum number of mDNS services                            |\n| `MEM_LIBC_MALLOC`         | Zero to enable use of lwIP-defined malloc functions        |\n| `MEM_SIZE`                | Heap memory size; unused if `MEM_LIBC_MALLOC` is enabled   |\n| `MEMP_NUM_IGMP_GROUP`     | Number of multicast groups                                 |\n| `MEMP_NUM_TCP_PCB`        | Number of listening TCP sockets                            |\n| `MEMP_NUM_TCP_PCB_LISTEN` | Number of TCP sockets                                      |\n| `MEMP_NUM_UDP_PCB`        | Number of UDP sockets                                      |\n\nSome extra conditions to keep in mind:\n* `MEMP_NUM_IGMP_GROUP`: Count must include 1 for the \"all systems\" group and 1\n  if mDNS is enabled.\n* `MEMP_NUM_UDP_PCB`: Count must include one if mDNS is enabled.\n\n## Complete list of features\n\nThis section is an attempt to provide a complete list of features in the\n_QNEthernet_ library.\n\n1. Compatible with the Arduino-style Ethernet API\n2. [Additional functions and features not in the Arduino-style API](#additional-functions-and-features-not-in-the-arduino-style-api)\n3. Automatic MAC address detection on the Teensy platform; it's not necessary to\n   initialize the library with your own MAC address for that platform\n4. A [DNS client](#dnsclient)\n5. [mDNS](#mdns) support\n6. [Raw Ethernet frame](#raw-ethernet-frames) support\n7. [`stdio`](#stdio) output redirection support for `stdout` and `stderr`\n8. [VLAN tagging](#how-to-implement-vlan-tagging) support\n9. [Zero-length UDP packets](#parsepacket-return-values)\n10. [UDP](#udp-receive-buffering) and [raw frame](#raw-frame-receive-buffering)\n    receive buffering for when data arrives in bursts that are faster than the\n    ability of the program to process them\n11. [Listeners](#how-to-use-listeners) to watch link, network interface, and\n    address state\n12. With some additions:\n    1. IPv6\n    2. IEEE 1588, Precision Time Protocol (PTP)\n    3. TLS and TCP proxies\n    4. IEEE 802.3az, Energy-Efficient Ethernet (EEE)\n    5. Secure TCP initial sequence numbers (ISNs)\n13. [Client shutdown options](#ethernetclient): _close_ (start close process\n    without waiting), _closeOutput_ (close just the output side, also called a\n    \"half-close\"), _abort_ (shuts down the connection without going through the\n    TCP close process), _stop_ (close and wait)\n14. Ability to [fully write](#how-to-write-data-to-connections) data to a\n    client connection\n15. [Multicast](#how-to-use-multicast) support\n16. [Promiscuous mode](#promiscuous-mode)\n17. `SO_REUSEADDR` support (see [`EthernetServer`](#ethernetserver)\n    and [`EthernetUDP`](#ethernetudp))\n18. [`TCP_NODELAY`](#tcp-socket-options) support\n19. Configuration via [Configuration macros](#configuration-macros)\n20. Non-blocking TCP connections\n21. Teensy platform: Internal [Entropy generation](#entropy-generation)\n    functions to avoid the _Entropy_ lib dependency; this can be disabled with a\n    configuration macro\n22. A \"random device\" satisfying the _UniformRandomBitGenerator_ C++ named\n    requirement that provides access to hardware-generated entropy (see\n    [The `RandomDevice` _UniformRandomBitGenerator_](#the-randomdevice-uniformrandombitgenerator))\n23. Driver support for:\n    1. Teensy 4.1\n    2. W5500\n24. Straightforward to add new Ethernet frame drivers\n25. Ability to toggle Nagle's algorithm for TCP\n26. Ability to set some IP header fields: differentiated services (DiffServ)\n    and TTL\n27. Secure TCP initial sequence numbers (ISNs)\n\n## Compatibility with other APIs\n\nThis section describes compatibility with other Ethernet APIs. The term\n\"API unifictation effort\" will be used to describe the information from here:\n[Guide for Arduino networking library developers](https://github.com/Networking-for-Arduino/Arduino-Networking-API/blob/6370554129b4728bd1deb7ca5d429105c54d8bba/ArduinoNetAPIDev.md)\n\nWhat follows are explanatory notes for each differing API function from\nthat link.\n\n`uint8_t* Ethernet.macAddress(uint8_t* mac)`:-\\\nFor MAC address retrieval, see `uint8_t* Ethernet.macAddress()` and\n`void Ethernet.macAddress(uint8_t* mac)`. This form isn't implemented because\nthe return value is ambiguous when the input is NULL.\n\n`Ethernet.begin(...)` functions:-\\\nExcept for the Arduino-defined `begin(mac, timeout)` function, the `begin`\nfunctions are all non-blocking. Regarding parameter order, the \"ip, subnet,\ngateway, dns\" order is more sensible than the \"ip, dns, gateway, subnet\" order.\nAdditionally, the default values for the \"ip, dns, gateway, subnet\" order don't\nwork well except for very specific networks.\n\n`Ethernet.config(address, dns, gateway, mask)`:-\\\nThere's some ambiguity on how this function is defined with relation to `begin`.\n\nDNS functions:-\\\nThe Arduino API defines `Ethernet.dnsServerIP()` and\n`Ethernet.setDnsServerIP(dns)`. The _QNEthernet_ library defines `dnsServerIP()`\nand `setDNSServerIP(dns)` for more consistent naming. There are also versions\nthat take an index parameter for retrieving and setting different DNS addresses.\nThe unification effort describes inconsistent `setDNS(dns1, dns2)` and\n`dnsIP(index)` functions. These are inconsistent in two ways: the names, and the\nimbalance between only allowing two to be set but any number to be retrieved. So\nas not to add more variants, it was chosen to keep the original names.\n\n`EthernetClient::stop()`:-\\\nThe Arduino version states that this function has a disconnect timeout in the\n`setConnectionTimeout(ms)` description. The _QNEthernet_ library version waits\nfor a timeout and provides a `close()` function that does not wait. There is\nalso an `abort()` function that aborts the connection with a RST segment.\n\n`EthernetClient::status()`:-\\\nReturns a value of type `enum tcp_state`, an lwIP type.\n\n`EthernetUDP::parsePacket()`:-\\\nThe _QNEthernet_ library version returns -1 for no packet available and not\nzero, because empty UDP packets exist. If zero is returned when no packet is\navailable then empty packets could not be detected. (A `std::optional` might be\na better choice for the return value here, or even a `hasPacket()` function that\nsplits out presence from size.)\n\n## Other notes\n\nI'm not 100% percent certain where this library will go, but I want it to be\namazing. It was originally meant to be an alternative to the NativeEthernet/FNET\nlibrary on the Teensy 4.1. Now it can be used as an alternative to other\nlibraries on other platforms.\n\nI'm also not settled on the name.\n\nInput is welcome.\n\n## To do\n\n* Tune lwIP.\n* A better API design than the Arduino-defined API.\n* Perhaps zero-copy is an option.\n* More unit tests.\n* I have seen\n  `Assertion \"tcp_slowtmr: TIME-WAIT pcb-\u003estate == TIME-WAIT\" failed at line 1442 in src/lwip/tcp.c`\n  when sending a large amount of data. Either it's an lwIP bug or I'm doing\n  ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssilverman%2Fqnethernet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssilverman%2Fqnethernet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssilverman%2Fqnethernet/lists"}