Skip to content

Instantly share code, notes, and snippets.

@tobert
Last active May 21, 2024 18:12
Show Gist options
  • Save tobert/806944fca7742637f591ebb3335f2a13 to your computer and use it in GitHub Desktop.
Save tobert/806944fca7742637f591ebb3335f2a13 to your computer and use it in GitHub Desktop.
bring up gvisor stack, send an ICMPv6 echo request, receive echo reply
package main
// writing repros is occasionally handy because in the course of doing
// so I discovered the bug in my own code that looked like a bug in
// gvisor
import (
"encoding/hex"
"log"
"net"
"sync"
"github.com/gopacket/gopacket"
"github.com/gopacket/gopacket/layers"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
// ReproNotifier is just enough data structure to get this repro working.
type ReproNotifier struct {
link *channel.Endpoint
nic tcpip.NICID
ip net.IP
wg sync.WaitGroup
}
var srcIP, dstIP net.IP
func init() {
srcIP = []byte{0xfd, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01} // fd01::1
dstIP = []byte{0xfd, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02} // fd01::2
}
// WriteNotify implements the channel.Notify interface.
func (gs *ReproNotifier) WriteNotify() {
pktBuf := gs.link.Read()
// TODO: not sure if this is the right way to tell?
if pktBuf == nil {
return
}
defer pktBuf.DecRef()
view := pktBuf.ToView()
pkt := view.AsSlice()
log.Printf("Returned Packet Hex: %q", hex.EncodeToString(pkt))
gopkt := gopacket.NewPacket(pkt, layers.LayerTypeIPv6, gopacket.NoCopy)
for i, layer := range gopkt.Layers() {
log.Printf("Layer %d: %s", i, layer.LayerType())
}
gs.wg.Done()
}
func main() {
// set up a gvisor tcpip stack the same way as my application...
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
HandleLocal: true,
})
gs := &ReproNotifier{
link: channel.New(4, 1500, ""),
ip: dstIP,
}
gs.link.AddNotify(gs)
gs.nic = tcpip.NICID(s.UniqueID())
terr := s.CreateNIC(gs.nic, gs.link)
if terr != nil {
log.Fatalf("error from stack.CreateNIC: %s", terr)
}
addr := tcpip.AddrFrom16Slice(gs.ip)
protoAddr := tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: addr.WithPrefix(),
}
terr = s.AddProtocolAddress(gs.nic, protoAddr, stack.AddressProperties{})
if terr != nil {
log.Fatalf("error assigning address %q to interface %d of stack: %s", gs.ip, gs.nic, terr)
}
s.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: gs.nic})
/* compose an ICMPv6 echo request packet */
ip := &layers.IPv6{
Version: 6,
NextHeader: layers.IPProtocolICMPv6,
HopLimit: 64,
SrcIP: srcIP,
DstIP: dstIP,
}
icmp6 := &layers.ICMPv6{
TypeCode: layers.CreateICMPv6TypeCode(layers.ICMPv6TypeEchoRequest, 0),
}
payload := gopacket.Payload([]byte("testing 1 2 3"))
icmp6.SetNetworkLayerForChecksum(ip)
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
FixLengths: true,
ComputeChecksums: true,
}
err := gopacket.SerializeLayers(buf, opts, ip, icmp6, payload)
if err != nil {
log.Fatalf("serializing packet failed: %s", err)
}
gs.wg.Add(1) // will get decremented by WaitNotify
// send the packet along to the stack
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(buf.Bytes())})
gs.link.InjectInbound(header.IPv6ProtocolNumber, pkb)
gs.wg.Wait() // wait for the packet to be received and printed before exiting
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment