summaryrefslogtreecommitdiffhomepage
path: root/net/isoping/isoping.go
blob: e43e664f629dd1ad23bf96cc01831cece6ba9e66 (plain)
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// package isoping implements isoping in Go.
package isoping

import (
	"bytes"
	"encoding/binary"
	"log"
	"math"
	"net"
	"time"
)

type Packet struct {
	Magic      uint32 // Magic number to reject bogus packets
	Id         uint32 // Id is a sequential packet id number
	Txtime     uint32 // Txtime is the transmitter's monotonic time when pkt was sent
	Clockdiff  uint32 // Clockdiff is an estimate of (transmitter's clk) - (receiver's clk)
	UsecPerPkt uint32 // Usec_per_pkt microseconds of delay between packets
	NumLost    uint32 // Num_lost is the number of pkts transmitter expected to get but didn't
	FirstAck   uint32 // First_ack is the starting index in acks[] circular buffer
	Acks       [64]struct {
		// txtime==0 for empty elements in this array.
		Id     uint32 // Id field from a received packet
		Rxtime uint32 // Rxtime is a receiver's monotonic time when pkt arrived
	}
}

type Isoping struct {
	ClockStartTime time.Time    // ClockStartTime is the time the program starts
	IsServer       bool         // IsServer distinguishes if we are a server or client
	Conn           *net.UDPConn // Conn is either the server or client's connection
	Tx             Packet       // Tx is a Packet that will be sent
	Rx             Packet       // Rx is a Packet that will be received
	LastAckInfo    string       // LastAckInfo human readable format of latest ack
	ListenAddr     *net.UDPAddr // ListenAddr is the address of the listener
	RemoteAddr     *net.UDPAddr // RemtoteAddr remote UDP address we send to.
	RxAddr         *net.UDPAddr // RxAddr keeps track of what address we are sending to
	LastRxAddr     *net.UDPAddr // LastRxAddr keeps track of what we last used
	Quiet          bool         // Option to show output or not

	printsPerSec   float64
	packetsPerSec  float64
	usecPerPkt     int32
	usecPerPrint   int32
	nextTxId       uint32
	nextRxId       uint32
	nextRxackId    uint32
	startRtxtime   uint32 // remote's txtime at startup
	startRxtime    uint32 // local rxtime at startup
	lastRxtime     uint32 // local rxtime of last received packet
	minCycleRxdiff int32  // smallest packet delay seen this cycle
	nextCycle      uint32 // time when next cycle begins
	now            uint32 // current time
	nextSend       uint32 // time when we'll send next pkt
	numLost        uint32 // number of rx packets not received
	nextTxackIndex int    // next array item to fill in tx.acks
	lastPrint      uint32 // time of last packet printout
	latTx          int64
	latTxMin       int64
	latTxMax       int64
	latTxCount     int64
	latTxSum       int64
	latTxVarSum    int64

	latRx       int64
	latRxMin    int64
	latRxMax    int64
	latRxCount  int64
	latRxSum    int64
	latRxVarSum int64
}

// Incremental standard deviation calculation, without needing to know the
// mean in advance.  See:
// http://mathcentral.uregina.ca/QQ/database/QQ.09.02/carlos1.html
func onePassStddev(sumsq, sum, count int64) float64 {
	numer := (count * sumsq) - (sum * sum)
	denom := count * (count - 1)
	return math.Sqrt(DIV(numer, denom))
}

// UsecMonoTimeNow returns the monotonic number of microseconds since the program started.
func (srv *Isoping) UsecMonoTimeNow() uint64 {
	tn := time.Since(srv.ClockStartTime)
	return uint64(tn.Microseconds())
}

// UsecMonoTime returns the monotonic number of microseconds since the program started, as a uint32.
func (srv *Isoping) UsecMonoTime() uint32 {
	return uint32(srv.UsecMonoTimeNow())
}

// initClock keeps track of when the server/client starts.
// keeps the exact time and we can subtract from the time
// to get monotonicClock values
func (srv *Isoping) initClock() {
	srv.ClockStartTime = time.Now()
}

// initClient sets the Isoping.Conn, to the address string otherwise
// uses [::]:4948 as the default
func (srv *Isoping) initClient(address string) {
	srv.initClock()
	srv.IsServer = false
	udpaddr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		log.Println(err)
		addr := DEFAULT_PORT
		udpaddr, err = net.ResolveUDPAddr("udp", addr)
		if err != nil {
			log.Println(err)
			return
		}
		log.Printf("Address %v failed to resolve\n", address)
	}

	conn, err := net.DialUDP("udp", nil, udpaddr)
	if err != nil {
		log.Println(err)
		return
	}

	srv.RemoteAddr = udpaddr
	srv.Conn = conn
}

