ping: Fix missing TTL and ICMP error handling
This commit is contained in:
parent
3a09076491
commit
1251022fce
5 changed files with 153 additions and 60 deletions
|
|
@ -105,23 +105,29 @@ func (d *Destination) loopRead() {
|
||||||
}
|
}
|
||||||
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
||||||
if d.needFilter() {
|
if d.needFilter() {
|
||||||
if icmpHdr.Type() != header.ICMPv4EchoReply {
|
switch icmpHdr.Type() {
|
||||||
continue
|
case header.ICMPv4EchoReply:
|
||||||
}
|
|
||||||
var requestExists bool
|
|
||||||
request := pingRequest{Source: ipHdr.DestinationAddr(), Destination: ipHdr.SourceAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}
|
request := pingRequest{Source: ipHdr.DestinationAddr(), Destination: ipHdr.SourceAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}
|
||||||
d.requestAccess.Lock()
|
d.requestAccess.Lock()
|
||||||
_, loaded := d.requests[request]
|
_, loaded := d.requests[request]
|
||||||
if loaded {
|
if loaded {
|
||||||
requestExists = true
|
|
||||||
delete(d.requests, request)
|
delete(d.requests, request)
|
||||||
}
|
}
|
||||||
d.requestAccess.Unlock()
|
d.requestAccess.Unlock()
|
||||||
if !requestExists {
|
if !loaded {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
d.logger.TraceContext(d.ctx, "read ICMPv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
|
d.logger.TraceContext(d.ctx, "read ICMPv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
|
||||||
|
case header.ICMPv4TimeExceeded, header.ICMPv4DstUnreachable:
|
||||||
|
if !d.rewriteICMPv4Error(ipHdr, icmpHdr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d.logger.TraceContext(d.ctx, "read ICMPv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ipHdr := header.IPv6(buffer.Bytes())
|
ipHdr := header.IPv6(buffer.Bytes())
|
||||||
if !ipHdr.IsValid(buffer.Len()) {
|
if !ipHdr.IsValid(buffer.Len()) {
|
||||||
|
|
@ -191,6 +197,45 @@ func (d *Destination) WritePacket(packet *buf.Buffer) error {
|
||||||
return d.conn.WriteIP(packet)
|
return d.conn.WriteIP(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Destination) rewriteICMPv4Error(ipHdr header.IPv4, icmpHdr header.ICMPv4) bool {
|
||||||
|
inner := icmpHdr.Payload()
|
||||||
|
if len(inner) < header.IPv4MinimumSize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
innerIPHdr := header.IPv4(inner)
|
||||||
|
headerLen := int(innerIPHdr.HeaderLength())
|
||||||
|
if headerLen < header.IPv4MinimumSize || len(inner) < headerLen+header.ICMPv4MinimumSize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if innerIPHdr.TransportProtocol() != header.ICMPv4ProtocolNumber {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
innerICMP := header.ICMPv4(inner[headerLen:])
|
||||||
|
if innerICMP.Type() != header.ICMPv4Echo {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
originalIdent := ^innerICMP.Ident()
|
||||||
|
request := pingRequest{
|
||||||
|
Source: ipHdr.DestinationAddr(),
|
||||||
|
Destination: innerIPHdr.DestinationAddr(),
|
||||||
|
Identifier: originalIdent,
|
||||||
|
Sequence: innerICMP.Sequence(),
|
||||||
|
}
|
||||||
|
d.requestAccess.Lock()
|
||||||
|
_, loaded := d.requests[request]
|
||||||
|
d.requestAccess.Unlock()
|
||||||
|
if !loaded {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
innerICMP.SetIdent(originalIdent)
|
||||||
|
innerICMP.SetChecksum(header.ICMPv4Checksum(innerICMP, 0))
|
||||||
|
innerIPHdr.SetSourceAddr(ipHdr.DestinationAddr())
|
||||||
|
innerIPHdr.SetChecksum(^innerIPHdr.CalculateChecksum())
|
||||||
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, 0))
|
||||||
|
d.logger.TraceContext(d.ctx, "read ICMPv4 error type ", int(icmpHdr.Type()), " from ", ipHdr.SourceAddr(), " seq ", innerICMP.Sequence())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Destination) needFilter() bool {
|
func (d *Destination) needFilter() bool {
|
||||||
return !d.conn.isLinuxUnprivileged()
|
return !d.conn.isLinuxUnprivileged()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
42
ping/ping.go
42
ping/ping.go
|
|
@ -157,11 +157,21 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
|
||||||
DstAddr: c.source.Load(),
|
DstAddr: c.source.Load(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if runtime.GOOS == "linux" || runtime.GOOS == "android" || runtime.GOOS == "windows" {
|
||||||
|
// An unconnected SOCK_RAW IPv4 socket delivers the full packet including the IP
|
||||||
|
// header via ReadMsgIP, whereas ReadFrom strips it.
|
||||||
|
n, _, _, err := c.readMsg(buffer.FreeBytes(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
} else {
|
} else {
|
||||||
_, err := buffer.ReadOnceFrom(c.conn)
|
_, err := buffer.ReadOnceFrom(c.conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if !c.destination.Is6() {
|
if !c.destination.Is6() {
|
||||||
ipHdr := header.IPv4(buffer.Bytes())
|
ipHdr := header.IPv4(buffer.Bytes())
|
||||||
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
|
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
|
||||||
|
|
@ -177,10 +187,12 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
|
||||||
ipHdr.SetDestinationAddr(c.source.Load())
|
ipHdr.SetDestinationAddr(c.source.Load())
|
||||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
||||||
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
||||||
|
if icmpHdr.Type() == header.ICMPv4EchoReply {
|
||||||
if !c.isLinuxUnprivileged() {
|
if !c.isLinuxUnprivileged() {
|
||||||
icmpHdr.SetIdent(^icmpHdr.Ident())
|
icmpHdr.SetIdent(^icmpHdr.Ident())
|
||||||
}
|
}
|
||||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, 0))
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, 0))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ipHdr := header.IPv6(buffer.Bytes())
|
ipHdr := header.IPv6(buffer.Bytes())
|
||||||
if !ipHdr.IsValid(buffer.Len()) {
|
if !ipHdr.IsValid(buffer.Len()) {
|
||||||
|
|
@ -202,19 +214,34 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) ReadICMP(buffer *buf.Buffer) error {
|
func (c *Conn) ReadICMP(buffer *buf.Buffer) error {
|
||||||
|
if !c.isLinuxUnprivileged() && !c.destination.Is6() {
|
||||||
|
if runtime.GOOS == "linux" || runtime.GOOS == "android" || runtime.GOOS == "windows" {
|
||||||
|
// An unconnected SOCK_RAW IPv4 socket delivers the full packet including the IP
|
||||||
|
// header via ReadMsgIP, whereas ReadFrom strips it.
|
||||||
|
n, _, _, err := c.readMsg(buffer.FreeBytes(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
} else {
|
||||||
_, err := buffer.ReadOnceFrom(c.conn)
|
_, err := buffer.ReadOnceFrom(c.conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !c.isLinuxUnprivileged() {
|
}
|
||||||
if !c.destination.Is6() {
|
|
||||||
ipHdr := header.IPv4(buffer.Bytes())
|
ipHdr := header.IPv4(buffer.Bytes())
|
||||||
buffer.Advance(int(ipHdr.HeaderLength()))
|
buffer.Advance(int(ipHdr.HeaderLength()))
|
||||||
|
|
||||||
icmpHdr := header.ICMPv4(buffer.Bytes())
|
icmpHdr := header.ICMPv4(buffer.Bytes())
|
||||||
icmpHdr.SetIdent(^icmpHdr.Ident())
|
icmpHdr.SetIdent(^icmpHdr.Ident())
|
||||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, 0))
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, 0))
|
||||||
} else {
|
return nil
|
||||||
|
}
|
||||||
|
_, err := buffer.ReadOnceFrom(c.conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.destination.Is6() && !c.isLinuxUnprivileged() {
|
||||||
icmpHdr := header.ICMPv6(buffer.Bytes())
|
icmpHdr := header.ICMPv6(buffer.Bytes())
|
||||||
icmpHdr.SetIdent(^icmpHdr.Ident())
|
icmpHdr.SetIdent(^icmpHdr.Ident())
|
||||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
|
|
@ -223,7 +250,6 @@ func (c *Conn) ReadICMP(buffer *buf.Buffer) error {
|
||||||
Dst: c.source.Load().AsSlice(),
|
Dst: c.source.Load().AsSlice(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,6 +258,10 @@ func (c *Conn) WriteIP(buffer *buf.Buffer) error {
|
||||||
if !c.destination.Is6() {
|
if !c.destination.Is6() {
|
||||||
ipHdr := header.IPv4(buffer.Bytes())
|
ipHdr := header.IPv4(buffer.Bytes())
|
||||||
if !c.isLinuxUnprivileged() {
|
if !c.isLinuxUnprivileged() {
|
||||||
|
err := ipv4.NewConn(c.conn).SetTTL(int(ipHdr.TTL()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
||||||
icmpHdr.SetIdent(^icmpHdr.Ident())
|
icmpHdr.SetIdent(^icmpHdr.Ident())
|
||||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, 0))
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, 0))
|
||||||
|
|
@ -241,6 +271,10 @@ func (c *Conn) WriteIP(buffer *buf.Buffer) error {
|
||||||
} else {
|
} else {
|
||||||
ipHdr := header.IPv6(buffer.Bytes())
|
ipHdr := header.IPv6(buffer.Bytes())
|
||||||
if !c.isLinuxUnprivileged() {
|
if !c.isLinuxUnprivileged() {
|
||||||
|
err := ipv6.NewConn(c.conn).SetHopLimit(int(ipHdr.HopLimit()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||||
icmpHdr.SetIdent(^icmpHdr.Ident())
|
icmpHdr.SetIdent(^icmpHdr.Ident())
|
||||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||||
|
|
|
||||||
|
|
@ -87,15 +87,25 @@ func connect(privileged bool, controlFunc control.Func, destination netip.Addr)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" && !privileged {
|
useUnconnected := (runtime.GOOS == "darwin" && !privileged) ||
|
||||||
// When running in NetworkExtension on macOS, write to connected socket results in EPIPE.
|
((runtime.GOOS == "linux" || runtime.GOOS == "android") && privileged)
|
||||||
|
if useUnconnected {
|
||||||
|
// A connected ICMP socket only receives messages whose source is the connected peer,
|
||||||
|
// so the Time Exceeded replies that transit routers send for traceroute never reach it.
|
||||||
|
// Additionally, on macOS NetworkExtension, writing to a connected socket returns EPIPE.
|
||||||
var packetConn net.PacketConn
|
var packetConn net.PacketConn
|
||||||
packetConn, err = net.FilePacketConn(file)
|
packetConn, err = net.FilePacketConn(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return bufio.NewBindPacketConn(packetConn, M.SocksaddrFrom(destination, 0).UDPAddr()), nil
|
var writeTarget net.Addr
|
||||||
|
if privileged {
|
||||||
|
writeTarget = M.SocksaddrFrom(destination, 0).IPAddr()
|
||||||
} else {
|
} else {
|
||||||
|
writeTarget = M.SocksaddrFrom(destination, 0).UDPAddr()
|
||||||
|
}
|
||||||
|
return bufio.NewBindPacketConn(packetConn, writeTarget), nil
|
||||||
|
}
|
||||||
err = unix.Connect(fd, M.AddrPortToSockaddr(netip.AddrPortFrom(destination, 0)))
|
err = unix.Connect(fd, M.AddrPortToSockaddr(netip.AddrPortFrom(destination, 0)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -107,4 +117,3 @@ func connect(privileged bool, controlFunc control.Func, destination netip.Addr)
|
||||||
}
|
}
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,29 @@
|
||||||
package ping
|
package ping
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
func connect(privileged bool, controlFunc control.Func, destination netip.Addr) (net.Conn, error) {
|
func connect(privileged bool, controlFunc control.Func, destination netip.Addr) (net.Conn, error) {
|
||||||
var dialer net.Dialer
|
var listenConfig net.ListenConfig
|
||||||
dialer.Control = controlFunc
|
listenConfig.Control = controlFunc
|
||||||
if destination.Is6() {
|
if destination.Is6() {
|
||||||
dialer.Control = control.Append(dialer.Control, func(network, address string, conn syscall.RawConn) error {
|
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_HOPLIMIT, 1)
|
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_HOPLIMIT, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_RECVTCLASS, 1)
|
return windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_RECVTCLASS, 1)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -34,5 +33,11 @@ func connect(privileged bool, controlFunc control.Func, destination netip.Addr)
|
||||||
} else {
|
} else {
|
||||||
network = "ip6:ipv6-icmp"
|
network = "ip6:ipv6-icmp"
|
||||||
}
|
}
|
||||||
return dialer.Dial(network, destination.String())
|
// A connected raw socket only receives messages from the connected peer, so transit routers'
|
||||||
|
// Time Exceeded replies needed by traceroute never arrive.
|
||||||
|
packetConn, err := listenConfig.ListenPacket(context.Background(), network, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bufio.NewBindPacketConn(packetConn, M.SocksaddrFrom(destination, 0).IPAddr()), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||||
"github.com/sagernet/sing-tun/internal/rawfile_darwin"
|
rawfile "github.com/sagernet/sing-tun/internal/rawfile_darwin"
|
||||||
"github.com/sagernet/sing-tun/internal/stopfd_darwin"
|
"github.com/sagernet/sing-tun/internal/stopfd_darwin"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue