Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

weaveworks / weave / #6002

13 Apr 2016 - 6:49 coverage decreased (-0.1%) to 75.607%
#6002

Pull #2154

circleci

2f1e5f233f2b7283a9bf3277e75bf30a?size=18&default=identiconrade
Lock round TestRouter map accesses, and copy the set where we may block.
Pull Request #2154: Lock round TestRouter map accesses

6137 of 8117 relevant lines covered (75.61%)

129553.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.93
/router/sleeve.go
1
// This contains the Overlay implementation for weave's own UDP
2
// encapsulation protocol ("sleeve" because a sleeve encapsulates
3
// something, it's often woven, it rhymes with "weave", make up your
4
// own cheesy reason).
5

6
package router
7

8
import (
9
        "bytes"
10
        "encoding/binary"
11
        "fmt"
12
        "io"
13
        "net"
14
        "os"
15
        "sync"
16
        "syscall"
17
        "time"
18

19
        "github.com/google/gopacket"
20
        "github.com/google/gopacket/layers"
21
        "github.com/weaveworks/mesh"
22
)
23

24
// This diagram explains the various arithmetic and variables related
25
// to packet offsets and lengths below:
26
//
27
// +----+-----+--------+--------+----------+--------------------------+
28
// | IP | UDP | Sleeve | Sleeve | Overlay  | Overlay Layer 3 Payload  |
29
// |    |     | Packet | Frame  | Ethernet |                          |
30
// |    |     | Header | Header |          |                          |
31
// +----+-----+--------+--------+----------+--------------------------+
32
//
33
// <------------------------------------ msgTooBigError.underlayPMTU ->
34
//
35
//            <-------------------------- sleeveForwarder.maxPayload ->
36
//
37
// <---------->                                             UDPOverhead
38
//
39
//            <-------->                       Encryptor.PacketOverhead
40
//
41
//                     <-------->               Encryptor.FrameOverhead
42
//
43
//                              <---------->           EthernetOverhead
44
//
45
// <---------------------------------------> sleeveForwarder.overheadDF
46
//
47
// sleeveForwarder.mtu                     <-------------------------->
48

49
const (
50
        EthernetOverhead  = 14
51
        UDPOverhead       = 28 // 20 bytes for IPv4, 8 bytes for UDP
52
        DefaultMTU        = 65535
53
        FragTestSize      = 60001
54
        PMTUDiscoverySize = 60000
55
        FragTestInterval  = 5 * time.Minute
56
        MTUVerifyAttempts = 8
57
        MTUVerifyTimeout  = 10 * time.Millisecond // doubled with each attempt
58

59
        ProtocolConnectionEstablished = mesh.ProtocolReserved1
60
        ProtocolFragmentationReceived = mesh.ProtocolReserved2
61
        ProtocolPMTUVerified          = mesh.ProtocolReserved3
62
)
63

64
type SleeveOverlay struct {
65
        host      string
66
        localPort int
67

68
        // These fields are set in StartConsumingPackets, and not
69
        // subsequently modified
70
        localPeer    *mesh.Peer
71
        localPeerBin []byte
72
        consumer     OverlayConsumer
73
        peers        *mesh.Peers
74
        conn         *net.UDPConn
75

76
        lock       sync.Mutex
77
        forwarders map[mesh.PeerName]*sleeveForwarder
78
}
79

80
func NewSleeveOverlay(host string, localPort int) NetworkOverlay {
78×
81
        return &SleeveOverlay{host: host, localPort: localPort}
78×
82
}
78×
83

84
func (sleeve *SleeveOverlay) StartConsumingPackets(localPeer *mesh.Peer, peers *mesh.Peers, consumer OverlayConsumer) error {
78×
85
        localAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprint(sleeve.host, ":", sleeve.localPort))
78×
86
        if err != nil {
78×
87
                return err
!
88
        }
!
89

90
        conn, err := net.ListenUDP("udp4", localAddr)
78×
91
        if err != nil {
78×
92
                return err
!
93
        }
!
94

95
        f, err := conn.File()
78×
96
        if err != nil {
78×
97
                return err
!
98
        }
!
99

100
        defer f.Close()
78×
101
        fd := int(f.Fd())
78×
102

78×
103
        // This makes sure all packets we send out do not have DF set
78×
104
        // on them.
78×
105
        err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DONT)
78×
106
        if err != nil {
78×
107
                return err
!
108
        }
!
109

110
        sleeve.lock.Lock()
78×
111
        defer sleeve.lock.Unlock()
78×
112

78×
113
        if sleeve.localPeer != nil {
78×
114
                conn.Close()
!
115
                return fmt.Errorf("StartConsumingPackets already called")
!
116
        }
!
117

118
        sleeve.localPeer = localPeer
78×
119
        sleeve.localPeerBin = localPeer.NameByte
78×
120
        sleeve.consumer = consumer
78×
121
        sleeve.peers = peers
78×
122
        sleeve.conn = conn
78×
123
        sleeve.forwarders = make(map[mesh.PeerName]*sleeveForwarder)
78×
124
        go sleeve.readUDP()
78×
125
        return nil
78×
126
}
127

128
func (*SleeveOverlay) InvalidateRoutes() {
353×
129
        // no cached information, so nothing to do
353×
130
}
353×
131

132
func (*SleeveOverlay) InvalidateShortIDs() {
2×
133
        // no cached information, so nothing to do
2×
134
}
2×
135

136
func (*SleeveOverlay) AddFeaturesTo(map[string]string) {
!
137
        // No features to be provided, to facilitate compatibility
!
138
}
!
139

140
func (*SleeveOverlay) Diagnostics() interface{} {
159×
141
        return nil
159×
142
}
159×
143

144
func (sleeve *SleeveOverlay) lookupForwarder(peer mesh.PeerName) *sleeveForwarder {
378×
145
        sleeve.lock.Lock()
378×
146
        defer sleeve.lock.Unlock()
378×
147
        return sleeve.forwarders[peer]
378×
148
}
378×
149

150
func (sleeve *SleeveOverlay) addForwarder(peer mesh.PeerName, fwd *sleeveForwarder) {
66×
151
        sleeve.lock.Lock()
66×
152
        defer sleeve.lock.Unlock()
66×
153
        sleeve.forwarders[peer] = fwd
66×
154
}
66×
155

