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