MagiTrickle/kvas2.go

587 lines
14 KiB
Go
Raw Normal View History

2024-08-26 19:10:40 +03:00
package main
import (
"context"
2024-08-27 03:07:58 +03:00
"errors"
2024-08-26 19:10:40 +03:00
"fmt"
2024-08-30 04:44:08 +03:00
"net"
2024-10-21 00:03:51 +03:00
"os"
2025-02-11 02:50:13 +03:00
"strconv"
2024-09-06 16:50:56 +03:00
"strings"
2024-08-27 03:07:58 +03:00
"time"
2025-02-11 02:50:13 +03:00
"kvas2-go/dns-mitm-proxy"
2024-08-26 19:10:40 +03:00
"kvas2-go/models"
2024-09-06 14:24:55 +03:00
"kvas2-go/netfilter-helper"
2025-02-11 02:50:13 +03:00
"kvas2-go/records"
2024-09-04 09:15:03 +03:00
"github.com/google/uuid"
"github.com/miekg/dns"
2024-09-04 09:15:03 +03:00
"github.com/rs/zerolog/log"
2024-09-06 15:48:15 +03:00
"github.com/vishvananda/netlink"
2024-10-22 23:23:07 +03:00
"github.com/vishvananda/netlink/nl"
2024-08-27 03:07:58 +03:00
)
var (
2024-08-30 04:29:05 +03:00
ErrAlreadyRunning = errors.New("already running")
2024-08-27 03:07:58 +03:00
ErrGroupIDConflict = errors.New("group id conflict")
2024-08-26 19:10:40 +03:00
)
type Config struct {
MinimalTTL time.Duration
2024-10-21 22:36:02 +03:00
ChainPrefix string
IpSetPrefix string
2024-10-22 23:23:07 +03:00
LinkName string
2024-08-26 19:10:40 +03:00
TargetDNSServerAddress string
2025-01-27 01:14:17 +03:00
ListenDNSPort uint16
2024-09-05 09:53:24 +03:00
UseSoftwareRouting bool
2024-08-26 19:10:40 +03:00
}
type App struct {
Config Config
2025-02-11 02:50:13 +03:00
DNSMITM *dnsMitmProxy.DNSMITMProxy
2024-09-14 15:16:50 +03:00
NetfilterHelper4 *netfilterHelper.NetfilterHelper
2024-10-22 01:52:31 +03:00
NetfilterHelper6 *netfilterHelper.NetfilterHelper
2025-02-11 02:50:13 +03:00
Records *records.Records
Groups map[uuid.UUID]*Group
2024-08-30 04:29:05 +03:00
2024-10-22 23:23:07 +03:00
Link netlink.Link
2024-09-14 15:16:50 +03:00
isRunning bool
dnsOverrider4 *netfilterHelper.PortRemap
2024-10-22 01:52:31 +03:00
dnsOverrider6 *netfilterHelper.PortRemap
2024-08-26 19:10:40 +03:00
}
2024-10-21 00:03:51 +03:00
func (a *App) handleLink(event netlink.LinkUpdate) {
switch event.Change {
2025-02-11 02:50:13 +03:00
case 0x00000001:
2024-10-21 00:03:51 +03:00
log.Debug().
Str("interface", event.Link.Attrs().Name).
Str("operstatestr", event.Attrs().OperState.String()).
Int("operstate", int(event.Attrs().OperState)).
Msg("interface change")
switch event.Attrs().OperState {
case netlink.OperUp:
ifaceName := event.Link.Attrs().Name
2024-10-21 00:03:51 +03:00
for _, group := range a.Groups {
if group.Interface != ifaceName {
continue
}
err := group.ifaceToIPSetNAT.IfaceHandle()
if err != nil {
log.Error().Str("group", group.ID.String()).Err(err).Msg("error while handling interface up")
2024-10-21 00:03:51 +03:00
}
}
}
case 0xFFFFFFFF:
switch event.Header.Type {
case 16:
log.Debug().
Str("interface", event.Link.Attrs().Name).
Int("type", int(event.Header.Type)).
Msg("interface add")
case 17:
log.Debug().
Str("interface", event.Link.Attrs().Name).
Int("type", int(event.Header.Type)).
Msg("interface del")
}
2024-08-30 04:29:05 +03:00
}
2024-10-21 00:03:51 +03:00
}
2024-08-26 19:10:40 +03:00
func (a *App) start(ctx context.Context) (err error) {
2024-10-21 00:03:51 +03:00
newCtx, cancel := context.WithCancel(ctx)
defer cancel()
2024-08-26 19:10:40 +03:00
errChan := make(chan error)
/*
DNS Proxy
*/
go func() {
2025-02-11 02:50:13 +03:00
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)
if err != nil {
errChan <- fmt.Errorf("failed to serve DNS UDP proxy: %v", err)
2025-02-11 02:50:13 +03:00
return
}
}()
2024-10-21 00:03:51 +03:00
go func() {
2025-02-11 02:50:13 +03:00
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)
2024-10-21 00:03:51 +03:00
if err != nil {
errChan <- fmt.Errorf("failed to serve DNS TCP proxy: %v", err)
2025-02-11 02:50:13 +03:00
return
2024-08-26 19:10:40 +03:00
}
}()
2024-10-22 23:23:07 +03:00
addrList, err := netlink.AddrList(a.Link, nl.FAMILY_ALL)
if err != nil {
return fmt.Errorf("failed to list address of interface: %w", err)
2024-10-22 23:23:07 +03:00
}
2025-01-27 01:14:17 +03:00
a.dnsOverrider4 = a.NetfilterHelper4.PortRemap(fmt.Sprintf("%sDNSOR", a.Config.ChainPrefix), 53, a.Config.ListenDNSPort, addrList)
2024-10-21 00:03:51 +03:00
err = a.dnsOverrider4.Enable()
if err != nil {
2024-10-22 01:52:31 +03:00
return fmt.Errorf("failed to override DNS (IPv4): %v", err)
2024-10-21 00:03:51 +03:00
}
2025-02-11 02:50:13 +03:00
defer func() { _ = a.dnsOverrider4.Disable() }()
2024-08-26 19:10:40 +03:00
2025-01-27 01:14:17 +03:00
a.dnsOverrider6 = a.NetfilterHelper6.PortRemap(fmt.Sprintf("%sDNSOR", a.Config.ChainPrefix), 53, a.Config.ListenDNSPort, addrList)
2024-10-22 01:52:31 +03:00
err = a.dnsOverrider6.Enable()
if err != nil {
return fmt.Errorf("failed to override DNS (IPv6): %v", err)
}
2025-02-11 02:50:13 +03:00
defer func() { _ = a.dnsOverrider6.Disable() }()
2024-10-22 01:52:31 +03:00
/*
Groups
*/
2024-09-06 14:52:42 +03:00
for _, group := range a.Groups {
err = group.Enable()
2024-08-27 03:19:10 +03:00
if err != nil {
2024-10-21 00:03:51 +03:00
return fmt.Errorf("failed to enable group: %w", err)
2024-08-27 03:19:10 +03:00
}
}
2024-10-21 00:03:51 +03:00
defer func() {
for _, group := range a.Groups {
_ = group.Disable()
2024-08-26 19:10:40 +03:00
}
}()
/*
Socket (for netfilter.d events)
*/
2024-10-21 00:03:51 +03:00
socketPath := "/opt/var/run/kvas2-go.sock"
err = os.Remove(socketPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to remove existed UNIX socket: %w", err)
}
socket, err := net.Listen("unix", socketPath)
2024-09-06 16:50:56 +03:00
if err != nil {
2024-10-21 00:03:51 +03:00
return fmt.Errorf("error while serve UNIX socket: %v", err)
2024-09-06 16:50:56 +03:00
}
2024-10-21 00:03:51 +03:00
defer func() {
_ = socket.Close()
_ = os.Remove(socketPath)
}()
2024-09-06 16:50:56 +03:00
go func() {
for {
2025-02-11 02:50:13 +03:00
if newCtx.Err() != nil {
return
}
2024-10-21 00:03:51 +03:00
conn, err := socket.Accept()
2024-09-06 16:50:56 +03:00
if err != nil {
2024-10-21 00:03:51 +03:00
if !strings.Contains(err.Error(), "use of closed network connection") {
log.Error().Err(err).Msg("error while listening unix socket")
}
break
2024-09-06 16:50:56 +03:00
}
go func(conn net.Conn) {
2025-02-11 02:50:13 +03:00
defer func() { _ = conn.Close() }()
2024-09-06 16:50:56 +03:00
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
return
}
args := strings.Split(string(buf[:n]), ":")
if len(args) == 3 && args[0] == "netfilter.d" {
log.Debug().Str("table", args[2]).Msg("netfilter.d event")
2024-09-14 15:16:50 +03:00
if a.dnsOverrider4.Enabled {
err := a.dnsOverrider4.PutIPTable(args[2])
2024-09-06 16:50:56 +03:00
if err != nil {
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
}
}
2025-02-11 02:50:13 +03:00
if a.dnsOverrider6.Enabled {
err = a.dnsOverrider6.PutIPTable(args[2])
if err != nil {
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
}
}
2024-09-06 16:50:56 +03:00
for _, group := range a.Groups {
if group.ifaceToIPSetNAT.Enabled {
err := group.ifaceToIPSetNAT.PutIPTable(args[2])
2024-09-06 16:50:56 +03:00
if err != nil {
log.Error().Err(err).Msg("error while fixing iptables after netfilter.d")
}
}
}
}
}(conn)
}
}()
/*
Interface updates
*/
linkUpdateChannel := make(chan netlink.LinkUpdate)
linkUpdateDone := make(chan struct{})
err = netlink.LinkSubscribe(linkUpdateChannel, linkUpdateDone)
2024-10-21 00:03:51 +03:00
if err != nil {
return fmt.Errorf("failed to subscribe to link updates: %w", err)
}
defer func() {
close(linkUpdateDone)
2024-10-21 00:03:51 +03:00
}()
/*
Global loop
*/
2024-09-06 15:48:15 +03:00
for {
select {
case event := <-linkUpdateChannel:
2024-10-21 00:03:51 +03:00
a.handleLink(event)
case err := <-errChan:
return err
2024-09-06 15:48:15 +03:00
case <-ctx.Done():
2024-10-21 00:03:51 +03:00
return nil
2024-09-06 15:48:15 +03:00
}
2024-08-26 19:10:40 +03:00
}
2024-10-21 00:03:51 +03:00
}
2024-08-26 19:10:40 +03:00
func (a *App) Start(ctx context.Context) (err error) {
2024-10-21 00:03:51 +03:00
if a.isRunning {
return ErrAlreadyRunning
2024-08-27 03:19:10 +03:00
}
2024-10-21 00:03:51 +03:00
a.isRunning = true
defer func() {
a.isRunning = false
}()
2024-08-27 03:19:10 +03:00
2024-10-21 00:03:51 +03:00
defer func() {
if r := recover(); r != nil {
var ok bool
if err, ok = r.(error); !ok {
err = fmt.Errorf("%v", r)
2024-10-21 00:03:51 +03:00
}
err = fmt.Errorf("recovered error: %w", err)
2024-08-30 04:29:05 +03:00
}
2024-10-21 00:03:51 +03:00
}()
err = a.start(ctx)
2024-08-30 04:29:05 +03:00
2024-10-21 00:03:51 +03:00
return err
2024-08-27 03:07:58 +03:00
}
2024-09-14 16:13:59 +03:00
func (a *App) AddGroup(group *models.Group) error {
if _, exists := a.Groups[group.ID]; exists {
return ErrGroupIDConflict
}
ipsetName := fmt.Sprintf("%s%8x", a.Config.IpSetPrefix, group.ID.ID())
2024-09-14 18:20:44 +03:00
ipset, err := a.NetfilterHelper4.IPSet(ipsetName)
if err != nil {
return fmt.Errorf("failed to initialize ipset: %w", err)
}
2024-09-14 16:13:59 +03:00
grp := &Group{
Group: group,
iptables: a.NetfilterHelper4.IPTables,
ipset: ipset,
2025-02-11 02:50:13 +03:00
ifaceToIPSetNAT: a.NetfilterHelper4.IfaceToIPSet(fmt.Sprintf("%sR_%8x", a.Config.ChainPrefix, group.ID.ID()), group.Interface, ipsetName, false),
2024-09-14 16:13:59 +03:00
}
2025-02-11 02:50:13 +03:00
grp.ifaceToIPSetNAT.SoftwareMode = a.Config.UseSoftwareRouting
a.Groups[grp.ID] = grp
2024-09-14 18:20:44 +03:00
return a.SyncGroup(grp)
}
2024-09-14 16:13:59 +03:00
2024-09-14 18:20:44 +03:00
func (a *App) SyncGroup(group *Group) error {
now := time.Now()
2025-02-11 02:50:13 +03:00
addresses := make(map[string]time.Duration)
2024-09-14 18:20:44 +03:00
knownDomains := a.Records.ListKnownDomains()
for _, domain := range group.Rules {
2024-09-14 18:20:44 +03:00
if !domain.IsEnabled() {
2024-09-14 16:13:59 +03:00
continue
}
2024-09-14 18:20:44 +03:00
for _, domainName := range knownDomains {
2024-09-14 16:13:59 +03:00
if !domain.IsMatch(domainName) {
continue
}
2025-02-11 02:50:13 +03:00
domainAddresses := a.Records.GetARecords(domainName)
for _, address := range domainAddresses {
2024-09-14 18:20:44 +03:00
ttl := now.Sub(address.Deadline)
2025-02-11 02:50:13 +03:00
if oldTTL, ok := addresses[string(address.Address)]; !ok || ttl > oldTTL {
addresses[string(address.Address)] = ttl
2024-09-14 16:13:59 +03:00
}
}
2024-09-14 18:20:44 +03:00
}
}
2025-02-11 02:50:13 +03:00
currentAddresses, err := group.ListIPv4()
if err != nil {
return fmt.Errorf("failed to get old ipset list: %w", err)
}
for addr, ttl := range addresses {
// TODO: Check TTL
if _, exists := currentAddresses[addr]; exists {
2024-09-14 18:20:44 +03:00
continue
}
ip := net.IP(addr)
err = group.AddIPv4(ip, ttl)
if err != nil {
log.Error().
Str("address", ip.String()).
Err(err).
Msg("failed to add address")
2025-02-11 02:50:13 +03:00
} else {
log.Trace().
Str("address", ip.String()).
Err(err).
Msg("add address")
2024-09-14 18:20:44 +03:00
}
}
2025-02-11 02:50:13 +03:00
for addr := range currentAddresses {
if _, ok := addresses[addr]; ok {
2024-09-14 18:20:44 +03:00
continue
}
ip := net.IP(addr)
err = group.DelIPv4(ip)
if err != nil {
log.Error().
Str("address", ip.String()).
Err(err).
Msg("failed to delete address")
2024-09-14 19:26:23 +03:00
} else {
log.Trace().
Str("address", ip.String()).
Err(err).
2025-02-11 02:50:13 +03:00
Msg("del address")
2024-09-14 16:13:59 +03:00
}
}
return nil
}
2024-08-30 04:44:08 +03:00
func (a *App) ListInterfaces() ([]net.Interface, error) {
interfaceNames := make([]net.Interface, 0)
interfaces, err := net.Interfaces()
if err != nil {
return nil, fmt.Errorf("failed to get interfaces: %w", err)
}
for _, iface := range interfaces {
if iface.Flags&net.FlagPointToPoint == 0 {
continue
}
interfaceNames = append(interfaceNames, iface)
}
return interfaceNames, nil
}
func (a *App) processARecord(aRecord dns.A) {
2024-09-04 09:15:03 +03:00
log.Trace().
Str("name", aRecord.Hdr.Name).
Str("address", aRecord.A.String()).
Int("ttl", int(aRecord.Hdr.Ttl)).
2024-09-04 09:15:03 +03:00
Msg("processing a record")
ttlDuration := time.Duration(aRecord.Hdr.Ttl) * time.Second
2024-08-27 01:44:17 +03:00
if ttlDuration < a.Config.MinimalTTL {
ttlDuration = a.Config.MinimalTTL
}
2025-02-11 02:50:13 +03:00
a.Records.AddARecord(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1], aRecord.A, ttlDuration)
2024-08-27 01:44:17 +03:00
2025-02-11 02:50:13 +03:00
names := a.Records.GetAliases(aRecord.Hdr.Name[:len(aRecord.Hdr.Name)-1])
2024-08-27 01:44:17 +03:00
for _, group := range a.Groups {
Rule:
for _, domain := range group.Rules {
2024-09-14 18:36:23 +03:00
if !domain.IsEnabled() {
continue
}
2024-09-14 18:20:44 +03:00
for _, name := range names {
if !domain.IsMatch(name) {
continue
}
2025-02-11 02:50:13 +03:00
// TODO: Check already existed
err := group.AddIPv4(aRecord.A, ttlDuration)
2024-09-14 18:20:44 +03:00
if err != nil {
log.Error().
Str("address", aRecord.A.String()).
2024-09-14 18:20:44 +03:00
Err(err).
Msg("failed to add address")
2024-09-14 19:26:23 +03:00
} else {
log.Trace().
Str("address", aRecord.A.String()).
Str("aRecordDomain", aRecord.Hdr.Name).
2024-09-14 19:26:23 +03:00
Str("cNameDomain", name).
Err(err).
Msg("add address")
2024-09-14 18:20:44 +03:00
}
break Rule
2024-09-14 18:20:44 +03:00
}
2024-08-27 01:44:17 +03:00
}
}
}
func (a *App) processCNameRecord(cNameRecord dns.CNAME) {
2024-09-14 18:20:44 +03:00
log.Trace().
Str("name", cNameRecord.Hdr.Name).
Str("cname", cNameRecord.Target).
Int("ttl", int(cNameRecord.Hdr.Ttl)).
2024-09-14 18:20:44 +03:00
Msg("processing cname record")
ttlDuration := time.Duration(cNameRecord.Hdr.Ttl) * time.Second
2024-08-27 01:44:17 +03:00
if ttlDuration < a.Config.MinimalTTL {
ttlDuration = a.Config.MinimalTTL
}
2025-02-11 02:50:13 +03:00
a.Records.AddCNameRecord(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1], cNameRecord.Target, ttlDuration)
2024-09-14 19:26:23 +03:00
// TODO: Optimization
now := time.Now()
2025-02-11 02:50:13 +03:00
aRecords := a.Records.GetARecords(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1])
names := a.Records.GetAliases(cNameRecord.Hdr.Name[:len(cNameRecord.Hdr.Name)-1])
2024-09-14 19:26:23 +03:00
for _, group := range a.Groups {
Rule:
for _, domain := range group.Rules {
2024-09-14 19:26:23 +03:00
if !domain.IsEnabled() {
continue
}
for _, name := range names {
if !domain.IsMatch(name) {
continue
}
for _, aRecord := range aRecords {
err := group.AddIPv4(aRecord.Address, now.Sub(aRecord.Deadline))
if err != nil {
log.Error().
Str("address", aRecord.Address.String()).
Err(err).
Msg("failed to add address")
} else {
log.Trace().
Str("address", aRecord.Address.String()).
Str("cNameDomain", name).
Err(err).
Msg("add address")
}
}
continue Rule
2024-09-14 19:26:23 +03:00
}
}
}
2024-08-27 01:44:17 +03:00
}
func (a *App) handleRecord(rr dns.RR) {
2024-08-30 03:37:00 +03:00
switch v := rr.(type) {
case *dns.A:
a.processARecord(*v)
case *dns.CNAME:
a.processCNameRecord(*v)
2024-08-30 03:37:00 +03:00
default:
2024-08-26 19:10:40 +03:00
}
2024-08-30 03:37:00 +03:00
}
2024-08-26 19:10:40 +03:00
func (a *App) handleMessage(msg dns.Msg) {
for _, rr := range msg.Answer {
2024-08-30 03:37:00 +03:00
a.handleRecord(rr)
2024-08-26 19:10:40 +03:00
}
}
func New(config Config) (*App, error) {
var err error
app := &App{}
app.Config = config
2025-02-11 02:50:13 +03:00
app.DNSMITM = dnsMitmProxy.New()
app.DNSMITM.TargetDNSServerAddress = app.Config.TargetDNSServerAddress
app.DNSMITM.TargetDNSServerPort = 53
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)
return &respMsg, nil
}
2025-02-11 02:50:13 +03:00
app.Records = records.New()
app.Groups = make(map[uuid.UUID]*Group, 0)
2024-10-22 23:23:07 +03:00
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
2024-09-14 15:16:50 +03:00
nh4, err := netfilterHelper.New(false)
2024-08-26 19:10:40 +03:00
if err != nil {
2024-09-06 14:24:55 +03:00
return nil, fmt.Errorf("netfilter helper init fail: %w", err)
2024-08-26 19:10:40 +03:00
}
2024-09-14 15:16:50 +03:00
app.NetfilterHelper4 = nh4
2024-10-21 23:11:59 +03:00
err = app.NetfilterHelper4.ClearIPTables(app.Config.ChainPrefix)
if err != nil {
return nil, fmt.Errorf("failed to clear iptables: %w", err)
}
2024-08-26 19:10:40 +03:00
2024-10-22 01:52:31 +03:00
nh6, err := netfilterHelper.New(true)
if err != nil {
return nil, fmt.Errorf("netfilter helper init fail: %w", err)
}
app.NetfilterHelper6 = nh6
err = app.NetfilterHelper6.ClearIPTables(app.Config.ChainPrefix)
if err != nil {
return nil, fmt.Errorf("failed to clear iptables: %w", err)
}
app.Groups = make(map[uuid.UUID]*Group)
2024-08-26 19:10:40 +03:00
return app, nil
}