156
func (sleeve *SleeveOverlay) removeForwarder(peer mesh.PeerName, fwd *sleeveForwarder) {
39×
157
        sleeve.lock.Lock()
39×
158
        defer sleeve.lock.Unlock()
39×
159
        if sleeve.forwarders[peer] == fwd {
72×
160
                delete(sleeve.forwarders, peer)
33×
161
        }
33×
162
}
163

164
func (sleeve *SleeveOverlay) readUDP() {
78×
165
        defer sleeve.conn.Close()
78×
166
        dec := NewEthernetDecoder()
78×
167
        buf := make([]byte, MaxUDPPacketSize)
78×
168

78×
169
        for {
534×
170
                n, sender, err := sleeve.conn.ReadFromUDP(buf)
456×
171
                if err == io.EOF {
456×
172
                        return
!
173
                } else if err != nil {
378×
174
                        log.Print("ignoring UDP read error ", err)
!
175
                        continue
!
176
                } else if n < NameSize {
378×
177
                        log.Print("ignoring too short UDP packet from ", sender)
!
178
                        continue
!
179
                }
180

181
                fwdName := mesh.PeerNameFromBin(buf[:NameSize])
378×
182
                fwd := sleeve.lookupForwarder(fwdName)
378×
183
                if fwd == nil {
384×
184
                        continue
6×
185
                }
186

187
                packet := make([]byte, n-NameSize)
372×
188
                copy(packet, buf[NameSize:n])
372×
189

372×
190
                err = fwd.crypto.Dec.IterateFrames(packet,
372×
191
                        func(src []byte, dst []byte, frame []byte) {
758×
192
                                sleeve.handleFrame(sender, fwd, src, dst, frame, dec)
386×
193
                        })
386×
194
                if err != nil {
372×
195
                        // Errors during UDP packet decoding /
!
196
                        // processing are non-fatal. One common cause
!
197
                        // is that we receive and attempt to decrypt a
!
198
                        // "stray" packet. This can actually happen
!
199
                        // quite easily if there is some connection
!
200
                        // churn between two peers. After all, UDP
!
201
                        // isn't a connection-oriented protocol, yet
!
202
                        // we pretend it is.
!
203
                        //
!
204
                        // If anything really is seriously,
!
205
                        // unrecoverably amiss with a connection, that
!
206
                        // will typically result in missed heartbeats
!
207
                        // and the connection getting shut down
!
208
                        // because of that.
!
209
                        log.Print(fwd.logPrefixFor(sender), err)
!
210
                }
!
211
        }
212
}
213

214
func (sleeve *SleeveOverlay) handleFrame(sender *net.UDPAddr, fwd *sleeveForwarder, src []byte, dst []byte, frame []byte, dec *EthernetDecoder) {
386×
215
        dec.DecodeLayers(frame)
386×
216
        decodedLen := len(dec.decoded)
386×
217
        if decodedLen == 0 {
386×
218
                return
!
219
        }
!
220

221
        srcPeer := sleeve.peers.Fetch(mesh.PeerNameFromBin(src))
386×
222
        dstPeer := sleeve.peers.Fetch(mesh.PeerNameFromBin(dst))
386×
223
        if srcPeer == nil || dstPeer == nil {
386×
224
                return
!
225
        }
!
226

227
        // Handle special frames produced internally (rather than
228
        // captured/forwarded) by the remote router.
229
        //
230
        // We really shouldn't be decoding these above, since they are
231
        // not genuine Ethernet frames. However, it is actually more
232
        // efficient to do so, as we want to optimise for the common
233
        // (i.e. non-special) frames. These always need decoding, and
234
        // detecting special frames is cheaper post decoding than pre.
235
        if decodedLen == 1 && dec.IsSpecial() {
659×
236
                if srcPeer == fwd.remotePeer && dstPeer == fwd.sleeve.localPeer {
546×
237
                        select {
273×
238
                        case fwd.specialChan <- specialFrame{sender, frame}:
273×
239
                        case <-fwd.finishedChan:
!
240
                        }
241
                }
242

243
                return
273×
244
        }
245

246
        sleeve.sendToConsumer(srcPeer, dstPeer, frame, dec)
113×
247
}
248

249
func (sleeve *SleeveOverlay) sendToConsumer(srcPeer, dstPeer *mesh.Peer, frame []byte, dec *EthernetDecoder) {
114×
250
        if sleeve.consumer == nil {
114×
251
                return
!
252
        }
!
253

254
        fop := sleeve.consumer(ForwardPacketKey{
114×
255
                SrcPeer:   srcPeer,
114×
256
                DstPeer:   dstPeer,
114×
257
                PacketKey: dec.PacketKey(),
114×
258
        })
114×
259
        if fop != nil {
228×
260
                fop.Process(frame, dec, false)
114×
261
        }
114×
262
}
263

264
type udpSender interface {
265
        send([]byte, *net.UDPAddr) error
266
}
267

268
func (sleeve *SleeveOverlay) send(msg []byte, raddr *net.UDPAddr) error {
108×
269
        sleeve.lock.Lock()
108×
270
        conn := sleeve.conn
108×
271
        sleeve.lock.Unlock()
108×
272

108×
273
        if conn == nil {
108×
274
                // Consume wasn't called yet
!
275
                return nil
!
276
        }
!
277

278
        _, err := conn.WriteToUDP(msg, raddr)
108×
279
        return err
108×
280
}
281

282
type sleeveCrypto struct {
283
        Dec   Decryptor
284
        Enc   Encryptor
285
        EncDF Encryptor
286
}
287

288
func newSleeveCrypto(name []byte, sessionKey *[32]byte, outbound bool) sleeveCrypto {
71×
289
        if sessionKey == nil {
141×
290
                return sleeveCrypto{
70×
291
                        Dec:   NewNonDecryptor(),
70×
292
                        Enc:   NewNonEncryptor(name),
70×
293
                        EncDF: NewNonEncryptor(name),
70×
294
                }
70×
295
        }
70×
296
        return sleeveCrypto{
1×
297
                Dec:   NewNaClDecryptor(sessionKey, outbound),
1×
298
                Enc:   NewNaClEncryptor(name, sessionKey, outbound, false),
1×
299
                EncDF: NewNaClEncryptor(name, sessionKey, outbound, true),
1×
300
        }
1×
301
}
302

