Alien

View on GitHub

NetWork

Todo List

相关设计

启动网络功能

在启动时附带上NET=y(例如make run LOG=WARN img=fat32 SMP=1 GUI=n NET=y)即可启动网络功能

启动虚拟网卡

当启动时附带上NET=y时,会在Qemu的参数中加入netdev user,id=net -device virtio-net-device,netdev=net启动虚拟网卡

ifeq ($(NET),y)
QEMU_ARGS += -netdev user,id=net \
			 -device virtio-net-device,netdev=net
endif

初始化网卡设备和接口

/// driver/dtb.rs
fn virtio_device(transport: MmioTransport, addr: usize, irq: usize) {
    match transport.device_type() {
        // ...
        DeviceType::Network => virto_net(transport),
        // ...
    }
}

fn virto_net(transport: MmioTransport){
    let net = VirtIONet::<HalImpl, MmioTransport, NET_QUEUE_SIZE>::new(transport, NET_BUFFER_LEN)
        .expect("failed to create net driver");
    println!("MAC address: {:02x?}", net.mac_address());
    NET_DEVICE.call_once(|| VirtIONetWrapper::new(net));
    println!("virtio-net init finished");
    /// ...
    
}

使用virtio中的VirtIONet初始化网卡设备NET_DEVICE。此时需要将VirtIONet包装成VirtIONetWrapper,一方面为了满足多线程共享,另一方面要为VirtIONetWrapper实现smoltcp中的Device特征,以便使用VirtIONetWrapper初始化smoltcp上一层的Interface(Interface结构体是网络接口的抽象表示,它提供了与该接口相关的功能和操作)。其中对于Interface结构主要使用的方法为poll(),它的作用包括:

在socket每次需要发送或接收数据包前,都需要调用poll(),之后将对其进行封装,方便socket调用。

smoltcp中的相关socket结构

smoltcp是一个用于嵌入式系统的轻量级TCP/IP协议栈。它提供了一组简单易用的API来实现网络通信功能。

SocketSet

用于管理套接字(socket)的集合,提供方便的方式来创建、配置和管理多个套接字。通过Interface结构,可以配置和管理网络接口的地址、路由表,发送和接收IP数据包,以及处理邻居设备的信息。它是smoltcp库中用于网络通信的重要组件之一。

SocketSet结构的一些重要字段和方法:

SocketSet结构提供了一些方法来管理套接字集合,包括:

后面将其封装成SocketSetWrapper,并提供快速创建TCP、UDP套接字并将其放入集合中的接口。

TcpSocket

用于表示TCP套接字。

TcpSocket结构体包含了以下字段:

TcpSocket结构体还提供了一些方法,例如connectlistenacceptsendreceive等,用于实现TCP连接的建立、数据的发送和接收等操作。

UdpSocket

用于表示UDP套接字

下面是UdpSocket结构的一些重要字段和方法:

UdpSocket结构提供了一些方法来创建和管理UDP连接,包括:

通过UdpSocket结构,可以创建一个UDP套接字,并使用bind方法将其绑定到指定的端点。然后,使用send方法向指定的目标端点发送UDP数据,使用receive方法接收UDP数据。还可以使用can_sendcan_receive方法来检查套接字是否可以发送和接收数据。UdpSocket提供了一种简单而灵活的方式来创建和管理UDP连接,以进行无连接的、不可靠的数据传输。

Alien中使用的SocketData结构

在Alien中,我们使用SocketData结构来记录Socket的相关信息,其定义如下:

pub struct SocketData {
    /// 用于在SocketSet中找到smoltcp下的socket套接字
    pub handler: Option<SocketHandle>,
    /// socket 通信域  
    pub domain: Domain,
    /// 连接类型
    pub s_type: SocketType,
    /// 具体的通信协议
    pub protocol: usize,
    /// 连接的远端服务器的信息
    pub peer_addr: IpAddr,
    /// 本地的信息
    pub local_addr: IpAddr,
    pub listening: bool,
    pub is_server: bool,
}

