prepairing for production
Some checks failed
Build and Package OPKG / Build aarch64 (push) Failing after 28s
Build and Package OPKG / Build armv5 (push) Failing after 16s
Build and Package OPKG / Build armv7 (push) Failing after 20s
Build and Package OPKG / Build mips (push) Failing after 18s
Build and Package OPKG / Build mipsel (push) Failing after 15s

This commit is contained in:
Vladimir Avtsenov 2025-02-13 21:08:11 +03:00
parent 1419285de3
commit 8369de6845
14 changed files with 272 additions and 146 deletions

View File

@ -7,7 +7,7 @@ on:
jobs: jobs:
build: build:
run-name: Build ${{ matrix.arch }} name: Build ${{ matrix.arch }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@ -46,33 +46,12 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y fakeroot sudo apt-get install -y fakeroot
- name: Build binary - name: Build and Package
run: | run: |
go mod tidy ARCH=${{ matrix.arch }} GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} GOMIPS=${{ matrix.gomips }} GOARM=${{ matrix.goarm }} make
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} GOMIPS=${{ matrix.gomips }} GOARM=${{ matrix.goarm }} go build -o kvas2-${{ matrix.arch }} -v -a -trimpath -ldflags="-w -s" .
- name: Package as OPKG
run: |
mkdir -p build/${{ matrix.arch }}/control
echo 'Package: kvas2' > build/${{ matrix.arch }}/control/control
echo 'Version: 0.0.1' >> build/${{ matrix.arch }}/control/control
echo 'Architecture: ${{ matrix.arch }}' >> build/${{ matrix.arch }}/control/control
echo 'Maintainer: Vladimir Avtsenov <vladimir.lsk.cool@gmail.com>' >> build/${{ matrix.arch }}/control/control
echo 'Description: kvas2' >> build/${{ matrix.arch }}/control/control
echo 'Section: base' >> build/${{ matrix.arch }}/control/control
echo 'Priority: optional' >> build/${{ matrix.arch }}/control/control
echo 'Depends: libc, iptables, socat' >> build/${{ matrix.arch }}/control/control
mkdir -p build/${{ matrix.arch }}/data/opt/usr/bin
cp kvas2-${{ matrix.arch }} build/${{ matrix.arch }}/data/opt/usr/bin/kvas2
cp -r opt build/${{ matrix.arch }}/data/
chmod +x build/${{ matrix.arch }}/data/opt/usr/bin/kvas2
fakeroot sh -c "tar -C build/${{ matrix.arch }}/control -cvf build/${{ matrix.arch }}/control.tar ."
fakeroot sh -c "tar -C build/${{ matrix.arch }}/data -cvf build/${{ matrix.arch }}/data.tar ."
echo '2.0' > build/${{ matrix.arch }}/debian-binary
ar r build/kvas2_${{ matrix.arch }}.ipk build/${{ matrix.arch }}/debian-binary build/${{ matrix.arch }}/control.tar build/${{ matrix.arch }}/data.tar
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: kvas2_${{ matrix.arch }}.ipk name: kvas2_${{ matrix.arch }}.ipk
path: build/kvas2_${{ matrix.arch }}.ipk path: .build/kvas2_${{ matrix.arch }}.ipk

3
.gitignore vendored
View File

@ -1,2 +1 @@
kvas2-go /.build
config.yaml

40
Makefile Normal file
View File

