Skip to content

Commit

Permalink
add reverse_lookup plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
IrineSistiana committed Jun 22, 2022
1 parent efcf116 commit 09a1320
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 0 deletions.
1 change: 1 addition & 0 deletions plugin/enabled_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
_ "github.com/IrineSistiana/mosdns/v4/plugin/executable/nftset"
_ "github.com/IrineSistiana/mosdns/v4/plugin/executable/padding"
_ "github.com/IrineSistiana/mosdns/v4/plugin/executable/redirect"
_ "github.com/IrineSistiana/mosdns/v4/plugin/executable/reverse_lookup"
_ "github.com/IrineSistiana/mosdns/v4/plugin/executable/sequence"
_ "github.com/IrineSistiana/mosdns/v4/plugin/executable/single_flight"
_ "github.com/IrineSistiana/mosdns/v4/plugin/executable/sleep"
Expand Down
123 changes: 123 additions & 0 deletions plugin/executable/reverse_lookup/reverse_lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

package reverselookup

import (
"context"
"fmt"
"github.com/IrineSistiana/mosdns/v4/coremain"
"github.com/IrineSistiana/mosdns/v4/pkg/executable_seq"
"github.com/IrineSistiana/mosdns/v4/pkg/query_context"
"github.com/miekg/dns"
"net"
"net/http"
"net/netip"
"time"
)

const (
PluginType = "reverse_lookup"
)

func init() {
coremain.RegNewPluginFunc(PluginType, Init, func() interface{} { return new(Args) })
}

var _ coremain.ExecutablePlugin = (*reverseLookup)(nil)

type Args struct {
TTL int // Default is 10.
}

type reverseLookup struct {
*coremain.BP
args *Args
store *store
}

func (p *reverseLookup) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ipStr := req.URL.Query().Get("ip")
addr, err := netip.ParseAddr(ipStr)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}

d := p.store.lookup(addr)
w.Write([]byte(d))
}

func Init(bp *coremain.BP, args interface{}) (p coremain.Plugin, err error) {
return newReverseLookup(bp, args.(*Args)), nil
}

func newReverseLookup(bp *coremain.BP, args *Args) coremain.Plugin {
p := &reverseLookup{
BP: bp,
args: args,
store: newStore(),
}

bp.M().GetHTTPAPIMux().Handle("/plugins/"+bp.Tag(), p)
return p
}

func (p *reverseLookup) Exec(ctx context.Context, qCtx *query_context.Context, next executable_seq.ExecutableChainNode) error {
if err := executable_seq.ExecChainNode(ctx, qCtx, next); err != nil {
return err
}
r := qCtx.R()
if r == nil {
return 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
}
addr, ok := netip.AddrFromSlice(ip)
if !ok {
return fmt.Errorf("invalid ip %s", ip)
}

h := rr.Header()
ttl := uint32(p.args.TTL)
if ttl == 0 {
ttl = 10
}
if h.Ttl > ttl {
h.Ttl = ttl
}
p.store.save(rr.Header().Name, time.Duration(ttl)*time.Second, addr)
}
return nil
}

func (p *reverseLookup) Close() error {
p.store.close()
return nil
}
107 changes: 107 additions & 0 deletions plugin/executable/reverse_lookup/store.go
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

package reverselookup

import (
"net/netip"
"sync"
"time"
)

type store struct {
sync.RWMutex
m map[netip.Addr]*elem

closeOnce sync.Once
closeChan chan struct{}
}

type elem struct {
expire time.Time
d string
}

func newStore() *store {
s := &store{
m: make(map[netip.Addr]*elem),
closeChan: make(chan struct{}),
}

go func() {
ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop()

for {
select {
case <-s.closeChan:
return
case <-ticker.C:
s.clean()
}
}
}()

return s
}

func (s *store) save(domain string, ttl time.Duration, ip ...netip.Addr) {
if len(ip) == 0 {
return
}

e := &elem{
expire: time.Now().Add(ttl),
d: domain,
}

s.Lock()
for _, addr := range ip {
s.m[addr] = e
}
s.Unlock()
}

func (s *store) lookup(ip netip.Addr) string {
s.RLock()
defer s.RUnlock()
d := s.m[ip]
if d == nil {
return ""
}
return d.d
}

func (s *store) clean() {
now := time.Now()
s.Lock()
defer s.Unlock()

for addr, e := range s.m {
if e.expire.Before(now) {
delete(s.m, addr)
}
}
}

func (s *store) close() {
s.closeOnce.Do(func() {
close(s.closeChan)
})
}

0 comments on commit 09a1320

Please sign in to comment.