Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
57cc17f8ac | |||
468bc28482 | |||
78f964e9c4 | |||
552b175a48 | |||
4de4f1745f | |||
87ee5f5f9b | |||
e8d53872b0 | |||
c7dac46d67 | |||
cc8d3ede3c | |||
867788dc4c | |||
1ab11fa899 | |||
9926cfa0fb | |||
7833bc3db4 | |||
965b4e6718 | |||
c6831f98e0 | |||
891f6ee7c2 | |||
bfe6a00589 | |||
4c49d2ff10 | |||
1967d4e0dc | |||
6a2c5e6c11 | |||
61905052d9 | |||
b16f7fc876 | |||
baab8eca40 | |||
07c07b6aba | |||
5e909ac18d | |||
a65599eafa | |||
37a2cac335 | |||
2371abc9fd | |||
fb60f838bc | |||
43a13664f2 | |||
258fa0a275 | |||
f50101ac97 | |||
41a25ad095 | |||
1063815a10 | |||
e4cecf6022 | |||
a9ee952b0f | |||
97992a6e5e | |||
3dd0003016 | |||
4be873beb3 | |||
d223d7e22e | |||
1a47e0f817 |
90
.github/workflows/build-for-release.yml
vendored
Normal file
90
.github/workflows/build-for-release.yml
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
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
58
.github/workflows/build.yml
vendored
@ -1,58 +0,0 @@
|
|||||||
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
|
|
57
.github/workflows/check.yml
vendored
Normal file
57
.github/workflows/check.yml
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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 ./...
|
@ -1,5 +1,5 @@
|
|||||||
# Contributors
|
# Коллабораторы
|
||||||
|
|
||||||
## Consultants
|
## Консультация
|
||||||
|
|
||||||
- **nesteroff561** - [GitHub](https://github.com/nesteroff561) - "Help with understanding `iptables`"
|
- **nesteroff561** ([GitHub](https://github.com/nesteroff561)) - Помощь с `iptables`
|
48
Makefile
48
Makefile
@ -1,41 +1,55 @@
|
|||||||
APP_NAME = kvas2
|
APP_NAME = magitrickle
|
||||||
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)
|
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
COMMITS_SINCE_TAG = $(shell git rev-list ${TAG}..HEAD --count || echo "0")
|
UPSTREAM_VERSION = $(shell git describe --tags --abbrev=0 2> /dev/null || echo "0.0.0")
|
||||||
VERSION ?= $(TAG)
|
PKG_REVISION ?= 1
|
||||||
|
|
||||||
ARCH ?= mipsel
|
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
|
||||||
GOOS ?= linux
|
GOOS ?= linux
|
||||||
GOARCH ?= mipsle
|
GOARCH ?= mipsle
|
||||||
GOMIPS ?= softfloat
|
GOMIPS ?= softfloat
|
||||||
GOARM ?=
|
GOARM ?=
|
||||||
|
|
||||||
BUILD_DIR = ./.build
|
BUILD_DIR = ./.build
|
||||||
PKG_DIR = $(BUILD_DIR)/$(ARCH)
|
PKG_DIR = $(BUILD_DIR)/$(TARGET)
|
||||||
BIN_DIR = $(PKG_DIR)/data/opt/usr/bin
|
BIN_DIR = $(PKG_DIR)/data/opt/bin
|
||||||
PARAMS = -v -a -trimpath -ldflags="-X 'kvas2/constant.Version=$(VERSION)' -X 'kvas2/constant.Commit=$(COMMIT)' -w -s"
|
PARAMS = -v -a -trimpath -ldflags="-X 'magitrickle/constant.Version=$(UPSTREAM_VERSION)$(PRERELEASE_POSTFIX)' -X 'magitrickle/constant.Commit=$(COMMIT)' -w -s"
|
||||||
|
|
||||||
all: build_daemon package
|
all: clear 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)/kvas2d ./cmd/kvas2d
|
GOOS=$(GOOS) GOARCH=$(GOARCH) GOMIPS=$(GOMIPS) GOARM=$(GOARM) go build $(PARAMS) -o $(BIN_DIR)/magitrickled ./cmd/magitrickled
|
||||||
|
|
||||||
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: $(VERSION)-$(COMMITS_SINCE_TAG)' >> $(PKG_DIR)/control/control
|
@echo 'Version: $(UPSTREAM_VERSION)$(PRERELEASE_POSTFIX)-$(PKG_REVISION)' >> $(PKG_DIR)/control/control
|
||||||
@echo 'Architecture: $(ARCH)' >> $(PKG_DIR)/control/control
|
@echo 'Architecture: $(TARGET)' >> $(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: base' >> $(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 -cvf $(PKG_DIR)/control.tar ."
|
@fakeroot sh -c "tar -C $(PKG_DIR)/control -czvf $(PKG_DIR)/control.tar.gz ."
|
||||||
@fakeroot sh -c "tar -C $(PKG_DIR)/data -cvf $(PKG_DIR)/data.tar ."
|
@fakeroot sh -c "tar -C $(PKG_DIR)/data -czvf $(PKG_DIR)/data.tar.gz ."
|
||||||
@ar r $(BUILD_DIR)/$(APP_NAME)_$(ARCH).ipk $(PKG_DIR)/debian-binary $(PKG_DIR)/control.tar $(PKG_DIR)/data.tar
|
@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
|
||||||
|
131
README.md
131
README.md
@ -1,3 +1,130 @@
|
|||||||
# kvas2
|
# MagiTrickle
|
||||||
|
|
||||||
Better implementation of [KVAS](https://github.com/qzeleza/kvas)
|
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
|
||||||
|
```
|
||||||
|
7
cmd/magitrickle/main.go
Normal file
7
cmd/magitrickle/main.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package magitrickle
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("dummy file")
|
||||||
|
}
|
@ -10,18 +10,18 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"kvas2"
|
"magitrickle"
|
||||||
"kvas2/constant"
|
"magitrickle/constant"
|
||||||
"kvas2/models"
|
"magitrickle/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/kvas2"
|
const cfgFolderLocation = "/opt/var/lib/magitrickle"
|
||||||
const cfgFileLocation = cfgFolderLocation + "/config.yaml"
|
const cfgFileLocation = cfgFolderLocation + "/config.yaml"
|
||||||
const pidFileLocation = "/opt/var/run/kvas2.pid"
|
const pidFileLocation = "/opt/var/run/magitrickle.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 kvas2 daemon")
|
Msg("starting MagiTrickle daemon")
|
||||||
|
|
||||||
if err := checkPIDFile(); err != nil {
|
if err := checkPIDFile(); err != nil {
|
||||||
log.Fatal().Err(err).Msg("failed to start kvas2 daemon")
|
log.Fatal().Err(err).Msg("failed to start MagiTrickle daemon")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createPIDFile(); err != nil {
|
if err := createPIDFile(); err != nil {
|
||||||
@ -70,30 +70,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer removePIDFile()
|
defer removePIDFile()
|
||||||
|
|
||||||
cfg := models.ConfigFile{}
|
cfg := models.Config{}
|
||||||
|
|
||||||
cfgFile, err := os.ReadFile(cfgFileLocation)
|
cfgFile, err := os.ReadFile(cfgFileLocation)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
err = yaml.Unmarshal(cfgFile, &cfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("failed to parse config.yaml")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
log.Fatal().Err(err).Msg("failed to read config.yaml")
|
log.Fatal().Err(err).Msg("failed to read config.yaml")
|
||||||
}
|
}
|
||||||
cfg = models.ConfigFile{
|
cfg = models.Config{
|
||||||
AppConfig: models.AppConfig{
|
ConfigVersion: "0.1.0",
|
||||||
LogLevel: "info",
|
App: magitrickle.DefaultAppConfig,
|
||||||
AdditionalTTL: 216000,
|
|
||||||
ChainPrefix: "KVAS2_",
|
|
||||||
IPSetPrefix: "kvas2_",
|
|
||||||
LinkName: "br0",
|
|
||||||
TargetDNSServerAddress: "127.0.0.1",
|
|
||||||
TargetDNSServerPort: 53,
|
|
||||||
ListenDNSPort: 3553,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
out, err := yaml.Marshal(cfg)
|
out, err := yaml.Marshal(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,9 +92,14 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("failed to save config.yaml")
|
log.Fatal().Err(err).Msg("failed to save config.yaml")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
err = yaml.Unmarshal(cfgFile, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("failed to parse config.yaml")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch cfg.AppConfig.LogLevel {
|
switch cfg.App.LogLevel {
|
||||||
case "trace":
|
case "trace":
|
||||||
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
||||||
case "debug":
|
case "debug":
|
||||||
@ -132,18 +122,18 @@ func main() {
|
|||||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := kvas2.New(cfg)
|
app := magitrickle.New()
|
||||||
|
err = app.ImportConfig(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("failed to initialize application")
|
log.Fatal().Err(err).Msg("failed to import config")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
log.Info().Msg("starting service")
|
log.Info().Msg("starting service")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Starting app with graceful shutdown
|
Starting app with graceful shutdown
|
||||||
*/
|
*/
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
appResult := make(chan error)
|
appResult := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
appResult <- app.Start(ctx)
|
appResult <- app.Start(ctx)
|
@ -12,33 +12,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DNSMITMProxy struct {
|
type DNSMITMProxy struct {
|
||||||
TargetDNSServerAddress string
|
UpstreamDNSAddress string
|
||||||
TargetDNSServerPort uint16
|
UpstreamDNSPort uint16
|
||||||
|
|
||||||
RequestHook func(net.Addr, dns.Msg, string) (*dns.Msg, *dns.Msg, error)
|
RequestHook func(net.Addr, dns.Msg, string) (*dns.Msg, *dns.Msg, error)
|
||||||
ResponseHook func(net.Addr, dns.Msg, dns.Msg, string) (*dns.Msg, error)
|
ResponseHook func(net.Addr, dns.Msg, dns.Msg, string) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p DNSMITMProxy) requestDNS(req []byte, network string) ([]byte, error) {
|
func (p DNSMITMProxy) requestDNS(req []byte, network string) ([]byte, error) {
|
||||||
serverConn, err := net.Dial(network, fmt.Sprintf("%s:%d", p.TargetDNSServerAddress, p.TargetDNSServerPort))
|
upstreamConn, err := net.Dial(network, fmt.Sprintf("%s:%d", p.UpstreamDNSAddress, p.UpstreamDNSPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to dial DNS server: %w", err)
|
return nil, fmt.Errorf("failed to dial DNS upstream: %w", err)
|
||||||
}
|
}
|
||||||
defer func() { _ = serverConn.Close() }()
|
defer func() { _ = upstreamConn.Close() }()
|
||||||
|
|
||||||
err = serverConn.SetDeadline(time.Now().Add(time.Second * 5))
|
err = upstreamConn.SetDeadline(time.Now().Add(time.Second * 5))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to set deadline: %w", err)
|
return nil, fmt.Errorf("failed to set deadline: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if network == "tcp" {
|
if network == "tcp" {
|
||||||
err = binary.Write(serverConn, binary.BigEndian, uint16(len(req)))
|
err = binary.Write(upstreamConn, binary.BigEndian, uint16(len(req)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to write length: %w", err)
|
return nil, fmt.Errorf("failed to write length: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := serverConn.Write(req)
|
n, err := upstreamConn.Write(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to write request: %w", err)
|
return nil, fmt.Errorf("failed to write request: %w", err)
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ func (p DNSMITMProxy) requestDNS(req []byte, network string) ([]byte, error) {
|
|||||||
var resp []byte
|
var resp []byte
|
||||||
if network == "tcp" {
|
if network == "tcp" {
|
||||||
var respLen uint16
|
var respLen uint16
|
||||||
err = binary.Read(serverConn, binary.BigEndian, &respLen)
|
err = binary.Read(upstreamConn, binary.BigEndian, &respLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read length: %w", err)
|
return nil, fmt.Errorf("failed to read length: %w", err)
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ func (p DNSMITMProxy) requestDNS(req []byte, network string) ([]byte, error) {
|
|||||||
resp = make([]byte, 512)
|
resp = make([]byte, 512)
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err = serverConn.Read(resp)
|
n, err = upstreamConn.Read(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||||
}
|
}
|
||||||
@ -213,9 +213,3 @@ func (p DNSMITMProxy) ListenUDP(ctx context.Context, addr *net.UDPAddr) error {
|
|||||||
}(conn, clientAddr)
|
}(conn, clientAddr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *DNSMITMProxy {
|
|
||||||
return &DNSMITMProxy{
|
|
||||||
TargetDNSServerPort: 53,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,9 +5,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kvas2/models"
|
"magitrickle/models"
|
||||||
"kvas2/netfilter-helper"
|
"magitrickle/netfilter-helper"
|
||||||
"kvas2/records"
|
"magitrickle/records"
|
||||||
|
|
||||||
"github.com/coreos/go-iptables/iptables"
|
"github.com/coreos/go-iptables/iptables"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
*models.Group
|
models.Group
|
||||||
|
|
||||||
enabled bool
|
enabled bool
|
||||||
iptables *iptables.IPTables
|
iptables *iptables.IPTables
|
||||||
@ -86,6 +86,15 @@ func (g *Group) Disable() []error {
|
|||||||
return errs
|
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 {
|
func (g *Group) Sync(records *records.Records) error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
@ -117,9 +126,14 @@ func (g *Group) Sync(records *records.Records) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for addr, ttl := range addresses {
|
for addr, ttl := range addresses {
|
||||||
// TODO: Check TTL
|
|
||||||
if _, exists := currentAddresses[addr]; exists {
|
if _, exists := currentAddresses[addr]; exists {
|
||||||
continue
|
if currentAddresses[addr] == nil {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if ttl < *currentAddresses[addr] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ip := net.IP(addr)
|
ip := net.IP(addr)
|
||||||
err = g.AddIP(ip, ttl)
|
err = g.AddIP(ip, ttl)
|
||||||
@ -173,7 +187,7 @@ func (g *Group) LinkUpdateHook(event netlink.LinkUpdate) error {
|
|||||||
return g.ipsetToLink.LinkUpdateHook(event)
|
return g.ipsetToLink.LinkUpdateHook(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroup(group *models.Group, nh4 *netfilterHelper.NetfilterHelper, chainPrefix, ipsetNamePrefix string) (*Group, error) {
|
func NewGroup(group models.Group, nh4 *netfilterHelper.NetfilterHelper, chainPrefix, ipsetNamePrefix string) (*Group, error) {
|
||||||
ipsetName := fmt.Sprintf("%s%8x", ipsetNamePrefix, group.ID)
|
ipsetName := fmt.Sprintf("%s%8x", ipsetNamePrefix, group.ID)
|
||||||
ipset, err := nh4.IPSet(ipsetName)
|
ipset, err := nh4.IPSet(ipsetName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
package kvas2
|
package magitrickle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kvas2/dns-mitm-proxy"
|
"magitrickle/dns-mitm-proxy"
|
||||||
"kvas2/group"
|
"magitrickle/group"
|
||||||
"kvas2/models"
|
"magitrickle/models"
|
||||||
"kvas2/netfilter-helper"
|
"magitrickle/netfilter-helper"
|
||||||
"kvas2/records"
|
"magitrickle/records"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -25,37 +22,42 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrAlreadyRunning = errors.New("already running")
|
ErrAlreadyRunning = errors.New("already running")
|
||||||
ErrGroupIDConflict = errors.New("group id conflict")
|
ErrGroupIDConflict = errors.New("group id conflict")
|
||||||
ErrRuleIDConflict = errors.New("rule id conflict")
|
ErrRuleIDConflict = errors.New("rule id conflict")
|
||||||
|
ErrConfigUnsupportedVersion = errors.New("config unsupported version")
|
||||||
)
|
)
|
||||||
|
|
||||||
func randomId() [4]byte {
|
var DefaultAppConfig = models.App{
|
||||||
id := make([]byte, 4)
|
DNSProxy: models.DNSProxy{
|
||||||
binary.BigEndian.PutUint32(id, rand.Uint32())
|
Host: models.DNSProxyServer{Address: "[::]", Port: 3553},
|
||||||
return [4]byte(id)
|
Upstream: models.DNSProxyServer{Address: "127.0.0.1", Port: 53},
|
||||||
}
|
DisableRemap53: false,
|
||||||
|
DisableFakePTR: false,
|
||||||
type Config struct {
|
DisableDropAAAA: false,
|
||||||
AdditionalTTL uint32
|
},
|
||||||
ChainPrefix string
|
Netfilter: models.Netfilter{
|
||||||
IPSetPrefix string
|
IPTables: models.IPTables{
|
||||||
LinkName string
|
ChainPrefix: "MT_",
|
||||||
TargetDNSServerAddress string
|
},
|
||||||
TargetDNSServerPort uint16
|
IPSet: models.IPSet{
|
||||||
ListenDNSPort uint16
|
TablePrefix: "mt_",
|
||||||
|
AdditionalTTL: 3600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Link: []string{"br0"},
|
||||||
|
LogLevel: "info",
|
||||||
}
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Config Config
|
config models.App
|
||||||
|
unprocessedGroups []models.Group
|
||||||
|
|
||||||
DNSMITM *dnsMitmProxy.DNSMITMProxy
|
dnsMITM *dnsMitmProxy.DNSMITMProxy
|
||||||
NetfilterHelper4 *netfilterHelper.NetfilterHelper
|
nfHelper4 *netfilterHelper.NetfilterHelper
|
||||||
NetfilterHelper6 *netfilterHelper.NetfilterHelper
|
nfHelper6 *netfilterHelper.NetfilterHelper
|
||||||
Records *records.Records
|
records *records.Records
|
||||||
Groups []*group.Group
|
groups []*group.Group
|
||||||
|
|
||||||
Link netlink.Link
|
|
||||||
|
|
||||||
isRunning bool
|
isRunning bool
|
||||||
dnsOverrider4 *netfilterHelper.PortRemap
|
dnsOverrider4 *netfilterHelper.PortRemap
|
||||||
@ -63,7 +65,6 @@ type App struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) handleLink(event netlink.LinkUpdate) {
|
func (a *App) handleLink(event netlink.LinkUpdate) {
|
||||||
|
|
||||||
switch event.Change {
|
switch event.Change {
|
||||||
case 0x00000001:
|
case 0x00000001:
|
||||||
log.Trace().
|
log.Trace().
|
||||||
@ -71,7 +72,7 @@ func (a *App) handleLink(event netlink.LinkUpdate) {
|
|||||||
Int("change", int(event.Change)).
|
Int("change", int(event.Change)).
|
||||||
Msg("interface event")
|
Msg("interface event")
|
||||||
ifaceName := event.Link.Attrs().Name
|
ifaceName := event.Link.Attrs().Name
|
||||||
for _, group := range a.Groups {
|
for _, group := range a.groups {
|
||||||
if group.Interface != ifaceName {
|
if group.Interface != ifaceName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -98,6 +99,72 @@ func (a *App) handleLink(event netlink.LinkUpdate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) start(ctx context.Context) (err error) {
|
func (a *App) start(ctx context.Context) (err error) {
|
||||||
|
a.dnsMITM = &dnsMitmProxy.DNSMITMProxy{
|
||||||
|
UpstreamDNSAddress: a.config.DNSProxy.Upstream.Address,
|
||||||
|
UpstreamDNSPort: a.config.DNSProxy.Upstream.Port,
|
||||||
|
RequestHook: func(clientAddr net.Addr, reqMsg dns.Msg, network string) (*dns.Msg, *dns.Msg, error) {
|
||||||
|
if a.config.DNSProxy.DisableFakePTR {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Проверить на интерфейс
|
||||||
|
if len(reqMsg.Question) == 1 && reqMsg.Question[0].Qtype == dns.TypePTR {
|
||||||
|
respMsg := &dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: reqMsg.Id,
|
||||||
|
Response: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: dns.RcodeNameError,
|
||||||
|
},
|
||||||
|
Question: reqMsg.Question,
|
||||||
|
}
|
||||||
|
return nil, respMsg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, nil
|
||||||
|
},
|
||||||
|
ResponseHook: func(clientAddr net.Addr, reqMsg dns.Msg, respMsg dns.Msg, network string) (*dns.Msg, error) {
|
||||||
|
defer a.handleMessage(respMsg, clientAddr, &network)
|
||||||
|
|
||||||
|
if a.config.DNSProxy.DisableDropAAAA {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
for _, answer := range respMsg.Answer {
|
||||||
|
if answer.Header().Rrtype == dns.TypeAAAA {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
respMsg.Answer[idx] = answer
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
respMsg.Answer = respMsg.Answer[:idx]
|
||||||
|
|
||||||
|
return &respMsg, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.records = records.New()
|
||||||
|
|
||||||
|
nh4, err := netfilterHelper.New(false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("netfilter helper init fail: %w", err)
|
||||||
|
}
|
||||||
|
err = nh4.CleanIPTables(a.config.Netfilter.IPTables.ChainPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to clear iptables: %w", err)
|
||||||
|
}
|
||||||
|
a.nfHelper4 = nh4
|
||||||
|
|
||||||
|
nh6, err := netfilterHelper.New(true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("netfilter helper init fail: %w", err)
|
||||||
|
}
|
||||||
|
err = nh6.CleanIPTables(a.config.Netfilter.IPTables.ChainPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to clear iptables: %w", err)
|
||||||
|
}
|
||||||
|
a.nfHelper6 = nh6
|
||||||
|
|
||||||
newCtx, cancel := context.WithCancel(ctx)
|
newCtx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@ -108,12 +175,12 @@ func (a *App) start(ctx context.Context) (err error) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
addr, err := net.ResolveUDPAddr("udp", "[::]:"+strconv.Itoa(int(a.Config.ListenDNSPort)))
|
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", a.config.DNSProxy.Host.Address, a.config.DNSProxy.Host.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- fmt.Errorf("failed to resolve udp address: %v", err)
|
errChan <- fmt.Errorf("failed to resolve udp address: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.DNSMITM.ListenUDP(newCtx, addr)
|
err = a.dnsMITM.ListenUDP(newCtx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- fmt.Errorf("failed to serve DNS UDP proxy: %v", err)
|
errChan <- fmt.Errorf("failed to serve DNS UDP proxy: %v", err)
|
||||||
return
|
return
|
||||||
@ -121,57 +188,73 @@ func (a *App) start(ctx context.Context) (err error) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
addr, err := net.ResolveTCPAddr("tcp", "[::]:"+strconv.Itoa(int(a.Config.ListenDNSPort)))
|
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", a.config.DNSProxy.Host.Address, a.config.DNSProxy.Host.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- fmt.Errorf("failed to resolve tcp address: %v", err)
|
errChan <- fmt.Errorf("failed to resolve tcp address: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.DNSMITM.ListenTCP(newCtx, addr)
|
err = a.dnsMITM.ListenTCP(newCtx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- fmt.Errorf("failed to serve DNS TCP proxy: %v", err)
|
errChan <- fmt.Errorf("failed to serve DNS TCP proxy: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addrList, err := netlink.AddrList(a.Link, nl.FAMILY_ALL)
|
var addrList []netlink.Addr
|
||||||
if err != nil {
|
for _, linkName := range a.config.Link {
|
||||||
return fmt.Errorf("failed to list address of interface: %w", err)
|
link, err := netlink.LinkByName(linkName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find link %s: %w", linkName, err)
|
||||||
|
}
|
||||||
|
linkAddrList, err := netlink.AddrList(link, nl.FAMILY_ALL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list address of interface: %w", err)
|
||||||
|
}
|
||||||
|
addrList = append(addrList, linkAddrList...)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.dnsOverrider4 = a.NetfilterHelper4.PortRemap(fmt.Sprintf("%sDNSOR", a.Config.ChainPrefix), 53, a.Config.ListenDNSPort, addrList)
|
if !a.config.DNSProxy.DisableRemap53 {
|
||||||
err = a.dnsOverrider4.Enable()
|
a.dnsOverrider4 = a.nfHelper4.PortRemap(fmt.Sprintf("%sDNSOR", a.config.Netfilter.IPTables.ChainPrefix), 53, a.config.DNSProxy.Host.Port, addrList)
|
||||||
if err != nil {
|
err = a.dnsOverrider4.Enable()
|
||||||
return fmt.Errorf("failed to override DNS (IPv4): %v", err)
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("failed to override DNS (IPv4): %v", err)
|
||||||
defer func() { _ = a.dnsOverrider4.Disable() }()
|
}
|
||||||
|
defer func() { _ = a.dnsOverrider4.Disable() }()
|
||||||
|
|
||||||
a.dnsOverrider6 = a.NetfilterHelper6.PortRemap(fmt.Sprintf("%sDNSOR", a.Config.ChainPrefix), 53, a.Config.ListenDNSPort, addrList)
|
a.dnsOverrider6 = a.nfHelper6.PortRemap(fmt.Sprintf("%sDNSOR", a.config.Netfilter.IPTables.ChainPrefix), 53, a.config.DNSProxy.Host.Port, addrList)
|
||||||
err = a.dnsOverrider6.Enable()
|
err = a.dnsOverrider6.Enable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to override DNS (IPv6): %v", err)
|
return fmt.Errorf("failed to override DNS (IPv6): %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = a.dnsOverrider6.Disable() }()
|
||||||
}
|
}
|
||||||
defer func() { _ = a.dnsOverrider6.Disable() }()
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Groups
|
Groups
|
||||||
*/
|
*/
|
||||||
|
|
||||||
for _, group := range a.Groups {
|
for _, group := range a.unprocessedGroups {
|
||||||
|
err := a.AddGroup(group)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, group := range a.groups {
|
||||||
err = group.Enable()
|
err = group.Enable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to enable group: %w", err)
|
return fmt.Errorf("failed to enable group: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
for _, group := range a.Groups {
|
for _, group := range a.groups {
|
||||||
_ = group.Disable()
|
_ = group.Destroy()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Socket (for netfilter.d events)
|
Socket (for netfilter.d events)
|
||||||
*/
|
*/
|
||||||
socketPath := "/opt/var/run/kvas2.sock"
|
socketPath := "/opt/var/run/magitrickle.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)
|
||||||
@ -219,7 +302,7 @@ func (a *App) start(ctx context.Context) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
|
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
|
||||||
}
|
}
|
||||||
for _, group := range a.Groups {
|
for _, group := range a.groups {
|
||||||
err := group.NetfilterDHook(args[2])
|
err := group.NetfilterDHook(args[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
|
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
|
||||||
@ -281,8 +364,8 @@ func (a *App) Start(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) AddGroup(groupModel *models.Group) error {
|
func (a *App) AddGroup(groupModel models.Group) error {
|
||||||
for _, group := range a.Groups {
|
for _, group := range a.groups {
|
||||||
if groupModel.ID == group.ID {
|
if groupModel.ID == group.ID {
|
||||||
return ErrGroupIDConflict
|
return ErrGroupIDConflict
|
||||||
}
|
}
|
||||||
@ -295,15 +378,18 @@ func (a *App) AddGroup(groupModel *models.Group) error {
|
|||||||
dup[rule.ID] = struct{}{}
|
dup[rule.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
grp, err := group.NewGroup(groupModel, a.NetfilterHelper4, a.Config.ChainPrefix, a.Config.IPSetPrefix)
|
grp, err := group.NewGroup(groupModel, a.nfHelper4, a.config.Netfilter.IPTables.ChainPrefix, a.config.Netfilter.IPSet.TablePrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create group: %w", err)
|
return fmt.Errorf("failed to create group: %w", err)
|
||||||
}
|
}
|
||||||
a.Groups = append(a.Groups, grp)
|
a.groups = append(a.groups, grp)
|
||||||
|
|
||||||
log.Debug().Str("id", grp.ID.String()).Str("name", grp.Name).Msg("added group")
|
log.Debug().Str("id", grp.ID.String()).Str("name", grp.Name).Msg("added group")
|
||||||
|
|
||||||
return grp.Sync(a.Records)
|
if a.isRunning {
|
||||||
|
return grp.Sync(a.records)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) ListInterfaces() ([]net.Interface, error) {
|
func (a *App) ListInterfaces() ([]net.Interface, error) {
|
||||||
@ -341,12 +427,12 @@ func (a *App) processARecord(aRecord dns.A, clientAddr net.Addr, network *string
|
|||||||
Str("network", networkStr).
|
Str("network", networkStr).
|
||||||
Msg("processing a record")
|
Msg("processing a record")
|
||||||
|
|
||||||
ttlDuration := aRecord.Hdr.Ttl + a.Config.AdditionalTTL
|
ttlDuration := aRecord.Hdr.Ttl + a.config.Netfilter.IPSet.AdditionalTTL
|
||||||
|
|
||||||
a.Records.AddARecord(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1], aRecord.A, ttlDuration)
|
a.records.AddARecord(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1], aRecord.A, ttlDuration)
|
||||||
|
|
||||||
names := a.Records.GetAliases(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1])
|
names := a.records.GetAliases(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1])
|
||||||
for _, group := range a.Groups {
|
for _, group := range a.groups {
|
||||||
Rule:
|
Rule:
|
||||||
for _, domain := range group.Rules {
|
for _, domain := range group.Rules {
|
||||||
if !domain.IsEnabled() {
|
if !domain.IsEnabled() {
|
||||||
@ -392,15 +478,15 @@ func (a *App) processCNameRecord(cNameRecord dns.CNAME, clientAddr net.Addr, net
|
|||||||
Str("network", networkStr).
|
Str("network", networkStr).
|
||||||
Msg("processing cname record")
|
Msg("processing cname record")
|
||||||
|
|
||||||
ttlDuration := cNameRecord.Hdr.Ttl + a.Config.AdditionalTTL
|
ttlDuration := cNameRecord.Hdr.Ttl + a.config.Netfilter.IPSet.AdditionalTTL
|
||||||
|
|
||||||
a.Records.AddCNameRecord(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1], cNameRecord.Target[:len(cNameRecord.Target)-1], ttlDuration)
|
a.records.AddCNameRecord(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1], cNameRecord.Target[:len(cNameRecord.Target)-1], ttlDuration)
|
||||||
|
|
||||||
// TODO: Optimization
|
// TODO: Optimization
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
aRecords := a.Records.GetARecords(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1])
|
aRecords := a.records.GetARecords(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1])
|
||||||
names := a.Records.GetAliases(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1])
|
names := a.records.GetAliases(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1])
|
||||||
for _, group := range a.Groups {
|
for _, group := range a.groups {
|
||||||
Rule:
|
Rule:
|
||||||
for _, domain := range group.Rules {
|
for _, domain := range group.Rules {
|
||||||
if !domain.IsEnabled() {
|
if !domain.IsEnabled() {
|
||||||
@ -446,124 +532,51 @@ func (a *App) handleMessage(msg dns.Msg, clientAddr net.Addr, network *string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) ImportConfig(cfg models.ConfigFile) error {
|
func (a *App) ImportConfig(cfg models.Config) error {
|
||||||
a.Config = Config{
|
if !strings.HasPrefix(cfg.ConfigVersion, "0.1.") {
|
||||||
AdditionalTTL: cfg.AppConfig.AdditionalTTL,
|
return ErrConfigUnsupportedVersion
|
||||||
ChainPrefix: cfg.AppConfig.ChainPrefix,
|
|
||||||
IPSetPrefix: cfg.AppConfig.IPSetPrefix,
|
|
||||||
LinkName: cfg.AppConfig.LinkName,
|
|
||||||
TargetDNSServerAddress: cfg.AppConfig.TargetDNSServerAddress,
|
|
||||||
TargetDNSServerPort: cfg.AppConfig.TargetDNSServerPort,
|
|
||||||
ListenDNSPort: cfg.AppConfig.ListenDNSPort,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.App.DNSProxy.Upstream.Address != "" {
|
||||||
|
a.config.DNSProxy.Upstream.Address = cfg.App.DNSProxy.Upstream.Address
|
||||||
|
}
|
||||||
|
if cfg.App.DNSProxy.Upstream.Port != 0 {
|
||||||
|
a.config.DNSProxy.Upstream.Port = cfg.App.DNSProxy.Upstream.Port
|
||||||
|
}
|
||||||
|
if cfg.App.DNSProxy.Host.Address != "" {
|
||||||
|
a.config.DNSProxy.Host.Address = cfg.App.DNSProxy.Host.Address
|
||||||
|
}
|
||||||
|
if cfg.App.DNSProxy.Host.Port != 0 {
|
||||||
|
a.config.DNSProxy.Host.Port = cfg.App.DNSProxy.Host.Port
|
||||||
|
}
|
||||||
|
a.config.DNSProxy.DisableRemap53 = cfg.App.DNSProxy.DisableRemap53
|
||||||
|
a.config.DNSProxy.DisableFakePTR = cfg.App.DNSProxy.DisableFakePTR
|
||||||
|
a.config.DNSProxy.DisableDropAAAA = cfg.App.DNSProxy.DisableDropAAAA
|
||||||
|
if cfg.App.Netfilter.IPTables.ChainPrefix != "" {
|
||||||
|
a.config.Netfilter.IPTables.ChainPrefix = cfg.App.Netfilter.IPTables.ChainPrefix
|
||||||
|
}
|
||||||
|
if cfg.App.Netfilter.IPSet.TablePrefix != "" {
|
||||||
|
a.config.Netfilter.IPSet.TablePrefix = cfg.App.Netfilter.IPSet.TablePrefix
|
||||||
|
}
|
||||||
|
a.config.Netfilter.IPSet.AdditionalTTL = cfg.App.Netfilter.IPSet.AdditionalTTL
|
||||||
|
|
||||||
|
a.unprocessedGroups = cfg.Groups
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) ExportConfig() models.ConfigFile {
|
func (a *App) ExportConfig() models.Config {
|
||||||
groups := make([]models.Group, len(a.Groups))
|
groups := make([]models.Group, len(a.groups))
|
||||||
for idx, group := range a.Groups {
|
for idx, group := range a.groups {
|
||||||
groups[idx] = *group.Group
|
groups[idx] = group.Group
|
||||||
}
|
}
|
||||||
return models.ConfigFile{
|
return models.Config{
|
||||||
AppConfig: models.AppConfig{
|
ConfigVersion: "0.1.0",
|
||||||
AdditionalTTL: a.Config.AdditionalTTL,
|
App: a.config,
|
||||||
ChainPrefix: a.Config.ChainPrefix,
|
Groups: groups,
|
||||||
IPSetPrefix: a.Config.IPSetPrefix,
|
|
||||||
LinkName: a.Config.LinkName,
|
|
||||||
TargetDNSServerAddress: a.Config.TargetDNSServerAddress,
|
|
||||||
TargetDNSServerPort: a.Config.TargetDNSServerPort,
|
|
||||||
ListenDNSPort: a.Config.ListenDNSPort,
|
|
||||||
},
|
|
||||||
Groups: groups,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config models.ConfigFile) (*App, error) {
|
func New() *App {
|
||||||
var err error
|
return &App{config: DefaultAppConfig}
|
||||||
|
|
||||||
app := &App{}
|
|
||||||
|
|
||||||
app.Config = Config{
|
|
||||||
AdditionalTTL: config.AppConfig.AdditionalTTL,
|
|
||||||
ChainPrefix: config.AppConfig.ChainPrefix,
|
|
||||||
IPSetPrefix: config.AppConfig.IPSetPrefix,
|
|
||||||
LinkName: config.AppConfig.LinkName,
|
|
||||||
TargetDNSServerAddress: config.AppConfig.TargetDNSServerAddress,
|
|
||||||
TargetDNSServerPort: config.AppConfig.TargetDNSServerPort,
|
|
||||||
ListenDNSPort: config.AppConfig.ListenDNSPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
app.DNSMITM = dnsMitmProxy.New()
|
|
||||||
app.DNSMITM.TargetDNSServerAddress = app.Config.TargetDNSServerAddress
|
|
||||||
app.DNSMITM.TargetDNSServerPort = app.Config.TargetDNSServerPort
|
|
||||||
app.DNSMITM.RequestHook = func(clientAddr net.Addr, reqMsg dns.Msg, network string) (*dns.Msg, *dns.Msg, error) {
|
|
||||||
// TODO: Need to understand why it not works in proxy mode
|
|
||||||
if len(reqMsg.Question) == 1 && reqMsg.Question[0].Qtype == dns.TypePTR {
|
|
||||||
respMsg := &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{
|
|
||||||
Id: reqMsg.Id,
|
|
||||||
Response: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: dns.RcodeNameError,
|
|
||||||
},
|
|
||||||
Question: reqMsg.Question,
|
|
||||||
}
|
|
||||||
return nil, respMsg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
app.DNSMITM.ResponseHook = func(clientAddr net.Addr, reqMsg dns.Msg, respMsg dns.Msg, network string) (*dns.Msg, error) {
|
|
||||||
// TODO: Make it optional
|
|
||||||
var idx int
|
|
||||||
for _, a := range respMsg.Answer {
|
|
||||||
if a.Header().Rrtype == dns.TypeAAAA {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
respMsg.Answer[idx] = a
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
respMsg.Answer = respMsg.Answer[:idx]
|
|
||||||
|
|
||||||
app.handleMessage(respMsg, clientAddr, &network)
|
|
||||||
|
|
||||||
return &respMsg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Records = records.New()
|
|
||||||
|
|
||||||
link, err := netlink.LinkByName(app.Config.LinkName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to find link %s: %w", app.Config.LinkName, err)
|
|
||||||
}
|
|
||||||
app.Link = link
|
|
||||||
|
|
||||||
nh4, err := netfilterHelper.New(false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("netfilter helper init fail: %w", err)
|
|
||||||
}
|
|
||||||
app.NetfilterHelper4 = nh4
|
|
||||||
err = app.NetfilterHelper4.CleanIPTables(app.Config.ChainPrefix)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to clear iptables: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nh6, err := netfilterHelper.New(true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("netfilter helper init fail: %w", err)
|
|
||||||
}
|
|
||||||
app.NetfilterHelper6 = nh6
|
|
||||||
err = app.NetfilterHelper6.CleanIPTables(app.Config.ChainPrefix)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to clear iptables: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, group := range config.Groups {
|
|
||||||
err = app.AddGroup(&group)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return app, nil
|
|
||||||
}
|
}
|
@ -1,17 +1,41 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type ConfigFile struct {
|
type Config struct {
|
||||||
AppConfig AppConfig `yaml:"appConfig"`
|
ConfigVersion string `yaml:"configVersion"`
|
||||||
Groups []Group `yaml:"groups"`
|
App App `yaml:"app"`
|
||||||
|
Groups []Group `yaml:"groups"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppConfig struct {
|
type App struct {
|
||||||
LogLevel string `yaml:"logLevel"`
|
DNSProxy DNSProxy `yaml:"dnsProxy"`
|
||||||
AdditionalTTL uint32 `yaml:"additionalTTL"`
|
Netfilter Netfilter `yaml:"netfilter"`
|
||||||
ChainPrefix string `yaml:"chainPrefix"`
|
Link []string `yaml:"link"`
|
||||||
IPSetPrefix string `yaml:"ipsetPrefix"`
|
LogLevel string `yaml:"logLevel"`
|
||||||
LinkName string `yaml:"linkName"`
|
}
|
||||||
TargetDNSServerAddress string `yaml:"targetDNSServerAddress"`
|
|
||||||
TargetDNSServerPort uint16 `yaml:"targetDNSServerPort"`
|
type DNSProxy struct {
|
||||||
ListenDNSPort uint16 `yaml:"listenDNSPort"`
|
Host DNSProxyServer `yaml:"host"`
|
||||||
|
Upstream DNSProxyServer `yaml:"upstream"`
|
||||||
|
DisableRemap53 bool `yaml:"disableRemap53"`
|
||||||
|
DisableFakePTR bool `yaml:"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"`
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,16 @@ package models
|
|||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestDomain_IsMatch_Plaintext(t *testing.T) {
|
func TestDomain_IsMatch_Domain(t *testing.T) {
|
||||||
rule := &Rule{
|
rule := &Rule{
|
||||||
Type: "plaintext",
|
Type: "domain",
|
||||||
Rule: "example.com",
|
Rule: "example.com",
|
||||||
}
|
}
|
||||||
if !rule.IsMatch("example.com") {
|
if !rule.IsMatch("example.com") {
|
||||||
t.Fatal("&Rule{Type: \"plaintext\", Rule: \"example.com\"}.IsMatch(\"example.com\") returns false")
|
t.Fatal("&Rule{Type: \"domain\", Rule: \"example.com\"}.IsMatch(\"example.com\") returns false")
|
||||||
}
|
}
|
||||||
if rule.IsMatch("noexample.com") {
|
if rule.IsMatch("noexample.com") {
|
||||||
t.Fatal("&Rule{Type: \"plaintext\", Rule: \"example.com\"}.IsMatch(\"noexample.com\") returns true")
|
t.Fatal("&Rule{Type: \"domain\", Rule: \"example.com\"}.IsMatch(\"noexample.com\") returns true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/coreos/go-iptables/iptables"
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
"github.com/vishvananda/netlink/nl"
|
"github.com/vishvananda/netlink/nl"
|
||||||
)
|
)
|
||||||
@ -36,6 +37,7 @@ 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"},
|
||||||
} {
|
} {
|
||||||
@ -111,6 +113,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +137,7 @@ func (r *IPSetToLink) insertIPRoute() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: Нормально отлавливать ошибку
|
// TODO: Нормально отлавливать ошибку
|
||||||
if err.Error() == "Link not found" {
|
if err.Error() == "Link not found" {
|
||||||
// TODO: Логи
|
log.Debug().Str("iface", r.IfaceName).Msg("interface not found (waiting for it to exist)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("error while getting interface: %w", err)
|
return fmt.Errorf("error while getting interface: %w", err)
|
||||||
|
@ -21,7 +21,8 @@ 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" {
|
||||||
err := r.IPTables.NewChain("nat", r.ChainName)
|
preroutingChain := r.ChainName + "_PRR"
|
||||||
|
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) {
|
||||||
@ -34,18 +35,62 @@ func (r *PortRemap) insertIPTablesRules(table string) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, iptablesArgs := range [][]string{
|
if r.IPTables.Proto() != iptables.ProtocolIPv6 {
|
||||||
{"-p", "tcp", "-d", addr.IP.String(), "--dport", strconv.Itoa(int(r.From)), "-j", "DNAT", "--to-destination", fmt.Sprintf(":%d", r.To)},
|
for _, iptablesArgs := range [][]string{
|
||||||
{"-p", "udp", "-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", fmt.Sprintf("%d", r.From), "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", r.To)},
|
||||||
err = r.IPTables.AppendUnique("nat", r.ChainName, iptablesArgs...)
|
} {
|
||||||
if err != nil {
|
err = r.IPTables.AppendUnique("nat", preroutingChain, iptablesArgs...)
|
||||||
return fmt.Errorf("failed to append rule: %w", err)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.IPTables.InsertUnique("nat", "PREROUTING", 1, "-j", r.ChainName)
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to linking chain: %w", err)
|
return fmt.Errorf("failed to linking chain: %w", err)
|
||||||
}
|
}
|
||||||
@ -57,7 +102,14 @@ func (r *PortRemap) insertIPTablesRules(table string) error {
|
|||||||
func (r *PortRemap) deleteIPTablesRules() []error {
|
func (r *PortRemap) deleteIPTablesRules() []error {
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
err := r.IPTables.DeleteIfExists("nat", "PREROUTING", "-j", r.ChainName)
|
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)
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
10
opt/etc/init.d/S99magitrickle
Executable file
10
opt/etc/init.d/S99magitrickle
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/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
|
@ -1,5 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
SOCKET_PATH="/opt/var/run/kvas2.sock"
|
SOCKET_PATH="/opt/var/run/magitrickle.sock"
|
||||||
if [ ! -S "$SOCKET_PATH" ]; then
|
if [ ! -S "$SOCKET_PATH" ]; then
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
@ -1,11 +1,24 @@
|
|||||||
appConfig:
|
configVersion: 0.1.0
|
||||||
additionalTTL: 216000
|
app:
|
||||||
chainPrefix: KVAS2_
|
dnsProxy:
|
||||||
ipsetPrefix: kvas2_
|
host:
|
||||||
linkName: br0
|
address: '[::]'
|
||||||
targetDNSServerAddress: 127.0.0.1
|
port: 3553
|
||||||
targetDNSServerPort: 53
|
upstream:
|
||||||
listenDNSPort: 3553
|
address: 127.0.0.1
|
||||||
|
port: 53
|
||||||
|
disableRemap53: false
|
||||||
|
disableFakePTR: false
|
||||||
|
disableDropAAAA: false
|
||||||
|
netfilter:
|
||||||
|
iptables:
|
||||||
|
chainPrefix: MT_
|
||||||
|
ipset:
|
||||||
|
tablePrefix: mt_
|
||||||
|
additionalTTL: 3600
|
||||||
|
link:
|
||||||
|
- br0
|
||||||
|
logLevel: info
|
||||||
groups:
|
groups:
|
||||||
- id: d663876a
|
- id: d663876a
|
||||||
name: Example
|
name: Example
|
@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
func TestLoop(t *testing.T) {
|
func TestLoop(t *testing.T) {
|
||||||
r := New()
|
r := New()
|
||||||
r.AddCNameRecord("1", "2", time.Minute)
|
r.AddCNameRecord("1", "2", 60)
|
||||||
r.AddCNameRecord("2", "1", time.Minute)
|
r.AddCNameRecord("2", "1", 60)
|
||||||
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}, time.Minute)
|
r.AddARecord("example.com", []byte{1, 2, 3, 4}, 60)
|
||||||
r.AddCNameRecord("gateway.example.com", "example.com", time.Minute)
|
r.AddCNameRecord("gateway.example.com", "example.com", 60)
|
||||||
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}, time.Minute)
|
r.AddARecord("example.com", []byte{1, 2, 3, 4}, 60)
|
||||||
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,7 +46,8 @@ 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}, -time.Minute)
|
r.AddARecord("example.com", []byte{1, 2, 3, 4}, 0)
|
||||||
|
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")
|
||||||
@ -63,7 +64,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", time.Minute)
|
r.AddCNameRecord("gateway.example.com", "example.com", 60)
|
||||||
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")
|
||||||
@ -72,8 +73,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", time.Minute)
|
r.AddCNameRecord("gateway.example.com", "example.com", 60)
|
||||||
r.AddARecord("gateway.example.com", []byte{1, 2, 3, 4}, time.Minute)
|
r.AddARecord("gateway.example.com", []byte{1, 2, 3, 4}, 60)
|
||||||
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")
|
||||||
@ -82,11 +83,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}, time.Minute)
|
r.AddARecord("1", []byte{1, 2, 3, 4}, 60)
|
||||||
r.AddCNameRecord("2", "1", time.Minute)
|
r.AddCNameRecord("2", "1", 60)
|
||||||
r.AddCNameRecord("3", "2", time.Minute)
|
r.AddCNameRecord("3", "2", 60)
|
||||||
r.AddCNameRecord("4", "2", time.Minute)
|
r.AddCNameRecord("4", "2", 60)
|
||||||
r.AddCNameRecord("5", "1", time.Minute)
|
r.AddCNameRecord("5", "1", 60)
|
||||||
aliases := r.GetAliases("1")
|
aliases := r.GetAliases("1")
|
||||||
if aliases == nil {
|
if aliases == nil {
|
||||||
t.Fatal("no aliases")
|
t.Fatal("no aliases")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user