platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
102
selective-vpn-api/app/traffic_appmarks_ops_mutations_add.go
Normal file
102
selective-vpn-api/app/traffic_appmarks_ops_mutations_add.go
Normal file
@@ -0,0 +1,102 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user