自己动手编写tcp/ip协议栈1:tcp包解析

简介: 学习tuntap中的tun的使用方法,并使用tun接口解析了ip包和tcp包,这是实现tcp/ip协议栈的第一步。

首发于github page 自己动手编写tcp/ip协议栈1:tcp包解析

tuntap

由于linux内核控制了网络接口,所以应用层不能直接使用网络接口来处理网络包。linux通过提供tuntap虚拟网络接口的机制,让用户可以在应用层处理原始的网络包。

tun使用示例

tuntap可以创建两种虚拟网络接口:tun和tap。tap是二层网络接口,提供mac帧。tun是三层网络接口,提供ip包。
我们处理tcp,ip协议,只需要使用tun接口,如果要处理arp,icmp协议则需要使用tap接口。这里只演示tun接口的使用。

test tun

func Test_tun(t *testing.T) {
   
    args := struct {
   
        cidr string
        name string
    }{
   
        cidr: "11.0.0.1/24",
        name: "testtun1",
    }
    fd, err := CreateTunTap(args.name, syscall.IFF_TUN|syscall.IFF_NO_PI)
    if err != nil {
   
        log.Fatalln(err)
    }

    out, err := exec.Command("ip", "addr", "add", args.cidr, "dev", args.name).CombinedOutput()
    if err != nil {
   
        log.Fatalln(err)
    }
    fmt.Println(out)

    out, err = exec.Command("ip", "link", "set", args.name, "up").CombinedOutput()
    if err != nil {
   
        log.Fatalln(err)
    }
    fmt.Println(out)
    buf := make([]byte, 1024)
    for {
   
        n, err := syscall.Read(fd, buf)
        if err != nil {
   
            log.Fatalln(err)
        }
        fmt.Println(hex.Dump(buf[:n]))
    }
}

使用curl发送一个简单的请求测试一下

curl -v  http://11.0.0.2/hello

将会得到类似下面的输出,这就是一个原始的ip包了

00000000  45 00 00 3c 80 40 40 00  40 06 a4 79 0b 00 00 01  |E..<.@@.@..y....|
00000010  0b 00 00 02 bb f8 00 50  08 a8 4a 04 00 00 00 00  |.......P..J.....|
00000020  a0 02 fa f0 67 67 00 00  02 04 05 b4 04 02 08 0a  |....gg..........|
00000030  bf b6 00 fa 00 00 00 00  01 03 03 07              |............|

解析ip包

直接看rfc791的对ip包的格式定义

rfc791#section-3.1

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Identification        |Flags|      Fragment Offset    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |         Header Checksum       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Source Address                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Destination Address                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

对照着rfc可以来解析如下这个包

00000000  45 00 00 3c 80 40 40 00  40 06 a4 79 0b 00 00 01  |E..<.@@.@..y....|
00000010  0b 00 00 02 bb f8 00 50  08 a8 4a 04 00 00 00 00  |.......P..J.....|
00000020  a0 02 fa f0 67 67 00 00  02 04 05 b4 04 02 08 0a  |....gg..........|
00000030  bf b6 00 fa 00 00 00 00  01 03 03 07              |............|

结果如下

IP 偏移量 TCP 偏移量 字节值 描述
4/8 0x4 IP 版本:IPv4
1 0x5 IP 首部长度为 5个32位数字:5 * 4 = 20 字节
2 0x00 服务类型
4 0x003c 总长度为 60 字节
6 0x8040 IP 标识
6 + 3/8 010 标志位:0: 保留位;必须为 0,1: 禁止分片 (DF) 0: 还有更多分片 (MF)
8 0 0000 0000 0000 分片偏移量:此处为 0
9 0x40 生存时间:64 秒
10 0x06 协议:0x06 表示 TCP
12 0xa479 首部校验和
16 0x0b 00 00 01 源 IP 地址:11.0.0.1
20 0x0b 00 00 02 目的 IP 地址:11.0.0.2

解析的代码如下

ip.go

func (i *IPPack) Decode(data []byte) (*IPPack, error) {
   
    header := &IPHeader{
   
        Version:        data[0] >> 4,
        HeaderLength:   (data[0] & 0x0f) * 4,
        TypeOfService:  data[1],
        TotalLength:    binary.BigEndian.Uint16(data[2:4]),
        Identification: binary.BigEndian.Uint16(data[4:6]),
        Flags:          data[6] >> 5,
        FragmentOffset: binary.BigEndian.Uint16(data[6:8]) & 0x1fff,
        TimeToLive:     data[8],
        Protocol:       data[9],
        HeaderChecksum: binary.BigEndian.Uint16(data[10:12]),
        SrcIP:          net.IP(data[12:16]),
        DstIP:          net.IP(data[16:20]),
    }
    header.Options = data[20:header.HeaderLength]
    i.IPHeader = header
    payload, err := i.Payload.Decode(data[header.HeaderLength:])
    if err != nil {
   
        return nil, err
    }
    i.Payload = payload
    return i, nil
}

