-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathnet.go
158 lines (139 loc) · 3.45 KB
/
net.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
package pixelflut
import (
"fmt"
"math/rand"
"net"
"sync"
"time"
)
const (
timeoutMin = 100 * time.Millisecond
timeoutMax = 10 * time.Second
)
// Performance contains pixelflut metrics
type Performance struct {
Enabled bool
Conns int
BytesPerSec int
BytesTotal int
connsReporter chan int
bytesReporter chan int
bytes int
}
func (p Performance) String() string {
return fmt.Sprintf("%v conns\t%v\t%v/s",
p.Conns, fmtBytes(p.BytesTotal), fmtBit(p.BytesPerSec))
}
// https://yourbasic.org/golang/byte-count.go
func fmtBytes(b int) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %ciB",
float64(b)/float64(div), "KMGTPE"[exp])
}
func fmtBit(b int) string {
const unit = 1000
b *= 8
if b < unit {
return fmt.Sprintf("%d b", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cb",
float64(b)/float64(div), "kMGTPE"[exp])
}
// PerformanceReporter provides pixelflut performance metrics, when Enabled is true.
// @speed: Note that enabling costs ~9% bomb performance under high throughput.
var PerformanceReporter = initPerfReporter()
// should be called only once
func initPerfReporter() *Performance {
r := new(Performance)
r.bytesReporter = make(chan int, 512)
r.connsReporter = make(chan int, 512)
go func() {
for {
select {
case b := <-r.bytesReporter:
r.bytes += b
r.BytesTotal += b
case c := <-r.connsReporter:
r.Conns += c
}
}
}()
go func() {
for {
time.Sleep(time.Second)
r.BytesPerSec = r.bytes
r.bytes = 0
}
}()
return r
}
// bombAddress opens a TCP connection to `address`, and writes `message` repeatedly, until `stop` is closed.
// It retries with exponential backoff on network errors.
func bombAddress(message []byte, address string, maxOffsetX, maxOffsetY int, stop chan bool, wg *sync.WaitGroup) {
wg.Add(1)
defer wg.Done()
timeout := timeoutMin
for {
conn, err := net.Dial("tcp", address)
if err != nil {
// this was a network error, retry!
fmt.Printf("[net] error: %s. retrying in %s\n", err, timeout)
time.Sleep(timeout)
timeout *= 2
if timeout > timeoutMax {
timeout = timeoutMax
}
continue
}
fmt.Printf("[net] bombing %s with new connection\n", address)
err = bombConn(message, maxOffsetX, maxOffsetY, conn, stop)
conn.Close()
timeout = timeoutMin
if err == nil {
break // we're supposed to exit
}
fmt.Printf("[net] error: %s\n", err)
}
}
// bombConn writes the given message to the given connection in a tight loop, until `stop` is closed.
// Does no transformation on the given message, so make sure packet splitting / nagle works.
func bombConn(message []byte, maxOffsetX, maxOffsetY int, conn net.Conn, stop chan bool) error {
PerformanceReporter.connsReporter <- 1
defer func() { PerformanceReporter.connsReporter <- -1 }()
var msg = make([]byte, len(message)+16) // leave some space for offset cmd
msg = message
randOffset := maxOffsetX > 0 && maxOffsetY > 0
for {
select {
case <-stop:
return nil
default:
if randOffset {
msg = append(
OffsetCmd(rand.Intn(maxOffsetX), rand.Intn(maxOffsetY)),
message...,
)
}
b, err := conn.Write(msg)
if err != nil {
return err
}
if PerformanceReporter.Enabled {
PerformanceReporter.bytesReporter <- b
}
}
}
}