303
func (crypto sleeveCrypto) Overhead() int {
71×
304
        return UDPOverhead + crypto.EncDF.PacketOverhead() + crypto.EncDF.FrameOverhead() + EthernetOverhead
71×
305
}
71×
306

307
type sleeveForwarder struct {
308
        // Immutable
309
        sleeve         *SleeveOverlay
310
        remotePeer     *mesh.Peer
311
        remotePeerBin  []byte
312
        sendControlMsg func(byte, []byte) error
313
        connUID        uint64
314

315
        // Channels to communicate with the aggregator goroutine
316
        aggregatorChan   chan<- aggregatorFrame
317
        aggregatorDFChan chan<- aggregatorFrame
318
        specialChan      chan<- specialFrame
319
        controlMsgChan   chan<- controlMessage
320
        confirmedChan    chan<- struct{}
321
        finishedChan     <-chan struct{}
322

323
        // listener channels
324
        establishedChan chan struct{}
325
        errorChan       chan error
326

327
        // Explicitly locked state
328
        lock       sync.RWMutex
329
        remoteAddr *net.UDPAddr
330

331
        // These fields are accessed and updated independently, so no
332
        // locking needed.
333
        mtu       int // the mtu for this link on the overlay network
334
        stackFrag bool
335

336
        // State only used within the forwarder goroutine
337
        crypto     sleeveCrypto
338
        senderDF   *udpSenderDF
339
        maxPayload int
340

341
        // How many bytes of overhead it takes to turn an IP packet on
342
        // the overlay network into an encapsulated packet on the underlay
343
        // network
344
        overheadDF int
345

346
        heartbeatInterval time.Duration
347
        heartbeatTimer    *time.Timer
348
        heartbeatTimeout  *time.Timer
349
        fragTestTicker    *time.Ticker
350
        ackedHeartbeat    bool
351

352
        mtuTestTimeout *time.Timer
353
        mtuTestsSent   uint
354
        mtuHighestGood int
355
        mtuLowestBad   int
356
        mtuCandidate   int
357
}
358

359
type aggregatorFrame struct {
360
        src   []byte
361
        dst   []byte
362
        frame []byte
363
}
364

365
// A "special" frame over UDP
366
type specialFrame struct {
367
        sender *net.UDPAddr
368
        frame  []byte
369
}
370

371
// A control message
372
type controlMessage struct {
373
        tag byte
374
        msg []byte
375
}
376

377
func (sleeve *SleeveOverlay) PrepareConnection(params mesh.OverlayConnectionParams) (mesh.OverlayConnection, error) {
71×
378
        aggChan := make(chan aggregatorFrame, ChannelSize)
71×
379
        aggDFChan := make(chan aggregatorFrame, ChannelSize)
71×
380
        specialChan := make(chan specialFrame, 1)
71×
381
        controlMsgChan := make(chan controlMessage, 1)
71×
382
        confirmedChan := make(chan struct{})
71×
383
        finishedChan := make(chan struct{})
71×
384

71×
385
        var remoteAddr *net.UDPAddr
71×
386
        if params.Outbound {
108×
387
                remoteAddr = makeUDPAddr(params.RemoteAddr)
37×
388
        }
37×
389

390
        crypto := newSleeveCrypto(sleeve.localPeer.NameByte, params.SessionKey, params.Outbound)
71×
391

71×
392
        fwd := &sleeveForwarder{
71×
393
                sleeve:           sleeve,
71×
394
                remotePeer:       params.RemotePeer,
71×
395
                remotePeerBin:    params.RemotePeer.NameByte,
71×
396
                sendControlMsg:   params.SendControlMessage,
71×
397
                connUID:          params.ConnUID,
71×
398
                aggregatorChan:   aggChan,
71×
399
                aggregatorDFChan: aggDFChan,
71×
400
                specialChan:      specialChan,
71×
401
                controlMsgChan:   controlMsgChan,
71×
402
                confirmedChan:    confirmedChan,
71×
403
                finishedChan:     finishedChan,
71×
404
                establishedChan:  make(chan struct{}),
71×
405
                errorChan:        make(chan error, 1),
71×
406
                remoteAddr:       remoteAddr,
71×
407
                mtu:              DefaultMTU,
71×
408
                crypto:           crypto,
71×
409
                maxPayload:       DefaultMTU - UDPOverhead,
71×
410
                overheadDF:       crypto.Overhead(),
71×
411
                senderDF:         newUDPSenderDF(params.LocalAddr.IP, sleeve.localPort),
71×
412
        }
71×
413

71×
414
        go fwd.run(aggChan, aggDFChan, specialChan, controlMsgChan, confirmedChan, finishedChan)
71×
415
        return fwd, nil
71×
416
}
417

418
func (fwd *sleeveForwarder) logPrefixFor(sender *net.UDPAddr) string {
946×
419
        return fmt.Sprintf("sleeve ->[%s|%s]: ", sender, fwd.remotePeer)
946×
420
}
946×
421

422
func (fwd *sleeveForwarder) logPrefix() string {
946×
423
        fwd.lock.RLock()
946×
424
        remoteAddr := fwd.remoteAddr
946×
425
        fwd.lock.RUnlock()
946×
426
        return fwd.logPrefixFor(remoteAddr)
946×
427
}
946×
428

429
func (fwd *sleeveForwarder) Confirm() {
66×
430
        log.Debug(fwd.logPrefix(), "Confirm")
66×
431
        select {
66×
432
        case fwd.confirmedChan <- struct{}{}:
66×
433
        case <-fwd.finishedChan:
!
434
        }
435
}
436

437
func (fwd *sleeveForwarder) EstablishedChannel() <-chan struct{} {
71×
438
        return fwd.establishedChan
71×
439
}
71×
440

441
func (fwd *sleeveForwarder) ErrorChannel() <-chan error {
136×
442
        return fwd.errorChan
136×
443
}
136×
444

445
type curriedForward struct {
446
        NonDiscardingFlowOp
447
        fwd *sleeveForwarder
448
        key ForwardPacketKey
449
}
450

451
func (fwd *sleeveForwarder) Forward(key ForwardPacketKey) FlowOp {
87×
452
        return curriedForward{fwd: fwd, key: key}
87×
453
}
87×
454

