117 lines
2.8 KiB
Go
117 lines
2.8 KiB
Go
package trafficmode
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type RunCommandFunc func(name string, args ...string) (stdout string, stderr string, code int, err error)
|
|
|
|
type RulesConfig struct {
|
|
RoutesTableName string
|
|
Mark string
|
|
MarkIngress string
|
|
PrefSelective int
|
|
PrefFull int
|
|
PrefMarkIngressReply int
|
|
ModeFull string
|
|
ModeSelective string
|
|
ModeDirect string
|
|
}
|
|
|
|
type RulesState struct {
|
|
Mark bool
|
|
Full bool
|
|
IngressReply bool
|
|
}
|
|
|
|
func PrefStr(v int) string {
|
|
return strconv.Itoa(v)
|
|
}
|
|
|
|
func ReadRules(cfg RulesConfig, run RunCommandFunc) RulesState {
|
|
if run == nil {
|
|
return RulesState{}
|
|
}
|
|
out, _, _, _ := run("ip", "rule", "show")
|
|
var st RulesState
|
|
for _, line := range strings.Split(out, "\n") {
|
|
l := strings.ToLower(strings.TrimSpace(line))
|
|
if l == "" {
|
|
continue
|
|
}
|
|
fields := strings.Fields(l)
|
|
if len(fields) == 0 {
|
|
continue
|
|
}
|
|
prefRaw := strings.TrimSuffix(fields[0], ":")
|
|
pref, _ := strconv.Atoi(prefRaw)
|
|
switch pref {
|
|
case cfg.PrefSelective:
|
|
if strings.Contains(l, "lookup "+cfg.RoutesTableName) {
|
|
st.Mark = true
|
|
}
|
|
case cfg.PrefFull:
|
|
if strings.Contains(l, "lookup "+cfg.RoutesTableName) {
|
|
st.Full = true
|
|
}
|
|
case cfg.PrefMarkIngressReply:
|
|
if strings.Contains(l, "fwmark "+strings.ToLower(cfg.MarkIngress)) && strings.Contains(l, "lookup main") {
|
|
st.IngressReply = true
|
|
}
|
|
}
|
|
}
|
|
return st
|
|
}
|
|
|
|
func DetectAppliedMode(cfg RulesConfig, rules RulesState) string {
|
|
if rules.Full {
|
|
return cfg.ModeFull
|
|
}
|
|
if rules.Mark {
|
|
return cfg.ModeSelective
|
|
}
|
|
return cfg.ModeDirect
|
|
}
|
|
|
|
func ProbeMode(cfg RulesConfig, mode string, iface string, run RunCommandFunc) (bool, string) {
|
|
if run == nil {
|
|
return false, "run command func is nil"
|
|
}
|
|
mode = strings.ToLower(strings.TrimSpace(mode))
|
|
iface = strings.TrimSpace(iface)
|
|
|
|
args := []string{"-4", "route", "get", "1.1.1.1"}
|
|
if mode == strings.ToLower(cfg.ModeSelective) {
|
|
args = append(args, "mark", cfg.Mark)
|
|
}
|
|
|
|
out, _, code, err := run("ip", args...)
|
|
if err != nil || code != 0 {
|
|
if err == nil {
|
|
err = fmt.Errorf("ip route get exited with %d", code)
|
|
}
|
|
return false, err.Error()
|
|
}
|
|
|
|
text := strings.ToLower(out)
|
|
switch mode {
|
|
case strings.ToLower(cfg.ModeDirect):
|
|
if strings.Contains(text, " table "+strings.ToLower(cfg.RoutesTableName)) {
|
|
return false, "route probe still uses agvpn table"
|
|
}
|
|
return true, "route probe direct path ok"
|
|
case strings.ToLower(cfg.ModeFull), strings.ToLower(cfg.ModeSelective):
|
|
if iface == "" {
|
|
return false, "route probe has empty iface"
|
|
}
|
|
if !strings.Contains(text, "dev "+strings.ToLower(iface)) {
|
|
return false, fmt.Sprintf("route probe mismatch: expected dev %s", iface)
|
|
}
|
|
return true, "route probe vpn path ok"
|
|
default:
|
|
return false, "route probe unknown mode"
|
|
}
|
|
}
|