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