455
func (f curriedForward) Process(frame []byte, dec *EthernetDecoder, broadcast bool) {
87×
456
        fwd := f.fwd
87×
457
        fwd.lock.RLock()
87×
458
        haveContact := (fwd.remoteAddr != nil)
87×
459
        mtu := fwd.mtu
87×
460
        stackFrag := fwd.stackFrag
87×
461
        fwd.lock.RUnlock()
87×
462

87×
463
        if !haveContact {
87×
464
                log.Print(fwd.logPrefix(), "Cannot forward frame yet - awaiting contact")
!
465
                return
!
466
        }
!
467

468
        srcName := f.key.SrcPeer.NameByte
87×
469
        dstName := f.key.DstPeer.NameByte
87×
470

87×
471
        // We could use non-blocking channel sends here, i.e. drop frames
87×
472
        // on the floor when the forwarder is busy. This would allow our
87×
473
        // caller - the capturing loop in the router - to read frames more
87×
474
        // quickly when under load, i.e. we'd drop fewer frames on the
87×
475
        // floor during capture. And we could maximise CPU utilisation
87×
476
        // since we aren't stalling a thread. However, a lot of work has
87×
477
        // already been done by the time we get here. Since any packet we
87×
478
        // drop will likely get re-transmitted we end up paying that cost
87×
479
        // multiple times. So it's better to drop things at the beginning
87×
480
        // of our pipeline.
87×
481
        if dec.DF() {
112×
482
                if !frameTooBig(frame, mtu) {
49×
483
                        fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, frame)
24×
484
                        return
24×
485
                }
24×
486

487
                // Why do we need an explicit broadcast hint here,
488
                // rather than just checking the frame for a broadcast
489
                // destination MAC address?  Because even
490
                // non-broadcast frames can be broadcast, if the
491
                // destination MAC was not in our MAC cache.
492
                if broadcast {
1×
493
                        log.Print(fwd.logPrefix(), "dropping too big DF broadcast frame (", dec.IP.SrcIP, " -> ", dec.IP.DstIP, "): MTU=", mtu)
!
494
                        return
!
495
                }
!
496

497
                // Send an ICMP back to where the frame came from
498
                fragNeededPacket, err := dec.makeICMPFragNeeded(mtu)
1×
499
                if err != nil {
1×
500
                        log.Print(fwd.logPrefix(), err)
!
501
                        return
!
502
                }
!
503

504
                dec.DecodeLayers(fragNeededPacket)
1×
505

1×
506
                // The frag-needed packet does not have DF set, so the
1×
507
                // potential recursion here is bounded.
1×
508
                fwd.sleeve.sendToConsumer(f.key.DstPeer, f.key.SrcPeer, fragNeededPacket, dec)
1×
509
                return
1×
510
        }
511

512
        if stackFrag || len(dec.decoded) < 2 {
108×
513
                fwd.aggregate(fwd.aggregatorChan, srcName, dstName, frame)
46×
514
                return
46×
515
        }
46×
516

517
        // Don't have trustworthy stack, so we're going to have to
518
        // send it DF in any case.
519
        if !frameTooBig(frame, mtu) {
18×
520
                fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, frame)
2×
521
                return
2×
522
        }
2×
523

524
        // We can't trust the stack to fragment, we have IP, and we
525
        // have a frame that's too big for the MTU, so we have to
526
        // fragment it ourself.
527
        checkWarn(fragment(dec.Eth, dec.IP, mtu,
14×
528
                func(segFrame []byte) {
56×
529
                        fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, segFrame)
42×
530
                }))
42×
531
}
532

533
func (fwd *sleeveForwarder) aggregate(ch chan<- aggregatorFrame, src []byte, dst []byte, frame []byte) {
114×
534
        select {
114×
535
        case ch <- aggregatorFrame{src, dst, frame}:
114×
536
        case <-fwd.finishedChan:
!
537
        }
538
}
539

540
func fragment(eth layers.Ethernet, ip layers.IPv4, mtu int, forward func([]byte)) error {
14×
541
        // We are not doing any sort of NAT, so we don't need to worry
14×
542
        // about checksums of IP payload (eg UDP checksum).
14×
543
        headerSize := int(ip.IHL) * 4
14×
544
        // &^ is bit clear (AND NOT). So here we're clearing the lowest 3
14×
545
        // bits.
14×
546
        maxSegmentSize := (mtu - headerSize) &^ 7
14×
547
        opts := gopacket.SerializeOptions{
14×
548
                FixLengths:       false,
14×
549
                ComputeChecksums: true}
14×
550
        payloadSize := int(ip.Length) - headerSize
14×
551
        payload := ip.BaseLayer.Payload[:payloadSize]
14×
552
        offsetBase := int(ip.FragOffset) << 3
14×
553
        origFlags := ip.Flags
14×
554
        ip.Flags = ip.Flags | layers.IPv4MoreFragments
14×
555
        ip.Length = uint16(headerSize + maxSegmentSize)
14×
556
        if eth.EthernetType == layers.EthernetTypeLLC {
14×
557
                // using LLC, so must set eth length correctly. eth length
!
558
                // is just the length of the payload
!
559
                eth.Length = ip.Length
!
560
        } else {
14×
561
                eth.Length = 0
14×
562
        }
14×
563
        for offset := 0; offset < payloadSize; offset += maxSegmentSize {
56×
564
                var segmentPayload []byte
42×
565
                if len(payload) <= maxSegmentSize {
56×
566
                        // last one
14×
567
                        segmentPayload = payload
14×
568
                        ip.Length = uint16(len(payload) + headerSize)
14×
569
                        ip.Flags = origFlags
14×
570
                        if eth.EthernetType == layers.EthernetTypeLLC {
14×
571
                                eth.Length = ip.Length
!
572
                        } else {
14×
573
                                eth.Length = 0
14×
574
                        }
14×
575
                } else {
28×
576
                        segmentPayload = payload[:maxSegmentSize]
28×
577
                        payload = payload[maxSegmentSize:]
28×
578
                }
28×
579
                ip.FragOffset = uint16((offset + offsetBase) >> 3)
42×
580
                buf := gopacket.NewSerializeBuffer()
42×
581
                segPayload := gopacket.Payload(segmentPayload)
42×
582
                err := gopacket.SerializeLayers(buf, opts, &eth, &ip, &segPayload)
42×
583
                if err != nil {
42×
584
                        return err
!
585
                }
!
586

587
                forward(buf.Bytes())
42×
588
        }
589
        return nil
14×
590
}
591

592
func frameTooBig(frame []byte, mtu int) bool {
41×
593
        // We capture/forward complete ethernet frames. Therefore the
41×
594
        // frame length includes the ethernet header. However, MTUs
41×
595
        // operate at the IP layer and thus do not include the ethernet
41×
596
        // header. To put it another way, when a sender that was told an
41×
597
        // MTU of M sends an IP packet of exactly that length, we will
41×
598
        // capture/forward M + EthernetOverhead bytes of data.
41×
599
        return len(frame) > mtu+EthernetOverhead
41×
600
}
41×
601

602
func (fwd *sleeveForwarder) ControlMessage(tag byte, msg []byte) {
198×
603
        select {
198×
604
        case fwd.controlMsgChan <- controlMessage{tag, msg}:
198×
605
        case <-fwd.finishedChan:
!
606
        }
607
}
608

609
func (fwd *sleeveForwarder) DisplayName() string {
3×
610
        return "sleeve"
3×
611
}
3×
612

613
func (fwd *sleeveForwarder) Stop() {
39×
614
        fwd.sleeve.removeForwarder(fwd.remotePeer.Name, fwd)
39×
615

39×
616
        // Tell the forwarder goroutine to finish.  We don't need to
39×
617
        // wait for it.
39×
618
        close(fwd.confirmedChan)
39×
619
}
39×
620

621
func (fwd *sleeveForwarder) run(aggChan <-chan aggregatorFrame,
622
        aggDFChan <-chan aggregatorFrame,
623
        specialChan <-chan specialFrame,
624
        controlMsgChan <-chan controlMessage,
625
        confirmedChan <-chan struct{},
626
        finishedChan chan<- struct{}) {
71×
627
        defer close(finishedChan)
71×
628

71×
629
        var err error
71×
630
loop:
71×
631
        for err == nil {
849×
632
                select {
778×
633
                case frame := <-aggChan:
29×
634
                        err = fwd.aggregateAndSend(frame, aggChan, fwd.crypto.Enc, fwd.sleeve, MaxUDPPacketSize-UDPOverhead)
29×
635

636
                case frame := <-aggDFChan:
10×
637
                        err = fwd.aggregateAndSend(frame, aggDFChan, fwd.crypto.EncDF, fwd.senderDF, fwd.maxPayload)
10×
638

639
                case sf := <-specialChan:
273×
640
                        err = fwd.handleSpecialFrame(sf)
273×
641

642
                case cm := <-controlMsgChan:
198×
643
                        err = fwd.handleControlMessage(cm)
198×
644

645
                case _, ok := <-confirmedChan:
105×
646
                        if !ok {
144×
647
                                // confirmedChan is closed to indicate
39×
648
                                // the forwarder is being closed
39×
649
                                break loop
39×
650
                        }
651

652
                        err = fwd.confirmed()
66×
653

654
                case <-timerChan(fwd.heartbeatTimer):
83×
655
                        err = fwd.sendHeartbeat()
83×
656

657
                case <-timerChan(fwd.heartbeatTimeout):
!
658
                        err = fmt.Errorf("timed out waiting for UDP heartbeat")
!
659

660
                case <-tickerChan(fwd.fragTestTicker):
!
661
                        err = fwd.sendFragTest()
!
662

663
                case <-timerChan(fwd.mtuTestTimeout):
48×
664
                        err = fwd.handleMTUTestFailure()
48×
665
                }
666
        }
667

668
        if fwd.heartbeatTimer != nil {
72×
669
                fwd.heartbeatTimer.Stop()
33×
670
        }
33×
671
        if fwd.heartbeatTimeout != nil {
73×
672
                fwd.heartbeatTimeout.Stop()
34×
673
        }
34×
674
        if fwd.fragTestTicker != nil {
72×
675
                fwd.fragTestTicker.Stop()
33×
676
        }
33×
677
        if fwd.mtuTestTimeout != nil {
39×
678
                fwd.mtuTestTimeout.Stop()
!
679
        }
!
680

681
        checkWarn(fwd.senderDF.close())
39×
682

39×
683
        fwd.lock.RLock()
39×
684
        defer fwd.lock.RUnlock()
39×
685

39×
686
        // this is the only place we send an error to errorChan
39×
687
        fwd.errorChan <- err
39×
688
}
689

690
func (fwd *sleeveForwarder) aggregateAndSend(frame aggregatorFrame, aggChan <-chan aggregatorFrame, enc Encryptor, sender udpSender, limit int) error {
39×
691
        // Give up after processing N frames, to avoid starving the
39×
692
        // other activities of the forwarder goroutine.
39×
693
        i := 0
39×
694

39×
695
        for {
139×
696
                // Adding the first frame to an empty buffer
100×
697
                if !fits(frame, enc, limit) {
100×
698
                        log.Print(fwd.logPrefix(), "Dropping too big frame during forwarding: frame len ", len(frame.frame), ", limit ", limit)
!
699
                        return nil
!
700
                }
!
701

702
                for {
214×
703
                        enc.AppendFrame(frame.src, frame.dst, frame.frame)
114×
704
                        i++
114×
705

114×
706
                        gotOne := false
114×
707
                        if i < 100 {
228×
708
                                select {
114×
709
                                case frame = <-aggChan:
75×
710
                                        gotOne = true
75×
711
                                default:
39×
712
                                }
713
                        }
714

715
                        if !gotOne {
153×
716
                                return fwd.flushEncryptor(enc, sender)
39×
717
                        }
39×
718

719
                        // Accumulate frames until doing so would
720
                        // exceed the MTU.  Even in the non-DF case,
721
                        // it doesn't seem worth adding a frame where
722
                        // that would lead to fragmentation,
723
                        // potentially delaying or risking other
724
                        // frames.
725
                        if !fits(frame, enc, fwd.maxPayload) {
136×
726
                                break
61×
727
                        }
728
                }
729

730
                if err := fwd.flushEncryptor(enc, sender); err != nil {
61×
731
                        return err
!
732
                }
!
733
        }
734
}
735

736
func fits(frame aggregatorFrame, enc Encryptor, limit int) bool {
175×
737
        return enc.TotalLen()+enc.FrameOverhead()+len(frame.frame) <= limit
175×
738
}
175×
739

