Golang如何通过GTP隧道生成并传输HTTP流量(如Get请求)?

Golang如何通过GTP隧道生成并传输HTTP流量(如Get请求)? 大家好,很高兴认识你们!我是Go语言和这个论坛的新用户!

那么,我的问题如下: 我在我的电脑上部署了一个模拟的5G核心网络(项目是Free5GC)。我需要生成不同类型的流量(我打算从一个简单的对给定URL的HTTP GET请求开始)。这个流量将被发送到一个本地IP地址,该地址是模拟的UE(用户设备)。然后,这个UE会将流量路由到网络外部,并将响应返回给我。这个处理过程对我来说是透明的(我可以用Wireshark观察它)。 目前我手头只有项目提供的用于测试ping的代码,如下所示:

// ping IP(tunnel IP) from 60.60.0.2(127.0.0.1) to 60.60.0.20(127.0.0.8)
	gtpHdr, err := hex.DecodeString("32ff00340000000100000000")
	assert.Nil(t, err)
	icmpData, err := hex.DecodeString("8c870d0000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637")
	assert.Nil(t, err)
	ipv4hdr := ipv4.Header{
		Version:  4,
		Len:      20,
		Protocol: 1,
		Flags:    0,
		TotalLen: 48,
		TTL:      64,
		Src:      net.ParseIP("60.60.0.1").To4(),
		Dst:      net.ParseIP("60.60.0.101").To4(),
		ID:       1,
	}
	checksum := test.CalculateIpv4HeaderChecksum(&ipv4hdr)
	ipv4hdr.Checksum = int(checksum)

	v4HdrBuf, err := ipv4hdr.Marshal()
	assert.Nil(t, err)
	tt := append(gtpHdr, v4HdrBuf...)

	m := icmp.Message{
		Type: ipv4.ICMPTypeEcho, Code: 0,
		Body: &icmp.Echo{
			ID: 12394, Seq: 1,
			Data: icmpData,
		},
	}
	b, err := m.Marshal(nil)
	assert.Nil(t, err)
	b[2] = 0xaf
	b[3] = 0x88
	_, err = upfConn.Write(append(tt, b...))
	assert.Nil(t, err)

老实说,我不太清楚我需要做什么。我知道要发起一个HTTP GET请求,我可以使用: resp, err := http.Get(url) 但由于我必须将其发送到一个IP地址,而该地址又有一个GTP隧道,我不知道该如何操作。 我应该创建一个IPv4消息吗?我该如何在其中放入信息,使其成为一个HTTP GET请求? 抱歉我无法提供更具体的信息,但我对这个主题有点迷茫,并且一直没能找到有用的相关信息。 期待您的回复。 此致,Víctor。


更多关于Golang如何通过GTP隧道生成并传输HTTP流量(如Get请求)?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

更多关于Golang如何通过GTP隧道生成并传输HTTP流量(如Get请求)?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您的回答。

我目前正在研究那个 GitHub 项目,但我不太确定它是否真的允许我生成 GTP 头部。此外,如果我的意图是生成 ICMP 之外的其他类型的流量(比如 HTTP 请求,或任何类型),我理解我将需要构建数据包的不同层。

我计划使用 Go,因为连接不同网络功能的函数已经实现。按照您的建议,使用 cgo 的话,这会如何工作呢?

我今天在编码时,基本上创建了一个HTTP请求。然后我将GTP头部附加到IPv4头部,最后再附上HTTP请求。当然,这没有成功,因为我无法在IPv4头部中将HTTP设置为协议,因为它不是IPv4列出的协议。 数据包的层次结构如下: IP/UDP/GTP/IP/APP 在APP层,例如当我运行ping测试时,ICMP就位于此层。 对于HTTP消息,有什么建议我需要做什么吗?

现在我已经能够通过GTP隧道了。我必须手动构建GTP头部和IP头部的各个比特位,这意味着我目前已经实现的是加粗显示的这几层: IP/UDP/GTP/IP/APP。 应用层流量本身必须是一个完整的协议栈(例如,它可能是TCP/HTTP或任何其他使用传输层的应用流量)。然而,我不知道如何创建数据包来实现这一部分。我一直在寻找解决方案,但到目前为止似乎还没有找到。 理想情况下,我应该不仅能生成HTTP流量,还能模拟观看YouTube视频等场景。 有什么想法吗?

你具体不知道如何实现哪一部分?你的示例代码基本上包含了一个 GTP 头部的二进制数据块和一个 ICMP 数据的二进制数据块。如果你是在问如何生成额外的流量,你应该可以将这段代码放入一个循环中来持续进行 ping 操作;或者如果你需要更改数据,那么你需要找到一个能让你生成和/或解析 GTP 头部和/或 ICMP 数据的包。我搜索了一下,两分钟后能找到的只有一个:https://github.com/wmnsk/go-gtp,这也是 Oleg Butuzov 在你 StackOverflow 问题上推荐的。

也许你可以找一个用 C/C++/Python 等语言实现的包,然后通过 cgo 来使用它?

根据你的需求,你需要通过GTP隧道发送HTTP流量。以下是具体实现步骤:

1. 创建GTP隧道连接

首先需要建立到UPF的GTP-U连接:

package main

import (
    "encoding/hex"
    "fmt"
    "net"
    "net/http"
    "bytes"
    "golang.org/x/net/ipv4"
    "golang.org/x/net/icmp"
)

// 建立GTP连接
func setupGTPConnection(upfAddr string) (net.Conn, error) {
    conn, err := net.Dial("udp", upfAddr)
    if err != nil {
        return nil, fmt.Errorf("连接UPF失败: %v", err)
    }
    return conn, nil
}

