306 lines
7.7 KiB
Go
306 lines
7.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"kvas2-go/models"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netlink/nl"
|
|
)
|
|
|
|
type GroupOptions struct {
|
|
Enabled bool
|
|
ipRule *netlink.Rule
|
|
ipRoute *netlink.Route
|
|
}
|
|
|
|
type Group struct {
|
|
*models.Group
|
|
ipsetName string
|
|
options GroupOptions
|
|
}
|
|
|
|
func (g *Group) HandleIPv4(names []string, address net.IP, ttl time.Duration) error {
|
|
if !g.options.Enabled {
|
|
return nil
|
|
}
|
|
|
|
ttlSeconds := uint32(ttl.Seconds())
|
|
|
|
DomainSearch:
|
|
for _, domain := range g.Domains {
|
|
if !domain.IsEnabled() {
|
|
continue
|
|
}
|
|
for _, name := range names {
|
|
if domain.IsMatch(name) {
|
|
err := netlink.IpsetAdd(g.ipsetName, &netlink.IPSetEntry{
|
|
IP: address,
|
|
Timeout: &ttlSeconds,
|
|
Replace: true,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to assign address: %w", err)
|
|
}
|
|
break DomainSearch
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *Group) Enable(a *App) error {
|
|
if g.options.Enabled {
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
|
|
markMap := make(map[uint32]struct{})
|
|
tableMap := map[int]struct{}{
|
|
0: {},
|
|
253: {},
|
|
254: {},
|
|
255: {},
|
|
}
|
|
var table int
|
|
var mark uint32
|
|
|
|
rules, err := netlink.RuleList(nl.FAMILY_ALL)
|
|
if err != nil {
|
|
return fmt.Errorf("error while getting rules: %w", err)
|
|
}
|
|
for _, rule := range rules {
|
|
markMap[rule.Mark] = struct{}{}
|
|
tableMap[rule.Table] = struct{}{}
|
|
}
|
|
|
|
routes, err := netlink.RouteListFiltered(nl.FAMILY_ALL, &netlink.Route{}, netlink.RT_FILTER_TABLE)
|
|
if err != nil {
|
|
return fmt.Errorf("error while getting routes: %w", err)
|
|
}
|
|
for _, route := range routes {
|
|
tableMap[route.Table] = struct{}{}
|
|
}
|
|
|
|
for {
|
|
if _, exists := tableMap[table]; exists {
|
|
table++
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
for {
|
|
if _, exists := markMap[mark]; exists {
|
|
mark++
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
rule := netlink.NewRule()
|
|
rule.Mark = mark
|
|
rule.Table = table
|
|
if err != nil {
|
|
return fmt.Errorf("error while getting free table: %w", err)
|
|
}
|
|
err = netlink.RuleAdd(rule)
|
|
if err != nil {
|
|
return fmt.Errorf("error while adding rule: %w", err)
|
|
}
|
|
g.options.ipRule = rule
|
|
|
|
iface, err := netlink.LinkByName(g.Interface)
|
|
if err != nil {
|
|
log.Warn().Str("interface", g.Interface).Msg("error while getting interface")
|
|
}
|
|
|
|
if iface != nil {
|
|
route := &netlink.Route{
|
|
LinkIndex: iface.Attrs().Index,
|
|
Table: rule.Table,
|
|
Dst: &net.IPNet{
|
|
IP: []byte{0, 0, 0, 0},
|
|
Mask: []byte{0, 0, 0, 0},
|
|
},
|
|
}
|
|
err = netlink.RouteAdd(route)
|
|
if err != nil {
|
|
return fmt.Errorf("error while adding route: %w", err)
|
|
}
|
|
g.options.ipRoute = route
|
|
}
|
|
|
|
defaultTimeout := uint32(300)
|
|
err = netlink.IpsetDestroy(g.ipsetName)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to destroy ipset: %w", err)
|
|
}
|
|
err = netlink.IpsetCreate(g.ipsetName, "hash:ip", netlink.IpsetCreateOptions{
|
|
Timeout: &defaultTimeout,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create ipset: %w", err)
|
|
}
|
|
|
|
//preroutingChainName := fmt.Sprintf("%sROUTING_%d_PREROUTING", a.Config.ChainPostfix, g.ID)
|
|
//
|
|
//err = a.IPTables.ClearChain("mangle", preroutingChainName)
|
|
//if err != nil {
|
|
// return fmt.Errorf("failed to clear chain: %w", err)
|
|
//}
|
|
//
|
|
//err = a.IPTables.AppendUnique("mangle", preroutingChainName, "-m", "set", "--match-set", g.ipsetName, "dst", "-j", "MARK", "--set-mark", strconv.Itoa(int(mark)))
|
|
//if err != nil {
|
|
// return fmt.Errorf("failed to create rule: %w", err)
|
|
//}
|
|
//
|
|
//err = a.IPTables.AppendUnique("mangle", "PREROUTING", "-j", preroutingChainName)
|
|
//if err != nil {
|
|
// return fmt.Errorf("failed to linking chain: %w", err)
|
|
//}
|
|
|
|
postroutingChainName := fmt.Sprintf("%sROUTING_%d_POSTROUTING", a.Config.ChainPostfix, g.ID)
|
|
|
|
err = a.IPTables.ClearChain("nat", postroutingChainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to clear chain: %w", err)
|
|
}
|
|
|
|
err = a.IPTables.AppendUnique("nat", postroutingChainName, "-o", g.Interface, "-j", "MASQUERADE")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create rule: %w", err)
|
|
}
|
|
|
|
err = a.IPTables.AppendUnique("nat", "POSTROUTING", "-j", postroutingChainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to linking chain: %w", err)
|
|
}
|
|
|
|
// Chain name
|
|
chainName := fmt.Sprintf("%sROUTING_%d", a.Config.ChainPostfix, g.ID)
|
|
|
|
// Clear existing chain
|
|
err = a.IPTables.ClearChain("mangle", chainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to clear chain: %w", err)
|
|
}
|
|
|
|
// Append rules to the chain
|
|
ruless := []struct {
|
|
Args []string
|
|
}{
|
|
// Source: https://github.com/qzeleza/kvas/blob/3fdbbd1ace7b57b11bf88d8db3882d94a1d6e01c/opt/etc/ndm/ndm#L194-L206
|
|
{Args: []string{"-m", "set", "!", "--match-set", g.ipsetName, "dst", "-j", "RETURN"}},
|
|
{Args: []string{"-j", "CONNMARK", "--restore-mark"}},
|
|
{Args: []string{"-m", "mark", "--mark", strconv.Itoa(int(mark)), "-j", "RETURN"}},
|
|
//This command not working
|
|
//{Args: []string{"--syn", "-j", "MARK", "--set-mark", strconv.Itoa(int(mark))}},
|
|
{Args: []string{"-m", "conntrack", "--ctstate", "NEW", "-j", "MARK", "--set-mark", strconv.Itoa(int(mark))}},
|
|
{Args: []string{"-j", "CONNMARK", "--save-mark"}},
|
|
}
|
|
|
|
for _, rule := range ruless {
|
|
err = a.IPTables.AppendUnique("mangle", chainName, rule.Args...)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to append rule: %w", err)
|
|
}
|
|
}
|
|
|
|
// Append rules to PREROUTING and OUTPUT chains
|
|
err = a.IPTables.AppendUnique("mangle", "PREROUTING", "-m", "set", "--match-set", g.ipsetName, "dst", "-j", chainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to append rule to PREROUTING: %w", err)
|
|
}
|
|
|
|
err = a.IPTables.AppendUnique("mangle", "OUTPUT", "-m", "set", "--match-set", g.ipsetName, "dst", "-j", chainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to append rule to OUTPUT: %w", err)
|
|
}
|
|
|
|
g.options.Enabled = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *Group) Disable(a *App) error {
|
|
if !g.options.Enabled {
|
|
return nil
|
|
}
|
|
|
|
var err error
|
|
|
|
//preroutingChainName := fmt.Sprintf("%sROUTING_%d_PREROUTING", a.Config.ChainPostfix, g.ID)
|
|
//
|
|
//err = a.IPTables.DeleteIfExists("mangle", "PREROUTING", "-j", preroutingChainName)
|
|
//if err != nil {
|
|
// return fmt.Errorf("failed to unlinking chain: %w", err)
|
|
//}
|
|
//
|
|
//err = a.IPTables.ClearAndDeleteChain("mangle", preroutingChainName)
|
|
//if err != nil {
|
|
// return fmt.Errorf("failed to delete chain: %w", err)
|
|
//}
|
|
|
|
postroutingChainName := fmt.Sprintf("%sROUTING_%d_POSTROUTING", a.Config.ChainPostfix, g.ID)
|
|
|
|
err = a.IPTables.DeleteIfExists("nat", "POSTROUTING", "-j", postroutingChainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unlinking chain: %w", err)
|
|
}
|
|
|
|
err = a.IPTables.ClearAndDeleteChain("nat", postroutingChainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete chain: %w", err)
|
|
}
|
|
|
|
chainName := fmt.Sprintf("%sROUTING_%d", a.Config.ChainPostfix, g.ID)
|
|
err = a.IPTables.DeleteIfExists("mangle", "PREROUTING", "-m", "set", "--match-set", g.ipsetName, "dst", "-j", chainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete rule to PREROUTING: %w", err)
|
|
}
|
|
|
|
err = a.IPTables.DeleteIfExists("mangle", "OUTPUT", "-m", "set", "--match-set", g.ipsetName, "dst", "-j", chainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete rule to OUTPUT: %w", err)
|
|
}
|
|
|
|
err = a.IPTables.ClearAndDeleteChain("mangle", chainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete chain: %w", err)
|
|
}
|
|
|
|
if g.options.ipRule != nil {
|
|
err = netlink.RuleDel(g.options.ipRule)
|
|
if err != nil {
|
|
return fmt.Errorf("error while deleting rule: %w", err)
|
|
}
|
|
g.options.ipRule = nil
|
|
}
|
|
|
|
if g.options.ipRoute != nil {
|
|
err = netlink.RouteDel(g.options.ipRoute)
|
|
if err != nil {
|
|
return fmt.Errorf("error while deleting route: %w", err)
|
|
}
|
|
g.options.ipRule = nil
|
|
}
|
|
|
|
err = netlink.IpsetDestroy(g.ipsetName)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to destroy ipset: %w", err)
|
|
}
|
|
|
|
g.options.Enabled = false
|
|
|
|
return nil
|
|
}
|