mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-10-21 11:33:20 +00:00
Validate IP addresses and prefixes in JSON schema
This commit is contained in:
parent
6cea6c95a1
commit
2c28efdcad
6 changed files with 288 additions and 224 deletions
420
docs/schema.json
420
docs/schema.json
File diff suppressed because it is too large
Load diff
|
|
@ -151,9 +151,9 @@ type RawDefaultRule struct {
|
|||
Geosite badoption.Listable[string] `json:"geosite,omitempty" schema:"omit"`
|
||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty" schema:"omit"`
|
||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty" schema:"omit"`
|
||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty" schema:"prefixable"`
|
||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty" schema:"prefixable"`
|
||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ type RawDefaultDNSRule struct {
|
|||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty" schema:"prefixable"`
|
||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||
|
|
@ -176,7 +176,7 @@ type RawDefaultDNSRule struct {
|
|||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty" reference:"rule_set"`
|
||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||
MatchResponse *DNSRuleMatchResponse `json:"match_response,omitempty"`
|
||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty" schema:"prefixable"`
|
||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
|
||||
ResponseRcode *DNSRCode `json:"response_rcode,omitempty"`
|
||||
|
|
|
|||
|
|
@ -254,8 +254,8 @@ type DefaultHeadlessRule struct {
|
|||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty" schema:"prefixable"`
|
||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty" schema:"prefixable"`
|
||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding"
|
||||
stdjson "encoding/json"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
|
@ -25,6 +26,8 @@ var (
|
|||
addrType = reflect.TypeFor[badoption.Addr]()
|
||||
prefixType = reflect.TypeFor[badoption.Prefix]()
|
||||
prefixableType = reflect.TypeFor[badoption.Prefixable]()
|
||||
netipAddrType = reflect.TypeFor[netip.Addr]()
|
||||
netipPrefixType = reflect.TypeFor[netip.Prefix]()
|
||||
httpHeaderType = reflect.TypeFor[badoption.HTTPHeader]()
|
||||
memoryBytesType = reflect.TypeFor[byteformats.MemoryBytes]()
|
||||
networkBytesCompatType = reflect.TypeFor[byteformats.NetworkBytesCompat]()
|
||||
|
|
@ -77,8 +80,18 @@ func (g *generator) Describe(valueType reflect.Type) (*Node, error) {
|
|||
return g.Define("Duration", func() (*Node, error) {
|
||||
return DurationNode(), nil
|
||||
})
|
||||
case addrType, prefixType, prefixableType:
|
||||
return StringNode(), nil
|
||||
case addrType, netipAddrType:
|
||||
return g.Define("IPAddress", func() (*Node, error) {
|
||||
return IPAddressNode(), nil
|
||||
})
|
||||
case prefixType, netipPrefixType:
|
||||
return g.Define("IPPrefix", func() (*Node, error) {
|
||||
return IPPrefixNode(), nil
|
||||
})
|
||||
case prefixableType:
|
||||
return g.Define("IPAddressOrPrefix", func() (*Node, error) {
|
||||
return IPAddressOrPrefixNode(), nil
|
||||
})
|
||||
case httpHeaderType:
|
||||
return g.Define("HTTPHeader", func() (*Node, error) {
|
||||
return &Node{Type: "object", AdditionalProperties: ListableOf(StringNode())}, nil
|
||||
|
|
@ -208,7 +221,8 @@ func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error {
|
|||
for fieldType.Kind() == reflect.Pointer {
|
||||
fieldType = fieldType.Elem()
|
||||
}
|
||||
if field.Tag.Get("schema") == "omit" {
|
||||
schemaTag := field.Tag.Get("schema")
|
||||
if schemaTag == "omit" {
|
||||
continue
|
||||
}
|
||||
if field.Anonymous && tagName == "" {
|
||||
|
|
@ -227,8 +241,8 @@ func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error {
|
|||
g.path = append(g.path, structType.Name()+"."+tagName)
|
||||
var fieldNode *Node
|
||||
var err error
|
||||
if enumTag != "" || examplesTag != "" || referenceTag != "" {
|
||||
fieldNode, err = taggedFieldNode(fieldType, enumTag, examplesTag, referenceTag)
|
||||
if enumTag != "" || examplesTag != "" || referenceTag != "" || schemaTag != "" {
|
||||
fieldNode, err = g.taggedFieldNode(fieldType, enumTag, examplesTag, referenceTag, schemaTag)
|
||||
} else {
|
||||
fieldNode, err = g.Describe(fieldType)
|
||||
}
|
||||
|
|
@ -241,7 +255,7 @@ func (g *generator) FlattenStruct(node *Node, structType reflect.Type) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func taggedFieldNode(fieldType reflect.Type, enumTag string, examplesTag string, referenceTag string) (*Node, error) {
|
||||
func (g *generator) taggedFieldNode(fieldType reflect.Type, enumTag string, examplesTag string, referenceTag string, schemaTag string) (*Node, error) {
|
||||
elementType := fieldType
|
||||
for elementType.Kind() == reflect.Pointer {
|
||||
elementType = elementType.Elem()
|
||||
|
|
@ -255,16 +269,21 @@ func taggedFieldNode(fieldType reflect.Type, enumTag string, examplesTag string,
|
|||
}
|
||||
var element *Node
|
||||
var err error
|
||||
if enumTag != "" {
|
||||
switch {
|
||||
case enumTag != "":
|
||||
element, err = taggedValueNode(elementType, strings.Split(enumTag, ","))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
element, err = taggedValueNode(elementType, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
case schemaTag == "prefixable":
|
||||
if elementType.Kind() != reflect.String {
|
||||
return nil, E.New("prefixable schema tags require a string field, got ", fieldType.String())
|
||||
}
|
||||
element, err = g.Describe(prefixableType)
|
||||
case schemaTag != "":
|
||||
return nil, E.New("unknown schema tag ", schemaTag, " on ", fieldType.String())
|
||||
default:
|
||||
element, err = g.Describe(elementType)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if examplesTag != "" {
|
||||
examples, parseErr := taggedValues(elementType, strings.Split(examplesTag, ","))
|
||||
|
|
|
|||
|
|
@ -76,6 +76,39 @@ func DurationNode() *Node {
|
|||
return &Node{Type: "string", Pattern: durationPattern}
|
||||
}
|
||||
|
||||
const (
|
||||
ipv4Octet = `(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])`
|
||||
ipv4Address = ipv4Octet + `(?:\.` + ipv4Octet + `){3}`
|
||||
ipv6Group = `[0-9A-Fa-f]{1,4}`
|
||||
ipv6Low32Bits = `(?:` + ipv6Group + `:` + ipv6Group + `|` + ipv4Address + `)`
|
||||
ipv6Address = `(?:` +
|
||||
`(?:` + ipv6Group + `:){6}` + ipv6Low32Bits +
|
||||
`|::(?:` + ipv6Group + `:){5}` + ipv6Low32Bits +
|
||||
`|(?:` + ipv6Group + `)?::(?:` + ipv6Group + `:){4}` + ipv6Low32Bits +
|
||||
`|(?:(?:` + ipv6Group + `:)?` + ipv6Group + `)?::(?:` + ipv6Group + `:){3}` + ipv6Low32Bits +
|
||||
`|(?:(?:` + ipv6Group + `:){0,2}` + ipv6Group + `)?::(?:` + ipv6Group + `:){2}` + ipv6Low32Bits +
|
||||
`|(?:(?:` + ipv6Group + `:){0,3}` + ipv6Group + `)?::` + ipv6Group + `:` + ipv6Low32Bits +
|
||||
`|(?:(?:` + ipv6Group + `:){0,4}` + ipv6Group + `)?::` + ipv6Low32Bits +
|
||||
`|(?:(?:` + ipv6Group + `:){0,5}` + ipv6Group + `)?::` + ipv6Group +
|
||||
`|(?:(?:` + ipv6Group + `:){0,6}` + ipv6Group + `)?::` +
|
||||
`)`
|
||||
ipv6Zone = `%.+`
|
||||
ipv4PrefixBits = `/(?:3[0-2]|[12]?[0-9])`
|
||||
ipv6PrefixBits = `/(?:12[0-8]|1[01][0-9]|[1-9]?[0-9])`
|
||||
)
|
||||
|
||||
func IPAddressNode() *Node {
|
||||
return &Node{Type: "string", Pattern: `^(?:` + ipv4Address + `|` + ipv6Address + `(?:` + ipv6Zone + `)?)$`}
|
||||
}
|
||||
|
||||
func IPPrefixNode() *Node {
|
||||
return &Node{Type: "string", Pattern: `^(?:` + ipv4Address + ipv4PrefixBits + `|` + ipv6Address + ipv6PrefixBits + `)$`}
|
||||
}
|
||||
|
||||
func IPAddressOrPrefixNode() *Node {
|
||||
return &Node{Type: "string", Pattern: `^(?:` + ipv4Address + `(?:` + ipv4PrefixBits + `)?|` + ipv6Address + `(?:` + ipv6PrefixBits + `|` + ipv6Zone + `)?)$`}
|
||||
}
|
||||
|
||||
func StringEnum(values ...string) *Node {
|
||||
anyValues := make([]any, 0, len(values))
|
||||
for _, value := range values {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue