File: ping.go

package info (click to toggle)
receptor 1.5.5-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,772 kB
  • sloc: python: 1,643; makefile: 305; sh: 174
file content (141 lines) | stat: -rw-r--r-- 3,700 bytes parent folder | download | duplicates (2)
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
package netceptor

import (
	"context"
	"fmt"
	"strings"
	"time"
)

// NetcForPing should include all methods of Netceptor needed by the Ping function.
type NetcForPing interface {
	ListenPacket(service string) (PacketConner, error)
	NewAddr(target string, service string) Addr
	NodeID() string
	Context() context.Context
}

// Ping calls SendPing to sends a single test packet and waits for a reply or error.
func (s *Netceptor) Ping(ctx context.Context, target string, hopsToLive byte) (time.Duration, string, error) {
	return SendPing(ctx, s, target, hopsToLive)
}

// SendPing creates Ping by sending a single test packet and waits for a replay or error.
func SendPing(ctx context.Context, s NetcForPing, target string, hopsToLive byte) (time.Duration, string, error) {
	pc, err := s.ListenPacket("")
	if err != nil {
		return 0, "", err
	}
	ctxPing, ctxCancel := context.WithCancel(ctx)
	defer func() {
		ctxCancel()
		_ = pc.Close()
	}()
	pc.SetHopsToLive(hopsToLive)
	doneChan := make(chan struct{})
	unrCh := pc.SubscribeUnreachable(doneChan)
	defer close(doneChan)
	type errorResult struct {
		err      error
		fromNode string
	}
	errorChan := make(chan errorResult)
	go func() {
		for msg := range unrCh {
			errorChan <- errorResult{
				err:      fmt.Errorf(msg.Problem), //nolint:govet
				fromNode: msg.ReceivedFromNode,
			}
		}
	}()
	startTime := time.Now()
	replyChan := make(chan string)
	go func() {
		buf := make([]byte, 8)
		_, addr, err := pc.ReadFrom(buf)
		fromNode := ""
		if addr != nil {
			fromNode = addr.String()
			fromNode = strings.TrimSuffix(fromNode, ":ping")
		}
		if err == nil {
			select {
			case replyChan <- fromNode:
			case <-ctxPing.Done():
			case <-s.Context().Done():
			}
		} else {
			select {
			case errorChan <- errorResult{
				err:      err,
				fromNode: fromNode,
			}:
			case <-ctx.Done():
			case <-s.Context().Done():
			}
		}
	}()
	_, err = pc.WriteTo([]byte{}, s.NewAddr(target, "ping"))
	if err != nil {
		return time.Since(startTime), s.NodeID(), err
	}
	select {
	case errRes := <-errorChan:
		return time.Since(startTime), errRes.fromNode, errRes.err
	case remote := <-replyChan:
		return time.Since(startTime), remote, nil
	case <-time.After(10 * time.Second):
		return time.Since(startTime), "", fmt.Errorf("timeout")
	case <-ctxPing.Done():
		return time.Since(startTime), "", fmt.Errorf("user cancelled")
	case <-s.Context().Done():
		return time.Since(startTime), "", fmt.Errorf("netceptor shutdown")
	}
}

type NetcForTraceroute interface {
	MaxForwardingHops() byte
	Ping(ctx context.Context, target string, hopsToLive byte) (time.Duration, string, error)
	Context() context.Context
}

// TracerouteResult is the result of one hop of a traceroute.
type TracerouteResult struct {
	From string
	Time time.Duration
	Err  error
}

func (s *Netceptor) Traceroute(ctx context.Context, target string) <-chan *TracerouteResult {
	return CreateTraceroute(ctx, s, target)
}

// CreateTraceroute returns a channel which will receive a series of hops between this node and the target.
func CreateTraceroute(ctx context.Context, s NetcForTraceroute, target string) <-chan *TracerouteResult {
	results := make(chan *TracerouteResult)
	go func() {
		defer close(results)
		for i := 0; i <= int(s.MaxForwardingHops()); i++ {
			pingTime, pingRemote, err := s.Ping(ctx, target, byte(i))
			res := &TracerouteResult{
				From: pingRemote,
				Time: pingTime,
			}
			if err != nil && err.Error() != ProblemExpiredInTransit {
				res.Err = err
			}
			select {
			case results <- res:
			case <-ctx.Done():
				return
			case <-s.Context().Done():
				return
			}
			if res.Err != nil || err == nil {
				return
			}
		}
	}()

	return results
}