1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package routing
import (
"fmt"
"strings"
)
// Region codes for routing.region: the country whose sites and networks go
// directly, like the "region" of v2ray clients such as Hiddify. NoRegion
// keeps only private networks direct.
const (
RegionIran = "ir"
RegionChina = "cn"
RegionRussia = "ru"
RegionAfghanistan = "af"
RegionTurkey = "tr"
RegionIndonesia = "id"
RegionBrazil = "br"
NoRegion = ""
)
// privateRule keeps local and private networks off the proxy in every
// region.
const privateRule = "geoip:private"
// regionRules are the preset Direct rules of each region. The default lists
// (Chocolate4U/Iran-v2ray-rules) carry every tag named here; a region
// without a geosite tag in them uses its country-code top-level domain.
var regionRules = map[string][]string{
RegionIran: {"geosite:ir", "ext:" + IranFile + ":ir", "geoip:ir"},
RegionChina: {"geosite:cn", "geoip:cn"},
RegionRussia: {"geosite:category-ru", "geosite:tld-ru", "geoip:ru"},
RegionAfghanistan: {"domain:af", "geoip:af"},
RegionTurkey: {"domain:tr", "geoip:tr"},
RegionIndonesia: {"domain:id", "geoip:id"},
RegionBrazil: {"domain:br", "geoip:br"},
NoRegion: {},
}
// IranFile is bootmortis/iran-hosted-domains' list of sites hosted in
// Iran, more of them than geosite:ir (69,511 names against 57,554 in
// October 2026), in v2ray's geosite format with the tag ir.
const IranFile = "iran.dat"
// IranFileURL is where IranFile comes from: its latest release.
const IranFileURL = "https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat"
// ExtraFile is another geosite file that rules name (ext:FILE:TAG).
type ExtraFile struct{ Name, URL string }
// knownFiles are the other geosite files this program can download.
var knownFiles = []ExtraFile{{IranFile, IranFileURL}}
// FilesFor lists the other geosite files that rules name and this program
// can download, to download with the two lists.
func FilesFor(rules []string) []ExtraFile {
var out []ExtraFile
for _, f := range knownFiles {
prefix := "ext:" + strings.ToLower(f.Name) + ":"
for _, r := range rules {
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(r)), prefix) {
out = append(out, f)
break
}
}
}
return out
}
// Regions lists the valid region codes, NoRegion last.
var Regions = []string{RegionIran, RegionChina, RegionRussia, RegionAfghanistan, RegionTurkey, RegionIndonesia, RegionBrazil, NoRegion}
// ValidateRegion reports whether region is a known region code.
func ValidateRegion(region string) error {
if _, ok := regionRules[region]; ok {
return nil
}
return fmt.Errorf("unknown region %q (use one of ir, cn, ru, af, tr, id, br, or \"\" for none)", region)
}
// RegionRules returns the preset Direct rules of region, private networks
// included; an unknown region gets only the private networks.
func RegionRules(region string) []string {
return append(append([]string{}, regionRules[region]...), privateRule)
}
// WithRegionRules returns region's preset rules followed by direct,
// without repeating a rule the presets have. The presets live in the
// Direct list itself, where the user can change or remove them.
func WithRegionRules(region string, direct []string) []string {
out := RegionRules(region)
seen := make(map[string]bool, len(out))
for _, r := range out {
seen[r] = true
}
for _, r := range direct {
key := strings.ToLower(strings.TrimSpace(r))
if seen[key] {
continue
}
seen[key] = true
out = append(out, r)
}
return out
}
// SwapRegionRules replaces from's preset rules in direct with to's (a
// region change): the user's own rules stay, after the new presets.
func SwapRegionRules(from, to string, direct []string) []string {
old := make(map[string]bool)
for _, r := range RegionRules(from) {
old[r] = true
}
var rest []string
for _, r := range direct {
if !old[strings.ToLower(strings.TrimSpace(r))] {
rest = append(rest, r)
}
}
return WithRegionRules(to, rest)
}