@ -0,0 +1,40 @@
APP_NAME = kvas2
APP_DESCRIPTION = DNS-based routing application
APP_MAINTAINER = Vladimir Avtsenov <vladimir.lsk.cool@gmail.com>
TAG = $(shell git describe --tags --abbrev=0)
COMMIT = $(shell git rev-parse --short HEAD)
COMMITS_SINCE_TAG = $(shell git rev-list ${TAG}..HEAD --count || echo "0")
VERSION ?= $(TAG)
ARCH ?= mipsel
GOOS ?= linux
GOARCH ?= mipsle
GOMIPS ?= softfloat
GOARM ?=
PKG_DIR = ./.build/$(ARCH)
BIN_DIR = $(PKG_DIR)/data/opt/usr/bin
PARAMS = -v -a -trimpath -ldflags="-X 'kvas2/constant.Version=$(VERSION)' -X 'kvas2/constant.Commit=$(COMMIT)' -w -s"
all: build_daemon package
build_daemon:
GOOS=$(GOOS) GOARCH=$(GOARCH) GOMIPS=$(GOMIPS) GOARM=$(GOARM) go build $(PARAMS) -o $(BIN_DIR)/kvas2d ./cmd/kvas2d
package:
@mkdir -p $(PKG_DIR)/control
@echo '2.0' > $(PKG_DIR)/debian-binary
@echo 'Package: $(APP_NAME)' > $(PKG_DIR)/control/control
@echo 'Version: $(VERSION)-$(COMMITS_SINCE_TAG)' >> $(PKG_DIR)/control/control
@echo 'Architecture: $(ARCH)' >> $(PKG_DIR)/control/control
@echo 'Maintainer: $(APP_MAINTAINER)' >> $(PKG_DIR)/control/control
@echo 'Description: $(APP_DESCRIPTION)' >> $(PKG_DIR)/control/control
@echo 'Section: base' >> $(PKG_DIR)/control/control
@echo 'Priority: optional' >> $(PKG_DIR)/control/control
@echo 'Depends: libc, iptables, socat' >> $(PKG_DIR)/control/control
@mkdir -p $(PKG_DIR)/data/opt/usr/bin
@cp -r ./opt/* $(PKG_DIR)/data/
@fakeroot sh -c "tar -C $(PKG_DIR)/control -cvf $(PKG_DIR)/control.tar ."
@fakeroot sh -c "tar -C $(PKG_DIR)/data -cvf $(PKG_DIR)/data.tar ."
@ar r $(PKG_DIR)/$(APP_NAME)_$(ARCH).ipk $(PKG_DIR)/debian-binary $(PKG_DIR)/control.tar $(PKG_DIR)/data.tar

View File

@ -1,25 +1,3 @@
# kvas2-go # kvas2
Better implementation of [KVAS](https://github.com/qzeleza/kvas) Better implementation of [KVAS](https://github.com/qzeleza/kvas)
Realized features:
- [x] DNS Proxy (UDP)
- [x] DNS Proxy (TCP)
- [x] Records memory
- [x] IPTables rules for rebind DNS server port
- [X] IPSet integration
- [X] IP integration
- [X] IPTables rules to IPSet
- [X] Catch interface up/down
- [X] Catch `netfilter.d` event
- [X] Rule composer (CRUD)
- [ ] GORM integration
- [X] Listing of interfaces
- [ ] HTTP API
- [ ] HTTP GUI
- [ ] CLI
- [X] (Keenetic) Support for custom interfaces
- [ ] It is not a concept now... REFACTORING TIME!!!
- [ ] (Keenetic) Getting readable names of interfaces from Keenetic NDMS
- [ ] HTTP Auth
- [ ] IPv6 support

View File

@ -1 +0,0 @@
GOOS=linux GOMIPS=softfloat GOARCH=mipsle go build -v -a -trimpath -ldflags="-w -s" .

173
cmd/kvas2d/main.go Normal file
View File

@ -0,0 +1,173 @@
package main
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"kvas2"
"kvas2/constant"
"kvas2/models"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
)
const cfgFolderLocation = "/opt/var/lib/kvas2"
const cfgFileLocation = cfgFolderLocation + "/config.yaml"
const pidFileLocation = "/opt/var/run/kvas2.pid"
func checkPIDFile() error {
data, err := os.ReadFile(pidFileLocation)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
pid, err := strconv.Atoi(string(data))
if err != nil {
return errors.New("invalid PID file content")
}
if err := syscall.Kill(pid, 0); err == nil {
return fmt.Errorf("process %d is already running", pid)
}
_ = os.Remove(pidFileLocation)
return nil
}
func createPIDFile() error {
pid := os.Getpid()
return os.WriteFile(pidFileLocation, []byte(strconv.Itoa(pid)), 0644)
}
func removePIDFile() {
_ = os.Remove(pidFileLocation)
}
func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
log.Info().
Str("version", constant.Version).
Str("commit", constant.Commit).
Msg("starting kvas2 daemon")
if err := checkPIDFile(); err != nil {
log.Fatal().Err(err).Msg("failed to start kvas2 daemon")
}
if err := createPIDFile(); err != nil {
log.Fatal().Err(err).Msg("failed to create PID file")
}
defer removePIDFile()
cfg := models.ConfigFile{}
cfgFile, err := os.ReadFile(cfgFileLocation)
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) {
log.Fatal().Err(err).Msg("failed to read config.yaml")
}
cfg = models.ConfigFile{
AppConfig: models.AppConfig{
LogLevel: "info",
AdditionalTTL: 216000,
ChainPrefix: "KVAS2_",
IPSetPrefix: "kvas2_",
LinkName: "br0",
TargetDNSServerAddress: "127.0.0.1",
TargetDNSServerPort: 53,
ListenDNSPort: 3553,
},
}
out, err := yaml.Marshal(cfg)
if err != nil {
log.Fatal().Err(err).Msg("failed to serialize config.yaml")
}
err = os.MkdirAll(cfgFolderLocation, os.ModePerm)
if err != nil {
log.Fatal().Err(err).Msg("failed to create config directory")
}
err = os.WriteFile(cfgFileLocation, out, 0600)
if err != nil {
log.Fatal().Err(err).Msg("failed to save config.yaml")
}
}
switch cfg.AppConfig.LogLevel {
case "trace":
zerolog.SetGlobalLevel(zerolog.TraceLevel)
case "debug":
zerolog.SetGlobalLevel(zerolog.DebugLevel)
case "info":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "warn":
zerolog.SetGlobalLevel(zerolog.WarnLevel)
case "error":
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
case "fatal":
zerolog.SetGlobalLevel(zerolog.FatalLevel)
case "panic":
zerolog.SetGlobalLevel(zerolog.PanicLevel)
case "nolevel":
zerolog.SetGlobalLevel(zerolog.NoLevel)
case "disabled":
zerolog.SetGlobalLevel(zerolog.Disabled)
default:
zerolog.SetGlobalLevel(zerolog.InfoLevel)
}
app, err := kvas2.New(cfg)
if err != nil {
log.Fatal().Err(err).Msg("failed to initialize application")
}
ctx, cancel := context.WithCancel(context.Background())
log.Info().Msg("starting service")
/*
Starting app with graceful shutdown
*/
appResult := make(chan error)
go func() {
appResult <- app.Start(ctx)
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
var once sync.Once
closeEvent := func() {
log.Info().Msg("shutting down service")
cancel()
}
for {
select {
case err, _ := <-appResult:
if err != nil {
log.Error().Err(err).Msg("failed to start application")
}
log.Info().Msg("exiting application")
return
case <-c:
once.Do(closeEvent)
}
}
}

