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