diff --git a/go.mod b/go.mod index 3f436aa9..16befbb4 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/Dreamacro/clash v1.11.8 github.com/caddyserver/certmagic v0.17.2 github.com/cloudflare/circl v1.2.1-0.20220831060716-4cf0150356fc + github.com/coreos/go-iptables v0.6.0 github.com/cretz/bine v0.2.0 github.com/database64128/tfo-go/v2 v2.0.2 github.com/dustin/go-humanize v1.0.0 @@ -15,6 +16,7 @@ require ( github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.2 github.com/gofrs/uuid v4.3.0+incompatible + github.com/google/gopacket v1.1.19 github.com/hashicorp/yamux v0.1.1 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mholt/acmez v1.0.4 diff --git a/go.sum b/go.sum index 632b02d4..84f8e743 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,8 @@ github.com/cloudflare/circl v1.2.1-0.20220831060716-4cf0150356fc/go.mod h1:+CauB github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= @@ -80,6 +82,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -205,6 +209,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/inbound/hysteria.go b/inbound/hysteria.go index 5beab69a..f237796c 100644 --- a/inbound/hysteria.go +++ b/inbound/hysteria.go @@ -5,6 +5,8 @@ package inbound import ( "bytes" "context" + "net" + "net/netip" "sync" "github.com/sagernet/quic-go" @@ -15,6 +17,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/hysteria" + "github.com/sagernet/sing-box/transport/tcpraw" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" @@ -25,12 +28,14 @@ var _ adapter.Inbound = (*Hysteria)(nil) type Hysteria struct { myInboundAdapter + tcpRaw bool quicConfig *quic.Config tlsConfig tls.ServerConfig authKey []byte xplusKey []byte sendBPS uint64 recvBPS uint64 + rawConn net.PacketConn listener quic.Listener udpAccess sync.RWMutex udpSessionId uint32 @@ -110,6 +115,13 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL recvBPS: down, udpSessions: make(map[uint32]chan *hysteria.UDPMessage), } + switch options.Mode { + case "", "udp": + case "faketcp": + inbound.tcpRaw = true + default: + return nil, E.New("unsupported mode: ", options.Mode) + } if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired } @@ -125,7 +137,16 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL } func (h *Hysteria) Start() error { - packetConn, err := h.myInboundAdapter.ListenUDP() + var ( + packetConn net.PacketConn + err error + ) + if h.tcpRaw { + bindAddr := M.SocksaddrFrom(netip.Addr(h.listenOptions.Listen), h.listenOptions.ListenPort) + packetConn, err = tcpraw.Listen("tcp", bindAddr.String()) + } else { + packetConn, err = h.myInboundAdapter.ListenUDP() + } if err != nil { return err } @@ -135,17 +156,21 @@ func (h *Hysteria) Start() error { } err = h.tlsConfig.Start() if err != nil { + packetConn.Close() return err } rawConfig, err := h.tlsConfig.Config() if err != nil { + packetConn.Close() return err } listener, err := quic.Listen(packetConn, rawConfig, h.quicConfig) if err != nil { + packetConn.Close() return err } h.listener = listener + h.rawConn = packetConn h.logger.Info("udp server started at ", listener.Addr()) go h.acceptLoop() return nil @@ -311,6 +336,7 @@ func (h *Hysteria) Close() error { return common.Close( &h.myInboundAdapter, h.listener, + h.rawConn, h.tlsConfig, ) } diff --git a/option/hysteria.go b/option/hysteria.go index f9266924..44bd8209 100644 --- a/option/hysteria.go +++ b/option/hysteria.go @@ -2,6 +2,7 @@ package option type HysteriaInboundOptions struct { ListenOptions + Mode string `json:"mode,omitempty"` Up string `json:"up,omitempty"` UpMbps int `json:"up_mbps,omitempty"` Down string `json:"down,omitempty"` @@ -19,6 +20,7 @@ type HysteriaInboundOptions struct { type HysteriaOutboundOptions struct { DialerOptions ServerOptions + Mode string `json:"mode,omitempty"` Up string `json:"up,omitempty"` UpMbps int `json:"up_mbps,omitempty"` Down string `json:"down,omitempty"` diff --git a/outbound/hysteria.go b/outbound/hysteria.go index d3875f0c..d4e93903 100644 --- a/outbound/hysteria.go +++ b/outbound/hysteria.go @@ -16,6 +16,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/transport/hysteria" + "github.com/sagernet/sing-box/transport/tcpraw" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" @@ -27,6 +28,7 @@ var _ adapter.Outbound = (*Hysteria)(nil) type Hysteria struct { myOutboundAdapter + tcpRaw bool ctx context.Context dialer N.Dialer serverAddr M.Socksaddr @@ -38,7 +40,7 @@ type Hysteria struct { recvBPS uint64 connAccess sync.Mutex conn quic.Connection - rawConn net.Conn + rawConn net.PacketConn udpAccess sync.RWMutex udpSessions map[uint32]chan *hysteria.UDPMessage udpDefragger hysteria.Defragger @@ -114,7 +116,7 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL if down < hysteria.MinSpeedBPS { return nil, E.New("invalid down speed") } - return &Hysteria{ + outbound := &Hysteria{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeHysteria, network: options.Network.Build(), @@ -131,7 +133,15 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL xplusKey: xplus, sendBPS: up, recvBPS: down, - }, nil + } + switch options.Mode { + case "", "udp": + case "faketcp": + outbound.tcpRaw = true + default: + return nil, E.New("unsupported mode: ", options.Mode) + } + return outbound, nil } func (h *Hysteria) offer(ctx context.Context) (quic.Connection, error) { @@ -163,17 +173,28 @@ func (h *Hysteria) offer(ctx context.Context) (quic.Connection, error) { } func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) { - udpConn, err := h.dialer.DialContext(h.ctx, "udp", h.serverAddr) + var packetConn net.PacketConn + var remoteAddr net.Addr + var err error + if h.tcpRaw { + packetConn, err = tcpraw.Dial("tcp", h.serverAddr.String()) + remoteAddr = h.serverAddr.UDPAddr() + } else { + var conn net.Conn + conn, err = h.dialer.DialContext(h.ctx, "udp", h.serverAddr) + if err == nil { + packetConn = bufio.NewUnbindPacketConn(conn) + remoteAddr = conn.RemoteAddr() + } + } if err != nil { return nil, err } - var packetConn net.PacketConn - packetConn = bufio.NewUnbindPacketConn(udpConn) if h.xplusKey != nil { packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey) } packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn} - quicConn, err := quic.Dial(packetConn, udpConn.RemoteAddr(), h.serverAddr.AddrString(), h.tlsConfig, h.quicConfig) + quicConn, err := quic.Dial(packetConn, remoteAddr, h.serverAddr.AddrString(), h.tlsConfig, h.quicConfig) if err != nil { packetConn.Close() return nil, err @@ -203,7 +224,7 @@ func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) { } quicConn.SetCongestionControl(hysteria.NewBrutalSender(congestion.ByteCount(serverHello.RecvBPS))) h.conn = quicConn - h.rawConn = udpConn + h.rawConn = packetConn return quicConn, nil } diff --git a/test/go.mod b/test/go.mod index 17a9cd9b..f67ee389 100644 --- a/test/go.mod +++ b/test/go.mod @@ -15,7 +15,7 @@ require ( github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.0 go.uber.org/goleak v1.2.0 - golang.org/x/net v0.0.0-20221004154528-8021a29435af + golang.org/x/net v0.0.0-20221017152216-f25eb7ecb193 ) //replace github.com/sagernet/sing => ../../sing @@ -28,12 +28,13 @@ require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/caddyserver/certmagic v0.17.2 // indirect github.com/cloudflare/circl v1.2.1-0.20220831060716-4cf0150356fc // indirect + github.com/coreos/go-iptables v0.6.0 // indirect github.com/cretz/bine v0.2.0 // indirect - github.com/database64128/tfo-go/v2 v2.0.1 // indirect + github.com/database64128/tfo-go/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-chi/chi/v5 v5.0.7 // indirect github.com/go-chi/cors v1.2.1 // indirect github.com/go-chi/render v1.0.2 // indirect @@ -42,8 +43,9 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/gopacket v1.1.19 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/klauspost/compress v1.13.6 // indirect + github.com/klauspost/compress v1.15.9 // indirect github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/libdns/libdns v0.2.1 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect @@ -62,13 +64,13 @@ require ( github.com/pires/go-proxyproto v0.6.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/refraction-networking/utls v1.1.2 // indirect + github.com/refraction-networking/utls v1.1.5 // indirect github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3 // indirect - github.com/sagernet/sing-tun v0.0.0-20221009132126-1ede22e6eb7e // indirect + github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd // indirect github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685 // indirect github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect @@ -79,17 +81,17 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.23.0 // indirect go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab // indirect - golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b // indirect + golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b // indirect google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect - google.golang.org/grpc v1.49.0 // indirect + google.golang.org/grpc v1.50.1 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/test/go.sum b/test/go.sum index d21c096d..dd792a61 100644 --- a/test/go.sum +++ b/test/go.sum @@ -25,12 +25,14 @@ github.com/cloudflare/circl v1.2.1-0.20220831060716-4cf0150356fc/go.mod h1:+CauB github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= -github.com/database64128/tfo-go/v2 v2.0.1 h1:VFfturVoq6NmPGfhXO1K15x82KR19aAfw1RYtTzyVv4= -github.com/database64128/tfo-go/v2 v2.0.1/go.mod h1:FDdt4JaAsRU66wsYHxSVytYimPkKIHupVsxM+5DhvjY= +github.com/database64128/tfo-go/v2 v2.0.2 h1:5rGgkJeLEKlNaqredfrPQNLnctn1b+1fq/8tdKdOzJg= +github.com/database64128/tfo-go/v2 v2.0.2/go.mod h1:FDdt4JaAsRU66wsYHxSVytYimPkKIHupVsxM+5DhvjY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -50,8 +52,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -92,6 +94,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -99,8 +103,8 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -152,8 +156,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/refraction-networking/utls v1.1.2 h1:a7GQauRt72VG+wtNm0lnrAaCGlyX47gEi1++dSsDBpw= -github.com/refraction-networking/utls v1.1.2/go.mod h1:+D89TUtA8+NKVFj1IXWr0p3tSdX1+SqUB7rL0QnGqyg= +github.com/refraction-networking/utls v1.1.5 h1:JtrojoNhbUQkBqEg05sP3gDgDj6hIEAAVKbI9lx4n6w= +github.com/refraction-networking/utls v1.1.5/go.mod h1:jRQxtYi7nkq1p28HF2lwOH5zQm9aC8rpK0O9lIIzGh8= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34= github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g= @@ -171,8 +175,8 @@ github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3 h1:AEdyJxEDFq38z github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww= github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4= github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM= -github.com/sagernet/sing-tun v0.0.0-20221009132126-1ede22e6eb7e h1:UIE1wKIr92d2VUWox3On8JQknvewl7aeG+JfthISX0w= -github.com/sagernet/sing-tun v0.0.0-20221009132126-1ede22e6eb7e/go.mod h1:qbqV9lwcXJnj1Tw4we7oA6Z8zGE/kCXQBCzuhzRWVw8= +github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd h1:TtoZDwg09Cpqi+gCmCtL6w4oEUZ5lHz+vHIjdr1UBNY= +github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd/go.mod h1:1u3pjXA9HmH7kRiBJqM3C/zPxrxnCLd3svmqtub/RFU= github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685 h1:AZzFNRR/ZwMTceUQ1b/mxx6oyKqmFymdMn/yleJmoVM= github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM= github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38= @@ -220,9 +224,9 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0= -golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= @@ -230,6 +234,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -255,10 +260,11 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= -golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221017152216-f25eb7ecb193 h1:3Moaxt4TfzNcQH6DWvlYKraN1ozhBXQHcgvXjRGeim0= +golang.org/x/net v0.0.0-20221017152216-f25eb7ecb193/go.mod h1:RpDiru2p0u2F0lLpEoqnP2+7xs0ifAuOcJ442g6GU2s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -293,13 +299,14 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= -golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -352,8 +359,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/test/hysteria_test.go b/test/hysteria_test.go index 5ffaba5c..2ea1d8c0 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -9,6 +9,14 @@ import ( ) func TestHysteriaSelf(t *testing.T) { + testHysteriaSelf(t, "udp") +} + +func TestHysteriaTCPRawSelf(t *testing.T) { + testHysteriaSelf(t, "faketcp") +} + +func testHysteriaSelf(t *testing.T, mode string) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ Inbounds: []option.Inbound{ @@ -29,6 +37,7 @@ func TestHysteriaSelf(t *testing.T) { Listen: option.ListenAddress(netip.IPv4Unspecified()), ListenPort: serverPort, }, + Mode: mode, UpMbps: 100, DownMbps: 100, AuthString: "password", @@ -54,6 +63,7 @@ func TestHysteriaSelf(t *testing.T) { Server: "127.0.0.1", ServerPort: serverPort, }, + Mode: mode, UpMbps: 100, DownMbps: 100, AuthString: "password", diff --git a/transport/hysteria/fake.go b/transport/hysteria/fake.go new file mode 100644 index 00000000..c1b95213 --- /dev/null +++ b/transport/hysteria/fake.go @@ -0,0 +1,19 @@ +package hysteria + +import "syscall" + +var _ syscall.RawConn = (*FakeSyscallConn)(nil) + +type FakeSyscallConn struct{} + +func (c *FakeSyscallConn) Control(f func(fd uintptr)) error { + return nil +} + +func (c *FakeSyscallConn) Read(f func(fd uintptr) (done bool)) error { + return nil +} + +func (c *FakeSyscallConn) Write(f func(fd uintptr) (done bool)) error { + return nil +} diff --git a/transport/hysteria/wrap.go b/transport/hysteria/wrap.go index 8d2a63c8..26cad2db 100644 --- a/transport/hysteria/wrap.go +++ b/transport/hysteria/wrap.go @@ -10,24 +10,47 @@ import ( "github.com/sagernet/sing/common" ) +type AbstractPacketConn interface { + SetReadBuffer(int) error + SetWriteBuffer(int) error + File() (*os.File, error) +} + type PacketConnWrapper struct { net.PacketConn } func (c *PacketConnWrapper) SetReadBuffer(bytes int) error { - return common.MustCast[*net.UDPConn](c.PacketConn).SetReadBuffer(bytes) + conn, ok := common.Cast[AbstractPacketConn](c.PacketConn) + if !ok { + return os.ErrInvalid + } + return conn.SetReadBuffer(bytes) } func (c *PacketConnWrapper) SetWriteBuffer(bytes int) error { - return common.MustCast[*net.UDPConn](c.PacketConn).SetWriteBuffer(bytes) + conn, ok := common.Cast[AbstractPacketConn](c.PacketConn) + if !ok { + return os.ErrInvalid + } + return conn.SetWriteBuffer(bytes) } func (c *PacketConnWrapper) SyscallConn() (syscall.RawConn, error) { - return common.MustCast[*net.UDPConn](c.PacketConn).SyscallConn() + conn, ok := common.Cast[syscall.Conn](c.PacketConn) + if !ok { + // fix quic-go + return &FakeSyscallConn{}, nil + } + return conn.SyscallConn() } func (c *PacketConnWrapper) File() (f *os.File, err error) { - return common.MustCast[*net.UDPConn](c.PacketConn).File() + r, ok := common.Cast[AbstractPacketConn](c.PacketConn) + if !ok { + return nil, os.ErrInvalid + } + return r.File() } func (c *PacketConnWrapper) Upstream() any { diff --git a/transport/tcpraw/tcp_linux.go b/transport/tcpraw/tcp_linux.go new file mode 100644 index 00000000..4efaf6d9 --- /dev/null +++ b/transport/tcpraw/tcp_linux.go @@ -0,0 +1,609 @@ +//go:build linux && with_tcpraw + +package tcpraw + +// kanged from https://github.com/xtaci/tcpraw + +import ( + "crypto/rand" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/coreos/go-iptables/iptables" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" +) + +var ( + errOpNotImplemented = errors.New("operation not implemented") + errTimeout = errors.New("timeout") + expire = time.Minute +) + +// a message from NIC +type message struct { + bts []byte + addr net.Addr +} + +// a tcp flow information of a connection pair +type tcpFlow struct { + conn *net.TCPConn // the related system TCP connection of this flow + handle *net.IPConn // the handle to send packets + seq uint32 // TCP sequence number + ack uint32 // TCP acknowledge number + networkLayer gopacket.SerializableLayer // network layer header for tx + ts time.Time // last packet incoming time + buf gopacket.SerializeBuffer // a buffer for write + tcpHeader layers.TCP +} + +// TCPConn defines a TCP-packet oriented connection +type TCPConn struct { + die chan struct{} + dieOnce sync.Once + + // the main golang sockets + tcpconn *net.TCPConn // from net.Dial + listener *net.TCPListener // from net.Listen + + // handles + handles []*net.IPConn + + // packets captured from all related NICs will be delivered to this channel + chMessage chan message + + // all TCP flows + flowTable map[string]*tcpFlow + flowsLock sync.Mutex + + // iptables + iptables *iptables.IPTables + iprule []string + + ip6tables *iptables.IPTables + ip6rule []string + + // deadlines + readDeadline atomic.Value + writeDeadline atomic.Value + + // serialization + opts gopacket.SerializeOptions +} + +// lockflow locks the flow table and apply function `f` to the entry, and create one if not exist +func (conn *TCPConn) lockflow(addr net.Addr, f func(e *tcpFlow)) { + key := addr.String() + conn.flowsLock.Lock() + e := conn.flowTable[key] + if e == nil { // entry first visit + e = new(tcpFlow) + e.ts = time.Now() + e.buf = gopacket.NewSerializeBuffer() + } + f(e) + conn.flowTable[key] = e + conn.flowsLock.Unlock() +} + +// clean expired flows +func (conn *TCPConn) cleaner() { + ticker := time.NewTicker(time.Minute) + select { + case <-conn.die: + return + case <-ticker.C: + conn.flowsLock.Lock() + for k, v := range conn.flowTable { + if time.Now().Sub(v.ts) > expire { + if v.conn != nil { + setTTL(v.conn, 64) + v.conn.Close() + } + delete(conn.flowTable, k) + } + } + conn.flowsLock.Unlock() + } +} + +// captureFlow capture every inbound packets based on rules of BPF +func (conn *TCPConn) captureFlow(handle *net.IPConn, port int) { + buf := make([]byte, 2048) + opt := gopacket.DecodeOptions{NoCopy: true, Lazy: true} + for { + n, addr, err := handle.ReadFromIP(buf) + if err != nil { + return + } + + // try decoding TCP frame from buf[:n] + packet := gopacket.NewPacket(buf[:n], layers.LayerTypeTCP, opt) + transport := packet.TransportLayer() + tcp, ok := transport.(*layers.TCP) + if !ok { + continue + } + + // port filtering + if int(tcp.DstPort) != port { + continue + } + + // address building + var src net.TCPAddr + src.IP = addr.IP + src.Port = int(tcp.SrcPort) + + var orphan bool + // flow maintaince + conn.lockflow(&src, func(e *tcpFlow) { + if e.conn == nil { // make sure it's related to net.TCPConn + orphan = true // mark as orphan if it's not related net.TCPConn + } + + // to keep track of TCP header related to this source + e.ts = time.Now() + if tcp.ACK { + e.seq = tcp.Ack + } + if tcp.SYN { + e.ack = tcp.Seq + 1 + } + if tcp.PSH { + if e.ack == tcp.Seq { + e.ack = tcp.Seq + uint32(len(tcp.Payload)) + } + } + e.handle = handle + }) + + // push data if it's not orphan + if !orphan && tcp.PSH { + payload := make([]byte, len(tcp.Payload)) + copy(payload, tcp.Payload) + select { + case conn.chMessage <- message{payload, &src}: + case <-conn.die: + return + } + } + } +} + +// ReadFrom implements the PacketConn ReadFrom method. +func (conn *TCPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + var timer *time.Timer + var deadline <-chan time.Time + if d, ok := conn.readDeadline.Load().(time.Time); ok && !d.IsZero() { + timer = time.NewTimer(time.Until(d)) + defer timer.Stop() + deadline = timer.C + } + + select { + case <-deadline: + return 0, nil, errTimeout + case <-conn.die: + return 0, nil, io.EOF + case packet := <-conn.chMessage: + n = copy(p, packet.bts) + return n, packet.addr, nil + } +} + +// WriteTo implements the PacketConn WriteTo method. +func (conn *TCPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + var deadline <-chan time.Time + if d, ok := conn.writeDeadline.Load().(time.Time); ok && !d.IsZero() { + timer := time.NewTimer(time.Until(d)) + defer timer.Stop() + deadline = timer.C + } + + select { + case <-deadline: + return 0, errTimeout + case <-conn.die: + return 0, io.EOF + default: + raddr, err := net.ResolveTCPAddr("tcp", addr.String()) + if err != nil { + return 0, err + } + + var lport int + if conn.tcpconn != nil { + lport = conn.tcpconn.LocalAddr().(*net.TCPAddr).Port + } else { + lport = conn.listener.Addr().(*net.TCPAddr).Port + } + + conn.lockflow(addr, func(e *tcpFlow) { + // if the flow doesn't have handle , assume this packet has lost, without notification + if e.handle == nil { + n = len(p) + return + } + + // build tcp header with local and remote port + e.tcpHeader.SrcPort = layers.TCPPort(lport) + e.tcpHeader.DstPort = layers.TCPPort(raddr.Port) + binary.Read(rand.Reader, binary.LittleEndian, &e.tcpHeader.Window) + e.tcpHeader.Window |= 0x8000 // make sure it's larger than 32768 + e.tcpHeader.Ack = e.ack + e.tcpHeader.Seq = e.seq + e.tcpHeader.PSH = true + e.tcpHeader.ACK = true + + // build IP header with src & dst ip for TCP checksum + if raddr.IP.To4() != nil { + ip := &layers.IPv4{ + Protocol: layers.IPProtocolTCP, + SrcIP: e.handle.LocalAddr().(*net.IPAddr).IP.To4(), + DstIP: raddr.IP.To4(), + } + e.tcpHeader.SetNetworkLayerForChecksum(ip) + } else { + ip := &layers.IPv6{ + NextHeader: layers.IPProtocolTCP, + SrcIP: e.handle.LocalAddr().(*net.IPAddr).IP.To16(), + DstIP: raddr.IP.To16(), + } + e.tcpHeader.SetNetworkLayerForChecksum(ip) + } + + e.buf.Clear() + gopacket.SerializeLayers(e.buf, conn.opts, &e.tcpHeader, gopacket.Payload(p)) + if conn.tcpconn != nil { + _, err = e.handle.Write(e.buf.Bytes()) + } else { + _, err = e.handle.WriteToIP(e.buf.Bytes(), &net.IPAddr{IP: raddr.IP}) + } + // increase seq in flow + e.seq += uint32(len(p)) + n = len(p) + }) + } + return +} + +// Close closes the connection. +func (conn *TCPConn) Close() error { + var err error + conn.dieOnce.Do(func() { + // signal closing + close(conn.die) + + // close all established tcp connections + if conn.tcpconn != nil { // client + setTTL(conn.tcpconn, 64) + err = conn.tcpconn.Close() + } else if conn.listener != nil { + err = conn.listener.Close() // server + conn.flowsLock.Lock() + for k, v := range conn.flowTable { + if v.conn != nil { + setTTL(v.conn, 64) + v.conn.Close() + } + delete(conn.flowTable, k) + } + conn.flowsLock.Unlock() + } + + // close handles + for k := range conn.handles { + conn.handles[k].Close() + } + + // delete iptable + if conn.iptables != nil { + conn.iptables.Delete("filter", "OUTPUT", conn.iprule...) + } + if conn.ip6tables != nil { + conn.ip6tables.Delete("filter", "OUTPUT", conn.ip6rule...) + } + }) + return err +} + +// LocalAddr returns the local network address. +func (conn *TCPConn) LocalAddr() net.Addr { + if conn.tcpconn != nil { + return conn.tcpconn.LocalAddr() + } else if conn.listener != nil { + return conn.listener.Addr() + } + return nil +} + +// SetDeadline implements the Conn SetDeadline method. +func (conn *TCPConn) SetDeadline(t time.Time) error { + if err := conn.SetReadDeadline(t); err != nil { + return err + } + if err := conn.SetWriteDeadline(t); err != nil { + return err + } + return nil +} + +// SetReadDeadline implements the Conn SetReadDeadline method. +func (conn *TCPConn) SetReadDeadline(t time.Time) error { + conn.readDeadline.Store(t) + return nil +} + +// SetWriteDeadline implements the Conn SetWriteDeadline method. +func (conn *TCPConn) SetWriteDeadline(t time.Time) error { + conn.writeDeadline.Store(t) + return nil +} + +// SetDSCP sets the 6bit DSCP field in IPv4 header, or 8bit Traffic Class in IPv6 header. +func (conn *TCPConn) SetDSCP(dscp int) error { + for k := range conn.handles { + if err := setDSCP(conn.handles[k], dscp); err != nil { + return err + } + } + return nil +} + +// SetReadBuffer sets the size of the operating system's receive buffer associated with the connection. +func (conn *TCPConn) SetReadBuffer(bytes int) error { + var err error + for k := range conn.handles { + if err := conn.handles[k].SetReadBuffer(bytes); err != nil { + return err + } + } + return err +} + +// SetWriteBuffer sets the size of the operating system's transmit buffer associated with the connection. +func (conn *TCPConn) SetWriteBuffer(bytes int) error { + var err error + for k := range conn.handles { + if err := conn.handles[k].SetWriteBuffer(bytes); err != nil { + return err + } + } + return err +} + +// Dial connects to the remote TCP port, +// and returns a single packet-oriented connection +func Dial(network, address string) (*TCPConn, error) { + // remote address resolve + raddr, err := net.ResolveTCPAddr(network, address) + if err != nil { + return nil, err + } + + // AF_INET + handle, err := net.DialIP("ip:tcp", nil, &net.IPAddr{IP: raddr.IP}) + if err != nil { + return nil, err + } + + // create an established tcp connection + // will hack this tcp connection for packet transmission + tcpconn, err := net.DialTCP(network, nil, raddr) + if err != nil { + return nil, err + } + + // fields + conn := new(TCPConn) + conn.die = make(chan struct{}) + conn.flowTable = make(map[string]*tcpFlow) + conn.tcpconn = tcpconn + conn.chMessage = make(chan message) + conn.lockflow(tcpconn.RemoteAddr(), func(e *tcpFlow) { e.conn = tcpconn }) + conn.handles = append(conn.handles, handle) + conn.opts = gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + go conn.captureFlow(handle, tcpconn.LocalAddr().(*net.TCPAddr).Port) + go conn.cleaner() + + // iptables + err = setTTL(tcpconn, 1) + if err != nil { + return nil, err + } + + if ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4); err == nil { + rule := []string{"-m", "ttl", "--ttl-eq", "1", "-p", "tcp", "-d", raddr.IP.String(), "--dport", fmt.Sprint(raddr.Port), "-j", "DROP"} + if exists, err := ipt.Exists("filter", "OUTPUT", rule...); err == nil { + if !exists { + if err = ipt.Append("filter", "OUTPUT", rule...); err == nil { + conn.iprule = rule + conn.iptables = ipt + } + } + } + } + if ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv6); err == nil { + rule := []string{"-m", "hl", "--hl-eq", "1", "-p", "tcp", "-d", raddr.IP.String(), "--dport", fmt.Sprint(raddr.Port), "-j", "DROP"} + if exists, err := ipt.Exists("filter", "OUTPUT", rule...); err == nil { + if !exists { + if err = ipt.Append("filter", "OUTPUT", rule...); err == nil { + conn.ip6rule = rule + conn.ip6tables = ipt + } + } + } + } + + // discard everything + go io.Copy(ioutil.Discard, tcpconn) + + return conn, nil +} + +// Listen acts like net.ListenTCP, +// and returns a single packet-oriented connection +func Listen(network, address string) (*TCPConn, error) { + // fields + conn := new(TCPConn) + conn.flowTable = make(map[string]*tcpFlow) + conn.die = make(chan struct{}) + conn.chMessage = make(chan message) + conn.opts = gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + + // resolve address + laddr, err := net.ResolveTCPAddr(network, address) + if err != nil { + return nil, err + } + + // AF_INET + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + if laddr.IP == nil || laddr.IP.IsUnspecified() { // if address is not specified, capture on all ifaces + var lasterr error + for _, iface := range ifaces { + if addrs, err := iface.Addrs(); err == nil { + for _, addr := range addrs { + if ipaddr, ok := addr.(*net.IPNet); ok { + if handle, err := net.ListenIP("ip:tcp", &net.IPAddr{IP: ipaddr.IP}); err == nil { + conn.handles = append(conn.handles, handle) + go conn.captureFlow(handle, laddr.Port) + } else { + lasterr = err + } + } + } + } + } + if len(conn.handles) == 0 { + return nil, lasterr + } + } else { + if handle, err := net.ListenIP("ip:tcp", &net.IPAddr{IP: laddr.IP}); err == nil { + conn.handles = append(conn.handles, handle) + go conn.captureFlow(handle, laddr.Port) + } else { + return nil, err + } + } + + // start listening + l, err := net.ListenTCP(network, laddr) + if err != nil { + return nil, err + } + + conn.listener = l + + // start cleaner + go conn.cleaner() + + // iptables drop packets marked with TTL = 1 + // TODO: what if iptables is not available, the next hop will send back ICMP Time Exceeded, + // is this still an acceptable behavior? + if ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4); err == nil { + rule := []string{"-m", "ttl", "--ttl-eq", "1", "-p", "tcp", "--sport", fmt.Sprint(laddr.Port), "-j", "DROP"} + if exists, err := ipt.Exists("filter", "OUTPUT", rule...); err == nil { + if !exists { + if err = ipt.Append("filter", "OUTPUT", rule...); err == nil { + conn.iprule = rule + conn.iptables = ipt + } + } + } + } + if ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv6); err == nil { + rule := []string{"-m", "hl", "--hl-eq", "1", "-p", "tcp", "--sport", fmt.Sprint(laddr.Port), "-j", "DROP"} + if exists, err := ipt.Exists("filter", "OUTPUT", rule...); err == nil { + if !exists { + if err = ipt.Append("filter", "OUTPUT", rule...); err == nil { + conn.ip6rule = rule + conn.ip6tables = ipt + } + } + } + } + + // discard everything in original connection + go func() { + for { + tcpconn, err := l.AcceptTCP() + if err != nil { + return + } + + // if we cannot set TTL = 1, the only thing reasonable is panic + if err := setTTL(tcpconn, 1); err != nil { + panic(err) + } + + // record net.Conn + conn.lockflow(tcpconn.RemoteAddr(), func(e *tcpFlow) { e.conn = tcpconn }) + + // discard everything + go io.Copy(ioutil.Discard, tcpconn) + } + }() + + return conn, nil +} + +// setTTL sets the Time-To-Live field on a given connection +func setTTL(c *net.TCPConn, ttl int) error { + raw, err := c.SyscallConn() + if err != nil { + return err + } + addr := c.LocalAddr().(*net.TCPAddr) + + if addr.IP.To4() == nil { + raw.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_UNICAST_HOPS, ttl) + }) + } else { + raw.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TTL, ttl) + }) + } + return err +} + +// setDSCP sets the 6bit DSCP field in IPv4 header, or 8bit Traffic Class in IPv6 header. +func setDSCP(c *net.IPConn, dscp int) error { + raw, err := c.SyscallConn() + if err != nil { + return err + } + addr := c.LocalAddr().(*net.IPAddr) + + if addr.IP.To4() == nil { + raw.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_TCLASS, dscp) + }) + } else { + raw.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TOS, dscp<<2) + }) + } + return err +} diff --git a/transport/tcpraw/tcp_stub.go b/transport/tcpraw/tcp_stub.go new file mode 100644 index 00000000..f6cb0149 --- /dev/null +++ b/transport/tcpraw/tcp_stub.go @@ -0,0 +1,20 @@ +//go:build !linux || !with_tcpraw + +package tcpraw + +import ( + "errors" + "net" +) + +type TCPConn struct{ *net.UDPConn } + +// Dial connects to the remote TCP port, +// and returns a single packet-oriented connection +func Dial(network, address string) (*TCPConn, error) { + return nil, errors.New("tcpraw is not supported on current os") +} + +func Listen(network, address string) (*TCPConn, error) { + return nil, errors.New("os not supported") +}