github.com/sagernet/sing-box@v1.9.0-rc.20/outbound/hysteria2.go (about) 1 //go:build with_quic 2 3 package outbound 4 5 import ( 6 "context" 7 "net" 8 "os" 9 10 "github.com/sagernet/sing-box/adapter" 11 "github.com/sagernet/sing-box/common/dialer" 12 "github.com/sagernet/sing-box/common/tls" 13 C "github.com/sagernet/sing-box/constant" 14 "github.com/sagernet/sing-box/log" 15 "github.com/sagernet/sing-box/option" 16 "github.com/sagernet/sing-quic/hysteria" 17 "github.com/sagernet/sing-quic/hysteria2" 18 "github.com/sagernet/sing/common" 19 "github.com/sagernet/sing/common/bufio" 20 E "github.com/sagernet/sing/common/exceptions" 21 M "github.com/sagernet/sing/common/metadata" 22 N "github.com/sagernet/sing/common/network" 23 ) 24 25 var ( 26 _ adapter.Outbound = (*TUIC)(nil) 27 _ adapter.InterfaceUpdateListener = (*TUIC)(nil) 28 ) 29 30 type Hysteria2 struct { 31 myOutboundAdapter 32 client *hysteria2.Client 33 } 34 35 func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (*Hysteria2, error) { 36 options.UDPFragmentDefault = true 37 if options.TLS == nil || !options.TLS.Enabled { 38 return nil, C.ErrTLSRequired 39 } 40 tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS)) 41 if err != nil { 42 return nil, err 43 } 44 var salamanderPassword string 45 if options.Obfs != nil { 46 if options.Obfs.Password == "" { 47 return nil, E.New("missing obfs password") 48 } 49 switch options.Obfs.Type { 50 case hysteria2.ObfsTypeSalamander: 51 salamanderPassword = options.Obfs.Password 52 default: 53 return nil, E.New("unknown obfs type: ", options.Obfs.Type) 54 } 55 } 56 outboundDialer, err := dialer.New(router, options.DialerOptions) 57 if err != nil { 58 return nil, err 59 } 60 networkList := options.Network.Build() 61 client, err := hysteria2.NewClient(hysteria2.ClientOptions{ 62 Context: ctx, 63 Dialer: outboundDialer, 64 Logger: logger, 65 BrutalDebug: options.BrutalDebug, 66 ServerAddress: options.ServerOptions.Build(), 67 SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps), 68 ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps), 69 SalamanderPassword: salamanderPassword, 70 Password: options.Password, 71 TLSConfig: tlsConfig, 72 UDPDisabled: !common.Contains(networkList, N.NetworkUDP), 73 }) 74 if err != nil { 75 return nil, err 76 } 77 return &Hysteria2{ 78 myOutboundAdapter: myOutboundAdapter{ 79 protocol: C.TypeHysteria2, 80 network: networkList, 81 router: router, 82 logger: logger, 83 tag: tag, 84 dependencies: withDialerDependency(options.DialerOptions), 85 }, 86 client: client, 87 }, nil 88 } 89 90 func (h *Hysteria2) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 91 switch N.NetworkName(network) { 92 case N.NetworkTCP: 93 h.logger.InfoContext(ctx, "outbound connection to ", destination) 94 return h.client.DialConn(ctx, destination) 95 case N.NetworkUDP: 96 conn, err := h.ListenPacket(ctx, destination) 97 if err != nil { 98 return nil, err 99 } 100 return bufio.NewBindPacketConn(conn, destination), nil 101 default: 102 return nil, E.New("unsupported network: ", network) 103 } 104 } 105 106 func (h *Hysteria2) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { 107 h.logger.InfoContext(ctx, "outbound packet connection to ", destination) 108 return h.client.ListenPacket(ctx) 109 } 110 111 func (h *Hysteria2) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { 112 return NewConnection(ctx, h, conn, metadata) 113 } 114 115 func (h *Hysteria2) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { 116 return NewPacketConnection(ctx, h, conn, metadata) 117 } 118 119 func (h *Hysteria2) InterfaceUpdated() error { 120 return h.client.CloseWithError(E.New("network changed")) 121 } 122 123 func (h *Hysteria2) Close() error { 124 return h.client.CloseWithError(os.ErrClosed) 125 }