-
Notifications
You must be signed in to change notification settings - Fork 43
/
firewall.go
138 lines (123 loc) · 3.26 KB
/
firewall.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
Package firewall provides firewall service to the caller
*/
package firewall
import (
"fmt"
"sync"
"github.com/NordSecurity/nordvpn-linux/events"
)
// Firewall is responsible for correctly changing one firewall agent over another.
//
// Thread-safe.
type Firewall struct {
rules OrderedRules
current Agent
noop Agent
working Agent
publisher events.Publisher[string]
enabled bool
mu sync.Mutex
}
// NewFirewall produces an instance of Firewall.
func NewFirewall(noop, working Agent, publisher events.Publisher[string], enabled bool) *Firewall {
var current Agent
if enabled {
current = working
} else {
current = noop
}
return &Firewall{
working: working,
noop: noop,
enabled: enabled,
current: current,
publisher: publisher,
}
}
// Add rules to the firewall.
func (fw *Firewall) Add(rules []Rule) error {
fw.mu.Lock()
defer fw.mu.Unlock()
for _, rule := range rules {
fw.publisher.Publish(fmt.Sprintf("adding rule %s", rule.Name))
if rule.Name == "" {
return NewError(ErrRuleWithoutName)
}
existingRule, err := fw.rules.Get(rule.Name)
if err == nil {
// rule with the given name exists, check if the rules are equal
if existingRule.Equal(rule) {
return NewError(ErrRuleAlreadyExists)
}
fw.publisher.Publish(fmt.Sprintf("replacing existing rule %s", rule.Name))
}
if err := fw.current.Add(rule); err != nil {
return NewError(fmt.Errorf("adding %s: %w", rule.Name, err))
}
if err := fw.rules.Add(rule); err != nil {
return NewError(fmt.Errorf("adding %s to memory: %w", rule.Name, err))
}
if err == nil {
// remove older rule
if err := fw.current.Delete(existingRule); err != nil {
return NewError(fmt.Errorf("removing replaced rule %s to memory: %w", rule.Name, err))
}
}
}
return nil
}
// Delete rules from firewall by their names.
func (fw *Firewall) Delete(names []string) error {
fw.mu.Lock()
defer fw.mu.Unlock()
for _, name := range names {
fw.publisher.Publish(fmt.Sprintf("deleting rule %s", name))
rule, err := fw.rules.Get(name)
if err != nil {
return NewError(fmt.Errorf("getting %s: %w", name, err))
}
err = fw.current.Delete(rule)
if err != nil {
return NewError(fmt.Errorf("deleting %s: %w", name, err))
}
err = fw.rules.Delete(rule.Name)
if err != nil {
return NewError(fmt.Errorf("deleting %s from memory: %s", name, err))
}
}
return nil
}
// Enable restores firewall operations from no-ops.
func (fw *Firewall) Enable() error {
fw.mu.Lock()
defer fw.mu.Unlock()
if fw.enabled {
return NewError(ErrFirewallAlreadyEnabled)
}
fw.enabled = true
fw.current = fw.working
return fw.swap(fw.noop, fw.current)
}
// Disable turns all firewall operations into no-ops.
func (fw *Firewall) Disable() error {
fw.mu.Lock()
defer fw.mu.Unlock()
if !fw.enabled {
return NewError(ErrFirewallAlreadyDisabled)
}
fw.enabled = false
fw.current = fw.noop
return fw.swap(fw.working, fw.current)
}
func (fw *Firewall) swap(current Agent, next Agent) error {
for _, rule := range fw.rules.rules {
if err := current.Delete(rule); err != nil {
return NewError(fmt.Errorf("deleting rule %s: %w", rule.Name, err))
}
if err := next.Add(rule); err != nil {
return NewError(fmt.Errorf("adding rule %s: %w", rule.Name, err))
}
}
return nil
}