config support

This commit is contained in:
Vladimir Avtsenov 2025-02-12 00:34:14 +03:00
parent 60e1f4c540
commit 4a0c0938e6
9 changed files with 174 additions and 33 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
kvas2-go
config.yaml

34
config.yaml.example Normal file
View File

@ -0,0 +1,34 @@
appConfig:
additionalTTL: 216000
chainPrefix: KVAS2_
ipsetPrefix: kvas2_
linkName: br0
targetDNSServerAddress: 127.0.0.1
targetDNSServerPort: 53
listenDNSPort: 3553
groups:
- id: d663876a
name: Example
interface: nwg0
fixProtect: false
rules:
- id: 6f34ee91
name: Wildcard Example
type: wildcard
rule: '*wildcard.example.com'
enable: true
- id: 00ae5f7c
name: RegEx Example
type: regex
rule: '^.*.regex.example.com$'
enable: true
- id: 6120dc8a
name: Domain Example
type: domain
rule: 'domain.example.com'
enable: true
- id: b9751782
name: Namespace Example
type: namespace
rule: 'namespace.example.com'
enable: true

2
go.mod
View File

@ -5,10 +5,10 @@ go 1.21
require (
github.com/IGLOU-EU/go-wildcard/v2 v2.0.2
github.com/coreos/go-iptables v0.7.0
github.com/google/uuid v1.6.0
github.com/miekg/dns v1.1.63
github.com/rs/zerolog v1.33.0
github.com/vishvananda/netlink v1.3.0
gopkg.in/yaml.v3 v3.0.1
)
require (

View File

@ -3,7 +3,6 @@ package main
import (
"context"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math/rand"
@ -28,6 +27,7 @@ import (
var (
ErrAlreadyRunning = errors.New("already running")
ErrGroupIDConflict = errors.New("group id conflict")
ErrRuleIDConflict = errors.New("rule id conflict")
)
func randomId() [4]byte {
@ -39,7 +39,7 @@ func randomId() [4]byte {
type Config struct {
AdditionalTTL uint32
ChainPrefix string
IpSetPrefix string
IPSetPrefix string
LinkName string
TargetDNSServerAddress string
TargetDNSServerPort uint16
@ -53,7 +53,7 @@ type App struct {
NetfilterHelper4 *netfilterHelper.NetfilterHelper
NetfilterHelper6 *netfilterHelper.NetfilterHelper
Records *records.Records
Groups map[[4]byte]*group.Group
Groups []*group.Group
Link netlink.Link
@ -80,7 +80,7 @@ func (a *App) handleLink(event netlink.LinkUpdate) {
err := group.LinkUpdateHook(event)
if err != nil {
log.Error().Str("group", hex.EncodeToString(group.ID[:])).Err(err).Msg("error while handling interface up")
log.Error().Str("group", group.ID.String()).Err(err).Msg("error while handling interface up")
}
}
}
@ -287,15 +287,27 @@ func (a *App) Start(ctx context.Context) (err error) {
}
func (a *App) AddGroup(groupModel *models.Group) error {
if _, exists := a.Groups[groupModel.ID]; exists {
return ErrGroupIDConflict
for _, group := range a.Groups {
if groupModel.ID == group.ID {
return ErrGroupIDConflict
}
}
dup := make(map[[4]byte]struct{})
for _, rule := range groupModel.Rules {
if _, exists := dup[rule.ID]; exists {
return ErrRuleIDConflict
}
dup[rule.ID] = struct{}{}
}
grp, err := group.NewGroup(groupModel, a.NetfilterHelper4, a.Config.ChainPrefix, a.Config.IpSetPrefix)
grp, err := group.NewGroup(groupModel, a.NetfilterHelper4, a.Config.ChainPrefix, a.Config.IPSetPrefix)
if err != nil {
return fmt.Errorf("failed to create group: %w", err)
}
a.Groups[grp.ID] = grp
a.Groups = append(a.Groups, grp)
log.Trace().Str("id", grp.ID.String()).Str("name", grp.Name).Msg("added group")
return grp.Sync(a.Records)
}
@ -423,12 +435,52 @@ func (a *App) handleMessage(msg dns.Msg) {
}
}
func New(config Config) (*App, error) {
func (a *App) ImportConfig(cfg models.ConfigFile) error {
a.Config = Config{
AdditionalTTL: cfg.AppConfig.AdditionalTTL,
ChainPrefix: cfg.AppConfig.ChainPrefix,
IPSetPrefix: cfg.AppConfig.IPSetPrefix,
LinkName: cfg.AppConfig.LinkName,
TargetDNSServerAddress: cfg.AppConfig.TargetDNSServerAddress,
TargetDNSServerPort: cfg.AppConfig.TargetDNSServerPort,
ListenDNSPort: cfg.AppConfig.ListenDNSPort,
}
return nil
}
func (a *App) ExportConfig() models.ConfigFile {
groups := make([]models.Group, len(a.Groups))
for idx, group := range a.Groups {
groups[idx] = *group.Group
}
return models.ConfigFile{
AppConfig: models.AppConfig{
AdditionalTTL: a.Config.AdditionalTTL,
ChainPrefix: a.Config.ChainPrefix,
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) {
var err error
app := &App{}
app.Config = config
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
@ -468,7 +520,6 @@ func New(config Config) (*App, error) {
}
app.Records = records.New()
app.Groups = make(map[[4]byte]*group.Group)
link, err := netlink.LinkByName(app.Config.LinkName)
if err != nil {
@ -496,7 +547,12 @@ func New(config Config) (*App, error) {
return nil, fmt.Errorf("failed to clear iptables: %w", err)
}
app.Groups = make(map[[4]byte]*group.Group)
for _, group := range config.Groups {
err = app.AddGroup(&group)
if err != nil {
return nil, err
}
}
return app, nil
}

25
main.go
View File

@ -6,22 +6,29 @@ import (
"os/signal"
"syscall"
"kvas2-go/models"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
)
func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
app, err := New(Config{
AdditionalTTL: 216000, // 1 hour
ChainPrefix: "KVAS2_",
IpSetPrefix: "kvas2_",
LinkName: "br0",
TargetDNSServerAddress: "127.0.0.1",
TargetDNSServerPort: 53,
ListenDNSPort: 3553,
})
cfgFile, err := os.ReadFile("config.yaml")
if err != nil {
log.Fatal().Err(err).Msg("failed to read config.yaml")
}
cfg := models.ConfigFile{}
err = yaml.Unmarshal(cfgFile, &cfg)
if err != nil {
log.Fatal().Err(err).Msg("failed to parse config.yaml")
}
app, err := New(cfg)
if err != nil {
log.Fatal().Err(err).Msg("failed to initialize application")
}

16
models/config.go Normal file
View File

@ -0,0 +1,16 @@
package models
type ConfigFile struct {
AppConfig AppConfig `yaml:"appConfig"`
Groups []Group `yaml:"groups"`
}
type AppConfig struct {
AdditionalTTL uint32 `yaml:"additionalTTL"`
ChainPrefix string `yaml:"chainPrefix"`
IPSetPrefix string `yaml:"ipsetPrefix"`
LinkName string `yaml:"linkName"`
TargetDNSServerAddress string `yaml:"targetDNSServerAddress"`
TargetDNSServerPort uint16 `yaml:"targetDNSServerPort"`
ListenDNSPort uint16 `yaml:"listenDNSPort"`
}

View File

@ -1,9 +1,9 @@
package models
type Group struct {
ID [4]byte
Name string
Interface string
Rules []*Rule
FixProtect bool
ID ID `yaml:"id"`
Name string `yaml:"name"`
Interface string `yaml:"interface"`
FixProtect bool `yaml:"fixProtect"`
Rules []*Rule `yaml:"rules"`
}

20
models/id.go Normal file
View File

@ -0,0 +1,20 @@
package models
import (
"encoding/hex"
)
type ID [4]byte
func (id *ID) String() string {
return hex.EncodeToString(id[:])
}
func (id *ID) MarshalText() ([]byte, error) {
return []byte(id.String()), nil
}
func (id *ID) UnmarshalText(data []byte) error {
_, err := hex.Decode(id[:], data)
return err
}

View File

@ -2,16 +2,17 @@ package models
import (
"regexp"
"strings"
"github.com/IGLOU-EU/go-wildcard/v2"
)
type Rule struct {
ID [4]byte
Name string
Type string
Rule string
Enable bool
ID ID `yaml:"id"`
Name string `yaml:"name"`
Type string `yaml:"type"`
Rule string `yaml:"rule"`
Enable bool `yaml:"enable"`
}
func (d *Rule) IsEnabled() bool {
@ -25,8 +26,13 @@ func (d *Rule) IsMatch(domainName string) bool {
case "regex":
ok, _ := regexp.MatchString(d.Rule, domainName)
return ok
case "plaintext":
case "domain":
return domainName == d.Rule
case "namespace":
if domainName == d.Rule {
return true
}
return strings.HasSuffix(domainName, "."+d.Rule)
}
return false
}