├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.yml
│ ├── feature_request.md
│ └── other-questions.md
└── workflows
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── coremain
├── config.go
├── interface.go
├── mosdns.go
├── register.go
├── run.go
├── server.go
└── service.go
├── go.mod
├── go.sum
├── main.go
├── mlog
└── logger.go
├── pkg
├── bundled_upstream
│ └── bundled_upstream.go
├── cache
│ ├── cache.go
│ ├── mem_cache
│ │ ├── mem_cache.go
│ │ └── mem_cache_test.go
│ └── redis_cache
│ │ ├── redis_cache.go
│ │ └── redis_cache_test.go
├── concurrent_limiter
│ ├── client_limiter.go
│ └── client_limiter_test.go
├── concurrent_lru
│ ├── concurrent_lru.go
│ └── concurrent_lru_test.go
├── concurrent_map
│ ├── concurrent_map.go
│ └── concurrent_map_test.go
├── data_provider
│ └── data_provider.go
├── dnsutils
│ ├── edns0.go
│ ├── edns0_ecs.go
│ ├── edns0_padding.go
│ ├── edns0_padding_test.go
│ ├── msg.go
│ └── net_io.go
├── executable_seq
│ ├── executable_sequence.go
│ ├── executable_sequence_test.go
│ ├── fallback.go
│ ├── fallback_test.go
│ ├── if_node.go
│ ├── iface.go
│ ├── load_balance.go
│ ├── parallel.go
│ ├── parallel_test.go
│ └── utils.go
├── ext_exec
│ ├── exec.go
│ └── exec_test.go
├── hosts
│ ├── hosts.go
│ └── hosts_test.go
├── ip_observer
│ └── observer.go
├── list
│ ├── elem.go
│ ├── list.go
│ └── list_test.go
├── lru
│ ├── lru.go
│ └── lru_test.go
├── matcher
│ ├── domain
│ │ ├── interface.go
│ │ ├── load_helper.go
│ │ ├── load_helper_test.go
│ │ ├── matcher.go
│ │ ├── matcher_test.go
│ │ ├── utils.go
│ │ └── utils_test.go
│ ├── elem
│ │ ├── rr_type.go
│ │ └── rr_type_test.go
│ ├── msg_matcher
│ │ ├── query.go
│ │ ├── query_test.go
│ │ ├── response.go
│ │ └── response_test.go
│ ├── netlist
│ │ ├── interface.go
│ │ ├── list.go
│ │ ├── load_helper.go
│ │ └── netlist_test.go
│ └── v2data
│ │ ├── data.pb.go
│ │ └── data.proto
├── nftset_utils
│ ├── handler.go
│ └── handler_test.go
├── pool
│ ├── allocator.go
│ ├── allocator_test.go
│ ├── bytes_buf.go
│ ├── msg_buf.go
│ ├── msg_buf_test.go
│ └── timer.go
├── query_context
│ └── context.go
├── safe_close
│ └── safe_close.go
├── server
│ ├── dns_handler
│ │ └── entry_handler.go
│ ├── doh.go
│ ├── dot.go
│ ├── http_handler
│ │ └── handler.go
│ ├── server.go
│ ├── server_test.go
│ ├── tcp.go
│ ├── testdata
│ │ ├── test.test.cert
│ │ └── test.test.key
│ ├── udp.go
│ ├── udp_linux.go
│ └── udp_others.go
├── upstream
│ ├── bootstrap
│ │ └── bootstrap.go
│ ├── doh
│ │ └── upstream.go
│ ├── h3roundtripper
│ │ └── h3roundtripper.go
│ ├── transport
│ │ ├── transport.go
│ │ ├── transport_test.go
│ │ └── utils.go
│ ├── upstream.go
│ ├── upstream_test.go
│ ├── utils.go
│ ├── utils_others.go
│ └── utils_unix.go
├── utils
│ ├── config_helper.go
│ ├── errors.go
│ ├── net.go
│ ├── ptr_parser.go
│ ├── ptr_parser_test.go
│ ├── strings.go
│ ├── utils.go
│ └── utils_test.go
└── zone_file
│ ├── zone_file.go
│ └── zone_file_test.go
├── plugin
├── enabled_plugin.go
├── enabled_plugin_test.go
├── executable
│ ├── arbitrary
│ │ └── arbitrary.go
│ ├── blackhole
│ │ ├── blackhole.go
│ │ └── blackhole_test.go
│ ├── bufsize
│ │ └── bufsize.go
│ ├── cache
│ │ └── cache.go
│ ├── client_limiter
│ │ └── client_limiter.go
│ ├── dual_selector
│ │ ├── dual_selector.go
│ │ └── dual_selector_test.go
│ ├── ecs
│ │ ├── ecs.go
│ │ └── ecs_test.go
│ ├── edns0_filter
│ │ └── filter.go
│ ├── fast_forward
│ │ ├── fast_forward.go
│ │ └── udpme.go
│ ├── forward
│ │ └── forward.go
│ ├── hosts
│ │ └── hosts.go
│ ├── ipset
│ │ ├── ipset.go
│ │ ├── ipset_linux.go
│ │ └── ipset_other.go
│ ├── marker
│ │ └── marker.go
│ ├── metrics_collector
│ │ └── collector.go
│ ├── misc_optm
│ │ └── misc.go
│ ├── nftset
│ │ ├── nftset.go
│ │ ├── nftset_linux.go
│ │ └── nftset_other.go
│ ├── padding
│ │ └── padding.go
│ ├── query_summary
│ │ └── query_summary.go
│ ├── redirect
│ │ └── redirect.go
│ ├── reverse_lookup
│ │ └── reverse_lookup.go
│ ├── sequence
│ │ └── sequence.go
│ ├── sleep
│ │ └── sleep.go
│ └── ttl
│ │ └── ttl.go
└── matcher
│ ├── query_matcher
│ └── query_matcher.go
│ └── response_matcher
│ └── response_matcher.go
├── release.py
├── scripts
├── openwrt
│ └── mosdns-init-openwrt
└── update_chn_ip_domain.py
└── tools
├── config.go
├── init.go
├── probe.go
└── v2dat.go
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.yml:
--------------------------------------------------------------------------------
1 | name: "Bug report"
2 | description: "[反馈 Bug 必须用此模板] 程序不能运行?或者没有按照期望的那样工作?"
3 | title: "[Bug] "
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: "感谢热心的反馈 bug。描述越详细越有助于定位和解决 Bug。不提供有效信息的反馈可能会被直接关闭。"
8 |
9 | - type: checkboxes
10 | id: pre-check
11 | attributes:
12 | label: "在提交之前,请确认"
13 | options:
14 | - label: "我已经尝试搜索过 Issue ,但没有找到相关问题。"
15 | required: true
16 | - label: "我正在使用最新的 mosdns 版本(或者最新的 commit),问题依旧存在。"
17 | required: true
18 | - label: "我仔细看过 wiki 后仍然无法自行解决该问题。"
19 | required: true
20 | - label: "我非常确定这是 mosdns 核心的问题。(如果是通过第三方衍生软件使用 mosdns
21 | 核心,不确定问题源头时,请先向衍生软件开发者提交问题。)"
22 | required: true
23 |
24 | - type: input
25 | id: version
26 | attributes:
27 | label: mosdns 版本
28 | description: "不清楚可用 `mosdns version` 查看。"
29 | placeholder: v9.9.9
30 | validations:
31 | required: true
32 |
33 | - type: input
34 | id: system
35 | attributes:
36 | label: 操作系统
37 | placeholder: ubuntu
38 | validations:
39 | required: true
40 |
41 | - type: textarea
42 | id: what-happened
43 | attributes:
44 | label: Bug 描述和复现步骤
45 | description: "描述越详细越有助于定位和解决 Bug。"
46 | placeholder: "示例: 使用 dig www.google.com 发送请求,mosdns 会崩溃。"
47 | validations:
48 | required: true
49 |
50 | - type: textarea
51 | id: config
52 | attributes:
53 | label: 使用的配置文件
54 | render: yaml
55 | description: "必须是完整的配置文件。不要只复制某个插件的配置片段。"
56 | validations:
57 | required: true
58 |
59 | - type: textarea
60 | id: log
61 | attributes:
62 | label: mosdns 的 log 记录
63 | render: txt
64 | description: "请复制连续的几十行 log,不要只复制一行。大部分情况一行 log 提供的信息有限。建议用 `debug` 级别的 log。"
65 |
66 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: 希望添加新功能
4 | title: "[Feature request]"
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **希望添加的功能**
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/other-questions.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Other questions
3 | about: 不要在 Issue 里提问。有问题请进入 Discussions 讨论。
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | 不要在 issue 里提问。有问题请进入 Discussions 讨论。
11 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release mosdns
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 |
10 | build-release:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - name: Set up Go
16 | uses: actions/setup-go@v3
17 | with:
18 | go-version: 1.19
19 | check-latest: true
20 | cache: true
21 |
22 | - name: Test
23 | run: go test -race -v ./...
24 |
25 | - name: Set up Python
26 | uses: actions/setup-python@v2
27 | with:
28 | python-version: '3.8'
29 |
30 | - name: Build
31 | run: python ./release.py
32 | env:
33 | CGO_ENABLED: '0'
34 |
35 | - name: Publish
36 | uses: softprops/action-gh-release@v1
37 | with:
38 | files: './release/mosdns*.zip'
39 | prerelease: true
40 | env:
41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test mosdns
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 |
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 | - name: Set up Go
14 | uses: actions/setup-go@v3
15 | with:
16 | go-version: 1.19
17 | check-latest: true
18 | cache: true
19 |
20 | - name: Build
21 | run: go build -v ./...
22 |
23 | - name: Test
24 | run: go test -race -v ./...
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | vendor/
16 |
17 | # release dir
18 | release/
19 |
20 | # ide
21 | .vscode/
22 | .idea/
23 |
24 | # test utils
25 | testutils/
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mosdns
2 |
3 | 功能概述、配置方式、教程等,详见: [wiki](https://irine-sistiana.gitbook.io/mosdns-wiki/)
4 |
5 | 下载预编译文件、更新日志,详见: [release](https://github.com/IrineSistiana/mosdns/releases)
6 |
7 | docker 镜像: [docker hub](https://hub.docker.com/r/irinesistiana/mosdns)
8 |
--------------------------------------------------------------------------------
/coremain/interface.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package coremain
21 |
22 | import (
23 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
24 | "io"
25 | )
26 |
27 | // Plugin represents the basic plugin.
28 | type Plugin interface {
29 | Tag() string
30 | Type() string
31 | io.Closer
32 | }
33 |
34 | // ExecutablePlugin represents a Plugin that is Executable.
35 | type ExecutablePlugin interface {
36 | Plugin
37 | executable_seq.Executable
38 | }
39 |
40 | // MatcherPlugin represents a Plugin that is a Matcher.
41 | type MatcherPlugin interface {
42 | Plugin
43 | executable_seq.Matcher
44 | }
45 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/IrineSistiana/mosdns/v4
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/AdguardTeam/dnsproxy v0.46.2
7 | github.com/Knetic/govaluate v3.0.0+incompatible
8 | github.com/fsnotify/fsnotify v1.6.0
9 | github.com/go-redis/redis/v8 v8.11.5
10 | github.com/golang/snappy v0.0.4
11 | github.com/google/nftables v0.0.0-20221029063419-3ad45c080caa
12 | github.com/kardianos/service v1.2.2
13 | github.com/lucas-clemente/quic-go v0.30.0
14 | github.com/miekg/dns v1.1.50
15 | github.com/mitchellh/mapstructure v1.5.0
16 | github.com/nadoo/ipset v0.5.0
17 | github.com/pires/go-proxyproto v0.6.2
18 | github.com/prometheus/client_golang v1.13.0
19 | github.com/spf13/cobra v1.6.1
20 | github.com/spf13/viper v1.13.0
21 | github.com/stretchr/testify v1.8.0
22 | go.uber.org/zap v1.23.0
23 | go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab
24 | golang.org/x/exp v0.0.0-20221028150844-83b7d23a625f
25 | golang.org/x/net v0.1.0
26 | golang.org/x/sync v0.1.0
27 | golang.org/x/sys v0.1.0
28 | google.golang.org/protobuf v1.28.1
29 | gopkg.in/yaml.v3 v3.0.1
30 | )
31 |
32 | replace github.com/nadoo/ipset v0.5.0 => github.com/IrineSistiana/ipset v0.5.1-0.20220703061533-6e0fc3b04c0a
33 |
34 | require (
35 | github.com/AdguardTeam/golibs v0.11.2 // indirect
36 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
37 | github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
38 | github.com/ameshkov/dnscrypt/v2 v2.2.5 // indirect
39 | github.com/ameshkov/dnsstamps v1.0.3 // indirect
40 | github.com/beorn7/perks v1.0.1 // indirect
41 | github.com/cespare/xxhash/v2 v2.1.2 // indirect
42 | github.com/davecgh/go-spew v1.1.1 // indirect
43 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
44 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
45 | github.com/golang/mock v1.6.0 // indirect
46 | github.com/golang/protobuf v1.5.2 // indirect
47 | github.com/google/go-cmp v0.5.9 // indirect
48 | github.com/google/pprof v0.0.0-20221010195024-131d412537ea // indirect
49 | github.com/hashicorp/hcl v1.0.0 // indirect
50 | github.com/inconshreveable/mousetrap v1.0.1 // indirect
51 | github.com/josharian/native v1.0.0 // indirect
52 | github.com/magiconair/properties v1.8.6 // indirect
53 | github.com/marten-seemann/qpack v0.3.0 // indirect
54 | github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
55 | github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
56 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
57 | github.com/mdlayher/netlink v1.6.2 // indirect
58 | github.com/mdlayher/socket v0.2.3 // indirect
59 | github.com/onsi/ginkgo/v2 v2.4.0 // indirect
60 | github.com/pelletier/go-toml v1.9.5 // indirect
61 | github.com/pelletier/go-toml/v2 v2.0.5 // indirect
62 | github.com/pmezard/go-difflib v1.0.0 // indirect
63 | github.com/prometheus/client_model v0.3.0 // indirect
64 | github.com/prometheus/common v0.37.0 // indirect
65 | github.com/prometheus/procfs v0.8.0 // indirect
66 | github.com/spf13/afero v1.9.2 // indirect
67 | github.com/spf13/cast v1.5.0 // indirect
68 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
69 | github.com/spf13/pflag v1.0.5 // indirect
70 | github.com/subosito/gotenv v1.4.1 // indirect
71 | github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
72 | go.uber.org/atomic v1.10.0 // indirect
73 | go.uber.org/multierr v1.8.0 // indirect
74 | golang.org/x/crypto v0.1.0 // indirect
75 | golang.org/x/mod v0.6.0 // indirect
76 | golang.org/x/text v0.4.0 // indirect
77 | golang.org/x/tools v0.2.0 // indirect
78 | gopkg.in/ini.v1 v1.67.0 // indirect
79 | gopkg.in/yaml.v2 v2.4.0 // indirect
80 | )
81 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package main
21 |
22 | import (
23 | "fmt"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/mlog"
26 | _ "github.com/IrineSistiana/mosdns/v4/plugin"
27 | _ "github.com/IrineSistiana/mosdns/v4/tools"
28 | "github.com/spf13/cobra"
29 | _ "net/http/pprof"
30 | )
31 |
32 | var (
33 | version = "dev/unknown"
34 | )
35 |
36 | func init() {
37 | coremain.AddSubCmd(&cobra.Command{
38 | Use: "version",
39 | Short: "Print out version info and exit.",
40 | Run: func(cmd *cobra.Command, args []string) {
41 | fmt.Println(version)
42 | },
43 | })
44 | }
45 |
46 | func main() {
47 | if err := coremain.Run(); err != nil {
48 | mlog.S().Fatal(err)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/mlog/logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package mlog
21 |
22 | import (
23 | "fmt"
24 | "go.uber.org/zap"
25 | "go.uber.org/zap/zapcore"
26 | "os"
27 | )
28 |
29 | type LogConfig struct {
30 | // Level, See also zapcore.ParseLevel.
31 | Level string `yaml:"level"`
32 |
33 | // File that logger will be writen into.
34 | // Default is stderr.
35 | File string `yaml:"file"`
36 |
37 | // Production enables json output.
38 | Production bool `yaml:"production"`
39 |
40 | // OmitTime omits the time in log.
41 | OmitTime bool `yaml:"omit_time"`
42 |
43 | // parsed level
44 | lvl zapcore.Level
45 | }
46 |
47 | var (
48 | stderr = zapcore.Lock(os.Stderr)
49 |
50 | lvl = zap.NewAtomicLevelAt(zap.InfoLevel)
51 | l = newLogger(zapcore.NewConsoleEncoder, defaultEncoderConfig(), lvl, stderr)
52 | s = l.Sugar()
53 | )
54 |
55 | func NewLogger(lc *LogConfig) (*zap.Logger, error) {
56 | lvl, err := zapcore.ParseLevel(lc.Level)
57 | if err != nil {
58 | return nil, fmt.Errorf("invalid log level: %w", err)
59 | }
60 | lc.lvl = lvl
61 |
62 | return newLoggerFromCfg(lc)
63 | }
64 |
65 | func newLoggerFromCfg(lc *LogConfig) (*zap.Logger, error) {
66 | var out zapcore.WriteSyncer
67 | if lf := lc.File; len(lf) > 0 {
68 | f, _, err := zap.Open(lf)
69 | if err != nil {
70 | return nil, fmt.Errorf("open log file: %w", err)
71 | }
72 | out = zapcore.Lock(f)
73 | } else {
74 | out = stderr
75 | }
76 |
77 | ec := defaultEncoderConfig()
78 | if lc.OmitTime {
79 | ec.TimeKey = ""
80 | }
81 |
82 | if lc.Production {
83 | return newLogger(zapcore.NewJSONEncoder, ec, lc.lvl, out), nil
84 | }
85 | return newLogger(zapcore.NewConsoleEncoder, ec, lc.lvl, out), nil
86 | }
87 |
88 | func newLogger(
89 | encoderFactory func(config zapcore.EncoderConfig) zapcore.Encoder,
90 | encoderCfg zapcore.EncoderConfig,
91 | lvl zapcore.LevelEnabler,
92 | out zapcore.WriteSyncer,
93 | ) *zap.Logger {
94 | core := zapcore.NewCore(encoderFactory(encoderCfg), out, lvl)
95 | return zap.New(core)
96 | }
97 |
98 | func L() *zap.Logger {
99 | return l
100 | }
101 |
102 | func SetLevel(l zapcore.Level) {
103 | lvl.SetLevel(l)
104 | }
105 |
106 | func S() *zap.SugaredLogger {
107 | return s
108 | }
109 | func defaultEncoderConfig() zapcore.EncoderConfig {
110 | return zapcore.EncoderConfig{
111 | TimeKey: "time",
112 | MessageKey: "msg",
113 | LevelKey: "level",
114 | NameKey: "logger",
115 | CallerKey: "caller",
116 | EncodeLevel: zapcore.LowercaseLevelEncoder,
117 | EncodeTime: zapcore.ISO8601TimeEncoder,
118 | EncodeDuration: zapcore.StringDurationEncoder,
119 | EncodeCaller: zapcore.ShortCallerEncoder,
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/pkg/bundled_upstream/bundled_upstream.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package bundled_upstream
21 |
22 | import (
23 | "context"
24 | "errors"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
26 | "github.com/miekg/dns"
27 | "go.uber.org/zap"
28 | )
29 |
30 | type Upstream interface {
31 | // Exchange sends q to the upstream and waits for response.
32 | // If any error occurs. Implements must return a nil msg with a non nil error.
33 | // Otherwise, Implements must a msg with nil error.
34 | Exchange(ctx context.Context, q *dns.Msg) (*dns.Msg, error)
35 |
36 | // Trusted indicates whether this Upstream is trusted/reliable.
37 | // If true, responses from this Upstream will be accepted without checking its rcode.
38 | Trusted() bool
39 |
40 | Address() string
41 | }
42 |
43 | type parallelResult struct {
44 | r *dns.Msg
45 | err error
46 | from Upstream
47 | }
48 |
49 | var nopLogger = zap.NewNop()
50 |
51 | var ErrAllFailed = errors.New("all upstreams failed")
52 |
53 | func ExchangeParallel(ctx context.Context, qCtx *query_context.Context, upstreams []Upstream, logger *zap.Logger) (*dns.Msg, error) {
54 | if logger == nil {
55 | logger = nopLogger
56 | }
57 |
58 | q := qCtx.Q()
59 | t := len(upstreams)
60 | if t == 1 {
61 | return upstreams[0].Exchange(ctx, q)
62 | }
63 |
64 | c := make(chan *parallelResult, t) // use buf chan to avoid blocking.
65 | qCopy := q.Copy() // qCtx is not safe for concurrent use.
66 | for _, u := range upstreams {
67 | u := u
68 | go func() {
69 | r, err := u.Exchange(ctx, qCopy)
70 | c <- ¶llelResult{
71 | r: r,
72 | err: err,
73 | from: u,
74 | }
75 | }()
76 | }
77 |
78 | for i := 0; i < t; i++ {
79 | select {
80 | case res := <-c:
81 | if res.err != nil {
82 | logger.Warn("upstream err", qCtx.InfoField(), zap.String("addr", res.from.Address()))
83 | continue
84 | }
85 |
86 | if res.r == nil {
87 | continue
88 | }
89 |
90 | if res.from.Trusted() || res.r.Rcode == dns.RcodeSuccess {
91 | return res.r, nil
92 | }
93 | continue
94 |
95 | case <-ctx.Done():
96 | return nil, ctx.Err()
97 | }
98 | }
99 | return nil, ErrAllFailed
100 | }
101 |
--------------------------------------------------------------------------------
/pkg/cache/cache.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package cache
21 |
22 | import (
23 | "io"
24 | "time"
25 | )
26 |
27 | // Backend represents a cache backend.
28 | // The Backend does not raise errors cause a cache error is not a
29 | // fatal error to a dns query. The caller usually does not care too
30 | // much about the cache error. Implements should handle errors themselves.
31 | // Cache Backend is expected to be very fast. All operations should be
32 | // done (or returned) in a short time. e.g. 50 ms.
33 | type Backend interface {
34 | // Get retrieves v from Backend. The returned v may be the original value. The caller should
35 | // not modify it.
36 | Get(key string) (v []byte, storedTime, expirationTime time.Time)
37 |
38 | // Store stores a copy of v into Backend. v cannot be nil.
39 | // If expirationTime is already passed, Store is a noop.
40 | Store(key string, v []byte, storedTime, expirationTime time.Time)
41 |
42 | Len() int
43 |
44 | // Closer closes the cache backend. Get and Store should become noop calls.
45 | io.Closer
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/cache/mem_cache/mem_cache_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package mem_cache
21 |
22 | import (
23 | "strconv"
24 | "sync"
25 | "testing"
26 | "time"
27 | )
28 |
29 | func Test_memCache(t *testing.T) {
30 | c := NewMemCache(1024, 0)
31 | for i := 0; i < 128; i++ {
32 | key := strconv.Itoa(i)
33 | c.Store(key, []byte{byte(i)}, time.Now(), time.Now().Add(time.Millisecond*200))
34 | v, _, _ := c.Get(key)
35 |
36 | if v[0] != byte(i) {
37 | t.Fatal("cache kv mismatched")
38 | }
39 | }
40 |
41 | for i := 0; i < 1024*4; i++ {
42 | key := strconv.Itoa(i)
43 | c.Store(key, []byte{}, time.Now(), time.Now().Add(time.Millisecond*200))
44 | }
45 |
46 | if c.Len() > 1024 {
47 | t.Fatal("cache overflow")
48 | }
49 | }
50 |
51 | func Test_memCache_cleaner(t *testing.T) {
52 | c := NewMemCache(1024, time.Millisecond*10)
53 | defer c.Close()
54 | for i := 0; i < 64; i++ {
55 | key := strconv.Itoa(i)
56 | c.Store(key, make([]byte, 0), time.Now(), time.Now().Add(time.Millisecond*10))
57 | }
58 |
59 | time.Sleep(time.Millisecond * 100)
60 | if c.Len() != 0 {
61 | t.Fatal()
62 | }
63 | }
64 |
65 | func Test_memCache_race(t *testing.T) {
66 | c := NewMemCache(1024, -1)
67 | defer c.Close()
68 |
69 | wg := sync.WaitGroup{}
70 | for i := 0; i < 32; i++ {
71 | wg.Add(1)
72 | go func() {
73 | defer wg.Done()
74 | for i := 0; i < 256; i++ {
75 | c.Store(strconv.Itoa(i), []byte{}, time.Now(), time.Now().Add(time.Minute))
76 | _, _, _ = c.Get(strconv.Itoa(i))
77 | c.lru.Clean(c.cleanFunc())
78 | }
79 | }()
80 | }
81 | wg.Wait()
82 | }
83 |
--------------------------------------------------------------------------------
/pkg/cache/redis_cache/redis_cache_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package redis_cache
21 |
22 | import (
23 | "reflect"
24 | "testing"
25 | "time"
26 | )
27 |
28 | func Test_RedisValue(t *testing.T) {
29 | tests := []struct {
30 | name string
31 | storedTime time.Time
32 | expirationTime time.Time
33 | m []byte
34 | }{
35 | {"test", time.Now(), time.Now().Add(time.Second), make([]byte, 1024)},
36 | }
37 | for _, tt := range tests {
38 | t.Run(tt.name, func(t *testing.T) {
39 | data := packRedisData(tt.storedTime, tt.expirationTime, tt.m)
40 | defer data.Release()
41 |
42 | storedTime, expirationTime, m, err := unpackRedisValue(data.Bytes())
43 | if err != nil {
44 | t.Fatal(err)
45 | }
46 | if storedTime.Unix() != tt.storedTime.Unix() {
47 | t.Fatalf("storedTime: want %v, got %v", tt.storedTime, storedTime)
48 | }
49 | if expirationTime.Unix() != tt.expirationTime.Unix() {
50 | t.Fatalf("expirationTime: want %v, got %v", tt.expirationTime, expirationTime)
51 | }
52 | if !reflect.DeepEqual(m, tt.m) {
53 | t.Fatalf("m: want %v, got %v", tt.m, m)
54 | }
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/concurrent_limiter/client_limiter_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package concurrent_limiter
21 |
22 | import (
23 | "net/netip"
24 | "testing"
25 | "time"
26 | )
27 |
28 | func Test_HPClientLimiter(t *testing.T) {
29 | limiter, err := NewHPClientLimiter(HPLimiterOpts{
30 | Threshold: 8,
31 | IPv4Mask: 24,
32 | })
33 |
34 | if err != nil {
35 | t.Fatal(err)
36 | }
37 |
38 | for suffix := 0; suffix < 256; suffix++ {
39 | addr := netip.AddrFrom4([4]byte{0, 0, byte(suffix), 0})
40 | for i := 1; i <= 16; i++ {
41 | ok := limiter.AcquireToken(addr)
42 |
43 | if i <= 8 && !ok { // if it not reaches the limit but return a false
44 | t.Fatal()
45 | }
46 |
47 | if i > 8 && ok { // if it reached the limit but return a true
48 | t.Fatal()
49 | }
50 | }
51 | }
52 |
53 | if limiter.m.Len() != 256 {
54 | t.Fatal()
55 | }
56 |
57 | limiter.GC(time.Now().Add(counterIdleTimeout).Add(time.Hour)) // all counter should be cleaned
58 |
59 | if remain := limiter.m.Len(); remain != 0 {
60 | t.Fatal("gc test failed")
61 | }
62 | }
63 |
64 | func Benchmark_HPClientLimiter_AcquireToken(b *testing.B) {
65 | l, err := NewHPClientLimiter(HPLimiterOpts{
66 | Threshold: 4,
67 | })
68 | if err != nil {
69 | b.Fatal(err)
70 | }
71 | for i := 0; i < b.N; i++ {
72 | addr := netip.AddrFrom4([4]byte{(byte)(i)})
73 | l.AcquireToken(addr)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/concurrent_lru/concurrent_lru.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package concurrent_lru
21 |
22 | import (
23 | "github.com/IrineSistiana/mosdns/v4/pkg/lru"
24 | "hash/maphash"
25 | "sync"
26 | )
27 |
28 | type ShardedLRU[V any] struct {
29 | seed maphash.Seed
30 | l []*ConcurrentLRU[string, V]
31 | }
32 |
33 | func NewShardedLRU[V any](
34 | shardNum, maxSizePerShard int,
35 | onEvict func(key string, v V),
36 | ) *ShardedLRU[V] {
37 | cl := &ShardedLRU[V]{
38 | seed: maphash.MakeSeed(),
39 | l: make([]*ConcurrentLRU[string, V], shardNum),
40 | }
41 |
42 | for i := range cl.l {
43 | cl.l[i] = &ConcurrentLRU[string, V]{
44 | lru: lru.NewLRU[string, V](maxSizePerShard, onEvict),
45 | }
46 | }
47 |
48 | return cl
49 | }
50 |
51 | func (c *ShardedLRU[V]) Add(key string, v V) {
52 | sl := c.getShard(key)
53 | sl.Add(key, v)
54 | }
55 |
56 | func (c *ShardedLRU[V]) Del(key string) {
57 | sl := c.getShard(key)
58 | sl.Del(key)
59 | }
60 |
61 | func (c *ShardedLRU[V]) Clean(f func(key string, v V) (remove bool)) (removed int) {
62 | for i := range c.l {
63 | removed += c.l[i].Clean(f)
64 | }
65 | return removed
66 | }
67 |
68 | func (c *ShardedLRU[V]) Get(key string) (v V, ok bool) {
69 | sl := c.getShard(key)
70 | v, ok = sl.Get(key)
71 | return
72 | }
73 |
74 | func (c *ShardedLRU[V]) Len() int {
75 | sum := 0
76 | for _, shard := range c.l {
77 | sum += shard.Len()
78 | }
79 | return sum
80 | }
81 |
82 | func (c *ShardedLRU[V]) shardNum() int {
83 | return len(c.l)
84 | }
85 |
86 | func (c *ShardedLRU[V]) getShard(key string) *ConcurrentLRU[string, V] {
87 | h := maphash.Hash{}
88 | h.SetSeed(c.seed)
89 |
90 | h.WriteString(key)
91 | n := h.Sum64() % uint64(c.shardNum())
92 | return c.l[n]
93 | }
94 |
95 | // ConcurrentLRU is a lru.LRU with a lock.
96 | // It is concurrent safe.
97 | type ConcurrentLRU[K comparable, V any] struct {
98 | sync.Mutex
99 | lru *lru.LRU[K, V]
100 | }
101 |
102 | func NewConecurrentLRU[K comparable, V any](maxSize int, onEvict func(key K, v V)) *ConcurrentLRU[K, V] {
103 | return &ConcurrentLRU[K, V]{
104 | lru: lru.NewLRU[K, V](maxSize, onEvict),
105 | }
106 | }
107 |
108 | func (c *ConcurrentLRU[K, V]) Add(key K, v V) {
109 | c.Lock()
110 | defer c.Unlock()
111 |
112 | c.lru.Add(key, v)
113 | }
114 |
115 | func (c *ConcurrentLRU[K, V]) Del(key K) {
116 | c.Lock()
117 | defer c.Unlock()
118 |
119 | c.lru.Del(key)
120 | }
121 |
122 | func (c *ConcurrentLRU[K, V]) Clean(f func(key K, v V) (remove bool)) (removed int) {
123 | c.Lock()
124 | defer c.Unlock()
125 |
126 | return c.lru.Clean(f)
127 | }
128 |
129 | func (c *ConcurrentLRU[K, V]) Get(key K) (v V, ok bool) {
130 | c.Lock()
131 | defer c.Unlock()
132 |
133 | v, ok = c.lru.Get(key)
134 | return
135 | }
136 |
137 | func (c *ConcurrentLRU[K, V]) Len() int {
138 | c.Lock()
139 | defer c.Unlock()
140 |
141 | return c.lru.Len()
142 | }
143 |
--------------------------------------------------------------------------------
/pkg/concurrent_lru/concurrent_lru_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package concurrent_lru
21 |
22 | import (
23 | "reflect"
24 | "strconv"
25 | "testing"
26 | )
27 |
28 | func TestConcurrentLRU(t *testing.T) {
29 | onEvict := func(key string, v int) {}
30 |
31 | var cache *ShardedLRU[int]
32 | reset := func(shardNum, maxShardSize int) {
33 | cache = NewShardedLRU[int](shardNum, maxShardSize, onEvict)
34 | }
35 |
36 | add := func(keys ...int) {
37 | for _, key := range keys {
38 | cache.Add(strconv.Itoa(key), key)
39 | }
40 | }
41 |
42 | mustGet := func(keys ...int) {
43 | for _, key := range keys {
44 | gotV, ok := cache.Get(strconv.Itoa(key))
45 | if !ok || !reflect.DeepEqual(gotV, key) {
46 | t.Fatalf("want %v, got %v", key, gotV)
47 | }
48 | }
49 | }
50 |
51 | emptyGet := func(keys ...int) {
52 | for _, key := range keys {
53 | gotV, ok := cache.Get(strconv.Itoa(key))
54 | if ok {
55 | t.Fatalf("want empty, got %v", gotV)
56 | }
57 | }
58 | }
59 |
60 | checkLen := func(want int) {
61 | if want != cache.Len() {
62 | t.Fatalf("want %v, got %v", want, cache.Len())
63 | }
64 | }
65 |
66 | // test add
67 | reset(4, 16)
68 | add(1, 1, 1, 1, 2, 2, 3, 3, 4)
69 | checkLen(4)
70 | mustGet(1, 2, 3, 4)
71 | emptyGet(5, 6, 7, 9999)
72 |
73 | // test add overflow
74 | reset(4, 16) // max size is 64
75 | for i := 0; i < 1024; i++ {
76 | add(i)
77 | }
78 | if cache.Len() > 64 {
79 | t.Fatalf("lru overflowed: want len = %d, got = %d", 64, cache.Len())
80 | }
81 |
82 | // test del
83 | reset(4, 16)
84 | add(1, 2, 3, 4)
85 | cache.Del("2")
86 | cache.Del("4")
87 | cache.Del("9999")
88 | mustGet(1, 3)
89 | emptyGet(2, 4)
90 |
91 | // test clean
92 | reset(4, 16)
93 | add(1, 2, 3, 4)
94 | cleanFunc := func(key string, v int) (remove bool) {
95 | switch key {
96 | case "1", "3":
97 | return true
98 | }
99 | return false
100 | }
101 | if cleaned := cache.Clean(cleanFunc); cleaned != 2 {
102 | t.Fatalf("q.Clean want cleaned = 2, got %v", cleaned)
103 | }
104 | mustGet(2, 4)
105 | emptyGet(1, 3)
106 | }
107 |
--------------------------------------------------------------------------------
/pkg/concurrent_map/concurrent_map.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package concurrent_map
21 |
22 | import (
23 | "sync"
24 | )
25 |
26 | const (
27 | mapShardSize = 64
28 | )
29 |
30 | type MapHashable interface {
31 | comparable
32 | MapHash() int
33 | }
34 |
35 | type TestAndSetFunc[K comparable, V any] func(key K, v V, ok bool) (newV V, setV, deleteV bool)
36 |
37 | type Map[K MapHashable, V any] struct {
38 | shards [mapShardSize]netipAddrMapShard[K, V]
39 | }
40 |
41 | func NewMap[K MapHashable, V any]() *Map[K, V] {
42 | m := new(Map[K, V])
43 | for i := range m.shards {
44 | m.shards[i] = newNetipAddrMapShard[K, V]()
45 | }
46 | return m
47 | }
48 |
49 | func (m *Map[K, V]) getShard(key K) *netipAddrMapShard[K, V] {
50 | return &m.shards[key.MapHash()%mapShardSize]
51 | }
52 |
53 | func (m *Map[K, V]) Get(key K) (V, bool) {
54 | return m.getShard(key).get(key)
55 | }
56 |
57 | func (m *Map[K, V]) Set(key K, v V) {
58 | m.getShard(key).set(key, v)
59 | }
60 |
61 | func (m *Map[K, V]) Del(key K) {
62 | m.getShard(key).del(key)
63 | }
64 |
65 | func (m *Map[K, V]) TestAndSet(key K, f TestAndSetFunc[K, V]) {
66 | m.getShard(key).testAndSet(key, f)
67 | }
68 |
69 | func (m *Map[K, V]) RangeDo(f TestAndSetFunc[K, V]) {
70 | for i := range m.shards {
71 | m.shards[i].rangeDo(f)
72 | }
73 | }
74 |
75 | func (m *Map[K, V]) Len() int {
76 | l := 0
77 | for i := range m.shards {
78 | l += m.shards[i].len()
79 | }
80 | return l
81 | }
82 |
83 | type netipAddrMapShard[K comparable, V any] struct {
84 | l sync.RWMutex
85 | m map[K]V
86 | }
87 |
88 | func newNetipAddrMapShard[K comparable, V any]() netipAddrMapShard[K, V] {
89 | return netipAddrMapShard[K, V]{
90 | m: make(map[K]V),
91 | }
92 | }
93 |
94 | func (m *netipAddrMapShard[K, V]) get(key K) (V, bool) {
95 | m.l.RLock()
96 | defer m.l.RUnlock()
97 | v, ok := m.m[key]
98 | return v, ok
99 | }
100 |
101 | func (m *netipAddrMapShard[K, V]) set(key K, v V) {
102 | m.l.Lock()
103 | defer m.l.Unlock()
104 | m.m[key] = v
105 | }
106 |
107 | func (m *netipAddrMapShard[K, V]) del(key K) {
108 | m.l.Lock()
109 | defer m.l.Unlock()
110 | delete(m.m, key)
111 | }
112 |
113 | func (m *netipAddrMapShard[K, V]) testAndSet(key K, f TestAndSetFunc[K, V]) {
114 | m.l.Lock()
115 | defer m.l.Unlock()
116 | v, ok := m.m[key]
117 | newV, setV, deleteV := f(key, v, ok)
118 | switch {
119 | case setV:
120 | m.m[key] = newV
121 | case deleteV && ok:
122 | delete(m.m, key)
123 | }
124 | }
125 |
126 | func (m *netipAddrMapShard[K, V]) len() int {
127 | m.l.RLock()
128 | defer m.l.RUnlock()
129 | return len(m.m)
130 | }
131 |
132 | func (m *netipAddrMapShard[K, V]) rangeDo(f TestAndSetFunc[K, V]) {
133 | m.l.Lock()
134 | defer m.l.Unlock()
135 | for k, v := range m.m {
136 | newV, setV, deleteV := f(k, v, true)
137 | switch {
138 | case setV:
139 | m.m[k] = newV
140 | case deleteV:
141 | delete(m.m, k)
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/pkg/dnsutils/edns0.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package dnsutils
21 |
22 | import "github.com/miekg/dns"
23 |
24 | // UpgradeEDNS0 enables EDNS0 for m and returns it's dns.OPT record.
25 | // m must be a msg without dns.OPT.
26 | func UpgradeEDNS0(m *dns.Msg) *dns.OPT {
27 | o := new(dns.OPT)
28 | o.SetUDPSize(dns.MinMsgSize)
29 | o.Hdr.Name = "."
30 | o.Hdr.Rrtype = dns.TypeOPT
31 | m.Extra = append(m.Extra, o)
32 | return o
33 | }
34 |
35 | // RemoveEDNS0 removes the OPT record from m.
36 | func RemoveEDNS0(m *dns.Msg) {
37 | for i := len(m.Extra) - 1; i >= 0; i-- {
38 | if m.Extra[i].Header().Rrtype == dns.TypeOPT {
39 | m.Extra = append(m.Extra[:i], m.Extra[i+1:]...)
40 | return
41 | }
42 | }
43 | return
44 | }
45 |
46 | func RemoveEDNS0Option(opt *dns.OPT, option uint16) {
47 | for i := range opt.Option {
48 | if opt.Option[i].Option() == option {
49 | opt.Option = append(opt.Option[:i], opt.Option[i+1:]...)
50 | return
51 | }
52 | }
53 | return
54 | }
55 |
56 | func GetEDNS0Option(opt *dns.OPT, option uint16) dns.EDNS0 {
57 | for o := range opt.Option {
58 | if opt.Option[o].Option() == option {
59 | return opt.Option[o]
60 | }
61 | }
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/dnsutils/edns0_ecs.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package dnsutils
21 |
22 | import (
23 | "github.com/miekg/dns"
24 | "net"
25 | )
26 |
27 | func GetMsgECS(m *dns.Msg) (e *dns.EDNS0_SUBNET) {
28 | opt := m.IsEdns0()
29 | if opt == nil { // no opt, no ecs
30 | return nil
31 | }
32 | return GetECS(opt)
33 | }
34 |
35 | // RemoveMsgECS removes the *dns.EDNS0_SUBNET record in m.
36 | func RemoveMsgECS(m *dns.Msg) {
37 | opt := m.IsEdns0()
38 | if opt == nil { // no opt, no ecs
39 | return
40 | }
41 | RemoveECS(opt)
42 | }
43 |
44 | func GetECS(opt *dns.OPT) (e *dns.EDNS0_SUBNET) {
45 | for o := range opt.Option {
46 | if opt.Option[o].Option() == dns.EDNS0SUBNET {
47 | return opt.Option[o].(*dns.EDNS0_SUBNET)
48 | }
49 | }
50 | return nil
51 | }
52 |
53 | func RemoveECS(opt *dns.OPT) {
54 | for i := range opt.Option {
55 | if opt.Option[i].Option() == dns.EDNS0SUBNET {
56 | opt.Option = append(opt.Option[:i], opt.Option[i+1:]...)
57 | return
58 | }
59 | }
60 | return
61 | }
62 |
63 | // AddECS adds ecs to opt.
64 | func AddECS(opt *dns.OPT, ecs *dns.EDNS0_SUBNET, overwrite bool) (newECS bool) {
65 | for o := range opt.Option {
66 | if opt.Option[o].Option() == dns.EDNS0SUBNET {
67 | if overwrite {
68 | opt.Option[o] = ecs
69 | return false
70 | }
71 | return false
72 | }
73 | }
74 | opt.Option = append(opt.Option, ecs)
75 | return true
76 | }
77 |
78 | func NewEDNS0Subnet(ip net.IP, mask uint8, v6 bool) *dns.EDNS0_SUBNET {
79 | edns0Subnet := new(dns.EDNS0_SUBNET)
80 | // edns family: https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml
81 | // ipv4 = 1
82 | // ipv6 = 2
83 | if !v6 { // ipv4
84 | edns0Subnet.Family = 1
85 | } else { // ipv6
86 | edns0Subnet.Family = 2
87 | }
88 |
89 | edns0Subnet.SourceNetmask = mask
90 | edns0Subnet.Code = dns.EDNS0SUBNET
91 | edns0Subnet.Address = ip
92 |
93 | // SCOPE PREFIX-LENGTH, an unsigned octet representing the leftmost
94 | // number of significant bits of ADDRESS that the response covers.
95 | // In queries, it MUST be set to 0.
96 | // https://tools.ietf.org/html/rfc7871
97 | edns0Subnet.SourceScope = 0
98 | return edns0Subnet
99 | }
100 |
--------------------------------------------------------------------------------
/pkg/dnsutils/edns0_padding.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package dnsutils
21 |
22 | import "github.com/miekg/dns"
23 |
24 | // PadToMinimum pads m to the minimum length.
25 | // If the length of m is larger than minLen, PadToMinimum won't do anything.
26 | // upgraded indicates the m was upgraded to an EDNS0 msg.
27 | // newPadding indicates the Padding option is new to m.
28 | func PadToMinimum(m *dns.Msg, minLen int) (upgraded, newPadding bool) {
29 | l := m.Len()
30 | if l >= minLen {
31 | return false, false
32 | }
33 |
34 | opt := m.IsEdns0()
35 | if opt != nil {
36 | if edns0 := GetEDNS0Option(opt, dns.EDNS0PADDING); edns0 != nil { // q is padded.
37 | pd := edns0.(*dns.EDNS0_PADDING)
38 | paddingLen := minLen - l + len(pd.Padding)
39 | if paddingLen < 0 {
40 | return false, false
41 | }
42 | pd.Padding = make([]byte, paddingLen)
43 | return false, false
44 | }
45 | paddingLen := minLen - 4 - l // a Padding option has a 4 bytes header.
46 | if paddingLen < 0 {
47 | return false, false
48 | }
49 | opt.Option = append(opt.Option, &dns.EDNS0_PADDING{Padding: make([]byte, paddingLen)})
50 | return false, true
51 | }
52 |
53 | paddingLen := minLen - 15 - l // 4 bytes padding header + 11 bytes EDNS0 header.
54 | if paddingLen < 0 {
55 | return false, false
56 | }
57 | opt = UpgradeEDNS0(m)
58 | opt.Option = append(opt.Option, &dns.EDNS0_PADDING{Padding: make([]byte, paddingLen)})
59 | return true, true
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/dnsutils/edns0_padding_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package dnsutils
21 |
22 | import (
23 | "github.com/miekg/dns"
24 | "strings"
25 | "testing"
26 | )
27 |
28 | func TestPadToMinimum(t *testing.T) {
29 | q := new(dns.Msg)
30 | q.SetQuestion(".", dns.TypeA)
31 |
32 | qEDNS0 := q.Copy()
33 | UpgradeEDNS0(qEDNS0)
34 |
35 | qPadded := qEDNS0.Copy()
36 | opt := qPadded.IsEdns0()
37 | opt.Option = append(opt.Option, &dns.EDNS0_PADDING{Padding: make([]byte, 16)})
38 |
39 | qLarge := new(dns.Msg)
40 | qLarge.SetQuestion(strings.Repeat("a.", 100), dns.TypeA)
41 |
42 | tests := []struct {
43 | name string
44 | q *dns.Msg
45 | minLen int
46 | wantLen int
47 | wantUpgraded bool
48 | wantNewPadding bool
49 | }{
50 | {"", q.Copy(), 128, 128, true, true},
51 | {"", qLarge.Copy(), 128, qLarge.Len(), false, false},
52 | {"", qEDNS0.Copy(), 128, 128, false, true},
53 | {"", qPadded.Copy(), 128, 128, false, false},
54 | }
55 | for _, tt := range tests {
56 | t.Run(tt.name, func(t *testing.T) {
57 | gotUpgraded, gotNewPadding := PadToMinimum(tt.q, tt.minLen)
58 | if gotUpgraded != tt.wantUpgraded {
59 | t.Errorf("pad() gotUpgraded = %v, want %v", gotUpgraded, tt.wantUpgraded)
60 | }
61 | if gotNewPadding != tt.wantNewPadding {
62 | t.Errorf("pad() gotNewPadding = %v, want %v", gotNewPadding, tt.wantNewPadding)
63 | }
64 | if qLen := tt.q.Len(); qLen != tt.wantLen {
65 | t.Errorf("pad() query length = %v, want %v", qLen, tt.wantLen)
66 | }
67 | })
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/pkg/executable_seq/iface.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package executable_seq
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
25 | )
26 |
27 | // Executable represents something that is executable.
28 | type Executable interface {
29 | Exec(ctx context.Context, qCtx *query_context.Context, next ExecutableChainNode) error
30 | }
31 |
32 | // ExecutableChainNode represents a node in a executable chain.
33 | type ExecutableChainNode interface {
34 | Executable
35 | LinkedListNode
36 | }
37 |
38 | // Matcher represents a matcher that can match a certain patten in Context.
39 | type Matcher interface {
40 | Match(ctx context.Context, qCtx *query_context.Context) (matched bool, err error)
41 | }
42 |
43 | // ExecutableNodeWrapper wraps a Executable to a ExecutableChainNode.
44 | type ExecutableNodeWrapper struct {
45 | Executable
46 | NodeLinker
47 | }
48 |
49 | // WrapExecutable wraps a Executable to a ExecutableChainNode.
50 | func WrapExecutable(e Executable) ExecutableChainNode {
51 | if ecn, ok := e.(ExecutableChainNode); ok {
52 | return ecn
53 | }
54 | return &ExecutableNodeWrapper{Executable: e}
55 | }
56 |
57 | type LinkedListNode interface {
58 | Next() ExecutableChainNode
59 | LinkNext(n ExecutableChainNode)
60 | }
61 |
62 | // NodeLinker implements LinkedListNode.
63 | type NodeLinker struct {
64 | next ExecutableChainNode
65 | }
66 |
67 | func (l *NodeLinker) Next() ExecutableChainNode {
68 | return l.next
69 | }
70 |
71 | func (l *NodeLinker) LinkNext(n ExecutableChainNode) {
72 | l.next = n
73 | }
74 |
--------------------------------------------------------------------------------
/pkg/executable_seq/load_balance.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package executable_seq
21 |
22 | import (
23 | "context"
24 | "fmt"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
26 | "go.uber.org/zap"
27 | "strconv"
28 | "sync/atomic"
29 | )
30 |
31 | type LBNode struct {
32 | prev, next ExecutableChainNode
33 | branchNode []ExecutableChainNode
34 | p uint32
35 | }
36 |
37 | func (lbn *LBNode) Next() ExecutableChainNode {
38 | return lbn.next
39 | }
40 |
41 | func (lbn *LBNode) LinkNext(n ExecutableChainNode) {
42 | lbn.next = n
43 | for _, branch := range lbn.branchNode {
44 | LastNode(branch).LinkNext(n)
45 | }
46 | }
47 |
48 | type LBConfig struct {
49 | LoadBalance []interface{} `yaml:"load_balance"`
50 | }
51 |
52 | func ParseLBNode(
53 | c *LBConfig,
54 | logger *zap.Logger,
55 | execs map[string]Executable,
56 | matchers map[string]Matcher,
57 | ) (*LBNode, error) {
58 | if logger == nil {
59 | logger = zap.NewNop()
60 | }
61 | ps := make([]ExecutableChainNode, 0, len(c.LoadBalance))
62 | for i, subSequence := range c.LoadBalance {
63 | es, err := BuildExecutableLogicTree(subSequence, logger.Named("lb_seq_"+strconv.Itoa(i)), execs, matchers)
64 | if err != nil {
65 | return nil, fmt.Errorf("invalid load balance command #%d: %w", i, err)
66 | }
67 | ps = append(ps, es)
68 | }
69 |
70 | return &LBNode{branchNode: ps}, nil
71 | }
72 |
73 | func (lbn *LBNode) Exec(ctx context.Context, qCtx *query_context.Context, next ExecutableChainNode) error {
74 | if len(lbn.branchNode) == 0 {
75 | return ExecChainNode(ctx, qCtx, next)
76 | }
77 |
78 | nextIdx := atomic.AddUint32(&lbn.p, 1) % uint32(len(lbn.branchNode))
79 | err := ExecChainNode(ctx, qCtx, lbn.branchNode[nextIdx])
80 | if err != nil {
81 | return fmt.Errorf("command sequence #%d: %w", nextIdx, err)
82 | }
83 | return nil
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/executable_seq/parallel.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package executable_seq
21 |
22 | import (
23 | "context"
24 | "fmt"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
26 | "go.uber.org/zap"
27 | "strconv"
28 | "time"
29 | )
30 |
31 | type ParallelNode struct {
32 | s []ExecutableChainNode
33 | timeout time.Duration
34 |
35 | logger *zap.Logger // not nil
36 | }
37 |
38 | const (
39 | defaultParallelTimeout = time.Second * 5
40 | )
41 |
42 | type ParallelConfig struct {
43 | Parallel []interface{} `yaml:"parallel"`
44 | }
45 |
46 | func ParseParallelNode(
47 | c *ParallelConfig,
48 | logger *zap.Logger,
49 | execs map[string]Executable,
50 | matchers map[string]Matcher,
51 | ) (*ParallelNode, error) {
52 | if logger == nil {
53 | logger = zap.NewNop()
54 | }
55 | ps := make([]ExecutableChainNode, 0, len(c.Parallel))
56 | for i, subSequence := range c.Parallel {
57 | es, err := BuildExecutableLogicTree(subSequence, logger.Named("parallel_seq_"+strconv.Itoa(i)), execs, matchers)
58 | if err != nil {
59 | return nil, fmt.Errorf("invalid parallel command at index %d: %w", i, err)
60 | }
61 | ps = append(ps, es)
62 | }
63 |
64 | return &ParallelNode{
65 | s: ps,
66 | logger: logger,
67 | }, nil
68 | }
69 |
70 | type parallelECSResult struct {
71 | qCtx *query_context.Context
72 | err error
73 | from int
74 | }
75 |
76 | func (p *ParallelNode) Exec(ctx context.Context, qCtx *query_context.Context, next ExecutableChainNode) error {
77 | if err := p.exec(ctx, qCtx); err != nil {
78 | return err
79 | }
80 | return ExecChainNode(ctx, qCtx, next)
81 | }
82 |
83 | func (p *ParallelNode) exec(ctx context.Context, qCtx *query_context.Context) error {
84 | if len(p.s) == 0 {
85 | return nil
86 | }
87 |
88 | t := len(p.s)
89 | c := make(chan *parallelECSResult, len(p.s)) // use buf chan to avoid blocking.
90 |
91 | for i, n := range p.s {
92 | i := i
93 | n := n
94 | qCtxCopy := qCtx.Copy()
95 |
96 | var pCtx context.Context
97 | var cancel func()
98 | if p.timeout > 0 {
99 | pCtx, cancel = context.WithTimeout(context.Background(), p.timeout)
100 | } else {
101 | if ddl, ok := ctx.Deadline(); ok {
102 | pCtx, cancel = context.WithDeadline(ctx, ddl)
103 | }
104 | pCtx, cancel = context.WithTimeout(ctx, defaultParallelTimeout)
105 | }
106 |
107 | go func() {
108 | defer cancel()
109 |
110 | err := ExecChainNode(pCtx, qCtxCopy, n)
111 | c <- ¶llelECSResult{
112 | qCtx: qCtxCopy,
113 | err: err,
114 | from: i,
115 | }
116 | }()
117 | }
118 |
119 | return asyncWait(ctx, qCtx, p.logger, c, t)
120 | }
121 |
--------------------------------------------------------------------------------
/pkg/executable_seq/parallel_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package executable_seq
21 |
22 | import (
23 | "context"
24 | "errors"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
26 | "github.com/miekg/dns"
27 | "go.uber.org/zap"
28 | "testing"
29 | )
30 |
31 | func Test_ParallelNode(t *testing.T) {
32 | r1 := new(dns.Msg)
33 | r2 := new(dns.Msg)
34 |
35 | er := errors.New("")
36 | tests := []struct {
37 | name string
38 | r1 *dns.Msg
39 | e1 error
40 | r2 *dns.Msg
41 | e2 error
42 | wantR *dns.Msg
43 | wantErr bool
44 | }{
45 | {"failed #1", nil, er, nil, er, nil, true},
46 | {"failed #2", nil, nil, nil, nil, nil, true},
47 | {"p1 response #1", r1, nil, nil, nil, r1, false},
48 | {"p1 response #2", r1, nil, nil, er, r1, false},
49 | {"p2 response #1", nil, nil, r2, nil, r2, false},
50 | {"p2 response #2", nil, er, r2, nil, r2, false},
51 | }
52 |
53 | ctx := context.Background()
54 | for _, tt := range tests {
55 | t.Run(tt.name, func(t *testing.T) {
56 | execs := make(map[string]Executable)
57 | execs["p1"] = &DummyExecutable{
58 | WantSleep: 0,
59 | WantR: tt.r1,
60 | WantErr: tt.e1,
61 | }
62 | execs["p2"] = &DummyExecutable{
63 | WantSleep: 0,
64 | WantR: tt.r2,
65 | WantErr: tt.e2,
66 | }
67 |
68 | pc := &ParallelConfig{
69 | Parallel: []interface{}{"p1", "p2"},
70 | }
71 |
72 | parallelNode, err := ParseParallelNode(pc, zap.NewNop(), execs, nil)
73 | if err != nil {
74 | t.Fatal(err)
75 | }
76 |
77 | qCtx := query_context.NewContext(new(dns.Msg), nil)
78 | err = ExecChainNode(ctx, qCtx, WrapExecutable(parallelNode))
79 | if tt.wantErr != (err != nil) {
80 | t.Fatalf("execCmd() error = %v, wantErr %v", err, tt.wantErr)
81 | }
82 |
83 | if tt.wantR != qCtx.R() {
84 | t.Fatalf("execCmd() qCtx.R() = %p, wantR %p", qCtx.R(), tt.wantR)
85 | }
86 | })
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/pkg/executable_seq/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package executable_seq
21 |
22 | import (
23 | "context"
24 | "errors"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
26 | "github.com/miekg/dns"
27 | "go.uber.org/zap"
28 | "sync"
29 | "time"
30 | )
31 |
32 | func asyncWait(ctx context.Context, qCtx *query_context.Context, logger *zap.Logger, c chan *parallelECSResult, total int) error {
33 | for i := 0; i < total; i++ {
34 | select {
35 | case res := <-c:
36 | if res.err != nil {
37 | logger.Warn("sequence failed", qCtx.InfoField(), zap.Int("sequence", res.from), zap.Error(res.err))
38 | continue
39 | }
40 |
41 | if r := res.qCtx.R(); r != nil {
42 | logger.Debug("sequence returned a response", qCtx.InfoField(), zap.Int("sequence", res.from))
43 | qCtx.SetResponse(r)
44 | return nil
45 | }
46 |
47 | logger.Debug("sequence returned with an empty response", qCtx.InfoField(), zap.Int("sequence", res.from))
48 | continue
49 |
50 | case <-ctx.Done():
51 | return ctx.Err()
52 | }
53 | }
54 |
55 | // No response
56 | return errors.New("no response")
57 | }
58 |
59 | // LastNode returns the Latest node of chain of n.
60 | func LastNode(n ExecutableChainNode) ExecutableChainNode {
61 | p := n
62 | for {
63 | if nn := p.Next(); nn == nil {
64 | return p
65 | } else {
66 | p = nn
67 | }
68 | }
69 | }
70 |
71 | func ExecChainNode(ctx context.Context, qCtx *query_context.Context, n ExecutableChainNode) error {
72 | if n == nil {
73 | return nil
74 | }
75 |
76 | // TODO: Error logging
77 | return n.Exec(ctx, qCtx, n.Next())
78 | }
79 |
80 | type DummyMatcher struct {
81 | Matched bool
82 | WantErr error
83 | }
84 |
85 | func (d *DummyMatcher) Match(_ context.Context, _ *query_context.Context) (matched bool, err error) {
86 | return d.Matched, d.WantErr
87 | }
88 |
89 | type DummyExecutable struct {
90 | sync.Mutex
91 | WantSkip bool
92 | WantSleep time.Duration
93 | WantR *dns.Msg
94 | WantErr error
95 | }
96 |
97 | func (d *DummyExecutable) Exec(ctx context.Context, qCtx *query_context.Context, next ExecutableChainNode) error {
98 | if d.WantSleep != 0 {
99 | time.Sleep(d.WantSleep)
100 | }
101 |
102 | d.Lock()
103 | if d.WantSkip {
104 | d.Unlock()
105 | return nil
106 | }
107 | if err := d.WantErr; err != nil {
108 | d.Unlock()
109 | return err
110 | }
111 | if d.WantR != nil {
112 | qCtx.SetResponse(d.WantR)
113 | }
114 | d.Unlock()
115 | return ExecChainNode(ctx, qCtx, next)
116 | }
117 |
118 | func LogicalAndMatcherGroup(ctx context.Context, qCtx *query_context.Context, mg []Matcher) (matched bool, err error) {
119 | if len(mg) == 0 {
120 | return false, nil
121 | }
122 | for _, matcher := range mg {
123 | ok, err := matcher.Match(ctx, qCtx)
124 | if err != nil {
125 | return false, err
126 | }
127 | if !ok {
128 | return false, nil
129 | }
130 | }
131 | return true, nil
132 | }
133 |
--------------------------------------------------------------------------------
/pkg/ext_exec/exec.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package ext_exec
21 |
22 | import (
23 | "context"
24 | "fmt"
25 | "os"
26 | "os/exec"
27 | "strings"
28 | )
29 |
30 | // GetOutputFromCmd exec the cmd and return the combined output.
31 | func GetOutputFromCmd(ctx context.Context, cmd string) ([]byte, error) {
32 | ss := strings.Split(cmd, " ")
33 | name := ss[0]
34 | args := ss[1:]
35 | c := exec.CommandContext(ctx, name, args...)
36 | c.Stderr = os.Stderr
37 | out, err := c.Output()
38 | if err != nil {
39 | if len(out) != 0 {
40 | return nil, fmt.Errorf("cmd err: %w, output: %s", err, string(out))
41 | }
42 | return nil, err
43 | }
44 |
45 | return out, nil
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/ext_exec/exec_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package ext_exec
21 |
22 | import (
23 | "bytes"
24 | "context"
25 | "testing"
26 | )
27 |
28 | func TestGetStringFromCmd(t *testing.T) {
29 | out, err := GetOutputFromCmd(context.Background(), "go version")
30 | if err != nil {
31 | t.Fatal(err)
32 | }
33 | if !bytes.HasPrefix(out, []byte("go version go")) {
34 | t.Fatalf("unexpected output: [%s]", out)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/hosts/hosts.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package hosts
21 |
22 | import (
23 | "errors"
24 | "fmt"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/dnsutils"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/matcher/domain"
27 | "github.com/miekg/dns"
28 | "net/netip"
29 | "strings"
30 | )
31 |
32 | type Hosts struct {
33 | matcher domain.Matcher[*IPs]
34 | }
35 |
36 | // NewHosts creates a hosts using m.
37 | func NewHosts(m domain.Matcher[*IPs]) *Hosts {
38 | return &Hosts{
39 | matcher: m,
40 | }
41 | }
42 |
43 | func (h *Hosts) Lookup(fqdn string) (ipv4, ipv6 []netip.Addr) {
44 | ips, ok := h.matcher.Match(fqdn)
45 | if !ok {
46 | return nil, nil // no such host
47 | }
48 | return ips.IPv4, ips.IPv6
49 | }
50 |
51 | func (h *Hosts) LookupMsg(m *dns.Msg) *dns.Msg {
52 | if len(m.Question) != 1 {
53 | return nil
54 | }
55 | q := m.Question[0]
56 | typ := q.Qtype
57 | fqdn := q.Name
58 | if q.Qclass != dns.ClassINET || (typ != dns.TypeA && typ != dns.TypeAAAA) {
59 | return nil
60 | }
61 |
62 | ipv4, ipv6 := h.Lookup(fqdn)
63 | if len(ipv4)+len(ipv6) == 0 {
64 | return nil // no such host
65 | }
66 |
67 | r := new(dns.Msg)
68 | r.SetReply(m)
69 | r.RecursionAvailable = true
70 | switch {
71 | case typ == dns.TypeA && len(ipv4) > 0:
72 | for _, ip := range ipv4 {
73 | rr := &dns.A{
74 | Hdr: dns.RR_Header{
75 | Name: fqdn,
76 | Rrtype: dns.TypeA,
77 | Class: dns.ClassINET,
78 | Ttl: 10,
79 | },
80 | A: ip.AsSlice(),
81 | }
82 | r.Answer = append(r.Answer, rr)
83 | }
84 | case typ == dns.TypeAAAA && len(ipv6) > 0:
85 | for _, ip := range ipv6 {
86 | rr := &dns.AAAA{
87 | Hdr: dns.RR_Header{
88 | Name: fqdn,
89 | Rrtype: dns.TypeAAAA,
90 | Class: dns.ClassINET,
91 | Ttl: 10,
92 | },
93 | AAAA: ip.AsSlice(),
94 | }
95 | r.Answer = append(r.Answer, rr)
96 | }
97 | }
98 |
99 | // Append fake SOA record for empty reply.
100 | if len(r.Answer) == 0 {
101 | r.Ns = []dns.RR{dnsutils.FakeSOA(fqdn)}
102 | }
103 | return r
104 | }
105 |
106 | type IPs struct {
107 | IPv4 []netip.Addr
108 | IPv6 []netip.Addr
109 | }
110 |
111 | var _ domain.ParseStringFunc[*IPs] = ParseIPs
112 |
113 | func ParseIPs(s string) (string, *IPs, error) {
114 | f := strings.Fields(s)
115 | if len(f) == 0 {
116 | return "", nil, errors.New("empty string")
117 | }
118 |
119 | pattern := f[0]
120 | v := new(IPs)
121 | for _, ipStr := range f[1:] {
122 | ip, err := netip.ParseAddr(ipStr)
123 | if err != nil {
124 | return "", nil, fmt.Errorf("invalid ip addr %s, %w", ipStr, err)
125 | }
126 |
127 | if ip.Is4() { // is ipv4
128 | v.IPv4 = append(v.IPv4, ip)
129 | } else { // is ipv6
130 | v.IPv6 = append(v.IPv6, ip)
131 | }
132 | }
133 |
134 | return pattern, v, nil
135 | }
136 |
--------------------------------------------------------------------------------
/pkg/hosts/hosts_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package hosts
21 |
22 | import (
23 | "bytes"
24 | "github.com/IrineSistiana/mosdns/v4/pkg/matcher/domain"
25 | "github.com/miekg/dns"
26 | "net"
27 | "testing"
28 | )
29 |
30 | var test_hosts = `
31 | # comment
32 | # empty line
33 | dns.google 8.8.8.8 8.8.4.4 2001:4860:4860::8844 2001:4860:4860::8888
34 | regexp:^123456789 192.168.1.1
35 | test.com 1.2.3.4 # will be replaced
36 | test.com 2.3.4.5
37 | # nxdomain.com 1.2.3.4
38 | `
39 |
40 | func Test_hostsContainer_Match(t *testing.T) {
41 | m := domain.NewMixMatcher[*IPs]()
42 | m.SetDefaultMatcher(domain.MatcherDomain)
43 | err := domain.LoadFromTextReader[*IPs](m, bytes.NewBuffer([]byte(test_hosts)), ParseIPs)
44 | if err != nil {
45 | t.Fatal(err)
46 | }
47 | h := NewHosts(m)
48 |
49 | type args struct {
50 | name string
51 | typ uint16
52 | }
53 | tests := []struct {
54 | name string
55 | args args
56 | wantMatched bool
57 | wantAddr []string
58 | }{
59 | {"matched A", args{name: "dns.google.", typ: dns.TypeA}, true, []string{"8.8.8.8", "8.8.4.4"}},
60 | {"matched AAAA", args{name: "dns.google.", typ: dns.TypeAAAA}, true, []string{"2001:4860:4860::8844", "2001:4860:4860::8888"}},
61 | {"not matched A", args{name: "nxdomain.com.", typ: dns.TypeA}, false, nil},
62 | {"not matched A", args{name: "sub.dns.google.", typ: dns.TypeA}, false, nil},
63 | {"matched regexp A", args{name: "123456789.test.", typ: dns.TypeA}, true, []string{"192.168.1.1"}},
64 | {"not matched regexp A", args{name: "0123456789.test.", typ: dns.TypeA}, false, nil},
65 | {"test replacement", args{name: "test.com.", typ: dns.TypeA}, true, []string{"2.3.4.5"}},
66 | {"test matched domain with mismatched type", args{name: "test.com.", typ: dns.TypeAAAA}, true, nil},
67 | }
68 | for _, tt := range tests {
69 | q := new(dns.Msg)
70 | q.SetQuestion(tt.args.name, tt.args.typ)
71 |
72 | t.Run(tt.name, func(t *testing.T) {
73 | r := h.LookupMsg(q)
74 | if tt.wantMatched && r == nil {
75 | t.Fatal("Lookup() should not return a nil result")
76 | }
77 |
78 | for _, s := range tt.wantAddr {
79 | wantIP := net.ParseIP(s)
80 | if wantIP == nil {
81 | t.Fatal("invalid test case addr")
82 | }
83 | found := false
84 | for _, rr := range r.Answer {
85 | var ip net.IP
86 | switch rr := rr.(type) {
87 | case *dns.A:
88 | ip = rr.A
89 | case *dns.AAAA:
90 | ip = rr.AAAA
91 | default:
92 | continue
93 | }
94 | if ip.Equal(wantIP) {
95 | found = true
96 | break
97 | }
98 | }
99 | if !found {
100 | t.Fatal("wanted ip is not found in response")
101 | }
102 | }
103 | })
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/pkg/ip_observer/observer.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package ip_observer
21 |
22 | import (
23 | "net/netip"
24 | )
25 |
26 | type IPObserver interface {
27 | // Observe notifies the IPObserver. addr must be valid.
28 | Observe(addr netip.Addr)
29 | }
30 |
31 | type NopObserver struct{}
32 |
33 | func NewNopObserver() NopObserver {
34 | return NopObserver{}
35 | }
36 | func (n NopObserver) Observe(_ netip.Addr) {}
37 |
--------------------------------------------------------------------------------
/pkg/list/elem.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package list
21 |
22 | type Elem[V any] struct {
23 | prev, next *Elem[V]
24 | list *List[V]
25 |
26 | Value V
27 | }
28 |
29 | func NewElem[V any](v V) *Elem[V] {
30 | return &Elem[V]{
31 | Value: v,
32 | }
33 | }
34 |
35 | func (e *Elem[V]) Prev() *Elem[V] {
36 | return e.prev
37 | }
38 |
39 | func (e *Elem[V]) Next() *Elem[V] {
40 | return e.next
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/list/list.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package list
21 |
22 | type List[V any] struct {
23 | front, back *Elem[V]
24 | length int
25 | }
26 |
27 | func New[V any]() *List[V] {
28 | return &List[V]{}
29 | }
30 |
31 | func mustBeFreeElem[V any](e *Elem[V]) {
32 | if e.prev != nil || e.next != nil || e.list != nil {
33 | panic("element is in use")
34 | }
35 | }
36 |
37 | func (l *List[V]) Front() *Elem[V] {
38 | return l.front
39 | }
40 |
41 | func (l *List[V]) Back() *Elem[V] {
42 | return l.back
43 | }
44 |
45 | func (l *List[V]) Len() int {
46 | return l.length
47 | }
48 |
49 | func (l *List[V]) PushFront(e *Elem[V]) *Elem[V] {
50 | mustBeFreeElem(e)
51 | l.length++
52 | e.list = l
53 | if l.front == nil {
54 | l.front = e
55 | l.back = e
56 | } else {
57 | e.next = l.front
58 | l.front.prev = e
59 | l.front = e
60 | }
61 | return e
62 | }
63 |
64 | func (l *List[V]) PushBack(e *Elem[V]) *Elem[V] {
65 | mustBeFreeElem(e)
66 | l.length++
67 | e.list = l
68 | if l.back == nil {
69 | l.front = e
70 | l.back = e
71 | } else {
72 | e.prev = l.back
73 | l.back.next = e
74 | l.back = e
75 | }
76 | return e
77 | }
78 |
79 | func (l *List[V]) PopElem(e *Elem[V]) *Elem[V] {
80 | if e.list != l {
81 | panic("elem is not belong to this list")
82 | }
83 |
84 | l.length--
85 | if p := e.prev; p != nil {
86 | p.next = e.next
87 | }
88 | if n := e.next; n != nil {
89 | n.prev = e.prev
90 | }
91 | if e == l.front {
92 | l.front = e.next
93 | }
94 | if e == l.back {
95 | l.back = e.prev
96 | }
97 | e.prev, e.next, e.list = nil, nil, nil
98 | return e
99 | }
100 |
--------------------------------------------------------------------------------
/pkg/list/list_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package list
21 |
22 | import (
23 | "github.com/stretchr/testify/assert"
24 | "reflect"
25 | "testing"
26 | )
27 |
28 | func checkLinkPointers[V any](t *testing.T, l *List[V]) {
29 | t.Helper()
30 |
31 | e := l.front
32 | for e != nil {
33 | if (e.next != nil && e.next.prev != e) || (e.prev != nil && e.prev.next != e) {
34 | t.Fatal("broken list")
35 | }
36 | e = e.next
37 | }
38 | }
39 |
40 | func makeElems(n []int) []*Elem[int] {
41 | s := make([]*Elem[int], 0, len(n))
42 | for _, i := range n {
43 | s = append(s, NewElem(i))
44 | }
45 | return s
46 | }
47 |
48 | func allValue[V any](l *List[V]) []V {
49 | s := make([]V, 0)
50 | node := l.front
51 | for node != nil {
52 | s = append(s, node.Value)
53 | node = node.next
54 | }
55 | return s
56 | }
57 |
58 | func TestList_Push(t *testing.T) {
59 | l := new(List[int])
60 | l.PushBack(NewElem(1))
61 | l.PushBack(NewElem(2))
62 | assert.Equal(t, []int{1, 2}, allValue(l))
63 | checkLinkPointers(t, l)
64 |
65 | l = new(List[int])
66 | l.PushFront(NewElem(1))
67 | l.PushFront(NewElem(2))
68 | assert.Equal(t, []int{2, 1}, allValue(l))
69 | checkLinkPointers(t, l)
70 | }
71 |
72 | func TestList_PopElem(t *testing.T) {
73 | tests := []struct {
74 | name string
75 | in []int
76 | pop int
77 | want int
78 | wantList []int
79 | }{
80 | {"pop front", []int{0, 1, 2}, 0, 0, []int{1, 2}},
81 | {"pop mid", []int{0, 1, 2}, 1, 1, []int{0, 2}},
82 | {"pop back", []int{0, 1, 2}, 2, 2, []int{0, 1}},
83 | }
84 | for _, tt := range tests {
85 | t.Run(tt.name, func(t *testing.T) {
86 | l := new(List[int])
87 | le := makeElems(tt.in)
88 | for _, e := range le {
89 | l.PushBack(e)
90 | }
91 | checkLinkPointers(t, l)
92 |
93 | got := l.PopElem(le[tt.pop])
94 | checkLinkPointers(t, l)
95 |
96 | if got.Value != tt.want {
97 | t.Errorf("PopElem() = %v, want %v", got.Value, tt.want)
98 | }
99 | if !reflect.DeepEqual(allValue(l), tt.wantList) {
100 | t.Errorf("allValue() = %v, want %v", allValue(l), tt.wantList)
101 | }
102 | })
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/pkg/lru/lru.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package lru
21 |
22 | import (
23 | "fmt"
24 | "github.com/IrineSistiana/mosdns/v4/pkg/list"
25 | )
26 |
27 | type LRU[K comparable, V any] struct {
28 | maxSize int
29 | onEvict func(key K, v V)
30 |
31 | l *list.List[KV[K, V]]
32 | m map[K]*list.Elem[KV[K, V]]
33 | }
34 |
35 | type KV[K comparable, V any] struct {
36 | key K
37 | v V
38 | }
39 |
40 | func NewLRU[K comparable, V any](maxSize int, onEvict func(key K, v V)) *LRU[K, V] {
41 | if maxSize <= 0 {
42 | panic(fmt.Sprintf("LRU: invalid max size: %d", maxSize))
43 | }
44 |
45 | return &LRU[K, V]{
46 | maxSize: maxSize,
47 | onEvict: onEvict,
48 | l: list.New[KV[K, V]](),
49 | m: make(map[K]*list.Elem[KV[K, V]]),
50 | }
51 | }
52 |
53 | func (q *LRU[K, V]) Add(key K, v V) {
54 | if e, ok := q.m[key]; ok { // update existed key
55 | e.Value.v = v
56 | q.l.PushBack(q.l.PopElem(e))
57 | return
58 | }
59 |
60 | o := q.Len() - q.maxSize + 1
61 | for o > 0 {
62 | key, v, _ := q.PopOldest()
63 | if q.onEvict != nil {
64 | q.onEvict(key, v)
65 | }
66 | o--
67 | }
68 |
69 | e := list.NewElem(KV[K, V]{
70 | key: key,
71 | v: v,
72 | })
73 | q.m[key] = e
74 | q.l.PushBack(e)
75 | }
76 |
77 | func (q *LRU[K, V]) Del(key K) {
78 | e := q.m[key]
79 | if e != nil {
80 | q.delElem(e)
81 | }
82 | }
83 |
84 | func (q *LRU[K, V]) delElem(e *list.Elem[KV[K, V]]) {
85 | key, v := e.Value.key, e.Value.v
86 | q.l.PopElem(e)
87 | delete(q.m, key)
88 | if q.onEvict != nil {
89 | q.onEvict(key, v)
90 | }
91 | }
92 |
93 | func (q *LRU[K, V]) PopOldest() (key K, v V, ok bool) {
94 | e := q.l.Front()
95 | if e != nil {
96 | q.l.PopElem(e)
97 | key, v = e.Value.key, e.Value.v
98 | delete(q.m, key)
99 | ok = true
100 | return
101 | }
102 | return
103 | }
104 |
105 | func (q *LRU[K, V]) Clean(f func(key K, v V) (remove bool)) (removed int) {
106 | e := q.l.Front()
107 | for e != nil {
108 | next := e.Next() // Delete e will clean its pointers. Save it first.
109 | key, v := e.Value.key, e.Value.v
110 | if remove := f(key, v); remove {
111 | q.delElem(e)
112 | removed++
113 | }
114 | e = next
115 | }
116 | return removed
117 | }
118 |
119 | func (q *LRU[K, V]) Get(key K) (v V, ok bool) {
120 | e, ok := q.m[key]
121 | if !ok {
122 | return
123 | }
124 | q.l.PushBack(q.l.PopElem(e))
125 | return e.Value.v, true
126 | }
127 |
128 | func (q *LRU[K, V]) Len() int {
129 | return q.l.Len()
130 | }
131 |
--------------------------------------------------------------------------------
/pkg/lru/lru_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package lru
21 |
22 | import (
23 | "testing"
24 | )
25 |
26 | func Test_lru(t *testing.T) {
27 | var q *LRU[int, int]
28 | reset := func(maxSize int) {
29 | t.Helper()
30 | q = NewLRU[int, int](maxSize, nil)
31 | }
32 |
33 | add := func(keys ...int) {
34 | t.Helper()
35 | for _, key := range keys {
36 | q.Add(key, key)
37 | }
38 | }
39 |
40 | mustGet := func(keys ...int) {
41 | t.Helper()
42 | for _, key := range keys {
43 | gotV, ok := q.Get(key)
44 | if !ok || gotV != key {
45 | t.Fatalf("want %v, got %v", key, gotV)
46 | }
47 | }
48 | }
49 |
50 | emptyGet := func(keys ...int) {
51 | t.Helper()
52 | for _, key := range keys {
53 | gotV, ok := q.Get(key)
54 | if ok {
55 | t.Fatalf("want empty, got %v", gotV)
56 | }
57 | }
58 | }
59 |
60 | mustPopOldest := func(keys ...int) {
61 | t.Helper()
62 | for _, key := range keys {
63 | gotKey, gotV, ok := q.PopOldest()
64 | if !ok {
65 | t.Fatal()
66 | }
67 | if gotKey != key || gotV != gotKey {
68 | t.Fatalf("want key: %v, v: %v, got key: %v, v:%v", key, key, gotKey, gotV)
69 | }
70 | }
71 | }
72 |
73 | emptyPop := func() {
74 | t.Helper()
75 | gotKey, gotV, ok := q.PopOldest()
76 | if ok {
77 | t.Fatalf("want empty result, got key: %v, v:%v", gotKey, gotV)
78 | }
79 | }
80 |
81 | checkLen := func(want int) {
82 | t.Helper()
83 | if q.l.Len() != len(q.m) {
84 | t.Fatalf("possible mem leak: q.l.Len() %v != len(q.m){ %v", q.l.Len(), len(q.m))
85 | }
86 | if want != q.Len() {
87 | t.Fatalf("want %v, got %v", want, q.Len())
88 | }
89 | }
90 |
91 | // test add
92 | reset(4)
93 | add(1, 1, 1, 1, 1, 1, 2, 3)
94 | checkLen(3)
95 | mustGet(1, 2, 3)
96 |
97 | // test add overflow
98 | reset(2)
99 | add(1, 2, 3, 4, 5)
100 | checkLen(2)
101 | mustGet(4, 5)
102 | emptyGet(1, 2, 3)
103 |
104 | // test pop
105 | reset(3)
106 | add(1, 2, 3)
107 | mustPopOldest(1, 2, 3)
108 | checkLen(0)
109 | emptyPop()
110 |
111 | // test del
112 | reset(3)
113 | add(1, 2, 3)
114 | q.Del(2)
115 | q.Del(9999)
116 | mustPopOldest(1, 3)
117 |
118 | // test clean
119 | reset(4)
120 | add(1, 2, 3, 4)
121 | cleanFunc := func(key int, v int) (remove bool) {
122 | switch key {
123 | case 1, 3:
124 | return true
125 | }
126 | return false
127 | }
128 | if cleaned := q.Clean(cleanFunc); cleaned != 2 {
129 | t.Fatalf("q.Clean want cleaned = 2, got %v", cleaned)
130 | }
131 | mustPopOldest(2, 4)
132 |
133 | // test lru
134 | reset(4)
135 | add(1, 2, 3, 4) // 1 2 3 4
136 | mustGet(2, 3) // 1 4 2 3
137 | mustPopOldest(1, 4, 2, 3)
138 | }
139 |
--------------------------------------------------------------------------------
/pkg/matcher/domain/interface.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package domain
21 |
22 | // "fqdn-insensitive" means the domain in Add() and Match() call
23 | // is fqdn-insensitive. "google.com" and "google.com." will get
24 | // the same outcome.
25 | // The logic for case-insensitive is the same as above.
26 |
27 | type Matcher[T any] interface {
28 | // Match matches the domain s.
29 | // s could be a fqdn or not, and should be case-insensitive.
30 | Match(s string) (v T, ok bool)
31 | Len() int
32 | }
33 |
34 | type WriteableMatcher[T any] interface {
35 | Matcher[T]
36 | Add(pattern string, v T) error
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/matcher/domain/load_helper_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package domain
21 |
22 | import (
23 | "reflect"
24 | "testing"
25 | )
26 |
27 | func TestParseV2Suffix(t *testing.T) {
28 | tests := []struct {
29 | name string
30 | args string
31 | want []*V2filter
32 | }{
33 | {"1", "test@a1@a2,,", []*V2filter{{Tag: "test", Attrs: []string{"a1", "a2"}}}},
34 | {"1", ",test@a1,,test@a1", []*V2filter{{Tag: "test", Attrs: []string{"a1"}}, {Tag: "test", Attrs: []string{"a1"}}}},
35 | }
36 | for _, tt := range tests {
37 | t.Run(tt.name, func(t *testing.T) {
38 | if got := ParseV2Suffix(tt.args); !reflect.DeepEqual(got, tt.want) {
39 | t.Errorf("ParseV2Suffix() = %v, want %v", got, tt.want)
40 | }
41 | })
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/matcher/domain/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package domain
21 |
22 | import (
23 | "strings"
24 | )
25 |
26 | type ReverseDomainScanner struct {
27 | s string // not fqdn
28 | p int
29 | t int
30 | }
31 |
32 | func NewReverseDomainScanner(s string) *ReverseDomainScanner {
33 | s = TrimDot(s)
34 | return &ReverseDomainScanner{
35 | s: s,
36 | p: len(s),
37 | t: len(s),
38 | }
39 | }
40 |
41 | func (s *ReverseDomainScanner) Scan() bool {
42 | if s.p <= 0 {
43 | return false
44 | }
45 | s.t = s.p
46 | s.p = strings.LastIndexByte(s.s[:s.p], '.')
47 | return true
48 | }
49 |
50 | func (s *ReverseDomainScanner) NextLabelOffset() int {
51 | return s.p + 1
52 | }
53 |
54 | func (s *ReverseDomainScanner) NextLabel() (label string) {
55 | return s.s[s.p+1 : s.t]
56 | }
57 |
58 | // NormalizeDomain normalize domain string s.
59 | // It removes the suffix "." and make sure the domain is in lower case.
60 | // e.g. a fqdn "GOOGLE.com." will become "google.com"
61 | func NormalizeDomain(s string) string {
62 | return strings.ToLower(TrimDot(s))
63 | }
64 |
65 | // TrimDot trims suffix '.'
66 | func TrimDot(s string) string {
67 | if len(s) >= 1 && s[len(s)-1] == '.' {
68 | s = s[:len(s)-1]
69 | }
70 | return s
71 | }
72 |
73 | // labelNode can store dns labels.
74 | type labelNode[T any] struct {
75 | children map[string]*labelNode[T] // lazy init
76 |
77 | v T
78 | hasV bool
79 | }
80 |
81 | func (n *labelNode[T]) storeValue(v T) {
82 | n.v = v
83 | n.hasV = true
84 | }
85 |
86 | func (n *labelNode[T]) getValue() (T, bool) {
87 | return n.v, n.hasV
88 | }
89 |
90 | func (n *labelNode[T]) hasValue() bool {
91 | return n.hasV
92 | }
93 |
94 | func (n *labelNode[T]) newChild(key string) *labelNode[T] {
95 | if n.children == nil {
96 | n.children = make(map[string]*labelNode[T])
97 | }
98 | node := new(labelNode[T])
99 | n.children[key] = node
100 | return node
101 | }
102 |
103 | func (n *labelNode[T]) getChild(key string) *labelNode[T] {
104 | return n.children[key]
105 | }
106 |
107 | func (n *labelNode[T]) len() int {
108 | l := 0
109 | for _, node := range n.children {
110 | l += node.len()
111 | if node.hasValue() {
112 | l++
113 | }
114 | }
115 | return l
116 | }
117 |
--------------------------------------------------------------------------------
/pkg/matcher/domain/utils_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package domain
21 |
22 | import (
23 | "reflect"
24 | "testing"
25 | )
26 |
27 | func TestDomainScanner(t *testing.T) {
28 | tests := []struct {
29 | name string
30 | fqdn string
31 | wantOffsets []int
32 | wantLabels []string
33 | }{
34 | {"empty", "", []int{}, []string{}},
35 | {"root", ".", []int{}, []string{}},
36 | {"non fqdn", "a.2", []int{2, 0}, []string{"2", "a"}},
37 | {"domain", "1.2.3.", []int{4, 2, 0}, []string{"3", "2", "1"}},
38 | }
39 | for _, tt := range tests {
40 | t.Run(tt.name, func(t *testing.T) {
41 | s := NewReverseDomainScanner(tt.fqdn)
42 | gotOffsets := make([]int, 0)
43 | for s.Scan() {
44 | gotOffsets = append(gotOffsets, s.NextLabelOffset())
45 | }
46 | if !reflect.DeepEqual(gotOffsets, tt.wantOffsets) {
47 | t.Errorf("PrevLabelOffset() = %v, want %v", gotOffsets, tt.wantOffsets)
48 | }
49 |
50 | s = NewReverseDomainScanner(tt.fqdn)
51 | gotLabels := make([]string, 0)
52 | for s.Scan() {
53 | pl := s.NextLabel()
54 | gotLabels = append(gotLabels, pl)
55 | }
56 | if !reflect.DeepEqual(gotLabels, tt.wantLabels) {
57 | t.Errorf("PrevLabel() = %v, want %v", gotLabels, tt.wantLabels)
58 | }
59 | })
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/pkg/matcher/elem/rr_type.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package elem
21 |
22 | type IntMatcher struct {
23 | m map[int]struct{}
24 | }
25 |
26 | // NewIntMatcher inits a new IntMatcher.
27 | func NewIntMatcher(elem []int) *IntMatcher {
28 | matcher := &IntMatcher{m: make(map[int]struct{})}
29 |
30 | for _, v := range elem {
31 | matcher.m[v] = struct{}{}
32 | }
33 | return matcher
34 | }
35 |
36 | func (m *IntMatcher) Match(v int) bool {
37 | _, ok := m.m[v]
38 | return ok
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/matcher/elem/rr_type_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package elem
21 |
22 | import "testing"
23 |
24 | func TestIntMatcher_Match(t *testing.T) {
25 | tests := []struct {
26 | name string
27 | m []int
28 | args int
29 | want bool
30 | }{
31 | {"nil 1", nil, 1, false},
32 | {"matched 1", []int{1, 2, 3}, 1, true},
33 | {"matched 2", []int{1, 2, 3}, 3, true},
34 | {"not matched 1", []int{1, 2, 3}, 0, false},
35 | {"not matched 2", []int{1, 2, 3}, 4, false},
36 | }
37 | for _, tt := range tests {
38 | t.Run(tt.name, func(t *testing.T) {
39 | m := NewIntMatcher(tt.m)
40 | if got := m.Match(tt.args); got != tt.want {
41 | t.Errorf("Match() = %v, want %v", got, tt.want)
42 | }
43 | })
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/matcher/msg_matcher/response.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package msg_matcher
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/pkg/matcher/domain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/matcher/elem"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/matcher/netlist"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
28 | "github.com/miekg/dns"
29 | "net"
30 | "net/netip"
31 | )
32 |
33 | type AAAAAIPMatcher struct {
34 | ipMatcher netlist.Matcher
35 | }
36 |
37 | func NewAAAAAIPMatcher(ipMatcher netlist.Matcher) *AAAAAIPMatcher {
38 | return &AAAAAIPMatcher{ipMatcher: ipMatcher}
39 | }
40 |
41 | func (m *AAAAAIPMatcher) Match(_ context.Context, qCtx *query_context.Context) (bool, error) {
42 | r := qCtx.R()
43 | if r == nil {
44 | return false, nil
45 | }
46 |
47 | return m.MatchMsg(r)
48 | }
49 |
50 | func (m *AAAAAIPMatcher) MatchMsg(msg *dns.Msg) (bool, error) {
51 | for _, rr := range msg.Answer {
52 | var ip net.IP
53 | switch rr := rr.(type) {
54 | case *dns.A:
55 | ip = rr.A
56 | case *dns.AAAA:
57 | ip = rr.AAAA
58 | default:
59 | continue
60 | }
61 | addr, _ := netip.AddrFromSlice(ip)
62 | matched, err := m.ipMatcher.Match(addr)
63 | if err != nil {
64 | return false, err
65 | }
66 | if matched {
67 | return true, nil
68 | }
69 | }
70 | return false, nil
71 | }
72 |
73 | type CNameMatcher struct {
74 | domainMatcher domain.Matcher[struct{}]
75 | }
76 |
77 | func NewCNameMatcher(domainMatcher domain.Matcher[struct{}]) *CNameMatcher {
78 | return &CNameMatcher{domainMatcher: domainMatcher}
79 | }
80 |
81 | func (m *CNameMatcher) Match(_ context.Context, qCtx *query_context.Context) (matched bool, _ error) {
82 | r := qCtx.R()
83 | if r == nil {
84 | return false, nil
85 | }
86 |
87 | return m.MatchMsg(r), nil
88 | }
89 |
90 | func (m *CNameMatcher) MatchMsg(msg *dns.Msg) bool {
91 | for _, rr := range msg.Answer {
92 | if cname, ok := rr.(*dns.CNAME); ok {
93 | if _, ok := m.domainMatcher.Match(cname.Target); ok {
94 | return true
95 | }
96 | }
97 | }
98 | return false
99 | }
100 |
101 | type RCodeMatcher struct {
102 | elemMatcher *elem.IntMatcher
103 | }
104 |
105 | func NewRCodeMatcher(elemMatcher *elem.IntMatcher) *RCodeMatcher {
106 | return &RCodeMatcher{elemMatcher: elemMatcher}
107 | }
108 |
109 | func (m *RCodeMatcher) Match(_ context.Context, qCtx *query_context.Context) (matched bool, _ error) {
110 | r := qCtx.R()
111 | if r == nil {
112 | return false, nil
113 | }
114 | return m.MatchMsg(r), nil
115 | }
116 |
117 | func (m *RCodeMatcher) MatchMsg(msg *dns.Msg) bool {
118 | return m.elemMatcher.Match(msg.Rcode)
119 | }
120 |
--------------------------------------------------------------------------------
/pkg/matcher/msg_matcher/response_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package msg_matcher
21 |
22 | import (
23 | "github.com/IrineSistiana/mosdns/v4/pkg/matcher/netlist"
24 | "github.com/miekg/dns"
25 | "net"
26 | "testing"
27 | )
28 |
29 | func TestAAAAAIPMatcher_MatchMsg(t *testing.T) {
30 | nl := netlist.NewList()
31 | if err := netlist.LoadFromText(nl, "127.0.0.0/24"); err != nil {
32 | t.Fatal(err)
33 | }
34 | nl.Sort()
35 | m := NewAAAAAIPMatcher(nl)
36 |
37 | ip1271 := net.ParseIP("127.0.0.1")
38 | ip1281 := net.ParseIP("128.0.0.1")
39 |
40 | msg := new(dns.Msg)
41 | msg.Answer = []dns.RR{&dns.A{A: ip1281}, &dns.A{A: ip1271}}
42 | if matched, err := m.MatchMsg(msg); !matched || err != nil {
43 | t.Fatal()
44 | }
45 |
46 | msg.Answer = []dns.RR{&dns.A{A: ip1281}}
47 | if matched, err := m.MatchMsg(msg); matched || err != nil {
48 | t.Fatal()
49 | }
50 |
51 | msg.Answer = []dns.RR{}
52 | if matched, err := m.MatchMsg(msg); matched || err != nil {
53 | t.Fatal()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/matcher/netlist/interface.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package netlist
21 |
22 | import (
23 | "net/netip"
24 | )
25 |
26 | type Matcher interface {
27 | Match(addr netip.Addr) (bool, error)
28 | Len() int
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/matcher/netlist/netlist_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package netlist
21 |
22 | import (
23 | "bytes"
24 | "net/netip"
25 | "testing"
26 | )
27 |
28 | func TestIPNetList_Sort_And_Merge(t *testing.T) {
29 | raw := `
30 | 192.168.0.0/32 # merged
31 | 192.168.0.0/24 # merged
32 | 192.168.0.0/16
33 | 192.168.1.1/24 # merged
34 | 192.168.9.24/24 # merged
35 | 192.168.3.0/24 # merged
36 | 192.169.0.0/16
37 | `
38 | ipNetList := NewList()
39 | err := LoadFromReader(ipNetList, bytes.NewBufferString(raw))
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | ipNetList.Sort()
44 |
45 | if ipNetList.Len() != 2 {
46 | t.Fatalf("unexpected length %d", ipNetList.Len())
47 | }
48 |
49 | tests := []struct {
50 | name string
51 | testIP netip.Addr
52 | want bool
53 | }{
54 | {"0", netip.MustParseAddr("192.167.255.255"), false},
55 | {"1", netip.MustParseAddr("192.168.0.0"), true},
56 | {"2", netip.MustParseAddr("192.168.1.1"), true},
57 | {"3", netip.MustParseAddr("192.168.9.255"), true},
58 | {"4", netip.MustParseAddr("192.168.255.255"), true},
59 | {"5", netip.MustParseAddr("192.169.1.1"), true},
60 | {"6", netip.MustParseAddr("192.170.1.1"), false},
61 | {"7", netip.MustParseAddr("1.1.1.1"), false},
62 | }
63 | for _, tt := range tests {
64 | t.Run(tt.name, func(t *testing.T) {
65 | if got, _ := ipNetList.Match(tt.testIP); got != tt.want {
66 | t.Errorf("IPNetList.Match() = %v, want %v", got, tt.want)
67 | }
68 | })
69 | }
70 | }
71 |
72 | func TestIPNetList_New_And_Contains(t *testing.T) {
73 | raw := `
74 | # comment line
75 | 1.0.0.0/24 additional strings should be ignored
76 | 2.0.0.0/23 # comment
77 | 3.0.0.0
78 |
79 | 2000:0000::/32
80 | 2000:2000::1
81 | `
82 |
83 | ipNetList := NewList()
84 | err := LoadFromReader(ipNetList, bytes.NewBufferString(raw))
85 | if err != nil {
86 | t.Fatal(err)
87 | }
88 | ipNetList.Sort()
89 |
90 | tests := []struct {
91 | name string
92 | testIP netip.Addr
93 | want bool
94 | }{
95 | {"", netip.MustParseAddr("1.0.0.0"), true},
96 | {"", netip.MustParseAddr("1.0.0.1"), true},
97 | {"", netip.MustParseAddr("1.0.1.0"), false},
98 | {"", netip.MustParseAddr("2.0.0.0"), true},
99 | {"", netip.MustParseAddr("2.0.1.255"), true},
100 | {"", netip.MustParseAddr("2.0.2.0"), false},
101 | {"", netip.MustParseAddr("3.0.0.0"), true},
102 | {"", netip.MustParseAddr("2000:0000::"), true},
103 | {"", netip.MustParseAddr("2000:0000::1"), true},
104 | {"", netip.MustParseAddr("2000:0000:1::"), true},
105 | {"", netip.MustParseAddr("2000:0001::"), false},
106 | {"", netip.MustParseAddr("2000:2000::1"), true},
107 | }
108 | for _, tt := range tests {
109 | t.Run(tt.name, func(t *testing.T) {
110 | if got, _ := ipNetList.Match(tt.testIP); got != tt.want {
111 | t.Errorf("IPNetList.Match() = %v, want %v", got, tt.want)
112 | }
113 | })
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/pkg/matcher/v2data/data.proto:
--------------------------------------------------------------------------------
1 | // This proto is a copy of v2ray.com/core/app/router/config.proto
2 |
3 | syntax = "proto3";
4 |
5 | package v2data;
6 |
7 | option go_package = "pkg/matcher/v2data";
8 |
9 | // Domain for routing decision.
10 | message Domain {
11 | // Type of domain value.
12 | enum Type {
13 | // The value is used as is.
14 | Plain = 0;
15 | // The value is used as a regular expression.
16 | Regex = 1;
17 | // The value is a root domain.
18 | Domain = 2;
19 | // The value is a domain.
20 | Full = 3;
21 | }
22 |
23 | // Domain matching type.
24 | Type type = 1;
25 |
26 | // Domain value.
27 | string value = 2;
28 |
29 | message Attribute {
30 | string key = 1;
31 |
32 | oneof typed_value {
33 | bool bool_value = 2;
34 | int64 int_value = 3;
35 | }
36 | }
37 |
38 | // Attributes of this domain. May be used for filtering.
39 | repeated Attribute attribute = 3;
40 | }
41 |
42 | // IP for routing decision, in CIDR form.
43 | message CIDR {
44 | // IP address, should be either 4 or 16 bytes.
45 | bytes ip = 1;
46 |
47 | // Number of leading ones in the network mask.
48 | uint32 prefix = 2;
49 | }
50 |
51 | message GeoIP {
52 | string country_code = 1;
53 | repeated CIDR cidr = 2;
54 | }
55 |
56 | message GeoIPList {
57 | repeated GeoIP entry = 1;
58 | }
59 |
60 | message GeoSite {
61 | string country_code = 1;
62 | repeated Domain domain = 2;
63 | }
64 |
65 | message GeoSiteList {
66 | repeated GeoSite entry = 1;
67 | }
68 |
--------------------------------------------------------------------------------
/pkg/nftset_utils/handler.go:
--------------------------------------------------------------------------------
1 | //go:build linux
2 |
3 | /*
4 | * Copyright (C) 2020-2022, IrineSistiana
5 | *
6 | * This file is part of mosdns.
7 | *
8 | * mosdns is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * mosdns is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with this program. If not, see .
20 | */
21 |
22 | package nftset_utils
23 |
24 | import (
25 | "fmt"
26 | "github.com/google/nftables"
27 | "go4.org/netipx"
28 | "net/netip"
29 | "sync"
30 | "time"
31 | )
32 |
33 | // NftSetHandler can add netip.Prefix to the corresponding set.
34 | // The table that contains this set must be an inet family table.
35 | // If the set has a 'interval' flag, the prefix from netip.Prefix will be
36 | // applied.
37 | type NftSetHandler struct {
38 | opts HandlerOpts
39 | table *nftables.Table
40 |
41 | m sync.Mutex
42 | lastUpdate time.Time
43 | set *nftables.Set
44 | }
45 |
46 | type HandlerOpts struct {
47 | Conn *nftables.Conn // Required.
48 | TableFamily nftables.TableFamily
49 | TableName string // Required
50 | SetName string // Required
51 | }
52 |
53 | // NewNtSetHandler inits NftSetHandler.
54 | func NewNtSetHandler(opts HandlerOpts) *NftSetHandler {
55 | table := &nftables.Table{
56 | Name: opts.TableName,
57 | Family: opts.TableFamily,
58 | }
59 | return &NftSetHandler{
60 | opts: opts,
61 | table: table,
62 | }
63 | }
64 |
65 | // getSet get set info from kernel. It has an internal cache and won't
66 | // invoke a syscall every time.
67 | func (h *NftSetHandler) getSet() (*nftables.Set, error) {
68 | const refreshInterval = time.Second
69 |
70 | now := time.Now()
71 | h.m.Lock()
72 | defer h.m.Unlock()
73 | if h.set != nil && now.Sub(h.lastUpdate) < refreshInterval {
74 | return h.set, nil
75 | }
76 | set, err := h.opts.Conn.GetSetByName(h.table, h.opts.SetName)
77 | if err != nil {
78 | return nil, err
79 | }
80 | h.set = set
81 | h.lastUpdate = now
82 | return set, nil
83 | }
84 |
85 | // AddElems adds SetIPElem(s) to set in a single batch.
86 | func (h *NftSetHandler) AddElems(es ...netip.Prefix) error {
87 | set, err := h.getSet()
88 | if err != nil {
89 | return fmt.Errorf("failed to get set, %w", err)
90 | }
91 |
92 | var elems []nftables.SetElement
93 | if set.Interval {
94 | elems = make([]nftables.SetElement, 0, 2*len(es))
95 | } else {
96 | elems = make([]nftables.SetElement, 0, len(es))
97 | }
98 |
99 | for _, e := range es {
100 | if set.Interval {
101 | r := netipx.RangeOfPrefix(e)
102 | start := r.From()
103 | end := r.To()
104 | elems = append(
105 | elems,
106 | nftables.SetElement{Key: start.AsSlice(), IntervalEnd: false},
107 | nftables.SetElement{Key: end.Next().AsSlice(), IntervalEnd: true},
108 | )
109 | } else {
110 | elems = append(elems, nftables.SetElement{Key: e.Addr().AsSlice()})
111 | }
112 | }
113 |
114 | err = h.opts.Conn.SetAddElements(set, elems)
115 | if err != nil {
116 | return err
117 | }
118 | return h.opts.Conn.Flush()
119 | }
120 |
--------------------------------------------------------------------------------
/pkg/nftset_utils/handler_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package nftset_utils
21 |
22 | import (
23 | "github.com/google/nftables"
24 | "net/netip"
25 | "os"
26 | "testing"
27 | )
28 |
29 | func skipCI(t *testing.T) {
30 | if os.Getenv("TEST_NFTSET") == "" {
31 | t.SkipNow()
32 | }
33 | }
34 |
35 | func prepareSet(t testing.TB, tableName, setName string, interval bool) {
36 | t.Helper()
37 | nc, err := nftables.New()
38 | if err != nil {
39 | t.Fatal(err)
40 | }
41 |
42 | table := &nftables.Table{Name: tableName, Family: nftables.TableFamilyINet}
43 | nc.AddTable(table)
44 | if err := nc.AddSet(&nftables.Set{Name: setName, Table: table, KeyType: nftables.TypeIPAddr, Interval: interval}, nil); err != nil {
45 | t.Fatal(err)
46 | }
47 | if err := nc.Flush(); err != nil {
48 | t.Fatal(err)
49 | }
50 | }
51 |
52 | func Test_AddElems(t *testing.T) {
53 | skipCI(t)
54 | n := "test"
55 | prepareSet(t, n, n, false)
56 |
57 | nc, err := nftables.New()
58 | if err != nil {
59 | t.Fatal(err)
60 | }
61 |
62 | h := NewNtSetHandler(HandlerOpts{
63 | Conn: nc,
64 | TableFamily: nftables.TableFamilyINet,
65 | TableName: n,
66 | SetName: n,
67 | })
68 |
69 | if err := h.AddElems(netip.MustParsePrefix("127.0.0.1/24")); err != nil {
70 | t.Fatal(err)
71 | }
72 | elems, err := nc.GetSetElements(h.set)
73 | if err != nil {
74 | t.Fatal(err)
75 | }
76 | if len(elems) == 0 {
77 | t.Fatal("set is empty")
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/pool/allocator_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package pool
21 |
22 | import (
23 | "fmt"
24 | "strconv"
25 | "testing"
26 | )
27 |
28 | func TestAllocator_Get(t *testing.T) {
29 | alloc := NewAllocator(8) // 256 bytes
30 | tests := []struct {
31 | size int
32 | wantCap int
33 | wantPanic bool
34 | }{
35 | {-1, 0, true}, // invalid
36 | {0, 1, false},
37 | {1, 1, false},
38 | {2, 2, false},
39 | {12, 16, false},
40 | {256, 256, false},
41 | {257, 0, true}, // invalid, too large
42 | }
43 | for _, tt := range tests {
44 | t.Run(strconv.Itoa(tt.size), func(t *testing.T) {
45 | if tt.wantPanic {
46 | defer func() {
47 | msg := recover()
48 | if msg == nil {
49 | t.Error("no panic")
50 | }
51 | }()
52 | }
53 |
54 | for i := 0; i < 5; i++ {
55 | b := alloc.Get(tt.size)
56 | if b.Len() != tt.size {
57 | t.Fatalf("buffer size, want %d, got %d", tt.size, b.Len())
58 | }
59 | if b.Cap() != tt.wantCap {
60 | t.Fatalf("buffer cap, want %d, got %d", tt.wantCap, b.Cap())
61 | }
62 | alloc.Release(b)
63 | }
64 | })
65 | }
66 | }
67 |
68 | func Test_shard(t *testing.T) {
69 | tests := []struct {
70 | size int
71 | want int
72 | }{
73 | {-1, 0},
74 | {0, 0},
75 | {1, 0},
76 | {2, 1},
77 | {3, 2},
78 | {4, 2},
79 | {5, 3},
80 | {8, 3},
81 | {1023, 10},
82 | {1024, 10},
83 | {1025, 11},
84 | }
85 | for _, tt := range tests {
86 | t.Run(strconv.Itoa(tt.size), func(t *testing.T) {
87 | if got := shard(tt.size); got != tt.want {
88 | t.Errorf("shard() = %v, want %v", got, tt.want)
89 | }
90 | })
91 | }
92 | }
93 |
94 | func Benchmark_Allocator(b *testing.B) {
95 | allocator := NewAllocator(16)
96 |
97 | for l := 0; l <= 16; l += 4 {
98 | bufLen := 1 << l
99 | b.Run(fmt.Sprintf("length %d", bufLen), func(b *testing.B) {
100 | b.ReportAllocs()
101 | for i := 0; i < b.N; i++ {
102 | buf := allocator.Get(bufLen)
103 | allocator.Release(buf)
104 | }
105 | })
106 | }
107 | }
108 |
109 | func Benchmark_MakeByteSlice(b *testing.B) {
110 | for l := 0; l <= 8; l++ {
111 | bufLen := 1 << l
112 | b.Run(fmt.Sprintf("length %d", bufLen), func(b *testing.B) {
113 | b.ReportAllocs()
114 | for i := 0; i < b.N; i++ {
115 | _ = make([]byte, bufLen)
116 | }
117 | })
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/pkg/pool/bytes_buf.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package pool
21 |
22 | import (
23 | "bytes"
24 | "fmt"
25 | "sync"
26 | )
27 |
28 | type BytesBufPool struct {
29 | p sync.Pool
30 | }
31 |
32 | func NewBytesBufPool(initSize int) *BytesBufPool {
33 | if initSize < 0 {
34 | panic(fmt.Sprintf("utils.NewBytesBufPool: negative init size %d", initSize))
35 | }
36 |
37 | return &BytesBufPool{
38 | p: sync.Pool{New: func() interface{} {
39 | b := new(bytes.Buffer)
40 | b.Grow(initSize)
41 | return b
42 | }},
43 | }
44 | }
45 |
46 | func (p *BytesBufPool) Get() *bytes.Buffer {
47 | return p.p.Get().(*bytes.Buffer)
48 | }
49 |
50 | func (p *BytesBufPool) Release(b *bytes.Buffer) {
51 | b.Reset()
52 | p.p.Put(b)
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/pool/msg_buf.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package pool
21 |
22 | import (
23 | "github.com/miekg/dns"
24 | )
25 |
26 | // There is no such way to give dns.Msg.PackBuffer() a buffer
27 | // with a proper size.
28 | // Just give it a big buf and hope the buf will be reused in most scenes.
29 | const packBufSize = 4096
30 |
31 | // PackBuffer packs the dns msg m to wire format.
32 | // Callers should release the buf after they have done with the wire []byte.
33 | func PackBuffer(m *dns.Msg) (wire []byte, buf *Buffer, err error) {
34 | buf = GetBuf(packBufSize)
35 | wire, err = m.PackBuffer(buf.Bytes())
36 | if err != nil {
37 | buf.Release()
38 | return nil, nil, err
39 | }
40 | return wire, buf, nil
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/pool/msg_buf_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package pool
21 |
22 | import (
23 | "github.com/miekg/dns"
24 | "testing"
25 | )
26 |
27 | func TestPackBuffer_No_Allocation(t *testing.T) {
28 | m := new(dns.Msg)
29 | m.SetQuestion("123.", dns.TypeAAAA)
30 | wire, buf, err := PackBuffer(m)
31 | if err != nil {
32 | t.Fatal(err)
33 | }
34 |
35 | if cap(wire) != buf.Cap() {
36 | t.Fatalf("wire and buf have different cap, wire %d, buf %d", cap(wire), buf.Cap())
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/pool/timer.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package pool
21 |
22 | import (
23 | "sync"
24 | "time"
25 | )
26 |
27 | var (
28 | timerPool = sync.Pool{}
29 | )
30 |
31 | func GetTimer(t time.Duration) *time.Timer {
32 | timer, ok := timerPool.Get().(*time.Timer)
33 | if !ok {
34 | return time.NewTimer(t)
35 | }
36 | if timer.Reset(t) {
37 | panic("dispatcher.go getTimer: active timer trapped in timerPool")
38 | }
39 | return timer
40 | }
41 |
42 | func ReleaseTimer(timer *time.Timer) {
43 | if !timer.Stop() {
44 | select {
45 | case <-timer.C:
46 | default:
47 | }
48 | }
49 | timerPool.Put(timer)
50 | }
51 |
52 | func ResetAndDrainTimer(timer *time.Timer, d time.Duration) {
53 | if !timer.Stop() {
54 | select {
55 | case <-timer.C:
56 | default:
57 | }
58 | }
59 | timer.Reset(d)
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/safe_close/safe_close.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package safe_close
21 |
22 | import "sync"
23 |
24 | // SafeClose can achieve safe close where CloseWait returns only after
25 | // all sub goroutines exited.
26 | //
27 | // 1. Main service goroutine starts and wait on ReceiveCloseSignal and call Done before returns.
28 | // 2. Any service's sub goroutine should be started by Attach and wait on ReceiveCloseSignal.
29 | // 3. If any fatal err occurs, any service goroutine can call SendCloseSignal to close the service.
30 | // Note that CloseWait cannot be called in the service, otherwise it will be deadlocked.
31 | // 4. Any third party caller can call CloseWait to close the service.
32 | type SafeClose struct {
33 | m sync.Mutex
34 | wg sync.WaitGroup
35 | closeSignal chan struct{}
36 | done chan struct{}
37 | closeErr error
38 | }
39 |
40 | func NewSafeClose() *SafeClose {
41 | return &SafeClose{
42 | closeSignal: make(chan struct{}),
43 | done: make(chan struct{}),
44 | }
45 | }
46 |
47 | // CloseWait sends a close signal to SafeClose and wait until it is closed.
48 | // It is concurrent safe and can be called multiple times.
49 | // CloseWait blocks until s.Done() is called and all Attach-ed goroutines is done.
50 | func (s *SafeClose) CloseWait() {
51 | s.m.Lock()
52 | select {
53 | case <-s.closeSignal:
54 | default:
55 | close(s.closeSignal)
56 | }
57 | s.m.Unlock()
58 |
59 | s.wg.Wait()
60 | <-s.done
61 | }
62 |
63 | // SendCloseSignal sends a close signal.
64 | func (s *SafeClose) SendCloseSignal(err error) {
65 | s.m.Lock()
66 | select {
67 | case <-s.closeSignal:
68 | default:
69 | s.closeErr = err
70 | close(s.closeSignal)
71 | }
72 | s.m.Unlock()
73 | }
74 |
75 | // Err returns the first SendCloseSignal error.
76 | func (s *SafeClose) Err() error {
77 | s.m.Lock()
78 | defer s.m.Unlock()
79 | return s.closeErr
80 | }
81 |
82 | func (s *SafeClose) ReceiveCloseSignal() <-chan struct{} {
83 | return s.closeSignal
84 | }
85 |
86 | // Attach add this goroutine to s.wg CloseWait.
87 | // f must receive closeSignal and call done when it is done.
88 | // If s was closed, f will not run.
89 | func (s *SafeClose) Attach(f func(done func(), closeSignal <-chan struct{})) {
90 | s.m.Lock()
91 | select {
92 | case <-s.closeSignal:
93 | default:
94 | s.wg.Add(1)
95 | go func() {
96 | f(s.attachDone, s.closeSignal)
97 | }()
98 | }
99 | s.m.Unlock()
100 | }
101 |
102 | func (s *SafeClose) attachDone() {
103 | s.m.Lock()
104 | defer s.m.Unlock()
105 | s.wg.Done()
106 | }
107 |
108 | // Done notifies CloseWait that is done.
109 | // It is concurrent safe and can be called multiple times.
110 | func (s *SafeClose) Done() {
111 | s.m.Lock()
112 | defer s.m.Unlock()
113 |
114 | select {
115 | case <-s.done:
116 | default:
117 | close(s.done)
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/pkg/server/doh.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package server
21 |
22 | import (
23 | "go.uber.org/zap"
24 | "golang.org/x/net/http2"
25 | "io"
26 | "net"
27 | "net/http"
28 | "time"
29 | )
30 |
31 | func (s *Server) ServeHTTP(l net.Listener) error {
32 | return s.serveHTTP(l, false)
33 | }
34 |
35 | func (s *Server) ServeHTTPS(l net.Listener) error {
36 | return s.serveHTTP(l, true)
37 | }
38 |
39 | func (s *Server) serveHTTP(l net.Listener, https bool) error {
40 | defer l.Close()
41 |
42 | if s.opts.HttpHandler == nil {
43 | return errMissingHTTPHandler
44 | }
45 |
46 | hs := &http.Server{
47 | Handler: s.opts.HttpHandler,
48 | ReadHeaderTimeout: time.Millisecond * 500,
49 | ReadTimeout: time.Second * 5,
50 | WriteTimeout: time.Second * 5,
51 | IdleTimeout: s.opts.IdleTimeout,
52 | MaxHeaderBytes: 2048,
53 | TLSConfig: s.opts.TLSConfig.Clone(),
54 | }
55 | closer := io.Closer(hs)
56 | if ok := s.trackCloser(&closer, true); !ok {
57 | return ErrServerClosed
58 | }
59 | defer s.trackCloser(&closer, false)
60 |
61 | if err := http2.ConfigureServer(hs, &http2.Server{IdleTimeout: s.opts.IdleTimeout}); err != nil {
62 | s.opts.Logger.Error("failed to set up http2 support", zap.Error(err))
63 | }
64 |
65 | var err error
66 | if https {
67 | err = hs.ServeTLS(l, s.opts.Cert, s.opts.Key)
68 | } else {
69 | err = hs.Serve(l)
70 | }
71 | if err == http.ErrServerClosed { // Replace http.ErrServerClosed with our ErrServerClosed
72 | return ErrServerClosed
73 | }
74 | return err
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/server/dot.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package server
21 |
22 | import (
23 | "crypto/tls"
24 | "errors"
25 | "net"
26 | )
27 |
28 | func (s *Server) ServeTLS(l net.Listener) error {
29 | var tlsConf *tls.Config
30 | if s.opts.TLSConfig != nil {
31 | tlsConf = s.opts.TLSConfig.Clone()
32 | } else {
33 | tlsConf = new(tls.Config)
34 | }
35 |
36 | if len(s.opts.Key)+len(s.opts.Cert) != 0 {
37 | cert, err := tls.LoadX509KeyPair(s.opts.Cert, s.opts.Key)
38 | if err != nil {
39 | return err
40 | }
41 | tlsConf.Certificates = append(tlsConf.Certificates, cert)
42 | }
43 |
44 | if len(tlsConf.Certificates) == 0 {
45 | return errors.New("missing certificate for tls listener")
46 | }
47 |
48 | l = tls.NewListener(l, tlsConf)
49 | return s.ServeTCP(l)
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/server/tcp.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package server
21 |
22 | import (
23 | "context"
24 | "fmt"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/dnsutils"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/pool"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
28 | "github.com/IrineSistiana/mosdns/v4/pkg/utils"
29 | "go.uber.org/zap"
30 | "io"
31 | "net"
32 | "time"
33 | )
34 |
35 | const (
36 | defaultTCPIdleTimeout = time.Second * 10
37 | tcpFirstReadTimeout = time.Millisecond * 500
38 | )
39 |
40 | func (s *Server) ServeTCP(l net.Listener) error {
41 | defer l.Close()
42 |
43 | handler := s.opts.DNSHandler
44 | if handler == nil {
45 | return errMissingDNSHandler
46 | }
47 |
48 | closer := l.(io.Closer)
49 | if ok := s.trackCloser(&closer, true); !ok {
50 | return ErrServerClosed
51 | }
52 | defer s.trackCloser(&closer, false)
53 |
54 | // handle listener
55 | listenerCtx, cancel := context.WithCancel(context.Background())
56 | defer cancel()
57 | for {
58 | c, err := l.Accept()
59 | if err != nil {
60 | if s.Closed() {
61 | return ErrServerClosed
62 | }
63 | return fmt.Errorf("unexpected listener err: %w", err)
64 | }
65 |
66 | // handle connection
67 | tcpConnCtx, cancelConn := context.WithCancel(listenerCtx)
68 | go func() {
69 | defer c.Close()
70 | defer cancelConn()
71 |
72 | closer := c.(io.Closer)
73 | if !s.trackCloser(&closer, true) {
74 | return
75 | }
76 | defer s.trackCloser(&closer, false)
77 |
78 | firstReadTimeout := tcpFirstReadTimeout
79 | idleTimeout := s.opts.IdleTimeout
80 | if idleTimeout < firstReadTimeout {
81 | firstReadTimeout = idleTimeout
82 | }
83 |
84 | clientAddr := utils.GetAddrFromAddr(c.RemoteAddr())
85 | meta := &query_context.RequestMeta{
86 | ClientAddr: clientAddr,
87 | }
88 |
89 | firstRead := true
90 | for {
91 | if firstRead {
92 | firstRead = false
93 | c.SetReadDeadline(time.Now().Add(firstReadTimeout))
94 | } else {
95 | c.SetReadDeadline(time.Now().Add(idleTimeout))
96 | }
97 | req, _, err := dnsutils.ReadMsgFromTCP(c)
98 | if err != nil {
99 | return // read err, close the connection
100 | }
101 |
102 | // handle query
103 | go func() {
104 | r, err := handler.ServeDNS(tcpConnCtx, req, meta)
105 | if err != nil {
106 | s.opts.Logger.Warn("handler err", zap.Error(err))
107 | c.Close()
108 | return
109 | }
110 |
111 | b, buf, err := pool.PackBuffer(r)
112 | if err != nil {
113 | s.opts.Logger.Error("failed to unpack handler's response", zap.Error(err), zap.Stringer("msg", r))
114 | return
115 | }
116 | defer buf.Release()
117 |
118 | if _, err := dnsutils.WriteRawMsgToTCP(c, b); err != nil {
119 | s.opts.Logger.Warn("failed to write response", zap.Stringer("client", c.RemoteAddr()), zap.Error(err))
120 | return
121 | }
122 | }()
123 | }
124 | }()
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/pkg/server/testdata/test.test.cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIBcTCCARegAwIBAgIQDmAzIsMq/6e3slB6nTEK0TAKBggqhkjOPQQDAjAUMRIw
3 | EAYDVQQDEwl0ZXN0LnRlc3QwHhcNMjEwNDE4MDQzNzMxWhcNMzEwNDE4MDQzNzMx
4 | WjAUMRIwEAYDVQQDEwl0ZXN0LnRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
5 | AARhxRbCpNr2B6qk6gxnPpvScH/NAhpYD5PZYZHu15ZUjf1LWrEpfoyjsot0ltRk
6 | jYhBu3NgyxKm62UmI0UC+moio0swSTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAww
7 | CgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALggl0ZXN0LnRlc3Qw
8 | CgYIKoZIzj0EAwIDSAAwRQIhAL47WhwilIcDLy7BiQguiPXQeY7qSaBhKJ1KmQs1
9 | FZZdAiBO+A2E60qm8snZJDuSyI0tz1CYiHEmAzpijNi/pCsZ9Q==
10 | -----END CERTIFICATE-----
11 |
--------------------------------------------------------------------------------
/pkg/server/testdata/test.test.key:
--------------------------------------------------------------------------------
1 | -----BEGIN EC PRIVATE KEY-----
2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgcr8+ki8M2LEnG+P8
3 | bC9kbHg4zvNxoxw8w8+FNkmWC1KhRANCAARhxRbCpNr2B6qk6gxnPpvScH/NAhpY
4 | D5PZYZHu15ZUjf1LWrEpfoyjsot0ltRkjYhBu3NgyxKm62UmI0UC+moi
5 | -----END EC PRIVATE KEY-----
6 |
--------------------------------------------------------------------------------
/pkg/server/udp_others.go:
--------------------------------------------------------------------------------
1 | //go:build !linux
2 |
3 | /*
4 | * Copyright (C) 2020-2022, IrineSistiana
5 | *
6 | * This file is part of mosdns.
7 | *
8 | * mosdns is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * mosdns is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with this program. If not, see .
20 | */
21 |
22 | package server
23 |
24 | import "net"
25 |
26 | func newCmc(c *net.UDPConn) (cmcUDPConn, error) {
27 | return newDummyCmc(c), nil
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/upstream/bootstrap/bootstrap.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package bootstrap
21 |
22 | import (
23 | "context"
24 | "net"
25 | "strings"
26 | )
27 |
28 | // NewPlainBootstrap returns a customized *net.Resolver which Dial func is modified to dial s.
29 | // s SHOULD be a literal IP address and the port SHOULD also be literal.
30 | // Port can be omitted. In this case, the default port is :53.
31 | // e.g. NewPlainBootstrap("8.8.8.8"), NewPlainBootstrap("127.0.0.1:5353")
32 | // If s is empty, NewPlainBootstrap returns nil. (A nil *net.Resolver is valid in net.Dialer.)
33 | // Note that not all platform support a customized *net.Resolver. It also depends on the
34 | // version of go runtime.
35 | // See the package docs from the net package for more info.
36 | func NewPlainBootstrap(s string) *net.Resolver {
37 | if len(s) == 0 {
38 | return nil
39 | }
40 | // Add port.
41 | _, _, err := net.SplitHostPort(s)
42 | if err != nil { // no port, add it.
43 | s = net.JoinHostPort(strings.Trim(s, "[]"), "53")
44 | }
45 |
46 | return &net.Resolver{
47 | PreferGo: true,
48 | StrictErrors: false,
49 | Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
50 | d := new(net.Dialer)
51 | return d.DialContext(ctx, network, s)
52 | },
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/upstream/h3roundtripper/h3roundtripper.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package h3roundtripper
21 |
22 | import (
23 | "context"
24 | "crypto/tls"
25 | "github.com/lucas-clemente/quic-go"
26 | "github.com/lucas-clemente/quic-go/http3"
27 | "go.uber.org/zap"
28 | "net/http"
29 | "sync"
30 | "time"
31 | )
32 |
33 | const (
34 | retryThreshold = time.Millisecond * 50
35 | )
36 |
37 | var (
38 | nopLogger = zap.NewNop()
39 | )
40 |
41 | // H3RTHelper is a helper of original http3.RoundTripper.
42 | // This is a workaround of
43 | // https://github.com/lucas-clemente/quic-go/issues/765
44 | type H3RTHelper struct {
45 | Logger *zap.Logger
46 | TLSConfig *tls.Config
47 | QUICConfig *quic.Config
48 | DialFunc func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error)
49 |
50 | m sync.Mutex
51 | rt *http3.RoundTripper
52 | }
53 |
54 | func (h *H3RTHelper) logger() *zap.Logger {
55 | if h.Logger == nil {
56 | return nopLogger
57 | }
58 | return h.Logger
59 | }
60 |
61 | func (h *H3RTHelper) getRT() *http3.RoundTripper {
62 | h.m.Lock()
63 | defer h.m.Unlock()
64 | if h.rt == nil {
65 | h.rt = &http3.RoundTripper{
66 | Dial: h.DialFunc,
67 | TLSClientConfig: h.TLSConfig,
68 | QuicConfig: h.QUICConfig,
69 | }
70 | }
71 | return h.rt
72 | }
73 |
74 | func (h *H3RTHelper) markAsDead(rt *http3.RoundTripper) {
75 | h.m.Lock()
76 | defer h.m.Unlock()
77 | if h.rt == rt {
78 | h.rt = nil
79 | }
80 | }
81 |
82 | func (h *H3RTHelper) RoundTrip(request *http.Request) (*http.Response, error) {
83 | start := time.Now()
84 | resp, err := h.roundTrip(request)
85 | if err != nil {
86 | if time.Since(start) < retryThreshold {
87 | return h.roundTrip(request)
88 | }
89 | }
90 | return resp, err
91 | }
92 |
93 | func (h *H3RTHelper) roundTrip(request *http.Request) (*http.Response, error) {
94 | rt := h.getRT()
95 | resp, err := rt.RoundTrip(request)
96 | if err != nil {
97 | h.markAsDead(rt)
98 | rt.Close()
99 | h.logger().Debug("quic round trip closed", zap.Error(err))
100 | }
101 | return resp, err
102 | }
103 |
--------------------------------------------------------------------------------
/pkg/upstream/transport/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package transport
21 |
22 | import (
23 | "context"
24 | "github.com/miekg/dns"
25 | "time"
26 | )
27 |
28 | // getContextDeadline tries to get the deadline of ctx or return a default
29 | // deadline.
30 | func getContextDeadline(ctx context.Context, defTimeout time.Duration) time.Time {
31 | ddl, ok := ctx.Deadline()
32 | if ok {
33 | return ddl
34 | }
35 | return time.Now().Add(defTimeout)
36 | }
37 |
38 | func shadowCopy(m *dns.Msg) *dns.Msg {
39 | nm := new(dns.Msg)
40 | *nm = *m
41 | return nm
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/upstream/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package upstream
21 |
22 | import (
23 | "context"
24 | "fmt"
25 | "golang.org/x/net/proxy"
26 | "net"
27 | )
28 |
29 | type socketOpts struct {
30 | so_mark int
31 | bind_to_device string
32 | }
33 |
34 | func dialTCP(ctx context.Context, addr, socks5 string, dialer *net.Dialer) (net.Conn, error) {
35 | if len(socks5) > 0 {
36 | socks5Dialer, err := proxy.SOCKS5("tcp", socks5, nil, dialer)
37 | if err != nil {
38 | return nil, fmt.Errorf("failed to init socks5 dialer: %w", err)
39 | }
40 | return socks5Dialer.(proxy.ContextDialer).DialContext(ctx, "tcp", addr)
41 | }
42 |
43 | return dialer.DialContext(ctx, "tcp", addr)
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/upstream/utils_others.go:
--------------------------------------------------------------------------------
1 | //go:build !linux
2 |
3 | /*
4 | * Copyright (C) 2020-2022, IrineSistiana
5 | *
6 | * This file is part of mosdns.
7 | *
8 | * mosdns is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * mosdns is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with this program. If not, see .
20 | */
21 |
22 | package upstream
23 |
24 | import "syscall"
25 |
26 | func getSocketControlFunc(_ socketOpts) func(string, string, syscall.RawConn) error {
27 | return nil
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/upstream/utils_unix.go:
--------------------------------------------------------------------------------
1 | //go:build linux
2 |
3 | /*
4 | * Copyright (C) 2020-2022, IrineSistiana
5 | *
6 | * This file is part of mosdns.
7 | *
8 | * mosdns is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * mosdns is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with this program. If not, see .
20 | */
21 |
22 | package upstream
23 |
24 | import (
25 | "golang.org/x/sys/unix"
26 | "os"
27 | "syscall"
28 | )
29 |
30 | func getSocketControlFunc(opts socketOpts) func(string, string, syscall.RawConn) error {
31 | return func(_, _ string, c syscall.RawConn) error {
32 | var sysCallErr error
33 | if err := c.Control(func(fd uintptr) {
34 | // SO_MARK
35 | if opts.so_mark > 0 {
36 | sysCallErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, opts.so_mark)
37 | if sysCallErr != nil {
38 | sysCallErr = os.NewSyscallError("failed to set SO_MARK", sysCallErr)
39 | return
40 | }
41 | }
42 |
43 | // SO_BINDTODEVICE
44 | if len(opts.bind_to_device) > 0 {
45 | sysCallErr = unix.SetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_BINDTODEVICE, opts.bind_to_device)
46 | if sysCallErr != nil {
47 | sysCallErr = os.NewSyscallError("failed to set SO_BINDTODEVICE", sysCallErr)
48 | return
49 | }
50 | }
51 |
52 | }); err != nil {
53 | return err
54 | }
55 | return sysCallErr
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/utils/config_helper.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package utils
21 |
22 | import (
23 | "github.com/mitchellh/mapstructure"
24 | "golang.org/x/exp/constraints"
25 | )
26 |
27 | func SetDefaultNum[K constraints.Integer | constraints.Float](p *K, d K) {
28 | if *p == 0 {
29 | *p = d
30 | }
31 | }
32 |
33 | func CheckNumRange[K constraints.Integer | constraints.Float](v, min, max K) bool {
34 | if v < min || v > max {
35 | return false
36 | }
37 | return true
38 | }
39 |
40 | // WeakDecode decodes args from config to output.
41 | func WeakDecode(in map[string]interface{}, output interface{}) error {
42 | config := &mapstructure.DecoderConfig{
43 | ErrorUnused: true,
44 | Result: output,
45 | WeaklyTypedInput: true,
46 | TagName: "yaml",
47 | }
48 |
49 | decoder, err := mapstructure.NewDecoder(config)
50 | if err != nil {
51 | return err
52 | }
53 |
54 | return decoder.Decode(in)
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/utils/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package utils
21 |
22 | import (
23 | "fmt"
24 | "strings"
25 | )
26 |
27 | type Errors []error
28 |
29 | func (es *Errors) Error() string {
30 | return es.String()
31 | }
32 |
33 | func (es *Errors) Append(err error) {
34 | *es = append(*es, err)
35 | }
36 |
37 | func (es *Errors) Build() error {
38 | switch len(*es) {
39 | case 0:
40 | return nil
41 | case 1:
42 | return (*es)[0]
43 | default:
44 | return es
45 | }
46 | }
47 |
48 | func (es *Errors) String() string {
49 | sb := new(strings.Builder)
50 | sb.WriteString("joint errors:")
51 | for i, err := range *es {
52 | sb.WriteString(fmt.Sprintf(" #%d: %v", i, err))
53 | }
54 | return sb.String()
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/utils/net.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package utils
21 |
22 | import (
23 | "net"
24 | "net/netip"
25 | )
26 |
27 | // GetIPFromAddr returns a net.IP from the given net.Addr.
28 | // addr can be *net.TCPAddr, *net.UDPAddr, *net.IPNet, *net.IPAddr
29 | // Will return nil otherwise.
30 | func GetIPFromAddr(addr net.Addr) (ip net.IP) {
31 | switch v := addr.(type) {
32 | case *net.TCPAddr:
33 | return v.IP
34 | case *net.UDPAddr:
35 | return v.IP
36 | case *net.IPNet:
37 | return v.IP
38 | case *net.IPAddr:
39 | return v.IP
40 | }
41 | return nil
42 | }
43 |
44 | // GetAddrFromAddr returns netip.Addr from net.Addr.
45 | // See also: GetIPFromAddr.
46 | func GetAddrFromAddr(addr net.Addr) netip.Addr {
47 | a, _ := netip.AddrFromSlice(GetIPFromAddr(addr))
48 | return a
49 | }
50 |
51 | // SplitSchemeAndHost splits addr to protocol and host.
52 | func SplitSchemeAndHost(addr string) (protocol, host string) {
53 | if protocol, host, ok := SplitString2(addr, "://"); ok {
54 | return protocol, host
55 | } else {
56 | return "", addr
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/utils/ptr_parser.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package utils
21 |
22 | import (
23 | "errors"
24 | "net/netip"
25 | "strings"
26 | )
27 |
28 | var errNotPTRDomain = errors.New("domain does not has a ptr suffix")
29 |
30 | const (
31 | IP4arpa = ".in-addr.arpa."
32 | IP6arpa = ".ip6.arpa."
33 | )
34 |
35 | func ParsePTRName(fqdn string) (netip.Addr, error) {
36 | switch {
37 | case strings.HasSuffix(fqdn, IP4arpa):
38 | s := strings.TrimSuffix(fqdn, IP4arpa)
39 | return reverse4(s)
40 | case strings.HasSuffix(fqdn, IP6arpa):
41 | s := strings.TrimSuffix(fqdn, IP6arpa)
42 | return reverse6(s)
43 | default:
44 | return netip.Addr{}, errNotPTRDomain
45 | }
46 | }
47 |
48 | func reverse4(s string) (netip.Addr, error) {
49 | b := new(strings.Builder)
50 | b.Grow(15)
51 | for offset := len(s); offset > 0; {
52 | l := strings.LastIndexByte(s[:offset], '.')
53 | b.WriteString(s[l+1 : offset])
54 | if l != -1 {
55 | b.WriteByte('.')
56 | }
57 | offset = l
58 | }
59 | return netip.ParseAddr(b.String())
60 | }
61 |
62 | func reverse6(s string) (netip.Addr, error) {
63 | b := new(strings.Builder)
64 | b.Grow(63)
65 | writen := 0
66 | for i := 0; i < len(s); i++ {
67 | r := len(s) - 1 - i
68 | if s[r] == '.' {
69 | continue
70 | }
71 | b.WriteByte(s[r])
72 | writen++
73 | if writen != 0 && writen != 32 && writen%4 == 0 {
74 | b.WriteByte(':')
75 | }
76 | }
77 | return netip.ParseAddr(b.String())
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/utils/ptr_parser_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package utils
21 |
22 | import (
23 | "net/netip"
24 | "reflect"
25 | "testing"
26 | )
27 |
28 | func Test_reverse4(t *testing.T) {
29 | tests := []struct {
30 | name string
31 | s string
32 | want netip.Addr
33 | wantErr bool
34 | }{
35 | {"v4", "4.4.8.8", netip.MustParseAddr("8.8.4.4"), false},
36 | {"err", "123114123", netip.Addr{}, true},
37 | }
38 | for _, tt := range tests {
39 | t.Run(tt.name, func(t *testing.T) {
40 | got, err := reverse4(tt.s)
41 | if (err != nil) != tt.wantErr {
42 | t.Errorf("reverse4() error = %v, wantErr %v", err, tt.wantErr)
43 | return
44 | }
45 | if !reflect.DeepEqual(got, tt.want) {
46 | t.Errorf("reverse4() got = %v, want %v", got, tt.want)
47 | }
48 | })
49 | }
50 | }
51 |
52 | func Test_reverse6(t *testing.T) {
53 | tests := []struct {
54 | name string
55 | s string
56 | want netip.Addr
57 | wantErr bool
58 | }{
59 | {"v6", "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2", netip.MustParseAddr("2001:db8::567:89ab"), false},
60 | {"err", "123114123", netip.Addr{}, true},
61 | }
62 | for _, tt := range tests {
63 | t.Run(tt.name, func(t *testing.T) {
64 | got, err := reverse6(tt.s)
65 | if (err != nil) != tt.wantErr {
66 | t.Errorf("reverse6() error = %v, wantErr %v", err, tt.wantErr)
67 | return
68 | }
69 | if !reflect.DeepEqual(got, tt.want) {
70 | t.Errorf("reverse4() got = %v, want %v", got, tt.want)
71 | }
72 | })
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/pkg/utils/strings.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package utils
21 |
22 | import (
23 | "regexp"
24 | "strings"
25 | "unsafe"
26 | )
27 |
28 | // BytesToStringUnsafe converts bytes to string.
29 | func BytesToStringUnsafe(b []byte) string {
30 | return *(*string)(unsafe.Pointer(&b))
31 | }
32 |
33 | var charBlockExpr = regexp.MustCompile("\\S+")
34 |
35 | // SplitLineReg extracts words from s by using regexp "\S+".
36 | func SplitLineReg(s string) []string {
37 | return charBlockExpr.FindAllString(s, -1)
38 | }
39 |
40 | // RemoveComment removes comment after "symbol".
41 | func RemoveComment(s, symbol string) string {
42 | if i := strings.Index(s, symbol); i >= 0 {
43 | return s[:i]
44 | }
45 | return s
46 | }
47 |
48 | // SplitString2 split s to two parts by given symbol
49 | func SplitString2(s, symbol string) (s1 string, s2 string, ok bool) {
50 | if len(symbol) == 0 {
51 | return "", s, true
52 | }
53 | if i := strings.Index(s, symbol); i >= 0 {
54 | return s[:i], s[i+len(symbol):], true
55 | }
56 | return "", "", false
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/utils/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package utils
21 |
22 | import (
23 | "crypto/ecdsa"
24 | "crypto/elliptic"
25 | "crypto/rand"
26 | "crypto/tls"
27 | "crypto/x509"
28 | "crypto/x509/pkix"
29 | "encoding/pem"
30 | "fmt"
31 | "math/big"
32 | "os"
33 | "time"
34 | )
35 |
36 | // LoadCertPool reads and loads certificates in certs.
37 | func LoadCertPool(certs []string) (*x509.CertPool, error) {
38 | rootCAs := x509.NewCertPool()
39 | for _, cert := range certs {
40 | b, err := os.ReadFile(cert)
41 | if err != nil {
42 | return nil, err
43 | }
44 |
45 | if ok := rootCAs.AppendCertsFromPEM(b); !ok {
46 | return nil, fmt.Errorf("no certificate was successfully parsed in %s", cert)
47 | }
48 | }
49 | return rootCAs, nil
50 | }
51 |
52 | // GenerateCertificate generates an ecdsa certificate with given dnsName.
53 | // This should only use in test.
54 | func GenerateCertificate(dnsName string) (cert tls.Certificate, err error) {
55 | key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
56 | if err != nil {
57 | return
58 | }
59 |
60 | //serial number
61 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
62 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
63 | if err != nil {
64 | err = fmt.Errorf("generate serial number: %w", err)
65 | return
66 | }
67 |
68 | template := x509.Certificate{
69 | SerialNumber: serialNumber,
70 | Subject: pkix.Name{CommonName: dnsName},
71 | DNSNames: []string{dnsName},
72 |
73 | NotBefore: time.Now(),
74 | NotAfter: time.Now().AddDate(10, 0, 0),
75 |
76 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
77 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
78 | BasicConstraintsValid: true,
79 | }
80 |
81 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
82 | if err != nil {
83 | return
84 | }
85 | b, err := x509.MarshalPKCS8PrivateKey(key)
86 | if err != nil {
87 | return
88 | }
89 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
90 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
91 | return tls.X509KeyPair(certPEM, keyPEM)
92 | }
93 |
94 | // ClosedChan returns true if c is closed.
95 | // c must not use for sending data and must be used in close() only.
96 | // If ClosedChan receives something from c, it panics.
97 | func ClosedChan(c chan struct{}) bool {
98 | select {
99 | case _, ok := <-c:
100 | if !ok {
101 | return true
102 | }
103 | panic("received from the chan")
104 | default:
105 | return false
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/pkg/utils/utils_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package utils
21 |
22 | import (
23 | "reflect"
24 | "testing"
25 | )
26 |
27 | func TestSplitString2(t *testing.T) {
28 | type args struct {
29 | s string
30 | symbol string
31 | }
32 | tests := []struct {
33 | name string
34 | args args
35 | wantS1 string
36 | wantS2 string
37 | wantOk bool
38 | }{
39 | {"blank", args{"", ""}, "", "", true},
40 | {"blank", args{"///", ""}, "", "///", true},
41 | {"split", args{"///", "/"}, "", "//", true},
42 | {"split", args{"--/", "/"}, "--", "", true},
43 | {"split", args{"https://***.***.***", "://"}, "https", "***.***.***", true},
44 | {"split", args{"://***.***.***", "://"}, "", "***.***.***", true},
45 | {"split", args{"https://", "://"}, "https", "", true},
46 | {"split", args{"--/", "*"}, "", "", false},
47 | }
48 | for _, tt := range tests {
49 | t.Run(tt.name, func(t *testing.T) {
50 | gotS1, gotS2, gotOk := SplitString2(tt.args.s, tt.args.symbol)
51 | if gotS1 != tt.wantS1 {
52 | t.Errorf("SplitString2() gotS1 = %v, want %v", gotS1, tt.wantS1)
53 | }
54 | if gotS2 != tt.wantS2 {
55 | t.Errorf("SplitString2() gotS2 = %v, want %v", gotS2, tt.wantS2)
56 | }
57 | if gotOk != tt.wantOk {
58 | t.Errorf("SplitString2() gotOk = %v, want %v", gotOk, tt.wantOk)
59 | }
60 | })
61 | }
62 | }
63 |
64 | func TestRemoveComment(t *testing.T) {
65 | type args struct {
66 | s string
67 | symbol string
68 | }
69 | tests := []struct {
70 | name string
71 | args args
72 | want string
73 | }{
74 | {name: "empty", args: args{s: "", symbol: ""}, want: ""},
75 | {name: "empty symbol", args: args{s: "12345", symbol: ""}, want: ""},
76 | {name: "empty string", args: args{s: "", symbol: "#"}, want: ""},
77 | {name: "remove 1", args: args{s: "123/456", symbol: "/"}, want: "123"},
78 | {name: "remove 2", args: args{s: "123//456", symbol: "//"}, want: "123"},
79 | {name: "remove 3", args: args{s: "123/*/456", symbol: "//"}, want: "123/*/456"},
80 | }
81 | for _, tt := range tests {
82 | t.Run(tt.name, func(t *testing.T) {
83 | if got := RemoveComment(tt.args.s, tt.args.symbol); got != tt.want {
84 | t.Errorf("RemoveComment() = %v, want %v", got, tt.want)
85 | }
86 | })
87 | }
88 | }
89 |
90 | type TestArgsStruct struct {
91 | A string `yaml:"1"`
92 | B []int `yaml:"2"`
93 | }
94 |
95 | func Test_WeakDecode(t *testing.T) {
96 | testObj := new(TestArgsStruct)
97 | testArgs := map[string]interface{}{
98 | "1": "test",
99 | "2": []int{1, 2, 3},
100 | }
101 | wantObj := &TestArgsStruct{
102 | A: "test",
103 | B: []int{1, 2, 3},
104 | }
105 |
106 | err := WeakDecode(testArgs, testObj)
107 | if err != nil {
108 | t.Fatal(err)
109 | }
110 |
111 | if !reflect.DeepEqual(testObj, wantObj) {
112 | t.Fatalf("args decode failed, want %v, got %v", wantObj, testObj)
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/pkg/zone_file/zone_file.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package zone_file
21 |
22 | import (
23 | "github.com/miekg/dns"
24 | "io"
25 | "os"
26 | )
27 |
28 | type Matcher struct {
29 | m map[dns.Question][]dns.RR
30 | }
31 |
32 | func (m *Matcher) LoadFile(s string) error {
33 | f, err := os.Open(s)
34 | if err != nil {
35 | return err
36 | }
37 | defer f.Close()
38 |
39 | return m.Load(f)
40 | }
41 |
42 | func (m *Matcher) Load(r io.Reader) error {
43 | if m.m == nil {
44 | m.m = make(map[dns.Question][]dns.RR)
45 | }
46 |
47 | parser := dns.NewZoneParser(r, "", "")
48 | parser.SetDefaultTTL(3600)
49 | for {
50 | rr, ok := parser.Next()
51 | if !ok {
52 | break
53 | }
54 | h := rr.Header()
55 | q := dns.Question{
56 | Name: h.Name,
57 | Qtype: h.Rrtype,
58 | Qclass: h.Class,
59 | }
60 | m.m[q] = append(m.m[q], rr)
61 | }
62 | return parser.Err()
63 | }
64 |
65 | func (m *Matcher) Search(q dns.Question) []dns.RR {
66 | return m.m[q]
67 | }
68 |
69 | func (m *Matcher) Reply(q *dns.Msg) *dns.Msg {
70 | var r *dns.Msg
71 | for _, question := range q.Question {
72 | rr := m.Search(question)
73 | if rr != nil {
74 | if r == nil {
75 | r = new(dns.Msg)
76 | r.SetReply(q)
77 | }
78 | r.Answer = append(r.Answer, rr...)
79 | }
80 | }
81 | return r
82 | }
83 |
--------------------------------------------------------------------------------
/pkg/zone_file/zone_file_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package zone_file
21 |
22 | import (
23 | "github.com/miekg/dns"
24 | "strings"
25 | "testing"
26 | )
27 |
28 | const data = `
29 | $TTL 3600
30 | example.com. IN A 192.0.2.1
31 | 1.example.com. IN AAAA 2001:db8:10::1
32 | `
33 |
34 | func TestMatcher(t *testing.T) {
35 | m := new(Matcher)
36 | err := m.Load(strings.NewReader(data))
37 | if err != nil {
38 | t.Fatal(err)
39 | }
40 |
41 | q := new(dns.Msg)
42 | q.SetQuestion("example.com.", dns.TypeA)
43 | r := m.Reply(q)
44 | if r == nil {
45 | t.Fatal("search failed")
46 | }
47 | if got := r.Answer[0].(*dns.A).A.String(); got != "192.0.2.1" {
48 | t.Fatalf("want ip 192.0.2.1, got %s", got)
49 | }
50 |
51 | q = new(dns.Msg)
52 | q.SetQuestion("1.example.com.", dns.TypeAAAA)
53 | r = m.Reply(q)
54 | if r == nil {
55 | t.Fatal("search failed")
56 | }
57 | if got := r.Answer[0].(*dns.AAAA).AAAA.String(); got != "2001:db8:10::1" {
58 | t.Fatalf("want ip 2001:db8:10::1, got %s", got)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/plugin/enabled_plugin.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package plugin
21 |
22 | // import all plugins
23 | import (
24 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/arbitrary"
25 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/blackhole"
26 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/bufsize"
27 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/cache"
28 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/client_limiter"
29 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/dual_selector"
30 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/ecs"
31 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/edns0_filter"
32 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/fast_forward"
33 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/forward"
34 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/hosts"
35 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/ipset"
36 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/marker"
37 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/metrics_collector"
38 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/misc_optm"
39 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/nftset"
40 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/padding"
41 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/query_summary"
42 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/redirect"
43 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/reverse_lookup"
44 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/sequence"
45 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/sleep"
46 | _ "github.com/IrineSistiana/mosdns/v4/plugin/executable/ttl"
47 | _ "github.com/IrineSistiana/mosdns/v4/plugin/matcher/query_matcher"
48 | _ "github.com/IrineSistiana/mosdns/v4/plugin/matcher/response_matcher"
49 | )
50 |
--------------------------------------------------------------------------------
/plugin/enabled_plugin_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package plugin
21 |
22 | import "testing"
23 |
24 | // This is an empty test, but it can run all init() of enabled plugins.
25 | func Test_plugins_init(t *testing.T) {
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/plugin/executable/arbitrary/arbitrary.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package arbitrary
21 |
22 | import (
23 | "context"
24 | "fmt"
25 | "github.com/IrineSistiana/mosdns/v4/coremain"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
28 | "github.com/IrineSistiana/mosdns/v4/pkg/zone_file"
29 | "strings"
30 | )
31 |
32 | const PluginType = "arbitrary"
33 |
34 | func init() {
35 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
36 | }
37 |
38 | type Args struct {
39 | RR []string `yaml:"rr"`
40 | }
41 |
42 | var _ coremain.ExecutablePlugin = (*arbitraryPlugin)(nil)
43 |
44 | type arbitraryPlugin struct {
45 | *coremain.BP
46 | m *zone_file.Matcher
47 | }
48 |
49 | func (p *arbitraryPlugin) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
50 | if r := p.m.Reply(qCtx.Q()); r != nil {
51 | qCtx.SetResponse(r)
52 | return nil
53 | }
54 | return executable_seq.ExecChainNode(ctx, qCtx, next)
55 | }
56 |
57 | func Init(bp *coremain.BP, v interface{}) (p coremain.Plugin, err error) {
58 | args := v.(*Args)
59 | m := new(zone_file.Matcher)
60 |
61 | //TODO: Support data provider
62 | for i, s := range args.RR {
63 | if err := m.Load(strings.NewReader(s)); err != nil {
64 | return nil, fmt.Errorf("failed to load rr #%d [%s], %w", i, s, err)
65 | }
66 | }
67 | return &arbitraryPlugin{
68 | BP: bp,
69 | m: m,
70 | }, nil
71 | }
72 |
--------------------------------------------------------------------------------
/plugin/executable/blackhole/blackhole_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package blackhole
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
26 | "github.com/miekg/dns"
27 | "net"
28 | "testing"
29 | )
30 |
31 | func Test_blackhole_Exec(t *testing.T) {
32 | tests := []struct {
33 | name string
34 | args *Args
35 | queryType uint16
36 | wantResponse bool
37 | wantRcode int
38 | wantIP string
39 | }{
40 | {"drop response1", &Args{RCode: -1}, dns.TypeA, false, 0, ""},
41 | {"respond with rcode 2", &Args{RCode: 2}, dns.TypeA, true, 2, ""},
42 | {"respond with ipv4 1", &Args{IPv4: []string{"127.0.0.1"}}, dns.TypeA, true, 0, "127.0.0.1"},
43 | {"respond with ipv4 2", &Args{IPv4: []string{"127.0.0.1"}, RCode: 2}, dns.TypeAAAA, true, 2, ""},
44 | {"respond with ipv6", &Args{IPv6: []string{"::1"}}, dns.TypeAAAA, true, 0, "::1"},
45 | }
46 |
47 | ctx := context.Background()
48 | for _, tt := range tests {
49 | t.Run(tt.name, func(t *testing.T) {
50 | b, err := newBlackHole(coremain.NewBP("test", PluginType, nil, nil), tt.args)
51 | if err != nil {
52 | t.Fatal(err)
53 | }
54 |
55 | q := new(dns.Msg)
56 | q.SetQuestion("example.com", tt.queryType)
57 | r := new(dns.Msg)
58 | r.SetReply(q)
59 | qCtx := query_context.NewContext(q, nil)
60 | qCtx.SetResponse(r)
61 |
62 | err = b.Exec(ctx, qCtx, nil)
63 | if err != nil {
64 | t.Fatal(err)
65 | }
66 |
67 | if !tt.wantResponse && qCtx.R() != nil {
68 | t.Error("response should be dropped")
69 | }
70 |
71 | if tt.wantResponse {
72 | if len(tt.wantIP) != 0 {
73 | wantIP := net.ParseIP(tt.wantIP)
74 | var gotIP net.IP
75 | switch tt.queryType {
76 | case dns.TypeA:
77 | gotIP = qCtx.R().Answer[0].(*dns.A).A
78 | case dns.TypeAAAA:
79 | gotIP = qCtx.R().Answer[0].(*dns.AAAA).AAAA
80 | }
81 | if !wantIP.Equal(gotIP) {
82 | t.Fatalf("ip mismatched, want %v, got %v", wantIP, gotIP)
83 | }
84 | }
85 |
86 | if tt.wantRcode != qCtx.R().Rcode {
87 | t.Fatalf("response should have rcode %d, but got %d", tt.wantRcode, qCtx.R().Rcode)
88 | }
89 | }
90 | })
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/plugin/executable/bufsize/bufsize.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package bufsize
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
27 | )
28 |
29 | const PluginType = "bufsize"
30 |
31 | func init() {
32 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
33 | }
34 |
35 | type Args struct {
36 | Size uint16 `yaml:"size"` // The maximum UDP Size. Default value is 512, and the value should be within 512 - 4096.
37 | }
38 |
39 | var _ coremain.ExecutablePlugin = (*bufSize)(nil)
40 |
41 | type bufSize struct {
42 | *coremain.BP
43 | size uint16
44 | }
45 |
46 | func (b *bufSize) getSize() uint16 {
47 | if b.size < 512 {
48 | return 512
49 | }
50 | if b.size > 4096 {
51 | return 4096
52 | }
53 | return b.size
54 | }
55 |
56 | func (b *bufSize) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
57 | q := qCtx.Q()
58 | if opt := q.IsEdns0(); opt != nil {
59 | maxSize := b.getSize()
60 | if opt.UDPSize() > maxSize {
61 | opt.SetUDPSize(maxSize)
62 | }
63 | }
64 |
65 | return executable_seq.ExecChainNode(ctx, qCtx, next)
66 | }
67 |
68 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
69 | return &bufSize{
70 | BP: bp,
71 | size: args.(*Args).Size,
72 | }, nil
73 | }
74 |
--------------------------------------------------------------------------------
/plugin/executable/client_limiter/client_limiter.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package client_limiter
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/concurrent_limiter"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
28 | "github.com/miekg/dns"
29 | "sync"
30 | "time"
31 | )
32 |
33 | const PluginType = "client_limiter"
34 |
35 | func init() {
36 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
37 | }
38 |
39 | type Args struct {
40 | MaxQPS int `yaml:"max_qps"`
41 | V4Mask int `yaml:"v4_mask"` // default is 32
42 | V6Mask int `yaml:"v6_mask"` // default is 48
43 | }
44 |
45 | var _ coremain.ExecutablePlugin = (*Limiter)(nil)
46 |
47 | type Limiter struct {
48 | *coremain.BP
49 |
50 | closeOnce sync.Once
51 | closeNotify chan struct{}
52 | hpLimiter *concurrent_limiter.HPClientLimiter
53 | }
54 |
55 | func NewLimiter(bp *coremain.BP, args *Args) (*Limiter, error) {
56 | hpl, err := concurrent_limiter.NewHPClientLimiter(concurrent_limiter.HPLimiterOpts{
57 | Threshold: args.MaxQPS,
58 | Interval: time.Second,
59 | })
60 | if err != nil {
61 | return nil, err
62 | }
63 | l := &Limiter{
64 | BP: bp,
65 | hpLimiter: hpl,
66 | closeNotify: make(chan struct{}),
67 | }
68 | go l.cleanerLoop()
69 | return l, nil
70 | }
71 |
72 | func (l *Limiter) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
73 | addr := qCtx.ReqMeta().ClientAddr
74 | if !addr.IsValid() {
75 | return executable_seq.ExecChainNode(ctx, qCtx, next)
76 | }
77 | if ok := l.hpLimiter.AcquireToken(addr); !ok {
78 | r := new(dns.Msg)
79 | r.SetRcode(qCtx.Q(), dns.RcodeRefused)
80 | qCtx.SetResponse(r)
81 | return nil
82 | }
83 | return executable_seq.ExecChainNode(ctx, qCtx, next)
84 | }
85 |
86 | func (l *Limiter) Close() error {
87 | l.closeOnce.Do(func() {
88 | close(l.closeNotify)
89 | })
90 | return nil
91 | }
92 |
93 | func (l *Limiter) cleanerLoop() {
94 | ticker := time.NewTicker(time.Second * 5)
95 | defer ticker.Stop()
96 | for {
97 | select {
98 | case now := <-ticker.C:
99 | l.hpLimiter.GC(now)
100 | case <-l.closeNotify:
101 | return
102 | }
103 | }
104 | }
105 |
106 | // Init is a handler.NewPluginFunc.
107 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
108 | return NewLimiter(bp, args.(*Args))
109 | }
110 |
--------------------------------------------------------------------------------
/plugin/executable/fast_forward/udpme.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package fastforward
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/pkg/dnsutils"
25 | "net"
26 | "time"
27 |
28 | "github.com/miekg/dns"
29 | )
30 |
31 | type udpmeUpstream struct {
32 | addr string
33 | trusted bool
34 | }
35 |
36 | func newUDPME(addr string, trusted bool) *udpmeUpstream {
37 | if _, _, err := net.SplitHostPort(addr); err != nil {
38 | addr = net.JoinHostPort(addr, "53")
39 | }
40 | return &udpmeUpstream{addr: addr, trusted: trusted}
41 | }
42 |
43 | func (u *udpmeUpstream) Address() string {
44 | return u.addr
45 | }
46 |
47 | func (u *udpmeUpstream) Trusted() bool {
48 | return u.trusted
49 | }
50 |
51 | func (u *udpmeUpstream) Exchange(ctx context.Context, m *dns.Msg) (*dns.Msg, error) {
52 | ddl, ok := ctx.Deadline()
53 | if !ok {
54 | ddl = time.Now().Add(time.Second * 3)
55 | }
56 |
57 | if m.IsEdns0() != nil {
58 | return u.exchangeOPTM(m, ddl)
59 | }
60 | mc := m.Copy()
61 | mc.SetEdns0(512, false)
62 | r, err := u.exchangeOPTM(mc, ddl)
63 | if err != nil {
64 | return nil, err
65 | }
66 | dnsutils.RemoveEDNS0(r)
67 | return r, nil
68 | }
69 |
70 | func (u *udpmeUpstream) exchangeOPTM(m *dns.Msg, ddl time.Time) (*dns.Msg, error) {
71 | c, err := dns.Dial("udp", u.addr)
72 | if err != nil {
73 | return nil, err
74 | }
75 | defer c.Close()
76 | c.SetDeadline(ddl)
77 | if opt := m.IsEdns0(); opt != nil {
78 | c.UDPSize = opt.UDPSize()
79 | }
80 |
81 | if err := c.WriteMsg(m); err != nil {
82 | return nil, err
83 | }
84 |
85 | for {
86 | r, err := c.ReadMsg()
87 | if err != nil {
88 | return nil, err
89 | }
90 | if r.IsEdns0() == nil {
91 | continue
92 | }
93 | return r, nil
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/plugin/executable/hosts/hosts.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package hosts
21 |
22 | import (
23 | "bytes"
24 | "context"
25 | "github.com/IrineSistiana/mosdns/v4/coremain"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/hosts"
28 | "github.com/IrineSistiana/mosdns/v4/pkg/matcher/domain"
29 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
30 | "io"
31 | )
32 |
33 | const PluginType = "hosts"
34 |
35 | func init() {
36 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
37 | }
38 |
39 | var _ coremain.ExecutablePlugin = (*hostsPlugin)(nil)
40 |
41 | type Args struct {
42 | Hosts []string `yaml:"hosts"`
43 | }
44 |
45 | type hostsPlugin struct {
46 | *coremain.BP
47 | h *hosts.Hosts
48 | matcherCloser io.Closer
49 | }
50 |
51 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
52 | return newHostsContainer(bp, args.(*Args))
53 | }
54 |
55 | func newHostsContainer(bp *coremain.BP, args *Args) (*hostsPlugin, error) {
56 | staticMatcher := domain.NewMixMatcher[*hosts.IPs]()
57 | staticMatcher.SetDefaultMatcher(domain.MatcherFull)
58 | m, err := domain.BatchLoadProvider[*hosts.IPs](
59 | args.Hosts,
60 | staticMatcher,
61 | hosts.ParseIPs,
62 | bp.M().GetDataManager(),
63 | func(b []byte) (domain.Matcher[*hosts.IPs], error) {
64 | mixMatcher := domain.NewMixMatcher[*hosts.IPs]()
65 | mixMatcher.SetDefaultMatcher(domain.MatcherFull)
66 | if err := domain.LoadFromTextReader[*hosts.IPs](mixMatcher, bytes.NewReader(b), hosts.ParseIPs); err != nil {
67 | return nil, err
68 | }
69 | return mixMatcher, nil
70 | },
71 | )
72 | if err != nil {
73 | return nil, err
74 | }
75 | return &hostsPlugin{
76 | BP: bp,
77 | h: hosts.NewHosts(m),
78 | matcherCloser: m,
79 | }, nil
80 | }
81 |
82 | func (h *hostsPlugin) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
83 | r := h.h.LookupMsg(qCtx.Q())
84 | if r != nil {
85 | qCtx.SetResponse(r)
86 | return nil
87 | }
88 |
89 | return executable_seq.ExecChainNode(ctx, qCtx, next)
90 | }
91 |
92 | func (h *hostsPlugin) Close() error {
93 | _ = h.matcherCloser.Close()
94 | return nil
95 | }
96 |
--------------------------------------------------------------------------------
/plugin/executable/ipset/ipset.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package ipset
21 |
22 | import (
23 | "github.com/IrineSistiana/mosdns/v4/coremain"
24 | )
25 |
26 | const PluginType = "ipset"
27 |
28 | func init() {
29 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
30 | }
31 |
32 | type Args struct {
33 | SetName4 string `yaml:"set_name4"`
34 | SetName6 string `yaml:"set_name6"`
35 | Mask4 int `yaml:"mask4"` // default 24
36 | Mask6 int `yaml:"mask6"` // default 32
37 | }
38 |
39 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
40 | return newIpsetPlugin(bp, args.(*Args))
41 | }
42 |
--------------------------------------------------------------------------------
/plugin/executable/ipset/ipset_linux.go:
--------------------------------------------------------------------------------
1 | //go:build linux
2 |
3 | /*
4 | * Copyright (C) 2020-2022, IrineSistiana
5 | *
6 | * This file is part of mosdns.
7 | *
8 | * mosdns is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * mosdns is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with this program. If not, see .
20 | */
21 |
22 | package ipset
23 |
24 | import (
25 | "context"
26 | "fmt"
27 | "github.com/IrineSistiana/mosdns/v4/coremain"
28 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
29 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
30 | "github.com/miekg/dns"
31 | "github.com/nadoo/ipset"
32 | "go.uber.org/zap"
33 | "net/netip"
34 | )
35 |
36 | var _ coremain.ExecutablePlugin = (*ipsetPlugin)(nil)
37 |
38 | type ipsetPlugin struct {
39 | *coremain.BP
40 | args *Args
41 | nl *ipset.NetLink
42 | }
43 |
44 | func newIpsetPlugin(bp *coremain.BP, args *Args) (*ipsetPlugin, error) {
45 | if args.Mask4 == 0 {
46 | args.Mask4 = 24
47 | }
48 | if args.Mask6 == 0 {
49 | args.Mask6 = 32
50 | }
51 |
52 | nl, err := ipset.Init()
53 | if err != nil {
54 | return nil, err
55 | }
56 |
57 | return &ipsetPlugin{
58 | BP: bp,
59 | args: args,
60 | nl: nl,
61 | }, nil
62 | }
63 |
64 | func (p *ipsetPlugin) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
65 | r := qCtx.R()
66 | if r != nil {
67 | er := p.addIPSet(r)
68 | if er != nil {
69 | p.L().Warn("failed to add response IP to ipset", qCtx.InfoField(), zap.Error(er))
70 | }
71 | }
72 |
73 | return executable_seq.ExecChainNode(ctx, qCtx, next)
74 | }
75 |
76 | func (p *ipsetPlugin) Close() error {
77 | return p.nl.Close()
78 | }
79 |
80 | func (p *ipsetPlugin) addIPSet(r *dns.Msg) error {
81 | for i := range r.Answer {
82 | switch rr := r.Answer[i].(type) {
83 | case *dns.A:
84 | if len(p.args.SetName4) == 0 {
85 | continue
86 | }
87 | addr, ok := netip.AddrFromSlice(rr.A.To4())
88 | if !ok {
89 | return fmt.Errorf("invalid A record with ip: %s", rr.A)
90 | }
91 | if err := ipset.AddPrefix(p.nl, p.args.SetName4, netip.PrefixFrom(addr, p.args.Mask4)); err != nil {
92 | return err
93 | }
94 |
95 | case *dns.AAAA:
96 | if len(p.args.SetName6) == 0 {
97 | continue
98 | }
99 | addr, ok := netip.AddrFromSlice(rr.AAAA.To16())
100 | if !ok {
101 | return fmt.Errorf("invalid AAAA record with ip: %s", rr.AAAA)
102 | }
103 | if err := ipset.AddPrefix(p.nl, p.args.SetName6, netip.PrefixFrom(addr, p.args.Mask6)); err != nil {
104 | return err
105 | }
106 | default:
107 | continue
108 | }
109 | }
110 |
111 | return nil
112 | }
113 |
--------------------------------------------------------------------------------
/plugin/executable/ipset/ipset_other.go:
--------------------------------------------------------------------------------
1 | //go:build !linux
2 |
3 | /*
4 | * Copyright (C) 2020-2022, IrineSistiana
5 | *
6 | * This file is part of mosdns.
7 | *
8 | * mosdns is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * mosdns is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with this program. If not, see .
20 | */
21 |
22 | package ipset
23 |
24 | import (
25 | "context"
26 | "github.com/IrineSistiana/mosdns/v4/coremain"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
28 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
29 | )
30 |
31 | type ipsetPlugin struct {
32 | *coremain.BP
33 | }
34 |
35 | func newIpsetPlugin(bp *coremain.BP, args *Args) (*ipsetPlugin, error) {
36 | return &ipsetPlugin{BP: bp}, nil
37 | }
38 |
39 | func (p *ipsetPlugin) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
40 | return executable_seq.ExecChainNode(ctx, qCtx, next)
41 | }
42 |
--------------------------------------------------------------------------------
/plugin/executable/marker/marker.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package marker
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
27 | )
28 |
29 | const PluginType = "marker"
30 |
31 | func init() {
32 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
33 | }
34 |
35 | var _ coremain.ExecutablePlugin = (*markerPlugin)(nil)
36 | var _ coremain.MatcherPlugin = (*markerPlugin)(nil)
37 |
38 | type markerPlugin struct {
39 | *coremain.BP
40 | markId uint
41 | }
42 |
43 | func (s *markerPlugin) Match(_ context.Context, qCtx *query_context.Context) (matched bool, err error) {
44 | return qCtx.HasMark(s.markId), nil
45 | }
46 |
47 | type Args struct{}
48 |
49 | // Exec implements handler.Executable.
50 | func (s *markerPlugin) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
51 | qCtx.AddMark(s.markId)
52 | return executable_seq.ExecChainNode(ctx, qCtx, next)
53 | }
54 |
55 | func Init(bp *coremain.BP, _ interface{}) (p coremain.Plugin, err error) {
56 | markId, err := query_context.AllocateMark()
57 | if err != nil {
58 | return nil, err
59 | }
60 | return &markerPlugin{
61 | BP: bp,
62 | markId: markId,
63 | }, nil
64 | }
65 |
--------------------------------------------------------------------------------
/plugin/executable/metrics_collector/collector.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package metrics_collector
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
27 | "github.com/prometheus/client_golang/prometheus"
28 | "time"
29 | )
30 |
31 | const PluginType = "metrics_collector"
32 |
33 | func init() {
34 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
35 | }
36 |
37 | type Args struct{}
38 |
39 | var _ coremain.ExecutablePlugin = (*Collector)(nil)
40 |
41 | type Collector struct {
42 | *coremain.BP
43 |
44 | queryTotal prometheus.Counter
45 | errTotal prometheus.Counter
46 | thread prometheus.Gauge
47 | responseLatency prometheus.Histogram
48 | }
49 |
50 | func NewCollector(bp *coremain.BP, args *Args) *Collector {
51 | var c = &Collector{
52 | BP: bp,
53 | queryTotal: prometheus.NewCounter(prometheus.CounterOpts{
54 | Name: "query_total",
55 | Help: "The total number of queries pass through this collector",
56 | }),
57 | errTotal: prometheus.NewCounter(prometheus.CounterOpts{
58 | Name: "err_total",
59 | Help: "The total number of queries failed after this collector",
60 | }),
61 | thread: prometheus.NewGauge(prometheus.GaugeOpts{
62 | Name: "thread",
63 | Help: "The number of threads currently through this collector",
64 | }),
65 | responseLatency: prometheus.NewHistogram(prometheus.HistogramOpts{
66 | Name: "response_latency_millisecond",
67 | Help: "The response latency in millisecond",
68 | Buckets: []float64{1, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000},
69 | }),
70 | }
71 | bp.GetMetricsReg().MustRegister(c.queryTotal, c.errTotal, c.thread, c.responseLatency)
72 | return c
73 | }
74 |
75 | func (c *Collector) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
76 | c.thread.Inc()
77 | defer c.thread.Dec()
78 |
79 | c.queryTotal.Inc()
80 | start := time.Now()
81 | err := executable_seq.ExecChainNode(ctx, qCtx, next)
82 | if err != nil {
83 | c.errTotal.Inc()
84 | }
85 | if qCtx.R() != nil {
86 | c.responseLatency.Observe(float64(time.Since(start).Milliseconds()))
87 | }
88 | return err
89 | }
90 |
91 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
92 | return NewCollector(bp, args.(*Args)), nil
93 | }
94 |
--------------------------------------------------------------------------------
/plugin/executable/misc_optm/misc.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package misc_optm
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/dnsutils"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
28 | "github.com/miekg/dns"
29 | "math/rand"
30 | )
31 |
32 | const (
33 | PluginType = "misc_optm"
34 | )
35 |
36 | const (
37 | maxUDPSize = 1200 // 1280 (min ipv6 mtu) - 40 (ipv6 header) - 8 (udp header) - 8 (pppoe header) - (24) reserved
38 | )
39 |
40 | func init() {
41 | coremain.RegNewPersetPluginFunc("_misc_optm", func(bp *coremain.BP) (coremain.Plugin, error) {
42 | return &optm{BP: bp}, nil
43 | })
44 | }
45 |
46 | var _ coremain.ExecutablePlugin = (*optm)(nil)
47 |
48 | type optm struct {
49 | *coremain.BP
50 | }
51 |
52 | func (t *optm) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
53 | q := qCtx.Q()
54 |
55 | // Block query that is unusual.
56 | if isUnusualQuery(q) {
57 | r := new(dns.Msg)
58 | r.SetRcode(q, dns.RcodeRefused)
59 | qCtx.SetResponse(r)
60 | return nil
61 | }
62 |
63 | // limit edns0 udp size.
64 | if opt := q.IsEdns0(); opt != nil {
65 | if opt.UDPSize() > maxUDPSize {
66 | opt.SetUDPSize(maxUDPSize)
67 | }
68 | }
69 |
70 | if err := executable_seq.ExecChainNode(ctx, qCtx, next); err != nil {
71 | return err
72 | }
73 |
74 | r := qCtx.R()
75 | if r == nil {
76 | return nil
77 | }
78 |
79 | // Trim and shuffle answers for A and AAAA.
80 | switch qt := q.Question[0].Qtype; qt {
81 | case dns.TypeA, dns.TypeAAAA:
82 | rr := r.Answer[:0]
83 | for _, ar := range r.Answer {
84 | if ar.Header().Rrtype == qt {
85 | rr = append(rr, ar)
86 | }
87 | ar.Header().Name = q.Question[0].Name
88 | }
89 |
90 | rand.Shuffle(len(rr), func(i, j int) {
91 | rr[i], rr[j] = rr[j], rr[i]
92 | })
93 |
94 | r.Answer = rr
95 | }
96 |
97 | // Remove padding
98 | if rOpt := r.IsEdns0(); rOpt != nil {
99 | dnsutils.RemoveEDNS0Option(rOpt, dns.EDNS0PADDING)
100 | }
101 |
102 | // EDNS0 consistency
103 | if qOpt := q.IsEdns0(); qOpt == nil {
104 | dnsutils.RemoveEDNS0(r)
105 | }
106 | return nil
107 | }
108 |
109 | func isUnusualQuery(q *dns.Msg) bool {
110 | return !isValidQuery(q) || len(q.Question) != 1 || q.Question[0].Qclass != dns.ClassINET
111 | }
112 |
113 | func isValidQuery(q *dns.Msg) bool {
114 | return !q.Response && q.Opcode == dns.OpcodeQuery && !q.Authoritative && !q.Zero && // check header
115 | len(q.Answer) == 0 && len(q.Ns) == 0 // check body
116 | }
117 |
--------------------------------------------------------------------------------
/plugin/executable/nftset/nftset.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package nftset
21 |
22 | import (
23 | "github.com/IrineSistiana/mosdns/v4/coremain"
24 | )
25 |
26 | const PluginType = "nftset"
27 |
28 | func init() {
29 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
30 | }
31 |
32 | var _ coremain.ExecutablePlugin = (*nftsetPlugin)(nil)
33 |
34 | type Args struct {
35 | TableFamily4 string `yaml:"table_family4"`
36 | TableFamily6 string `yaml:"table_family6"`
37 | TableName4 string `yaml:"table_name4"`
38 | TableName6 string `yaml:"table_name6"`
39 | SetName4 string `yaml:"set_name4"`
40 | SetName6 string `yaml:"set_name6"`
41 | Mask4 int `yaml:"mask4"` // default 24
42 | Mask6 int `yaml:"mask6"` // default 32
43 | }
44 |
45 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
46 | return newNftsetPlugin(bp, args.(*Args))
47 | }
48 |
--------------------------------------------------------------------------------
/plugin/executable/nftset/nftset_other.go:
--------------------------------------------------------------------------------
1 | //go:build !linux
2 | // +build !linux
3 |
4 | /*
5 | * Copyright (C) 2020-2022, IrineSistiana
6 | *
7 | * This file is part of mosdns.
8 | *
9 | * mosdns is free software: you can redistribute it and/or modify
10 | * it under the terms of the GNU General Public License as published by
11 | * the Free Software Foundation, either version 3 of the License, or
12 | * (at your option) any later version.
13 | *
14 | * mosdns is distributed in the hope that it will be useful,
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | * GNU General Public License for more details.
18 | *
19 | * You should have received a copy of the GNU General Public License
20 | * along with this program. If not, see .
21 | */
22 |
23 | package nftset
24 |
25 | import (
26 | "context"
27 | "github.com/IrineSistiana/mosdns/v4/coremain"
28 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
29 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
30 | )
31 |
32 | type nftsetPlugin struct {
33 | *coremain.BP
34 | }
35 |
36 | func newNftsetPlugin(bp *coremain.BP, args *Args) (*nftsetPlugin, error) {
37 | return &nftsetPlugin{BP: bp}, nil
38 | }
39 |
40 | func (p *nftsetPlugin) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
41 | return executable_seq.ExecChainNode(ctx, qCtx, next)
42 | }
43 |
--------------------------------------------------------------------------------
/plugin/executable/query_summary/query_summary.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package query_summary
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
27 | "go.uber.org/zap"
28 | "time"
29 | )
30 |
31 | const (
32 | PluginType = "query_summary"
33 | )
34 |
35 | func init() {
36 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(*Args) })
37 | coremain.RegNewPersetPluginFunc("_query_summary", func(bp *coremain.BP) (coremain.Plugin, error) {
38 | return newLogger(bp, &Args{}), nil
39 | })
40 | }
41 |
42 | var _ coremain.ExecutablePlugin = (*logger)(nil)
43 |
44 | type Args struct {
45 | Msg string `yaml:"msg"`
46 | }
47 |
48 | func (a *Args) init() {
49 | if len(a.Msg) == 0 {
50 | a.Msg = "query summary"
51 | }
52 | }
53 |
54 | type logger struct {
55 | args *Args
56 | *coremain.BP
57 | }
58 |
59 | // Init is a handler.NewPluginFunc.
60 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
61 | return newLogger(bp, args.(*Args)), nil
62 | }
63 |
64 | func newLogger(bp *coremain.BP, args *Args) coremain.Plugin {
65 | args.init()
66 | return &logger{BP: bp, args: args}
67 | }
68 |
69 | func (l *logger) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
70 | err := executable_seq.ExecChainNode(ctx, qCtx, next)
71 |
72 | q := qCtx.Q()
73 | if len(q.Question) != 1 {
74 | return nil
75 | }
76 | question := q.Question[0]
77 | respRcode := -1
78 | if r := qCtx.R(); r != nil {
79 | respRcode = r.Rcode
80 | }
81 |
82 | l.BP.L().Info(
83 | l.args.Msg,
84 | zap.Uint32("uqid", qCtx.Id()),
85 | zap.String("qname", question.Name),
86 | zap.Uint16("qtype", question.Qtype),
87 | zap.Uint16("qclass", question.Qclass),
88 | zap.Stringer("client", qCtx.ReqMeta().ClientAddr),
89 | zap.Int("resp_rcode", respRcode),
90 | zap.Duration("elapsed", time.Now().Sub(qCtx.StartTime())),
91 | zap.Error(err),
92 | )
93 | return err
94 | }
95 |
--------------------------------------------------------------------------------
/plugin/executable/sequence/sequence.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package sequence
21 |
22 | import (
23 | "context"
24 | "fmt"
25 | "github.com/IrineSistiana/mosdns/v4/coremain"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
28 | )
29 |
30 | const PluginType = "sequence"
31 |
32 | func init() {
33 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
34 | coremain.RegNewPersetPluginFunc("_return", func(bp *coremain.BP) (coremain.Plugin, error) {
35 | return &_return{BP: bp}, nil
36 | })
37 | }
38 |
39 | type sequence struct {
40 | *coremain.BP
41 |
42 | ecs executable_seq.ExecutableChainNode
43 | }
44 |
45 | type Args struct {
46 | Exec interface{} `yaml:"exec"`
47 | }
48 |
49 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
50 | return newSequencePlugin(bp, args.(*Args))
51 | }
52 |
53 | func newSequencePlugin(bp *coremain.BP, args *Args) (*sequence, error) {
54 | ecs, err := executable_seq.BuildExecutableLogicTree(args.Exec, bp.L(), bp.M().GetExecutables(), bp.M().GetMatchers())
55 | if err != nil {
56 | return nil, fmt.Errorf("cannot build sequence: %w", err)
57 | }
58 |
59 | return &sequence{
60 | BP: bp,
61 | ecs: ecs,
62 | }, nil
63 | }
64 |
65 | func (s *sequence) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
66 | if err := executable_seq.ExecChainNode(ctx, qCtx, s.ecs); err != nil {
67 | return err
68 | }
69 |
70 | return executable_seq.ExecChainNode(ctx, qCtx, next)
71 | }
72 |
73 | var _ coremain.ExecutablePlugin = (*_return)(nil)
74 |
75 | type _return struct {
76 | *coremain.BP
77 | }
78 |
79 | func (n *_return) Exec(_ context.Context, _ *query_context.Context, _ executable_seq.ExecutableChainNode) error {
80 | return nil
81 | }
82 |
--------------------------------------------------------------------------------
/plugin/executable/sleep/sleep.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package sleep
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/pool"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
28 | "time"
29 | )
30 |
31 | const PluginType = "sleep"
32 |
33 | func init() {
34 | // Register this plugin type with its initialization funcs. So that, this plugin
35 | // can be configured by user from configuration file.
36 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
37 |
38 | // You can also register a plugin object directly. (If plugin do not need to configure)
39 | // Then you can directly use "_sleep_500ms" in configuration file.
40 | coremain.RegNewPersetPluginFunc("_sleep_500ms", func(bp *coremain.BP) (coremain.Plugin, error) {
41 | return &sleep{BP: bp, d: time.Millisecond * 500}, nil
42 | })
43 | }
44 |
45 | // Args is the arguments of plugin. It will be decoded from yaml.
46 | // So it is recommended to use `yaml` as struct field's tag.
47 | type Args struct {
48 | Duration uint `yaml:"duration"` // (milliseconds) duration for sleep.
49 | }
50 |
51 | var _ coremain.ExecutablePlugin = (*sleep)(nil)
52 |
53 | // sleep implements handler.ExecutablePlugin.
54 | type sleep struct {
55 | *coremain.BP
56 | d time.Duration
57 | }
58 |
59 | // Exec implements handler.Executable.
60 | func (s *sleep) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
61 | if s.d > 0 {
62 | timer := pool.GetTimer(s.d)
63 | defer pool.ReleaseTimer(timer)
64 | select {
65 | case <-timer.C:
66 | case <-ctx.Done():
67 | return ctx.Err()
68 | }
69 | }
70 |
71 | // Call handler.ExecChainNode() can execute next plugin.
72 | return executable_seq.ExecChainNode(ctx, qCtx, next)
73 |
74 | // You can control how/when to execute next plugin.
75 | // For more complex example, see plugin "cache".
76 | }
77 |
78 | // Init is a handler.NewPluginFunc.
79 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
80 | d := args.(*Args).Duration
81 | return &sleep{
82 | BP: bp,
83 | d: time.Duration(d) * time.Millisecond,
84 | }, nil
85 | }
86 |
--------------------------------------------------------------------------------
/plugin/executable/ttl/ttl.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package ttl
21 |
22 | import (
23 | "context"
24 | "github.com/IrineSistiana/mosdns/v4/coremain"
25 | "github.com/IrineSistiana/mosdns/v4/pkg/dnsutils"
26 | "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
27 | "github.com/IrineSistiana/mosdns/v4/pkg/query_context"
28 | )
29 |
30 | const (
31 | PluginType = "ttl"
32 | )
33 |
34 | func init() {
35 | coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
36 | }
37 |
38 | var _ coremain.ExecutablePlugin = (*ttl)(nil)
39 |
40 | type Args struct {
41 | MaximumTTL uint32 `yaml:"maximum_ttl"`
42 | MinimalTTL uint32 `yaml:"minimal_ttl"`
43 | }
44 |
45 | type ttl struct {
46 | *coremain.BP
47 | args *Args
48 | }
49 |
50 | func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
51 | return newTTL(bp, args.(*Args)), nil
52 | }
53 |
54 | func newTTL(bp *coremain.BP, args *Args) coremain.Plugin {
55 | return &ttl{
56 | BP: bp,
57 | args: args,
58 | }
59 | }
60 |
61 | func (t *ttl) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
62 | if r := qCtx.R(); r != nil {
63 | if t.args.MaximumTTL > 0 {
64 | dnsutils.ApplyMaximumTTL(r, t.args.MaximumTTL)
65 | }
66 | if t.args.MinimalTTL > 0 {
67 | dnsutils.ApplyMinimalTTL(r, t.args.MinimalTTL)
68 | }
69 | }
70 | return executable_seq.ExecChainNode(ctx, qCtx, next)
71 | }
72 |
--------------------------------------------------------------------------------
/scripts/openwrt/mosdns-init-openwrt:
--------------------------------------------------------------------------------
1 | #!/bin/sh /etc/rc.common
2 | #
3 | # Copyright (C) 2020-2022, IrineSistiana
4 | #
5 | # This file is part of mosdns.
6 | #
7 | # mosdns is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # mosdns is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 | #
20 |
21 | START=99
22 | USE_PROCD=1
23 |
24 | ##### ONLY CHANGE THIS BLOCK ######
25 | PROG=/usr/bin/mosdns # where is mosdns
26 | RES_DIR=/etc/mosdns/ # resource dir / working dir / the dir where you store ip/domain lists
27 | CONF=./config.yaml # where is the config file, it can be a relative path to $RES_DIR
28 | ##### ONLY CHANGE THIS BLOCK ######
29 |
30 | start_service() {
31 | procd_open_instance
32 | procd_set_param command $PROG start -d $RES_DIR -c $CONF
33 |
34 | procd_set_param user root
35 | procd_set_param stdout 1
36 | procd_set_param stderr 1
37 | procd_set_param respawn "${respawn_threshold:-3600}" "${respawn_timeout:-5}" "${respawn_retry:-5}"
38 | procd_close_instance
39 | echo "mosdns is started!"
40 | }
41 |
42 | reload_service() {
43 | stop
44 | sleep 2s
45 | echo "mosdns is restarted!"
46 | start
47 | }
48 |
--------------------------------------------------------------------------------
/tools/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package tools
21 |
22 | import (
23 | "github.com/IrineSistiana/mosdns/v4/mlog"
24 | "github.com/spf13/cobra"
25 | "github.com/spf13/viper"
26 | "strings"
27 | )
28 |
29 | func newConvCmd() *cobra.Command {
30 | var (
31 | in string
32 | out string
33 | )
34 |
35 | c := &cobra.Command{
36 | Use: "conv -i input_cfg -o output_cfg",
37 | Args: cobra.NoArgs,
38 | Short: "Convert configuration file format. Supported extensions: " + strings.Join(viper.SupportedExts, ", "),
39 | Run: func(cmd *cobra.Command, args []string) {
40 | if err := convCfg(in, out); err != nil {
41 | mlog.S().Fatal(err)
42 | }
43 | },
44 | DisableFlagsInUseLine: true,
45 | }
46 | c.Flags().StringVarP(&in, "in", "i", "", "input config")
47 | c.Flags().StringVarP(&out, "out", "o", "", "output config")
48 | c.MarkFlagRequired("in")
49 | c.MarkFlagRequired("out")
50 | c.MarkFlagFilename("in")
51 | c.MarkFlagFilename("out")
52 | return c
53 | }
54 |
55 | func newGenCmd() *cobra.Command {
56 | c := &cobra.Command{
57 | Use: "gen config_file",
58 | Short: "Generate a template config. Supported extensions: " + strings.Join(viper.SupportedExts, ", "),
59 | Args: cobra.ExactArgs(1),
60 | Run: func(cmd *cobra.Command, args []string) {
61 | if err := genCfg(args[0]); err != nil {
62 | mlog.S().Fatal(err)
63 | }
64 | },
65 | DisableFlagsInUseLine: true,
66 | }
67 | return c
68 | }
69 |
70 | func convCfg(in, out string) error {
71 | v := viper.New()
72 | v.SetConfigFile(in)
73 | if err := v.ReadInConfig(); err != nil {
74 | return err
75 | }
76 | return v.SafeWriteConfigAs(out)
77 | }
78 |
79 | func genCfg(out string) error {
80 | cfg := `
81 | log:
82 | level: info
83 | file: ""
84 |
85 | plugins:
86 | - tag: forward_google
87 | type: fast_forward
88 | args:
89 | upstream:
90 | - addr: https://8.8.8.8/dns-query
91 |
92 | servers:
93 | - exec: forward_google
94 | listeners:
95 | - protocol: udp
96 | addr: 127.0.0.1:5533
97 | - protocol: tcp
98 | addr: 127.0.0.1:5533
99 | `
100 | v := viper.New()
101 | v.SetConfigType("yaml")
102 | if err := v.ReadConfig(strings.NewReader(cfg)); err != nil {
103 | return err
104 | }
105 |
106 | return v.WriteConfigAs(out)
107 | }
108 |
--------------------------------------------------------------------------------
/tools/init.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022, IrineSistiana
3 | *
4 | * This file is part of mosdns.
5 | *
6 | * mosdns is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * mosdns is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package tools
21 |
22 | import (
23 | "github.com/IrineSistiana/mosdns/v4/coremain"
24 | "github.com/spf13/cobra"
25 | )
26 |
27 | func init() {
28 | probeCmd := &cobra.Command{
29 | Use: "probe",
30 | Short: "Run some server tests.",
31 | }
32 | probeCmd.AddCommand(
33 | newConnReuseCmd(),
34 | newIdleTimeoutCmd(),
35 | newPipelineCmd(),
36 | )
37 | coremain.AddSubCmd(probeCmd)
38 |
39 | v2datCmd := &cobra.Command{
40 | Use: "v2dat",
41 | Short: "Tools that can unpack v2ray data file to text files.",
42 | }
43 | v2datCmd.AddCommand(
44 | newUnpackDomainCmd(),
45 | newUnpackIPCmd(),
46 | )
47 | coremain.AddSubCmd(v2datCmd)
48 |
49 | configCmd := &cobra.Command{
50 | Use: "config",
51 | Short: "Tools that can generate/convert mosdns config file.",
52 | }
53 | configCmd.AddCommand(newGenCmd(), newConvCmd())
54 | coremain.AddSubCmd(configCmd)
55 | }
56 |
--------------------------------------------------------------------------------