starnet/ping_logic_test.go
starainrt b5bd7595a1
1. 优化ping功能
2. 新增重试机制
3. 优化错误处理逻辑
2026-03-19 16:42:45 +08:00

215 lines
5.8 KiB
Go

package starnet
import (
"context"
"errors"
"net"
"testing"
"time"
)
func buildICMPPacket(typ uint8, identifier, seq uint16) []byte {
icmp := ICMP{
Type: typ,
Code: 0,
CheckSum: 0,
Identifier: identifier,
SequenceNum: seq,
}
buf := marshalICMPPacket(icmp, nil)
cs := checkSum(buf)
buf[2] = byte(cs >> 8)
buf[3] = byte(cs)
return buf
}
func TestNextPingIdentifierChanges(t *testing.T) {
id1 := nextPingIdentifier()
id2 := nextPingIdentifier()
if id1 == id2 {
t.Fatalf("identifier should change between calls: %d == %d", id1, id2)
}
}
func TestIsExpectedEchoReplyIPv4(t *testing.T) {
identifier := uint16(0x1234)
seq := uint16(0x0102)
reply := buildICMPPacket(icmpTypeEchoReplyV4, identifier, seq)
if !isExpectedEchoReply(reply, 4, icmpTypeEchoReplyV4, identifier, seq) {
t.Fatal("expected IPv4 echo reply to match")
}
if isExpectedEchoReply(reply, 4, icmpTypeEchoReplyV4, identifier, seq+1) {
t.Fatal("mismatched sequence should not match")
}
}
func TestIsExpectedEchoReplyIPv4WithIPHeader(t *testing.T) {
identifier := uint16(0x1111)
seq := uint16(0x2222)
ipHeader := make([]byte, 20)
ipHeader[0] = 0x45 // version=4, ihl=5
reply := buildICMPPacket(icmpTypeEchoReplyV4, identifier, seq)
packet := append(ipHeader, reply...)
if !isExpectedEchoReply(packet, 4, icmpTypeEchoReplyV4, identifier, seq) {
t.Fatal("expected IPv4 packet with header to match")
}
}
func TestIsExpectedEchoReplyIPv6WithHeader(t *testing.T) {
identifier := uint16(0xabcd)
seq := uint16(0x00ff)
ipv6Header := make([]byte, 40)
ipv6Header[0] = 0x60 // version=6
reply := buildICMPPacket(icmpTypeEchoReplyV6, identifier, seq)
packet := append(ipv6Header, reply...)
if !isExpectedEchoReply(packet, 6, icmpTypeEchoReplyV6, identifier, seq) {
t.Fatal("expected IPv6 packet with header to match")
}
}
func TestPingInvalidTimeout(t *testing.T) {
_, err := Ping("127.0.0.1", 1, 0)
if err == nil {
t.Fatal("expected error for non-positive timeout")
}
if !errors.Is(err, ErrPingInvalidTimeout) {
t.Fatalf("expected ErrPingInvalidTimeout, got: %v", err)
}
}
func TestIsIPPingableInvalidRetry(t *testing.T) {
if IsIpPingable("127.0.0.1", time.Millisecond, 0) {
t.Fatal("retryLimit=0 should return false")
}
}
func TestSocketSpecForIP(t *testing.T) {
v4, err := socketSpecForIP(net.ParseIP("127.0.0.1"))
if err != nil {
t.Fatalf("unexpected v4 error: %v", err)
}
if v4.network != "ip4:icmp" || v4.family != 4 || v4.requestType != icmpTypeEchoRequestV4 || v4.replyType != icmpTypeEchoReplyV4 {
t.Fatalf("unexpected v4 spec: %+v", v4)
}
v6, err := socketSpecForIP(net.ParseIP("::1"))
if err != nil {
t.Fatalf("unexpected v6 error: %v", err)
}
if v6.network != "ip6:ipv6-icmp" || v6.family != 6 || v6.requestType != icmpTypeEchoRequestV6 || v6.replyType != icmpTypeEchoReplyV6 {
t.Fatalf("unexpected v6 spec: %+v", v6)
}
_, err = socketSpecForIP(nil)
if err == nil {
t.Fatal("expected error for nil ip")
}
if !errors.Is(err, ErrInvalidIP) {
t.Fatalf("expected ErrInvalidIP, got: %v", err)
}
}
func TestResolvePingTargetsLiteral(t *testing.T) {
v4, err := resolvePingTargets("127.0.0.1", false, false)
if err != nil {
t.Fatalf("unexpected v4 resolve error: %v", err)
}
if len(v4) != 1 || v4[0] == nil || v4[0].IP == nil || v4[0].IP.To4() == nil {
t.Fatalf("unexpected v4 targets: %+v", v4)
}
v6, err := resolvePingTargets("::1", false, false)
if err != nil {
t.Fatalf("unexpected v6 resolve error: %v", err)
}
if len(v6) != 1 || v6[0] == nil || v6[0].IP == nil || v6[0].IP.To16() == nil || v6[0].IP.To4() != nil {
t.Fatalf("unexpected v6 targets: %+v", v6)
}
}
func TestNormalizePingDialError(t *testing.T) {
perr := normalizePingDialError(errors.New("socket: operation not permitted"))
if !errors.Is(perr, ErrPingPermissionDenied) {
t.Fatalf("expected ErrPingPermissionDenied, got: %v", perr)
}
uerr := normalizePingDialError(errors.New("unknown network ip6:ipv6-icmp"))
if !errors.Is(uerr, ErrPingProtocolUnsupported) {
t.Fatalf("expected ErrPingProtocolUnsupported, got: %v", uerr)
}
}
func TestOrderPingTargets(t *testing.T) {
targets := []*net.IPAddr{
{IP: net.ParseIP("::1")},
{IP: net.ParseIP("127.0.0.1")},
}
v4First := orderPingTargets(targets, true, false)
if v4First[0].IP.To4() == nil {
t.Fatalf("expected IPv4 first, got: %v", v4First[0].IP)
}
v6First := orderPingTargets(targets, false, true)
if v6First[0].IP.To4() != nil {
t.Fatalf("expected IPv6 first, got: %v", v6First[0].IP)
}
}
func TestNormalizePingOptions(t *testing.T) {
opts, err := normalizePingOptions(nil, 3, 2*time.Second)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if opts.Count != 3 || opts.Timeout != 2*time.Second {
t.Fatalf("unexpected defaults: %+v", opts)
}
_, err = normalizePingOptions(&PingOptions{Count: -1}, 3, 2*time.Second)
if err == nil {
t.Fatal("expected error for negative count")
}
_, err = normalizePingOptions(&PingOptions{Timeout: -1}, 3, 2*time.Second)
if err == nil {
t.Fatal("expected error for negative timeout")
}
_, err = normalizePingOptions(&PingOptions{PayloadSize: maxPingPayloadSize + 1}, 3, 2*time.Second)
if err == nil {
t.Fatal("expected error for too large payload")
}
}
func TestPingWithContextCanceled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := PingWithContext(ctx, "127.0.0.1", 1, time.Second)
if err == nil {
t.Fatal("expected canceled error")
}
if !errors.Is(err, context.Canceled) {
t.Fatalf("expected context.Canceled, got: %v", err)
}
}
func TestPingableInvalidOptions(t *testing.T) {
_, err := Pingable("127.0.0.1", &PingOptions{Count: -1})
if err == nil {
t.Fatal("expected invalid count error")
}
_, err = Pingable("127.0.0.1", &PingOptions{Interval: -1})
if err == nil {
t.Fatal("expected invalid interval error")
}
}