用户态协议栈之协议栈设计

简介: 用户态协议栈之协议栈设计

在物理层上,双绞线传的是电信号,光纤传的是光信号。网卡的作用就是将物理层的光电信号转换为数字信号(0101010)。在send的过程其实就是把数字信号转换为模拟信号(光电信号)。网卡即不在物理层,也不在数据链路层,而是在这两层之间做转换。然后把这个数据(通过sk_buff(搬运工)) 迁移到协议栈。 然后协议栈解析完数据之后将数据放入recv buffer ,然后应用程序通过系统调用就能得到这个数据。  

这个过程有两次拷贝,后来出现一种方式(DMA),将网卡映射到内存中去(mmap)。 应用程序是可以在内存中间直接读取这块映射过来的数据的。(DMA的方式不需要通过CPU去执行指令,直接将数据放入内存)这就叫零拷贝。

MMAP的底层实现是有DMA这种方式的支持的。需要一条总线。当DMA传输完数据之后会给CPU引发一个中断。

网卡驱动是运行在内核里面的,它使得网卡能够正常工作,能够正常收发数据。

有了用户态协议栈,我们对网卡的想象空间会更大

如何取到一帧完整的数据:

1:使用原生的socket    (RAW SOCKET)

2:利用一些开源框架   ----   netmap/dpdk        (旁路)

3:hook     bpf/ebpf

柔性数组

5 #define ETH_LEN 6
  6 
  7 
  8 struct ethhdr{
  9     unsigned char dst[ETH_LEN]; // 目的地址
 10     unsigned char src[ETH_LEN]; // 源地址
 11     unsigned short proto;       // 类型
 12 };
 13 
 14 struct iphead{
 15     unsigned char version:4,   // 版本
 16                   headlen:4;   // 首部长度
 17     unsigned char tos;         // 服务类型
 18     unsigned short totlen;     // 总长度 
 19     unsigned short id;         // 16位标识
 20     unsigned short flag:3,     // 3位标志位
 21                    offset:13,  // 13位片偏移 
 22     unsigned char ttl;         // 8位生存时间
 23     unsigned char proto;       // 8位协议,用来形容传输层用的是什么协议
 24     unsigned short check;      // 校验和
 25     
 26     unsigned int sip;          // 源地址
 27     unsigned int dip;          // 目的地址     
 28 }; 
 29 
 30 
 31 struct updhdr{ 
 32     unsigned short sport;  // 源端口号
 33     unsigned short dport;  // 目的端口号
 34     unsigned short length; // UDP长度
 35     unsigned short check;  // UDP校验
 36 };
 37 
 38 // UDP包
 39 struct udppkt{
 40     struct ethhdr eth; // 以太网头
 41     struct iphdr   ip; // IP头
 42     struct udphdr  udp;// UDP头
 43   
 44     unsigned char payload[0]; // 柔性数组(0长数组)
 45   
 46 };

长度为0的数组的主要用途是为了满足需要变长度的结构体

  • 用法 : 在一个结构体的最后, 申明一个长度为0的数组, 就可以使得这个结构体是可变长的. 对于编译器来说, 此时长度为0的数组并不占用空间, 因为数组名本身不占空间, 它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量
  • 对于编译器而言, 数组名仅仅是一个符号, 它不会占用任何空间, 它在结构体中, 只是代表了一个偏移量, 代表一个不可修改的地址常量!

两个情况下是可以使用柔性数组的:

1:内存是已经分配好的。

2:这个柔性数组的长度不确定但是我们是可以通过其他方法计算出来的。

eth0:物理网卡    ens33:虚拟网卡                    

