1 /* Captive Portal Example
2 
3     This example code is in the Public Domain (or CC0 licensed, at your option.)
4 
5     Unless required by applicable law or agreed to in writing, this
6     software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
7     CONDITIONS OF ANY KIND, either express or implied.
8 */
9 
10 #include <sys/param.h>
11 
12 #include "esp_log.h"
13 #include "esp_system.h"
14 #include "esp_netif.h"
15 
16 #include "lwip/err.h"
17 #include "lwip/sockets.h"
18 #include "lwip/sys.h"
19 #include "lwip/netdb.h"
20 
21 #define DNS_PORT (53)
22 #define DNS_MAX_LEN (256)
23 
24 #define OPCODE_MASK (0x7800)
25 #define QR_FLAG (1 << 7)
26 #define QD_TYPE_A (0x0001)
27 #define ANS_TTL_SEC (300)
28 
29 static const char *TAG = "example_dns_redirect_server";
30 
31 // DNS Header Packet
32 typedef struct __attribute__((__packed__))
33 {
34     uint16_t id;
35     uint16_t flags;
36     uint16_t qd_count;
37     uint16_t an_count;
38     uint16_t ns_count;
39     uint16_t ar_count;
40 } dns_header_t;
41 
42 // DNS Question Packet
43 typedef struct {
44     uint16_t type;
45     uint16_t class;
46 } dns_question_t;
47 
48 // DNS Answer Packet
49 typedef struct __attribute__((__packed__))
50 {
51     uint16_t ptr_offset;
52     uint16_t type;
53     uint16_t class;
54     uint32_t ttl;
55     uint16_t addr_len;
56     uint32_t ip_addr;
57 } dns_answer_t;
58 
59 /*
60     Parse the name from the packet from the DNS name format to a regular .-seperated name
61     returns the pointer to the next part of the packet
62 */
parse_dns_name(char * raw_name,char * parsed_name,size_t parsed_name_max_len)63 static char *parse_dns_name(char *raw_name, char *parsed_name, size_t parsed_name_max_len)
64 {
65 
66     char *label = raw_name;
67     char *name_itr = parsed_name;
68     int name_len = 0;
69 
70     do {
71         int sub_name_len = *label;
72         // (len + 1) since we are adding  a '.'
73         name_len += (sub_name_len + 1);
74         if (name_len > parsed_name_max_len) {
75             return NULL;
76         }
77 
78         // Copy the sub name that follows the the label
79         memcpy(name_itr, label + 1, sub_name_len);
80         name_itr[sub_name_len] = '.';
81         name_itr += (sub_name_len + 1);
82         label += sub_name_len + 1;
83     } while (*label != 0);
84 
85     // Terminate the final string, replacing the last '.'
86     parsed_name[name_len - 1] = '\0';
87     // Return pointer to first char after the name
88     return label + 1;
89 }
90 
91 // Parses the DNS request and prepares a DNS response with the IP of the softAP
parse_dns_request(char * req,size_t req_len,char * dns_reply,size_t dns_reply_max_len)92 static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t dns_reply_max_len)
93 {
94     if (req_len > dns_reply_max_len) {
95         return -1;
96     }
97 
98     // Prepare the reply
99     memset(dns_reply, 0, dns_reply_max_len);
100     memcpy(dns_reply, req, req_len);
101 
102     // Endianess of NW packet different from chip
103     dns_header_t *header = (dns_header_t *)dns_reply;
104     ESP_LOGD(TAG, "DNS query with header id: 0x%X, flags: 0x%X, qd_count: %d",
105              ntohs(header->id), ntohs(header->flags), ntohs(header->qd_count));
106 
107     // Not a standard query
108     if ((header->flags & OPCODE_MASK) != 0) {
109         return 0;
110     }
111 
112     // Set question response flag
113     header->flags |= QR_FLAG;
114 
115     uint16_t qd_count = ntohs(header->qd_count);
116     header->an_count = htons(qd_count);
117 
118     int reply_len = qd_count * sizeof(dns_answer_t) + req_len;
119     if (reply_len > dns_reply_max_len) {
120         return -1;
121     }
122 
123     // Pointer to current answer and question
124     char *cur_ans_ptr = dns_reply + req_len;
125     char *cur_qd_ptr = dns_reply + sizeof(dns_header_t);
126     char name[128];
127 
128     // Respond to all questions with the ESP32's IP address
129     for (int i = 0; i < qd_count; i++) {
130         char *name_end_ptr = parse_dns_name(cur_qd_ptr, name, sizeof(name));
131         if (name_end_ptr == NULL) {
132             ESP_LOGE(TAG, "Failed to parse DNS question: %s", cur_qd_ptr);
133             return -1;
134         }
135 
136         dns_question_t *question = (dns_question_t *)(name_end_ptr);
137         uint16_t qd_type = ntohs(question->type);
138         uint16_t qd_class = ntohs(question->class);
139 
140         ESP_LOGD(TAG, "Received type: %d | Class: %d | Question for: %s", qd_type, qd_class, name);
141 
142         if (qd_type == QD_TYPE_A) {
143             dns_answer_t *answer = (dns_answer_t *)cur_ans_ptr;
144 
145             answer->ptr_offset = htons(0xC000 | (cur_qd_ptr - dns_reply));
146             answer->type = htons(qd_type);
147             answer->class = htons(qd_class);
148             answer->ttl = htonl(ANS_TTL_SEC);
149 
150             esp_netif_ip_info_t ip_info;
151             esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
152             ESP_LOGD(TAG, "Answer with PTR offset: 0x%X and IP 0x%X", ntohs(answer->ptr_offset), ip_info.ip.addr);
153 
154             answer->addr_len = htons(sizeof(ip_info.ip.addr));
155             answer->ip_addr = ip_info.ip.addr;
156         }
157     }
158     return reply_len;
159 }
160 
161 /*
162     Sets up a socket and listen for DNS queries,
163     replies to all type A queries with the IP of the softAP
164 */
dns_server_task(void * pvParameters)165 void dns_server_task(void *pvParameters)
166 {
167     char rx_buffer[128];
168     char addr_str[128];
169     int addr_family;
170     int ip_protocol;
171 
172     while (1) {
173 
174         struct sockaddr_in dest_addr;
175         dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
176         dest_addr.sin_family = AF_INET;
177         dest_addr.sin_port = htons(DNS_PORT);
178         addr_family = AF_INET;
179         ip_protocol = IPPROTO_IP;
180         inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1);
181 
182         int sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
183         if (sock < 0) {
184             ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
185             break;
186         }
187         ESP_LOGI(TAG, "Socket created");
188 
189         int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
190         if (err < 0) {
191             ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
192         }
193         ESP_LOGI(TAG, "Socket bound, port %d", DNS_PORT);
194 
195         while (1) {
196             ESP_LOGI(TAG, "Waiting for data");
197             struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6
198             socklen_t socklen = sizeof(source_addr);
199             int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
200 
201             // Error occurred during receiving
202             if (len < 0) {
203                 ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
204                 close(sock);
205                 break;
206             }
207             // Data received
208             else {
209                 // Get the sender's ip address as string
210                 if (source_addr.sin6_family == PF_INET) {
211                     inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);
212                 } else if (source_addr.sin6_family == PF_INET6) {
213                     inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1);
214                 }
215 
216                 // Null-terminate whatever we received and treat like a string...
217                 rx_buffer[len] = 0;
218 
219                 char reply[DNS_MAX_LEN];
220                 int reply_len = parse_dns_request(rx_buffer, len, reply, DNS_MAX_LEN);
221 
222                 ESP_LOGI(TAG, "Received %d bytes from %s | DNS reply with len: %d", len, addr_str, reply_len);
223                 if (reply_len <= 0) {
224                     ESP_LOGE(TAG, "Failed to prepare a DNS reply");
225                 } else {
226                     int err = sendto(sock, reply, reply_len, 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
227                     if (err < 0) {
228                         ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
229                         break;
230                     }
231                 }
232             }
233         }
234 
235         if (sock != -1) {
236             ESP_LOGE(TAG, "Shutting down socket");
237             shutdown(sock, 0);
238             close(sock);
239         }
240     }
241     vTaskDelete(NULL);
242 }
243 
start_dns_server(void)244 void start_dns_server(void)
245 {
246     xTaskCreate(dns_server_task, "dns_server", 4096, NULL, 5, NULL);
247 }
248