14.04.2026 Update
This commit is contained in:
@@ -6,34 +6,45 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IfDown brings an interface down via ifdown (or ip link set down as fallback).
|
||||
func IfDown(name string) error {
|
||||
out, err := exec.Command("ifdown", "--force", name).CombinedOutput()
|
||||
// LinkDown sets admin state down without deconfiguring (ip link set <name> down).
|
||||
func LinkDown(name string) error {
|
||||
out, err := exec.Command("ip", "link", "set", name, "down").CombinedOutput()
|
||||
if err != nil {
|
||||
// fallback: ip link set down
|
||||
out2, err2 := exec.Command("ip", "link", "set", name, "down").CombinedOutput()
|
||||
if err2 != nil {
|
||||
return fmt.Errorf("ifdown %s: %s\nip fallback: %s", name, strings.TrimSpace(string(out)), strings.TrimSpace(string(out2)))
|
||||
}
|
||||
return fmt.Errorf("ip link set down %s: %s", name, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IfUp brings an interface up via ifup (or ip link set up as fallback).
|
||||
func IfUp(name string) error {
|
||||
out, err := exec.Command("ifup", name).CombinedOutput()
|
||||
// LinkUp sets admin state up without re-running ifup (ip link set <name> up).
|
||||
func LinkUp(name string) error {
|
||||
out, err := exec.Command("ip", "link", "set", name, "up").CombinedOutput()
|
||||
if err != nil {
|
||||
out2, err2 := exec.Command("ip", "link", "set", name, "up").CombinedOutput()
|
||||
if err2 != nil {
|
||||
return fmt.Errorf("ifup %s: %s\nip fallback: %s", name, strings.TrimSpace(string(out)), strings.TrimSpace(string(out2)))
|
||||
}
|
||||
return fmt.Errorf("ip link set up %s: %s", name, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IfDown brings an interface down via ifdown --force.
|
||||
func IfDown(name string) error {
|
||||
out, err := exec.Command("ifdown", "--force", name).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ifdown %s: %s", name, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IfUp brings an interface up via ifup --force.
|
||||
func IfUp(name string) error {
|
||||
out, err := exec.Command("ifup", "--force", name).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ifup %s: %s", name, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IfRestart brings an interface down then up.
|
||||
func IfRestart(name string) error {
|
||||
_ = IfDown(name) // ignore "already down" errors
|
||||
_ = IfDown(name)
|
||||
return IfUp(name)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,15 @@ const ConfigFile = "/etc/network/interfaces"
|
||||
// InterfaceConfig represents one stanza in /etc/network/interfaces.
|
||||
type InterfaceConfig struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label,omitempty"` // display name, stored in config.yaml only
|
||||
Auto bool `json:"auto"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Type string `json:"type,omitempty"` // wan or lan
|
||||
Auto bool `json:"auto"`
|
||||
Mode string `json:"mode"` // dhcp, static, loopback, manual
|
||||
Address string `json:"address,omitempty"` // static only
|
||||
Netmask string `json:"netmask,omitempty"`
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
DNS []string `json:"dns,omitempty"`
|
||||
Extra map[string]string `json:"extra,omitempty"` // other raw options
|
||||
Extra map[string]string `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
// --- Pending config store (in-memory, not yet written to disk) ---
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
type InterfaceStats struct {
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"` // up, down, unknown
|
||||
State string `json:"state"`
|
||||
IPv4 string `json:"ipv4"`
|
||||
IPv4Mask string `json:"ipv4_mask"`
|
||||
IPv6 []string `json:"ipv6"`
|
||||
@@ -20,7 +20,8 @@ type InterfaceStats struct {
|
||||
TxBytes uint64 `json:"tx_bytes"`
|
||||
RxPackets uint64 `json:"rx_packets"`
|
||||
TxPackets uint64 `json:"tx_packets"`
|
||||
Mode string `json:"mode"` // dhcp, static, loopback, manual, unknown
|
||||
Mode string `json:"mode"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// GetInterfaces returns all network interface names from /sys/class/net.
|
||||
@@ -40,7 +41,6 @@ func GetInterfaces() ([]string, error) {
|
||||
func GetInterfaceStats(name string) (*InterfaceStats, error) {
|
||||
s := &InterfaceStats{Name: name, IPv6: []string{}}
|
||||
|
||||
// Operational state
|
||||
if raw, err := os.ReadFile("/sys/class/net/" + name + "/operstate"); err == nil {
|
||||
s.State = strings.TrimSpace(string(raw))
|
||||
} else {
|
||||
|
||||
87
network/overlap.go
Normal file
87
network/overlap.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type SubnetOverlap struct {
|
||||
Interface string `json:"interface"`
|
||||
Subnet string `json:"subnet"`
|
||||
Label string `json:"label,omitempty"`
|
||||
}
|
||||
|
||||
func parseIPNet(ipStr, maskStr string) (*net.IPNet, error) {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid IP: %s", ipStr)
|
||||
}
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid IPv4: %s", ipStr)
|
||||
}
|
||||
|
||||
mask := ipMaskFromString(maskStr)
|
||||
if mask == nil {
|
||||
return nil, fmt.Errorf("invalid netmask: %s", maskStr)
|
||||
}
|
||||
|
||||
return &net.IPNet{IP: ip.Mask(mask), Mask: mask}, nil
|
||||
}
|
||||
|
||||
func ipMaskFromString(maskStr string) net.IPMask {
|
||||
m := net.ParseIP(maskStr)
|
||||
if m != nil {
|
||||
if m4 := m.To4(); m4 != nil {
|
||||
return net.IPMask(m4)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func subnetsOverlap(a, b *net.IPNet) bool {
|
||||
return a.Contains(b.IP) || b.Contains(a.IP)
|
||||
}
|
||||
|
||||
func CheckOverlap(newIP, newMask, excludeIface string, existing []SubnetOverlap) []SubnetOverlap {
|
||||
newNet, err := parseIPNet(newIP, newMask)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result []SubnetOverlap
|
||||
for _, s := range existing {
|
||||
if s.Interface == excludeIface {
|
||||
continue
|
||||
}
|
||||
parts, err := parseSubnetStr(s.Subnet)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
existingNet, err := parseIPNet(parts[0], parts[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if subnetsOverlap(newNet, existingNet) {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ParseSubnet(ipStr, maskStr string) (string, error) {
|
||||
n, err := parseIPNet(ipStr, maskStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return n.String(), nil
|
||||
}
|
||||
|
||||
func parseSubnetStr(s string) ([2]string, error) {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] == '/' {
|
||||
return [2]string{s[:i], s[i+1:]}, nil
|
||||
}
|
||||
}
|
||||
return [2]string{}, fmt.Errorf("invalid subnet format: %s", s)
|
||||
}
|
||||
Reference in New Issue
Block a user