github.com/metacubex/mihomo@v1.18.5/adapter/outbound/snell.go (about) 1 package outbound 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "strconv" 8 9 N "github.com/metacubex/mihomo/common/net" 10 "github.com/metacubex/mihomo/common/structure" 11 "github.com/metacubex/mihomo/component/dialer" 12 "github.com/metacubex/mihomo/component/proxydialer" 13 C "github.com/metacubex/mihomo/constant" 14 obfs "github.com/metacubex/mihomo/transport/simple-obfs" 15 "github.com/metacubex/mihomo/transport/snell" 16 ) 17 18 type Snell struct { 19 *Base 20 option *SnellOption 21 psk []byte 22 pool *snell.Pool 23 obfsOption *simpleObfsOption 24 version int 25 } 26 27 type SnellOption struct { 28 BasicOption 29 Name string `proxy:"name"` 30 Server string `proxy:"server"` 31 Port int `proxy:"port"` 32 Psk string `proxy:"psk"` 33 UDP bool `proxy:"udp,omitempty"` 34 Version int `proxy:"version,omitempty"` 35 ObfsOpts map[string]any `proxy:"obfs-opts,omitempty"` 36 } 37 38 type streamOption struct { 39 psk []byte 40 version int 41 addr string 42 obfsOption *simpleObfsOption 43 } 44 45 func streamConn(c net.Conn, option streamOption) *snell.Snell { 46 switch option.obfsOption.Mode { 47 case "tls": 48 c = obfs.NewTLSObfs(c, option.obfsOption.Host) 49 case "http": 50 _, port, _ := net.SplitHostPort(option.addr) 51 c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port) 52 } 53 return snell.StreamConn(c, option.psk, option.version) 54 } 55 56 // StreamConnContext implements C.ProxyAdapter 57 func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) { 58 c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) 59 if metadata.NetWork == C.UDP { 60 err := snell.WriteUDPHeader(c, s.version) 61 return c, err 62 } 63 err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version) 64 return c, err 65 } 66 67 // DialContext implements C.ProxyAdapter 68 func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { 69 if s.version == snell.Version2 && len(opts) == 0 { 70 c, err := s.pool.Get() 71 if err != nil { 72 return nil, err 73 } 74 75 if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil { 76 c.Close() 77 return nil, err 78 } 79 return NewConn(c, s), err 80 } 81 82 return s.DialContextWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata) 83 } 84 85 // DialContextWithDialer implements C.ProxyAdapter 86 func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { 87 if len(s.option.DialerProxy) > 0 { 88 dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer) 89 if err != nil { 90 return nil, err 91 } 92 } 93 c, err := dialer.DialContext(ctx, "tcp", s.addr) 94 if err != nil { 95 return nil, fmt.Errorf("%s connect error: %w", s.addr, err) 96 } 97 N.TCPKeepAlive(c) 98 99 defer func(c net.Conn) { 100 safeConnClose(c, err) 101 }(c) 102 103 c, err = s.StreamConnContext(ctx, c, metadata) 104 return NewConn(c, s), err 105 } 106 107 // ListenPacketContext implements C.ProxyAdapter 108 func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 109 return s.ListenPacketWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata) 110 } 111 112 // ListenPacketWithDialer implements C.ProxyAdapter 113 func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) { 114 var err error 115 if len(s.option.DialerProxy) > 0 { 116 dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer) 117 if err != nil { 118 return nil, err 119 } 120 } 121 c, err := dialer.DialContext(ctx, "tcp", s.addr) 122 if err != nil { 123 return nil, err 124 } 125 N.TCPKeepAlive(c) 126 c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) 127 128 err = snell.WriteUDPHeader(c, s.version) 129 if err != nil { 130 return nil, err 131 } 132 133 pc := snell.PacketConn(c) 134 return newPacketConn(pc, s), nil 135 } 136 137 // SupportWithDialer implements C.ProxyAdapter 138 func (s *Snell) SupportWithDialer() C.NetWork { 139 return C.ALLNet 140 } 141 142 // SupportUOT implements C.ProxyAdapter 143 func (s *Snell) SupportUOT() bool { 144 return true 145 } 146 147 func NewSnell(option SnellOption) (*Snell, error) { 148 addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) 149 psk := []byte(option.Psk) 150 151 decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) 152 obfsOption := &simpleObfsOption{Host: "bing.com"} 153 if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil { 154 return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err) 155 } 156 157 switch obfsOption.Mode { 158 case "tls", "http", "": 159 break 160 default: 161 return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode) 162 } 163 164 // backward compatible 165 if option.Version == 0 { 166 option.Version = snell.DefaultSnellVersion 167 } 168 switch option.Version { 169 case snell.Version1, snell.Version2: 170 if option.UDP { 171 return nil, fmt.Errorf("snell version %d not support UDP", option.Version) 172 } 173 case snell.Version3: 174 default: 175 return nil, fmt.Errorf("snell version error: %d", option.Version) 176 } 177 178 s := &Snell{ 179 Base: &Base{ 180 name: option.Name, 181 addr: addr, 182 tp: C.Snell, 183 udp: option.UDP, 184 tfo: option.TFO, 185 mpTcp: option.MPTCP, 186 iface: option.Interface, 187 rmark: option.RoutingMark, 188 prefer: C.NewDNSPrefer(option.IPVersion), 189 }, 190 option: &option, 191 psk: psk, 192 obfsOption: obfsOption, 193 version: option.Version, 194 } 195 196 if option.Version == snell.Version2 { 197 s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) { 198 var err error 199 var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions()...) 200 if len(s.option.DialerProxy) > 0 { 201 cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer) 202 if err != nil { 203 return nil, err 204 } 205 } 206 c, err := cDialer.DialContext(ctx, "tcp", addr) 207 if err != nil { 208 return nil, err 209 } 210 211 N.TCPKeepAlive(c) 212 return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil 213 }) 214 } 215 return s, nil 216 }