diff --git a/go.mod b/go.mod index 183977686..5a4e914a8 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,8 @@ require ( github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.5.0 // indirect github.com/onsi/ginkgo/v2 v2.13.0 // indirect + github.com/oschwald/geoip2-golang v1.9.0 // indirect + github.com/oschwald/maxminddb-golang v1.11.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.5.0 // indirect diff --git a/go.sum b/go.sum index 41a59b2d2..069deb98c 100644 --- a/go.sum +++ b/go.sum @@ -183,6 +183,10 @@ github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4 github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc= +github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= +github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0= +github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 764811fc7..6e810e6f9 100644 --- a/plugin/enabled_plugins.go +++ b/plugin/enabled_plugins.go @@ -24,6 +24,7 @@ import ( // data provider _ "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" // matcher _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/client_ip" @@ -38,6 +39,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" _ "github.com/IrineSistiana/mosdns/v5/plugin/matcher/string_exp" // executable 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 +}