genderequalitygoals

genderequalitygoals

Monday, 1 April 2024

Behind the router

I have recently finished a project that involves local DNS and DHCP servers. Basically, I was given a black box IoT device that is configured to connect to a certain server, and I must redirect its connection to my own custom server without being able t…
Read on blog or Reader
Site logo image Fujihita Read on blog or Reader

Behind the router

fujihita

April 1

I have recently finished a project that involves local DNS and DHCP servers. Basically, I was given a black box IoT device that is configured to connect to a certain server, and I must redirect its connection to my own custom server without being able to change the device's configuration.

Nothing nefarious about what I'm doing here. The client wanted everything to be managed by a local control system, but the supplier provided no API to integrate into a local system, they only provided API to interact with their public data server, which then issues MQTT packets to the device. Bypassing the public server wasn't supported.

I think it's a good time to brush up on the Network Engineering lectures I attended a decade ago.

Acquiring an IP address

When a device connects to a network, it does so with a "Network interface". The network interfaces come with their unique "MAC address" that can be attached to an "IP address". A device with two network interfaces, WLAN and Ethernet for example, can connect to two different networks at the same time and possess two different IP addresses at the minimum.

Within the IP protocol suite, there are some one-to-one (unicast) protocols and there are some one-to-all broadcast protocols. A new device to the network will broadcast a message looking for the network's "DHCP" or the authority server responsible for managing IP addresses within the network. All devices within the network will receive this message.

The DHCP server, usually operated by the router, will then offer the new device an IP address and the IP address of a "default gateway". The default gateway is usually also the router itself (192.168.1.1 for most routers).

This tells the new device who the boss of the network is.

Sending packets

There are two ways to address a data packet: with "IP address" and with "domain name".

A "DNS" or "Domain Name System" server is responsible for translating domain names into IP addresses. Every device can decide for itself which DNS server it will use. Those that have no preference will just use the DNS server provided by the default gateway.

If the said router is connected to the internet, the DNS server will, by default, be the ISP's DNS server. This does give the ISP (and governments) power to block certain domain names from resolving, and this is why querying Google DNS or Open DNS can sometimes bypass censorship. That is, of course, if the government doesn't also use the DNS to block IP addresses of these other websites as well.

By configuring the router to direct all clients to a local DNS server, it is possible to assign human-readable domain names that are valid only within the local network.

Hosting local DHCP

On to the project itself...

I don't have a router in the local network, everything was connected using switches, and I cannot manually set the IP address of the IoT device in question, I have to begin by setting up a local DHCP server. The ESP8266 can run a DHCP server for its own wifi networks so I wouldn't need one when I used it as a shell for the IoT device.

But, in the end, I ended up using OpenDHCP on a server machine within the local network after the scope of the project was later expanded.

Hosting local DNS

The local network also did not use a DNS server. Therefore, I hosted one on an ESP8266. Someone on the internet was helpful enough to provide a solution here and it does work with some modifications:



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters


/* Create a WiFi access point and provide a web server on it. */
#include <ESP8266WiFi.h>
#include "./DNSServer.h" // Patched lib
#include <ESP8266WebServer.h>
const byte DNS_PORT = 53; // Capture DNS requests on port 53
IPAddress apIP(10, 10, 10, 1); // Private network for server
DNSServer dnsServer; // Create the DNS object
ESP8266WebServer webServer(80); // HTTP server
String responseHTML = "<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"utf-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
"<title>Internet of Bottles</title>"
"</head>"
"<body>"
"<p>I'm just a stupid bottle with WiFi.</p>"
"</body>"
"</html>";
void setup() {
// turn the LED on (HIGH is the voltage level)
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
// configure access point
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
WiFi.softAP("IoT --- Free WiFi"); // WiFi name
// if DNSServer is started with "*" for domain name, it will reply with
// provided IP to all DNS request
dnsServer.start(DNS_PORT, "*", apIP);
// replay to all requests with same HTML
webServer.onNotFound([]() {
webServer.send(200, "text/html", responseHTML);
});
webServer.begin();
}
void loop() {
dnsServer.processNextRequest();
webServer.handleClient();
}
view raw

AccessPoint.ino

hosted with ❤ by GitHub



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters


#include "./DNSServer.h"
#include <lwip/def.h>
#include <Arduino.h>
#define DEBUG
#define DEBUG_OUTPUT Serial
DNSServer::DNSServer()
{
_ttl = htonl(60);
_errorReplyCode = DNSReplyCode::NonExistentDomain;
}
bool DNSServer::start(const uint16_t &port, const String &domainName,
const IPAddress &resolvedIP)
{
_port = port;
_domainName = domainName;
_resolvedIP[0] = resolvedIP[0];
_resolvedIP[1] = resolvedIP[1];
_resolvedIP[2] = resolvedIP[2];
_resolvedIP[3] = resolvedIP[3];
downcaseAndRemoveWwwPrefix(_domainName);
return _udp.begin(_port) == 1;
}
void DNSServer::setErrorReplyCode(const DNSReplyCode &replyCode)
{
_errorReplyCode = replyCode;
}
void DNSServer::setTTL(const uint32_t &ttl)
{
_ttl = htonl(ttl);
}
void DNSServer::stop()
{
_udp.stop();
}
void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName)
{
domainName.toLowerCase();
domainName.replace("www.", "");
}
void DNSServer::processNextRequest()
{
_currentPacketSize = _udp.parsePacket();
if (_currentPacketSize)
{
_buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char));
_udp.read(_buffer, _currentPacketSize);
_dnsHeader = (DNSHeader*) _buffer;
if (_dnsHeader->QR == DNS_QR_QUERY &&
_dnsHeader->OPCode == DNS_OPCODE_QUERY &&
requestIncludesOnlyOneQuestion() &&
(_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName)
)
{
replyWithIP();
}
else if (_dnsHeader->QR == DNS_QR_QUERY)
{
replyWithCustomCode();
}
free(_buffer);
}
}
bool DNSServer::requestIncludesOnlyOneQuestion()
{
return ntohs(_dnsHeader->QDCount) == 1 &&
_dnsHeader->ANCount == 0 &&
_dnsHeader->NSCount == 0 &&
_dnsHeader->ARCount == 0;
}
String DNSServer::getDomainNameWithoutWwwPrefix()
{
String parsedDomainName = "";
unsigned char *start = _buffer + 12;
if (*start == 0)
{
return parsedDomainName;
}
int pos = 0;
while(true)
{
unsigned char labelLength = *(start + pos);
for(int i = 0; i < labelLength; i++)
{
pos++;
parsedDomainName += (char)*(start + pos);
}
pos++;
if (*(start + pos) == 0)
{
downcaseAndRemoveWwwPrefix(parsedDomainName);
return parsedDomainName;
}
else
{
parsedDomainName += ".";
}
}
}
void DNSServer::replyWithIP()
{
_dnsHeader->QR = DNS_QR_RESPONSE;
_dnsHeader->ANCount = _dnsHeader->QDCount;
_dnsHeader->QDCount = _dnsHeader->QDCount;
//_dnsHeader->RA = 1;
_udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
_udp.write(_buffer, _currentPacketSize);
_udp.write((uint8_t)192); // answer name is a pointer
_udp.write((uint8_t)12); // pointer to offset at 0x00c
_udp.write((uint8_t)0); // 0x0001 answer is type A query (host address)
_udp.write((uint8_t)1);
_udp.write((uint8_t)0); //0x0001 answer is class IN (internet address)
_udp.write((uint8_t)1);
_udp.write((unsigned char*)&_ttl, 4);
// Length of RData is 4 bytes (because, in this case, RData is IPv4)
_udp.write((uint8_t)0);
_udp.write((uint8_t)4);
_udp.write(_resolvedIP, sizeof(_resolvedIP));
_udp.endPacket();
#ifdef DEBUG
DEBUG_OUTPUT.print("DNS responds: ");
DEBUG_OUTPUT.print(_resolvedIP[0]);
DEBUG_OUTPUT.print(".");
DEBUG_OUTPUT.print(_resolvedIP[1]);
DEBUG_OUTPUT.print(".");
DEBUG_OUTPUT.print(_resolvedIP[2]);
DEBUG_OUTPUT.print(".");
DEBUG_OUTPUT.print(_resolvedIP[3]);
DEBUG_OUTPUT.print(" for ");
DEBUG_OUTPUT.println(getDomainNameWithoutWwwPrefix());
#endif
}
void DNSServer::replyWithCustomCode()
{
_dnsHeader->QR = DNS_QR_RESPONSE;
_dnsHeader->RCode = (unsigned char)_errorReplyCode;
_dnsHeader->QDCount = 0;
_udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
_udp.write(_buffer, sizeof(DNSHeader));
_udp.endPacket();
}
view raw

DNSServer.cpp

hosted with ❤ by GitHub



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters


#ifndef DNSServer_h
#define DNSServer_h
#include <WiFiUdp.h>
#define DNS_QR_QUERY 0
#define DNS_QR_RESPONSE 1
#define DNS_OPCODE_QUERY 0
enum class DNSReplyCode
{
NoError = 0,
FormError = 1,
ServerFailure = 2,
NonExistentDomain = 3,
NotImplemented = 4,
Refused = 5,
YXDomain = 6,
YXRRSet = 7,
NXRRSet = 8
};
struct DNSHeader
{
uint16_t ID; // identification number
unsigned char RD : 1; // recursion desired
unsigned char TC : 1; // truncated message
unsigned char AA : 1; // authoritive answer
unsigned char OPCode : 4; // message_type
unsigned char QR : 1; // query/response flag
unsigned char RCode : 4; // response code
unsigned char Z : 3; // its z! reserved
unsigned char RA : 1; // recursion available
uint16_t QDCount; // number of question entries
uint16_t ANCount; // number of answer entries
uint16_t NSCount; // number of authority entries
uint16_t ARCount; // number of resource entries
};
class DNSServer
{
public:
DNSServer();
void processNextRequest();
void setErrorReplyCode(const DNSReplyCode &replyCode);
void setTTL(const uint32_t &ttl);
// Returns true if successful, false if there are no sockets available
bool start(const uint16_t &port,
const String &domainName,
const IPAddress &resolvedIP);
// stops the DNS server
void stop();
private:
WiFiUDP _udp;
uint16_t _port;
String _domainName;
unsigned char _resolvedIP[4];
int _currentPacketSize;
unsigned char* _buffer;
DNSHeader* _dnsHeader;
uint32_t _ttl;
DNSReplyCode _errorReplyCode;
void downcaseAndRemoveWwwPrefix(String &domainName);
String getDomainNameWithoutWwwPrefix();
bool requestIncludesOnlyOneQuestion();
void replyWithIP();
void replyWithCustomCode();
};
#endif
view raw

