config refactoring
All checks were successful
Build and Package OPKG / Build for aarch64-3.10 (push) Successful in 45s
Build and Package OPKG / Build for armv5-3.2 (push) Successful in 41s
Build and Package OPKG / Build for armv7-2.6 (push) Successful in 40s
Build and Package OPKG / Build for armv7-3.2 (push) Successful in 41s
Build and Package OPKG / Build for mips-3.4 (push) Successful in 41s
Build and Package OPKG / Build for mipsel-3.4 (push) Successful in 40s

This commit is contained in:
Vladimir Avtsenov 2025-02-14 03:17:43 +03:00
parent 5e909ac18d
commit 07c07b6aba
7 changed files with 283 additions and 243 deletions

View File

@ -70,30 +70,15 @@ func main() {
} }
defer removePIDFile() defer removePIDFile()
cfg := models.ConfigFile{} cfg := models.Config{}
cfgFile, err := os.ReadFile(cfgFileLocation) cfgFile, err := os.ReadFile(cfgFileLocation)
if err == nil { if err != nil {
err = yaml.Unmarshal(cfgFile, &cfg)
if err != nil {
log.Fatal().Err(err).Msg("failed to parse config.yaml")
}
} else {
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
log.Fatal().Err(err).Msg("failed to read config.yaml") log.Fatal().Err(err).Msg("failed to read config.yaml")
} }
cfg = models.ConfigFile{ cfg = models.Config{
AppConfig: models.AppConfig{ ConfigVersion: "0.1.0",
LogLevel: "info", App: kvas2.DefaultAppConfig,
AdditionalTTL: 216000,
ChainPrefix: "KVAS2_",
IPSetPrefix: "kvas2_",
LinkName: "br0",
TargetDNSServerAddress: "127.0.0.1",
TargetDNSServerPort: 53,
ListenDNSPort: 3553,
},
} }
out, err := yaml.Marshal(cfg) out, err := yaml.Marshal(cfg)
if err != nil { if err != nil {
@ -107,9 +92,14 @@ func main() {
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("failed to save config.yaml") log.Fatal().Err(err).Msg("failed to save config.yaml")
} }
} else {
err = yaml.Unmarshal(cfgFile, &cfg)
if err != nil {
log.Fatal().Err(err).Msg("failed to parse config.yaml")
}
} }
switch cfg.AppConfig.LogLevel { switch cfg.App.LogLevel {
case "trace": case "trace":
zerolog.SetGlobalLevel(zerolog.TraceLevel) zerolog.SetGlobalLevel(zerolog.TraceLevel)
case "debug": case "debug":
@ -132,18 +122,18 @@ func main() {
zerolog.SetGlobalLevel(zerolog.InfoLevel) zerolog.SetGlobalLevel(zerolog.InfoLevel)
} }
app, err := kvas2.New(cfg) app := kvas2.New()
err = app.ImportConfig(cfg)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("failed to initialize application") log.Fatal().Err(err).Msg("failed to import config")
} }
ctx, cancel := context.WithCancel(context.Background())
log.Info().Msg("starting service") log.Info().Msg("starting service")
/* /*
Starting app with graceful shutdown Starting app with graceful shutdown
*/ */
ctx, cancel := context.WithCancel(context.Background())
appResult := make(chan error) appResult := make(chan error)
go func() { go func() {
appResult <- app.Start(ctx) appResult <- app.Start(ctx)

View File

@ -12,33 +12,33 @@ import (
) )
type DNSMITMProxy struct { type DNSMITMProxy struct {
TargetDNSServerAddress string UpstreamDNSAddress string
TargetDNSServerPort uint16 UpstreamDNSPort uint16
RequestHook func(net.Addr, dns.Msg, string) (*dns.Msg, *dns.Msg, error) RequestHook func(net.Addr, dns.Msg, string) (*dns.Msg, *dns.Msg, error)
ResponseHook func(net.Addr, dns.Msg, dns.Msg, string) (*dns.Msg, error) ResponseHook func(net.Addr, dns.Msg, dns.Msg, string) (*dns.Msg, error)
} }
func (p DNSMITMProxy) requestDNS(req []byte, network string) ([]byte, error) { func (p DNSMITMProxy) requestDNS(req []byte, network string) ([]byte, error) {
serverConn, err := net.Dial(network, fmt.Sprintf("%s:%d", p.TargetDNSServerAddress, p.TargetDNSServerPort)) upstreamConn, err := net.Dial(network, fmt.Sprintf("%s:%d", p.UpstreamDNSAddress, p.UpstreamDNSPort))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to dial DNS server: %w", err) return nil, fmt.Errorf("failed to dial DNS upstream: %w", err)
} }
defer func() { _ = serverConn.Close() }() defer func() { _ = upstreamConn.Close() }()
err = serverConn.SetDeadline(time.Now().Add(time.Second * 5)) err = upstreamConn.SetDeadline(time.Now().Add(time.Second * 5))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to set deadline: %w", err) return nil, fmt.Errorf("failed to set deadline: %w", err)
} }
if network == "tcp" { if network == "tcp" {
err = binary.Write(serverConn, binary.BigEndian, uint16(len(req))) err = binary.Write(upstreamConn, binary.BigEndian, uint16(len(req)))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to write length: %w", err) return nil, fmt.Errorf("failed to write length: %w", err)
} }
} }
n, err := serverConn.Write(req) n, err := upstreamConn.Write(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to write request: %w", err) return nil, fmt.Errorf("failed to write request: %w", err)
} }
@ -46,7 +46,7 @@ func (p DNSMITMProxy) requestDNS(req []byte, network string) ([]byte, error) {
var resp []byte var resp []byte
if network == "tcp" { if network == "tcp" {
var respLen uint16 var respLen uint16
err = binary.Read(serverConn, binary.BigEndian, &respLen) err = binary.Read(upstreamConn, binary.BigEndian, &respLen)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read length: %w", err) return nil, fmt.Errorf("failed to read length: %w", err)
} }
@ -55,7 +55,7 @@ func (p DNSMITMProxy) requestDNS(req []byte, network string) ([]byte, error) {
resp = make([]byte, 512) resp = make([]byte, 512)
} }
n, err = serverConn.Read(resp) n, err = upstreamConn.Read(resp)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err) return nil, fmt.Errorf("failed to read response: %w", err)
} }
@ -213,9 +213,3 @@ func (p DNSMITMProxy) ListenUDP(ctx context.Context, addr *net.UDPAddr) error {
}(conn, clientAddr) }(conn, clientAddr)
} }
} }
func New() *DNSMITMProxy {
return &DNSMITMProxy{
TargetDNSServerPort: 53,
}
}

