- 分离 Request 的配置态与执行态,修复二次 Do、raw 模式网络配置失效和 body 来源互斥问题 - 新增 starnet trace 抽象,补齐 DNS/连接/TLS/重试事件,并优化动态 transport 缓存与代理解析路径 - 收紧非法代理为 fail-fast,多目标目标回退仅限幂等请求,修复 Host/TLS/SNI 等语义边界 - 补充防御性拷贝、专项回归测试、本地代理/TLS 用例与 README 行为说明
231 lines
4.9 KiB
Go
231 lines
4.9 KiB
Go
package pingcore
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"net"
|
|
"os"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
const icmpHeaderLen = 8
|
|
|
|
type ICMP struct {
|
|
Type uint8
|
|
Code uint8
|
|
CheckSum uint16
|
|
Identifier uint16
|
|
SequenceNum uint16
|
|
}
|
|
|
|
type Options struct {
|
|
Count int
|
|
Timeout time.Duration
|
|
Interval time.Duration
|
|
Deadline time.Time
|
|
PreferIPv4 bool
|
|
PreferIPv6 bool
|
|
SourceIP net.IP
|
|
PayloadSize int
|
|
}
|
|
|
|
type Result struct {
|
|
Duration time.Duration
|
|
RecvCount int
|
|
RemoteIP string
|
|
}
|
|
|
|
var identifierSeed uint32
|
|
|
|
func NextIdentifier() uint16 {
|
|
pid := uint32(os.Getpid() & 0xffff)
|
|
n := atomic.AddUint32(&identifierSeed, 1)
|
|
return uint16((pid + n) & 0xffff)
|
|
}
|
|
|
|
func Payload(size int) []byte {
|
|
if size <= 0 {
|
|
return nil
|
|
}
|
|
payload := make([]byte, size)
|
|
for index := 0; index < len(payload); index++ {
|
|
payload[index] = byte(index)
|
|
}
|
|
return payload
|
|
}
|
|
|
|
func BuildICMP(seq, identifier uint16, typ uint8, payload []byte) ICMP {
|
|
icmp := ICMP{
|
|
Type: typ,
|
|
Code: 0,
|
|
CheckSum: 0,
|
|
Identifier: identifier,
|
|
SequenceNum: seq,
|
|
}
|
|
buf := MarshalPacket(icmp, payload)
|
|
icmp.CheckSum = Checksum(buf)
|
|
return icmp
|
|
}
|
|
|
|
func Checksum(data []byte) uint16 {
|
|
var (
|
|
sum uint32
|
|
length = len(data)
|
|
index int
|
|
)
|
|
for length > 1 {
|
|
sum += uint32(data[index])<<8 + uint32(data[index+1])
|
|
index += 2
|
|
length -= 2
|
|
}
|
|
if length > 0 {
|
|
sum += uint32(data[index]) << 8
|
|
}
|
|
for sum>>16 != 0 {
|
|
sum = (sum & 0xffff) + (sum >> 16)
|
|
}
|
|
return uint16(^sum)
|
|
}
|
|
|
|
func Marshal(icmp ICMP) []byte {
|
|
return MarshalPacket(icmp, nil)
|
|
}
|
|
|
|
func MarshalPacket(icmp ICMP, payload []byte) []byte {
|
|
buf := make([]byte, icmpHeaderLen+len(payload))
|
|
buf[0] = icmp.Type
|
|
buf[1] = icmp.Code
|
|
binary.BigEndian.PutUint16(buf[2:], icmp.CheckSum)
|
|
binary.BigEndian.PutUint16(buf[4:], icmp.Identifier)
|
|
binary.BigEndian.PutUint16(buf[6:], icmp.SequenceNum)
|
|
copy(buf[icmpHeaderLen:], payload)
|
|
return buf
|
|
}
|
|
|
|
func IsExpectedEchoReply(packet []byte, family int, expectedType uint8, identifier, seq uint16) bool {
|
|
for _, offset := range CandidateICMPOffsets(packet, family) {
|
|
if offset < 0 || offset+icmpHeaderLen > len(packet) {
|
|
continue
|
|
}
|
|
if packet[offset] != expectedType || packet[offset+1] != 0 {
|
|
continue
|
|
}
|
|
if binary.BigEndian.Uint16(packet[offset+4:offset+6]) != identifier {
|
|
continue
|
|
}
|
|
if binary.BigEndian.Uint16(packet[offset+6:offset+8]) != seq {
|
|
continue
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func CandidateICMPOffsets(packet []byte, family int) []int {
|
|
offsets := []int{0}
|
|
if len(packet) == 0 {
|
|
return offsets
|
|
}
|
|
|
|
version := packet[0] >> 4
|
|
if version == 4 && len(packet) >= 20 {
|
|
ihl := int(packet[0]&0x0f) * 4
|
|
if ihl >= 20 && ihl <= len(packet)-icmpHeaderLen {
|
|
offsets = append(offsets, ihl)
|
|
}
|
|
} else if version == 6 && len(packet) >= 40+icmpHeaderLen {
|
|
offsets = append(offsets, 40)
|
|
}
|
|
|
|
if family == 4 && len(packet) >= 20+icmpHeaderLen {
|
|
offsets = append(offsets, 20)
|
|
}
|
|
if family == 6 && len(packet) >= 40+icmpHeaderLen {
|
|
offsets = append(offsets, 40)
|
|
}
|
|
|
|
return DedupOffsets(offsets)
|
|
}
|
|
|
|
func DedupOffsets(offsets []int) []int {
|
|
if len(offsets) <= 1 {
|
|
return offsets
|
|
}
|
|
seen := make(map[int]struct{}, len(offsets))
|
|
out := make([]int, 0, len(offsets))
|
|
for _, offset := range offsets {
|
|
if _, ok := seen[offset]; ok {
|
|
continue
|
|
}
|
|
seen[offset] = struct{}{}
|
|
out = append(out, offset)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func ResolveTargets(host string, preferIPv4, preferIPv6 bool) ([]*net.IPAddr, error) {
|
|
if parsed := net.ParseIP(host); parsed != nil {
|
|
return []*net.IPAddr{{IP: parsed}}, nil
|
|
}
|
|
|
|
var targets []*net.IPAddr
|
|
var err4 error
|
|
var err6 error
|
|
|
|
if ip4, err := net.ResolveIPAddr("ip4", host); err == nil && ip4 != nil && ip4.IP != nil {
|
|
targets = append(targets, ip4)
|
|
} else {
|
|
err4 = err
|
|
}
|
|
|
|
if ip6, err := net.ResolveIPAddr("ip6", host); err == nil && ip6 != nil && ip6.IP != nil {
|
|
targets = append(targets, ip6)
|
|
} else {
|
|
err6 = err
|
|
}
|
|
|
|
if len(targets) > 0 {
|
|
return OrderTargets(targets, preferIPv4, preferIPv6), nil
|
|
}
|
|
if err4 != nil {
|
|
return nil, err4
|
|
}
|
|
if err6 != nil {
|
|
return nil, err6
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func OrderTargets(targets []*net.IPAddr, preferIPv4, preferIPv6 bool) []*net.IPAddr {
|
|
if len(targets) <= 1 || preferIPv4 == preferIPv6 {
|
|
return targets
|
|
}
|
|
|
|
ordered := make([]*net.IPAddr, 0, len(targets))
|
|
if preferIPv4 {
|
|
for _, target := range targets {
|
|
if target != nil && target.IP != nil && target.IP.To4() != nil {
|
|
ordered = append(ordered, target)
|
|
}
|
|
}
|
|
for _, target := range targets {
|
|
if target != nil && target.IP != nil && target.IP.To4() == nil {
|
|
ordered = append(ordered, target)
|
|
}
|
|
}
|
|
return ordered
|
|
}
|
|
|
|
for _, target := range targets {
|
|
if target != nil && target.IP != nil && target.IP.To4() == nil {
|
|
ordered = append(ordered, target)
|
|
}
|
|
}
|
|
for _, target := range targets {
|
|
if target != nil && target.IP != nil && target.IP.To4() != nil {
|
|
ordered = append(ordered, target)
|
|
}
|
|
}
|
|
return ordered
|
|
}
|