Skip to content

Commit a271d08

Browse files
dhrgitalxiord
authored andcommitted
devices/vsock: added connection handshake
Changed the host-initiated vsock connection protocol to include a trivial handshake. The new protocol looks like this: - [host] CONNECT <port><LF> - [guest/success] OK <assigned_host_port><LF> On connection failure, the host host connection is reset without any accompanying message, as before. This allows host software to more easily detect connection failures, for instance when attempting to connect to a guest server that may have not yet started listening for client connections. Signed-off-by: Dan Horobeanu <[email protected]>
1 parent 9cba328 commit a271d08

File tree

6 files changed

+43
-3
lines changed

6 files changed

+43
-3
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
appear to support multiple vsock devices. Any subsequent calls to this API
4040
endpoint will override the previous vsock device configuration.
4141
- Removed unused 'Halting' and 'Halted' instance states.
42+
- Vsock host-initiated connections now implement a trivial handshake protocol.
43+
See the [vsock doc](docs/vsock.md#host-initiated-connections) for details.
44+
Related to:
45+
[#1253](https://github.com/firecracker-microvm/firecracker/issues/1253),
46+
[#1432](https://github.com/firecracker-microvm/firecracker/issues/1432),
47+
[#1443](https://github.com/firecracker-microvm/firecracker/pull/1443)
4248

4349
### Fixed
4450

docs/vsock.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ send a connect command, in text form, specifying the destination AF_VSOCK port:
3939
"CONNECT PORT\n". Where PORT is the decimal port number, and "\n" is EOL (ASCII
4040
0x0A). Following that, the same connection will be forwarded by Firecracker to
4141
the guest software listening on that port, thus establishing the requested
42-
channel. If no one is listening, Firecracker will terminate the host
42+
channel. If the connection has been established, Firecracker will send an
43+
acknowledgement message to the connecting end (host-side), in the form
44+
"OK PORT\n", where `PORT` is the vsock port number assigned to
45+
the host end. If no one is listening, Firecracker will terminate the host
4346
connection.
4447

4548
1. Host: At VM configuration time, add a virtio-vsock device, with some path
@@ -48,6 +51,7 @@ connection.
4851
3. Host: `connect()` to AF_UNIX at `uds_path`.
4952
4. Host: `send()` "CONNECT <port_num>\n".
5053
5. Guest: `accept()` the new connection.
54+
6. Host: `read()` "OK <assigned_hostside_port>\n".
5155

5256
The channel is established between the sockets obtained at steps 3 (host)
5357
and 5 (guest).
@@ -126,7 +130,12 @@ port:
126130
```bash
127131
$ socat - UNIX-CONNECT:./v.sock
128132
CONNECT 52
133+
```
134+
135+
`socat` will display the connection acknowledgement message:
129136

137+
```
138+
OK 1073741824
130139
```
131140

132141
The connection should now be established (in the above example, between

src/devices/src/virtio/vsock/csm/connection.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ where
541541
///
542542
/// Raw data can either be sent straight to the host stream, or to our TX buffer, if the
543543
/// former fails.
544-
fn send_bytes(&mut self, buf: &[u8]) -> Result<()> {
544+
pub fn send_bytes(&mut self, buf: &[u8]) -> Result<()> {
545545
// If there is data in the TX buffer, that means we're already registered for EPOLLOUT
546546
// events on the underlying stream. Therefore, there's no point in attempting a write
547547
// at this point. `self.notify()` will get called when EPOLLOUT arrives, and it will
@@ -576,6 +576,11 @@ where
576576
Ok(())
577577
}
578578

579+
/// Return the connections state.
580+
pub fn state(&self) -> ConnState {
581+
self.state
582+
}
583+
579584
/// Check if the credit information the peer has last received from us is outdated.
580585
fn peer_needs_credit_update(&self) -> bool {
581586
(self.fwd_cnt - self.last_fwd_cnt_to_peer).0 as usize >= defs::CONN_CREDIT_UPDATE_THRESHOLD

src/devices/src/virtio/vsock/csm/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub enum Error {
3636
type Result<T> = std::result::Result<T, Error>;
3737

3838
/// A vsock connection state.
39-
#[derive(Debug, PartialEq)]
39+
#[derive(Clone, Copy, Debug, PartialEq)]
4040
pub enum ConnState {
4141
/// The connection has been initiated by the host end, but is yet to be confirmed by the guest.
4242
LocalInit,

src/devices/src/virtio/vsock/unix/muxer.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use std::io::Read;
3535
use std::os::unix::io::{AsRawFd, RawFd};
3636
use std::os::unix::net::{UnixListener, UnixStream};
3737

38+
use super::super::csm::ConnState;
3839
use super::super::defs::uapi;
3940
use super::super::packet::VsockPacket;
4041
use super::super::{
@@ -625,9 +626,20 @@ impl VsockMuxer {
625626
if let Some(conn) = self.conn_map.get_mut(&key) {
626627
let had_rx = conn.has_pending_rx();
627628
let was_expiring = conn.will_expire();
629+
let prev_state = conn.state();
628630

629631
mut_fn(conn);
630632

633+
// If this is a host-initiated connection that has just become established, we'll have
634+
// to send an ack message to the host end.
635+
if prev_state == ConnState::LocalInit && conn.state() == ConnState::Established {
636+
conn.send_bytes(format!("OK {}\n", key.local_port).as_bytes())
637+
.unwrap_or_else(|err| {
638+
conn.kill();
639+
warn!("vsock: unable to ack host connection: {:?}", err);
640+
});
641+
}
642+
631643
// If the connection wasn't previously scheduled for RX, add it to our RX queue.
632644
if !had_rx && conn.has_pending_rx() {
633645
self.rxq.push(MuxerRx::ConnRx(key));
@@ -885,6 +897,10 @@ mod tests {
885897
self.init_pkt(local_port, peer_port, uapi::VSOCK_OP_RESPONSE);
886898
self.send();
887899

900+
let mut buf = vec![0u8; 32];
901+
let len = stream.read(&mut buf[..]).unwrap();
902+
assert_eq!(&buf[..len], format!("OK {}\n", local_port).as_bytes());
903+
888904
(stream, local_port)
889905
}
890906
}

tests/integration_tests/functional/test_vsock.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from select import select
2020
from socket import socket, AF_UNIX, SOCK_STREAM
2121
from threading import Thread, Event
22+
import re
2223

2324
from host_tools.network import SSHConnection
2425

@@ -306,4 +307,7 @@ def _vsock_connect_to_guest(uds_path, port):
306307
buf = bytearray("CONNECT {}\n".format(port).encode("utf-8"))
307308
sock.send(buf)
308309

310+
ack_buf = sock.recv(32)
311+
assert re.match("^OK [0-9]+\n$", ack_buf.decode('utf-8')) is not None
312+
309313
return sock

0 commit comments

Comments
 (0)