Compare commits

..

No commits in common. "main" and "0.1.0" have entirely different histories.
main ... 0.1.0

19 changed files with 95 additions and 381 deletions

View File

@ -1,90 +0,0 @@
name: Build and Package OPKG
on:
push:
branches:
- develop
- main
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/build-for-release.yml'
release:
types: [published]
jobs:
build:
name: Build for "${{ matrix.target }}"
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: mipsel-3.4
goos: linux
goarch: mipsle
gomips: softfloat
- target: mips-3.4
goos: linux
goarch: mips
gomips: softfloat
- target: aarch64-3.10
goos: linux
goarch: arm64
- target: armv7-3.2
goos: linux
goarch: arm
goarm: 7
- target: armv7-2.6
goos: linux
goarch: arm
goarm: 7
- target: armv5-3.2
goos: linux
goarch: arm
goarm: 5
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.21'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y fakeroot
- name: Build and Package
run: |
TARGET=${{ matrix.target }} GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} GOMIPS=${{ matrix.gomips }} GOARM=${{ matrix.goarm }} make
- name: Fetching file list for artifact
if: ${{ github.event_name != 'release' }}
id: create_file_list
run: |
echo 'file_list<<EOF' >> $GITHUB_OUTPUT
find . -name "magitrickle_*_${{ matrix.target }}.ipk" -print >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
- name: Upload artifact
if: ${{ github.event_name != 'release' }}
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.target }}
path: ${{ steps.create_file_list.outputs.file_list }}
if-no-files-found: error
- name: Upload asset to release
if: ${{ github.event_name == 'release' }}
uses: https://gitea.com/actions/release-action@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |-
./.build/magitrickle_*_${{ matrix.target }}.ipk
api_key: '${{secrets.RELEASE_TOKEN}}'

View File

@ -1,18 +1,13 @@
name: Checking name: Build and Package OPKG
on: on:
pull_request: push:
branches: branches:
- develop
- main - main
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/check.yml'
jobs: jobs:
check: build:
name: Finding suspicious constructs for ${{ matrix.arch }} name: Build for ${{ matrix.arch }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@ -48,10 +43,24 @@ jobs:
fetch-tags: true fetch-tags: true
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: '1.21' go-version: '1.21'
cache: false
- name: Finding suspicious constructs - name: Install dependencies
run: | run: |
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} GOMIPS=${{ matrix.gomips }} GOARM=${{ matrix.goarm }} go vet ./... sudo apt-get update
sudo apt-get install -y fakeroot
- name: Build and Package
run: |
ARCH=${{ matrix.arch }} GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} GOMIPS=${{ matrix.gomips }} GOARM=${{ matrix.goarm }} make
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: kvas2_${{ matrix.arch }}.ipk.zip
path: .build/kvas2_${{ matrix.arch }}.ipk
if-no-files-found: error
compression-level: 0

View File