相关方法:

在相关调用中需要先判断套接字类型,然后对不同的类型采用不同的实现。

alien提供的系统调用

pub fn sys_socket(domain: usize, socket_type: usize, protocol:usize) -> isize;

pub fn sys_sendto(
    socket: usize,
    message: *const u8,
    length: usize, 
    flags:i32, 
    dest_addr: *const usize, 
    dest_len: usize
    ) -> isize;

pub fn sys_recvfrom(
    socket: usize, 
    buffer: *mut u8, 
    length: usize, 
    flags:i32, 
    src_addr: *mut usize, 
    address_len: *mut u32
    ) -> isize;

pub fn sys_shutdown(socket: usize, how: usize) -> isize 

// sys_socket:   syscall_id 198
// 创建一个未绑定的socket套接字
// 
// domain: 指明套接字被创建的协议簇(包括文件路径协议簇和网络地址协议簇)
// type: 指明被创建的socket的类型
// protocol: 指明该socket应用于某一个特定的协议上。当确定了套接字使用的协议簇和类型,该参数可以取为0
// 返回: 如果创建成功则返回一个能在之后使用的文件描述符,否则返回错误信息。
pub fn sys_socket(domain: usize, socket_type: usize, protocol:usize) -> isize;



// sys_socket_pair:  syscall_id 199
// 创建一对未绑定的socket套接字,该对套接字可以用于全双工通信,或者用于父子进程之间的通信
// 如果向其中的一个socket写入后,再从该socket读时,就会发生阻塞。只能在另一个套接字中读。
// 往往和shutdown()配合使用
//
// domain: 指明套接字被创建的通信域(包括文件路径域和网络地址域)
// type: 指明被创建的socket的类型
// protocol: 指明该socket应用于某一个特定的协议上。取值为0时将导致此socket被视为未指明的默认类型,即实际需要的socket类型。
// sv[2]:  用于存放一对套接字的文件描述符。
// 返回: 如果创建成功则返回0,否则返回错误信息。
fn sys_socket_pair(domain: usize, socket_type: usize, protocol:usize, sv: *const usize ) -> SysResult;



// sys_bind: syscall_id 200
// 绑定socket的地址和端口
// 
// socket: 指明要操作socket的文件描述符id
// address: 指明存储有关绑定信息(sockaddr结构)的地址(sockaddr结构包括地址组信息address_family和要绑定的地址信息socket_address)
// address_len: address(即sockaddr结构)的长度。
// 返回: 执行成功则返回0,否则返回错误信息
fn sys_bind(socket: usize, address: *const usize, address_len: usize) -> SysResult;



// sys_listen: syscall_id 201
// 用于等待用户提交连接请求,一般用于bind之后,accept之前 
//
// socket: 指明要操作socket的文件描述符id
// backlog: 指明套接字侦听队列中正在处于半连接状态(等待accept)的请求数最大值。如果该值小于等于0,则自动调为0,同时也有最大值上限。
// 返回: 执行成功返回0,否则返回错误信息
fn sys_listen(socket: usize, backlog: usize) -> SysResult;



// sys_accept: syscall_id 202
// 用于取出套接字listen队列中的第一个连接,创建一个与指定套接字具有相同套接字类型的地址族的新套接字
// 新套接字用于传递数据,原套接字继续处理侦听队列中的连接请求。如果侦听队列中无请求,accept()将阻塞。
// 
// socket: 指明要操作socket的文件描述符id,需经过bind()和listen()处理
// address: 要么为空,要么指明连接的客户端相关信息(sockaddr结构)的保存地址
// address_len: 保存连接的客户端相关信息长度的地址。
// 返回: 执行成功则返回新的套接字的文件描述符,否则返回错误信息
fn sys_accept(socket: usize, address: *mut usize, address_len: *usize) -> SysResult;