6
constant/version.go Normal file
View File

@ -0,0 +1,6 @@
package constant
var (
Version = "unattached"
Commit = "undef"
)

2
go.mod
View File

@ -1,4 +1,4 @@
module kvas2-go module kvas2
go 1.21 go 1.21

View File

@ -5,9 +5,9 @@ import (
"net" "net"
"time" "time"
"kvas2-go/models" "kvas2/models"
"kvas2-go/netfilter-helper" "kvas2/netfilter-helper"
"kvas2-go/records" "kvas2/records"
"github.com/coreos/go-iptables/iptables" "github.com/coreos/go-iptables/iptables"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"

View File

@ -1,4 +1,4 @@
package main package kvas2
import ( import (
"context" "context"
@ -12,11 +12,11 @@ import (
"strings" "strings"
"time" "time"
"kvas2-go/dns-mitm-proxy" "kvas2/dns-mitm-proxy"
"kvas2-go/group" "kvas2/group"
"kvas2-go/models" "kvas2/models"
"kvas2-go/netfilter-helper" "kvas2/netfilter-helper"
"kvas2-go/records" "kvas2/records"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -63,14 +63,13 @@ type App struct {
} }
func (a *App) handleLink(event netlink.LinkUpdate) { func (a *App) handleLink(event netlink.LinkUpdate) {
log.Trace().
Str("interface", event.Link.Attrs().Name).
Str("operstatestr", event.Attrs().OperState.String()).
Int("operstate", int(event.Attrs().OperState)).
Int("change", int(event.Change)).
Msg("interface event")
switch event.Change { switch event.Change {
case 0x00000001: case 0x00000001:
log.Trace().
Str("interface", event.Link.Attrs().Name).
Int("change", int(event.Change)).
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 {
@ -172,7 +171,7 @@ func (a *App) start(ctx context.Context) (err error) {
/* /*
Socket (for netfilter.d events) Socket (for netfilter.d events)
*/ */
socketPath := "/opt/var/run/kvas2-go.sock" socketPath := "/opt/var/run/kvas2.sock"
err = os.Remove(socketPath) err = os.Remove(socketPath)
if err != nil && !errors.Is(err, os.ErrNotExist) { if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to remove existed UNIX socket: %w", err) return fmt.Errorf("failed to remove existed UNIX socket: %w", err)
@ -302,7 +301,7 @@ func (a *App) AddGroup(groupModel *models.Group) error {
} }
a.Groups = append(a.Groups, grp) a.Groups = append(a.Groups, grp)
log.Trace().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) return grp.Sync(a.Records)
} }
@ -326,11 +325,20 @@ func (a *App) ListInterfaces() ([]net.Interface, error) {
return interfaceNames, nil return interfaceNames, nil
} }
func (a *App) processARecord(aRecord dns.A) { func (a *App) processARecord(aRecord dns.A, clientAddr net.Addr, network *string) {
var clientAddrStr, networkStr string
if clientAddr != nil {
clientAddrStr = clientAddr.String()
}
if network != nil {
networkStr = *network
}
log.Trace(). log.Trace().
Str("name", aRecord.Hdr.Name). Str("name", aRecord.Hdr.Name).
Str("address", aRecord.A.String()). Str("address", aRecord.A.String()).
Int("ttl", int(aRecord.Hdr.Ttl)). Int("ttl", int(aRecord.Hdr.Ttl)).
Str("clientAddr", clientAddrStr).
Str("network", networkStr).
Msg("processing a record") Msg("processing a record")
ttlDuration := aRecord.Hdr.Ttl + a.Config.AdditionalTTL ttlDuration := aRecord.Hdr.Ttl + a.Config.AdditionalTTL
@ -356,11 +364,10 @@ func (a *App) processARecord(aRecord dns.A) {
Err(err). Err(err).
Msg("failed to add address") Msg("failed to add address")
} else { } else {
log.Trace(). log.Debug().
Str("address", aRecord.A.String()). Str("address", aRecord.A.String()).
Str("aRecordDomain", aRecord.Hdr.Name). Str("aRecordDomain", aRecord.Hdr.Name).
Str("cNameDomain", name). Str("cNameDomain", name).
Err(err).
Msg("add address") Msg("add address")
} }
break Rule break Rule
@ -369,11 +376,20 @@ func (a *App) processARecord(aRecord dns.A) {
} }
} }
func (a *App) processCNameRecord(cNameRecord dns.CNAME) { func (a *App) processCNameRecord(cNameRecord dns.CNAME, clientAddr net.Addr, network *string) {
var clientAddrStr, networkStr string
if clientAddr != nil {
clientAddrStr = clientAddr.String()
}
if network != nil {
networkStr = *network
}
log.Trace(). log.Trace().
Str("name", cNameRecord.Hdr.Name). Str("name", cNameRecord.Hdr.Name).
Str("cname", cNameRecord.Target). Str("cname", cNameRecord.Target).
Int("ttl", int(cNameRecord.Hdr.Ttl)). Int("ttl", int(cNameRecord.Hdr.Ttl)).
Str("clientAddr", clientAddrStr).
Str("network", networkStr).
Msg("processing cname record") Msg("processing cname record")
ttlDuration := cNameRecord.Hdr.Ttl + a.Config.AdditionalTTL ttlDuration := cNameRecord.Hdr.Ttl + a.Config.AdditionalTTL
@ -402,10 +418,9 @@ func (a *App) processCNameRecord(cNameRecord dns.CNAME) {
Err(err). Err(err).
Msg("failed to add address") Msg("failed to add address")
} else { } else {
log.Trace(). log.Debug().
Str("address", aRecord.Address.String()). Str("address", aRecord.Address.String()).
Str("cNameDomain", name). Str("cNameDomain", name).
Err(err).
Msg("add address") Msg("add address")
} }
} }
@ -415,19 +430,19 @@ func (a *App) processCNameRecord(cNameRecord dns.CNAME) {
} }
} }
func (a *App) handleRecord(rr dns.RR) { func (a *App) handleRecord(rr dns.RR, clientAddr net.Addr, network *string) {
switch v := rr.(type) { switch v := rr.(type) {
case *dns.A: case *dns.A:
a.processARecord(*v) a.processARecord(*v, clientAddr, network)
case *dns.CNAME: case *dns.CNAME:
a.processCNameRecord(*v) a.processCNameRecord(*v, clientAddr, network)
default: default:
} }
} }
func (a *App) handleMessage(msg dns.Msg) { func (a *App) handleMessage(msg dns.Msg, clientAddr net.Addr, network *string) {
for _, rr := range msg.Answer { for _, rr := range msg.Answer {
a.handleRecord(rr) a.handleRecord(rr, clientAddr, network)
} }
} }
@ -510,7 +525,7 @@ func New(config models.ConfigFile) (*App, error) {
} }
respMsg.Answer = respMsg.Answer[:idx] respMsg.Answer = respMsg.Answer[:idx]
app.handleMessage(respMsg) app.handleMessage(respMsg, clientAddr, &network)
return &respMsg, nil return &respMsg, nil
} }