@ -1,5 +1,5 @@
# Коллабораторы # Contributors
## Консультация ## Consultants
- **nesteroff561** ([GitHub](https://github.com/nesteroff561)) - Помощь с `iptables` - **nesteroff561** - [GitHub](https://github.com/nesteroff561) - "Help with understanding `iptables`"

View File

@ -1,55 +1,41 @@
APP_NAME = magitrickle APP_NAME = kvas2
APP_DESCRIPTION = DNS-based routing application APP_DESCRIPTION = DNS-based routing application
APP_MAINTAINER = Vladimir Avtsenov <vladimir.lsk.cool@gmail.com> APP_MAINTAINER = Vladimir Avtsenov <vladimir.lsk.cool@gmail.com>
TAG = $(shell git describe --tags --abbrev=0 2> /dev/null || git rev-parse --short HEAD)
COMMIT = $(shell git rev-parse --short HEAD) COMMIT = $(shell git rev-parse --short HEAD)
UPSTREAM_VERSION = $(shell git describe --tags --abbrev=0 2> /dev/null || echo "0.0.0") COMMITS_SINCE_TAG = $(shell git rev-list ${TAG}..HEAD --count || echo "0")
PKG_REVISION ?= 1 VERSION ?= $(TAG)
TAG = $(shell git describe --tags --abbrev=0 2> /dev/null) ARCH ?= mipsel
COMMITS_SINCE_TAG = $(shell git rev-list ${TAG}..HEAD --count 2>/dev/null)
PRERELEASE_POSTFIX =
PRERELEASE_DATE = $(shell date +%Y%m%d)
ifneq ($(TAG),)
ifneq ($(COMMITS_SINCE_TAG), 0)
PRERELEASE_POSTFIX = ~git$(PRERELEASE_DATE).$(COMMIT)
endif
else
PRERELEASE_POSTFIX = ~git$(PRERELEASE_DATE).$(COMMIT)
endif
TARGET ?= mipsel-3.4
GOOS ?= linux GOOS ?= linux
GOARCH ?= mipsle GOARCH ?= mipsle
GOMIPS ?= softfloat GOMIPS ?= softfloat
GOARM ?= GOARM ?=
BUILD_DIR = ./.build BUILD_DIR = ./.build
PKG_DIR = $(BUILD_DIR)/$(TARGET) PKG_DIR = $(BUILD_DIR)/$(ARCH)
BIN_DIR = $(PKG_DIR)/data/opt/bin BIN_DIR = $(PKG_DIR)/data/opt/bin
PARAMS = -v -a -trimpath -ldflags="-X 'magitrickle/constant.Version=$(UPSTREAM_VERSION)$(PRERELEASE_POSTFIX)' -X 'magitrickle/constant.Commit=$(COMMIT)' -w -s" PARAMS = -v -a -trimpath -ldflags="-X 'kvas2/constant.Version=$(VERSION)' -X 'kvas2/constant.Commit=$(COMMIT)' -w -s"
all: clear build_daemon package all: build_daemon package
clear:
echo $(shell git rev-parse --abbrev-ref HEAD)
rm -rf $(PKG_DIR)
build_daemon: build_daemon:
GOOS=$(GOOS) GOARCH=$(GOARCH) GOMIPS=$(GOMIPS) GOARM=$(GOARM) go build $(PARAMS) -o $(BIN_DIR)/magitrickled ./cmd/magitrickled GOOS=$(GOOS) GOARCH=$(GOARCH) GOMIPS=$(GOMIPS) GOARM=$(GOARM) go build $(PARAMS) -o $(BIN_DIR)/kvas2d ./cmd/kvas2d
package: package:
@mkdir -p $(PKG_DIR)/control @mkdir -p $(PKG_DIR)/control
@echo '2.0' > $(PKG_DIR)/debian-binary @echo '2.0' > $(PKG_DIR)/debian-binary
@echo 'Package: $(APP_NAME)' > $(PKG_DIR)/control/control @echo 'Package: $(APP_NAME)' > $(PKG_DIR)/control/control
@echo 'Version: $(UPSTREAM_VERSION)$(PRERELEASE_POSTFIX)-$(PKG_REVISION)' >> $(PKG_DIR)/control/control @echo 'Version: $(VERSION)-$(COMMITS_SINCE_TAG)' >> $(PKG_DIR)/control/control
@echo 'Architecture: $(TARGET)' >> $(PKG_DIR)/control/control @echo 'Architecture: $(ARCH)' >> $(PKG_DIR)/control/control
@echo 'Maintainer: $(APP_MAINTAINER)' >> $(PKG_DIR)/control/control @echo 'Maintainer: $(APP_MAINTAINER)' >> $(PKG_DIR)/control/control
@echo 'Description: $(APP_DESCRIPTION)' >> $(PKG_DIR)/control/control @echo 'Description: $(APP_DESCRIPTION)' >> $(PKG_DIR)/control/control
@echo 'Section: net' >> $(PKG_DIR)/control/control @echo 'Section: net' >> $(PKG_DIR)/control/control
@echo 'Priority: optional' >> $(PKG_DIR)/control/control @echo 'Priority: optional' >> $(PKG_DIR)/control/control
@echo 'Depends: libc, iptables, socat' >> $(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/ @cp -r ./opt $(PKG_DIR)/data/
@fakeroot sh -c "tar -C $(PKG_DIR)/control -czvf $(PKG_DIR)/control.tar.gz ." @fakeroot sh -c "tar -C $(PKG_DIR)/control -czvf $(PKG_DIR)/control.tar.gz ."
@fakeroot sh -c "tar -C $(PKG_DIR)/data -czvf $(PKG_DIR)/data.tar.gz ." @fakeroot sh -c "tar -C $(PKG_DIR)/data -czvf $(PKG_DIR)/data.tar.gz ."
@tar -C $(PKG_DIR) -czvf $(BUILD_DIR)/$(APP_NAME)_$(UPSTREAM_VERSION)$(PRERELEASE_POSTFIX)-$(PKG_REVISION)_$(TARGET).ipk ./debian-binary ./control.tar.gz ./data.tar.gz @tar -C $(PKG_DIR) -czvf $(BUILD_DIR)/$(APP_NAME)_$(ARCH).ipk ./debian-binary ./control.tar.gz ./data.tar.gz

131
README.md
View File

@ -1,130 +1,3 @@
# MagiTrickle # kvas2
MagiTrickle - Маршрутизация трафика на основе DNS запросов для роутеров Keenetic (под управлением [Entware](https://github.com/The-BB/Entware-Keenetic)). Better implementation of [KVAS](https://github.com/qzeleza/kvas)
*(Продукт в данный момент находится в состоянии разработки)*
Данное программное обеспечение реализует маршрутизацию трафика на основе проксирования через себя DNS запросов. Можно указать список доменных имён, которые нужно маршрутизировать на тот, или иной интерфейс, вместо бесконечного накопления IP адресов.
### Особенности, в сравнении с другим ПО:
1. Не требует отключения встроенного в Keenetic DNS сервера - всё работает методом перенаправления портов.
2. Работает с любыми туннелями, которые умеют поднимать UNIX интерфейс.
3. Несколько типов правил - domain, namespace, wildcard и regex.
4. Не тянет за собой огромное количество сторонних пакетов пакетов. Вся конфигурация находится в одном месте (в одном файле).
5. Возможность создавать несколько групп на разные сети.
6. Моментальное бесшовное включение/выключение сервиса.
### Roadmap:
1. CLI интерфейс для добавления/удаления записей в режиме реального времени. (Уже заложен функционал обработки записей в реальном времени, необходимо заняться CLI интерфейсом)
2. Дружелюбный к пользователю Web-GUI для конфигурации записей.
3. Поддержка подсетей и диапазона IP адресов.
4. Поддержка автообновляемых "подпискок" на список доменных имён (готовые списки подключаемые несколькими кликами мышки).
### Установка:
Т.к. в данный момент нету никакого дружелюбного к пользователю интерфейсов - данное руководство рассчитано на тех, кому просто нужна маршрутизация на требуемые для него домены без отключения встроенного в Keenetic DNS сервера.
Программа не была досканально протестирована, возможны очень редкие "вылеты". Максимально возможный риск заключается в том, что придётся перезапускать роутер, но шанс этого маловероятен.
1. Устанавливаем пакет:
```bash
opkg install magitrickle_<version>_<arch>.ipk
```
2. Копируем конфиг:
```bash
cp /opt/var/lib/magitrickle/config.yaml.example /opt/var/lib/magitrickle/config.yaml
```
3. Настраиваем конфиг (если не понимаете что делаете - не трогайте группу "app"!):
```yaml
configVersion: 0.1.0
app: # Настройки программы - не трогайте, если не знаете что к чему
dnsProxy:
host:
address: '[::]' # Адрес, который будет слушать программа для приёма DNS запросов
port: 3553 # Порт
upstream:
address: 127.0.0.1 # Адрес, используемый для отправки DNS запросов
port: 53 # Порт
disableRemap53: false # Флаг отключения перепривязки 53 порта
disableFakePTR: false # Флаг отключения подделки PTR записи (без неё есть проблемы, может быть будет исправлено в будущем)
disableDropAAAA: false # Флаг отключения откидывания AAAA записей
netfilter:
iptables:
chainPrefix: MT_ # Префикс для названий цепочек IPTables
ipset:
tablePrefix: mt_ # Префикс для названий таблиц IPSet
additionalTTL: 3600 # Дополнительный TTL (если от DNS пришел TTL 300, то к этому числу прибавится указанный TTL)
link: # Список адресов где будет подменяться DNS
- br0
- br1
logLevel: info # Уровень логов (trace, debug, info, warn, error)
groups: # Список групп
- id: d663876a # Уникальный ID группы (8 символов в диапозоне "0123456789abcdef")
name: Routing 1 # Человеко-читаемое имя (для будущего CLI и Web-GUI)
interface: nwg0 # Интерфейс, на который будет выполняться маршрутизация
fixProtect: false # Подключение интерфейса в список для выхода в интернет (для неподдерживаемых Keenetic туннелей)
rules: # Список правил
- id: 6f34ee91 # Уникальный ID правила (8 символов в диапозоне "0123456789abcdef")
name: Wildcard Example # Человеко-читаемое имя (для будущего CLI и Web-GUI)
type: wildcard # Тип правила
rule: '*.example.com' # Правило
enable: true # Флаг активации
- id: 00ae5f7c
name: RegEx Example
type: regex
rule: '^.*.regex.example.com$'
enable: true
- id: d663876b
name: Routing 2
interface: nwg1
fixProtect: false
rules:
- id: 6120dc8a
name: Domain Example
type: domain
rule: 'domain.example.com'
enable: true
```
Примеры правил:
* Domain (один домен без поддоменов)
```yaml
- id: 6120dc8a
name: Domain Example
type: domain
rule: 'example.com'
enable: true
```
* Namespace (домен и все его поддомены)
```yaml
- id: b9751782
name: Namespace Example
type: namespace
rule: 'example.com'
enable: true
```
* Wildcard
```yaml
- id: 6f34ee91
name: Wildcard Example
type: wildcard
rule: '*.example.com'
enable: true
```
* RegEx
```yaml
- id: 00ae5f7c
name: RegEx Example
type: regex
rule: '^.*.regex.example.com$'
enable: true
```
4. Запускаем сервис:
```bash
/opt/etc/init.d/S99magitrickle start
```
### Отладка
Если вам нужна отладка, то останавливаем сервис и запускаем "демона" руками:
```bash
/opt/etc/init.d/S99magitrickle stop
magitrickled
```

1
asd
View File

@ -1 +0,0 @@
sad

View File

@ -10,18 +10,18 @@ import (
"sync" "sync"
"syscall" "syscall"
"magitrickle" "kvas2"
"magitrickle/constant" "kvas2/constant"
"magitrickle/models" "kvas2/models"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const cfgFolderLocation = "/opt/var/lib/magitrickle" const cfgFolderLocation = "/opt/var/lib/kvas2"
const cfgFileLocation = cfgFolderLocation + "/config.yaml" const cfgFileLocation = cfgFolderLocation + "/config.yaml"
const pidFileLocation = "/opt/var/run/magitrickle.pid" const pidFileLocation = "/opt/var/run/kvas2.pid"
func checkPIDFile() error { func checkPIDFile() error {
data, err := os.ReadFile(pidFileLocation) data, err := os.ReadFile(pidFileLocation)
@ -59,10 +59,10 @@ func main() {
log.Info(). log.Info().
Str("version", constant.Version). Str("version", constant.Version).
Str("commit", constant.Commit). Str("commit", constant.Commit).
Msg("starting MagiTrickle daemon") Msg("starting kvas2 daemon")
if err := checkPIDFile(); err != nil { if err := checkPIDFile(); err != nil {
log.Fatal().Err(err).Msg("failed to start MagiTrickle daemon") log.Fatal().Err(err).Msg("failed to start kvas2 daemon")
} }
if err := createPIDFile(); err != nil { if err := createPIDFile(); err != nil {
@ -78,7 +78,7 @@ func main() {
} }
cfg = models.Config{ cfg = models.Config{
ConfigVersion: "0.1.0", ConfigVersion: "0.1.0",
App: magitrickle.DefaultAppConfig, App: kvas2.DefaultAppConfig,
} }
out, err := yaml.Marshal(cfg) out, err := yaml.Marshal(cfg)
if err != nil { if err != nil {
@ -122,7 +122,7 @@ func main() {
zerolog.SetGlobalLevel(zerolog.InfoLevel) zerolog.SetGlobalLevel(zerolog.InfoLevel)
} }
app := magitrickle.New() app := kvas2.New()
err = app.ImportConfig(cfg) err = app.ImportConfig(cfg)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("failed to import config") log.Fatal().Err(err).Msg("failed to import config")

View File

@ -1,7 +0,0 @@
package magitrickle
import "fmt"
func main() {
fmt.Println("dummy file")
}

2
go.mod
View File

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

View File

@ -5,9 +5,9 @@ import (
"net" "net"
"time" "time"
"magitrickle/models" "kvas2/models"
"magitrickle/netfilter-helper" "kvas2/netfilter-helper"
"magitrickle/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 magitrickle package kvas2
import ( import (
"context" "context"
@ -9,11 +9,11 @@ import (
"strings" "strings"
"time" "time"
"magitrickle/dns-mitm-proxy" "kvas2/dns-mitm-proxy"
"magitrickle/group" "kvas2/group"
"magitrickle/models" "kvas2/models"
"magitrickle/netfilter-helper" "kvas2/netfilter-helper"
"magitrickle/records" "kvas2/records"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -38,10 +38,10 @@ var DefaultAppConfig = models.App{
}, },
Netfilter: models.Netfilter{ Netfilter: models.Netfilter{
IPTables: models.IPTables{ IPTables: models.IPTables{
ChainPrefix: "MT_", ChainPrefix: "KVAS2_",
}, },
IPSet: models.IPSet{ IPSet: models.IPSet{
TablePrefix: "mt_", TablePrefix: "kvas2_",
AdditionalTTL: 3600, AdditionalTTL: 3600,
}, },
}, },
@ -254,7 +254,7 @@ func (a *App) start(ctx context.Context) (err error) {
/* /*
Socket (for netfilter.d events) Socket (for netfilter.d events)
*/ */
socketPath := "/opt/var/run/magitrickle.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)

View File

@ -17,7 +17,7 @@ type DNSProxy struct {
Host DNSProxyServer `yaml:"host"` Host DNSProxyServer `yaml:"host"`
Upstream DNSProxyServer `yaml:"upstream"` Upstream DNSProxyServer `yaml:"upstream"`
DisableRemap53 bool `yaml:"disableRemap53"` DisableRemap53 bool `yaml:"disableRemap53"`
DisableFakePTR bool `yaml:"disableFakePTR"` DisableFakePTR bool `yaml:"disableDropPTR"`
DisableDropAAAA bool `yaml:"disableDropAAAA"` DisableDropAAAA bool `yaml:"disableDropAAAA"`
} }

View File

@ -2,16 +2,16 @@ package models
import "testing" import "testing"
func TestDomain_IsMatch_Domain(t *testing.T) { func TestDomain_IsMatch_Plaintext(t *testing.T) {
rule := &Rule{ rule := &Rule{
Type: "domain", Type: "plaintext",
Rule: "example.com", Rule: "example.com",
} }
if !rule.IsMatch("example.com") { if !rule.IsMatch("example.com") {
t.Fatal("&Rule{Type: \"domain\", Rule: \"example.com\"}.IsMatch(\"example.com\") returns false") t.Fatal("&Rule{Type: \"plaintext\", Rule: \"example.com\"}.IsMatch(\"example.com\") returns false")
} }
if rule.IsMatch("noexample.com") { if rule.IsMatch("noexample.com") {
t.Fatal("&Rule{Type: \"domain\", Rule: \"example.com\"}.IsMatch(\"noexample.com\") returns true") t.Fatal("&Rule{Type: \"plaintext\", Rule: \"example.com\"}.IsMatch(\"noexample.com\") returns true")
} }
} }

View File

@ -37,7 +37,6 @@ func (r *IPSetToLink) insertIPTablesRules(table string) error {
} }
for _, iptablesArgs := range [][]string{ for _, iptablesArgs := range [][]string{
{"-j", "CONNMARK", "--restore-mark"},
{"-j", "MARK", "--set-mark", strconv.Itoa(int(r.mark))}, {"-j", "MARK", "--set-mark", strconv.Itoa(int(r.mark))},
{"-j", "CONNMARK", "--save-mark"}, {"-j", "CONNMARK", "--save-mark"},
} { } {
@ -113,8 +112,6 @@ func (r *IPSetToLink) insertIPRule() error {
} }
r.ipRule = rule r.ipRule = rule
log.Trace().Int("table", r.table).Int("mark", int(r.mark)).Msg("using ip table and mark")
return nil return nil
} }

View File

@ -21,8 +21,7 @@ type PortRemap struct {
func (r *PortRemap) insertIPTablesRules(table string) error { func (r *PortRemap) insertIPTablesRules(table string) error {
if table == "" || table == "nat" { if table == "" || table == "nat" {
preroutingChain := r.ChainName + "_PRR" err := r.IPTables.NewChain("nat", r.ChainName)
err := r.IPTables.NewChain("nat", preroutingChain)
if err != nil { if err != nil {
// If not "AlreadyExists" // If not "AlreadyExists"
if eerr, eok := err.(*iptables.Error); !(eok && eerr.ExitStatus() == 1) { if eerr, eok := err.(*iptables.Error); !(eok && eerr.ExitStatus() == 1) {
@ -35,62 +34,18 @@ func (r *PortRemap) insertIPTablesRules(table string) error {
continue continue
} }
if r.IPTables.Proto() != iptables.ProtocolIPv6 { for _, iptablesArgs := range [][]string{
for _, iptablesArgs := range [][]string{ {"-p", "tcp", "-d", addr.IP.String(), "--dport", strconv.Itoa(int(r.From)), "-j", "DNAT", "--to-destination", fmt.Sprintf(":%d", r.To)},
{"-p", "tcp", "-d", addr.IP.String(), "--dport", fmt.Sprintf("%d", r.From), "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", r.To)}, {"-p", "udp", "-d", addr.IP.String(), "--dport", strconv.Itoa(int(r.From)), "-j", "DNAT", "--to-destination", fmt.Sprintf(":%d", r.To)},
{"-p", "udp", "-d", addr.IP.String(), "--dport", fmt.Sprintf("%d", r.From), "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", r.To)}, } {
} { err = r.IPTables.AppendUnique("nat", r.ChainName, iptablesArgs...)
err = r.IPTables.AppendUnique("nat", preroutingChain, iptablesArgs...) if err != nil {
if err != nil { return fmt.Errorf("failed to append rule: %w", err)
return fmt.Errorf("failed to append rule: %w", err)
}
}
} else {
for _, iptablesArgs := range [][]string{
{"-p", "tcp", "-d", addr.IP.String(), "--dport", strconv.Itoa(int(r.From)), "-j", "DNAT", "--to-destination", fmt.Sprintf(":%d", r.To)},
{"-p", "udp", "-d", addr.IP.String(), "--dport", strconv.Itoa(int(r.From)), "-j", "DNAT", "--to-destination", fmt.Sprintf(":%d", r.To)},
} {
err = r.IPTables.AppendUnique("nat", preroutingChain, iptablesArgs...)
if err != nil {
return fmt.Errorf("failed to append rule: %w", err)
}
} }
} }
} }
err = r.IPTables.InsertUnique("nat", "PREROUTING", 1, "-j", preroutingChain) err = r.IPTables.InsertUnique("nat", "PREROUTING", 1, "-j", r.ChainName)
if err != nil {
return fmt.Errorf("failed to linking chain: %w", err)
}
postroutingChain := r.ChainName + "_POR"
err = r.IPTables.NewChain("nat", postroutingChain)
if err != nil {
// If not "AlreadyExists"
if eerr, eok := err.(*iptables.Error); !(eok && eerr.ExitStatus() == 1) {
return fmt.Errorf("failed to create chain: %w", err)
}
}
for _, addr := range r.Addresses {
if !((r.IPTables.Proto() == iptables.ProtocolIPv4 && len(addr.IP) == net.IPv4len) || (r.IPTables.Proto() == iptables.ProtocolIPv6 && len(addr.IP) == net.IPv6len)) {
continue
}
if r.IPTables.Proto() == iptables.ProtocolIPv4 {
for _, iptablesArgs := range [][]string{
{"-p", "tcp", "-d", addr.IP.String(), "--sport", strconv.Itoa(int(r.To)), "-j", "SNAT", "--to-source", addr.IP.String()},
{"-p", "udp", "-d", addr.IP.String(), "--sport", strconv.Itoa(int(r.To)), "-j", "SNAT", "--to-source", addr.IP.String()},
} {
err = r.IPTables.AppendUnique("nat", postroutingChain, iptablesArgs...)
if err != nil {
return fmt.Errorf("failed to append rule: %w", err)
}
}
}
}
err = r.IPTables.InsertUnique("nat", "POSTROUTING", 1, "-j", postroutingChain)
if err != nil { if err != nil {
return fmt.Errorf("failed to linking chain: %w", err) return fmt.Errorf("failed to linking chain: %w", err)
} }
@ -102,14 +57,7 @@ func (r *PortRemap) insertIPTablesRules(table string) error {
func (r *PortRemap) deleteIPTablesRules() []error { func (r *PortRemap) deleteIPTablesRules() []error {
var errs []error var errs []error
preroutingChain := r.ChainName + "_PRR" err := r.IPTables.DeleteIfExists("nat", "PREROUTING", "-j", r.ChainName)
err := r.IPTables.DeleteIfExists("nat", "PREROUTING", "-j", preroutingChain)
if err != nil {
errs = append(errs, fmt.Errorf("failed to unlinking chain: %w", err))
}
postroutingChain := r.ChainName + "_POR"
err = r.IPTables.DeleteIfExists("nat", "POSTROUTING", "-j", postroutingChain)
if err != nil { if err != nil {
errs = append(errs, fmt.Errorf("failed to unlinking chain: %w", err)) errs = append(errs, fmt.Errorf("failed to unlinking chain: %w", err))
} }

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
ENABLED=yes ENABLED=yes
PROCS=magitrickled PROCS=kvas2d
ARGS="" ARGS=""
PREARGS="" PREARGS=""
DESC=$PROCS DESC=$PROCS

View File

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

View File

@ -8,13 +8,13 @@ app:
address: 127.0.0.1 address: 127.0.0.1
port: 53 port: 53
disableRemap53: false disableRemap53: false
disableFakePTR: false disableDropPTR: false
disableDropAAAA: false disableDropAAAA: false
netfilter: netfilter:
iptables: iptables:
chainPrefix: MT_ chainPrefix: KVAS2_
ipset: ipset:
tablePrefix: mt_ tablePrefix: kvas2_
additionalTTL: 3600 additionalTTL: 3600
link: link:
- br0 - br0

View File

@ -9,8 +9,8 @@ import (
func TestLoop(t *testing.T) { func TestLoop(t *testing.T) {
r := New() r := New()
r.AddCNameRecord("1", "2", 60) r.AddCNameRecord("1", "2", time.Minute)
r.AddCNameRecord("2", "1", 60) r.AddCNameRecord("2", "1", time.Minute)
if r.GetARecords("1") != nil { if r.GetARecords("1") != nil {
t.Fatal("loop detected") t.Fatal("loop detected")
} }
@ -21,8 +21,8 @@ func TestLoop(t *testing.T) {
func TestCName(t *testing.T) { func TestCName(t *testing.T) {
r := New() r := New()
r.AddARecord("example.com", []byte{1, 2, 3, 4}, 60) r.AddARecord("example.com", []byte{1, 2, 3, 4}, time.Minute)
r.AddCNameRecord("gateway.example.com", "example.com", 60) r.AddCNameRecord("gateway.example.com", "example.com", time.Minute)
records := r.GetARecords("gateway.example.com") records := r.GetARecords("gateway.example.com")
if records == nil { if records == nil {
t.Fatal("no records") t.Fatal("no records")
@ -34,7 +34,7 @@ func TestCName(t *testing.T) {
func TestA(t *testing.T) { func TestA(t *testing.T) {
r := New() r := New()
r.AddARecord("example.com", []byte{1, 2, 3, 4}, 60) r.AddARecord("example.com", []byte{1, 2, 3, 4}, time.Minute)
records := r.GetARecords("example.com") records := r.GetARecords("example.com")
if records == nil { if records == nil {
t.Fatal("no records") t.Fatal("no records")
@ -46,8 +46,7 @@ func TestA(t *testing.T) {
func TestDeprecated(t *testing.T) { func TestDeprecated(t *testing.T) {
r := New() r := New()
r.AddARecord("example.com", []byte{1, 2, 3, 4}, 0) r.AddARecord("example.com", []byte{1, 2, 3, 4}, -time.Minute)
time.Sleep(time.Second)
records := r.GetARecords("example.com") records := r.GetARecords("example.com")
if records != nil { if records != nil {
t.Fatal("deprecated records") t.Fatal("deprecated records")
@ -64,7 +63,7 @@ func TestNotExistedA(t *testing.T) {
func TestNotExistedCNameAlias(t *testing.T) { func TestNotExistedCNameAlias(t *testing.T) {
r := New() r := New()
r.AddCNameRecord("gateway.example.com", "example.com", 60) r.AddCNameRecord("gateway.example.com", "example.com", time.Minute)
records := r.GetARecords("gateway.example.com") records := r.GetARecords("gateway.example.com")
if records != nil { if records != nil {
t.Fatal("not existed records") t.Fatal("not existed records")
@ -73,8 +72,8 @@ func TestNotExistedCNameAlias(t *testing.T) {
func TestReplacing(t *testing.T) { func TestReplacing(t *testing.T) {
r := New() r := New()
r.AddCNameRecord("gateway.example.com", "example.com", 60) r.AddCNameRecord("gateway.example.com", "example.com", time.Minute)
r.AddARecord("gateway.example.com", []byte{1, 2, 3, 4}, 60) r.AddARecord("gateway.example.com", []byte{1, 2, 3, 4}, time.Minute)
records := r.GetARecords("gateway.example.com") records := r.GetARecords("gateway.example.com")
if bytes.Compare(records[0].Address, []byte{1, 2, 3, 4}) != 0 { if bytes.Compare(records[0].Address, []byte{1, 2, 3, 4}) != 0 {
t.Fatal("mismatch") t.Fatal("mismatch")
@ -83,11 +82,11 @@ func TestReplacing(t *testing.T) {
func TestAliases(t *testing.T) { func TestAliases(t *testing.T) {
r := New() r := New()
r.AddARecord("1", []byte{1, 2, 3, 4}, 60) r.AddARecord("1", []byte{1, 2, 3, 4}, time.Minute)
r.AddCNameRecord("2", "1", 60) r.AddCNameRecord("2", "1", time.Minute)
r.AddCNameRecord("3", "2", 60) r.AddCNameRecord("3", "2", time.Minute)
r.AddCNameRecord("4", "2", 60) r.AddCNameRecord("4", "2", time.Minute)
r.AddCNameRecord("5", "1", 60) r.AddCNameRecord("5", "1", time.Minute)
aliases := r.GetAliases("1") aliases := r.GetAliases("1")
if aliases == nil { if aliases == nil {
t.Fatal("no aliases") t.Fatal("no aliases")