1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
|
# Sending and Receiving
## Basic IO setup
By default, s2n-tls sends and receives data using a provided file descriptor
(usually a socket) and the read/write system calls. The file descriptor can be set with
`s2n_connection_set_fd()`, or separate read and write file descriptors can be
set with `s2n_connection_set_read_fd()` and `s2n_connection_set_write_fd()`.
The provided file descriptor should be active and connected.
In general the application is free to configure the file descriptor as preferred,
including socket options. s2n-tls itself sets a few socket options:
* If available, TCP_QUICKACK is used during the TLS handshake
* If available and enabled via `s2n_connection_use_corked_io()`, TCP_CORK is
used during the TLS handshake. TCP_NOPUSH or TCP_NODELAY may be used if TCP_CORK
is not available.
**Important Note:**
If the read end of the pipe is closed unexpectedly, writing to the pipe will raise
a SIGPIPE signal. **s2n-tls does NOT handle SIGPIPE.** A SIGPIPE signal will cause
the process to terminate unless it is handled or ignored by the application.
See the [signal man page](https://linux.die.net/man/2/signal) for instructions on
how to handle C signals, or simply ignore the SIGPIPE signal by calling
`signal(SIGPIPE, SIG_IGN)` before calling any s2n-tls IO methods.
## Blocking or Non-Blocking?
s2n-tls supports both blocking and non-blocking I/O.
* In blocking mode, each s2n-tls I/O function will not return until it has completed
the requested IO operation.
* In non-blocking mode, s2n-tls I/O functions will immediately return, even if the socket couldn't
send or receive all the requested data. In this case, the I/O function will return `S2N_FAILURE`,
and `s2n_error_get_type()` will return `S2N_ERR_T_BLOCKED`. The I/O operation will have to be
called again in order to send or receive the remaining requested data.
Some s2n-tls I/O functions take a `blocked` argument. If an I/O function returns an
`S2N_ERR_T_BLOCKED` error, the `blocked` argument will be set to a `s2n_blocked_status` value,
indicating what s2n-tls is currently blocked on. Note that unless an I/O function returns
`S2N_FAILURE` with an `S2N_ERR_T_BLOCKED` error, the `blocked` argument is meaningless, and should
not be used in any application logic.
Servers in particular usually prefer non-blocking mode. In blocking mode, a single connection
blocks the thread while waiting for more IO. In non-blocking mode, multiple connections
can make progress by returning control while waiting for more IO using methods like
[`poll`](https://linux.die.net/man/2/poll) or [`select`](https://linux.die.net/man/2/select).
To use s2n-tls in non-blocking mode, set the underlying file descriptors as non-blocking.
For example:
```c
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return S2N_FAILURE;
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) return S2N_FAILURE;
```
## Errors and Alerts
If the peer sends an alert, the next call to a read IO method will report **S2N_FAILURE** and
`s2n_error_get_type()` will return **S2N_ERR_T_ALERT**. The specific alert received
is available by calling `s2n_connection_get_alert()`.
In TLS1.3, all alerts are fatal. s2n-tls also treats all alerts as fatal in earlier
versions of TLS by default. `s2n_config_set_alert_behavior()` can be called to
force s2n-tls to treat pre-TLS1.3 warning alerts as not fatal, but that behavior
is not recommended unless required for compatibility. In the past, attacks against
TLS have involved manipulating the alert level to disguise fatal alerts as warnings.
If s2n-tls encounters a fatal error, the next call to a write IO method will send
a close_notify alert to the peer. Except for a few exceptions, s2n-tls does not
send specific alerts in order to avoid leaking information that could be used for
a sidechannel attack. To ensure that the alert is sent, `s2n_shutdown()` should
be called after an error.
## Performing the TLS Handshake
Before application data can be sent or received, an application must perform a handshake
to establish a TLS connection with the peer.
To perform the handshake, call `s2n_negotiate()` until it either returns **S2N_SUCCESS**
or returns **S2N_FAILURE** without a **S2N_ERR_T_BLOCKED** error.
For an example of how to perform a basic handshake, see [examples/s2n_negotiate.c](https://github.com/aws/s2n-tls/blob/main/docs/examples/s2n_negotiate.c)
## Application Data
After the TLS handshake, an application can send and receive encrypted data.
Although most s2n-tls APIs are not thread-safe, `s2n_send()` and `s2n_recv()`
may be called simultaneously from two different threads. This means that an
application may have one thread calling `s2n_send()` and one thread calling `s2n_recv()`,
but NOT multiple threads calling `s2n_recv()` or multiple threads calling `s2n_send()`.
Even if an application only intends to send data or only intends to receive data,
it should implement both send and receive in order to handle alerts and post-handshake
TLS control messages like session tickets.
### Sending Application Data
`s2n_send()` and its variants encrypt and send application data to the peer.
The sending methods return the number of bytes written and may indicate a partial
write. Partial writes are possible not just for non-blocking I/O, but also for
connections aborted while active.
A single call to `s2n_send()` may involve multiple system calls to write the
provided application data. s2n-tls breaks the application data into fixed-sized
records before encryption, and calls write for each record.
[See the record size documentation for how record size may impact performance](./ch08-record-sizes.md).
In non-blocking mode, `s2n_send()` will send data from the provided buffer and return the number of
bytes sent, as long as the socket was able to send at least 1 byte. If no bytes could be sent on the
socket, `s2n_send()` will return `S2N_FAILURE`, and `s2n_error_get_type()` will return
`S2N_ERR_T_BLOCKED`. To ensure that all the provided data gets sent, applications should continue
calling `s2n_send()` until the return values across all calls have added up to the length of the
data, or until `s2n_send()` returns an `S2N_ERR_T_BLOCKED` error. After an `S2N_ERR_T_BLOCKED`
error is returned, applications should call `s2n_send()` again only after the socket is
able to send more data. This can be determined by using methods like
[`poll`](https://linux.die.net/man/2/poll) or [`select`](https://linux.die.net/man/2/select).
Unlike OpenSSL, repeated calls to `s2n_send()` should not duplicate the original
parameters, but should update the inputs per the indication of size written.
`s2n_sendv_with_offset()` behaves like `s2n_send()`, but supports vectorized buffers.
The offset input should be updated between calls to reflect the data already written.
`s2n_sendv()` also supports vectorized buffers, but assumes an offset of 0.
Because of this assumption, a caller would have to make sure that the input vectors
are updated to account for a partial write. Therefore `s2n_sendv_with_offset()`
is preferred.
For examples of how to send `data` of length `data_size` with `s2n_send()`
or `s2n_sendv_with_offset()`, see [examples/s2n_send.c](https://github.com/aws/s2n-tls/blob/main/docs/examples/s2n_send.c)
### Receiving Application Data
`s2n_recv()` reads and decrypts application data from the peer, copying it into
the application-provided output buffer. It returns the number of bytes read, and
may indicate a partial read even if blocking IO is used.
It returns "0" to indicate that the peer has shutdown the connection.
By default, `s2n_recv()` will return after reading a single TLS record. `s2n_recv()` can be called
repeatedly to read multiple records. To allow `s2n_recv()` to read multiple records with a single
call, use `s2n_config_set_recv_multi_record()`.
In non-blocking mode, `s2n_recv()` will read data into the provided buffer and return the number of
bytes read, as long as at least 1 byte was read from the socket. If no bytes could be read from the
socket, `s2n_recv()` will return `S2N_FAILURE`, and `s2n_error_get_type()` will return
`S2N_ERR_T_BLOCKED`. To ensure that all data on the socket is properly received, applications
should continue calling `s2n_recv()` until it returns an `S2N_ERR_T_BLOCKED` error. After an
`S2N_ERR_T_BLOCKED` error is returned, applications should call `s2n_recv()` again only after the
socket has received more data. This can be determined by using methods like
[`poll`](https://linux.die.net/man/2/poll) or [`select`](https://linux.die.net/man/2/select).
Unlike OpenSSL, repeated calls to `s2n_recv()` should not duplicate the original parameters,
but should update the inputs per the indication of size read.
For an example of how to read all the data sent by the peer into one buffer,
see `s2n_example_recv()` in [examples/s2n_recv.c](https://github.com/aws/s2n-tls/blob/main/docs/examples/s2n_recv.c)
For an example of how to echo any data sent by the peer,
see `s2n_example_recv_echo()` in [examples/s2n_recv.c](https://github.com/aws/s2n-tls/blob/main/docs/examples/s2n_recv.c)
`s2n_peek()` can be used to check if more application data may be returned
from `s2n_recv()` without performing another read from the file descriptor.
This is useful when using `select()` on the underlying s2n-tls file descriptor, because
a call to `s2n_recv()` may read more data into s2n-tls's internal buffer than
was requested or can fit into the application-provided output buffer. This extra
application data will be returned by the next call to `s2n_recv()`, but `select()`
will be unable to tell the application that there is more data available and that
`s2n_recv()` should be called again. An application can solve this problem by
calling `s2n_peek()` to determine if `s2n_recv()` needs to be called again.
## Closing the Connection
`s2n_shutdown()` attempts a graceful closure at the TLS layer. It does not close the
underlying transport. The call may block on either reading or writing.
`s2n_shutdown()` should be called after an error in order to ensure that s2n-tls
sends an alert to notify the peer of the failure.
`s2n_shutdown()` will discard any application data received from the peer. This
can lead to data truncation, so `s2n_shutdown_send()` may be preferred for TLS1.3
connections where the peer continues sending after the application initiates
shutdown. See [Closing the connection for writes](#closing-the-connection-for-writes)
below.
Because `s2n_shutdown()` attempts a graceful shutdown, it will not return success
unless a close_notify alert is successfully both sent and received. As a result,
`s2n_shutdown()` may fail when interacting with a non-conformant TLS implementation
or if called on a connection in a bad state.
Once `s2n_shutdown()` is complete:
* The s2n_connection handle cannot be used for reading or writing.
* The underlying transport can be closed, most likely via `shutdown()` or `close()`.
* The s2n_connection handle can be freed via `s2n_connection_free()` or reused
via `s2n_connection_wipe()`
### Closing the connection for writes
TLS1.3 supports closing the write side of a TLS connection while leaving the read
side unaffected. This indicates "end-of-data" to the peer without preventing
future reads. This feature is usually referred to as "half-close".
s2n-tls offers the `s2n_shutdown_send()` method to close the write side of
a connection. Unlike `s2n_shutdown()`, it does not wait for the peer to respond
with a close_notify alert and does not discard any incoming application data. An
application can continue to call `s2n_recv()` after a call to `s2n_shutdown_send()`.
`s2n_shutdown_send()` may still be called for earlier TLS versions, but most
TLS implementations will react by immediately discarding any pending writes and
closing the connection.
If `s2n_shutdown_send()` is used, the application should still call `s2n_shutdown()`
or wait for `s2n_recv()` to return 0 to indicate end-of-data before cleaning up
the connection or closing the read side of the underlying transport.
## Custom IO Callbacks
By default, s2n-tls sends and receives data using a provided file descriptor
(usually a socket) and the read/write system calls. To change this default behavior,
an application can implement custom send and receive methods using `s2n_connection_set_recv_cb()`
and `s2n_connection_set_send_cb()`.
The application can pass inputs (such as a file descriptor or destination buffer)
to the custom IO methods by using `s2n_connection_set_recv_ctx()` and `s2n_connection_set_send_ctx()`.
s2n-tls will call the custom IO methods with the custom context instead of calling
the default implementation.
The custom IO methods may send or receive less than the requested length. They
should return the number of bytes sent/received, or set errno and return an error code < 0.
s2n-tls will interpret errno set to EWOULDBLOCK or EAGAIN as indicating a retriable
blocking error, and set **s2n_errno** and the s2n_blocked_status appropriately.
s2n-tls will interpret a return value of 0 as a closed connection.
|