Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
{"schema":"libjg2-1", "vpath":"/git/", "avatar":"/git/avatar/", "alang":"", "gen_ut":1752654913, "reponame":"libwebsockets", "desc":"libwebsockets lightweight C networking library", "owner": { "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" },"url":"https://libwebsockets.org/repo/libwebsockets", "f":3, "items": [ {"schema":"libjg2-1", "cid":"bfa5d599f0180a89530885735078d20b", "commit": {"type":"commit", "time": 1583930641, "time_ofs": 0, "oid_tree": { "oid": "bf05e562d3bf73c9b6b08a198a6ce720396d6a47", "alias": []}, "oid":{ "oid": "a60cb84c9ea1c7013c151e3b8cdadbe0e5bd884b", "alias": []}, "msg": "captive portal", "sig_commit": { "git_time": { "time": 1583930641, "offset": 0 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" }, "sig_author": { "git_time": { "time": 1583930641, "offset": 0 }, "name": "Andy Green", "email": "andy@warmcat.com", "md5": "c50933ca2aa61e0fe2c43d46bb6b59cb" }}, "body": "captive portal\n\nImplement Captive Portal detection support in lws, with the actual\ndetection happening in platform code hooked up by lws_system_ops_t.\n\nAdd an implementation using Secure Streams as well, if the policy\ndefines captive_portal_detect streamtype, a SS using that streamtype\nis used to probe if it's behind a captive portal." , "diff": "diff --git a/CMakeLists.txt b/CMakeLists.txt\nindex 2cab9b3..1dd4cbc 100644\n--- a/CMakeLists.txt\n+++ b/CMakeLists.txt\n@@ -1178,6 +1178,7 @@ if (LWS_WITH_NETWORK)\n \t\t\tlib/secure-streams/secure-streams.c\n \t\t\tlib/secure-streams/policy.c\n \t\t\tlib/secure-streams/system/fetch-policy/fetch-policy.c\n+\t\t\tlib/secure-streams/system/captive-portal-detect/captive-portal-detect.c\n \t\t)\n \t\tif (LWS_ROLE_H1)\n \t\t\tlist(APPEND SOURCES\n@@ -1218,6 +1219,7 @@ if (LWS_WITH_NETWORK)\n \t\t\t\tlib/secure-streams/system/auth-api.amazon.com/auth.c\n \t\t\t)\n \t\tendif()\n+\n \tendif()\n \n \tif (LWS_WITH_STATS)\ndiff --git a/READMEs/README.captive-portal-detection.md b/READMEs/README.captive-portal-detection.md\nnew file mode 100644\nindex 0000000..ceab7b4\n--- /dev/null\n+++ b/READMEs/README.captive-portal-detection.md\n@@ -0,0 +1,88 @@\n+# Captive Portal Detection\n+\n+## Background\n+\n+Wifi devices may face some interception of their connection to the\n+internet, it's very common for, eg, coffee shop wifi to present some\n+kind of login or other clickthrough before access to the Internet is\n+granted. Devices may need to understand that they are in this\n+situation, and there are several different techniques for trying to\n+gague it.\n+\n+Sequence-wise the device has been granted a DHCP lease and has been\n+configured with DNS, but the DNS may be wrongly resolving everything\n+to an address on the LAN or a portal on the net.\n+\n+Whether there is a captive portal active should be a sticky state for a given\n+connection if there is not going to be any attempt to login or pass the landing\n+page, it only needs checking for after DHCP acquisition then. If there will be\n+an attempt to satisfy the landing page, the test should be repeated after the\n+attempt.\n+\n+## Detection schemes\n+\n+The most popular detection scheme by numbers is Android's method,\n+which is to make an HTTP client GET to `http://connectivitycheck.android.com/generate_204`\n+and see if a 204 is coming back... if intercepted, typically there'll be a\n+3xx redirect to the portal, perhaps on https. Or, it may reply on http with\n+a 200 and show the portal directly... either way it won't deliver a 204\n+like the real remote server does.\n+\n+Variations include expecting a 200 but with specific http body content, and\n+doing a DNS lookup for a static IP that the device knows; if it's resolved to\n+something else, it knows there's monkey business implying a captive portal.\n+\n+Other schemes involve https connections going out and detecting that the cert\n+of the server it's actually talking to doesn't check out, although this is\n+potentially ambiguous.\n+\n+Yet more methods are possible outside of tcp or http.\n+\n+## lws captive portal detect support\n+\n+lws provides a generic api to start captive portal detection...\n+\n+```\n+LWS_EXTERN LWS_VISIBLE int\n+lws_system_cpd_start(struct lws_context *context);\n+```\n+\n+and two states in `lws_system` states to trigger it from, either\n+`LWS_SYSTATE_CPD_PRE_TIME` which happens after DHCP acquisition but before\n+ntpclient and is suitable for non https-based scheme where the time doesn't\n+need to be known, or the alternative `LWS_SYSTATE_CPD_POST_TIME` state which\n+happens after ntpclient has completed and we know the time.\n+\n+The actual platform implementation is set using `lws_system_ops_t` function\n+pointer `captive_portal_detect_request`, ie\n+\n+```\n+\tint (*captive_portal_detect_request)(struct lws_context *context);\n+\t/**\u003c Check if we can go out on the internet cleanly, or if we are being\n+\t * redirected or intercepted by a captive portal.\n+\t * Start the check that proceeds asynchronously, and report the results\n+\t * by calling lws_captive_portal_detect_result() api\n+\t */\n+```\n+\n+User platform code can provide this to implement whatever scheme they want, when\n+it has arrived at a result, it can call the lws api `lws_system_cpd_result()` to\n+inform lws. If there isn't any captive portal, this will also try to advance the\n+system state towards OPERATIONAL.\n+\n+```\n+/**\n+ * lws_system_cpd_result() - report the result of the captive portal detection\n+ *\n+ * \u005cparam context: the lws_context\n+ * \u005cparam result: one of the LWS_CPD_ constants representing captive portal state\n+ * \u005cparam redirect_url: NULL, or the url we were redirected to if result is\n+ * LWS_CPD_HTTP_REDIRECT\n+ *\n+ * Sets the context's captive portal detection state to result. User captive\n+ * portal detection code would call this once it had a result from its test.\n+ */\n+LWS_EXTERN LWS_VISIBLE int\n+lws_system_cpd_result(struct lws_context *context, int result, const char *redirect_url);\n+```\n+\ndiff --git a/include/libwebsockets/lws-callbacks.h b/include/libwebsockets/lws-callbacks.h\nindex 727fdca..50cb738 100644\n--- a/include/libwebsockets/lws-callbacks.h\n+++ b/include/libwebsockets/lws-callbacks.h\n@@ -390,6 +390,9 @@ enum lws_callback_reasons {\n \t * lws know by calling lws_client_http_body_pending(wsi, 0)\n \t */\n \n+\tLWS_CALLBACK_CLIENT_HTTP_REDIRECT\t\t\t\u003d 104,\n+\t/**\u003c we're handling a 3xx redirect... return nonzero to hang up */\n+\n \tLWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL\t\t\t\u003d 85,\n \tLWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL\t\t\t\u003d 76,\n \ndiff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h\nindex 7cf3dce..f64fd80 100644\n--- a/include/libwebsockets/lws-secure-streams-policy.h\n+++ b/include/libwebsockets/lws-secure-streams-policy.h\n@@ -206,6 +206,9 @@ typedef struct lws_ss_policy {\n \t\t\t\t\t/* false \u003d TEXT, true \u003d BINARY */\n \t\t\t\t} ws;\n \t\t\t} u;\n+\n+\t\t\tuint16_t\tresp_expect;\n+\t\t\tuint8_t\t\tfail_redirect:1;\n \t\t} http;\n \n \t\tstruct {\ndiff --git a/include/libwebsockets/lws-system.h b/include/libwebsockets/lws-system.h\nindex 61ddf3b..1f43706 100644\n--- a/include/libwebsockets/lws-system.h\n+++ b/include/libwebsockets/lws-system.h\n@@ -98,9 +98,24 @@ typedef enum { /* keep system_state_names[] in sync in context.c */\n \t\t\t\t\t * can operate normally */\n \tLWS_SYSTATE_IFACE_COLDPLUG,\t /* existing net ifaces iterated */\n \tLWS_SYSTATE_DHCP,\t\t /* at least one net iface configured */\n+\tLWS_SYSTATE_CPD_PRE_TIME,\t /* Captive portal detect without valid\n+\t\t\t\t\t * time, good for non-https tests... if\n+\t\t\t\t\t * you care about it, implement and\n+\t\t\t\t\t * call lws_system_ops_t\n+\t\t\t\t\t * .captive_portal_detect_request()\n+\t\t\t\t\t * and move the state forward according\n+\t\t\t\t\t * to the result. */\n \tLWS_SYSTATE_TIME_VALID,\t\t /* ntpclient ran, or hw time valid...\n \t\t\t\t\t * tls cannot work until we reach here\n \t\t\t\t\t */\n+\tLWS_SYSTATE_CPD_POST_TIME,\t /* Captive portal detect after time was\n+\t\t\t\t\t * time, good for https tests... if\n+\t\t\t\t\t * you care about it, implement and\n+\t\t\t\t\t * call lws_system_ops_t\n+\t\t\t\t\t * .captive_portal_detect_request()\n+\t\t\t\t\t * and move the state forward according\n+\t\t\t\t\t * to the result. */\n+\n \tLWS_SYSTATE_POLICY_VALID,\t /* user code knows how to operate... */\n \tLWS_SYSTATE_REGISTERED,\t\t /* device has an identity... */\n \tLWS_SYSTATE_AUTH1,\t\t /* identity used for main auth token */\n@@ -114,6 +129,16 @@ typedef enum { /* keep system_state_names[] in sync in context.c */\n \t\t\t\t\t * LWS_SYSTATE_POLICY_VALID */\n } lws_system_states_t;\n \n+/* Captive Portal Detect -related */\n+\n+typedef enum {\n+\tLWS_CPD_UNKNOWN \u003d 0,\t/* test didn't happen ince last DHCP acq yet */\n+\tLWS_CPD_INTERNET_OK,\t/* no captive portal: our CPD test passed OK,\n+\t\t\t\t * we can go out on the internet */\n+\tLWS_CPD_CAPTIVE_PORTAL,\t/* we inferred we're behind a captive portal */\n+\tLWS_CPD_NO_INTERNET,\t/* we couldn't touch anything */\n+} lws_cpd_result_t;\n+\n \n typedef void (*lws_attach_cb_t)(struct lws_context *context, int tsi, void *opaque);\n struct lws_attach_item;\n@@ -138,6 +163,12 @@ typedef struct lws_system_ops {\n \t * __lws_system_attach() is provided to do the actual work inside the\n \t * system-specific locking.\n \t */\n+\tint (*captive_portal_detect_request)(struct lws_context *context);\n+\t/**\u003c Check if we can go out on the internet cleanly, or if we are being\n+\t * redirected or intercepted by a captive portal.\n+\t * Start the check that proceeds asynchronously, and report the results\n+\t * by calling lws_captive_portal_detect_result() api\n+\t */\n } lws_system_ops_t;\n \n /**\n@@ -231,7 +262,7 @@ typedef int (*dhcpc_cb_t)(void *opaque, int af, uint8_t *ip, int ip_len);\n * Register a network interface as being managed by DHCP. lws will proceed to\n * try to acquire an IP. Requires LWS_WITH_SYS_DHCP_CLIENT at cmake.\n */\n-int\n+LWS_EXTERN LWS_VISIBLE int\n lws_dhcpc_request(struct lws_context *c, const char *i, int af, dhcpc_cb_t cb,\n \t\tvoid *opaque);\n \n@@ -243,7 +274,7 @@ lws_dhcpc_request(struct lws_context *c, const char *i, int af, dhcpc_cb_t cb,\n *\n * Remove handling of the network interface from dhcp.\n */\n-int\n+LWS_EXTERN LWS_VISIBLE int\n lws_dhcpc_remove(struct lws_context *context, const char *iface);\n \n /**\n@@ -255,5 +286,42 @@ lws_dhcpc_remove(struct lws_context *context, const char *iface);\n * Returns 1 if any network interface managed by dhcpc has reached the BOUND\n * state (has acquired an IP, gateway and DNS server), otherwise 0.\n */\n-int\n+LWS_EXTERN LWS_VISIBLE int\n lws_dhcpc_status(struct lws_context *context, lws_sockaddr46 *sa46);\n+\n+/**\n+ * lws_system_cpd_start() - helper to initiate captive portal detection\n+ *\n+ * \u005cparam context: the lws_context\n+ *\n+ * Resets the context's captive portal state to LWS_CPD_UNKNOWN and calls the\n+ * lws_system_ops_t captive_portal_detect_request() implementation to begin\n+ * testing the captive portal state.\n+ */\n+LWS_EXTERN LWS_VISIBLE int\n+lws_system_cpd_start(struct lws_context *context);\n+\n+\n+/**\n+ * lws_system_cpd_set() - report the result of the captive portal detection\n+ *\n+ * \u005cparam context: the lws_context\n+ * \u005cparam result: one of the LWS_CPD_ constants representing captive portal state\n+ *\n+ * Sets the context's captive portal detection state to result. User captive\n+ * portal detection code would call this once it had a result from its test.\n+ */\n+LWS_EXTERN LWS_VISIBLE void\n+lws_system_cpd_set(struct lws_context *context, lws_cpd_result_t result);\n+\n+\n+/**\n+ * lws_system_cpd_state_get() - returns the last tested captive portal state\n+ *\n+ * \u005cparam context: the lws_context\n+ *\n+ * Returns one of the LWS_CPD_ constants indicating the system's understanding\n+ * of the current captive portal situation.\n+ */\n+LWS_EXTERN LWS_VISIBLE lws_cpd_result_t\n+lws_system_cpd_state_get(struct lws_context *context);\ndiff --git a/lib/core-net/state.c b/lib/core-net/state.c\nindex fd92429..5642464 100644\n--- a/lib/core-net/state.c\n+++ b/lib/core-net/state.c\n@@ -119,6 +119,9 @@ lws_state_transition_steps(lws_state_manager_t *mgr, int target)\n \tchar temp8[8];\n #endif\n \n+\tif (mgr-\u003estate \u003e target)\n+\t\treturn 0;\n+\n \twhile (!n \u0026\u0026 mgr-\u003estate !\u003d target)\n \t\tn \u003d _lws_state_transition(mgr, mgr-\u003estate + 1);\n \ndiff --git a/lib/core/context.c b/lib/core/context.c\nindex 66eb625..ced00eb 100644\n--- a/lib/core/context.c\n+++ b/lib/core/context.c\n@@ -82,7 +82,9 @@ static const char * system_state_names[] \u003d {\n \t\u0022INITIALIZED\u0022,\n \t\u0022IFACE_COLDPLUG\u0022,\n \t\u0022DHCP\u0022,\n+\t\u0022CPD_PRE_TIME\u0022,\n \t\u0022TIME_VALID\u0022,\n+\t\u0022CPD_POST_TIME\u0022,\n \t\u0022POLICY_VALID\u0022,\n \t\u0022REGISTERED\u0022,\n \t\u0022AUTH1\u0022,\n@@ -146,6 +148,20 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr,\n \n #if defined(LWS_WITH_SECURE_STREAMS)\n \t/*\n+\t * See if we should do the SS Captive Portal Detection\n+\t */\n+\tif (target \u003d\u003d LWS_SYSTATE_CPD_PRE_TIME) {\n+\t\tif (lws_system_cpd_state_get(context))\n+\t\t\treturn 0; /* allow it */\n+\n+\t\tlwsl_info(\u0022%s: LWS_SYSTATE_CPD_PRE_TIME\u005cn\u0022, __func__);\n+\t\tif (!lws_system_cpd_start(context))\n+\t\t\treturn 1;\n+\n+\t\t/* it failed, eg, no streamtype for it in the policy */\n+\t}\n+\n+\t/*\n \t * Skip this if we are running something without the policy for it\n \t */\n \tif (target \u003d\u003d LWS_SYSTATE_POLICY_VALID \u0026\u0026\n@@ -896,10 +912,56 @@ fail_event_libs:\n \treturn NULL;\n }\n \n+#if defined(LWS_WITH_NETWORK)\n+int\n+lws_system_cpd_start(struct lws_context *cx)\n+{\n+\tcx-\u003ecaptive_portal_detect \u003d LWS_CPD_UNKNOWN;\n+\n+\t/* if there's a platform implementation, use it */\n+\n+\tif (lws_system_get_ops(cx) \u0026\u0026\n+\t lws_system_get_ops(cx)-\u003ecaptive_portal_detect_request)\n+\t\treturn lws_system_get_ops(cx)-\u003ecaptive_portal_detect_request(cx);\n+\n+#if defined(LWS_WITH_SECURE_STREAMS)\n+\t/*\n+\t * Otherwise try to use SS \u0022captive_portal_detect\u0022 if that's enabled\n+\t */\n+\treturn lws_ss_sys_cpd(cx);\n+#else\n+\treturn 0;\n+#endif\n+}\n+\n+static const char *cname[] \u003d { \u0022?\u0022, \u0022OK\u0022, \u0022Captive\u0022, \u0022No internet\u0022 };\n+\n+void\n+lws_system_cpd_set(struct lws_context *cx, lws_cpd_result_t result)\n+{\n+\tif (cx-\u003ecaptive_portal_detect !\u003d LWS_CPD_UNKNOWN)\n+\t\treturn;\n+\n+\tlwsl_notice(\u0022%s: setting CPD result %s\u005cn\u0022, __func__, cname[result]);\n+\n+\tcx-\u003ecaptive_portal_detect \u003d (uint8_t)result;\n+\n+\t/* if nothing is there to intercept anything, go all the way */\n+\tlws_state_transition_steps(\u0026cx-\u003emgr_system, LWS_SYSTATE_OPERATIONAL);\n+}\n+\n+lws_cpd_result_t\n+lws_system_cpd_state_get(struct lws_context *cx)\n+{\n+\treturn (lws_cpd_result_t)cx-\u003ecaptive_portal_detect;\n+}\n+\n+#endif\n+\n int\n-lws_context_is_deprecated(struct lws_context *context)\n+lws_context_is_deprecated(struct lws_context *cx)\n {\n-\treturn context-\u003edeprecated;\n+\treturn cx-\u003edeprecated;\n }\n \n /*\ndiff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h\nindex c508e91..7738045 100644\n--- a/lib/core/private-lib-core.h\n+++ b/lib/core/private-lib-core.h\n@@ -523,6 +523,8 @@ struct lws_context {\n \tuint8_t max_fi;\n \tuint8_t udp_loss_sim_tx_pc;\n \tuint8_t udp_loss_sim_rx_pc;\n+\tuint8_t captive_portal_detect;\n+\tuint8_t captive_portal_detect_type;\n \n #if defined(LWS_WITH_STATS)\n \tuint8_t updated;\ndiff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c\nindex 76b788f..009a918 100644\n--- a/lib/roles/http/client/client-http.c\n+++ b/lib/roles/http/client/client-http.c\n@@ -678,6 +678,15 @@ lws_client_interpret_server_handshake(struct lws *wsi)\n \t\t\tgoto bail3;\n \t\t}\n \n+\t\t/* let's let the user code know, if he cares */\n+\n+\t\tif (wsi-\u003eprotocol-\u003ecallback(wsi,\n+\t\t\t\t\t LWS_CALLBACK_CLIENT_HTTP_REDIRECT,\n+\t\t\t\t\t wsi-\u003euser_space, p, n)) {\n+\t\t\tcce \u003d \u0022HS: user code rejected redirect\u0022;\n+\t\t\tgoto bail3;\n+\t\t}\n+\n \t\t/*\n \t\t * Some redirect codes imply we have to change the method\n \t\t * used for the subsequent transaction, commonly POST -\u003e\ndiff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md\nindex 8f815e0..59ccf31 100644\n--- a/lib/secure-streams/README.md\n+++ b/lib/secure-streams/README.md\n@@ -177,6 +177,23 @@ to validate the remote server cert.\n HTTP method to use with http-related protocols, like GET or POST.\n Not required for ws.\n \n+### `http_expect`\n+\n+Optionally indicates that success for HTTP transactions using this\n+streamtype is different than the default 200 - 299.\n+\n+Eg, you may choose to set this to 204 for Captive Portal Detect usage\n+if that's what you expect the server to reply with to indicate\n+success. In that case, anything other than 204 will be treated as a\n+connection failure.\n+\n+### `http_fail_redirect`\n+\n+Set to `true` if you want to fail the connection on meeting an\n+http redirect. This is needed to, eg, detect Captive Portals\n+correctly. Normally, if on https, you would want the default behaviour\n+of following the redirect.\n+\n ### `http_url`\n \n Url path to use with http-related protocols\n@@ -352,6 +369,26 @@ The secure-streams-proxy minimal example shows how this is done and\n fetches its real policy from warmcat.com at startup using the built-in\n one.\n \n+## Captive Portal Detection\n+\n+If the policy contains a streamtype `captive_portal_detect` then the\n+type of transaction described there is automatically performed after\n+acquiring a DHCP address to try to determine the captive portal\n+situation.\n+\n+```\n+\t\t\u0022captive_portal_detect\u0022: {\n+ \u0022endpoint\u0022: \u0022connectivitycheck.android.com\u0022,\n+ \u0022port\u0022: 80,\n+ \u0022protocol\u0022: \u0022h1\u0022,\n+ \u0022http_method\u0022: \u0022GET\u0022,\n+ \u0022http_url\u0022: \u0022generate_204\u0022,\n+ \u0022opportunistic\u0022: true,\n+ \u0022http_expect\u0022: 204,\n+\t\t\t\u0022http_fail_redirect\u0022: true\n+ }\n+```\n+\n ## Stream serialization and proxying\n \n By default Secure Streams expects to make the outgoing connection described in\ndiff --git a/lib/secure-streams/policy.c b/lib/secure-streams/policy.c\nindex ec59e7f..d35c003 100644\n--- a/lib/secure-streams/policy.c\n+++ b/lib/secure-streams/policy.c\n@@ -79,6 +79,8 @@ static const char * const lejp_tokens_policy[] \u003d {\n \t\u0022s[].*.http_multipart_filename\u0022,\n \t\u0022s[].*.http_mime_content_type\u0022,\n \t\u0022s[].*.http_www_form_urlencoded\u0022,\n+\t\u0022s[].*.http_expect\u0022,\n+\t\u0022s[].*.http_fail_redirect\u0022,\n \t\u0022s[].*.ws_subprotocol\u0022,\n \t\u0022s[].*.ws_binary\u0022,\n \t\u0022s[].*.local_sink\u0022,\n@@ -142,6 +144,8 @@ typedef enum {\n \tLSSPPT_HTTP_MULTIPART_FILENAME,\n \tLSSPPT_HTTP_MULTIPART_CONTENT_TYPE,\n \tLSSPPT_HTTP_WWW_FORM_URLENCODED,\n+\tLSSPPT_HTTP_EXPECT,\n+\tLSSPPT_HTTP_FAIL_REDIRECT,\n \tLSSPPT_WS_SUBPROTOCOL,\n \tLSSPPT_WS_BINARY,\n \tLSSPPT_LOCAL_SINK,\n@@ -524,6 +528,10 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)\n \t\ta-\u003ecurr[LTY_POLICY].p-\u003eclient_cert \u003d atoi(ctx-\u003ebuf) + 1;\n \t\tbreak;\n \n+\tcase LSSPPT_HTTP_EXPECT:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eu.http.resp_expect \u003d atoi(ctx-\u003ebuf);\n+\t\tbreak;\n+\n \tcase LSSPPT_OPPORTUNISTIC:\n \t\tif (reason \u003d\u003d LEJPCB_VAL_TRUE)\n \t\t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_OPPORTUNISTIC;\n@@ -644,6 +652,12 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)\n \t\ta-\u003ecurr[LTY_POLICY].p-\u003eflags |\u003d LWSSSPOLF_HTTP_MULTIPART;\n \t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.multipart_content_type;\n \t\tgoto string2;\n+\n+\tcase LSSPPT_HTTP_FAIL_REDIRECT:\n+\t\ta-\u003ecurr[LTY_POLICY].p-\u003eu.http.fail_redirect \u003d\n+\t\t\t\t\t\treason \u003d\u003d LEJPCB_VAL_TRUE;\n+\t\tbreak;\n+\n \tcase LSSPPT_WS_SUBPROTOCOL:\n \t\tpp \u003d (char **)\u0026a-\u003ecurr[LTY_POLICY].p-\u003eu.http.u.ws.subprotocol;\n \t\tgoto string2;\ndiff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h\nindex b22211a..fc8b88e 100644\n--- a/lib/secure-streams/private-lib-secure-streams.h\n+++ b/lib/secure-streams/private-lib-secure-streams.h\n@@ -318,6 +318,9 @@ int\n lws_ss_exp_cb_metadata(void *priv, const char *name, char *out, size_t *pos,\n \t\t\tsize_t olen, size_t *exp_ofs);\n \n+int\n+lws_ss_sys_cpd(struct lws_context *cx);\n+\n typedef int (* const secstream_protocol_connect_munge_t)(lws_ss_handle_t *h,\n \t\tchar *buf, size_t len, struct lws_client_connect_info *i,\n \t\tunion lws_ss_contemp *ct);\ndiff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c\nindex c9c6185..1de0868 100644\n--- a/lib/secure-streams/protocols/ss-h1.c\n+++ b/lib/secure-streams/protocols/ss-h1.c\n@@ -167,7 +167,6 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \n \tswitch (reason) {\n \n-\t/* because we are protocols[0] ... */\n \tcase LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n \t\tassert(h);\n \t\tassert(h-\u003epolicy);\n@@ -178,6 +177,13 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t\tlws_ss_backoff(h);\n \t\tbreak;\n \n+\tcase LWS_CALLBACK_CLIENT_HTTP_REDIRECT:\n+\t\tif (h-\u003epolicy-\u003eu.http.fail_redirect)\n+\t\t\tlws_system_cpd_set(lws_get_context(wsi),\n+\t\t\t\t\t LWS_CPD_CAPTIVE_PORTAL);\n+\t\t/* don't follow it */\n+\t\treturn 1;\n+\n \tcase LWS_CALLBACK_CLOSED_CLIENT_HTTP:\n \t\tif (!h)\n \t\t\tbreak;\n@@ -201,7 +207,12 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,\n \t//\tif (!status)\n \t\t\t/* it's just telling use we connected / joined the nwsi */\n \t//\t\tbreak;\n-\t\th-\u003eu.http.good_respcode \u003d (status \u003e\u003d 200 \u0026\u0026 status \u003c 300);\n+\n+\t\tif (h-\u003epolicy-\u003eu.http.resp_expect)\n+\t\t\th-\u003eu.http.good_respcode \u003d\n+\t\t\t\t\tstatus \u003d\u003d h-\u003epolicy-\u003eu.http.resp_expect;\n+\t\telse\n+\t\t\th-\u003eu.http.good_respcode \u003d (status \u003e\u003d 200 \u0026\u0026 status \u003c 300);\n \t\t// lwsl_err(\u0022%s: good resp %d %d\u005cn\u0022, __func__, status, h-\u003eu.http.good_respcode);\n \n \t\tif (h-\u003eu.http.good_respcode)\ndiff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c\nindex 652f6c9..b5eb403 100644\n--- a/lib/secure-streams/secure-streams.c\n+++ b/lib/secure-streams/secure-streams.c\n@@ -302,8 +302,8 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,\n \n \tpol \u003d lws_ss_policy_lookup(context, ssi-\u003estreamtype);\n \tif (!pol) {\n-\t\tlwsl_err(\u0022%s: unknown stream type %s\u005cn\u0022, __func__,\n-\t\t\t ssi-\u003estreamtype);\n+\t\tlwsl_notice(\u0022%s: unknown stream type %s\u005cn\u0022, __func__,\n+\t\t\t ssi-\u003estreamtype);\n \t\treturn 1;\n \t}\n \ndiff --git a/lib/secure-streams/system/captive-portal-detect/captive-portal-detect.c b/lib/secure-streams/system/captive-portal-detect/captive-portal-detect.c\nnew file mode 100644\nindex 0000000..82b7494\n--- /dev/null\n+++ b/lib/secure-streams/system/captive-portal-detect/captive-portal-detect.c\n@@ -0,0 +1,111 @@\n+/*\n+ * Captive portal detect for Secure Streams\n+ *\n+ * libwebsockets - small server side websockets and web server implementation\n+ *\n+ * Copyright (C) 2019 - 2020 Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \u0022Software\u0022), to\n+ * deal in the Software without restriction, including without limitation the\n+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n+ * sell copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \u0022AS IS\u0022, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n+ * IN THE SOFTWARE.\n+ */\n+\n+#include \u003cprivate-lib-core.h\u003e\n+\n+typedef struct ss_cpd {\n+\tstruct lws_ss_handle \t*ss;\n+\tvoid\t\t\t*opaque_data;\n+\t/* ... application specific state ... */\n+\n+\tlws_sorted_usec_list_t\tsul;\n+\n+\tuint8_t\t\t\tpartway;\n+} ss_cpd_t;\n+\n+/* secure streams payload interface */\n+\n+static int\n+ss_cpd_rx(void *userobj, const uint8_t *buf, size_t len, int flags)\n+{\n+\treturn 0;\n+}\n+\n+static int\n+ss_cpd_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,\n+\t\t size_t *len, int *flags)\n+{\n+\treturn 1;\n+}\n+\n+static int\n+ss_cpd_state(void *userobj, void *sh, lws_ss_constate_t state,\n+\t lws_ss_tx_ordinal_t ack)\n+{\n+\tss_cpd_t *m \u003d (ss_cpd_t *)userobj;\n+\tstruct lws_context *cx \u003d (struct lws_context *)m-\u003eopaque_data;\n+\n+\tlwsl_info(\u0022%s: %s, ord 0x%x\u005cn\u0022, __func__, lws_ss_state_name(state),\n+\t\t (unsigned int)ack);\n+\n+\tswitch (state) {\n+\tcase LWSSSCS_CREATING:\n+\t\tlws_ss_request_tx(m-\u003ess);\n+\t\tbreak;\n+\tcase LWSSSCS_QOS_ACK_REMOTE:\n+\t\tlws_system_cpd_set(cx, LWS_CPD_INTERNET_OK);\n+\t\tbreak;\n+\n+\tcase LWSSSCS_ALL_RETRIES_FAILED:\n+\tcase LWSSSCS_DISCONNECTED:\n+\t\t/*\n+\t\t * First result reported sticks... if nothing else, this will\n+\t\t * cover the situation we didn't connect to anything\n+\t\t */\n+\t\tlws_system_cpd_set(cx, LWS_CPD_NO_INTERNET);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+lws_ss_sys_cpd(struct lws_context *cx)\n+{\n+\tlws_ss_info_t ssi;\n+\n+\t/* We're making an outgoing secure stream ourselves */\n+\n+\tmemset(\u0026ssi, 0, sizeof(ssi));\n+\tssi.handle_offset\t \u003d offsetof(ss_cpd_t, ss);\n+\tssi.opaque_user_data_offset \u003d offsetof(ss_cpd_t, opaque_data);\n+\tssi.rx\t\t\t \u003d ss_cpd_rx;\n+\tssi.tx\t\t\t \u003d ss_cpd_tx;\n+\tssi.state\t\t \u003d ss_cpd_state;\n+\tssi.user_alloc\t\t \u003d sizeof(ss_cpd_t);\n+\tssi.streamtype\t\t \u003d \u0022captive_portal_detect\u0022;\n+\n+\tif (lws_ss_create(cx, 0, \u0026ssi, cx, NULL, NULL, NULL)) {\n+\t\tlwsl_info(\u0022%s: Create stream failed (policy?)\u005cn\u0022, __func__);\n+\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/minimal-examples/http-client/minimal-http-client-captive-portal/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client-captive-portal/CMakeLists.txt\nnew file mode 100644\nindex 0000000..b9d34fd\n--- /dev/null\n+++ b/minimal-examples/http-client/minimal-http-client-captive-portal/CMakeLists.txt\n@@ -0,0 +1,82 @@\n+project(lws-minimal-http-client-captive-portal)\n+cmake_minimum_required(VERSION 2.8)\n+include(CheckIncludeFile)\n+include(CheckCSourceCompiles)\n+\n+set(SAMP lws-minimal-http-client-captive-portal)\n+set(SRCS minimal-http-client-captive-portal.c)\n+\n+# If we are being built as part of lws, confirm current build config supports\n+# reqconfig, else skip building ourselves.\n+#\n+# If we are being built externally, confirm installed lws was configured to\n+# support reqconfig, else error out with a helpful message about the problem.\n+#\n+MACRO(require_lws_config reqconfig _val result)\n+\n+\tif (DEFINED ${reqconfig})\n+\tif (${reqconfig})\n+\t\tset (rq 1)\n+\telse()\n+\t\tset (rq 0)\n+\tendif()\n+\telse()\n+\t\tset(rq 0)\n+\tendif()\n+\n+\tif (${_val} EQUAL ${rq})\n+\t\tset(SAME 1)\n+\telse()\n+\t\tset(SAME 0)\n+\tendif()\n+\n+\tif (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})\n+\t\tif (${_val})\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws being built without ${reqconfig}\u0022)\n+\t\telse()\n+\t\t\tmessage(\u0022${SAMP}: skipping as lws built with ${reqconfig}\u0022)\n+\t\tendif()\n+\t\tset(${result} 0)\n+\telse()\n+\t\tif (LWS_WITH_MINIMAL_EXAMPLES)\n+\t\t\tset(MET ${SAME})\n+\t\telse()\n+\t\t\tCHECK_C_SOURCE_COMPILES(\u0022#include \u003clibwebsockets.h\u003e\u005cnint main(void) {\u005cn#if defined(${reqconfig})\u005cn return 0;\u005cn#else\u005cn fail;\u005cn#endif\u005cn return 0;\u005cn}\u005cn\u0022 HAS_${reqconfig})\n+\t\t\tif (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})\n+\t\t\t\tset(HAS_${reqconfig} 0)\n+\t\t\telse()\n+\t\t\t\tset(HAS_${reqconfig} 1)\n+\t\t\tendif()\n+\t\t\tif ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))\n+\t\t\t\tset(MET 1)\n+\t\t\telse()\n+\t\t\t\tset(MET 0)\n+\t\t\tendif()\n+\t\tendif()\n+\t\tif (NOT MET)\n+\t\t\tif (${_val})\n+\t\t\t\tmessage(FATAL_ERROR \u0022This project requires lws must have been configured with ${reqconfig}\u0022)\n+\t\t\telse()\n+\t\t\t\tmessage(FATAL_ERROR \u0022Lws configuration of ${reqconfig} is incompatible with this project\u0022)\n+\t\t\tendif()\n+\t\tendif()\n+\tendif()\n+ENDMACRO()\n+\n+set(requirements 1)\n+if (WIN32)\n+\tset(requirements 0)\n+endif()\n+require_lws_config(LWS_ROLE_H1 1 requirements)\n+require_lws_config(LWS_WITH_CLIENT 1 requirements)\n+\n+if (requirements)\n+\tadd_executable(${SAMP} ${SRCS})\n+\n+\tif (websockets_shared)\n+\t\ttarget_link_libraries(${SAMP} websockets_shared pthread)\n+\t\tadd_dependencies(${SAMP} websockets_shared)\n+\telse()\n+\t\ttarget_link_libraries(${SAMP} websockets pthread)\n+\tendif()\n+endif()\ndiff --git a/minimal-examples/http-client/minimal-http-client-captive-portal/README.md b/minimal-examples/http-client/minimal-http-client-captive-portal/README.md\nnew file mode 100644\nindex 0000000..8db2002\n--- /dev/null\n+++ b/minimal-examples/http-client/minimal-http-client-captive-portal/README.md\n@@ -0,0 +1,45 @@\n+# lws minimal http client captive portal detect\n+\n+This demonstrates how to perform captive portal detection integrated\n+with `lws_system` states.\n+\n+After reaching the `lws_system` DHCP state, the application tries to\n+connect through to `http://connectivitycheck.android.com/generate_204`\n+over http... if it succeeds, it will get a 204 response and set the\n+captive portal detection state to `LWS_CPD_INTERNET_OK` and perform\n+a GET from warmcat.com.\n+\n+If there is a problem detected, the captive portal detection state is\n+set accordingly and the app will respond by exiting without trying the\n+read from warmcat.com.\n+\n+The captive portal detection scheme is implemented in the user code\n+and can be modified according to the strategy that's desired for\n+captive portal detection.\n+\n+## build\n+\n+```\n+ $ cmake . \u0026\u0026 make\n+```\n+\n+## usage\n+\n+```\n+$ ./bin/lws-minimal-http-client-captive-portal\n+[2020/03/11 13:07:07:4519] U: LWS minimal http client captive portal detect\n+[2020/03/11 13:07:07:4519] N: lws_create_context: using ss proxy bind '(null)', port 0, ads '(null)'\n+[2020/03/11 13:07:07:5022] U: callback_cpd_http: established with resp 204\n+[2020/03/11 13:07:07:5023] U: app_system_state_nf: OPERATIONAL, cpd 1\n+[2020/03/11 13:07:07:5896] U: Connected to 46.105.127.147, http response: 200\n+[2020/03/11 13:07:07:5931] U: RECEIVE_CLIENT_HTTP_READ: read 4087\n+[2020/03/11 13:07:07:5931] U: RECEIVE_CLIENT_HTTP_READ: read 4096\n+[2020/03/11 13:07:07:6092] U: RECEIVE_CLIENT_HTTP_READ: read 4087\n+[2020/03/11 13:07:07:6092] U: RECEIVE_CLIENT_HTTP_READ: read 4096\n+[2020/03/11 13:07:07:6112] U: RECEIVE_CLIENT_HTTP_READ: read 4087\n+[2020/03/11 13:07:07:6113] U: RECEIVE_CLIENT_HTTP_READ: read 4096\n+[2020/03/11 13:07:07:6113] U: RECEIVE_CLIENT_HTTP_READ: read 2657\n+[2020/03/11 13:07:07:6113] U: LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n+[2020/03/11 13:07:07:6119] U: main: finished OK\n+```\n+\ndiff --git a/minimal-examples/http-client/minimal-http-client-captive-portal/minimal-http-client-captive-portal.c b/minimal-examples/http-client/minimal-http-client-captive-portal/minimal-http-client-captive-portal.c\nnew file mode 100644\nindex 0000000..38d1a76\n--- /dev/null\n+++ b/minimal-examples/http-client/minimal-http-client-captive-portal/minimal-http-client-captive-portal.c\n@@ -0,0 +1,321 @@\n+/*\n+ * lws-minimal-http-client-captive-portal\n+ *\n+ * Written in 2010-2020 by Andy Green \u003candy@warmcat.com\u003e\n+ *\n+ * This file is made available under the Creative Commons CC0 1.0\n+ * Universal Public Domain Dedication.\n+ *\n+ * This demonstrates how to use the lws_system captive portal detect integration\n+ *\n+ * We check for a captive portal by doing a GET from\n+ * http://connectivitycheck.android.com/generate_204, if we really are going\n+ * out on the Internet he'll return with a 204 response code and we will\n+ * understand there's no captive portal. If we get something else, we take it\n+ * there is a captive portal.\n+ */\n+\n+#include \u003clibwebsockets.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csignal.h\u003e\n+\n+static struct lws_context *context;\n+static int interrupted, bad \u003d 1, status;\n+static lws_state_notify_link_t nl;\n+\n+/*\n+ * this is the user code http handler\n+ */\n+\n+static int\n+callback_http(struct lws *wsi, enum lws_callback_reasons reason,\n+\t void *user, void *in, size_t len)\n+{\n+\tswitch (reason) {\n+\n+\t/* because we are protocols[0] ... */\n+\tcase LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n+\t\tlwsl_err(\u0022CLIENT_CONNECTION_ERROR: %s\u005cn\u0022,\n+\t\t\t in ? (char *)in : \u0022(null)\u0022);\n+\t\tinterrupted \u003d 1;\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:\n+\t\t{\n+\t\t\tchar buf[128];\n+\n+\t\t\tlws_get_peer_simple(wsi, buf, sizeof(buf));\n+\t\t\tstatus \u003d lws_http_client_http_response(wsi);\n+\n+\t\t\tlwsl_user(\u0022Connected to %s, http response: %d\u005cn\u0022,\n+\t\t\t\t\tbuf, status);\n+\t\t}\n+\t\tbreak;\n+\n+\t/* chunks of chunked content, with header removed */\n+\tcase LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:\n+\t\tlwsl_user(\u0022RECEIVE_CLIENT_HTTP_READ: read %d\u005cn\u0022, (int)len);\n+\n+#if 0 /* enable to dump the html */\n+\t\t{\n+\t\t\tconst char *p \u003d in;\n+\n+\t\t\twhile (len--)\n+\t\t\t\tif (*p \u003c 0x7f)\n+\t\t\t\t\tputchar(*p++);\n+\t\t\t\telse\n+\t\t\t\t\tputchar('.');\n+\t\t}\n+#endif\n+\t\treturn 0; /* don't passthru */\n+\n+\t/* uninterpreted http content */\n+\tcase LWS_CALLBACK_RECEIVE_CLIENT_HTTP:\n+\t\t{\n+\t\t\tchar buffer[1024 + LWS_PRE];\n+\t\t\tchar *px \u003d buffer + LWS_PRE;\n+\t\t\tint lenx \u003d sizeof(buffer) - LWS_PRE;\n+\n+\t\t\tif (lws_http_client_read(wsi, \u0026px, \u0026lenx) \u003c 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\treturn 0; /* don't passthru */\n+\n+\tcase LWS_CALLBACK_COMPLETED_CLIENT_HTTP:\n+\t\tlwsl_user(\u0022LWS_CALLBACK_COMPLETED_CLIENT_HTTP\u005cn\u0022);\n+\t\tinterrupted \u003d 1;\n+\t\tbad \u003d status !\u003d 200;\n+\t\tlws_cancel_service(lws_get_context(wsi)); /* abort poll wait */\n+\t\tbreak;\n+\n+\tcase LWS_CALLBACK_CLOSED_CLIENT_HTTP:\n+\t\tinterrupted \u003d 1;\n+\t\tbad \u003d status !\u003d 200;\n+\t\tlws_cancel_service(lws_get_context(wsi)); /* abort poll wait */\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+}\n+\n+/*\n+ * This is the platform's custom captive portal detection handler\n+ */\n+\n+static int\n+callback_cpd_http(struct lws *wsi, enum lws_callback_reasons reason,\n+\t\t void *user, void *in, size_t len)\n+{\n+\tint resp;\n+\n+\tswitch (reason) {\n+\n+\tcase LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:\n+\t\tresp \u003d lws_http_client_http_response(wsi);\n+\t\tif (!resp)\n+\t\t\tbreak;\n+\t\tlwsl_user(\u0022%s: established with resp %d\u005cn\u0022, __func__, resp);\n+\t\tswitch (resp) {\n+\n+\t\tcase HTTP_STATUS_NO_CONTENT:\n+\t\t\t/*\n+\t\t\t * We got the 204 which is used to distinguish the real\n+\t\t\t * endpoint\n+\t\t\t */\n+\t\t\tlws_system_cpd_set(lws_get_context(wsi),\n+\t\t\t\t\t LWS_CPD_INTERNET_OK);\n+\t\t\treturn 0;\n+\n+\t\t/* also case HTTP_STATUS_OK: ... */\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* fallthru */\n+\n+\tcase LWS_CALLBACK_CLIENT_HTTP_REDIRECT:\n+\t\tlws_system_cpd_set(lws_get_context(wsi), LWS_CPD_CAPTIVE_PORTAL);\n+\t\t/* don't follow it, just report it */\n+\t\treturn 1;\n+\n+\tcase LWS_CALLBACK_CLIENT_CONNECTION_ERROR:\n+\tcase LWS_CALLBACK_CLOSED_CLIENT_HTTP:\n+\t\t/* only the first result counts */\n+\t\tlws_system_cpd_set(lws_get_context(wsi), LWS_CPD_NO_INTERNET);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn lws_callback_http_dummy(wsi, reason, user, in, len);\n+}\n+\n+static const struct lws_protocols protocols[] \u003d {\n+\t{\n+\t\t\u0022http\u0022,\n+\t\tcallback_http,\n+\t\t0,\n+\t\t0,\n+\t}, {\n+\t\t\u0022lws-cpd-http\u0022,\n+\t\tcallback_cpd_http\n+\t},\n+\t{ NULL, NULL, 0, 0 }\n+};\n+\n+void sigint_handler(int sig)\n+{\n+\tinterrupted \u003d 1;\n+}\n+\n+/*\n+ * This triggers our platform implementation of captive portal detection, the\n+ * actual test can be whatever you need.\n+ *\n+ * In this example, we detect it using Android's\n+ *\n+ * http://connectivitycheck.android.com/generate_204\n+ *\n+ * and seeing if we get an http 204 back.\n+ */\n+\n+static int\n+captive_portal_detect_request(struct lws_context *context)\n+{\n+\tstruct lws_client_connect_info i;\n+\n+\tmemset(\u0026i, 0, sizeof i);\n+\ti.context \u003d context;\n+\ti.port \u003d 80;\n+\ti.address \u003d \u0022connectivitycheck.android.com\u0022;\n+\ti.path \u003d \u0022/generate_204\u0022;\n+\ti.host \u003d i.address;\n+\ti.origin \u003d i.address;\n+\ti.method \u003d \u0022GET\u0022;\n+\n+\ti.protocol \u003d \u0022lws-cpd-http\u0022;\n+\n+\treturn !lws_client_connect_via_info(\u0026i);\n+}\n+\n+\n+lws_system_ops_t ops \u003d {\n+\t.captive_portal_detect_request \u003d captive_portal_detect_request\n+};\n+\n+\n+static int\n+app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,\n+\t\t int current, int target)\n+{\n+\tstruct lws_context *cx \u003d lws_system_context_from_system_mgr(mgr);\n+\n+\tswitch (target) {\n+\tcase LWS_SYSTATE_CPD_PRE_TIME:\n+\t\tif (lws_system_cpd_state_get(cx))\n+\t\t\treturn 0; /* allow it */\n+\n+\t\tlwsl_info(\u0022%s: LWS_SYSTATE_CPD_PRE_TIME\u005cn\u0022, __func__);\n+\t\tlws_system_cpd_start(cx);\n+\t\t/* we'll move the state on when we get a result */\n+\t\treturn 1;\n+\n+\tcase LWS_SYSTATE_OPERATIONAL:\n+\t\tif (current \u003d\u003d LWS_SYSTATE_OPERATIONAL) {\n+\t\t\tstruct lws_client_connect_info i;\n+\n+\t\t\tlwsl_user(\u0022%s: OPERATIONAL, cpd %d\u005cn\u0022, __func__,\n+\t\t\t\t\tlws_system_cpd_state_get(cx));\n+\n+\t\t\t/*\n+\t\t\t * When we reach the OPERATIONAL lws_system state, we\n+\t\t\t * can do our main job knowing we have DHCP, ntpclient,\n+\t\t\t * captive portal testing done.\n+\t\t\t */\n+\n+\t\t\tif (lws_system_cpd_state_get(cx) !\u003d LWS_CPD_INTERNET_OK) {\n+\t\t\t\tlwsl_warn(\u0022%s: There's no internet...\u005cn\u0022, __func__);\n+\t\t\t\tinterrupted \u003d 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tmemset(\u0026i, 0, sizeof i);\n+\t\t\ti.context \u003d context;\n+\t\t\ti.ssl_connection \u003d LCCSCF_USE_SSL;\n+\t\t\ti.ssl_connection |\u003d LCCSCF_H2_QUIRK_OVERFLOWS_TXCR |\n+\t\t\t\t\t LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM;\n+\t\t\ti.port \u003d 443;\n+\t\t\ti.address \u003d \u0022warmcat.com\u0022;\n+\t\t\ti.path \u003d \u0022/\u0022;\n+\t\t\ti.host \u003d i.address;\n+\t\t\ti.origin \u003d i.address;\n+\t\t\ti.method \u003d \u0022GET\u0022;\n+\n+\t\t\ti.protocol \u003d protocols[0].name;\n+\n+\t\t\tlws_client_connect_via_info(\u0026i);\n+\t\t\tbreak;\n+\t\t}\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static lws_state_notify_link_t * const app_notifier_list[] \u003d {\n+\t\u0026nl, NULL\n+};\n+\n+/*\n+ * We made this into a different thread to model it being run from completely\n+ * different codebase that's all linked together\n+ */\n+\n+\n+int main(int argc, const char **argv)\n+{\n+\tint logs \u003d LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;\n+\tstruct lws_context_creation_info info;\n+\tconst char *p;\n+\n+\tsignal(SIGINT, sigint_handler);\n+\n+\tif ((p \u003d lws_cmdline_option(argc, argv, \u0022-d\u0022)))\n+\t\tlogs \u003d atoi(p);\n+\n+\tlws_set_log_level(logs, NULL);\n+\tlwsl_user(\u0022LWS minimal http client captive portal detect\u005cn\u0022);\n+\n+\tmemset(\u0026info, 0, sizeof info);\n+\tinfo.port \u003d CONTEXT_PORT_NO_LISTEN;\n+\tinfo.options \u003d LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;\n+\tinfo.system_ops \u003d \u0026ops;\n+\tinfo.protocols \u003d protocols;\n+\n+\t/* integrate us with lws system state management when context created */\n+\n+\tnl.name \u003d \u0022app\u0022;\n+\tnl.notify_cb \u003d app_system_state_nf;\n+\tinfo.register_notifier_list \u003d app_notifier_list;\n+\n+\tcontext \u003d lws_create_context(\u0026info);\n+\tif (!context) {\n+\t\tlwsl_err(\u0022lws init failed\u005cn\u0022);\n+\t\treturn 1;\n+\t}\n+\n+\twhile (!interrupted)\n+\t\tif (lws_service(context, 0))\n+\t\t\tinterrupted \u003d 1;\n+\n+\tlws_context_destroy(context);\n+\n+\tlwsl_user(\u0022%s: finished %s\u005cn\u0022, __func__, bad ? \u0022FAIL\u0022: \u0022OK\u0022);\n+\n+\treturn bad;\n+}\ndiff --git a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c\nindex fe44a43..2c5a278 100644\n--- a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c\n+++ b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c\n@@ -154,6 +154,11 @@ static const char * const default_ss_policy \u003d\n \t\t\u0022}\u0022\n \t \u0022],\u0022\n \t \u0022\u005c\u0022s\u005c\u0022: [\u0022\n+\t \t/*\n+\t\t * \u0022fetch_policy\u0022 decides from where the real policy\n+\t\t * will be fetched, if present. Otherwise the initial\n+\t\t * policy is treated as the whole, hardcoded, policy.\n+\t\t */\n \t\t\u0022{\u005c\u0022fetch_policy\u005c\u0022: {\u0022\n \t\t\t\u0022\u005c\u0022endpoint\u005c\u0022:\u0022\t\t\u0022\u005c\u0022warmcat.com\u005c\u0022,\u0022\n \t\t\t\u0022\u005c\u0022port\u005c\u0022:\u0022\t\t\u0022443,\u0022\n@@ -168,8 +173,42 @@ static const char * const default_ss_policy \u003d\n \t\t\t\u0022\u005c\u0022opportunistic\u005c\u0022:\u0022\t\u0022true,\u0022\n \t\t\t\u0022\u005c\u0022retry\u005c\u0022:\u0022\t\t\u0022\u005c\u0022default\u005c\u0022,\u0022\n \t\t\t\u0022\u005c\u0022tls_trust_store\u005c\u0022:\u0022\t\u0022\u005c\u0022le_via_isrg\u005c\u0022\u0022\n-\t\t\u0022}}\u0022\n-\t\u0022}\u0022\n+\t\t\u0022}},{\u0022\n+\t\t\t/*\n+\t\t\t * \u0022captive_portal_detect\u0022 describes\n+\t\t\t * what to do in order to check if the path to\n+\t\t\t * the Internet is being interrupted by a\n+\t\t\t * captive portal. If there's a larger policy\n+\t\t\t * fetched from elsewhere, it should also include\n+\t\t\t * this since it needs to be done at least after\n+\t\t\t * every DHCP acquisition\n+\t\t\t */\n+\t\t \u0022\u005c\u0022captive_portal_detect\u005c\u0022: {\u0022\n+#if 1\n+\t\t\t/* this does the actual test */\n+ \u0022\u005c\u0022endpoint\u005c\u0022: \u005c\u0022connectivitycheck.android.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022: \u005c\u0022generate_204\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022: 80,\u0022\n+#endif\n+#if 0\n+\t\t\t/* this looks like a captive portal due to redirect */\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022: \u005c\u0022google.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022: \u005c\u0022/\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022: 80,\u0022\n+#endif\n+#if 0\n+\t\t\t/* this looks like no internet */\n+\t\t\t\u0022\u005c\u0022endpoint\u005c\u0022: \u005c\u0022warmcat.com\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022http_url\u005c\u0022: \u005c\u0022/\u005c\u0022,\u0022\n+\t\t\t\u0022\u005c\u0022port\u005c\u0022: 999,\u0022\n+#endif\n+ \u0022\u005c\u0022protocol\u005c\u0022: \u005c\u0022h1\u005c\u0022,\u0022\n+ \u0022\u005c\u0022http_method\u005c\u0022: \u005c\u0022GET\u005c\u0022,\u0022\n+ \u0022\u005c\u0022opportunistic\u005c\u0022: true,\u0022\n+ \u0022\u005c\u0022http_expect\u005c\u0022: 204,\u0022\n+\t\t\t\u0022\u005c\u0022http_fail_redirect\u005c\u0022: true\u0022\n+ \u0022}}\u0022\n+\t\u0022]}\u0022\n ;\n \n #endif\n","s":{"c":1752654913,"u": 14620}} ],"g": 17649,"chitpc": 0,"ehitpc": 0,"indexed":0 , "ab": 0, "si": 0, "db":0, "di":0, "sat":0, "lfc": "0000"}