64
main.go
View File

@ -1,64 +0,0 @@
package main
import (
"context"
"os"
"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})
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")
}
ctx, cancel := context.WithCancel(context.Background())
log.Info().Msg("starting service")
/*
Starting app with graceful shutdown
*/
appResult := make(chan error)
go func() {
appResult <- app.Start(ctx)
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
for {
select {
case err, _ := <-appResult:
if err != nil {
log.Error().Err(err).Msg("failed to start application")
}
log.Info().Msg("exiting application")
return
case <-c:
log.Info().Msg("shutting down service")
cancel()
}
}
}

View File

@ -6,6 +6,7 @@ type ConfigFile struct {
} }
type AppConfig struct { type AppConfig struct {
LogLevel string `yaml:"logLevel"`
AdditionalTTL uint32 `yaml:"additionalTTL"` AdditionalTTL uint32 `yaml:"additionalTTL"`
ChainPrefix string `yaml:"chainPrefix"` ChainPrefix string `yaml:"chainPrefix"`
IPSetPrefix string `yaml:"ipsetPrefix"` IPSetPrefix string `yaml:"ipsetPrefix"`

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
SOCKET_PATH="/opt/var/run/kvas2-go.sock" SOCKET_PATH="/opt/var/run/kvas2.sock"
if [ ! -S "$SOCKET_PATH" ]; then if [ ! -S "$SOCKET_PATH" ]; then
exit exit
fi fi