2. 构建HTTP GET请求并通过GTP隧道发送

func sendHTTPOverGTP(conn net.Conn, targetURL, srcIP, dstIP string) error {
    // 1. 构建HTTP GET请求
    req, err := http.NewRequest("GET", targetURL, nil)
    if err != nil {
        return fmt.Errorf("创建HTTP请求失败: %v", err)
    }
    
    // 序列化HTTP请求
    var buf bytes.Buffer
    req.Write(&buf)
    httpData := buf.Bytes()
    
    // 2. 构建TCP段(简化版,实际需要完整的TCP握手)
    // 这里我们构建一个包含HTTP请求的TCP数据包
    tcpData := buildTCPPacket(srcIP, dstIP, 8080, 80, httpData)
    
    // 3. 构建IPv4头部
    ipv4hdr := ipv4.Header{
        Version:  4,
        Len:      20,
        Protocol: 6, // TCP协议
        Flags:    0,
        TotalLen: 20 + len(tcpData),
        TTL:      64,
        Src:      net.ParseIP(srcIP).To4(),
        Dst:      net.ParseIP(dstIP).To4(),
        ID:       1,
    }
    
    // 计算校验和
    checksum := calculateIPv4Checksum(&ipv4hdr)
    ipv4hdr.Checksum = int(checksum)
    
    // 序列化IPv4头部
    v4HdrBuf, err := ipv4hdr.Marshal()
    if err != nil {
        return fmt.Errorf("序列化IPv4头部失败: %v", err)
    }
    
    // 4. 构建GTP头部
    gtpHdr, err := hex.DecodeString("32ff00340000000100000000")
    if err != nil {
        return fmt.Errorf("解析GTP头部失败: %v", err)
    }
    
    // 5. 组合完整数据包
    packet := append(gtpHdr, v4HdrBuf...)
    packet = append(packet, tcpData...)
    
    // 6. 通过GTP隧道发送
    _, err = conn.Write(packet)
    if err != nil {
        return fmt.Errorf("发送数据失败: %v", err)
    }
    
    return nil
}

3. TCP数据包构建函数

func buildTCPPacket(srcIP, dstIP string, srcPort, dstPort int, data []byte) []byte {
    // TCP头部(简化版本,实际需要完整实现)
    tcpHeader := []byte{
        byte(srcPort >> 8), byte(srcPort), // 源端口
        byte(dstPort >> 8), byte(dstPort), // 目的端口
        0x00, 0x00, 0x00, 0x00, // 序列号
        0x00, 0x00, 0x00, 0x00, // 确认号
        0x50, 0x18, 0x00, 0x00, // 数据偏移 + 标志位
        0x00, 0x00, 0x00, 0x00, // 窗口大小和校验位
    }
    
    // 组合TCP头部和数据
    tcpPacket := append(tcpHeader, data...)
    
    // 计算TCP校验和(需要伪头部)
    return tcpPacket
}

func calculateIPv4Checksum(header *ipv4.Header) uint16 {
    // 简化的校验和计算
    // 实际应该使用完整的校验和算法
    data, _ := header.Marshal()
    var sum uint32
    for i := 0; i < len(data); i += 2 {
        sum += uint32(data[i])<<8 | uint32(data[i+1])
    }
    for sum>>16 != 0 {
        sum = (sum & 0xffff) + (sum >> 16)
    }
    return uint16(^sum)
}

4. 完整示例

func main() {
    // 配置参数
    upfAddr := "127.0.0.1:2152" // UPF地址
    targetURL := "http://example.com"
    srcIP := "60.60.0.1"        // 隧道内源IP
    dstIP := "60.60.0.101"      // 隧道内目的IP
    
    // 建立GTP连接
    conn, err := setupGTPConnection(upfAddr)
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        return
    }
    defer conn.Close()
    
    // 发送HTTP请求
    err = sendHTTPOverGTP(conn, targetURL, srcIP, dstIP)
    if err != nil {
        fmt.Printf("发送HTTP请求失败: %v\n", err)
        return
    }
    
    fmt.Println("HTTP请求已通过GTP隧道发送")
    
    // 接收响应(需要实现响应解析)
    // buffer := make([]byte, 1500)
    // n, err := conn.Read(buffer)
    // if err != nil {
    //     fmt.Printf("接收响应失败: %v\n", err)
    //     return
    // }
    // fmt.Printf("收到响应: %v bytes\n", n)
}

5. 使用原始套接字的替代方案

如果你需要更底层的控制,可以使用原始套接字:

import (
    "golang.org/x/net/ipv4"
    "syscall"
)

func sendRawSocket() {
    fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
    if err != nil {
        panic(err)
    }
    defer syscall.Close(fd)
    
    // 构建完整的数据包
    packet := buildCompletePacket()
    
    addr := syscall.SockaddrInet4{
        Port: 0,
        Addr: [4]byte{127, 0, 0, 1},
    }
    
    err = syscall.Sendto(fd, packet, 0, &addr)
    if err != nil {
        panic(err)
    }
}

注意事项:

  1. TCP握手:完整的HTTP通信需要TCP三次握手,上述示例是简化版本
  2. GTP头部:需要根据你的Free5GC配置调整GTP头部字段
  3. IP分片:大数据包可能需要分片处理
  4. 响应处理:需要实现接收和解析响应的逻辑

这个实现展示了如何将HTTP请求封装在TCP/IP数据包中,并通过GTP隧道发送。实际使用时需要根据你的网络配置调整IP地址和端口。

回到顶部