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
}
|