200 lines
3.9 KiB
Go
200 lines
3.9 KiB
Go
package ipHelper
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
ErrMaxTableSize = errors.New("max table size")
|
|
ErrMaxFwMarkSize = errors.New("max fwmark size")
|
|
)
|
|
|
|
func ExecIp(args ...string) ([]byte, error) {
|
|
cmd := exec.Command("ip", args...)
|
|
var out bytes.Buffer
|
|
cmd.Stdout = &out
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return out.Bytes(), nil
|
|
}
|
|
|
|
func GetUsedFwMarks() ([]uint32, error) {
|
|
markMap := make(map[uint32]struct{})
|
|
|
|
out, err := ExecIp("rule", "show")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error while getting rules: %w", err)
|
|
}
|
|
|
|
re := regexp.MustCompile(`fwmark\s+0x([0-9a-fA-F]+)`)
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
match := re.FindStringSubmatch(line)
|
|
if len(match) < 2 {
|
|
continue
|
|
}
|
|
|
|
hexStr := match[1]
|
|
hexValue, err := strconv.ParseInt(hexStr, 16, 64)
|
|
if err == nil {
|
|
markMap[uint32(hexValue)] = struct{}{}
|
|
}
|
|
}
|
|
|
|
marks := make([]uint32, len(markMap))
|
|
counter := 0
|
|
for mark, _ := range markMap {
|
|
marks[counter] = mark
|
|
counter++
|
|
}
|
|
|
|
return marks, nil
|
|
}
|
|
|
|
func GetUnusedFwMark() (uint32, error) {
|
|
usedFwMarks, err := GetUsedFwMarks()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("error while getting used fwmarks: %w", err)
|
|
}
|
|
|
|
fwmark := uint32(1)
|
|
for slices.Contains(usedFwMarks, fwmark) {
|
|
fwmark++
|
|
if fwmark == 0xFFFFFFFF {
|
|
return 0, ErrMaxFwMarkSize
|
|
}
|
|
}
|
|
return fwmark, nil
|
|
}
|
|
|
|
func GetTableAliases() (map[string]uint16, error) {
|
|
tables := map[string]uint16{
|
|
"unspec": 0,
|
|
"default": 253,
|
|
"main": 254,
|
|
"local": 255,
|
|
}
|
|
|
|
file, err := os.Open("/opt/etc/iproute2/rt_tables")
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return tables, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if strings.HasPrefix(line, "#") || len(strings.TrimSpace(line)) == 0 {
|
|
continue
|
|
}
|
|
|
|
parts := strings.Fields(line)
|
|
if len(parts) >= 2 {
|
|
tableID, err := strconv.Atoi(parts[0])
|
|
if err == nil {
|
|
tables[parts[1]] = uint16(tableID)
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return tables, nil
|
|
}
|
|
|
|
func GetUsedTables() ([]uint16, error) {
|
|
tableMap := map[uint16]struct{}{
|
|
0: {},
|
|
253: {},
|
|
254: {},
|
|
255: {},
|
|
}
|
|
|
|
tableAliases, err := GetTableAliases()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error while getting table aliases: %w", err)
|
|
}
|
|
|
|
out, err := ExecIp("route", "show", "table", "all")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error while getting routes: %w", err)
|
|
}
|
|
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
if strings.Contains(line, "table") {
|
|
parts := strings.Fields(line)
|
|
for i, part := range parts {
|
|
if part == "table" && i+1 < len(parts) {
|
|
tableNum, ok := tableAliases[parts[i+1]]
|
|
if !ok {
|
|
tableNumInt, _ := strconv.Atoi(parts[i+1])
|
|
tableNum = uint16(tableNumInt)
|
|
}
|
|
tableMap[tableNum] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out, err = ExecIp("rule", "show")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error while getting rules: %w", err)
|
|
}
|
|
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
if strings.Contains(line, "lookup") {
|
|
parts := strings.Fields(line)
|
|
for i, part := range parts {
|
|
if part == "lookup" && i+1 < len(parts) {
|
|
tableNum, ok := tableAliases[parts[i+1]]
|
|
if !ok {
|
|
tableNumInt, _ := strconv.Atoi(parts[i+1])
|
|
tableNum = uint16(tableNumInt)
|
|
}
|
|
tableMap[tableNum] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
tables := make([]uint16, len(tableMap))
|
|
counter := 0
|
|
for table, _ := range tableMap {
|
|
tables[counter] = table
|
|
counter++
|
|
}
|
|
|
|
return tables, nil
|
|
}
|
|
|
|
func GetUnusedTable() (uint16, error) {
|
|
usedTables, err := GetUsedTables()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("error while getting used tables: %w", err)
|
|
}
|
|
|
|
tableID := uint16(1)
|
|
for slices.Contains(usedTables, tableID) {
|
|
tableID++
|
|
if tableID > 0x3FF {
|
|
return 0, ErrMaxTableSize
|
|
}
|
|
}
|
|
return tableID, nil
|
|
}
|