强制网络门户

强制网络门户

强制网络门户(captive portal)就是手机在连接上某个wifi的时候,自动弹出一个页面,这个页面通常是要我们输入账号密码才能连接网络。wifi还没有大面积普及的时候,在商场或餐厅提供的wifi常常使用这个功能。现在wifi设备多了,这个功能逐渐被遗忘了,但是仍然有一定的用处,比如手机连上wifi自动后弹出配网页面。

原理

手机连上wifi后,但不能保证能连接到互联网,所以手机连上某个wifi的第一件事就是检查下这个wifi是否有网。不同品牌的手机检查网络方式略有不同,但基本上大同小异。一般是向某个固定的地址发起一个HTTP请求,如果请求到正确的数据,则证明这个wifi可以正常连接互联网,如果请求不到数据,或者请求到了错误的数据,则说明这个wifi不能正常连接互联网。
上述检查网络的过程可分为三种情况:

  • 1.请求到正常的数据(该wifi可直接上网)
  • 2.没有请求到数据(该wifi无法连接网络)
  • 3.请求到错误(非手机期待)的数据(可能需要认证才能连接网络)

如果遇到上述第三种情况,那么手机就会打开相应的页面,就是所谓的自动弹出页面。
HTTP请求的过程,首先通过DNS获取服务器的IP地址,然后再向服务器发起HTTP请求。
以苹果手机为例,连上某个wifi后会访问如下页面
http://captive.apple.com/hotspot-detect.html
访问过程如下,先向DNS服务器发起DNS请求,获得captive.apple.com对应的IP地址,然后再向刚刚获取到的地址发送HTTP请求,如果DNS请求失败,则不会(也不能)发起Http请求。

示例

手机连上ESP8266后,默认的DNS地址就是模块的地址(192.168.4.1),所以我们要在模块上建立一个DNS服务器,响应手机发来的DNS请求,将所有的域名都解析到模块本身的IP地址上,那么手机访问http://captive.apple.com/hotspot-detect.html就相当于访问http://192.168.4.1/hotspot-detect.html,我们的模组就能成功的捕获到手机发来的HTTP请求了。(这种做法通常叫做DNS拦截)。
捕获到HTTP请求后,还要做相应的HTTP响应,才能让手机弹出页面。所以模组还需要建立一个HTTP服务器,相应手机发来的HTTP请求。HTTP底层协议是TCP,默认使用的是80端口,我们只需要建立一个TCP服务器,监听80端口,收到数据后做相应的回复即可。

实现代码

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
static void initialise_wifi(void)
{
tcpip_adapter_init();
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );

wifi_config_t wifi_ap_config = {
.ap = {
.ssid = "Free Wifi",
.ssid_len = strlen("Free Wifi"),
.authmode = WIFI_AUTH_OPEN,
.max_connection = 3,
},
};

ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_AP) );

ESP_LOGI(TAG, "Setting WiFi softAP SSID %s...", wifi_ap_config.ap.ssid);
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_ap_config) );
ESP_ERROR_CHECK( esp_wifi_start() );
}


void app_main()
{
ESP_ERROR_CHECK( nvs_flash_init() );
initialise_wifi(); //初始化wifi

dns_server_start(); //开启DNS服务
web_server_start(); //开启http服务
}

DNS报文解析

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
static void my_dns_server(void *pvParameters)
{
uint8_t data[128];
int len = 0;
struct sockaddr_in client = { 0 };
socklen_t client_len=sizeof(struct sockaddr_in);
uint32_t i = 0;

ESP_LOGI(TAG,"DNS server start ...");
int sock = create_udp_socket(53);

if (sock < 0) {
ESP_LOGE(TAG,"Failed to create IPv4 multicast socket");
}

while(1)
{

len=recvfrom(sock,data,100,0,(struct sockaddr *)&client,&client_len); //阻塞式

if((len < 0) || ( len > 100))
{
ESP_LOGE(TAG,"recvfrom error\n");
continue;
}

//过滤掉一些杂乱的域名
if( strstr((const char *)data+0xc,"taobao")||
strstr((const char *)data+0xc,"qq") ||
strstr((const char *)data+0xc,"sogou") ||
strstr((const char *)data+0xc,"amap") ||
strstr((const char *)data+0xc,"alipay")||
strstr((const char *)data+0xc,"youku") ||
strstr((const char *)data+0xc,"iqiyi") ||
strstr((const char *)data+0xc,"baidu"))
{
continue;
}

data[2] |= 0x80;
data[3] |= 0x80;
data[7] =1;

data[len++] =0xc0;
data[len++] =0x0c;

data[len++] =0x00;
data[len++] =0x01;
data[len++] =0x00;
data[len++] =0x01;

data[len++] =0x00;
data[len++] =0x00;
data[len++] =0x00;
data[len++] =0x0A;

data[len++] =0x00;
data[len++] =0x04;

data[len++] =192;
data[len++] =168;
data[len++] =4;
data[len++] =1;

sendto(sock,data,len,0,(struct sockaddr*)&client,client_len);

vTaskDelay(10);
}

ESP_LOGE(TAG,"DNS server stop ...");
shutdown(sock, 0);
close(sock);
vTaskDelete(NULL);
}

void webserver(void *pvParameters)
{
int sockfd,new_fd;/*socket句柄和建立连接后的句柄*/
struct sockaddr_in my_addr;/*本方地址信息结构体,下面有具体的属性赋值*/
struct sockaddr_in their_addr;/*对方地址信息*/
socklen_t sin_size;

struct timeval tv;//发送接收超时时间
tv.tv_sec = 10;
tv.tv_usec = 0;

sin_size=sizeof(struct sockaddr_in);
sockfd=socket(AF_INET,SOCK_STREAM,0);//建立socket
if(sockfd==-1)
{
ESP_LOGE(TAG, "socket failed:%d",errno);
vTaskDelete(NULL);
return;
}
my_addr.sin_family=AF_INET;/*该属性表示接收本机或其他机器传输*/
my_addr.sin_port=htons(80);/*端口号*/
my_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*IP,括号内容表示本机IP*/
bzero(&(my_addr.sin_zero),8);/*将其他属性置0*/

if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0)//绑定地址结构体和socket
{
ESP_LOGE(TAG,"bind error");
vTaskDelete(NULL);
return;
}

listen(sockfd,8);//开启监听 ,第二个参数是最大监听数
ESP_LOGI(TAG, "webserver start...");
while(1)
{
new_fd=accept(sockfd,(struct sockaddr*)&their_addr,&sin_size);//在这里阻塞知道接收到消息,参数分别是socket句柄,接收到的地址信息以及大小
if(new_fd==-1)
{
ESP_LOGE(TAG,"accept failed");
}
else
{
ESP_LOGI(TAG,"Accept new socket: %d",new_fd);

setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));

int *para_fd = malloc(sizeof(int));
*para_fd = new_fd;
xTaskCreate(&handle_http_request, "socket_task", 1024*3, para_fd, 6, NULL);
}
vTaskDelay(10);
}
vTaskDelete(NULL);
}
-->

请我喝杯咖啡吧~

支付宝
微信