捕获TCP/IP协议栈数据包的原理

网络 网络管理
wireshark或tcpdump相信大家都用过,这些工具看起来都很酷,因为我们平时都是在界面看到应用层的数据,这些工具居然可以让我们看到tcp/ip协议栈每层的数据。

[[398932]]

 本文转载自微信公众号「编程杂技」,作者 theanarkh   。转载本文请联系编程杂技公众号。

wireshark或tcpdump相信大家都用过,这些工具看起来都很酷,因为我们平时都是在界面看到应用层的数据,这些工具居然可以让我们看到tcp/ip协议栈每层的数据。本文介绍一下查看tcp/ip协议栈数据的方法。并实现一个简陋的sniffer,通过nodejs暴露出来使用。我们先看实现。

#include <stdio.h> 
#include <errno.h>  
#include <unistd.h> 
#include <sys/socket.h> 
#include <sys/types.h>   
#include <linux/in.h> 
#include <linux/if_ether.h> 
#include <stdlib.h> 
#include <node_api.h> 
#define DATA_LEN 500 
 
static napi_value start(napi_env env, napi_callback_info info) { 
  int sockfd; 
  int bytes; 
  char data[DATA_LEN]; 
  unsigned char *ipHeader; 
  unsigned char *macHeader; 
  unsigned char *transportHeader; 
  // 对ETH_P_IP协议的数据包感兴趣,PF_PACKET在早期内核是AF_INET 
  sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); 
  if (sockfd < 0) { 
    printf("创建socket错误"); 
    exit(1); 
  } 
 
  while (1) { 
    bytes = recvfrom(sockfd,data,DATA_LEN,0,NULL,NULL); 
    printf("读到字节数:%d\n",bytes); 
    macHeader = data; 
    printf("MAC报文----------\n"); 
    printf("源Mac地址: %02x:%02x:%02x:%02x:%02x:%02x\n"
           macHeader[0],macHeader[1],macHeader[2], 
           macHeader[3],macHeader[4],macHeader[5]); 
    printf("目的Mac地址: %02x:%02x:%02x:%02x:%02x:%02x\n"
           macHeader[6],macHeader[7],macHeader[8], 
           macHeader[9],macHeader[10],macHeader[11]); 
    printf("上层协议: %04x\n"
           (macHeader[12] << 8) + macHeader[13]); 
    // 跳过Mac头 
    ipHeader = data + 6 + 6 + 2; 
    printf("IP报文--------\n"); 
    printf("ip协议版本:%d\n"
             (ipHeader[0] & 0xF0) >> 4);  
    int ipHeaderLen = (ipHeader[0] & 0x0F) << 2; 
    printf("首部长度:%d\n"
         ipHeaderLen); 
    printf("区分服务:%d\n"
         ipHeader[1]);      
    printf("总长度:%d\n"
         (ipHeader[2]<<8)+ipHeader[3]);  
    printf("标识:%d\n"
         (ipHeader[4]<<8)+ipHeader[5]); 
    printf("标志:%d\n"
         (ipHeader[6] & 0xE0) >> 5);      
    printf("片偏移:%d\n"
         (ipHeader[6] & 0x11) + ipHeader[7]);   
    printf("TTL:%d\n"
         ipHeader[8]); 
    printf("上层协议:%d\n"
         ipHeader[9]);      
    printf("首部校验和:%x%x\n"
         ipHeader[10]+ipHeader[11]);                           
    printf("源ip:%d.%d.%d.%d\n"
         ipHeader[12],ipHeader[13], 
         ipHeader[14],ipHeader[15]); 
    printf("目的ip:%d.%d.%d.%d\n"
         ipHeader[16],ipHeader[17], 
         ipHeader[18],ipHeader[19]); 
 
    transportHeader = ipHeader + ipHeaderLen; 
    printf("传输层报文-----------\n"); 
    printf("源端口:%d\n"
         (transportHeader[0]<<8)+transportHeader[1]); 
    printf("目的端口:%d\n"
         (transportHeader[2]<<8)+transportHeader[3]); 
    printf("序列号:%ud%ud%ud%ud\n"
         transportHeader[4],transportHeader[5],transportHeader[6],transportHeader[7]); 
    printf("确认号:%ud\n"
         (transportHeader[8]<<24)+(transportHeader[9]<<16)+(transportHeader[10]<<8)+(transportHeader[11])); 
    printf("传输层首部长度:%d\n"
        ((transportHeader[12] & 0xF0) >> 4) * 4); 
    printf("FIN:%d\n"
        transportHeader[13] & 0x01); 
    printf("SYN:%d\n"
        (transportHeader[13] & 0x02) >> 1); 
    printf("RST:%d\n"
        (transportHeader[13] & 0x04) >> 2); 
    printf("PSH:%d\n"
        (transportHeader[13] & 0x08) >> 3); 
    printf("ACK:%d\n"
        (transportHeader[13] & 0x016) >> 4); 
    printf("URG:%d\n"
        (transportHeader[13] & 0x32) >> 5); 
    printf("窗口大小:%d\n"
        (transportHeader[14] << 8) + transportHeader[15]); 
    }} 
 
