mirror of
https://github.com/SagerNet/sing-tun.git
synced 2025-10-05 08:37:03 +08:00
Add ping client
This commit is contained in:
42
.github/workflows/test.yml
vendored
42
.github/workflows/test.yml
vendored
@@ -26,12 +26,14 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.23
|
go-version: ^1.25.0
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make test
|
make test
|
||||||
build_go120:
|
go test -c -o ping_test ./ping
|
||||||
name: Linux (Go 1.20)
|
sudo ./ping_test -test.v
|
||||||
|
build_go124:
|
||||||
|
name: Linux (Go 1.24)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -41,13 +43,15 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.20
|
go-version: ~1.24
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make test
|
make test
|
||||||
build_go121:
|
go test -c -o ping_test ./ping
|
||||||
name: Linux (Go 1.21)
|
sudo ./ping_test -test.v
|
||||||
|
build_go123:
|
||||||
|
name: Linux (Go 1.23)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -57,27 +61,13 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.21
|
go-version: ~1.23
|
||||||
continue-on-error: true
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
make test
|
|
||||||
build_go122:
|
|
||||||
name: Linux (Go 1.22)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.22
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make test
|
make test
|
||||||
|
go test -c -o ping_test ./ping
|
||||||
|
sudo ./ping_test -test.v
|
||||||
build_windows:
|
build_windows:
|
||||||
name: Windows
|
name: Windows
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
@@ -94,6 +84,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make test
|
make test
|
||||||
|
go test -v ./ping
|
||||||
build_darwin:
|
build_darwin:
|
||||||
name: macOS
|
name: macOS
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
@@ -109,4 +100,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make test
|
make test
|
||||||
|
go test -v ./ping
|
||||||
|
go test -c -o ping_test ./ping
|
||||||
|
sudo ./ping_test -test.v
|
4
Makefile
4
Makefile
@@ -29,5 +29,5 @@ lint_install:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
go build -v .
|
go build -v .
|
||||||
go test -bench=. ./internal/checksum_test
|
#go test -bench=. ./internal/checksum_test
|
||||||
#go test -v .
|
go test -v .
|
||||||
|
10
go.mod
10
go.mod
@@ -9,20 +9,24 @@ require (
|
|||||||
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237
|
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4
|
github.com/sagernet/nftables v0.3.0-beta.4
|
||||||
github.com/sagernet/sing v0.7.0-beta.1
|
github.com/sagernet/sing v0.7.6-0.20250823024003-88f1880f43af
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||||
golang.org/x/net v0.26.0
|
golang.org/x/net v0.43.0
|
||||||
golang.org/x/sys v0.26.0
|
golang.org/x/sys v0.35.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
github.com/mdlayher/socket v0.4.1 // indirect
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/time v0.7.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
14
go.sum
14
go.sum
@@ -24,8 +24,8 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
|
|||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
github.com/sagernet/sing v0.7.0-beta.1 h1:2D44KzgeDZwD/R4Ts8jwSUHTRR238a1FpXDrl7l4tVw=
|
github.com/sagernet/sing v0.7.6-0.20250823024003-88f1880f43af h1:/1H30c/+j7Q9BBPuJuX6eHyzKpbGWrr7S/4DcdtNIfw=
|
||||||
github.com/sagernet/sing v0.7.0-beta.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.7.6-0.20250823024003-88f1880f43af/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
@@ -34,14 +34,16 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
29
icmp.go
29
icmp.go
@@ -1,29 +0,0 @@
|
|||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewICMPDestination(ctx context.Context, logger logger.Logger, dialer net.Dialer, network string, address netip.Addr, routeContext DirectRouteContext) (DirectRouteDestination, error) {
|
|
||||||
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
|
|
||||||
return NewUnprivilegedICMPDestination(ctx, logger, dialer, network, address, routeContext)
|
|
||||||
} else {
|
|
||||||
destination, err := NewPrivilegedICMPDestination(ctx, logger, dialer, network, address, routeContext)
|
|
||||||
if err != nil {
|
|
||||||
if E.IsMulti(err, os.ErrPermission, unix.EPERM) {
|
|
||||||
return NewUnprivilegedICMPDestination(ctx, logger, dialer, network, address, routeContext)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return destination, nil
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,112 +0,0 @@
|
|||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
|
|
||||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PrivilegedICMPDestination struct {
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelCauseFunc
|
|
||||||
logger logger.Logger
|
|
||||||
routeContext DirectRouteContext
|
|
||||||
isIPv6 bool
|
|
||||||
localAddr atomic.TypedValue[netip.Addr]
|
|
||||||
rawConn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPrivilegedICMPDestination(ctx context.Context, logger logger.Logger, dialer net.Dialer, network string, address netip.Addr, routeContext DirectRouteContext) (DirectRouteDestination, error) {
|
|
||||||
var dialNetwork string
|
|
||||||
switch network {
|
|
||||||
case N.NetworkICMPv4:
|
|
||||||
dialNetwork = "ip4:icmp"
|
|
||||||
case N.NetworkICMPv6:
|
|
||||||
dialNetwork = "ip6:icmp"
|
|
||||||
default:
|
|
||||||
return nil, E.New("unsupported network: ", network)
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithCancelCause(ctx)
|
|
||||||
rawConn, err := dialer.DialContext(ctx, dialNetwork, address.String())
|
|
||||||
if err != nil {
|
|
||||||
cancel(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
d := &PrivilegedICMPDestination{
|
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
logger: logger,
|
|
||||||
routeContext: routeContext,
|
|
||||||
isIPv6: network == N.NetworkICMPv6,
|
|
||||||
rawConn: rawConn,
|
|
||||||
}
|
|
||||||
go d.loopRead()
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *PrivilegedICMPDestination) loopRead() {
|
|
||||||
for {
|
|
||||||
buffer := buf.NewPacket()
|
|
||||||
_, err := buffer.ReadOnceFrom(d.rawConn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !d.isIPv6 {
|
|
||||||
ipHdr := header.IPv4(buffer.Bytes())
|
|
||||||
ipHdr.SetDestinationAddr(d.localAddr.Load())
|
|
||||||
ipHdr.SetChecksum(0)
|
|
||||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
|
||||||
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
|
||||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
|
||||||
} else {
|
|
||||||
ipHdr := header.IPv6(buffer.Bytes())
|
|
||||||
ipHdr.SetDestinationAddr(d.localAddr.Load())
|
|
||||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
|
||||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
|
||||||
Header: icmpHdr,
|
|
||||||
Src: ipHdr.SourceAddress(),
|
|
||||||
Dst: ipHdr.DestinationAddress(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
err = d.routeContext.WritePacket(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *PrivilegedICMPDestination) WritePacket(packet *buf.Buffer) error {
|
|
||||||
if !d.isIPv6 {
|
|
||||||
ipHdr := header.IPv4(packet.Bytes())
|
|
||||||
d.localAddr.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
|
||||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
|
||||||
_, err := d.rawConn.Write(icmpHdr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ipHdr := header.IPv6(packet.Bytes())
|
|
||||||
d.localAddr.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
|
||||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
|
||||||
_, err := d.rawConn.Write(icmpHdr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *PrivilegedICMPDestination) Close() error {
|
|
||||||
d.cancel(os.ErrClosed)
|
|
||||||
return d.rawConn.Close()
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
//go:build with_gvisor
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *PrivilegedICMPDestination) WritePacketBuffer(packetBuffer *stack.PacketBuffer) error {
|
|
||||||
ipHdr := packetBuffer.Network()
|
|
||||||
if !d.isIPv6 {
|
|
||||||
d.localAddr.Store(netip.AddrFrom4(ipHdr.SourceAddress().As4()))
|
|
||||||
} else {
|
|
||||||
d.localAddr.Store(netip.AddrFrom16(ipHdr.SourceAddress().As16()))
|
|
||||||
}
|
|
||||||
packetSlice := packetBuffer.TransportHeader().Slice()
|
|
||||||
packetSlice = append(packetSlice, packetBuffer.Data().AsRange().ToSlice()...)
|
|
||||||
_, err := d.rawConn.Write(packetSlice)
|
|
||||||
return err
|
|
||||||
}
|
|
@@ -1,154 +0,0 @@
|
|||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
|
|
||||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UnprivilegedICMPDestination struct {
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelCauseFunc
|
|
||||||
logger logger.Logger
|
|
||||||
routeContext DirectRouteContext
|
|
||||||
isIPv6 bool
|
|
||||||
localAddr atomic.TypedValue[netip.Addr]
|
|
||||||
rawConn net.Conn
|
|
||||||
ipHdr bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUnprivilegedICMPDestination(ctx context.Context, logger logger.Logger, dialer net.Dialer, network string, address netip.Addr, routeContext DirectRouteContext) (DirectRouteDestination, error) {
|
|
||||||
var (
|
|
||||||
isIPv6 bool
|
|
||||||
fd int
|
|
||||||
ipHdr bool
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
var dialNetwork string
|
|
||||||
switch network {
|
|
||||||
case N.NetworkICMPv4:
|
|
||||||
dialNetwork = "ip4:icmp"
|
|
||||||
case N.NetworkICMPv6:
|
|
||||||
dialNetwork = "ip6:icmp"
|
|
||||||
isIPv6 = true
|
|
||||||
default:
|
|
||||||
return nil, E.New("unsupported network: ", network)
|
|
||||||
}
|
|
||||||
if !isIPv6 {
|
|
||||||
fd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP)
|
|
||||||
} else {
|
|
||||||
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
name, nameLen := bufio.ToSockaddr(M.SocksaddrFrom(address, 0).AddrPort())
|
|
||||||
err = unixConnect(fd, name, nameLen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawConn, err := net.FileConn(os.NewFile(uintptr(fd), "datagram-oriented icmp"))
|
|
||||||
if err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if dialer.Control != nil {
|
|
||||||
var syscallConn syscall.RawConn
|
|
||||||
syscallConn, err = rawConn.(syscall.Conn).SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = dialer.Control(dialNetwork, address.String(), syscallConn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d := &UnprivilegedICMPDestination{
|
|
||||||
ctx: ctx,
|
|
||||||
logger: logger,
|
|
||||||
routeContext: routeContext,
|
|
||||||
isIPv6: network == N.NetworkICMPv6,
|
|
||||||
rawConn: rawConn,
|
|
||||||
ipHdr: ipHdr,
|
|
||||||
}
|
|
||||||
go d.loopRead()
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname unixConnect golang.org/x/sys/unix.connect
|
|
||||||
func unixConnect(fd int, addr unsafe.Pointer, addrlen uint32) error
|
|
||||||
|
|
||||||
func (d *UnprivilegedICMPDestination) loopRead() {
|
|
||||||
for {
|
|
||||||
buffer := buf.NewPacket()
|
|
||||||
_, err := buffer.ReadOnceFrom(d.rawConn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if d.ipHdr {
|
|
||||||
if !d.isIPv6 {
|
|
||||||
ipHdr := header.IPv4(buffer.Bytes())
|
|
||||||
ipHdr.SetDestinationAddr(d.localAddr.Load())
|
|
||||||
ipHdr.SetChecksum(0)
|
|
||||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
|
||||||
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
|
||||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
|
||||||
} else {
|
|
||||||
ipHdr := header.IPv6(buffer.Bytes())
|
|
||||||
ipHdr.SetDestinationAddr(d.localAddr.Load())
|
|
||||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
|
||||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
|
||||||
Header: icmpHdr,
|
|
||||||
Src: ipHdr.SourceAddress(),
|
|
||||||
Dst: ipHdr.DestinationAddress(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
err = d.routeContext.WritePacket(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic("impl no hdr version for windows and linux")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *UnprivilegedICMPDestination) WritePacket(packet *buf.Buffer) error {
|
|
||||||
if !d.isIPv6 {
|
|
||||||
ipHdr := header.IPv4(packet.Bytes())
|
|
||||||
d.localAddr.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
|
||||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
|
||||||
_, err := d.rawConn.Write(icmpHdr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ipHdr := header.IPv6(packet.Bytes())
|
|
||||||
d.localAddr.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
|
||||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
|
||||||
_, err := d.rawConn.Write(icmpHdr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *UnprivilegedICMPDestination) Close() error {
|
|
||||||
d.cancel(os.ErrClosed)
|
|
||||||
return d.rawConn.Close()
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
//go:build with_gvisor
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *UnprivilegedICMPDestination) WritePacketBuffer(packetBuffer *stack.PacketBuffer) error {
|
|
||||||
ipHdr := packetBuffer.Network()
|
|
||||||
if !d.isIPv6 {
|
|
||||||
d.localAddr.Store(netip.AddrFrom4(ipHdr.SourceAddress().As4()))
|
|
||||||
} else {
|
|
||||||
d.localAddr.Store(netip.AddrFrom16(ipHdr.SourceAddress().As16()))
|
|
||||||
}
|
|
||||||
packetSlice := packetBuffer.TransportHeader().Slice()
|
|
||||||
packetSlice = append(packetSlice, packetBuffer.Data().AsRange().ToSlice()...)
|
|
||||||
_, err := d.rawConn.Write(packetSlice)
|
|
||||||
return err
|
|
||||||
}
|
|
@@ -276,8 +276,8 @@ func (b ICMPv6) Payload() []byte {
|
|||||||
// ICMPv6ChecksumParams contains parameters to calculate ICMPv6 checksum.
|
// ICMPv6ChecksumParams contains parameters to calculate ICMPv6 checksum.
|
||||||
type ICMPv6ChecksumParams struct {
|
type ICMPv6ChecksumParams struct {
|
||||||
Header ICMPv6
|
Header ICMPv6
|
||||||
Src tcpip.Address
|
Src []byte
|
||||||
Dst tcpip.Address
|
Dst []byte
|
||||||
PayloadCsum uint16
|
PayloadCsum uint16
|
||||||
PayloadLen int
|
PayloadLen int
|
||||||
}
|
}
|
||||||
@@ -287,7 +287,7 @@ type ICMPv6ChecksumParams struct {
|
|||||||
func ICMPv6Checksum(params ICMPv6ChecksumParams) uint16 {
|
func ICMPv6Checksum(params ICMPv6ChecksumParams) uint16 {
|
||||||
h := params.Header
|
h := params.Header
|
||||||
|
|
||||||
xsum := PseudoHeaderChecksum(ICMPv6ProtocolNumber, params.Src.AsSlice(), params.Dst.AsSlice(), uint16(len(h)+params.PayloadLen))
|
xsum := PseudoHeaderChecksum(ICMPv6ProtocolNumber, params.Src, params.Dst, uint16(len(h)+params.PayloadLen))
|
||||||
xsum = checksum.Combine(xsum, params.PayloadCsum)
|
xsum = checksum.Combine(xsum, params.PayloadCsum)
|
||||||
|
|
||||||
// h[2:4] is the checksum itself, skip it to avoid checksumming the checksum.
|
// h[2:4] is the checksum itself, skip it to avoid checksumming the checksum.
|
||||||
|
@@ -88,12 +88,16 @@ type Network interface {
|
|||||||
|
|
||||||
SourceAddr() netip.Addr
|
SourceAddr() netip.Addr
|
||||||
|
|
||||||
|
SourceAddressSlice() []byte
|
||||||
|
|
||||||
// DestinationAddress returns the value of the "destination address"
|
// DestinationAddress returns the value of the "destination address"
|
||||||
// field.
|
// field.
|
||||||
DestinationAddress() tcpip.Address
|
DestinationAddress() tcpip.Address
|
||||||
|
|
||||||
DestinationAddr() netip.Addr
|
DestinationAddr() netip.Addr
|
||||||
|
|
||||||
|
DestinationAddressSlice() []byte
|
||||||
|
|
||||||
// Checksum returns the value of the "checksum" field.
|
// Checksum returns the value of the "checksum" field.
|
||||||
Checksum() uint16
|
Checksum() uint16
|
||||||
|
|
||||||
|
16
ping/cmsg_unix.go
Normal file
16
ping/cmsg_unix.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseIPv6ControlMessage(cmsg []byte) (*ipv6.ControlMessage, error) {
|
||||||
|
var controlMessage ipv6.ControlMessage
|
||||||
|
err := controlMessage.Parse(cmsg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &controlMessage, nil
|
||||||
|
}
|
46
ping/cmsg_windows.go
Normal file
46
ping/cmsg_windows.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IPV6_HOPLIMIT = 21
|
||||||
|
IPV6_TCLASS = 39
|
||||||
|
IPV6_RECVTCLASS = 40
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
alignedSizeofCmsghdr = (sizeofCmsghdr + cmsgAlignTo - 1) & ^(cmsgAlignTo - 1)
|
||||||
|
sizeofCmsghdr = int(unsafe.Sizeof(windows.WSACMSGHDR{}))
|
||||||
|
cmsgAlignTo = int(unsafe.Sizeof(uintptr(0)))
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmsgAlign(n int) int {
|
||||||
|
return (n + cmsgAlignTo - 1) & ^(cmsgAlignTo - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIPv6ControlMessage(cmsg []byte) (*ipv6.ControlMessage, error) {
|
||||||
|
var controlMessage ipv6.ControlMessage
|
||||||
|
for len(cmsg) >= sizeofCmsghdr {
|
||||||
|
cmsghdr := (*windows.WSACMSGHDR)(unsafe.Pointer(unsafe.SliceData(cmsg)))
|
||||||
|
msgLen := int(cmsghdr.Len)
|
||||||
|
msgSize := cmsgAlign(msgLen)
|
||||||
|
if msgLen < sizeofCmsghdr || msgSize > len(cmsg) {
|
||||||
|
return nil, fmt.Errorf("invalid control message length %d", cmsghdr.Len)
|
||||||
|
}
|
||||||
|
switch cmsghdr.Type {
|
||||||
|
case IPV6_TCLASS:
|
||||||
|
controlMessage.TrafficClass = int(binary.NativeEndian.Uint32(cmsg[alignedSizeofCmsghdr : alignedSizeofCmsghdr+4]))
|
||||||
|
case IPV6_HOPLIMIT:
|
||||||
|
controlMessage.HopLimit = int(binary.NativeEndian.Uint32(cmsg[alignedSizeofCmsghdr : alignedSizeofCmsghdr+4]))
|
||||||
|
}
|
||||||
|
cmsg = cmsg[msgSize:]
|
||||||
|
}
|
||||||
|
return &controlMessage, nil
|
||||||
|
}
|
75
ping/destination.go
Normal file
75
ping/destination.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ tun.DirectRouteDestination = (*Destination)(nil)
|
||||||
|
|
||||||
|
type Destination struct {
|
||||||
|
logger logger.Logger
|
||||||
|
routeContext tun.DirectRouteContext
|
||||||
|
conn *Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectDestination(logger logger.Logger, controlFunc control.Func, address netip.Addr, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
|
||||||
|
var (
|
||||||
|
conn *Conn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin", "ios", "windows":
|
||||||
|
conn, err = Connect(false, controlFunc, address)
|
||||||
|
default:
|
||||||
|
conn, err = Connect(true, controlFunc, address)
|
||||||
|
if errors.Is(err, os.ErrPermission) {
|
||||||
|
conn, err = Connect(false, controlFunc, address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d := &Destination{
|
||||||
|
logger: logger,
|
||||||
|
routeContext: routeContext,
|
||||||
|
conn: conn,
|
||||||
|
}
|
||||||
|
go d.loopRead()
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Destination) loopRead() {
|
||||||
|
for {
|
||||||
|
buffer := buf.NewPacket()
|
||||||
|
err := d.conn.ReadIP(buffer)
|
||||||
|
if err != nil {
|
||||||
|
buffer.Release()
|
||||||
|
if !E.IsClosed(err) {
|
||||||
|
d.logger.Error(E.Cause(err, "receive ICMP echo reply"))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = d.routeContext.WritePacket(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
d.logger.Error(E.Cause(err, "write ICMP echo reply"))
|
||||||
|
}
|
||||||
|
buffer.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Destination) WritePacket(packet *buf.Buffer) error {
|
||||||
|
return d.conn.WriteIP(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Destination) Close() error {
|
||||||
|
return d.conn.Close()
|
||||||
|
}
|
207
ping/ping.go
Normal file
207
ping/ping.go
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
|
||||||
|
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
privileged bool
|
||||||
|
conn net.Conn
|
||||||
|
destination netip.Addr
|
||||||
|
source atomic.TypedValue[netip.Addr]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Connect(privileged bool, controlFunc control.Func, destination netip.Addr) (*Conn, error) {
|
||||||
|
conn, err := connect(privileged, controlFunc, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Conn{
|
||||||
|
privileged: privileged,
|
||||||
|
conn: conn,
|
||||||
|
destination: destination,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReadIP(buffer *buf.Buffer) error {
|
||||||
|
if c.destination.Is6() || runtime.GOOS == "linux" && !c.privileged {
|
||||||
|
var readMsg func(b, oob []byte) (n, oobn int, addr netip.Addr, err error)
|
||||||
|
switch conn := c.conn.(type) {
|
||||||
|
case *net.IPConn:
|
||||||
|
readMsg = func(b, oob []byte) (n, oobn int, addr netip.Addr, err error) {
|
||||||
|
var ipAddr *net.IPAddr
|
||||||
|
n, oobn, _, ipAddr, err = conn.ReadMsgIP(b, oob)
|
||||||
|
if ipAddr != nil {
|
||||||
|
addr = M.AddrFromNet(ipAddr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case *net.UDPConn:
|
||||||
|
readMsg = func(b, oob []byte) (n, oobn int, addr netip.Addr, err error) {
|
||||||
|
var udpAddr *net.UDPAddr
|
||||||
|
n, oobn, _, udpAddr, err = conn.ReadMsgUDP(b, oob)
|
||||||
|
if udpAddr != nil {
|
||||||
|
addr = M.AddrFromNet(udpAddr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return E.New("unsupported conn type: ", reflect.TypeOf(c.conn))
|
||||||
|
}
|
||||||
|
if !c.destination.Is6() {
|
||||||
|
oob := ipv4.NewControlMessage(ipv4.FlagTTL)
|
||||||
|
buffer.Advance(header.IPv4MinimumSize)
|
||||||
|
var ttl int
|
||||||
|
// tos int
|
||||||
|
n, oobn, addr, err := readMsg(buffer.FreeBytes(), oob)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
if oobn > 0 {
|
||||||
|
var controlMessage ipv4.ControlMessage
|
||||||
|
err = controlMessage.Parse(oob[:oobn])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ttl = controlMessage.TTL
|
||||||
|
}
|
||||||
|
ipHdr := header.IPv4(buffer.ExtendHeader(header.IPv4MinimumSize))
|
||||||
|
ipHdr.Encode(&header.IPv4Fields{
|
||||||
|
// TOS: uint8(tos),
|
||||||
|
SrcAddr: addr,
|
||||||
|
DstAddr: c.source.Load(),
|
||||||
|
Protocol: uint8(header.ICMPv4ProtocolNumber),
|
||||||
|
TTL: uint8(ttl),
|
||||||
|
TotalLength: uint16(buffer.Len()),
|
||||||
|
})
|
||||||
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
||||||
|
} else {
|
||||||
|
oob := make([]byte, 1024)
|
||||||
|
buffer.Advance(header.IPv6MinimumSize)
|
||||||
|
var (
|
||||||
|
hopLimit int
|
||||||
|
trafficClass int
|
||||||
|
)
|
||||||
|
n, oobn, addr, err := readMsg(buffer.FreeBytes(), oob)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
if oobn > 0 {
|
||||||
|
var controlMessage *ipv6.ControlMessage
|
||||||
|
controlMessage, err = parseIPv6ControlMessage(oob[:oobn])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hopLimit = controlMessage.HopLimit
|
||||||
|
trafficClass = controlMessage.TrafficClass
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv6(buffer.Bytes())
|
||||||
|
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
|
Header: icmpHdr[:header.ICMPv6DstUnreachableMinimumSize],
|
||||||
|
Src: addr.AsSlice(),
|
||||||
|
Dst: c.source.Load().AsSlice(),
|
||||||
|
}))
|
||||||
|
ipHdr := header.IPv6(buffer.ExtendHeader(header.IPv6MinimumSize))
|
||||||
|
ipHdr.Encode(&header.IPv6Fields{
|
||||||
|
TrafficClass: uint8(trafficClass),
|
||||||
|
PayloadLength: uint16(buffer.Len() - header.IPv6MinimumSize),
|
||||||
|
TransportProtocol: header.ICMPv6ProtocolNumber,
|
||||||
|
HopLimit: uint8(hopLimit),
|
||||||
|
SrcAddr: addr,
|
||||||
|
DstAddr: c.source.Load(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err := buffer.ReadOnceFrom(c.conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !c.destination.Is6() {
|
||||||
|
ipHdr := header.IPv4(buffer.Bytes())
|
||||||
|
ipHdr.SetDestinationAddr(c.source.Load())
|
||||||
|
ipHdr.SetChecksum(0)
|
||||||
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
||||||
|
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
||||||
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
||||||
|
} else {
|
||||||
|
ipHdr := header.IPv6(buffer.Bytes())
|
||||||
|
ipHdr.SetDestinationAddr(c.source.Load())
|
||||||
|
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||||
|
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
|
Header: icmpHdr,
|
||||||
|
Src: ipHdr.SourceAddressSlice(),
|
||||||
|
Dst: ipHdr.DestinationAddressSlice(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReadICMP(buffer *buf.Buffer) error {
|
||||||
|
_, err := buffer.ReadOnceFrom(c.conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.destination.Is6() || runtime.GOOS == "linux" && !c.privileged {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !c.destination.Is6() {
|
||||||
|
ipHdr := header.IPv4(buffer.Bytes())
|
||||||
|
buffer.Advance(int(ipHdr.HeaderLength()))
|
||||||
|
} else {
|
||||||
|
ipHdr := header.IPv6(buffer.Bytes())
|
||||||
|
buffer.Advance(buffer.Len() - int(ipHdr.PayloadLength()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriteIP(buffer *buf.Buffer) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
if !c.destination.Is6() {
|
||||||
|
ipHdr := header.IPv4(buffer.Bytes())
|
||||||
|
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||||
|
return common.Error(c.conn.Write(ipHdr.Payload()))
|
||||||
|
} else {
|
||||||
|
ipHdr := header.IPv6(buffer.Bytes())
|
||||||
|
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||||
|
return common.Error(c.conn.Write(ipHdr.Payload()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriteICMP(buffer *buf.Buffer) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
return common.Error(c.conn.Write(buffer.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) SetLocalAddr(addr netip.Addr) {
|
||||||
|
c.source.Store(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
193
ping/ping_test.go
Normal file
193
ping/ping_test.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package ping_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/gvisor/pkg/rand"
|
||||||
|
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||||
|
"github.com/sagernet/sing-tun/ping"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const addr4 = "127.0.0.1"
|
||||||
|
t.Run("ipv4", func(t *testing.T) {
|
||||||
|
t.Run("unprivileged", func(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
t.Run("read-icmp", func(t *testing.T) {
|
||||||
|
testPingIPv4ReadICMP(t, false, addr4)
|
||||||
|
})
|
||||||
|
t.Run("read-ip", func(t *testing.T) {
|
||||||
|
testPingIPv4ReadIP(t, false, addr4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("privileged", func(t *testing.T) {
|
||||||
|
if runtime.GOOS != "windows" && os.Getuid() != 0 {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
t.Run("read-icmp", func(t *testing.T) {
|
||||||
|
testPingIPv4ReadICMP(t, true, addr4)
|
||||||
|
})
|
||||||
|
t.Run("read-ip", func(t *testing.T) {
|
||||||
|
testPingIPv4ReadIP(t, true, addr4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// const addr6 = "2606:4700:4700::1001"
|
||||||
|
const addr6 = "::1"
|
||||||
|
t.Run("ipv6", func(t *testing.T) {
|
||||||
|
t.Run("unprivileged", func(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
t.Run("read-icmp", func(t *testing.T) {
|
||||||
|
testPingIPv6ReadICMP(t, false, addr6)
|
||||||
|
})
|
||||||
|
t.Run("read-ip", func(t *testing.T) {
|
||||||
|
testPingIPv6ReadIP(t, false, addr6)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("privileged", func(t *testing.T) {
|
||||||
|
if runtime.GOOS != "windows" && os.Getuid() != 0 {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
t.Run("read-icmp", func(t *testing.T) {
|
||||||
|
testPingIPv6ReadICMP(t, true, addr6)
|
||||||
|
})
|
||||||
|
t.Run("read-ip", func(t *testing.T) {
|
||||||
|
testPingIPv6ReadIP(t, true, addr6)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPingIPv4ReadIP(t *testing.T, privileged bool, addr string) {
|
||||||
|
conn, err := ping.Connect(privileged, nil, netip.MustParseAddr(addr))
|
||||||
|
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
request := make(header.ICMPv4, header.ICMPv4MinimumSize)
|
||||||
|
request.SetType(header.ICMPv4Echo)
|
||||||
|
request.SetIdent(uint16(rand.Uint32()))
|
||||||
|
request.SetChecksum(header.ICMPv4Checksum(request, 0))
|
||||||
|
|
||||||
|
err = conn.WriteICMP(buf.As(request))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
conn.SetLocalAddr(netip.MustParseAddr("127.0.0.1"))
|
||||||
|
require.NoError(t, conn.SetReadDeadline(time.Now().Add(3*time.Second)))
|
||||||
|
|
||||||
|
response := buf.NewPacket()
|
||||||
|
err = conn.ReadIP(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if runtime.GOOS == "linux" && privileged {
|
||||||
|
response.Reset()
|
||||||
|
err = conn.ReadIP(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
ipHdr := header.IPv4(response.Bytes())
|
||||||
|
require.NotZero(t, ipHdr.TTL())
|
||||||
|
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
||||||
|
require.Equal(t, header.ICMPv4EchoReply, icmpHdr.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPingIPv4ReadICMP(t *testing.T, privileged bool, addr string) {
|
||||||
|
conn, err := ping.Connect(privileged, nil, netip.MustParseAddr(addr))
|
||||||
|
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
request := make(header.ICMPv4, header.ICMPv4MinimumSize)
|
||||||
|
request.SetType(header.ICMPv4Echo)
|
||||||
|
request.SetIdent(uint16(rand.Uint32()))
|
||||||
|
request.SetChecksum(header.ICMPv4Checksum(request, 0))
|
||||||
|
|
||||||
|
err = conn.WriteICMP(buf.As(request))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, conn.SetReadDeadline(time.Now().Add(3*time.Second)))
|
||||||
|
|
||||||
|
response := buf.NewPacket()
|
||||||
|
err = conn.ReadICMP(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if runtime.GOOS == "linux" && privileged {
|
||||||
|
response.Reset()
|
||||||
|
err = conn.ReadICMP(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
icmpHdr := header.ICMPv4(response.Bytes())
|
||||||
|
require.Equal(t, header.ICMPv4EchoReply, icmpHdr.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPingIPv6ReadIP(t *testing.T, privileged bool, addr string) {
|
||||||
|
conn, err := ping.Connect(privileged, nil, netip.MustParseAddr(addr))
|
||||||
|
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
request := make(header.ICMPv6, header.ICMPv6MinimumSize)
|
||||||
|
request.SetType(header.ICMPv6EchoRequest)
|
||||||
|
request.SetIdent(uint16(rand.Uint32()))
|
||||||
|
|
||||||
|
err = conn.WriteICMP(buf.As(request))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
conn.SetLocalAddr(netip.MustParseAddr("::1"))
|
||||||
|
require.NoError(t, conn.SetReadDeadline(time.Now().Add(3*time.Second)))
|
||||||
|
|
||||||
|
response := buf.NewPacket()
|
||||||
|
err = conn.ReadIP(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" && privileged {
|
||||||
|
response.Reset()
|
||||||
|
err = conn.ReadIP(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
ipHdr := header.IPv6(response.Bytes())
|
||||||
|
require.NotZero(t, ipHdr.HopLimit())
|
||||||
|
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||||
|
require.Equal(t, header.ICMPv6EchoReply, icmpHdr.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPingIPv6ReadICMP(t *testing.T, privileged bool, addr string) {
|
||||||
|
conn, err := ping.Connect(privileged, nil, netip.MustParseAddr(addr))
|
||||||
|
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
request := make(header.ICMPv6, header.ICMPv6MinimumSize)
|
||||||
|
request.SetType(header.ICMPv6EchoRequest)
|
||||||
|
request.SetIdent(uint16(rand.Uint32()))
|
||||||
|
|
||||||
|
err = conn.WriteICMP(buf.As(request))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, conn.SetReadDeadline(time.Now().Add(3*time.Second)))
|
||||||
|
|
||||||
|
response := buf.NewPacket()
|
||||||
|
err = conn.ReadICMP(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" && privileged {
|
||||||
|
response.Reset()
|
||||||
|
err = conn.ReadICMP(response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv6(response.Bytes())
|
||||||
|
require.Equal(t, header.ICMPv6EchoReply, icmpHdr.Type())
|
||||||
|
}
|
86
ping/socket_unix.go
Normal file
86
ping/socket_unix.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
//go:build unix
|
||||||
|
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func connect(privileged bool, controlFunc control.Func, destination netip.Addr) (net.Conn, error) {
|
||||||
|
var (
|
||||||
|
network string
|
||||||
|
fd int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if destination.Is4() {
|
||||||
|
network = "ip4:icmp"
|
||||||
|
if !privileged {
|
||||||
|
fd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP)
|
||||||
|
} else {
|
||||||
|
fd, err = unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_ICMP)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
network = "ip6:icmp"
|
||||||
|
if !privileged {
|
||||||
|
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6)
|
||||||
|
} else {
|
||||||
|
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_ICMPV6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "socket()")
|
||||||
|
}
|
||||||
|
file := os.NewFile(uintptr(fd), "datagram-oriented icmp")
|
||||||
|
defer file.Close()
|
||||||
|
err = unix.Connect(fd, M.AddrPortToSockaddr(netip.AddrPortFrom(destination, 0)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "connect()")
|
||||||
|
}
|
||||||
|
|
||||||
|
if destination.Is4() && runtime.GOOS == "linux" {
|
||||||
|
//err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVTOS, 1)
|
||||||
|
//if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
//}
|
||||||
|
err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVTTL, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "setsockopt()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if destination.Is6() {
|
||||||
|
err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_RECVHOPLIMIT, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "setsockopt()")
|
||||||
|
}
|
||||||
|
err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "setsockopt()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.FileConn(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if controlFunc != nil {
|
||||||
|
var syscallConn syscall.RawConn
|
||||||
|
syscallConn, err = conn.(syscall.Conn).SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = controlFunc(network, destination.String(), syscallConn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
38
ping/socket_windows.go
Normal file
38
ping/socket_windows.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func connect(privileged bool, controlFunc control.Func, destination netip.Addr) (net.Conn, error) {
|
||||||
|
var dialer net.Dialer
|
||||||
|
dialer.Control = controlFunc
|
||||||
|
if destination.Is6() {
|
||||||
|
dialer.Control = control.Append(dialer.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
|
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_HOPLIMIT, 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_RECVTCLASS, 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var network string
|
||||||
|
if destination.Is4() {
|
||||||
|
network = "ip4:icmp"
|
||||||
|
} else {
|
||||||
|
network = "ip6:ipv6-icmp"
|
||||||
|
}
|
||||||
|
return dialer.Dial(network, destination.String())
|
||||||
|
}
|
@@ -98,8 +98,8 @@ func (w *NatWriter) RewritePacket(packet []byte) {
|
|||||||
icmpHdr.SetChecksum(0)
|
icmpHdr.SetChecksum(0)
|
||||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
Header: icmpHdr,
|
Header: icmpHdr,
|
||||||
Src: ipHdr.SourceAddress(),
|
Src: ipHdr.SourceAddressSlice(),
|
||||||
Dst: ipHdr.DestinationAddress(),
|
Dst: ipHdr.DestinationAddressSlice(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
if ipHdr4, isIPv4 := ipHdr.(header.IPv4); isIPv4 {
|
if ipHdr4, isIPv4 := ipHdr.(header.IPv4); isIPv4 {
|
||||||
|
@@ -7,6 +7,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DirectRouteDestination interface {
|
type DirectRouteDestination interface {
|
||||||
DirectRouteAction
|
|
||||||
WritePacket(packet *buf.Buffer) error
|
WritePacket(packet *buf.Buffer) error
|
||||||
|
Close() error
|
||||||
}
|
}
|
||||||
|
@@ -746,8 +746,8 @@ func (s *System) processIPv6ICMP(ipHdr header.IPv6, icmpHdr header.ICMPv6) (bool
|
|||||||
ipHdr.SetDestinationAddr(sourceAddress)
|
ipHdr.SetDestinationAddr(sourceAddress)
|
||||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
Header: icmpHdr,
|
Header: icmpHdr,
|
||||||
Src: ipHdr.SourceAddress(),
|
Src: ipHdr.SourceAddressSlice(),
|
||||||
Dst: ipHdr.DestinationAddress(),
|
Dst: ipHdr.DestinationAddressSlice(),
|
||||||
}))
|
}))
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@@ -782,8 +782,8 @@ func (s *System) rejectIPv6WithICMP(ipHdr header.IPv6, code header.ICMPv6Code) e
|
|||||||
icmpHdr.SetCode(code)
|
icmpHdr.SetCode(code)
|
||||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
Header: icmpHdr[:header.ICMPv6DstUnreachableMinimumSize],
|
Header: icmpHdr[:header.ICMPv6DstUnreachableMinimumSize],
|
||||||
Src: newIPHdr.SourceAddress(),
|
Src: newIPHdr.SourceAddressSlice(),
|
||||||
Dst: newIPHdr.DestinationAddress(),
|
Dst: newIPHdr.DestinationAddressSlice(),
|
||||||
PayloadCsum: checksum.Checksum(payload, 0),
|
PayloadCsum: checksum.Checksum(payload, 0),
|
||||||
PayloadLen: len(payload),
|
PayloadLen: len(payload),
|
||||||
}))
|
}))
|
||||||
|
Reference in New Issue
Block a user