Files
elmprodvpn/selective-vpn-api/app/traffic_appmarks_ops_mutations_add.go

103 lines
2.9 KiB
Go

package app
import (
"fmt"
"strings"
"time"
)
func appMarksAdd(target string, id uint64, cgAbs string, rel string, level int, unit string, command string, appKey string, ttlSec int, vpnIface string) error {
target = strings.ToLower(strings.TrimSpace(target))
if target != "vpn" && target != "direct" {
return fmt.Errorf("invalid target")
}
if id == 0 {
return fmt.Errorf("invalid cgroup id")
}
if strings.TrimSpace(rel) == "" || level <= 0 {
return fmt.Errorf("invalid cgroup path")
}
if ttlSec <= 0 {
ttlSec = defaultAppMarkTTLSeconds
}
appMarksMu.Lock()
defer appMarksMu.Unlock()
st := loadAppMarksState()
changed := pruneExpiredAppMarksLocked(&st, time.Now().UTC())
unit = strings.TrimSpace(unit)
command = strings.TrimSpace(command)
appKey = canonicalizeAppKey(appKey, command)
// EN: Keep only one effective mark per app and avoid cross-target conflicts.
// EN: If the same app_key is re-marked with another target, old mark is removed first.
// RU: Держим только одну эффективную метку на приложение и убираем конфликты между target.
// RU: Если тот же app_key перемечается на другой target — старая метка удаляется.
kept := st.Items[:0]
for _, it := range st.Items {
itTarget := strings.ToLower(strings.TrimSpace(it.Target))
itKey := strings.TrimSpace(it.AppKey)
remove := false
// Same cgroup id but different target => conflicting rules (mark+guard).
if it.ID == id && it.ID != 0 && itTarget != target {
remove = true
}
// Same app_key (if known) should not keep multiple active runtime routes.
if !remove && appKey != "" && itKey != "" && itKey == appKey {
if it.ID != id || itTarget != target {
remove = true
}
}
if remove {
_ = nftDeleteAppMarkRule(itTarget, it.ID)
changed = true
continue
}
kept = append(kept, it)
}
st.Items = kept
// Replace any existing rule/state for this (target,id).
_ = nftDeleteAppMarkRule(target, id)
if err := nftInsertAppMarkRule(target, rel, level, id, vpnIface); err != nil {
return err
}
if !nftHasAppMarkRule(target, id) {
_ = nftDeleteAppMarkRule(target, id)
return fmt.Errorf("appmark rule not active after insert (target=%s id=%d)", target, id)
}
now := time.Now().UTC()
expiresAt := ""
if ttlSec > 0 {
expiresAt = now.Add(time.Duration(ttlSec) * time.Second).Format(time.RFC3339)
}
item := appMarkItem{
ID: id,
Target: target,
Cgroup: cgAbs,
CgroupRel: rel,
Level: level,
Unit: unit,
Command: command,
AppKey: appKey,
AddedAt: now.Format(time.RFC3339),
ExpiresAt: expiresAt,
}
st.Items = upsertAppMarkItem(st.Items, item)
changed = true
if changed {
if err := saveAppMarksState(st); err != nil {
// Keep runtime state and nft in sync on disk write errors.
_ = nftDeleteAppMarkRule(target, id)
return err
}
}
return nil
}