diff --git a/.sai.json b/.sai.json index 692203e73..1d9fcfc98 100644 --- a/.sai.json +++ b/.sai.json @@ -117,6 +117,10 @@ "cmake": "-DLWS_WITH_UDP=0", "platforms": "w10/x86_64-amd/msvc, w10/x86_64-amd/noptmsvc, freertos-linkit/arm32-m4-mt7697-usi/gcc, linux-ubuntu-2004/aarch64-a72-bcm2711-rpi4/gcc, w10/x86_64-amd/mingw32, w10/x86_64-amd/mingw64, netbsd/aarch64BE-bcm2837-a53/gcc, w10/x86_64-amd/wmbedtlsmsvc" }, + "fault-injection": { + "cmake": "-DLWS_WITH_SYS_FAULT_INJECTION=1 -DLWS_WITH_MINIMAL_EXAMPLES=1", + "platforms": "w10/x86_64-amd/msvc" + }, "esp32-heltec": { "cmake": "-DLWS_IPV6=0", "cpack": "esp-heltec-wb32", diff --git a/READMEs/README.fault-injection.md b/READMEs/README.fault-injection.md index d2b21245d..b7e3f3076 100644 --- a/READMEs/README.fault-injection.md +++ b/READMEs/README.fault-injection.md @@ -1,40 +1,116 @@ # `lws_fi` Fault Injection -To provide better quality there's a need to not just test the code paths for -normal operation, but also that it acts correctly under various fault -conditions that may be difficult to arrange at test-time. +Most efforts during development go towards trying to make the system do what +it is supposed to do during normal operation. -Code handling the failures may be anywhere including during early initialization -or in user code before lws intialization. +But to provide reliable quality there's a need to not just test the code paths +for normal operation, but also to be able to easily confirm that they act +correctly under various fault conditions that may be difficult to arrange at +test-time. It's otherwise very easy for error conditions that are low +probability to be overlooked and turn out to do the wrong thing, eg, try to +clean up things they had not actually initialized, or forget to free things etc. -To help with this lws has `LWS_WITH_SYS_FAULT_INJECTION` build option that -provides a simple but powerful api for fault injection in any lws or user code. +Code handling the operational failures we want to check may be anywhere, +including during early initialization or in user code before lws intialization. + +To help with this lws has a `LWS_WITH_SYS_FAULT_INJECTION` build option that +provides a simple but powerful api for targeted fault injection in any lws or +user code, and provides a wide range of well-known internal faults inside lws +you can trigger from outside. ## Fault contexts and faults -`lws_fi_t` objects represent a named fault injection rules, just in terms of -whether and how often to inject the fault. +The basic idea is objects in the user code can choose to initialize "fault +contexts" inside objects, that list named, well-known "faults" that the code +supoorts and that the user wants to inject. -`lws_fi_ctx_t` objects are linked-lists of `lws_fi_t` objects. When Fault -Injection is enabled at build-time, the key system objects like the -`lws_context`, `lws_vhost`, `wsi` and Secure Stream handles / SSPC handles -contain their own `lws_fi_ctx_t` lists that may have any number of `lws_fi_t` -added to them. +Although these "fault contexts" can be embedded in objects directly at object +creation time, eg, for lws in the lws_context creation info struct, or the +client connection info struct, or Secure Stream info struct, it's usually +inconvenient to pass the desired faults directly deep into the code and attach +them at creation time. Eg, if you want to cause a fault in a wsi instantiated +by a Secure Stream, that is internal lws code one step removed from the Secure +Stream object creation making it difficult to arrange. -`lws_fi_ctx_t` objects are hierarchical, if a named rule is not found in, eg, -a wsi Fault injection context, then the vhost and finally the lws_context Fault -Injection contexts are searched for it before giving up. This allows for both -global and individual overridden Fault Injection rules at each level. +For that reason, faults have a targeted inheritance scheme using namespace +paths, it's usually enough to just list the faults you want at context creation +time and they will be filter down to the internal objects you want to target +when they are created later. -## Integrating fault injection conditionals into code +![Fault Injection Overview](../doc-assets/fault-injection.png) -A simple api `lws_fi(fi_ctx, "name")` is provided that returns 0 if no fault to -be injected, or 1 if the fault should be synthesized. If there is no rule -matching "name", the answer is always to not inject a fault, ie, returns 0. +A fault injection request is made in `lws_fi_t` objects, specifying the +fault name and whether, and how often to inject the fault. + +The "fault context" objects `lws_fi_ctx_t` embedded in the creation info +structs are linked-lists of `lws_fi_t` objects. When Fault Injection is enabled +at build-time, the key system objects like the `lws_context`, `lws_vhost`, `wsi` +and Secure Stream handles / SSPC handles contain their own `lws_fi_ctx_t` lists +that may have any number of `lws_fi_t` added to them. + +When downstream objects are created, eg, when an lws_context creates a Secure +Stream, in addition to using any faults provided directly in the SS info, +the lws_context faults are consulted to see if any relate to that streamtype +and should be applied. + +Although faults can be added to objects at creation, it is far more convenient +to just pass a list of faults you want into the lws_context and have the +objects later match them using namespacing, described later. + +## Integrating fault injection conditionals into code in private lws code + +A simple query api `lws_fi(fi_ctx, "name")` is provided that returns 0 if no +fault to be injected, or 1 if the fault should be synthesized. If there is no +rule matching "name", the answer is always to not inject a fault, ie, returns 0. + +Similarly for convenience if FAULT_INJECTION is disabled at build, the `lws_fi()` +call always returns the constant `0`. By default then just enabling Fault Injection at build does not have any impact -on code operation since the user must first add the fault injection rules he -wants. +on code operation since the user must also add the fault injection rules he +wants to the objects's Fault Injection context. + +## Integrating fault injection conditionals into user code with public apis + +These public apis query the fault context in a wsi, lws_context, ss handle, or +sspc handle (client side of proxy) to find any matching rule, if so they return +1 if the conditions (eg, probability) are met and the fault should be injected. + +These allow user code to use the whole Fault Injection system without having to +understand anything except the common object like a wsi they want to query and +the name of the fault rule they are checking. + +|FI context owner|Public API| +|---|---| +|lws_context|`int lws_fi_user_context_fi(struct lws_context *ctx, const char *rule)`| +|wsi|`int lws_fi_user_wsi_fi(struct lws *wsi, const char *rule)`| +|ss handle|`int lws_fi_user_ss_fi(struct lws_ss_handle *h, const char *rule)`| +|sspc handle|`int lws_fi_user_sspc_fi(struct lws_sspc_handle *h, const char *rule)`| + +For example, the minimal-http-client user code example contains this in its +ESTABLISHED callback + +``` + if (lws_fi_user_wsi_fi(wsi, "user_reject_at_est")) + return -1; +``` + +which can be triggered by running it with + +`lws-minimal-http-client --fault-injection 'wsi/user_reject_at_est'`, causing + +``` +... +[2021/03/11 13:41:05:2769] U: Connected to 46.105.127.147, http response: 200 +[2021/03/11 13:41:05:2776] W: lws_fi: Injecting fault unk->user_reject_at_est +[2021/03/11 13:41:05:2789] E: CLIENT_CONNECTION_ERROR: HS: disallowed at ESTABLISHED +... +``` + +When `LWS_WITH_SYS_FAULT_INJECTION` is disabled, these public apis become +preprocessor defines to `(0)`, so the related code is removed by the compiler. + +## Types of fault injection "when" strategy The api keeps track of each time the context was asked and uses this information to drive the decision about when to say yes, according to the type of rule @@ -44,49 +120,215 @@ to drive the decision about when to say yes, according to the type of rule |`LWSFI_ALWAYS`|Unconditionally inject the fault| |`LWSFI_DETERMINISTIC`|after `pre` times without the fault, the next `count` times exhibit the fault`| |`LWSFI_PROBABILISTIC`|exhibit a fault `pre` percentage of the time| -|`LWSFI_PATTERN`|Reference `pre` bits pointed to by `pattern` and fault if the bit set| +|`LWSFI_PATTERN`|Reference `pre` bits pointed to by `pattern` and fault if the bit set, pointing to static array| +|`LWSFI_PATTERN_ALLOC`|Reference `pre` bits pointed to by `pattern` and fault if the bit set, pointing to allocated array, freed when fault goes out of scope| + +Probabalistic choices are sourced from a PRNG with a seed set in the context +creation info Fault Injection Context. By default the lws helper +`lws_cmdline_option_handle_builtin()` sets this to the time in us, but it can +be overridden using `--fault-seed `, and the effective PRNG seed is +logged when the commandline options are initially parsed. ## Addings Fault Injection Rules to `lws_fi_ctx_t` -User code should prepare a `lws_fi_ctx_t` cleared down to zero if necessary, -and one of these, eg on the stack +Typically the lws_context is used as the central, toplevel place to define +faults. This is done by adding prepared `lws_fi_t` objects on the stack one by +one to the context creation info struct's `.fic` member, using +`lws_fi_add(lws_fi_ctx_t *fic, const lws_fi_t *fi);`, this will allocate and copy +the provided `fi` into the allocation, and attach it to the `lws_fi_ctx_t` list. -``` -typedef struct lws_fi { - const char *name; - uint8_t *pattern; - uint64_t pre__prob1; - uint64_t count__prob2; - char type; /* LWSFI_* */ -} lws_fi_t; -``` - -and call `lws_fi_add(lws_fi_ctx_t *fic, const lws_fi_t *fi);`, this will -allocate and copy the provided `fi` into the allocation, and attach it to -the `lws_fi_ctx_t` list. - -The creation info struct associated with the context, vhost, wsi or Secure -Stream has a `*fi` pointer you can set to your `lws_fi_ctx_t`, when creating -the object it will take ownership of any `lws_fi_t` you attached to it. - -So the `lws_fi_ctx_t` and the `lws_fi_t` used as a template for adding the -rules may be on the stack and safely and go out of scope after the object -creation api is called. The `lws_fi_t` `name` is also copied into the -allocation and does not need to continue to exist after it is added to the -`lws_fi_ctx_t`. The only exception is the `pattern` member if used, the -array pointed to is not copied and must exist for the lifetime of the rule. +When the context (or other object using the same scheme) is created, it imports +all the faults from the info structure `.fic` and takes ownership of them, +leaving the info `.fic` empty and ready to go out of scope. ## Passing in fault injection rules A key requirement is that Fault Injection rules must be availble to the code creating an object before the object has been created. This is why the user -code prepares a temporary context listing his rules, and offers it as part of -the creation info struct, rather than waiting for the object to be created and -then attach Fault Injection rules... it's too late to test faults during the -creation by then otherwise. +code prepares a Fault Injection context listing his rules in the creation info +struct, rather than waiting for the object to be created and then attach Fault +Injection rules... it's too late then to test faults during the creation. + +## Directly applying fault contexts + +You can pass in a Fault Injection context prepared with lws_fi_t added to it +when creating the following kinds of objects + +|Object being created|info struct|Fault injection Context member| +|---|---|---| +|lws context|struct lws_context_creation_info|`fic`| +|vhost|struct lws_context_creation_info|`fic`| +|Secure Stream|struct lws_ss_info|`fic`| +|client wsi|struct lws_client_connect_info|`fic`| + +However typically the approach is just provide a list of faults at context +creation time, and let the objects match and inherit using namespacing, +described next. ## Using the namespace to target specific instances -Wsi client connection can directly have fault injection objects attached to it -at client connection creation time. +Lws objects created by the user can directly have a Fault Injection context +attached to them at creation time, so the fault injection objects directly +relate to the object. +But in other common scenarios, there is no direct visibility of the object that +we want to trigger faults in, it may not exist until some time later. Eg, we +want to trigger faults in the listen socket of a vhost. To allow this, the +fault names can be structured with a /path/ type namespace so objects created +later can inherit faults. + +Notice that if you are directly creating the vhost, Secure Stream or wsi, you +can directly attach the subrule yourself without the namespacing needed. The +namespacing is used when you have access to a higher level object at creation- +time, like the lws_context, and it will itself create the object you want to +target without your having any direct access to it. + +|namespace form|effect| +|---|---| +|**vh=myvhost/**subrule|subrule is inherited by the vhost named "myvhost" when it is created| +|**vh/**subrule|subrule is inherited by any vhost when it is created| +|**ss=mystream/**subrule|subrule is inherited by SS of streamtype "mystream" (also covers SSPC / proxy client)| +|**ss/**subrule|subrule is inherited by all SS of any streamtype (also covers SSPC / proxy client)| +|**wsi=myname/**subrule|subrule is inherited by client wsi created with `info->fi_wsi_name` "myname"| +|**wsi/**subrule|subrule is inherited by any wsi| + +Namespaces can be combined, for example `vh=myvhost/wsi/listenskt` will set the +`listenskt` fault on wsi created by the server vhost "myvhost", ie, it will +cause the listen socket for the vhost to error out on creation. + +In the case of wsi migration when it's the network connection wsi on an h2 +connection that is migrated to be SID 1, the attached faults also migrate. + +Here is which Fault Injection Contexts each type of object inherits matching +Fault Injection rules from: + +|Object type|Initialized with|Inherit matching faults from| +|---|---|---| +|context|`struct lws_context_creation_info` .fic|-| +|vhost|`struct lws_context_creation_info` .fic|context FIC| +|client wsi|`struct lws_client_connect_info` .fic|context FIC, vhost FIC| +|ss / sspc|`lws_ss_info_t` .fic|context FIC| +|ss / sspc wsi|-|context FIC, vhost FIC, ss / sspc .fic| + +Since everything can be reached from the lws_context fault context, directly or +by additional inheritence, and that's the most convenient to set from the +outside, that's typically the original source of all injected faults. + +## Integration with minimal examples + +All the minimal examples that use the `lws_cmdline_option_handle_builtin()` api +can take an additional `--fault-injection "...,..."` switch, which automatically +parses the comma-separated list in the argument to add faults with the given +name to the lws_context. For example, + +`lws-minimal-http-client --fault-injection "wsi/dnsfail"` + +will force all wsi dns lookups to fail for that run of the example. + +### Specifying when to inject the fault + +By default, if you just give the name part, if the namespace is absent or +matches an object, the fault will be injected every time. It's also possible +to make the fault inject itself at a random probability, or in a cyclic pattern, +by giving additional information in brackets, eg + +|Syntax|Meaning| +|---|---| +|`wsi/thefault`|Inject the fault every time| +|`wsi/thefault(10%)`|Randomly inject the fault at 10% probability| +|`wsi/thefault(.............X.X)`|Inject the fault on the 14th and 16th try, every 16 tries| + +You must quote the strings containing these symbols, since they may otherwise be +interpreted by your shell. + +## Well-known fault names in lws + +|Scope|Namespc|Name|Fault effect| +|---|---|---|---| +|context||`ctx_createfail1`|Fail context creation immediately at entry| +|context||`ctx_createfail_plugin_init`|Fail context creation as if a plugin init failed (if plugins enabled)| +|context||`ctx_createfail_evlib_plugin`|Fail context creation due to event lib plugin failed init (if evlib plugins enabled)| +|context||`ctx_createfail_evlib_sel`|Fail context creation due to unable to select event lib| +|context||`ctx_createfail_oom_ctx`|Fail context creation due to OOM on context object| +|context||`ctx_createfail_privdrop`|Fail context creation due to failure dropping privileges| +|context||`ctx_createfail_maxfds`|Fail context creation due to unable to determine process fd limit| +|context||`ctx_createfail_oom_fds`|Fail context creation due to OOM on fds table| +|context||`ctx_createfail_plat_init`|Fail context creation due to platform init failed| +|context||`ctx_createfail_evlib_init`|Fail context creation due to event lib init failed| +|context||`ctx_createfail_evlib_pt`|Fail context creation due to event lib pt init failed| +|context||`ctx_createfail_sys_vh`|Fail context creation due to system vhost creation failed| +|context||`ctx_createfail_sys_vh_init`|Fail context creaton due to system vhost init failed| +|context||`ctx_createfail_def_vh`|Fail context creation due to default vhost creation failed| +|context||`ctx_createfail_ss_pol1`|Fail context creation due to ss policy parse start failed (if policy enabled)| +|context||`ctx_createfail_ss_pol2`|Fail context creation due to ss policy parse failed (if policy enabled)| +|context||`ctx_createfail_ss_pol3`|Fail context creation due to ss policy set failed (if policy enabled)| +|vhost|`vh`|`vh_create_oom`|Fail vh creation on vh object alloc OOM| +|vhost|`vh`|`vh_create_pcols_oom`|Fail vh creation at protocols alloc OOM| +|vhost|`vh`|`vh_create_access_log_open_fail`|Fail vh creation due to unable to open access log (LWS_WITH_ACCESS_LOG)| +|vhost|`vh`|`vh_create_ssl_srv`|Fail server ssl_ctx init| +|vhost|`vh`|`vh_create_ssl_cli`|Fail client ssl_ctx init| +|vhost|`vh`|`vh_create_srv_init`|Fail server init| +|vhost|`vh`|`vh_create_protocol_init`|Fail late protocol init (for late vhost creation)| +|srv vhost|`vh=xxx/wsi`|`listenskt`|Causes `socket()` allocation for vhost listen socket to fail| +|cli wsi|`wsi`|`dnsfail`|Sync: `getaddrinfo()` is not called and a EAI_FAIL return synthesized, Async: request not started and immediate fail synthesized| +|cli wsi|`wsi`|`sendfail`|Attempts to send data on the wsi socket fail| +|cli wsi|`wsi`|`connfail`|Attempts to connect on the wsi socket fail| +|cli wsi|`wsi`|`createfail`|Creating the client wsi itself fails| +|udp wsi|`wsi`|`udp_rx_loss`|Drop UDP RX that was actually received, useful with probabalistic mode| +|udp wsi|`wsi`|`udp_tx_loss`|Drop UDP TX so that it's not actually sent, useful with probabalistic mode| +|srv ss|`ss`|`ss_srv_vh_fail`|Secure Streams Server vhost creation forced to fail| +|cli ss|`ss`|`ss_no_streamtype_policy`|The policy for the streamtype is made to seem as if it is missing| +|sspc|`ss`|`sspc_fail_on_linkup`|Reject the connection to the proxy when we hear it has succeeded, it will provoke endless retries| +|sspc|`ss`|`sspc_fake_rxparse_disconnect_me`|Force client-proxy link parse to seem to ask to be disconnected, it will provoke endless retries| +|sspc|`ss`|`sspc_fake_rxparse_destroy_me`|Force client-proxy link parse to seem to ask to destroy the SS, it will destroy the SS cleanly| +|sspc|`ss`|`sspc_link_write_fail`|Force write on the link to fail, it will provoke endless retries| +|sspc|`ss`|`sspc_create_oom`|Cause the sspc handle allocation to fail as if OOM at creation time| +|sspc|`ss`|`sspc_fail_metadata_set`|Cause the metadata allocation to fail| +|sspc|`ss`|`sspc_rx_fake_destroy_me`|Make it seem that client's user code *rx() returned DESTROY_ME| +|sspc|`ss`|`sspc_rx_metadata_oom`|Cause metadata from proxy allocation to fail| +|ssproxy|`ss`|`ssproxy_dsh_create_oom`|Cause proxy's creation of DSH to fail| +|ssproxy|`ss`|`ssproxy_dsh_rx_queue_oom`|Cause proxy's allocation in the onward SS->P[->C] DSH rx direction to fail as if OOM, this causes the onward connection to disconnect| +|ssproxy|`wsi`|`ssproxy_client_adopt_oom`|Cause proxy to be unable to allocate for new client - proxy link connection object| +|ssproxy|`wsi`|`ssproxy_client_write_fail`|Cause proxy write to client to fail| +|ssproxy|`wsi`|`sspc_dsh_ss2p_oom`|Cause ss->proxy dsh allocation to fail| +|ssproxy|`ss`|`ssproxy_onward_conn_fail`|Act as if proxy onward client connection failed immediately| +|ssproxy|`ss`|`ssproxy_dsh_c2p_pay_oom`|Cause proxy's DSH alloc for C->P payload to fail| + + +## Well-known namespace targets + +Namespaces can be used to target these more precisely, for example even though +we are only passing the faults we want inject at the lws_context, we can use +the namespace "paths" to target only the wsis created by other things. + +To target wsis from SS-based connections, you can use `ss=stream_type_name/`, +eg for captive portal detection, to have it unable to find its policy entry: + +`ss=captive_portal_detect/ss_no_streamtype_policy` (disables CPD from operating) + +...to force it to fail to resolve the server DNS: + +`ss=captive_portal_detect/wsi/dnsfail` (this makes CPD feel there is no internet) + +...to target the connection part of the captive portal testing instead: + +`ss=captive_portal_detect/wsi/connfail` (this also makes CPD feel there is no internet) + +### Well-known internal wsi type names + +Wsi created for internal features like Async DNS processing can also be targeted + +|wsi target|Meaning| +|---|---| +|`wsi=asyncdns/`|UDP wsi used by lws Async DNS support to talk to DNS servers| +|`wsi=dhcpc/`|UDP wsi used by lws DHCP Client| +|`wsi=ntpclient/`|UDP wsi used by lws NTP Client| + +For example, passing in at lws_context level `wsi=asyncdns/udp_tx_loss` +will force async dns to be unable to resolve anything since its UDP tx is +being suppressed. + +At client connection creation time, user code can also specify their own names +to match on these `wsi=xxx/` namespace parts, so the faults only apply to +specific wsi they are creating themselves later. This is done by setting the +client creation info struct `.fi_wsi_name` to the string "xxx". diff --git a/READMEs/README.udp.md b/READMEs/README.udp.md index ee3d94eda..e986be8b2 100644 --- a/READMEs/README.udp.md +++ b/READMEs/README.udp.md @@ -44,7 +44,7 @@ In the callback, it should simply call `lws_callback_on_writable()` for the udp ## Simulating packetloss -lws now allows you to set the amount of simulated packetloss on udp rx and tx in -the context creation info struct, using `.udp_loss_sim_tx_pc` and `.udp_loss_sim_rx_pc`, -the values are percentages between 0 and 100. 0, the default, means no packetloss. - +You can simulate udp packetloss at tx and rx by using the Fault Injection apis +with the well-known fault names "udp_tx_loss" and "udp_rx_loss", typically +with the probabilistic setting, in commandline format something like +`--fault-injection "wsi/udp_tx_loss(10%)"` diff --git a/doc-assets/fault-injection.png b/doc-assets/fault-injection.png new file mode 100644 index 000000000..acaf9d42b Binary files /dev/null and b/doc-assets/fault-injection.png differ diff --git a/include/libwebsockets/lws-adopt.h b/include/libwebsockets/lws-adopt.h index a503327b1..94a1818d2 100644 --- a/include/libwebsockets/lws-adopt.h +++ b/include/libwebsockets/lws-adopt.h @@ -152,6 +152,7 @@ typedef struct lws_adopt_desc { const char *vh_prot_name; /**< NULL or vh protocol name to bind raw connection to */ struct lws *parent; /**< NULL or struct lws to attach new_wsi to as a child */ void *opaque; /**< opaque pointer to set on created wsi */ + const char *fi_wsi_name; /**< NULL, or Fault Injection inheritence filter for wsi=string/ context faults */ } lws_adopt_desc_t; /** @@ -249,6 +250,11 @@ lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, * \param parent_wsi: NULL or parent wsi new wsi will be a child of * \param opaque: set created wsi opaque ptr to this * \param retry_policy: NULL for vhost default policy else wsi specific policy + * \param fi_wsi_name: NULL, or string to inherit Fault Injection rules in + * form "wsi=string/rule". "wsi/rule" faults will be + * automatically applied as well. It's done at creation + * time so the rules can, eg, inject faults related to + * creation. * * Either returns new wsi bound to accept_fd, or closes accept_fd and * returns NULL, having cleaned up any new wsi pieces. @@ -257,7 +263,7 @@ LWS_VISIBLE LWS_EXTERN struct lws * lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, int flags, const char *protocol_name, const char *ifname, struct lws *parent_wsi, void *opaque, - const lws_retry_bo_t *retry_policy); + const lws_retry_bo_t *retry_policy, const char *fi_wsi_name); #endif diff --git a/include/libwebsockets/lws-client.h b/include/libwebsockets/lws-client.h index 679a7e31f..38de89762 100644 --- a/include/libwebsockets/lws-client.h +++ b/include/libwebsockets/lws-client.h @@ -206,10 +206,15 @@ struct lws_client_connect_info { #endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_ctx_t *fi; + lws_fi_ctx_t fic; /**< Attach external Fault Injection context to the client wsi, * hierarchy is wsi -> vhost -> context */ #endif + /* for convenience, available when FI disabled in build */ + const char *fi_wsi_name; + /**< specific Fault Injection namespace name for wsi created for this + * connection, allows targeting by "wsi=XXX/..." if you give XXX here. + */ uint16_t keep_warm_secs; /**< 0 means 5s. If the client connection to the endpoint becomes idle, diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index 3e0057154..ee7d57dae 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -810,17 +810,9 @@ struct lws_context_creation_info { */ #endif /* PEER_LIMITS */ -#if defined(LWS_WITH_UDP) - uint8_t udp_loss_sim_tx_pc; - /**< CONTEXT: percentage of udp writes we could have performed - * to instead not do, in order to simulate and test udp retry flow */ - uint8_t udp_loss_sim_rx_pc; - /**< CONTEXT: percentage of udp reads we actually received - * to make disappear, in order to simulate and test udp retry flow */ -#endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_ctx_t *fi; + lws_fi_ctx_t fic; /**< CONTEXT | VHOST: attach external Fault Injection context to the * lws_context or vhost. If creating the context + default vhost in * one step, only the context binds to \p fi. When creating a vhost diff --git a/include/libwebsockets/lws-fault-injection.h b/include/libwebsockets/lws-fault-injection.h index 8f46f0efe..6f8e1aa81 100644 --- a/include/libwebsockets/lws-fault-injection.h +++ b/include/libwebsockets/lws-fault-injection.h @@ -24,13 +24,54 @@ * Fault injection api if built with LWS_WITH_SYS_FAULT_INJECTION */ +typedef struct lws_xos { + uint64_t s[4]; +} lws_xos_t; + +/** + * lws_xos_init() - seed xoshiro256 PRNG + * + * \param xos: the prng state object to initialize + * \param seed: the 64-bit seed + * + * Initialize PRNG \xos with the starting state represented by \p seed + */ +LWS_VISIBLE LWS_EXTERN void +lws_xos_init(struct lws_xos *xos, uint64_t seed); + +/** + * lws_xos() - get next xoshiro256 PRNG result and update state + * + * \param xos: the PRNG state to use + * + * Returns next 64-bit PRNG result. These are cheap to get, + * quite a white noise sequence, and completely deterministic + * according to the seed it was initialized with. + */ +LWS_VISIBLE LWS_EXTERN uint64_t LWS_WARN_UNUSED_RESULT +lws_xos(struct lws_xos *xos); + +/** + * lws_xos_percent() - return 1 a given percent of the time on average + * + * \param xos: the PRNG state to use + * \param percent: chance in 100 of returning 1 + * + * Returns 1 if next random % 100 is < \p percent, such that + * 100 always returns 1, 0 never returns 1, and the chance linearly scales + * inbetween + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_xos_percent(struct lws_xos *xos, int percent); + #if defined(LWS_WITH_SYS_FAULT_INJECTION) enum { LWSFI_ALWAYS, LWSFI_DETERMINISTIC, /* do .count injections after .pre then stop */ - LWSFI_PROBABILISTIC, /* .prob1 / .prob2 chance of injection */ + LWSFI_PROBABILISTIC, /* .pre % chance of injection */ LWSFI_PATTERN, /* use .count bits in .pattern after .pre */ + LWSFI_PATTERN_ALLOC, /* as _PATTERN, but .pattern is malloc'd */ }; typedef struct lws_fi { @@ -44,8 +85,8 @@ typedef struct lws_fi { typedef struct lws_fi_ctx { lws_dll2_owner_t fi_owner; + struct lws_xos xos; const char *name; - struct lws_fi_ctx *parent; } lws_fi_ctx_t; /** @@ -65,7 +106,7 @@ typedef struct lws_fi_ctx { * If LWS_WITH_SYS_FAULT_INJECTION is not defined, then this always return 0. */ LWS_VISIBLE LWS_EXTERN int -lws_fi(lws_fi_ctx_t *fic, const char *fi_name); +lws_fi(const lws_fi_ctx_t *fic, const char *fi_name); /** * lws_fi_add() - add an allocated copy of fault injection to a context @@ -74,7 +115,7 @@ lws_fi(lws_fi_ctx_t *fic, const char *fi_name); * \param fi: the fault injection details * * This allocates a copy of \p fi and attaches it to the fault injection context - * \p fic. + * \p fic. \p fi can go out of scope after this safely. */ LWS_VISIBLE LWS_EXTERN int lws_fi_add(lws_fi_ctx_t *fic, const lws_fi_t *fi); @@ -91,6 +132,35 @@ lws_fi_add(lws_fi_ctx_t *fic, const lws_fi_t *fi); LWS_VISIBLE LWS_EXTERN void lws_fi_remove(lws_fi_ctx_t *fic, const char *name); +/** + * lws_fi_import() - transfers all the faults from one context to another + * + * \param fic_dest: the fault context to receive the faults + * \param fic_src: the fault context that will be emptied out into \p fic_dest + * + * This is used to initialize created object fault injection contexts from + * the caller. + */ +LWS_VISIBLE LWS_EXTERN void +lws_fi_import(lws_fi_ctx_t *fic_dest, const lws_fi_ctx_t *fic_src); + +/** + * lws_fi_inherit_copy() - attach copies of matching fault injection objects to dest + * + * \param fic_dest: destination Fault Injection context + * \param fic_src: parent fault context that may contain matching rules + * \param scope: the name of the path match required, eg, "vh" + * \param value: the dynamic name of our match, eg, "myvhost" + * + * If called with scope "vh" and value "myvhost", then matches faults starting + * "vh=myvhost/", strips that part of the name if it matches and makes a copy + * of the rule with the modified name attached to the destination Fault Injection + * context. + */ +LWS_VISIBLE LWS_EXTERN void +lws_fi_inherit_copy(lws_fi_ctx_t *fic_dest, const lws_fi_ctx_t *fic_src, + const char *scope, const char *value); + /** * lws_fi_destroy() - removes all allocated fault injection entries * @@ -101,7 +171,44 @@ lws_fi_remove(lws_fi_ctx_t *fic, const char *name); * not usually directly allocated. */ LWS_VISIBLE LWS_EXTERN void -lws_fi_destroy(lws_fi_ctx_t *fic); +lws_fi_destroy(const lws_fi_ctx_t *fic); + +/** + * lws_fi_deserialize() - adds fault in string form to Fault Injection Context + * + * \p fic: the fault injection context + * \p sers: the string serializing the desired fault details + * + * This turns a string like "ss=captive_portal_detect/wsi/dnsfail(10%)" into + * a fault injection struct added to the fault injection context \p fic + * + * You can prepare the context creation info .fic with these before creating + * the context, and use namespace paths on those to target other objects. + */ + +LWS_VISIBLE LWS_EXTERN void +lws_fi_deserialize(lws_fi_ctx_t *fic, const char *sers); + +LWS_VISIBLE LWS_EXTERN int +_lws_fi_user_wsi_fi(struct lws *wsi, const char *name); +LWS_VISIBLE LWS_EXTERN int +_lws_fi_user_context_fi(struct lws_context *ctx, const char *name); + +#if defined(LWS_WITH_SECURE_STREAMS) +struct lws_ss_handle; +LWS_VISIBLE LWS_EXTERN int +_lws_fi_user_ss_fi(struct lws_ss_handle *h, const char *name); +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) +struct lws_sspc_handle; +LWS_VISIBLE LWS_EXTERN int +_lws_fi_user_sspc_fi(struct lws_sspc_handle *h, const char *name); +#endif +#endif + +#define lws_fi_user_wsi_fi(_wsi, _name) _lws_fi_user_wsi_fi(_wsi, _name) +#define lws_fi_user_context_fi(_ctx, _name) _lws_fi_user_context_fi(_ctx, _name) +#define lws_fi_user_ss_fi(_h, _name) _lws_fi_user_ss_fi(_h, _name) +#define lws_fi_user_sspc_fi(_h, _name) _lws_fi_user_sspc_fi(_h, _name) #else @@ -111,5 +218,12 @@ lws_fi_destroy(lws_fi_ctx_t *fic); */ #define lws_fi(_fi_name, _fic) (0) +#define lws_fi_destroy(_x) +#define lws_fi_inherit_copy(_a, _b, _c, _d) +#define lws_fi_deserialize(_x, _y) +#define lws_fi_user_wsi_fi(_wsi, _name) (0) +#define lws_fi_user_context_fi(_wsi, _name) (0) +#define lws_fi_user_ss_fi(_h, _name) (0) +#define lws_fi_user_sspc_fi(_h, _name) (0) #endif diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index b853a2c45..17a4b3bd6 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 - 2019 Andy Green + * Copyright (C) 2010 - 2021 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to diff --git a/include/libwebsockets/lws-secure-streams-client.h b/include/libwebsockets/lws-secure-streams-client.h index fa12014f3..eac12e79a 100644 --- a/include/libwebsockets/lws-secure-streams-client.h +++ b/include/libwebsockets/lws-secure-streams-client.h @@ -61,6 +61,7 @@ #define lws_ss_change_handlers lws_sspc_change_handlers #define lws_smd_ss_rx_forward lws_smd_sspc_rx_forward #define lws_ss_tag lws_sspc_tag +#define _lws_fi_user_ss_fi _lws_fi_user_sspc_fi #endif diff --git a/include/libwebsockets/lws-secure-streams.h b/include/libwebsockets/lws-secure-streams.h index d00cc00f3..718c5cda9 100644 --- a/include/libwebsockets/lws-secure-streams.h +++ b/include/libwebsockets/lws-secure-streams.h @@ -354,7 +354,7 @@ typedef struct lws_ss_info { #endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_ctx_t *fi; + lws_fi_ctx_t fic; /**< Attach external Fault Injection context to the stream, hierarchy * is ss->context */ #endif diff --git a/lib/core-net/adopt.c b/lib/core-net/adopt.c index 98c92d348..fc3f205ba 100644 --- a/lib/core-net/adopt.c +++ b/lib/core-net/adopt.c @@ -115,7 +115,7 @@ lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi, const char *de static struct lws * lws_adopt_descriptor_vhost1(struct lws_vhost *vh, lws_adoption_type type, const char *vh_prot_name, struct lws *parent, - void *opaque) + void *opaque, const char *fi_wsi_name) { struct lws_context *context = vh->context; struct lws_context_per_thread *pt; @@ -139,6 +139,15 @@ lws_adopt_descriptor_vhost1(struct lws_vhost *vh, lws_adoption_type type, return NULL; } + /* bring in specific fault injection rules early */ + lws_fi_inherit_copy(&new_wsi->fic, &context->fic, "wsi", fi_wsi_name); + + if (lws_fi(&new_wsi->fic, "createfail")) { + lws_fi_destroy(&new_wsi->fic); + lws_context_unlock(vh->context); + return NULL; + } + new_wsi->a.opaque_user_data = opaque; pt = &context->pt[(int)new_wsi->tsi]; @@ -201,6 +210,8 @@ bail: if (new_wsi->user_space) lws_free(new_wsi->user_space); + lws_fi_destroy(&new_wsi->fic); + lws_vhost_unbind_wsi(new_wsi); lws_free(new_wsi); @@ -509,7 +520,7 @@ lws_adopt_descriptor_vhost_via_info(const lws_adopt_desc_t *info) new_wsi = lws_adopt_descriptor_vhost1(info->vh, info->type, info->vh_prot_name, info->parent, - info->opaque); + info->opaque, info->fi_wsi_name); if (!new_wsi) { if (info->type & LWS_ADOPT_SOCKET) compatible_close(info->fd.sockfd); @@ -772,7 +783,7 @@ struct lws * lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, int flags, const char *protocol_name, const char *ifname, struct lws *parent_wsi, void *opaque, - const lws_retry_bo_t *retry_policy) + const lws_retry_bo_t *retry_policy, const char *fi_wsi_name) { #if !defined(LWS_PLAT_OPTEE) struct lws *wsi; @@ -782,8 +793,10 @@ lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port, /* create the logical wsi without any valid fd */ - wsi = lws_adopt_descriptor_vhost1(vhost, LWS_ADOPT_SOCKET | LWS_ADOPT_RAW_SOCKET_UDP, - protocol_name, parent_wsi, opaque); + wsi = lws_adopt_descriptor_vhost1(vhost, LWS_ADOPT_SOCKET | + LWS_ADOPT_RAW_SOCKET_UDP, + protocol_name, parent_wsi, opaque, + fi_wsi_name); if (!wsi) { lwsl_err("%s: udp wsi creation failed\n", __func__); goto bail; diff --git a/lib/core-net/client/connect.c b/lib/core-net/client/connect.c index edc4b9a5b..4c632b504 100644 --- a/lib/core-net/client/connect.c +++ b/lib/core-net/client/connect.c @@ -86,7 +86,7 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) struct lws *wsi, *safe = NULL; const struct lws_protocols *p; const char *cisin[CIS_COUNT]; - struct lws_vhost *vh; + struct lws_vhost *vh, *v; int #if LWS_MAX_SMP > 1 tid = 0, @@ -151,15 +151,41 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) vh = vh->vhost_next; } +#if defined(LWS_WITH_SECURE_STREAMS) + /* any of these imply we are a client wsi bound to an SS, which + * implies our opaque user ptr is the ss (or sspc if PROXY_LINK) handle + */ + wsi->for_ss = !!(i->ssl_connection & (LCCSCF_SECSTREAM_CLIENT | LCCSCF_SECSTREAM_PROXY_LINK | LCCSCF_SECSTREAM_PROXY_ONWARD)); + wsi->client_bound_sspc = !!(i->ssl_connection & LCCSCF_SECSTREAM_PROXY_LINK); /* so wsi close understands need to remove sspc ptr to wsi */ + wsi->client_proxy_onward = !!(i->ssl_connection & LCCSCF_SECSTREAM_PROXY_ONWARD); +#endif + #if defined(LWS_WITH_SYS_FAULT_INJECTION) - wsi->fi.name = "wsi"; - wsi->fi.parent = &vh->fi; - if (i->fi) + wsi->fic.name = "wsi"; + if (i->fic.fi_owner.count) /* - * This moves all the lws_fi_t from info->fi to the vhost fi, + * This moves all the lws_fi_t from i->fi to the vhost fi, * leaving it empty */ - lws_fi_import(&wsi->fi, i->fi); + lws_fi_import(&wsi->fic, &i->fic); + + lws_fi_inherit_copy(&wsi->fic, &i->context->fic, "wsi", i->fi_wsi_name); + + if (lws_fi(&wsi->fic, "createfail")) + goto bail; + +#if defined(LWS_WITH_SECURE_STREAMS) +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) + if (wsi->client_bound_sspc) { + lws_sspc_handle_t *fih = (lws_sspc_handle_t *)i->opaque_user_data; + lws_fi_inherit_copy(&wsi->fic, &fih->fic, "wsi", NULL); + } +#endif + if (wsi->for_ss) { + lws_ss_handle_t *fih = (lws_ss_handle_t *)i->opaque_user_data; + lws_fi_inherit_copy(&wsi->fic, &fih->fic, "wsi", NULL); + } +#endif #endif /* @@ -187,7 +213,7 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) wsi->conn_validity_wakesuspend = 1; if (!i->vhost) { - struct lws_vhost *v = i->context->vhost_list; + v = i->context->vhost_list; if (!v) { /* coverity */ lwsl_err("%s: no vhost\n", __func__); @@ -195,9 +221,15 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) } if (!strcmp(v->name, "system")) v = v->vhost_next; - lws_vhost_bind_wsi(v, wsi); } else - lws_vhost_bind_wsi(i->vhost, wsi); + v = i->vhost; + + lws_vhost_bind_wsi(v, wsi); + +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + /* additionally inerit from vhost we bound to */ + lws_fi_inherit_copy(&wsi->fic, &v->fic, "wsi", i->fi_wsi_name); +#endif if (!wsi->a.vhost) { lwsl_err("%s: No vhost in the context\n", __func__); @@ -332,12 +364,6 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) i->opaque_user_data; #if defined(LWS_WITH_SECURE_STREAMS) - /* any of these imply we are a client wsi bound to an SS, which - * implies our opaque user ptr is the ss (or sspc if PROXY_LINK) handle - */ - wsi->for_ss = !!(i->ssl_connection & (LCCSCF_SECSTREAM_CLIENT | LCCSCF_SECSTREAM_PROXY_LINK | LCCSCF_SECSTREAM_PROXY_ONWARD)); - wsi->client_bound_sspc = !!(i->ssl_connection & LCCSCF_SECSTREAM_PROXY_LINK); /* so wsi close understands need to remove sspc ptr to wsi */ - wsi->client_proxy_onward = !!(i->ssl_connection & LCCSCF_SECSTREAM_PROXY_ONWARD); if (wsi->for_ss) { /* it's related to ss... the options are @@ -515,6 +541,7 @@ bail1: lws_free_set_NULL(wsi->stash); bail: + lws_fi_destroy(&wsi->fic); lws_free(wsi); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) bail2: diff --git a/lib/core-net/client/connect2.c b/lib/core-net/client/connect2.c index 97d619c0b..f1526551d 100644 --- a/lib/core-net/client/connect2.c +++ b/lib/core-net/client/connect2.c @@ -64,7 +64,10 @@ lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) #endif wsi->dns_reachability = 0; - n = getaddrinfo(ads, NULL, &hints, result); + if (lws_fi(&wsi->fic, "dnsfail")) + n = EAI_FAIL; + else + n = getaddrinfo(ads, NULL, &hints, result); #if defined(LWS_WITH_CONMON) wsi->conmon.ciu_dns = (lws_conmon_interval_us_t) @@ -99,7 +102,7 @@ lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) lws_snprintf(buckname, sizeof(buckname), "dns=\"unreachable %d\"", n); lws_metrics_hist_bump_priv_wsi(wsi, mth_conn_failures, buckname); #endif - lwsl_notice("%s: asking to recheck CPD in 1s\n", __func__); + lwsl_debug("%s: asking to recheck CPD in 1s\n", __func__); lws_system_cpd_start_defer(wsi->a.context, LWS_US_PER_SEC); } @@ -340,7 +343,10 @@ solo: #else /* this is either FAILED, CONTINUING, or already called connect_4 */ - n = lws_async_dns_query(wsi->a.context, wsi->tsi, ads, + if (lws_fi(&wsi->fic, "dnsfail")) + return lws_client_connect_3_connect(wsi, NULL, NULL, -4, NULL); + else + n = lws_async_dns_query(wsi->a.context, wsi->tsi, ads, LWS_ADNS_RECORD_A, lws_client_connect_3_connect, wsi, NULL); diff --git a/lib/core-net/client/connect3.c b/lib/core-net/client/connect3.c index 38d127b13..0236510fa 100644 --- a/lib/core-net/client/connect3.c +++ b/lib/core-net/client/connect3.c @@ -151,6 +151,9 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads, lws_dns_sort_t *curr; ssize_t plen = 0; lws_dll2_t *d; +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + int cfail; +#endif int m; /* @@ -433,7 +436,14 @@ ads_known: #endif wsi->socket_is_permanently_unusable = 0; - m = connect(wsi->desc.sockfd, (const struct sockaddr *)psa, (unsigned int)n); + +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + cfail = lws_fi(&wsi->fic, "connfail"); + if (cfail) + m = -1; + else +#endif + m = connect(wsi->desc.sockfd, (const struct sockaddr *)psa, (unsigned int)n); #if defined(LWS_WITH_CONMON) wsi->conmon_datum = lws_now_usecs(); @@ -450,6 +460,12 @@ ads_known: int errno_copy = LWS_ERRNO; +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + if (cfail) + /* fake an abnormal, fatal situation */ + errno_copy = 999; +#endif + lwsl_debug("%s: connect: errno: %d\n", __func__, errno_copy); if (errno_copy && @@ -481,10 +497,10 @@ ads_known: lws_sa46_write_numeric_address(&wsi->sa46_peer, nads, sizeof(nads)); - lwsl_info("%s: Connect failed: %s port %d\n", __func__, - nads, port); wsi->sa46_peer.sa4.sin_family = 0; + lwsl_info("%s: Connect failed: %s port %d (errno %d)\n", + __func__, nads, port, errno_copy); #if defined(LWS_WITH_UNIX_SOCK) } #endif diff --git a/lib/core-net/close.c b/lib/core-net/close.c index c58f6e3c3..a86c3b589 100644 --- a/lib/core-net/close.c +++ b/lib/core-net/close.c @@ -831,7 +831,7 @@ __lws_close_free_wsi_final(struct lws *wsi) #endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_destroy(&wsi->fi); + lws_fi_destroy(&wsi->fic); #endif __lws_wsi_remove_from_sul(wsi); diff --git a/lib/core-net/output.c b/lib/core-net/output.c index 72d1de0dc..a8ff396ad 100644 --- a/lib/core-net/output.c +++ b/lib/core-net/output.c @@ -114,7 +114,11 @@ lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) /* nope, send it on the socket directly */ - m = (unsigned int)lws_ssl_capable_write(wsi, buf, n); + if (lws_fi(&wsi->fic, "sendfail")) + m = (unsigned int)LWS_SSL_CAPABLE_ERROR; + else + m = (unsigned int)lws_ssl_capable_write(wsi, buf, n); + lwsl_info("%s: ssl_capable_write (%d) says %d\n", __func__, n, m); /* something got written, it can have been truncated now */ @@ -329,20 +333,11 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, size_t len) #if defined(LWS_WITH_UDP) if (lws_wsi_is_udp(wsi)) { - if (wsi->a.context->udp_loss_sim_tx_pc) { - uint16_t u16; - /* - * We should randomly drop some of these - */ - if (lws_get_random(wsi->a.context, &u16, 2) == 2 && - ((u16 * 100) / 0xffff) <= - wsi->a.context->udp_loss_sim_tx_pc) { - lwsl_warn("%s: dropping udp tx\n", __func__); - /* pretend it was sent */ - n = (int)(ssize_t)len; - goto post_send; - } + if (lws_fi(&wsi->fic, "udp_tx_loss")) { + /* pretend it was sent */ + n = (int)(ssize_t)len; + goto post_send; } if (lws_has_buffered_out(wsi)) diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index 126f6baa0..d933f0e86 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -469,7 +469,7 @@ struct lws_vhost { #endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_ctx_t fi; + lws_fi_ctx_t fic; /**< Fault Injection ctx for the vhost, hierarchy vhost->context */ #endif @@ -693,7 +693,7 @@ struct lws { #endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_ctx_t fi; + lws_fi_ctx_t fic; /**< Fault Injection ctx for the wsi, hierarchy wsi->vhost->context */ #endif diff --git a/lib/core-net/route.c b/lib/core-net/route.c index dd44d2fcb..efe08ba1b 100644 --- a/lib/core-net/route.c +++ b/lib/core-net/route.c @@ -159,9 +159,14 @@ _lws_route_remove(struct lws_context_per_thread *pt, lws_route_t *robj, int flag void _lws_route_table_empty(struct lws_context_per_thread *pt) { + + if (!pt->context) + return; + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, lws_dll2_get_head(&pt->context->routing_table)) { lws_route_t *rou = lws_container_of(d, lws_route_t, list); + lws_dll2_remove(&rou->list); lws_free(rou); diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index 529cc95f3..be48f1324 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -559,14 +559,16 @@ lws_create_vhost(struct lws_context *context, #endif int n; - - vh = lws_zalloc(sizeof(*vh) + if (lws_fi(&info->fic, "vh_create_oom")) + vh = NULL; + else + vh = lws_zalloc(sizeof(*vh) #if defined(LWS_WITH_EVENT_LIBS) + context->event_loop_ops->evlib_size_vh #endif , __func__); if (!vh) - return NULL; + goto early_bail; #if defined(LWS_WITH_EVENT_LIBS) vh->evlib_vh = (void *)&vh[1]; @@ -599,14 +601,17 @@ lws_create_vhost(struct lws_context *context, info->iface ? info->iface : "", info->port); #if defined(LWS_WITH_SYS_FAULT_INJECTION) - vh->fi.name = "vh"; - vh->fi.parent = &context->fi; - if (info->fi) + vh->fic.name = "vh"; + if (info->fic.fi_owner.count) /* * This moves all the lws_fi_t from info->fi to the vhost fi, * leaving it empty */ - lws_fi_import(&vh->fi, info->fi); + lws_fi_import(&vh->fic, &info->fic); + + lws_fi_inherit_copy(&vh->fic, &context->fic, "vh", vh->name); + if (lws_fi(&vh->fic, "vh_create_oom")) + goto bail; #endif #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) @@ -725,16 +730,19 @@ lws_create_vhost(struct lws_context *context, * - the ones that came from plugins * - his user protocols */ - lwsp = lws_zalloc(sizeof(struct lws_protocols) * + + if (lws_fi(&vh->fic, "vh_create_pcols_oom")) + lwsp = NULL; + else + lwsp = lws_zalloc(sizeof(struct lws_protocols) * ((unsigned int)vh->count_protocols + (unsigned int)abs_pcol_count + (unsigned int)sec_pcol_count + (unsigned int)context->plugin_protocol_count + - (unsigned int)fx + 1), - "vhost-specific plugin table"); + (unsigned int)fx + 1), "vh plugin table"); if (!lwsp) { lwsl_err("OOM\n"); - return NULL; + goto bail; } /* @@ -923,7 +931,10 @@ lws_create_vhost(struct lws_context *context, #ifdef LWS_WITH_ACCESS_LOG if (info->log_filepath) { - vh->log_fd = lws_open(info->log_filepath, + if (lws_fi(&vh->fic, "vh_create_access_log_open_fail")) + vh->log_fd = (int)LWS_INVALID_FILE; + else + vh->log_fd = lws_open(info->log_filepath, O_CREAT | O_APPEND | O_RDWR, 0600); if (vh->log_fd == (int)LWS_INVALID_FILE) { lwsl_err("unable to open log filepath %s\n", @@ -940,17 +951,22 @@ lws_create_vhost(struct lws_context *context, } else vh->log_fd = (int)LWS_INVALID_FILE; #endif - if (lws_context_init_server_ssl(info, vh)) { + if (lws_fi(&vh->fic, "vh_create_ssl_srv") || + lws_context_init_server_ssl(info, vh)) { lwsl_err("%s: lws_context_init_server_ssl failed\n", __func__); goto bail1; } - if (lws_context_init_client_ssl(info, vh)) { + if (lws_fi(&vh->fic, "vh_create_ssl_cli") || + lws_context_init_client_ssl(info, vh)) { lwsl_err("%s: lws_context_init_client_ssl failed\n", __func__); goto bail1; } #if defined(LWS_WITH_SERVER) lws_context_lock(context, __func__); - n = _lws_vhost_init_server(info, vh); + if (lws_fi(&vh->fic, "vh_create_srv_init")) + n = -1; + else + n = _lws_vhost_init_server(info, vh); lws_context_unlock(context); if (n < 0) { lwsl_err("init server failed\n"); @@ -978,7 +994,8 @@ lws_create_vhost(struct lws_context *context, /* for the case we are adding a vhost much later, after server init */ if (context->protocol_init_done) - if (lws_protocol_init(context)) { + if (lws_fi(&vh->fic, "vh_create_protocol_init") || + lws_protocol_init(context)) { lwsl_err("%s: lws_protocol_init failed\n", __func__); goto bail1; } @@ -990,10 +1007,13 @@ bail1: return NULL; -#ifdef LWS_WITH_ACCESS_LOG bail: + __lws_lc_untag(&vh->lc); + lws_fi_destroy(&vh->fic); lws_free(vh); -#endif + +early_bail: + lws_fi_destroy(&info->fic); return NULL; } @@ -1449,7 +1469,7 @@ __lws_vhost_destroy2(struct lws_vhost *vh) lws_dll2_remove(&vh->vh_being_destroyed_list); #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_destroy(&vh->fi); + lws_fi_destroy(&vh->fic); #endif __lws_lc_untag(&vh->lc); diff --git a/lib/core-net/wsi.c b/lib/core-net/wsi.c index ec1d5abfc..5bd30aaa7 100644 --- a/lib/core-net/wsi.c +++ b/lib/core-net/wsi.c @@ -240,6 +240,18 @@ __lws_wsi_create_with_role(struct lws_context *context, int tsi, // lwsl_debug("%s: tsi %d: role: %s\n", __func__, tsi, // ops ? ops->name : "none"); +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + lws_xos_init(&wsi->fic.xos, lws_xos(&context->fic.xos)); +#endif + + lws_fi_inherit_copy(&wsi->fic, &context->fic, "wsi", NULL); + + if (lws_fi(&wsi->fic, "createfail")) { + lws_fi_destroy(&wsi->fic); + lws_free(wsi); + return NULL; + } + return wsi; } diff --git a/lib/core/context.c b/lib/core/context.c index 3ca945f56..78ab969c5 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -323,6 +323,9 @@ static const char * const opts_str = #if defined(LWS_WITH_MBEDTLS) "MbedTLS " #endif +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + "FLTINJ " +#endif #if defined(LWS_WITH_SYS_ASYNC_DNS) "ASYNC_DNS " #endif @@ -399,6 +402,9 @@ lws_create_context(const struct lws_context_creation_info *info) char fatal_exit_defer = 0; #endif + if (lws_fi(&info->fic, "ctx_createfail1")) + goto early_bail; + if (lpf) { lpf+= 2; #if defined(LWS_WITH_SYS_ASYNC_DNS) @@ -424,7 +430,7 @@ lws_create_context(const struct lws_context_creation_info *info) lwsl_notice("%s%s\n", opts_str, s); if (lws_plat_context_early_init()) - return NULL; + goto early_bail; #if defined(LWS_WITH_NETWORK) if (info->count_threads) @@ -482,7 +488,7 @@ lws_create_context(const struct lws_context_creation_info *info) map[n].name, NULL, NULL)) ok = 1; - if (!ok) { + if (!ok || lws_fi(&info->fic, "ctx_createfail_plugin_init")) { lwsl_err("%s: failed to load %s\n", __func__, map[n].name); goto bail; @@ -493,7 +499,8 @@ lws_create_context(const struct lws_context_creation_info *info) fatal_exit_defer = !!info->foreign_loops; #endif - if (!evlib_plugin_list) { + if (!evlib_plugin_list || + lws_fi(&info->fic, "ctx_createfail_evlib_plugin")) { lwsl_err("%s: unable to load evlib plugin %s\n", __func__, map[n].name); @@ -570,7 +577,7 @@ lws_create_context(const struct lws_context_creation_info *info) #endif /* not with ev plugins */ - if (!plev) + if (!plev || lws_fi(&info->fic, "ctx_createfail_evlib_sel")) goto fail_event_libs; #if defined(LWS_WITH_NETWORK) @@ -581,9 +588,12 @@ lws_create_context(const struct lws_context_creation_info *info) #endif context = lws_zalloc(size, "context"); - if (!context) { - lwsl_err("No memory for websocket context\n"); - return NULL; + if (!context || lws_fi(&info->fic, "ctx_createfail_oom_ctx")) { +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + lws_free(context); +#endif + lwsl_err("No memory for lws_context\n"); + goto early_bail; } #if defined(LWS_WITH_NETWORK) @@ -609,13 +619,13 @@ lws_create_context(const struct lws_context_creation_info *info) context->pt_serv_buf_size = (unsigned int)s1; #if defined(LWS_WITH_SYS_FAULT_INJECTION) - context->fi.name = "ctx"; - if (info->fi) + context->fic.name = "ctx"; + if (info->fic.fi_owner.count) /* * This moves all the lws_fi_t from info->fi to the context fi, * leaving it empty, so no injection added to default vhost */ - lws_fi_import(&context->fi, info->fi); + lws_fi_import(&context->fic, &info->fic); #endif @@ -635,16 +645,6 @@ lws_create_context(const struct lws_context_creation_info *info) #endif #endif -#if defined(LWS_WITH_UDP) - context->udp_loss_sim_tx_pc = info->udp_loss_sim_tx_pc; - context->udp_loss_sim_rx_pc = info->udp_loss_sim_rx_pc; - - if (context->udp_loss_sim_tx_pc || context->udp_loss_sim_rx_pc) - lwsl_warn("%s: simulating udp loss tx: %d%%, rx: %d%%\n", - __func__, context->udp_loss_sim_tx_pc, - context->udp_loss_sim_rx_pc); -#endif - #if defined(LWS_WITH_NETWORK) context->lcg[LWSLCG_WSI].tag_prefix = "wsi"; context->lcg[LWSLCG_VHOST].tag_prefix = "vh"; @@ -800,8 +800,9 @@ lws_create_context(const struct lws_context_creation_info *info) #endif /* if he gave us names, set the uid / gid */ - if (lws_plat_drop_app_privileges(context, 0)) - goto bail; + if (lws_plat_drop_app_privileges(context, 0) || + lws_fi(&context->fic, "ctx_createfail_privdrop")) + goto free_context_fail2; #if defined(LWS_WITH_TLS) && defined(LWS_WITH_NETWORK) #if defined(LWS_WITH_MBEDTLS) @@ -900,7 +901,7 @@ lws_create_context(const struct lws_context_creation_info *info) if (n == -1) { lwsl_err("Get RLIMIT_NOFILE failed!\n"); - goto free_context_fail; + goto free_context_fail2; } context->max_fds = (unsigned int)rt.rlim_cur; #else @@ -920,11 +921,12 @@ lws_create_context(const struct lws_context_creation_info *info) context->max_fds = (unsigned int)l; } #endif - if ((int)context->max_fds < 0) { + if ((int)context->max_fds < 0 || + lws_fi(&context->fic, "ctx_createfail_maxfds")) { lwsl_err("%s: problem getting process max files\n", __func__); - goto free_context_fail; + goto free_context_fail2; } #endif @@ -1094,7 +1096,7 @@ lws_create_context(const struct lws_context_creation_info *info) if (!info->ka_interval && info->ka_time > 0) { lwsl_err("info->ka_interval can't be 0 if ka_time used\n"); - return NULL; + goto free_context_fail; } #if defined(LWS_WITH_PEER_LIMITS) @@ -1120,9 +1122,13 @@ lws_create_context(const struct lws_context_creation_info *info) n = (int)(sizeof(struct lws_pollfd) * context->count_threads * context->fd_limit_per_thread); context->pt[0].fds = lws_zalloc((unsigned int)n, "fds table"); - if (context->pt[0].fds == NULL) { + if (context->pt[0].fds == NULL || + lws_fi(&context->fic, "ctx_createfail_oom_fds")) { +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + lws_free(context->pt[0].fds); +#endif lwsl_err("OOM allocating %d fds\n", context->max_fds); - goto bail; + goto free_context_fail; } #endif @@ -1163,14 +1169,21 @@ lws_create_context(const struct lws_context_creation_info *info) * loop and if libuv, have to take care about how to unpick them... */ - if (lws_plat_init(context, info)) + if (lws_plat_init(context, info) || + lws_fi(&context->fic, "ctx_createfail_plat_init")) goto bail_libuv_aware; #if defined(LWS_WITH_NETWORK) + + if (lws_fi(&context->fic, "ctx_createfail_evlib_init")) + goto bail_libuv_aware; + if (context->event_loop_ops->init_context) if (context->event_loop_ops->init_context(context, info)) goto bail_libuv_aware; + if (lws_fi(&context->fic, "ctx_createfail_evlib_pt")) + goto bail_libuv_aware; if (context->event_loop_ops->init_pt) for (n = 0; n < context->count_threads; n++) { @@ -1255,7 +1268,10 @@ lws_create_context(const struct lws_context_creation_info *info) ii.pprotocols = pp; ii.port = CONTEXT_PORT_NO_LISTEN; - vh = lws_create_vhost(context, &ii); + if (lws_fi(&context->fic, "ctx_createfail_sys_vh")) + vh = NULL; + else + vh = lws_create_vhost(context, &ii); if (!vh) { lwsl_err("%s: failed to create system vhost\n", __func__); @@ -1264,7 +1280,8 @@ lws_create_context(const struct lws_context_creation_info *info) context->vhost_system = vh; - if (lws_protocol_init_vhost(vh, NULL)) { + if (lws_protocol_init_vhost(vh, NULL) || + lws_fi(&context->fic, "ctx_createfail_sys_vh_init")) { lwsl_err("%s: failed to init system vhost\n", __func__); goto bail_libuv_aware; } @@ -1309,15 +1326,17 @@ lws_create_context(const struct lws_context_creation_info *info) * if he's not saying he'll make his own vhosts later then act * compatibly and make a default vhost using the data in the info */ - if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) - if (!lws_create_vhost(context, info)) { + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) { + if (!lws_create_vhost(context, info) || + lws_fi(&context->fic, "ctx_createfail_def_vh")) { lwsl_err("Failed to create default vhost\n"); #if defined(LWS_WITH_PEER_LIMITS) lws_free_set_NULL(context->pl_hash_table); #endif - goto fail_clean_pipes; + goto bail; } + } #if defined(LWS_WITH_SECURE_STREAMS) @@ -1330,16 +1349,25 @@ lws_create_context(const struct lws_context_creation_info *info) assert(lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)); - if (lws_ss_policy_parse_begin(context, 0)) + if (lws_ss_policy_parse_begin(context, 0) || + lws_fi(&context->fic, "ctx_createfail_ss_pol1")) { +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + lws_ss_policy_parse_abandon(context); +#endif goto bail_libuv_aware; + } n = lws_ss_policy_parse(context, (uint8_t *)context->pss_policies_json, strlen(context->pss_policies_json)); - if (n != LEJP_CONTINUE && n < 0) + if ((n != LEJP_CONTINUE && n < 0) || + lws_fi(&context->fic, "ctx_createfail_ss_pol2")) { + lws_ss_policy_parse_abandon(context); goto bail_libuv_aware; + } - if (lws_ss_policy_set(context, "hardcoded")) { + if (lws_ss_policy_set(context, "hardcoded") || + lws_fi(&context->fic, "ctx_createfail_ss_pol3")) { lwsl_err("%s: policy set failed\n", __func__); goto bail_libuv_aware; } @@ -1348,7 +1376,8 @@ lws_create_context(const struct lws_context_creation_info *info) if (context->pss_policies) { /* user code set the policy objects directly, no parsing step */ - if (lws_ss_policy_set(context, "hardcoded")) { + if (lws_ss_policy_set(context, "hardcoded") || + lws_fi(&context->fic, "ctx_createfail_ss_pol3")) { lwsl_err("%s: policy set failed\n", __func__); goto bail_libuv_aware; } @@ -1368,7 +1397,8 @@ lws_create_context(const struct lws_context_creation_info *info) * listening, we don't want the power for anything else */ if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) - if (lws_plat_drop_app_privileges(context, 1)) + if (lws_plat_drop_app_privileges(context, 1) || + lws_fi(&context->fic, "ctx_createfail_privdrop")) goto bail_libuv_aware; #if defined(LWS_WITH_SYS_STATE) @@ -1391,6 +1421,12 @@ lws_create_context(const struct lws_context_creation_info *info) return context; +early_bail: + lws_fi_destroy(&info->fic); + + return NULL; + +#if 0 #if defined(LWS_WITH_NETWORK) fail_clean_pipes: @@ -1410,11 +1446,15 @@ fail_clean_pipes: return NULL; #endif +#endif +#if defined(LWS_WITH_NETWORK) bail: + lws_fi_destroy(&info->fic); lws_context_destroy(context); return NULL; +#endif bail_libuv_aware: lws_context_destroy(context); @@ -1429,7 +1469,22 @@ fail_event_libs: lwsl_err("Requested event library support not configured\n"); #endif +#if defined(LWS_WITH_NETWORK) free_context_fail: + if (context) { +#if defined(LWS_WITH_SYS_SMD) + _lws_smd_destroy(context); +#endif + } +#endif +free_context_fail2: + if (context) { +#if defined(LWS_WITH_SYS_METRICS) + lws_metrics_destroy(context); +#endif + lws_fi_destroy(&context->fic); + } + lws_fi_destroy(&info->fic); lws_free(context); return NULL; @@ -1912,6 +1967,7 @@ next: for (n = 0; n < context->count_threads; n++) { struct lws_context_per_thread *pt = &context->pt[n]; + (void)pt; #if defined(LWS_WITH_SEQUENCER) lws_seq_destroy_all_on_pt(pt); @@ -2074,7 +2130,7 @@ next: #endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_destroy(&context->fi); + lws_fi_destroy(&context->fic); #endif lws_free(context); diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index 935f30d16..c46408590 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -1335,13 +1335,18 @@ lws_cmdline_option(int argc, const char **argv, const char *val) static const char * const builtins[] = { "-d", -#if defined(LWS_WITH_UDP) - "--udp-tx-loss", - "--udp-rx-loss", -#endif + "--fault-injection", + "--fault-seed", "--ignore-sigterm" }; +enum opts { + OPT_DEBUGLEVEL, + OPT_FAULTINJECTION, + OPT_FAULT_SEED, + OPT_IGNORE_SIGTERM, +}; + #if !defined(LWS_PLAT_FREERTOS) static void lws_sigterm_catch(int sig) @@ -1355,6 +1360,9 @@ lws_cmdline_option_handle_builtin(int argc, const char **argv, { const char *p; int n, m, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + uint64_t seed = (uint64_t)lws_now_usecs(); +#endif for (n = 0; n < (int)LWS_ARRAY_SIZE(builtins); n++) { p = lws_cmdline_option(argc, argv, builtins[n]); @@ -1364,20 +1372,24 @@ lws_cmdline_option_handle_builtin(int argc, const char **argv, m = atoi(p); switch (n) { - case 0: + case OPT_DEBUGLEVEL: logs = m; break; -#if defined(LWS_WITH_UDP) - case 1: - info->udp_loss_sim_tx_pc = (uint8_t)m; - break; - case 2: - info->udp_loss_sim_rx_pc = (uint8_t)m; - break; - case 3: -#else - case 1: + + case OPT_FAULTINJECTION: +#if !defined(LWS_WITH_SYS_FAULT_INJECTION) + lwsl_err("%s: FAULT_INJECTION not built\n", __func__); #endif + lws_fi_deserialize(&info->fic, p); + break; + + case OPT_FAULT_SEED: +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + seed = (uint64_t)atoll(p); +#endif + break; + + case OPT_IGNORE_SIGTERM: #if !defined(LWS_PLAT_FREERTOS) signal(SIGTERM, lws_sigterm_catch); #endif @@ -1385,7 +1397,16 @@ lws_cmdline_option_handle_builtin(int argc, const char **argv, } } +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + lws_xos_init(&info->fic.xos, seed); +#endif lws_set_log_level(logs, NULL); + +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + if (info->fic.fi_owner.count) + lwsl_notice("%s: Fault Injection seed %llu\n", __func__, + (unsigned long long)seed); +#endif } diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index 2539d7826..d993d746c 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -524,7 +524,7 @@ struct lws_context { #endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_ctx_t fi; + lws_fi_ctx_t fic; /**< Toplevel Fault Injection ctx */ #endif @@ -729,8 +729,6 @@ struct lws_context { uint16_t us_wait_resolution; uint8_t max_fi; - uint8_t udp_loss_sim_tx_pc; - uint8_t udp_loss_sim_rx_pc; uint8_t captive_portal_detect; uint8_t captive_portal_detect_type; diff --git a/lib/misc/CMakeLists.txt b/lib/misc/CMakeLists.txt index d8979f918..cd040aa58 100644 --- a/lib/misc/CMakeLists.txt +++ b/lib/misc/CMakeLists.txt @@ -33,6 +33,7 @@ include_directories(.) list(APPEND SOURCES misc/base64-decode.c + misc/prng.c misc/lws-ring.c) if (LWS_WITH_FTS) diff --git a/lib/misc/prng.c b/lib/misc/prng.c new file mode 100644 index 000000000..8f05fadb9 --- /dev/null +++ b/lib/misc/prng.c @@ -0,0 +1,80 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2021 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * After Public Domain implementations + * + * https://github.com/svaarala/duktape/tree/master/misc + */ + +#include + +static inline uint64_t rol64(uint64_t x, int k) +{ + return (x << k) | (x >> (64 - k)); +} + +uint64_t +lws_xos(struct lws_xos *xos) +{ + uint64_t *s = &xos->s[0]; + uint64_t const result = rol64(s[1] * 5, 7) * 9; + uint64_t const c = s[1] << 17; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= c; + s[3] = rol64(s[3], 45); + + return result; +} + +static uint64_t +splitmix64(uint64_t *s) +{ + uint64_t r = *s; + + *s = r + 0x9E3779B97F4A7C15ull; + + r = (r ^ (r >> 30)) * 0xBF58476D1CE4E5B9ull; + r = (r ^ (r >> 27)) * 0x94D049BB133111EBull; + + return r ^ (r >> 31); +} + +void +lws_xos_init(struct lws_xos *xos, uint64_t seed) +{ + int n; + + for (n = 0; n < 4; n++) + xos->s[n] = splitmix64(&seed); +} + +int +lws_xos_percent(struct lws_xos *xos, int percent) +{ + return (int)(lws_xos(xos) % 100) < percent; +} diff --git a/lib/plat/unix/unix-service.c b/lib/plat/unix/unix-service.c index d5288de18..7a3808952 100644 --- a/lib/plat/unix/unix-service.c +++ b/lib/plat/unix/unix-service.c @@ -54,6 +54,7 @@ _lws_plat_service_forced_tsi(struct lws_context *context, int tsi) __func__, m); return -1; } + /* if something closed, retry this slot since may have been * swapped with end fd */ if (m && pt->fds[n].fd != fd) diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 02b8d6a1b..e3d9dc5b1 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -1459,6 +1459,9 @@ lws_h2_parse_end_of_frame(struct lws *wsi) /* pass on the initial headers to SID 1 */ h2n->swsi->http.ah = wsi->http.ah; +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + lws_fi_import(&h2n->swsi->fic, &wsi->fic); +#endif h2n->swsi->client_mux_substream = 1; h2n->swsi->client_h2_alpn = 1; #if defined(LWS_WITH_CLIENT) diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index c6b4bf632..6c635c5a3 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -175,17 +175,23 @@ done_list: #endif for (m = 0; m < limit; m++) { + + if (lws_fi(&vhost->fic, "listenskt")) { + sockfd = LWS_SOCK_INVALID; + } else { + #ifdef LWS_WITH_UNIX_SOCK - if (LWS_UNIX_SOCK_ENABLED(vhost)) - sockfd = socket(AF_UNIX, SOCK_STREAM, 0); - else + if (LWS_UNIX_SOCK_ENABLED(vhost)) + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + else #endif #ifdef LWS_WITH_IPV6 - if (LWS_IPV6_ENABLED(vhost)) - sockfd = socket(AF_INET6, SOCK_STREAM, 0); - else + if (LWS_IPV6_ENABLED(vhost)) + sockfd = socket(AF_INET6, SOCK_STREAM, 0); + else #endif - sockfd = socket(AF_INET, SOCK_STREAM, 0); + sockfd = socket(AF_INET, SOCK_STREAM, 0); + } if (sockfd == LWS_SOCK_INVALID) { lwsl_err("ERROR opening socket\n"); diff --git a/lib/roles/raw-skt/ops-raw-skt.c b/lib/roles/raw-skt/ops-raw-skt.c index cbe927cf4..ff01fd3ff 100644 --- a/lib/roles/raw-skt/ops-raw-skt.c +++ b/lib/roles/raw-skt/ops-raw-skt.c @@ -142,20 +142,9 @@ rops_handle_POLLIN_raw_skt(struct lws_context_per_thread *pt, struct lws *wsi, } #if defined(LWS_WITH_UDP) - if (wsi->a.context->udp_loss_sim_rx_pc) { - uint16_t u16; - /* - * We should randomly drop some of these - */ - - if (lws_get_random(wsi->a.context, &u16, 2) == 2 && - ((u16 * 100) / 0xffff) <= - wsi->a.context->udp_loss_sim_rx_pc) { - lwsl_warn("%s: dropping udp rx\n", __func__); - /* pretend it was handled */ - n = ebuf.len; - goto post_rx; - } + if (lws_fi(&wsi->fic, "udp_rx_loss")) { + n = ebuf.len; + goto post_rx; } #endif diff --git a/lib/secure-streams/policy-json.c b/lib/secure-streams/policy-json.c index 180de3207..c2fd361ce 100644 --- a/lib/secure-streams/policy-json.c +++ b/lib/secure-streams/policy-json.c @@ -1115,6 +1115,21 @@ int lws_ss_policy_parse_abandon(struct lws_context *context) { struct policy_cb_args *args = (struct policy_cb_args *)context->pol_args; + lws_ss_x509_t *x; + + x = args->heads[LTY_X509].x; + while (x) { + /* + * Free all the client DER buffers now they have been parsed + * into tls library X.509 objects + */ + if (!x->keep) { /* used for server */ + lws_free((void *)x->ca_der); + x->ca_der = NULL; + } + + x = x->next; + } lejp_destruct(&args->jctx); lwsac_free(&args->ac); diff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h index 7fe4ff055..2ce42146d 100644 --- a/lib/secure-streams/private-lib-secure-streams.h +++ b/lib/secure-streams/private-lib-secure-streams.h @@ -59,7 +59,7 @@ typedef struct lws_ss_handle { struct lws_dll2 cli_list; /**< same server clients list */ #endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_ctx_t fi; /**< Fault Injection context */ + lws_fi_ctx_t fic; /**< Fault Injection context */ #endif struct lws_dll2_owner src_list; /**< sink's list of bound sources */ @@ -285,7 +285,7 @@ typedef struct lws_sspc_handle { struct lws_ss_serialization_parser parser; #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_ctx_t fi; /**< Fault Injection context */ + lws_fi_ctx_t fic; /**< Fault Injection context */ #endif lws_dll2_owner_t metadata_owner; @@ -402,7 +402,7 @@ lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, int *flags); int -lws_ss_serialize_state(struct lws_dsh *dsh, lws_ss_constate_t state, +lws_ss_serialize_state(struct lws *wsi, struct lws_dsh *dsh, lws_ss_constate_t state, lws_ss_tx_ordinal_t ack); void diff --git a/lib/secure-streams/secure-streams-client.c b/lib/secure-streams/secure-streams-client.c index 2edfc18b3..9c877fad4 100644 --- a/lib/secure-streams/secure-streams-client.c +++ b/lib/secure-streams/secure-streams-client.c @@ -174,7 +174,7 @@ callback_sspc_client(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_RAW_CONNECTED: - if (!h) + if (!h || lws_fi(&h->fic, "sspc_fail_on_linkup")) return -1; lwsl_info("%s: CONNECTED (%s)\n", __func__, h->ssi.streamtype); @@ -234,9 +234,18 @@ callback_sspc_client(struct lws *wsi, enum lws_callback_reasons reason, return -1; } - n = lws_ss_deserialize_parse(&h->parser, lws_get_context(wsi), - h->dsh, in, len, &h->state, h, - (lws_ss_handle_t **)m, &h->ssi, 1); + if (lws_fi(&h->fic, "sspc_fake_rxparse_disconnect_me")) + n = LWSSSSRET_DISCONNECT_ME; + else + if (lws_fi(&h->fic, "sspc_fake_rxparse_destroy_me")) + n = LWSSSSRET_DESTROY_ME; + else + n = lws_ss_deserialize_parse(&h->parser, + lws_get_context(wsi), + h->dsh, in, len, + &h->state, h, + (lws_ss_handle_t **)m, + &h->ssi, 1); switch (n) { case LWSSSSRET_OK: break; @@ -476,7 +485,10 @@ do_write_nz: break; do_write: - n = lws_write(wsi, (uint8_t *)cp, (unsigned int)n, LWS_WRITE_RAW); + if (lws_fi(&h->fic, "sspc_link_write_fail")) + n = -1; + else + n = lws_write(wsi, (uint8_t *)cp, (unsigned int)n, LWS_WRITE_RAW); if (n < 0) { lwsl_notice("%s: WRITEABLE: %d\n", __func__, n); @@ -530,20 +542,32 @@ lws_sspc_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, * and the streamname */ h = malloc(sizeof(lws_sspc_handle_t) + ssi->user_alloc + - strlen(ssi->streamtype) + 1); + strlen(ssi->streamtype) + 1); if (!h) return 1; memset(h, 0, sizeof(*h)); - __lws_lc_tag(&context->lcg[LWSLCG_SSP_CLIENT], &h->lc, ssi->streamtype); - #if defined(LWS_WITH_SYS_FAULT_INJECTION) - h->fi.name = "sspc"; - h->fi.parent = &context->fi; - if (ssi->fi) - lws_fi_import(&h->fi, ssi->fi); + h->fic.name = "sspc"; + lws_xos_init(&h->fic.xos, lws_xos(&context->fic.xos)); + if (ssi->fic.fi_owner.count) + lws_fi_import(&h->fic, &ssi->fic); + + lws_fi_inherit_copy(&h->fic, &context->fic, "ss", ssi->streamtype); #endif + if (lws_fi(&h->fic, "sspc_create_oom")) { + /* + * We have to do this a litte later, so we can cleanly inherit + * the OOM pieces and drain the info fic + */ + lws_fi_destroy(&h->fic); + free(h); + return 1; + } + + __lws_lc_tag(&context->lcg[LWSLCG_SSP_CLIENT], &h->lc, ssi->streamtype); + memcpy(&h->ssi, ssi, sizeof(*ssi)); ua = (uint8_t *)&h[1]; memset(ua, 0, ssi->user_alloc); @@ -633,7 +657,7 @@ lws_sspc_destroy(lws_sspc_handle_t **ph) } #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_destroy(&h->fi); + lws_fi_destroy(&h->fic); #endif lws_sul_cancel(&h->sul_retry); @@ -823,7 +847,10 @@ _lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name, * We have to stash the metadata and pass it to the proxy */ - md = lws_malloc(sizeof(*md) + len, "set metadata"); + if (lws_fi(&h->fic, "sspc_fail_metadata_set")) + md = NULL; + else + md = lws_malloc(sizeof(*md) + len, "set metadata"); if (!md) { lwsl_err("%s: OOM\n", __func__); diff --git a/lib/secure-streams/secure-streams-process.c b/lib/secure-streams/secure-streams-process.c index acd55b4e4..dc24a6657 100644 --- a/lib/secure-streams/secure-streams-process.c +++ b/lib/secure-streams/secure-streams-process.c @@ -91,6 +91,8 @@ __lws_ss_proxy_bind_ss_to_conn_wsi(void *parconn, size_t dsh_size) pt = &conn->wsi->a.context->pt[(int)conn->wsi->tsi]; + if (lws_fi(&conn->ss->fic, "ssproxy_dsh_create_oom")) + return -1; conn->dsh = lws_dsh_create(&pt->ss_dsh_owner, dsh_size, 2); if (!conn->dsh) return -1; @@ -100,7 +102,7 @@ __lws_ss_proxy_bind_ss_to_conn_wsi(void *parconn, size_t dsh_size) return 0; } -/* secure streams payload interface */ +/* Onward secure streams payload interface */ static lws_ss_state_return_t ss_proxy_onward_rx(void *userobj, const uint8_t *buf, size_t len, int flags) @@ -120,9 +122,22 @@ ss_proxy_onward_rx(void *userobj, const uint8_t *buf, size_t len, int flags) flags |= LWSSS_FLAG_RIDESHARE; } - n = lws_ss_serialize_rx_payload(m->conn->dsh, buf, len, flags, rsp); + /* + * Apply SSS framing around this chunk of RX and stash it in the dsh + * in ss -> proxy [ -> client] direction. This can fail... + */ + + if (lws_fi(&m->ss->fic, "ssproxy_dsh_rx_queue_oom")) + n = 1; + else + n = lws_ss_serialize_rx_payload(m->conn->dsh, buf, len, + flags, rsp); if (n) - return n; + /* + * We couldn't buffer this rx, eg due to OOM, let's escalate it + * to be a "loss of connection", which it basically is... + */ + return LWSSSSRET_DISCONNECT_ME; /* * Manage rx flow on the SS (onward) side according to our situation @@ -141,7 +156,7 @@ ss_proxy_onward_rx(void *userobj, const uint8_t *buf, size_t len, int flags) if (m->conn->wsi) /* if possible, request client conn write */ lws_callback_on_writable(m->conn->wsi); - return 0; + return LWSSSSRET_OK; } /* @@ -221,6 +236,8 @@ ss_proxy_onward_state(void *userobj, void *sh, __func__, lws_ss_tag(m->ss), (unsigned long)dsh_size); + /* this includes ssproxy_dsh_create_oom fault generation */ + if (__lws_ss_proxy_bind_ss_to_conn_wsi(m->conn, dsh_size)) { /* failed to allocate the dsh */ @@ -257,7 +274,12 @@ ss_proxy_onward_state(void *userobj, void *sh, return LWSSSSRET_OK; } - lws_ss_serialize_state(m->conn->dsh, state, ack); + if (lws_ss_serialize_state(m->conn->wsi, m->conn->dsh, state, ack)) + /* + * Failed to alloc state packet that we want to send in dsh, + * we will lose coherence and have to disconnect the link + */ + return LWSSSSRET_DISCONNECT_ME; if (m->conn->wsi) /* if possible, request client conn write */ lws_callback_on_writable(m->conn->wsi); @@ -280,7 +302,7 @@ ss_proxy_onward_txcr(void *userobj, int bump) } /* - * Client - Proxy connection on unix domain socket + * Client <-> Proxy connection, usually on Unix Domain Socket */ static int @@ -315,9 +337,14 @@ callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason, lwsl_info("LWS_CALLBACK_RAW_ADOPT\n"); if (!pss) return -1; - pss->conn = malloc(sizeof(struct conn)); + + if (lws_fi(&wsi->fic, "ssproxy_client_adopt_oom")) + pss->conn = NULL; + else + pss->conn = malloc(sizeof(struct conn)); if (!pss->conn) return -1; + memset(pss->conn, 0, sizeof(*pss->conn)); /* dsh is allocated when the onward ss is done */ @@ -354,8 +381,8 @@ callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason, assert(conn->wsi == wsi); conn->wsi = NULL; - lwsl_notice("%s: cli->prox link %s closing\n", - __func__, lws_wsi_tag(wsi)); + lwsl_notice("%s: cli->prox link %s closing\n", __func__, + lws_wsi_tag(wsi)); /* sever relationship with conn */ lws_set_opaque_user_data(wsi, NULL); @@ -518,9 +545,11 @@ callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason, conn->state = LPCSPROX_OPERATIONAL; lws_set_timeout(wsi, 0, 0); break; + case LPCSPROX_OPERATIONAL: /* + * returning [onward -> ] proxy]-> client * rx metadata has priority */ @@ -546,7 +575,8 @@ callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason, p[3] = (uint8_t)naml; memcpy(&p[4], md->name, naml); p += 4 + naml; - memcpy(p, md->value__may_own_heap, md->length); + memcpy(p, md->value__may_own_heap, + md->length); p += md->length; n = lws_ptr_diff(p, cp); @@ -607,7 +637,10 @@ again: if (!n) break; - n = lws_write(wsi, (uint8_t *)cp, (unsigned int)n, LWS_WRITE_RAW); + if (lws_fi(&wsi->fic, "ssproxy_client_write_fail")) + n = -1; + else + n = lws_write(wsi, (uint8_t *)cp, (unsigned int)n, LWS_WRITE_RAW); if (n < 0) { lwsl_info("%s: WRITEABLE: %d\n", __func__, n); diff --git a/lib/secure-streams/secure-streams-serialize.c b/lib/secure-streams/secure-streams-serialize.c index abd23bff1..ec70e0917 100644 --- a/lib/secure-streams/secure-streams-serialize.c +++ b/lib/secure-streams/secure-streams-serialize.c @@ -262,7 +262,7 @@ lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi, */ int -lws_ss_serialize_state(struct lws_dsh *dsh, lws_ss_constate_t state, +lws_ss_serialize_state(struct lws *wsi, struct lws_dsh *dsh, lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) { uint8_t pre[12]; @@ -285,7 +285,8 @@ lws_ss_serialize_state(struct lws_dsh *dsh, lws_ss_constate_t state, lws_ser_wu32be(&pre[n], ack); - if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, (unsigned int)n + 4, NULL, 0)) { + if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, (unsigned int)n + 4, NULL, 0) || + (wsi && lws_fi(&wsi->fic, "sspc_dsh_ss2p_oom"))) { lwsl_err("%s: unable to alloc in dsh 2\n", __func__); return 1; @@ -428,7 +429,9 @@ lws_ss_deserialize_parse(struct lws_ss_serialization_parser *par, * We're going to try to do the onward connect */ - if (_lws_ss_client_connect(proxy_pss_to_ss_h(pss), + if ((proxy_pss_to_ss_h(pss) && + lws_fi(&proxy_pss_to_ss_h(pss)->fic, "ssproxy_onward_conn_fail")) || + _lws_ss_client_connect(proxy_pss_to_ss_h(pss), 0, parconn) == LWSSSSRET_DESTROY_ME) goto hangup; @@ -685,7 +688,9 @@ payload_ff: /* time used later to find proxy hold time */ lws_ser_wu64be(&p[15], (uint64_t)us); - if (lws_dsh_alloc_tail(dsh, KIND_C_TO_P, pre, + if ((proxy_pss_to_ss_h(pss) && + lws_fi(&proxy_pss_to_ss_h(pss)->fic, "ssproxy_dsh_c2p_pay_oom")) || + lws_dsh_alloc_tail(dsh, KIND_C_TO_P, pre, 23, cp, (unsigned int)n)) { lwsl_err("%s: unable to alloc in dsh 3\n", __func__); @@ -715,6 +720,11 @@ payload_ff: /* we still have an sspc handle */ int ret = ssi->rx(client_pss_to_userdata(pss), (uint8_t *)cp, (unsigned int)n, (int)flags); + + if (client_pss_to_sspc_h(pss, ssi) && + lws_fi(&client_pss_to_sspc_h(pss, ssi)->fic, "sspc_rx_fake_destroy_me")) + ret = LWSSSSRET_DESTROY_ME; + switch (ret) { case LWSSSSRET_OK: break; @@ -1005,7 +1015,10 @@ payload_ff: * Create the client's rx metadata entry */ - md = lws_malloc(sizeof(lws_sspc_metadata_t) + + if (h && lws_fi(&h->fic, "sspc_rx_metadata_oom")) + md = NULL; + else + md = lws_malloc(sizeof(lws_sspc_metadata_t) + par->rem + 1, "rxmeta"); if (!md) { lwsl_err("%s: OOM\n", __func__); @@ -1057,7 +1070,13 @@ payload_ff: lws_free_set_NULL(par->ssmd->value__may_own_heap); par->ssmd->value_on_lws_heap = 0; - par->ssmd->value__may_own_heap = lws_malloc((unsigned int)par->rem + 1, "metadata"); + if (proxy_pss_to_ss_h(pss) && + lws_fi(&proxy_pss_to_ss_h(pss)->fic, "ssproxy_rx_metadata_oom")) + par->ssmd->value__may_own_heap = NULL; + else + par->ssmd->value__may_own_heap = + lws_malloc((unsigned int)par->rem + 1, "metadata"); + if (!par->ssmd->value__may_own_heap) { lwsl_err("%s: OOM mdv\n", __func__); goto hangup; diff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c index 3e919d03b..8dd99f621 100644 --- a/lib/secure-streams/secure-streams.c +++ b/lib/secure-streams/secure-streams.c @@ -747,7 +747,29 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, pol = ssi->policy; if (!pol) { #endif + +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + lws_fi_ctx_t temp_fic; + + /* + * We have to do a temp inherit from context to find out + * early if we are supposed to inject a fault concealing + * the policy + */ + + memset(&temp_fic, 0, sizeof(temp_fic)); + lws_xos_init(&temp_fic.xos, lws_xos(&context->fic.xos)); + lws_fi_inherit_copy(&temp_fic, &context->fic, "ss", ssi->streamtype); + + if (lws_fi(&temp_fic, "ss_no_streamtype_policy")) + pol = NULL; + else + pol = lws_ss_policy_lookup(context, ssi->streamtype); + + lws_fi_destroy(&temp_fic); +#else pol = lws_ss_policy_lookup(context, ssi->streamtype); +#endif if (!pol) { lwsl_info("%s: unknown stream type %s\n", __func__, ssi->streamtype); @@ -821,10 +843,12 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, ssi->streamtype ? ssi->streamtype : "nostreamtype"); #if defined(LWS_WITH_SYS_FAULT_INJECTION) - h->fi.name = "ss"; - h->fi.parent = &context->fi; - if (ssi->fi) - lws_fi_import(&h->fi, ssi->fi); + h->fic.name = "ss"; + lws_xos_init(&h->fic.xos, lws_xos(&context->fic.xos)); + if (ssi->fic.fi_owner.count) + lws_fi_import(&h->fic, &ssi->fic); + + lws_fi_inherit_copy(&h->fic, &context->fic, "ss", ssi->streamtype); #endif h->info = *ssi; @@ -980,7 +1004,10 @@ lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi, } #endif - vho = lws_create_vhost(context, &i); + if (lws_fi(&ssi->fic, "ss_srv_vh_fail")) + vho = NULL; + else + vho = lws_create_vhost(context, &i); if (!vho) { lwsl_err("%s: failed to create vh", __func__); goto late_bail; @@ -1037,6 +1064,7 @@ late_bail: lws_dll2_remove(&h->list); lws_pt_unlock(pt); + lws_fi_destroy(&h->fic); __lws_lc_untag(&h->lc); lws_free(h); @@ -1199,7 +1227,7 @@ lws_ss_destroy(lws_ss_handle_t **ppss) #endif #if defined(LWS_WITH_SYS_FAULT_INJECTION) - lws_fi_destroy(&h->fi); + lws_fi_destroy(&h->fic); #endif #if defined(LWS_WITH_SYS_METRICS) diff --git a/lib/system/async-dns/async-dns.c b/lib/system/async-dns/async-dns.c index ba1552980..c46c8f34c 100644 --- a/lib/system/async-dns/async-dns.c +++ b/lib/system/async-dns/async-dns.c @@ -28,7 +28,7 @@ static const uint32_t botable[] = { 300, 500, 700, 1250, 5000 /* in case everything just dog slow */ }; static const lws_retry_bo_t retry_policy = { - botable, LWS_ARRAY_SIZE(botable), LWS_ARRAY_SIZE(botable), + botable, LWS_ARRAY_SIZE(botable), LWS_RETRY_CONCEAL_ALWAYS, /* don't conceal after the last table entry */ 0, 0, 20 }; void @@ -371,7 +371,7 @@ ok: dns->wsi = lws_create_adopt_udp(context->vhost_list, ads, 53, 0, lws_async_dns_protocol.name, NULL, - NULL, NULL, &retry_policy); + NULL, NULL, &retry_policy, "asyncdns"); if (!dns->wsi) { lwsl_err("%s: foreign socket adoption failed\n", __func__); return 1; diff --git a/lib/system/dhcpclient/dhcpc4.c b/lib/system/dhcpclient/dhcpc4.c index c841d2bc9..33c8494fc 100644 --- a/lib/system/dhcpclient/dhcpc4.c +++ b/lib/system/dhcpclient/dhcpc4.c @@ -299,7 +299,7 @@ lws_dhcpc4_retry_conn(struct lws_sorted_usec_list *sul) 68, LWS_CAUDP_PF_PACKET | LWS_CAUDP_BROADCAST, "lws-dhcp4client", (const char *)&r[1], - NULL, NULL, &bo2); + NULL, NULL, &bo2, "dhcpc"); lwsl_debug("%s: created wsi_raw: %s\n", __func__, lws_wsi_tag(r->wsi_raw)); if (!r->wsi_raw) { lwsl_err("%s: unable to create udp skt\n", __func__); diff --git a/lib/system/fault-injection/fault-injection.c b/lib/system/fault-injection/fault-injection.c index c833c6d6c..57082cc50 100644 --- a/lib/system/fault-injection/fault-injection.c +++ b/lib/system/fault-injection/fault-injection.c @@ -26,9 +26,8 @@ #include - static lws_fi_priv_t * -lws_fi_lookup(lws_fi_ctx_t *fic, const char *name) +lws_fi_lookup(const lws_fi_ctx_t *fic, const char *name) { lws_start_foreach_dll(struct lws_dll2 *, p, fic->fi_owner.head) { lws_fi_priv_t *pv = lws_container_of(p, lws_fi_priv_t, list); @@ -42,58 +41,81 @@ lws_fi_lookup(lws_fi_ctx_t *fic, const char *name) } int -lws_fi(lws_fi_ctx_t *fic, const char *name) +lws_fi(const lws_fi_ctx_t *fic, const char *name) { - lws_fi_priv_t *pv = NULL; + lws_fi_priv_t *pv; + int n; - do { - pv = lws_fi_lookup(fic, name); + pv = lws_fi_lookup(fic, name); - if (pv) { - int n; + if (!pv) + return 0; - switch (pv->fi.type) { - case LWSFI_ALWAYS: + switch (pv->fi.type) { + case LWSFI_ALWAYS: + goto inject; + + case LWSFI_DETERMINISTIC: + pv->fi.times++; + if (pv->fi.times >= pv->fi.pre) + if (pv->fi.times < pv->fi.pre + pv->fi.count) goto inject; + return 0; - case LWSFI_DETERMINISTIC: - pv->fi.times++; - if (pv->fi.times >= pv->fi.pre) - if (pv->fi.times < pv->fi.pre + pv->fi.count) - goto inject; - return 0; + case LWSFI_PROBABILISTIC: + if (lws_xos_percent((lws_xos_t *)&fic->xos, (int)pv->fi.pre)) + goto inject; + return 0; - case LWSFI_PROBABILISTIC: - pv->fi.times = (unsigned long)(pv->fi.times * 3) ^ - (unsigned long)lws_now_usecs(); - if ((uint16_t)pv->fi.times % 101 >= pv->fi.pre) - goto inject; - return 0; + case LWSFI_PATTERN: + case LWSFI_PATTERN_ALLOC: + n = (int)((pv->fi.times++) % pv->fi.count); + if (pv->fi.pattern[n >> 3] & (1 << (n & 7))) + goto inject; - case LWSFI_PATTERN: - n = (int)(pv->fi.times % pv->fi.pre); - if (pv->fi.pattern[n >> 3] & (1 << (n & 7))) - goto inject; + return 0; - return 0; - - default: - return 0; - } - } - - fic = fic->parent; - } while (fic); + default: + return 0; + } return 0; inject: - lwsl_warn("%s: Injecting fault %s->%s\n", __func__, fic->name, - pv->fi.name); + lwsl_warn("%s: Injecting fault %s->%s\n", __func__, + fic->name ? fic->name : "unk", pv->fi.name); return 1; } +int +_lws_fi_user_wsi_fi(struct lws *wsi, const char *name) +{ + return lws_fi(&wsi->fic, name); +} + +int +_lws_fi_user_context_fi(struct lws_context *ctx, const char *name) +{ + return lws_fi(&ctx->fic, name); +} + +#if defined(LWS_WITH_SECURE_STREAMS) +int +_lws_fi_user_ss_fi(struct lws_ss_handle *h, const char *name) +{ + return lws_fi(&h->fic, name); +} + +#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) +int +_lws_fi_user_sspc_fi(struct lws_sspc_handle *h, const char *name) +{ + return lws_fi(&h->fic, name); +} +#endif +#endif + int lws_fi_add(lws_fi_ctx_t *fic, const lws_fi_t *fi) { @@ -130,7 +152,12 @@ lws_fi_remove(lws_fi_ctx_t *fic, const char *name) void lws_fi_import(lws_fi_ctx_t *fic_dest, const lws_fi_ctx_t *fic_src) { - lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, fic_src->fi_owner.head) { + + /* inherit the PRNG seed for our context from source guy too */ + lws_xos_init(&fic_dest->xos, lws_xos((lws_xos_t *)&fic_src->xos)); + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + fic_src->fi_owner.head) { lws_fi_priv_t *pv = lws_container_of(p, lws_fi_priv_t, list); lws_dll2_remove(&pv->list); @@ -139,14 +166,210 @@ lws_fi_import(lws_fi_ctx_t *fic_dest, const lws_fi_ctx_t *fic_src) } lws_end_foreach_dll_safe(p, p1); } -void -lws_fi_destroy(lws_fi_ctx_t *fic) +static void +do_inherit(lws_fi_ctx_t *fic_dest, lws_fi_t *pfi, size_t trim) { - lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, fic->fi_owner.head) { + lws_fi_t fi = *pfi; + + fi.name += trim; + + lwsl_info("%s: %s: %s inherited as %s\n", __func__, fic_dest->name, + pfi->name, fi.name); + + if (fi.type == LWSFI_PATTERN_ALLOC) { + fi.pattern = lws_malloc((size_t)((fi.count >> 3) + 1), __func__); + if (!fi.pattern) + return; + memcpy((uint8_t *)fi.pattern, pfi->pattern, + (size_t)((fi.count >> 3) + 1)); + } + + lws_fi_add(fic_dest, &fi); +} + +void +lws_fi_inherit_copy(lws_fi_ctx_t *fic_dest, const lws_fi_ctx_t *fic_src, + const char *scope, const char *value) +{ + size_t sl = 0, vl = 0; + + if (scope) + sl = strlen(scope); + + if (value) + vl = strlen(value); + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + fic_src->fi_owner.head) { lws_fi_priv_t *pv = lws_container_of(p, lws_fi_priv_t, list); + size_t nl = strlen(pv->fi.name); + + if (!scope) + do_inherit(fic_dest, &pv->fi, 0); + else + if (nl > sl + 2 && + !strncmp(pv->fi.name, scope, sl) && + pv->fi.name[sl] == '/') + do_inherit(fic_dest, &pv->fi, sl + 1); + else { + if (value && nl > sl + vl + 2 && + pv->fi.name[sl] == '=' && + !strncmp(pv->fi.name + sl + 1, value, vl) && + pv->fi.name[sl + 1 + vl] == '/') + do_inherit(fic_dest, &pv->fi, sl + vl + 2); + } + + } lws_end_foreach_dll_safe(p, p1); +} + +void +lws_fi_destroy(const lws_fi_ctx_t *fic) +{ + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + fic->fi_owner.head) { + lws_fi_priv_t *pv = lws_container_of(p, lws_fi_priv_t, list); + + if (pv->fi.type == LWSFI_PATTERN_ALLOC && pv->fi.pattern) { + lws_free((void *)pv->fi.pattern); + pv->fi.pattern = NULL; + } lws_dll2_remove(&pv->list); lws_free(pv); } lws_end_foreach_dll_safe(p, p1); } + +enum { + PARSE_NAME, + PARSE_WHEN, + PARSE_PC, + PARSE_ENDBR +}; + +void +lws_fi_deserialize(lws_fi_ctx_t *fic, const char *sers) +{ + struct lws_tokenize ts; + lws_fi_t fi; + char nm[64]; + int state = PARSE_NAME; + + /* + * Go through the comma-separated list of faults + * creating them and adding to the lws_context info + */ + + lws_tokenize_init(&ts, sers, LWS_TOKENIZE_F_DOT_NONTERM | + LWS_TOKENIZE_F_NO_INTEGERS | + LWS_TOKENIZE_F_NO_FLOATS | + LWS_TOKENIZE_F_EQUALS_NONTERM | + LWS_TOKENIZE_F_SLASH_NONTERM | + LWS_TOKENIZE_F_MINUS_NONTERM); + ts.len = (unsigned int)strlen(sers); + if (ts.len < 1 || ts.len > 10240) + return; + + do { + ts.e = (int8_t)lws_tokenize(&ts); + switch (ts.e) { + case LWS_TOKZE_TOKEN: + + if (state == PARSE_NAME) { + /* + * One fault to inject looks like, eg, + * + * vh=xxx/listenskt + */ + + memset(&fi, 0, sizeof(fi)); + + lws_strnncpy(nm, ts.token, ts.token_len, sizeof(nm)); + fi.name = nm; + fi.type = LWSFI_ALWAYS; + + lwsl_notice("%s: name %.*s\n", __func__, (int)ts.token_len, ts.token); + + /* added later, potentially after (when) */ + break; + } + if (state == PARSE_WHEN) { + /* it's either numeric or a pattern */ + + lwsl_notice("%s: when\n", __func__); + + if (*ts.token == '.' || *ts.token == 'X') { + uint8_t *pat; + size_t n; + + /* + * pattern... we need to allocate it + */ + fi.type = LWSFI_PATTERN_ALLOC; + pat = lws_zalloc((ts.token_len >> 3) + 1, __func__); + if (!pat) + return; + fi.pattern = pat; + fi.count = (uint64_t)ts.token_len; + + for (n = 0; n < ts.token_len; n++) + if (ts.token[n] == 'X') + pat[n >> 3] = (uint8_t)( + pat[n >> 3] | (1 << (n & 7))); + + lwsl_hexdump_notice(pat, (ts.token_len >> 3) + 1); + + state = PARSE_ENDBR; + break; + } + + fi.pre = (uint64_t)atoi(ts.token); + lwsl_notice("%s: prob %d%%\n", __func__, (int)fi.pre); + fi.type = LWSFI_PROBABILISTIC; + state = PARSE_PC; + break; + } + break; + + case LWS_TOKZE_DELIMITER: + if (*ts.token == ',') { + lws_fi_add(fic, &fi); + state = PARSE_NAME; + break; + } + if (*ts.token == '(') { + lwsl_notice("%s: (\n", __func__); + if (state != PARSE_NAME) { + lwsl_err("%s: misplaced (\n", __func__); + return; + } + state = PARSE_WHEN; + break; + } + if (*ts.token == ')') { + if (state != PARSE_ENDBR) { + lwsl_err("%s: misplaced )\n", __func__); + return; + } + state = PARSE_NAME; + break; + } + if (*ts.token == '%') { + if (state != PARSE_PC) { + lwsl_err("%s: misplaced %%\n", __func__); + return; + } + state = PARSE_ENDBR; + break; + } + break; + + case LWS_TOKZE_ENDED: + lws_fi_add(fic, &fi); + return; + + default: + return; + } + } while (ts.e > 0); +} diff --git a/lib/system/ntpclient/ntpclient.c b/lib/system/ntpclient/ntpclient.c index 86536c898..bc5a96158 100644 --- a/lib/system/ntpclient/ntpclient.c +++ b/lib/system/ntpclient/ntpclient.c @@ -83,7 +83,7 @@ lws_ntpc_retry_conn(struct lws_sorted_usec_list *sul) v->retry_count_write = 0; v->wsi_udp = lws_create_adopt_udp(v->vhost, v->ntp_server_ads, 123, 0, v->protocol->name, NULL, NULL, NULL, - &bo2); + &bo2, "ntpclient"); lwsl_debug("%s: created wsi_udp: %s\n", __func__, lws_wsi_tag(v->wsi_udp)); if (!v->wsi_udp) { lwsl_err("%s: unable to create udp skt\n", __func__); diff --git a/minimal-examples/api-tests/api-test-lws_dsh/main.c b/minimal-examples/api-tests/api-test-lws_dsh/main.c index dc0f7d710..69bd25042 100644 --- a/minimal-examples/api-tests/api-test-lws_dsh/main.c +++ b/minimal-examples/api-tests/api-test-lws_dsh/main.c @@ -1,7 +1,7 @@ /* * lws-api-test-lws_dsh * - * Written in 2010-2019 by Andy Green + * Written in 2010-2021 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. @@ -252,7 +252,7 @@ test4(void) memset(blob, 0, sizeof(blob)); /* - * test 1: use up whole free list, then recover and alloc something + * test 4: use up whole free list, then recover and alloc something * else */ @@ -327,6 +327,92 @@ bail: return 1; } +int +test5(void) +{ + struct lws_dsh *dsh; + unsigned int budget; + uint8_t blob[4096]; + lws_xos_t xos; + size_t size; + void *a1; + + memset(blob, 0, sizeof(blob)); + lws_xos_init(&xos, 0x123456789abcdef0ull); + + budget = (unsigned int)(lws_xos(&xos) % 4000) + 4000; + + lwsl_notice("%s: budget %u\n", __func__, budget); + + + /* + * test 5: PRNG-based spamming and erratic bidi draining + */ + + dsh = lws_dsh_create(NULL, 409600, 2); + if (!dsh) { + lwsl_err("%s: Failed to create dsh\n", __func__); + + return 1; + } + + do { + + if (lws_xos_percent(&xos, 60)) { + /* kind 0 is going to try to write */ + + size = (size_t)((lws_xos(&xos) & 127) + 1); + + if (!lws_dsh_alloc_tail(dsh, 0, blob, size, NULL, 0)) + lwsl_notice("%s: kind 0 alloc %d\n", __func__, (int)size); + } + + if (lws_xos_percent(&xos, 80)) { + /* kind 1 is going to try to write */ + + size = (size_t)((lws_xos(&xos) & 127) + 1); + + if (!lws_dsh_alloc_tail(dsh, 1, blob, size, NULL, 0)) + lwsl_notice("%s: kind 1 alloc %d\n", __func__, (int)size); + } + + if (lws_xos_percent(&xos, 40)) { + /* kind 0 is going to try to read */ + + while (!lws_dsh_get_head(dsh, 0, &a1, &size)) { + lwsl_notice("%s: kind 0 read %d\n", __func__, (int)size); + lws_dsh_free(&a1); + } + } + + if (lws_xos_percent(&xos, 30)) { + /* kind 1 is going to try to read */ + + while (!lws_dsh_get_head(dsh, 1, &a1, &size)) { + lwsl_notice("%s: kind 1 read %d\n", __func__, (int)size); + lws_dsh_free(&a1); + } + } + + } while (budget--); + + while (!lws_dsh_get_head(dsh, 0, &a1, &size)) { + lwsl_notice("%s: kind 0 read %d\n", __func__, (int)size); + lws_dsh_free(&a1); + } + + while (!lws_dsh_get_head(dsh, 1, &a1, &size)) { + lwsl_notice("%s: kind 1 read %d\n", __func__, (int)size); + lws_dsh_free(&a1); + } + + lws_dsh_describe(dsh, "test dsh end state"); + + lws_dsh_destroy(&dsh); + + return 0; +} + int main(int argc, const char **argv) { int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; @@ -355,6 +441,10 @@ int main(int argc, const char **argv) lwsl_user("%s: test4: %d\n", __func__, n); ret |= n; + n = test5(); + lwsl_user("%s: test5: %d\n", __func__, n); + ret |= n; + lwsl_user("Completed: %s\n", ret ? "FAIL" : "PASS"); return ret; diff --git a/minimal-examples/api-tests/api-test-lws_struct-json/main.c b/minimal-examples/api-tests/api-test-lws_struct-json/main.c index 5f18af585..0aae74c2d 100644 --- a/minimal-examples/api-tests/api-test-lws_struct-json/main.c +++ b/minimal-examples/api-tests/api-test-lws_struct-json/main.c @@ -794,8 +794,7 @@ done: return 0; bail: -if (test2()) - return 1; + lwsl_user("Completed: FAIL\n"); return 1; diff --git a/minimal-examples/api-tests/api-test-lws_tokenize/main.c b/minimal-examples/api-tests/api-test-lws_tokenize/main.c index d69dc2fee..073bdb0b2 100644 --- a/minimal-examples/api-tests/api-test-lws_tokenize/main.c +++ b/minimal-examples/api-tests/api-test-lws_tokenize/main.c @@ -1,7 +1,7 @@ /* * lws-api-test-lws_tokenize * - * Written in 2010-2020 by Andy Green + * Written in 2010-2021 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. @@ -173,6 +173,10 @@ struct expected expected1[] = { expected17[] = { { LWS_TOKZE_TOKEN, "hello", 5 }, { LWS_TOKZE_ENDED, "", 0 }, + }, + expected18[] = { + { LWS_TOKZE_TOKEN, "x=y", 3 }, + { LWS_TOKZE_ENDED, "", 0 }, } ; @@ -253,6 +257,10 @@ struct tests tests[] = { { "# comment1\r\nhello #comment2\r\n#comment3", expected17, LWS_ARRAY_SIZE(expected17), LWS_TOKENIZE_F_HASH_COMMENT + }, + { + "x=y", expected18, + LWS_ARRAY_SIZE(expected18), LWS_TOKENIZE_F_EQUALS_NONTERM } }; diff --git a/minimal-examples/http-client/minimal-http-client-fi/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client-fi/CMakeLists.txt deleted file mode 100644 index 0e0d95792..000000000 --- a/minimal-examples/http-client/minimal-http-client-fi/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -project(lws-minimal-http-client-fi C) -cmake_minimum_required(VERSION 2.8.12) -find_package(libwebsockets CONFIG REQUIRED) -list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) -include(CheckCSourceCompiles) -include(LwsCheckRequirements) - -set(SAMP lws-minimal-http-client-fi) -set(SRCS minimal-http-client.c) - -set(requirements 1) -require_lws_config(LWS_ROLE_H1 1 requirements) -require_lws_config(LWS_WITH_CLIENT 1 requirements) -require_lws_config(LWS_WITH_SYS_STATE 1 requirements) -require_lws_config(LWS_WITH_SYS_FAULT_INJECTION 1 requirements) - -if (requirements) - add_executable(${SAMP} ${SRCS}) - - # if (LWS_CTEST_INTERNET_AVAILABLE) - # add_test(NAME http-client-warmcat COMMAND lws-minimal-http-client ) - # add_test(NAME http-client-warmcat-h1 COMMAND lws-minimal-http-client --h1) - # set_tests_properties(http-client-warmcat - # http-client-warmcat-h1 - # PROPERTIES - # WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/minimal-examples/http-client/minimal-http-client - # TIMEOUT 20) - # - #endif() - - if (websockets_shared) - target_link_libraries(${SAMP} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) - add_dependencies(${SAMP} websockets_shared) - else() - target_link_libraries(${SAMP} websockets ${LIBWEBSOCKETS_DEP_LIBS}) - endif() -endif() diff --git a/minimal-examples/http-client/minimal-http-client-fi/README.md b/minimal-examples/http-client/minimal-http-client-fi/README.md deleted file mode 100644 index 9387f8c75..000000000 --- a/minimal-examples/http-client/minimal-http-client-fi/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# lws minimal http client - -The application goes to either https://warmcat.com or -https://localhost:7681 (with `-l` option) and receives the page data. - -## build - -``` - $ cmake . && make -``` - -## usage - -Commandline option|Meaning ----|--- --d |Debug verbosity in decimal, eg, -d15 --l| Connect to https://localhost:7681 and accept selfsigned cert ---h1|Specify http/1.1 only using ALPN, rejects h2 even if server supports it ---server |set server name to connect to --k|Apply tls option LCCSCF_ALLOW_INSECURE --j|Apply tls option LCCSCF_ALLOW_SELFSIGNED --m|Apply tls option LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK --e|Apply tls option LCCSCF_ALLOW_EXPIRED --v|Connection validity use 3s / 10s instead of default 5m / 5m10s ---nossl| disable ssl connection ---user | Set Basic Auth username ---password | Set Basic Auth password - -``` - $ ./lws-minimal-http-client -[2018/03/04 14:43:20:8562] USER: LWS minimal http client -[2018/03/04 14:43:20:8571] NOTICE: Creating Vhost 'default' port -1, 1 protocols, IPv6 on -[2018/03/04 14:43:20:8616] NOTICE: created client ssl context for default -[2018/03/04 14:43:20:8617] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com -[2018/03/04 14:43:21:1496] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com -[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: incoming content length 26520 -[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: client connection up -[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 -[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 -[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 -[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 -[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 -[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1015 -[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 -[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 974 -[2018/03/04 14:43:22:3022] NOTICE: lws_http_client_read: transaction completed says -1 -[2018/03/04 14:43:23:3042] USER: Completed -``` - -You can also test the client Basic Auth support against the http-server/minimal-http-server-basicauth -example. In one console window run the server and in the other - -``` -$ lws-minimal-http-client -l --nossl --path /secret/index.html --user user --password password -``` - -The Basic Auth credentials for the test server are literally username "user" and password "password". - diff --git a/minimal-examples/http-client/minimal-http-client-fi/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client-fi/minimal-http-client.c deleted file mode 100644 index 2027b83d4..000000000 --- a/minimal-examples/http-client/minimal-http-client-fi/minimal-http-client.c +++ /dev/null @@ -1,346 +0,0 @@ -/* - * lws-minimal-http-client - * - * Written in 2010-2021 by Andy Green - * - * This file is made available under the Creative Commons CC0 1.0 - * Universal Public Domain Dedication. - * - * This demonstrates the a minimal http client using lws. - * - * It visits https://warmcat.com/ and receives the html page there. You - * can dump the page data by changing the #if 0 below. - */ - -#include -#include -#include - -static int interrupted, bad = 1, status; -#if defined(LWS_WITH_HTTP2) -static int long_poll; -#endif -static struct lws *client_wsi; -static const char *ba_user, *ba_password; - -static const lws_retry_bo_t retry = { - .secs_since_valid_ping = 3, - .secs_since_valid_hangup = 10, -}; - -static int -callback_http(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len) -{ - switch (reason) { - - /* because we are protocols[0] ... */ - case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: - lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", - in ? (char *)in : "(null)"); - interrupted = 1; - break; - - case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: - { - char buf[128]; - - lws_get_peer_simple(wsi, buf, sizeof(buf)); - status = (int)lws_http_client_http_response(wsi); - - lwsl_user("Connected to %s, http response: %d\n", - buf, status); - } -#if defined(LWS_WITH_HTTP2) - if (long_poll) { - lwsl_user("%s: Client entering long poll mode\n", __func__); - lws_h2_client_stream_long_poll_rxonly(wsi); - } -#endif - break; - -#if defined(LWS_WITH_HTTP_BASIC_AUTH) - - /* you only need this if you need to do Basic Auth */ - case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: - { - unsigned char **p = (unsigned char **)in, *end = (*p) + len; - char b[128]; - - if (!ba_user || !ba_password) - break; - - if (lws_http_basic_auth_gen(ba_user, ba_password, b, sizeof(b))) - break; - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_AUTHORIZATION, - (unsigned char *)b, (int)strlen(b), p, end)) - return -1; - - break; - } -#endif - - /* chunks of chunked content, with header removed */ - case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: - lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len); -#if defined(LWS_WITH_HTTP2) - if (long_poll) { - char dotstar[128]; - lws_strnncpy(dotstar, (const char *)in, len, - sizeof(dotstar)); - lwsl_notice("long poll rx: %d '%s'\n", (int)len, - dotstar); - } -#endif -#if 0 /* enable to dump the html */ - { - const char *p = in; - - while (len--) - if (*p < 0x7f) - putchar(*p++); - else - putchar('.'); - } -#endif - return 0; /* don't passthru */ - - /* uninterpreted http content */ - case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: - { - char buffer[1024 + LWS_PRE]; - char *px = buffer + LWS_PRE; - int lenx = sizeof(buffer) - LWS_PRE; - - if (lws_http_client_read(wsi, &px, &lenx) < 0) - return -1; - } - return 0; /* don't passthru */ - - case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: - lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); - interrupted = 1; - bad = status != 200; - lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ - break; - - case LWS_CALLBACK_CLOSED_CLIENT_HTTP: - interrupted = 1; - bad = status != 200; - lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ - break; - - default: - break; - } - - return lws_callback_http_dummy(wsi, reason, user, in, len); -} - -static const struct lws_protocols protocols[] = { - { - "http", - callback_http, - 0, - 0, - }, - { NULL, NULL, 0, 0 } -}; - -static void -sigint_handler(int sig) -{ - interrupted = 1; -} - -struct args { - int argc; - const char **argv; -}; - -static const lws_fi_t fi = { - .name = "cliwsi.dnsfail", - .type = LWSFI_ALWAYS -}; - -static int -make_client_connection(struct lws_context *context) -{ - struct lws_client_connect_info i; - struct args *a = lws_context_user(context); - lws_fi_ctx_t fic; - const char *p; - - memset(&fic, 0, sizeof(fic)); - lws_fi_add(&fic, &fi); - - memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ - i.context = context; - i.fi = &fic; - - if (!lws_cmdline_option(a->argc, a->argv, "-n")) { - i.ssl_connection = LCCSCF_USE_SSL; -#if defined(LWS_WITH_HTTP2) - /* requires h2 */ - if (lws_cmdline_option(a->argc, a->argv, "--long-poll")) { - lwsl_user("%s: long poll mode\n", __func__); - long_poll = 1; - } -#endif - } - - if (lws_cmdline_option(a->argc, a->argv, "-l")) { - i.port = 7681; - i.address = "localhost"; - i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; - } else { - i.port = 443; - i.address = "warmcat.com"; - } - - if (lws_cmdline_option(a->argc, a->argv, "--nossl")) - i.ssl_connection = 0; - - i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR | - LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; - - i.alpn = "h2"; - if (lws_cmdline_option(a->argc, a->argv, "--h1")) - i.alpn = "http/1.1"; - - if (lws_cmdline_option(a->argc, a->argv, "--h2-prior-knowledge")) - i.ssl_connection |= LCCSCF_H2_PRIOR_KNOWLEDGE; - - if ((p = lws_cmdline_option(a->argc, a->argv, "-p"))) - i.port = atoi(p); - - if ((p = lws_cmdline_option(a->argc, a->argv, "--user"))) - ba_user = p; - if ((p = lws_cmdline_option(a->argc, a->argv, "--password"))) - ba_password = p; - - if (lws_cmdline_option(a->argc, a->argv, "-j")) - i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; - - if (lws_cmdline_option(a->argc, a->argv, "-k")) - i.ssl_connection |= LCCSCF_ALLOW_INSECURE; - - if (lws_cmdline_option(a->argc, a->argv, "-m")) - i.ssl_connection |= LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; - - if (lws_cmdline_option(a->argc, a->argv, "-e")) - i.ssl_connection |= LCCSCF_ALLOW_EXPIRED; - - if ((p = lws_cmdline_option(a->argc, a->argv, "-f"))) { - i.ssl_connection |= LCCSCF_H2_MANUAL_RXFLOW; - i.manual_initial_tx_credit = atoi(p); - lwsl_notice("%s: manual peer tx credit %d\n", __func__, - i.manual_initial_tx_credit); - } - - /* the default validity check is 5m / 5m10s... -v = 3s / 10s */ - - if (lws_cmdline_option(a->argc, a->argv, "-v")) - i.retry_and_idle_policy = &retry; - - if ((p = lws_cmdline_option(a->argc, a->argv, "--server"))) - i.address = p; - - if ((p = lws_cmdline_option(a->argc, a->argv, "--path"))) - i.path = p; - else - i.path = "/"; - - i.host = i.address; - i.origin = i.address; - i.method = "GET"; - - i.protocol = protocols[0].name; - i.pwsi = &client_wsi; - - return !lws_client_connect_via_info(&i); -} - -static int -system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link, - int current, int target) -{ - struct lws_context *context = mgr->parent; - - if (current != LWS_SYSTATE_OPERATIONAL || target != LWS_SYSTATE_OPERATIONAL) - return 0; - - lwsl_info("%s: operational\n", __func__); - - make_client_connection(context); - - return 0; -} - -int main(int argc, const char **argv) -{ - lws_state_notify_link_t notifier = { {0}, system_notify_cb, "app" }; - lws_state_notify_link_t *na[] = { ¬ifier, NULL }; - struct lws_context_creation_info info; - struct lws_context *context; - struct args args; - int n = 0; - // uint8_t memcert[4096]; - - args.argc = argc; - args.argv = argv; - - signal(SIGINT, sigint_handler); - - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - lws_cmdline_option_handle_builtin(argc, argv, &info); - - lwsl_user("LWS minimal http client [-d] [-l] [--h1]\n"); - - info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; - info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ - info.protocols = protocols; - info.user = &args; - info.register_notifier_list = na; - info.connect_timeout_secs = 30; - - /* - * since we know this lws context is only ever going to be used with - * one client wsis / fds / sockets at a time, let lws know it doesn't - * have to use the default allocations for fd tables up to ulimit -n. - * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we - * will use. - */ - info.fd_limit_per_thread = 1 + 1 + 1; - -#if defined(LWS_WITH_MBEDTLS) || defined(USE_WOLFSSL) - /* - * OpenSSL uses the system trust store. mbedTLS has to be told which - * CA to trust explicitly. - */ - info.client_ssl_ca_filepath = "./warmcat.com.cer"; -#endif -#if 0 - n = open("./warmcat.com.cer", O_RDONLY); - if (n >= 0) { - info.client_ssl_ca_mem_len = read(n, memcert, sizeof(memcert)); - info.client_ssl_ca_mem = memcert; - close(n); - n = 0; - memcert[info.client_ssl_ca_mem_len++] = '\0'; - } -#endif - context = lws_create_context(&info); - if (!context) { - lwsl_err("lws init failed\n"); - return 1; - } - - while (n >= 0 && !interrupted) - n = lws_service(context, 0); - - lws_context_destroy(context); - lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); - - return bad; -} diff --git a/minimal-examples/http-client/minimal-http-client-fi/warmcat.com.cer b/minimal-examples/http-client/minimal-http-client-fi/warmcat.com.cer deleted file mode 100644 index 8dc39efc5..000000000 --- a/minimal-examples/http-client/minimal-http-client-fi/warmcat.com.cer +++ /dev/null @@ -1,21 +0,0 @@ - -----BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow -PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD -Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O -rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq -OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b -xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw -7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD -aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG -SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 -ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr -AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz -R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 -JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo -Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ------END CERTIFICATE----- - diff --git a/minimal-examples/http-client/minimal-http-client/CMakeLists.txt b/minimal-examples/http-client/minimal-http-client/CMakeLists.txt index 23d5dc0d1..eeb26a904 100644 --- a/minimal-examples/http-client/minimal-http-client/CMakeLists.txt +++ b/minimal-examples/http-client/minimal-http-client/CMakeLists.txt @@ -8,16 +8,37 @@ include(LwsCheckRequirements) set(SAMP lws-minimal-http-client) set(SRCS minimal-http-client.c) +set(has_fault_injection 1) set(has_h2 1) +set(has_plugins 1) +set(has_ss_policy_parse 1) +set(has_no_system_vhost 1) +set(has_async_dns 1) + set(requirements 1) + require_lws_config(LWS_ROLE_H1 1 requirements) require_lws_config(LWS_WITH_CLIENT 1 requirements) require_lws_config(LWS_WITH_SYS_STATE 1 requirements) require_lws_config(LWS_ROLE_H2 1 has_h2) +require_lws_config(LWS_WITH_SYS_FAULT_INJECTION 1 has_fault_injection) +require_lws_config(LWS_WITH_EVLIB_PLUGINS 1 has_plugins) +require_lws_config(LWS_WITH_EVENT_LIBS 1 has_plugins) + +require_lws_config(LWS_WITH_SECURE_STREAMS 1 has_ss_policy_parse) +require_lws_config(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY 0 has_ss_policy_parse) + +require_lws_config(LWS_WITH_SYS_ASYNC_DNS 0 has_no_system_vhost) +require_lws_config(LWS_WITH_SYS_NTPCLIENT 0 has_no_system_vhost) +require_lws_config(LWS_WITH_SYS_DHCP_CLIENT 0 has_no_system_vhost) + +require_lws_config(LWS_WITH_SYS_ASYNC_DNS 1 has_async_dns) if (requirements) add_executable(${SAMP} ${SRCS}) + + find_program(VALGRIND "valgrind") sai_resource(warmcat_conns 1 40 http_client_warmcat) @@ -27,15 +48,102 @@ if (requirements) add_test(NAME http-client-warmcat COMMAND lws-minimal-http-client ) list(APPEND mytests http-client-warmcat) endif() + + add_test(NAME http-client-warmcat-h1 COMMAND lws-minimal-http-client --h1) set_tests_properties(${mytests} PROPERTIES WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/minimal-examples/http-client/minimal-http-client TIMEOUT 20) + if (has_fault_injection) + + # creation related faults + + list(APPEND mytests http-client-fi-ctx1) + add_test(NAME http-client-fi-ctx1 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail1") + + # if (has_plugins) + # !!! need to actually select an available evlib plugin to trigger this + # list(APPEND mytests http-client-fi-pi) + # add_test(NAME http-client-fi-pi COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_plugin_init") + # endif() + + list(APPEND mytests http-client-fi-ctx2) + add_test(NAME http-client-fi-ctx2 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_evlib_sel") + + list(APPEND mytests http-client-fi-ctx3) + add_test(NAME http-client-fi-ctx3 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_oom_ctx") + + list(APPEND mytests http-client-fi-ctx4) + add_test(NAME http-client-fi-ctx4 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_privdrop") + + list(APPEND mytests http-client-fi-ctx5) + add_test(NAME http-client-fi-ctx5 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_maxfds") + + list(APPEND mytests http-client-fi-ctx6) + add_test(NAME http-client-fi-ctx6 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_oom_fds") + + list(APPEND mytests http-client-fi-ctx7) + add_test(NAME http-client-fi-ctx7 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_plat_init") + + list(APPEND mytests http-client-fi-ctx8) + add_test(NAME http-client-fi-ctx8 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_evlib_init") + + list(APPEND mytests http-client-fi-ctx9) + add_test(NAME http-client-fi-ctx9 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_evlib_pt") + + if (NOT has_no_system_vhost) + + list(APPEND mytests http-client-fi-ctx10) + add_test(NAME http-client-fi-ctx10 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_sys_vh") + + list(APPEND mytests http-client-fi-ctx11) + add_test(NAME http-client-fi-ctx11 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_sys_vh_init") + + endif() + + list(APPEND mytests http-client-fi-ctx12) + add_test(NAME http-client-fi-ctx12 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "ctx_createfail_def_vh") + + + list(APPEND mytests http-client-fi-vh1) + add_test(NAME http-client-fi-vh1 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "vh/vh_create_oom") + + list(APPEND mytests http-client-fi-vh2) + add_test(NAME http-client-fi-vh2 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "vh/vh_create_pcols_oom") + + list(APPEND mytests http-client-fi-vh3) + add_test(NAME http-client-fi-vh3 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "vh/vh_create_ssl_srv") + + list(APPEND mytests http-client-fi-vh4) + add_test(NAME http-client-fi-vh4 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "vh/vh_create_ssl_cli") + + list(APPEND mytests http-client-fi-vh5) + add_test(NAME http-client-fi-vh5 COMMAND lws-minimal-http-client --expected-exit 5 --fault-injection "vh/vh_create_srv_init") + + + list(APPEND mytests http-client-fi-dnsfail) + add_test(NAME http-client-fi-dnsfail COMMAND lws-minimal-http-client --expected-exit 3 --fault-injection "wsi=user/dnsfail") + + if (has_async_dns) + list(APPEND mytests http-client-fi-connfail) + add_test(NAME http-client-fi-connfail COMMAND lws-minimal-http-client --expected-exit 3 --fault-injection "wsi=user/connfail") + else() + list(APPEND mytests http-client-fi-connfail) + add_test(NAME http-client-fi-connfail COMMAND lws-minimal-http-client --expected-exit 2 --fault-injection "wsi=user/connfail") + endif() + + list(APPEND mytests http-client-fi-user-est-fail) + add_test(NAME http-client-fi-user-est-fail COMMAND lws-minimal-http-client --expected-exit 3 --fault-injection "wsi/user_reject_at_est") + + + endif() + + if (DEFINED ENV{SAI_OVN}) set_tests_properties(${mytests} PROPERTIES FIXTURES_REQUIRED "res_http_client_warmcat") - endif() + endif() endif() diff --git a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c index 87d5a8e29..01243e5ad 100644 --- a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c +++ b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c @@ -73,6 +73,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", in ? (char *)in : "(null)"); interrupted = 1; + bad = 3; /* connection failed before we could make connection */ lws_cancel_service(lws_get_context(wsi)); #if defined(LWS_WITH_CONMON) @@ -97,6 +98,10 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, lws_h2_client_stream_long_poll_rxonly(wsi); } #endif + + if (lws_fi_user_wsi_fi(wsi, "user_reject_at_est")) + return -1; + break; #if defined(LWS_WITH_HTTP_BASIC_AUTH) @@ -132,17 +137,10 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, dotstar); } #endif -#if 0 /* enable to dump the html */ - { - const char *p = in; - - while (len--) - if (*p < 0x7f) - putchar(*p++); - else - putchar('.'); - } +#if 0 + lwsl_hexdump_notice(in, len); #endif + return 0; /* don't passthru */ /* uninterpreted http content */ @@ -152,6 +150,9 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, char *px = buffer + LWS_PRE; int lenx = sizeof(buffer) - LWS_PRE; + if (lws_fi_user_wsi_fi(wsi, "user_reject_at_rx")) + return -1; + if (lws_http_client_read(wsi, &px, &lenx) < 0) return -1; } @@ -304,8 +305,18 @@ system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link, i.protocol = protocols[0].name; i.pwsi = &client_wsi; + i.fi_wsi_name = "user"; - return !lws_client_connect_via_info(&i); + if (!lws_client_connect_via_info(&i)) { + lwsl_err("Client creation failed\n"); + interrupted = 1; + bad = 2; /* could not even start client connection */ + lws_cancel_service(context); + + return 1; + } + + return 0; } int main(int argc, const char **argv) @@ -314,8 +325,9 @@ int main(int argc, const char **argv) lws_state_notify_link_t *na[] = { ¬ifier, NULL }; struct lws_context_creation_info info; struct lws_context *context; + int n = 0, expected = 0; struct args args; - int n = 0; + const char *p; // uint8_t memcert[4096]; args.argc = argc; @@ -334,7 +346,7 @@ int main(int argc, const char **argv) info.protocols = protocols; info.user = &args; info.register_notifier_list = na; - info.connect_timeout_secs = 30; + info.connect_timeout_secs = 30; /* * since we know this lws context is only ever going to be used with @@ -365,14 +377,24 @@ int main(int argc, const char **argv) context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); - return 1; + bad = 5; + goto bail; } while (n >= 0 && !interrupted) n = lws_service(context, 0); lws_context_destroy(context); - lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); - return bad; +bail: + if ((p = lws_cmdline_option(argc, argv, "--expected-exit"))) + expected = atoi(p); + + if (bad == expected) { + lwsl_user("Completed: OK (seen expected %d)\n", expected); + return 0; + } else + lwsl_err("Completed: failed: exit %d, expected %d\n", bad, expected); + + return 1; } diff --git a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c index 7be451c1d..35aaaab12 100644 --- a/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c +++ b/minimal-examples/http-server/minimal-http-server-eventlib-foreign/minimal-http-server-eventlib-foreign.c @@ -150,6 +150,9 @@ do_client_conn(void) i.origin = i.address; i.method = "GET"; i.local_protocol_name = protocols[0].name; +#if defined(LWS_WITH_SYS_FAULT_INJECTION) + i.fi_wsi_name = "user"; +#endif if (!lws_client_connect_via_info(&i)) { lwsl_err("Client creation failed\n"); diff --git a/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c b/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c index 9a9d11422..e93b54298 100644 --- a/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c +++ b/minimal-examples/raw/minimal-raw-adopt-udp/minimal-raw-adopt-udp.c @@ -182,7 +182,8 @@ int main(int argc, const char **argv) * Create our own "foreign" UDP socket bound to 7681/udp */ if (!lws_create_adopt_udp(vhost, NULL, 7681, LWS_CAUDP_BIND, - protocols[0].name, NULL, NULL, NULL, NULL)) { + protocols[0].name, NULL, NULL, NULL, NULL, + "user")) { lwsl_err("%s: foreign socket adoption failed\n", __func__); goto bail; } diff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c index 122cf7c96..773f11e6d 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c +++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c @@ -34,6 +34,7 @@ static int interrupted, bad = 1, port = 0 /* unix domain socket */; static const char *ibind = NULL; /* default to unix domain skt "proxy.ss.lws" */ static lws_state_notify_link_t nl; +static struct lws_context *context; /* * We just define enough policy so it can fetch the latest one securely @@ -233,41 +234,21 @@ static const lws_system_ops_t system_ops = { static void sigint_handler(int sig) { + lwsl_notice("%s\n", __func__); interrupted = 1; -} - -static void -assert_bt(int sig) -{ -#if defined(__APPLE__) || defined(__linux__) - void *array[20]; - char **strings; - int size, i; - - size = backtrace (array, 10); - strings = backtrace_symbols (array, size); - if (!strings) - return; - - for (i = 0; i < size; i++) - printf("%s\n", strings[i]); - - free (strings); -#endif + lws_cancel_service(context); } int main(int argc, const char **argv) { - int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; struct lws_context_creation_info info; - struct lws_context *context; const char *p; + int n = 0; + + memset(&info, 0, sizeof info); + lws_cmdline_option_handle_builtin(argc, argv, &info); signal(SIGINT, sigint_handler); - signal(SIGABRT, assert_bt); - - if ((p = lws_cmdline_option(argc, argv, "-d"))) - logs = atoi(p); /* connect to ssproxy via UDS by default, else via tcp with this port */ if ((p = lws_cmdline_option(argc, argv, "-p"))) @@ -278,11 +259,8 @@ int main(int argc, const char **argv) if ((p = lws_cmdline_option(argc, argv, "-i"))) ibind = p; - lws_set_log_level(logs, NULL); lwsl_user("LWS secure streams Proxy [-d]\n"); - memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ - info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; @@ -311,8 +289,9 @@ int main(int argc, const char **argv) /* the event loop */ - while (n >= 0 && !interrupted) + do { n = lws_service(context, 0); + } while (n >= 0 && !interrupted); bad = 0; diff --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 index 36704fc6e..a53503939 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c +++ b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c @@ -248,13 +248,18 @@ myss_state(void *userobj, void *sh, lws_ss_constate_t state, case LWSSSCS_ALL_RETRIES_FAILED: /* if we're out of retries, we want to close the app and FAIL */ interrupted = 1; + bad = 2; break; + case LWSSSCS_QOS_ACK_REMOTE: lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__); break; case LWSSSCS_TIMEOUT: lwsl_notice("%s: LWSSSCS_TIMEOUT\n", __func__); + /* if we're out of time */ + interrupted = 1; + bad = 3; break; case LWSSSCS_USER_BASE: @@ -405,8 +410,8 @@ int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; + int n = 0, expected = 0; const char *p; - int n = 0; signal(SIGINT, sigint_handler); @@ -475,7 +480,7 @@ int main(int argc, const char **argv) context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); - return 1; + goto bail; } #if !defined(LWS_SS_USE_SSPC) @@ -520,7 +525,15 @@ int main(int argc, const char **argv) lws_context_destroy(context); - lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); +bail: + if ((p = lws_cmdline_option(argc, argv, "--expected-exit"))) + expected = atoi(p); - return bad; + if (bad == expected) { + lwsl_user("Completed: OK (seen expected %d)\n", expected); + return 0; + } else + lwsl_err("Completed: failed: exit %d, expected %d\n", bad, expected); + + return 1; }