// initServer sets the Conn field of Isoping, for the listener side.
func (srv *Isoping) initServer(port string) {
	srv.initClock()
	srv.IsServer = true
	addr, err := net.ResolveUDPAddr("udp", port)
	if err != nil {
		log.Println(err)
		return
	}

	srv.ListenAddr = addr
	srv.Conn, err = net.ListenUDP("udp", addr)
	if err != nil {
		log.Printf("%v\n", err)
		return
	}
}

func NewInstance() *Isoping {
	clockStartTime := time.Now()

	packetsPerSec := DEFAULT_PACKETS_PER_SEC
	printsPerSec := -1

	usecPerPkt := int32(1e6 / packetsPerSec)
	usecPerPrint := int32(0)
	if usecPerPrint > 0 {
		usecPerPrint = int32(1e6 / printsPerSec)
	}
	log.Println("UsecPerPkt : ", usecPerPkt)
	log.Println("UsecPerPrint : ", usecPerPrint)

	nextTxId := 1
	nextRxId := 0

	nextRxackId := 0
	startRtxtime := 0
	startRxtime := 0
	lastRxtime := 0

	minCycleRxdiff := 0
	nextCycle := 0
	nextSend := 0
	nextTxackIndex := 0

	LastAckInfo := ""
	inst := &Isoping{
		packetsPerSec:  packetsPerSec,
		printsPerSec:   float64(printsPerSec),
		usecPerPkt:     int32(1e6 / DEFAULT_PACKETS_PER_SEC),
		usecPerPrint:   usecPerPrint,
		nextTxId:       uint32(nextTxId),
		nextRxId:       uint32(nextRxId),
		nextRxackId:    uint32(nextRxackId),
		startRtxtime:   uint32(startRtxtime),
		startRxtime:    uint32(startRxtime),
		lastRxtime:     uint32(lastRxtime),
		minCycleRxdiff: int32(minCycleRxdiff),
		nextCycle:      uint32(nextCycle),
		nextSend:       uint32(nextSend),
		nextTxackIndex: nextTxackIndex,
		Tx:             Packet{},
		Rx:             Packet{},
		LastAckInfo:    LastAckInfo,
		ClockStartTime: clockStartTime,

		latTx:       0,
		latTxMin:    0x7fffffff,
		latTxMax:    0,
		latTxCount:  0,
		latTxSum:    0,
		latTxVarSum: 0,
		latRx:       0,
		latRxMin:    0x7fffffff,
		latRxMax:    0,
		latRxCount:  0,
		latRxSum:    0,
		latRxVarSum: 0,
	}

	// Setup the clock functions after creating the fields
	inst.now = inst.UsecMonoTime()
	inst.lastPrint = inst.now - uint32(inst.usecPerPkt)
	return inst
}

// generateInitialPacket generates the inital packet Tx
func (srv *Isoping) generateInitialPacket() (*bytes.Buffer, error) {
	srv.Tx.Magic = MAGIC
	srv.Tx.Id = srv.nextTxId
	srv.nextTxId++
	srv.Tx.Txtime = srv.nextSend
	srv.Tx.UsecPerPkt = uint32(srv.usecPerPkt)
	srv.Tx.Clockdiff = 0
	if srv.startRtxtime > 0 {
		srv.Rx.Clockdiff = srv.startRtxtime - srv.startRxtime
	}
	srv.Tx.NumLost = srv.numLost
	srv.Tx.FirstAck = uint32(srv.nextTxackIndex)

	// Setup the Tx to be sent from either server of client
	buf := new(bytes.Buffer)
	return buf, binary.Write(buf, binary.BigEndian, srv.Tx)
}

// StartServer starts the Isoping Server with port
// If no port is given, then starts with DEFAULT_PORT
func (srv *Isoping) StartServer(port string) {
	if port != "" {
		srv.initServer(port)
	} else {
		srv.initServer(DEFAULT_PORT)
	}
}

// StartServer starts the Isoping Client with port
// If no port is given, then starts with DEFAULT_PORT
func (srv *Isoping) StartClient(port string) {
	if port != "" {
		srv.initClient(port)
	} else {
		srv.initClient(DEFAULT_PORT)
	}
}