1/* 2 * lws-minimal-http-client-captive-portal 3 * 4 * Written in 2010-2020 by Andy Green <andy@warmcat.com> 5 * 6 * This file is made available under the Creative Commons CC0 1.0 7 * Universal Public Domain Dedication. 8 * 9 * This demonstrates how to use the lws_system captive portal detect integration 10 * 11 * We check for a captive portal by doing a GET from 12 * http://connectivitycheck.android.com/generate_204, if we really are going 13 * out on the Internet he'll return with a 204 response code and we will 14 * understand there's no captive portal. If we get something else, we take it 15 * there is a captive portal. 16 */ 17 18#include <libwebsockets.h> 19#include <string.h> 20#include <signal.h> 21 22static struct lws_context *context; 23static int interrupted, bad = 1, status; 24static lws_state_notify_link_t nl; 25 26/* 27 * this is the user code http handler 28 */ 29 30static int 31callback_http(struct lws *wsi, enum lws_callback_reasons reason, 32 void *user, void *in, size_t len) 33{ 34 switch (reason) { 35 36 /* because we are protocols[0] ... */ 37 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: 38 lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", 39 in ? (char *)in : "(null)"); 40 interrupted = 1; 41 break; 42 43 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: 44 { 45 char buf[128]; 46 47 lws_get_peer_simple(wsi, buf, sizeof(buf)); 48 status = (int)lws_http_client_http_response(wsi); 49 50 lwsl_user("Connected to %s, http response: %d\n", 51 buf, status); 52 } 53 break; 54 55 /* chunks of chunked content, with header removed */ 56 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: 57 lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len); 58 59#if 0 /* enable to dump the html */ 60 { 61 const char *p = in; 62 63 while (len--) 64 if (*p < 0x7f) 65 putchar(*p++); 66 else 67 putchar('.'); 68 } 69#endif 70 return 0; /* don't passthru */ 71 72 /* uninterpreted http content */ 73 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: 74 { 75 char buffer[1024 + LWS_PRE]; 76 char *px = buffer + LWS_PRE; 77 int lenx = sizeof(buffer) - LWS_PRE; 78 79 if (lws_http_client_read(wsi, &px, &lenx) < 0) 80 return -1; 81 } 82 return 0; /* don't passthru */ 83 84 case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: 85 lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); 86 interrupted = 1; 87 bad = status != 200; 88 lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ 89 break; 90 91 case LWS_CALLBACK_CLOSED_CLIENT_HTTP: 92 interrupted = 1; 93 bad = status != 200; 94 lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ 95 break; 96 97 default: 98 break; 99 } 100 101 return lws_callback_http_dummy(wsi, reason, user, in, len); 102} 103 104/* 105 * This is the platform's custom captive portal detection handler 106 */ 107 108static int 109callback_cpd_http(struct lws *wsi, enum lws_callback_reasons reason, 110 void *user, void *in, size_t len) 111{ 112 int resp; 113 114 switch (reason) { 115 116 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: 117 resp = (int)lws_http_client_http_response(wsi); 118 if (!resp) 119 break; 120 lwsl_user("%s: established with resp %d\n", __func__, resp); 121 switch (resp) { 122 123 case HTTP_STATUS_NO_CONTENT: 124 /* 125 * We got the 204 which is used to distinguish the real 126 * endpoint 127 */ 128 lws_system_cpd_set(lws_get_context(wsi), 129 LWS_CPD_INTERNET_OK); 130 return 0; 131 132 /* also case HTTP_STATUS_OK: ... */ 133 default: 134 break; 135 } 136 137 /* fallthru */ 138 139 case LWS_CALLBACK_CLIENT_HTTP_REDIRECT: 140 lws_system_cpd_set(lws_get_context(wsi), LWS_CPD_CAPTIVE_PORTAL); 141 /* don't follow it, just report it */ 142 return 1; 143 144 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: 145 case LWS_CALLBACK_CLOSED_CLIENT_HTTP: 146 /* only the first result counts */ 147 lws_system_cpd_set(lws_get_context(wsi), LWS_CPD_NO_INTERNET); 148 break; 149 150 default: 151 break; 152 } 153 154 return lws_callback_http_dummy(wsi, reason, user, in, len); 155} 156 157static const struct lws_protocols protocols[] = { 158 { 159 "http", 160 callback_http, 161 0, 0, 0, NULL, 0 162 }, { 163 "lws-cpd-http", 164 callback_cpd_http, 165 0, 0, 0, NULL, 0 166 }, 167 LWS_PROTOCOL_LIST_TERM 168}; 169 170void sigint_handler(int sig) 171{ 172 interrupted = 1; 173} 174 175/* 176 * This triggers our platform implementation of captive portal detection, the 177 * actual test can be whatever you need. 178 * 179 * In this example, we detect it using Android's 180 * 181 * http://connectivitycheck.android.com/generate_204 182 * 183 * and seeing if we get an http 204 back. 184 */ 185 186static int 187captive_portal_detect_request(struct lws_context *context) 188{ 189 struct lws_client_connect_info i; 190 191 memset(&i, 0, sizeof i); 192 i.context = context; 193 i.port = 80; 194 i.address = "connectivitycheck.android.com"; 195 i.path = "/generate_204"; 196 i.host = i.address; 197 i.origin = i.address; 198 i.method = "GET"; 199 200 i.protocol = "lws-cpd-http"; 201 202 return !lws_client_connect_via_info(&i); 203} 204 205 206lws_system_ops_t ops = { 207 .captive_portal_detect_request = captive_portal_detect_request 208}; 209 210 211static int 212app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, 213 int current, int target) 214{ 215 struct lws_context *cx = lws_system_context_from_system_mgr(mgr); 216 217 switch (target) { 218 case LWS_SYSTATE_CPD_PRE_TIME: 219 if (lws_system_cpd_state_get(cx)) 220 return 0; /* allow it */ 221 222 lwsl_info("%s: LWS_SYSTATE_CPD_PRE_TIME\n", __func__); 223 lws_system_cpd_start(cx); 224 /* we'll move the state on when we get a result */ 225 return 1; 226 227 case LWS_SYSTATE_OPERATIONAL: 228 if (current == LWS_SYSTATE_OPERATIONAL) { 229 struct lws_client_connect_info i; 230 231 lwsl_user("%s: OPERATIONAL, cpd %d\n", __func__, 232 lws_system_cpd_state_get(cx)); 233 234 /* 235 * When we reach the OPERATIONAL lws_system state, we 236 * can do our main job knowing we have DHCP, ntpclient, 237 * captive portal testing done. 238 */ 239 240 if (lws_system_cpd_state_get(cx) != LWS_CPD_INTERNET_OK) { 241 lwsl_warn("%s: There's no internet...\n", __func__); 242 interrupted = 1; 243 break; 244 } 245 246 memset(&i, 0, sizeof i); 247 i.context = context; 248 i.ssl_connection = LCCSCF_USE_SSL; 249 i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR | 250 LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; 251 i.port = 443; 252 i.address = "warmcat.com"; 253 i.path = "/"; 254 i.host = i.address; 255 i.origin = i.address; 256 i.method = "GET"; 257 258 i.protocol = protocols[0].name; 259 260 lws_client_connect_via_info(&i); 261 break; 262 } 263 default: 264 break; 265 } 266 267 return 0; 268} 269 270static lws_state_notify_link_t * const app_notifier_list[] = { 271 &nl, NULL 272}; 273 274/* 275 * We made this into a different thread to model it being run from completely 276 * different codebase that's all linked together 277 */ 278 279 280int main(int argc, const char **argv) 281{ 282 int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; 283 struct lws_context_creation_info info; 284 const char *p; 285 286 signal(SIGINT, sigint_handler); 287 288 if ((p = lws_cmdline_option(argc, argv, "-d"))) 289 logs = atoi(p); 290 291 lws_set_log_level(logs, NULL); 292 lwsl_user("LWS minimal http client captive portal detect\n"); 293 294 memset(&info, 0, sizeof info); 295 info.port = CONTEXT_PORT_NO_LISTEN; 296 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; 297 info.system_ops = &ops; 298 info.protocols = protocols; 299 300 /* integrate us with lws system state management when context created */ 301 302 nl.name = "app"; 303 nl.notify_cb = app_system_state_nf; 304 info.register_notifier_list = app_notifier_list; 305 306 context = lws_create_context(&info); 307 if (!context) { 308 lwsl_err("lws init failed\n"); 309 return 1; 310 } 311 312 while (!interrupted) 313 if (lws_service(context, 0)) 314 interrupted = 1; 315 316 lws_context_destroy(context); 317 318 lwsl_user("%s: finished %s\n", __func__, bad ? "FAIL": "OK"); 319 320 return bad; 321} 322