diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e1b98fe8..eca8ff32 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,7 +1,7 @@ -如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2ray/discussion/issues)进行讨论。 +如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2fly/discussion/issues)进行讨论。 此 Issue 会被立即关闭。 -If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2ray/discussion/issues) first. +If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2fly/discussion/issues) first. This issue will be closed immediately. diff --git a/.github/ISSUE_TEMPLATE/bug_cn.md b/.github/ISSUE_TEMPLATE/bug_cn.md index 410920fa..326f1d60 100644 --- a/.github/ISSUE_TEMPLATE/bug_cn.md +++ b/.github/ISSUE_TEMPLATE/bug_cn.md @@ -1,11 +1,10 @@ --- -name: V2Ray程序问题 +name: V2Ray 程序问题 about: "提交一个 V2Ray 的程序问题报告。" --- -提交 Issue 之前请先阅读 [Issue 指引](https://github.com/v2ray/v2ray-core/blob/master/.github/SUPPORT.md),然后回答下面的问题,谢谢。 除非特殊情况,请完整填写所有问题。不按模板发的 issue 将直接被关闭。 -如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2ray/discussion/issues)进行讨论。 +如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2fly/discussion/issues)进行讨论。 1) 你正在使用哪个版本的 V2Ray?(如果服务器和客户端使用了不同版本,请注明) diff --git a/.github/ISSUE_TEMPLATE/bug_en.md b/.github/ISSUE_TEMPLATE/bug_en.md index e5b0b4bf..1f556ac6 100644 --- a/.github/ISSUE_TEMPLATE/bug_en.md +++ b/.github/ISSUE_TEMPLATE/bug_en.md @@ -3,9 +3,8 @@ name: Bug report about: "Create a bug report to help us improve" --- -Please read the [instruction](https://github.com/v2ray/v2ray-core/blob/master/.github/SUPPORT.md) and answer the following questions before submitting your issue. Thank you. Please answer all the questions with enough information. All issues not following this template will be closed immediately. -If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2ray/discussion/issues) first. +If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2fly/discussion/issues) first. 1) What version of V2Ray are you using (If you deploy different version on server and client, please explicitly point out)? diff --git a/.github/ISSUE_TEMPLATE/crash_en.md b/.github/ISSUE_TEMPLATE/crash_en.md deleted file mode 100644 index 8c2b7f96..00000000 --- a/.github/ISSUE_TEMPLATE/crash_en.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Crash report -about: "Create a report for panics" ---- - -Please answer all the questions with enough information. All issues not following this template will be closed immediately. -If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2ray/discussion/issues) first. - -1) What version of V2Ray are you using (If you deploy different version on server and client, please explicitly point out)? - -2) What's your scenario of using V2Ray? E.g., Watching YouTube videos in Chrome via Socks/VMess proxy. - -3) Please attach full panic log. - -You may get panic log using command `journalctl -u v2ray` if your system is Linux (systemd). - -4) Please attach your configuration file (**Mask IP addresses before submit this issue**). - -```javascript - // Please attach your configuration here. -``` - -Please review your issue before submitting. diff --git a/.github/ISSUE_TEMPLATE/feature_en.md b/.github/ISSUE_TEMPLATE/feature_en.md deleted file mode 100644 index dcb635e2..00000000 --- a/.github/ISSUE_TEMPLATE/feature_en.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -name: Feature Request -about: "Open a feature request to V2Ray" ---- - -Please describe the new feature you want in detail. diff --git a/.github/ISSUE_TEMPLATE/other_en.md b/.github/ISSUE_TEMPLATE/other_en.md index 9220a5fc..ee9653b1 100644 --- a/.github/ISSUE_TEMPLATE/other_en.md +++ b/.github/ISSUE_TEMPLATE/other_en.md @@ -1,12 +1,12 @@ --- name: Other -about: "其它问题请使用 https://github.com/v2ray/discussion/issues 进行讨论 / Please discuss other issues at https://github.com/v2ray/discussion/issues" +about: "其它问题请使用 https://github.com/v2fly/discussion/issues 进行讨论 / Please discuss other issues at https://github.com/v2fly/discussion/issues" --- -如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2ray/discussion/issues)进行讨论。 +如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2fly/discussion/issues)进行讨论。 此 Issue 会被立即关闭。 -If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2ray/discussion/issues) first. +If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2fly/discussion/issues) first. This issue will be closed immediately. diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md deleted file mode 100644 index 36212225..00000000 --- a/.github/SUPPORT.md +++ /dev/null @@ -1,63 +0,0 @@ -# V2Ray 用户支持 (User Support) - -**English reader please skip to the [English section](#way-to-get-support) below** - -## 获得帮助信息的途径 - -您可以从以下渠道获取帮助: - -1. 官方网站:[v2ray.com](https://www.v2ray.com) -1. Github:[Issues](https://github.com/v2ray/v2ray-core/issues) -1. Telegram:[主群](https://t.me/projectv2ray) - -## Github Issue 规则 - -1. 请按模板填写 issue; -1. 配置文件内容使用格式化代码段进行修饰(见下面的解释); -1. 在提交 issue 前尝试减化配置文件,比如删除不必要 inbound / outbound 模块; -1. 在提交 issue 前尝试确定问题所在,比如将 socks 代理换成 http 再次观察问题是否能重现; -1. 配置文件必须结构完整,即除了必要的隐私信息之外,配置文件可以直接拿来运行。 - -**不按模板填写的 issue 将直接被关闭** - -## 格式化代码段 - -在配置文件上下加入 Markdown 特定的修饰符,如下: - -\`\`\`javascript - -{ - // 配置文件内容 -} - -\`\`\` - -## Way to Get Support - -You may get help in the following ways: - -1. Office Site: [v2ray.com](https://www.v2ray.com) -1. Github: [Issues](https://github.com/v2ray/v2ray-core/issues) -1. Telegram: [Main Group](https://t.me/projectv2ray) - -## Github Issue Rules - -1. Please fill in the issue template. -1. Decorate config file with Markdown formatter (See below). -1. Try to simplify config file before submitting the issue, such as removing unnecessary inbound / outbound blocks. -1. Try to determine the cause of the issue, for example, replacing socks inbound with http inbound to see if the issue still exists. -1. Config file must be structurally complete. - -**Any issue not following the issue template will be closed immediately.** - -## Code formatter - -Add the following Markdown decorator to config file content: - -\`\`\`javascript - -{ - // config file -} - -\`\`\` diff --git a/LICENSE b/LICENSE index 32acc7a4..8bdd927f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2019 V2Ray +Copyright (c) 2015-2020 V2Ray Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/app/dns/hosts.go b/app/dns/hosts.go index b4d18ff1..f47a32fe 100644 --- a/app/dns/hosts.go +++ b/app/dns/hosts.go @@ -106,11 +106,14 @@ func filterIP(ips []net.Address, option IPOption) []net.Address { // LookupIP returns IP address for the given domain, if exists in this StaticHosts. func (h *StaticHosts) LookupIP(domain string, option IPOption) []net.Address { - id := h.matchers.Match(domain) - if id == 0 { + indices := h.matchers.Match(domain) + if len(indices) == 0 { return nil } - ips := h.ips[id] + ips := []net.Address{} + for _, id := range indices { + ips = append(ips, h.ips[id]...) + } if len(ips) == 1 && ips[0].Family().IsDomain() { return ips } diff --git a/app/dns/server.go b/app/dns/server.go index 6598008f..551af9ce 100644 --- a/app/dns/server.go +++ b/app/dns/server.go @@ -86,10 +86,18 @@ func New(ctx context.Context, config *Config) (*Server, error) { } server.hosts = hosts - addNameServer := func(endpoint *net.Endpoint) int { + addNameServer := func(ns *NameServer) int { + endpoint := ns.Address address := endpoint.Address.AsAddress() if address.Family().IsDomain() && address.Domain() == "localhost" { server.clients = append(server.clients, NewLocalNameServer()) + if len(ns.PrioritizedDomain) == 0 { // Priotize local domain with .local domain or without any dot to local DNS + ns.PrioritizedDomain = []*NameServer_PriorityDomain{ + {Type: DomainMatchingType_Regex, Domain: "^[^.]*$"}, // This will only match domain without any dot + {Type: DomainMatchingType_Subdomain, Domain: "local"}, + {Type: DomainMatchingType_Subdomain, Domain: "localdomain"}, + } + } } else if address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https+local://") { // URI schemed string treated as domain // DOH Local mode @@ -137,7 +145,7 @@ func New(ctx context.Context, config *Config) (*Server, error) { if len(config.NameServers) > 0 { features.PrintDeprecatedFeatureWarning("simple DNS server") for _, destPB := range config.NameServers { - addNameServer(destPB) + addNameServer(&NameServer{Address: destPB}) } } @@ -148,7 +156,7 @@ func New(ctx context.Context, config *Config) (*Server, error) { var geoIPMatcherContainer router.GeoIPMatcherContainer for _, ns := range config.NameServer { - idx := addNameServer(ns.Address) + idx := addNameServer(ns) for _, domain := range ns.PrioritizedDomain { matcher, err := toStrMatcher(domain.Type, domain.Domain) @@ -307,11 +315,6 @@ func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, err domain = domain[:len(domain)-1] } - // skip domain without any dot - if !strings.Contains(domain, ".") { - return nil, newError("invalid domain name").AtWarning() - } - ips := s.lookupStatic(domain, option, 0) if ips != nil && ips[0].Family().IsIP() { newError("returning ", len(ips), " IPs for domain ", domain).WriteToLog() @@ -327,8 +330,8 @@ func (s *Server) lookupIPInternal(domain string, option IPOption) ([]net.IP, err var lastErr error var matchedClient Client if s.domainMatcher != nil { - idx := s.domainMatcher.Match(domain) - if idx > 0 { + indices := s.domainMatcher.Match(domain) + for _, idx := range indices { matchedClient = s.clients[s.domainIndexMap[idx]] ips, err := s.queryIPTimeout(s.domainIndexMap[idx], matchedClient, domain, option) if len(ips) > 0 { diff --git a/app/dns/server_test.go b/app/dns/server_test.go index 05617411..de3f8caf 100644 --- a/app/dns/server_test.go +++ b/app/dns/server_test.go @@ -50,6 +50,12 @@ func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { rr, _ := dns.NewRR("google.com. IN A 8.8.4.4") ans.Answer = append(ans.Answer, rr) } + } else if q.Name == "api.google.com." && q.Qtype == dns.TypeA { + rr, _ := dns.NewRR("api.google.com. IN A 8.8.7.7") + ans.Answer = append(ans.Answer, rr) + } else if q.Name == "v2.api.google.com." && q.Qtype == dns.TypeA { + rr, _ := dns.NewRR("v2.api.google.com. IN A 8.8.7.8") + ans.Answer = append(ans.Answer, rr) } else if q.Name == "facebook.com." && q.Qtype == dns.TypeA { rr, _ := dns.NewRR("facebook.com. IN A 9.9.9.9") ans.Answer = append(ans.Answer, rr) @@ -63,6 +69,27 @@ func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { ans.Answer = append(ans.Answer, rr) } else if q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA { ans.MsgHdr.Rcode = dns.RcodeNameError + } else if q.Name == "hostname." && q.Qtype == dns.TypeA { + rr, _ := dns.NewRR("hostname. IN A 127.0.0.1") + ans.Answer = append(ans.Answer, rr) + } else if q.Name == "hostname.local." && q.Qtype == dns.TypeA { + rr, _ := dns.NewRR("hostname.local. IN A 127.0.0.1") + ans.Answer = append(ans.Answer, rr) + } else if q.Name == "hostname.localdomain." && q.Qtype == dns.TypeA { + rr, _ := dns.NewRR("hostname.localdomain. IN A 127.0.0.1") + ans.Answer = append(ans.Answer, rr) + } else if q.Name == "localhost." && q.Qtype == dns.TypeA { + rr, _ := dns.NewRR("localhost. IN A 127.0.0.2") + ans.Answer = append(ans.Answer, rr) + } else if q.Name == "localhost-a." && q.Qtype == dns.TypeA { + rr, _ := dns.NewRR("localhost-a. IN A 127.0.0.3") + ans.Answer = append(ans.Answer, rr) + } else if q.Name == "localhost-b." && q.Qtype == dns.TypeA { + rr, _ := dns.NewRR("localhost-b. IN A 127.0.0.4") + ans.Answer = append(ans.Answer, rr) + } else if q.Name == "Mijia\\ Cloud." && q.Qtype == dns.TypeA { + rr, _ := dns.NewRR("Mijia\\ Cloud. IN A 127.0.0.1") + ans.Answer = append(ans.Answer, rr) } } w.WriteMsg(ans) @@ -537,3 +564,395 @@ func TestIPMatch(t *testing.T) { t.Error("DNS query doesn't finish in 2 seconds.") } } + +func TestLocalDomain(t *testing.T) { + port := udp.PickPort() + + dnsServer := dns.Server{ + Addr: "127.0.0.1:" + port.String(), + Net: "udp", + Handler: &staticHandler{}, + UDPSize: 1200, + } + + go dnsServer.ListenAndServe() + time.Sleep(time.Second) + + config := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&Config{ + NameServers: []*net.Endpoint{ + { + Network: net.Network_UDP, + Address: &net.IPOrDomain{ + Address: &net.IPOrDomain_Ip{ + Ip: []byte{127, 0, 0, 1}, + }, + }, + Port: 9999, /* unreachable */ + }, + }, + NameServer: []*NameServer{ + { + Address: &net.Endpoint{ + Network: net.Network_UDP, + Address: &net.IPOrDomain{ + Address: &net.IPOrDomain_Ip{ + Ip: []byte{127, 0, 0, 1}, + }, + }, + Port: uint32(port), + }, + PrioritizedDomain: []*NameServer_PriorityDomain{ + // Equivalent of dotless:localhost + {Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"}, + }, + Geoip: []*router.GeoIP{ + { // Will match localhost, localhost-a and localhost-b, + CountryCode: "local", + Cidr: []*router.CIDR{ + {Ip: []byte{127, 0, 0, 2}, Prefix: 32}, + {Ip: []byte{127, 0, 0, 3}, Prefix: 32}, + {Ip: []byte{127, 0, 0, 4}, Prefix: 32}, + }, + }, + }, + }, + { + Address: &net.Endpoint{ + Network: net.Network_UDP, + Address: &net.IPOrDomain{ + Address: &net.IPOrDomain_Ip{ + Ip: []byte{127, 0, 0, 1}, + }, + }, + Port: uint32(port), + }, + PrioritizedDomain: []*NameServer_PriorityDomain{ + // Equivalent of dotless: and domain:local + {Type: DomainMatchingType_Regex, Domain: "^[^.]*$"}, + {Type: DomainMatchingType_Subdomain, Domain: "local"}, + {Type: DomainMatchingType_Subdomain, Domain: "localdomain"}, + }, + }, + }, + StaticHosts: []*Config_HostMapping{ + { + Type: DomainMatchingType_Full, + Domain: "hostnamestatic", + Ip: [][]byte{{127, 0, 0, 53}}, + }, + { + Type: DomainMatchingType_Full, + Domain: "hostnamealias", + ProxiedDomain: "hostname.localdomain", + }, + }, + }), + serial.ToTypedMessage(&dispatcher.Config{}), + serial.ToTypedMessage(&proxyman.OutboundConfig{}), + serial.ToTypedMessage(&policy.Config{}), + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + v, err := core.New(config) + common.Must(err) + + client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) + + startTime := time.Now() + + { // Will match dotless: + ips, err := client.LookupIP("hostname") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { + t.Fatal(r) + } + } + + { // Will match domain:local + ips, err := client.LookupIP("hostname.local") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { + t.Fatal(r) + } + } + + { // Will match static ip + ips, err := client.LookupIP("hostnamestatic") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 53}}); r != "" { + t.Fatal(r) + } + } + + { // Will match domain replacing + ips, err := client.LookupIP("hostnamealias") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { + t.Fatal(r) + } + } + + { // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless: + ips, err := client.LookupIP("localhost") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 2}}); r != "" { + t.Fatal(r) + } + } + + { // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 + ips, err := client.LookupIP("localhost-a") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 3}}); r != "" { + t.Fatal(r) + } + } + + { // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 + ips, err := client.LookupIP("localhost-b") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 4}}); r != "" { + t.Fatal(r) + } + } + + { // Will match dotless: + ips, err := client.LookupIP("Mijia Cloud") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{127, 0, 0, 1}}); r != "" { + t.Fatal(r) + } + } + + endTime := time.Now() + if startTime.After(endTime.Add(time.Second * 2)) { + t.Error("DNS query doesn't finish in 2 seconds.") + } +} + +func TestMultiMatchPrioritizedDomain(t *testing.T) { + port := udp.PickPort() + + dnsServer := dns.Server{ + Addr: "127.0.0.1:" + port.String(), + Net: "udp", + Handler: &staticHandler{}, + UDPSize: 1200, + } + + go dnsServer.ListenAndServe() + time.Sleep(time.Second) + + config := &core.Config{ + App: []*serial.TypedMessage{ + serial.ToTypedMessage(&Config{ + NameServers: []*net.Endpoint{ + { + Network: net.Network_UDP, + Address: &net.IPOrDomain{ + Address: &net.IPOrDomain_Ip{ + Ip: []byte{127, 0, 0, 1}, + }, + }, + Port: 9999, /* unreachable */ + }, + }, + NameServer: []*NameServer{ + { + Address: &net.Endpoint{ + Network: net.Network_UDP, + Address: &net.IPOrDomain{ + Address: &net.IPOrDomain_Ip{ + Ip: []byte{127, 0, 0, 1}, + }, + }, + Port: uint32(port), + }, + PrioritizedDomain: []*NameServer_PriorityDomain{ + { + Type: DomainMatchingType_Subdomain, + Domain: "google.com", + }, + }, + Geoip: []*router.GeoIP{ + { // Will only match 8.8.8.8 and 8.8.4.4 + Cidr: []*router.CIDR{ + {Ip: []byte{8, 8, 8, 8}, Prefix: 32}, + {Ip: []byte{8, 8, 4, 4}, Prefix: 32}, + }, + }, + }, + }, + { + Address: &net.Endpoint{ + Network: net.Network_UDP, + Address: &net.IPOrDomain{ + Address: &net.IPOrDomain_Ip{ + Ip: []byte{127, 0, 0, 1}, + }, + }, + Port: uint32(port), + }, + PrioritizedDomain: []*NameServer_PriorityDomain{ + { + Type: DomainMatchingType_Subdomain, + Domain: "google.com", + }, + }, + Geoip: []*router.GeoIP{ + { // Will match 8.8.8.8 and 8.8.8.7, etc + Cidr: []*router.CIDR{ + {Ip: []byte{8, 8, 8, 7}, Prefix: 24}, + }, + }, + }, + }, + { + Address: &net.Endpoint{ + Network: net.Network_UDP, + Address: &net.IPOrDomain{ + Address: &net.IPOrDomain_Ip{ + Ip: []byte{127, 0, 0, 1}, + }, + }, + Port: uint32(port), + }, + PrioritizedDomain: []*NameServer_PriorityDomain{ + { + Type: DomainMatchingType_Subdomain, + Domain: "api.google.com", + }, + }, + Geoip: []*router.GeoIP{ + { // Will only match 8.8.7.7 (api.google.com) + Cidr: []*router.CIDR{ + {Ip: []byte{8, 8, 7, 7}, Prefix: 32}, + }, + }, + }, + }, + { + Address: &net.Endpoint{ + Network: net.Network_UDP, + Address: &net.IPOrDomain{ + Address: &net.IPOrDomain_Ip{ + Ip: []byte{127, 0, 0, 1}, + }, + }, + Port: uint32(port), + }, + PrioritizedDomain: []*NameServer_PriorityDomain{ + { + Type: DomainMatchingType_Full, + Domain: "v2.api.google.com", + }, + }, + Geoip: []*router.GeoIP{ + { // Will only match 8.8.7.8 (v2.api.google.com) + Cidr: []*router.CIDR{ + {Ip: []byte{8, 8, 7, 8}, Prefix: 32}, + }, + }, + }, + }, + }, + }), + serial.ToTypedMessage(&dispatcher.Config{}), + serial.ToTypedMessage(&proxyman.OutboundConfig{}), + serial.ToTypedMessage(&policy.Config{}), + }, + Outbound: []*core.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + v, err := core.New(config) + common.Must(err) + + client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) + + startTime := time.Now() + + { // Will match server 1,2 and server 1 returns expected ip + ips, err := client.LookupIP("google.com") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 8}}); r != "" { + t.Fatal(r) + } + } + + { // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one + clientv4 := client.(feature_dns.IPv4Lookup) + ips, err := clientv4.LookupIPv4("ipv6.google.com") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{8, 8, 8, 7}}); r != "" { + t.Fatal(r) + } + } + + { // Will match server 3,1,2 and server 3 returns expected one + ips, err := client.LookupIP("api.google.com") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{8, 8, 7, 7}}); r != "" { + t.Fatal(r) + } + } + + { // Will match server 4,3,1,2 and server 4 returns expected one + ips, err := client.LookupIP("v2.api.google.com") + if err != nil { + t.Fatal("unexpected error: ", err) + } + + if r := cmp.Diff(ips, []net.IP{{8, 8, 7, 8}}); r != "" { + t.Fatal(r) + } + } + + endTime := time.Now() + if startTime.After(endTime.Add(time.Second * 2)) { + t.Error("DNS query doesn't finish in 2 seconds.") + } +} diff --git a/app/dns/udpns.go b/app/dns/udpns.go index f1f60aca..a362c1df 100644 --- a/app/dns/udpns.go +++ b/app/dns/udpns.go @@ -134,7 +134,7 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot } elapsed := time.Since(req.start) - newError(s.name, " got answere: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog() + newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog() if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) { s.updateIP(req.domain, rec) } diff --git a/app/router/condition.go b/app/router/condition.go index 209a60a3..ffafdb3c 100644 --- a/app/router/condition.go +++ b/app/router/condition.go @@ -82,7 +82,7 @@ func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) { } func (m *DomainMatcher) ApplyDomain(domain string) bool { - return m.matchers.Match(domain) > 0 + return len(m.matchers.Match(domain)) > 0 } func (m *DomainMatcher) Apply(ctx *Context) bool { @@ -154,20 +154,32 @@ func (m *MultiGeoIPMatcher) Apply(ctx *Context) bool { } type PortMatcher struct { - port net.MemoryPortList + port net.MemoryPortList + onSource bool } -func NewPortMatcher(list *net.PortList) *PortMatcher { +// NewPortMatcher create a new port matcher that can match source or destination port +func NewPortMatcher(list *net.PortList, onSource bool) *PortMatcher { return &PortMatcher{ - port: net.PortListFromProto(list), + port: net.PortListFromProto(list), + onSource: onSource, } } func (v *PortMatcher) Apply(ctx *Context) bool { - if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() { - return false + var port net.Port + if v.onSource { + if ctx.Inbound == nil || !ctx.Inbound.Source.IsValid() { + return false + } + port = ctx.Inbound.Source.Port + } else { + if ctx.Outbound == nil || !ctx.Outbound.Target.IsValid() { + return false + } + port = ctx.Outbound.Target.Port } - return v.port.Contains(ctx.Outbound.Target.Port) + return v.port.Contains(port) } type NetworkMatcher struct { diff --git a/app/router/condition_test.go b/app/router/condition_test.go index 5655d743..20ead4ba 100644 --- a/app/router/condition_test.go +++ b/app/router/condition_test.go @@ -264,6 +264,34 @@ func TestRoutingRule(t *testing.T) { }, }, }, + { + rule: &RoutingRule{ + SourcePortList: &net.PortList{ + Range: []*net.PortRange{ + {From: 123, To: 123}, + {From: 9993, To: 9999}, + }, + }, + }, + test: []ruleTest{ + { + input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 123)}), + output: true, + }, + { + input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9999)}), + output: true, + }, + { + input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9994)}), + output: true, + }, + { + input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 53)}), + output: false, + }, + }, + }, { rule: &RoutingRule{ Protocol: []string{"http"}, diff --git a/app/router/config.go b/app/router/config.go index 585bd160..932fcd85 100644 --- a/app/router/config.go +++ b/app/router/config.go @@ -83,9 +83,13 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) { } if rr.PortList != nil { - conds.Add(NewPortMatcher(rr.PortList)) + conds.Add(NewPortMatcher(rr.PortList, false)) } else if rr.PortRange != nil { - conds.Add(NewPortMatcher(&net.PortList{Range: []*net.PortRange{rr.PortRange}})) + conds.Add(NewPortMatcher(&net.PortList{Range: []*net.PortRange{rr.PortRange}}, false)) + } + + if rr.SourcePortList != nil { + conds.Add(NewPortMatcher(rr.SourcePortList, true)) } if len(rr.Networks) > 0 { diff --git a/app/router/config.pb.go b/app/router/config.pb.go index 83561bff..44bd8eda 100644 --- a/app/router/config.pb.go +++ b/app/router/config.pb.go @@ -1,3 +1,9 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc (unknown) +// source: v2ray.com/core/app/router/config.proto + package router import ( @@ -501,10 +507,12 @@ type RoutingRule struct { SourceCidr []*CIDR `protobuf:"bytes,6,rep,name=source_cidr,json=sourceCidr,proto3" json:"source_cidr,omitempty"` // List of GeoIPs for source IP address matching. If this entry exists, the source_cidr above will have no effect. SourceGeoip []*GeoIP `protobuf:"bytes,11,rep,name=source_geoip,json=sourceGeoip,proto3" json:"source_geoip,omitempty"` - UserEmail []string `protobuf:"bytes,7,rep,name=user_email,json=userEmail,proto3" json:"user_email,omitempty"` - InboundTag []string `protobuf:"bytes,8,rep,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"` - Protocol []string `protobuf:"bytes,9,rep,name=protocol,proto3" json:"protocol,omitempty"` - Attributes string `protobuf:"bytes,15,opt,name=attributes,proto3" json:"attributes,omitempty"` + // List of ports for source port matching. + SourcePortList *net.PortList `protobuf:"bytes,16,opt,name=source_port_list,json=sourcePortList,proto3" json:"source_port_list,omitempty"` + UserEmail []string `protobuf:"bytes,7,rep,name=user_email,json=userEmail,proto3" json:"user_email,omitempty"` + InboundTag []string `protobuf:"bytes,8,rep,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"` + Protocol []string `protobuf:"bytes,9,rep,name=protocol,proto3" json:"protocol,omitempty"` + Attributes string `protobuf:"bytes,15,opt,name=attributes,proto3" json:"attributes,omitempty"` } func (x *RoutingRule) Reset() { @@ -627,6 +635,13 @@ func (x *RoutingRule) GetSourceGeoip() []*GeoIP { return nil } +func (x *RoutingRule) GetSourcePortList() *net.PortList { + if x != nil { + return x.SourcePortList + } + return nil +} + func (x *RoutingRule) GetUserEmail() []string { if x != nil { return x.UserEmail @@ -934,7 +949,7 @@ var file_v2ray_com_core_app_router_config_proto_rawDesc = []byte{ 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, - 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xff, 0x05, 0x0a, 0x0b, 0x52, 0x6f, 0x75, + 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xca, 0x06, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c, 0x20, @@ -974,44 +989,48 @@ var file_v2ray_com_core_app_router_config_proto_rawDesc = []byte{ 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x65, 0x6f, 0x69, 0x70, - 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, - 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x08, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x0c, 0x0a, 0x0a, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x4e, 0x0a, 0x0d, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, - 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, - 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0xad, 0x02, 0x0a, 0x06, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x55, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, - 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x36, 0x0a, 0x04, - 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, + 0x12, 0x49, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, + 0x6c, 0x69, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x32, 0x72, + 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, + 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0e, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x09, 0x75, 0x73, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x4e, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, + 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0xad, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x55, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, + 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x36, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, + 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, + 0x4b, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, + 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, + 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, + 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, + 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, + 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x3d, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, - 0x72, 0x75, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, - 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, - 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, - 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, - 0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, - 0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, - 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, - 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x3d, 0x0a, 0x19, 0x63, 0x6f, - 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x72, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, - 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x65, 0x72, 0x50, 0x01, 0x5a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x15, 0x56, + 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1062,14 +1081,15 @@ var file_v2ray_com_core_app_router_config_proto_depIdxs = []int32{ 15, // 12: v2ray.core.app.router.RoutingRule.networks:type_name -> v2ray.core.common.net.Network 3, // 13: v2ray.core.app.router.RoutingRule.source_cidr:type_name -> v2ray.core.app.router.CIDR 4, // 14: v2ray.core.app.router.RoutingRule.source_geoip:type_name -> v2ray.core.app.router.GeoIP - 1, // 15: v2ray.core.app.router.Config.domain_strategy:type_name -> v2ray.core.app.router.Config.DomainStrategy - 8, // 16: v2ray.core.app.router.Config.rule:type_name -> v2ray.core.app.router.RoutingRule - 9, // 17: v2ray.core.app.router.Config.balancing_rule:type_name -> v2ray.core.app.router.BalancingRule - 18, // [18:18] is the sub-list for method output_type - 18, // [18:18] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 13, // 15: v2ray.core.app.router.RoutingRule.source_port_list:type_name -> v2ray.core.common.net.PortList + 1, // 16: v2ray.core.app.router.Config.domain_strategy:type_name -> v2ray.core.app.router.Config.DomainStrategy + 8, // 17: v2ray.core.app.router.Config.rule:type_name -> v2ray.core.app.router.RoutingRule + 9, // 18: v2ray.core.app.router.Config.balancing_rule:type_name -> v2ray.core.app.router.BalancingRule + 19, // [19:19] is the sub-list for method output_type + 19, // [19:19] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_v2ray_com_core_app_router_config_proto_init() } diff --git a/app/router/config.proto b/app/router/config.proto index f0d778ae..1c4ff982 100644 --- a/app/router/config.proto +++ b/app/router/config.proto @@ -9,7 +9,7 @@ option java_multiple_files = true; import "v2ray.com/core/common/net/port.proto"; import "v2ray.com/core/common/net/network.proto"; -// Domain for routing decision. +// Domain for routing decision. message Domain { // Type of domain value. enum Type { @@ -77,7 +77,7 @@ message RoutingRule { // Tag of routing balancer. string balancing_tag = 12; } - + // List of domains for target domain matching. repeated Domain domain = 2; @@ -85,7 +85,7 @@ message RoutingRule { // List of CIDRs for target IP address matching. // Deprecated. Use geoip below. repeated CIDR cidr = 3 [deprecated = true]; - + // List of GeoIPs for target IP address matching. If this entry exists, the cidr above will have no effect. // GeoIP fields with the same country code are supposed to contain exactly same content. They will be merged during runtime. // For customized GeoIPs, please leave country code empty. @@ -110,6 +110,9 @@ message RoutingRule { // List of GeoIPs for source IP address matching. If this entry exists, the source_cidr above will have no effect. repeated GeoIP source_geoip = 11; + // List of ports for source port matching. + v2ray.core.common.net.PortList source_port_list = 16; + repeated string user_email = 7; repeated string inbound_tag = 8; repeated string protocol = 9; diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7408e56c..e232c57d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,7 +20,7 @@ steps: - checkout: self - task: GoTool@0 inputs: - version: '1.14.6' + version: '1.14.7' - script: | mkdir triggersrc ls -I "triggersrc" | xargs cp -rf -t triggersrc diff --git a/common/strmatcher/domain_matcher.go b/common/strmatcher/domain_matcher.go index aabbf43e..ae8e65bc 100644 --- a/common/strmatcher/domain_matcher.go +++ b/common/strmatcher/domain_matcher.go @@ -7,8 +7,8 @@ func breakDomain(domain string) []string { } type node struct { - value uint32 - sub map[string]*node + values []uint32 + sub map[string]*node } // DomainMatcherGroup is a IndexMatcher for a large set of Domain matchers. @@ -25,11 +25,6 @@ func (g *DomainMatcherGroup) Add(domain string, value uint32) { current := g.root parts := breakDomain(domain) for i := len(parts) - 1; i >= 0; i-- { - if current.value > 0 { - // if current node is already a match, it is not necessary to match further. - return - } - part := parts[i] if current.sub == nil { current.sub = make(map[string]*node) @@ -42,22 +37,21 @@ func (g *DomainMatcherGroup) Add(domain string, value uint32) { current = next } - current.value = value - current.sub = nil // shortcut sub nodes as current node is a match. + current.values = append(current.values, value) } func (g *DomainMatcherGroup) addMatcher(m domainMatcher, value uint32) { g.Add(string(m), value) } -func (g *DomainMatcherGroup) Match(domain string) uint32 { +func (g *DomainMatcherGroup) Match(domain string) []uint32 { if domain == "" { - return 0 + return nil } current := g.root if current == nil { - return 0 + return nil } nextPart := func(idx int) int { @@ -69,6 +63,7 @@ func (g *DomainMatcherGroup) Match(domain string) uint32 { return -1 } + matches := [][]uint32{} idx := len(domain) for { if idx == -1 || current.sub == nil { @@ -83,6 +78,21 @@ func (g *DomainMatcherGroup) Match(domain string) uint32 { } current = next idx = nidx + if len(current.values) > 0 { + matches = append(matches, current.values) + } + } + switch len(matches) { + case 0: + return nil + case 1: + return matches[0] + default: + result := []uint32{} + for idx := range matches { + // Insert reversely, the subdomain that matches further ranks higher + result = append(result, matches[len(matches)-1-idx]...) + } + return result } - return current.value } diff --git a/common/strmatcher/domain_matcher_test.go b/common/strmatcher/domain_matcher_test.go index a6319740..660594a9 100644 --- a/common/strmatcher/domain_matcher_test.go +++ b/common/strmatcher/domain_matcher_test.go @@ -1,6 +1,7 @@ package strmatcher_test import ( + "reflect" "testing" . "v2ray.com/core/common/strmatcher" @@ -13,48 +14,54 @@ func TestDomainMatcherGroup(t *testing.T) { g.Add("x.a.com", 3) g.Add("a.b.com", 4) g.Add("c.a.b.com", 5) + g.Add("x.y.com", 4) + g.Add("x.y.com", 6) testCases := []struct { Domain string - Result uint32 + Result []uint32 }{ { Domain: "x.v2ray.com", - Result: 1, + Result: []uint32{1}, }, { Domain: "y.com", - Result: 0, + Result: nil, }, { Domain: "a.b.com", - Result: 4, + Result: []uint32{4}, }, - { + { // Matches [c.a.b.com, a.b.com] Domain: "c.a.b.com", - Result: 4, + Result: []uint32{5, 4}, }, { Domain: "c.a..b.com", - Result: 0, + Result: nil, }, { Domain: ".com", - Result: 0, + Result: nil, }, { Domain: "com", - Result: 0, + Result: nil, }, { Domain: "", - Result: 0, + Result: nil, + }, + { + Domain: "x.y.com", + Result: []uint32{4, 6}, }, } for _, testCase := range testCases { r := g.Match(testCase.Domain) - if r != testCase.Result { + if !reflect.DeepEqual(r, testCase.Result) { t.Error("Failed to match domain: ", testCase.Domain, ", expect ", testCase.Result, ", but got ", r) } } @@ -63,7 +70,7 @@ func TestDomainMatcherGroup(t *testing.T) { func TestEmptyDomainMatcherGroup(t *testing.T) { g := new(DomainMatcherGroup) r := g.Match("v2ray.com") - if r != 0 { - t.Error("Expect 0, but ", r) + if len(r) != 0 { + t.Error("Expect [], but ", r) } } diff --git a/common/strmatcher/full_matcher.go b/common/strmatcher/full_matcher.go index fc7e0c33..e00d02aa 100644 --- a/common/strmatcher/full_matcher.go +++ b/common/strmatcher/full_matcher.go @@ -1,24 +1,24 @@ package strmatcher type FullMatcherGroup struct { - matchers map[string]uint32 + matchers map[string][]uint32 } func (g *FullMatcherGroup) Add(domain string, value uint32) { if g.matchers == nil { - g.matchers = make(map[string]uint32) + g.matchers = make(map[string][]uint32) } - g.matchers[domain] = value + g.matchers[domain] = append(g.matchers[domain], value) } func (g *FullMatcherGroup) addMatcher(m fullMatcher, value uint32) { g.Add(string(m), value) } -func (g *FullMatcherGroup) Match(str string) uint32 { +func (g *FullMatcherGroup) Match(str string) []uint32 { if g.matchers == nil { - return 0 + return nil } return g.matchers[str] diff --git a/common/strmatcher/full_matcher_test.go b/common/strmatcher/full_matcher_test.go index a19a60c7..2fe6ee34 100644 --- a/common/strmatcher/full_matcher_test.go +++ b/common/strmatcher/full_matcher_test.go @@ -1,6 +1,7 @@ package strmatcher_test import ( + "reflect" "testing" . "v2ray.com/core/common/strmatcher" @@ -11,24 +12,30 @@ func TestFullMatcherGroup(t *testing.T) { g.Add("v2ray.com", 1) g.Add("google.com", 2) g.Add("x.a.com", 3) + g.Add("x.y.com", 4) + g.Add("x.y.com", 6) testCases := []struct { Domain string - Result uint32 + Result []uint32 }{ { Domain: "v2ray.com", - Result: 1, + Result: []uint32{1}, }, { Domain: "y.com", - Result: 0, + Result: nil, + }, + { + Domain: "x.y.com", + Result: []uint32{4, 6}, }, } for _, testCase := range testCases { r := g.Match(testCase.Domain) - if r != testCase.Result { + if !reflect.DeepEqual(r, testCase.Result) { t.Error("Failed to match domain: ", testCase.Domain, ", expect ", testCase.Result, ", but got ", r) } } @@ -37,7 +44,7 @@ func TestFullMatcherGroup(t *testing.T) { func TestEmptyFullMatcherGroup(t *testing.T) { g := new(FullMatcherGroup) r := g.Match("v2ray.com") - if r != 0 { - t.Error("Expect 0, but ", r) + if len(r) != 0 { + t.Error("Expect [], but ", r) } } diff --git a/common/strmatcher/strmatcher.go b/common/strmatcher/strmatcher.go index fb63eda5..6486d836 100644 --- a/common/strmatcher/strmatcher.go +++ b/common/strmatcher/strmatcher.go @@ -49,7 +49,7 @@ func (t Type) New(pattern string) (Matcher, error) { // IndexMatcher is the interface for matching with a group of matchers. type IndexMatcher interface { // Match returns the the index of a matcher that matches the input. It returns 0 if no such matcher exists. - Match(input string) uint32 + Match(input string) []uint32 } type matcherEntry struct { @@ -87,22 +87,16 @@ func (g *MatcherGroup) Add(m Matcher) uint32 { } // Match implements IndexMatcher.Match. -func (g *MatcherGroup) Match(pattern string) uint32 { - if c := g.fullMatcher.Match(pattern); c > 0 { - return c - } - - if c := g.domainMatcher.Match(pattern); c > 0 { - return c - } - +func (g *MatcherGroup) Match(pattern string) []uint32 { + result := []uint32{} + result = append(result, g.fullMatcher.Match(pattern)...) + result = append(result, g.domainMatcher.Match(pattern)...) for _, e := range g.otherMatchers { if e.m.Match(pattern) { - return e.id + result = append(result, e.id) } } - - return 0 + return result } // Size returns the number of matchers in the MatcherGroup. diff --git a/infra/conf/router.go b/infra/conf/router.go index cf4879d2..77549481 100644 --- a/infra/conf/router.go +++ b/infra/conf/router.go @@ -299,6 +299,16 @@ func parseDomainRule(domain string) ([]*router.Domain, error) { case strings.HasPrefix(domain, "keyword:"): domainRule.Type = router.Domain_Plain domainRule.Value = domain[8:] + case strings.HasPrefix(domain, "dotless:"): + domainRule.Type = router.Domain_Regex + switch substr := domain[8:]; { + case substr == "": + domainRule.Value = "^[^.]*$" + case !strings.Contains(substr, "."): + domainRule.Value = "^[^.]*" + substr + "[^.]*$" + default: + return nil, newError("Substr in dotless rule should not contain a dot: ", substr) + } default: domainRule.Type = router.Domain_Plain domainRule.Value = domain @@ -380,6 +390,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { Port *PortList `json:"port"` Network *NetworkList `json:"network"` SourceIP *StringList `json:"source"` + SourcePort *PortList `json:"sourcePort"` User *StringList `json:"user"` InboundTag *StringList `json:"inboundTag"` Protocols *StringList `json:"protocol"` @@ -438,6 +449,10 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { rule.SourceGeoip = geoipList } + if rawFieldRule.SourcePort != nil { + rule.SourcePortList = rawFieldRule.SourcePort.Build() + } + if rawFieldRule.User != nil { for _, s := range *rawFieldRule.User { rule.UserEmail = append(rule.UserEmail, s) diff --git a/release/install-release.sh b/release/install-release.sh index 272b0a00..ba2ab100 100755 --- a/release/install-release.sh +++ b/release/install-release.sh @@ -9,484 +9,12 @@ # 2: Application error # 3: Network error -# CLI arguments -PROXY='' -HELP='' -FORCE='' -CHECK='' -REMOVE='' -VERSION='' -VSRC_ROOT='/tmp/v2ray' -EXTRACT_ONLY='' -LOCAL='' -LOCAL_INSTALL='' -DIST_SRC='github' -ERROR_IF_UPTODATE='' - -CUR_VER="" -NEW_VER="" -VDIS='' -ZIPFILE="/tmp/v2ray/v2ray.zip" -V2RAY_RUNNING=0 - -CMD_INSTALL="" -CMD_UPDATE="" -SOFTWARE_UPDATED=0 - -SYSTEMCTL_CMD=$(command -v systemctl 2>/dev/null) -SERVICE_CMD=$(command -v service 2>/dev/null) - #######color code######## RED="31m" # Error message -GREEN="32m" # Success message YELLOW="33m" # Warning message -BLUE="36m" # Info message - - -######################### -while [[ $# > 0 ]]; do - case "$1" in - -p|--proxy) - PROXY="-x ${2}" - shift # past argument - ;; - -h|--help) - HELP="1" - ;; - -f|--force) - FORCE="1" - ;; - -c|--check) - CHECK="1" - ;; - --remove) - REMOVE="1" - ;; - --version) - VERSION="$2" - shift - ;; - --extract) - VSRC_ROOT="$2" - shift - ;; - --extractonly) - EXTRACT_ONLY="1" - ;; - -l|--local) - LOCAL="$2" - LOCAL_INSTALL="1" - shift - ;; - --source) - DIST_SRC="$2" - shift - ;; - --errifuptodate) - ERROR_IF_UPTODATE="1" - ;; - *) - # unknown option - ;; - esac - shift # past argument or value -done - -############################### colorEcho(){ echo -e "\033[${1}${@:2}\033[0m" 1>& 2 } -archAffix(){ - case "${1:-"$(uname -m)"}" in - i686|i386) - echo '32' - ;; - x86_64|amd64) - echo '64' - ;; - *armv7*|armv6l) - echo 'arm' - ;; - *armv8*|aarch64) - echo 'arm64' - ;; - *mips64le*) - echo 'mips64le' - ;; - *mips64*) - echo 'mips64' - ;; - *mipsle*) - echo 'mipsle' - ;; - *mips*) - echo 'mips' - ;; - *s390x*) - echo 's390x' - ;; - ppc64le) - echo 'ppc64le' - ;; - ppc64) - echo 'ppc64' - ;; - *) - return 1 - ;; - esac - - return 0 -} - -zipRoot() { - unzip -lqq "$1" | awk -e ' - NR == 1 { - prefix = $4; - } - NR != 1 { - prefix_len = length(prefix); - cur_len = length($4); - - for (len = prefix_len < cur_len ? prefix_len : cur_len; len >= 1; len -= 1) { - sub_prefix = substr(prefix, 1, len); - sub_cur = substr($4, 1, len); - - if (sub_prefix == sub_cur) { - prefix = sub_prefix; - break; - } - } - - if (len == 0) { - prefix = ""; - nextfile; - } - } - END { - print prefix; - } - ' -} - -downloadV2Ray(){ - rm -rf /tmp/v2ray - mkdir -p /tmp/v2ray - if [[ "${DIST_SRC}" == "jsdelivr" ]]; then - DOWNLOAD_LINK="https://cdn.jsdelivr.net/gh/v2ray/dist/v2ray-linux-${VDIS}.zip" - else - DOWNLOAD_LINK="https://github.com/v2fly/v2ray-core/releases/download/${NEW_VER}/v2ray-linux-${VDIS}.zip" - fi - colorEcho ${BLUE} "Downloading V2Ray: ${DOWNLOAD_LINK}" - curl ${PROXY} -L -H "Cache-Control: no-cache" -o ${ZIPFILE} ${DOWNLOAD_LINK} - if [ $? != 0 ];then - colorEcho ${RED} "Failed to download! Please check your network or try again." - return 3 - fi - return 0 -} - -installSoftware(){ - COMPONENT=$1 - if [[ -n `command -v $COMPONENT` ]]; then - return 0 - fi - - getPMT - if [[ $? -eq 1 ]]; then - colorEcho ${RED} "The system package manager tool isn't APT or YUM, please install ${COMPONENT} manually." - return 1 - fi - if [[ $SOFTWARE_UPDATED -eq 0 ]]; then - colorEcho ${BLUE} "Updating software repo" - $CMD_UPDATE - SOFTWARE_UPDATED=1 - fi - - colorEcho ${BLUE} "Installing ${COMPONENT}" - $CMD_INSTALL $COMPONENT - if [[ $? -ne 0 ]]; then - colorEcho ${RED} "Failed to install ${COMPONENT}. Please install it manually." - return 1 - fi - return 0 -} - -# return 1: not apt, yum, or zypper -getPMT(){ - if [[ -n `command -v apt-get` ]];then - CMD_INSTALL="apt-get -y -qq install" - CMD_UPDATE="apt-get -qq update" - elif [[ -n `command -v yum` ]]; then - CMD_INSTALL="yum -y -q install" - CMD_UPDATE="yum -q makecache" - elif [[ -n `command -v zypper` ]]; then - CMD_INSTALL="zypper -y install" - CMD_UPDATE="zypper ref" - else - return 1 - fi - return 0 -} - -normalizeVersion() { - if [ -n "$1" ]; then - case "$1" in - v*) - echo "$1" - ;; - *) - echo "v$1" - ;; - esac - else - echo "" - fi -} - -# 1: new V2Ray. 0: no. 2: not installed. 3: check failed. 4: don't check. -getVersion(){ - if [[ -n "$VERSION" ]]; then - NEW_VER="$(normalizeVersion "$VERSION")" - return 4 - else - VER="$(/usr/bin/v2ray/v2ray -version 2>/dev/null)" - RETVAL=$? - CUR_VER="$(normalizeVersion "$(echo "$VER" | head -n 1 | cut -d " " -f2)")" - TAG_URL="https://api.github.com/repos/v2fly/v2ray-core/releases/latest" - NEW_VER="$(normalizeVersion "$(curl ${PROXY} -H "Accept: application/json" -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:74.0) Gecko/20100101 Firefox/74.0" -s "${TAG_URL}" --connect-timeout 10| grep 'tag_name' | cut -d\" -f4)")" - - if [[ $? -ne 0 ]] || [[ $NEW_VER == "" ]]; then - colorEcho ${RED} "Failed to fetch release information. Please check your network or try again." - return 3 - elif [[ $RETVAL -ne 0 ]];then - return 2 - elif [[ $NEW_VER != $CUR_VER ]];then - return 1 - fi - return 0 - fi -} - -stopV2ray(){ - colorEcho ${BLUE} "Shutting down V2Ray service." - if [[ -n "${SYSTEMCTL_CMD}" ]] || [[ -f "/lib/systemd/system/v2ray.service" ]] || [[ -f "/etc/systemd/system/v2ray.service" ]]; then - ${SYSTEMCTL_CMD} stop v2ray - elif [[ -n "${SERVICE_CMD}" ]] || [[ -f "/etc/init.d/v2ray" ]]; then - ${SERVICE_CMD} v2ray stop - fi - if [[ $? -ne 0 ]]; then - colorEcho ${YELLOW} "Failed to shutdown V2Ray service." - return 2 - fi - return 0 -} - -startV2ray(){ - if [ -n "${SYSTEMCTL_CMD}" ] && [[ -f "/lib/systemd/system/v2ray.service" || -f "/etc/systemd/system/v2ray.service" ]]; then - ${SYSTEMCTL_CMD} start v2ray - elif [ -n "${SERVICE_CMD}" ] && [ -f "/etc/init.d/v2ray" ]; then - ${SERVICE_CMD} v2ray start - fi - if [[ $? -ne 0 ]]; then - colorEcho ${YELLOW} "Failed to start V2Ray service." - return 2 - fi - return 0 -} - -installV2Ray(){ - # Install V2Ray binary to /usr/bin/v2ray - mkdir -p '/etc/v2ray' '/var/log/v2ray' && \ - unzip -oj "$1" "$2v2ray" "$2v2ctl" "$2geoip.dat" "$2geosite.dat" -d '/usr/bin/v2ray' && \ - chmod +x '/usr/bin/v2ray/v2ray' '/usr/bin/v2ray/v2ctl' || { - colorEcho ${RED} "Failed to copy V2Ray binary and resources." - return 1 - } - - # Install V2Ray server config to /etc/v2ray - if [ ! -f '/etc/v2ray/config.json' ]; then - local PORT="$(($RANDOM + 10000))" - local UUID="$(cat '/proc/sys/kernel/random/uuid')" - - unzip -pq "$1" "$2vpoint_vmess_freedom.json" | \ - sed -e "s/10086/${PORT}/g; s/23ad6b10-8d1a-40f7-8ad0-e3e35cd38297/${UUID}/g;" - > \ - '/etc/v2ray/config.json' || { - colorEcho ${YELLOW} "Failed to create V2Ray configuration file. Please create it manually." - return 1 - } - - colorEcho ${BLUE} "PORT:${PORT}" - colorEcho ${BLUE} "UUID:${UUID}" - fi -} - - -installInitScript(){ - if [[ -n "${SYSTEMCTL_CMD}" ]]; then - if [[ ! -f "/etc/systemd/system/v2ray.service" && ! -f "/lib/systemd/system/v2ray.service" ]]; then - unzip -oj "$1" "$2systemd/v2ray.service" -d '/etc/systemd/system' && \ - systemctl enable v2ray.service - fi - elif [[ -n "${SERVICE_CMD}" ]] && [[ ! -f "/etc/init.d/v2ray" ]]; then - installSoftware 'daemon' && \ - unzip -oj "$1" "$2systemv/v2ray" -d '/etc/init.d' && \ - chmod +x '/etc/init.d/v2ray' && \ - update-rc.d v2ray defaults - fi -} - -Help(){ - cat - 1>& 2 << EOF -./install-release.sh [-h] [-c] [--remove] [-p proxy] [-f] [--version vx.y.z] [-l file] - -h, --help Show help - -p, --proxy To download through a proxy server, use -p socks5://127.0.0.1:1080 or -p http://127.0.0.1:3128 etc - -f, --force Force install - --version Install a particular version, use --version v3.15 - -l, --local Install from a local file - --remove Remove installed V2Ray - -c, --check Check for update -EOF -} - -remove(){ - if [[ -n "${SYSTEMCTL_CMD}" ]] && [[ -f "/etc/systemd/system/v2ray.service" ]];then - if pgrep "v2ray" > /dev/null ; then - stopV2ray - fi - systemctl disable v2ray.service - rm -rf "/usr/bin/v2ray" "/etc/systemd/system/v2ray.service" - if [[ $? -ne 0 ]]; then - colorEcho ${RED} "Failed to remove V2Ray." - return 0 - else - colorEcho ${GREEN} "Removed V2Ray successfully." - colorEcho ${BLUE} "If necessary, please remove configuration file and log file manually." - return 0 - fi - elif [[ -n "${SYSTEMCTL_CMD}" ]] && [[ -f "/lib/systemd/system/v2ray.service" ]];then - if pgrep "v2ray" > /dev/null ; then - stopV2ray - fi - systemctl disable v2ray.service - rm -rf "/usr/bin/v2ray" "/lib/systemd/system/v2ray.service" - if [[ $? -ne 0 ]]; then - colorEcho ${RED} "Failed to remove V2Ray." - return 0 - else - colorEcho ${GREEN} "Removed V2Ray successfully." - colorEcho ${BLUE} "If necessary, please remove configuration file and log file manually." - return 0 - fi - elif [[ -n "${SERVICE_CMD}" ]] && [[ -f "/etc/init.d/v2ray" ]]; then - if pgrep "v2ray" > /dev/null ; then - stopV2ray - fi - rm -rf "/usr/bin/v2ray" "/etc/init.d/v2ray" - if [[ $? -ne 0 ]]; then - colorEcho ${RED} "Failed to remove V2Ray." - return 0 - else - colorEcho ${GREEN} "Removed V2Ray successfully." - colorEcho ${BLUE} "If necessary, please remove configuration file and log file manually." - return 0 - fi - else - colorEcho ${YELLOW} "V2Ray not found." - return 0 - fi -} - -checkUpdate(){ - echo "Checking for update." - VERSION="" - getVersion - RETVAL="$?" - if [[ $RETVAL -eq 1 ]]; then - colorEcho ${BLUE} "Found new version ${NEW_VER} for V2Ray.(Current version:$CUR_VER)" - elif [[ $RETVAL -eq 0 ]]; then - colorEcho ${BLUE} "No new version. Current version is ${NEW_VER}." - elif [[ $RETVAL -eq 2 ]]; then - colorEcho ${YELLOW} "No V2Ray installed." - colorEcho ${BLUE} "The newest version for V2Ray is ${NEW_VER}." - fi - return 0 -} - -main(){ - #helping information - [[ "$HELP" == "1" ]] && Help && return - [[ "$CHECK" == "1" ]] && checkUpdate && return - [[ "$REMOVE" == "1" ]] && remove && return - - local ARCH=$(uname -m) - VDIS="$(archAffix)" - - # extract local file - if [[ $LOCAL_INSTALL -eq 1 ]]; then - colorEcho ${YELLOW} "Installing V2Ray via local file. Please make sure the file is a valid V2Ray package, as we are not able to determine that." - NEW_VER=local - rm -rf /tmp/v2ray - ZIPFILE="$LOCAL" - #FILEVDIS=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f4` - #SYSTEM=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f3` - #if [[ ${SYSTEM} != "linux" ]]; then - # colorEcho ${RED} "The local V2Ray can not be installed in linux." - # return 1 - #elif [[ ${FILEVDIS} != ${VDIS} ]]; then - # colorEcho ${RED} "The local V2Ray can not be installed in ${ARCH} system." - # return 1 - #else - # NEW_VER=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f2` - #fi - else - # download via network and extract - installSoftware "curl" || return $? - getVersion - RETVAL="$?" - if [[ $RETVAL == 0 ]] && [[ "$FORCE" != "1" ]]; then - colorEcho ${BLUE} "Latest version ${CUR_VER} is already installed." - if [ -n "${ERROR_IF_UPTODATE}" ]; then - return 10 - fi - return - elif [[ $RETVAL == 3 ]]; then - return 3 - else - colorEcho ${BLUE} "Installing V2Ray ${NEW_VER} on ${ARCH}" - downloadV2Ray || return $? - fi - fi - - local ZIPROOT="$(zipRoot "${ZIPFILE}")" - installSoftware unzip || return $? - - if [ -n "${EXTRACT_ONLY}" ]; then - colorEcho ${BLUE} "Extracting V2Ray package to ${VSRC_ROOT}." - - if unzip -o "${ZIPFILE}" -d ${VSRC_ROOT}; then - colorEcho ${GREEN} "V2Ray extracted to ${VSRC_ROOT%/}${ZIPROOT:+/${ZIPROOT%/}}, and exiting..." - return 0 - else - colorEcho ${RED} "Failed to extract V2Ray." - return 2 - fi - fi - - if pgrep "v2ray" > /dev/null ; then - V2RAY_RUNNING=1 - stopV2ray - fi - installV2Ray "${ZIPFILE}" "${ZIPROOT}" || return $? - installInitScript "${ZIPFILE}" "${ZIPROOT}" || return $? - if [[ ${V2RAY_RUNNING} -eq 1 ]];then - colorEcho ${BLUE} "Restarting V2Ray service." - startV2ray - fi - colorEcho ${GREEN} "V2Ray ${NEW_VER} is installed." - rm -rf /tmp/v2ray - return 0 -} - -main +colorEcho ${RED} "WARN: This script will be obsolete soon, please switch to the fhs-install-v2ray project in time." +colorEcho ${YELLOW} "URL: https://github.com/v2fly/fhs-install-v2ray"