starnet/errors.go

258 lines
6.6 KiB
Go
Raw Permalink Normal View History

2026-03-08 20:19:40 +08:00
package starnet
import (
"context"
"crypto/tls"
"crypto/x509"
2026-03-08 20:19:40 +08:00
"errors"
"fmt"
"net"
"net/url"
"strings"
2026-03-08 20:19:40 +08:00
)
var (
// ErrInvalidMethod 无效的 HTTP 方法
ErrInvalidMethod = errors.New("starnet: invalid HTTP method")
// ErrInvalidURL 无效的 URL
ErrInvalidURL = errors.New("starnet: invalid URL")
// ErrInvalidIP 无效的 IP 地址
ErrInvalidIP = errors.New("starnet: invalid IP address")
// ErrInvalidDNS 无效的 DNS 服务器
ErrInvalidDNS = errors.New("starnet: invalid DNS server")
// ErrNilClient HTTP Client 为 nil
ErrNilClient = errors.New("starnet: http client is nil")
// ErrNilReader Reader 为 nil
ErrNilReader = errors.New("starnet: reader is nil")
// ErrFileNotFound 文件不存在
ErrFileNotFound = errors.New("starnet: file not found")
// ErrRequestNotPrepared 请求未准备好
ErrRequestNotPrepared = errors.New("starnet: request not prepared")
// ErrBodyAlreadyConsumed Body 已被消费
ErrBodyAlreadyConsumed = errors.New("starnet: response body already consumed")
// ErrRespBodyTooLarge 响应体超过允许上限
ErrRespBodyTooLarge = errors.New("starnet: response body too large")
// ErrPingInvalidTimeout ping 超时参数无效
ErrPingInvalidTimeout = errors.New("starnet: invalid ping timeout")
// ErrPingPermissionDenied ping 需要更高权限raw socket
ErrPingPermissionDenied = errors.New("starnet: ping permission denied")
// ErrPingProtocolUnsupported ping 协议/地址族不受当前平台支持
ErrPingProtocolUnsupported = errors.New("starnet: ping protocol unsupported")
// ErrPingNoResolvedTarget ping 目标无法解析为可用地址
ErrPingNoResolvedTarget = errors.New("starnet: ping target not resolved")
2026-03-08 20:19:40 +08:00
)
// wrapError 包装错误,添加上下文信息
func wrapError(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
msg := fmt.Sprintf(format, args...)
return fmt.Errorf("%s: %w", msg, err)
}
var (
// ErrNilConn indicates a nil net.Conn argument.
ErrNilConn = errors.New("starnet: nil connection")
// ErrTLSSniffFailed indicates TLS sniffing/parsing failed before handshake setup.
ErrTLSSniffFailed = errors.New("starnet: tls sniff failed")
// ErrTLSConfigSelectionFailed indicates dynamic TLS config selection failed.
ErrTLSConfigSelectionFailed = errors.New("starnet: tls config selection failed")
2026-03-08 20:19:40 +08:00
// ErrNonTLSNotAllowed indicates plain TCP was detected while non-TLS is forbidden.
ErrNonTLSNotAllowed = errors.New("starnet: non-TLS connection not allowed")
// ErrNotTLS indicates caller asked for TLS-only object but conn is plain TCP.
ErrNotTLS = errors.New("starnet: connection is not TLS")
// ErrNoTLSConfig indicates TLS was detected but no usable TLS config is available.
ErrNoTLSConfig = errors.New("starnet: no TLS config available")
)
// ErrorKind is a normalized high-level category for request errors.
type ErrorKind string
const (
ErrorKindNone ErrorKind = "none"
ErrorKindCanceled ErrorKind = "canceled"
ErrorKindTimeout ErrorKind = "timeout"
ErrorKindDNS ErrorKind = "dns"
ErrorKindTLS ErrorKind = "tls"
ErrorKindProxy ErrorKind = "proxy"
ErrorKindOther ErrorKind = "other"
)
// IsCanceled reports whether err is a cancellation-related error.
func IsCanceled(err error) bool {
if err == nil {
return false
}
if errors.Is(err, context.Canceled) {
return true
}
msg := strings.ToLower(err.Error())
return strings.Contains(msg, "context canceled") ||
strings.Contains(msg, "operation was canceled") ||
strings.Contains(msg, "request canceled")
}
// ClassifyError maps low-level errors to a stable category for business handling.
func ClassifyError(err error) ErrorKind {
if err == nil {
return ErrorKindNone
}
if IsCanceled(err) {
return ErrorKindCanceled
}
if IsProxy(err) {
return ErrorKindProxy
}
if IsDNS(err) {
return ErrorKindDNS
}
if IsTLS(err) {
return ErrorKindTLS
}
if IsTimeout(err) {
return ErrorKindTimeout
}
return ErrorKindOther
}
// IsTimeout reports whether err is a timeout-related error.
func IsTimeout(err error) bool {
if err == nil {
return false
}
if errors.Is(err, context.DeadlineExceeded) {
return true
}
var uerr *url.Error
if errors.As(err, &uerr) && uerr.Timeout() {
return true
}
var nerr net.Error
if errors.As(err, &nerr) && nerr.Timeout() {
return true
}
msg := strings.ToLower(err.Error())
return strings.Contains(msg, "timeout") || strings.Contains(msg, "deadline exceeded")
}
// IsDNS reports whether err is a DNS resolution related error.
func IsDNS(err error) bool {
if err == nil {
return false
}
var derr *net.DNSError
if errors.As(err, &derr) {
return true
}
msg := strings.ToLower(err.Error())
if strings.Contains(msg, "no such host") ||
strings.Contains(msg, "server misbehaving") ||
strings.Contains(msg, "temporary failure in name resolution") {
return true
}
return strings.Contains(msg, "lookup ") &&
(strings.Contains(msg, "dns") || strings.Contains(msg, "i/o timeout"))
}
// IsTLS reports whether err is TLS/Certificate related.
func IsTLS(err error) bool {
if err == nil {
return false
}
if errors.Is(err, ErrNotTLS) || errors.Is(err, ErrNoTLSConfig) || errors.Is(err, ErrNonTLSNotAllowed) ||
errors.Is(err, ErrTLSSniffFailed) || errors.Is(err, ErrTLSConfigSelectionFailed) {
return true
}
var recErr tls.RecordHeaderError
if errors.As(err, &recErr) {
return true
}
var uaErr x509.UnknownAuthorityError
if errors.As(err, &uaErr) {
return true
}
var hnErr x509.HostnameError
if errors.As(err, &hnErr) {
return true
}
var certErr x509.CertificateInvalidError
if errors.As(err, &certErr) {
return true
}
var rootsErr x509.SystemRootsError
if errors.As(err, &rootsErr) {
return true
}
msg := strings.ToLower(err.Error())
return strings.Contains(msg, "tls:") || strings.Contains(msg, "x509:")
}
// IsProxy reports whether err is proxy related.
func IsProxy(err error) bool {
if err == nil {
return false
}
if isProxyMessage(strings.ToLower(err.Error())) {
return true
}
var uerr *url.Error
if errors.As(err, &uerr) {
if strings.Contains(strings.ToLower(uerr.Op), "proxy") {
return true
}
if uerr.Err != nil && isProxyMessage(strings.ToLower(uerr.Err.Error())) {
return true
}
}
var opErr *net.OpError
if errors.As(err, &opErr) && strings.Contains(strings.ToLower(opErr.Op), "proxy") {
return true
}
return false
}
func isProxyMessage(msg string) bool {
return strings.Contains(msg, "proxyconnect") ||
strings.Contains(msg, "proxy error") ||
strings.Contains(msg, "proxy authentication required") ||
strings.Contains(msg, "proxy: unknown scheme") ||
strings.Contains(msg, "socks connect") ||
strings.Contains(msg, "socks5")
}