summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoyalsoldier <[email protected]>2024-06-23 11:07:37 +0800
committerLoyalsoldier <[email protected]>2024-06-23 12:17:07 +0800
commit7effab8525352b3d8f0daa4c3f1bcd407df32dc9 (patch)
tree54f88a1981088ccea59e86735aceb6440f618aee
parent09fed9bfdfa2319cac484701145306be127a3dd9 (diff)
Feat: support sing-box SRS format as input & output
-rw-r--r--.github/workflows/build.yml3
-rw-r--r--.gitignore55
-rw-r--r--README.md52
-rw-r--r--config-example.json47
-rw-r--r--config.json4
-rw-r--r--go.mod19
-rw-r--r--go.sum61
-rw-r--r--init.go1
-rw-r--r--plugin/singbox/srs_in.go232
-rw-r--r--plugin/singbox/srs_out.go183
10 files changed, 623 insertions, 34 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index dd302384..d80c5a76 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -94,6 +94,7 @@ jobs:
mkdir -p publish
mv ./output/dat/*.dat ./output/dat/*.sha256sum ./output/maxmind/*.mmdb ./output/maxmind/*.sha256sum *.gz *.zip ./publish/
cp -fpPR ./output/text ./publish
+ cp -fpPR ./output/srs ./publish
- name: Git push assets to "release" branch
run: |
@@ -115,7 +116,7 @@ jobs:
done
- name: Remove some files to avoid publishing to GitHub release
- run: rm -rf ./publish/*.{gz,zip} ./publish/text
+ run: rm -rf ./publish/*.{gz,zip} ./publish/text ./publish/srs
- name: Upload files to GitHub release
uses: svenstaro/upload-release-action@v2
diff --git a/.gitignore b/.gitignore
index c1cfc265..22e58a6c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,55 @@
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
# Binaries for programs and plugins
*.exe
*.exe~
@@ -19,3 +71,6 @@ data/
geolite2/
output/
geoip
+*.dat
+*.mmdb
+*.srs
diff --git a/README.md b/README.md
index 90c2a777..23505cf5 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# 简介
-本项目每周四自动生成 GeoIP 文件,同时提供命令行界面(CLI)供用户自行定制 GeoIP 文件,包括但不限于 V2Ray dat 格式路由规则文件 `geoip.dat` 和 MaxMind mmdb 格式文件 `Country.mmdb`。
+本项目每周四自动生成 GeoIP 文件,同时提供命令行界面(CLI)供用户自行定制 GeoIP 文件,包括但不限于 V2Ray dat 格式路由规则文件 `geoip.dat`、MaxMind mmdb 格式文件 `Country.mmdb` 和 sing-box SRS 格式文件。
-This project releases GeoIP files automatically every Thursday. It also provides a command line interface(CLI) for users to customize their own GeoIP files, included but not limited to V2Ray dat format file `geoip.dat` and MaxMind mmdb format file `Country.mmdb`.
+This project releases GeoIP files automatically every Thursday. It also provides a command line interface(CLI) for users to customize their own GeoIP files, included but not limited to V2Ray dat format file `geoip.dat`, MaxMind mmdb format file `Country.mmdb` and sing-box SRS format files.
## 与官方版 GeoIP 的区别
@@ -64,6 +64,37 @@ rules:
在 [Leaf](https://github.com/eycorsican/leaf) 中使用本项目 `.mmdb` 格式文件的参考配置,查看[官方 README](https://github.com/eycorsican/leaf/blob/master/README.zh.md#geoip)。
+在 [sing-box](https://github.com/SagerNet/sing-box) 中使用本项目 `.srs` 格式文件的参考配置:
+
+```json
+"route": {
+ "rules": [
+ {
+ "rule_set": "geoip-cn",
+ "outbound": "direct"
+ },
+ {
+ "rule_set": "geoip-us",
+ "outbound": "block"
+ }
+ ],
+ "rule_set": [
+ {
+ "tag": "geoip-cn",
+ "type": "remote",
+ "format": "binary",
+ "url": "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/srs/cn.srs"
+ },
+ {
+ "tag": "geoip-us",
+ "type": "remote",
+ "format": "binary",
+ "url": "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/srs/us.srs"
+ }
+ ]
+}
+```
+
## 下载地址
> 如果无法访问域名 `raw.githubusercontent.com`,可以使用第二个地址 `cdn.jsdelivr.net`。
@@ -127,6 +158,12 @@ rules:
- [https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country-asn.mmdb.sha256sum](https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country-asn.mmdb.sha256sum)
- [https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country-asn.mmdb.sha256sum](https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country-asn.mmdb.sha256sum)
+### sing-box SRS 格式路由规则文件
+
+> 适用于 [sing-box](https://github.com/SagerNet/sing-box)。
+
+请查看本项目 `release` 分支下的 [srs](https://github.com/Loyalsoldier/geoip/tree/release/srs) 目录。
+
## 定制 GeoIP 文件
可通过以下几种方式定制 GeoIP 文件:
@@ -162,6 +199,7 @@ These two concepts are notable: `input` and `output`. The `input` is the data so
- **v2rayGeoIPDat**:V2Ray GeoIP dat 格式(`geoip.dat`)
- **maxmindMMDB**:MaxMind mmdb 数据格式(`GeoLite2-Country.mmdb`)
- **maxmindGeoLite2CountryCSV**:MaxMind GeoLite2 country CSV 数据(`GeoLite2-Country-CSV.zip`)
+- **singboxSRS**:sing-box SRS 格式(`geoip-cn.srs`)
- **clashRuleSetClassical**:[classical 类型的 Clash RuleSet](https://github.com/Dreamacro/clash/wiki/premium-core-features#classical)
- **clashRuleSet**:[ipcidr 类型的 Clash RuleSet](https://github.com/Dreamacro/clash/wiki/premium-core-features#ipcidr)
- **surgeRuleSet**:[Surge RuleSet](https://manual.nssurge.com/rule/ruleset.html)
@@ -171,6 +209,7 @@ These two concepts are notable: `input` and `output`. The `input` is the data so
- **text**:纯文本 CIDR(例如:`1.0.0.0/24`)
- **v2rayGeoIPDat**:V2Ray GeoIP dat 格式(`geoip.dat`,适用于 [V2Ray](https://github.com/v2fly/v2ray-core)、[Xray-core](https://github.com/XTLS/Xray-core) 和 [Trojan-Go](https://github.com/p4gefau1t/trojan-go))
- **maxmindMMDB**:MaxMind mmdb 数据格式(`GeoLite2-Country.mmdb`,适用于 [Clash](https://github.com/Dreamacro/clash) 和 [Leaf](https://github.com/eycorsican/leaf))
+- **singboxSRS**:sing-box SRS 格式(`geoip-cn.srs`,适用于 [sing-box](https://github.com/SagerNet/sing-box))
- **clashRuleSetClassical**:[classical 类型的 Clash RuleSet](https://github.com/Dreamacro/clash/wiki/premium-core-features#classical)
- **clashRuleSet**:[ipcidr 类型的 Clash RuleSet](https://github.com/Dreamacro/clash/wiki/premium-core-features#ipcidr)
- **surgeRuleSet**:[Surge RuleSet](https://manual.nssurge.com/rule/ruleset.html)
@@ -207,12 +246,20 @@ $ ./geoip -c config.json
2021/08/29 12:11:39 ✅ [text] cloudfront.txt --> output/text
2021/08/29 12:11:39 ✅ [text] facebook.txt --> output/text
2021/08/29 12:11:39 ✅ [text] fastly.txt --> output/text
+2021/08/29 12:11:45 ✅ [singboxSRS] netflix.txt --> output/srs
+2021/08/29 12:11:45 ✅ [singboxSRS] telegram.txt --> output/srs
+2021/08/29 12:11:45 ✅ [singboxSRS] cn.txt --> output/srs
+2021/08/29 12:11:45 ✅ [singboxSRS] cloudflare.txt --> output/srs
+2021/08/29 12:11:45 ✅ [singboxSRS] cloudfront.txt --> output/srs
+2021/08/29 12:11:45 ✅ [singboxSRS] facebook.txt --> output/srs
+2021/08/29 12:11:45 ✅ [singboxSRS] fastly.txt --> output/srs
$ ./geoip -l
All available input formats:
- v2rayGeoIPDat (Convert V2Ray GeoIP dat to other formats)
- maxmindMMDB (Convert MaxMind mmdb database to other formats)
- maxmindGeoLite2CountryCSV (Convert MaxMind GeoLite2 country CSV data to other formats)
+ - singboxSRS (Convert sing-box SRS data to other formats)
- private (Convert LAN and private network CIDR to other formats)
- text (Convert plaintext IP & CIDR to other formats)
- clashRuleSetClassical (Convert classical type of Clash RuleSet to other formats (just processing IP & CIDR lines))
@@ -223,6 +270,7 @@ All available input formats:
All available output formats:
- v2rayGeoIPDat (Convert data to V2Ray GeoIP dat format)
- maxmindMMDB (Convert data to MaxMind mmdb database format)
+ - singboxSRS (Convert data to sing-box SRS format)
- clashRuleSetClassical (Convert data to classical type of Clash RuleSet)
- clashRuleSet (Convert data to ipcidr type of Clash RuleSet)
- surgeRuleSet (Convert data to Surge RuleSet)
diff --git a/config-example.json b/config-example.json
index a7b933fb..4986e459 100644
--- a/config-example.json
+++ b/config-example.json
@@ -57,6 +57,31 @@
}
},
{
+ "type": "singboxSRS",
+ "action": "add",
+ "args": {
+ "name": "cn",
+ "uri": "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/srs/cn.srs"
+ }
+ },
+ {
+ "type": "singboxSRS",
+ "action": "add",
+ "args": {
+ "name": "cn",
+ "uri": "./srs/cn.srs",
+ "onlyIPType": "ipv4"
+ }
+ },
+ {
+ "type": "singboxSRS",
+ "action": "add",
+ "args": {
+ "inputDir": "./srs",
+ "onlyIPType": "ipv6"
+ }
+ },
+ {
"type": "cutter",
"action": "remove",
"args": {
@@ -165,6 +190,28 @@
}
},
{
+ "type": "singboxSRS",
+ "action": "output",
+ "args": {
+ "outputDir": "./publish"
+ }
+ },
+ {
+ "type": "singboxSRS",
+ "action": "output",
+ "args": {
+ "wantedList": ["cn", "us"]
+ }
+ },
+ {
+ "type": "singboxSRS",
+ "action": "output",
+ "args": {
+ "wantedList": ["cn", "us"],
+ "onlyIPType": "ipv4"
+ }
+ },
+ {
"type": "text",
"action": "output",
"args": {
diff --git a/config.json b/config.json
index 511610ff..696f5bf8 100644
--- a/config.json
+++ b/config.json
@@ -166,6 +166,10 @@
}
},
{
+ "type": "singboxSRS",
+ "action": "output"
+ },
+ {
"type": "text",
"action": "output"
}
diff --git a/go.mod b/go.mod
index abc4773f..37f698b3 100644
--- a/go.mod
+++ b/go.mod
@@ -7,24 +7,31 @@ toolchain go1.21.10
require (
github.com/maxmind/mmdbwriter v1.0.0
github.com/oschwald/maxminddb-golang v1.13.0
+ github.com/sagernet/sing-box v1.9.3
github.com/v2fly/v2ray-core/v5 v5.16.1
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
- google.golang.org/protobuf v1.34.1
+ google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/adrg/xdg v0.4.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
- github.com/kr/pretty v0.3.1 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/miekg/dns v1.1.59 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/quic-go/quic-go v0.43.0 // indirect
+ github.com/sagernet/sing v0.4.1 // indirect
+ github.com/sagernet/sing-dns v0.2.0 // indirect
go.starlark.net v0.0.0-20230612165344-9532f5667272 // indirect
- golang.org/x/crypto v0.22.0 // indirect
- golang.org/x/net v0.24.0 // indirect
- golang.org/x/sys v0.20.0 // indirect
- golang.org/x/text v0.14.0 // indirect
+ golang.org/x/crypto v0.23.0 // indirect
+ golang.org/x/mod v0.17.0 // indirect
+ golang.org/x/net v0.25.0 // indirect
+ golang.org/x/sync v0.7.0 // indirect
+ golang.org/x/sys v0.21.0 // indirect
+ golang.org/x/text v0.15.0 // indirect
+ golang.org/x/tools v0.21.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index db17cada..9a115a95 100644
--- a/go.sum
+++ b/go.sum
@@ -71,8 +71,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
-github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
-github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
+github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
+github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg=
@@ -83,8 +83,6 @@ github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/q
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU=
github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@@ -93,10 +91,14 @@ github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/maxmind/mmdbwriter v1.0.0 h1:bieL4P6yaYaHvbtLSwnKtEvScUKKD6jcKaLiTM3WSMw=
github.com/maxmind/mmdbwriter v1.0.0/go.mod h1:noBMCUtyN5PUQ4H8ikkOvGSHhzhLok51fON2hcrpKj8=
+github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
+github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/mustafaturan/bus v1.0.2 h1:2x3ErwZ0uUPwwZ5ZZoknEQprdaxr68Yl3mY8jDye1Ws=
github.com/mustafaturan/bus v1.0.2/go.mod h1:h7gfehm8TThv4Dcaa+wDQG7r7j6p74v+7ftr0Rq9i1Q=
github.com/mustafaturan/monoton v1.0.0 h1:8SCej+JiNn0lyps7V+Jzc1CRAkDR4EZPWrTupQ61YCQ=
github.com/mustafaturan/monoton v1.0.0/go.mod h1:FOnE7NV3s3EWPXb8/7+/OSdiMBbdlkV0Lz8p1dc+vy8=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
@@ -119,18 +121,27 @@ github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3
github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
+github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
+github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
+github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.43.0 h1:sjtsTKWX0dsHpuMJvLxGqoQdtgJnbAPWY+W+5vjYW/g=
github.com/quic-go/quic-go v0.43.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
github.com/refraction-networking/utls v1.6.5 h1:Jlfqgs/t1Uy6FHHQ8Fz9ZTrRmP/zS7d/NZw7BLahaL8=
github.com/refraction-networking/utls v1.6.5/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/sagernet/quic-go v0.43.1-beta.2 h1:6YRCE9t1Q3UbNX1/dJGqpwFQbh6DXC6XBrQr2xp6hXY=
+github.com/sagernet/quic-go v0.43.1-beta.2/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhYiNPyYEBL/BVJ1ifc=
+github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
+github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
+github.com/sagernet/sing-box v1.9.3 h1:jXiAqQRzBeXCSLTTl0Z92OLs5GkVotsdiNRVATZWpoY=
+github.com/sagernet/sing-box v1.9.3/go.mod h1:6Rx5nzbqIfN7HlUaHgO/IdkP7fDPPQ/U/TAC5asEjSM=
+github.com/sagernet/sing-dns v0.2.0 h1:dka3weRX6+CrYO3v+hrTy2z68rCOCZXNBiNXpLZ6JNs=
+github.com/sagernet/sing-dns v0.2.0/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg=
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 h1:zOjq+1/uLzn/Xo40stbvjIY/yehG0+mfmlsiEmc0xmQ=
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4/go.mod h1:aI+8yClBW+1uovkHw6HM01YXnYB8vohtB9C83wzx34E=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
@@ -160,22 +171,22 @@ go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
-golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
-golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
+golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
-golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
-golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -187,20 +198,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
-golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
+golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
+golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@@ -223,11 +234,11 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
-google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/init.go b/init.go
index a663ad24..9d559af4 100644
--- a/init.go
+++ b/init.go
@@ -3,6 +3,7 @@ package main
import (
_ "github.com/Loyalsoldier/geoip/plugin/maxmind"
_ "github.com/Loyalsoldier/geoip/plugin/plaintext"
+ _ "github.com/Loyalsoldier/geoip/plugin/singbox"
_ "github.com/Loyalsoldier/geoip/plugin/special"
_ "github.com/Loyalsoldier/geoip/plugin/v2ray"
)
diff --git a/plugin/singbox/srs_in.go b/plugin/singbox/srs_in.go
new file mode 100644
index 00000000..f54c2d44
--- /dev/null
+++ b/plugin/singbox/srs_in.go
@@ -0,0 +1,232 @@
+package singbox
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "github.com/Loyalsoldier/geoip/lib"
+ "github.com/sagernet/sing-box/common/srs"
+)
+
+const (
+ typeSRSIn = "singboxSRS"
+ descSRSIn = "Convert sing-box SRS data to other formats"
+)
+
+func init() {
+ lib.RegisterInputConfigCreator(typeSRSIn, func(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
+ return newSRSIn(action, data)
+ })
+ lib.RegisterInputConverter(typeSRSIn, &srsIn{
+ Description: descSRSIn,
+ })
+}
+
+func newSRSIn(action lib.Action, data json.RawMessage) (lib.InputConverter, error) {
+ var tmp struct {
+ Name string `json:"name"`
+ URI string `json:"uri"`
+ InputDir string `json:"inputDir"`
+ OnlyIPType lib.IPType `json:"onlyIPType"`
+ }
+
+ if len(data) > 0 {
+ if err := json.Unmarshal(data, &tmp); err != nil {
+ return nil, err
+ }
+ }
+
+ if tmp.Name == "" && tmp.URI == "" && tmp.InputDir == "" {
+ return nil, fmt.Errorf("type %s | action %s missing inputdir or name or uri", typeSRSIn, action)
+ }
+
+ if (tmp.Name != "" && tmp.URI == "") || (tmp.Name == "" && tmp.URI != "") {
+ return nil, fmt.Errorf("type %s | action %s name & uri must be specified together", typeSRSIn, action)
+ }
+
+ return &srsIn{
+ Type: typeSRSIn,
+ Action: action,
+ Description: descSRSIn,
+ Name: tmp.Name,
+ URI: tmp.URI,
+ InputDir: tmp.InputDir,
+ OnlyIPType: tmp.OnlyIPType,
+ }, nil
+}
+
+type srsIn struct {
+ Type string
+ Action lib.Action
+ Description string
+ Name string
+ URI string
+ InputDir string
+ OnlyIPType lib.IPType
+}
+
+func (s *srsIn) GetType() string {
+ return s.Type
+}
+
+func (s *srsIn) GetAction() lib.Action {
+ return s.Action
+}
+
+func (s *srsIn) GetDescription() string {
+ return s.Description
+}
+
+func (s *srsIn) Input(container lib.Container) (lib.Container, error) {
+ entries := make(map[string]*lib.Entry)
+ var err error
+
+ switch {
+ case s.InputDir != "":
+ err = s.walkDir(s.InputDir, entries)
+ case s.Name != "" && s.URI != "":
+ switch {
+ case strings.HasPrefix(s.URI, "http://"), strings.HasPrefix(s.URI, "https://"):
+ err = s.walkRemoteFile(s.URI, s.Name, entries)
+ default:
+ err = s.walkLocalFile(s.URI, s.Name, entries)
+ }
+ default:
+ return nil, fmt.Errorf("config missing argument inputDir or name or uri")
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ var ignoreIPType lib.IgnoreIPOption
+ switch s.OnlyIPType {
+ case lib.IPv4:
+ ignoreIPType = lib.IgnoreIPv6
+ case lib.IPv6:
+ ignoreIPType = lib.IgnoreIPv4
+ }
+
+ if len(entries) == 0 {
+ return nil, fmt.Errorf("type %s | action %s no entry are generated", s.Type, s.Action)
+ }
+
+ for _, entry := range entries {
+ switch s.Action {
+ case lib.ActionAdd:
+ if err := container.Add(entry, ignoreIPType); err != nil {
+ return nil, err
+ }
+ case lib.ActionRemove:
+ container.Remove(entry.GetName(), ignoreIPType)
+ }
+ }
+
+ return container, nil
+}
+
+func (s *srsIn) walkDir(dir string, entries map[string]*lib.Entry) error {
+ err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if info.IsDir() {
+ return nil
+ }
+
+ if err := s.walkLocalFile(path, "", entries); err != nil {
+ return err
+ }
+
+ return nil
+ })
+
+ return err
+}
+
+func (s *srsIn) walkLocalFile(path, name string, entries map[string]*lib.Entry) error {
+ name = strings.TrimSpace(name)
+ var filename string
+ if name != "" {
+ filename = name
+ } else {
+ filename = filepath.Base(path)
+ }
+
+ // check filename
+ if !regexp.MustCompile(`^[a-zA-Z0-9_.\-]+$`).MatchString(filename) {
+ return fmt.Errorf("filename %s cannot be entry name, please remove special characters in it", filename)
+ }
+ dotIndex := strings.LastIndex(filename, ".")
+ if dotIndex > 0 {
+ filename = filename[:dotIndex]
+ }
+
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ if err := s.generateEntries(filename, file, entries); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *srsIn) walkRemoteFile(url, name string, entries map[string]*lib.Entry) error {
+ resp, err := http.Get(url)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ return fmt.Errorf("failed to get remote file %s, http status code %d", url, resp.StatusCode)
+ }
+
+ if err := s.generateEntries(name, resp.Body, entries); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *srsIn) generateEntries(name string, reader io.Reader, entries map[string]*lib.Entry) error {
+ entry := lib.NewEntry(name)
+ if theEntry, found := entries[entry.GetName()]; found {
+ fmt.Printf("⚠️ [type %s | action %s] found duplicated entry: %s. Process anyway\n", typeSRSIn, s.Action, name)
+ entry = theEntry
+ }
+
+ plainRuleSet, err := srs.Read(reader, true)
+ if err != nil {
+ return err
+ }
+
+ for _, rule := range plainRuleSet.Rules {
+ for _, cidrStr := range rule.DefaultOptions.IPCIDR {
+ switch s.Action {
+ case lib.ActionAdd:
+ if err := entry.AddPrefix(cidrStr); err != nil {
+ return err
+ }
+ case lib.ActionRemove:
+ if err := entry.RemovePrefix(cidrStr); err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ entries[entry.GetName()] = entry
+
+ return nil
+}
diff --git a/plugin/singbox/srs_out.go b/plugin/singbox/srs_out.go
new file mode 100644
index 00000000..7adfa579
--- /dev/null
+++ b/plugin/singbox/srs_out.go
@@ -0,0 +1,183 @@
+package singbox
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/Loyalsoldier/geoip/lib"
+ "github.com/sagernet/sing-box/common/srs"
+ "github.com/sagernet/sing-box/constant"
+ "github.com/sagernet/sing-box/option"
+)
+
+const (
+ typeSRSOut = "singboxSRS"
+ descSRSOut = "Convert data to sing-box SRS format"
+)
+
+var (
+ defaultOutputDir = filepath.Join("./", "output", "srs")
+)
+
+func init() {
+ lib.RegisterOutputConfigCreator(typeSRSOut, func(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
+ return newSRSOut(action, data)
+ })
+ lib.RegisterOutputConverter(typeSRSOut, &srsOut{
+ Description: descSRSOut,
+ })
+}
+
+func newSRSOut(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
+ var tmp struct {
+ OutputDir string `json:"outputDir"`
+ Want []string `json:"wantedList"`
+ OnlyIPType lib.IPType `json:"onlyIPType"`
+ }
+
+ if len(data) > 0 {
+ if err := json.Unmarshal(data, &tmp); err != nil {
+ return nil, err
+ }
+ }
+
+ if tmp.OutputDir == "" {
+ tmp.OutputDir = defaultOutputDir
+ }
+
+ return &srsOut{
+ Type: typeSRSOut,
+ Action: action,
+ Description: descSRSOut,
+ OutputDir: tmp.OutputDir,
+ Want: tmp.Want,
+ OnlyIPType: tmp.OnlyIPType,
+ }, nil
+}
+
+type srsOut struct {
+ Type string
+ Action lib.Action
+ Description string
+ OutputDir string
+ Want []string
+ OnlyIPType lib.IPType
+}
+
+func (s *srsOut) GetType() string {
+ return s.Type
+}
+
+func (s *srsOut) GetAction() lib.Action {
+ return s.Action
+}
+
+func (s *srsOut) GetDescription() string {
+ return s.Description
+}
+
+func (s *srsOut) Output(container lib.Container) error {
+ // Filter want list
+ wantList := make(map[string]bool)
+ for _, want := range s.Want {
+ if want = strings.ToUpper(strings.TrimSpace(want)); want != "" {
+ wantList[want] = true
+ }
+ }
+
+ switch len(wantList) {
+ case 0:
+ for entry := range container.Loop() {
+ if err := s.run(entry); err != nil {
+ return err
+ }
+ }
+
+ default:
+ for name := range wantList {
+ entry, found := container.GetEntry(name)
+ if !found {
+ log.Printf("❌ entry %s not found", name)
+ continue
+ }
+
+ if err := s.run(entry); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+func (s *srsOut) run(entry *lib.Entry) error {
+ ruleset, err := s.generateRuleSet(entry)
+ if err != nil {
+ return err
+ }
+
+ filename := strings.ToLower(entry.GetName()) + ".srs"
+ if err := s.writeFile(filename, ruleset); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (s *srsOut) generateRuleSet(entry *lib.Entry) (*option.PlainRuleSet, error) {
+ var entryCidr []string
+ var err error
+ switch s.OnlyIPType {
+ case lib.IPv4:
+ entryCidr, err = entry.MarshalText(lib.IgnoreIPv6)
+ case lib.IPv6:
+ entryCidr, err = entry.MarshalText(lib.IgnoreIPv4)
+ default:
+ entryCidr, err = entry.MarshalText()
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ var headlessRule option.DefaultHeadlessRule
+ headlessRule.IPCIDR = entryCidr
+
+ var plainRuleSet option.PlainRuleSet
+ plainRuleSet.Rules = []option.HeadlessRule{
+ {
+ Type: constant.RuleTypeDefault,
+ DefaultOptions: headlessRule,
+ },
+ }
+
+ if len(headlessRule.IPCIDR) > 0 {
+ return &plainRuleSet, nil
+ }
+
+ return nil, fmt.Errorf("entry %s has no CIDR", entry.GetName())
+}
+
+func (s *srsOut) writeFile(filename string, ruleset *option.PlainRuleSet) error {
+ if err := os.MkdirAll(s.OutputDir, 0755); err != nil {
+ return err
+ }
+
+ f, err := os.Create(filepath.Join(s.OutputDir, filename))
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ err = srs.Write(f, *ruleset)
+ if err != nil {
+ return err
+ }
+
+ log.Printf("✅ [%s] %s --> %s", s.Type, filename, s.OutputDir)
+
+ return nil
+}