【计算机网络】OAI虚拟网卡通信

计算机网络

网络模型

OSI七层网络模型

OSI(Open System Interconnection)模型,即开放式系统互联,是国际标准化组织(ISO)制定的⼀个⽤于计算机或通信系统间互联的标准体系,旨在将计算机⽹络通信划分为七个不同的层级,每个层级都负责特定的功能。每个层级都构建在其下⽅的层级之上,并为上⽅的层级提供服务。七层从下到上分别是物理层、数据链路层、⽹络层、传输层、会话层、表示层和应⽤层。可以简称为“物数⽹传会表应”。

  1. 物理层:负责物理传输媒介的传输,例如电缆、光纤或⽆线信号。主要作⽤是传输⽐特流(就是由 1、0 转化为电流强弱来进⾏传输,到达⽬的地后再转化为 1、0,也就是我们常说的数模转换与模数转换)。这⼀层的数据叫做⽐特。

  2. 数据链路层:建⽴逻辑连接、进⾏硬件地址寻址、差错校验等功能。定义了如何让格式化数据以帧为单位进⾏ 传输,以及如何控制对物理介质的访问。将⽐特组合成字节进⽽组合成帧,⽤ MAC 地址访问介质,传输单位是桢。

  3. ⽹络层:负责数据的路由和转发,选择最佳路径将数据从源主机传输到⽬标主机。它使⽤IP地址来标识不同主 机和⽹络,并进⾏逻辑地址寻址。传输单位是数据报。常⻅的协议有ICMP、ARP、IP

  4. 传输层:提供端到端的数据传输服务。它使⽤TCP(传输控制协议)和UDP(⽤户数据报协议)来管理数据传输。

  5. 会话层:建⽴、管理和终⽌应⽤程序之间的会话连接。它处理会话建⽴、维护和终⽌,以及处理会话过程中的异常情况。

  6. 表示层:负责数据的格式转换、加密和解密,确保数据在不同系统之间的正确解释和呈现,也就是把计算机能够识别的东⻄转换成⼈能够能识别的东⻄(如图⽚、声⾳等)。

  7. 应⽤层:⽹络服务与最终⽤户的⼀个接⼝。这⼀层为⽤户的应⽤程序(例如电⼦邮件、⽂件传输和终端仿真)提供⽹络服务。常⻅的协议有:FTP、SMTP、HTTP、DNS。

TCP/IP四层网络模型

TCP/IP模型是⼀种⽤于组织和描述计算机⽹络通信的标准模型,它是互联⽹最常⽤的协议栈。TCP/IP模型由两个主 要协议组成:TCP(Transmission Control Protocol)和IP(Internet Protocol)。它是互联⽹通信的基础,也被 ⼴泛⽤于局域⽹和⼴域⽹等各种⽹络环境。 TCP/IP模型分为四个层级,每个层级负责特定的⽹络功能。以下是TCP/IP模型的层级及其功能:

  1. 应⽤层(Application Layer):该层与OSI模型的应⽤层和表示层以及会话层类似,提供直接与⽤户应⽤程序交互的接⼝。它为⽹络上的各种应⽤程序提供服务,如电⼦邮件(SMTP)、⽹⻚浏览(HTTP)、⽂件传输(FTP)等。

  2. 传输层(Transport Layer):该层对应OSI模型的传输层。它负责端到端的数据传输,提供可靠的、⽆连接的数据传输服务。主要的传输层协议有TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)。TCP提供可靠的数据传输,确保数据的正确性和完整性;⽽UDP则是⽆连接的,适⽤于不要求可靠性的传输,如实时⾳频和视频流。

  3. ⽹际层(Internet Layer):该层对应OSI模型的⽹络层。主要协议是IP(Internet Protocol),它负责数据包的路由和转发,选择最佳路径将数据从源主机传输到⽬标主机。IP协议使⽤IP地址来标识主机和⽹络,并进⾏逻辑地址寻址。

  4. ⽹络接⼝层(Link Layer):该层对应OSI模型的数据链路层和物理层。它负责物理传输媒介的传输,例如以太⽹、Wi-Fi等,并提供错误检测和纠正的功能。此外,⽹络接⼝层还包含硬件地址(MAC地址)的管理。

五层网络体系结构

