aboutgitcodebugslistschat
path: root/tcp.c
Commit message (Collapse)AuthorAgeFilesLines
* tcp: Rework window handling, timers, add SO_RCVLOWAT and pools for sockets/pipesStefano Brivio2021-09-271-456/+719
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This introduces a number of fundamental changes that would be quite messy to split. Summary: - advertised window scaling can be as big as we want, we just need to clamp window sizes to avoid exceeding the size of our "discard" buffer for unacknowledged data from socket - add macros to compare sequence numbers - force sending ACK to guest/tap on PSH segments, always in pasta mode, whenever we see an overlapping segment, or when we reach a given threshold compared to our window - we don't actually use recvmmsg() here, fix comments and label - introduce pools for pre-opened sockets and pipes, to decrease latency on new connections - set receiving and sending buffer sizes to the maximum allowed, kernel will clamp and round appropriately - defer clean-up of spliced and non-spliced connection to timer - in tcp_send_to_tap(), there's no need anymore to keep a large buffer, shrink it down to what we actually need - introduce SO_RCVLOWAT setting and activity tracking for spliced connections, to coalesce data moved by splice() calls as much as possible - as we now have a compacted connection table, there's no need to keep sparse bitmaps tracking connection activity -- simply go through active connections with a loop in the timer handler - always clamp the advertised window to half our sending buffer, too, to minimise retransmissions from the guest/tap - set TCP_QUICKACK for originating socket in spliced connections, there's no need to delay them - fix up timeout for unacknowledged data from socket Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Drop TODO about sequence collision attacksStefano Brivio2021-09-271-1/+0
| | | | | | | A random initial sequence number based on a secret has already been there for a while. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tap: Completely de-serialise input message batchesStefano Brivio2021-09-271-15/+11
| | | | | | | | | | | | | Until now, messages would be passed to protocol handlers in a single batch only if they happened to be dequeued in a row. Packets interleaved between different connections would result in multiple calls to the same protocol handler for a single connection. Instead, keep track of incoming packet descriptors, arrange them in sequences, and call protocol handlers only as we completely sorted input messages in batches. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Decrease TCP_TAP_FRAMES to 8Stefano Brivio2021-09-271-1/+1
| | | | | | This significantly improves fairness in serving concurrent connections. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta, tcp: Update comment about spliced connection statesStefano Brivio2021-09-271-1/+4
| | | | | | ...we now have SPLICE_FIN_{FROM,TO,BOTH} too. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Don't reset connection from ESTABLISHED state on EPOLLHUPStefano Brivio2021-09-161-6/+1
| | | | | | | | | | | That might just mean we shut down the socket -- but we still have to go through the other states to ensure a orderly shutdown guest-side. While at it, drop the EPOLLHUP check for unhandled states: we should never hit that, but if we do, resetting the connection at that point is probably the wrong thing to do. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta, tcp: Mask EPOLLIN and EPOLLRDHUP after sending FINStefano Brivio2021-09-161-2/+14
| | | | | | | Now that we dropped EPOLLET, we'll keep getting EPOLLRDHUP, and possibly EPOLLIN, even if there's nothing to read anymore. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta, tcp: Break splice() loop once we've written everything that was readStefano Brivio2021-09-161-2/+7
| | | | | | That's a guarantee that we don't need to retry writing. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta, tcp: Don't set SPLICE_FIN_BOTH state on EPOLLHUPStefano Brivio2021-09-161-3/+1
| | | | | | | EPOLLHUP just means we shut down one side of the connection on *one* socket: remember, we have two sockets here. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta, tcp: Don't reset 'never_read' flag on write retriesStefano Brivio2021-09-161-2/+3
| | | | Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta, tcp: Don't set TCP_CORK on spliced socketsStefano Brivio2021-09-161-6/+1
| | | | | | | | ...throughput isn't everything: this leads (of course) to horrible latency with small, sparse messages. As a consequence, there's no need to set TCP_NODELAY either. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Fix setting window from maximum ACK sequence in batchStefano Brivio2021-09-161-6/+2
| | | | | | | | | If we're at the first message in a batch, it's safe to get the window value from it, and there's no need to subtract anything for a comparison on that's not even done -- we'll override it later in any case if we find messages with a higher ACK sequence number. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta, tcp: Set pipe descriptor numbers to -1 after closingStefano Brivio2021-09-161-0/+4
| | | | | | ...so that we don't try to close them again, even if harmless. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta, tcp: Drop EPOLLET for spliced, established connectionsStefano Brivio2021-09-161-5/+5
| | | | | | | ...tcp_handler_splice() doesn't guarantee we read all the available data, the sending buffer might be full. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Read SO_SNDBUF unconditionallyStefano Brivio2021-09-161-17/+11
| | | | | | | | Checking it only when the cached value is smaller than the current window of the receiver is not enough: it might shrink further while the receiver window is growing. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta: Clean up FIN connection flags once a connection is deletedStefano Brivio2021-09-151-0/+1
| | | | Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* pasta: Set spliced connection flag in epoll reference on compactionStefano Brivio2021-09-151-2/+2
| | | | | | ...otherwise, we'll mix indices with non-spliced connections. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Request retransmission with updated sequence also on partial write to ↵Stefano Brivio2021-09-141-11/+8
| | | | | | | | | | | socket If we couldn't write the whole batch of received packets to the socket, and we have missing segments, we still need to request their retransmission right away, otherwise it will take ages for the guest to figure out we're missing them. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: In ESTABLISHED state, acknowledge segments as they're sent to the socketStefano Brivio2021-09-141-6/+15
| | | | | | | | | ...instead of waiting for the remote peer to do that -- it's especially important in case we request retransmissions from the guest, but it also helps speeding up slow start. This should probably be a configurable behaviour in the future. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Properly time out ACK wait from tapStefano Brivio2021-09-141-3/+3
| | | | | | | | | Seen with iperf3: a control connection is established, no data flows for a while, all segments are acknowledged. The socket starts closing it, and we immediately time out because the last ACK from tap was one minute before that. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Don't mistake a FIN segment with no data for a Fast Retransmit requestStefano Brivio2021-09-141-1/+2
| | | | | | | It carries no data and usually duplicates the previous ACK sequence, but it's just a FIN. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Check errno on sendmmsg() failure, not just the return valueStefano Brivio2021-09-141-1/+1
| | | | Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Make sure sending window is initialised before sending to tapStefano Brivio2021-09-141-1/+1
| | | | | | | Seen with iperf3: the first packet from socket (data connection) is 65520 bytes and doesn't fit in the window. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Fixes for closing states, spliced connections, out-of-order packets, etc.Stefano Brivio2021-09-091-207/+400
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This fixes a number of issues found with some heavier testing with uperf and neper: - in most closing states, we can still accept data, check for EPOLLIN when appropriate - introduce a new state, ESTABLISHED_SOCK_FIN_SENT, to track the fact we already sent a FIN segment to the tap device, for proper sequence number bookkeeping - for pasta mode only: spliced connections also need tracking of (inferred) FIN segments and clean half-pipe shutdowns - streamline resetting epoll_wait bitmaps with a new function, tcp_tap_epoll_mask(), instead of repeating the logic all over the place - set EPOLLET for tap connections too, whenever we are waiting for EPOLLRDHUP or an event from the tap to proceed with data transfer, to avoid useless loops with EPOLLIN set - impose an additional limit on the sending window advertised to the guest, given by SO_SNDBUF: it makes no sense to completely fill the sending buffer and send a zero window: stop a bit before we hit that - handle *all* interrupted system calls as needed - simplify the logic for reordering of out-of-order segments received from tap: it's not a corner case, and the previous logic allowed for deadloops - fix comparison of seen IPv4 address when we get a new connection from a socket directed to the configured guest address Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp, udp: Restore usage of gateway for guest to connect to local hostStefano Brivio2021-09-011-0/+5
| | | | | | | This went lost in a recent rework: if the guest wants to connect directly to the host, it can use the address of the default gateway. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* passt, pasta: Introduce command-line options and port re-mappingStefano Brivio2021-09-011-62/+75
| | | | Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Fixes for early data in SOCK_SYN_SENT, closing states, clamping windowStefano Brivio2021-09-011-23/+30
| | | | | | More details here after rebase. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Proper error handling for sendmmsg() to UNIX domain socketStefano Brivio2021-08-261-37/+64
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | As data from socket is forwarded to the guest, sendmmsg() might send fewer bytes than requested in three different ways: - failing altogether with a negative error code -- ignore that, we'll get an error on the UNIX domain socket later if there's really an issue with it and reset the connection to the guest - sending less than 'vlen' messages -- instead of assuming success in that case and waiting for the guest to send a duplicate ACK indicating missing data, update the sequence number according to what was actually sent and spare some retransmissions - somewhat unexpectedly to me, sending 'vlen' or less than 'vlen' messages, returning up to 'vlen', with the last message being partially sent, and no further indication of errors other than the returned msg_len for the last partially sent message being less than iov_len. In this case, we would assume success and proceed as nothing happened. However, qemu would fail to parse any further message, having received a partial descriptor, and eventually close the connection, logging: serious error: oversized packet received,connection terminated. as the length descriptor for the next message would be sourced from the middle of the next successfully sent message, not from its header. Handle this by checking the msg_len returned for the last (even partially) sent message, and force re-sending the missing bytes, if any, with a blocking sendmsg() -- qemu must not receive anything else than that anyway. While at it, allow to send up to 64KiB for each message, the previous 32KiB limit isn't actually required, and just switch to a new message at each iteration on sending buffers, they are already MSS-sized anyway, so the check in the loop isn't really needed. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Never send ACK because of pending unacknowleged data when sending SYNStefano Brivio2021-08-241-1/+3
| | | | | | | | | | | | | | With a kernel older than 5.3 (no_snd_wnd set), ack_pending in tcp_send_to_tap() might be true at the beginning of a new connection initiated by a socket. This means we send the first SYN segment to the tap together with ACK set, which is clearly invalid and triggers the receiver to reply with an RST segment right away. Set ack_pending to 0 whenever we're sending a SYN segment. In case of a SYN, ACK segment sent by the caller, the caller passes the ACK flag explicitly. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Drop EPOLLET for non-spliced connectionsStefano Brivio2021-08-241-4/+4
| | | | | | | Socket-facing functions don't guarantee that all data is handled before they return: stick to level-triggered mode for TCP sockets. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Fast re-transmit, more fixes for closing states and no_snd_wndStefano Brivio2021-08-041-45/+73
| | | | | | | | | | | ...and while at it, fix an issue in the calculation of the last IOV buffer size: if we can't receive enough data to fill up the window, the last buffer can be filled completely. Also streamline the code setting iovec lengths if cached values are not matching. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Always allow ACKs when pending, fixes for no_snd_wnd and closing statesStefano Brivio2021-08-041-10/+23
| | | | | | | | | | | | | | | | We won't necessarily have another choice to ACK in a timely fashion if we skip ACKs from a number of states (including ESTABLISHED) when there's enough window left. Check for ACKed bytes as soon as it makes sense. If the sending window is not reported by the kernel, ACK as soon as we queue onto the socket, given that we're forced to use a rather small window. In FIN_WAIT_1_SOCK_FIN, we also have to account for the FIN flag sent by the peer in the sequence. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Lower TCP_TAP_FRAMES to 32Stefano Brivio2021-08-041-1/+1
| | | | | | | | Sending 64 frames in a batch looks quite bad when a duplicate ACK comes right at the beginning of it. Lowering this to 32 doesn't affect performance noticeably, with 16 the impact is more apparent. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Full batched processing for tap messagesStefano Brivio2021-07-271-121/+156
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Similar to UDP, but using a simple sendmsg() on iovec-style buffers from tap instead, as we don't need to preserve message boundaries. A quick test in PASTA mode, from namespace to init via tap: # ip link set dev pasta0 mtu 16384 # iperf3 -c 192.168.1.222 -t 60 [...] [ ID] Interval Transfer Bitrate [ 5] 0.00-60.00 sec 80.4 GBytes 11.5 Gbits/sec receiver # iperf3 -c 2a02:6d40:3cfc:3a01:2b20:4a6a:c25a:3056 -t 60 [...] [ ID] Interval Transfer Bitrate [ 5] 0.00-60.01 sec 39.9 GBytes 5.71 Gbits/sec receiver # ip link set dev pasta0 mtu 65520 # iperf3 -c 192.168.1.222 -t 60 [...] [ ID] Interval Transfer Bitrate [ 5] 0.00-60.01 sec 88.7 GBytes 12.7 Gbits/sec receiver # iperf3 -c 2a02:6d40:3cfc:3a01:2b20:4a6a:c25a:3056 -t 60 [...] [ ID] Interval Transfer Bitrate [ 5] 0.00-60.00 sec 79.5 GBytes 11.4 Gbits/sec receiver Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Limit TCP_INFO getsockopt() syscallsStefano Brivio2021-07-271-1/+2
| | | | | | | | There's no need to constantly query the socket for number of acknowledged bytes if we're far from exhausting the sending window, just do it if we're at least down to 90% of it. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp, udp: Map source address to gateway for any traffic from 127.0.0.0/8Stefano Brivio2021-07-261-3/+3
| | | | | | ...instead of just 127.0.0.1. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Fix re-send mechanism to tap on ACK timeoutStefano Brivio2021-07-261-2/+3
| | | | Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Simplify ACK accounting, skip some useless operations on tap handlingStefano Brivio2021-07-261-19/+10
| | | | Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Introduce scatter-gather IO path from socket to tapStefano Brivio2021-07-261-45/+509
| | | | | | | | | | | | | | | | | ...similarly to what was done for UDP. Quick performance test with 32KiB buffers, host to VM: $ iperf3 -c 192.0.2.2 -N [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-10.00 sec 8.47 GBytes 7.27 Gbits/sec 0 sender [ 5] 0.00-10.00 sec 8.45 GBytes 7.26 Gbits/sec receiver $ iperf3 -c 2a01:598:88ba:a056:271f:473a:c0d9:abc1 [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-10.00 sec 8.43 GBytes 7.24 Gbits/sec 0 sender [ 5] 0.00-10.00 sec 8.41 GBytes 7.22 Gbits/sec receiver Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp, udp: Allow binding ports in init namespace to both tap and loopbackStefano Brivio2021-07-261-18/+37
| | | | | | | | Traffic with loopback source address will be forwarded to the direct loopback connection in the namespace, and the tap interface is used for the rest. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tap: Fill the IPv6 flow label field to represent flow associationStefano Brivio2021-07-261-1/+6
| | | | | | | | | This isn't optional: TCP streams must carry a unique, hard-to-guess, non-zero label for each direction. Linux, probably among others, will otherwise refuse to associate packets in a given stream to the same connection. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Don't open a new connection from tap if both SYN and ACK are setStefano Brivio2021-07-211-1/+1
| | | | Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp, udp: Split IPv4 and IPv6 bound port setsStefano Brivio2021-07-211-23/+38
| | | | | | | | | | | Allow to bind IPv4 and IPv6 ports to tap, namespace or init separately. Port numbers of TCP ports that are bound in a namespace are also bound for UDP for convenience (e.g. iperf3), and IPv4 ports are always bound if the corresponding IPv6 port is bound (socket might not have the IPV6_V6ONLY option set). This will also be configurable later. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Replace source address also if it's the same as the guest addressStefano Brivio2021-07-211-2/+4
| | | | | | | | ...not just for loopback addresses, with the address of the default gateway. Otherwise, the guest might receive packets with source and destination set to the same address. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Increase maximum window scaling factor from 8 to 9Stefano Brivio2021-07-211-1/+1
| | | | | | | This is actually reasonable in terms of memory consumption and allows for better performance with local services. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Fix partial (ACK) message coalescing, ACK timeout, MSG_MORE flag settingStefano Brivio2021-07-171-2/+5
| | | | Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* passt: Add PASTA mode, major reworkStefano Brivio2021-07-171-458/+1085
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | PASTA (Pack A Subtle Tap Abstraction) provides quasi-native host connectivity to an otherwise disconnected, unprivileged network and user namespace, similarly to slirp4netns. Given that the implementation is largely overlapping with PASST, no separate binary is built: 'pasta' (and 'passt4netns' for clarity) both link to 'passt', and the mode of operation is selected depending on how the binary is invoked. Usage example: $ unshare -rUn # echo $$ 1871759 $ ./pasta 1871759 # From another terminal # udhcpc -i pasta0 2>/dev/null # ping -c1 pasta.pizza PING pasta.pizza (64.190.62.111) 56(84) bytes of data. 64 bytes from 64.190.62.111 (64.190.62.111): icmp_seq=1 ttl=255 time=34.6 ms --- pasta.pizza ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 34.575/34.575/34.575/0.000 ms # ping -c1 spaghetti.pizza PING spaghetti.pizza(2606:4700:3034::6815:147a (2606:4700:3034::6815:147a)) 56 data bytes 64 bytes from 2606:4700:3034::6815:147a (2606:4700:3034::6815:147a): icmp_seq=1 ttl=255 time=29.0 ms --- spaghetti.pizza ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 28.967/28.967/28.967/0.000 ms This entails a major rework, especially with regard to the storage of tracked connections and to the semantics of epoll(7) references. Indexing TCP and UDP bindings merely by socket proved to be inflexible and unsuitable to handle different connection flows: pasta also provides Layer-2 to Layer-2 socket mapping between init and a separate namespace for local connections, using a pair of splice() system calls for TCP, and a recvmmsg()/sendmmsg() pair for UDP local bindings. For instance, building on the previous example: # ip link set dev lo up # iperf3 -s $ iperf3 -c ::1 -Z -w 32M -l 1024k -P2 | tail -n4 [SUM] 0.00-10.00 sec 52.3 GBytes 44.9 Gbits/sec 283 sender [SUM] 0.00-10.43 sec 52.3 GBytes 43.1 Gbits/sec receiver iperf Done. epoll(7) references now include a generic part in order to demultiplex data to the relevant protocol handler, using 24 bits for the socket number, and an opaque portion reserved for usage by the single protocol handlers, in order to track sockets back to corresponding connections and bindings. A number of fixes pertaining to TCP state machine and congestion window handling are also included here. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Add support for kernels not exporting tcpi_snd_wnd via TCP_INFOStefano Brivio2021-06-081-6/+15
| | | | | | | | | | | | | | | | | | | | | Before commit 8f7baad7f035 ("tcp: Add snd_wnd to TCP_INFO"), the kernel didn't export tcpi_snd_wnd via TCP_INFO, which means we don't know what's the window size of the receiver, socket-side. To get TCP connections working in that case, ignore this value if it's zero during handshake, and use the initial window value as suggested by RFC 6928 (14 600 bytes, instead of 4 380 bytes), to keep network performance usable. To make the TCP dynamic responsive enough in this case, also check the socket for available data whenever we get an ACK segment from tap, instead of waiting until all the data from the tap is dequeued. While at it, fix the window scaling value sent for SYN and SYN, ACK segments: we want to increase the data pointer after writing the option, not the value itself. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Fix window size in initial SYN, ACK segment to guestStefano Brivio2021-06-051-4/+7
| | | | | | | | | | | | During handshake, the initial SYN, ACK segment to the guest, send as a response to the SYN segment, needs to report the unscaled value for the window, given that the handshake hasn't completed yet. While at it, fix the endianness for the window value in case TCP parameters can't be queried via TCP_INFO and we need to use the default value. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
* tcp: Properly initialise parameters for SO_ACCEPTCONN getsockopt()Stefano Brivio2021-05-211-2/+3
| | | | Signed-off-by: Stefano Brivio <sbrivio@redhat.com>