napi_value Init(napi_env env, napi_value exports) { 
  napi_value func; 
  napi_create_function(env, 
                    NULL
                    NAPI_AUTO_LENGTH, 
                    start, 
                    NULL
                    &func); 
  napi_set_named_property(env, exports, "start", func); 
  return exports; 

 
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.

我们看到实现并不复杂,首先创建一个socket,然后接收socket上面的数据进行分析就行。上面的代码可以捕获到所有发给本机的tcp/ip包,下面我们看看效果(有些字段还没有仔细处理)。

下面我们来看看底层的实现(2.6.13.1内核)。我们从socket函数的实现开始分析。

asmlinkage long sys_socket(int family, int type, int protocol){ 
  int retval; 
  struct socket *sock; 
  // 创建一个socket 
  retval = sock_create(family, type, protocol, &sock); 
  // 返回文件描述符给用户 
  retval = sock_map_fd(sock); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

接着看sock_create。

int sock_create(int family, int type, int protocol, struct socket **res){ 
  return __sock_create(family, type, protocol, res, 0); 

 
static int __sock_create(int family, int type, int protocol, struct socket **res, int kern){ 
  int err; 
  struct socket *sock; 
  // 分配一个socket 
  if (!(sock = sock_alloc())) { 
    // ... 
  } 
  // socket类型 
  sock->type  = type; 
  err = -EAFNOSUPPORT; 
  // 根据协议簇拿到对应的函数集,然后调用create函数 
  if ((err = net_families[family]->create(sock, protocol)) < 0) 
    goto out_module_put; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

我们看到__sock_create的逻辑很简单,根据协议簇拿到对应的函数集,然后执行其create函数。我们看看PF_PACKET协议簇对应的函数集。PF_PACKET协议簇通过packet_init注册了对应的函数集。

static int __init packet_init(void){ 
  sock_register(&packet_family_ops); 

 
static struct net_proto_family packet_family_ops = { 
  .family = PF_PACKET, 
  .create = packet_create, 
  .owner  = THIS_MODULE, 
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

我们看到create函数的值是packet_create。

static int packet_create(struct socket *sock, int protocol){ 
  struct sock *sk; 
  struct packet_sock *po; 
  int err; 
  // 分配一个packet_sock结构体 
  sk = sk_alloc(PF_PACKET, GFP_KERNEL, &packet_proto, 1); 
  // 赋值函数集 
  sock->ops = &packet_ops; 
  // 关联socket和sock 
  sock_init_data(sock, sk); 
  // 拿到一个packet_sock结构体,第一个字段是sock结构体(struct packet_sock *po) 
  po = pkt_sk(sk); 
  sk->sk_family = PF_PACKET; 
  // 接收数据包的函数 
  po->prot_hook.func = packet_rcv; 
  po->prot_hook.af_packet_priv = sk; 
 
  if (protocol) { 
    po->prot_hook.type = protocol; 
    dev_add_pack(&po->prot_hook); 
    sock_hold(sk); 
    po->running = 1; 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

packet_create首先创建了一个packet_sock结构体并初始化,最后调用dev_add_pack。

static struct list_head ptype_base[16];  
 
void dev_add_pack(struct packet_type *pt){ 
  int hash; 
 
  spin_lock_bh(&ptype_lock); 
  if (pt->type == htons(ETH_P_ALL)) { 
    netdev_nit++; 
    list_add_rcu(&pt->list, &ptype_all); 
  } else { 
    hash = ntohs(pt->type) & 15; 
    list_add_rcu(&pt->list, &ptype_base[hash]); 
  } 
  spin_unlock_bh(&ptype_lock); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

我们看到dev_add_pack的逻辑是往ptype_base对应的队列加入一个节点。接着我们看看网卡收到数据包的时候是如何处理的。

int netif_receive_skb(struct sk_buff *skb){ 
  type = skb->protocol; 
  list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) { 
    if (ptype->type == type && 
        (!ptype->dev || ptype->dev == skb->dev)) { 
      if (pt_prev)  
        ret = deliver_skb(skb, pt_prev); 
      pt_prev = ptype; 
    } 
  } 
  ret = pt_prev->func(skb, skb->dev, pt_prev); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

netif_receive_skb的逻辑中会根据收到mac包中上层协议字段找到对应的处理函数,比如本文的packet。最后执行func。从刚才的create函数我们看到func的值是packet_rcv。

static int packet_rcv(struct sk_buff *skb, struct net_device *dev,  struct packet_type *pt) { 
  __skb_queue_tail(&sk->sk_receive_queue, skb); 
  sk->sk_data_ready(sk, skb->len); 

  • 1.
  • 2.
  • 3.
  • 4.

packet_rcv首先把收到的数据包插入socket的接收队列,然后调用sk_data_ready通知socket,对应函数是sock_def_readable。

static void sock_def_readable(struct sock *sk, int len){ 
  if (sk->sk_sleep && waitqueue_active(sk->sk_sleep)) 
    wake_up_interruptible(sk->sk_sleep); 

  • 1.
  • 2.
  • 3.
  • 4.

sock_def_readable会唤醒阻塞在该socket的进程。那么这个队列里有什么呢?我们回到文章开始的代码,我们创建socket后阻塞在recvfrom。recvfrom通过层层调用最后执行对应函数集的recvmsg。

static int packet_recvmsg(struct kiocb *iocb, struct socket *sock, 
        struct msghdr *msg, size_t len, int flags){ 
 
  struct sk_buff *skb; 
  skb=skb_recv_datagram(sk,flags,flags&MSG_DONTWAIT,&err); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

packet_recvmsg从socket的接收队列取出一个数据包,我们看看skb_recv_datagram。

struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, 
          int noblock, int *err){ 
 
  struct sk_buff *skb; 
  long timeo; 
  /* 
    static inline long sock_rcvtimeo(const struct sock *sk, int noblock) 
    { 
      return noblock ? 0 : sk->sk_rcvtimeo; 
    } 
    获取没有数据包时等待的超时时间 
  */ 
  timeo = sock_rcvtimeo(sk, noblock); 
 
  do { 
    skb = skb_dequeue(&sk->sk_receive_queue); 
    // 有则返回 
    if (skb) 
      return skb; 
 
    // 没有 
    error = -EAGAIN; 
    // 不等待则直接返回 
    if (!timeo) 
      goto no_packet; 
  // 否则等待一段时间 
  } while (!wait_for_packet(sk, err, &timeo)); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

我们看到没有数据包的时候会等待一段时间,我们看看这个时间是多少。

sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT; 
#define MAX_SCHEDULE_TIMEOUT  LONG_MAX 
  • 1.
  • 2.

我们看到超时时间非常长,当然这个值我们可以通过setsockopt的SO_RCVTIMEO选项设置。接着我们看等待的逻辑wait_for_packet。

#define DEFINE_WAIT(name)           \ 
  wait_queue_t name = {           \ 
    .private  = current,        \ 
    .func   = autoremove_wake_function,   \ 
    .task_list  = LIST_HEAD_INIT((name).task_list), \ 
  } 
 
static int wait_for_packet(struct sock *sk, int *err, long *timeo_p){ 
  DEFINE_WAIT(wait); 
  prepare_to_wait_exclusive(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE); 
  int error = 0; 
  *timeo_p = schedule_timeout(*timeo_p); 
out
  finish_wait(sk->sk_sleep, &wait); 
  return error 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

wait_for_packet首先把当前进程插入对应的等待队列并修改进程状态为非就绪(TASK_INTERRUPTIBLE)

void fastcall prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state){ 
  // 把当前进程插入等待队列 
  if (list_empty(&wait->task_list)) 
    __add_wait_queue_tail(q, wait); 
  // 修改进程状态 
  set_current_state(state); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

接着执行进程调度schedule_timeout。

fastcall signed long __sched schedule_timeout(signed long timeout){ 
  struct timer_list timer; 
  unsigned long expire; 
  // 超时时间 
  expire = timeout + jiffies; 
  // 开启定时器 
  init_timer(&timer); 
  timer.expires = expire; 
  timer.data = (unsigned long) current
  timer.function = process_timeout; 
  // 启动定时器 
  add_timer(&timer); 
  // 进程调度 
  schedule(); 
  timeout = expire - jiffies; 
 
 out
  return timeout < 0 ? 0 : timeout; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

以上就是实现捕获tcp/ip协议栈数据包的底层原理。代码仓库https://github.com/theanarkh/node-sniffer

 

责任编辑:武晓燕 来源: 编程杂技
相关推荐

2019-08-21 05:48:06

TCPIP协议栈

2014-07-09 09:43:59

2010-09-08 15:11:36

TCP IP协议栈

2010-06-19 13:32:36

TCP IP协议栈

2019-03-28 13:34:22

IP TCP握手

2014-10-15 09:14:24

IP

2019-09-30 09:28:26

LinuxTCPIP

2010-09-08 15:24:28

TCP IP协议栈

2010-09-08 15:34:27

TCP IP协议栈

2010-09-08 15:15:12

TCP IP协议栈

2010-09-27 13:25:58

TCP IP协议栈

2010-06-13 14:54:40

TCP IP协议栈linux

2010-09-09 14:43:08

TCP IP协议栈

2019-07-01 08:51:49

TCPIPLinux

2011-11-28 16:03:49

wireshark数据包

2021-07-09 08:55:23

LinuxTCPIP

2010-06-13 13:39:46

TCP IP协议栈

2021-07-06 21:29:16

TCPIP协议栈

2010-06-19 14:10:35

TCP IP协议栈

2019-04-29 07:53:11

TCP数据包TCP网络编程
点赞
收藏

51CTO技术栈公众号