├── .gitignore ├── LICENSE ├── README.md ├── common.go ├── domainlist.go ├── examples ├── kcp-vmess-dynamic-port-utp-masquerade │ ├── v2fly-kcp-vmess-dynamic-port-utp-masquerade.client.json │ └── v2fly-kcp-vmess-dynamic-port-utp-masquerade.server.json ├── kcp-vmess │ ├── v2fly-kcp-vmess.client.json │ └── v2fly-kcp-vmess.server.json ├── quic-chacha20-vless-dynamic-port-utp-masquerade │ ├── v2fly-quic-chacha20-vless-dynamic-port-utp-masquerade.client.json │ └── v2fly-quic-chacha20-vless-dynamic-port-utp-masquerade.server.json ├── quic-vless │ ├── v2fly-quic-vless.client.json │ └── v2fly-quic-vless.server.json ├── quic-vmess │ ├── v2fly-quic-vmess.client.json │ └── v2fly-quic-vmess.server.json └── websocket-tls-vmess │ ├── Caddyfile │ ├── v2fly-websocket-tls-vmess.client.json │ └── v2fly-websocket-tls-vmess.server.json ├── go.mod ├── go.sum ├── main.go └── trie.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.exe~ 3 | *.dll 4 | *.so 5 | *.dylib 6 | *.test 7 | *.out 8 | *.dat 9 | *.iws 10 | *.iml 11 | *.ipr 12 | go.work 13 | vendor/ 14 | Godeps/ 15 | z-i/ 16 | publish/ 17 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Omar Assadi 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AntiZapret-V2Ray 2 | 3 | A utility written in Go to generate a proxy ruleset matching the list of domains blocked 4 | in the Russian Federation for use with V2Ray or XRay to circumvent government censorship 5 | and evade detection. 6 | 7 | ## Zapret 8 | 9 | ***Noun*** 10 | 11 | запре́т • (zaprét) m inan 12 | 13 | Definition: 14 | * prohibition, interdiction, ban 15 | 16 | ## How it works 17 | 18 | The Russian government provides a list of all domains and IP addresses on the blocklist 19 | maintained by The Federal Service for Supervision of Communications, Information 20 | Technology, and Mass Media (Roskomnadzor) to telecommunications providers, like 21 | datacenters, cloud operators, and ISPs. 22 | 23 | The intent behind providing the list is to allow those companies and services to 24 | enforce the blocklist, as the government relies on the individual ISPs to do the 25 | filtering. 26 | 27 | However, this also makes it relatively easy to generate a ruleset to proxy 28 | the banned services and only the banned services by making use of the same list. 29 | 30 | This tool makes use of a dump of the blocklist provided by Zapret-Info, which can be 31 | found below. 32 | 33 | * [GitHub](https://github.com/zapret-info/z-i) 34 | * [Assembla](https://app.assembla.com/spaces/z-i/git/source) 35 | * [SourceForge](https://sourceforge.net/p/zapret-info/code/) 36 | * [I2P](http://7pk2wryitzvsym36hgrsl66fp6fwfqs4ul7bb6abkinn66xi36ta.b32.i2p/zapret-info/) 37 | 38 | ## Usage 39 | 40 | 1. Obtain a CSV dump of the Roskomnadzor list from Zapret-Info, the main `dump.csv` file, 41 | and place it in `z-i/dump.csv` in the root directory of the project 42 | 2. Build the project using `go build` 43 | 3. Run the built executable 44 | 4. Set your proxy rules to use generated `geosite.dat` file located in `publish/` 45 | to route traffic. The default "geosite" country code of the list is `ZAPRETINFO`. 46 | 47 | ### Sample V2Ray configurations 48 | 49 | Included in the [examples/](examples/) directory are a few starter V2Ray starter 50 | configurations. In each example, only government-blocked traffic is proxied; non-blocked 51 | traffic is routed directly over the client's default outbound connection. 52 | 53 | * [VMess over KCP](examples/kcp-vmess) 54 | * [VLESS over QUIC with ChaCha20 and dynamically rotating ports, masquerading as UDP 55 | torrent traffic.](examples/quic-chacha20-vless-dynamic-port-utp-masquerade) 56 | * [VLESS over QUIC](examples/quic-vless) 57 | * [VMess over WebSockets with TLS, using Caddy as a reverse proxy](examples/websocket-tls-vmess) 58 | 59 | *NOTE:* *please replace any security keys and UUIDs with your own locally generated ones!* 60 | 61 | #### Which Configuration To Pick 62 | 63 | Depending on your needs, different setups may be more appropriate. In terms of security, 64 | a traditional VPN setup with more mature, peer-reviewed protocols is likely the safest 65 | option. 66 | 67 | However, while the traffic going through the tunnel is secure when using WireGuard or 68 | OpenVPN, it is also much easier to detect the fact that a VPN is in use. If evasion of 69 | DPI and anti-circumvention technology is required, or a more covert solution is necessary 70 | for some other reason, one of the VMess/VLESS options is the best choice currently. 71 | 72 | ##### VMess vs. VLESS 73 | 74 | VLESS is a newer stateless transport protocol that does not require system time to be 75 | in sync nor the usage of alter ids. However, it does not implement its own encryption. 76 | And thus, it would be inappropriate to VLESS without some sort of layer, like TLS, 77 | sitting on top (e.g., VLESS + KCP *without* TLS would be ill-advised). 78 | 79 | VMess, on the other hand, does depend on the server and client system times to be no more 80 | than 90 seconds out of sync. For clarity, this is timezone independent, i.e., the client 81 | and server may both have distinct non-UTC timezones, but the time difference must be no 82 | more than 90 seconds from each other once UTC offsets have been taken into account. 83 | 84 | A pro and a con of VMess, however, is that it implements its own encryption layer, which 85 | can be either AES-128-GCM or ChaCha20-Poly1305. This adds additional overhead when used 86 | with TLS due to the double encryption, but it also allows VMess to be used without TLS. 87 | 88 | ##### TCP vs. KCP vs. QUIC vs. WebSockets 89 | 90 | [KCP](https://github.com/skywind3000/kcp/blob/master/README.en.md) is a reliable 91 | transport protocol built on-top of UDP with the intention of minimizing latency 92 | at the cost of lower throughput. It is well-supported and more mature than the QUIC 93 | options provided in V2Ray. It is suitable for general use and can be paired with VMess 94 | for encryption and masquerading. TCP may be a better choice if throughput is a bigger 95 | concern than latency. 96 | 97 | The TCP transport layer is the oldest and most mature option in V2Ray, and can be paired 98 | with VMess or VLESS and TLS. 99 | 100 | V2Ray also has a WebSocket transport layer, which can be used with VMess or VLESS and 101 | TLS. However, it will have more overhead than TCP or KCP and thus is generally not 102 | advisable unless there is the need to use a standard webserver like NGINX or Caddy 103 | as a reverse proxy. Another use case for the WebSocket transport is for use behind 104 | WebSocket-enabled CDNs, like CloudFlare. 105 | 106 | QUIC is the least mature transport option out of the ones list thus far, but should 107 | result in better throughput and latency than TCP with TLS. 108 | 109 | When in doubt, use TCP and VLESS with TLS or KCP and VMess, unless CloudFlare is in use, 110 | in which case WebSockets or gRPC and VLESS would be more appropriate. 111 | 112 | ## Acknowledgements 113 | 114 | Special thanks to the following groups and individuals for their efforts. 115 | 116 | * [Zapret-Info](https://github.com/zapret-info/z-i) - Providing regular dumps of the 117 | Russian government blocklist 118 | * [Shadowsocks](https://github.com/shadowsocks/) - Proxy encryption protocol designed to 119 | circumvent China's great firewall 120 | * [Clowwindy](https://github.com/Clowwindy) - Original development of Shadowsocks 121 | * [V2Ray](https://github.com/v2ray) - A platform for building proxies, and the VMess 122 | protocol, designed to bypass anti-circumvention techniques 123 | * [XRay](https://github.com/XTLS) - An alternative v2ray-core with support for XTLS 124 | * [Loyalsoldier](https://github.com/loyalsoldier) - References for generating V2Ray rules -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "strings" 4 | 5 | func isBlank(s string) bool { 6 | return strings.TrimSpace(s) == "" 7 | } 8 | 9 | func isEmpty(s string) bool { 10 | return s == "" 11 | } 12 | 13 | func trimStrings(arr []string) { 14 | for i := range arr { 15 | arr[i] = strings.TrimSpace(arr[i]) 16 | } 17 | } 18 | 19 | func splitAndTrim(s string, d string) (int, []string) { 20 | parts := strings.Split(s, d) 21 | trimStrings(parts) 22 | return len(parts), parts 23 | } 24 | 25 | func splitAfterAndCount(str string, delimiter string) (int, []string) { 26 | parts := strings.SplitAfter(str, delimiter) 27 | return len(parts), parts 28 | } 29 | -------------------------------------------------------------------------------- /domainlist.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "github.com/pkg/errors" 6 | router "github.com/v2fly/v2ray-core/v5/app/router/routercommon" 7 | "golang.org/x/text/encoding" 8 | "os" 9 | "regexp" 10 | "sort" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type DomainList struct { 16 | Name string 17 | FullDomains []*router.Domain 18 | RegexpDomains []*router.Domain 19 | DomainDomains []*router.Domain 20 | UniqueDomainDomains []*router.Domain 21 | GeoSite *router.GeoSite 22 | } 23 | 24 | func (l *DomainList) push(rule *router.Domain) { 25 | switch rule.Type { 26 | case router.Domain_Full: 27 | l.FullDomains = append(l.FullDomains, rule) 28 | case router.Domain_RootDomain: 29 | l.DomainDomains = append(l.DomainDomains, rule) 30 | case router.Domain_Regex: 31 | l.RegexpDomains = append(l.RegexpDomains, rule) 32 | } 33 | } 34 | 35 | func (l *DomainList) parseDomainType(domain string) router.Domain_Type { 36 | if strings.Contains(domain, "*") { 37 | return router.Domain_Regex 38 | } 39 | scheme := regexp.MustCompile("^[a-zA-Z]+://") 40 | if scheme.MatchString(domain) { 41 | return router.Domain_Full 42 | } 43 | return router.Domain_RootDomain 44 | } 45 | 46 | func (l *DomainList) wildcardToRegex(domain string) (string, error) { 47 | _, parts := splitAndTrim(strings.ReplaceAll(strings.ReplaceAll(domain, "https://", ""), "http://", ""), "/") 48 | _, parts = splitAfterAndCount(parts[0], "*.") 49 | var sb strings.Builder 50 | for _, column := range parts { 51 | if column == "*." { 52 | sb.WriteString("(.*\\.)?") 53 | continue 54 | } 55 | sb.WriteString(regexp.QuoteMeta(column)) 56 | } 57 | return sb.String(), nil 58 | } 59 | 60 | func (l *DomainList) parseDomain(domain string) (*router.Domain, error) { 61 | if isBlank(domain) { 62 | return nil, nil 63 | } 64 | domainType := l.parseDomainType(domain) 65 | var sb strings.Builder 66 | switch domainType { 67 | case router.Domain_Regex: 68 | parsed, err := l.wildcardToRegex(domain) 69 | if err != nil { 70 | return nil, err 71 | } 72 | if isBlank(parsed) { 73 | return nil, nil 74 | } 75 | sb.WriteString(parsed) 76 | case router.Domain_Full: 77 | sb.WriteString(domain) 78 | case router.Domain_RootDomain: 79 | sb.WriteString(domain) 80 | default: 81 | return nil, errors.New("unexpected domain type: " + domainType.String()) 82 | } 83 | domain = sb.String() 84 | var routerDomain router.Domain 85 | routerDomain.Type = domainType 86 | routerDomain.Value = domain 87 | return &routerDomain, nil 88 | } 89 | 90 | func (l *DomainList) parseDomains(line string) ([]*router.Domain, error) { 91 | if isBlank(line) { 92 | return nil, nil 93 | } 94 | _, parts := splitAndTrim(line, "|") 95 | var domains []*router.Domain 96 | for _, domain := range parts { 97 | routerDomain, err := l.parseDomain(domain) 98 | if err != nil { 99 | return nil, err 100 | } 101 | if routerDomain == nil { 102 | continue 103 | } 104 | domains = append(domains, routerDomain) 105 | } 106 | return domains, nil 107 | } 108 | 109 | func (l *DomainList) parseRule(line string) ([]*router.Domain, error) { 110 | if isBlank(line) { 111 | return nil, errors.New("line is empty") 112 | } 113 | count, columns := splitAndTrim(line, ";") 114 | if count <= 1 { 115 | return nil, errors.New("line is missing delimiters") 116 | } 117 | var domains []*router.Domain 118 | for index, column := range columns[:count-2] { 119 | if index == 0 { 120 | continue 121 | } 122 | parsed, err := l.parseDomains(column) 123 | if err != nil { 124 | return nil, err 125 | } 126 | domains = append(domains, parsed...) 127 | } 128 | return domains, nil 129 | } 130 | 131 | func (l *DomainList) Flatten() error { 132 | sort.Slice(l.DomainDomains, func(i, j int) bool { 133 | return len(strings.Split(l.DomainDomains[i].GetValue(), ".")) < len(strings.Split(l.DomainDomains[j].GetValue(), ".")) 134 | }) 135 | trie := NewDomainTrie() 136 | for _, domain := range l.DomainDomains { 137 | success, err := trie.Insert(domain.GetValue()) 138 | if err != nil { 139 | return err 140 | } 141 | if success { 142 | l.UniqueDomainDomains = append(l.UniqueDomainDomains, domain) 143 | } 144 | } 145 | return nil 146 | } 147 | 148 | func (l *DomainList) ToGeoSites() *router.GeoSiteList { 149 | domainList := new(router.GeoSiteList) 150 | domain := new(router.GeoSite) 151 | domain.CountryCode = l.Name 152 | domain.Domain = append(domain.Domain, l.FullDomains...) 153 | domain.Domain = append(domain.Domain, l.UniqueDomainDomains...) 154 | domain.Domain = append(domain.Domain, l.RegexpDomains...) 155 | domain.Domain = append(domain.Domain) 156 | l.GeoSite = domain 157 | domainList.Entry = append(domainList.Entry, l.GeoSite) 158 | return domainList 159 | } 160 | 161 | func (l *DomainList) ToPlainText() []byte { 162 | bytes := make([]byte, 0, 1024*512) 163 | for _, rule := range l.GeoSite.Domain { 164 | ruleVal := strings.TrimSpace(rule.GetValue()) 165 | if len(ruleVal) == 0 { 166 | continue 167 | } 168 | var ruleString string 169 | switch rule.Type { 170 | case router.Domain_Full: 171 | ruleString = "full:" + ruleVal 172 | case router.Domain_RootDomain: 173 | ruleString = "domain:" + ruleVal 174 | case router.Domain_Regex: 175 | ruleString = "regexp:" + ruleVal 176 | } 177 | bytes = append(bytes, []byte(ruleString+"\n")...) 178 | } 179 | return bytes 180 | } 181 | 182 | func readAndParseLine(decoder *encoding.Decoder, scanner *bufio.Scanner, list *DomainList, lineNum int) (int, error) { 183 | if err := scanner.Err(); err != nil { 184 | return lineNum, err 185 | } 186 | if !scanner.Scan() { 187 | return lineNum, nil 188 | } 189 | utf8, err := decoder.Bytes(scanner.Bytes()) 190 | if err != nil { 191 | return lineNum, err 192 | } 193 | line := strings.TrimSpace(string(utf8)) 194 | if isEmpty(line) { 195 | return readAndParseLine(decoder, scanner, list, lineNum+1) 196 | } 197 | parsedRules, err := list.parseRule(line) 198 | if err != nil { 199 | return lineNum, err 200 | } 201 | for _, rule := range parsedRules { 202 | list.push(rule) 203 | } 204 | return readAndParseLine(decoder, scanner, list, lineNum+1) 205 | } 206 | 207 | func NewDomainList(name string) *DomainList { 208 | return &DomainList{ 209 | Name: name, 210 | FullDomains: make([]*router.Domain, 0, 10), 211 | RegexpDomains: make([]*router.Domain, 0, 10), 212 | DomainDomains: make([]*router.Domain, 0, 10), 213 | UniqueDomainDomains: make([]*router.Domain, 0, 10), 214 | } 215 | } 216 | 217 | func (l *DomainList) unmarshalCSV(decoder *encoding.Decoder, file *os.File) (int, error) { 218 | scanner := bufio.NewScanner(file) 219 | buf := make([]byte, 0, 64*1024) 220 | scanner.Buffer(buf, 1024*1024) 221 | if scanner.Scan() && scanner.Err() == nil { 222 | if lineNum, err := readAndParseLine(decoder, scanner, l, 2); err != nil { 223 | return lineNum, err 224 | } 225 | } 226 | return 1, scanner.Err() 227 | } 228 | 229 | func Unmarshal(decoder *encoding.Decoder, listName string, path string) (*DomainList, error) { 230 | file, err := os.Open(path) 231 | if err != nil { 232 | return nil, err 233 | } 234 | defer func(file *os.File) { 235 | if err := file.Close(); err != nil { 236 | panic(err) 237 | } 238 | }(file) 239 | list := NewDomainList(listName) 240 | if lineNum, err := list.unmarshalCSV(decoder, file); err != nil { 241 | ex := errors.Wrap(err, "could not parse rule at line number: "+strconv.Itoa(lineNum)) 242 | return nil, ex 243 | } 244 | return list, nil 245 | } 246 | -------------------------------------------------------------------------------- /examples/kcp-vmess-dynamic-port-utp-masquerade/v2fly-kcp-vmess-dynamic-port-utp-masquerade.client.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "dns": { 6 | "servers": [ 7 | "https://1.1.1.1/dns-query" 8 | ] 9 | }, 10 | "routing": { 11 | "domainStrategy": "AsIs", 12 | "rules": [ 13 | { 14 | "type": "field", 15 | "domainMatcher": "mph", 16 | "domains": [ 17 | "ext:antizapret.dat:ZAPRETINFO" 18 | ], 19 | "outboundTag": "proxy" 20 | }, 21 | { 22 | "type": "field", 23 | "port": "0-65535", 24 | "network": "tcp,udp", 25 | "protocol": [ 26 | "http", 27 | "tls", 28 | "bittorrent" 29 | ], 30 | "outboundTag": "direct" 31 | } 32 | ] 33 | }, 34 | "inbounds": [ 35 | { 36 | "port": 1080, 37 | "listen": "127.0.0.1", 38 | "protocol": "socks", 39 | "sniffing": { 40 | "enabled": true, 41 | "destOverride": [ 42 | "http", 43 | "tls" 44 | ] 45 | }, 46 | "settings": { 47 | "auth": "noauth" 48 | } 49 | } 50 | ], 51 | "outbounds": [ 52 | { 53 | "protocol": "vmess", 54 | "settings": { 55 | "vnext": [ 56 | { 57 | "address": "inconspicuous-domain.local", 58 | "port": 49152, 59 | "users": [ 60 | { 61 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 62 | "alterId": 0, 63 | "security": "auto", 64 | "level": 0 65 | } 66 | ] 67 | } 68 | ] 69 | }, 70 | "streamSettings": { 71 | "network": "mkcp", 72 | "kcpSettings": { 73 | "congestion": true, 74 | "header": { 75 | "type": "utp" 76 | } 77 | } 78 | }, 79 | "tag": "proxy" 80 | }, 81 | { 82 | "protocol": "freedom", 83 | "settings": {}, 84 | "tag": "direct" 85 | } 86 | ] 87 | } -------------------------------------------------------------------------------- /examples/kcp-vmess-dynamic-port-utp-masquerade/v2fly-kcp-vmess-dynamic-port-utp-masquerade.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "routing": { 6 | "domainStrategy": "AsIs", 7 | "rules": [ 8 | { 9 | "type": "field", 10 | "domainMatcher": "mph", 11 | "domains": [ 12 | "ext:antizapret.dat:ZAPRETINFO" 13 | ], 14 | "outboundTag": "allow" 15 | }, 16 | { 17 | "type": "field", 18 | "port": "0-65535", 19 | "network": "tcp,udp", 20 | "protocol": [ 21 | "http", 22 | "tls", 23 | "bittorrent" 24 | ], 25 | "outboundTag": "deny" 26 | } 27 | ] 28 | }, 29 | "inbounds": [ 30 | { 31 | "port": 49152, 32 | "listen": "0.0.0.0", 33 | "protocol": "vmess", 34 | "settings": { 35 | "clients": [ 36 | { 37 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 38 | "alterId": 0, 39 | "security": "auto", 40 | "level": 0 41 | } 42 | ], 43 | "detour": { 44 | "to": "dynamicPort" 45 | } 46 | }, 47 | "streamSettings": { 48 | "network": "mkcp", 49 | "kcpSettings": { 50 | "congestion": true, 51 | "header": { 52 | "type": "utp" 53 | } 54 | } 55 | }, 56 | "tag": "default" 57 | }, 58 | { 59 | "port": "49153-65534", 60 | "listen": "0.0.0.0", 61 | "protocol": "vmess", 62 | "settings": { 63 | "clients": [ 64 | { 65 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 66 | "alterId": 0, 67 | "security": "auto", 68 | "level": 0 69 | } 70 | ] 71 | }, 72 | "allocate": { 73 | "strategy": "random", 74 | "concurrency": 2, 75 | "refresh": 3 76 | }, 77 | "streamSettings": { 78 | "network": "mkcp", 79 | "kcpSettings": { 80 | "congestion": true, 81 | "header": { 82 | "type": "utp" 83 | } 84 | } 85 | }, 86 | "tag": "dynamicPort" 87 | } 88 | ], 89 | "outbounds": [ 90 | { 91 | "protocol": "blackhole", 92 | "settings": {}, 93 | "tag": "deny" 94 | }, 95 | { 96 | "protocol": "freedom", 97 | "settings": {}, 98 | "tag": "allow" 99 | } 100 | ] 101 | } 102 | -------------------------------------------------------------------------------- /examples/kcp-vmess/v2fly-kcp-vmess.client.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "dns": { 6 | "servers": [ 7 | "https://1.1.1.1/dns-query" 8 | ] 9 | }, 10 | "routing": { 11 | "domainStrategy": "AsIs", 12 | "rules": [ 13 | { 14 | "type": "field", 15 | "domainMatcher": "mph", 16 | "domains": [ 17 | "ext:antizapret.dat:ZAPRETINFO" 18 | ], 19 | "outboundTag": "proxy" 20 | }, 21 | { 22 | "type": "field", 23 | "port": "0-65535", 24 | "network": "tcp,udp", 25 | "protocol": [ 26 | "http", 27 | "tls", 28 | "bittorrent" 29 | ], 30 | "outboundTag": "direct" 31 | } 32 | ] 33 | }, 34 | "inbounds": [ 35 | { 36 | "port": 1080, 37 | "listen": "127.0.0.1", 38 | "protocol": "socks", 39 | "sniffing": { 40 | "enabled": true, 41 | "destOverride": [ 42 | "http", 43 | "tls" 44 | ] 45 | }, 46 | "settings": { 47 | "auth": "noauth" 48 | } 49 | } 50 | ], 51 | "outbounds": [ 52 | { 53 | "protocol": "vmess", 54 | "settings": { 55 | "vnext": [ 56 | { 57 | "address": "inconspicuous-domain.local", 58 | "port": 10000, 59 | "users": [ 60 | { 61 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 62 | "alterId": 0, 63 | "security": "auto", 64 | "level": 0 65 | } 66 | ] 67 | } 68 | ] 69 | }, 70 | "streamSettings": { 71 | "network": "mkcp", 72 | "kcpSettings": { 73 | "congestion": true, 74 | "header": { 75 | "type": "none" 76 | } 77 | } 78 | }, 79 | "tag": "proxy" 80 | }, 81 | { 82 | "protocol": "freedom", 83 | "settings": {}, 84 | "tag": "direct" 85 | } 86 | ] 87 | } -------------------------------------------------------------------------------- /examples/kcp-vmess/v2fly-kcp-vmess.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "routing": { 6 | "domainStrategy": "AsIs", 7 | "rules": [ 8 | { 9 | "type": "field", 10 | "domainMatcher": "mph", 11 | "domains": [ 12 | "ext:antizapret.dat:ZAPRETINFO" 13 | ], 14 | "outboundTag": "allow" 15 | }, 16 | { 17 | "type": "field", 18 | "port": "0-65535", 19 | "network": "tcp,udp", 20 | "protocol": [ 21 | "http", 22 | "tls", 23 | "bittorrent" 24 | ], 25 | "outboundTag": "deny" 26 | } 27 | ] 28 | }, 29 | "inbounds": [ 30 | { 31 | "port": 10000, 32 | "listen": "0.0.0.0", 33 | "protocol": "vmess", 34 | "settings": { 35 | "clients": [ 36 | { 37 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 38 | "alterId": 0, 39 | "security": "auto", 40 | "level": 0 41 | } 42 | ] 43 | }, 44 | "streamSettings": { 45 | "network": "mkcp", 46 | "kcpSettings": { 47 | "congestion": true, 48 | "header": { 49 | "type": "none" 50 | } 51 | } 52 | }, 53 | "tag": "default" 54 | } 55 | ], 56 | "outbounds": [ 57 | { 58 | "protocol": "blackhole", 59 | "settings": {}, 60 | "tag": "deny" 61 | }, 62 | { 63 | "protocol": "freedom", 64 | "settings": {}, 65 | "tag": "allow" 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /examples/quic-chacha20-vless-dynamic-port-utp-masquerade/v2fly-quic-chacha20-vless-dynamic-port-utp-masquerade.client.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "dns": { 6 | "servers": [ 7 | "https://1.1.1.1/dns-query" 8 | ] 9 | }, 10 | "routing": { 11 | "domainStrategy": "AsIs", 12 | "rules": [ 13 | { 14 | "type": "field", 15 | "domainMatcher": "mph", 16 | "domains": [ 17 | "ext:antizapret.dat:ZAPRETINFO" 18 | ], 19 | "outboundTag": "proxy" 20 | }, 21 | { 22 | "type": "field", 23 | "port": "0-65535", 24 | "network": "tcp,udp", 25 | "protocol": [ 26 | "http", 27 | "tls", 28 | "bittorrent" 29 | ], 30 | "outboundTag": "direct" 31 | } 32 | ] 33 | }, 34 | "inbounds": [ 35 | { 36 | "port": 1080, 37 | "listen": "127.0.0.1", 38 | "protocol": "socks", 39 | "sniffing": { 40 | "enabled": true, 41 | "destOverride": [ 42 | "http", 43 | "tls" 44 | ] 45 | }, 46 | "settings": { 47 | "auth": "noauth" 48 | } 49 | } 50 | ], 51 | "outbounds": [ 52 | { 53 | "protocol": "vless", 54 | "settings": { 55 | "vnext": [ 56 | { 57 | "address": "inconspicuous-domain.local", 58 | "port": 49152, 59 | "users": [ 60 | { 61 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 62 | "encryption": "none", 63 | "level": 0 64 | } 65 | ] 66 | } 67 | ] 68 | }, 69 | "streamSettings": { 70 | "network": "quic", 71 | "quicSettings": { 72 | "security": "chacha20-poly1305", 73 | "key": "EPK4saescW54sjHmJL3K7J4bhaxUi2iN", 74 | "header": { 75 | "type": "utp" 76 | } 77 | } 78 | }, 79 | "tag": "proxy" 80 | }, 81 | { 82 | "protocol": "freedom", 83 | "settings": {}, 84 | "tag": "direct" 85 | } 86 | ] 87 | } -------------------------------------------------------------------------------- /examples/quic-chacha20-vless-dynamic-port-utp-masquerade/v2fly-quic-chacha20-vless-dynamic-port-utp-masquerade.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "routing": { 6 | "domainStrategy": "AsIs", 7 | "rules": [ 8 | { 9 | "type": "field", 10 | "domainMatcher": "mph", 11 | "domains": [ 12 | "ext:antizapret.dat:ZAPRETINFO" 13 | ], 14 | "outboundTag": "allow" 15 | }, 16 | { 17 | "type": "field", 18 | "port": "0-65535", 19 | "network": "tcp,udp", 20 | "protocol": [ 21 | "http", 22 | "tls", 23 | "bittorrent" 24 | ], 25 | "outboundTag": "deny" 26 | } 27 | ] 28 | }, 29 | "inbounds": [ 30 | { 31 | "port": 49152, 32 | "listen": "0.0.0.0", 33 | "protocol": "vless", 34 | "settings": { 35 | "decryption": "none", 36 | "clients": [ 37 | { 38 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 39 | "decryption": "none", 40 | "level": 0 41 | } 42 | ], 43 | "detour": { 44 | "to": "dynamicPort" 45 | } 46 | }, 47 | "streamSettings": { 48 | "network": "quic", 49 | "quicSettings": { 50 | "security": "chacha20-poly1305", 51 | "key": "EPK4saescW54sjHmJL3K7J4bhaxUi2iN", 52 | "header": { 53 | "type": "utp" 54 | } 55 | } 56 | }, 57 | "tag": "default" 58 | }, 59 | { 60 | "port": "49153-65534", 61 | "listen": "0.0.0.0", 62 | "protocol": "vless", 63 | "settings": { 64 | "decryption": "none", 65 | "clients": [ 66 | { 67 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 68 | "decryption": "none", 69 | "level": 0 70 | } 71 | ] 72 | }, 73 | "allocate": { 74 | "strategy": "random", 75 | "concurrency": 2, 76 | "refresh": 3 77 | }, 78 | "streamSettings": { 79 | "network": "quic", 80 | "quicSettings": { 81 | "security": "chacha20-poly1305", 82 | "key": "EPK4saescW54sjHmJL3K7J4bhaxUi2iN", 83 | "header": { 84 | "type": "utp" 85 | } 86 | } 87 | }, 88 | "tag": "dynamicPort" 89 | } 90 | ], 91 | "outbounds": [ 92 | { 93 | "protocol": "blackhole", 94 | "settings": {}, 95 | "tag": "deny" 96 | }, 97 | { 98 | "protocol": "freedom", 99 | "settings": {}, 100 | "tag": "allow" 101 | } 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /examples/quic-vless/v2fly-quic-vless.client.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "dns": { 6 | "servers": [ 7 | "https://1.1.1.1/dns-query" 8 | ] 9 | }, 10 | "routing": { 11 | "domainStrategy": "AsIs", 12 | "rules": [ 13 | { 14 | "type": "field", 15 | "domainMatcher": "mph", 16 | "domains": [ 17 | "ext:antizapret.dat:ZAPRETINFO" 18 | ], 19 | "outboundTag": "proxy" 20 | }, 21 | { 22 | "type": "field", 23 | "port": "0-65535", 24 | "network": "tcp,udp", 25 | "protocol": [ 26 | "http", 27 | "tls", 28 | "bittorrent" 29 | ], 30 | "outboundTag": "direct" 31 | } 32 | ] 33 | }, 34 | "inbounds": [ 35 | { 36 | "port": 1080, 37 | "listen": "127.0.0.1", 38 | "protocol": "socks", 39 | "sniffing": { 40 | "enabled": true, 41 | "destOverride": [ 42 | "http", 43 | "tls" 44 | ] 45 | }, 46 | "settings": { 47 | "auth": "noauth" 48 | } 49 | } 50 | ], 51 | "outbounds": [ 52 | { 53 | "protocol": "vless", 54 | "settings": { 55 | "vnext": [ 56 | { 57 | "address": "inconspicuous-domain.local", 58 | "port": 10000, 59 | "users": [ 60 | { 61 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 62 | "encryption": "none", 63 | "level": 0 64 | } 65 | ] 66 | } 67 | ] 68 | }, 69 | "streamSettings": { 70 | "network": "quic", 71 | "quicSettings": { 72 | "security": "none", 73 | "key": "", 74 | "header": { 75 | "type": "none" 76 | } 77 | } 78 | }, 79 | "tag": "proxy" 80 | }, 81 | { 82 | "protocol": "freedom", 83 | "settings": {}, 84 | "tag": "direct" 85 | } 86 | ] 87 | } -------------------------------------------------------------------------------- /examples/quic-vless/v2fly-quic-vless.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "routing": { 6 | "domainStrategy": "AsIs", 7 | "rules": [ 8 | { 9 | "type": "field", 10 | "domainMatcher": "mph", 11 | "domains": [ 12 | "ext:antizapret.dat:ZAPRETINFO" 13 | ], 14 | "outboundTag": "allow" 15 | }, 16 | { 17 | "type": "field", 18 | "port": "0-65535", 19 | "network": "tcp,udp", 20 | "protocol": [ 21 | "http", 22 | "tls", 23 | "bittorrent" 24 | ], 25 | "outboundTag": "deny" 26 | } 27 | ] 28 | }, 29 | "inbounds": [ 30 | { 31 | "port": 10000, 32 | "listen": "0.0.0.0", 33 | "protocol": "vless", 34 | "settings": { 35 | "decryption": "none", 36 | "clients": [ 37 | { 38 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 39 | "decryption": "none", 40 | "level": 0 41 | } 42 | ] 43 | }, 44 | "streamSettings": { 45 | "network": "quic", 46 | "quicSettings": { 47 | "security": "none", 48 | "key": "", 49 | "header": { 50 | "type": "none" 51 | } 52 | } 53 | }, 54 | "tag": "default" 55 | } 56 | ], 57 | "outbounds": [ 58 | { 59 | "protocol": "blackhole", 60 | "settings": {}, 61 | "tag": "deny" 62 | }, 63 | { 64 | "protocol": "freedom", 65 | "settings": {}, 66 | "tag": "allow" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /examples/quic-vmess/v2fly-quic-vmess.client.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "dns": { 6 | "servers": [ 7 | "https://1.1.1.1/dns-query" 8 | ] 9 | }, 10 | "routing": { 11 | "domainStrategy": "AsIs", 12 | "rules": [ 13 | { 14 | "type": "field", 15 | "domainMatcher": "mph", 16 | "domains": [ 17 | "ext:antizapret.dat:ZAPRETINFO" 18 | ], 19 | "outboundTag": "proxy" 20 | }, 21 | { 22 | "type": "field", 23 | "port": "0-65535", 24 | "network": "tcp,udp", 25 | "protocol": [ 26 | "http", 27 | "tls", 28 | "bittorrent" 29 | ], 30 | "outboundTag": "direct" 31 | } 32 | ] 33 | }, 34 | "inbounds": [ 35 | { 36 | "port": 1080, 37 | "listen": "127.0.0.1", 38 | "protocol": "socks", 39 | "sniffing": { 40 | "enabled": true, 41 | "destOverride": [ 42 | "http", 43 | "tls" 44 | ] 45 | }, 46 | "settings": { 47 | "auth": "noauth" 48 | } 49 | } 50 | ], 51 | "outbounds": [ 52 | { 53 | "protocol": "vmess", 54 | "settings": { 55 | "vnext": [ 56 | { 57 | "address": "inconspicuous-domain.local", 58 | "port": 10000, 59 | "users": [ 60 | { 61 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 62 | "alterId": 0, 63 | "security": "auto", 64 | "level": 0 65 | } 66 | ] 67 | } 68 | ] 69 | }, 70 | "streamSettings": { 71 | "network": "quic", 72 | "quicSettings": { 73 | "security": "none", 74 | "key": "", 75 | "header": { 76 | "type": "none" 77 | } 78 | } 79 | }, 80 | "tag": "proxy" 81 | }, 82 | { 83 | "protocol": "freedom", 84 | "settings": {}, 85 | "tag": "direct" 86 | } 87 | ] 88 | } -------------------------------------------------------------------------------- /examples/quic-vmess/v2fly-quic-vmess.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "routing": { 6 | "domainStrategy": "AsIs", 7 | "rules": [ 8 | { 9 | "type": "field", 10 | "domainMatcher": "mph", 11 | "domains": [ 12 | "ext:antizapret.dat:ZAPRETINFO" 13 | ], 14 | "outboundTag": "allow" 15 | }, 16 | { 17 | "type": "field", 18 | "port": "0-65535", 19 | "network": "tcp,udp", 20 | "protocol": [ 21 | "http", 22 | "tls", 23 | "bittorrent" 24 | ], 25 | "outboundTag": "deny" 26 | } 27 | ] 28 | }, 29 | "inbounds": [ 30 | { 31 | "port": 10000, 32 | "listen": "0.0.0.0", 33 | "protocol": "vmess", 34 | "settings": { 35 | "clients": [ 36 | { 37 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 38 | "alterId": 0, 39 | "security": "auto", 40 | "level": 0 41 | } 42 | ] 43 | }, 44 | "streamSettings": { 45 | "network": "quic", 46 | "quicSettings": { 47 | "security": "none", 48 | "key": "", 49 | "header": { 50 | "type": "none" 51 | } 52 | } 53 | }, 54 | "tag": "default" 55 | } 56 | ], 57 | "outbounds": [ 58 | { 59 | "protocol": "blackhole", 60 | "settings": {}, 61 | "tag": "deny" 62 | }, 63 | { 64 | "protocol": "freedom", 65 | "settings": {}, 66 | "tag": "allow" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /examples/websocket-tls-vmess/Caddyfile: -------------------------------------------------------------------------------- 1 | inconspicuous-domain.local { 2 | log { 3 | output file /etc/caddy/caddy.log 4 | } 5 | tls { 6 | protocols tls1.2 tls1.3 7 | ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 8 | curves x25519 9 | } 10 | @v2ray_websocket { 11 | path /ray 12 | header Connection Upgrade 13 | header Upgrade websocket 14 | } 15 | reverse_proxy @v2ray_websocket localhost:10000 16 | } 17 | -------------------------------------------------------------------------------- /examples/websocket-tls-vmess/v2fly-websocket-tls-vmess.client.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "dns": { 6 | "servers": [ 7 | "https://1.1.1.1/dns-query" 8 | ] 9 | }, 10 | "routing": { 11 | "domainStrategy": "AsIs", 12 | "rules": [ 13 | { 14 | "type": "field", 15 | "domainMatcher": "mph", 16 | "domains": [ 17 | "ext:antizapret.dat:ZAPRETINFO" 18 | ], 19 | "outboundTag": "proxy" 20 | }, 21 | { 22 | "type": "field", 23 | "port": "0-65535", 24 | "network": "tcp,udp", 25 | "protocol": [ 26 | "http", 27 | "tls", 28 | "bittorrent" 29 | ], 30 | "outboundTag": "direct" 31 | } 32 | ] 33 | }, 34 | "inbounds": [ 35 | { 36 | "port": 1080, 37 | "listen": "127.0.0.1", 38 | "protocol": "socks", 39 | "sniffing": { 40 | "enabled": true, 41 | "destOverride": [ 42 | "http", 43 | "tls" 44 | ] 45 | }, 46 | "settings": { 47 | "auth": "noauth" 48 | } 49 | } 50 | ], 51 | "outbounds": [ 52 | { 53 | "protocol": "vmess", 54 | "settings": { 55 | "vnext": [ 56 | { 57 | "address": "inconspicuous-domain.local", 58 | "port": 443, 59 | "users": [ 60 | { 61 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 62 | "alterId": 0, 63 | "security": "auto", 64 | "level": 0 65 | } 66 | ] 67 | } 68 | ] 69 | }, 70 | "streamSettings": { 71 | "network": "ws", 72 | "security": "tls", 73 | "wsSettings": { 74 | "path": "/ray" 75 | } 76 | }, 77 | "tag": "proxy" 78 | }, 79 | { 80 | "protocol": "freedom", 81 | "settings": {}, 82 | "tag": "direct" 83 | } 84 | ] 85 | } -------------------------------------------------------------------------------- /examples/websocket-tls-vmess/v2fly-websocket-tls-vmess.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "routing": { 6 | "domainStrategy": "AsIs", 7 | "rules": [ 8 | { 9 | "type": "field", 10 | "domainMatcher": "mph", 11 | "domains": [ 12 | "ext:antizapret.dat:ZAPRETINFO" 13 | ], 14 | "outboundTag": "allow" 15 | }, 16 | { 17 | "type": "field", 18 | "port": "0-65535", 19 | "network": "tcp,udp", 20 | "protocol": [ 21 | "http", 22 | "tls", 23 | "bittorrent" 24 | ], 25 | "outboundTag": "deny" 26 | } 27 | ] 28 | }, 29 | "inbounds": [ 30 | { 31 | "port": 10000, 32 | "listen": "127.0.0.1", 33 | "protocol": "vmess", 34 | "settings": { 35 | "clients": [ 36 | { 37 | "id": "9d0a470f-aaac-46df-96f4-3e2b16963d39", 38 | "alterId": 0, 39 | "security": "auto", 40 | "level": 0 41 | } 42 | ] 43 | }, 44 | "streamSettings": { 45 | "network": "ws", 46 | "wsSettings": { 47 | "path": "/ray" 48 | } 49 | }, 50 | "tag": "default" 51 | } 52 | ], 53 | "outbounds": [ 54 | { 55 | "protocol": "blackhole", 56 | "settings": {}, 57 | "tag": "deny" 58 | }, 59 | { 60 | "protocol": "freedom", 61 | "settings": {}, 62 | "tag": "allow" 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module antizapret-xray 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.2 7 | github.com/v2fly/v2ray-core/v5 v5.0.7 8 | golang.org/x/text v0.3.7 9 | github.com/pkg/errors v0.9.1 10 | ) 11 | 12 | require ( 13 | github.com/adrg/xdg v0.4.0 // indirect 14 | golang.org/x/sys v0.0.0-20220325203850-36772127a21f // indirect 15 | google.golang.org/protobuf v1.28.0 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= 2 | github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 6 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 7 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 8 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 9 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 10 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 11 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 16 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 17 | github.com/v2fly/v2ray-core/v5 v5.0.7 h1:wR8x5KyYpe0W35tcJz/dlkpCClDhc/xe+36BQjVV3EM= 18 | github.com/v2fly/v2ray-core/v5 v5.0.7/go.mod h1:whgevEWmA6LrAfnPoM97IGMYhUF8837sAZ4U6MNJfzk= 19 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 20 | golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU= 21 | golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 22 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 23 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 24 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 25 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 26 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 27 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 28 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 30 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 31 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 32 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/golang/protobuf/proto" 7 | "golang.org/x/text/encoding/charmap" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | const listNamePlaceHolder = ".txt" 15 | 16 | var ( 17 | listName = flag.String("list-name", "ZAPRETINFO", "Name of the list") 18 | input = flag.String("input", filepath.Join("./", "z-i", "dump.csv"), "Path to the Zapret-Info CSV") 19 | geoSiteFile = flag.String("geosite-filename", "geosite.dat", "Name of the output file") 20 | plainTextFile = flag.String("plaintext-filename", listNamePlaceHolder, "Name of the plaintext output file") 21 | outputPath = flag.String("output-dir", "./publish", "Output path to the generated files") 22 | ) 23 | 24 | func main() { 25 | flag.Parse() 26 | domainList, parseErr := Unmarshal(charmap.Windows1251.NewDecoder(), strings.ToUpper(*listName), *input) 27 | if parseErr != nil { 28 | panic(parseErr) 29 | } 30 | 31 | if err := domainList.Flatten(); err != nil { 32 | fmt.Println("Failed:", err) 33 | os.Exit(1) 34 | } 35 | 36 | if geoSites := domainList.ToGeoSites(); geoSites != nil { 37 | geoSiteData, err := proto.Marshal(geoSites) 38 | if err != nil { 39 | fmt.Println("Failed:", err) 40 | os.Exit(1) 41 | } 42 | if err := os.MkdirAll(*outputPath, 0755); err != nil { 43 | fmt.Println("Failed:", err) 44 | os.Exit(1) 45 | } 46 | if err := ioutil.WriteFile(filepath.Join(*outputPath, *geoSiteFile), geoSiteData, 0644); err != nil { 47 | fmt.Println("Failed:", err) 48 | os.Exit(1) 49 | } 50 | fmt.Printf("%s has been generated successfully in '%s'.\n", *geoSiteFile, *outputPath) 51 | } 52 | 53 | var outputName string 54 | if *plainTextFile == listNamePlaceHolder { 55 | outputName = *listName + ".txt" 56 | } else { 57 | outputName = *plainTextFile 58 | } 59 | 60 | if err := ioutil.WriteFile(filepath.Join(*outputPath, outputName), domainList.ToPlainText(), 0644); err != nil { 61 | fmt.Println("Failed:", err) 62 | os.Exit(1) 63 | } 64 | fmt.Printf("%s has been generated successfully in '%s'.\n", outputName, *outputPath) 65 | } 66 | -------------------------------------------------------------------------------- /trie.go: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2020 Loyalsoldier 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | package main 26 | 27 | import ( 28 | "errors" 29 | "strings" 30 | ) 31 | 32 | type node struct { 33 | leaf bool 34 | children map[string]*node 35 | } 36 | 37 | func newNode() *node { 38 | return &node{ 39 | leaf: false, 40 | children: make(map[string]*node), 41 | } 42 | } 43 | 44 | func (n *node) getChild(s string) *node { 45 | return n.children[s] 46 | } 47 | 48 | func (n *node) hasChild(s string) bool { 49 | return n.getChild(s) != nil 50 | } 51 | 52 | func (n *node) addChild(s string, child *node) { 53 | n.children[s] = child 54 | } 55 | 56 | func (n *node) isLeaf() bool { 57 | return n.leaf 58 | } 59 | 60 | type DomainTrie struct { 61 | root *node 62 | } 63 | 64 | func NewDomainTrie() *DomainTrie { 65 | return &DomainTrie{ 66 | root: newNode(), 67 | } 68 | } 69 | 70 | func (t *DomainTrie) Insert(domain string) (bool, error) { 71 | if domain == "" { 72 | return false, errors.New("empty domain") 73 | } 74 | parts := strings.Split(domain, ".") 75 | 76 | node := t.root 77 | for i := len(parts) - 1; i >= 0; i-- { 78 | part := parts[i] 79 | 80 | if node.isLeaf() { 81 | return false, nil 82 | } 83 | if !node.hasChild(part) { 84 | node.addChild(part, newNode()) 85 | if i == 0 { 86 | node.getChild(part).leaf = true 87 | return true, nil 88 | } 89 | } 90 | node = node.getChild(part) 91 | } 92 | return false, nil 93 | } 94 | --------------------------------------------------------------------------------