计算机网络
网络模型
OSI七层网络模型
OSI(Open System Interconnection)模型,即开放式系统互联,是国际标准化组织(ISO)制定的⼀个⽤于计算机或通信系统间互联的标准体系,旨在将计算机⽹络通信划分为七个不同的层级,每个层级都负责特定的功能。每个层级都构建在其下⽅的层级之上,并为上⽅的层级提供服务。七层从下到上分别是物理层、数据链路层、⽹络层、传输层、会话层、表示层和应⽤层。可以简称为“物数⽹传会表应”。
-
物理层:负责物理传输媒介的传输,例如电缆、光纤或⽆线信号。主要作⽤是传输⽐特流(就是由 1、0 转化为电流强弱来进⾏传输,到达⽬的地后再转化为 1、0,也就是我们常说的数模转换与模数转换)。这⼀层的数据叫做⽐特。
-
数据链路层:建⽴逻辑连接、进⾏硬件地址寻址、差错校验等功能。定义了如何让格式化数据以帧为单位进⾏ 传输,以及如何控制对物理介质的访问。将⽐特组合成字节进⽽组合成帧,⽤ MAC 地址访问介质,传输单位是桢。
-
⽹络层:负责数据的路由和转发,选择最佳路径将数据从源主机传输到⽬标主机。它使⽤IP地址来标识不同主 机和⽹络,并进⾏逻辑地址寻址。传输单位是数据报。常⻅的协议有ICMP、ARP、IP
-
传输层:提供端到端的数据传输服务。它使⽤TCP(传输控制协议)和UDP(⽤户数据报协议)来管理数据传输。
-
会话层:建⽴、管理和终⽌应⽤程序之间的会话连接。它处理会话建⽴、维护和终⽌,以及处理会话过程中的异常情况。
-
表示层:负责数据的格式转换、加密和解密,确保数据在不同系统之间的正确解释和呈现,也就是把计算机能够识别的东⻄转换成⼈能够能识别的东⻄(如图⽚、声⾳等)。
-
应⽤层:⽹络服务与最终⽤户的⼀个接⼝。这⼀层为⽤户的应⽤程序(例如电⼦邮件、⽂件传输和终端仿真)提供⽹络服务。常⻅的协议有:FTP、SMTP、HTTP、DNS。
TCP/IP四层网络模型
TCP/IP模型是⼀种⽤于组织和描述计算机⽹络通信的标准模型,它是互联⽹最常⽤的协议栈。TCP/IP模型由两个主 要协议组成:TCP(Transmission Control Protocol)和IP(Internet Protocol)。它是互联⽹通信的基础,也被 ⼴泛⽤于局域⽹和⼴域⽹等各种⽹络环境。 TCP/IP模型分为四个层级,每个层级负责特定的⽹络功能。以下是TCP/IP模型的层级及其功能:
-
应⽤层(Application Layer):该层与OSI模型的应⽤层和表示层以及会话层类似,提供直接与⽤户应⽤程序交互的接⼝。它为⽹络上的各种应⽤程序提供服务,如电⼦邮件(SMTP)、⽹⻚浏览(HTTP)、⽂件传输(FTP)等。
-
传输层(Transport Layer):该层对应OSI模型的传输层。它负责端到端的数据传输,提供可靠的、⽆连接的数据传输服务。主要的传输层协议有TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)。TCP提供可靠的数据传输,确保数据的正确性和完整性;⽽UDP则是⽆连接的,适⽤于不要求可靠性的传输,如实时⾳频和视频流。
-
⽹际层(Internet Layer):该层对应OSI模型的⽹络层。主要协议是IP(Internet Protocol),它负责数据包的路由和转发,选择最佳路径将数据从源主机传输到⽬标主机。IP协议使⽤IP地址来标识主机和⽹络,并进⾏逻辑地址寻址。
-
⽹络接⼝层(Link Layer):该层对应OSI模型的数据链路层和物理层。它负责物理传输媒介的传输,例如以太⽹、Wi-Fi等,并提供错误检测和纠正的功能。此外,⽹络接⼝层还包含硬件地址(MAC地址)的管理。
五层网络体系结构
五层⽹络体系结构是综合了OSI模型和TCP/IP模型所得来的。
五层⽹络体系结构分别为:应⽤层、运输层、⽹络层、数据链路层、物理层。各层功能分别如下:
-
应⽤层(Application Layer):与直接为⽤户的应⽤进程提供服务,是操作系统中的⽤户态,常⻅的有⽀持 万维⽹应⽤的HTTP协议、⽀持电⼦邮件的SMTP协议,⽀持⽂件传送的FTP协议等等。
-
传输层(Transport Layer):负责向两个主机中进程之间的通信提供服务,是端(端⼝)到端的通信。传输 层有两个传输协议。
-
TCP:⾯向连接的、可靠的传输控制协议
-
UDP: ⽆连接的,不提供可靠服务的⽤户数据报协议。
-
⽹络层(Network Layer):负责数据的路由和转发。它选择最佳路径将数据从源主机传输到⽬标主机,并使 ⽤逻辑地址(如IP地址)来标识主机和⽹络。
-
数据链路层(Data Link Layer):在直连⽹络中传输数据帧。它提供错误检测和纠正的功能,并负责数据的 帧同步、地址寻址和流量控制。在这⼀层级上,通常会使⽤MAC地址来标识⽹络设备。
-
物理层(Physical Layer):负责物理传输媒介的传输。这包括电缆、光纤、⽆线信号等。该层级定义了传输 数据位的形式、电压级别、传输速率等特性。
Linux网络协议栈
Linux tun虚拟网卡通信
什么是Linux tun设备
-
Linux TUN 设备是一种虚拟网络设备,用于在用户空间和内核空间之间建立数据通道,使用户空间程序可以通过这个设备与内核网络栈进行交互。
-
TUN 设备是一种通用的网络隧道设备,常用于实现虚拟专用网络(VPN)和其他网络隧道技术。
TUN 设备的工作原理
-
将网络数据包从用户空间发送到内核空间,或者从内核空间发送到用户空间。可以收发第三层数据报文包,如IP封包,这使得用户空间的应用程序可以读取和处理传入的数据包,然后将数据包发送回TUN 设备,再由内核负责将数据包发送到目标地址。
-
在使用 TUN 设备时,用户空间程序通常会打开 TUN 设备文件,并像读写普通文件一样对其进行读写操作。这样,用户空间程序就可以将网络数据包发送到 TUN 设备或者从 TUN 设备读取接收到的数据包。TUN 设备通常具有一个虚拟的 IP 地址,作为与内核网络栈进行交互的入口和出口
基本处理框架
-
创建 TUN 设备: 在 Linux 系统中,可以使用 ip 命令或者其他网络管理工具来创建 TUN 设备。用户空间应用程序与 TUN 设备交互: 用户空间的应用程序通常会打开 TUN 设备文件,这类似于打开普通文件。例如,应用程序可以打开 /dev/net/tun 设备文件。
-
数据包传输: 当用户空间的应用程序向 TUN 设备文件写入数据包时,数据包将被发送到内核空间的 TUN 设备驱动程序。这个过程是由内核的 TUN/TAP 驱动来完成的。
-
内核处理: 内核中的 TUN/TAP 驱动程序接收从用户空间传入的数据包。对于 TUN 设备,它会将数据包解析为 IP 数据包,并将其发送到内核网络栈进行进一步处理。
-
数据包处理: 在内核网络栈中,数据包将按照路由表和网络配置进行处理。如果数据包的目标地址与本地网络或者路由表匹配,那么数据包将被内核转发到目标地址。
-
接收数据包: 当内核收到其他网络设备传入的数据包(如网络接口收到的数据包),如果目标地址是 TUN 设备的 IP 地址,那么数据包将被传递给 TUN 设备驱动程序。
-
用户空间读取: 数据包通过 TUN 设备驱动程序传递到用户空间的应用程序打开的 TUN 设备文件。应用程序可以读取这些数据包并进行处理。
如下是框架流程图:
参考资料:虚拟网卡tun/tap驱动程序设计原理 - 空间 (360doc.com)
OAI UE tun通信
Iperf <-> TUN <-> 协议栈 <-> 射频
初始化
Tun创建: netlink_init_tun()
-
创建tun口
-
绑定tun口名称: oaitun_ue1
static int tun_alloc(char *dev) {
struct ifreq ifr;
int fd, err;
if( (fd = open("/dev/net/tun", O_RDWR)) < 0 ) {
LOG_E(PDCP, "[TUN] failed to open /dev/net/tun\n");
return -1;
}
memset(&ifr, 0, sizeof(ifr));
/* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
*
* IFF_NO_PI - Do not provide packet information
*/
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
if( *dev )
strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)-1);
if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) {
close(fd);
return err;
}
strcpy(dev, ifr.ifr_name);
return fd;
}
int netlink_init_tun(char *ifprefix, int num_if, int id) {//for UE, id = 1, 2, ...,
int ret;
char ifname[64];
int begx = (id == 0) ? 0 : id - 1;
int endx = (id == 0) ? num_if : id;
for (int i = begx; i < endx; i++) {
sprintf(ifname, "oaitun_%.3s%d",ifprefix,i+1);
nas_sock_fd[i] = tun_alloc(ifname);
if (nas_sock_fd[i] == -1) {
LOG_E(PDCP, "TUN: Error opening socket %s (%d:%s)\n",ifname,errno, strerror(errno));
exit(1);
}
LOG_I(PDCP, "TUN: Opened socket %s with fd nas_sock_fd[%d]=%d\n",
ifname, i, nas_sock_fd[i]);
ret = fcntl(nas_sock_fd[i],F_SETFL,O_NONBLOCK); // 使用 fcntl 设置文件描述符为非阻塞模式(O_NONBLOCK)可以使得 I/O 操作不会被阻塞
if (ret == -1) {
LOG_E(PDCP, "TUN: Error fcntl (%d:%s)\n",errno, strerror(errno));
if (LINK_ENB_PDCP_TO_IP_DRIVER) {
exit(1);
}
}
} /* for */
return 1;
}
配置IP、添加静态路由:nas_config()
-
通过ioctl配置oaitun_ue1
/* 为了配置网络接口(例如绑定 IP 地址),需要通过系统调用 ioctl 向内核发送控制命令。ioctl 调用需要一个打开的套接字来发送这些命令。 */
int setInterfaceParameter(char *interfaceName, char *settingAddress, int operation) {
int sock_fd;
struct ifreq ifr;
struct sockaddr_in addr;
// 创建套接字
if((sock_fd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
LOG_E(OIP,"设置操作 %d,接口 %s,地址 %s:套接字创建失败\n",
operation, interfaceName, settingAddress);
return 1;
}
// 初始化 ifreq 结构体并设置接口名称
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, interfaceName, sizeof(ifr.ifr_name)-1);
// 初始化 sockaddr_in 结构体并设置地址
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
inet_aton(settingAddress, &addr.sin_addr);
memcpy(&ifr.ifr_ifru.ifru_addr, &addr, sizeof(struct sockaddr_in));
// 使用 ioctl 进行操作
if(ioctl(sock_fd, operation, &ifr) < 0) {
close(sock_fd);
LOG_E(OIP,"设置操作 %d,接口 %s,地址 %s:ioctl 调用失败\n",
operation, interfaceName, settingAddress);
return 2;
}
// 关闭套接字并返回成功
close(sock_fd);
return 0;
}
/* 通过 ioctl 系统调用来启用或禁用网络接口 */
int bringInterfaceUp(char *interfaceName, int up) {
int sock_fd;
struct ifreq ifr;
// 创建套接字
if((sock_fd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
LOG_E(OIP,"Bringing interface UP, for %s, failed creating socket\n", interfaceName);
return 1;
}
// 初始化 ifreq 结构体并设置接口名称
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, interfaceName, sizeof(ifr.ifr_name)-1);
// 设置或清除 IFF_UP 标志
if(up) {
ifr.ifr_flags |= IFF_UP | IFF_NOARP | IFF_MULTICAST;
if (ioctl(sock_fd, SIOCSIFFLAGS, (caddr_t)&ifr) == -1) {// 获取当前接口标志
close(sock_fd);
LOG_E(OIP,"Bringing interface UP, for %s, failed UP ioctl\n", interfaceName);
return 2;
}
} else {
ifr.ifr_flags &= (~IFF_UP);
if (ioctl(sock_fd, SIOCSIFFLAGS, (caddr_t)&ifr) == -1) {// 使用 ioctl 设置接口标志
close(sock_fd);
LOG_E(OIP,"Bringing interface down, for %s, failed UP ioctl\n", interfaceName);
return 2;
}
}
// 关闭套接字并返回成功
close( sock_fd );
return 0;
}
int nas_config(int interface_id, int thirdOctet, int fourthOctet, char *ifname) {
char ipAddress[20];
char broadcastAddress[20];
char interfaceName[20];
int returnValue;
/***** 网络接口配置 ****/
sprintf(ipAddress, "%s.%d.%d", baseNetAddress,thirdOctet,fourthOctet); // 确定IP地址
sprintf(broadcastAddress, "%s.%d.255",baseNetAddress, thirdOctet); // 确定广播地址
sprintf(interfaceName, "%s%s%d", (UE_NAS_USE_TUN || ENB_NAS_USE_TUN)?"oaitun_":ifname,
UE_NAS_USE_TUN?"ue": (ENB_NAS_USE_TUN?"enb":""),interface_id); // 确定网络接口名称
// 禁用网络接口
bringInterfaceUp(interfaceName, 0);
// 配置IP地址
returnValue= setInterfaceParameter(interfaceName, ipAddress,SIOCSIFADDR);
// 配置子网掩码
if(!returnValue)
returnValue= setInterfaceParameter(interfaceName, netMask,SIOCSIFNETMASK);
// 配置广播地址
if(!returnValue)
returnValue= setInterfaceParameter(interfaceName, broadcastAddress,SIOCSIFBRDADDR);
// 启动网络接口
if(!returnValue)
returnValue=bringInterfaceUp(interfaceName, 1);
if(!returnValue)
LOG_I(OIP,"Interface %s successfully configured, ip address %s, mask %s broadcast address %s\n",
interfaceName, ipAddress, netMask, broadcastAddress);
else
LOG_E(OIP,"Interface %s couldn't be configured (ip address %s, mask %s broadcast address %s)\n",
interfaceName, ipAddress, netMask, broadcastAddress);
/***** 添加静态路由 ****/
int res;
char command_line[500];
res = sprintf(command_line,
"ip rule add from %s/32 table %d && " // 添加一个规则,将来自特定 IP 地址的流量使用特定的表
"ip rule add to %s/32 table %d && " // 添加一个规则,将流向特定 IP 地址的流量使用相同的表
"ip route add default dev %s%d table %d", // 为该表添加默认路由,指定网络接口
ipAddress, interface_id - 1 + 10000,
ipAddress, interface_id - 1 + 10000,
UE_NAS_USE_TUN ? "oaitun_ue" : "oip",
interface_id, interface_id - 1 + 10000);
if (res < 0) {
LOG_E(OIP,"Could not create ip rule/route commands string\n");
return res;
}
background_system(command_line); // 通过pipe管道执行shell命令(具体实现参考OAI)
return returnValue;
}
基于上述操作,已经完成oaitun_ue1虚拟网络接口的创建和配置。
接收(应用层发送)
-
接收应用层程序发往oaitun_ue1的数据:
ue_tun_read_thread()
static void *ue_tun_read_thread(void *_)
{
extern int nas_sock_fd[]; // 之前创建的TUN fd
char rx_buf[NL_MAX_PAYLOAD];
int len;
int rnti;
protocol_ctxt_t ctxt;
int rb_id = 1;
pthread_setname_np( pthread_self(),"ue_tun_read");
while (1) {
len = read(nas_sock_fd[0], &rx_buf, NL_MAX_PAYLOAD); // 接收TUN口数据
if (len == -1) {
LOG_E(PDCP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
exit(1);
}
/* 协议栈发送处理逻辑,省略 */
}
return NULL;
}
参考资料:tun驱动之read_tun read函数-CSDN博客
发送(应用层接收)
-
发送数据至oaitun_ue1,进而实现应用程序的数据接收:
nr_sdap_rx_entity()
static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
rb_id_t pdcp_entity,
int is_gnb,
int has_sdap,
int has_sdapHeader,
int pdusession_id,
int rnti,
char *buf,
int size) {
/* 协议栈接收处理逻辑,省略 */
/* 向oaitun_ue1写数 */
extern int nas_sock_fd[];
int len = write(nas_sock_fd[0], &buf[offset], size-offset);
if (len != size-offset)
LOG_E(SDAP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
}