129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
//go:build with_gvisor
|
|
|
|
package ping
|
|
|
|
import (
|
|
"context"
|
|
"net/netip"
|
|
"time"
|
|
|
|
"github.com/sagernet/gvisor/pkg/tcpip"
|
|
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
|
|
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
|
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
|
"github.com/sagernet/gvisor/pkg/tcpip/transport"
|
|
"github.com/sagernet/gvisor/pkg/waiter"
|
|
"github.com/sagernet/sing-tun"
|
|
"github.com/sagernet/sing/common"
|
|
"github.com/sagernet/sing/common/buf"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
"github.com/sagernet/sing/common/logger"
|
|
)
|
|
|
|
var _ tun.DirectRouteDestination = (*GVisorDestination)(nil)
|
|
|
|
type GVisorDestination struct {
|
|
ctx context.Context
|
|
logger logger.ContextLogger
|
|
endpoint tcpip.Endpoint
|
|
conn *gonet.TCPConn
|
|
rewriter *Rewriter
|
|
timeout time.Duration
|
|
}
|
|
|
|
func ConnectGVisor(
|
|
ctx context.Context, logger logger.ContextLogger,
|
|
sourceAddress, destinationAddress netip.Addr,
|
|
routeContext tun.DirectRouteContext,
|
|
stack *stack.Stack,
|
|
bindAddress4, bindAddress6 netip.Addr,
|
|
timeout time.Duration,
|
|
) (*GVisorDestination, error) {
|
|
var (
|
|
bindAddress tcpip.Address
|
|
wq waiter.Queue
|
|
endpoint tcpip.Endpoint
|
|
gErr tcpip.Error
|
|
)
|
|
if !destinationAddress.Is6() {
|
|
if !bindAddress4.IsValid() {
|
|
return nil, E.New("missing IPv4 interface address")
|
|
}
|
|
bindAddress = tun.AddressFromAddr(bindAddress4)
|
|
endpoint, gErr = stack.NewRawEndpoint(header.ICMPv4ProtocolNumber, header.IPv4ProtocolNumber, &wq, true)
|
|
} else {
|
|
if !bindAddress6.IsValid() {
|
|
return nil, E.New("missing IPv6 interface address")
|
|
}
|
|
bindAddress = tun.AddressFromAddr(bindAddress6)
|
|
endpoint, gErr = stack.NewRawEndpoint(header.ICMPv6ProtocolNumber, header.IPv6ProtocolNumber, &wq, true)
|
|
}
|
|
if gErr != nil {
|
|
return nil, gonet.TranslateNetstackError(gErr)
|
|
}
|
|
gErr = endpoint.Bind(tcpip.FullAddress{
|
|
NIC: 1,
|
|
Addr: bindAddress,
|
|
})
|
|
if gErr != nil {
|
|
return nil, gonet.TranslateNetstackError(gErr)
|
|
}
|
|
gErr = endpoint.Connect(tcpip.FullAddress{
|
|
NIC: 1,
|
|
Addr: tun.AddressFromAddr(destinationAddress),
|
|
})
|
|
if gErr != nil {
|
|
return nil, gonet.TranslateNetstackError(gErr)
|
|
}
|
|
endpoint.SocketOptions().SetHeaderIncluded(true)
|
|
rewriter := NewRewriter(ctx, logger, bindAddress4, bindAddress6)
|
|
rewriter.CreateSession(tun.DirectRouteSession{Source: sourceAddress, Destination: destinationAddress}, routeContext)
|
|
destination := &GVisorDestination{
|
|
ctx: ctx,
|
|
logger: logger,
|
|
endpoint: endpoint,
|
|
conn: gonet.NewTCPConn(&wq, endpoint),
|
|
rewriter: rewriter,
|
|
timeout: timeout,
|
|
}
|
|
go destination.loopRead()
|
|
return destination, nil
|
|
}
|
|
|
|
func (d *GVisorDestination) loopRead() {
|
|
defer d.endpoint.Close()
|
|
for {
|
|
buffer := buf.NewPacket()
|
|
err := d.conn.SetReadDeadline(time.Now().Add(d.timeout))
|
|
if err != nil {
|
|
d.logger.ErrorContext(d.ctx, E.Cause(err, "set read deadline for ICMP conn"))
|
|
}
|
|
n, err := d.conn.Read(buffer.FreeBytes())
|
|
if err != nil {
|
|
buffer.Release()
|
|
if !E.IsClosed(err) {
|
|
d.logger.ErrorContext(d.ctx, E.Cause(err, "receive ICMP echo reply"))
|
|
}
|
|
return
|
|
}
|
|
buffer.Truncate(n)
|
|
_, err = d.rewriter.WriteBack(buffer.Bytes())
|
|
if err != nil {
|
|
d.logger.ErrorContext(d.ctx, E.Cause(err, "write ICMP echo reply"))
|
|
}
|
|
buffer.Release()
|
|
}
|
|
}
|
|
|
|
func (d *GVisorDestination) WritePacket(packet *buf.Buffer) error {
|
|
d.rewriter.RewritePacket(packet.Bytes())
|
|
return common.Error(d.conn.Write(packet.Bytes()))
|
|
}
|
|
|
|
func (d *GVisorDestination) Close() error {
|
|
return d.conn.Close()
|
|
}
|
|
|
|
func (d *GVisorDestination) IsClosed() bool {
|
|
return transport.DatagramEndpointState(d.endpoint.State()) == transport.DatagramEndpointStateClosed
|
|
}
|