github.com/v2fly/v2ray-core/v5@v5.16.2-0.20240507031116-8191faa6e095/infra/conf/v4/trojan.go (about) 1 package v4 2 3 import ( 4 "encoding/json" 5 "path/filepath" 6 "runtime" 7 "strconv" 8 "strings" 9 "syscall" 10 11 "github.com/golang/protobuf/proto" 12 13 "github.com/v2fly/v2ray-core/v5/common/net" 14 "github.com/v2fly/v2ray-core/v5/common/protocol" 15 "github.com/v2fly/v2ray-core/v5/common/serial" 16 "github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon" 17 "github.com/v2fly/v2ray-core/v5/proxy/trojan" 18 ) 19 20 // TrojanServerTarget is configuration of a single trojan server 21 type TrojanServerTarget struct { 22 Address *cfgcommon.Address `json:"address"` 23 Port uint16 `json:"port"` 24 Password string `json:"password"` 25 Email string `json:"email"` 26 Level byte `json:"level"` 27 } 28 29 // TrojanClientConfig is configuration of trojan servers 30 type TrojanClientConfig struct { 31 Servers []*TrojanServerTarget `json:"servers"` 32 } 33 34 // Build implements Buildable 35 func (c *TrojanClientConfig) Build() (proto.Message, error) { 36 config := new(trojan.ClientConfig) 37 38 if len(c.Servers) == 0 { 39 return nil, newError("0 Trojan server configured.") 40 } 41 42 serverSpecs := make([]*protocol.ServerEndpoint, len(c.Servers)) 43 for idx, rec := range c.Servers { 44 if rec.Address == nil { 45 return nil, newError("Trojan server address is not set.") 46 } 47 if rec.Port == 0 { 48 return nil, newError("Invalid Trojan port.") 49 } 50 if rec.Password == "" { 51 return nil, newError("Trojan password is not specified.") 52 } 53 account := &trojan.Account{ 54 Password: rec.Password, 55 } 56 trojan := &protocol.ServerEndpoint{ 57 Address: rec.Address.Build(), 58 Port: uint32(rec.Port), 59 User: []*protocol.User{ 60 { 61 Level: uint32(rec.Level), 62 Email: rec.Email, 63 Account: serial.ToTypedMessage(account), 64 }, 65 }, 66 } 67 68 serverSpecs[idx] = trojan 69 } 70 71 config.Server = serverSpecs 72 73 return config, nil 74 } 75 76 // TrojanInboundFallback is fallback configuration 77 type TrojanInboundFallback struct { 78 Alpn string `json:"alpn"` 79 Path string `json:"path"` 80 Type string `json:"type"` 81 Dest json.RawMessage `json:"dest"` 82 Xver uint64 `json:"xver"` 83 } 84 85 // TrojanUserConfig is user configuration 86 type TrojanUserConfig struct { 87 Password string `json:"password"` 88 Level byte `json:"level"` 89 Email string `json:"email"` 90 } 91 92 // TrojanServerConfig is Inbound configuration 93 type TrojanServerConfig struct { 94 Clients []*TrojanUserConfig `json:"clients"` 95 Fallback json.RawMessage `json:"fallback"` 96 Fallbacks []*TrojanInboundFallback `json:"fallbacks"` 97 } 98 99 // Build implements Buildable 100 func (c *TrojanServerConfig) Build() (proto.Message, error) { 101 config := new(trojan.ServerConfig) 102 config.Users = make([]*protocol.User, len(c.Clients)) 103 for idx, rawUser := range c.Clients { 104 user := new(protocol.User) 105 account := &trojan.Account{ 106 Password: rawUser.Password, 107 } 108 109 user.Email = rawUser.Email 110 user.Level = uint32(rawUser.Level) 111 user.Account = serial.ToTypedMessage(account) 112 config.Users[idx] = user 113 } 114 115 if c.Fallback != nil { 116 return nil, newError(`Trojan settings: please use "fallbacks":[{}] instead of "fallback":{}`) 117 } 118 for _, fb := range c.Fallbacks { 119 var i uint16 120 var s string 121 if err := json.Unmarshal(fb.Dest, &i); err == nil { 122 s = strconv.Itoa(int(i)) 123 } else { 124 _ = json.Unmarshal(fb.Dest, &s) 125 } 126 config.Fallbacks = append(config.Fallbacks, &trojan.Fallback{ 127 Alpn: fb.Alpn, 128 Path: fb.Path, 129 Type: fb.Type, 130 Dest: s, 131 Xver: fb.Xver, 132 }) 133 } 134 for _, fb := range config.Fallbacks { 135 /* 136 if fb.Alpn == "h2" && fb.Path != "" { 137 return nil, newError(`Trojan fallbacks: "alpn":"h2" doesn't support "path"`) 138 } 139 */ 140 if fb.Path != "" && fb.Path[0] != '/' { 141 return nil, newError(`Trojan fallbacks: "path" must be empty or start with "/"`) 142 } 143 if fb.Type == "" && fb.Dest != "" { 144 if fb.Dest == "serve-ws-none" { // nolint:gocritic 145 fb.Type = "serve" 146 } else if filepath.IsAbs(fb.Dest) || fb.Dest[0] == '@' { 147 fb.Type = "unix" 148 if strings.HasPrefix(fb.Dest, "@@") && (runtime.GOOS == "linux" || runtime.GOOS == "android") { 149 fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path)) // may need padding to work with haproxy 150 copy(fullAddr, fb.Dest[1:]) 151 fb.Dest = string(fullAddr) 152 } 153 } else { 154 if _, err := strconv.Atoi(fb.Dest); err == nil { 155 fb.Dest = "127.0.0.1:" + fb.Dest 156 } 157 if _, _, err := net.SplitHostPort(fb.Dest); err == nil { 158 fb.Type = "tcp" 159 } 160 } 161 } 162 if fb.Type == "" { 163 return nil, newError(`Trojan fallbacks: please fill in a valid value for every "dest"`) 164 } 165 if fb.Xver > 2 { 166 return nil, newError(`Trojan fallbacks: invalid PROXY protocol version, "xver" only accepts 0, 1, 2`) 167 } 168 } 169 170 return config, nil 171 }