-
-
Notifications
You must be signed in to change notification settings - Fork 274
/
op_ping.go
149 lines (124 loc) Β· 3.7 KB
/
op_ping.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
package crew
import (
"crypto/subtle"
"time"
"github.com/safing/portbase/container"
"github.com/safing/portbase/formats/dsd"
"github.com/safing/portbase/rng"
"github.com/safing/portmaster/spn/terminal"
)
const (
// PingOpType is the type ID of the latency test operation.
PingOpType = "ping"
pingOpNonceSize = 16
pingOpTimeout = 3 * time.Second
)
// PingOp is used to measure latency.
type PingOp struct {
terminal.OneOffOperationBase
started time.Time
nonce []byte
}
// PingOpRequest is a ping request.
type PingOpRequest struct {
Nonce []byte `json:"n,omitempty"`
}
// PingOpResponse is a ping response.
type PingOpResponse struct {
Nonce []byte `json:"n,omitempty"`
Time time.Time `json:"t,omitempty"`
}
// Type returns the type ID.
func (op *PingOp) Type() string {
return PingOpType
}
func init() {
terminal.RegisterOpType(terminal.OperationFactory{
Type: PingOpType,
Start: startPingOp,
})
}
// NewPingOp runs a latency test.
func NewPingOp(t terminal.Terminal) (*PingOp, *terminal.Error) {
// Generate nonce.
nonce, err := rng.Bytes(pingOpNonceSize)
if err != nil {
return nil, terminal.ErrInternalError.With("failed to generate ping nonce: %w", err)
}
// Create operation and init.
op := &PingOp{
started: time.Now().UTC(),
nonce: nonce,
}
op.OneOffOperationBase.Init()
// Create request.
pingRequest, err := dsd.Dump(&PingOpRequest{
Nonce: op.nonce,
}, dsd.CBOR)
if err != nil {
return nil, terminal.ErrInternalError.With("failed to create ping request: %w", err)
}
// Send ping.
tErr := t.StartOperation(op, container.New(pingRequest), pingOpTimeout)
if tErr != nil {
return nil, tErr
}
return op, nil
}
// Deliver delivers a message to the operation.
func (op *PingOp) Deliver(msg *terminal.Msg) *terminal.Error {
defer msg.Finish()
// Parse response.
response := &PingOpResponse{}
_, err := dsd.Load(msg.Data.CompileData(), response)
if err != nil {
return terminal.ErrMalformedData.With("failed to parse ping response: %w", err)
}
// Check if the nonce matches.
if subtle.ConstantTimeCompare(op.nonce, response.Nonce) != 1 {
return terminal.ErrIntegrity.With("ping nonce mismatched")
}
return terminal.ErrExplicitAck
}
func startPingOp(t terminal.Terminal, opID uint32, data *container.Container) (terminal.Operation, *terminal.Error) {
// Parse request.
request := &PingOpRequest{}
_, err := dsd.Load(data.CompileData(), request)
if err != nil {
return nil, terminal.ErrMalformedData.With("failed to parse ping request: %w", err)
}
// Create response.
response, err := dsd.Dump(&PingOpResponse{
Nonce: request.Nonce,
Time: time.Now().UTC(),
}, dsd.CBOR)
if err != nil {
return nil, terminal.ErrInternalError.With("failed to create ping response: %w", err)
}
// Send response.
msg := terminal.NewMsg(response)
msg.FlowID = opID
msg.Unit.MakeHighPriority()
if terminal.UsePriorityDataMsgs {
msg.Type = terminal.MsgTypePriorityData
}
tErr := t.Send(msg, pingOpTimeout)
if tErr != nil {
// Finish message unit on failure.
msg.Finish()
return nil, tErr.With("failed to send ping response")
}
// Operation is just one response and finished successfully.
return nil, nil
}
// HandleStop gives the operation the ability to cleanly shut down.
// The returned error is the error to send to the other side.
// Should never be called directly. Call Stop() instead.
func (op *PingOp) HandleStop(err *terminal.Error) (errorToSend *terminal.Error) {
// Prevent remote from sending explicit ack, as we use it as a success signal internally.
if err.Is(terminal.ErrExplicitAck) && err.IsExternal() {
err = terminal.ErrStopping.AsExternal()
}
// Continue with usual handling of inherited base.
return op.OneOffOperationBase.HandleStop(err)
}