// sys_connect: syscall_id 203
// 用于客户端请求在一个套接字上建立连接
// 
// socket: 指明要操作socket的文件描述符id
// address: 指明包含服务器地址和端口号的数据结构(sockaddr结构)的地址
// address_len: address(即sockaddr结构)的长度。
// 返回: 执行成功则返回0,否则返回错误信息
fn sys_connect(socket: usize, address: *const usize, address_len: usize) -> SysResult;



// sys_getsockname: syscall_id 204
// 查询一个套接字本地bind()的相关信息
//
// socket: 指明要操作socket的文件描述符id
// address: 指明相关信息(sockaddr结构)的保存地址
// address_len: 保存address长度的地址。
// 返回: 执行成功则返回0,否则返回错误信息
fn sys_getsockname(socket: usize, address: *const usize, address_len: *usize) -> SysResult;



// sys_getpeername: syscall_id 205
// 用于获取一个本地套接字所连接的远程服务器的信息。
// 
// socket: 指明要操作socket的文件描述符id
// address: 指明连接的客户端相关信息(sockaddr结构)的保存地址
// address_len: 保存address长度的地址。
// 返回: 执行成功则返回0,否则返回错误信息
fn sys_getpeername(socket: usize, address: *const usize, address_len: *usize) -> SysResult;




// sys_shutdown:  syscall_id 210 
// 关闭一个socket的发送操作或者接收操作
//
// socket: 指明要操作socket的文件描述符id
// how: 指明要关闭的操作:包括只关闭Read,只关闭Write,RW都关闭。
// 返回: 执行成功则返回0,否则返回错误信息
fn sys_shutdown(socket: usize, how: usize) -> SysResult;



// sys_sendto: syscall_id 206
// 发送消息。当面向连接时,dest_addr被忽略;当非面向连接时,消息发送给dest_addr。
//
// socket: 指明要操作socket的文件描述符id
// message: 指明要发送的message的首地址
// length: 指明message的长度
// flags: 指明message发送的类型
// dest_addr: 指明目的地的相关信息(sockaddr结构)的保存地址
// dest_len: 指明dest_addr的结构体的大小
// 返回: 如果发送成功,返回发送的字节数;否则返回错误信息
pub fn sys_sendto(socket: usize, message: *const u8, length: usize, flags:i32, dest_addr: *const usize, dest_len: usize) -> isize;

// sys_send:
// 同sys_sendto,简化版。
fn sys_send(socket: usize, message: *const usize, length: usize, flags:usize) -> SysResult;


// sys_recvfrom: syscall_id 207
// 接收消息。消息源地址的相关信息将会保存在src_addr所指向的位置处。
//
// socket: 指明要操作socket的文件描述符id
// buffer: 指明接收message的缓冲区的首地址
// length: 指明能接收message的最大长度
// flags: 指明接收message的类型
// src_addr: 指明信息发送方的相关信息(sockaddr结构)的保存地址
// address_len: 指明src_addr的(sockaddr)结构体的长度的地址
// 返回: 如果接收成功,返回接收message的字节数;否则返回错误信息
pub fn sys_recvfrom(socket: usize, buffer: *mut u8, length: usize, flags:i32, src_addr: *mut usize, address_len: *mut u32) -> isize;


// sys_recv:
// 同sys_recvfrom, 简化版
fn sys_recv(socket: usize, buffer: *const usize, length: usize, flags:usize) -> SysResult;


// sys_setsockopt: syscall_id 208
// sys_getsockopt: syscall_id 209
// sys_sendmsg:  syscall_id 211
// sys_recvmsg:  syscall_id 212



参考资料

socket.h

Matruin实现 socket

rCore-N net

virtio-net

组件化OS–aceros的改进:支持和优化lwip网络协议栈

详解:VirtIO Networking 虚拟网络设备实现架构

[smoltcp - Rust - Docs.rs]