Overview

This tutorial demonstrates how Mongoose Library can be used to implement a captive DNS portal. It is usually required for device configuration. For example, an un-configured device starts its own WiFi network, and on this network, any DNS name gets resolved to that device. This way, a user might not know device's IP address to get to the WiFi configuration page.

A full source code for this tutorial is at https://github.com/cesanta/mongoose/tree/master/examples/captive-dns-server

Build and test

Assuming we're on a Mac or Linux workstation, start a new terminal and execute the following to build and start the websocket server:

$ git clone https://github.com/cesanta/mongoose
$ cd mongoose/examples/captive-dns-portal
$ make
cc ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1  -o example main.c
./example 
2021-11-23 13:59:07 3 sock.c:502:mg_listen       1 accepting on udp://0.0.0.0:5533 (port 5533)

Usually DNS servers use port 53. However that port is privileged - a program that opens that port must have root permissions. That is why our example uses port 5533 instead of port 53.

Now start another terminal and type a command that resolves any domain name, for example "example.com":

$ dig @localhost -p 5533 -4 example.com A
...
;; QUESTION SECTION:
;example.com.            IN    A
;; ANSWER SECTION:
example.com.        120    IN    A    1.2.3.4

Here we see that "example.com" has been resolved to "1.2.3.4" which is hardcoded by the server.

How it works

In the usual event manager initialisation code, we start a UDP listener:

In the event handler function, we catch MG_EV_READ, which is triggered every time we receive a DNS request:

There, we try to parse the received message. If it is a valid DNS request, we craft a response which has a hardcoded IP address of "1.2.3.4", send the response back, and clean up the c->recv IO buffer.