Resolve common IANA protocol names and reject ports on portless protocols
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

This commit is contained in:
2026-10-03 20:49:54 +10:00
parent 9c30f1fa54
commit 852d6bf2ca
2 changed files with 54 additions and 38 deletions
+33 -38
View File
@@ -972,7 +972,19 @@ func l4Matches(proto string, dports, sports config.PortSpec) ([]l4Match, error)
if proto != "" && p == "" {
return nil, fmt.Errorf("empty element in proto list %q", proto)
}
isICMP := strings.EqualFold(p, "icmp") || strings.EqualFold(p, "icmpv6") || strings.EqualFold(p, "ipv6-icmp")
var pm []expr.Any
isICMP := false
if p != "" {
n, err := protoNumber(p)
if err != nil {
return nil, err
}
isICMP = n == unix.IPPROTO_ICMP || n == unix.IPPROTO_ICMPV6
if (len(dports) > 0 && !isICMP || len(sports) > 0) && !hasPorts(n) {
return nil, fmt.Errorf("protocol %q does not support ports", p)
}
pm = matchProtoNum(n)
}
parseD := parsePortOrRange
if isICMP {
if len(protos) > 1 && len(dports) > 0 {
@@ -980,14 +992,6 @@ func l4Matches(proto string, dports, sports config.PortSpec) ([]l4Match, error)
}
parseD = matchICMPType
}
var pm []expr.Any
if p != "" {
m, err := matchProto(p)
if err != nil {
return nil, err
}
pm = m
}
dalts, err := portAlternatives(dports, parseD)
if err != nil {
return nil, err
@@ -1051,36 +1055,27 @@ func matchIfaceName(input bool, name string) []expr.Any {
}
}
func matchProto(proto string) ([]expr.Any, error) {
var protoNum byte
switch strings.ToLower(proto) {
case "tcp":
protoNum = unix.IPPROTO_TCP
case "udp":
protoNum = unix.IPPROTO_UDP
case "icmp":
protoNum = unix.IPPROTO_ICMP
case "icmpv6", "ipv6-icmp":
protoNum = unix.IPPROTO_ICMPV6
case "gre":
protoNum = 47
case "esp":
protoNum = 50
case "ah":
protoNum = 51
case "sctp":
protoNum = unix.IPPROTO_SCTP
default:
n, err := strconv.ParseUint(proto, 10, 8)
if err != nil {
return nil, fmt.Errorf("unknown protocol %q", proto)
}
protoNum = byte(n)
var protoNumbers = map[string]byte{
"icmp": 1, "igmp": 2, "ipip": 4, "ipencap": 4, "tcp": 6, "udp": 17,
"gre": 47, "esp": 50, "ah": 51, "icmpv6": 58, "ipv6-icmp": 58,
"ospf": 89, "ospfigp": 89, "pim": 103, "vrrp": 112, "l2tp": 115,
"sctp": 132, "udplite": 136,
}
// protoNumber resolves a common IANA protocol name or a 0-255 number.
func protoNumber(proto string) (byte, error) {
if n, ok := protoNumbers[strings.ToLower(proto)]; ok {
return n, nil
}
return []expr.Any{
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{protoNum}},
}, nil
n, err := strconv.ParseUint(proto, 10, 8)
if err != nil {
return 0, fmt.Errorf("unknown protocol %q", proto)
}
return byte(n), nil
}
func hasPorts(proto byte) bool {
return proto == unix.IPPROTO_TCP || proto == unix.IPPROTO_UDP || proto == unix.IPPROTO_SCTP || proto == unix.IPPROTO_UDPLITE
}
func matchDPort(port uint16) []expr.Any {
+21
View File
@@ -1581,6 +1581,24 @@ func TestCompile_PortAndProtoLists(t *testing.T) {
chain: "prerouting",
want: []want{{6, "53"}, {17, "53"}},
},
{
name: "protocol names",
rule: config.Rule{Action: config.RuleAccept, Source: "net", Dest: "fw", Proto: "ospf,OSPFIGP,igmp,gre,esp,ah,vrrp,pim,ipencap,ipv6-icmp"},
chain: "input",
want: []want{{89, ""}, {89, ""}, {2, ""}, {47, ""}, {50, ""}, {51, ""}, {112, ""}, {103, ""}, {4, ""}, {58, ""}},
},
{
name: "protocol numbers",
rule: config.Rule{Action: config.RuleAccept, Source: "net", Dest: "fw", Proto: "0,89,255"},
chain: "input",
want: []want{{0, ""}, {89, ""}, {255, ""}},
},
{
name: "numeric tcp with port",
rule: config.Rule{Action: config.RuleAccept, Source: "net", Dest: "fw", Proto: "6,udplite", DPort: config.PortSpec{"22"}},
chain: "input",
want: []want{{6, "22"}, {136, "22"}},
},
}
for _, tt := range tests {
@@ -1724,6 +1742,9 @@ func TestCompile_ListErrors(t *testing.T) {
{"icmp code out of range", config.Rule{Proto: "icmp", DPort: config.PortSpec{"3/256"}}},
{"unknown proto in list", config.Rule{Proto: "tcp,udpp", DPort: config.PortSpec{"53"}}},
{"proto number out of range", config.Rule{Proto: "256"}},
{"unknown proto name", config.Rule{Proto: "bogus"}},
{"dport with ospf", config.Rule{Proto: "ospf", DPort: config.PortSpec{"80"}}},
{"sport with gre", config.Rule{Proto: "gre", SPort: config.PortSpec{"80"}}},
{"dport with icmp in proto list", config.Rule{Proto: "icmp,tcp", DPort: config.PortSpec{"80"}}},
{"ratelimit with port list", config.Rule{Proto: "tcp", DPort: config.PortSpec{"80,443"}, RateLimit: "10/sec"}},
{"connlimit with proto list", config.Rule{Proto: "tcp,udp", DPort: config.PortSpec{"53"}, ConnLimit: "10"}},