Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions intra/netstack/icmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package netstack

import (
"encoding/binary"
"math"

"github.com/celzero/firestack/intra/core"
Expand All @@ -19,6 +20,79 @@
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
)

type ICMPHackTarget struct{}

func (t *ICMPHackTarget) Action(pkt *stack.PacketBuffer, hook stack.Hook, r *stack.Route, _ stack.AddressableEndpoint) (stack.RuleVerdict, int) {
transportHdr := pkt.TransportHeader()
if len(transportHdr.Slice()) < 8 {
return stack.RuleDrop, 0
}
switch pkt.TransportProtocolNumber {
case header.ICMPv6ProtocolNumber:
icmp6Hdr := header.ICMPv6(transportHdr.Slice())
if icmp6Hdr.Type() == header.ICMPv6EchoRequest {
//https://www.rfc-editor.org/rfc/rfc4443.html#section-2.1
//200 Private experimentation
icmp6Hdr.SetType(200)
icmp6Hdr.SetChecksum(0)

ipv6Hdr := pkt.NetworkHeader().Slice()
icmp6Msg := stack.PayloadSince(transportHdr).AsSlice()
t1 := checksum.Checksumer{}
t1.Add(ipv6Hdr[8:40])
var t2 [8]byte
binary.BigEndian.PutUint32(t2[:4], uint32(len(icmp6Msg)))
t2[7] = uint8(header.ICMPv6ProtocolNumber)
t1.Add(t2[:])
t1.Add(icmp6Msg)

icmp6Hdr.SetChecksum(t1.Checksum())
}
case header.ICMPv4ProtocolNumber:
icmp4Hdr := header.ICMPv4(transportHdr.Slice())
if icmp4Hdr.Type() == header.ICMPv4Echo {
//https://www.rfc-editor.org/rfc/rfc4727.html#section-4
//253 RFC3692-style Experiment 1
icmp4Hdr.SetType(253)
icmp4Hdr.SetChecksum(0)
icmp4Msg := stack.PayloadSince(transportHdr).AsSlice()
icmp4Hdr.SetChecksum(checksum.Checksum(icmp4Msg, 0))
}
}
return stack.RuleAccept, 0
}

func restoreICMPv6Type(h header.ICMPv6) {
if h.Type() == 200 {
h.SetType(header.ICMPv6EchoRequest)
}
}
func restoreICMPv4Type(h header.ICMPv4) {
if h.Type() == 253 {
h.SetType(header.ICMPv4Echo)
}
}

func ICMPHack(s *stack.Stack, target stack.Target) {
ipt := s.IPTables()

table := ipt.GetTable(stack.MangleID, true)
index := table.BuiltinChains[stack.Prerouting]
rules := table.Rules
rules[index].Filter.Protocol = header.ICMPv6ProtocolNumber
rules[index].Filter.CheckProtocol = true
rules[index].Target = target
ipt.ReplaceTable(stack.MangleID, table, true)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried bumping gvisor for my gvisor-playground, found that new gvisor requires ForceReplaceTable() to work. All rules in iptables become useless if shouldSkipOrPopulateTables() returns true……

Copy link
Contributor

@ignoramous ignoramous Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying, for the current changes to work, firestack would have to be pinned to its current gvisor@go version v0.0.0-20250816201027-ba3b9ca85f20?

gvisor.dev/gvisor v0.0.0-20250816201027-ba3b9ca85f20

(I never really got the hang of the gvisor/netstack internals at all...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried bumping gvisor for my gvisor-playground, found that new gvisor requires ForceReplaceTable() to work. All rules in iptables become useless if shouldSkipOrPopulateTables() returns true……

Are you saying, for the current changes to work, firestack would have to be pinned to its current gvisor@go version v0.0.0-20250816201027-ba3b9ca85f20?

I mean that shouldSkipOrPopulateTables() return true ( https://github.com/google/gvisor/blob/fd370915f22e/pkg/tcpip/stack/iptables.go#L315 in some cases gvisor thinks modified iptables are not modified ) so 2cc55a2 doesn't work, but IPTables.ForceReplaceTable() solves this problem, IPTables.ReplaceTable() in gvisor v0.0.0-20231023213702-2691a8f9b1cf ( used by my gvisor-playground ) always works.

The comment about gvisor v0.0.0-20250816201027-ba3b9ca85f20 means that firestack's handler is not called after stack.SetTransportProtocolHandler(icmp.ProtocolNumber6, …)


table = ipt.GetTable(stack.MangleID, false)
index = table.BuiltinChains[stack.Prerouting]
rules = table.Rules
rules[index].Filter.Protocol = header.ICMPv4ProtocolNumber
rules[index].Filter.CheckProtocol = true
rules[index].Target = target
ipt.ReplaceTable(stack.MangleID, table, false)
}

type GICMPHandler interface {
GBaseConnHandler
GEchoConnHandler
Expand All @@ -33,6 +107,8 @@
// github.com/google/gvisor/blob/738e1d995f/pkg/tcpip/network/ipv4/icmp.go
// github.com/google/gvisor/blob/738e1d995f/pkg/tcpip/network/ipv6/icmp.go
func OutboundICMP(id string, s *stack.Stack, hdl GICMPHandler) {
ICMPHack(s, &ICMPHackTarget{})

// remove default handlers
s.SetTransportProtocolHandler(icmp.ProtocolNumber4, nil)
s.SetTransportProtocolHandler(icmp.ProtocolNumber6, nil)
Expand Down Expand Up @@ -69,6 +145,7 @@

// ref: github.com/google/gvisor/blob/acf460d0d735/pkg/tcpip/stack/conntrack.go#L933
hdr := header.ICMPv4(pkt.TransportHeader().Slice())
restoreICMPv4Type(hdr)
if hdr.Type() != header.ICMPv4Echo {
// netstack handles other msgs except echo / ping
log.D("icmp: v4: %s: type %v passthrough", f.o, hdr.Type())
Expand Down Expand Up @@ -166,6 +243,7 @@
}

hdr := header.ICMPv6(pkt.TransportHeader().Slice())
restoreICMPv6Type(hdr)
if hdr.Type() != header.ICMPv6EchoRequest {
log.D("icmp: v6: %s: type %v/%v passthrough", f.o, hdr.Type(), hdr.Code())
return // netstack to handle other msgs except echo / ping
Expand Down
Loading