├── .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 | --------------------------------------------------------------------------------