ping: Add filter to destination
This commit is contained in:
parent
d0ff7b6f6c
commit
144683d882
3 changed files with 108 additions and 31 deletions
|
|
@ -6,9 +6,11 @@ import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
@ -18,18 +20,28 @@ import (
|
||||||
var _ tun.DirectRouteDestination = (*Destination)(nil)
|
var _ tun.DirectRouteDestination = (*Destination)(nil)
|
||||||
|
|
||||||
type Destination struct {
|
type Destination struct {
|
||||||
conn *Conn
|
conn *Conn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
routeContext tun.DirectRouteContext
|
destination netip.Addr
|
||||||
timeout time.Duration
|
routeContext tun.DirectRouteContext
|
||||||
|
timeout time.Duration
|
||||||
|
requestAccess sync.Mutex
|
||||||
|
requests map[pingRequest]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pingRequest struct {
|
||||||
|
Source netip.Addr
|
||||||
|
Destination netip.Addr
|
||||||
|
Identifier uint16
|
||||||
|
Sequence uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectDestination(
|
func ConnectDestination(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
logger logger.ContextLogger,
|
logger logger.ContextLogger,
|
||||||
controlFunc control.Func,
|
controlFunc control.Func,
|
||||||
address netip.Addr,
|
destination netip.Addr,
|
||||||
routeContext tun.DirectRouteContext,
|
routeContext tun.DirectRouteContext,
|
||||||
timeout time.Duration,
|
timeout time.Duration,
|
||||||
) (tun.DirectRouteDestination, error) {
|
) (tun.DirectRouteDestination, error) {
|
||||||
|
|
@ -39,11 +51,11 @@ func ConnectDestination(
|
||||||
)
|
)
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "darwin", "ios", "windows":
|
case "darwin", "ios", "windows":
|
||||||
conn, err = Connect(ctx, logger, false, controlFunc, address)
|
conn, err = Connect(ctx, false, controlFunc, destination)
|
||||||
default:
|
default:
|
||||||
conn, err = Connect(ctx, logger, true, controlFunc, address)
|
conn, err = Connect(ctx, true, controlFunc, destination)
|
||||||
if errors.Is(err, os.ErrPermission) {
|
if errors.Is(err, os.ErrPermission) {
|
||||||
conn, err = Connect(ctx, logger, false, controlFunc, address)
|
conn, err = Connect(ctx, false, controlFunc, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -53,8 +65,10 @@ func ConnectDestination(
|
||||||
conn: conn,
|
conn: conn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
destination: destination,
|
||||||
routeContext: routeContext,
|
routeContext: routeContext,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
|
requests: make(map[pingRequest]bool),
|
||||||
}
|
}
|
||||||
go d.loopRead()
|
go d.loopRead()
|
||||||
return d, nil
|
return d, nil
|
||||||
|
|
@ -76,6 +90,59 @@ func (d *Destination) loopRead() {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !d.destination.Is6() {
|
||||||
|
ipHdr := header.IPv4(buffer.Bytes())
|
||||||
|
if !ipHdr.IsValid(buffer.Len()) {
|
||||||
|
d.logger.ErrorContext(d.ctx, E.New("invalid IPv4 header received"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ipHdr.PayloadLength() < header.ICMPv4MinimumSize {
|
||||||
|
d.logger.ErrorContext(d.ctx, E.New("invalid ICMPv4 header received"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
||||||
|
if icmpHdr.Type() != header.ICMPv4EchoReply {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var requestExists bool
|
||||||
|
request := pingRequest{Source: ipHdr.DestinationAddr(), Destination: ipHdr.SourceAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}
|
||||||
|
d.requestAccess.Lock()
|
||||||
|
if d.requests[request] {
|
||||||
|
requestExists = true
|
||||||
|
delete(d.requests, request)
|
||||||
|
}
|
||||||
|
d.requestAccess.Unlock()
|
||||||
|
if !requestExists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d.logger.TraceContext(d.ctx, "read ICMPv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
|
||||||
|
} else {
|
||||||
|
ipHdr := header.IPv6(buffer.Bytes())
|
||||||
|
if !ipHdr.IsValid(buffer.Len()) {
|
||||||
|
d.logger.ErrorContext(d.ctx, E.New("invalid IPv6 header received"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ipHdr.PayloadLength() < header.ICMPv6MinimumSize {
|
||||||
|
d.logger.ErrorContext(d.ctx, E.New("invalid ICMPv6 header received"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||||
|
if icmpHdr.Type() != header.ICMPv6EchoReply {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var requestExists bool
|
||||||
|
request := pingRequest{Source: ipHdr.DestinationAddr(), Destination: ipHdr.SourceAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}
|
||||||
|
d.requestAccess.Lock()
|
||||||
|
if d.requests[request] {
|
||||||
|
requestExists = true
|
||||||
|
delete(d.requests, request)
|
||||||
|
}
|
||||||
|
d.requestAccess.Unlock()
|
||||||
|
if !requestExists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d.logger.TraceContext(d.ctx, "read ICMPv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
|
||||||
|
}
|
||||||
err = d.routeContext.WritePacket(buffer.Bytes())
|
err = d.routeContext.WritePacket(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.ErrorContext(d.ctx, E.Cause(err, "write ICMP echo reply"))
|
d.logger.ErrorContext(d.ctx, E.Cause(err, "write ICMP echo reply"))
|
||||||
|
|
@ -85,6 +152,33 @@ func (d *Destination) loopRead() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Destination) WritePacket(packet *buf.Buffer) error {
|
func (d *Destination) WritePacket(packet *buf.Buffer) error {
|
||||||
|
if !d.destination.Is6() {
|
||||||
|
ipHdr := header.IPv4(packet.Bytes())
|
||||||
|
if !ipHdr.IsValid(packet.Len()) {
|
||||||
|
return E.New("invalid IPv4 header")
|
||||||
|
}
|
||||||
|
if ipHdr.PayloadLength() < header.ICMPv4MinimumSize {
|
||||||
|
return E.New("invalid ICMPv4 header")
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
||||||
|
d.requestAccess.Lock()
|
||||||
|
d.requests[pingRequest{Source: ipHdr.SourceAddr(), Destination: ipHdr.DestinationAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}] = true
|
||||||
|
d.requestAccess.Unlock()
|
||||||
|
d.logger.TraceContext(d.ctx, "write ICMPv4 echo request from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
|
||||||
|
} else {
|
||||||
|
ipHdr := header.IPv6(packet.Bytes())
|
||||||
|
if !ipHdr.IsValid(packet.Len()) {
|
||||||
|
return E.New("invalid IPv6 header")
|
||||||
|
}
|
||||||
|
if ipHdr.PayloadLength() < header.ICMPv6MinimumSize {
|
||||||
|
return E.New("invalid ICMPv6 header")
|
||||||
|
}
|
||||||
|
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||||
|
d.requestAccess.Lock()
|
||||||
|
d.requests[pingRequest{Source: ipHdr.SourceAddr(), Destination: ipHdr.DestinationAddr(), Identifier: icmpHdr.Ident(), Sequence: icmpHdr.Sequence()}] = true
|
||||||
|
d.requestAccess.Unlock()
|
||||||
|
d.logger.TraceContext(d.ctx, "write ICMPv6 echo request from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr(), " id ", icmpHdr.Ident(), " seq ", icmpHdr.Sequence())
|
||||||
|
}
|
||||||
return d.conn.WriteIP(packet)
|
return d.conn.WriteIP(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
17
ping/ping.go
17
ping/ping.go
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
|
|
@ -24,7 +23,6 @@ import (
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger logger.ContextLogger
|
|
||||||
privileged bool
|
privileged bool
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
destination netip.Addr
|
destination netip.Addr
|
||||||
|
|
@ -32,10 +30,9 @@ type Conn struct {
|
||||||
closed atomic.Bool
|
closed atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func Connect(ctx context.Context, logger logger.ContextLogger, privileged bool, controlFunc control.Func, destination netip.Addr) (*Conn, error) {
|
func Connect(ctx context.Context, privileged bool, controlFunc control.Func, destination netip.Addr) (*Conn, error) {
|
||||||
c := &Conn{
|
c := &Conn{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: logger,
|
|
||||||
privileged: privileged,
|
privileged: privileged,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +120,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
|
||||||
TotalLength: uint16(buffer.Len()),
|
TotalLength: uint16(buffer.Len()),
|
||||||
})
|
})
|
||||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
||||||
c.logger.TraceContext(c.ctx, "read icmpv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
|
|
||||||
} else {
|
} else {
|
||||||
oob := make([]byte, 1024)
|
oob := make([]byte, 1024)
|
||||||
buffer.Advance(header.IPv6MinimumSize)
|
buffer.Advance(header.IPv6MinimumSize)
|
||||||
|
|
@ -164,7 +160,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
|
||||||
SrcAddr: addr,
|
SrcAddr: addr,
|
||||||
DstAddr: c.source.Load(),
|
DstAddr: c.source.Load(),
|
||||||
})
|
})
|
||||||
c.logger.TraceContext(c.ctx, "read icmpv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := buffer.ReadOnceFrom(c.conn)
|
_, err := buffer.ReadOnceFrom(c.conn)
|
||||||
|
|
@ -192,7 +187,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
|
||||||
}
|
}
|
||||||
icmpHdr.SetChecksum(0)
|
icmpHdr.SetChecksum(0)
|
||||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
||||||
c.logger.TraceContext(c.ctx, "read icmpv4 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
|
|
||||||
} else {
|
} else {
|
||||||
ipHdr := header.IPv6(buffer.Bytes())
|
ipHdr := header.IPv6(buffer.Bytes())
|
||||||
if !ipHdr.IsValid(buffer.Len()) {
|
if !ipHdr.IsValid(buffer.Len()) {
|
||||||
|
|
@ -209,7 +203,6 @@ func (c *Conn) ReadIP(buffer *buf.Buffer) error {
|
||||||
Src: ipHdr.SourceAddressSlice(),
|
Src: ipHdr.SourceAddressSlice(),
|
||||||
Dst: ipHdr.DestinationAddressSlice(),
|
Dst: ipHdr.DestinationAddressSlice(),
|
||||||
}))
|
}))
|
||||||
c.logger.TraceContext(c.ctx, "read icmpv6 echo reply from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -254,7 +247,6 @@ func (c *Conn) WriteIP(buffer *buf.Buffer) error {
|
||||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
||||||
}
|
}
|
||||||
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||||
c.logger.TraceContext(c.ctx, "write icmpv4 echo request from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
|
|
||||||
return common.Error(c.conn.Write(ipHdr.Payload()))
|
return common.Error(c.conn.Write(ipHdr.Payload()))
|
||||||
} else {
|
} else {
|
||||||
ipHdr := header.IPv6(buffer.Bytes())
|
ipHdr := header.IPv6(buffer.Bytes())
|
||||||
|
|
@ -269,7 +261,6 @@ func (c *Conn) WriteIP(buffer *buf.Buffer) error {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||||
c.logger.TraceContext(c.ctx, "write icmpv6 echo request from ", ipHdr.SourceAddr(), " to ", ipHdr.DestinationAddr())
|
|
||||||
return common.Error(c.conn.Write(ipHdr.Payload()))
|
return common.Error(c.conn.Write(ipHdr.Payload()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +273,6 @@ func (c *Conn) WriteICMP(buffer *buf.Buffer) error {
|
||||||
icmpHdr.SetIdent(^icmpHdr.Ident())
|
icmpHdr.SetIdent(^icmpHdr.Ident())
|
||||||
icmpHdr.SetChecksum(0)
|
icmpHdr.SetChecksum(0)
|
||||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
||||||
c.logger.TraceContext(c.ctx, "write icmpv4 echo request to ", c.destination)
|
|
||||||
} else {
|
} else {
|
||||||
icmpHdr := header.ICMPv6(buffer.Bytes())
|
icmpHdr := header.ICMPv6(buffer.Bytes())
|
||||||
icmpHdr.SetIdent(^icmpHdr.Ident())
|
icmpHdr.SetIdent(^icmpHdr.Ident())
|
||||||
|
|
@ -294,11 +284,6 @@ func (c *Conn) WriteICMP(buffer *buf.Buffer) error {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !c.destination.Is6() {
|
|
||||||
c.logger.TraceContext(c.ctx, "write icmpv4 echo request to ", c.destination)
|
|
||||||
} else {
|
|
||||||
c.logger.TraceContext(c.ctx, "write icmpv6 echo request to ", c.destination)
|
|
||||||
}
|
|
||||||
return common.Error(c.conn.Write(buffer.Bytes()))
|
return common.Error(c.conn.Write(buffer.Bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ import (
|
||||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||||
"github.com/sagernet/sing-tun/ping"
|
"github.com/sagernet/sing-tun/ping"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -73,7 +71,7 @@ func TestPing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPingIPv4ReadIP(t *testing.T, privileged bool, addr string) {
|
func testPingIPv4ReadIP(t *testing.T, privileged bool, addr string) {
|
||||||
conn, err := ping.Connect(context.Background(), logger.NOP(), privileged, nil, netip.MustParseAddr(addr))
|
conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr))
|
||||||
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +104,7 @@ func testPingIPv4ReadIP(t *testing.T, privileged bool, addr string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPingIPv4ReadICMP(t *testing.T, privileged bool, addr string) {
|
func testPingIPv4ReadICMP(t *testing.T, privileged bool, addr string) {
|
||||||
conn, err := ping.Connect(context.Background(), logger.NOP(), privileged, nil, netip.MustParseAddr(addr))
|
conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr))
|
||||||
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +136,7 @@ func testPingIPv4ReadICMP(t *testing.T, privileged bool, addr string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPingIPv6ReadIP(t *testing.T, privileged bool, addr string) {
|
func testPingIPv6ReadIP(t *testing.T, privileged bool, addr string) {
|
||||||
conn, err := ping.Connect(context.Background(), logger.NOP(), privileged, nil, netip.MustParseAddr(addr))
|
conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr))
|
||||||
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +168,7 @@ func testPingIPv6ReadIP(t *testing.T, privileged bool, addr string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPingIPv6ReadICMP(t *testing.T, privileged bool, addr string) {
|
func testPingIPv6ReadICMP(t *testing.T, privileged bool, addr string) {
|
||||||
conn, err := ping.Connect(context.Background(), logger.NOP(), privileged, nil, netip.MustParseAddr(addr))
|
conn, err := ping.Connect(context.Background(), privileged, nil, netip.MustParseAddr(addr))
|
||||||
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue