diff --git a/inbound/builder.go b/inbound/builder.go index f9e4b18c..87379dcf 100644 --- a/inbound/builder.go +++ b/inbound/builder.go @@ -41,6 +41,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions) case C.TypeShadowTLS: return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions) + case C.TypeVLite: + return NewVLite(ctx, router, logger, options.Tag, options.VLiteOptions) default: return nil, E.New("unknown inbound type: ", options.Type) } diff --git a/inbound/vlite.go b/inbound/vlite.go new file mode 100644 index 00000000..bd06875d --- /dev/null +++ b/inbound/vlite.go @@ -0,0 +1,48 @@ +//go:build with_vlite + +package inbound + +import ( + "context" + "net/netip" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/vlite" + "github.com/sagernet/sing/common/buf" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat" +) + +type VLite struct { + myInboundAdapter + server *vlite.Server + udpNat *udpnat.Service[netip.AddrPort] +} + +func NewVLite(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLiteInboundOptions) (*VLite, error) { + inbound := &VLite{ + myInboundAdapter: myInboundAdapter{ + protocol: C.TypeVLite, + network: []string{N.NetworkUDP}, + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + }, + server: vlite.NewServer(ctx, options), + } + inbound.udpNat = udpnat.New[netip.AddrPort](options.UDPTimeout, adapter.NewUpstreamContextHandler(nil, inbound.server.NewPacketConnection, inbound)) + inbound.packetHandler = inbound + return inbound, nil +} + +func (h *VLite) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { + h.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) { + return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &tproxyPacketWriter{ctx: ctx, source: natConn, destination: metadata.Destination} + }) + return nil +} diff --git a/inbound/vlite_stub.go b/inbound/vlite_stub.go new file mode 100644 index 00000000..b9f192eb --- /dev/null +++ b/inbound/vlite_stub.go @@ -0,0 +1,16 @@ +//go:build !with_vlite + +package inbound + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func NewVLite(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLiteInboundOptions) (adapter.Inbound, error) { + return nil, E.New(`VLite is not included in this build, rebuild with -tags with_vlite`) +} diff --git a/option/inbound.go b/option/inbound.go index e717c7ba..b9dc5055 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -22,6 +22,7 @@ type _Inbound struct { NaiveOptions NaiveInboundOptions `json:"-"` HysteriaOptions HysteriaInboundOptions `json:"-"` ShadowTLSOptions ShadowTLSInboundOptions `json:"-"` + VLiteOptions VLiteInboundOptions `json:"-"` } type Inbound _Inbound @@ -55,6 +56,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) { v = h.HysteriaOptions case C.TypeShadowTLS: v = h.ShadowTLSOptions + case C.TypeVLite: + v = h.VLiteOptions default: return nil, E.New("unknown inbound type: ", h.Type) } @@ -94,6 +97,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error { v = &h.HysteriaOptions case C.TypeShadowTLS: v = &h.ShadowTLSOptions + case C.TypeVLite: + v = &h.VLiteOptions default: return E.New("unknown inbound type: ", h.Type) } diff --git a/option/vlite.go b/option/vlite.go index 93080136..092a21a3 100644 --- a/option/vlite.go +++ b/option/vlite.go @@ -1,8 +1,17 @@ package option +type VLiteInboundOptions struct { + ListenOptions + VLiteOptions +} + type VLiteOutboundOptions struct { DialerOptions ServerOptions + VLiteOptions +} + +type VLiteOptions struct { Password string `json:"password,omitempty"` ScramblePacket bool `json:"scramble_packet,omitempty"` EnableFEC bool `json:"enable_fec,omitempty"` diff --git a/transport/vlite/client.go b/transport/vlite/client.go index f2c8b769..784defa5 100644 --- a/transport/vlite/client.go +++ b/transport/vlite/client.go @@ -31,7 +31,6 @@ var _ N.Dialer = (*Client)(nil) type Client struct { ctx context.Context - password []byte msgbus *bus.Bus udpdialer vlite_transport.UnderlayTransportDialer puni *puniClient.PacketUniClient @@ -40,15 +39,14 @@ type Client struct { TunnelTxToTun chan interfaces.UDPPacket TunnelRxFromTun chan interfaces.UDPPacket connAdp *udpconn2tun.UDPConn2Tun - config option.VLiteOutboundOptions access sync.Mutex + options option.VLiteOutboundOptions } func NewClient(ctx context.Context, dialer N.Dialer, options option.VLiteOutboundOptions) *Client { //nolint:unparam client := &Client{ - password: []byte(options.Password), - config: options, - msgbus: ibus.NewMessageBus(), + options: options, + msgbus: ibus.NewMessageBus(), } ctx = context.WithValue(ctx, interfaces.ExtraOptionsDisableAutoQuitForClient, true) //nolint:revive,staticcheck ctx = context.WithValue(ctx, interfaces.ExtraOptionsUDPMask, options.Password) //nolint:revive,staticcheck @@ -124,12 +122,12 @@ func (c *Client) Start() error { c.TunnelTxToTun = TunnelTxToTun c.TunnelRxFromTun = TunnelRxFromTun - if c.config.EnableStabilization && c.config.EnableRenegotiation { - c.puni = puniClient.NewPacketUniClient(C_C2STraffic2, C_C2SDataTraffic2, C_S2CTraffic2, c.password, connctx) + if c.options.EnableStabilization && c.options.EnableRenegotiation { + c.puni = puniClient.NewPacketUniClient(C_C2STraffic2, C_C2SDataTraffic2, C_S2CTraffic2, []byte(c.options.Password), connctx) c.puni.OnAutoCarrier(conn, connctx) c.udpserver = worker_client.UDPClient(connctx, C_C2STraffic, C_C2SDataTraffic, C_S2CTraffic, TunnelTxToTun, TunnelRxFromTun, c.puni) } else { - c.udprelay = sctp_server.NewPacketRelayClient(conn, C_C2STraffic2, C_C2SDataTraffic2, C_S2CTraffic2, c.password, connctx) + c.udprelay = sctp_server.NewPacketRelayClient(conn, C_C2STraffic2, C_C2SDataTraffic2, C_S2CTraffic2, []byte(c.options.Password), connctx) c.udpserver = worker_client.UDPClient(connctx, C_C2STraffic, C_C2SDataTraffic, C_S2CTraffic, TunnelTxToTun, TunnelRxFromTun, c.udprelay) } c.ctx = connctx diff --git a/transport/vlite/conn.go b/transport/vlite/conn.go new file mode 100644 index 00000000..20c371e2 --- /dev/null +++ b/transport/vlite/conn.go @@ -0,0 +1,41 @@ +package vlite + +import ( + "net" + + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + N "github.com/sagernet/sing/common/network" +) + +var _ N.NetPacketConn = (*connWrapper)(nil) + +type connWrapper struct { + N.PacketConn + done chan struct{} +} + +func newConnWrapper(conn N.PacketConn) (N.NetPacketConn, chan struct{}) { + done := make(chan struct{}) + return &connWrapper{ + PacketConn: conn, + done: done, + }, done +} + +func (c *connWrapper) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + return bufio.ReadPacket(c, buf.With(p)) +} + +func (c *connWrapper) WriteTo(p []byte, addr net.Addr) (n int, err error) { + return bufio.WritePacket(c, p, addr) +} + +func (c *connWrapper) Close() error { + select { + case <-c.done: + default: + close(c.done) + } + return c.PacketConn.Close() +} diff --git a/transport/vlite/server.go b/transport/vlite/server.go new file mode 100644 index 00000000..0d9db5bd --- /dev/null +++ b/transport/vlite/server.go @@ -0,0 +1,161 @@ +package vlite + +import ( + "context" + "io" + "math/rand" + "net" + "strconv" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + + "github.com/mustafaturan/bus" + "github.com/xiaokangwang/VLite/interfaces" + "github.com/xiaokangwang/VLite/interfaces/ibus" + "github.com/xiaokangwang/VLite/transport" + sctp_server "github.com/xiaokangwang/VLite/transport/packetsctp/sctprelay" + "github.com/xiaokangwang/VLite/transport/packetuni/puniServer" + "github.com/xiaokangwang/VLite/transport/udp/udpServer" + "github.com/xiaokangwang/VLite/transport/udp/udpuni/udpunis" + "github.com/xiaokangwang/VLite/transport/uni/uniserver" + "github.com/xiaokangwang/VLite/workers/server" +) + +var ( + _ transport.UnderlayTransportListener = (*Server)(nil) + _ adapter.PacketConnectionHandler = (*Server)(nil) +) + +type Server struct { + ctx context.Context + msgbus *bus.Bus + transport transport.UnderlayTransportListener + access sync.Mutex + options option.VLiteInboundOptions +} + +func NewServer(ctx context.Context, options option.VLiteInboundOptions) *Server { + server := &Server{ + ctx: ctx, + options: options, + msgbus: ibus.NewMessageBus(), + } + + server.ctx = context.WithValue(server.ctx, interfaces.ExtraOptionsMessageBus, server.msgbus) //nolint:revive,staticcheck + + if options.ScramblePacket { + server.ctx = context.WithValue(server.ctx, interfaces.ExtraOptionsUDPShouldMask, true) //nolint:revive,staticcheck + } + + if options.EnableFEC { + server.ctx = context.WithValue(server.ctx, interfaces.ExtraOptionsUDPFECEnabled, true) //nolint:revive,staticcheck + } + + server.ctx = context.WithValue(server.ctx, interfaces.ExtraOptionsUDPMask, options.Password) //nolint:revive,staticcheck + + if options.HandshakeMaskingPaddingSize != 0 { + ctxv := &interfaces.ExtraOptionsUsePacketArmorValue{PacketArmorPaddingTo: options.HandshakeMaskingPaddingSize, UsePacketArmor: true} + server.ctx = context.WithValue(server.ctx, interfaces.ExtraOptionsUsePacketArmor, ctxv) //nolint:revive,staticcheck + } + + server.transport = server + if options.EnableStabilization { + server.transport = uniserver.NewUnifiedConnectionTransportHub(server.transport, server.ctx) + } + if options.EnableStabilization { + server.transport = udpunis.NewUdpUniServer(string(server.options.Password), server.ctx, server.transport) + } + + return server +} + +func (s *Server) Connection(conn net.Conn, ctx context.Context) context.Context { + S_S2CTraffic := make(chan server.UDPServerTxToClientTraffic, 8) //nolint:revive,stylecheck + S_S2CDataTraffic := make(chan server.UDPServerTxToClientDataTraffic, 8) //nolint:revive,stylecheck + S_C2STraffic := make(chan server.UDPServerRxFromClientTraffic, 8) //nolint:revive,stylecheck + + S_S2CTraffic2 := make(chan interfaces.TrafficWithChannelTag, 8) //nolint:revive,stylecheck + S_S2CDataTraffic2 := make(chan interfaces.TrafficWithChannelTag, 8) //nolint:revive,stylecheck + S_C2STraffic2 := make(chan interfaces.TrafficWithChannelTag, 8) //nolint:revive,stylecheck + + go func(ctx context.Context) { + for { + select { + case data := <-S_S2CTraffic: + S_S2CTraffic2 <- interfaces.TrafficWithChannelTag(data) + case <-ctx.Done(): + return + } + } + }(ctx) + + go func(ctx context.Context) { + for { + select { + case data := <-S_S2CDataTraffic: + S_S2CDataTraffic2 <- interfaces.TrafficWithChannelTag(data) + case <-ctx.Done(): + return + } + } + }(ctx) + + go func(ctx context.Context) { + for { + select { + case data := <-S_C2STraffic2: + S_C2STraffic <- server.UDPServerRxFromClientTraffic(data) + case <-ctx.Done(): + return + } + } + }(ctx) + + if !s.options.EnableStabilization || !s.options.EnableRenegotiation { + relay := sctp_server.NewPacketRelayServer(conn, S_S2CTraffic2, S_S2CDataTraffic2, S_C2STraffic2, s, []byte(s.options.Password), ctx) + udpserver := server.UDPServer(ctx, S_S2CTraffic, S_S2CDataTraffic, S_C2STraffic, relay) + _ = udpserver + } else { + relay := puniServer.NewPacketUniServer(S_S2CTraffic2, S_S2CDataTraffic2, S_C2STraffic2, s, []byte(s.options.Password), ctx) + relay.OnAutoCarrier(conn, ctx) + udpserver := server.UDPServer(ctx, S_S2CTraffic, S_S2CDataTraffic, S_C2STraffic, relay) + _ = udpserver + } + return ctx +} + +func (s *Server) RelayStream(conn io.ReadWriteCloser, ctx context.Context) { +} + +func (s *Server) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + wrapper, done := newConnWrapper(conn) + pc := &bufio.BindPacketConn{ + PacketConn: wrapper, + Addr: metadata.Destination, + } + var initialData [1600]byte + c, err := pc.Read(initialData[:]) + if err != nil { + return E.Cause(err, "unable to read initial data") + } + id, loaded := log.IDFromContext(ctx) + if !loaded { + id = rand.Uint32() + } + vconn, connctx := udpServer.PrepareIncomingUDPConnection(pc, s.ctx, initialData[:c], strconv.FormatInt(int64(id), 10)) + connctx = s.transport.Connection(vconn, connctx) + if connctx == nil { + return E.New("invalid connection discarded") + } + select { + case <-done: + case <-ctx.Done(): + } + return nil +}