五层⽹络体系结构是综合了OSI模型和TCP/IP模型所得来的。

五层⽹络体系结构分别为:应⽤层、运输层、⽹络层、数据链路层、物理层。各层功能分别如下:

  1. 应⽤层(Application Layer):与直接为⽤户的应⽤进程提供服务,是操作系统中的⽤户态,常⻅的有⽀持 万维⽹应⽤的HTTP协议、⽀持电⼦邮件的SMTP协议,⽀持⽂件传送的FTP协议等等。

  2. 传输层(Transport Layer):负责向两个主机中进程之间的通信提供服务,是端(端⼝)到端的通信。传输 层有两个传输协议。

  • TCP:⾯向连接的、可靠的传输控制协议

  • UDP: ⽆连接的,不提供可靠服务的⽤户数据报协议。

  1. ⽹络层(Network Layer):负责数据的路由和转发。它选择最佳路径将数据从源主机传输到⽬标主机,并使 ⽤逻辑地址(如IP地址)来标识主机和⽹络。

  2. 数据链路层(Data Link Layer):在直连⽹络中传输数据帧。它提供错误检测和纠正的功能,并负责数据的 帧同步、地址寻址和流量控制。在这⼀层级上,通常会使⽤MAC地址来标识⽹络设备。

  3. 物理层(Physical Layer):负责物理传输媒介的传输。这包括电缆、光纤、⽆线信号等。该层级定义了传输 数据位的形式、电压级别、传输速率等特性。

Linux网络协议栈

Linux tun虚拟网卡通信

什么是Linux tun设备

  • Linux TUN 设备是一种虚拟网络设备,用于在用户空间和内核空间之间建立数据通道,使用户空间程序可以通过这个设备与内核网络栈进行交互。

  • TUN 设备是一种通用的网络隧道设备,常用于实现虚拟专用网络(VPN)和其他网络隧道技术。

TUN 设备的工作原理

  • 将网络数据包从用户空间发送到内核空间,或者从内核空间发送到用户空间。可以收发第三层数据报文包,如IP封包,这使得用户空间的应用程序可以读取和处理传入的数据包,然后将数据包发送回TUN 设备,再由内核负责将数据包发送到目标地址。

  • 在使用 TUN 设备时,用户空间程序通常会打开 TUN 设备文件,并像读写普通文件一样对其进行读写操作。这样,用户空间程序就可以将网络数据包发送到 TUN 设备或者从 TUN 设备读取接收到的数据包。TUN 设备通常具有一个虚拟的 IP 地址,作为与内核网络栈进行交互的入口和出口

基本处理框架

  1. 创建 TUN 设备: 在 Linux 系统中,可以使用 ip 命令或者其他网络管理工具来创建 TUN 设备。用户空间应用程序与 TUN 设备交互: 用户空间的应用程序通常会打开 TUN 设备文件,这类似于打开普通文件。例如,应用程序可以打开 /dev/net/tun 设备文件。

  2. 数据包传输: 当用户空间的应用程序向 TUN 设备文件写入数据包时,数据包将被发送到内核空间的 TUN 设备驱动程序。这个过程是由内核的 TUN/TAP 驱动来完成的。

  3. 内核处理: 内核中的 TUN/TAP 驱动程序接收从用户空间传入的数据包。对于 TUN 设备,它会将数据包解析为 IP 数据包,并将其发送到内核网络栈进行进一步处理。

  4. 数据包处理: 在内核网络栈中,数据包将按照路由表和网络配置进行处理。如果数据包的目标地址与本地网络或者路由表匹配,那么数据包将被内核转发到目标地址。

  5. 接收数据包: 当内核收到其他网络设备传入的数据包(如网络接口收到的数据包),如果目标地址是 TUN 设备的 IP 地址,那么数据包将被传递给 TUN 设备驱动程序。

  6. 用户空间读取: 数据包通过 TUN 设备驱动程序传递到用户空间的应用程序打开的 TUN 设备文件。应用程序可以读取这些数据包并进行处理。

    如下是框架流程图:

参考资料:Linux tun虚拟网卡通信初识-CSDN博客

参考资料:虚拟网卡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__);

}

参考资料:tun驱动之write_tun的write返回值-CSDN博客

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值