diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 7ab61ff6..ab5167bf 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -21,7 +21,6 @@ import ( "github.com/metacubex/mihomo/component/resolver" tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" - "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/transport/gun" "github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/vless" @@ -513,8 +512,6 @@ func NewVless(option VlessOption) (*Vless, error) { if option.Flow != vless.XRV { return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow) } - - log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV) addons = &vless.Addons{ Flow: option.Flow, } diff --git a/component/generater/cmd.go b/component/generater/cmd.go new file mode 100644 index 00000000..9d2c3d97 --- /dev/null +++ b/component/generater/cmd.go @@ -0,0 +1,37 @@ +package generater + +import ( + "encoding/base64" + "fmt" + + "github.com/gofrs/uuid/v5" +) + +func Main(args []string) { + if len(args) < 1 { + panic("Using: generate uuid/reality-keypair/wg-keypair") + } + switch args[0] { + case "uuid": + newUUID, err := uuid.NewV4() + if err != nil { + panic(err) + } + fmt.Println(newUUID.String()) + case "reality-keypair": + privateKey, err := GeneratePrivateKey() + if err != nil { + panic(err) + } + publicKey := privateKey.PublicKey() + fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:])) + fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:])) + case "wg-keypair": + privateKey, err := GeneratePrivateKey() + if err != nil { + panic(err) + } + fmt.Println("PrivateKey: " + privateKey.String()) + fmt.Println("PublicKey: " + privateKey.PublicKey().String()) + } +} diff --git a/component/generater/types.go b/component/generater/types.go new file mode 100644 index 00000000..06f59e94 --- /dev/null +++ b/component/generater/types.go @@ -0,0 +1,97 @@ +// Copy from https://github.com/WireGuard/wgctrl-go/blob/a9ab2273dd1075ea74b88c76f8757f8b4003fcbf/wgtypes/types.go#L71-L155 + +package generater + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + + "golang.org/x/crypto/curve25519" +) + +// KeyLen is the expected key length for a WireGuard key. +const KeyLen = 32 // wgh.KeyLen + +// A Key is a public, private, or pre-shared secret key. The Key constructor +// functions in this package can be used to create Keys suitable for each of +// these applications. +type Key [KeyLen]byte + +// GenerateKey generates a Key suitable for use as a pre-shared secret key from +// a cryptographically safe source. +// +// The output Key should not be used as a private key; use GeneratePrivateKey +// instead. +func GenerateKey() (Key, error) { + b := make([]byte, KeyLen) + if _, err := rand.Read(b); err != nil { + return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %v", err) + } + + return NewKey(b) +} + +// GeneratePrivateKey generates a Key suitable for use as a private key from a +// cryptographically safe source. +func GeneratePrivateKey() (Key, error) { + key, err := GenerateKey() + if err != nil { + return Key{}, err + } + + // Modify random bytes using algorithm described at: + // https://cr.yp.to/ecdh.html. + key[0] &= 248 + key[31] &= 127 + key[31] |= 64 + + return key, nil +} + +// NewKey creates a Key from an existing byte slice. The byte slice must be +// exactly 32 bytes in length. +func NewKey(b []byte) (Key, error) { + if len(b) != KeyLen { + return Key{}, fmt.Errorf("wgtypes: incorrect key size: %d", len(b)) + } + + var k Key + copy(k[:], b) + + return k, nil +} + +// ParseKey parses a Key from a base64-encoded string, as produced by the +// Key.String method. +func ParseKey(s string) (Key, error) { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return Key{}, fmt.Errorf("wgtypes: failed to parse base64-encoded key: %v", err) + } + + return NewKey(b) +} + +// PublicKey computes a public key from the private key k. +// +// PublicKey should only be called when k is a private key. +func (k Key) PublicKey() Key { + var ( + pub [KeyLen]byte + priv = [KeyLen]byte(k) + ) + + // ScalarBaseMult uses the correct base value per https://cr.yp.to/ecdh.html, + // so no need to specify it. + curve25519.ScalarBaseMult(&pub, &priv) + + return Key(pub) +} + +// String returns the base64-encoded string representation of a Key. +// +// ParseKey can be used to produce a new Key from this string. +func (k Key) String() string { + return base64.StdEncoding.EncodeToString(k[:]) +} diff --git a/constant/metadata.go b/constant/metadata.go index 54362989..e4167845 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -25,6 +25,7 @@ const ( SOCKS5 SHADOWSOCKS VMESS + VLESS REDIR TPROXY TUNNEL @@ -69,6 +70,8 @@ func (t Type) String() string { return "ShadowSocks" case VMESS: return "Vmess" + case VLESS: + return "Vless" case REDIR: return "Redir" case TPROXY: @@ -103,6 +106,8 @@ func ParseType(t string) (*Type, error) { res = SHADOWSOCKS case "VMESS": res = VMESS + case "VLESS": + res = VLESS case "REDIR": res = REDIR case "TPROXY": diff --git a/docs/config.yaml b/docs/config.yaml index b92246ae..26e0f767 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -1176,6 +1176,30 @@ listeners: network: [tcp, udp] target: target.com + - name: vless-in-1 + type: vless + port: 10817 + listen: 0.0.0.0 + # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules + # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) + users: + - username: 1 + uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68 + flow: xtls-rprx-vision + # ws-path: "/" # 如果不为空则开启 websocket 传输层 + # 下面两项如果填写则开启 tls(需要同时填写) + # certificate: ./server.crt + # private-key: ./server.key + # 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写) + reality-config: + dest: test.com:443 + private-key: jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0 # 可由 mihomo generate reality-keypair 命令生成 + short-id: + - 0123456789abcdef + server-names: + - test.com + ### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ### + - name: tun-in-1 type: tun # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules diff --git a/go.mod b/go.mod index 3585bc79..585e5c59 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/metacubex/sing-shadowsocks v0.2.8 github.com/metacubex/sing-shadowsocks2 v0.2.2 github.com/metacubex/sing-tun v0.4.5 - github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 + github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3 github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 github.com/metacubex/utls v1.6.6 @@ -40,6 +40,7 @@ require ( github.com/sagernet/cors v1.2.1 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a + github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.5.1 github.com/sagernet/sing-mux v0.2.1 github.com/sagernet/sing-shadowtls v0.1.5 diff --git a/go.sum b/go.sum index 33ad5355..ae4f31d8 100644 --- a/go.sum +++ b/go.sum @@ -122,8 +122,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhD github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0= github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0= -github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I= -github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= +github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3 h1:2kq6azIvsTjTnyw66xXDl5zMzIJqF7GTbvLpkroHssg= +github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ= github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg= github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= @@ -170,6 +170,8 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= +github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= +github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo= github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE= github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo= diff --git a/listener/config/vless.go b/listener/config/vless.go new file mode 100644 index 00000000..97456acc --- /dev/null +++ b/listener/config/vless.go @@ -0,0 +1,38 @@ +package config + +import ( + "github.com/metacubex/mihomo/listener/sing" + + "encoding/json" +) + +type VlessUser struct { + Username string + UUID string + Flow string +} + +type VlessServer struct { + Enable bool + Listen string + Users []VlessUser + WsPath string + Certificate string + PrivateKey string + RealityConfig RealityConfig + MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` +} + +func (t VlessServer) String() string { + b, _ := json.Marshal(t) + return string(b) +} + +type RealityConfig struct { + Dest string + PrivateKey string + ShortID []string + ServerNames []string + MaxTimeDifference int + Proxy string +} diff --git a/listener/inbound/vless.go b/listener/inbound/vless.go new file mode 100644 index 00000000..5a6da133 --- /dev/null +++ b/listener/inbound/vless.go @@ -0,0 +1,125 @@ +package inbound + +import ( + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing_vless" + "github.com/metacubex/mihomo/log" +) + +type VlessOption struct { + BaseOption + Users []VlessUser `inbound:"users"` + WsPath string `inbound:"ws-path,omitempty"` + Certificate string `inbound:"certificate,omitempty"` + PrivateKey string `inbound:"private-key,omitempty"` + RealityConfig RealityConfig `inbound:"reality-config,omitempty"` + MuxOption MuxOption `inbound:"mux-option,omitempty"` +} + +type VlessUser struct { + Username string `inbound:"username,omitempty"` + UUID string `inbound:"uuid"` + Flow string `inbound:"flow,omitempty"` +} + +type RealityConfig struct { + Dest string `inbound:"dest"` + PrivateKey string `inbound:"private-key"` + ShortID []string `inbound:"short-id"` + ServerNames []string `inbound:"server-names"` + MaxTimeDifference int `inbound:"max-time-difference,omitempty"` + Proxy string `inbound:"proxy,omitempty"` +} + +func (c RealityConfig) Build() LC.RealityConfig { + return LC.RealityConfig{ + Dest: c.Dest, + PrivateKey: c.PrivateKey, + ShortID: c.ShortID, + ServerNames: c.ServerNames, + MaxTimeDifference: c.MaxTimeDifference, + Proxy: c.Proxy, + } +} + +func (o VlessOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) +} + +type Vless struct { + *Base + config *VlessOption + l C.MultiAddrListener + vs LC.VlessServer +} + +func NewVless(options *VlessOption) (*Vless, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + users := make([]LC.VlessUser, len(options.Users)) + for i, v := range options.Users { + users[i] = LC.VlessUser{ + Username: v.Username, + UUID: v.UUID, + Flow: v.Flow, + } + } + return &Vless{ + Base: base, + config: options, + vs: LC.VlessServer{ + Enable: true, + Listen: base.RawAddress(), + Users: users, + WsPath: options.WsPath, + Certificate: options.Certificate, + PrivateKey: options.PrivateKey, + RealityConfig: options.RealityConfig.Build(), + MuxOption: options.MuxOption.Build(), + }, + }, nil +} + +// Config implements constant.InboundListener +func (v *Vless) Config() C.InboundConfig { + return v.config +} + +// Address implements constant.InboundListener +func (v *Vless) Address() string { + if v.l != nil { + for _, addr := range v.l.AddrList() { + return addr.String() + } + } + return "" +} + +// Listen implements constant.InboundListener +func (v *Vless) Listen(tunnel C.Tunnel) error { + var err error + users := make([]LC.VlessUser, len(v.config.Users)) + for i, v := range v.config.Users { + users[i] = LC.VlessUser{ + Username: v.Username, + UUID: v.UUID, + Flow: v.Flow, + } + } + v.l, err = sing_vless.New(v.vs, tunnel, v.Additions()...) + if err != nil { + return err + } + log.Infoln("Vless[%s] proxy listening at: %s", v.Name(), v.Address()) + return nil +} + +// Close implements constant.InboundListener +func (v *Vless) Close() error { + return v.l.Close() +} + +var _ C.InboundListener = (*Vless)(nil) diff --git a/listener/parse.go b/listener/parse.go index 1c8b6463..38082e92 100644 --- a/listener/parse.go +++ b/listener/parse.go @@ -86,6 +86,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) { return nil, err } listener, err = IN.NewVmess(vmessOption) + case "vless": + vlessOption := &IN.VlessOption{} + err = decoder.Decode(mapping, vlessOption) + if err != nil { + return nil, err + } + listener, err = IN.NewVless(vlessOption) case "hysteria2": hysteria2Option := &IN.Hysteria2Option{} err = decoder.Decode(mapping, hysteria2Option) diff --git a/listener/sing_vless/server.go b/listener/sing_vless/server.go new file mode 100644 index 00000000..f537de2d --- /dev/null +++ b/listener/sing_vless/server.go @@ -0,0 +1,263 @@ +package sing_vless + +import ( + "context" + "crypto/tls" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "net" + "net/http" + "reflect" + "strings" + "time" + "unsafe" + + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + tlsC "github.com/metacubex/mihomo/component/tls" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/inner" + "github.com/metacubex/mihomo/listener/sing" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/ntp" + mihomoVMess "github.com/metacubex/mihomo/transport/vmess" + + "github.com/metacubex/sing-vmess/vless" + utls "github.com/metacubex/utls" + "github.com/sagernet/reality" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/metadata" +) + +func init() { + vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { + tlsConn, loaded := common.Cast[*reality.Conn](conn) + if !loaded { + return + } + return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn).Elem(), unsafe.Pointer(tlsConn) + }) + + vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { + tlsConn, loaded := common.Cast[*utls.UConn](conn) + if !loaded { + return + } + return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn) + }) + + vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { + tlsConn, loaded := common.Cast[*tlsC.UConn](conn) + if !loaded { + return + } + return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn) + }) +} + +type Listener struct { + closed bool + config LC.VlessServer + listeners []net.Listener + service *vless.Service[string] +} + +func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) { + if len(additions) == 0 { + additions = []inbound.Addition{ + inbound.WithInName("DEFAULT-VLESS"), + inbound.WithSpecialRules(""), + } + } + h, err := sing.NewListenerHandler(sing.ListenerConfig{ + Tunnel: tunnel, + Type: C.VLESS, + Additions: additions, + MuxOption: config.MuxOption, + }) + if err != nil { + return nil, err + } + + service := vless.NewService[string](log.SingLogger, h) + service.UpdateUsers( + common.Map(config.Users, func(it LC.VlessUser) string { + return it.Username + }), + common.Map(config.Users, func(it LC.VlessUser) string { + return it.UUID + }), + common.Map(config.Users, func(it LC.VlessUser) string { + return it.Flow + })) + + sl = &Listener{false, config, nil, service} + + tlsConfig := &tls.Config{} + var realityConfig *reality.Config + var httpMux *http.ServeMux + + if config.Certificate != "" && config.PrivateKey != "" { + cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + if config.WsPath != "" { + httpMux = http.NewServeMux() + httpMux.HandleFunc(config.WsPath, func(w http.ResponseWriter, r *http.Request) { + conn, err := mihomoVMess.StreamUpgradedWebsocketConn(w, r) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + sl.HandleConn(conn, tunnel) + }) + tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1") + } + if config.RealityConfig.PrivateKey != "" { + if tlsConfig.Certificates != nil { + return nil, errors.New("certificate is unavailable in reality") + } + realityConfig = &reality.Config{} + realityConfig.SessionTicketsDisabled = true + realityConfig.Type = "tcp" + realityConfig.Dest = config.RealityConfig.Dest + realityConfig.Time = ntp.Now + realityConfig.ServerNames = make(map[string]bool) + for _, it := range config.RealityConfig.ServerNames { + realityConfig.ServerNames[it] = true + } + privateKey, err := base64.RawURLEncoding.DecodeString(config.RealityConfig.PrivateKey) + if err != nil { + return nil, fmt.Errorf("decode private key: %w", err) + } + if len(privateKey) != 32 { + return nil, errors.New("invalid private key") + } + realityConfig.PrivateKey = privateKey + + realityConfig.MaxTimeDiff = time.Duration(config.RealityConfig.MaxTimeDifference) * time.Microsecond + + realityConfig.ShortIds = make(map[[8]byte]bool) + for i, shortIDString := range config.RealityConfig.ShortID { + var shortID [8]byte + decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString)) + if err != nil { + return nil, fmt.Errorf("decode short_id[%d] '%s': %w", i, shortIDString, err) + } + if decodedLen > 8 { + return nil, fmt.Errorf("invalid short_id[%d]: %s", i, shortIDString) + } + realityConfig.ShortIds[shortID] = true + } + + realityConfig.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { + return inner.HandleTcp(address, config.RealityConfig.Proxy) + } + } + + for _, addr := range strings.Split(config.Listen, ",") { + addr := addr + + //TCP + l, err := inbound.Listen("tcp", addr) + if err != nil { + return nil, err + } + if realityConfig != nil { + l = reality.NewListener(l, realityConfig) + // Due to low implementation quality, the reality server intercepted half close and caused memory leaks. + // We fixed it by calling Close() directly. + l = realityListenerWrapper{l} + } else if len(tlsConfig.Certificates) > 0 { + l = tls.NewListener(l, tlsConfig) + } else { + return nil, errors.New("disallow using Vless without both certificates/reality config") + } + sl.listeners = append(sl.listeners, l) + + go func() { + if httpMux != nil { + _ = http.Serve(l, httpMux) + return + } + for { + c, err := l.Accept() + if err != nil { + if sl.closed { + break + } + continue + } + + go sl.HandleConn(c, tunnel) + } + }() + } + + return sl, nil +} + +func (l *Listener) Close() error { + l.closed = true + var retErr error + for _, lis := range l.listeners { + err := lis.Close() + if err != nil { + retErr = err + } + } + return retErr +} + +func (l *Listener) Config() string { + return l.config.String() +} + +func (l *Listener) AddrList() (addrList []net.Addr) { + for _, lis := range l.listeners { + addrList = append(addrList, lis.Addr()) + } + return +} + +func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { + ctx := sing.WithAdditions(context.TODO(), additions...) + err := l.service.NewConnection(ctx, conn, metadata.Metadata{ + Protocol: "vless", + Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()), + }) + if err != nil { + _ = conn.Close() + return + } +} + +type realityConnWrapper struct { + *reality.Conn +} + +func (c realityConnWrapper) Upstream() any { + return c.Conn +} + +func (c realityConnWrapper) CloseWrite() error { + return c.Close() +} + +type realityListenerWrapper struct { + net.Listener +} + +func (l realityListenerWrapper) Accept() (net.Conn, error) { + c, err := l.Listener.Accept() + if err != nil { + return nil, err + } + return realityConnWrapper{c.(*reality.Conn)}, nil +} diff --git a/main.go b/main.go index 9a2222df..3bc3d74f 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "strings" "syscall" + "github.com/metacubex/mihomo/component/generater" "github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/config" @@ -71,6 +72,11 @@ func main() { return } + if len(os.Args) > 1 && os.Args[1] == "generate" { + generater.Main(os.Args[2:]) + return + } + if version { fmt.Printf("Mihomo Meta %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)