diff --git a/go.mod b/go.mod index 2212cd31f..54d35b82e 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,8 @@ require ( github.com/mdlayher/netlink v1.7.1 // indirect github.com/mdlayher/socket v0.4.0 // indirect github.com/onsi/ginkgo/v2 v2.9.1 // indirect + github.com/oschwald/geoip2-golang v1.8.0 // indirect + github.com/oschwald/maxminddb-golang v1.10.0 // indirect github.com/pelletier/go-toml/v2 v2.0.7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect diff --git a/go.sum b/go.sum index 960ad98d5..5ec4e1268 100644 --- a/go.sum +++ b/go.sum @@ -179,6 +179,10 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= github.com/onsi/gomega v1.27.3 h1:5VwIwnBY3vbBDOJrNtA4rVdiTZCsq9B5F12pvy1Drmk= +github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs= +github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw= +github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= +github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/plugin/data_provider/iface.go b/plugin/data_provider/iface.go index 7ec08d205..a4563844e 100644 --- a/plugin/data_provider/iface.go +++ b/plugin/data_provider/iface.go @@ -22,6 +22,7 @@ package data_provider import ( "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain" "github.com/IrineSistiana/mosdns/v5/pkg/matcher/netlist" + "github.com/oschwald/geoip2-golang" ) type DomainMatcherProvider interface { @@ -31,3 +32,7 @@ type DomainMatcherProvider interface { type IPMatcherProvider interface { GetIPMatcher() netlist.Matcher } + +type MmdbMatcherProvider interface { + GetMmdbMatcher() *geoip2.Reader +} diff --git a/plugin/data_provider/mmdb/mmdb.go b/plugin/data_provider/mmdb/mmdb.go new file mode 100644 index 000000000..542f69d3e --- /dev/null +++ b/plugin/data_provider/mmdb/mmdb.go @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020-2022, IrineSistiana + * + * This file is part of mosdns. + * + * mosdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * mosdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package mmdb + +import ( + "github.com/IrineSistiana/mosdns/v5/coremain" + "github.com/IrineSistiana/mosdns/v5/plugin/data_provider" + "github.com/oschwald/geoip2-golang" +) + +const PluginType = "mmdb" + +func init() { + coremain.RegNewPluginFunc(PluginType, Init, func() any { return new(Args) }) +} + +func Init(bp *coremain.BP, args any) (any, error) { + return NewMmdb(bp, args.(*Args)) +} + +type Args struct { + File string `yaml:"file"` +} + +var _ data_provider.MmdbMatcherProvider = (*Mmdb)(nil) + +type Mmdb struct { + mmdb *geoip2.Reader +} + +func (m *Mmdb) GetMmdbMatcher() *geoip2.Reader { + return m.mmdb +} + +func NewMmdb(bp *coremain.BP, args *Args) (*Mmdb, error) { + m := &Mmdb{} + + db, err := geoip2.Open(args.File) + if err == nil { + m.mmdb = db + } + + return m, nil +} diff --git a/plugin/enabled_plugins.go b/plugin/enabled_plugins.go index 199587c80..90b5e6b7f 100644 --- a/plugin/enabled_plugins.go +++ b/plugin/enabled_plugins.go @@ -23,6 +23,7 @@ package plugin import ( _ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/domain_set" _ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/ip_set" + _ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/mmdb" ) // matches @@ -39,6 +40,7 @@ import ( _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/random" _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/rcode" _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/resp_ip" + _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/resp_ip_mmdb" ) // executables diff --git a/plugin/matcher/resp_ip_mmdb/resp_ip_mmdb.go b/plugin/matcher/resp_ip_mmdb/resp_ip_mmdb.go new file mode 100644 index 000000000..33d93be0f --- /dev/null +++ b/plugin/matcher/resp_ip_mmdb/resp_ip_mmdb.go @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020-2022, IrineSistiana + * + * This file is part of mosdns. + * + * mosdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * mosdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package resp_ip_mmdb + +import ( + "context" + "errors" + "fmt" + "github.com/IrineSistiana/mosdns/v5/pkg/query_context" + "github.com/IrineSistiana/mosdns/v5/plugin/data_provider" + "github.com/IrineSistiana/mosdns/v5/plugin/executable/sequence" + "github.com/miekg/dns" + "github.com/oschwald/geoip2-golang" + "net" + "strings" +) + +const PluginType = "resp_ip_mmdb" + +func init() { + sequence.MustRegMatchQuickSetup(PluginType, QuickSetup) +} + +func QuickSetup(bq sequence.BQ, s string) (sequence.Matcher, error) { + if len(s) == 0 { + return nil, errors.New("a iso code probability is required") + } + + args := strings.Fields(s) + if len(args) != 2 { + return nil, errors.New("probability error, must like: resp_ip_mmdb $plugin_name CN") + } + + mmdbName, _ := cutPrefix(args[0], "$") + + p := bq.M().GetPlugin(mmdbName) + provider, _ := p.(data_provider.MmdbMatcherProvider) + if provider == nil { + return nil, fmt.Errorf("cannot find mmdb %s", mmdbName) + } + m := provider.GetMmdbMatcher() + + return &Matcher{args[1], m}, nil +} + +type Matcher struct { + isoCode string + mmdb *geoip2.Reader +} + +func (m *Matcher) Match(_ context.Context, qCtx *query_context.Context) (bool, error) { + r := qCtx.R() + if r == nil { + return false, nil + } + + if m.mmdb == nil { + return false, nil + } + + for _, rr := range r.Answer { + var ip net.IP + switch rr := rr.(type) { + case *dns.A: + ip = rr.A + case *dns.AAAA: + ip = rr.AAAA + default: + continue + } + + record, err := m.mmdb.Country(ip) + if err != nil { + continue + } + + if record.Country.IsoCode == m.isoCode { + return true, nil + } + } + + return false, nil +} + +func cutPrefix(s string, p string) (string, bool) { + if strings.HasPrefix(s, p) { + return strings.TrimPrefix(s, p), true + } + return s, false +}