|
8 | 8 | #include <aws/iotdevice/secure_tunneling.h> |
9 | 9 |
|
10 | 10 | #include <aws/common/condition_variable.h> |
| 11 | +#include <aws/common/hash_table.h> |
11 | 12 | #include <aws/common/mutex.h> |
| 13 | +#include <aws/common/task_scheduler.h> |
| 14 | +#include <aws/http/proxy.h> |
| 15 | +#include <aws/http/websocket.h> |
| 16 | +#include <aws/io/socket.h> |
12 | 17 | #include <aws/io/tls_channel_handler.h> |
13 | 18 |
|
| 19 | +/** |
| 20 | + * The various states that the secure tunnel can be in. A secure tunnel has both a current state and a desired state. |
| 21 | + * Desired state is only allowed to be one of {STOPPED, CONNECTED, TERMINATED}. The secure tunnel transitions states |
| 22 | + * based on either |
| 23 | + * (1) changes in desired state, or |
| 24 | + * (2) external events. |
| 25 | + * |
| 26 | + * Most states are interruptible (in the sense of a change in desired state causing an immediate change in state) but |
| 27 | + * CONNECTING cannot be interrupted due to waiting for an asynchronous callback (that has no |
| 28 | + * cancel) to complete. |
| 29 | + */ |
| 30 | +enum aws_secure_tunnel_state { |
| 31 | + /* |
| 32 | + * The secure tunnel is not connected and not waiting for anything to happen. |
| 33 | + * |
| 34 | + * Next States: |
| 35 | + * CONNECTING - if the user invokes Connect() on the secure tunnel |
| 36 | + * TERMINATED - if the user releases the last ref count on the secure tunnel |
| 37 | + */ |
| 38 | + AWS_STS_STOPPED, |
| 39 | + |
| 40 | + /* |
| 41 | + * The secure tunnel is attempting to connect to a remote endpoint and establish a WebSocket upgrade. This state is |
| 42 | + * not interruptible by any means other than WebSocket setup completion. |
| 43 | + * |
| 44 | + * Next States: |
| 45 | + * CONNECTED - if WebSocket handshake is successful and desired state is still CONNECTED |
| 46 | + * WEBSOCKET_SHUTDOWN - if the WebSocket completes setup with no error but desired state is not CONNECTED |
| 47 | + * PENDING_RECONNECT - if the WebSocket fails to complete setup and desired state is still CONNECTED |
| 48 | + * STOPPED - if the WebSocket fails to complete setup and desired state is not CONNECTED |
| 49 | + */ |
| 50 | + AWS_STS_CONNECTING, |
| 51 | + |
| 52 | + /* |
| 53 | + * The secure tunnel is ready to perform user-requested operations. |
| 54 | + * |
| 55 | + * Next States: |
| 56 | + * WEBSOCKET_SHUTDOWN - desired state is no longer CONNECTED |
| 57 | + * PENDING_RECONNECT - unexpected WebSocket shutdown completion and desired state still CONNECTED |
| 58 | + * STOPPED - unexpected WebSocket shutdown completion and desired state no longer CONNECTED |
| 59 | + */ |
| 60 | + AWS_STS_CONNECTED, |
| 61 | + |
| 62 | + /* |
| 63 | + * The secure tunnel is attempting to shut down a WebSocket connection cleanly by finishing the current operation |
| 64 | + * and then transmitting a STREAM RESET message to all open streams. |
| 65 | + * |
| 66 | + * Next States: |
| 67 | + * WEBSOCKET_SHUTDOWN - on sucessful (or unsuccessful) disconnection |
| 68 | + * PENDING_RECONNECT - unexpected WebSocket shutdown completion and desired state still CONNECTED |
| 69 | + * STOPPED - unexpected WebSocket shutdown completion and desired state no longer CONNECTED |
| 70 | + */ |
| 71 | + AWS_STS_CLEAN_DISCONNECT, |
| 72 | + |
| 73 | + /* |
| 74 | + * The secure tunnel is waiting for the WebSocket to completely shut down. This state is not interruptible. |
| 75 | + * |
| 76 | + * Next States: |
| 77 | + * PENDING_RECONNECT - the WebSocket has shut down and desired state is still CONNECTED |
| 78 | + * STOPPED - the WebSocket has shut down and desired state is not CONNECTED |
| 79 | + */ |
| 80 | + AWS_STS_WEBSOCKET_SHUTDOWN, |
| 81 | + |
| 82 | + /* |
| 83 | + * The secure tunnel is waiting for the reconnect timer to expire before attempting to connect again. |
| 84 | + * |
| 85 | + * Next States: |
| 86 | + * CONNECTING - the reconnect timer has expired and desired state is still CONNECTED |
| 87 | + * STOPPED - desired state is no longer CONNECTED |
| 88 | + */ |
| 89 | + AWS_STS_PENDING_RECONNECT, |
| 90 | + |
| 91 | + /* |
| 92 | + * The secure tunnel is performing final shutdown and release of all resources. This state is only realized for |
| 93 | + * a non-observable instant of time (transition out of STOPPED). |
| 94 | + */ |
| 95 | + AWS_STS_TERMINATED |
| 96 | +}; |
| 97 | + |
14 | 98 | struct data_tunnel_pair { |
| 99 | + struct aws_allocator *allocator; |
15 | 100 | struct aws_byte_buf buf; |
16 | 101 | struct aws_byte_cursor cur; |
17 | 102 | const struct aws_secure_tunnel *secure_tunnel; |
18 | 103 | bool length_prefix_written; |
19 | 104 | }; |
20 | 105 |
|
21 | | -struct aws_secure_tunnel_vtable { |
22 | | - int (*connect)(struct aws_secure_tunnel *secure_tunnel); |
23 | | - int (*send_data)(struct aws_secure_tunnel *secure_tunnel, const struct aws_byte_cursor *data); |
24 | | - int (*send_stream_start)(struct aws_secure_tunnel *secure_tunnel); |
25 | | - int (*send_stream_reset)(struct aws_secure_tunnel *secure_tunnel); |
26 | | - int (*close)(struct aws_secure_tunnel *secure_tunnel); |
| 106 | +/* |
| 107 | + * Secure tunnel configuration |
| 108 | + */ |
| 109 | +struct aws_secure_tunnel_options_storage { |
| 110 | + struct aws_allocator *allocator; |
| 111 | + |
| 112 | + /* backup */ |
| 113 | + |
| 114 | + struct aws_client_bootstrap *bootstrap; |
| 115 | + struct aws_socket_options socket_options; |
| 116 | + struct aws_http_proxy_options http_proxy_options; |
| 117 | + struct aws_http_proxy_config *http_proxy_config; |
| 118 | + struct aws_string *access_token; |
| 119 | + struct aws_string *client_token; |
| 120 | + |
| 121 | + struct aws_string *endpoint_host; |
| 122 | + |
| 123 | + /* Stream related info */ |
| 124 | + int32_t stream_id; |
| 125 | + |
| 126 | + struct aws_hash_table service_ids; |
| 127 | + |
| 128 | + /* Callbacks */ |
| 129 | + aws_secure_tunnel_message_received_fn *on_message_received; |
| 130 | + aws_secure_tunneling_on_connection_complete_fn *on_connection_complete; |
| 131 | + aws_secure_tunneling_on_connection_shutdown_fn *on_connection_shutdown; |
| 132 | + aws_secure_tunneling_on_stream_start_fn *on_stream_start; |
| 133 | + aws_secure_tunneling_on_stream_reset_fn *on_stream_reset; |
| 134 | + aws_secure_tunneling_on_session_reset_fn *on_session_reset; |
| 135 | + aws_secure_tunneling_on_stopped_fn *on_stopped; |
| 136 | + |
| 137 | + aws_secure_tunneling_on_send_data_complete_fn *on_send_data_complete; |
| 138 | + aws_secure_tunneling_on_termination_complete_fn *on_termination_complete; |
| 139 | + void *secure_tunnel_on_termination_user_data; |
| 140 | + |
| 141 | + void *user_data; |
| 142 | + enum aws_secure_tunneling_local_proxy_mode local_proxy_mode; |
27 | 143 | }; |
28 | 144 |
|
29 | | -struct aws_websocket_client_connection_options; |
30 | | -struct aws_websocket_send_frame_options; |
| 145 | +struct aws_secure_tunnel_vtable { |
| 146 | + /* aws_high_res_clock_get_ticks */ |
| 147 | + uint64_t (*get_current_time_fn)(void); |
| 148 | + |
| 149 | + /* For test verification */ |
| 150 | + int (*aws_websocket_client_connect_fn)(const struct aws_websocket_client_connection_options *options); |
| 151 | + |
| 152 | + /* For test verification */ |
| 153 | + int (*aws_websocket_send_frame_fn)( |
| 154 | + struct aws_websocket *websocket, |
| 155 | + const struct aws_websocket_send_frame_options *options); |
31 | 156 |
|
32 | | -struct aws_websocket_vtable { |
33 | | - int (*client_connect)(const struct aws_websocket_client_connection_options *options); |
34 | | - int (*send_frame)(struct aws_websocket *websocket, const struct aws_websocket_send_frame_options *options); |
35 | | - void (*close)(struct aws_websocket *websocket, bool free_scarce_resources_immediately); |
36 | | - void (*release)(struct aws_websocket *websocket); |
| 157 | + /* For test verification */ |
| 158 | + void (*aws_websocket_release_fn)(struct aws_websocket *websocket); |
| 159 | + |
| 160 | + /* For test verification */ |
| 161 | + void (*aws_websocket_close_fn)(struct aws_websocket *websocket, bool free_scarce_resources_immediately); |
| 162 | + |
| 163 | + void *vtable_user_data; |
37 | 164 | }; |
38 | 165 |
|
39 | 166 | struct aws_secure_tunnel { |
40 | 167 | /* Static settings */ |
41 | | - struct aws_allocator *alloc; |
42 | | - struct aws_secure_tunnel_options_storage *options_storage; |
43 | | - struct aws_secure_tunnel_options *options; |
| 168 | + struct aws_allocator *allocator; |
| 169 | + struct aws_ref_count ref_count; |
| 170 | + |
| 171 | + const struct aws_secure_tunnel_vtable *vtable; |
| 172 | + |
| 173 | + /* |
| 174 | + * Secure tunnel configuration |
| 175 | + */ |
| 176 | + struct aws_secure_tunnel_options_storage *config; |
| 177 | + |
44 | 178 | struct aws_tls_ctx *tls_ctx; |
45 | 179 | struct aws_tls_connection_options tls_con_opt; |
46 | | - struct aws_secure_tunnel_vtable vtable; |
47 | | - struct aws_websocket_vtable websocket_vtable; |
48 | 180 |
|
49 | | - struct aws_ref_count ref_count; |
| 181 | + /* |
| 182 | + * The recurrent task that runs all secure tunnel logic outside of external event callbacks. Bound to the secure |
| 183 | + * tunnel's event loop. |
| 184 | + */ |
| 185 | + struct aws_task service_task; |
| 186 | + |
| 187 | + /* |
| 188 | + * Tracks when the secure tunnel's service task is next schedule to run. Is zero if the task is not scheduled to |
| 189 | + * run or we are in the middle of a service (so technically not scheduled too). |
| 190 | + */ |
| 191 | + uint64_t next_service_task_run_time; |
| 192 | + |
| 193 | + /* |
| 194 | + * True if the secure tunnel's service task is running. Used to skip service task reevaluation due to state changes |
| 195 | + * while running the service task. Reevaluation will occur at the very end of the service. |
| 196 | + */ |
| 197 | + bool in_service; |
| 198 | + |
| 199 | + /* |
| 200 | + * Event loop all the secure tunnel's tasks will be pinned to, ensuring serialization and |
| 201 | + * concurrency safety. |
| 202 | + */ |
| 203 | + struct aws_event_loop *loop; |
| 204 | + |
| 205 | + /* |
| 206 | + * What state is the secure tunnel working towards? |
| 207 | + */ |
| 208 | + enum aws_secure_tunnel_state desired_state; |
| 209 | + |
| 210 | + /* |
| 211 | + * What is the secure tunnel's current state? |
| 212 | + */ |
| 213 | + enum aws_secure_tunnel_state current_state; |
50 | 214 |
|
51 | | - /* Used only during initial websocket setup. Otherwise, should be NULL */ |
| 215 | + /* |
| 216 | + * handshake_request exists between the transform completion timepoint and the websocket setup callback. |
| 217 | + */ |
52 | 218 | struct aws_http_message *handshake_request; |
53 | 219 |
|
54 | 220 | /* Dynamic data */ |
55 | | - int32_t stream_id; |
| 221 | + |
56 | 222 | struct aws_websocket *websocket; |
57 | 223 |
|
58 | 224 | /* Stores what has been received but not processed */ |
59 | 225 | struct aws_byte_buf received_data; |
60 | 226 |
|
61 | | - /* The secure tunneling endpoint ELB drops idle connect after 1 minute. We need to send a ping periodically to keep |
62 | | - * the connection */ |
| 227 | + /* |
| 228 | + * When should the secure tunnel next attempt to reconnect? Only used by PENDING_RECONNECT state. |
| 229 | + */ |
| 230 | + uint64_t next_reconnect_time_ns; |
| 231 | + |
| 232 | + /* |
| 233 | + * How many consecutive reconnect failures have we experienced? |
| 234 | + */ |
| 235 | + uint64_t reconnect_count; |
63 | 236 |
|
64 | | - struct ping_task_context *ping_task_context; |
| 237 | + struct aws_linked_list queued_operations; |
| 238 | + struct aws_secure_tunnel_operation *current_operation; |
| 239 | + |
| 240 | + /* |
| 241 | + * Is there a WebSocket message in transit (to the socket) that has not invoked its write completion callback yet? |
| 242 | + * The secure tunnel implementation only allows one in-transit message at a time, and so if this is true, we don't |
| 243 | + * send additional ones/ |
| 244 | + */ |
| 245 | + bool pending_write_completion; |
| 246 | + |
| 247 | + /* |
| 248 | + * When should the next PINGREQ be sent? |
| 249 | + * The secure tunneling endpoint ELB drops idle connect after 1 minute. we need to send a ping periodically to keep |
| 250 | + * the connection alive. |
| 251 | + */ |
| 252 | + uint64_t next_ping_time; |
65 | 253 | }; |
66 | 254 |
|
| 255 | +AWS_EXTERN_C_BEGIN |
| 256 | + |
| 257 | +/* |
| 258 | + * Override the vtable used by the secure tunnel; useful for mocking certain scenarios. |
| 259 | + */ |
| 260 | +AWS_IOTDEVICE_API void aws_secure_tunnel_set_vtable( |
| 261 | + struct aws_secure_tunnel *secure_tunnel, |
| 262 | + const struct aws_secure_tunnel_vtable *vtable); |
| 263 | + |
| 264 | +/* |
| 265 | + * Gets the default vtable used by the secure tunnel. In order to mock something, we start with the default and then |
| 266 | + * mutate it selectively to achieve the scenario we're interested in. |
| 267 | + */ |
| 268 | +AWS_IOTDEVICE_API const struct aws_secure_tunnel_vtable *aws_secure_tunnel_get_default_vtable(void); |
| 269 | + |
| 270 | +AWS_EXTERN_C_END |
| 271 | + |
67 | 272 | #endif /* AWS_IOTDEVICE_SECURE_TUNNELING_IMPL_H */ |
0 commit comments