mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-02 12:13:56 +08:00
update 2026-05-10 09:48:30
This commit is contained in:
1 parent
95ac96413e
commit
0b362674f1
6244 files changed
+2992248
No files matched your search
@@ -0,0 +1,43 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=mosdns
|
||||
PKG_VERSION:=5.3.4
|
||||
PKG_RELEASE:=5
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/IrineSistiana/mosdns/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=0302a685db2a6c3c09af7bf4ff0dffd24f1e583383a47f064564f5270033671b
|
||||
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
PKG_LICENSE_FILE:=LICENSE
|
||||
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
GO_PKG:=github.com/IrineSistiana/mosdns
|
||||
GO_PKG_LDFLAGS_X:=main.version=v$(PKG_VERSION)-$(PKG_RELEASE)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
|
||||
define Package/mosdns
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=IP Addresses and Names
|
||||
TITLE:=A plug-in DNS forwarder/splitter
|
||||
URL:=https://github.com/IrineSistiana/mosdns
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
endef
|
||||
|
||||
define Package/mosdns/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,mosdns))
|
||||
$(eval $(call BuildPackage,mosdns))
|
||||
@@ -0,0 +1,238 @@
|
||||
From 201408484de552cb927f6a50b76a34d65c8ac8c6 Mon Sep 17 00:00:00 2001
|
||||
From: sbwml <admin@cooluc.com>
|
||||
Date: Fri, 20 Mar 2026 10:39:22 +0800
|
||||
Subject: [PATCH] mosdns: update dependencies
|
||||
|
||||
Signed-off-by: sbwml <admin@cooluc.com>
|
||||
---
|
||||
.github/workflows/release.yml | 2 +-
|
||||
.github/workflows/test.yml | 2 +-
|
||||
go.mod | 36 +++++++++----------
|
||||
go.sum | 68 +++++++++++++++++------------------
|
||||
pkg/query_context/context.go | 1 -
|
||||
5 files changed, 54 insertions(+), 55 deletions(-)
|
||||
|
||||
--- a/.github/workflows/release.yml
|
||||
+++ b/.github/workflows/release.yml
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
- go-version: "1.25"
|
||||
+ go-version: "1.26"
|
||||
check-latest: true
|
||||
cache: true
|
||||
|
||||
--- a/.github/workflows/test.yml
|
||||
+++ b/.github/workflows/test.yml
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
- go-version: "1.25"
|
||||
+ go-version: "1.26"
|
||||
check-latest: true
|
||||
cache: true
|
||||
|
||||
--- a/go.mod
|
||||
+++ b/go.mod
|
||||
@@ -1,30 +1,30 @@
|
||||
module github.com/IrineSistiana/mosdns/v5
|
||||
|
||||
-go 1.24.9
|
||||
+go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/IrineSistiana/go-bytes-pool v0.0.0-20230918115058-c72bd9761c57
|
||||
- github.com/go-chi/chi/v5 v5.2.3
|
||||
- github.com/go-viper/mapstructure/v2 v2.4.0
|
||||
+ github.com/go-chi/chi/v5 v5.2.5
|
||||
+ github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
github.com/google/nftables v0.3.0
|
||||
github.com/kardianos/service v1.2.4
|
||||
- github.com/klauspost/compress v1.18.2
|
||||
- github.com/miekg/dns v1.1.70
|
||||
+ github.com/klauspost/compress v1.18.4
|
||||
+ github.com/miekg/dns v1.1.72
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nadoo/ipset v0.5.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
- github.com/quic-go/quic-go v0.58.1
|
||||
+ github.com/quic-go/quic-go v0.59.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/vishvananda/netlink v1.3.0
|
||||
go.uber.org/zap v1.27.1
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
- golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
|
||||
- golang.org/x/net v0.48.0
|
||||
- golang.org/x/sync v0.19.0
|
||||
- golang.org/x/sys v0.40.0
|
||||
- golang.org/x/time v0.14.0
|
||||
+ golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
|
||||
+ golang.org/x/net v0.52.0
|
||||
+ golang.org/x/sync v0.20.0
|
||||
+ golang.org/x/sys v0.42.0
|
||||
+ golang.org/x/time v0.15.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
@@ -37,14 +37,14 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
- github.com/mdlayher/netlink v1.8.0 // indirect
|
||||
+ github.com/mdlayher/netlink v1.9.0 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
- github.com/prometheus/procfs v0.19.2 // indirect
|
||||
+ github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
@@ -55,11 +55,11 @@ require (
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
- go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
+ go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
- golang.org/x/crypto v0.46.0 // indirect
|
||||
- golang.org/x/mod v0.32.0 // indirect
|
||||
- golang.org/x/text v0.33.0 // indirect
|
||||
- golang.org/x/tools v0.40.0 // indirect
|
||||
+ golang.org/x/crypto v0.49.0 // indirect
|
||||
+ golang.org/x/mod v0.34.0 // indirect
|
||||
+ golang.org/x/text v0.35.0 // indirect
|
||||
+ golang.org/x/tools v0.43.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
--- a/go.sum
|
||||
+++ b/go.sum
|
||||
@@ -13,10 +13,10 @@ github.com/frankban/quicktest v1.14.6 h1
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
-github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
-github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
-github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
-github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
+github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
+github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
+github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
+github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg=
|
||||
@@ -25,20 +25,20 @@ github.com/inconshreveable/mousetrap v1.
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kardianos/service v1.2.4 h1:XNlGtZOYNx2u91urOdg/Kfmc+gfmuIo1Dd3rEi2OgBk=
|
||||
github.com/kardianos/service v1.2.4/go.mod h1:E4V9ufUuY82F7Ztlu1eN9VXWIQxg8NoLQlmFe0MtrXc=
|
||||
-github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
-github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
+github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
+github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
-github.com/mdlayher/netlink v1.8.0 h1:e7XNIYJKD7hUct3Px04RuIGJbBxy1/c4nX7D5YyvvlM=
|
||||
-github.com/mdlayher/netlink v1.8.0/go.mod h1:UhgKXUlDQhzb09DrCl2GuRNEglHmhYoWAHid9HK3594=
|
||||
+github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
|
||||
+github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
-github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
|
||||
-github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
+github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
+github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
@@ -53,12 +53,12 @@ github.com/prometheus/client_model v0.6.
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
-github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
-github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
+github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
+github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
-github.com/quic-go/quic-go v0.58.1 h1:J0s55TVauDbnCEY5tU2B8e7nYb3gnKMSez5Fwi1dl2s=
|
||||
-github.com/quic-go/quic-go v0.58.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
+github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
+github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -91,32 +91,32 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSx
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
-go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
-go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
-golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
-golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||
-golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
-golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
-golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
-golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
-golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
-golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
-golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
+golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
+golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
+golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
|
||||
+golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
|
||||
+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
-golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
-golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
-golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
-golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
-golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
-golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
-golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
-golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
+golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
+golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
--- a/pkg/query_context/context.go
|
||||
+++ b/pkg/query_context/context.go
|
||||
@@ -108,7 +108,6 @@ func (ctx *Context) QQuestion() dns.Ques
|
||||
// It's a helper func for searching opt in Q() manually.
|
||||
func (ctx *Context) QOpt() *dns.OPT {
|
||||
opt := findOpt(ctx.query)
|
||||
- ctx.query.IsEdns0()
|
||||
if opt == nil {
|
||||
panic("query opt is missing")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
From 0b86b89629f32e7c8b859239aa1a4814f256053c Mon Sep 17 00:00:00 2001
|
||||
From: sbwml <admin@cooluc.com>
|
||||
Date: Thu, 28 Sep 2023 16:42:54 +0800
|
||||
Subject: [PATCH 3/5] add response for bad request in ServeHTTP handler
|
||||
|
||||
---
|
||||
pkg/server/http_handler.go | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
--- a/pkg/server/http_handler.go
|
||||
+++ b/pkg/server/http_handler.go
|
||||
@@ -91,6 +91,7 @@ func (h *HttpHandler) ServeHTTP(w http.R
|
||||
if err != nil {
|
||||
h.warnErr(req, "invalid request", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
+ w.Write([]byte("Bad Request"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
From e34dca717e78d24a84b98c2b5d371c4253b7e260 Mon Sep 17 00:00:00 2001
|
||||
From: sbwml <admin@cooluc.com>
|
||||
Date: Wed, 20 Sep 2023 14:51:19 +0800
|
||||
Subject: [PATCH 4/5] black_hole: apply Fisher-Yates shuffle algorithm to
|
||||
randomize IP order
|
||||
|
||||
---
|
||||
plugin/executable/black_hole/black_hole.go | 15 +++++++++++++++
|
||||
1 file changed, 15 insertions(+)
|
||||
|
||||
--- a/plugin/executable/black_hole/black_hole.go
|
||||
+++ b/plugin/executable/black_hole/black_hole.go
|
||||
@@ -27,6 +27,8 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
"net/netip"
|
||||
"strings"
|
||||
+ "math/rand"
|
||||
+ "sync"
|
||||
)
|
||||
|
||||
const PluginType = "black_hole"
|
||||
@@ -40,6 +42,7 @@ var _ sequence.Executable = (*BlackHole)
|
||||
type BlackHole struct {
|
||||
ipv4 []netip.Addr
|
||||
ipv6 []netip.Addr
|
||||
+ shuffleMutex sync.Mutex
|
||||
}
|
||||
|
||||
// QuickSetup format: [ipv4|ipv6] ...
|
||||
@@ -65,9 +68,21 @@ func NewBlackHole(ips []string) (*BlackH
|
||||
return b, nil
|
||||
}
|
||||
|
||||
+func (b *BlackHole) shuffleIPs() {
|
||||
+ b.shuffleMutex.Lock()
|
||||
+ defer b.shuffleMutex.Unlock()
|
||||
+ rand.Shuffle(len(b.ipv4), func(i, j int) {
|
||||
+ b.ipv4[i], b.ipv4[j] = b.ipv4[j], b.ipv4[i]
|
||||
+ })
|
||||
+ rand.Shuffle(len(b.ipv6), func(i, j int) {
|
||||
+ b.ipv6[i], b.ipv6[j] = b.ipv6[j], b.ipv6[i]
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
// Exec implements sequence.Executable. It set a response with given ips if
|
||||
// query has corresponding qtypes.
|
||||
func (b *BlackHole) Exec(_ context.Context, qCtx *query_context.Context) error {
|
||||
+ b.shuffleIPs()
|
||||
if r := b.Response(qCtx.Q()); r != nil {
|
||||
qCtx.SetResponse(r)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
From 2dc08749e2de8f19ef869e7f89c9979edbbc71ff Mon Sep 17 00:00:00 2001
|
||||
From: sbwml <admin@cooluc.com>
|
||||
Date: Wed, 20 Sep 2023 21:05:18 +0800
|
||||
Subject: [PATCH 5/5] format logtime
|
||||
|
||||
---
|
||||
mlog/logger.go | 18 ++++++++++++++----
|
||||
1 file changed, 14 insertions(+), 4 deletions(-)
|
||||
|
||||
--- a/mlog/logger.go
|
||||
+++ b/mlog/logger.go
|
||||
@@ -21,9 +21,11 @@ package mlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
+ "os"
|
||||
+ "time"
|
||||
+
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
- "os"
|
||||
)
|
||||
|
||||
type LogConfig struct {
|
||||
@@ -64,10 +66,18 @@ func NewLogger(lc LogConfig) (*zap.Logge
|
||||
out = stderr
|
||||
}
|
||||
|
||||
- if lc.Production {
|
||||
- return zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), out, lvl)), nil
|
||||
+ encoderConfig := zap.NewDevelopmentEncoderConfig()
|
||||
+ encoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
||||
+ enc.AppendString(t.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
- return zap.New(zapcore.NewCore(zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), out, lvl)), nil
|
||||
+
|
||||
+ core := zapcore.NewCore(
|
||||
+ zapcore.NewConsoleEncoder(encoderConfig),
|
||||
+ out,
|
||||
+ lvl,
|
||||
+ )
|
||||
+
|
||||
+ return zap.New(core), nil
|
||||
}
|
||||
|
||||
// L is a global logger.
|
||||
@@ -0,0 +1,697 @@
|
||||
From 9554c5b29aff4107504bdf45c35582e6d9743a93 Mon Sep 17 00:00:00 2001
|
||||
From: sbwml <admin@cooluc.com>
|
||||
Date: Mon, 20 Apr 2026 23:06:17 +0800
|
||||
Subject: [PATCH] add auto-reload rule files on modification
|
||||
|
||||
Signed-off-by: sbwml <admin@cooluc.com>
|
||||
---
|
||||
pkg/utils/file_watcher.go | 73 +++++++++++++++
|
||||
plugin/data_provider/domain_set/domain_set.go | 71 +++++++++++---
|
||||
plugin/data_provider/ip_set/ip_set.go | 75 +++++++++++----
|
||||
plugin/executable/arbitrary/arbitrary.go | 81 +++++++++++-----
|
||||
plugin/executable/hosts/hosts.go | 87 ++++++++++++-----
|
||||
plugin/executable/redirect/redirect.go | 93 +++++++++++++------
|
||||
6 files changed, 374 insertions(+), 106 deletions(-)
|
||||
create mode 100644 pkg/utils/file_watcher.go
|
||||
|
||||
--- /dev/null
|
||||
+++ b/pkg/utils/file_watcher.go
|
||||
@@ -0,0 +1,73 @@
|
||||
+package utils
|
||||
+
|
||||
+import (
|
||||
+ "os"
|
||||
+ "time"
|
||||
+)
|
||||
+
|
||||
+// FileWatcher periodically checks the modification time of the specified files.
|
||||
+type FileWatcher struct {
|
||||
+ done chan struct{}
|
||||
+}
|
||||
+
|
||||
+// StartFileWatcher starts a new FileWatcher that checks files every 'interval'.
|
||||
+// If any file modification time changes or a file is missing, onChange is triggered.
|
||||
+// Note: onChange must be safe to run concurrently with other components.
|
||||
+func StartFileWatcher(files []string, interval time.Duration, onChange func(changedFiles []string)) *FileWatcher {
|
||||
+ if len(files) == 0 {
|
||||
+ return nil
|
||||
+ }
|
||||
+ fw := &FileWatcher{
|
||||
+ done: make(chan struct{}),
|
||||
+ }
|
||||
+ go func() {
|
||||
+ modTimes := make(map[string]time.Time)
|
||||
+ for _, f := range files {
|
||||
+ stat, err := os.Stat(f)
|
||||
+ if err == nil {
|
||||
+ modTimes[f] = stat.ModTime()
|
||||
+ }
|
||||
+ }
|
||||
+ ticker := time.NewTicker(interval)
|
||||
+ defer ticker.Stop()
|
||||
+
|
||||
+ for {
|
||||
+ select {
|
||||
+ case <-fw.done:
|
||||
+ return
|
||||
+ case <-ticker.C:
|
||||
+ var changedFiles []string
|
||||
+ for _, f := range files {
|
||||
+ stat, err := os.Stat(f)
|
||||
+ if err != nil {
|
||||
+ if os.IsNotExist(err) {
|
||||
+ // File is missing or deleted.
|
||||
+ if mt, ok := modTimes[f]; ok && !mt.IsZero() {
|
||||
+ modTimes[f] = time.Time{} // mark as missing
|
||||
+ changedFiles = append(changedFiles, f)
|
||||
+ }
|
||||
+ }
|
||||
+ continue
|
||||
+ }
|
||||
+
|
||||
+ if mt, ok := modTimes[f]; !ok || mt != stat.ModTime() {
|
||||
+ modTimes[f] = stat.ModTime()
|
||||
+ changedFiles = append(changedFiles, f)
|
||||
+ }
|
||||
+ }
|
||||
+ if len(changedFiles) > 0 {
|
||||
+ onChange(changedFiles)
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }()
|
||||
+ return fw
|
||||
+}
|
||||
+
|
||||
+// Close stops the FileWatcher.
|
||||
+func (fw *FileWatcher) Close() error {
|
||||
+ if fw != nil {
|
||||
+ close(fw.done)
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
--- a/plugin/data_provider/domain_set/domain_set.go
|
||||
+++ b/plugin/data_provider/domain_set/domain_set.go
|
||||
@@ -22,10 +22,15 @@ package domain_set
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
+ "os"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
+
|
||||
"github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
"github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
|
||||
- "os"
|
||||
+ "go.uber.org/zap"
|
||||
)
|
||||
|
||||
const PluginType = "domain_set"
|
||||
@@ -51,33 +56,69 @@ type Args struct {
|
||||
var _ data_provider.DomainMatcherProvider = (*DomainSet)(nil)
|
||||
|
||||
type DomainSet struct {
|
||||
- mg []domain.Matcher[struct{}]
|
||||
+ mg atomic.Pointer[MatcherGroup]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
func (d *DomainSet) GetDomainMatcher() domain.Matcher[struct{}] {
|
||||
- return MatcherGroup(d.mg)
|
||||
+ return d
|
||||
+}
|
||||
+
|
||||
+func (d *DomainSet) Match(s string) (struct{}, bool) {
|
||||
+ if mg := d.mg.Load(); mg != nil {
|
||||
+ return mg.Match(s)
|
||||
+ }
|
||||
+ return struct{}{}, false
|
||||
+}
|
||||
+
|
||||
+func (d *DomainSet) Close() error {
|
||||
+ if d.fw != nil {
|
||||
+ return d.fw.Close()
|
||||
+ }
|
||||
+ return nil
|
||||
}
|
||||
|
||||
// NewDomainSet inits a DomainSet from given args.
|
||||
func NewDomainSet(bp *coremain.BP, args *Args) (*DomainSet, error) {
|
||||
ds := &DomainSet{}
|
||||
|
||||
- m := domain.NewDomainMixMatcher()
|
||||
- if err := LoadExpsAndFiles(args.Exps, args.Files, m); err != nil {
|
||||
- return nil, err
|
||||
+ loadInner := func() (*MatcherGroup, error) {
|
||||
+ var mg MatcherGroup
|
||||
+ m := domain.NewDomainMixMatcher()
|
||||
+ if err := LoadExpsAndFiles(args.Exps, args.Files, m); err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ if m.Len() > 0 {
|
||||
+ mg = append(mg, m)
|
||||
+ }
|
||||
+
|
||||
+ for _, tag := range args.Sets {
|
||||
+ provider, _ := bp.M().GetPlugin(tag).(data_provider.DomainMatcherProvider)
|
||||
+ if provider == nil {
|
||||
+ return nil, fmt.Errorf("%s is not a DomainMatcherProvider", tag)
|
||||
+ }
|
||||
+ m := provider.GetDomainMatcher()
|
||||
+ mg = append(mg, m)
|
||||
+ }
|
||||
+ return &mg, nil
|
||||
}
|
||||
- if m.Len() > 0 {
|
||||
- ds.mg = append(ds.mg, m)
|
||||
+
|
||||
+ mg, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
}
|
||||
+ ds.mg.Store(mg)
|
||||
|
||||
- for _, tag := range args.Sets {
|
||||
- provider, _ := bp.M().GetPlugin(tag).(data_provider.DomainMatcherProvider)
|
||||
- if provider == nil {
|
||||
- return nil, fmt.Errorf("%s is not a DomainMatcherProvider", tag)
|
||||
- }
|
||||
- m := provider.GetDomainMatcher()
|
||||
- ds.mg = append(ds.mg, m)
|
||||
+ if len(args.Files) > 0 {
|
||||
+ ds.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newMg, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ ds.mg.Store(newMg)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
|
||||
+ }
|
||||
+ })
|
||||
}
|
||||
+
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
--- a/plugin/data_provider/ip_set/ip_set.go
|
||||
+++ b/plugin/data_provider/ip_set/ip_set.go
|
||||
@@ -22,12 +22,17 @@ package ip_set
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
- "github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
- "github.com/IrineSistiana/mosdns/v5/pkg/matcher/netlist"
|
||||
- "github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
+
|
||||
+ "github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/netlist"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
|
||||
+ "go.uber.org/zap"
|
||||
)
|
||||
|
||||
const PluginType = "ip_set"
|
||||
@@ -49,31 +54,67 @@ type Args struct {
|
||||
var _ data_provider.IPMatcherProvider = (*IPSet)(nil)
|
||||
|
||||
type IPSet struct {
|
||||
- mg []netlist.Matcher
|
||||
+ mg atomic.Pointer[MatcherGroup]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
func (d *IPSet) GetIPMatcher() netlist.Matcher {
|
||||
- return MatcherGroup(d.mg)
|
||||
+ return d
|
||||
+}
|
||||
+
|
||||
+func (d *IPSet) Match(addr netip.Addr) bool {
|
||||
+ if mg := d.mg.Load(); mg != nil {
|
||||
+ return mg.Match(addr)
|
||||
+ }
|
||||
+ return false
|
||||
+}
|
||||
+
|
||||
+func (d *IPSet) Close() error {
|
||||
+ if d.fw != nil {
|
||||
+ return d.fw.Close()
|
||||
+ }
|
||||
+ return nil
|
||||
}
|
||||
|
||||
func NewIPSet(bp *coremain.BP, args *Args) (*IPSet, error) {
|
||||
p := &IPSet{}
|
||||
|
||||
- l := netlist.NewList()
|
||||
- if err := LoadFromIPsAndFiles(args.IPs, args.Files, l); err != nil {
|
||||
- return nil, err
|
||||
+ loadInner := func() (*MatcherGroup, error) {
|
||||
+ var mg MatcherGroup
|
||||
+ l := netlist.NewList()
|
||||
+ if err := LoadFromIPsAndFiles(args.IPs, args.Files, l); err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ l.Sort()
|
||||
+ if l.Len() > 0 {
|
||||
+ mg = append(mg, l)
|
||||
+ }
|
||||
+ for _, tag := range args.Sets {
|
||||
+ provider, _ := bp.M().GetPlugin(tag).(data_provider.IPMatcherProvider)
|
||||
+ if provider == nil {
|
||||
+ return nil, fmt.Errorf("%s is not an IPMatcherProvider", tag)
|
||||
+ }
|
||||
+ mg = append(mg, provider.GetIPMatcher())
|
||||
+ }
|
||||
+ return &mg, nil
|
||||
}
|
||||
- l.Sort()
|
||||
- if l.Len() > 0 {
|
||||
- p.mg = append(p.mg, l)
|
||||
+
|
||||
+ mg, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
}
|
||||
- for _, tag := range args.Sets {
|
||||
- provider, _ := bp.M().GetPlugin(tag).(data_provider.IPMatcherProvider)
|
||||
- if provider == nil {
|
||||
- return nil, fmt.Errorf("%s is not an IPMatcherProvider", tag)
|
||||
- }
|
||||
- p.mg = append(p.mg, provider.GetIPMatcher())
|
||||
+ p.mg.Store(mg)
|
||||
+
|
||||
+ if len(args.Files) > 0 {
|
||||
+ p.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newMg, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ p.mg.Store(newMg)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
|
||||
+ }
|
||||
+ })
|
||||
}
|
||||
+
|
||||
return p, nil
|
||||
}
|
||||
|
||||
--- a/plugin/executable/arbitrary/arbitrary.go
|
||||
+++ b/plugin/executable/arbitrary/arbitrary.go
|
||||
@@ -23,12 +23,17 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
+ "os"
|
||||
+ "strings"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
+
|
||||
"github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/zone_file"
|
||||
"github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
|
||||
- "os"
|
||||
- "strings"
|
||||
+ "go.uber.org/zap"
|
||||
)
|
||||
|
||||
const PluginType = "arbitrary"
|
||||
@@ -45,38 +50,68 @@ type Args struct {
|
||||
var _ sequence.Executable = (*Arbitrary)(nil)
|
||||
|
||||
type Arbitrary struct {
|
||||
- m *zone_file.Matcher
|
||||
+ m atomic.Pointer[zone_file.Matcher]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
-func NewArbitrary(args *Args) (*Arbitrary, error) {
|
||||
- m := new(zone_file.Matcher)
|
||||
- for i, s := range args.Rules {
|
||||
- if err := m.Load(strings.NewReader(s)); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load rr #%d [%s], %w", i, s, err)
|
||||
- }
|
||||
- }
|
||||
- for i, file := range args.Files {
|
||||
- b, err := os.ReadFile(file)
|
||||
- if err != nil {
|
||||
- return nil, fmt.Errorf("failed to read file #%d [%s], %w", i, file, err)
|
||||
+func NewArbitrary(bp *coremain.BP, args *Args) (*Arbitrary, error) {
|
||||
+ a := &Arbitrary{}
|
||||
+
|
||||
+ loadInner := func() (*zone_file.Matcher, error) {
|
||||
+ m := new(zone_file.Matcher)
|
||||
+ for i, s := range args.Rules {
|
||||
+ if err := m.Load(strings.NewReader(s)); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load rr #%d [%s], %w", i, s, err)
|
||||
+ }
|
||||
}
|
||||
- if err := m.Load(bytes.NewReader(b)); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load rr file #%d [%s], %w", i, file, err)
|
||||
+ for i, file := range args.Files {
|
||||
+ b, err := os.ReadFile(file)
|
||||
+ if err != nil {
|
||||
+ return nil, fmt.Errorf("failed to read file #%d [%s], %w", i, file, err)
|
||||
+ }
|
||||
+ if err := m.Load(bytes.NewReader(b)); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load rr file #%d [%s], %w", i, file, err)
|
||||
+ }
|
||||
}
|
||||
+ return m, nil
|
||||
+ }
|
||||
+
|
||||
+ inner, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ a.m.Store(inner)
|
||||
+
|
||||
+ if len(args.Files) > 0 {
|
||||
+ a.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newInner, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ a.m.Store(newInner)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
|
||||
+ }
|
||||
+ })
|
||||
}
|
||||
- return &Arbitrary{
|
||||
- m: m,
|
||||
- }, nil
|
||||
+
|
||||
+ return a, nil
|
||||
}
|
||||
|
||||
func (a *Arbitrary) Exec(_ context.Context, qCtx *query_context.Context) error {
|
||||
- if r := a.m.Reply(qCtx.Q()); r != nil {
|
||||
- qCtx.SetResponse(r)
|
||||
+ if inner := a.m.Load(); inner != nil {
|
||||
+ if r := inner.Reply(qCtx.Q()); r != nil {
|
||||
+ qCtx.SetResponse(r)
|
||||
+ }
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
+func (a *Arbitrary) Close() error {
|
||||
+ if a.fw != nil {
|
||||
+ return a.fw.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
-func Init(_ *coremain.BP, v any) (any, error) {
|
||||
+func Init(bp *coremain.BP, v any) (any, error) {
|
||||
args := v.(*Args)
|
||||
- return NewArbitrary(args)
|
||||
+ return NewArbitrary(bp, args)
|
||||
}
|
||||
--- a/plugin/executable/hosts/hosts.go
|
||||
+++ b/plugin/executable/hosts/hosts.go
|
||||
@@ -23,13 +23,18 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
+ "os"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
+
|
||||
"github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/hosts"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
"github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
|
||||
"github.com/miekg/dns"
|
||||
- "os"
|
||||
+ "go.uber.org/zap"
|
||||
)
|
||||
|
||||
const PluginType = "hosts"
|
||||
@@ -46,44 +51,76 @@ type Args struct {
|
||||
}
|
||||
|
||||
type Hosts struct {
|
||||
- h *hosts.Hosts
|
||||
+ h atomic.Pointer[hosts.Hosts]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
-func Init(_ *coremain.BP, args any) (any, error) {
|
||||
- return NewHosts(args.(*Args))
|
||||
+func Init(bp *coremain.BP, args any) (any, error) {
|
||||
+ return NewHosts(bp, args.(*Args))
|
||||
}
|
||||
|
||||
-func NewHosts(args *Args) (*Hosts, error) {
|
||||
- m := domain.NewMixMatcher[*hosts.IPs]()
|
||||
- m.SetDefaultMatcher(domain.MatcherFull)
|
||||
- for i, entry := range args.Entries {
|
||||
- if err := domain.Load[*hosts.IPs](m, entry, hosts.ParseIPs); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load entry #%d %s, %w", i, entry, err)
|
||||
- }
|
||||
- }
|
||||
- for i, file := range args.Files {
|
||||
- b, err := os.ReadFile(file)
|
||||
- if err != nil {
|
||||
- return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
|
||||
+func NewHosts(bp *coremain.BP, args *Args) (*Hosts, error) {
|
||||
+ h := &Hosts{}
|
||||
+
|
||||
+ loadInner := func() (*hosts.Hosts, error) {
|
||||
+ m := domain.NewMixMatcher[*hosts.IPs]()
|
||||
+ m.SetDefaultMatcher(domain.MatcherFull)
|
||||
+ for i, entry := range args.Entries {
|
||||
+ if err := domain.Load[*hosts.IPs](m, entry, hosts.ParseIPs); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load entry #%d %s, %w", i, entry, err)
|
||||
+ }
|
||||
}
|
||||
- if err := domain.LoadFromTextReader[*hosts.IPs](m, bytes.NewReader(b), hosts.ParseIPs); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
|
||||
+ for i, file := range args.Files {
|
||||
+ b, err := os.ReadFile(file)
|
||||
+ if err != nil {
|
||||
+ return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
|
||||
+ }
|
||||
+ if err := domain.LoadFromTextReader[*hosts.IPs](m, bytes.NewReader(b), hosts.ParseIPs); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
|
||||
+ }
|
||||
}
|
||||
+ return hosts.NewHosts(m), nil
|
||||
+ }
|
||||
+
|
||||
+ inner, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
}
|
||||
+ h.h.Store(inner)
|
||||
|
||||
- return &Hosts{
|
||||
- h: hosts.NewHosts(m),
|
||||
- }, nil
|
||||
+ if len(args.Files) > 0 {
|
||||
+ h.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newInner, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ h.h.Store(newInner)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles))
|
||||
+ }
|
||||
+ })
|
||||
+ }
|
||||
+
|
||||
+ return h, nil
|
||||
}
|
||||
|
||||
func (h *Hosts) Response(q *dns.Msg) *dns.Msg {
|
||||
- return h.h.LookupMsg(q)
|
||||
+ if inner := h.h.Load(); inner != nil {
|
||||
+ return inner.LookupMsg(q)
|
||||
+ }
|
||||
+ return nil
|
||||
}
|
||||
|
||||
func (h *Hosts) Exec(_ context.Context, qCtx *query_context.Context) error {
|
||||
- r := h.h.LookupMsg(qCtx.Q())
|
||||
- if r != nil {
|
||||
- qCtx.SetResponse(r)
|
||||
+ if inner := h.h.Load(); inner != nil {
|
||||
+ r := inner.LookupMsg(qCtx.Q())
|
||||
+ if r != nil {
|
||||
+ qCtx.SetResponse(r)
|
||||
+ }
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
+func (h *Hosts) Close() error {
|
||||
+ if h.fw != nil {
|
||||
+ return h.fw.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
--- a/plugin/executable/redirect/redirect.go
|
||||
+++ b/plugin/executable/redirect/redirect.go
|
||||
@@ -25,10 +25,13 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
|
||||
"github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/query_context"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
"github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence"
|
||||
"github.com/miekg/dns"
|
||||
"go.uber.org/zap"
|
||||
@@ -48,11 +51,12 @@ type Args struct {
|
||||
}
|
||||
|
||||
type Redirect struct {
|
||||
- m *domain.MixMatcher[string]
|
||||
+ m atomic.Pointer[domain.MixMatcher[string]]
|
||||
+ fw *utils.FileWatcher
|
||||
}
|
||||
|
||||
func Init(bp *coremain.BP, args any) (any, error) {
|
||||
- r, err := NewRedirect(args.(*Args))
|
||||
+ r, err := NewRedirect(bp, args.(*Args))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -60,7 +64,8 @@ func Init(bp *coremain.BP, args any) (an
|
||||
return r, nil
|
||||
}
|
||||
|
||||
-func NewRedirect(args *Args) (*Redirect, error) {
|
||||
+func NewRedirect(bp *coremain.BP, args *Args) (*Redirect, error) {
|
||||
+ r := &Redirect{}
|
||||
parseFunc := func(s string) (p, v string, err error) {
|
||||
f := strings.Fields(s)
|
||||
if len(f) != 2 {
|
||||
@@ -68,23 +73,44 @@ func NewRedirect(args *Args) (*Redirect,
|
||||
}
|
||||
return f[0], dns.Fqdn(f[1]), nil
|
||||
}
|
||||
- m := domain.NewMixMatcher[string]()
|
||||
- m.SetDefaultMatcher(domain.MatcherFull)
|
||||
- for i, rule := range args.Rules {
|
||||
- if err := domain.Load[string](m, rule, parseFunc); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load rule #%d %s, %w", i, rule, err)
|
||||
- }
|
||||
- }
|
||||
- for i, file := range args.Files {
|
||||
- b, err := os.ReadFile(file)
|
||||
- if err != nil {
|
||||
- return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
|
||||
+
|
||||
+ loadInner := func() (*domain.MixMatcher[string], error) {
|
||||
+ m := domain.NewMixMatcher[string]()
|
||||
+ m.SetDefaultMatcher(domain.MatcherFull)
|
||||
+ for i, rule := range args.Rules {
|
||||
+ if err := domain.Load[string](m, rule, parseFunc); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load rule #%d %s, %w", i, rule, err)
|
||||
+ }
|
||||
}
|
||||
- if err := domain.LoadFromTextReader[string](m, bytes.NewReader(b), parseFunc); err != nil {
|
||||
- return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
|
||||
+ for i, file := range args.Files {
|
||||
+ b, err := os.ReadFile(file)
|
||||
+ if err != nil {
|
||||
+ return nil, fmt.Errorf("failed to read file #%d %s, %w", i, file, err)
|
||||
+ }
|
||||
+ if err := domain.LoadFromTextReader[string](m, bytes.NewReader(b), parseFunc); err != nil {
|
||||
+ return nil, fmt.Errorf("failed to load file #%d %s, %w", i, file, err)
|
||||
+ }
|
||||
}
|
||||
+ return m, nil
|
||||
+ }
|
||||
+
|
||||
+ inner, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ r.m.Store(inner)
|
||||
+
|
||||
+ if len(args.Files) > 0 {
|
||||
+ r.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newInner, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ r.m.Store(newInner)
|
||||
+ bp.L().Info("reloaded files", zap.Strings("files", changedFiles), zap.Int("length", newInner.Len()))
|
||||
+ }
|
||||
+ })
|
||||
}
|
||||
- return &Redirect{m: m}, nil
|
||||
+
|
||||
+ return r, nil
|
||||
}
|
||||
|
||||
func (r *Redirect) Exec(ctx context.Context, qCtx *query_context.Context, next sequence.ChainWalker) error {
|
||||
@@ -94,7 +120,12 @@ func (r *Redirect) Exec(ctx context.Cont
|
||||
}
|
||||
|
||||
orgQName := q.Question[0].Name
|
||||
- redirectTarget, ok := r.m.Match(orgQName)
|
||||
+ inner := r.m.Load()
|
||||
+ if inner == nil {
|
||||
+ return next.ExecNext(ctx, qCtx)
|
||||
+ }
|
||||
+
|
||||
+ redirectTarget, ok := inner.Match(orgQName)
|
||||
if !ok {
|
||||
return next.ExecNext(ctx, qCtx)
|
||||
}
|
||||
@@ -104,16 +135,16 @@ func (r *Redirect) Exec(ctx context.Cont
|
||||
q.Question[0].Name = orgQName
|
||||
}()
|
||||
err := next.ExecNext(ctx, qCtx)
|
||||
- if r := qCtx.R(); r != nil {
|
||||
+ if rResp := qCtx.R(); rResp != nil {
|
||||
// Restore original query name.
|
||||
- for i := range r.Question {
|
||||
- if r.Question[i].Name == redirectTarget {
|
||||
- r.Question[i].Name = orgQName
|
||||
+ for i := range rResp.Question {
|
||||
+ if rResp.Question[i].Name == redirectTarget {
|
||||
+ rResp.Question[i].Name = orgQName
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a CNAME record.
|
||||
- newAns := make([]dns.RR, 1, len(r.Answer)+1)
|
||||
+ newAns := make([]dns.RR, 1, len(rResp.Answer)+1)
|
||||
newAns[0] = &dns.CNAME{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: orgQName,
|
||||
@@ -123,12 +154,22 @@ func (r *Redirect) Exec(ctx context.Cont
|
||||
},
|
||||
Target: redirectTarget,
|
||||
}
|
||||
- newAns = append(newAns, r.Answer...)
|
||||
- r.Answer = newAns
|
||||
+ newAns = append(newAns, rResp.Answer...)
|
||||
+ rResp.Answer = newAns
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Redirect) Len() int {
|
||||
- return r.m.Len()
|
||||
+ if inner := r.m.Load(); inner != nil {
|
||||
+ return inner.Len()
|
||||
+ }
|
||||
+ return 0
|
||||
+}
|
||||
+
|
||||
+func (r *Redirect) Close() error {
|
||||
+ if r.fw != nil {
|
||||
+ return r.fw.Close()
|
||||
+ }
|
||||
+ return nil
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
From 64614017e674960638bac87b4a2f9c0aea0065e6 Mon Sep 17 00:00:00 2001
|
||||
From: sbwml <admin@cooluc.com>
|
||||
Date: Fri, 1 May 2026 01:35:59 +0800
|
||||
Subject: [PATCH] add adblock_set database plugin
|
||||
|
||||
Signed-off-by: sbwml <admin@cooluc.com>
|
||||
---
|
||||
.../data_provider/adblock_set/adblock_set.go | 122 ++++++++++++++++++
|
||||
.../adblock_set/adblock_set_test.go | 107 +++++++++++++++
|
||||
plugin/data_provider/adblock_set/matcher.go | 43 ++++++
|
||||
plugin/data_provider/adblock_set/parser.go | 115 +++++++++++++++++
|
||||
plugin/enabled_plugins.go | 1 +
|
||||
5 files changed, 388 insertions(+)
|
||||
create mode 100644 plugin/data_provider/adblock_set/adblock_set.go
|
||||
create mode 100644 plugin/data_provider/adblock_set/adblock_set_test.go
|
||||
create mode 100644 plugin/data_provider/adblock_set/matcher.go
|
||||
create mode 100644 plugin/data_provider/adblock_set/parser.go
|
||||
|
||||
--- /dev/null
|
||||
+++ b/plugin/data_provider/adblock_set/adblock_set.go
|
||||
@@ -0,0 +1,122 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025, IrineSistiana
|
||||
+ *
|
||||
+ * This file is part of mosdns.
|
||||
+ *
|
||||
+ * mosdns is free software: you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License as published by
|
||||
+ * the Free Software Foundation, either version 3 of the License, or
|
||||
+ * (at your option) any later version.
|
||||
+ *
|
||||
+ * mosdns is distributed in the hope that it will be useful,
|
||||
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+ * GNU General Public License for more details.
|
||||
+ *
|
||||
+ * You should have received a copy of the GNU General Public License
|
||||
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
+ */
|
||||
+
|
||||
+package adblock_set
|
||||
+
|
||||
+import (
|
||||
+ "fmt"
|
||||
+ "os"
|
||||
+ "sync/atomic"
|
||||
+ "time"
|
||||
+
|
||||
+ "github.com/IrineSistiana/mosdns/v5/coremain"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
||||
+ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
|
||||
+ "go.uber.org/zap"
|
||||
+)
|
||||
+
|
||||
+const PluginType = "adblock_set"
|
||||
+
|
||||
+func init() {
|
||||
+ coremain.RegNewPluginFunc(PluginType, Init, func() any { return new(Args) })
|
||||
+}
|
||||
+
|
||||
+func Init(bp *coremain.BP, args any) (any, error) {
|
||||
+ m, err := NewAdblockSet(bp, args.(*Args))
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ return m, nil
|
||||
+}
|
||||
+
|
||||
+type Args struct {
|
||||
+ Files []string `yaml:"files"`
|
||||
+}
|
||||
+
|
||||
+var _ data_provider.DomainMatcherProvider = (*AdblockSet)(nil)
|
||||
+
|
||||
+type AdblockSet struct {
|
||||
+ matcher atomic.Pointer[AdblockMatcher]
|
||||
+ fw *utils.FileWatcher
|
||||
+}
|
||||
+
|
||||
+func (d *AdblockSet) GetDomainMatcher() domain.Matcher[struct{}] {
|
||||
+ return d
|
||||
+}
|
||||
+
|
||||
+func (d *AdblockSet) Match(s string) (struct{}, bool) {
|
||||
+ if m := d.matcher.Load(); m != nil {
|
||||
+ return m.Match(s)
|
||||
+ }
|
||||
+ return struct{}{}, false
|
||||
+}
|
||||
+
|
||||
+func (d *AdblockSet) Close() error {
|
||||
+ if d.fw != nil {
|
||||
+ return d.fw.Close()
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
+func NewAdblockSet(bp *coremain.BP, args *Args) (*AdblockSet, error) {
|
||||
+ ds := &AdblockSet{}
|
||||
+
|
||||
+ loadInner := func() (*AdblockMatcher, error) {
|
||||
+ m := &AdblockMatcher{
|
||||
+ blacklist: domain.NewMixMatcher[struct{}](),
|
||||
+ whitelist: domain.NewMixMatcher[struct{}](),
|
||||
+ }
|
||||
+ for _, f := range args.Files {
|
||||
+ if len(f) == 0 {
|
||||
+ continue
|
||||
+ }
|
||||
+ file, err := os.Open(f)
|
||||
+ if err != nil {
|
||||
+ return nil, fmt.Errorf("failed to open file %s: %w", f, err)
|
||||
+ }
|
||||
+ if err := ParseRules(file, m.blacklist, m.whitelist); err != nil {
|
||||
+ file.Close()
|
||||
+ return nil, fmt.Errorf("failed to parse rules from %s: %w", f, err)
|
||||
+ }
|
||||
+ file.Close()
|
||||
+ }
|
||||
+ return m, nil
|
||||
+ }
|
||||
+
|
||||
+ m, err := loadInner()
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ ds.matcher.Store(m)
|
||||
+
|
||||
+ if len(args.Files) > 0 {
|
||||
+ ds.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
|
||||
+ newM, err := loadInner()
|
||||
+ if err == nil {
|
||||
+ ds.matcher.Store(newM)
|
||||
+ bp.L().Info("reloaded adblock files", zap.Strings("files", changedFiles))
|
||||
+ } else {
|
||||
+ bp.L().Error("failed to reload adblock files", zap.Error(err))
|
||||
+ }
|
||||
+ })
|
||||
+ }
|
||||
+
|
||||
+ return ds, nil
|
||||
+}
|
||||
--- /dev/null
|
||||
+++ b/plugin/data_provider/adblock_set/adblock_set_test.go
|
||||
@@ -0,0 +1,107 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025, IrineSistiana
|
||||
+ *
|
||||
+ * This file is part of mosdns.
|
||||
+ *
|
||||
+ * mosdns is free software: you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License as published by
|
||||
+ * the Free Software Foundation, either version 3 of the License, or
|
||||
+ * (at your option) any later version.
|
||||
+ *
|
||||
+ * mosdns is distributed in the hope that it will be useful,
|
||||
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+ * GNU General Public License for more details.
|
||||
+ *
|
||||
+ * You should have received a copy of the GNU General Public License
|
||||
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
+ */
|
||||
+
|
||||
+package adblock_set
|
||||
+
|
||||
+import (
|
||||
+ "strings"
|
||||
+ "testing"
|
||||
+
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
+ "github.com/stretchr/testify/assert"
|
||||
+)
|
||||
+
|
||||
+func TestParseAndMatch(t *testing.T) {
|
||||
+ rules := `
|
||||
+! Comment
|
||||
+# Another comment
|
||||
+||example.com^
|
||||
+@@||whitelist.com^
|
||||
+|exact.com|
|
||||
+@@|white-exact.com|
|
||||
+/.*regex.*/
|
||||
+@@/.*white-regex.*/
|
||||
+*keyword*
|
||||
+@@*white-keyword*
|
||||
+blocked.com
|
||||
+domain:ntp.org
|
||||
+full:metrics.icloud.com
|
||||
+`
|
||||
+ blacklist := domain.NewMixMatcher[struct{}]()
|
||||
+ whitelist := domain.NewMixMatcher[struct{}]()
|
||||
+
|
||||
+ err := ParseRules(strings.NewReader(rules), blacklist, whitelist)
|
||||
+ assert.NoError(t, err)
|
||||
+
|
||||
+ matcher := &AdblockMatcher{
|
||||
+ blacklist: blacklist,
|
||||
+ whitelist: whitelist,
|
||||
+ }
|
||||
+
|
||||
+ tests := []struct {
|
||||
+ domain string
|
||||
+ want bool
|
||||
+ }{
|
||||
+ {"example.com", true},
|
||||
+ {"sub.example.com", true},
|
||||
+ {"whitelist.com", false},
|
||||
+ {"sub.whitelist.com", false},
|
||||
+ {"exact.com", true},
|
||||
+ {"sub.exact.com", false}, // |exact.com| is exact match
|
||||
+ {"white-exact.com", false},
|
||||
+ {"myregex123", true},
|
||||
+ {"mywhite-regex123", false},
|
||||
+ {"somekeywordhere", true},
|
||||
+ {"some-white-keyword-here", false},
|
||||
+ {"blocked.com", true},
|
||||
+ {"other.com", false},
|
||||
+ {"ntp.org", true}, // matched by domain:ntp.org
|
||||
+ {"sub.ntp.org", true}, // matched by domain:ntp.org
|
||||
+ {"metrics.icloud.com", true}, // matched by full:metrics.icloud.com
|
||||
+ {"sub.metrics.icloud.com", false}, // full match only
|
||||
+ }
|
||||
+
|
||||
+ for _, tt := range tests {
|
||||
+ _, ok := matcher.Match(tt.domain)
|
||||
+ assert.Equal(t, tt.want, ok, "Match(%s)", tt.domain)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func TestStripOptions(t *testing.T) {
|
||||
+ rules := `
|
||||
+||example.com^$important,third-party
|
||||
+@@||whitelist.com^$dnstype=A
|
||||
+`
|
||||
+ blacklist := domain.NewMixMatcher[struct{}]()
|
||||
+ whitelist := domain.NewMixMatcher[struct{}]()
|
||||
+
|
||||
+ err := ParseRules(strings.NewReader(rules), blacklist, whitelist)
|
||||
+ assert.NoError(t, err)
|
||||
+
|
||||
+ matcher := &AdblockMatcher{
|
||||
+ blacklist: blacklist,
|
||||
+ whitelist: whitelist,
|
||||
+ }
|
||||
+
|
||||
+ _, ok := matcher.Match("example.com")
|
||||
+ assert.True(t, ok)
|
||||
+
|
||||
+ _, ok = matcher.Match("whitelist.com")
|
||||
+ assert.False(t, ok)
|
||||
+}
|
||||
--- /dev/null
|
||||
+++ b/plugin/data_provider/adblock_set/matcher.go
|
||||
@@ -0,0 +1,43 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025, IrineSistiana
|
||||
+ *
|
||||
+ * This file is part of mosdns.
|
||||
+ *
|
||||
+ * mosdns is free software: you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License as published by
|
||||
+ * the Free Software Foundation, either version 3 of the License, or
|
||||
+ * (at your option) any later version.
|
||||
+ *
|
||||
+ * mosdns is distributed in the hope that it will be useful,
|
||||
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+ * GNU General Public License for more details.
|
||||
+ *
|
||||
+ * You should have received a copy of the GNU General Public License
|
||||
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
+ */
|
||||
+
|
||||
+package adblock_set
|
||||
+
|
||||
+import (
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
+)
|
||||
+
|
||||
+type AdblockMatcher struct {
|
||||
+ blacklist *domain.MixMatcher[struct{}]
|
||||
+ whitelist *domain.MixMatcher[struct{}]
|
||||
+}
|
||||
+
|
||||
+func (m *AdblockMatcher) Match(s string) (struct{}, bool) {
|
||||
+ // 1. Check whitelist. If matched, it's NOT a hit for the adblock set.
|
||||
+ if _, ok := m.whitelist.Match(s); ok {
|
||||
+ return struct{}{}, false
|
||||
+ }
|
||||
+
|
||||
+ // 2. Check blacklist.
|
||||
+ if _, ok := m.blacklist.Match(s); ok {
|
||||
+ return struct{}{}, true
|
||||
+ }
|
||||
+
|
||||
+ return struct{}{}, false
|
||||
+}
|
||||
--- /dev/null
|
||||
+++ b/plugin/data_provider/adblock_set/parser.go
|
||||
@@ -0,0 +1,115 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025, IrineSistiana
|
||||
+ *
|
||||
+ * This file is part of mosdns.
|
||||
+ *
|
||||
+ * mosdns is free software: you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License as published by
|
||||
+ * the Free Software Foundation, either version 3 of the License, or
|
||||
+ * (at your option) any later version.
|
||||
+ *
|
||||
+ * mosdns is distributed in the hope that it will be useful,
|
||||
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+ * GNU General Public License for more details.
|
||||
+ *
|
||||
+ * You should have received a copy of the GNU General Public License
|
||||
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
+ */
|
||||
+
|
||||
+package adblock_set
|
||||
+
|
||||
+import (
|
||||
+ "bufio"
|
||||
+ "io"
|
||||
+ "strings"
|
||||
+
|
||||
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
+)
|
||||
+
|
||||
+// ParseRules reads AdBlock rules from r and adds them to blacklist and whitelist matchers.
|
||||
+func ParseRules(r io.Reader, blacklist, whitelist *domain.MixMatcher[struct{}]) error {
|
||||
+ scanner := bufio.NewScanner(r)
|
||||
+ for scanner.Scan() {
|
||||
+ line := strings.TrimSpace(scanner.Text())
|
||||
+ if line == "" || line[0] == '!' || line[0] == '#' {
|
||||
+ continue
|
||||
+ }
|
||||
+
|
||||
+ // Handle whitelists
|
||||
+ isWhitelist := false
|
||||
+ if strings.HasPrefix(line, "@@") {
|
||||
+ isWhitelist = true
|
||||
+ line = line[2:]
|
||||
+ }
|
||||
+
|
||||
+ // Handle comments in rules (e.g., rule # comment)
|
||||
+ // But in AdBlock, # is usually for element hiding (##) or CSS selectors.
|
||||
+ // For DNS rules, we ignore anything starting with #.
|
||||
+ if strings.Contains(line, "##") || strings.Contains(line, "#?#") || strings.Contains(line, "#$#") {
|
||||
+ continue
|
||||
+ }
|
||||
+
|
||||
+ // Strip options (e.g., $important, $third-party)
|
||||
+ if idx := strings.LastIndex(line, "$"); idx != -1 {
|
||||
+ // Check if it's a regex or something else.
|
||||
+ // In ABP, $ marks the start of options.
|
||||
+ line = line[:idx]
|
||||
+ }
|
||||
+
|
||||
+ if line == "" {
|
||||
+ continue
|
||||
+ }
|
||||
+
|
||||
+ target := blacklist
|
||||
+ if isWhitelist {
|
||||
+ target = whitelist
|
||||
+ }
|
||||
+
|
||||
+ if err := parseAndAddRule(line, target); err != nil {
|
||||
+ // Skip invalid rules but maybe we should log them?
|
||||
+ continue
|
||||
+ }
|
||||
+ }
|
||||
+ return scanner.Err()
|
||||
+}
|
||||
+
|
||||
+func parseAndAddRule(rule string, m *domain.MixMatcher[struct{}]) error {
|
||||
+ // 0. Native MosDNS rules: type:pattern
|
||||
+ if strings.Contains(rule, ":") {
|
||||
+ typ, _, _ := strings.Cut(rule, ":")
|
||||
+ switch typ {
|
||||
+ case "full", "domain", "regexp", "keyword":
|
||||
+ return m.Add(rule, struct{}{})
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // 1. Regex: /regexp/
|
||||
+ if strings.HasPrefix(rule, "/") && strings.HasSuffix(rule, "/") && len(rule) > 2 {
|
||||
+ return m.Add("regexp:"+rule[1:len(rule)-1], struct{}{})
|
||||
+ }
|
||||
+
|
||||
+ // 2. Subdomain: ||example.com^
|
||||
+ if strings.HasPrefix(rule, "||") {
|
||||
+ domainStr := rule[2:]
|
||||
+ if strings.HasSuffix(domainStr, "^") {
|
||||
+ domainStr = domainStr[:len(domainStr)-1]
|
||||
+ }
|
||||
+ return m.Add("domain:"+domainStr, struct{}{})
|
||||
+ }
|
||||
+
|
||||
+ // 3. Exact match: |example.com|
|
||||
+ if strings.HasPrefix(rule, "|") && strings.HasSuffix(rule, "|") && len(rule) > 2 {
|
||||
+ return m.Add("full:"+rule[1:len(rule)-1], struct{}{})
|
||||
+ }
|
||||
+
|
||||
+ // 4. Keyword: *keyword*
|
||||
+ if strings.HasPrefix(rule, "*") && strings.HasSuffix(rule, "*") && len(rule) > 2 {
|
||||
+ return m.Add("keyword:"+rule[1:len(rule)-1], struct{}{})
|
||||
+ }
|
||||
+
|
||||
+ // 5. Default: treat as exact match if it looks like a domain
|
||||
+ // Many lists are just a list of domains.
|
||||
+ // AdGuard Home treats rules without || or ^ as exact matches.
|
||||
+ return m.Add("full:"+rule, struct{}{})
|
||||
+}
|
||||
--- a/plugin/enabled_plugins.go
|
||||
+++ b/plugin/enabled_plugins.go
|
||||
@@ -22,6 +22,7 @@ package plugin
|
||||
// data providers
|
||||
import (
|
||||
// data provider
|
||||
+ _ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/adblock_set"
|
||||
_ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/domain_set"
|
||||
_ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/ip_set"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
From 8f9c6ca1ac8e9b5e10a5c45f3f338546424c95f1 Mon Sep 17 00:00:00 2001
|
||||
From: sbwml <admin@cooluc.com>
|
||||
Date: Sat, 2 May 2026 13:43:31 +0800
|
||||
Subject: [PATCH] feat(adblock_set): support hosts file format blocking rules
|
||||
|
||||
Parse lines like "0.0.0.0 domain" and "127.0.0.1 domain" as exact-match
|
||||
blocking rules, compatible with AdGuard Home hosts-style lists.
|
||||
|
||||
Signed-off-by: sbwml <admin@cooluc.com>
|
||||
---
|
||||
plugin/data_provider/adblock_set/adblock_set_test.go | 6 ++++++
|
||||
plugin/data_provider/adblock_set/parser.go | 12 ++++++++++++
|
||||
2 files changed, 18 insertions(+)
|
||||
|
||||
--- a/plugin/data_provider/adblock_set/adblock_set_test.go
|
||||
+++ b/plugin/data_provider/adblock_set/adblock_set_test.go
|
||||
@@ -42,6 +42,8 @@ func TestParseAndMatch(t *testing.T) {
|
||||
blocked.com
|
||||
domain:ntp.org
|
||||
full:metrics.icloud.com
|
||||
+0.0.0.0 singlehotmilf.online
|
||||
+127.0.0.1 deezer.deliveryengine.adswizz.com
|
||||
`
|
||||
blacklist := domain.NewMixMatcher[struct{}]()
|
||||
whitelist := domain.NewMixMatcher[struct{}]()
|
||||
@@ -75,6 +77,10 @@ full:metrics.icloud.com
|
||||
{"sub.ntp.org", true}, // matched by domain:ntp.org
|
||||
{"metrics.icloud.com", true}, // matched by full:metrics.icloud.com
|
||||
{"sub.metrics.icloud.com", false}, // full match only
|
||||
+ {"singlehotmilf.online", true}, // hosts: 0.0.0.0
|
||||
+ {"sub.singlehotmilf.online", false}, // hosts: exact match only
|
||||
+ {"deezer.deliveryengine.adswizz.com", true}, // hosts: 127.0.0.1
|
||||
+ {"sub.deezer.deliveryengine.adswizz.com", false}, // hosts: exact match only
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
--- a/plugin/data_provider/adblock_set/parser.go
|
||||
+++ b/plugin/data_provider/adblock_set/parser.go
|
||||
@@ -22,6 +22,7 @@ package adblock_set
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
+ "net"
|
||||
"strings"
|
||||
|
||||
"github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
|
||||
@@ -36,6 +37,17 @@ func ParseRules(r io.Reader, blacklist,
|
||||
continue
|
||||
}
|
||||
|
||||
+ // Handle hosts file format: "0.0.0.0 domain" or "127.0.0.1 domain"
|
||||
+ if fields := strings.Fields(line); len(fields) >= 2 {
|
||||
+ if ip := net.ParseIP(fields[0]); ip != nil {
|
||||
+ host := fields[1]
|
||||
+ if host != "" && host != "localhost" {
|
||||
+ _ = blacklist.Add("full:"+host, struct{}{})
|
||||
+ }
|
||||
+ continue
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
// Handle whitelists
|
||||
isWhitelist := false
|
||||
if strings.HasPrefix(line, "@@") {
|
||||
Reference in new issue
Block a user