mirror of
https://github.com/juanfont/headscale.git
synced 2026-02-07 20:04:00 +01:00
policy: add ICMP protocols to default and export constants
When ACL rules don't specify a protocol, Headscale now defaults to [TCP, UDP, ICMP, ICMPv6] instead of just [TCP, UDP], matching Tailscale's behavior. Also export protocol number constants (ProtocolTCP, ProtocolUDP, etc.) for use in external test packages, renaming the string protocol constants to ProtoNameTCP, ProtoNameUDP, etc. to avoid conflicts. This resolves 78 ICMP-related TODOs in the Tailscale compatibility tests, reducing the total from 165 to 87. Updates #3036
This commit is contained in:
parent
53d17aa321
commit
f735502eae
@ -4,6 +4,7 @@
|
||||
|
||||
### Changes
|
||||
|
||||
- **ACL Policy**: Add ICMP and IPv6-ICMP protocols to default filter rules and export protocol constants [#3036](https://github.com/juanfont/headscale/pull/3036)
|
||||
- **ACL Policy**: Fix autogroup:self handling for tagged nodes - tagged nodes no longer incorrectly receive autogroup:self filter rules [#3036](https://github.com/juanfont/headscale/pull/3036)
|
||||
|
||||
## 0.28.0 (2026-02-04)
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/juanfont/headscale/hscontrol/policy"
|
||||
"github.com/juanfont/headscale/hscontrol/policy/policyutil"
|
||||
v2 "github.com/juanfont/headscale/hscontrol/policy/v2"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
@ -223,7 +224,7 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
Ports: tailcfg.PortRangeAny,
|
||||
},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
{
|
||||
SrcIPs: []string{
|
||||
@ -238,7 +239,7 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
Ports: tailcfg.PortRangeAny,
|
||||
},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -374,12 +375,12 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
Ports: tailcfg.PortRangeAny,
|
||||
},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
{
|
||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
||||
DstPorts: hsExitNodeDestForTest,
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -483,7 +484,7 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
Ports: tailcfg.PortRangeAny,
|
||||
},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
{
|
||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
||||
@ -519,7 +520,7 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
{IP: "200.0.0.0/5", Ports: tailcfg.PortRangeAny},
|
||||
{IP: "208.0.0.0/4", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -595,7 +596,7 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
Ports: tailcfg.PortRangeAny,
|
||||
},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
{
|
||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
||||
@ -609,7 +610,7 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
Ports: tailcfg.PortRangeAny,
|
||||
},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -685,7 +686,7 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
Ports: tailcfg.PortRangeAny,
|
||||
},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
{
|
||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
||||
@ -699,7 +700,7 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
Ports: tailcfg.PortRangeAny,
|
||||
},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -767,7 +768,7 @@ func TestReduceFilterRules(t *testing.T) {
|
||||
Ports: tailcfg.PortRangeAny,
|
||||
},
|
||||
},
|
||||
IPProto: []int{6, 17},
|
||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -103,7 +103,7 @@ func TestParsing(t *testing.T) {
|
||||
{IP: "::/0", Ports: tailcfg.PortRange{First: 3389, Last: 3389}},
|
||||
{IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
IPProto: []int{protocolTCP, protocolUDP},
|
||||
IPProto: []int{ProtocolTCP, ProtocolUDP, ProtocolICMP, ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
@ -157,21 +157,21 @@ func TestParsing(t *testing.T) {
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
IPProto: []int{protocolTCP},
|
||||
IPProto: []int{ProtocolTCP},
|
||||
},
|
||||
{
|
||||
SrcIPs: []string{"0.0.0.0/0", "::/0"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.100.100.100/32", Ports: tailcfg.PortRange{First: 53, Last: 53}},
|
||||
},
|
||||
IPProto: []int{protocolUDP},
|
||||
IPProto: []int{ProtocolUDP},
|
||||
},
|
||||
{
|
||||
SrcIPs: []string{"0.0.0.0/0", "::/0"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
IPProto: []int{protocolICMP, protocolIPv6ICMP},
|
||||
IPProto: []int{ProtocolICMP, ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
@ -205,7 +205,7 @@ func TestParsing(t *testing.T) {
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
IPProto: []int{protocolTCP, protocolUDP},
|
||||
IPProto: []int{ProtocolTCP, ProtocolUDP, ProtocolICMP, ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
@ -242,7 +242,7 @@ func TestParsing(t *testing.T) {
|
||||
Ports: tailcfg.PortRange{First: 5400, Last: 5500},
|
||||
},
|
||||
},
|
||||
IPProto: []int{protocolTCP, protocolUDP},
|
||||
IPProto: []int{ProtocolTCP, ProtocolUDP, ProtocolICMP, ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
@ -282,7 +282,7 @@ func TestParsing(t *testing.T) {
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
IPProto: []int{protocolTCP, protocolUDP},
|
||||
IPProto: []int{ProtocolTCP, ProtocolUDP, ProtocolICMP, ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
@ -316,7 +316,7 @@ func TestParsing(t *testing.T) {
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
IPProto: []int{protocolTCP, protocolUDP},
|
||||
IPProto: []int{ProtocolTCP, ProtocolUDP, ProtocolICMP, ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
@ -350,7 +350,7 @@ func TestParsing(t *testing.T) {
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny},
|
||||
},
|
||||
IPProto: []int{protocolTCP, protocolUDP},
|
||||
IPProto: []int{ProtocolTCP, ProtocolUDP, ProtocolICMP, ProtocolIPv6ICMP},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1281,21 +1281,21 @@ func (a SSHAction) MarshalJSON() ([]byte, error) {
|
||||
type Protocol string
|
||||
|
||||
const (
|
||||
ProtocolICMP Protocol = "icmp"
|
||||
ProtocolIGMP Protocol = "igmp"
|
||||
ProtocolIPv4 Protocol = "ipv4"
|
||||
ProtocolIPInIP Protocol = "ip-in-ip"
|
||||
ProtocolTCP Protocol = "tcp"
|
||||
ProtocolEGP Protocol = "egp"
|
||||
ProtocolIGP Protocol = "igp"
|
||||
ProtocolUDP Protocol = "udp"
|
||||
ProtocolGRE Protocol = "gre"
|
||||
ProtocolESP Protocol = "esp"
|
||||
ProtocolAH Protocol = "ah"
|
||||
ProtocolIPv6ICMP Protocol = "ipv6-icmp"
|
||||
ProtocolSCTP Protocol = "sctp"
|
||||
ProtocolFC Protocol = "fc"
|
||||
ProtocolWildcard Protocol = "*"
|
||||
ProtocolNameICMP Protocol = "icmp"
|
||||
ProtocolNameIGMP Protocol = "igmp"
|
||||
ProtocolNameIPv4 Protocol = "ipv4"
|
||||
ProtocolNameIPInIP Protocol = "ip-in-ip"
|
||||
ProtocolNameTCP Protocol = "tcp"
|
||||
ProtocolNameEGP Protocol = "egp"
|
||||
ProtocolNameIGP Protocol = "igp"
|
||||
ProtocolNameUDP Protocol = "udp"
|
||||
ProtocolNameGRE Protocol = "gre"
|
||||
ProtocolNameESP Protocol = "esp"
|
||||
ProtocolNameAH Protocol = "ah"
|
||||
ProtocolNameIPv6ICMP Protocol = "ipv6-icmp"
|
||||
ProtocolNameSCTP Protocol = "sctp"
|
||||
ProtocolNameFC Protocol = "fc"
|
||||
ProtocolNameWildcard Protocol = "*"
|
||||
)
|
||||
|
||||
// String returns the string representation of the Protocol.
|
||||
@ -1306,33 +1306,33 @@ func (p Protocol) String() string {
|
||||
// Description returns the human-readable description of the Protocol.
|
||||
func (p Protocol) Description() string {
|
||||
switch p {
|
||||
case ProtocolICMP:
|
||||
case ProtocolNameICMP:
|
||||
return "Internet Control Message Protocol"
|
||||
case ProtocolIGMP:
|
||||
case ProtocolNameIGMP:
|
||||
return "Internet Group Management Protocol"
|
||||
case ProtocolIPv4:
|
||||
case ProtocolNameIPv4:
|
||||
return "IPv4 encapsulation"
|
||||
case ProtocolTCP:
|
||||
case ProtocolNameTCP:
|
||||
return "Transmission Control Protocol"
|
||||
case ProtocolEGP:
|
||||
case ProtocolNameEGP:
|
||||
return "Exterior Gateway Protocol"
|
||||
case ProtocolIGP:
|
||||
case ProtocolNameIGP:
|
||||
return "Interior Gateway Protocol"
|
||||
case ProtocolUDP:
|
||||
case ProtocolNameUDP:
|
||||
return "User Datagram Protocol"
|
||||
case ProtocolGRE:
|
||||
case ProtocolNameGRE:
|
||||
return "Generic Routing Encapsulation"
|
||||
case ProtocolESP:
|
||||
case ProtocolNameESP:
|
||||
return "Encapsulating Security Payload"
|
||||
case ProtocolAH:
|
||||
case ProtocolNameAH:
|
||||
return "Authentication Header"
|
||||
case ProtocolIPv6ICMP:
|
||||
case ProtocolNameIPv6ICMP:
|
||||
return "Internet Control Message Protocol for IPv6"
|
||||
case ProtocolSCTP:
|
||||
case ProtocolNameSCTP:
|
||||
return "Stream Control Transmission Protocol"
|
||||
case ProtocolFC:
|
||||
case ProtocolNameFC:
|
||||
return "Fibre Channel"
|
||||
case ProtocolWildcard:
|
||||
case ProtocolNameWildcard:
|
||||
return "Wildcard (not supported - use specific protocol)"
|
||||
default:
|
||||
return "Unknown Protocol"
|
||||
@ -1344,42 +1344,43 @@ func (p Protocol) Description() string {
|
||||
func (p Protocol) parseProtocol() ([]int, bool) {
|
||||
switch p {
|
||||
case "":
|
||||
// Empty protocol applies to TCP and UDP traffic only
|
||||
return []int{protocolTCP, protocolUDP}, false
|
||||
case ProtocolWildcard:
|
||||
// Empty protocol applies to TCP, UDP, ICMP, and ICMPv6 traffic
|
||||
// This matches Tailscale's behavior for protocol defaults
|
||||
return []int{ProtocolTCP, ProtocolUDP, ProtocolICMP, ProtocolIPv6ICMP}, false
|
||||
case ProtocolNameWildcard:
|
||||
// Wildcard protocol - defensive handling (should not reach here due to validation)
|
||||
return nil, false
|
||||
case ProtocolIGMP:
|
||||
return []int{protocolIGMP}, true
|
||||
case ProtocolIPv4, ProtocolIPInIP:
|
||||
return []int{protocolIPv4}, true
|
||||
case ProtocolTCP:
|
||||
return []int{protocolTCP}, false
|
||||
case ProtocolEGP:
|
||||
return []int{protocolEGP}, true
|
||||
case ProtocolIGP:
|
||||
return []int{protocolIGP}, true
|
||||
case ProtocolUDP:
|
||||
return []int{protocolUDP}, false
|
||||
case ProtocolGRE:
|
||||
return []int{protocolGRE}, true
|
||||
case ProtocolESP:
|
||||
return []int{protocolESP}, true
|
||||
case ProtocolAH:
|
||||
return []int{protocolAH}, true
|
||||
case ProtocolSCTP:
|
||||
return []int{protocolSCTP}, false
|
||||
case ProtocolICMP:
|
||||
return []int{protocolICMP, protocolIPv6ICMP}, true
|
||||
case ProtocolNameIGMP:
|
||||
return []int{ProtocolIGMP}, true
|
||||
case ProtocolNameIPv4, ProtocolNameIPInIP:
|
||||
return []int{ProtocolIPv4}, true
|
||||
case ProtocolNameTCP:
|
||||
return []int{ProtocolTCP}, false
|
||||
case ProtocolNameEGP:
|
||||
return []int{ProtocolEGP}, true
|
||||
case ProtocolNameIGP:
|
||||
return []int{ProtocolIGP}, true
|
||||
case ProtocolNameUDP:
|
||||
return []int{ProtocolUDP}, false
|
||||
case ProtocolNameGRE:
|
||||
return []int{ProtocolGRE}, true
|
||||
case ProtocolNameESP:
|
||||
return []int{ProtocolESP}, true
|
||||
case ProtocolNameAH:
|
||||
return []int{ProtocolAH}, true
|
||||
case ProtocolNameSCTP:
|
||||
return []int{ProtocolSCTP}, false
|
||||
case ProtoNameICMP:
|
||||
return []int{ProtocolICMP, ProtocolIPv6ICMP}, true
|
||||
default:
|
||||
// Try to parse as a numeric protocol number
|
||||
// This should not fail since validation happened during unmarshaling
|
||||
protocolNumber, _ := strconv.Atoi(string(p))
|
||||
|
||||
// Determine if wildcard is needed based on protocol number
|
||||
needsWildcard := protocolNumber != protocolTCP &&
|
||||
protocolNumber != protocolUDP &&
|
||||
protocolNumber != protocolSCTP
|
||||
needsWildcard := protocolNumber != ProtocolTCP &&
|
||||
protocolNumber != ProtocolUDP &&
|
||||
protocolNumber != ProtocolSCTP
|
||||
|
||||
return []int{protocolNumber}, needsWildcard
|
||||
}
|
||||
@ -1403,11 +1404,11 @@ func (p *Protocol) UnmarshalJSON(b []byte) error {
|
||||
// validate checks if the Protocol is valid.
|
||||
func (p Protocol) validate() error {
|
||||
switch p {
|
||||
case "", ProtocolICMP, ProtocolIGMP, ProtocolIPv4, ProtocolIPInIP,
|
||||
ProtocolTCP, ProtocolEGP, ProtocolIGP, ProtocolUDP, ProtocolGRE,
|
||||
ProtocolESP, ProtocolAH, ProtocolSCTP:
|
||||
case "", ProtocolNameICMP, ProtocolNameIGMP, ProtocolNameIPv4, ProtocolNameIPInIP,
|
||||
ProtocolNameTCP, ProtocolNameEGP, ProtocolNameIGP, ProtocolNameUDP, ProtocolNameGRE,
|
||||
ProtocolNameESP, ProtocolNameAH, ProtocolNameSCTP:
|
||||
return nil
|
||||
case ProtocolWildcard:
|
||||
case ProtocolNameWildcard:
|
||||
// Wildcard "*" is not allowed - Tailscale rejects it
|
||||
return fmt.Errorf("proto name \"*\" not known; use protocol number 0-255 or protocol name (icmp, tcp, udp, etc.)")
|
||||
default:
|
||||
@ -1439,19 +1440,19 @@ func (p Protocol) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// Protocol constants matching the IANA numbers
|
||||
const (
|
||||
protocolICMP = 1 // Internet Control Message
|
||||
protocolIGMP = 2 // Internet Group Management
|
||||
protocolIPv4 = 4 // IPv4 encapsulation
|
||||
protocolTCP = 6 // Transmission Control
|
||||
protocolEGP = 8 // Exterior Gateway Protocol
|
||||
protocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP)
|
||||
protocolUDP = 17 // User Datagram
|
||||
protocolGRE = 47 // Generic Routing Encapsulation
|
||||
protocolESP = 50 // Encap Security Payload
|
||||
protocolAH = 51 // Authentication Header
|
||||
protocolIPv6ICMP = 58 // ICMP for IPv6
|
||||
protocolSCTP = 132 // Stream Control Transmission Protocol
|
||||
protocolFC = 133 // Fibre Channel
|
||||
ProtocolICMP = 1 // Internet Control Message
|
||||
ProtocolIGMP = 2 // Internet Group Management
|
||||
ProtocolIPv4 = 4 // IPv4 encapsulation
|
||||
ProtocolTCP = 6 // Transmission Control
|
||||
ProtocolEGP = 8 // Exterior Gateway Protocol
|
||||
ProtocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP)
|
||||
ProtocolUDP = 17 // User Datagram
|
||||
ProtocolGRE = 47 // Generic Routing Encapsulation
|
||||
ProtocolESP = 50 // Encap Security Payload
|
||||
ProtocolAH = 51 // Authentication Header
|
||||
ProtocolIPv6ICMP = 58 // ICMP for IPv6
|
||||
ProtocolSCTP = 132 // Stream Control Transmission Protocol
|
||||
ProtocolFC = 133 // Fibre Channel
|
||||
)
|
||||
|
||||
type ACL struct {
|
||||
@ -2114,7 +2115,7 @@ func unmarshalPolicy(b []byte) (*Policy, error) {
|
||||
// can have specific ports. All other protocols should only use wildcard ports.
|
||||
func validateProtocolPortCompatibility(protocol Protocol, destinations []AliasWithPorts) error {
|
||||
// Only TCP, UDP, and SCTP support specific ports
|
||||
supportsSpecificPorts := protocol == ProtocolTCP || protocol == ProtocolUDP || protocol == ProtocolSCTP || protocol == ""
|
||||
supportsSpecificPorts := protocol == ProtocolNameTCP || protocol == ProtocolNameUDP || protocol == ProtocolNameSCTP || protocol == ""
|
||||
|
||||
if supportsSpecificPorts {
|
||||
return nil // No validation needed for these protocols
|
||||
|
||||
Loading…
Reference in New Issue
Block a user