package bridge import ( "fmt" "os/exec" "strconv" ) type vlanMember struct { Device string VID int Untagged bool Self bool } func members(v *BridgeVLAN) map[vlanMember]bool { result := map[vlanMember]bool{} if v == nil || v.Disabled { return result } for _, vid := range parseVLANRange(v.VlanIDs) { for _, dev := range v.Tagged { result[vlanMember{dev, vid, false, dev == v.Bridge}] = true } for _, dev := range v.Untagged { result[vlanMember{dev, vid, true, dev == v.Bridge}] = true } } return result } func changeMember(action string, m vlanMember) error { args := []string{"vlan", action, "dev", m.Device, "vid", strconv.Itoa(m.VID)} if m.Self { args = append(args, "self") } if action == "add" && m.Untagged { args = append(args, "untagged") } out, err := exec.Command("bridge", args...).CombinedOutput() if err != nil { return fmt.Errorf("bridge VLAN %v failed: %s: %w", args, out, err) } return nil } func syncMembers(old, next *BridgeVLAN) error { before, after := members(old), members(next) var removed, added []vlanMember rollback := func() { for _, m := range added { _ = changeMember("del", m) } for _, m := range removed { _ = changeMember("add", m) } } for m := range before { if !after[m] { if err := changeMember("del", m); err != nil { rollback() return err } removed = append(removed, m) } } for m := range after { if !before[m] { if err := changeMember("add", m); err != nil { rollback() return err } added = append(added, m) } } return nil }