Skip to content

Commit

Permalink
update documentation (#97)
Browse files Browse the repository at this point in the history
* update documentation
* no longer building catch2 and test for macos
  • Loading branch information
RogerZhongAWS authored Jan 25, 2023
1 parent 989c1be commit 8058598
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 26 deletions.
16 changes: 3 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,12 @@ jobs:
cmake ../cmake
make
make install
- name: Install Catch2
working-directory: ${{ github.workspace }}
run: |
git clone --branch v2.13.6 https://github.com/catchorg/Catch2.git
cd Catch2
mkdir build
cd build
cmake ../
make
make install
- name: Building localproxy
working-directory: ${{ github.workspace }}
run: |
mkdir build
cd build
cmake .. -DOPENSSL_ROOT_DIR=/usr/local/Cellar/[email protected]/1.1.1s/ -DOPENSSL_LIBRARIES=/usr/local/Cellar/[email protected]/1.1.1s/lib/
cmake .. -DBUILD_TESTS=OFF -DOPENSSL_ROOT_DIR=/usr/local/Cellar/[email protected]/1.1.1s/ -DOPENSSL_LIBRARIES=/usr/local/Cellar/[email protected]/1.1.1s/lib/
make
- name: Upload Artifact
uses: actions/upload-artifact@v3
Expand Down Expand Up @@ -107,7 +97,7 @@ jobs:
run: |
mkdir build
cd build
cmake ..
cmake .. -DBUILD_TESTS=OFF
make
- name: Upload Artifact
uses: actions/upload-artifact@v3
Expand Down Expand Up @@ -180,7 +170,7 @@ jobs:
run: |
mkdir build
cd build
cmake -DBOOST_PKG_VERSION=1.76.0 -DWIN32_WINNT=0x0A00 -DBoost_USE_STATIC_LIBS=ON -DCMAKE_PREFIX_PATH="C:\Boost;C:\Program Files (x86)\Catch2;C:\Program Files (x86)\protobuf;C:\Program Files\OpenSSL" -G "Visual Studio 16 2019" -A x64 ..\
cmake -DBUILD_TESTS=OFF -DBOOST_PKG_VERSION=1.76.0 -DWIN32_WINNT=0x0A00 -DBoost_USE_STATIC_LIBS=ON -DCMAKE_PREFIX_PATH="C:\Boost;C:\Program Files (x86)\Catch2;C:\Program Files (x86)\protobuf;C:\Program Files\OpenSSL" -G "Visual Studio 16 2019" -A x64 ..\
msbuild localproxy.vcxproj -p:Configuration=Release
- name: Upload Artifact
uses: actions/upload-artifact@v3
Expand Down
14 changes: 14 additions & 0 deletions V1WebSocketProtocolGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,20 @@ Here are some important things to know for a high-level understanding of tunneli
- Locally detected network failures are communicated by sending _StreamReset_ over the tunnel using the active stream ID if one is active.
- If there is a network issue with the WebSocket connection, no control message is necessary to send. However, the active stream should be considered invalid and closed. Reconnect to the tunnel via the service and start a new stream

### Reconnecting to the secure tunnel

When the websocket is active, the local proxy will periodically send ping-pong message frames to keep the connection alive. The latency to the proxy server is also calculated during this time.
In the event of a network outage or connection timeout, the local proxy will keep running, close the active stream, and execute a retry loop to reestablish the websocket connection.
By default, the retry interval is 2.5 seconds, and there is no limit to the maximum number of retries. These defaults are configurable in the ProxySettings source file constant declarations.

### Recovering from a crash or unintended program exit

If the local proxy unexpectedly terminates, certain actions may be needed:
- If the local proxy terminated on the source side, the user is free to restart the local proxy with the same version and config.
- If the user wants to reconnect with an older version of the local proxy, they may need to restart the destination local proxy with a matching configuration. For example if using v1, remove any _serviceID_ -> port mappings.
- If the local proxy terminated on the destination side, the user needs to restart both the source and destination local proxies.
- The is because currently the tunnel peers have no knowledge if the other side has disconnected, and the source side will resend a stream start message as a result. While passing state information is technically possible through the payload of data messages, we do not support that at the moment.

### Tunneling message frames

WebSocket binary frames contain a sequence of tunnel frames or messages. Each data message has a **2-byte unsigned short, big endian** data length prefix, followed by sequence of bytes whose length is specified by the data length. These bytes must be parsed into a ProtocolBuffers object that uses the schema shown in this document. Every message received must be processed, and should be processed in order for data stream integrity. If the order of messages is lost or cannot be understood during processing by the client, it should end the data stream with a _StreamReset_. Messages may control the state of the data stream, or it may contain actual stream data. Inspecting the message's type is the first step in processing a message. A single data length + bytes parsed into a ProtocolBuffers message represents an entire tunneling message frame, and the beginning of the next frame's length prefix follows immediately. This is a visual diagram of a single frame:
Expand Down
59 changes: 49 additions & 10 deletions V2WebSocketProtocolGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The handshake performed to connect to a AWS IoT Secure Tunneling server is a sta
- It's recommended to use a UUIDv4 to generate the client token.
- The client token can be any string that matches the regex `^[a-zA-Z0-9-]{32,128}$`
- If a client token is provided, then local proxy needs to pass the same client token for subsequent retries (This is yet to be implemented in the current version of local proxy)
- If a client token is not provided, then the access token will become invalid after a successful handshake, and localproxy won't be able to reconnect using the same access token.
- If a client token is not provided, then the access token will become invalid after a successful handshake, and local proxy won't be able to reconnect using the same access token.

An example URI of where to connect is as follows:

Expand All @@ -62,6 +62,7 @@ Sec-WebSocket-Key: 9/h0zvwMEXrg06G+RjnmcA==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: aws.iot.securetunneling-2.0
access-token: AQGAAXiVzSmRL1VaJ22G7eRb\_CrPABsAAgABQQAMOTAwNTgyMDkxNTM4AAFUAANDQVQAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtZWFzdC0xOjcwMTU0NTg5ODcwNzprZXkvMmU4ZTAxMDEtYzE3YS00NjU1LTlhYWQtNjA2N2I2NGVhZWQyALgBAgEAeAJ2EsT4f5oCWm65Y8zRx\_nNaCjcG4FIeNV\_zMyhoOslAVAr521wChjzvogy-2-mxyoAAAB-MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwfBUUjMYI9gDEp0xwCARCAO1VX0NAiSjfU-Ar9PWYaNI5j9v77CxLcucht3tWZd57-Zq3aRQZBM4SQiy-D0Cgv31IfZ8pgWu8asm5FAgAAAAAMAAAQAAAAAAAAAAAAAAAAACniTwIAksExcMygMJ2uHs3\_\_\_\_\_AAAAAQAAAAAAAAAAAAAAAQAAAC9e5K3Isg5gHqO9LYX0geH4hrfthPEUhdrl9ZLksPxcVrk6XC4VugzrmUvEUPuR00J3etgVQZH\_RfxWrVt7Jmg=
client-token: 2da438cf-9a30-4148-b236-c338182f243c
User-Agent: localproxy Mac OS 64-bit/boost-1.68.0/openssl-3.0.0/protobuf-3.17.3
```

Expand Down Expand Up @@ -138,23 +139,55 @@ Local proxy will initiate a web socket handshake to connect to the tunnel, using
Once started successfully, source local proxy will listen for incoming connections on the configured ports. Destination local proxy, on the other hand, will wait for control message _StreamStart_. When client application connecting to a configured listening port, source local proxy will accept the TCP connection and sends a _StreamStart_ message to destination local proxy, for this specific service ID. When preparing to send _StreamStart_ message, source local proxy will also store service ID -> stream ID mapping for book keeping.
If multiple ports are used to start local proxy, each stream will send its own _StreamStart_ message when the TCP connection on the configured port is accepted. A _StreamStart_ message contains _streamID_ and _serviceID_. _serviceID_ helps uniquely identify a service transferred over a tunnel . _streamID_ helps to reset a stream and identify stale data.

#### Every serviceID is tied to its own streamID
In summary, every _serviceID_ has a one-to-one mapping with an active _streamID_, as this example describes below.

1. The user opens a tunnel and defines SSH1 and SSH2 service id's.
2. They then start running local proxy.
3. Upon startup, the source local proxy sends two stream start messages for SSH1 and SSH2, with _streamID_ 1 for both.
4. After sending the stream start messages, the mapping looks like: _serviceID_ SSH1 -> active _streamID_ 1 and _serviceID_SSH2 -> active _streamID_ 1.
5. The client applications start sending data for both service ids.
6. Eventually the ssh client for _serviceID_ SSH2 sends a signal that triggers the delivery of a stream_reset message.
7. This will mark _streamID_ 1 for _serviceID_ SSH2 as inactive and the source local proxy will send another stream_start message with _streamID_ 2 as part of the reset process.
8. After this the new mapping will be: _serviceID_ SSH1 -> active _streamID_ 1 and _serviceID_ SSH2 -> active _streamID_ 2.
9. Every data message received for SSH2 that has a _streamID_ of 1 thereafter will be ignored.

#### Step 3: End to end data transfer over the tunnel

On receiving a StreamStart, the destination local proxy will update the service ID -> Stream ID mapping and connect to the configured destination service for a service ID. The destination local proxy does not send a reply to the source local proxy on successful connection. Immediately after the source local proxy sends StreamStart and immediately after the destination establishes a valid TCP connection, each side respectively can begin to send and receive incoming messages on the active data stream. When the data stream is closed or disrupted (for the local proxy, this is a TCP close or I/O error on the TCP socket), a StreamReset control message with the currently stored stream ID and its service ID should be sent through the tunnel so the tunnel peer can react appropriately and end the data stream. Control messages associated with a stream should be processed with the same stream ID filter.

Here are some important things to know for a high-level understanding of tunneling data stream handling:

- The service may use the Service ID to decide how to route traffic between connected tunnel clients.
- For example, when local proxy received a data packet with Service ID SSH1, it will look up the configuration for SSH1 and see which port this service ID is mapped to. If SSH1 is mapped to port 22 on local host, then this data packet will be forward to port 22 on local host.
- The local proxy uses the service ID -> stream ID mapping to check the current active stream ID for a specific service ID.
- The stream ID validation for a certain stream(service ID) will only be performed on message type _StreamReset_ and _Data_. If a received message failed the stream ID validation, this message is considered to be stale and will be discarded by local proxy.
- The local proxy, and library clients may use stream ID to determine how to respond to or filter incoming messages
- The service may use the Service ID to decide how to route traffic between connected tunnel clients.
- For example, when local proxy received a data packet with Service ID SSH1, it will look up the configuration for SSH1 and see which port this service ID is mapped to. If SSH1 is mapped to port 22 on local host, then this data packet will be forward to port 22 on local host.
- A stream start message may include one of the service ID's defined as part of the tunnel, or no service ID. It may not, however, include any other service ID not defined during tunnel creation.
- If a stream is started without a service ID and then a subsequent message is sent with a service ID, the secure tunnel service closes the socket.
- As a best practice, the destination client should close all active streams attached to a service ID
- If the destination is operating in V1 mode and a new stream start message arrives with a service ID, the destination client should close the V1 stream and start a new active stream with that service ID.
- Any subsequent data messages must include a service ID associated with an active or previously active stream (a stream start message for the specific service ID must be sent first). Otherwise, the service will disconnect the client from the tunnel.
- The local proxy uses the service ID -> stream ID mapping to check the current active stream ID for a specific service ID.
- The stream ID validation for a certain stream(service ID) will only be performed on message type _StreamReset_ and _Data_. If a received message failed the stream ID validation, this message is considered to be stale and will be discarded by local proxy.
- The local proxy, and library clients may use stream ID to determine how to respond to or filter incoming messages
- For example: if a source sends a _StreamStart_ with a stream ID of 345 in response to a newly accepted TCP connection, and afterwards receives a _Data_ message marked with stream ID of 565, that data should be ignored. It's origin is tied to a prior connection over the tunnel from the perspective of the tunnel peer that originated it
- Another example: if a source local proxy sends a _StreamStart_ with a stream ID of 345 in response to a newly accepted TCP connection, and afterwards receives a _StreamReset_ message marked with stream ID of 565, that message should be ignored. Only a _StreamReset_ with a stream ID of 345 should cause the client to close its local connection
- Ending a data stream (normally or abnormally) is accomplished by either side sending a _StreamReset_ with the stream ID that is meant to be closed
- Locally detected network failures are communicated by sending _StreamReset_ over the tunnel using the active stream ID if one is active.
- If there is a network issue with the WebSocket connection, no control message is necessary to send. However, the active stream should be considered invalid and closed. Reconnect to the tunnel via the service and start a new stream.
- Ending a data stream (normally or abnormally) is accomplished by either side sending a _StreamReset_ with the service ID and stream ID that is meant to be closed
- Local TCP socket errors are communicated by sending _StreamReset_ over the tunnel using the active stream ID associated with a service ID if one exists.
- If there is a network issue with the WebSocket connection, no control message is necessary to send. However, all active streams should be considered invalid and closed. Reconnect to the tunnel via the service and start a new stream.

### Reconnecting to the secure tunnel

When the websocket is active, the local proxy will periodically send ping-pong message frames to keep the connection alive. The latency to the proxy server is also calculated during this time.
In the event of a network outage or connection timeout, the local proxy will keep running, close the active stream, and execute a retry loop to reestablish the websocket connection.
By default, the retry interval is 2.5 seconds, and there is no limit to the maximum number of retries. These defaults are configurable in the ProxySettings source file constant declarations.

### Recovering from a crash or unintended program exit

If the local proxy unexpectedly terminates, the tunnel peer will close its tcp sockets and upon reconnection, invalidate and reset all active streams.
If the user wants to restart the client and reconnect to the tunnel, certain actions may be needed:
- If the local proxy terminated on the source side, the user is free to restart the local proxy with the same version and config.
- If the user wants to reconnect with an older version of the local proxy, they may need to restart the destination local proxy with a matching configuration. For example if using v1, remove any _serviceID_ -> port mappings.
- If the local proxy terminated on the destination side, the user needs to restart both the source and destination local proxies.
- The is because currently the tunnel peers have no knowledge if the other side has disconnected, and the source side will resend a stream start message as a result. While passing state information is technically possible through the payload of data messages, we do not support that at the moment.

### Tunneling message frames

Expand Down Expand Up @@ -212,12 +245,18 @@ Tunneling frames (without the data length prefix) must parse into a _Message_ ob
- Change the tag numbers of exisiting field of ProtocolBuffers will cause backward compatibility issue between V1 and V2 local proxy. Fore more information, please read [Extending a Protocol Buffer](https://developers.google.com/protocol-buffers/docs/cpptutorial#extending-a-protocol-buffer).

### Backward compatibility

Backward compatibility does NOT apply if the client decides to send a previous version message format in the middle of an active websocket session.
Any attempts to do so will be rejected.
The following sections assume that both the source and destination have been configured prior to connecting to the websocket.
Any further configuration changes will require restarting the local proxy.

#### Backward compatibility between V1 and V2 local proxy
V1 local proxy protocol uses Sec-WebSocket-Protocol _aws.iot.securetunneling-1.0_ when communicates with AWS IoT Tunneling Service.
V2 local proxy protocol uses Sec-WebSocket-Protocol _aws.iot.securetunneling-2.0_ when communicates with AWS IoT Tunneling Service.
The communication between V1 and V2 local proxy is supported for a non-multiplexed tunnel.
- _aws.iot.securetunneling-1.0_ and _aws.iot.securetunneling-2.0_ subprotocol are interoperable.
- An empty service ID field in a message should be interpreted as service ID field is not present. This is because in protocol buffers _proto3_, it can not tell if a field is set with an empty string or a field is not present at all.
- An empty service ID field in a message should be interpreted as service ID field is not present. This is because in protocol buffers _proto3_, it can not tell if a field is set with an empty string or a field is not present at all. The local proxy assumes this value to be an empty string.
- Since V1 local proxy doesn't support multiplexing, data transferred using these two subprotocols can not be multiplexed. In that case, V2 local proxy needs to either use a single service ID or not send a service ID at all. Using V2 local proxy with multiple services to communicate with V1 local proxy is not supported.
- If V1 local proxy receives a message from V2 local proxy, it will ignore the service ID field.
- An empty service ID field in a received message should be interpreted as a message sent from V1 local proxy. In that case, V2 local proxy should ignore the service ID field.
Expand Down
Loading

0 comments on commit 8058598

Please sign in to comment.