/
captdns.c
345 lines (303 loc) · 9.9 KB
/
captdns.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
* this notice you can do whatever you want with this stuff. If we meet some day,
* and you think this stuff is worth it, you can buy me a beer in return.
* ----------------------------------------------------------------------------
*/
/*
This is a 'captive portal' DNS server: it basically replies with a fixed IP (in this case:
the one of the SoftAP interface of this ESP module) for any and all DNS queries. This can
be used to send mobile phones, tablets etc which connect to the ESP in AP mode directly to
the internal webserver.
*/
#include <esp8266.h>
#ifndef ESP32
#ifdef FREERTOS
#ifdef ESP32
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#else
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#endif
#include "lwip/sockets.h"
#include "lwip/err.h"
static int sockFd;
#endif
#define DNS_LEN 512
typedef struct __attribute__ ((packed)) {
uint16_t id;
uint8_t flags;
uint8_t rcode;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
} DnsHeader;
typedef struct __attribute__ ((packed)) {
uint8_t len;
uint8_t data;
} DnsLabel;
typedef struct __attribute__ ((packed)) {
//before: label
uint16_t type;
uint16_t class;
} DnsQuestionFooter;
typedef struct __attribute__ ((packed)) {
//before: label
uint16_t type;
uint16_t class;
uint32_t ttl;
uint16_t rdlength;
//after: rdata
} DnsResourceFooter;
typedef struct __attribute__ ((packed)) {
uint16_t prio;
uint16_t weight;
} DnsUriHdr;
#define FLAG_QR (1<<7)
#define FLAG_AA (1<<2)
#define FLAG_TC (1<<1)
#define FLAG_RD (1<<0)
#define QTYPE_A 1
#define QTYPE_NS 2
#define QTYPE_CNAME 5
#define QTYPE_SOA 6
#define QTYPE_WKS 11
#define QTYPE_PTR 12
#define QTYPE_HINFO 13
#define QTYPE_MINFO 14
#define QTYPE_MX 15
#define QTYPE_TXT 16
#define QTYPE_URI 256
#define QCLASS_IN 1
#define QCLASS_ANY 255
#define QCLASS_URI 256
//Function to put unaligned 16-bit network values
static void ICACHE_FLASH_ATTR setn16(void *pp, int16_t n) {
char *p=pp;
*p++=(n>>8);
*p++=(n&0xff);
}
//Function to put unaligned 32-bit network values
static void ICACHE_FLASH_ATTR setn32(void *pp, int32_t n) {
char *p=pp;
*p++=(n>>24)&0xff;
*p++=(n>>16)&0xff;
*p++=(n>>8)&0xff;
*p++=(n&0xff);
}
static uint16_t ICACHE_FLASH_ATTR my_ntohs(uint16_t *in) {
char *p=(char*)in;
return ((p[0]<<8)&0xff00)|(p[1]&0xff);
}
//Parses a label into a C-string containing a dotted
//Returns pointer to start of next fields in packet
static char* ICACHE_FLASH_ATTR labelToStr(char *packet, char *labelPtr, int packetSz, char *res, int resMaxLen) {
int i, j, k;
char *endPtr=NULL;
i=0;
do {
if ((*labelPtr&0xC0)==0) {
j=*labelPtr++; //skip past length
//Add separator period if there already is data in res
if (i<resMaxLen && i!=0) res[i++]='.';
//Copy label to res
for (k=0; k<j; k++) {
if ((labelPtr-packet)>packetSz) return NULL;
if (i<resMaxLen) res[i++]=*labelPtr++;
}
} else if ((*labelPtr&0xC0)==0xC0) {
//Compressed label pointer
endPtr=labelPtr+2;
int offset=my_ntohs(((uint16_t *)labelPtr))&0x3FFF;
//Check if offset points to somewhere outside of the packet
if (offset>packetSz) return NULL;
labelPtr=&packet[offset];
}
//check for out-of-bound-ness
if ((labelPtr-packet)>packetSz) return NULL;
} while (*labelPtr!=0);
res[i]=0; //zero-terminate
if (endPtr==NULL) endPtr=labelPtr+1;
return endPtr;
}
//Converts a dotted hostname to the weird label form dns uses.
static char ICACHE_FLASH_ATTR *strToLabel(char *str, char *label, int maxLen) {
char *len=label; //ptr to len byte
char *p=label+1; //ptr to next label byte to be written
while (1) {
if (*str=='.' || *str==0) {
*len=((p-len)-1); //write len of label bit
len=p; //pos of len for next part
p++; //data ptr is one past len
if (*str==0) break; //done
str++;
} else {
*p++=*str++; //copy byte
// if ((p-label)>maxLen) return NULL; //check out of bounds
}
}
*len=0;
return p; //ptr to first free byte in resp
}
//Receive a DNS packet and maybe send a response back
#ifndef FREERTOS
static void ICACHE_FLASH_ATTR captdnsRecv(void* arg, char *pusrdata, unsigned short length) {
struct espconn *conn=(struct espconn *)arg;
#else
static void ICACHE_FLASH_ATTR captdnsRecv(struct sockaddr_in *premote_addr, char *pusrdata, unsigned short length) {
#endif
char buff[DNS_LEN];
char reply[DNS_LEN];
int i;
char *rend=&reply[length];
char *p=pusrdata;
DnsHeader *hdr=(DnsHeader*)p;
DnsHeader *rhdr=(DnsHeader*)&reply[0];
p+=sizeof(DnsHeader);
// httpd_printf("DNS packet: id 0x%X flags 0x%X rcode 0x%X qcnt %d ancnt %d nscount %d arcount %d len %d\n",
// my_ntohs(&hdr->id), hdr->flags, hdr->rcode, my_ntohs(&hdr->qdcount), my_ntohs(&hdr->ancount), my_ntohs(&hdr->nscount), my_ntohs(&hdr->arcount), length);
//Some sanity checks:
if (length>DNS_LEN) return; //Packet is longer than DNS implementation allows
if (length<sizeof(DnsHeader)) return; //Packet is too short
if (hdr->ancount || hdr->nscount || hdr->arcount) return; //this is a reply, don't know what to do with it
if (hdr->flags&FLAG_TC) return; //truncated, can't use this
//Reply is basically the request plus the needed data
memcpy(reply, pusrdata, length);
rhdr->flags|=FLAG_QR;
for (i=0; i<my_ntohs(&hdr->qdcount); i++) {
//Grab the labels in the q string
p=labelToStr(pusrdata, p, length, buff, sizeof(buff));
if (p==NULL) return;
DnsQuestionFooter *qf=(DnsQuestionFooter*)p;
p+=sizeof(DnsQuestionFooter);
httpd_printf("DNS: Q (type 0x%X class 0x%X) for %s\n", my_ntohs(&qf->type), my_ntohs(&qf->class), buff);
if (my_ntohs(&qf->type)==QTYPE_A) {
//They want to know the IPv4 address of something.
//Build the response.
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
if (rend==NULL) return;
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
rend+=sizeof(DnsResourceFooter);
setn16(&rf->type, QTYPE_A);
setn16(&rf->class, QCLASS_IN);
setn32(&rf->ttl, 0);
setn16(&rf->rdlength, 4); //IPv4 addr is 4 bytes;
//Grab the current IP of the softap interface
struct ip_info info;
wifi_get_ip_info(SOFTAP_IF, &info);
*rend++=ip4_addr1(&info.ip);
*rend++=ip4_addr2(&info.ip);
*rend++=ip4_addr3(&info.ip);
*rend++=ip4_addr4(&info.ip);
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
// httpd_printf("Added A rec to resp. Resp len is %d\n", (rend-reply));
} else if (my_ntohs(&qf->type)==QTYPE_NS) {
//Give ns server. Basically can be whatever we want because it'll get resolved to our IP later anyway.
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
rend+=sizeof(DnsResourceFooter);
setn16(&rf->type, QTYPE_NS);
setn16(&rf->class, QCLASS_IN);
setn16(&rf->ttl, 0);
setn16(&rf->rdlength, 4);
*rend++=2;
*rend++='n';
*rend++='s';
*rend++=0;
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
// httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
} else if (my_ntohs(&qf->type)==QTYPE_URI) {
//Give uri to us
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
DnsResourceFooter *rf=(DnsResourceFooter *)rend;
rend+=sizeof(DnsResourceFooter);
DnsUriHdr *uh=(DnsUriHdr *)rend;
rend+=sizeof(DnsUriHdr);
setn16(&rf->type, QTYPE_URI);
setn16(&rf->class, QCLASS_URI);
setn16(&rf->ttl, 0);
setn16(&rf->rdlength, 4+16);
setn16(&uh->prio, 10);
setn16(&uh->weight, 1);
memcpy(rend, "http://esp.nonet", 16);
rend+=16;
setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1);
// httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
}
}
//Send the response
#ifndef FREERTOS
remot_info *remInfo=NULL;
//Send data to port/ip it came from, not to the ip/port we listen on.
if (espconn_get_connection_info(conn, &remInfo, 0)==ESPCONN_OK) {
conn->proto.udp->remote_port=remInfo->remote_port;
memcpy(conn->proto.udp->remote_ip, remInfo->remote_ip, sizeof(remInfo->remote_ip));
}
espconn_sendto(conn, (uint8*)reply, rend-reply);
#else
sendto(sockFd,(uint8*)reply, rend-reply, 0, (struct sockaddr *)premote_addr, sizeof(struct sockaddr_in));
#endif
}
#ifdef FREERTOS
static void captdnsTask(void *pvParameters) {
struct sockaddr_in server_addr;
int32 ret;
struct sockaddr_in from;
socklen_t fromlen;
struct ip_info ipconfig;
char udp_msg[DNS_LEN];
memset(&ipconfig, 0, sizeof(ipconfig));
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(53);
server_addr.sin_len = sizeof(server_addr);
do {
sockFd=socket(AF_INET, SOCK_DGRAM, 0);
if (sockFd==-1) {
httpd_printf("captdns_task failed to create sock!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while (sockFd==-1);
do {
ret=bind(sockFd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret!=0) {
httpd_printf("captdns_task failed to bind sock!\n");
vTaskDelay(1000/portTICK_RATE_MS);
}
} while (ret!=0);
httpd_printf("CaptDNS inited.\n");
while(1) {
memset(&from, 0, sizeof(from));
fromlen=sizeof(struct sockaddr_in);
ret=recvfrom(sockFd, (u8 *)udp_msg, DNS_LEN, 0,(struct sockaddr *)&from,(socklen_t *)&fromlen);
if (ret>0) captdnsRecv(&from,udp_msg,ret);
}
close(sockFd);
vTaskDelete(NULL);
}
void captdnsInit(void) {
#ifdef ESP32
xTaskCreate(captdnsTask, (const char *)"captdns_task", 1200, NULL, 3, NULL);
#else
xTaskCreate(captdnsTask, (const signed char *)"captdns_task", 1200, NULL, 3, NULL);
#endif
}
#else
void ICACHE_FLASH_ATTR captdnsInit(void) {
static struct espconn conn;
static esp_udp udpconn;
conn.type=ESPCONN_UDP;
conn.proto.udp=&udpconn;
conn.proto.udp->local_port = 53;
espconn_regist_recvcb(&conn, captdnsRecv);
espconn_create(&conn);
}
#endif
#endif