740
func (fwd *sleeveForwarder) flushEncryptor(enc Encryptor, sender udpSender) error {
495×
741
        msg, err := enc.Bytes()
495×
742
        if err != nil {
495×
743
                return err
!
744
        }
!
745

746
        return fwd.processSendError(sender.send(msg, fwd.remoteAddr))
495×
747
}
748

749
func (fwd *sleeveForwarder) sendSpecial(enc Encryptor, sender udpSender, data []byte) error {
395×
750
        enc.AppendFrame(fwd.sleeve.localPeerBin, fwd.remotePeerBin, data)
395×
751
        return fwd.flushEncryptor(enc, sender)
395×
752
}
395×
753

754
func (fwd *sleeveForwarder) handleSpecialFrame(special specialFrame) error {
273×
755
        // The special frame types are distinguished by length
273×
756
        switch len(special.frame) {
273×
757
        case EthernetOverhead + 8:
140×
758
                return fwd.handleHeartbeat(special)
140×
759

760
        case FragTestSize:
64×
761
                return fwd.handleFragTest(special.frame)
64×
762

763
        default:
69×
764
                return fwd.handleMTUTest(special.frame)
69×
765
        }
766
}
767

768
func (fwd *sleeveForwarder) handleControlMessage(cm controlMessage) error {
198×
769
        switch cm.tag {
198×
770
        case ProtocolConnectionEstablished:
65×
771
                return fwd.handleHeartbeatAck()
65×
772

773
        case ProtocolFragmentationReceived:
64×
774
                return fwd.handleFragTestAck()
64×
775

776
        case ProtocolPMTUVerified:
69×
777
                return fwd.handleMTUTestAck(cm.msg)
69×
778

779
        default:
!
780
                log.Print(fwd.logPrefix(), "Ignoring unknown control message tag: ", cm.tag)
!
781
                return nil
!
782
        }
783
}
784

785
func (fwd *sleeveForwarder) confirmed() error {
66×
786
        log.Debug(fwd.logPrefix(), "confirmed")
66×
787

66×
788
        if fwd.heartbeatInterval != 0 {
66×
789
                // already confirmed
!
790
                return nil
!
791
        }
!
792

793
        // when the connection is confirmed, this should be the only
794
        // forwarder to the peer.
795
        fwd.sleeve.addForwarder(fwd.remotePeer.Name, fwd)
66×
796

66×
797
        // heartbeatInterval flags that we want to send heartbeats,
66×
798
        // even if we don't do sendHeartbeat() yet due to lacking the
66×
799
        // remote address.
66×
800
        fwd.heartbeatInterval = FastHeartbeat
66×
801
        if fwd.remoteAddr != nil {
100×
802
                if err := fwd.sendHeartbeat(); err != nil {
34×
803
                        return err
!
804
                }
!
805
        }
806

807
        fwd.heartbeatTimeout = time.NewTimer(HeartbeatTimeout)
66×
808
        return nil
66×
809
}
810

811
func (fwd *sleeveForwarder) sendHeartbeat() error {
148×
812
        log.Debug(fwd.logPrefix(), "sendHeartbeat")
148×
813

148×
814
        // Prime the timer for the next heartbeat.  We don't use a
148×
815
        // ticker because the interval is not constant.
148×
816
        fwd.heartbeatTimer = setTimer(fwd.heartbeatTimer, fwd.heartbeatInterval)
148×
817

148×
818
        buf := make([]byte, EthernetOverhead+8)
148×
819
        binary.BigEndian.PutUint64(buf[EthernetOverhead:], fwd.connUID)
148×
820
        return fwd.sendSpecial(fwd.crypto.EncDF, fwd.senderDF, buf)
148×
821
}
148×
822

823
func (fwd *sleeveForwarder) handleHeartbeat(special specialFrame) error {
140×
824
        uid := binary.BigEndian.Uint64(special.frame[EthernetOverhead:])
140×
825
        if uid != fwd.connUID {
140×
826
                return nil
!
827
        }
!
828

829
        log.Debug(fwd.logPrefix(), "handleHeartbeat")
140×
830

140×
831
        if fwd.remoteAddr == nil {
171×
832
                fwd.setRemoteAddr(special.sender)
31×
833
                if fwd.heartbeatInterval != 0 {
62×
834
                        if err := fwd.sendHeartbeat(); err != nil {
31×
835
                                return err
!
836
                        }
!
837
                }
838
        } else if !udpAddrsEqual(fwd.remoteAddr, special.sender) {
109×
839
                log.Print(fwd.logPrefix(), "Peer UDP address changed to ", special.sender)
!
840
                fwd.setRemoteAddr(special.sender)
!
841
        }
!
842

843
        if !fwd.ackedHeartbeat {
205×
844
                fwd.ackedHeartbeat = true
65×
845
                if err := fwd.sendControlMsg(ProtocolConnectionEstablished, nil); err != nil {
65×
UNCOV
846
                        return err
!
UNCOV
847
                }
!
848
        }
849

850
        // we can receive a heartbeat before confirmed() has set up
851
        // heartbeatTimeout
852
        if fwd.heartbeatTimeout != nil {
280×
853
                fwd.heartbeatTimeout.Reset(HeartbeatTimeout)
140×
854
        }
140×
855

856
        return nil
140×
857
}
858

859
func (fwd *sleeveForwarder) setRemoteAddr(addr *net.UDPAddr) {
31×
860
        // remoteAddr is only modified here, so we don't need to hold
31×
861
        // the lock when reading it from the forwarder goroutine.  But
31×
862
        // other threads may read it while holding the read lock, so
31×
863
        // when we modify it, we need to hold the write lock.
31×
864
        fwd.lock.Lock()
31×
865
        fwd.remoteAddr = addr
31×
866
        fwd.lock.Unlock()
31×
867
}
31×
868