DNSServer.h

hosted with ❤ by GitHub

The ESP8266 has two separate "network interfaces" when it operates in hybrid mode, acting as a router access point (AP) and a station (STA) at the same time. It can attach its DHCP and DNS only to the AP interface for interactions with the IoT device itself while manually setting its own IP address to its STA interface.

This is a sort of a man-in-the-middle attack where the device thought it was talking to a public server, but it was, in fact, talking to the ESP8266 who acted as its container in the local network.

Somewhat intrigued by the work, the client then requested to assign human-readable domain names to other devices in the local network. They helpfully provided some space on their main server machine for a DNS and DHCP server.

Also, they weren't exactly a fan of the cheap-looking ESP8266.

MaraDNS can handle multiple domain names and a lot more lookup requests than ESP8266.

https://github.com/samboy/MaraDNS

Extra: Double NAT and VLAN

The second scope expansion of this project involved connecting similar local networks using a site-wide wifi network, which has its own DNS and DHCP from the ISP. I have investigated the possibility of using double NAT, as in, putting a second router behind the entire network's first router.

DHCPs are first come, first serve so the DHCP of the second router would be prioritized by the merit of proximity to the client device. In fact, most routers have the option to run their own subnet which must not overlap with its WAN's subnet.

Devices behind the second router (network B) can access devices in the first router's network (network A) but it's not possible to access devices within a third router's network (network C) because there is no valid subnet matching network C within network A. All packets would be dropped while in network A.

A more viable solution I'm considering is virtual LAN (VLAN) but that does require accessing the configuration of the first router, which the site management is hesitant to permit.

In VLAN, all devices operate on the same physical infrastructure, but each has its own "parallel universe" VLAN tag layer.

Comment
Like
You can also reply to this email to leave a comment.

Fujihita © 2024. Manage your email settings or unsubscribe.

WordPress.com and Jetpack Logos

Get the Jetpack app

Subscribe, bookmark, and get real-time notifications - all from one app!

Download Jetpack on Google Play Download Jetpack from the App Store
WordPress.com Logo and Wordmark title=

Automattic, Inc. - 60 29th St. #343, San Francisco, CA 94110  

at April 01, 2024
Email ThisBlogThis!Share to XShare to FacebookShare to Pinterest

No comments:

Post a Comment

Newer Post Older Post Home
Subscribe to: Post Comments (Atom)

A Quick Update From ASUN

Autistic Substance Use Network ͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏   ...

  • [New post] “You Might Go to Prison, Even if You’re Innocent”
    Delaw...
  • Autistic Mental Health Conference 2025
    Online & In-Person ͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏     ­͏    ...
  • [Blog Post] Principle #16: Take care of your teacher self.
    Dear Reader,  To read this week's post, click here:  https://teachingtenets.wordpress.com/2025/07/02/aphorism-24-take-care-of-your-teach...

Search This Blog

  • Home

About Me

GenderEqualityDigest
View my complete profile

Report Abuse

Blog Archive

  • January 2026 (32)
  • December 2025 (52)
  • November 2025 (57)
  • October 2025 (65)
  • September 2025 (71)
  • August 2025 (62)
  • July 2025 (59)
  • June 2025 (55)
  • May 2025 (34)
  • April 2025 (62)
  • March 2025 (50)
  • February 2025 (39)
  • January 2025 (44)
  • December 2024 (32)
  • November 2024 (19)
  • October 2024 (15)
  • September 2024 (19)
  • August 2024 (2651)
  • July 2024 (3129)
  • June 2024 (2936)
  • May 2024 (3138)
  • April 2024 (3103)
  • March 2024 (3214)
  • February 2024 (3054)
  • January 2024 (3244)
  • December 2023 (3092)
  • November 2023 (2678)
  • October 2023 (2235)
  • September 2023 (1691)
  • August 2023 (1347)
  • July 2023 (1465)
  • June 2023 (1484)
  • May 2023 (1488)
  • April 2023 (1383)
  • March 2023 (1469)
  • February 2023 (1268)
  • January 2023 (1364)
  • December 2022 (1351)
  • November 2022 (1343)
  • October 2022 (1062)
  • September 2022 (993)
  • August 2022 (1355)
  • July 2022 (1771)
  • June 2022 (1299)
  • May 2022 (1228)
  • April 2022 (1325)
  • March 2022 (1264)
  • February 2022 (858)
  • January 2022 (903)
  • December 2021 (1201)
  • November 2021 (3152)
  • October 2021 (2609)
Powered by Blogger.