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  }