869
func (fwd *sleeveForwarder) handleHeartbeatAck() error {
65×
870
        log.Debug(fwd.logPrefix(), "handleHeartbeatAck")
65×
871

65×
872
        if fwd.heartbeatInterval != SlowHeartbeat {
130×
873
                fwd.heartbeatInterval = SlowHeartbeat
65×
874
                if fwd.heartbeatTimer != nil {
130×
875
                        fwd.heartbeatTimer.Reset(fwd.heartbeatInterval)
65×
876
                }
65×
877

878
                // The connection is now regarded as established
879
                close(fwd.establishedChan)
65×
880
        }
881

882
        fwd.fragTestTicker = time.NewTicker(FragTestInterval)
65×
883
        if err := fwd.sendFragTest(); err != nil {
65×
884
                return err
!
885
        }
!
886

887
        // Send a large frame down the DF channel.  An EMSGSIZE will
888
        // result, which is handled in processSendError, prompting
889
        // PMTU discovery to start.
890
        return fwd.sendSpecial(fwd.crypto.EncDF, fwd.senderDF, make([]byte, PMTUDiscoverySize))
65×
891
}
892

893
func (fwd *sleeveForwarder) sendFragTest() error {
65×
894
        log.Debug(fwd.logPrefix(), "sendFragTest")
65×
895
        fwd.stackFrag = false
65×
896
        return fwd.sendSpecial(fwd.crypto.Enc, fwd.sleeve, make([]byte, FragTestSize))
65×
897
}
65×
898

899
func (fwd *sleeveForwarder) handleFragTest(frame []byte) error {
64×
900
        if !allZeros(frame) {
64×
901
                return nil
!
902
        }
!
903

904
        return fwd.sendControlMsg(ProtocolFragmentationReceived, nil)
64×
905
}
906

907
func (fwd *sleeveForwarder) handleFragTestAck() error {
64×
908
        log.Debug(fwd.logPrefix(), "handleFragTestAck")
64×
909
        fwd.stackFrag = true
64×
910
        return nil
64×
911
}
64×
912

913
func (fwd *sleeveForwarder) processSendError(err error) error {
495×
914
        if mtbe, ok := err.(msgTooBigError); ok {
560×
915
                mtu := mtbe.underlayPMTU - fwd.overheadDF
65×
916
                if fwd.mtuCandidate != 0 && mtu >= fwd.mtuCandidate {
65×
917
                        return nil
!
918
                }
!
919

920
                fwd.mtuHighestGood = 552
65×
921
                fwd.mtuLowestBad = mtu + 1
65×
922
                fwd.mtuCandidate = mtu
65×
923
                fwd.mtuTestsSent = 0
65×
924
                fwd.maxPayload = mtbe.underlayPMTU - UDPOverhead
65×
925
                fwd.mtu = mtu
65×
926
                return fwd.sendMTUTest()
65×
927
        }
928

929
        return err
430×
930
}
931

932
func (fwd *sleeveForwarder) sendMTUTest() error {
117×
933
        log.Debug(fwd.logPrefix(), "sendMTUTest: mtu candidate ", fwd.mtuCandidate)
117×
934

117×
935
        err := fwd.sendSpecial(fwd.crypto.EncDF, fwd.senderDF, make([]byte, fwd.mtuCandidate+EthernetOverhead))
117×
936
        if err != nil {
117×
937
                return err
!
938
        }
!
939

940
        fwd.mtuTestTimeout = setTimer(fwd.mtuTestTimeout, MTUVerifyTimeout<<fwd.mtuTestsSent)
117×
941
        fwd.mtuTestsSent++
117×
942
        return nil
117×
943
}
944

945
func (fwd *sleeveForwarder) handleMTUTest(frame []byte) error {
69×
946
        buf := make([]byte, 2)
69×
947
        binary.BigEndian.PutUint16(buf, uint16(len(frame)-EthernetOverhead))
69×
948
        return fwd.sendControlMsg(ProtocolPMTUVerified, buf)
69×
949
}
69×
950

951
func (fwd *sleeveForwarder) handleMTUTestAck(msg []byte) error {
69×
952
        if len(msg) < 2 {
69×
953
                log.Print(fwd.logPrefix(), "Received truncated MTUTestAck")
!
954
                return nil
!
955
        }
!
956

957
        mtu := int(binary.BigEndian.Uint16(msg))
69×
958
        log.Debug(fwd.logPrefix(), "handleMTUTestAck: for mtu candidate ", mtu)
69×
959
        if mtu != fwd.mtuCandidate {
69×
UNCOV
960
                return nil
!
UNCOV
961
        }
!
962

963
        fwd.mtuHighestGood = mtu
69×
964
        return fwd.searchMTU()
69×
965
}
966

967
func (fwd *sleeveForwarder) handleMTUTestFailure() error {
48×
968
        if fwd.mtuTestsSent < MTUVerifyAttempts {
90×
969
                return fwd.sendMTUTest()
42×
970
        }
42×
971

972
        log.Debug(fwd.logPrefix(), "handleMTUTestFailure")
6×
973
        fwd.mtuLowestBad = fwd.mtuCandidate
6×
974
        return fwd.searchMTU()
6×
975
}
976

977
func (fwd *sleeveForwarder) searchMTU() error {
75×
978
        log.Debug(fwd.logPrefix(), "searchMTU: ", fwd.mtuHighestGood, fwd.mtuLowestBad)
75×
979

75×
980
        if fwd.mtuHighestGood+1 >= fwd.mtuLowestBad {
140×
981
                mtu := fwd.mtuHighestGood
65×
982
                log.Print(fwd.logPrefix(), "Effective MTU verified at ", mtu)
65×
983

65×
984
                if fwd.mtuTestTimeout != nil {
130×
985
                        fwd.mtuTestTimeout.Stop()
65×
986
                        fwd.mtuTestTimeout = nil
65×
987
                }
65×
988

989
                fwd.mtuCandidate = 0
65×
990
                fwd.maxPayload = mtu + fwd.overheadDF - UDPOverhead
65×
991
                fwd.mtu = mtu
65×
992
                return nil
65×
993
        }
994

995
        fwd.mtuCandidate = (fwd.mtuHighestGood + fwd.mtuLowestBad) / 2
10×
996
        fwd.mtuTestsSent = 0
10×
997
        return fwd.sendMTUTest()
10×
998
}
999

1000
type udpSenderDF struct {
1001
        ipBuf     gopacket.SerializeBuffer
1002
        opts      gopacket.SerializeOptions
1003
        udpHeader *layers.UDP
1004
        localIP   net.IP
1005
        remoteIP  net.IP
1006
        socket    *net.IPConn
1007
}
1008

