-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdrain.go
180 lines (148 loc) · 4.8 KB
/
drain.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package main
import (
"fmt"
"github.com/docopt/docopt-go"
"github.com/shawnsi/drain/conntrack"
"github.com/shawnsi/drain/iptables"
"log"
"os"
"os/user"
"strconv"
"time"
)
func Chain(port string) string {
return fmt.Sprintf("DRAIN_%s", port)
}
func Monitor(ports []string, timeout string) {
timeoutInt, _ := strconv.Atoi(timeout)
connections := conntrack.Established(ports)
for elapsed := 0; len(connections) > 0 && (timeoutInt < 0 || elapsed < timeoutInt); elapsed++ {
fmt.Printf("%d connections remaining on ports %s...\n", len(connections), ports)
time.Sleep(1 * time.Second)
connections = conntrack.Established(ports)
}
}
func Reject(ports []string) error {
for _, port := range ports {
chain := Chain(port)
// Append REJECT for all TCP connections on the port
if out, err := iptables.Command(
"-A", chain,
"-p", "tcp",
"-j", "REJECT",
"--reject-with", "tcp-reset",
"--dport", port).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to add TCP REJECT for port %s!\n%s", port, out)
}
}
return nil
}
func Start(ports []string, excludes []string) error {
for _, port := range ports {
chain := Chain(port)
// Create DRAIN chain
if out, err := iptables.Command("-N", chain).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to add chain for port %s!\n%s", port, out)
}
// Append RETURN for each excluded hostname
for _, exclude := range excludes {
if out, err := iptables.Command("-A", chain, "-s", exclude, "-j", "RETURN").CombinedOutput(); err != nil {
return fmt.Errorf("Failed to add exclude for host %s!\n%s", exclude, out)
}
}
// Append REJECT for new TCP connections on the port
if out, err := iptables.Command(
"-A", chain,
"-m", "state", "--state", "NEW",
"-p", "tcp",
"-j", "REJECT",
"--reject-with", "tcp-reset",
"--dport", port).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to add TCP REJECT for NEW connections on port %s!\n%s", port, out)
}
// Jump to DRAIN chain in INPUT chain
if out, err := iptables.Command("-A", "INPUT", "-j", chain).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to add jump to INPUT chain for port %s!\n%s", port, out)
}
}
return nil
}
func Status(ports []string) {
drains, _ := iptables.Drains(iptables.IptablesFetcher{})
for port, exclusions := range drains {
fmt.Printf("%s => %s\n", port, exclusions)
}
}
func Stop(ports []string) error {
for _, port := range ports {
chain := fmt.Sprintf("DRAIN_%s", port)
// Delete jump to DRAIN chain in INPUT chain
if out, err := iptables.Command("-D", "INPUT", "-j", chain).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to delete jump from INPUT chain for port %s!\n%s", port, out)
}
// Flush the DRAIN chain
if out, err := iptables.Command("-F", chain).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to flush chain for port %s!\n%s", port, out)
}
// Delete the DRAIN chain
if out, err := iptables.Command("-X", chain).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to delete chain for port %s!\n%s", port, out)
}
}
return nil
}
func main() {
usage := `TCP Drain.
Usage:
drain [options] monitor <port> [--timeout=<seconds>]
drain [options] start [--exclude=<host>... --reject=<seconds>] <port>...
drain [options] stop <port>...
drain [options] status
Options:
-e --exclude=<host> Exclude a hostname or ip from the drain
-t --timeout=<seconds> If positive, set timeout for monitoring connections [default: 120].
-r --reject=<seconds> Remaining connections rejected after this time elapsed [default: 120].
-h --help Show this screen
-d --debug Print debug information
-v --version Show version
Commands:
monitor Monitor connection counts
start Stop new TCP connections and drain existing
stop Open all TCP connections
status Show active drains`
if user, err := user.Current(); err != nil {
log.Fatal(err)
} else if user.Uid != "0" {
log.Fatal("You must be root to run drain")
}
arguments, _ := docopt.Parse(usage, nil, true, "Drain 0.0.7", false)
if arguments["--debug"].(bool) {
os.Setenv("DEBUG", "1")
}
if arguments["monitor"].(bool) {
ports := arguments["<port>"].([]string)
timeout := arguments["--timeout"].(string)
Monitor(ports, timeout)
}
if arguments["start"].(bool) {
ports := arguments["<port>"].([]string)
excludes := arguments["--exclude"].([]string)
reject := arguments["--reject"].(string)
if err := Start(ports, excludes); err != nil {
fmt.Print(err)
} else {
Monitor(ports, reject)
Reject(ports)
}
}
if arguments["status"].(bool) {
ports := arguments["<port>"].([]string)
Status(ports)
}
if arguments["stop"].(bool) {
ports := arguments["<port>"].([]string)
if err := Stop(ports); err != nil {
fmt.Print(err)
}
}
}