gonetwork-programminggopacket

How To Serialize A Modified Go Packet Packet Into Real IP Packet


Why

I want to write a proxy server, proxy server changes IP/port of a packet and emits modified.

Attempt

package main

import (
  "encoding/hex"
  "github.com/google/gopacket"
  "github.com/google/gopacket/layers"
  "fmt"
  "net"
)

func main() {

  packetData := []byte{
    69, 0, 0, 63, 64, 237, 64, 0, 64, 6, 74, 221, 192,
    168, 1, 90, 52, 85, 184, 151, 141, 230, 0, 80,
    174, 147, 86, 192, 18, 107, 243, 149, 128, 24,
    0, 229, 92, 65, 0, 0, 1, 1, 8, 10, 22, 138, 85, 109,
    48, 16, 32, 253, 49, 50, 51, 52, 53, 54, 55, 56,
    57, 48, 10,
  }

  fmt.Println("Hex dump of real IP packet taken as input:\n")
  fmt.Println(hex.Dump(packetData))

  packet := gopacket.NewPacket(packetData, layers.LayerTypeIPv4, gopacket.Default)
  if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil {
    ip := ipLayer.(*layers.IPv4)
    dst := ip.DstIP.String()
    src := ip.SrcIP.String()

    if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
      tcp := tcpLayer.(*layers.TCP)
      dst = fmt.Sprintf("%s:%d", dst, tcp.DstPort)
      src = fmt.Sprintf("%s:%d", src, tcp.SrcPort)
      fmt.Printf("From %s to %s\n\n", src, dst)

      ip.DstIP = net.ParseIP("8.8.8.8")

      options := gopacket.SerializeOptions{
        ComputeChecksums: true,
        FixLengths: true,
      }
      newBuffer := gopacket.NewSerializeBuffer()
      gopacket.SerializeLayers(newBuffer, options,
          ip,
          tcp,
      )
      outgoingPacket := newBuffer.Bytes()

      fmt.Println("Hex dump of go packet serialization output:\n")
      fmt.Println(hex.Dump(outgoingPacket))

    }

  }

}

Output

Hex dump of real IP packet taken as input:

00000000  45 00 00 3f 40 ed 40 00  40 06 4a dd c0 a8 01 5a  |E..?@.@.@.J....Z|
00000010  34 55 b8 97 8d e6 00 50  ae 93 56 c0 12 6b f3 95  |4U.....P..V..k..|
00000020  80 18 00 e5 5c 41 00 00  01 01 08 0a 16 8a 55 6d  |....\A........Um|
00000030  30 10 20 fd 31 32 33 34  35 36 37 38 39 30 0a     |0. .1234567890.|

From 192.168.1.90:36326 to 52.85.184.151:80

Hex dump of go packet serialization output:

00000000  8d e6 00 50 ae 93 56 c0  12 6b f3 95 80 18 00 e5  |...P..V..k......|
00000010  00 00 00 00 01 01 08 0a  16 8a 55 6d 30 10 20 fd  |..........Um0. .|

What's Wrong

Second hex dump must start with 45 (most IPv4 packets start with 45, where 4 is a version of the protocol). Second hexdump must be identical to the first in many details except one IP address that was changed, TCP checksum and size values. Second hex dump must contain payload 1234567890\n.

Similar Questions

  1. How to use Golang to compose raw TCP packet (using gopacket) and send it via raw socket
  2. Sending UDP packets with gopacket
  3. Changing the IP address of a Gopacket and re-transmitting using raw sockets

Solution

  • First, always check returned errors — it's your feedback:

    err := gopacket.SerializePacket(newBuffer, options, packet)
    // OR
    err := gopacket.SerializeLayers(newBuffer, options,
      ip,
      tcp,
    )
    // THEN
    if err != nil {
      panic(err)
    }
    

    From code above you will get: TCP/IP layer 4 checksum cannot be computed without network layer... call SetNetworkLayerForChecksum to set which layer to use

    Then, the solution is to use tcp.SetNetworkLayerForChecksum(ip) and the final working code:

    package main
    
    import (
      "encoding/hex"
      "github.com/google/gopacket"
      "github.com/google/gopacket/layers"
      "fmt"
      "net"
    )
    
    func main() {
    
      packetData := []byte{
        69, 0, 0, 63, 64, 237, 64, 0, 64, 6, 74, 221, 192,
        168, 1, 90, 52, 85, 184, 151, 141, 230, 0, 80,
        174, 147, 86, 192, 18, 107, 243, 149, 128, 24,
        0, 229, 92, 65, 0, 0, 1, 1, 8, 10, 22, 138, 85, 109,
        48, 16, 32, 253, 49, 50, 51, 52, 53, 54, 55, 56,
        57, 48, 10,
      }
    
      fmt.Println("Hex dump of real IP packet taken as input:\n")
      fmt.Println(hex.Dump(packetData))
    
      packet := gopacket.NewPacket(packetData, layers.LayerTypeIPv4, gopacket.Default)
      if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil {
        ip := ipLayer.(*layers.IPv4)
        dst := ip.DstIP.String()
        src := ip.SrcIP.String()
    
        if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
          tcp := tcpLayer.(*layers.TCP)
          dst = fmt.Sprintf("%s:%d", dst, tcp.DstPort)
          src = fmt.Sprintf("%s:%d", src, tcp.SrcPort)
          fmt.Printf("From %s to %s\n\n", src, dst)
    
          ip.DstIP = net.ParseIP("8.8.8.8")
    
          options := gopacket.SerializeOptions{
            ComputeChecksums: true,
            FixLengths: true,
          }
    
          tcp.SetNetworkLayerForChecksum(ip)
    
          newBuffer := gopacket.NewSerializeBuffer()
          err := gopacket.SerializePacket(newBuffer, options, packet)
          if err != nil {
            panic(err)
          }
          outgoingPacket := newBuffer.Bytes()
    
          fmt.Println("Hex dump of go packet serialization output:\n")
          fmt.Println(hex.Dump(outgoingPacket))
    
        }
    
      }
    
    }
    

    Output

    Hex dump of real IP packet taken as input:
    
    00000000  45 00 00 3f 40 ed 40 00  40 06 4a dd c0 a8 01 5a  |E..?@.@.@.J....Z|
    00000010  34 55 b8 97 8d e6 00 50  ae 93 56 c0 12 6b f3 95  |4U.....P..V..k..|
    00000020  80 18 00 e5 5c 41 00 00  01 01 08 0a 16 8a 55 6d  |....\A........Um|
    00000030  30 10 20 fd 31 32 33 34  35 36 37 38 39 30 0a     |0. .1234567890.|
    
    From 192.168.1.90:36326 to 52.85.184.151:80
    
    Hex dump of go packet serialization output:
    
    00000000  45 00 00 3f 40 ed 40 00  40 06 27 ba c0 a8 01 5a  |E..?@.@.@.'....Z|
    00000010  08 08 08 08 8d e6 00 50  ae 93 56 c0 12 6b f3 95  |.......P..V..k..|
    00000020  80 18 00 e5 39 1e 00 00  01 01 08 0a 16 8a 55 6d  |....9.........Um|
    00000030  30 10 20 fd 31 32 33 34  35 36 37 38 39 30 0a     |0. .1234567890.|
    

    As you see 08 08 08 08 is a new IP and payload 1234567890\n is also preserved, IP packet starts with 45 as usual.