Files
RemLink/internal/subnetgateway/pingrelay/relay.go
T
qsc20001102 142e5dc7d6
ci / Go checks (ubuntu-latest) (push) Has been cancelled
ci / Go checks (windows-latest) (push) Has been cancelled
初版功能完成
2026-08-29 13:12:17 +08:00

86 lines
2.2 KiB
Go

// Package pingrelay probes a Site target and preserves Echo ID/Sequence.
package pingrelay
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
const DefaultTimeout = 3 * time.Second
type Relay struct {
Timeout time.Duration
}
// Echo sends one host ICMP Echo and waits for the matching target reply.
func (r Relay) Echo(ctx context.Context, target netip.Addr, id, sequence int, data []byte) error {
if !target.Is4() || id < 0 || id > 65535 || sequence < 0 || sequence > 65535 {
return errors.New("PingRelay target, ID, or Sequence is invalid")
}
timeout := r.Timeout
if timeout <= 0 {
timeout = DefaultTimeout
}
connection, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return fmt.Errorf("open host ICMP socket: %w", err)
}
defer connection.Close()
deadline := time.Now().Add(timeout)
if contextDeadline, ok := ctx.Deadline(); ok && contextDeadline.Before(deadline) {
deadline = contextDeadline
}
if err := connection.SetDeadline(deadline); err != nil {
return err
}
stopClose := make(chan struct{})
go func() {
select {
case <-ctx.Done():
_ = connection.Close()
case <-stopClose:
}
}()
defer close(stopClose)
message := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{ID: id, Seq: sequence, Data: append([]byte(nil), data...)},
}
encoded, err := message.Marshal(nil)
if err != nil {
return fmt.Errorf("marshal ICMP Echo: %w", err)
}
if _, err := connection.WriteTo(encoded, &net.IPAddr{IP: net.IP(target.AsSlice())}); err != nil {
return fmt.Errorf("send ICMP Echo to %s: %w", target, err)
}
buffer := make([]byte, 1500)
for {
count, peer, err := connection.ReadFrom(buffer)
if err != nil {
if ctx.Err() != nil {
return ctx.Err()
}
return fmt.Errorf("wait for ICMP Echo Reply from %s: %w", target, err)
}
peerIP, ok := netip.AddrFromSlice(peer.(*net.IPAddr).IP)
if !ok || peerIP.Unmap() != target {
continue
}
parsed, err := icmp.ParseMessage(1, buffer[:count])
if err != nil || parsed.Type != ipv4.ICMPTypeEchoReply {
continue
}
echo, ok := parsed.Body.(*icmp.Echo)
if ok && echo.ID == id && echo.Seq == sequence {
return nil
}
}
}