Files
sing-box/protocol/cloudflare/ip_rule_policy.go
2026-03-31 15:32:56 +08:00

97 lines
2.3 KiB
Go

//go:build with_cloudflared
package cloudflare
import (
"context"
"net"
"net/netip"
"sort"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
)
type compiledIPRule struct {
prefix netip.Prefix
ports []int
allow bool
}
type ipRulePolicy struct {
rules []compiledIPRule
}
func newIPRulePolicy(rawRules []IPRule) (*ipRulePolicy, error) {
policy := &ipRulePolicy{
rules: make([]compiledIPRule, 0, len(rawRules)),
}
for _, rawRule := range rawRules {
if rawRule.Prefix == "" {
return nil, E.New("ip_rule prefix cannot be blank")
}
prefix, err := netip.ParsePrefix(rawRule.Prefix)
if err != nil {
return nil, E.Cause(err, "parse ip_rule prefix")
}
ports := append([]int(nil), rawRule.Ports...)
sort.Ints(ports)
for _, port := range ports {
if port < 1 || port > 65535 {
return nil, E.New("invalid ip_rule port: ", port)
}
}
policy.rules = append(policy.rules, compiledIPRule{
prefix: prefix,
ports: ports,
allow: rawRule.Allow,
})
}
return policy, nil
}
func (p *ipRulePolicy) Allow(ctx context.Context, destination M.Socksaddr) (bool, error) {
if p == nil {
return false, nil
}
ipAddr, err := resolvePolicyDestination(ctx, destination)
if err != nil {
return false, err
}
port := int(destination.Port)
for _, rule := range p.rules {
if !rule.prefix.Contains(ipAddr) {
continue
}
if len(rule.ports) == 0 {
return rule.allow, nil
}
portIndex := sort.SearchInts(rule.ports, port)
if portIndex < len(rule.ports) && rule.ports[portIndex] == port {
return rule.allow, nil
}
}
return false, nil
}
func resolvePolicyDestination(ctx context.Context, destination M.Socksaddr) (netip.Addr, error) {
if destination.IsIP() {
return destination.Unwrap().Addr, nil
}
if !destination.IsFqdn() {
return netip.Addr{}, E.New("destination is neither IP nor FQDN")
}
ipAddrs, err := net.DefaultResolver.LookupIPAddr(ctx, destination.Fqdn)
if err != nil {
return netip.Addr{}, E.Cause(err, "resolve destination")
}
if len(ipAddrs) == 0 {
return netip.Addr{}, E.New("resolved destination is empty")
}
resolvedAddr, ok := netip.AddrFromSlice(ipAddrs[0].IP)
if !ok {
return netip.Addr{}, E.New("resolved destination is invalid")
}
return resolvedAddr.Unmap(), nil
}