sudo vim /etc/default/grub         -------------->    GRUB_CMDLINE_LINUX=  net.ifnames=0 biosdevname=0  (将ens33改成eth0)

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/poll.h>
#include <arpa/inet.h>
#define NETMAP_WITH_LIBS
#include <net/netmap_user.h> 
#pragma pack(1)
#define ETH_ALEN  6
#define PROTO_IP  0x0800
#define PROTO_ARP 0x0806
#define PROTO_UDP 17
#define PROTO_ICMP  1
#define PROTO_IGMP  2
struct ethhdr {
  unsigned char h_dest[ETH_ALEN];   // 目的地址
  unsigned char h_source[ETH_ALEN]; // 源地址
  unsigned short h_proto;           // 协议类型
};
struct iphdr {
  unsigned char version:4,  // 版本
                headlen:4;  // 首部长度
  unsigned char tos;        // 服务类型
  unsigned short tot_len;   // 总长度
  unsigned short id;        // 16位标识
  unsigned short flag:3,    // 3位标志位
                 offset:13; // 13位片偏移
  unsigned char ttl;        // 8位生存时间
  unsigned char protocol;   // 8位协议,用来形容传输层具体使用什么协议
  unsigned short check;     // 校验和
  unsigned int saddr;       // 源地址
  unsigned int daddr;       // 目的地址
};
struct udphdr {
  unsigned short source;    // 源端口
  unsigned short dest;      // 目的端口
  unsigned short len;       // UDP长度
  unsigned short check;     // UDP校验
};
struct udppkt {
  struct ethhdr eh;         //以太网头
  struct iphdr ip;          //ip头
  struct udphdr udp;        //udp头
  unsigned char body[128];  //包体
};
struct arphdr {
  unsigned short h_type;
  unsigned short h_proto;
  unsigned char h_addrlen;
  unsigned char protolen;
  unsigned short oper;
  unsigned char smac[ETH_ALEN];
  unsigned int sip;
  unsigned char dmac[ETH_ALEN];
  unsigned int dip;
};
struct arppkt {
  struct ethhdr eh;
  struct arphdr arp;
};
struct icmphdr {
  unsigned char type;
  unsigned char code;
  unsigned short check;
  unsigned short identifier;
  unsigned short seq;
  unsigned char data[32];
};
struct icmppkt {
  struct ethhdr eh;
  struct iphdr ip;
  struct icmphdr icmp;
};
struct tcphdr{
  unsigned short sport; // 源端口号
  unsigned short dport; // 目的端口号
  unsigned int seqnum;  // 序列号
  unsigned int acknum;  // 应答序列号
  unsigned char hdrlen:4, //tcp头
                resv:4;   //预留字段
  unsigned char cwr:1,
          ece:1,
          urg:1,  // 紧急
          ack:1,
          psh:1,
          rst:1,
          syn:1,
          fin:1;
  unsigned short win;         // 窗口大小
  unsigned short check;       // 校验和
  unsigned short urg_pointer; // 紧急数据指针
};
void print_mac(unsigned char *mac) {
  int i = 0;
  for (i = 0;i < ETH_ALEN-1;i ++) {
    printf("%02x:", mac[i]);
  }
  printf("%02x", mac[i]);
}
void print_ip(unsigned char *ip) {
  int i = 0;
  for (i = 0;i < 3;i ++) {
    printf("%d.", ip[i]);
  }
  printf("%d", ip[i]);
}
void print_arp(struct arppkt *arp) {
  print_mac(arp->eh.h_dest);
  printf(" ");
  print_mac(arp->eh.h_source);
  printf(" ");
  printf("0x%04x ", ntohs(arp->eh.h_proto));
  printf("  ");
}
int str2mac(char *mac, char *str) {
  char *p = str;
  unsigned char value = 0x0;
  int i = 0;
  while (p != '\0') {
    if (*p == ':') {
      mac[i++] = value;
      value = 0x0;
    } else {
      unsigned char temp = *p;
      if (temp <= '9' && temp >= '0') {
        temp -= '0';
      } else if (temp <= 'f' && temp >= 'a') {
        temp -= 'a';
        temp += 10;
      } else if (temp <= 'F' && temp >= 'A') {
        temp -= 'A';
        temp += 10;
      } else {  
        break;
      }
      value <<= 4;
      value |= temp;
    }
    p ++;
  }
  mac[i] = value;
  return 0;
}
void echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *hmac) {
  memcpy(arp_rt, arp, sizeof(struct arppkt));
  memcpy(arp_rt->eh.h_dest, arp->eh.h_source, ETH_ALEN);
  str2mac(arp_rt->eh.h_source, hmac);
  arp_rt->eh.h_proto = arp->eh.h_proto;
  arp_rt->arp.h_addrlen = 6;
  arp_rt->arp.protolen = 4;
  arp_rt->arp.oper = htons(2);
  str2mac(arp_rt->arp.smac, hmac);
  arp_rt->arp.sip = arp->arp.dip;
  memcpy(arp_rt->arp.dmac, arp->arp.smac, ETH_ALEN);
  arp_rt->arp.dip = arp->arp.sip;
}
void echo_udp_pkt(struct udppkt *udp, struct udppkt *udp_rt) {
  memcpy(udp_rt, udp, sizeof(struct udppkt));
  memcpy(udp_rt->eh.h_dest, udp->eh.h_source, ETH_ALEN);
  memcpy(udp_rt->eh.h_source, udp->eh.h_dest, ETH_ALEN);
  udp_rt->ip.saddr = udp->ip.daddr;
  udp_rt->ip.daddr = udp->ip.saddr;
  udp_rt->udp.source = udp->udp.dest;
  udp_rt->udp.dest = udp->udp.source;
}
unsigned short in_cksum(unsigned short *addr, int len)
{
  register int nleft = len;
  register unsigned short *w = addr;
  register int sum = 0;
  unsigned short answer = 0;
  while (nleft > 1)  {
    sum += *w++;
    nleft -= 2;
  }
  if (nleft == 1) {
    *(u_char *)(&answer) = *(u_char *)w ;
    sum += answer;
  }
  sum = (sum >> 16) + (sum & 0xffff); 
  sum += (sum >> 16);     
  answer = ~sum;
  return (answer);
}
void echo_icmp_pkt(struct icmppkt *icmp, struct icmppkt *icmp_rt) {
  memcpy(icmp_rt, icmp, sizeof(struct icmppkt));
  icmp_rt->icmp.type = 0x0; //
  icmp_rt->icmp.code = 0x0; //
  icmp_rt->icmp.check = 0x0;
  icmp_rt->ip.saddr = icmp->ip.daddr;
  icmp_rt->ip.daddr = icmp->ip.saddr;
  memcpy(icmp_rt->eh.h_dest, icmp->eh.h_source, ETH_ALEN);
  memcpy(icmp_rt->eh.h_source, icmp->eh.h_dest, ETH_ALEN);
  icmp_rt->icmp.check = in_cksum((unsigned short*)&icmp_rt->icmp, sizeof(struct icmphdr));
}
int main() {
  struct ethhdr *eh;
  struct pollfd pfd = {0};
  struct nm_pkthdr h;
  unsigned char *stream = NULL;
  struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);
  if (nmr == NULL) {
    return -1;
  }
  pfd.fd = nmr->fd;
  pfd.events = POLLIN;
  while (1) {
    int ret = poll(&pfd, 1, -1);
    if (ret < 0) continue;
    if (pfd.revents & POLLIN) {
      stream = nm_nextpkt(nmr, &h);
      eh = (struct ethhdr*)stream;
      if (ntohs(eh->h_proto) == PROTO_IP) {
        struct udppkt *udp = (struct udppkt*)stream;
        if (udp->ip.protocol == PROTO_UDP) {
          struct in_addr addr;
          addr.s_addr = udp->ip.saddr;
          int udp_length = ntohs(udp->udp.len);
          printf("%s:%d:length:%d, ip_len:%d --> ", inet_ntoa(addr), udp->udp.source, 
            udp_length, ntohs(udp->ip.tot_len));
          udp->body[udp_length-8] = '\0';
          printf("udp --> %s\n", udp->body);
#if 1
          struct udppkt udp_rt;
          echo_udp_pkt(udp, &udp_rt);
          nm_inject(nmr, &udp_rt, sizeof(struct udppkt));
#endif
#if 0
        } else if (udp->ip.protocol == PROTO_ICMP) {
          struct icmppkt *icmp = (struct icmppkt*)stream;
          printf("icmp ---------- --> %d, %x\n", icmp->icmp.type, icmp->icmp.check);
          if (icmp->icmp.type == 0x08) {
            struct icmppkt icmp_rt = {0};
            echo_icmp_pkt(icmp, &icmp_rt);
            //printf("icmp check %x\n", icmp_rt.icmp.check);
            nm_inject(nmr, &icmp_rt, sizeof(struct icmppkt));
          }
#endif  
        } else if (udp->ip.protocol == PROTO_IGMP) {
        } else {
          printf("other ip packet");
        }
#if 0
      }  else if (ntohs(eh->h_proto) == PROTO_ARP) {
        struct arppkt *arp = (struct arppkt *)stream;
        struct arppkt arp_rt;
        if (arp->arp.dip == inet_addr("192.168.2.217")) {
          echo_arp_pkt(arp, &arp_rt, "00:50:56:33:1c:ca");
          nm_inject(nmr, &arp_rt, sizeof(struct arppkt));
        }
#endif
      }
    } 
  }
}


目录
相关文章
|
25天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
116 9
|
16天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
22 1
|
3天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
23 5
|
19天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
22天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
24天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
47 4
|
28天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
2月前
数据结构(栈与列队)
数据结构(栈与列队)
20 1
|
2月前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
72 1
|
2月前
【数据结构】-- 栈和队列
【数据结构】-- 栈和队列
17 0