app instance
This commit is contained in:
parent
55d623dff2
commit
051aef824d
@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
Better implementation of [KVAS](https://github.com/qzeleza/kvas)
|
Better implementation of [KVAS](https://github.com/qzeleza/kvas)
|
||||||
|
|
||||||
Roadmap:
|
Realized features:
|
||||||
- [x] DNS Proxy
|
- [x] DNS Proxy (UDP)
|
||||||
- [x] DNS Records table
|
- [ ] DNS Proxy (TCP)
|
||||||
|
- [x] Records table
|
||||||
- [x] IPTables rules to remap DNS server [1]
|
- [x] IPTables rules to remap DNS server [1]
|
||||||
- [ ] Rule composer
|
- [ ] Rule composer
|
||||||
- [ ] List loading/watching (temporary)
|
- [ ] List loading/watching (temporary)
|
||||||
|
146
kvas2.go
Normal file
146
kvas2.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"kvas2-go/models"
|
||||||
|
"kvas2-go/pkg/dns-proxy"
|
||||||
|
"kvas2-go/pkg/iptables-helper"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
MinimalTTL time.Duration
|
||||||
|
ChainPostfix string
|
||||||
|
TargetDNSServerAddress string
|
||||||
|
ListenPort uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
Config Config
|
||||||
|
|
||||||
|
DNSProxy *dnsProxy.DNSProxy
|
||||||
|
DNSOverrider *iptablesHelper.DNSOverrider
|
||||||
|
Records *Records
|
||||||
|
Groups []*models.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Listen(ctx context.Context) []error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
isError := make(chan struct{})
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
|
var errsMu sync.Mutex
|
||||||
|
handleError := func(err error) {
|
||||||
|
errsMu.Lock()
|
||||||
|
defer errsMu.Unlock()
|
||||||
|
|
||||||
|
errs = append(errs, err)
|
||||||
|
once.Do(func() { close(isError) })
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if err, ok := r.(error); ok {
|
||||||
|
handleError(err)
|
||||||
|
} else {
|
||||||
|
handleError(fmt.Errorf("%v", r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
newCtx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := a.DNSOverrider.Enable(); err != nil {
|
||||||
|
handleError(fmt.Errorf("failed to override DNS: %w", err))
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := a.DNSProxy.Listen(newCtx); err != nil {
|
||||||
|
handleError(fmt.Errorf("failed to initialize DNS proxy: %v", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-isError:
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.DNSOverrider.Disable(); err != nil {
|
||||||
|
handleError(fmt.Errorf("failed to rollback override DNS changes: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) handleRecord(msg *dnsProxy.Message) {
|
||||||
|
printKnownRecords := func() {
|
||||||
|
for _, q := range msg.QD {
|
||||||
|
fmt.Printf("%04x: DBG Known addresses for: %s\n", msg.ID, q.QName.String())
|
||||||
|
for idx, addr := range a.Records.GetARecords(q.QName.String(), true) {
|
||||||
|
fmt.Printf("%04x: #%d: %s\n", msg.ID, idx, addr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parseResponseRecord := func(rr dnsProxy.ResourceRecord) {
|
||||||
|
switch v := rr.(type) {
|
||||||
|
case dnsProxy.Address:
|
||||||
|
fmt.Printf("%04x: -> A: Name: %s; Address: %s; TTL: %d\n", msg.ID, v.Name, v.Address.String(), v.TTL)
|
||||||
|
ttlDuration := time.Duration(v.TTL) * time.Second
|
||||||
|
if ttlDuration < a.Config.MinimalTTL {
|
||||||
|
ttlDuration = a.Config.MinimalTTL
|
||||||
|
}
|
||||||
|
a.Records.PutARecord(v.Name.String(), v.Address, ttlDuration)
|
||||||
|
case dnsProxy.CName:
|
||||||
|
fmt.Printf("%04x: -> CNAME: Name: %s; CName: %s\n", msg.ID, v.Name, v.CName)
|
||||||
|
ttlDuration := time.Duration(v.TTL) * time.Second
|
||||||
|
if ttlDuration < a.Config.MinimalTTL {
|
||||||
|
ttlDuration = a.Config.MinimalTTL
|
||||||
|
}
|
||||||
|
a.Records.PutCNameRecord(v.Name.String(), v.CName.String(), ttlDuration)
|
||||||
|
default:
|
||||||
|
fmt.Printf("%04x: -> Unknown: %x\n", msg.ID, v.EncodeResource())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printKnownRecords()
|
||||||
|
for _, q := range msg.QD {
|
||||||
|
fmt.Printf("%04x: <- Request name: %s\n", msg.ID, q.QName.String())
|
||||||
|
}
|
||||||
|
for _, a := range msg.AN {
|
||||||
|
parseResponseRecord(a)
|
||||||
|
}
|
||||||
|
for _, a := range msg.NS {
|
||||||
|
parseResponseRecord(a)
|
||||||
|
}
|
||||||
|
for _, a := range msg.AR {
|
||||||
|
parseResponseRecord(a)
|
||||||
|
}
|
||||||
|
printKnownRecords()
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config Config) (*App, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
app := &App{}
|
||||||
|
|
||||||
|
app.Config = config
|
||||||
|
|
||||||
|
app.DNSProxy = dnsProxy.New(app.Config.ListenPort, app.Config.TargetDNSServerAddress)
|
||||||
|
app.DNSProxy.MsgHandler = app.handleRecord
|
||||||
|
|
||||||
|
app.Records = NewRecords()
|
||||||
|
|
||||||
|
app.DNSOverrider, err = iptablesHelper.NewDNSOverrider(fmt.Sprintf("%s_DNSOVERRIDER", app.Config.ChainPostfix), app.Config.ListenPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize DNS overrider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Groups = make([]*models.Group, 0)
|
||||||
|
|
||||||
|
return app, nil
|
||||||
|
|
||||||
|
}
|
86
main.go
86
main.go
@ -7,93 +7,45 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
dnsProxy "kvas2-go/dns-proxy"
|
|
||||||
iptablesHelper "kvas2-go/iptables-helper"
|
|
||||||
ruleComposer "kvas2-go/rule-composer"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ChainPostfix = "KVAS2"
|
|
||||||
ListenPort = uint16(7548)
|
|
||||||
TargetDNSServerAddress = "127.0.0.1:53"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
records := ruleComposer.NewRecords()
|
app, err := New(Config{
|
||||||
proxy := dnsProxy.New(ListenPort, TargetDNSServerAddress)
|
MinimalTTL: time.Hour,
|
||||||
dnsOverrider, err := iptablesHelper.NewDNSOverrider(fmt.Sprintf("%s_DNSOVERRIDER", ChainPostfix), ListenPort)
|
ChainPostfix: "KVAS2",
|
||||||
|
TargetDNSServerAddress: "127.0.0.1:53",
|
||||||
|
ListenPort: 7548,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to initialize DNS overrider: %v", err)
|
log.Fatalf("failed to initialize application: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
proxy.MsgHandler = func(msg *dnsProxy.Message) {
|
|
||||||
printKnownRecords := func() {
|
|
||||||
for _, q := range msg.QD {
|
|
||||||
fmt.Printf("%04x: DBG Known addresses for: %s\n", msg.ID, q.QName.String())
|
|
||||||
for idx, addr := range records.GetARecords(q.QName.String(), true) {
|
|
||||||
fmt.Printf("%04x: #%d: %s\n", msg.ID, idx, addr.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parseResponseRecord := func(rr dnsProxy.ResourceRecord) {
|
|
||||||
switch v := rr.(type) {
|
|
||||||
case dnsProxy.Address:
|
|
||||||
fmt.Printf("%04x: -> A: Name: %s; Address: %s; TTL: %d\n", msg.ID, v.Name, v.Address.String(), v.TTL)
|
|
||||||
records.PutARecord(v.Name.String(), v.Address, int64(v.TTL))
|
|
||||||
case dnsProxy.CName:
|
|
||||||
fmt.Printf("%04x: -> CNAME: Name: %s; CName: %s\n", msg.ID, v.Name, v.CName)
|
|
||||||
records.PutCNameRecord(v.Name.String(), v.CName.String(), int64(v.TTL))
|
|
||||||
default:
|
|
||||||
fmt.Printf("%04x: -> Unknown: %x\n", msg.ID, v.EncodeResource())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printKnownRecords()
|
|
||||||
for _, q := range msg.QD {
|
|
||||||
fmt.Printf("%04x: <- Request name: %s\n", msg.ID, q.QName.String())
|
|
||||||
}
|
|
||||||
for _, a := range msg.AN {
|
|
||||||
parseResponseRecord(a)
|
|
||||||
}
|
|
||||||
for _, a := range msg.NS {
|
|
||||||
parseResponseRecord(a)
|
|
||||||
}
|
|
||||||
for _, a := range msg.AR {
|
|
||||||
parseResponseRecord(a)
|
|
||||||
}
|
|
||||||
printKnownRecords()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
appErrsChan := make(chan []error)
|
||||||
go func() {
|
go func() {
|
||||||
err := proxy.Listen(ctx)
|
errs := app.Listen(ctx)
|
||||||
if err != nil {
|
appErrsChan <- errs
|
||||||
log.Fatalf("failed to initialize DNS proxy: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = dnsOverrider.Enable()
|
fmt.Println("Started service...")
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to override DNS: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Started service on port '%d'\n", ListenPort)
|
|
||||||
|
|
||||||
c := make(chan os.Signal, 1)
|
c := make(chan os.Signal, 1)
|
||||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case appErrs, _ := <-appErrsChan:
|
||||||
|
for _, err := range appErrs {
|
||||||
|
// TODO: Error log level
|
||||||
|
log.Printf("failed to start application: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
case <-c:
|
case <-c:
|
||||||
fmt.Println("Graceful shutdown...")
|
fmt.Println("Graceful shutdown...")
|
||||||
cancel()
|
cancel()
|
||||||
err = dnsOverrider.Disable()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to rollback override DNS changes: %v", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
models/domain.go
Normal file
10
models/domain.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Domain struct {
|
||||||
|
ID int
|
||||||
|
Group *Group
|
||||||
|
Type string
|
||||||
|
Domain string
|
||||||
|
Enable bool
|
||||||
|
Comment string
|
||||||
|
}
|
8
models/group.go
Normal file
8
models/group.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
Interface string
|
||||||
|
Domains []*Domain
|
||||||
|
}
|
@ -47,7 +47,6 @@ func (p DNSProxy) Listen(ctx context.Context) error {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Println("Shutting down DNS proxy...")
|
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
buffer := make([]byte, DNSMaxUDPPackageSize)
|
buffer := make([]byte, DNSMaxUDPPackageSize)
|
@ -27,7 +27,7 @@ func parseName(response []byte, pos int) (*Name, int, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if length&0xC0 == 0xC0 {
|
if length&0xC0 != 0 {
|
||||||
if responseLen < pos+1 {
|
if responseLen < pos+1 {
|
||||||
return nil, pos, io.EOF
|
return nil, pos, io.EOF
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ruleComposer
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
@ -73,7 +73,7 @@ func (r *Records) GetARecords(domainName string, recursive bool) []net.IP {
|
|||||||
return aRecords
|
return aRecords
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Records) PutCNameRecord(domainName string, cName string, ttl int64) {
|
func (r *Records) PutCNameRecord(domainName string, cName string, ttl time.Duration) {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
@ -81,10 +81,10 @@ func (r *Records) PutCNameRecord(domainName string, cName string, ttl int64) {
|
|||||||
r.cnameRecords[domainName] = make(map[string]time.Time)
|
r.cnameRecords[domainName] = make(map[string]time.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.cnameRecords[domainName][cName] = time.Now().Add(time.Second * time.Duration(ttl))
|
r.cnameRecords[domainName][cName] = time.Now().Add(ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Records) PutARecord(domainName string, addr net.IP, ttl int64) {
|
func (r *Records) PutARecord(domainName string, addr net.IP, ttl time.Duration) {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ func (r *Records) PutARecord(domainName string, addr net.IP, ttl int64) {
|
|||||||
r.aRecords[domainName] = make(map[string]time.Time)
|
r.aRecords[domainName] = make(map[string]time.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.aRecords[domainName][string(addr)] = time.Now().Add(time.Second * time.Duration(ttl))
|
r.aRecords[domainName][string(addr)] = time.Now().Add(ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Records) Cleanup() {
|
func (r *Records) Cleanup() {
|
Loading…
x
Reference in New Issue
Block a user