1009
func newUDPSenderDF(localIP net.IP, localPort int) *udpSenderDF {
71×
1010
        return &udpSenderDF{
71×
1011
                ipBuf: gopacket.NewSerializeBuffer(),
71×
1012
                opts: gopacket.SerializeOptions{
71×
1013
                        FixLengths: true,
71×
1014
                        // UDP header is calculated with a phantom IP
71×
1015
                        // header. Yes, it's totally nuts. Thankfully,
71×
1016
                        // for UDP over IPv4, the checksum is
71×
1017
                        // optional. It's not optional for IPv6, but
71×
1018
                        // we'll ignore that for now. TODO
71×
1019
                        ComputeChecksums: false,
71×
1020
                },
71×
1021
                udpHeader: &layers.UDP{SrcPort: layers.UDPPort(localPort)},
71×
1022
                localIP:   localIP,
71×
1023
        }
71×
1024
}
71×
1025

1026
func (sender *udpSenderDF) dial() error {
65×
1027
        if sender.socket != nil {
65×
1028
                if err := sender.socket.Close(); err != nil {
!
1029
                        return err
!
1030
                }
!
1031

1032
                sender.socket = nil
!
1033
        }
1034

1035
        laddr := &net.IPAddr{IP: sender.localIP}
65×
1036
        raddr := &net.IPAddr{IP: sender.remoteIP}
65×
1037
        s, err := net.DialIP("ip4:UDP", laddr, raddr)
65×
1038

65×
1039
        f, err := s.File()
65×
1040
        if err != nil {
65×
1041
                return err
!
1042
        }
!
1043

1044
        defer f.Close()
65×
1045

65×
1046
        // This makes sure all packets we send out have DF set on them.
65×
1047
        err = syscall.SetsockoptInt(int(f.Fd()), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO)
65×
1048
        if err != nil {
65×
1049
                return err
!
1050
        }
!
1051

1052
        sender.socket = s
65×
1053
        return nil
65×
1054
}
1055

1056
func (sender *udpSenderDF) send(msg []byte, raddr *net.UDPAddr) error {
387×
1057
        // Ensure we have a socket sending to the right IP address
387×
1058
        if sender.socket == nil || !bytes.Equal(sender.remoteIP, raddr.IP) {
452×
1059
                sender.remoteIP = raddr.IP
65×
1060
                if err := sender.dial(); err != nil {
65×
1061
                        return err
!
1062
                }
!
1063
        }
1064

1065
        sender.udpHeader.DstPort = layers.UDPPort(raddr.Port)
387×
1066
        payload := gopacket.Payload(msg)
387×
1067
        err := gopacket.SerializeLayers(sender.ipBuf, sender.opts, sender.udpHeader, &payload)
387×
1068
        if err != nil {
387×
1069
                return err
!
1070
        }
!
1071

1072
        packet := sender.ipBuf.Bytes()
387×
1073
        _, err = sender.socket.Write(packet)
387×
1074
        if err == nil || PosixError(err) != syscall.EMSGSIZE {
709×
1075
                return err
322×
1076
        }
322×
1077

1078
        f, err := sender.socket.File()
65×
1079
        if err != nil {
65×
1080
                return err
!
1081
        }
!
1082
        defer f.Close()
65×
1083

65×
1084
        log.Print("EMSGSIZE on send, expecting PMTU update (IP packet was ", len(packet), " bytes, payload was ", len(msg), " bytes)")
65×
1085
        pmtu, err := syscall.GetsockoptInt(int(f.Fd()), syscall.IPPROTO_IP, syscall.IP_MTU)
65×
1086
        if err != nil {
65×
1087
                return err
!
1088
        }
!
1089

1090
        return msgTooBigError{underlayPMTU: pmtu}
65×
1091
}
1092

1093
type msgTooBigError struct {
1094
        underlayPMTU int // actual pmtu, i.e. what the kernel told us
1095
}
1096

1097
func (mtbe msgTooBigError) Error() string {
!
1098
        return fmt.Sprint("Msg too big error. PMTU is ", mtbe.underlayPMTU)
!
1099
}
!
1100

1101
func (sender *udpSenderDF) close() error {
39×
1102
        if sender.socket == nil {
45×
1103
                return nil
6×
1104
        }
6×
1105

1106
        return sender.socket.Close()
33×
1107
}
1108

1109
func udpAddrsEqual(a *net.UDPAddr, b *net.UDPAddr) bool {
209×
1110
        return bytes.Equal(a.IP, b.IP) && a.Port == b.Port && a.Zone == b.Zone
209×
1111
}
209×
1112

1113
func allZeros(s []byte) bool {
64×
1114
        for _, b := range s {
3,840,128×
1115
                if b != byte(0) {
3,840,064×
1116
                        return false
!
1117
                }
!
1118
        }
1119

1120
        return true
64×
1121
}
1122

1123
func setTimer(timer *time.Timer, d time.Duration) *time.Timer {
265×
1124
        if timer == nil {
395×
1125
                return time.NewTimer(d)
130×
1126
        }
130×
1127

1128
        timer.Reset(d)
135×
1129
        return timer
135×
1130

1131
}
1132

1133
func timerChan(timer *time.Timer) <-chan time.Time {
2,334×
1134
        if timer != nil {
3,930×
1135
                return timer.C
1,596×
1136
        }
1,596×
1137
        return nil
738×
1138
}
1139

1140
func tickerChan(ticker *time.Ticker) <-chan time.Time {
778×
1141
        if ticker != nil {
1,345×
1142
                return ticker.C
567×
1143
        }
567×
1144
        return nil
211×
1145
}
1146

1147
func makeUDPAddr(addr *net.TCPAddr) *net.UDPAddr {
73×
1148
        return &net.UDPAddr{IP: addr.IP, Port: addr.Port, Zone: addr.Zone}
73×
1149
}
73×
1150

1151
// Look inside an error produced by the net package to get to the
1152
// syscall.Errno at the root of the problem.
1153
func PosixError(err error) error {
65×
1154
        if operr, ok := err.(*net.OpError); ok {
130×
1155
                err = operr.Err
65×
1156
        }
65×
1157

1158
        // go1.5 wraps an Errno inside a SyscallError inside an OpError
1159
        if scerr, ok := err.(*os.SyscallError); ok {
130×
1160
                err = scerr.Err
65×
1161
        }
65×
1162

1163
        return err
65×
1164
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2022 Coveralls, Inc