in src/devices/src/virtio/vsock/csm/connection.rs [283:389]
fn send_pkt(&mut self, pkt: &VsockPacket, mem: &GuestMemoryMmap) -> VsockResult<()> {
// Update the peer credit information.
self.peer_buf_alloc = pkt.buf_alloc();
self.peer_fwd_cnt = Wrapping(pkt.fwd_cnt());
METRICS.vsock.tx_packets_count.inc();
match self.state {
// Most frequent case: this is an established connection that needs to forward some
// data to the host stream. Also works for a connection that has begun shutting
// down, but the peer still has some data to send.
ConnState::Established | ConnState::PeerClosed(_, false)
if pkt.op() == uapi::VSOCK_OP_RW =>
{
if pkt.buf_size() == 0 {
info!(
"vsock: dropping empty data packet from guest (lp={}, pp={}",
self.local_port, self.peer_port
);
return Ok(());
}
// Unwrapping here is safe, since we just checked `pkt.buf()` above.
if let Err(err) = self.send_bytes(mem, &pkt) {
// If we can't write to the host stream, that's an unrecoverable error, so
// we'll terminate this connection.
warn!(
"vsock: error writing to local stream (lp={}, pp={}): {:?}",
self.local_port, self.peer_port, err
);
self.kill();
return Ok(());
}
// We might've just consumed some data. If that's the case, we might need to
// update the peer on our buffer space situation, so that it can keep sending
// data packets our way.
if self.peer_needs_credit_update() {
self.pending_rx.insert(PendingRx::CreditUpdate);
}
}
// Next up: receiving a response / confirmation for a host-initiated connection.
// We'll move to an Established state, and pass on the good news through the host
// stream.
ConnState::LocalInit if pkt.op() == uapi::VSOCK_OP_RESPONSE => {
self.expiry = None;
self.state = ConnState::Established;
}
// The peer wants to shut down an established connection. If they have nothing
// more to send nor receive, and we don't have to wait to drain our TX buffer, we
// can schedule an RST packet (to terminate the connection on the next recv call).
// Otherwise, we'll arm the kill timer.
ConnState::Established if pkt.op() == uapi::VSOCK_OP_SHUTDOWN => {
let recv_off = pkt.flags() & uapi::VSOCK_FLAGS_SHUTDOWN_RCV != 0;
let send_off = pkt.flags() & uapi::VSOCK_FLAGS_SHUTDOWN_SEND != 0;
self.state = ConnState::PeerClosed(recv_off, send_off);
if recv_off && send_off {
if self.tx_buf.is_empty() {
self.pending_rx.insert(PendingRx::Rst);
} else {
self.expiry = Some(
Instant::now() + Duration::from_millis(defs::CONN_SHUTDOWN_TIMEOUT_MS),
);
}
}
}
// The peer wants to update a shutdown request, with more receive/send indications.
// The same logic as above applies.
ConnState::PeerClosed(ref mut recv_off, ref mut send_off)
if pkt.op() == uapi::VSOCK_OP_SHUTDOWN =>
{
*recv_off = *recv_off || (pkt.flags() & uapi::VSOCK_FLAGS_SHUTDOWN_RCV != 0);
*send_off = *send_off || (pkt.flags() & uapi::VSOCK_FLAGS_SHUTDOWN_SEND != 0);
if *recv_off && *send_off && self.tx_buf.is_empty() {
self.pending_rx.insert(PendingRx::Rst);
}
}
// A credit update from our peer is valid only in a state which allows data
// transfer towards the peer.
ConnState::Established | ConnState::PeerInit | ConnState::PeerClosed(false, _)
if pkt.op() == uapi::VSOCK_OP_CREDIT_UPDATE =>
{
// Nothing to do here; we've already updated peer credit.
}
// A credit request from our peer is valid only in a state which allows data
// transfer from the peer. We'll respond with a credit update packet.
ConnState::Established | ConnState::PeerInit | ConnState::PeerClosed(_, false)
if pkt.op() == uapi::VSOCK_OP_CREDIT_REQUEST =>
{
self.pending_rx.insert(PendingRx::CreditUpdate);
}
_ => {
debug!(
"vsock: dropping invalid TX pkt for connection: state={:?}, pkt.hdr={:?}",
self.state,
pkt.hdr()
);
}
};
Ok(())
}