需要注意的有以下几点

网络字节序

网络字节序都是大端的。大端和小端有些时候容易搞混,从网络包解析的场景来说就是解析包时一个数据的高位字节排在前面。
例如0x1234,大端表示为0x1234,小端表示为0x3412。可以发现大端表示法和我们日常书写的顺序一致。
golang中代码实现上也很简单。

func (bigEndian) Uint16(b []byte) uint16 {
   
    _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
    return uint16(b[1]) | uint16(b[0])<<8
}

func (littleEndian) Uint16(b []byte) uint16 {
   
    _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
    return uint16(b[0]) | uint16(b[1])<<8
}

ip头长度

ip包头的长度单位是32位数字,所以需要乘以4才是字节数。rfc原话是

IHL: 4 bits
Internet Header Length is the length of the internet header in 32
bit words, and thus points to the beginning of the data. Note that
the minimum value for a correct header is 5.

解析tcp包

rfc9293#name-header-format

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port          |       Destination Port        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Acknowledgment Number                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Data |       |C|E|U|A|P|R|S|F|                               |
| Offset| Rsrvd |W|C|R|C|S|S|Y|I|            Window             |
|       |       |R|E|G|K|H|T|N|N|                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Checksum            |         Urgent Pointer        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           [Options]                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               :
:                             Data                              :
:                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

对照着rfc可以来解析如下这个包

00000000  45 00 00 3c 80 40 40 00  40 06 a4 79 0b 00 00 01  |E..<.@@.@..y....|
00000010  0b 00 00 02 bb f8 00 50  08 a8 4a 04 00 00 00 00  |.......P..J.....|
00000020  a0 02 fa f0 67 67 00 00  02 04 05 b4 04 02 08 0a  |....gg..........|
00000030  bf b6 00 fa 00 00 00 00  01 03 03 07              |............|

结果如下

IP 偏移量 TCP 偏移量 字节值 描述
22 2 0xbbf8 源端口:48120
24 4 0x0050 目的端口:80
28 8 0x08a84a04 序列号:145246724
32 12 0x00000000 确认号:0
33 + 4/8 13 + 4/8 0xa 首部长度为10个32位数字:10 * 4 = 40 字节
33 + 10/8 13 + 10/8 0000 00 保留位
34 14 00 0010 标志位 URG:0 ACK:0 PSH:0 RST:0 SYN:1 FIN:0,所以是syn包
36 16 0xfaf0 窗口大小:64240
38 18 0x6767 校验和
40 20 0x0000 紧急指针
60 40 TCP 选项和填充

解析的代码如下

tcp.go

func (t *TcpPack) Decode(data []byte) (NetworkPacket, error) {
   
    header := &TcpHeader{
   
        SrcPort:        binary.BigEndian.Uint16(data[0:2]),
        DstPort:        binary.BigEndian.Uint16(data[2:4]),
        SequenceNumber: binary.BigEndian.Uint32(data[4:8]),
        AckNumber:      binary.BigEndian.Uint32(data[8:12]),
        DataOffset:     (data[12] >> 4) * 4,
        Reserved:       data[12] & 0x0F,
        Flags:          data[13],
        WindowSize:     binary.BigEndian.Uint16(data[14:16]),
        Checksum:       binary.BigEndian.Uint16(data[16:18]),
        UrgentPointer:  binary.BigEndian.Uint16(data[18:20]),
    }
    header.Options = data[20:header.DataOffset]
    t.TcpHeader = header
    payload, err := t.Payload.Decode(data[header.DataOffset:])
    if err != nil {
   
        return nil, err
    }
    t.Payload = payload
    return t, nil
}

有了解析ip包的经验后解析tcp包就简单了,需要注意的点和解析ip包时类似,就不做赘述了。

总结

本次我们学习了tuntap中的tun的使用方法,并使用tun接口解析了ip包和tcp包,这是我们自己实现tcp/ip协议栈的第一步。
文章中的代码在这里查看。

目录
相关文章
|
9月前
|
网络协议 Serverless
|
9月前
|
消息中间件 存储 网络协议
|
12月前
|
网络协议
网络通信的基石:TCP/IP协议栈的层次结构解析
在现代网络通信中,TCP/IP协议栈是构建互联网的基础。它定义了数据如何在网络中传输,以及如何确保数据的完整性和可靠性。本文将深入探讨TCP/IP协议栈的层次结构,揭示每一层的功能和重要性。
720 5
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
340 2
|
8月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
812 29
|
8月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
317 4
|
8月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
8月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
8月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
9月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
2240 1

推荐镜像

更多
  • DNS