Starts an ordinary TcpListener which is bound to port_forward.source. After accepting a connection, the socket is passed to handle_tcp_proxy_connection function along with virtual_port, port_forward and wg.
This function:
- prepares channels
- create a TcpVirtualInterface, and
- starts virtual_interface.poll_loop(), which is the heavy-lifter controls communication b/w wg and the virtual TCP stack.
virtual_interface.poll_loop() function
- Creates a VirtualIpDevice.
- Creates an Interface that wraps the device (i.e.,
Interface<VirtualIpDevice>
) - Creates a server socket (prepares tx/rx buffer, etc.)
- The server socket starts listening on destination.ip()
- Creates a client socket (prepares tx/rx buffer, etc.)
- Runs a loop
- virtual_interface.poll(socket_set)
- Client socket is treated more carefully. Looks at the TCP state of the client_socket:
- Closed? Established? Take corresponding actions
- can_recv()? receive and push data by data_to_real_client_tx.send()
- can_send()?
- read data from data_to_virtual_server_rx if avaialble
- client_socket.send_slice(..)
- Waits until virtual_client_ready_rx receives signal
- Runs a loop. tokio::select (Note socket is an ordinary tokio TcpStream).
- socket.readable()?
- Read from the socket
- Send data to data_to_virtual_server_tx
- data_to_real_client_rx.recv()?
- socket.try_write(data)
- socket.readable()?
A TcpVirtualInterface has access to three channels:
- virtual_client_ready_tx
- data_to_real_client_tx
- data_to_virtual_server_rx
data_to_real_client_tx.send() is called by the poll_loop. The virtual client forwards data to the real client.
If you want use smoltcp stack you should implment the following traits:
- Device
- fn receive() -> (RxToken, TxToken)
- fn transmite() -> (TxToken)
- fn capabilities(): ethernet or ip? what's the MTU?
- RxToken::consume(f)
- TxToken::consume(f)
You add options to the InterfaceBuidler (i.e., InterfaceBuilder::new(device).ip_addrs(...).routes(...)
)
and call .finalize()
to finally get a interface.
The most important methods for an interface is the poll(sockets)
function.
poll(sockets): calls socket_ingress() and socket_egress(), which calls receive() + consume() and transmit() + socket.dispatch(), respectively. dispatch() is a function that takes a txtoken and a packet and consumes
When you want to create a socket, initialize rx/tx ring buffers (e.g., TcpSocketBuffer; the data will be exchanged by the of either ethernet packet or IP packet format, based on the device capabilities()).
- socket.send(): You enqueue L7 data to the tx_buffer
- socket.recv(): You dequeue L7 data from rx_buffer
Question: then who enqueues packet to rx_buffer, and who dequeues packets from the tx_buffer? Answer:
- socket_ingress() calls process_ip() and then process_tcp(). Inside process_tcp(), there are actions that enqueue_unallocated() and write_unallocated() to the rx_buffer.
- Acknowledged octets (?) are dequeued (in particular, dequeue_allocated()) from the tx_buffer.
- Impl device, rxtoken, txtoken
- Create Interface wrapper around device
- Create TcpRxbuffer and TcpTxbuffer and create the TcpSocket
- Run a loop: iface.poll() and take actions.
- Done?
The wg opens a UDP listening port (that communicates w/ UDP port on the peer wg endpoint).
- although we don't create a wg interface for userspace wg (i.g.,
wg0
TUN), the peer will think that we are running a wireguard endpoint, and it will send packets to the host UDP packet.
Handles handshake, keep-alive, etc.
Receives encrypted packets from the WireGuard endpoint, decapsulates them, and dispatches newly received IP packets.
route_tcp_segment()
function will look up wg.virtual_port_ip_tx
and if it
finds a match, it will return RouteResult::Dispatch(virtual_port, <proto>)
.
Two arguments
- wg:
- ip_dispatch_rx:
A wg
maintains a mapping from virtual_port to ip_dispatch_tx.
route_tcp_segment(segment: &[u8])
:
/// Makes a decision on the handling of an incoming TCP segment.
fn route_tcp_segment(&self, segment: &[u8]) -> RouteResult {
TcpPacket::new_checked(segment)
.ok()
.map(|tcp| {
if self
.virtual_port_ip_tx
.get(&VirtualPort(tcp.dst_port(), PortProtocol::Tcp))
.is_some()
{
RouteResult::Dispatch(VirtualPort(tcp.dst_port(), PortProtocol::Tcp))
} else { /* ... */ }
})
.unwrap_or(RouteResult::Drop)
}
Question: how does wg endpoint forwards the connection to the remote wg endpoint server? Or how does it know that it has to forward the connection?