cacheoww
This commit is contained in:
2
go.mod
2
go.mod
@@ -5,6 +5,6 @@ go 1.23.0
|
|||||||
toolchain go1.23.4
|
toolchain go1.23.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
golang.org/x/net v0.37.0
|
golang.org/x/net v0.37.0
|
||||||
)
|
)
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1,4 +1,4 @@
|
|||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
|
|||||||
11
main.go
11
main.go
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"dnsthingymagik/recordcache"
|
||||||
"dnsthingymagik/resolver"
|
"dnsthingymagik/resolver"
|
||||||
"dnsthingymagik/resolver/entities"
|
"dnsthingymagik/resolver/entities"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -22,6 +23,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}(udpServer)
|
}(udpServer)
|
||||||
|
|
||||||
|
cache := recordcache.NewCache()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for {
|
for {
|
||||||
buf := make([]byte, 514)
|
buf := make([]byte, 514)
|
||||||
@@ -30,13 +33,13 @@ func main() {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go process(udpServer, addr, buf[:n], &wg)
|
go process(udpServer, addr, buf[:n], cache, &wg)
|
||||||
}
|
}
|
||||||
|
|
||||||
//recordCache.cache.PurgeExpiredEntries()
|
//recordCache.recordcache.PurgeExpiredEntries()
|
||||||
}
|
}
|
||||||
|
|
||||||
func process(udp net.PacketConn, addr net.Addr, buf []byte, wg *sync.WaitGroup) {
|
func process(udp net.PacketConn, addr net.Addr, buf []byte, cache *recordcache.Cache, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
msg, err := resolver.PacketParser(buf)
|
msg, err := resolver.PacketParser(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,7 +49,7 @@ func process(udp net.PacketConn, addr net.Addr, buf []byte, wg *sync.WaitGroup)
|
|||||||
var faq []entities.Record
|
var faq []entities.Record
|
||||||
for _, q := range msg.Questions {
|
for _, q := range msg.Questions {
|
||||||
if q.Type == dnsmessage.TypeA {
|
if q.Type == dnsmessage.TypeA {
|
||||||
nips, err := resolver.ResolveDN(q.Name.String(), msg.Header.ID, dnsmessage.TypeA)
|
nips, err := resolver.ResolveDN(q.Name, msg.Header.ID, dnsmessage.TypeA, cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|||||||
98
recordcache/Cache.go
Normal file
98
recordcache/Cache.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package recordcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dnsthingymagik/resolver/entities"
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
records map[string][]entities.Record
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCache() *Cache {
|
||||||
|
c := &Cache{
|
||||||
|
records: make(map[string][]entities.Record),
|
||||||
|
}
|
||||||
|
go c.cleanupExpiredRecords()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateKey(name dnsmessage.Name, rtype dnsmessage.Type) string {
|
||||||
|
return fmt.Sprintf("%s:%d", name.String(), rtype)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Set(record entities.Record) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
key := generateKey(record.Name, record.RType)
|
||||||
|
record.ExpireAt = time.Now().Add(time.Duration(record.TTL) * time.Second)
|
||||||
|
|
||||||
|
existingRecords, exists := c.records[key]
|
||||||
|
if exists {
|
||||||
|
for _, existingRecord := range existingRecords {
|
||||||
|
if existingRecord.IP.Equal(record.IP) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.records[key] = append(c.records[key], record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Get(name dnsmessage.Name, rtype dnsmessage.Type) ([]entities.Record, bool) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
key := generateKey(name, rtype)
|
||||||
|
records, exists := c.records[key]
|
||||||
|
if !exists {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
validRecords := []entities.Record{}
|
||||||
|
now := time.Now()
|
||||||
|
for _, record := range records {
|
||||||
|
remainingTTL := uint32(record.ExpireAt.Sub(now).Seconds())
|
||||||
|
if remainingTTL > 0 {
|
||||||
|
record.TTL = remainingTTL
|
||||||
|
validRecords = append(validRecords, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(validRecords) == 0 {
|
||||||
|
delete(c.records, key)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
c.records[key] = validRecords
|
||||||
|
return validRecords, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) cleanupExpiredRecords() {
|
||||||
|
ticker := time.NewTicker(time.Hour) // Run every hour
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
c.mu.Lock()
|
||||||
|
now := time.Now()
|
||||||
|
for key, records := range c.records {
|
||||||
|
validRecords := []entities.Record{}
|
||||||
|
for _, record := range records {
|
||||||
|
if now.Before(record.ExpireAt) {
|
||||||
|
validRecords = append(validRecords, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(validRecords) == 0 {
|
||||||
|
delete(c.records, key)
|
||||||
|
} else {
|
||||||
|
c.records[key] = validRecords
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,38 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"dnsthingymagik/recordcache"
|
||||||
"dnsthingymagik/resolver/entities"
|
"dnsthingymagik/resolver/entities"
|
||||||
"dnsthingymagik/resolver/query"
|
"dnsthingymagik/resolver/query"
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ResolveDN(domainName string, id uint16, rtype dnsmessage.Type) ([]entities.Record, error) {
|
func ResolveDN(domainName dnsmessage.Name, id uint16, rtype dnsmessage.Type, cache *recordcache.Cache) ([]entities.Record, error) {
|
||||||
|
if recs, found := cache.Get(domainName, dnsmessage.TypeA); found {
|
||||||
|
return recs, nil
|
||||||
|
}
|
||||||
|
|
||||||
ROOT_SERVERS := []string{
|
ROOT_SERVERS := []string{
|
||||||
"198.41.0.4",
|
"198.41.0.4",
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := resolveFromRoot(domainName, id, rtype, ROOT_SERVERS)
|
result, err := resolveFromRoot(domainName, id, rtype, ROOT_SERVERS, cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, rec := range result {
|
||||||
|
cache.Set(rec)
|
||||||
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveFromRoot(domainName string, id uint16, rtype dnsmessage.Type, ROOT_SERVERS []string) ([]entities.Record, error) {
|
func resolveFromRoot(domainName dnsmessage.Name, id uint16, rtype dnsmessage.Type, ROOT_SERVERS []string, cache *recordcache.Cache) ([]entities.Record, error) {
|
||||||
var records []entities.Record
|
var records []entities.Record
|
||||||
|
|
||||||
if !strings.HasSuffix(domainName, ".") {
|
|
||||||
domainName += "."
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rootServer := range ROOT_SERVERS {
|
for _, rootServer := range ROOT_SERVERS {
|
||||||
|
|
||||||
q := dnsmessage.Message{
|
q := dnsmessage.Message{
|
||||||
@@ -38,7 +42,7 @@ func resolveFromRoot(domainName string, id uint16, rtype dnsmessage.Type, ROOT_S
|
|||||||
},
|
},
|
||||||
Questions: []dnsmessage.Question{
|
Questions: []dnsmessage.Question{
|
||||||
{
|
{
|
||||||
Name: dnsmessage.MustNewName(domainName),
|
Name: domainName,
|
||||||
Type: rtype,
|
Type: rtype,
|
||||||
Class: dnsmessage.ClassINET,
|
Class: dnsmessage.ClassINET,
|
||||||
},
|
},
|
||||||
@@ -90,7 +94,7 @@ func resolveFromRoot(domainName string, id uint16, rtype dnsmessage.Type, ROOT_S
|
|||||||
//resolve from refferal
|
//resolve from refferal
|
||||||
for nameserver, nsip := range NSIPmap {
|
for nameserver, nsip := range NSIPmap {
|
||||||
if nsip == nil {
|
if nsip == nil {
|
||||||
nsips, err := ResolveDN(nameserver, id, dnsmessage.TypeA) // resolve nameserver if no ip
|
nsips, err := ResolveDN(dnsmessage.MustNewName(nameserver), id, dnsmessage.TypeA, cache) // resolve nameserver if no ip
|
||||||
nsip = nsips[0].IP
|
nsip = nsips[0].IP
|
||||||
if err != nil || len(nsip) == 0 {
|
if err != nil || len(nsip) == 0 {
|
||||||
log.Printf("DNS server %s not resolved: %s", nameserver, err)
|
log.Printf("DNS server %s not resolved: %s", nameserver, err)
|
||||||
@@ -100,7 +104,7 @@ func resolveFromRoot(domainName string, id uint16, rtype dnsmessage.Type, ROOT_S
|
|||||||
NSIPmap[nameserver] = nsip
|
NSIPmap[nameserver] = nsip
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := resolveFromRefferal(domainName, id, dnsmessage.TypeA, nsip.String()) // resolve refferal using nameserver
|
ip, err := resolveFromRefferal(domainName, id, dnsmessage.TypeA, nsip.String(), cache) // resolve refferal using nameserver
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("DNS server %s not resolving domain %s: %s", nameserver, domainName, err)
|
log.Printf("DNS server %s not resolving domain %s: %s", nameserver, domainName, err)
|
||||||
continue
|
continue
|
||||||
@@ -114,11 +118,7 @@ func resolveFromRoot(domainName string, id uint16, rtype dnsmessage.Type, ROOT_S
|
|||||||
return records, nil
|
return records, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveFromRefferal(domainName string, id uint16, rtype dnsmessage.Type, server string) ([]entities.Record, error) {
|
func resolveFromRefferal(domainName dnsmessage.Name, id uint16, rtype dnsmessage.Type, server string, cache *recordcache.Cache) ([]entities.Record, error) {
|
||||||
if !strings.HasSuffix(domainName, ".") {
|
|
||||||
domainName += "."
|
|
||||||
}
|
|
||||||
|
|
||||||
q := dnsmessage.Message{
|
q := dnsmessage.Message{
|
||||||
Header: dnsmessage.Header{
|
Header: dnsmessage.Header{
|
||||||
ID: id,
|
ID: id,
|
||||||
@@ -126,7 +126,7 @@ func resolveFromRefferal(domainName string, id uint16, rtype dnsmessage.Type, se
|
|||||||
},
|
},
|
||||||
Questions: []dnsmessage.Question{
|
Questions: []dnsmessage.Question{
|
||||||
{
|
{
|
||||||
Name: dnsmessage.MustNewName(domainName),
|
Name: domainName,
|
||||||
Type: rtype,
|
Type: rtype,
|
||||||
Class: dnsmessage.ClassINET,
|
Class: dnsmessage.ClassINET,
|
||||||
},
|
},
|
||||||
@@ -173,7 +173,7 @@ func resolveFromRefferal(domainName string, id uint16, rtype dnsmessage.Type, se
|
|||||||
|
|
||||||
for nameserver, nsip := range NSIPmap {
|
for nameserver, nsip := range NSIPmap {
|
||||||
if nsip == nil {
|
if nsip == nil {
|
||||||
nsips, err := ResolveDN(nameserver, id, dnsmessage.TypeA) // resolve nameserver if no ip
|
nsips, err := ResolveDN(dnsmessage.MustNewName(nameserver), id, dnsmessage.TypeA, cache) // resolve nameserver if no ip
|
||||||
nsip = nsips[0].IP
|
nsip = nsips[0].IP
|
||||||
if err != nil || len(nsip) == 0 {
|
if err != nil || len(nsip) == 0 {
|
||||||
log.Printf("DNS server %s not resolved: %s", nameserver, err)
|
log.Printf("DNS server %s not resolved: %s", nameserver, err)
|
||||||
@@ -183,7 +183,7 @@ func resolveFromRefferal(domainName string, id uint16, rtype dnsmessage.Type, se
|
|||||||
NSIPmap[nameserver] = nsip
|
NSIPmap[nameserver] = nsip
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := resolveFromRefferal(domainName, id, dnsmessage.TypeA, nsip.String()) // resolve refferal using nameserver
|
ip, err := resolveFromRefferal(domainName, id, dnsmessage.TypeA, nsip.String(), cache) // resolve refferal using nameserver
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("DNS server %s not resolving domain %s: %s", nameserver, domainName, err)
|
log.Printf("DNS server %s not resolving domain %s: %s", nameserver, domainName, err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package entities
|
|||||||
import (
|
import (
|
||||||
"golang.org/x/net/dns/dnsmessage"
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
IP net.IP
|
IP net.IP
|
||||||
RType dnsmessage.Type
|
RType dnsmessage.Type
|
||||||
TTL uint32
|
TTL uint32
|
||||||
Class dnsmessage.Class
|
Class dnsmessage.Class
|
||||||
Name dnsmessage.Name
|
Name dnsmessage.Name
|
||||||
|
ExpireAt time.Time
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user