-
Notifications
You must be signed in to change notification settings - Fork 9
/
conn.go
102 lines (95 loc) · 2.86 KB
/
conn.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
package lossy
import (
"math/rand"
"net"
"sync"
"time"
)
type conn struct {
net.Conn
minLatency time.Duration
maxLatency time.Duration
packetLossRate float64
writeDeadline time.Time
closed bool
mu *sync.Mutex
rand *rand.Rand
throttleMu *sync.Mutex
timeToWaitPerByte float64
headerOverhead int
}
// NewConn wraps the given net.Conn with a lossy connection.
//
// bandwidth is in bytes/second.
// i.e. enter 1024 * 1024 for a 8 Mbit/s connection.
// Enter 0 or a negative value for an unlimited bandwidth.
//
// minLatency and maxLatency is used to create a random latency for each packet.
// maxLatency should be equal or greater than minLatency.
// If bandwidth is not unlimited and there's no other packets waiting to be delivered,
// time to deliver a packet is (len(packet) + headerOverhead) / bandwidth + randomDuration(minLatency, maxLatency)
//
// packetLossRate is chance of a packet to be dropped.
// It should be less than 1 and equal or greater than 0.
//
// headerOverhead is the header size of the underlying protocol of the connection.
// It is used to simulate bandwidth more realistically.
// If bandwidth is unlimited, headerOverhead is ignored.
func NewConn(c net.Conn, bandwidth int, minLatency, maxLatency time.Duration, packetLossRate float64, headerOverhead int) net.Conn {
var timeToWaitPerByte float64
if bandwidth <= 0 {
timeToWaitPerByte = 0
} else {
timeToWaitPerByte = float64(time.Second) / float64(bandwidth)
}
return &conn{
Conn: c,
minLatency: minLatency,
maxLatency: maxLatency,
packetLossRate: packetLossRate,
writeDeadline: time.Time{},
closed: false,
mu: &sync.Mutex{},
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
throttleMu: &sync.Mutex{},
timeToWaitPerByte: timeToWaitPerByte,
headerOverhead: headerOverhead,
}
}
func (c *conn) Write(b []byte) (int, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed || !c.writeDeadline.Equal(time.Time{}) && c.writeDeadline.Before(time.Now()) {
return c.Conn.Write(b)
}
go func() {
c.throttleMu.Lock()
time.Sleep(time.Duration(c.timeToWaitPerByte * (float64(len(b) + c.headerOverhead))))
c.throttleMu.Unlock()
if c.rand.Float64() >= c.packetLossRate {
time.Sleep(c.minLatency + time.Duration(float64(c.maxLatency-c.minLatency)*c.rand.Float64()))
c.mu.Lock()
_, _ = c.Conn.Write(b)
c.mu.Unlock()
}
}()
return len(b), nil
}
func (c *conn) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
c.closed = true
return c.Conn.Close()
}
func (c *conn) SetDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.writeDeadline = t
return c.Conn.SetDeadline(t)
}
func (c *conn) SetWriteDeadline(t time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
c.writeDeadline = t
return c.Conn.SetWriteDeadline(t)
}