View File

@ -15,7 +15,7 @@ import (
) )
type Group struct { type Group struct {
*models.Group models.Group
enabled bool enabled bool
iptables *iptables.IPTables iptables *iptables.IPTables
@ -126,9 +126,14 @@ func (g *Group) Sync(records *records.Records) error {
} }
for addr, ttl := range addresses { for addr, ttl := range addresses {
// TODO: Check TTL
if _, exists := currentAddresses[addr]; exists { if _, exists := currentAddresses[addr]; exists {
continue if currentAddresses[addr] == nil {
continue
} else {
if ttl < *currentAddresses[addr] {
continue
}
}
} }
ip := net.IP(addr) ip := net.IP(addr)
err = g.AddIP(ip, ttl) err = g.AddIP(ip, ttl)
@ -182,7 +187,7 @@ func (g *Group) LinkUpdateHook(event netlink.LinkUpdate) error {
return g.ipsetToLink.LinkUpdateHook(event) return g.ipsetToLink.LinkUpdateHook(event)
} }
func NewGroup(group *models.Group, nh4 *netfilterHelper.NetfilterHelper, chainPrefix, ipsetNamePrefix string) (*Group, error) { func NewGroup(group models.Group, nh4 *netfilterHelper.NetfilterHelper, chainPrefix, ipsetNamePrefix string) (*Group, error) {
ipsetName := fmt.Sprintf("%s%8x", ipsetNamePrefix, group.ID) ipsetName := fmt.Sprintf("%s%8x", ipsetNamePrefix, group.ID)
ipset, err := nh4.IPSet(ipsetName) ipset, err := nh4.IPSet(ipsetName)
if err != nil { if err != nil {

367
kvas2.go
View File

@ -2,13 +2,10 @@ package kvas2
import ( import (
"context" "context"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"math/rand"
"net" "net"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
@ -25,37 +22,42 @@ import (
) )
var ( var (
ErrAlreadyRunning = errors.New("already running") ErrAlreadyRunning = errors.New("already running")
ErrGroupIDConflict = errors.New("group id conflict") ErrGroupIDConflict = errors.New("group id conflict")
ErrRuleIDConflict = errors.New("rule id conflict") ErrRuleIDConflict = errors.New("rule id conflict")
ErrConfigUnsupportedVersion = errors.New("config unsupported version")
) )
func randomId() [4]byte { var DefaultAppConfig = models.App{
id := make([]byte, 4) DNSProxy: models.DNSProxy{
binary.BigEndian.PutUint32(id, rand.Uint32()) Host: models.DNSProxyServer{Address: "[::]", Port: 3553},
return [4]byte(id) Upstream: models.DNSProxyServer{Address: "127.0.0.1", Port: 53},
} DisableRemap53: false,
DisableFakePTR: false,
type Config struct { DisableDropAAAA: false,
AdditionalTTL uint32 },
ChainPrefix string Netfilter: models.Netfilter{
IPSetPrefix string IPTables: models.IPTables{
LinkName string ChainPrefix: "KVAS2_",
TargetDNSServerAddress string },
TargetDNSServerPort uint16 IPSet: models.IPSet{
ListenDNSPort uint16 TablePrefix: "kvas2_",
AdditionalTTL: 3600,
},
},
Link: []string{"br0"},
LogLevel: "info",
} }
type App struct { type App struct {
Config Config config models.App
unprocessedGroups []models.Group
DNSMITM *dnsMitmProxy.DNSMITMProxy dnsMITM *dnsMitmProxy.DNSMITMProxy
NetfilterHelper4 *netfilterHelper.NetfilterHelper nfHelper4 *netfilterHelper.NetfilterHelper
NetfilterHelper6 *netfilterHelper.NetfilterHelper nfHelper6 *netfilterHelper.NetfilterHelper
Records *records.Records records *records.Records
Groups []*group.Group groups []*group.Group
Link netlink.Link
isRunning bool isRunning bool
dnsOverrider4 *netfilterHelper.PortRemap dnsOverrider4 *netfilterHelper.PortRemap
@ -63,7 +65,6 @@ type App struct {
} }
func (a *App) handleLink(event netlink.LinkUpdate) { func (a *App) handleLink(event netlink.LinkUpdate) {
switch event.Change { switch event.Change {
case 0x00000001: case 0x00000001:
log.Trace(). log.Trace().
@ -71,7 +72,7 @@ func (a *App) handleLink(event netlink.LinkUpdate) {
Int("change", int(event.Change)). Int("change", int(event.Change)).
Msg("interface event") Msg("interface event")
ifaceName := event.Link.Attrs().Name ifaceName := event.Link.Attrs().Name
for _, group := range a.Groups { for _, group := range a.groups {
if group.Interface != ifaceName { if group.Interface != ifaceName {
continue continue
} }
@ -98,6 +99,72 @@ func (a *App) handleLink(event netlink.LinkUpdate) {
} }
func (a *App) start(ctx context.Context) (err error) { func (a *App) start(ctx context.Context) (err error) {
a.dnsMITM = &dnsMitmProxy.DNSMITMProxy{
UpstreamDNSAddress: a.config.DNSProxy.Upstream.Address,
UpstreamDNSPort: a.config.DNSProxy.Upstream.Port,
RequestHook: func(clientAddr net.Addr, reqMsg dns.Msg, network string) (*dns.Msg, *dns.Msg, error) {
if a.config.DNSProxy.DisableFakePTR {
return nil, nil, nil
}
// TODO: Проверить на интерфейс
if len(reqMsg.Question) == 1 && reqMsg.Question[0].Qtype == dns.TypePTR {
respMsg := &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: reqMsg.Id,
Response: true,
RecursionAvailable: true,
Rcode: dns.RcodeNameError,
},
Question: reqMsg.Question,
}
return nil, respMsg, nil
}
return nil, nil, nil
},
ResponseHook: func(clientAddr net.Addr, reqMsg dns.Msg, respMsg dns.Msg, network string) (*dns.Msg, error) {
defer a.handleMessage(respMsg, clientAddr, &network)
if a.config.DNSProxy.DisableDropAAAA {
return nil, nil
}
var idx int
for _, answer := range respMsg.Answer {
if answer.Header().Rrtype == dns.TypeAAAA {
continue
}
respMsg.Answer[idx] = answer
idx++
}
respMsg.Answer = respMsg.Answer[:idx]
return &respMsg, nil
},
}
a.records = records.New()
nh4, err := netfilterHelper.New(false)
if err != nil {
return fmt.Errorf("netfilter helper init fail: %w", err)
}
err = nh4.CleanIPTables(a.config.Netfilter.IPTables.ChainPrefix)
if err != nil {
return fmt.Errorf("failed to clear iptables: %w", err)
}
a.nfHelper4 = nh4
nh6, err := netfilterHelper.New(true)
if err != nil {
return fmt.Errorf("netfilter helper init fail: %w", err)
}
err = nh6.CleanIPTables(a.config.Netfilter.IPTables.ChainPrefix)
if err != nil {
return fmt.Errorf("failed to clear iptables: %w", err)
}
a.nfHelper6 = nh6
newCtx, cancel := context.WithCancel(ctx) newCtx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
@ -108,12 +175,12 @@ func (a *App) start(ctx context.Context) (err error) {
*/ */
go func() { go func() {
addr, err := net.ResolveUDPAddr("udp", "[::]:"+strconv.Itoa(int(a.Config.ListenDNSPort))) addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", a.config.DNSProxy.Host.Address, a.config.DNSProxy.Host.Port))
if err != nil { if err != nil {
errChan <- fmt.Errorf("failed to resolve udp address: %v", err) errChan <- fmt.Errorf("failed to resolve udp address: %v", err)
return return
} }
err = a.DNSMITM.ListenUDP(newCtx, addr) err = a.dnsMITM.ListenUDP(newCtx, addr)
if err != nil { if err != nil {
errChan <- fmt.Errorf("failed to serve DNS UDP proxy: %v", err) errChan <- fmt.Errorf("failed to serve DNS UDP proxy: %v", err)
return return
@ -121,49 +188,65 @@ func (a *App) start(ctx context.Context) (err error) {
}() }()
go func() { go func() {
addr, err := net.ResolveTCPAddr("tcp", "[::]:"+strconv.Itoa(int(a.Config.ListenDNSPort))) addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", a.config.DNSProxy.Host.Address, a.config.DNSProxy.Host.Port))
if err != nil { if err != nil {
errChan <- fmt.Errorf("failed to resolve tcp address: %v", err) errChan <- fmt.Errorf("failed to resolve tcp address: %v", err)
return return
} }
err = a.DNSMITM.ListenTCP(newCtx, addr) err = a.dnsMITM.ListenTCP(newCtx, addr)
if err != nil { if err != nil {
errChan <- fmt.Errorf("failed to serve DNS TCP proxy: %v", err) errChan <- fmt.Errorf("failed to serve DNS TCP proxy: %v", err)
return return
} }
}() }()
addrList, err := netlink.AddrList(a.Link, nl.FAMILY_ALL) var addrList []netlink.Addr
if err != nil { for _, linkName := range a.config.Link {
return fmt.Errorf("failed to list address of interface: %w", err) link, err := netlink.LinkByName(linkName)
if err != nil {
return fmt.Errorf("failed to find link %s: %w", linkName, err)
}
linkAddrList, err := netlink.AddrList(link, nl.FAMILY_ALL)
if err != nil {
return fmt.Errorf("failed to list address of interface: %w", err)
}
addrList = append(addrList, linkAddrList...)
} }
a.dnsOverrider4 = a.NetfilterHelper4.PortRemap(fmt.Sprintf("%sDNSOR", a.Config.ChainPrefix), 53, a.Config.ListenDNSPort, addrList) if !a.config.DNSProxy.DisableRemap53 {
err = a.dnsOverrider4.Enable() a.dnsOverrider4 = a.nfHelper4.PortRemap(fmt.Sprintf("%sDNSOR", a.config.Netfilter.IPTables.ChainPrefix), 53, a.config.DNSProxy.Host.Port, addrList)
if err != nil { err = a.dnsOverrider4.Enable()
return fmt.Errorf("failed to override DNS (IPv4): %v", err) if err != nil {
} return fmt.Errorf("failed to override DNS (IPv4): %v", err)
defer func() { _ = a.dnsOverrider4.Disable() }() }
defer func() { _ = a.dnsOverrider4.Disable() }()
a.dnsOverrider6 = a.NetfilterHelper6.PortRemap(fmt.Sprintf("%sDNSOR", a.Config.ChainPrefix), 53, a.Config.ListenDNSPort, addrList) a.dnsOverrider6 = a.nfHelper6.PortRemap(fmt.Sprintf("%sDNSOR", a.config.Netfilter.IPTables.ChainPrefix), 53, a.config.DNSProxy.Host.Port, addrList)
err = a.dnsOverrider6.Enable() err = a.dnsOverrider6.Enable()
if err != nil { if err != nil {
return fmt.Errorf("failed to override DNS (IPv6): %v", err) return fmt.Errorf("failed to override DNS (IPv6): %v", err)
}
defer func() { _ = a.dnsOverrider6.Disable() }()
} }
defer func() { _ = a.dnsOverrider6.Disable() }()
/* /*
Groups Groups
*/ */
for _, group := range a.Groups { for _, group := range a.unprocessedGroups {
err := a.AddGroup(group)
if err != nil {
return err
}
}
for _, group := range a.groups {
err = group.Enable() err = group.Enable()
if err != nil { if err != nil {
return fmt.Errorf("failed to enable group: %w", err) return fmt.Errorf("failed to enable group: %w", err)
} }
} }
defer func() { defer func() {
for _, group := range a.Groups { for _, group := range a.groups {
_ = group.Destroy() _ = group.Destroy()
} }
}() }()
@ -219,7 +302,7 @@ func (a *App) start(ctx context.Context) (err error) {
if err != nil { if err != nil {
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d") log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
} }
for _, group := range a.Groups { for _, group := range a.groups {
err := group.NetfilterDHook(args[2]) err := group.NetfilterDHook(args[2])
if err != nil { if err != nil {
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d") log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
@ -281,8 +364,8 @@ func (a *App) Start(ctx context.Context) (err error) {
return err return err
} }
func (a *App) AddGroup(groupModel *models.Group) error { func (a *App) AddGroup(groupModel models.Group) error {
for _, group := range a.Groups { for _, group := range a.groups {
if groupModel.ID == group.ID { if groupModel.ID == group.ID {
return ErrGroupIDConflict return ErrGroupIDConflict
} }
@ -295,15 +378,18 @@ func (a *App) AddGroup(groupModel *models.Group) error {
dup[rule.ID] = struct{}{} dup[rule.ID] = struct{}{}
} }
grp, err := group.NewGroup(groupModel, a.NetfilterHelper4, a.Config.ChainPrefix, a.Config.IPSetPrefix) grp, err := group.NewGroup(groupModel, a.nfHelper4, a.config.Netfilter.IPTables.ChainPrefix, a.config.Netfilter.IPSet.TablePrefix)
if err != nil { if err != nil {
return fmt.Errorf("failed to create group: %w", err) return fmt.Errorf("failed to create group: %w", err)
} }
a.Groups = append(a.Groups, grp) a.groups = append(a.groups, grp)
log.Debug().Str("id", grp.ID.String()).Str("name", grp.Name).Msg("added group") log.Debug().Str("id", grp.ID.String()).Str("name", grp.Name).Msg("added group")
return grp.Sync(a.Records) if a.isRunning {
return grp.Sync(a.records)
}
return nil
} }
func (a *App) ListInterfaces() ([]net.Interface, error) { func (a *App) ListInterfaces() ([]net.Interface, error) {
@ -341,12 +427,12 @@ func (a *App) processARecord(aRecord dns.A, clientAddr net.Addr, network *string
Str("network", networkStr). Str("network", networkStr).
Msg("processing a record") Msg("processing a record")
ttlDuration := aRecord.Hdr.Ttl + a.Config.AdditionalTTL ttlDuration := aRecord.Hdr.Ttl + a.config.Netfilter.IPSet.AdditionalTTL
a.Records.AddARecord(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1], aRecord.A, ttlDuration) a.records.AddARecord(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1], aRecord.A, ttlDuration)
names := a.Records.GetAliases(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1]) names := a.records.GetAliases(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1])
for _, group := range a.Groups { for _, group := range a.groups {
Rule: Rule:
for _, domain := range group.Rules { for _, domain := range group.Rules {
if !domain.IsEnabled() { if !domain.IsEnabled() {
@ -392,15 +478,15 @@ func (a *App) processCNameRecord(cNameRecord dns.CNAME, clientAddr net.Addr, net
Str("network", networkStr). Str("network", networkStr).
Msg("processing cname record") Msg("processing cname record")
ttlDuration := cNameRecord.Hdr.Ttl + a.Config.AdditionalTTL ttlDuration := cNameRecord.Hdr.Ttl + a.config.Netfilter.IPSet.AdditionalTTL
a.Records.AddCNameRecord(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1], cNameRecord.Target[:len(cNameRecord.Target)-1], ttlDuration) a.records.AddCNameRecord(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1], cNameRecord.Target[:len(cNameRecord.Target)-1], ttlDuration)
// TODO: Optimization // TODO: Optimization
now := time.Now() now := time.Now()
aRecords := a.Records.GetARecords(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1]) aRecords := a.records.GetARecords(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1])
names := a.Records.GetAliases(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1]) names := a.records.GetAliases(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1])
for _, group := range a.Groups { for _, group := range a.groups {
Rule: Rule:
for _, domain := range group.Rules { for _, domain := range group.Rules {
if !domain.IsEnabled() { if !domain.IsEnabled() {
@ -446,124 +532,51 @@ func (a *App) handleMessage(msg dns.Msg, clientAddr net.Addr, network *string) {
} }
} }
func (a *App) ImportConfig(cfg models.ConfigFile) error { func (a *App) ImportConfig(cfg models.Config) error {
a.Config = Config{ if !strings.HasPrefix(cfg.ConfigVersion, "0.1.") {
AdditionalTTL: cfg.AppConfig.AdditionalTTL, return ErrConfigUnsupportedVersion
ChainPrefix: cfg.AppConfig.ChainPrefix,
IPSetPrefix: cfg.AppConfig.IPSetPrefix,
LinkName: cfg.AppConfig.LinkName,
TargetDNSServerAddress: cfg.AppConfig.TargetDNSServerAddress,
TargetDNSServerPort: cfg.AppConfig.TargetDNSServerPort,
ListenDNSPort: cfg.AppConfig.ListenDNSPort,
} }
if cfg.App.DNSProxy.Upstream.Address != "" {
a.config.DNSProxy.Upstream.Address = cfg.App.DNSProxy.Upstream.Address
}
if cfg.App.DNSProxy.Upstream.Port != 0 {
a.config.DNSProxy.Upstream.Port = cfg.App.DNSProxy.Upstream.Port
}
if cfg.App.DNSProxy.Host.Address != "" {
a.config.DNSProxy.Host.Address = cfg.App.DNSProxy.Host.Address
}
if cfg.App.DNSProxy.Host.Port != 0 {
a.config.DNSProxy.Host.Port = cfg.App.DNSProxy.Host.Port
}
a.config.DNSProxy.DisableRemap53 = cfg.App.DNSProxy.DisableRemap53
a.config.DNSProxy.DisableFakePTR = cfg.App.DNSProxy.DisableFakePTR
a.config.DNSProxy.DisableDropAAAA = cfg.App.DNSProxy.DisableDropAAAA
if cfg.App.Netfilter.IPTables.ChainPrefix != "" {
a.config.Netfilter.IPTables.ChainPrefix = cfg.App.Netfilter.IPTables.ChainPrefix
}
if cfg.App.Netfilter.IPSet.TablePrefix != "" {
a.config.Netfilter.IPSet.TablePrefix = cfg.App.Netfilter.IPSet.TablePrefix
}
a.config.Netfilter.IPSet.AdditionalTTL = cfg.App.Netfilter.IPSet.AdditionalTTL
a.unprocessedGroups = cfg.Groups
return nil return nil
} }
func (a *App) ExportConfig() models.ConfigFile { func (a *App) ExportConfig() models.Config {
groups := make([]models.Group, len(a.Groups)) groups := make([]models.Group, len(a.groups))
for idx, group := range a.Groups { for idx, group := range a.groups {
groups[idx] = *group.Group groups[idx] = group.Group
} }
return models.ConfigFile{ return models.Config{
AppConfig: models.AppConfig{ ConfigVersion: "0.1.0",
AdditionalTTL: a.Config.AdditionalTTL, App: a.config,
ChainPrefix: a.Config.ChainPrefix, Groups: groups,
IPSetPrefix: a.Config.IPSetPrefix,
LinkName: a.Config.LinkName,
TargetDNSServerAddress: a.Config.TargetDNSServerAddress,
TargetDNSServerPort: a.Config.TargetDNSServerPort,
ListenDNSPort: a.Config.ListenDNSPort,
},
Groups: groups,
} }
} }
func New(config models.ConfigFile) (*App, error) { func New() *App {
var err error return &App{config: DefaultAppConfig}
app := &App{}
app.Config = Config{
AdditionalTTL: config.AppConfig.AdditionalTTL,
ChainPrefix: config.AppConfig.ChainPrefix,
IPSetPrefix: config.AppConfig.IPSetPrefix,
LinkName: config.AppConfig.LinkName,
TargetDNSServerAddress: config.AppConfig.TargetDNSServerAddress,
TargetDNSServerPort: config.AppConfig.TargetDNSServerPort,
ListenDNSPort: config.AppConfig.ListenDNSPort,
}
app.DNSMITM = dnsMitmProxy.New()
app.DNSMITM.TargetDNSServerAddress = app.Config.TargetDNSServerAddress
app.DNSMITM.TargetDNSServerPort = app.Config.TargetDNSServerPort
app.DNSMITM.RequestHook = func(clientAddr net.Addr, reqMsg dns.Msg, network string) (*dns.Msg, *dns.Msg, error) {
// TODO: Need to understand why it not works in proxy mode
if len(reqMsg.Question) == 1 && reqMsg.Question[0].Qtype == dns.TypePTR {
respMsg := &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: reqMsg.Id,
Response: true,
RecursionAvailable: true,
Rcode: dns.RcodeNameError,
},
Question: reqMsg.Question,
}
return nil, respMsg, nil
}
return nil, nil, nil
}
app.DNSMITM.ResponseHook = func(clientAddr net.Addr, reqMsg dns.Msg, respMsg dns.Msg, network string) (*dns.Msg, error) {
// TODO: Make it optional
var idx int
for _, a := range respMsg.Answer {
if a.Header().Rrtype == dns.TypeAAAA {
continue
}
respMsg.Answer[idx] = a
idx++
}
respMsg.Answer = respMsg.Answer[:idx]
app.handleMessage(respMsg, clientAddr, &network)
return &respMsg, nil
}
app.Records = records.New()
link, err := netlink.LinkByName(app.Config.LinkName)
if err != nil {
return nil, fmt.Errorf("failed to find link %s: %w", app.Config.LinkName, err)
}
app.Link = link
nh4, err := netfilterHelper.New(false)
if err != nil {
return nil, fmt.Errorf("netfilter helper init fail: %w", err)
}
app.NetfilterHelper4 = nh4
err = app.NetfilterHelper4.CleanIPTables(app.Config.ChainPrefix)
if err != nil {
return nil, fmt.Errorf("failed to clear iptables: %w", err)
}
nh6, err := netfilterHelper.New(true)
if err != nil {
return nil, fmt.Errorf("netfilter helper init fail: %w", err)
}
app.NetfilterHelper6 = nh6
err = app.NetfilterHelper6.CleanIPTables(app.Config.ChainPrefix)
if err != nil {
return nil, fmt.Errorf("failed to clear iptables: %w", err)
}
for _, group := range config.Groups {
err = app.AddGroup(&group)
if err != nil {
return nil, err
}
}
return app, nil
} }

View File

@ -1,17 +1,41 @@
package models package models
type ConfigFile struct { type Config struct {
AppConfig AppConfig `yaml:"appConfig"` ConfigVersion string `yaml:"configVersion"`
Groups []Group `yaml:"groups"` App App `yaml:"app"`
Groups []Group `yaml:"groups"`
} }
type AppConfig struct { type App struct {
LogLevel string `yaml:"logLevel"` DNSProxy DNSProxy `yaml:"dnsProxy"`
AdditionalTTL uint32 `yaml:"additionalTTL"` Netfilter Netfilter `yaml:"netfilter"`
ChainPrefix string `yaml:"chainPrefix"` Link []string `yaml:"link"`
IPSetPrefix string `yaml:"ipsetPrefix"` LogLevel string `yaml:"logLevel"`
LinkName string `yaml:"linkName"` }
TargetDNSServerAddress string `yaml:"targetDNSServerAddress"`
TargetDNSServerPort uint16 `yaml:"targetDNSServerPort"` type DNSProxy struct {
ListenDNSPort uint16 `yaml:"listenDNSPort"` Host DNSProxyServer `yaml:"host"`
Upstream DNSProxyServer `yaml:"upstream"`
DisableRemap53 bool `yaml:"disableRemap53"`
DisableFakePTR bool `yaml:"disableDropPTR"`
DisableDropAAAA bool `yaml:"disableDropAAAA"`
}
type DNSProxyServer struct {
Address string `yaml:"address"`
Port uint16 `yaml:"port"`
}
type Netfilter struct {
IPTables IPTables `yaml:"iptables"`
IPSet IPSet `yaml:"ipset"`
}
type IPTables struct {
ChainPrefix string `yaml:"chainPrefix"`
}
type IPSet struct {
TablePrefix string `yaml:"tablePrefix"`
AdditionalTTL uint32 `yaml:"additionalTTL"`
} }

View File

@ -6,6 +6,7 @@ import (
"strconv" "strconv"
"github.com/coreos/go-iptables/iptables" "github.com/coreos/go-iptables/iptables"
"github.com/rs/zerolog/log"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl" "github.com/vishvananda/netlink/nl"
) )
@ -133,7 +134,7 @@ func (r *IPSetToLink) insertIPRoute() error {
if err != nil { if err != nil {
// TODO: Нормально отлавливать ошибку // TODO: Нормально отлавливать ошибку
if err.Error() == "Link not found" { if err.Error() == "Link not found" {
// TODO: Логи log.Debug().Str("iface", r.IfaceName).Msg("interface not found (waiting for it to exist)")
return nil return nil
} }
return fmt.Errorf("error while getting interface: %w", err) return fmt.Errorf("error while getting interface: %w", err)

View File

@ -1,11 +1,24 @@
appConfig: configVersion: 0.1.0
additionalTTL: 216000 app:
chainPrefix: KVAS2_ dnsProxy:
ipsetPrefix: kvas2_ host:
linkName: br0 address: '[::]'
targetDNSServerAddress: 127.0.0.1 port: 3553
targetDNSServerPort: 53 upstream:
listenDNSPort: 3553 address: 127.0.0.1
port: 53
disableRemap53: false
disableDropPTR: false
disableDropAAAA: false
netfilter:
iptables:
chainPrefix: KVAS2_
ipset:
tablePrefix: kvas2_
additionalTTL: 3600
link:
- br0
logLevel: info
groups: groups:
- id: d663876a - id: d663876a
name: Example name: Example