Compare commits

..

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

21 changed files with 372 additions and 725 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}}'

58
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,58 @@
name: Build and Package OPKG
on:
push:
branches:
- main
jobs:
build:
name: Build ${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
matrix:
arch: [mipsel, mips, aarch64, armv7, armv5]
include:
- arch: mipsel
goos: linux
goarch: mipsle
gomips: softfloat
- arch: mips
goos: linux
goarch: mips
gomips: softfloat
- arch: aarch64
goos: linux
goarch: arm64
- arch: armv7
goos: linux
goarch: arm
goarm: 7
- arch: armv5
goos: linux
goarch: arm
goarm: 5
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
cache: false
- name: Install dependencies
run: |
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
path: .build/kvas2_${{ matrix.arch }}.ipk

View File

@ -1,57 +0,0 @@
name: Checking
on:
pull_request:
branches:
- develop
- main
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/check.yml'
jobs:
check:
name: Finding suspicious constructs for ${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: mipsel-3.4
goos: linux
goarch: mipsle
gomips: softfloat
- arch: mips-3.4
goos: linux
goarch: mips
gomips: softfloat
- arch: aarch64-3.10
goos: linux
goarch: arm64
- arch: armv7-3.2
goos: linux
goarch: arm
goarm: 7
- arch: armv7-2.6
goos: linux
goarch: arm
goarm: 7
- arch: 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: Finding suspicious constructs
run: |
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} GOMIPS=${{ matrix.gomips }} GOARM=${{ matrix.goarm }} go vet ./...

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_MAINTAINER = Vladimir Avtsenov <vladimir.lsk.cool@gmail.com>
TAG = $(shell git describe --tags --abbrev=0)
COMMIT = $(shell git rev-parse --short HEAD)
UPSTREAM_VERSION = $(shell git describe --tags --abbrev=0 2> /dev/null || echo "0.0.0")
PKG_REVISION ?= 1
COMMITS_SINCE_TAG = $(shell git rev-list ${TAG}..HEAD --count || echo "0")
VERSION ?= $(TAG)
TAG = $(shell git describe --tags --abbrev=0 2> /dev/null)
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
ARCH ?= mipsel
GOOS ?= linux
GOARCH ?= mipsle
GOMIPS ?= softfloat
GOARM ?=
BUILD_DIR = ./.build
PKG_DIR = $(BUILD_DIR)/$(TARGET)
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"
PKG_DIR = $(BUILD_DIR)/$(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: clear build_daemon package
clear:
echo $(shell git rev-parse --abbrev-ref HEAD)
rm -rf $(PKG_DIR)
all: build_daemon package
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:
@mkdir -p $(PKG_DIR)/control
@echo '2.0' > $(PKG_DIR)/debian-binary
@echo 'Package: $(APP_NAME)' > $(PKG_DIR)/control/control
@echo 'Version: $(UPSTREAM_VERSION)$(PRERELEASE_POSTFIX)-$(PKG_REVISION)' >> $(PKG_DIR)/control/control
@echo 'Architecture: $(TARGET)' >> $(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: net' >> $(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 -czvf $(PKG_DIR)/control.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
@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 $(BUILD_DIR)/$(APP_NAME)_$(ARCH).ipk $(PKG_DIR)/debian-binary $(PKG_DIR)/control.tar $(PKG_DIR)/data.tar

131
README.md
View File

@ -1,130 +1,3 @@
# MagiTrickle
# kvas2
MagiTrickle - Маршрутизация трафика на основе DNS запросов для роутеров Keenetic (под управлением [Entware](https://github.com/The-BB/Entware-Keenetic)).
*(Продукт в данный момент находится в состоянии разработки)*
Данное программное обеспечение реализует маршрутизацию трафика на основе проксирования через себя 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
```
Better implementation of [KVAS](https://github.com/qzeleza/kvas)

1
asd
View File

@ -1 +0,0 @@
sad

View File

@ -10,18 +10,18 @@ import (
"sync"
"syscall"
"magitrickle"
"magitrickle/constant"
"magitrickle/models"
"kvas2"
"kvas2/constant"
"kvas2/models"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
)
const cfgFolderLocation = "/opt/var/lib/magitrickle"
const cfgFolderLocation = "/opt/var/lib/kvas2"
const cfgFileLocation = cfgFolderLocation + "/config.yaml"
const pidFileLocation = "/opt/var/run/magitrickle.pid"
const pidFileLocation = "/opt/var/run/kvas2.pid"
func checkPIDFile() error {
data, err := os.ReadFile(pidFileLocation)
@ -59,10 +59,10 @@ func main() {
log.Info().
Str("version", constant.Version).
Str("commit", constant.Commit).
Msg("starting MagiTrickle daemon")
Msg("starting kvas2 daemon")
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 {
@ -70,15 +70,30 @@ func main() {
}
defer removePIDFile()
cfg := models.Config{}
cfg := models.ConfigFile{}
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) {
log.Fatal().Err(err).Msg("failed to read config.yaml")
}
cfg = models.Config{
ConfigVersion: "0.1.0",
App: magitrickle.DefaultAppConfig,
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 {
@ -92,14 +107,9 @@ func main() {
if err != nil {
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.App.LogLevel {
switch cfg.AppConfig.LogLevel {
case "trace":
zerolog.SetGlobalLevel(zerolog.TraceLevel)
case "debug":
@ -122,18 +132,18 @@ func main() {
zerolog.SetGlobalLevel(zerolog.InfoLevel)
}
app := magitrickle.New()
err = app.ImportConfig(cfg)
app, err := kvas2.New(cfg)
if err != nil {
log.Fatal().Err(err).Msg("failed to import config")
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
*/
ctx, cancel := context.WithCancel(context.Background())
appResult := make(chan error)
go func() {
appResult <- app.Start(ctx)

View File

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

View File

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

2
go.mod
View File

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

View File

@ -5,9 +5,9 @@ import (
"net"
"time"
"magitrickle/models"
"magitrickle/netfilter-helper"
"magitrickle/records"
"kvas2/models"
"kvas2/netfilter-helper"
"kvas2/records"
"github.com/coreos/go-iptables/iptables"
"github.com/rs/zerolog/log"
@ -15,7 +15,7 @@ import (
)
type Group struct {
models.Group
*models.Group
enabled bool
iptables *iptables.IPTables
@ -86,15 +86,6 @@ func (g *Group) Disable() []error {
return errs
}
func (g *Group) Destroy() []error {
errs := g.Disable()
err := g.ipset.Destroy()
if err != nil {
errs = append(errs, err)
}
return errs
}
func (g *Group) Sync(records *records.Records) error {
now := time.Now()
@ -126,14 +117,9 @@ func (g *Group) Sync(records *records.Records) error {
}
for addr, ttl := range addresses {
// TODO: Check TTL
if _, exists := currentAddresses[addr]; exists {
if currentAddresses[addr] == nil {
continue
} else {
if ttl < *currentAddresses[addr] {
continue
}
}
continue
}
ip := net.IP(addr)
err = g.AddIP(ip, ttl)
@ -187,7 +173,7 @@ func (g *Group) LinkUpdateHook(event netlink.LinkUpdate) error {
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)
ipset, err := nh4.IPSet(ipsetName)
if err != nil {

View File

@ -1,19 +1,22 @@
package magitrickle
package kvas2
import (
"context"
"encoding/binary"
"errors"
"fmt"
"math/rand"
"net"
"os"
"strconv"
"strings"
"time"
"magitrickle/dns-mitm-proxy"
"magitrickle/group"
"magitrickle/models"
"magitrickle/netfilter-helper"
"magitrickle/records"
"kvas2/dns-mitm-proxy"
"kvas2/group"
"kvas2/models"
"kvas2/netfilter-helper"
"kvas2/records"
"github.com/miekg/dns"
"github.com/rs/zerolog/log"
@ -22,42 +25,37 @@ import (
)
var (
ErrAlreadyRunning = errors.New("already running")
ErrGroupIDConflict = errors.New("group id conflict")
ErrRuleIDConflict = errors.New("rule id conflict")
ErrConfigUnsupportedVersion = errors.New("config unsupported version")
ErrAlreadyRunning = errors.New("already running")
ErrGroupIDConflict = errors.New("group id conflict")
ErrRuleIDConflict = errors.New("rule id conflict")
)
var DefaultAppConfig = models.App{
DNSProxy: models.DNSProxy{
Host: models.DNSProxyServer{Address: "[::]", Port: 3553},
Upstream: models.DNSProxyServer{Address: "127.0.0.1", Port: 53},
DisableRemap53: false,
DisableFakePTR: false,
DisableDropAAAA: false,
},
Netfilter: models.Netfilter{
IPTables: models.IPTables{
ChainPrefix: "MT_",
},
IPSet: models.IPSet{
TablePrefix: "mt_",
AdditionalTTL: 3600,
},
},
Link: []string{"br0"},
LogLevel: "info",
func randomId() [4]byte {
id := make([]byte, 4)
binary.BigEndian.PutUint32(id, rand.Uint32())
return [4]byte(id)
}
type Config struct {
AdditionalTTL uint32
ChainPrefix string
IPSetPrefix string
LinkName string
TargetDNSServerAddress string
TargetDNSServerPort uint16
ListenDNSPort uint16
}
type App struct {
config models.App
unprocessedGroups []models.Group
Config Config
dnsMITM *dnsMitmProxy.DNSMITMProxy
nfHelper4 *netfilterHelper.NetfilterHelper
nfHelper6 *netfilterHelper.NetfilterHelper
records *records.Records
groups []*group.Group
DNSMITM *dnsMitmProxy.DNSMITMProxy
NetfilterHelper4 *netfilterHelper.NetfilterHelper
NetfilterHelper6 *netfilterHelper.NetfilterHelper
Records *records.Records
Groups []*group.Group
Link netlink.Link
isRunning bool
dnsOverrider4 *netfilterHelper.PortRemap
@ -65,6 +63,7 @@ type App struct {
}
func (a *App) handleLink(event netlink.LinkUpdate) {
switch event.Change {
case 0x00000001:
log.Trace().
@ -72,7 +71,7 @@ func (a *App) handleLink(event netlink.LinkUpdate) {
Int("change", int(event.Change)).
Msg("interface event")
ifaceName := event.Link.Attrs().Name
for _, group := range a.groups {
for _, group := range a.Groups {
if group.Interface != ifaceName {
continue
}
@ -99,72 +98,6 @@ func (a *App) handleLink(event netlink.LinkUpdate) {
}
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)
defer cancel()
@ -175,12 +108,12 @@ func (a *App) start(ctx context.Context) (err error) {
*/
go func() {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", a.config.DNSProxy.Host.Address, a.config.DNSProxy.Host.Port))
addr, err := net.ResolveUDPAddr("udp", "[::]:"+strconv.Itoa(int(a.Config.ListenDNSPort)))
if err != nil {
errChan <- fmt.Errorf("failed to resolve udp address: %v", err)
return
}
err = a.dnsMITM.ListenUDP(newCtx, addr)
err = a.DNSMITM.ListenUDP(newCtx, addr)
if err != nil {
errChan <- fmt.Errorf("failed to serve DNS UDP proxy: %v", err)
return
@ -188,73 +121,57 @@ func (a *App) start(ctx context.Context) (err error) {
}()
go func() {
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", a.config.DNSProxy.Host.Address, a.config.DNSProxy.Host.Port))
addr, err := net.ResolveTCPAddr("tcp", "[::]:"+strconv.Itoa(int(a.Config.ListenDNSPort)))
if err != nil {
errChan <- fmt.Errorf("failed to resolve tcp address: %v", err)
return
}
err = a.dnsMITM.ListenTCP(newCtx, addr)
err = a.DNSMITM.ListenTCP(newCtx, addr)
if err != nil {
errChan <- fmt.Errorf("failed to serve DNS TCP proxy: %v", err)
return
}
}()
var addrList []netlink.Addr
for _, linkName := range a.config.Link {
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...)
addrList, err := netlink.AddrList(a.Link, nl.FAMILY_ALL)
if err != nil {
return fmt.Errorf("failed to list address of interface: %w", err)
}
if !a.config.DNSProxy.DisableRemap53 {
a.dnsOverrider4 = a.nfHelper4.PortRemap(fmt.Sprintf("%sDNSOR", a.config.Netfilter.IPTables.ChainPrefix), 53, a.config.DNSProxy.Host.Port, addrList)
err = a.dnsOverrider4.Enable()
if err != nil {
return fmt.Errorf("failed to override DNS (IPv4): %v", err)
}
defer func() { _ = a.dnsOverrider4.Disable() }()
a.dnsOverrider6 = a.nfHelper6.PortRemap(fmt.Sprintf("%sDNSOR", a.config.Netfilter.IPTables.ChainPrefix), 53, a.config.DNSProxy.Host.Port, addrList)
err = a.dnsOverrider6.Enable()
if err != nil {
return fmt.Errorf("failed to override DNS (IPv6): %v", err)
}
defer func() { _ = a.dnsOverrider6.Disable() }()
a.dnsOverrider4 = a.NetfilterHelper4.PortRemap(fmt.Sprintf("%sDNSOR", a.Config.ChainPrefix), 53, a.Config.ListenDNSPort, addrList)
err = a.dnsOverrider4.Enable()
if err != nil {
return fmt.Errorf("failed to override DNS (IPv4): %v", err)
}
defer func() { _ = a.dnsOverrider4.Disable() }()
a.dnsOverrider6 = a.NetfilterHelper6.PortRemap(fmt.Sprintf("%sDNSOR", a.Config.ChainPrefix), 53, a.Config.ListenDNSPort, addrList)
err = a.dnsOverrider6.Enable()
if err != nil {
return fmt.Errorf("failed to override DNS (IPv6): %v", err)
}
defer func() { _ = a.dnsOverrider6.Disable() }()
/*
Groups
*/
for _, group := range a.unprocessedGroups {
err := a.AddGroup(group)
if err != nil {
return err
}
}
for _, group := range a.groups {
for _, group := range a.Groups {
err = group.Enable()
if err != nil {
return fmt.Errorf("failed to enable group: %w", err)
}
}
defer func() {
for _, group := range a.groups {
_ = group.Destroy()
for _, group := range a.Groups {
_ = group.Disable()
}
}()
/*
Socket (for netfilter.d events)
*/
socketPath := "/opt/var/run/magitrickle.sock"
socketPath := "/opt/var/run/kvas2.sock"
err = os.Remove(socketPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to remove existed UNIX socket: %w", err)
@ -302,7 +219,7 @@ func (a *App) start(ctx context.Context) (err error) {
if err != nil {
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])
if err != nil {
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
@ -364,8 +281,8 @@ func (a *App) Start(ctx context.Context) (err error) {
return err
}
func (a *App) AddGroup(groupModel models.Group) error {
for _, group := range a.groups {
func (a *App) AddGroup(groupModel *models.Group) error {
for _, group := range a.Groups {
if groupModel.ID == group.ID {
return ErrGroupIDConflict
}
@ -378,18 +295,15 @@ func (a *App) AddGroup(groupModel models.Group) error {
dup[rule.ID] = struct{}{}
}
grp, err := group.NewGroup(groupModel, a.nfHelper4, a.config.Netfilter.IPTables.ChainPrefix, a.config.Netfilter.IPSet.TablePrefix)
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 = append(a.groups, grp)
a.Groups = append(a.Groups, grp)
log.Debug().Str("id", grp.ID.String()).Str("name", grp.Name).Msg("added group")
if a.isRunning {
return grp.Sync(a.records)
}
return nil
return grp.Sync(a.Records)
}
func (a *App) ListInterfaces() ([]net.Interface, error) {
@ -427,12 +341,12 @@ func (a *App) processARecord(aRecord dns.A, clientAddr net.Addr, network *string
Str("network", networkStr).
Msg("processing a record")
ttlDuration := aRecord.Hdr.Ttl + a.config.Netfilter.IPSet.AdditionalTTL
ttlDuration := aRecord.Hdr.Ttl + a.Config.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])
for _, group := range a.groups {
names := a.Records.GetAliases(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1])
for _, group := range a.Groups {
Rule:
for _, domain := range group.Rules {
if !domain.IsEnabled() {
@ -478,15 +392,15 @@ func (a *App) processCNameRecord(cNameRecord dns.CNAME, clientAddr net.Addr, net
Str("network", networkStr).
Msg("processing cname record")
ttlDuration := cNameRecord.Hdr.Ttl + a.config.Netfilter.IPSet.AdditionalTTL
ttlDuration := cNameRecord.Hdr.Ttl + a.Config.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
now := time.Now()
aRecords := a.records.GetARecords(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 {
aRecords := a.Records.GetARecords(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 {
Rule:
for _, domain := range group.Rules {
if !domain.IsEnabled() {
@ -532,51 +446,124 @@ func (a *App) handleMessage(msg dns.Msg, clientAddr net.Addr, network *string) {
}
}
func (a *App) ImportConfig(cfg models.Config) error {
if !strings.HasPrefix(cfg.ConfigVersion, "0.1.") {
return ErrConfigUnsupportedVersion
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,
}
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
}
func (a *App) ExportConfig() models.Config {
groups := make([]models.Group, len(a.groups))
for idx, group := range a.groups {
groups[idx] = group.Group
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.Config{
ConfigVersion: "0.1.0",
App: a.config,
Groups: groups,
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() *App {
return &App{config: DefaultAppConfig}
func New(config models.ConfigFile) (*App, error) {
var err error
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,41 +1,17 @@
package models
type Config struct {
ConfigVersion string `yaml:"configVersion"`
App App `yaml:"app"`
Groups []Group `yaml:"groups"`
type ConfigFile struct {
AppConfig AppConfig `yaml:"appConfig"`
Groups []Group `yaml:"groups"`
}
type App struct {
DNSProxy DNSProxy `yaml:"dnsProxy"`
Netfilter Netfilter `yaml:"netfilter"`
Link []string `yaml:"link"`
LogLevel string `yaml:"logLevel"`
}
type DNSProxy struct {
Host DNSProxyServer `yaml:"host"`
Upstream DNSProxyServer `yaml:"upstream"`
DisableRemap53 bool `yaml:"disableRemap53"`
DisableFakePTR bool `yaml:"disableFakePTR"`
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"`
type AppConfig struct {
LogLevel string `yaml:"logLevel"`
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

@ -2,16 +2,16 @@ package models
import "testing"
func TestDomain_IsMatch_Domain(t *testing.T) {
func TestDomain_IsMatch_Plaintext(t *testing.T) {
rule := &Rule{
Type: "domain",
Type: "plaintext",
Rule: "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") {
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

@ -6,7 +6,6 @@ import (
"strconv"
"github.com/coreos/go-iptables/iptables"
"github.com/rs/zerolog/log"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
)
@ -37,7 +36,6 @@ func (r *IPSetToLink) insertIPTablesRules(table string) error {
}
for _, iptablesArgs := range [][]string{
{"-j", "CONNMARK", "--restore-mark"},
{"-j", "MARK", "--set-mark", strconv.Itoa(int(r.mark))},
{"-j", "CONNMARK", "--save-mark"},
} {
@ -113,8 +111,6 @@ func (r *IPSetToLink) insertIPRule() error {
}
r.ipRule = rule
log.Trace().Int("table", r.table).Int("mark", int(r.mark)).Msg("using ip table and mark")
return nil
}
@ -137,7 +133,7 @@ func (r *IPSetToLink) insertIPRoute() error {
if err != nil {
// TODO: Нормально отлавливать ошибку
if err.Error() == "Link not found" {
log.Debug().Str("iface", r.IfaceName).Msg("interface not found (waiting for it to exist)")
// TODO: Логи
return nil
}
return fmt.Errorf("error while getting interface: %w", err)

View File

@ -21,8 +21,7 @@ type PortRemap struct {
func (r *PortRemap) insertIPTablesRules(table string) error {
if table == "" || table == "nat" {
preroutingChain := r.ChainName + "_PRR"
err := r.IPTables.NewChain("nat", preroutingChain)
err := r.IPTables.NewChain("nat", r.ChainName)
if err != nil {
// If not "AlreadyExists"
if eerr, eok := err.(*iptables.Error); !(eok && eerr.ExitStatus() == 1) {
@ -35,62 +34,18 @@ func (r *PortRemap) insertIPTablesRules(table string) error {
continue
}
if r.IPTables.Proto() != iptables.ProtocolIPv6 {
for _, iptablesArgs := range [][]string{
{"-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", fmt.Sprintf("%d", r.From), "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", r.To)},
} {
err = r.IPTables.AppendUnique("nat", preroutingChain, iptablesArgs...)
if err != nil {
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)
}
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", r.ChainName, iptablesArgs...)
if err != nil {
return fmt.Errorf("failed to append rule: %w", err)
}
}
}
err = r.IPTables.InsertUnique("nat", "PREROUTING", 1, "-j", preroutingChain)
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)
err = r.IPTables.InsertUnique("nat", "PREROUTING", 1, "-j", r.ChainName)
if err != nil {
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 {
var errs []error
preroutingChain := r.ChainName + "_PRR"
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)
err := r.IPTables.DeleteIfExists("nat", "PREROUTING", "-j", r.ChainName)
if err != nil {
errs = append(errs, fmt.Errorf("failed to unlinking chain: %w", err))
}

View File

@ -1,10 +0,0 @@
#!/bin/sh
ENABLED=yes
PROCS=magitrickled
ARGS=""
PREARGS=""
DESC=$PROCS
PATH=/opt/sbin:/opt/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
. /opt/etc/init.d/rc.func

View File

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

View File

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

View File

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