github.com/laof/lite-speed-test@v0.0.0-20230930011949-1f39b7037845/config/clash.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "log" 6 "net" 7 "net/url" 8 "strings" 9 10 "github.com/laof/lite-speed-test/dns" 11 "gopkg.in/yaml.v3" 12 ) 13 14 // General config 15 type General struct { 16 Inbound 17 Controller 18 Mode string `json:"mode"` 19 LogLevel string `json:"log-level"` 20 IPv6 bool `json:"ipv6"` 21 Interface string `json:"-"` 22 } 23 24 // Inbound 25 type Inbound struct { 26 Port int `json:"port"` 27 SocksPort int `json:"socks-port"` 28 RedirPort int `json:"redir-port"` 29 TProxyPort int `json:"tproxy-port"` 30 MixedPort int `json:"mixed-port"` 31 Authentication []string `json:"authentication"` 32 AllowLan bool `json:"allow-lan"` 33 BindAddress string `json:"bind-address"` 34 } 35 36 // Controller 37 type Controller struct { 38 ExternalController string `json:"-"` 39 ExternalUI string `json:"-"` 40 Secret string `json:"-"` 41 } 42 43 // FallbackFilter config 44 type FallbackFilter struct { 45 GeoIP bool `yaml:"geoip"` 46 IPCIDR []*net.IPNet `yaml:"ipcidr"` 47 Domain []string `yaml:"domain"` 48 } 49 50 // Profile config 51 type Profile struct { 52 StoreSelected bool `yaml:"store-selected"` 53 } 54 55 // Experimental config 56 type Experimental struct{} 57 58 // Config is clash config manager 59 type ClashConfig struct { 60 General *General 61 Experimental *Experimental 62 Profile *Profile 63 Proxies []string 64 } 65 66 type RawFallbackFilter struct { 67 GeoIP bool `yaml:"geoip"` 68 IPCIDR []string `yaml:"ipcidr"` 69 Domain []string `yaml:"domain"` 70 } 71 72 type ClashRawConfig struct { 73 Port int `yaml:"port"` 74 SocksPort int `yaml:"socks-port"` 75 RedirPort int `yaml:"redir-port"` 76 TProxyPort int `yaml:"tproxy-port"` 77 MixedPort int `yaml:"mixed-port"` 78 Authentication []string `yaml:"authentication"` 79 AllowLan bool `yaml:"allow-lan"` 80 BindAddress string `yaml:"bind-address"` 81 Mode string `yaml:"mode"` 82 LogLevel string `yaml:"log-level"` 83 NamePrefix string `yaml:"name-prefix"` 84 IPv6 bool `yaml:"ipv6"` 85 ExternalController string `yaml:"external-controller"` 86 ExternalUI string `yaml:"external-ui"` 87 Secret string `yaml:"secret"` 88 Interface string `yaml:"interface-name"` 89 90 ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"` 91 Hosts map[string]string `yaml:"hosts"` 92 Experimental Experimental `yaml:"experimental"` 93 Profile Profile `yaml:"profile"` 94 Proxy []map[string]interface{} `yaml:"proxies"` 95 // ProxyGroup []map[string]interface{} `yaml:"proxy-groups"` 96 // Rule []string `yaml:"rules"` 97 } 98 99 type BaseProxy struct { 100 Name string `yaml:"name"` 101 Server string `yaml:"server"` 102 Port interface{} `yaml:"port"` // int, string 103 Type string `yaml:"type"` 104 } 105 106 func ParseBaseProxy(profile string) (*BaseProxy, error) { 107 idx := strings.IndexByte(profile, byte('{')) 108 idxLast := strings.LastIndexByte(profile, byte('}')) 109 if idx < 0 || idxLast <= idx { 110 // multiple lines form 111 return nil, nil 112 } 113 p := profile[idx:] 114 bp := &BaseProxy{} 115 err := yaml.Unmarshal([]byte(p), bp) 116 if err != nil { 117 return nil, err 118 } 119 return bp, nil 120 } 121 122 // Parse config 123 func ParseClash(buf []byte) (*ClashConfig, error) { 124 rawCfg, err := UnmarshalRawConfig(buf) 125 if err != nil { 126 return nil, err 127 } 128 129 return ParseRawConfig(rawCfg) 130 } 131 132 func UnmarshalRawConfig(buf []byte) (*ClashRawConfig, error) { 133 // config with default value 134 rawCfg := &ClashRawConfig{ 135 AllowLan: false, 136 BindAddress: "*", 137 Mode: "rule", 138 Authentication: []string{}, 139 LogLevel: "info", 140 Hosts: map[string]string{}, 141 // Rule: []string{}, 142 Proxy: []map[string]interface{}{}, 143 // ProxyGroup: []map[string]interface{}{}, 144 Profile: Profile{ 145 StoreSelected: true, 146 }, 147 } 148 149 if err := yaml.Unmarshal(buf, &rawCfg); err != nil { 150 return nil, err 151 } 152 153 return rawCfg, nil 154 } 155 156 func ParseRawConfig(rawCfg *ClashRawConfig) (*ClashConfig, error) { 157 config := &ClashConfig{} 158 159 config.Profile = &rawCfg.Profile 160 161 general, err := parseGeneral(rawCfg) 162 if err != nil { 163 return nil, err 164 } 165 config.General = general 166 167 proxies, err := parseProxies(rawCfg) 168 if err != nil { 169 return nil, err 170 } 171 config.Proxies = proxies 172 173 return config, nil 174 } 175 176 func parseGeneral(cfg *ClashRawConfig) (*General, error) { 177 return &General{ 178 Inbound: Inbound{ 179 Port: cfg.Port, 180 SocksPort: cfg.SocksPort, 181 RedirPort: cfg.RedirPort, 182 TProxyPort: cfg.TProxyPort, 183 MixedPort: cfg.MixedPort, 184 AllowLan: cfg.AllowLan, 185 BindAddress: cfg.BindAddress, 186 }, 187 Controller: Controller{ 188 ExternalController: cfg.ExternalController, 189 ExternalUI: cfg.ExternalUI, 190 Secret: cfg.Secret, 191 }, 192 Mode: cfg.Mode, 193 LogLevel: cfg.LogLevel, 194 IPv6: cfg.IPv6, 195 Interface: cfg.Interface, 196 }, nil 197 } 198 199 func parseProxies(cfg *ClashRawConfig) ([]string, error) { 200 201 proxyList := []string{} 202 // parse proxy 203 for idx, mapping := range cfg.Proxy { 204 link, err := ParseProxy(mapping, cfg.NamePrefix) 205 if err != nil { 206 log.Printf("parseProxies %d: %s", idx, err.Error()) 207 continue 208 } 209 proxyList = append(proxyList, link) 210 } 211 return proxyList, nil 212 } 213 214 func hostWithDefaultPort(host string, defPort string) (string, error) { 215 if !strings.Contains(host, ":") { 216 host += ":" 217 } 218 219 hostname, port, err := net.SplitHostPort(host) 220 if err != nil { 221 return "", err 222 } 223 224 if port == "" { 225 port = defPort 226 } 227 228 return net.JoinHostPort(hostname, port), nil 229 } 230 231 func parseNameServer(servers []string) ([]dns.NameServer, error) { 232 nameservers := []dns.NameServer{} 233 234 for idx, server := range servers { 235 // parse without scheme .e.g 8.8.8.8:53 236 if !strings.Contains(server, "://") { 237 server = "udp://" + server 238 } 239 u, err := url.Parse(server) 240 if err != nil { 241 return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) 242 } 243 244 var addr, dnsNetType string 245 switch u.Scheme { 246 case "udp": 247 addr, err = hostWithDefaultPort(u.Host, "53") 248 dnsNetType = "" // UDP 249 case "tcp": 250 addr, err = hostWithDefaultPort(u.Host, "53") 251 dnsNetType = "tcp" // TCP 252 case "tls": 253 addr, err = hostWithDefaultPort(u.Host, "853") 254 dnsNetType = "tcp-tls" // DNS over TLS 255 case "https": 256 clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path} 257 addr = clearURL.String() 258 dnsNetType = "https" // DNS over HTTPS 259 default: 260 return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) 261 } 262 263 if err != nil { 264 return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) 265 } 266 267 nameservers = append( 268 nameservers, 269 dns.NameServer{ 270 Net: dnsNetType, 271 Addr: addr, 272 }, 273 ) 274 } 275 return nameservers, nil 276 } 277 278 func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { 279 ipNets := []*net.IPNet{} 280 281 for idx, ip := range ips { 282 _, ipnet, err := net.ParseCIDR(ip) 283 if err != nil { 284 return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error()) 285 } 286 ipNets = append(ipNets, ipnet) 287 } 288 289 return ipNets, nil 290 }